This commit is contained in:
Luca 2022-11-22 13:25:44 +01:00
parent 8268fba83d
commit 7ed2a6e110
9565 changed files with 1315332 additions and 90 deletions

View file

@ -0,0 +1,39 @@
local gears = require("gears")
local beautiful = require("beautiful")
local op = beautiful.flash_focus_start_opacity or 0.6
local stp = beautiful.flash_focus_step or 0.01
local flashfocus = function(c)
if c and #c.screen.clients > 1 then
c.opacity = op
local q = op
local g = gears.timer({
timeout = stp,
call_now = false,
autostart = true,
})
g:connect_signal("timeout", function()
if not c.valid then
return
end
if q >= 1 then
c.opacity = 1
g:stop()
else
c.opacity = q
q = q + stp
end
end)
end
end
local enable = function()
client.connect_signal("focus", flashfocus)
end
local disable = function()
client.disconnect_signal("focus", flashfocus)
end
return { enable = enable, disable = disable, flashfocus = flashfocus }

View file

@ -0,0 +1,8 @@
return {
window_swallowing = require(... .. ".window_swallowing"),
tiled_wallpaper = require(... .. ".tiled_wallpaper"),
wallpaper = require(... .. ".wallpaper"),
flash_focus = require(... .. ".flash_focus"),
tabbed = require(... .. ".tabbed"),
scratchpad = require(... .. ".scratchpad"),
}

View file

@ -0,0 +1,374 @@
local awful = require("awful")
local gears = require("gears")
local naughty = require("naughty")
local helpers = require(tostring(...):match(".*bling") .. ".helpers")
local capi = { awesome = awesome, client = client }
local ruled = capi.awesome.version ~= "v4.3" and require("ruled") or nil
local pairs = pairs
local Scratchpad = { mt = {} }
--- Called when the turn off animation has ended
local function on_animate_turn_off_end(self, tag)
-- When toggling off a scratchpad that's present on multiple tags
-- depsite still being unminizmied on the other tags it will become invisible
-- as it's position could be outside the screen from the animation
self.client:geometry({
x = self.geometry.x + self.client.screen.geometry.x,
y = self.geometry.y + self.client.screen.geometry.y,
width = self.geometry.width,
height = self.geometry.height,
})
helpers.client.turn_off(self.client, tag)
self.turning_off = false
self:emit_signal("turn_off", self.client)
end
--- The turn off animation
local function animate_turn_off(self, anim, axis)
self.screen_on_toggled_scratchpad = self.client.screen
self.tag_on_toggled_scratchpad = self.screen_on_toggled_scratchpad.selected_tag
if self.client.floating == false then
-- Save the client geometry before floating it
local non_floating_x = self.client.x
local non_floating_y = self.client.y
local non_floating_width = self.client.width
local non_floating_height = self.client.height
-- Can't animate non floating clients
self.client.floating = true
-- Set the client geometry back to what it was before floating it
self.client:geometry({
x = non_floating_x,
y = non_floating_y,
width = non_floating_width,
height = non_floating_height,
})
end
if axis == "x" then
anim.pos = self.client.x
else
anim.pos = self.client.y
end
anim:set(anim:initial())
end
-- Handles changing tag mid animation
local function abort_if_tag_was_switched(self)
-- Check for the following scenerio:
-- Toggle on scratchpad at tag 1
-- Toggle on scratchpad at tag 2
-- Toggle off scratchpad at tag 1
-- Switch to tag 2
-- Outcome: The client will remain on tag 1 and will instead be removed from tag 2
if (self.turning_off) and (self.screen_on_toggled_scratchpad and
self.screen_on_toggled_scratchpad.selected_tag) ~= self.tag_on_toggled_scratchpad
then
if self.rubato.x then
self.rubato.x:abort()
end
if self.rubato.y then
self.rubato.y:abort()
end
on_animate_turn_off_end(self, self.tag_on_toggled_scratchpad)
self.screen_on_toggled_scratchpad.selected_tag = nil
self.tag_on_toggled_scratchpad = nil
end
end
--- The turn on animation
local function animate_turn_on(self, anim, axis)
-- Check for the following scenerio:
-- Toggle on scratchpad at tag 1
-- Toggle on scratchpad at tag 2
-- The animation will instantly end
-- as the timer pos is already at the on position
-- from toggling on the scratchpad at tag 1
if axis == "x" and anim.pos == self.geometry.x then
anim.pos = anim:initial()
else
if anim.pos == self.geometry.y then
anim.pos = anim:initial()
end
end
if axis == "x" then
anim:set(self.geometry.x)
else
anim:set(self.geometry.y)
end
end
--- Creates a new scratchpad object based on the argument
--
-- @param args A table of possible arguments
-- @return The new scratchpad object
function Scratchpad:new(args)
args = args or {}
if args.awestore then
naughty.notify({
title = "Bling Error",
text = "Awestore is no longer supported! Please take a look at the scratchpad documentation and use rubato for animations instead.",
})
end
args.rubato = args.rubato or {}
local ret = gears.object{}
gears.table.crush(ret, Scratchpad)
gears.table.crush(ret, args)
if ret.rubato.x then
ret.rubato.x:subscribe(function(pos)
if ret.client and ret.client.valid then
ret.client.x = pos
end
abort_if_tag_was_switched(ret)
end)
ret.rubato.x.ended:subscribe(function()
if ((ret.rubato.y and ret.rubato.y.state == false) or (ret.rubato.y == nil)) and ret.turning_off == true then
on_animate_turn_off_end(ret)
end
end)
end
if ret.rubato.y then
ret.rubato.y:subscribe(function(pos)
if ret.client and ret.client.valid then
ret.client.y = pos
end
abort_if_tag_was_switched(ret)
end)
ret.rubato.y.ended:subscribe(function()
if ((ret.rubato.x and ret.rubato.x.state == false) or (ret.rubato.x == nil)) and ret.turning_off == true then
on_animate_turn_off_end(ret)
end
end)
end
return ret
end
--- Find all clients that satisfy the the rule
--
-- @return A list of all clients that satisfy the rule
function Scratchpad:find()
return helpers.client.find(self.rule)
end
--- Applies the objects scratchpad properties to a given client
--
-- @param c A client to which to apply the properties
function Scratchpad:apply(c)
if not c or not c.valid then
return
end
c.floating = self.floating
c.sticky = self.sticky
c.fullscreen = false
c.maximized = false
c:geometry({
x = self.geometry.x + awful.screen.focused().geometry.x,
y = self.geometry.y + awful.screen.focused().geometry.y,
width = self.geometry.width,
height = self.geometry.height,
})
if self.autoclose then
c:connect_signal("unfocus", function(c1)
c1.sticky = false -- client won't turn off if sticky
helpers.client.turn_off(c1)
end)
end
end
--- Turns the scratchpad on
function Scratchpad:turn_on()
self.client = self:find()[1]
local anim_x = self.rubato.x
local anim_y = self.rubato.y
local in_anim = false
if (anim_x and anim_x.state == true) or (anim_y and anim_y.state == true) then
in_anim = true
end
if self.client and not in_anim and self.client.first_tag and self.client.first_tag.selected then
self.client:raise()
capi.client.focus = self.client
return
end
if self.client and not in_anim then
-- if a client was found, turn it on
if self.reapply then
self:apply(self.client)
end
-- c.sticky was set to false in turn_off so it has to be reapplied anyway
self.client.sticky = self.sticky
if anim_x then
animate_turn_on(self, anim_x, "x")
end
if anim_y then
animate_turn_on(self, anim_y, "y")
end
helpers.client.turn_on(self.client)
self:emit_signal("turn_on", self.client)
return
end
if not self.client then
-- if no client was found, spawn one, find the corresponding window,
-- apply the properties only once (until the next closing)
local pid = awful.spawn.with_shell(self.command)
if capi.awesome.version ~= "v4.3" then
ruled.client.append_rule({
id = "scratchpad",
rule = self.rule,
properties = {
-- If a scratchpad is opened it should spawn at the current tag
-- the same way it will behave if the client was already open
tag = awful.screen.focused().selected_tag,
switch_to_tags = false,
-- Hide the client until the gemoetry rules are applied
hidden = true,
minimized = true,
},
callback = function(c)
-- For a reason I can't quite get the gemotery rules will fail to apply unless we use this timer
gears.timer({
timeout = 0.15,
autostart = true,
single_shot = true,
callback = function()
self.client = c
self:apply(c)
c.hidden = false
c.minimized = false
-- Some clients fail to gain focus
c:activate({})
if anim_x then
animate_turn_on(self, anim_x, "x")
end
if anim_y then
animate_turn_on(self, anim_y, "y")
end
self:emit_signal("inital_apply", c)
-- Discord spawns 2 windows, so keep the rule until the 2nd window shows
if c.name ~= "Discord Updater" then
ruled.client.remove_rule("scratchpad")
end
-- In a case Discord is killed before the second window spawns
c:connect_signal("request::unmanage", function()
ruled.client.remove_rule("scratchpad")
end)
end,
})
end,
})
else
local function inital_apply(c1)
if helpers.client.is_child_of(c1, pid) then
self.client = c1
self:apply(c1)
if anim_x then
animate_turn_on(self, anim_x, "x")
end
if anim_y then
animate_turn_on(self, anim_y, "y")
end
self:emit_signal("inital_apply", c1)
client.disconnect_signal("manage", inital_apply)
end
end
client.connect_signal("manage", inital_apply)
end
end
end
--- Turns the scratchpad off
function Scratchpad:turn_off()
self.client = self:find()[1]
-- Get the tweens
local anim_x = self.rubato.x
local anim_y = self.rubato.y
local in_anim = false
if (anim_x and anim_x.state == true) or (anim_y and anim_y.state == true) then
in_anim = true
end
if self.client and not in_anim then
if anim_x then
self.turning_off = true
animate_turn_off(self, anim_x, "x")
end
if anim_y then
self.turning_off = true
animate_turn_off(self, anim_y, "y")
end
if not anim_x and not anim_y then
helpers.client.turn_off(self.client)
self:emit_signal("turn_off", self.client)
end
end
end
--- Turns the scratchpad off if it is focused otherwise it raises the scratchpad
function Scratchpad:toggle()
local is_turn_off = false
local c = self:find()[1]
if self.dont_focus_before_close then
if c then
if c.sticky and #c:tags() > 0 then
is_turn_off = true
else
local current_tag = c.screen.selected_tag
for k, tag in pairs(c:tags()) do
if tag == current_tag then
is_turn_off = true
break
else
is_turn_off = false
end
end
end
end
else
is_turn_off = capi.client.focus
and awful.rules.match(capi.client.focus, self.rule)
end
if is_turn_off then
self:turn_off()
else
self:turn_on()
end
end
--- Make the module callable without putting a `:new` at the end of it
--
-- @param args A table of possible arguments
-- @return The new scratchpad object
function Scratchpad.mt:__call(...)
return Scratchpad:new(...)
end
return setmetatable(Scratchpad, Scratchpad.mt)

View file

@ -0,0 +1,276 @@
--[[
This module currently works by adding a new property to each client that is tabbed.
That new property is called bling_tabbed.
So each client in a tabbed state has the property "bling_tabbed" which is a table.
Each client that is not tabbed doesn't have that property.
In the function themselves, the same object is refered to as "tabobj" which is why
you will often see something like: "local tabobj = some_client.bling_tabbed" at the beginning
of a function.
--]]
local awful = require("awful")
local wibox = require("wibox")
local gears = require("gears")
local beautiful = require("beautiful")
local helpers = require(tostring(...):match(".*bling") .. ".helpers")
local bar_style = beautiful.tabbar_style or "default"
local bar = require(
tostring(...):match(".*bling") .. ".widget.tabbar." .. bar_style
)
tabbed = {}
-- helper function to connect to the (un)focus signals
local function update_tabbar_from(c)
if not c or not c.bling_tabbed then
return
end
tabbed.update_tabbar(c.bling_tabbed)
end
-- used to change focused tab relative to the currently focused one
tabbed.iter = function(idx)
if not idx then
idx = 1
end
if not client.focus or not client.focus.bling_tabbed then
return
end
local tabobj = client.focus.bling_tabbed
local new_idx = (tabobj.focused_idx + idx) % #tabobj.clients
if new_idx == 0 then
new_idx = #tabobj.clients
end
tabbed.switch_to(tabobj, new_idx)
end
-- removes a given client from its tab object
tabbed.remove = function(c)
if not c or not c.bling_tabbed then
return
end
local tabobj = c.bling_tabbed
table.remove(tabobj.clients, tabobj.focused_idx)
if not beautiful.tabbar_disable then
awful.titlebar.hide(c, bar.position)
end
c.bling_tabbed = nil
c:disconnect_signal("focus", update_tabbar_from)
c:disconnect_signal("unfocus", update_tabbar_from)
awesome.emit_signal("bling::tabbed::client_removed", tabobj, c)
tabbed.switch_to(tabobj, 1)
end
-- removes the currently focused client from the tab object
tabbed.pop = function()
if not client.focus or not client.focus.bling_tabbed then
return
end
tabbed.remove(client.focus)
end
-- adds a client to a given tabobj
tabbed.add = function(c, tabobj)
if c.bling_tabbed then
tabbed.remove(c)
end
c:connect_signal("focus", update_tabbar_from)
c:connect_signal("unfocus", update_tabbar_from)
helpers.client.sync(c, tabobj.clients[tabobj.focused_idx])
tabobj.clients[#tabobj.clients + 1] = c
tabobj.focused_idx = #tabobj.clients
-- calls update even though switch_to calls update again
-- but the new client needs to have the tabobj property
-- before a clean switch can happen
tabbed.update(tabobj)
awesome.emit_signal("bling::tabbed::client_added", tabobj, c)
tabbed.switch_to(tabobj, #tabobj.clients)
end
-- use xwininfo to select one client and make it tab in the currently focused tab
tabbed.pick = function()
if not client.focus then
return
end
-- this function uses xwininfo to grab a client window id which is then
-- compared to all other clients window ids
local xwininfo_cmd =
[[ xwininfo | grep 'xwininfo: Window id:' | cut -d " " -f 4 ]]
awful.spawn.easy_async_with_shell(xwininfo_cmd, function(output)
for _, c in ipairs(client.get()) do
if tonumber(c.window) == tonumber(output) then
if not client.focus.bling_tabbed and not c.bling_tabbed then
tabbed.init(client.focus)
tabbed.add(c, client.focus.bling_tabbed)
end
if not client.focus.bling_tabbed and c.bling_tabbed then
tabbed.add(client.focus, c.bling_tabbed)
end
if client.focus.bling_tabbed and not c.bling_tabbed then
tabbed.add(c, client.focus.bling_tabbed)
end
-- TODO: Should also merge tabs when focus and picked
-- both are tab groups
end
end
end)
end
-- select a client by direction and make it tab in the currently focused tab
tabbed.pick_by_direction = function(direction)
local sel = client.focus
if not sel then
return
end
if not sel.bling_tabbed then
tabbed.init(sel)
end
local c = helpers.client.get_by_direction(direction)
if not c then
return
end
tabbed.add(c, sel.bling_tabbed)
end
-- use dmenu to select a client and make it tab in the currently focused tab
tabbed.pick_with_dmenu = function(dmenu_command)
if not client.focus then
return
end
if not dmenu_command then
dmenu_command = "rofi -dmenu -i"
end
-- get all clients from the current tag
-- ignores the case where multiple tags are selected
local t = awful.screen.focused().selected_tag
local list_clients = {}
local list_clients_string = ""
for idx, c in ipairs(t:clients()) do
if c.window ~= client.focus.window then
list_clients[#list_clients + 1] = c
if #list_clients ~= 1 then
list_clients_string = list_clients_string .. "\\n"
end
list_clients_string = list_clients_string
.. tostring(c.window)
.. " "
.. c.name
end
end
if #list_clients == 0 then
return
end
-- calls the actual dmenu
local xprop_cmd = [[ echo -e "]]
.. list_clients_string
.. [[" | ]]
.. dmenu_command
.. [[ | awk '{ print $1 }' ]]
awful.spawn.easy_async_with_shell(xprop_cmd, function(output)
for _, c in ipairs(list_clients) do
if tonumber(c.window) == tonumber(output) then
if not client.focus.bling_tabbed then
tabbed.init(client.focus)
end
local tabobj = client.focus.bling_tabbed
tabbed.add(c, tabobj)
end
end
end)
end
-- update everything about one tab object
tabbed.update = function(tabobj)
local currently_focused_c = tabobj.clients[tabobj.focused_idx]
-- update tabobj of each client and other things
for idx, c in ipairs(tabobj.clients) do
if c.valid then
c.bling_tabbed = tabobj
helpers.client.sync(c, currently_focused_c)
-- the following handles killing a client while the client is tabbed
c:connect_signal("unmanage", function(c)
tabbed.remove(c)
end)
end
end
-- Maybe remove if I'm the only one using it?
awesome.emit_signal("bling::tabbed::update", tabobj)
if not beautiful.tabbar_disable then
tabbed.update_tabbar(tabobj)
end
end
-- change focused tab by absolute index
tabbed.switch_to = function(tabobj, new_idx)
local old_focused_c = tabobj.clients[tabobj.focused_idx]
tabobj.focused_idx = new_idx
for idx, c in ipairs(tabobj.clients) do
if idx ~= new_idx then
helpers.client.turn_off(c)
else
helpers.client.turn_on(c)
c:raise()
if old_focused_c and old_focused_c.valid then
c:swap(old_focused_c)
end
helpers.client.sync(c, old_focused_c)
end
end
awesome.emit_signal("bling::tabbed::changed_focus", tabobj)
tabbed.update(tabobj)
end
tabbed.update_tabbar = function(tabobj)
local flexlist = bar.layout()
local tabobj_focused_client = tabobj.clients[tabobj.focused_idx]
local tabobj_is_focused = (client.focus == tabobj_focused_client)
-- itearte over all tabbed clients to create the widget tabbed list
for idx, c in ipairs(tabobj.clients) do
local buttons = gears.table.join(awful.button({}, 1, function()
tabbed.switch_to(tabobj, idx)
end))
local wid_temp = bar.create(c, (idx == tabobj.focused_idx), buttons,
not tabobj_is_focused)
flexlist:add(wid_temp)
end
-- add tabbar to each tabbed client (clients will be hided anyway)
if not beautiful.tabbar_disable then
for _, c in ipairs(tabobj.clients) do
local titlebar = awful.titlebar(c, {
bg = bar.bg_normal,
size = bar.size,
position = bar.position,
})
titlebar:setup({ layout = wibox.layout.flex.horizontal, flexlist })
end
end
end
tabbed.init = function(c)
local tabobj = {}
tabobj.clients = { c }
c:connect_signal("focus", update_tabbar_from)
c:connect_signal("unfocus", update_tabbar_from)
tabobj.focused_idx = 1
tabbed.update(tabobj)
end
if beautiful.tabbed_spawn_in_tab then
client.connect_signal("manage", function(c)
local s = awful.screen.focused()
local previous_client = awful.client.focus.history.get(s, 1)
if previous_client and previous_client.bling_tabbed then
tabbed.add(c, previous_client.bling_tabbed)
end
end)
end
return tabbed

View file

@ -0,0 +1,56 @@
--[[
This module makes use of cairo surfaces
For documentation take a look at the C docs:
https://www.cairographics.org/
They can be applied to lua by changing the naming conventions
and adjusting for the missing namespaces (and classes)
for example:
cairo_rectangle(cr, 1, 1, 1, 1) in C would be written as
cr:rectangle(1, 1, 1, 1) in lua
and
cairo_fill(cr) in C would be written as
cr:fill() in lua
--]]
local cairo = require("lgi").cairo
local gears = require("gears")
function create_tiled_wallpaper(str, s, args_table)
-- user input
args_table = args_table or {}
local fg = args_table.fg or "#ff0000"
local bg = args_table.bg or "#00ffff"
local offset_x = args_table.offset_x
local offset_y = args_table.offset_y
local font = args_table.font or "Hack"
local font_size = tonumber(args_table.font_size) or 16
local zickzack_bool = args_table.zickzack or false
local padding = args_table.padding or 100
-- create cairo image wallpaper
local img = cairo.ImageSurface(cairo.Format.RGB24, padding, padding)
cr = cairo.Context(img)
cr:set_source(gears.color(bg))
cr:paint()
cr:set_source(gears.color(fg))
cr:set_font_size(font_size)
cr:select_font_face(font)
if zickzack_bool then
cr:set_source(gears.color(fg))
cr:move_to(padding / 2 + font_size, padding / 2 + font_size)
cr:show_text(str)
end
cr:set_source(gears.color(fg))
cr:move_to(font_size, font_size)
cr:show_text(str)
-- tile cairo image
gears.wallpaper.tiled(img, s, { x = offset_x, y = offset_y })
end
return create_tiled_wallpaper

View file

@ -0,0 +1,362 @@
---------------------------------------------------------------------------
-- High-level declarative function for setting your wallpaper.
--
--
-- An easy way to setup a complex wallpaper with slideshow, random, schedule, extensibility.
--
-- @usage
-- local wallpaper = require("wallpaper")
-- -- A silly example
-- wallpaper.setup { -- I want a wallpaper
-- change_timer = 500, -- changing every 5 minutes
-- set_function = wallpaper.setters.random, -- in a random way
-- wallpaper = {"#abcdef",
-- "~/Pictures",
-- wallpaper.setters.awesome}, -- from this list (a color, a directory with pictures and the Awesome wallpaper)
-- recursive = false, -- do not read subfolders of "~/Pictures"
-- position = "centered", -- center it on the screen (for pictures)
-- scale = 2, -- 2 time bigger (for pictures)
-- }
--
-- @author Grumph
-- @copyright 2021 Grumph
--
---------------------------------------------------------------------------
local awful = require("awful")
local beautiful = require("beautiful")
local gears = require("gears")
local helpers = require(tostring(...):match(".*bling") .. ".helpers")
local setters = {}
--- Apply a wallpaper.
--
-- This function is a helper that will apply a wallpaper_object,
-- either using gears.wallpaper.set or gears.wallpaper.* higher level functions when applicable.
-- @param wallpaper_object A wallpaper object, either
-- a `pattern` (see `gears.wallpaper.set`)
-- a `surf` (see `gears.wallpaper.centered`)
-- a function that actually sets the wallpaper.
-- @tparam table args The argument table containing any of the arguments below.
-- @int[opt=nil] args.screen The screen to use (as used in `gears.wallpaper` functions)
-- @string[opt=nil or "centered"] args.position The `gears.wallpaper` position function to use.
-- Must be set when wallpaper is a file.
-- It can be `"centered"`, `"fit"`, `"tiled"` or `"maximized"`.
-- @string[opt=beautiful.bg_normal or "black"] args.background See `gears.wallpaper`.
-- @bool[opt=false] args.ignore_aspect See `gears.wallpaper`.
-- @tparam[opt={x=0,y=0}] table args.offset See `gears.wallpaper`.
-- @int[opt=1] args.scale See `gears.wallpaper`.
function apply(wallpaper_object, args)
args.background = args.background or beautiful.bg_normal or "black"
args.ignore_aspect = args.ignore_aspect or false -- false = keep aspect ratio
args.offset = args.offset or { x = 0, y = 0 }
args.scale = args.scale or 1
local positions = {
["centered"] = function(s)
gears.wallpaper.centered(
wallpaper_object,
s,
args.background,
args.scale
)
end,
["tiled"] = function(s)
gears.wallpaper.tiled(wallpaper_object, s, args.offset)
end,
["maximized"] = function(s)
gears.wallpaper.maximized(
wallpaper_object,
s,
args.ignore_aspect,
args.offset
)
end,
["fit"] = function(s)
gears.wallpaper.fit(wallpaper_object, s, args.background)
end,
}
local call_func = nil
if
type(wallpaper_object) == "string"
and gears.filesystem.file_readable(wallpaper_object)
then
-- path of an image file, we use a position function
local p = args.position or "centered"
call_func = positions[p]
elseif type(wallpaper_object) == "function" then
-- function
wallpaper_object(args)
elseif
(not gears.color.ensure_pango_color(wallpaper_object, nil))
and args.position
then
-- if the user sets a position function, wallpaper_object should be a cairo surface
call_func = positions[args.position]
else
gears.wallpaper.set(wallpaper_object)
end
if call_func then
call_func(args.screen)
end
end
--- Converts `args.wallpaper` to a list of `wallpaper_objects` readable by `apply` function).
--
-- @tparam table args The argument table containing the argument below.
-- @param[opt=`beautiful.wallpaper_path` or `"black"`] args.wallpaper A wallpaper object.
-- It can be a color or a cairo pattern (what `gears.wallpaper.set` understands),
-- a cairo suface (set with gears.wallpaper.set if `args.position` is nil, or with
-- `gears.wallpaper` position functions, see `args.position`),
-- a function similar to args.set_function that will effectively set a wallpaper (usually
-- with `gears.wallpaper` functions),
-- a path to a file,
-- path to a directory containing images,
-- or a list with any of the previous choices.
-- @tparam[opt=`{"jpg", "jpeg", "png", "bmp"}`] table args.image_formats A list of
-- file extensions to filter when `args.wallpaper` is a directory.
-- @bool[opt=true] args.recursive Either to recurse or not when `args.wallpaper` is a directory.
-- @treturn table A list of `wallpaper_objects` (what `apply` can read).
-- @see apply
function prepare_list(args)
args.image_formats = args.image_formats or { "jpg", "jpeg", "png", "bmp" }
args.recursive = args.recursive or true
local wallpapers = (args.wallpaper or beautiful.wallpaper_path or "black")
local res = {}
if type(wallpapers) ~= "table" then
wallpapers = { wallpapers }
end
for _, w in ipairs(wallpapers) do
-- w is either:
-- - a directory path (string)
-- - an image path or a color (string)
-- - a cairo surface or a cairo pattern
-- - a function for setting the wallpaper
if type(w) == "string" and gears.filesystem.dir_readable(w) then
local file_list = helpers.filesystem.list_directory_files(
w,
args.image_formats,
args.recursive
)
for _, f in ipairs(file_list) do
res[#res + 1] = w .. "/" .. f
end
else
res[#res + 1] = w
end
end
return res
end
local simple_index = 0
--- Set the next wallpaper in a list.
--
-- @tparam table args See `prepare_list` and `apply` arguments
-- @see apply
-- @see prepare_list
function setters.simple(args)
local wallpapers = prepare_list(args)
simple_index = (simple_index % #wallpapers) + 1
if type(args.screen) == 'table' then
for _,v in ipairs(args.screen) do
args.screen = v
apply(wallpapers[simple_index], args)
args.screen = nil
end
else
apply(wallpapers[simple_index], args)
end
end
--- Set a random wallpaper from a list.
--
-- @tparam table args See `prepare_list` and `apply` arguments
-- @see apply
-- @see prepare_list
function setters.random(args)
local wallpapers = prepare_list(args)
if type(args.screen) == 'table' then
for _,v in ipairs(args.screen) do
args.screen = v
apply(wallpapers[math.random(#wallpapers)], args)
args.screen = nil
end
else
apply(wallpapers[math.random(#wallpapers)], args)
end
end
local simple_schedule_object = nil
--- A schedule setter.
--
-- This simple schedule setter was freely inspired by [dynamic-wallpaper](https://github.com/manilarome/awesome-glorious-widgets/blob/master/dynamic-wallpaper/init.lua).
-- @tparam table args The argument table containing any of the arguments below.
-- @tparam table args.wallpaper The schedule table, with the form
-- {
-- ["HH:MM:SS"] = wallpaper,
-- ["HH:MM:SS"] = wallpaper2,
-- }
-- The wallpapers definition can be anything the `schedule_set_function` can read
-- (what you would place in `args.wallpaper` for this function),
-- @tparam[opt=`setters.simple`] function args.wallpaper_set_function The set_function used by default
function setters.simple_schedule(args)
local function update_wallpaper()
local fake_args = gears.table.join(args, {
wallpaper = args.wallpaper[simple_schedule_object.closest_lower_time],
})
simple_schedule_object.schedule_set_function(fake_args)
end
if not simple_schedule_object then
simple_schedule_object = {}
-- initialize the schedule object, so we don't do it for every call
simple_schedule_object.schedule_set_function = args.schedule_set_function
or setters.simple
-- we get the sorted time keys
simple_schedule_object.times = {}
for k in pairs(args.wallpaper) do
table.insert(simple_schedule_object.times, k)
end
table.sort(simple_schedule_object.times)
-- now we get the closest time which is below current time (the current applicable period)
local function update_timer()
local current_time = os.date("%H:%M:%S")
local next_time = simple_schedule_object.times[1]
simple_schedule_object.closest_lower_time =
simple_schedule_object.times[#simple_schedule_object.times]
for _, k in ipairs(simple_schedule_object.times) do
if k > current_time then
next_time = k
break
end
simple_schedule_object.closest_lower_time = k
end
simple_schedule_object.timer.timeout = helpers.time.time_diff(
next_time,
current_time
)
if simple_schedule_object.timer.timeout < 0 then
-- the next_time is the day after, so we add 24 hours to the timer
simple_schedule_object.timer.timeout = simple_schedule_object.timer.timeout
+ 86400
end
simple_schedule_object.timer:again()
update_wallpaper()
end
simple_schedule_object.timer = gears.timer({
callback = update_timer,
})
update_timer()
else
-- if called again (usually when the change_timer is set), we just change the wallpaper depending on current parameters
update_wallpaper()
end
end
--- Set the AWESOME wallpaper.
--
-- @tparam table args The argument table containing the argument below.
-- @param[opt=`beautiful.bg_normal`] args.colors.bg The bg color.
-- If the default is used, the color is darkened if `beautiful.bg_normal` is light
-- or lightned if `beautiful.bg_normal` is dark.
-- @param[opt=`beautiful.fg_normal`] args.colors.fg The fg color.
-- @param[opt=`beautiful.fg_focus`] args.colors.alt_fg The alt_fg color.
--
-- see beautiful.theme_assets.wallpaper
function setters.awesome_wallpaper(args)
local colors = {
bg = beautiful.bg_normal,
fg = beautiful.fg_normal,
alt_fg = beautiful.bg_focus,
}
colors.bg = helpers.color.is_dark(beautiful.bg_normal)
and helpers.color.lighten(colors.bg)
or helpers.color.darken(colors.bg)
if type(args.colors) == "table" then
colors.bg = args.colors.bg or colors.bg
colors.fg = args.colors.fg or colors.fg
colors.alt_fg = args.colors.alt_fg or colors.alt_fg
end
-- Generate wallpaper:
if not args.screen then
for s in screen do
gears.wallpaper.set(
beautiful.theme_assets.wallpaper(
colors.bg,
colors.fg,
colors.alt_fg,
s
)
)
end
else
gears.wallpaper.set(
beautiful.theme_assets.wallpaper(
colors.bg,
colors.fg,
colors.alt_fg,
args.screen
)
)
end
end
--- Setup a wallpaper.
--
-- @tparam table args Parameters for the wallpaper. It may also contain all parameters your `args.set_function` needs
-- @int[opt=nil] args.screen The screen to use (as used in `gears.wallpaper` functions)
-- @int[opt=nil] args.change_timer Time in seconds for wallpaper changes
-- @tparam[opt=`setters.awesome` or `setters.simple`] function args.set_function A function to set the wallpaper
-- It takes args as parameter (the same args as the setup function).
-- This function is called at `"request::wallpaper"` `screen` signals and at `args.change_timer` timeouts.
-- There is no obligation, but for consistency, the function should use `args.wallpaper` as a feeder.
-- If `args.wallpaper` is defined, the default function is `setters.simple`, else it will be `setters.awesome`.
--
-- @usage
-- local wallpaper = require("wallpaper")
-- wallpaper.setup {
-- change_timer = 631, -- Prime number is better
-- set_function = wallpaper.setters.random,
-- -- parameters for the random setter
-- wallpaper = '/data/pictures/wallpapers',
-- position = "maximized",
-- }
--
-- @see apply
-- @see prepare_list
-- @see setters.simple
function setup(args)
local config = args or {}
config.set_function = config.set_function
or (config.wallpaper and setters.simple or setters.awesome_wallpaper)
local function set_wallpaper(s)
if type(config.screen) ~= 'table' then
if config.screen and s and config.screen ~= s then return end
config.screen = s or config.screen
end
config.set_function(config)
end
if config.change_timer and config.change_timer > 0 then
gears.timer({
timeout = config.change_timer,
call_now = false,
autostart = true,
callback = function()
set_wallpaper()
end,
})
end
if awesome.version == "v4.3" or awesome.version == "4.3" then
awful.screen.connect_for_each_screen(set_wallpaper)
else
screen.connect_signal("request::wallpaper", set_wallpaper)
end
end
return {
setup = setup,
setters = setters,
apply = apply,
prepare_list = prepare_list,
}

View file

@ -0,0 +1,128 @@
local awful = require("awful")
local gears = require("gears")
local beautiful = require("beautiful")
local helpers = require(tostring(...):match(".*bling") .. ".helpers")
-- It might actually swallow too much, that's why there is a filter option by classname
-- without the don't-swallow-list it would also swallow for example
-- file pickers or new firefox windows spawned by an already existing one
local window_swallowing_activated = false
-- you might want to add or remove applications here
local parent_filter_list = beautiful.parent_filter_list
or beautiful.dont_swallow_classname_list
or { "firefox", "Gimp", "Google-chrome" }
local child_filter_list = beautiful.child_filter_list
or beautiful.dont_swallow_classname_list or { }
-- for boolean values the or chain way to set the values breaks with 2 vars
-- and always defaults to true so i had to do this to se the right value...
local swallowing_filter = true
local filter_vars = { beautiful.swallowing_filter, beautiful.dont_swallow_filter_activated }
for _, var in pairs(filter_vars) do
swallowing_filter = var
end
-- check if element exist in table
-- returns true if it is
local function is_in_table(element, table)
local res = false
for _, value in pairs(table) do
if element:match(value) then
res = true
break
end
end
return res
end
-- if the swallowing filter is active checks the child and parent classes
-- against their filters
local function check_swallow(parent, child)
local res = true
if swallowing_filter then
local prnt = not is_in_table(parent, parent_filter_list)
local chld = not is_in_table(child, child_filter_list)
res = ( prnt and chld )
end
return res
end
-- async function to get the parent's pid
-- recieves a child process pid and a callback function
-- parent_pid in format "init(1)---ancestorA(pidA)---ancestorB(pidB)...---process(pid)"
function get_parent_pid(child_ppid, callback)
local ppid_cmd = string.format("pstree -A -p -s %s", child_ppid)
awful.spawn.easy_async(ppid_cmd, function(stdout, stderr, reason, exit_code)
-- primitive error checking
if stderr and stderr ~= "" then
callback(stderr)
return
end
local ppid = stdout
callback(nil, ppid)
end)
end
-- the function that will be connected to / disconnected from the spawn client signal
local function manage_clientspawn(c)
-- get the last focused window to check if it is a parent window
local parent_client = awful.client.focus.history.get(c.screen, 1)
if not parent_client then
return
elseif parent_client.type == "dialog" or parent_client.type == "splash" then
return
end
get_parent_pid(c.pid, function(err, ppid)
if err then
return
end
parent_pid = ppid
if
-- will search for "(parent_client.pid)" inside the parent_pid string
( tostring(parent_pid):find("("..tostring(parent_client.pid)..")") )
and check_swallow(parent_client.class, c.class)
then
c:connect_signal("unmanage", function()
if parent_client then
helpers.client.turn_on(parent_client)
helpers.client.sync(parent_client, c)
end
end)
helpers.client.sync(c, parent_client)
helpers.client.turn_off(parent_client)
end
end)
end
-- without the following functions that module would be autoloaded by require("bling")
-- a toggle window swallowing hotkey is also possible that way
local function start()
client.connect_signal("manage", manage_clientspawn)
window_swallowing_activated = true
end
local function stop()
client.disconnect_signal("manage", manage_clientspawn)
window_swallowing_activated = false
end
local function toggle()
if window_swallowing_activated then
stop()
else
start()
end
end
return {
start = start,
stop = stop,
toggle = toggle,
}