diff --git a/.config/awesome/extras/bling/.editorconfig b/.config/awesome/extras/bling/.editorconfig
new file mode 100755
index 0000000..33997d4
--- /dev/null
+++ b/.config/awesome/extras/bling/.editorconfig
@@ -0,0 +1,24 @@
+root = true
+
+[*]
+charset = utf-8
+end_of_line = lf
+indent_size = 4
+indent_style = tab
+trim_trailing_whitespace = true
+
+[*.lua]
+indent_size = 4
+indent_style = space
+max_line_length = 80
+
+[*.yml]
+indent_size = 2
+indent_style = space
+
+[*.{html,css}]
+indent_size = 2
+indent_style = space
+
+[*.md]
+trim_trailing_whitespace = false
diff --git a/.config/awesome/extras/bling/helpers/client.lua b/.config/awesome/extras/bling/helpers/client.lua
new file mode 100755
index 0000000..0e14c51
--- /dev/null
+++ b/.config/awesome/extras/bling/helpers/client.lua
@@ -0,0 +1,127 @@
+local awful = require("awful")
+local gears = require("gears")
+
+local _client = {}
+
+--- Turn off passed client
+-- Remove current tag from window's tags
+--
+-- @param c A client
+function _client.turn_off(c, current_tag)
+ if current_tag == nil then
+ current_tag = c.screen.selected_tag
+ end
+ local ctags = {}
+ for k, tag in pairs(c:tags()) do
+ if tag ~= current_tag then
+ table.insert(ctags, tag)
+ end
+ end
+ c:tags(ctags)
+ c.sticky = false
+end
+
+--- Turn on passed client (add current tag to window's tags)
+--
+-- @param c A client
+function _client.turn_on(c)
+ local current_tag = c.screen.selected_tag
+ ctags = { current_tag }
+ for k, tag in pairs(c:tags()) do
+ if tag ~= current_tag then
+ table.insert(ctags, tag)
+ end
+ end
+ c:tags(ctags)
+ c:raise()
+ client.focus = c
+end
+
+--- Sync two clients
+--
+-- @param to_c The client to which to write all properties
+-- @param from_c The client from which to read all properties
+function _client.sync(to_c, from_c)
+ if not from_c or not to_c then
+ return
+ end
+ if not from_c.valid or not to_c.valid then
+ return
+ end
+ if from_c.modal then
+ return
+ end
+ to_c.floating = from_c.floating
+ to_c.maximized = from_c.maximized
+ to_c.above = from_c.above
+ to_c.below = from_c.below
+ to_c:geometry(from_c:geometry())
+ -- TODO: Should also copy over the position in a tiling layout
+end
+
+--- Checks whether the passed client is a childprocess of a given process ID
+--
+-- @param c A client
+-- @param pid The process ID
+-- @return True if the passed client is a childprocess of the given PID otherwise false
+function _client.is_child_of(c, pid)
+ -- io.popen is normally discouraged. Should probably be changed
+ if not c or not c.valid then
+ return false
+ end
+ if tostring(c.pid) == tostring(pid) then
+ return true
+ end
+ local pid_cmd = [[pstree -T -p -a -s ]]
+ .. tostring(c.pid)
+ .. [[ | sed '2q;d' | grep -o '[0-9]*$' | tr -d '\n']]
+ local handle = io.popen(pid_cmd)
+ local parent_pid = handle:read("*a")
+ handle:close()
+ return tostring(parent_pid) == tostring(pid)
+ or tostring(parent_pid) == tostring(c.pid)
+end
+
+--- Finds all clients that satisfy the passed rule
+--
+-- @param rule The rule to be searched for
+-- @retrun A list of clients that match the given rule
+function _client.find(rule)
+ local function matcher(c)
+ return awful.rules.match(c, rule)
+ end
+ local clients = client.get()
+ local findex = gears.table.hasitem(clients, client.focus) or 1
+ local start = gears.math.cycle(#clients, findex + 1)
+
+ local matches = {}
+ for c in awful.client.iterate(matcher, start) do
+ matches[#matches + 1] = c
+ end
+
+ return matches
+end
+
+--- Gets the next client by direction from the focused one
+--
+-- @param direction it the direction as a string ("up", "down", "left" or "right")
+-- @retrun the client in the given direction starting at the currently focused one, nil otherwise
+function _client.get_by_direction(direction)
+ local sel = client.focus
+ if not sel then
+ return nil
+ end
+ local cltbl = sel.screen:get_clients()
+ local geomtbl = {}
+ for i, cl in ipairs(cltbl) do
+ geomtbl[i] = cl:geometry()
+ end
+ local target = gears.geometry.rectangle.get_in_direction(
+ direction,
+ geomtbl,
+ sel:geometry()
+ )
+ return cltbl[target]
+end
+
+return _client
diff --git a/.config/awesome/extras/bling/helpers/color.lua b/.config/awesome/extras/bling/helpers/color.lua
new file mode 100755
index 0000000..4042360
--- /dev/null
+++ b/.config/awesome/extras/bling/helpers/color.lua
@@ -0,0 +1,158 @@
+local tonumber = tonumber
+local string = string
+local math = math
+local floor = math.floor
+local max = math.max
+local min = math.min
+local abs = math.abs
+local format = string.format
+
+local _color = {}
+
+--- Try to guess if a color is dark or light.
+--
+-- @string color The color with hexadecimal HTML format `"#RRGGBB"`.
+-- @treturn bool `true` if the color is dark, `false` if it is light.
+function _color.is_dark(color)
+ -- Try to determine if the color is dark or light
+ local numeric_value = 0
+ for s in color:gmatch("[a-fA-F0-9][a-fA-F0-9]") do
+ numeric_value = numeric_value + tonumber("0x" .. s)
+ end
+ return (numeric_value < 383)
+end
+
+function _color.is_opaque(color)
+ if type(color) == "string" then
+ color = _color.hex_to_rgba(color)
+ end
+
+ return color.a < 0.01
+end
+
+--- Lighten a color.
+--
+-- @string color The color to lighten with hexadecimal HTML format `"#RRGGBB"`.
+-- @int[opt=26] amount How much light from 0 to 255. Default is around 10%.
+-- @treturn string The lighter color
+function _color.lighten(color, amount)
+ amount = amount or 26
+ local c = {
+ r = tonumber("0x" .. color:sub(2, 3)),
+ g = tonumber("0x" .. color:sub(4, 5)),
+ b = tonumber("0x" .. color:sub(6, 7)),
+ }
+
+ c.r = c.r + amount
+ c.r = c.r < 0 and 0 or c.r
+ c.r = c.r > 255 and 255 or c.r
+ c.g = c.g + amount
+ c.g = c.g < 0 and 0 or c.g
+ c.g = c.g > 255 and 255 or c.g
+ c.b = c.b + amount
+ c.b = c.b < 0 and 0 or c.b
+ c.b = c.b > 255 and 255 or c.b
+
+ return string.format("#%02x%02x%02x", c.r, c.g, c.b)
+end
+
+--- Darken a color.
+--
+-- @string color The color to darken with hexadecimal HTML format `"#RRGGBB"`.
+-- @int[opt=26] amount How much dark from 0 to 255. Default is around 10%.
+-- @treturn string The darker color
+function _color.darken(color, amount)
+ amount = amount or 26
+ return _color.lighten(color, -amount)
+end
+
+-- Returns a value that is clipped to interval edges if it falls outside the interval
+function _color.clip(num, min_num, max_num)
+ return max(min(num, max_num), min_num)
+end
+
+-- Converts the given hex color to rgba
+function _color.hex_to_rgba(color)
+ color = color:gsub("#", "")
+ return { r = tonumber("0x" .. color:sub(1, 2)),
+ g = tonumber("0x" .. color:sub(3, 4)),
+ b = tonumber("0x" .. color:sub(5, 6)),
+ a = #color == 8 and tonumber("0x" .. color:sub(7, 8)) or 255 }
+end
+
+-- Converts the given rgba color to hex
+function _color.rgba_to_hex(color)
+ local r = _color.clip(color.r or color[1], 0, 255)
+ local g = _color.clip(color.g or color[2], 0, 255)
+ local b = _color.clip(color.b or color[3], 0, 255)
+ local a = _color.clip(color.a or color[4] or 255, 0, 255)
+ return "#" .. format("%02x%02x%02x%02x",
+ floor(r),
+ floor(g),
+ floor(b),
+ floor(a))
+end
+
+-- Converts the given hex color to hsv
+function _color.hex_to_hsv(color)
+ local color = _color.hex2rgb(color)
+ local C_max = max(color.r, color.g, color.b)
+ local C_min = min(color.r, color.g, color.b)
+ local delta = C_max - C_min
+ local H, S, V
+ if delta == 0 then
+ H = 0
+ elseif C_max == color.r then
+ H = 60 * (((color.g - color.b) / delta) % 6)
+ elseif C_max == color.g then
+ H = 60 * (((color.b - color.r) / delta) + 2)
+ elseif C_max == color.b then
+ H = 60 * (((color.r - color.g) / delta) + 4)
+ end
+ if C_max == 0 then
+ S = 0
+ else
+ S = delta / C_max
+ end
+ V = C_max
+
+ return { h = H,
+ s = S * 100,
+ v = V * 100 }
+end
+
+-- Converts the given hsv color to hex
+function _color.hsv_to_hex(H, S, V)
+ S = S / 100
+ V = V / 100
+ if H > 360 then H = 360 end
+ if H < 0 then H = 0 end
+ local C = V * S
+ local X = C * (1 - abs(((H / 60) % 2) - 1))
+ local m = V - C
+ local r_, g_, b_ = 0, 0, 0
+ if H >= 0 and H < 60 then
+ r_, g_, b_ = C, X, 0
+ elseif H >= 60 and H < 120 then
+ r_, g_, b_ = X, C, 0
+ elseif H >= 120 and H < 180 then
+ r_, g_, b_ = 0, C, X
+ elseif H >= 180 and H < 240 then
+ r_, g_, b_ = 0, X, C
+ elseif H >= 240 and H < 300 then
+ r_, g_, b_ = X, 0, C
+ elseif H >= 300 and H < 360 then
+ r_, g_, b_ = C, 0, X
+ end
+ local r, g, b = (r_ + m) * 255, (g_ + m) * 255, (b_ + m) * 255
+ return ("#%02x%02x%02x"):format(floor(r), floor(g), floor(b))
+end
+
+function _color.multiply(color, amount)
+ return { _color.clip(color.r * amount, 0, 255),
+ _color.clip(color.g * amount, 0, 255),
+ _color.clip(color.b * amount, 0, 255),
+ 255 }
+end
+
+return _color
diff --git a/.config/awesome/extras/bling/helpers/filesystem.lua b/.config/awesome/extras/bling/helpers/filesystem.lua
new file mode 100755
index 0000000..9f65d0e
--- /dev/null
+++ b/.config/awesome/extras/bling/helpers/filesystem.lua
@@ -0,0 +1,53 @@
+local Gio = require("lgi").Gio
+
+local _filesystem = {}
+
+--- Get a list of files from a given directory.
+-- @string path The directory to search.
+-- @tparam[opt] table exts Specific extensions to limit the search to. eg:`{ "jpg", "png" }`
+-- If ommited, all files are considered.
+-- @bool[opt=false] recursive List files from subdirectories
+-- @staticfct bling.helpers.filesystem.get_random_file_from_dir
+function _filesystem.list_directory_files(path, exts, recursive)
+ recursive = recursive or false
+ local files, valid_exts = {}, {}
+
+ -- Transforms { "jpg", ... } into { [jpg] = #, ... }
+ if exts then
+ for i, j in ipairs(exts) do
+ valid_exts[j:lower()] = i
+ end
+ end
+
+ -- Build a table of files from the path with the required extensions
+ local file_list = Gio.File.new_for_path(path):enumerate_children(
+ "standard::*",
+ 0
+ )
+ if file_list then
+ for file in function()
+ return file_list:next_file()
+ end do
+ local file_type = file:get_file_type()
+ if file_type == "REGULAR" then
+ local file_name = file:get_display_name()
+ if
+ not exts
+ or valid_exts[file_name:lower():match(".+%.(.*)$") or ""]
+ then
+ table.insert(files, file_name)
+ end
+ elseif recursive and file_type == "DIRECTORY" then
+ local file_name = file:get_display_name()
+ files = gears.table.join(
+ files,
+ list_directory_files(file_name, exts, recursive)
+ )
+ end
+ end
+ end
+
+ return files
+end
+
+return _filesystem
diff --git a/.config/awesome/extras/bling/helpers/icon_theme.lua b/.config/awesome/extras/bling/helpers/icon_theme.lua
new file mode 100755
index 0000000..4a1db92
--- /dev/null
+++ b/.config/awesome/extras/bling/helpers/icon_theme.lua
@@ -0,0 +1,134 @@
+local Gio = require("lgi").Gio
+local Gtk = require("lgi").Gtk
+local gobject = require("gears.object")
+local gtable = require("gears.table")
+local helpers = require("helpers")
+local setmetatable = setmetatable
+local ipairs = ipairs
+
+local icon_theme = { mt = {} }
+
+function icon_theme:get_client_icon_path(client)
+ local function find_icon(class)
+ if self._private.client_icon_cache[class] ~= nil then
+ return self._private.client_icon_cache[class]
+ end
+
+ for _, app in ipairs(Gio.AppInfo.get_all()) do
+ local id = Gio.AppInfo.get_id(app)
+ if id:match(helpers.misc.case_insensitive_pattern(class)) then
+ self._private.client_icon_cache[class] = self:get_gicon_path(Gio.AppInfo.get_icon(app))
+ return self._private.client_icon_cache[class]
+ end
+ end
+
+ return nil
+ end
+
+ local class = client.class
+ if class == "jetbrains-studio" then
+ class = "android-studio"
+ end
+
+ local icon = self:get_icon_path("gnome-window-manager")
+
+ if class ~= nil then
+ class = class:gsub("[%-]", "%%%0")
+ icon = find_icon(class) or icon
+
+ class = client.class
+ class = class:gsub("[%-]", "")
+ icon = find_icon(class) or icon
+
+ class = client.class
+ class = class:gsub("[%-]", ".")
+ icon = find_icon(class) or icon
+
+ class = client.class
+ class = class:match("(.-)-") or class
+ class = class:match("(.-)%.") or class
+ class = class:match("(.-)%s+") or class
+ class = class:gsub("[%-]", "%%%0")
+ icon = find_icon(class) or icon
+ end
+
+ return icon
+end
+
+function icon_theme:choose_icon(icons_names)
+ local icon_info = Gtk.IconTheme.choose_icon(self.gtk_theme, icons_names, self.icon_size, 0);
+ if icon_info then
+ local icon_path = Gtk.IconInfo.get_filename(icon_info)
+ if icon_path then
+ return icon_path
+ end
+ end
+
+ return ""
+end
+
+
+function icon_theme:get_gicon_path(gicon)
+ if gicon == nil then
+ return ""
+ end
+
+ if self._private.icon_cache[gicon] ~= nil then
+ return self._private.icon_cache[gicon]
+ end
+
+ local icon_info = Gtk.IconTheme.lookup_by_gicon(self.gtk_theme, gicon, self.icon_size, 0);
+ if icon_info then
+ local icon_path = Gtk.IconInfo.get_filename(icon_info)
+ if icon_path then
+ self._private.icon_cache[gicon] = icon_path
+ return icon_path
+ end
+ end
+
+ return ""
+end
+
+function icon_theme:get_icon_path(icon_name)
+ if self._private.icon_cache[icon_name] ~= nil then
+ return self._private.icon_cache[icon_name]
+ end
+
+ local icon_info = Gtk.IconTheme.lookup_icon(self.gtk_theme, icon_name, self.icon_size, 0);
+ if icon_info then
+ local icon_path = Gtk.IconInfo.get_filename(icon_info)
+ if icon_path then
+ self._private.icon_cache[icon_name] = icon_path
+ return icon_path
+ end
+ end
+
+ return ""
+end
+
+local function new(theme_name, icon_size)
+ local ret = gobject{}
+ gtable.crush(ret, icon_theme, true)
+
+ ret._private = {}
+ ret._private.client_icon_cache = {}
+ ret._private.icon_cache = {}
+
+ ret.name = theme_name or nil
+ ret.icon_size = icon_size or 48
+
+ if theme_name then
+ ret.gtk_theme = Gtk.IconTheme.new()
+ Gtk.IconTheme.set_custom_theme(ret.gtk_theme, theme_name);
+ else
+ ret.gtk_theme = Gtk.IconTheme.get_default()
+ end
+
+ return ret
+end
+
+function icon_theme.mt:__call(...)
+ return new(...)
+end
+
+return setmetatable(icon_theme, icon_theme.mt)
diff --git a/.config/awesome/extras/bling/helpers/init.lua b/.config/awesome/extras/bling/helpers/init.lua
new file mode 100755
index 0000000..f2c898e
--- /dev/null
+++ b/.config/awesome/extras/bling/helpers/init.lua
@@ -0,0 +1,7 @@
+return {
+ client = require(... .. ".client"),
+ color = require(... .. ".color"),
+ filesystem = require(... .. ".filesystem"),
+ shape = require(... .. ".shape"),
+ time = require(... .. ".time"),
+}
diff --git a/.config/awesome/extras/bling/helpers/shape.lua b/.config/awesome/extras/bling/helpers/shape.lua
new file mode 100755
index 0000000..9c96d83
--- /dev/null
+++ b/.config/awesome/extras/bling/helpers/shape.lua
@@ -0,0 +1,30 @@
+local gears = require("gears")
+
+shape = {}
+
+-- Create rounded rectangle shape (in one line)
+
+function shape.rrect(radius)
+ return function(cr, width, height)
+ gears.shape.rounded_rect(cr, width, height, radius)
+ end
+end
+
+-- Create partially rounded rect
+
+function shape.prrect(radius, tl, tr, br, bl)
+ return function(cr, width, height)
+ gears.shape.partially_rounded_rect(
+ cr,
+ width,
+ height,
+ tl,
+ tr,
+ br,
+ bl,
+ radius
+ )
+ end
+end
+
+return shape
diff --git a/.config/awesome/extras/bling/helpers/time.lua b/.config/awesome/extras/bling/helpers/time.lua
new file mode 100755
index 0000000..5ab0f25
--- /dev/null
+++ b/.config/awesome/extras/bling/helpers/time.lua
@@ -0,0 +1,24 @@
+local time = {}
+
+--- Parse a time string to seconds (from midnight)
+--
+-- @string time The time (`HH:MM:SS`)
+-- @treturn int The number of seconds since 00:00:00
+function time.hhmmss_to_seconds(time)
+ hour_sec = tonumber(string.sub(time, 1, 2)) * 3600
+ min_sec = tonumber(string.sub(time, 4, 5)) * 60
+ get_sec = tonumber(string.sub(time, 7, 8))
+ return (hour_sec + min_sec + get_sec)
+end
+
+--- Get time difference in seconds.
+--
+-- @tparam string base The time to compare from (`HH:MM:SS`).
+-- @tparam string base The time to compare to (`HH:MM:SS`).
+-- @treturn int Number of seconds between the two times.
+function time.time_diff(base, compare)
+ local diff = time.hhmmss_to_seconds(base) - time.hhmmss_to_seconds(compare)
+ return diff
+end
+
+return time
diff --git a/.config/awesome/extras/bling/icons/layouts/centered.png b/.config/awesome/extras/bling/icons/layouts/centered.png
new file mode 100755
index 0000000..5e4d07f
Binary files /dev/null and b/.config/awesome/extras/bling/icons/layouts/centered.png differ
diff --git a/.config/awesome/extras/bling/icons/layouts/deck.png b/.config/awesome/extras/bling/icons/layouts/deck.png
new file mode 100755
index 0000000..829d0c2
Binary files /dev/null and b/.config/awesome/extras/bling/icons/layouts/deck.png differ
diff --git a/.config/awesome/extras/bling/icons/layouts/equalarea.png b/.config/awesome/extras/bling/icons/layouts/equalarea.png
new file mode 100755
index 0000000..47164a9
Binary files /dev/null and b/.config/awesome/extras/bling/icons/layouts/equalarea.png differ
diff --git a/.config/awesome/extras/bling/icons/layouts/horizontal.png b/.config/awesome/extras/bling/icons/layouts/horizontal.png
new file mode 100755
index 0000000..217f4eb
Binary files /dev/null and b/.config/awesome/extras/bling/icons/layouts/horizontal.png differ
diff --git a/.config/awesome/extras/bling/icons/layouts/mstab.png b/.config/awesome/extras/bling/icons/layouts/mstab.png
new file mode 100755
index 0000000..4372846
Binary files /dev/null and b/.config/awesome/extras/bling/icons/layouts/mstab.png differ
diff --git a/.config/awesome/extras/bling/icons/layouts/vertical.png b/.config/awesome/extras/bling/icons/layouts/vertical.png
new file mode 100755
index 0000000..3561673
Binary files /dev/null and b/.config/awesome/extras/bling/icons/layouts/vertical.png differ
diff --git a/.config/awesome/extras/bling/images/bling_banner-2x.png b/.config/awesome/extras/bling/images/bling_banner-2x.png
new file mode 100755
index 0000000..9e2ab30
Binary files /dev/null and b/.config/awesome/extras/bling/images/bling_banner-2x.png differ
diff --git a/.config/awesome/extras/bling/images/bling_banner.png b/.config/awesome/extras/bling/images/bling_banner.png
new file mode 100755
index 0000000..43de6c9
Binary files /dev/null and b/.config/awesome/extras/bling/images/bling_banner.png differ
diff --git a/.config/awesome/extras/bling/init.lua b/.config/awesome/extras/bling/init.lua
new file mode 100755
index 0000000..93bde56
--- /dev/null
+++ b/.config/awesome/extras/bling/init.lua
@@ -0,0 +1,7 @@
+return {
+ layout = require(... .. ".layout"),
+ --module = require(... .. ".module"),
+ --helpers = require(... .. ".helpers"),
+ --signal = require(... .. ".signal"),
+ --widget = require(... .. ".widget"),
+}
diff --git a/.config/awesome/extras/bling/layout/centered.lua b/.config/awesome/extras/bling/layout/centered.lua
new file mode 100755
index 0000000..4e1ad24
--- /dev/null
+++ b/.config/awesome/extras/bling/layout/centered.lua
@@ -0,0 +1,84 @@
+local awful = require("awful")
+local math = math
+
+local mylayout = {}
+
+mylayout.name = "centered"
+
+function mylayout.arrange(p)
+ local area = p.workarea
+ local t = p.tag or screen[p.screen].selected_tag
+ local mwfact = t.master_width_factor
+ local nmaster = math.min(t.master_count, #p.clients)
+ local nslaves = #p.clients - nmaster
+
+ local master_area_width = area.width * mwfact
+ local slave_area_width = area.width - master_area_width
+ local master_area_x = area.x + 0.5 * slave_area_width
+
+ local number_of_left_sided_slaves = math.floor(nslaves / 2)
+ local number_of_right_sided_slaves = nslaves - number_of_left_sided_slaves
+ local left_iterator = 0
+ local right_iterator = 0
+
+ -- Special case: no maters -> rrelapse into awesomes fair layout
+ if t.master_count == 0 then
+ awful.layout.suit.fair.arrange(p)
+ return
+ end
+
+ -- Special case: one slave -> relapse into awesomes masterstack tile layout
+ if nslaves == 1 then
+ awful.layout.suit.tile.right.arrange(p)
+ return
+ end
+
+ -- Special case: no slaves -> fullscreen master area
+ if nslaves < 1 then
+ master_area_width = area.width
+ master_area_x = area.x
+ end
+
+ -- iterate through masters
+ for idx = 1, nmaster do
+ local c = p.clients[idx]
+ local g
+ g = {
+ x = master_area_x,
+ y = area.y + (nmaster - idx) * (area.height / nmaster),
+ width = master_area_width,
+ height = area.height / nmaster,
+ }
+ p.geometries[c] = g
+ end
+
+ -- iterate through slaves
+ for idx = 1, nslaves do -- idx=nmaster+1,#p.clients do
+ local c = p.clients[idx + nmaster]
+ local g
+ if idx % 2 == 0 then
+ g = {
+ x = area.x,
+ y = area.y
+ + left_iterator
+ * (area.height / number_of_left_sided_slaves),
+ width = slave_area_width / 2,
+ height = area.height / number_of_left_sided_slaves,
+ }
+ left_iterator = left_iterator + 1
+ else
+ g = {
+ x = area.x + master_area_width + slave_area_width / 2,
+ y = area.y
+ + right_iterator
+ * (area.height / number_of_right_sided_slaves),
+ width = slave_area_width / 2,
+ height = area.height / number_of_right_sided_slaves,
+ }
+ right_iterator = right_iterator + 1
+ end
+ p.geometries[c] = g
+ end
+end
+
+return mylayout
diff --git a/.config/awesome/extras/bling/layout/deck.lua b/.config/awesome/extras/bling/layout/deck.lua
new file mode 100755
index 0000000..e0500b9
--- /dev/null
+++ b/.config/awesome/extras/bling/layout/deck.lua
@@ -0,0 +1,37 @@
+local mylayout = {}
+
+mylayout.name = "deck"
+
+function mylayout.arrange(p)
+ local area = p.workarea
+ local t = p.tag or screen[p.screen].selected_tag
+ local client_count = #p.clients
+
+ if client_count == 1 then
+ local c = p.clients[1]
+ local g = {
+ x = area.x,
+ y = area.y,
+ width = area.width,
+ height = area.height,
+ }
+ p.geometries[c] = g
+ return
+ end
+
+ local xoffset = area.width * 0.1 / (client_count - 1)
+ local yoffset = area.height * 0.1 / (client_count - 1)
+
+ for idx = 1, client_count do
+ local c = p.clients[idx]
+ local g = {
+ x = area.x + (idx - 1) * xoffset,
+ y = area.y + (idx - 1) * yoffset,
+ width = area.width - (xoffset * (client_count - 1)),
+ height = area.height - (yoffset * (client_count - 1)),
+ }
+ p.geometries[c] = g
+ end
+end
+
+return mylayout
diff --git a/.config/awesome/extras/bling/layout/equalarea.lua b/.config/awesome/extras/bling/layout/equalarea.lua
new file mode 100755
index 0000000..37e972d
--- /dev/null
+++ b/.config/awesome/extras/bling/layout/equalarea.lua
@@ -0,0 +1,77 @@
+local math = math
+local screen = screen
+local mylayout = {}
+mylayout.name = "equalarea"
+
+local function divide(p, g, low, high, cls, mwfact, mcount)
+ if low == high then
+ p.geometries[cls[low]] = g
+ else
+ local masters = math.max(0, math.min(mcount, high) - low + 1)
+ local numblock = high - low + 1
+ local slaves = numblock - masters
+ local smalldiv
+ if numblock > 5 and (numblock % 5) == 0 then
+ smalldiv = math.floor(numblock / 5)
+ else
+ if (numblock % 3) == 0 then
+ smalldiv = math.floor(numblock / 3)
+ else
+ smalldiv = math.floor(numblock / 2)
+ end
+ end
+ local bigdiv = numblock - smalldiv
+ local smallmasters = math.min(masters, smalldiv)
+ local bigmasters = masters - smallmasters
+ local smallg = {}
+ local bigg = {}
+ smallg.x = g.x
+ smallg.y = g.y
+ if g.width > (g.height * 1.3) then
+ smallg.height = g.height
+ bigg.height = g.height
+ bigg.width = math.floor(
+ g.width
+ * (bigmasters * (mwfact - 1) + bigdiv)
+ / (slaves + mwfact * masters)
+ )
+ smallg.width = g.width - bigg.width
+ bigg.y = g.y
+ bigg.x = g.x + smallg.width
+ else
+ smallg.width = g.width
+ bigg.width = g.width
+ bigg.height = math.floor(
+ g.height
+ * (bigmasters * (mwfact - 1) + bigdiv)
+ / (slaves + mwfact * masters)
+ )
+ smallg.height = g.height - bigg.height
+ bigg.x = g.x
+ bigg.y = g.y + smallg.height
+ end
+ divide(p, smallg, low, high - bigdiv, cls, mwfact, mcount)
+ divide(p, bigg, low + smalldiv, high, cls, mwfact, mcount)
+ end
+ return
+end
+
+function mylayout.arrange(p)
+ local t = p.tag or screen[p.screen].selected_tag
+ local wa = p.workarea
+ local cls = p.clients
+
+ if #cls == 0 then
+ return
+ end
+ local mwfact = t.master_width_factor * 2
+ local mcount = t.master_count
+ local g = {}
+ g.height = wa.height
+ g.width = wa.width
+ g.x = wa.x
+ g.y = wa.y
+ divide(p, g, 1, #cls, cls, mwfact, mcount)
+end
+
+return mylayout
diff --git a/.config/awesome/extras/bling/layout/horizontal.lua b/.config/awesome/extras/bling/layout/horizontal.lua
new file mode 100755
index 0000000..23f9c9e
--- /dev/null
+++ b/.config/awesome/extras/bling/layout/horizontal.lua
@@ -0,0 +1,56 @@
+local math = math
+
+local mylayout = {}
+
+mylayout.name = "horizontal"
+
+function mylayout.arrange(p)
+ local area = p.workarea
+ local t = p.tag or screen[p.screen].selected_tag
+ local mwfact = t.master_width_factor
+ local nmaster = math.min(t.master_count, #p.clients)
+ local nslaves = #p.clients - nmaster
+
+ local master_area_height = area.height * mwfact
+ local slave_area_height = area.height - master_area_height
+
+ -- Special case: no slaves
+ if nslaves == 0 then
+ master_area_height = area.height
+ slave_area_height = 0
+ end
+
+ -- Special case: no masters
+ if nmaster == 0 then
+ master_area_height = 0
+ slave_area_height = area.height
+ end
+
+ -- itearte through masters
+ for idx = 1, nmaster do
+ local c = p.clients[idx]
+ local g = {
+ x = area.x + (idx - 1) * (area.width / nmaster),
+ y = area.y,
+ width = area.width / nmaster,
+ height = master_area_height,
+ }
+ p.geometries[c] = g
+ end
+
+ -- iterate through slaves
+ for idx = 1, nslaves do
+ local c = p.clients[idx + nmaster]
+ local g = {
+ x = area.x,
+ y = area.y
+ + master_area_height
+ + (idx - 1) * (slave_area_height / nslaves),
+ width = area.width,
+ height = slave_area_height / nslaves,
+ }
+ p.geometries[c] = g
+ end
+end
+
+return mylayout
diff --git a/.config/awesome/extras/bling/layout/init.lua b/.config/awesome/extras/bling/layout/init.lua
new file mode 100755
index 0000000..a57dd6a
--- /dev/null
+++ b/.config/awesome/extras/bling/layout/init.lua
@@ -0,0 +1,44 @@
+local beautiful = require("beautiful")
+local gears = require("gears")
+
+local M = {}
+local relative_lua_path = tostring(...)
+
+local function get_layout_icon_path(name)
+ local relative_icon_path = relative_lua_path
+ :match("^.*bling"):gsub("%.", "/")
+ .. "/icons/layouts/" .. name .. ".png"
+
+ for p in package.path:gmatch('([^;]+)') do
+ p = p:gsub("?.*", "")
+ local absolute_icon_path = p .. relative_icon_path
+ if gears.filesystem.file_readable(absolute_icon_path) then
+ return absolute_icon_path
+ end
+ end
+end
+
+local function get_icon(icon_raw)
+ if icon_raw ~= nil then
+ return gears.color.recolor_image(icon_raw, "#e6e6e6")
+ else
+ return nil
+ end
+end
+
+local layouts = {
+ "mstab",
+ "vertical",
+ "horizontal",
+ "centered",
+ "equalarea",
+ "deck"
+}
+
+for _, layout_name in ipairs(layouts) do
+ local icon_raw = get_layout_icon_path(layout_name)
+ beautiful["layout_" .. layout_name] = get_icon(icon_raw)
+ M[layout_name] = require(... .. "." .. layout_name)
+end
+
+return M
diff --git a/.config/awesome/extras/bling/layout/mstab.lua b/.config/awesome/extras/bling/layout/mstab.lua
new file mode 100755
index 0000000..93ceb0e
--- /dev/null
+++ b/.config/awesome/extras/bling/layout/mstab.lua
@@ -0,0 +1,249 @@
+local awful = require("awful")
+local gears = require("gears")
+local wibox = require("wibox")
+local beautiful = require("beautiful")
+
+local mylayout = {}
+
+mylayout.name = "mstab"
+
+local tabbar_disable = beautiful.mstab_bar_disable or false
+local tabbar_ontop = beautiful.mstab_bar_ontop or false
+local tabbar_padding = beautiful.mstab_bar_padding or "default"
+local border_radius = beautiful.mstab_border_radius
+ or beautiful.border_radius
+ or 0
+local tabbar_position = beautiful.mstab_tabbar_position
+ or beautiful.tabbar_position
+ or "top"
+
+local bar_style = beautiful.mstab_tabbar_style
+ or beautiful.tabbar_style
+ or "default"
+local bar = require(
+ tostring(...):match(".*bling") .. ".widget.tabbar." .. bar_style
+)
+local tabbar_size = bar.size
+ or beautiful.mstab_bar_height
+ or beautiful.tabbar_size
+ or 40
+local dont_resize_slaves = beautiful.mstab_dont_resize_slaves or false
+
+-- The top_idx is the idx of the slave clients (excluding all master clients)
+-- that should be on top of all other slave clients ("the focused slave")
+-- by creating a variable outside of the arrange function, this layout can "remember" that client
+-- by creating it as a new property of every tag, this layout can be active on different tags and
+-- still have different "focused slave clients"
+for idx, tag in ipairs(root.tags()) do
+ tag.top_idx = 1
+end
+
+-- Haven't found a signal that is emitted when a new tag is added. That should work though
+-- since you can't use a layout on a tag that you haven't selected previously
+tag.connect_signal("property::selected", function(t)
+ if not t.top_idx then
+ t.top_idx = 1
+ end
+end)
+
+function update_tabbar(
+ clients,
+ t,
+ top_idx,
+ area,
+ master_area_width,
+ slave_area_width
+)
+ local s = t.screen
+
+ -- create the list of clients for the tabbar
+ local clientlist = bar.layout()
+ for idx, c in ipairs(clients) do
+ -- focus with right click, kill with mid click, minimize with left click
+ local buttons = gears.table.join(
+ awful.button({}, 1, function()
+ c:raise()
+ client.focus = c
+ end),
+ awful.button({}, 2, function()
+ c:kill()
+ end),
+ awful.button({}, 3, function()
+ c.minimized = true
+ end)
+ )
+ local client_box = bar.create(c, (idx == top_idx), buttons)
+ clientlist:add(client_box)
+ end
+
+ -- if no tabbar exists, create one
+ if not s.tabbar then
+ s.tabbar = wibox({
+ ontop = tabbar_ontop,
+ shape = function(cr, width, height)
+ gears.shape.rounded_rect(cr, width, height, border_radius)
+ end,
+ bg = bar.bg_normal,
+ visible = true,
+ })
+
+ -- Change visibility of the tab bar when layout, selected tag or number of clients (visible, master, slave) changes
+ local function adjust_visiblity(t)
+ s.tabbar.visible = (#t:clients() - t.master_count > 1)
+ and (t.layout.name == mylayout.name)
+ end
+
+ tag.connect_signal("property::selected", function(t)
+ adjust_visiblity(t)
+ end)
+ tag.connect_signal("property::layout", function(t, layout)
+ adjust_visiblity(t)
+ end)
+ tag.connect_signal("tagged", function(t, c)
+ adjust_visiblity(t)
+ end)
+ tag.connect_signal("untagged", function(t, c)
+ adjust_visiblity(t)
+ end)
+ tag.connect_signal("property::master_count", function(t)
+ adjust_visiblity(t)
+ end)
+ client.connect_signal("property::minimized", function(c)
+ local t = c.first_tag
+ adjust_visiblity(t)
+ end)
+ end
+
+ -- update the tabbar size and position (to support gap size change on the fly)
+ if tabbar_position == "top" then
+ s.tabbar.x = area.x + master_area_width + t.gap
+ s.tabbar.y = area.y + t.gap
+ s.tabbar.width = slave_area_width - 2 * t.gap
+ s.tabbar.height = tabbar_size
+ elseif tabbar_position == "bottom" then
+ s.tabbar.x = area.x + master_area_width + t.gap
+ s.tabbar.y = area.y + area.height - tabbar_size - t.gap
+ s.tabbar.width = slave_area_width - 2 * t.gap
+ s.tabbar.height = tabbar_size
+ elseif tabbar_position == "left" then
+ s.tabbar.x = area.x + master_area_width + t.gap
+ s.tabbar.y = area.y + t.gap
+ s.tabbar.width = tabbar_size
+ s.tabbar.height = area.height - 2 * t.gap
+ elseif tabbar_position == "right" then
+ s.tabbar.x = area.x
+ + master_area_width
+ + slave_area_width
+ - tabbar_size
+ - t.gap
+ s.tabbar.y = area.y + t.gap
+ s.tabbar.width = tabbar_size
+ s.tabbar.height = area.height - 2 * t.gap
+ end
+
+ -- update clientlist
+ s.tabbar:setup({ layout = wibox.layout.flex.horizontal, clientlist })
+end
+
+function mylayout.arrange(p)
+ local area = p.workarea
+ local t = p.tag or screen[p.screen].selected_tag
+ local s = t.screen
+ local mwfact = t.master_width_factor
+ local nmaster = math.min(t.master_count, #p.clients)
+ local nslaves = #p.clients - nmaster
+
+ local master_area_width = area.width * mwfact
+ local slave_area_width = area.width - master_area_width
+
+ -- "default" means that it uses standard useless gap size
+ if tabbar_padding == "default" then
+ tabbar_padding = 2 * t.gap
+ end
+
+ -- Special case: No masters -> full screen slave width
+ if nmaster == 0 then
+ master_area_width = 1
+ slave_area_width = area.width
+ end
+
+ -- Special case: One or zero slaves -> no tabbar (essentially tile right)
+ if nslaves <= 1 then
+ -- since update_tabbar isnt called that way we have to hide it manually
+ if s.tabbar then
+ s.tabbar.visible = false
+ end
+ -- otherwise just do tile right
+ awful.layout.suit.tile.right.arrange(p)
+ return
+ end
+
+ -- Iterate through masters
+ for idx = 1, nmaster do
+ local c = p.clients[idx]
+ local g = {
+ x = area.x,
+ y = area.y + (idx - 1) * (area.height / nmaster),
+ width = master_area_width,
+ height = area.height / nmaster,
+ }
+ p.geometries[c] = g
+ end
+
+ local tabbar_size_change = 0
+ local tabbar_width_change = 0
+ local tabbar_y_change = 0
+ local tabbar_x_change = 0
+ if not tabbar_disable then
+ if tabbar_position == "top" then
+ tabbar_size_change = tabbar_size + tabbar_padding
+ tabbar_y_change = tabbar_size + tabbar_padding
+ elseif tabbar_position == "bottom" then
+ tabbar_size_change = tabbar_size + tabbar_padding
+ elseif tabbar_position == "left" then
+ tabbar_width_change = tabbar_size + tabbar_padding
+ tabbar_x_change = tabbar_size + tabbar_padding
+ elseif tabbar_position == "right" then
+ tabbar_width_change = tabbar_size + tabbar_padding
+ end
+ end
+
+ -- Iterate through slaves
+ -- (also creates a list of all slave clients for update_tabbar)
+ local slave_clients = {}
+ for idx = 1, nslaves do
+ local c = p.clients[idx + nmaster]
+ slave_clients[#slave_clients + 1] = c
+ if c == client.focus then
+ t.top_idx = #slave_clients
+ end
+ local g = {
+ x = area.x + master_area_width + tabbar_x_change,
+ y = area.y + tabbar_y_change,
+ width = slave_area_width - tabbar_width_change,
+ height = area.height - tabbar_size_change,
+ }
+ if not dont_resize_slaves and idx ~= t.top_idx then
+ g = {
+ x = area.x + master_area_width + slave_area_width / 4,
+ y = area.y + tabbar_size + area.height / 4,
+ width = slave_area_width / 2,
+ height = area.height / 4 - tabbar_size,
+ }
+ end
+ p.geometries[c] = g
+ end
+
+ if not tabbar_disable then
+ update_tabbar(
+ slave_clients,
+ t,
+ t.top_idx,
+ area,
+ master_area_width,
+ slave_area_width
+ )
+ end
+end
+
+return mylayout
diff --git a/.config/awesome/extras/bling/layout/vertical.lua b/.config/awesome/extras/bling/layout/vertical.lua
new file mode 100755
index 0000000..8b6811e
--- /dev/null
+++ b/.config/awesome/extras/bling/layout/vertical.lua
@@ -0,0 +1,56 @@
+local math = math
+
+local mylayout = {}
+
+mylayout.name = "vertical"
+
+function mylayout.arrange(p)
+ local area = p.workarea
+ local t = p.tag or screen[p.screen].selected_tag
+ local mwfact = t.master_width_factor
+ local nmaster = math.min(t.master_count, #p.clients)
+ local nslaves = #p.clients - nmaster
+
+ local master_area_width = area.width * mwfact
+ local slave_area_width = area.width - master_area_width
+
+ -- Special case: no slaves
+ if nslaves == 0 then
+ master_area_width = area.width
+ slave_area_width = 0
+ end
+
+ -- Special case: no masters
+ if nmaster == 0 then
+ master_area_width = 0
+ slave_area_width = area.width
+ end
+
+ -- iterate through masters
+ for idx = 1, nmaster do
+ local c = p.clients[idx]
+ local g = {
+ x = area.x,
+ y = area.y + (idx - 1) * (area.height / nmaster),
+ width = master_area_width,
+ height = area.height / nmaster,
+ }
+ p.geometries[c] = g
+ end
+
+ -- itearte through slaves
+ for idx = 1, nslaves do
+ local c = p.clients[idx + nmaster]
+ local g = {
+ x = area.x
+ + master_area_width
+ + (idx - 1) * (slave_area_width / nslaves),
+ y = area.y,
+ width = slave_area_width / nslaves,
+ height = area.height,
+ }
+ p.geometries[c] = g
+ end
+end
+
+return mylayout
diff --git a/.config/awesome/extras/bling/module/flash_focus.lua b/.config/awesome/extras/bling/module/flash_focus.lua
new file mode 100755
index 0000000..2fe3f47
--- /dev/null
+++ b/.config/awesome/extras/bling/module/flash_focus.lua
@@ -0,0 +1,44 @@
+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 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
+
+ -- Bring the focused client to the top
+ if c then
+ c:raise()
+ 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 }
diff --git a/.config/awesome/extras/bling/module/init.lua b/.config/awesome/extras/bling/module/init.lua
new file mode 100755
index 0000000..ed127f6
--- /dev/null
+++ b/.config/awesome/extras/bling/module/init.lua
@@ -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"),
+}
diff --git a/.config/awesome/extras/bling/module/scratchpad.lua b/.config/awesome/extras/bling/module/scratchpad.lua
new file mode 100755
index 0000000..6f1bf8b
--- /dev/null
+++ b/.config/awesome/extras/bling/module/scratchpad.lua
@@ -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)
+ self.client.disconnect_signal("manage", inital_apply)
+ end
+ end
+ self.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)
diff --git a/.config/awesome/extras/bling/module/tabbed.lua b/.config/awesome/extras/bling/module/tabbed.lua
new file mode 100755
index 0000000..c53ec03
--- /dev/null
+++ b/.config/awesome/extras/bling/module/tabbed.lua
@@ -0,0 +1,274 @@
+--[[
+
+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)
+ 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
+
+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
diff --git a/.config/awesome/extras/bling/module/tiled_wallpaper.lua b/.config/awesome/extras/bling/module/tiled_wallpaper.lua
new file mode 100755
index 0000000..75014cf
--- /dev/null
+++ b/.config/awesome/extras/bling/module/tiled_wallpaper.lua
@@ -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
diff --git a/.config/awesome/extras/bling/module/wallpaper.lua b/.config/awesome/extras/bling/module/wallpaper.lua
new file mode 100755
index 0000000..b8344ff
--- /dev/null
+++ b/.config/awesome/extras/bling/module/wallpaper.lua
@@ -0,0 +1,339 @@
+---------------------------------------------------------------------------
+-- 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()
+ gears.wallpaper.centered(
+ wallpaper_object,
+ args.screen,
+ args.background,
+ args.scale
+ )
+ end,
+ ["tiled"] = function()
+ gears.wallpaper.tiled(wallpaper_object, args.screen, args.offset)
+ end,
+ ["maximized"] = function()
+ gears.wallpaper.maximized(
+ wallpaper_object,
+ args.screen,
+ args.ignore_aspect,
+ args.offset
+ )
+ end,
+ ["fit"] = function()
+ gears.wallpaper.fit(wallpaper_object, args.screen, args.background)
+ end,
+ }
+ 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"
+ 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
+ positions[args.position]()
+ else
+ gears.wallpaper.set(wallpaper_object)
+ 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
+ apply(wallpapers[simple_index], args)
+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)
+ apply(wallpapers[math.random(#wallpapers)], args)
+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)
+ config.screen = s or config.screen
+ 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" 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,
+}
diff --git a/.config/awesome/extras/bling/module/window_swallowing.lua b/.config/awesome/extras/bling/module/window_swallowing.lua
new file mode 100755
index 0000000..368541b
--- /dev/null
+++ b/.config/awesome/extras/bling/module/window_swallowing.lua
@@ -0,0 +1,88 @@
+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 dont_swallow_classname_list = beautiful.dont_swallow_classname_list
+ or { "firefox", "Gimp", "Google-chrome" }
+local activate_dont_swallow_filter = beautiful.dont_swallow_filter_activated
+ or true
+
+-- checks if client classname matches with any entry of the dont-swallow-list
+local function check_if_swallow(c)
+ if not activate_dont_swallow_filter then
+ return true
+ end
+ for _, classname in ipairs(dont_swallow_classname_list) do
+ if classname == c.class then
+ return false
+ end
+ end
+ return true
+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
+ end
+
+ -- io.popen is normally discouraged. Should probably be changed
+ local handle = io.popen(
+ [[pstree -T -p -a -s ]]
+ .. tostring(c.pid)
+ .. [[ | sed '2q;d' | grep -o '[0-9]*$' | tr -d '\n']]
+ )
+ local parent_pid = handle:read("*a")
+ handle:close()
+
+ if
+ (tostring(parent_pid) == tostring(parent_client.pid))
+ and check_if_swallow(c)
+ then
+ c:connect_signal("unmanage", function()
+ helpers.client.turn_on(parent_client)
+ helpers.client.sync(parent_client, c)
+ end)
+
+ helpers.client.sync(c, parent_client)
+ helpers.client.turn_off(parent_client)
+ 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,
+}
diff --git a/.config/awesome/extras/bling/signal/init.lua b/.config/awesome/extras/bling/signal/init.lua
new file mode 100755
index 0000000..a953d59
--- /dev/null
+++ b/.config/awesome/extras/bling/signal/init.lua
@@ -0,0 +1 @@
+return { playerctl = require(... .. ".playerctl") }
diff --git a/.config/awesome/extras/bling/signal/playerctl/init.lua b/.config/awesome/extras/bling/signal/playerctl/init.lua
new file mode 100755
index 0000000..1d586ed
--- /dev/null
+++ b/.config/awesome/extras/bling/signal/playerctl/init.lua
@@ -0,0 +1,19 @@
+local beautiful = require("beautiful")
+
+-- Use CLI backend as default as it is supported on most if not all systems
+local backend_config = beautiful.playerctl_backend or "playerctl_cli"
+local backends = {
+ playerctl_cli = require(... .. ".playerctl_cli"),
+ playerctl_lib = require(... .. ".playerctl_lib"),
+}
+
+local function enable_wrapper(args)
+ backend_config = (args and args.backend) or backend_config
+ backends[backend_config].enable(args)
+end
+
+local function disable_wrapper()
+ backends[backend_config].disable()
+end
+
+return { enable = enable_wrapper, disable = disable_wrapper }
diff --git a/.config/awesome/extras/bling/signal/playerctl/playerctl_cli.lua b/.config/awesome/extras/bling/signal/playerctl/playerctl_cli.lua
new file mode 100755
index 0000000..93959f7
--- /dev/null
+++ b/.config/awesome/extras/bling/signal/playerctl/playerctl_cli.lua
@@ -0,0 +1,151 @@
+--
+-- Provides:
+-- bling::playerctl::status
+-- playing (boolean)
+-- bling::playerctl::title_artist_album
+-- title (string)
+-- artist (string)
+-- album_path (string)
+-- bling::playerctl::position
+-- interval_sec (number)
+-- length_sec (number)
+-- bling::playerctl::no_players
+--
+local awful = require("awful")
+local beautiful = require("beautiful")
+
+local interval = beautiful.playerctl_position_update_interval or 1
+
+local function emit_player_status()
+ local status_cmd = "playerctl status -F"
+
+ -- Follow status
+ awful.spawn.easy_async({
+ "pkill",
+ "--full",
+ "--uid",
+ os.getenv("USER"),
+ "^playerctl status",
+ }, function()
+ awful.spawn.with_line_callback(status_cmd, {
+ stdout = function(line)
+ local playing = false
+ if line:find("Playing") then
+ playing = true
+ else
+ playing = false
+ end
+ awesome.emit_signal("bling::playerctl::status", playing)
+ end,
+ })
+ collectgarbage("collect")
+ end)
+end
+
+local function emit_player_info()
+ local art_script = [[
+sh -c '
+
+tmp_dir="$XDG_CACHE_HOME/awesome/"
+
+if [ -z ${XDG_CACHE_HOME} ]; then
+ tmp_dir="$HOME/.cache/awesome/"
+fi
+
+tmp_cover_path=${tmp_dir}"cover.png"
+
+if [ ! -d $tmp_dir ]; then
+ mkdir -p $tmp_dir
+fi
+
+link="$(playerctl metadata mpris:artUrl)"
+
+curl -s "$link" --output $tmp_cover_path
+
+echo "$tmp_cover_path"
+']]
+
+ -- Command that lists artist and title in a format to find and follow
+ local song_follow_cmd =
+ "playerctl metadata --format 'artist_{{artist}}title_{{title}}' -F"
+
+ -- Progress Cmds
+ local prog_cmd = "playerctl position"
+ local length_cmd = "playerctl metadata mpris:length"
+
+ awful.widget.watch(prog_cmd, interval, function(_, interval)
+ awful.spawn.easy_async_with_shell(length_cmd, function(length)
+ local length_sec = tonumber(length) -- in microseconds
+ local interval_sec = tonumber(interval) -- in seconds
+ if length_sec and interval_sec then
+ if interval_sec >= 0 and length_sec > 0 then
+ awesome.emit_signal(
+ "bling::playerctl::position",
+ interval_sec,
+ length_sec / 1000000
+ )
+ end
+ end
+ end)
+ collectgarbage("collect")
+ end)
+
+ -- Follow title
+ awful.spawn.easy_async({
+ "pkill",
+ "--full",
+ "--uid",
+ os.getenv("USER"),
+ "^playerctl metadata",
+ }, function()
+ awful.spawn.with_line_callback(song_follow_cmd, {
+ stdout = function(line)
+ local album_path = ""
+ awful.spawn.easy_async_with_shell(art_script, function(out)
+ -- Get album path
+ album_path = out:gsub("%\n", "")
+ -- Get title and artist
+ local artist = line:match("artist_(.*)title_")
+ local title = line:match("title_(.*)")
+ -- If the title is nil or empty then the players stopped
+ if title and title ~= "" then
+ awesome.emit_signal(
+ "bling::playerctl::title_artist_album",
+ title,
+ artist,
+ album_path
+ )
+ else
+ awesome.emit_signal("bling::playerctl::no_players")
+ end
+ end)
+ collectgarbage("collect")
+ end,
+ })
+ collectgarbage("collect")
+ end)
+end
+
+-- Emit info
+-- emit_player_status()
+-- emit_player_info()
+
+local enable = function(args)
+ interval = (args and args.interval) or interval
+ emit_player_status()
+ emit_player_info()
+end
+
+local disable = function()
+ awful.spawn.with_shell(
+ "pkill --full --uid " .. os.getenv("USER") .. " '^playerctl status -F'"
+ )
+
+ awful.spawn.with_shell(
+ "pkill --full --uid "
+ .. os.getenv("USER")
+ .. " '^playerctl metadata --format'"
+ )
+end
+
+return { enable = enable, disable = disable }
diff --git a/.config/awesome/extras/bling/signal/playerctl/playerctl_lib.lua b/.config/awesome/extras/bling/signal/playerctl/playerctl_lib.lua
new file mode 100755
index 0000000..5e67f1f
--- /dev/null
+++ b/.config/awesome/extras/bling/signal/playerctl/playerctl_lib.lua
@@ -0,0 +1,350 @@
+-- Playerctl signals
+--
+-- Provides:
+-- bling::playerctl::status
+-- playing (boolean)
+-- player_name (string)
+-- bling::playerctl::title_artist_album
+-- title (string)
+-- artist (string)
+-- album_path (string)
+-- player_name (string)
+-- bling::playerctl::position
+-- interval_sec (number)
+-- length_sec (number)
+-- player_name (string)
+-- bling::playerctl::no_players
+-- (No parameters)
+
+local gears = require("gears")
+local awful = require("awful")
+local beautiful = require("beautiful")
+local Playerctl = nil
+
+local manager = nil
+local metadata_timer = nil
+local position_timer = nil
+
+local ignore = {}
+local priority = {}
+local update_on_activity = true
+local interval = 1
+
+-- Track position callback
+local last_position = -1
+local last_length = -1
+local function position_cb()
+ local player = manager.players[1]
+ if player then
+ local position = player:get_position() / 1000000
+ local length = (player.metadata.value["mpris:length"] or 0) / 1000000
+ if position ~= last_position or length ~= last_length then
+ awesome.emit_signal(
+ "bling::playerctl::position",
+ position,
+ length,
+ player.player_name
+ )
+ last_position = position
+ last_length = length
+ end
+ end
+end
+
+local function get_album_art(url)
+ return awful.util.shell
+ .. [[ -c '
+
+tmp_dir="$XDG_CACHE_HOME/awesome/"
+
+if [ -z "$XDG_CACHE_HOME" ]; then
+ tmp_dir="$HOME/.cache/awesome/"
+fi
+
+tmp_cover_path="${tmp_dir}cover.png"
+
+if [ ! -d "$tmp_dir" ]; then
+ mkdir -p $tmp_dir
+fi
+
+curl -s ']]
+ .. url
+ .. [[' --output $tmp_cover_path
+
+echo "$tmp_cover_path"
+']]
+end
+
+-- Metadata callback for title, artist, and album art
+local last_player = nil
+local last_title = ""
+local last_artist = ""
+local last_artUrl = ""
+local function metadata_cb(player, metadata)
+ if update_on_activity then
+ manager:move_player_to_top(player)
+ end
+
+ local data = metadata.value
+
+ local title = data["xesam:title"] or ""
+ local artist = data["xesam:artist"][1] or ""
+ for i = 2, #data["xesam:artist"] do
+ artist = artist .. ", " .. data["xesam:artist"][i]
+ end
+ local artUrl = data["mpris:artUrl"] or ""
+ -- Spotify client doesn't report its art URL's correctly...
+ if player.player_name == "spotify" then
+ artUrl = artUrl:gsub("open.spotify.com", "i.scdn.co")
+ end
+
+ if player == manager.players[1] then
+ -- Callback can be called even though values we care about haven't
+ -- changed, so check to see if they have
+ if
+ player ~= last_player
+ or title ~= last_title
+ or artist ~= last_artist
+ or artUrl ~= last_artUrl
+ then
+ if title == "" and artist == "" and artUrl == "" then
+ return
+ end
+
+ if metadata_timer ~= nil then
+ if metadata_timer.started then
+ metadata_timer:stop()
+ end
+ end
+
+ metadata_timer = gears.timer({
+ timeout = 0.3,
+ autostart = true,
+ single_shot = true,
+ callback = function()
+ if artUrl ~= "" then
+ awful.spawn.with_line_callback(get_album_art(artUrl), {
+ stdout = function(line)
+ awesome.emit_signal(
+ "bling::playerctl::title_artist_album",
+ title,
+ artist,
+ line,
+ player.player_name
+ )
+ end,
+ })
+ else
+ awesome.emit_signal(
+ "bling::playerctl::title_artist_album",
+ title,
+ artist,
+ "",
+ player.player_name
+ )
+ end
+ end,
+ })
+
+ -- Re-sync with position timer when track changes
+ position_timer:again()
+ last_player = player
+ last_title = title
+ last_artist = artist
+ last_artUrl = artUrl
+ end
+ end
+end
+
+-- Playback status callback
+-- Reported as PLAYING, PAUSED, or STOPPED
+local function playback_status_cb(player, status)
+ if update_on_activity then
+ manager:move_player_to_top(player)
+ end
+
+ if player == manager.players[1] then
+ if status == "PLAYING" then
+ awesome.emit_signal(
+ "bling::playerctl::status",
+ true,
+ player.player_name
+ )
+ else
+ awesome.emit_signal(
+ "bling::playerctl::status",
+ false,
+ player.player_name
+ )
+ end
+ end
+end
+
+-- Determine if player should be managed
+local function name_is_selected(name)
+ if ignore[name.name] then
+ return false
+ end
+
+ if #priority > 0 then
+ for _, arg in pairs(priority) do
+ if arg == name.name or arg == "%any" then
+ return true
+ end
+ end
+ return false
+ end
+
+ return true
+end
+
+-- Create new player and connect it to callbacks
+local function init_player(name)
+ if name_is_selected(name) then
+ local player = Playerctl.Player.new_from_name(name)
+ manager:manage_player(player)
+ player.on_playback_status = playback_status_cb
+ player.on_metadata = metadata_cb
+
+ -- Start position timer if its not already running
+ if not position_timer.started then
+ position_timer:again()
+ end
+ end
+end
+
+-- Determine if a player name comes before or after another according to the
+-- priority order
+local function player_compare_name(name_a, name_b)
+ local any_index = math.huge
+ local a_match_index = nil
+ local b_match_index = nil
+
+ if name_a == name_b then
+ return 0
+ end
+
+ for index, name in ipairs(priority) do
+ if name == "%any" then
+ any_index = (any_index == math.huge) and index or any_index
+ elseif name == name_a then
+ a_match_index = a_match_index or index
+ elseif name == name_b then
+ b_match_index = b_match_index or index
+ end
+ end
+
+ if not a_match_index and not b_match_index then
+ return 0
+ elseif not a_match_index then
+ return (b_match_index < any_index) and 1 or -1
+ elseif not b_match_index then
+ return (a_match_index < any_index) and -1 or 1
+ elseif a_match_index == b_match_index then
+ return 0
+ else
+ return (a_match_index < b_match_index) and -1 or 1
+ end
+end
+
+-- Sorting function used by manager if a priority order is specified
+local function player_compare(a, b)
+ local player_a = Playerctl.Player(a)
+ local player_b = Playerctl.Player(b)
+ return player_compare_name(player_a.player_name, player_b.player_name)
+end
+
+local function start_manager()
+ manager = Playerctl.PlayerManager()
+ if #priority > 0 then
+ manager:set_sort_func(player_compare)
+ end
+
+ -- Timer to update track position at specified interval
+ position_timer = gears.timer({
+ timeout = interval,
+ callback = position_cb,
+ })
+
+ -- Manage existing players on startup
+ for _, name in ipairs(manager.player_names) do
+ init_player(name)
+ end
+
+ -- Callback to manage new players
+ function manager:on_name_appeared(name)
+ init_player(name)
+ end
+
+ -- Callback to check if all players have exited
+ function manager:on_name_vanished(name)
+ if #manager.players == 0 then
+ metadata_timer:stop()
+ position_timer:stop()
+ awesome.emit_signal("bling::playerctl::no_players")
+ end
+ end
+end
+
+-- Parse arguments
+local function parse_args(args)
+ if args then
+ update_on_activity = args.update_on_activity or update_on_activity
+ interval = args.interval or interval
+
+ if type(args.ignore) == "string" then
+ ignore[args.ignore] = true
+ elseif type(args.ignore) == "table" then
+ for _, name in pairs(args.ignore) do
+ ignore[name] = true
+ end
+ end
+
+ if type(args.player) == "string" then
+ priority[1] = args.player
+ elseif type(args.player) == "table" then
+ priority = args.player
+ end
+ end
+end
+
+local function playerctl_enable(args)
+ args = args or {}
+ -- Grab settings from beautiful variables if not set explicitly
+ args.ignore = args.ignore or beautiful.playerctl_ignore
+ args.player = args.player or beautiful.playerctl_player
+ args.update_on_activity = args.update_on_activity
+ or beautiful.playerctl_update_on_activity
+ args.interval = args.interval
+ or beautiful.playerctl_position_update_interval
+ parse_args(args)
+
+ -- Grab playerctl library
+ Playerctl = require("lgi").Playerctl
+
+ -- Ensure main event loop has started before starting player manager
+ gears.timer.delayed_call(start_manager)
+end
+
+local function playerctl_disable()
+ -- Remove manager and timer
+ manager = nil
+ metadata_timer:stop()
+ metadata_timer = nil
+ position_timer:stop()
+ position_timer = nil
+ -- Restore default settings
+ ignore = {}
+ priority = {}
+ update_on_activity = true
+ interval = 1
+ -- Reset default values
+ last_position = -1
+ last_length = -1
+ last_player = nil
+ last_title = ""
+ last_artist = ""
+ last_artUrl = ""
+end
+
+return { enable = playerctl_enable, disable = playerctl_disable }
diff --git a/.config/awesome/extras/bling/widget/app_launcher/init.lua b/.config/awesome/extras/bling/widget/app_launcher/init.lua
new file mode 100755
index 0000000..7eeaf17
--- /dev/null
+++ b/.config/awesome/extras/bling/widget/app_launcher/init.lua
@@ -0,0 +1,1053 @@
+local Gio = require("lgi").Gio
+local awful = require("awful")
+local gobject = require("gears.object")
+local gtable = require("gears.table")
+local gtimer = require("gears.timer")
+local gfilesystem = require("gears.filesystem")
+local wibox = require("wibox")
+local beautiful = require("beautiful")
+local color = require(tostring(...):match(".*bling") .. ".helpers.color")
+local prompt = require(... .. ".prompt")
+local dpi = beautiful.xresources.apply_dpi
+local string = string
+local table = table
+local math = math
+local ipairs = ipairs
+local pairs = pairs
+local root = root
+local capi = { screen = screen, mouse = mouse }
+local path = ...
+
+local app_launcher = { mt = {} }
+
+local terminal_commands_lookup =
+{
+ alacritty = "alacritty -e",
+ termite = "termite -e",
+ rxvt = "rxvt -e",
+ terminator = "terminator -e"
+}
+
+local function string_levenshtein(str1, str2)
+ local len1 = string.len(str1)
+ local len2 = string.len(str2)
+ local matrix = {}
+ local cost = 0
+
+ -- quick cut-offs to save time
+ if (len1 == 0) then
+ return len2
+ elseif (len2 == 0) then
+ return len1
+ elseif (str1 == str2) then
+ return 0
+ end
+
+ -- initialise the base matrix values
+ for i = 0, len1, 1 do
+ matrix[i] = {}
+ matrix[i][0] = i
+ end
+ for j = 0, len2, 1 do
+ matrix[0][j] = j
+ end
+
+ -- actual Levenshtein algorithm
+ for i = 1, len1, 1 do
+ for j = 1, len2, 1 do
+ if (str1:byte(i) == str2:byte(j)) then
+ cost = 0
+ else
+ cost = 1
+ end
+
+ matrix[i][j] = math.min(matrix[i-1][j] + 1, matrix[i][j-1] + 1, matrix[i-1][j-1] + cost)
+ end
+ end
+
+ -- return the last value - this is the Levenshtein distance
+ return matrix[len1][len2]
+end
+
+local function case_insensitive_pattern(pattern)
+ -- find an optional '%' (group 1) followed by any character (group 2)
+ local p = pattern:gsub("(%%?)(.)", function(percent, letter)
+ if percent ~= "" or not letter:match("%a") then
+ -- if the '%' matched, or `letter` is not a letter, return "as is"
+ return percent .. letter
+ else
+ -- else, return a case-insensitive character class of the matched letter
+ return string.format("[%s%s]", letter:lower(), letter:upper())
+ end
+ end)
+
+ return p
+end
+
+local function has_value(tab, val)
+ for index, value in pairs(tab) do
+ if val:find(case_insensitive_pattern(value)) then
+ return true
+ end
+ end
+ return false
+end
+
+local function select_app(self, x, y)
+ local widgets = self._private.grid:get_widgets_at(x, y)
+ if widgets then
+ self._private.active_widget = widgets[1]
+ if self._private.active_widget ~= nil then
+ self._private.active_widget.selected = true
+ self._private.active_widget:get_children_by_id("background")[1].bg = self.app_selected_color
+ local name_widget = self._private.active_widget:get_children_by_id("name")[1]
+ if name_widget then
+ name_widget.markup = string.format("%s", self.app_name_selected_color, name_widget.text)
+ end
+ local generic_name_widget = self._private.active_widget:get_children_by_id("generic_name")[1]
+ if generic_name_widget then
+ generic_name_widget.markup = string.format("%s", self.app_name_selected_color, generic_name_widget.text)
+ end
+ end
+ end
+end
+
+local function unselect_app(self)
+ if self._private.active_widget ~= nil then
+ self._private.active_widget.selected = false
+ self._private.active_widget:get_children_by_id("background")[1].bg = self.app_normal_color
+ local name_widget = self._private.active_widget:get_children_by_id("name")[1]
+ if name_widget then
+ name_widget.markup = string.format("%s", self.app_name_normal_color, name_widget.text)
+ end
+ local generic_name_widget = self._private.active_widget:get_children_by_id("generic_name")[1]
+ if generic_name_widget then
+ generic_name_widget.markup = string.format("%s", self.app_name_normal_color, generic_name_widget.text)
+ end
+ self._private.active_widget = nil
+ end
+end
+
+local function create_app_widget(self, entry)
+ local icon = self.app_show_icon == true and
+ {
+ widget = wibox.widget.imagebox,
+ halign = self.app_icon_halign,
+ forced_width = self.app_icon_width,
+ forced_height = self.app_icon_height,
+ image = entry.icon
+ } or nil
+
+ local name = self.app_show_name == true and
+ {
+ widget = wibox.widget.textbox,
+ id = "name",
+ font = self.app_name_font,
+ markup = entry.name
+ } or nil
+
+ local generic_name = entry.generic_name ~= nil and self.app_show_generic_name == true and
+ {
+ widget = wibox.widget.textbox,
+ id = "generic_name",
+ font = self.app_name_font,
+ markup = entry.generic_name ~= "" and " (" .. entry.generic_name .. ")" or ""
+ } or nil
+
+ local app = wibox.widget
+ {
+ widget = wibox.container.background,
+ id = "background",
+ forced_width = self.app_width,
+ forced_height = self.app_height,
+ shape = self.app_shape,
+ bg = self.app_normal_color,
+ {
+ widget = wibox.container.margin,
+ margins = self.app_content_padding,
+ {
+ -- Using this hack instead of container.place because that will fuck with the name/icon halign
+ layout = wibox.layout.align.vertical,
+ expand = "outside",
+ nil,
+ {
+ layout = wibox.layout.fixed.vertical,
+ spacing = self.app_content_spacing,
+ icon,
+ {
+ widget = wibox.container.place,
+ halign = self.app_name_halign,
+ {
+ layout = wibox.layout.fixed.horizontal,
+ spacing = self.app_name_generic_name_spacing,
+ name,
+ generic_name
+ }
+ }
+ },
+ nil
+ }
+ }
+ }
+
+ function app.spawn()
+ if entry.terminal == true then
+ if self.terminal ~= nil then
+ local terminal_command = terminal_commands_lookup[self.terminal] or self.terminal
+ awful.spawn(terminal_command .. " " .. entry.executable)
+ else
+ awful.spawn.easy_async("gtk-launch " .. entry.executable, function(stdout, stderr)
+ if stderr then
+ awful.spawn(entry.executable)
+ end
+ end)
+ end
+ else
+ awful.spawn(entry.executable)
+ end
+
+ if self.hide_on_launch then
+ self:hide()
+ end
+ end
+
+ app:connect_signal("mouse::enter", function(_self)
+ local widget = capi.mouse.current_wibox
+ if widget then
+ widget.cursor = "hand2"
+ end
+
+ local app = _self
+ if app.selected then
+ app:get_children_by_id("background")[1].bg = self.app_selected_hover_color
+ else
+ local is_opaque = color.is_opaque(self.app_normal_color)
+ local is_dark = color.is_dark(self.app_normal_color)
+ local app_normal_color = color.hex_to_rgba(self.app_normal_color)
+ local hover_color = (is_dark or is_opaque) and
+ color.rgba_to_hex(color.multiply(app_normal_color, 2.5)) or
+ color.rgba_to_hex(color.multiply(app_normal_color, 0.5))
+ app:get_children_by_id("background")[1].bg = self.app_normal_hover_color
+ end
+ end)
+
+ app:connect_signal("mouse::leave", function(_self)
+ local widget = capi.mouse.current_wibox
+ if widget then
+ widget.cursor = "left_ptr"
+ end
+
+ local app = _self
+ if app.selected then
+ app:get_children_by_id("background")[1].bg = self.app_selected_color
+ else
+ app:get_children_by_id("background")[1].bg = self.app_normal_color
+ end
+ end)
+
+ app:connect_signal("button::press", function(_self, lx, ly, button, mods, find_widgets_result)
+ if button == 1 then
+ local app = _self
+ if self._private.active_widget == app or not self.select_before_spawn then
+ app.spawn()
+ else
+ -- Unmark the previous app
+ unselect_app(self)
+
+ -- Mark this app
+ local pos = self._private.grid:get_widget_position(app)
+ select_app(self, pos.row, pos.col)
+ end
+ end
+ end)
+
+ return app
+end
+
+local function search(self, text)
+ unselect_app(self)
+
+ local pos = self._private.grid:get_widget_position(self._private.active_widget)
+
+ -- Reset all the matched entries
+ self._private.matched_entries = {}
+ -- Remove all the grid widgets
+ self._private.grid:reset()
+
+ if text == "" then
+ self._private.matched_entries = self._private.all_entries
+ else
+ for index, entry in pairs(self._private.all_entries) do
+ text = text:gsub( "%W", "" )
+
+ -- Check if there's a match by the app name or app command
+ if string.find(entry.name, case_insensitive_pattern(text)) ~= nil or
+ self.search_commands and string.find(entry.commandline, case_insensitive_pattern(text)) ~= nil
+ then
+ table.insert(self._private.matched_entries, {
+ name = entry.name,
+ generic_name = entry.generic_name,
+ commandline = entry.commandline,
+ executable = entry.executable,
+ terminal = entry.terminal,
+ icon = entry.icon
+ })
+ end
+ end
+
+ -- Sort by string similarity
+ table.sort(self._private.matched_entries, function(a, b)
+ return string_levenshtein(text, a.name) < string_levenshtein(text, b.name)
+ end)
+ end
+ for index, entry in pairs(self._private.matched_entries) do
+ -- Only add the widgets for apps that are part of the first page
+ if #self._private.grid.children + 1 <= self._private.max_apps_per_page then
+ self._private.grid:add(create_app_widget(self, entry))
+ end
+ end
+
+ -- Recalculate the apps per page based on the current matched entries
+ self._private.apps_per_page = math.min(#self._private.matched_entries, self._private.max_apps_per_page)
+
+ -- Recalculate the pages count based on the current apps per page
+ self._private.pages_count = math.ceil(math.max(1, #self._private.matched_entries) / math.max(1, self._private.apps_per_page))
+
+ -- Page should be 1 after a search
+ self._private.current_page = 1
+
+ -- This is an option to mimic rofi behaviour where after a search
+ -- it will reselect the app whose index is the same as the app index that was previously selected
+ -- and if matched_entries.length < current_index it will instead select the app with the greatest index
+ if self.try_to_keep_index_after_searching then
+ if self._private.grid:get_widgets_at(pos.row, pos.col) == nil then
+ local app = self._private.grid.children[#self._private.grid.children]
+ pos = self._private.grid:get_widget_position(app)
+ end
+ select_app(self, pos.row, pos.col)
+ -- Otherwise select the first app on the list
+ else
+ select_app(self, 1, 1)
+ end
+end
+
+local function page_backward(self, direction)
+ if self._private.current_page > 1 then
+ self._private.current_page = self._private.current_page - 1
+ elseif self.wrap_page_scrolling and #self._private.matched_entries >= self._private.max_apps_per_page then
+ self._private.current_page = self._private.pages_count
+ elseif self.wrap_app_scrolling then
+ local rows, columns = self._private.grid:get_dimension()
+ unselect_app(self)
+ select_app(self, math.min(rows, #self._private.grid.children % self.apps_per_row), columns)
+ return
+ else
+ return
+ end
+
+ local pos = self._private.grid:get_widget_position(self._private.active_widget)
+
+ -- Remove the current page apps from the grid
+ self._private.grid:reset()
+
+ local max_app_index_to_include = self._private.apps_per_page * self._private.current_page
+ local min_app_index_to_include = max_app_index_to_include - self._private.apps_per_page
+
+ for index, entry in pairs(self._private.matched_entries) do
+ -- Only add widgets that are between this range (part of the current page)
+ if index > min_app_index_to_include and index <= max_app_index_to_include then
+ self._private.grid:add(create_app_widget(self, entry))
+ end
+ end
+
+ local rows, columns = self._private.grid:get_dimension()
+ if self._private.current_page < self._private.pages_count then
+ if direction == "up" then
+ select_app(self, rows, columns)
+ else
+ -- Keep the same row from last page
+ select_app(self, pos.row, columns)
+ end
+ elseif self.wrap_page_scrolling then
+ if direction == "up" then
+ select_app(self, math.min(rows, #self._private.grid.children % self.apps_per_row), columns)
+ else
+ -- Keep the same row from last page
+ select_app(self, math.min(pos.row, #self._private.grid.children % self.apps_per_row), columns)
+ end
+ end
+end
+
+local function page_forward(self, direction)
+ local min_app_index_to_include = 0
+ local max_app_index_to_include = self._private.apps_per_page
+
+ if self._private.current_page < self._private.pages_count then
+ min_app_index_to_include = self._private.apps_per_page * self._private.current_page
+ self._private.current_page = self._private.current_page + 1
+ max_app_index_to_include = self._private.apps_per_page * self._private.current_page
+ elseif self.wrap_page_scrolling and #self._private.matched_entries >= self._private.max_apps_per_page then
+ self._private.current_page = 1
+ min_app_index_to_include = 0
+ max_app_index_to_include = self._private.apps_per_page
+ elseif self.wrap_app_scrolling then
+ unselect_app(self)
+ select_app(self, 1, 1)
+ return
+ else
+ return
+ end
+
+ local pos = self._private.grid:get_widget_position(self._private.active_widget)
+
+ -- Remove the current page apps from the grid
+ self._private.grid:reset()
+
+ for index, entry in pairs(self._private.matched_entries) do
+ -- Only add widgets that are between this range (part of the current page)
+ if index > min_app_index_to_include and index <= max_app_index_to_include then
+ self._private.grid:add(create_app_widget(self, entry))
+ end
+ end
+
+ if self._private.current_page > 1 or self.wrap_page_scrolling then
+ if direction == "down" then
+ select_app(self, 1, 1)
+ else
+ local last_col_max_row = math.min(pos.row, #self._private.grid.children % self.apps_per_row)
+ if last_col_max_row ~= 0 then
+ select_app(self, last_col_max_row, 1)
+ else
+ select_app(self, pos.row, 1)
+ end
+ end
+ end
+end
+
+local function scroll_up(self)
+ if #self._private.grid.children < 1 then
+ self._private.active_widget = nil
+ return
+ end
+
+ local rows, columns = self._private.grid:get_dimension()
+ local pos = self._private.grid:get_widget_position(self._private.active_widget)
+ local is_bigger_than_first_app = pos.col > 1 or pos.row > 1
+
+ -- Check if the current marked app is not the first
+ if is_bigger_than_first_app then
+ unselect_app(self)
+ if pos.row == 1 then
+ select_app(self, rows, pos.col - 1)
+ else
+ select_app(self, pos.row - 1, pos.col)
+ end
+ else
+ page_backward(self, "up")
+ end
+end
+
+local function scroll_down(self)
+ if #self._private.grid.children < 1 then
+ self._private.active_widget = nil
+ return
+ end
+
+ local rows, columns = self._private.grid:get_dimension()
+ local pos = self._private.grid:get_widget_position(self._private.active_widget)
+ local is_less_than_max_app = self._private.grid:index(self._private.active_widget) < #self._private.grid.children
+
+ -- Check if we can scroll down the app list
+ if is_less_than_max_app then
+ -- Unmark the previous app
+ unselect_app(self)
+ if pos.row == rows then
+ select_app(self, 1, pos.col + 1)
+ else
+ select_app(self, pos.row + 1, pos.col)
+ end
+ else
+ page_forward(self, "down")
+ end
+end
+
+local function scroll_left(self)
+ if #self._private.grid.children < 1 then
+ self._private.active_widget = nil
+ return
+ end
+
+ local pos = self._private.grid:get_widget_position(self._private.active_widget)
+ local is_bigger_than_first_column = pos.col > 1
+
+ -- Check if the current marked app is not the first
+ if is_bigger_than_first_column then
+ unselect_app(self)
+ select_app(self, pos.row, pos.col - 1)
+ else
+ page_backward(self, "left")
+ end
+end
+
+local function scroll_right(self)
+ if #self._private.grid.children < 1 then
+ self._private.active_widget = nil
+ return
+ end
+
+ local rows, columns = self._private.grid:get_dimension()
+ local pos = self._private.grid:get_widget_position(self._private.active_widget)
+ local is_less_than_max_column = pos.col < columns
+ local is_less_than_max_page = self._private.current_page < self._private.pages_count
+
+ -- Check if we can scroll down the app list
+ if is_less_than_max_column then
+ -- Unmark the previous app
+ unselect_app(self)
+
+ -- Scroll up to the max app if there are directly to the right of previous app
+ if self._private.grid:get_widgets_at(pos.row, pos.col + 1) == nil then
+ local app = self._private.grid.children[#self._private.grid.children]
+ pos = self._private.grid:get_widget_position(app)
+ select_app(self, pos.row, pos.col)
+ else
+ select_app(self, pos.row, pos.col + 1)
+ end
+
+ else
+ page_forward(self, "right")
+ end
+end
+
+local function reset(self)
+ self._private.grid:reset()
+ self._private.matched_entries = self._private.all_entries
+ self._private.apps_per_page = self._private.max_apps_per_page
+ self._private.pages_count = math.ceil(#self._private.all_entries / self._private.apps_per_page)
+ self._private.current_page = 1
+
+ for index, entry in pairs(self._private.all_entries) do
+ -- Only add the apps that are part of the first page
+ if index <= self._private.apps_per_page then
+ self._private.grid:add(create_app_widget(self, entry))
+ else
+ break
+ end
+ end
+
+ select_app(self, 1, 1)
+end
+
+local function generate_apps(self)
+ self._private.all_entries = {}
+ self._private.matched_entries = {}
+
+ local app_info = Gio.AppInfo
+ local apps = app_info.get_all()
+ if self.sort_alphabetically then
+ table.sort(apps, function(a, b)
+ local app_a_score = app_info.get_name(a):lower()
+ if has_value(self.favorites, app_info.get_name(a)) then
+ app_a_score = "aaaaaaaaaaa" .. app_a_score
+ end
+ local app_b_score = app_info.get_name(b):lower()
+ if has_value(self.favorites, app_info.get_name(b)) then
+ app_b_score = "aaaaaaaaaaa" .. app_b_score
+ end
+
+ return app_a_score < app_b_score
+ end)
+ elseif self.reverse_sort_alphabetically then
+ table.sort(apps, function(a, b)
+ local app_a_score = app_info.get_name(a):lower()
+ if has_value(self.favorites, app_info.get_name(a)) then
+ app_a_score = "zzzzzzzzzzz" .. app_a_score
+ end
+ local app_b_score = app_info.get_name(b):lower()
+ if has_value(self.favorites, app_info.get_name(b)) then
+ app_b_score = "zzzzzzzzzzz" .. app_b_score
+ end
+
+ return app_a_score > app_b_score
+ end)
+ else
+ table.sort(apps, function(a, b)
+ local app_a_favorite = has_value(self.favorites, app_info.get_name(a))
+ local app_b_favorite = has_value(self.favorites, app_info.get_name(b))
+
+ if app_a_favorite and not app_b_favorite then
+ return true
+ elseif app_b_favorite and not app_a_favorite then
+ return false
+ elseif app_a_favorite and app_b_favorite then
+ return app_info.get_name(a):lower() < app_info.get_name(b):lower()
+ else
+ return false
+ end
+ end)
+ end
+
+ local icon_theme = require(tostring(path):match(".*bling") .. ".helpers.icon_theme")(self.icon_theme, self.icon_size)
+
+ for _, app in ipairs(apps) do
+ if app.should_show(app) then
+ local name = app_info.get_name(app)
+ local commandline = app_info.get_commandline(app)
+ local executable = app_info.get_executable(app)
+ local icon = icon_theme:get_gicon_path(app_info.get_icon(app))
+
+ -- Check if this app should be skipped, depanding on the skip_names / skip_commands table
+ if not has_value(self.skip_names, name) and not has_value(self.skip_commands, commandline) then
+ -- Check if this app should be skipped becuase it's iconless depanding on skip_empty_icons
+ if icon ~= "" or self.skip_empty_icons == false then
+ if icon == "" then
+ if self.default_app_icon_name ~= nil then
+ icon = icon_theme:get_icon_path(self.default_app_icon_name)
+ elseif self.default_app_icon_path ~= nil then
+ icon = self.default_app_icon_path
+ else
+ icon = icon_theme:choose_icon({"application-all", "application", "application-default-icon", "app"})
+ end
+ end
+
+ local desktop_app_info = Gio.DesktopAppInfo.new(app_info.get_id(app))
+ local terminal = Gio.DesktopAppInfo.get_string(desktop_app_info, "Terminal") == "true" and true or false
+ local generic_name = Gio.DesktopAppInfo.get_string(desktop_app_info, "GenericName") or nil
+
+ table.insert(self._private.all_entries, {
+ name = name,
+ generic_name = generic_name,
+ commandline = commandline,
+ executable = executable,
+ terminal = terminal,
+ icon = icon
+ })
+ end
+ end
+ end
+ end
+end
+
+--- Shows the app launcher
+function app_launcher:show()
+ local screen = self.screen
+ if self.show_on_focused_screen then
+ screen = awful.screen.focused()
+ end
+
+ screen.app_launcher = self._private.widget
+ screen.app_launcher.screen = screen
+ self._private.prompt:start()
+
+ local animation = self.rubato
+ if animation ~= nil then
+ if self._private.widget.goal_x == nil then
+ self._private.widget.goal_x = self._private.widget.x
+ end
+ if self._private.widget.goal_y == nil then
+ self._private.widget.goal_y = self._private.widget.y
+ self._private.widget.placement = nil
+ end
+
+ if animation.x then
+ animation.x.ended:unsubscribe()
+ animation.x:set(self._private.widget.goal_x)
+ gtimer {
+ timeout = 0.01,
+ call_now = false,
+ autostart = true,
+ single_shot = true,
+ callback = function()
+ screen.app_launcher.visible = true
+ end
+ }
+ end
+ if animation.y then
+ animation.y.ended:unsubscribe()
+ animation.y:set(self._private.widget.goal_y)
+ gtimer {
+ timeout = 0.01,
+ call_now = false,
+ autostart = true,
+ single_shot = true,
+ callback = function()
+ screen.app_launcher.visible = true
+ end
+ }
+ end
+ else
+ screen.app_launcher.visible = true
+ end
+
+ self:emit_signal("bling::app_launcher::visibility", true)
+end
+
+--- Hides the app launcher
+function app_launcher:hide()
+ local screen = self.screen
+ if self.show_on_focused_screen then
+ screen = awful.screen.focused()
+ end
+
+ if screen.app_launcher == nil or screen.app_launcher.visible == false then
+ return
+ end
+
+ self._private.prompt:stop()
+
+ local animation = self.rubato
+ if animation ~= nil then
+ if animation.x then
+ animation.x:set(animation.x:initial())
+ end
+ if animation.y then
+ animation.y:set(animation.y:initial())
+ end
+
+ local anim_x_duration = (animation.x and animation.x.duration) or 0
+ local anim_y_duration = (animation.y and animation.y.duration) or 0
+ local turn_off_on_anim_x_end = (anim_x_duration >= anim_y_duration) and true or false
+
+ if turn_off_on_anim_x_end then
+ animation.x.ended:subscribe(function()
+ if self.reset_on_hide == true then reset(self) end
+ screen.app_launcher.visible = false
+ screen.app_launcher = nil
+ animation.x.ended:unsubscribe()
+ end)
+ else
+ animation.y.ended:subscribe(function()
+ if self.reset_on_hide == true then reset(self) end
+ screen.app_launcher.visible = false
+ screen.app_launcher = nil
+ animation.y.ended:unsubscribe()
+ end)
+ end
+ else
+ if self.reset_on_hide == true then reset(self) end
+ screen.app_launcher.visible = false
+ screen.app_launcher = nil
+ end
+
+ self:emit_signal("bling::app_launcher::visibility", false)
+end
+
+--- Toggles the app launcher
+function app_launcher:toggle()
+ local screen = self.screen
+ if self.show_on_focused_screen then
+ screen = awful.screen.focused()
+ end
+
+ if screen.app_launcher and screen.app_launcher.visible then
+ self:hide()
+ else
+ self:show()
+ end
+end
+
+-- Returns a new app launcher
+local function new(args)
+ args = args or {}
+
+ args.terminal = args.terminal or nil
+ args.favorites = args.favorites or {}
+ args.search_commands = args.search_commands == nil and true or args.search_commands
+ args.skip_names = args.skip_names or {}
+ args.skip_commands = args.skip_commands or {}
+ args.skip_empty_icons = args.skip_empty_icons ~= nil and args.skip_empty_icons or false
+ args.sort_alphabetically = args.sort_alphabetically == nil and true or args.sort_alphabetically
+ args.reverse_sort_alphabetically = args.reverse_sort_alphabetically ~= nil and args.reverse_sort_alphabetically or false
+ args.select_before_spawn = args.select_before_spawn == nil and true or args.select_before_spawn
+ args.hide_on_left_clicked_outside = args.hide_on_left_clicked_outside == nil and true or args.hide_on_left_clicked_outside
+ args.hide_on_right_clicked_outside = args.hide_on_right_clicked_outside == nil and true or args.hide_on_right_clicked_outside
+ args.hide_on_launch = args.hide_on_launch == nil and true or args.hide_on_launch
+ args.try_to_keep_index_after_searching = args.try_to_keep_index_after_searching ~= nil and args.try_to_keep_index_after_searching or false
+ args.reset_on_hide = args.reset_on_hide == nil and true or args.reset_on_hide
+ args.save_history = args.save_history == nil and true or args.save_history
+ args.wrap_page_scrolling = args.wrap_page_scrolling == nil and true or args.wrap_page_scrolling
+ args.wrap_app_scrolling = args.wrap_app_scrolling == nil and true or args.wrap_app_scrolling
+
+ args.default_app_icon_name = args.default_app_icon_name or nil
+ args.default_app_icon_path = args.default_app_icon_path or nil
+ args.icon_theme = args.icon_theme or nil
+ args.icons_size = args.icons_size or nil
+
+ args.show_on_focused_screen = args.show_on_focused_screen == nil and true or args.show_on_focused_screen
+ args.screen = args.screen or capi.screen.primary
+ args.placement = args.placement or awful.placement.centered
+ args.rubato = args.rubato or nil
+ args.shirnk_width = args.shirnk_width ~= nil and args.shirnk_width or false
+ args.shrink_height = args.shrink_height ~= nil and args.shrink_height or false
+ args.background = args.background or "#000000"
+ args.shape = args.shape or nil
+
+ args.prompt_height = args.prompt_height or dpi(100)
+ args.prompt_margins = args.prompt_margins or dpi(0)
+ args.prompt_paddings = args.prompt_paddings or dpi(30)
+ args.prompt_shape = args.prompt_shape or nil
+ args.prompt_color = args.prompt_color or beautiful.fg_normal or "#FFFFFF"
+ args.prompt_border_width = args.prompt_border_width or beautiful.border_width or dpi(0)
+ args.prompt_border_color = args.prompt_border_color or beautiful.border_color or args.prompt_color
+ args.prompt_text_halign = args.prompt_text_halign or "left"
+ args.prompt_text_valign = args.prompt_text_valign or "center"
+ args.prompt_icon_text_spacing = args.prompt_icon_text_spacing or dpi(10)
+ args.prompt_show_icon = args.prompt_show_icon == nil and true or args.prompt_show_icon
+ args.prompt_icon_font = args.prompt_icon_font or beautiful.font
+ args.prompt_icon_color = args.prompt_icon_color or beautiful.bg_normal or "#000000"
+ args.prompt_icon = args.prompt_icon or ""
+ args.prompt_icon_markup = args.prompt_icon_markup or string.format("%s", args.prompt_icon_color, args.prompt_icon)
+ args.prompt_text = args.prompt_text or "Search: "
+ args.prompt_start_text = args.prompt_start_text or ""
+ args.prompt_font = args.prompt_font or beautiful.font
+ args.prompt_text_color = args.prompt_text_color or beautiful.bg_normal or "#000000"
+ args.prompt_cursor_color = args.prompt_cursor_color or beautiful.bg_normal or "#000000"
+
+ args.apps_per_row = args.apps_per_row or 5
+ args.apps_per_column = args.apps_per_column or 3
+ args.apps_margin = args.apps_margin or dpi(30)
+ args.apps_spacing = args.apps_spacing or dpi(30)
+
+ args.expand_apps = args.expand_apps == nil and true or args.expand_apps
+ args.app_width = args.app_width or dpi(300)
+ args.app_height = args.app_height or dpi(120)
+ args.app_shape = args.app_shape or nil
+ args.app_normal_color = args.app_normal_color or beautiful.bg_normal or "#000000"
+ args.app_normal_hover_color = args.app_normal_hover_color or (color.is_dark(args.app_normal_color) or color.is_opaque(args.app_normal_color)) and
+ color.rgba_to_hex(color.multiply(color.hex_to_rgba(args.app_normal_color), 2.5)) or
+ color.rgba_to_hex(color.multiply(color.hex_to_rgba(args.app_normal_color), 0.5))
+ args.app_selected_color = args.app_selected_color or beautiful.fg_normal or "#FFFFFF"
+ args.app_selected_hover_color = args.app_selected_hover_color or (color.is_dark(args.app_normal_color) or color.is_opaque(args.app_normal_color)) and
+ color.rgba_to_hex(color.multiply(color.hex_to_rgba(args.app_selected_color), 2.5)) or
+ color.rgba_to_hex(color.multiply(color.hex_to_rgba(args.app_selected_color), 0.5))
+ args.app_content_padding = args.app_content_padding or dpi(10)
+ args.app_content_spacing = args.app_content_spacing or dpi(10)
+ args.app_show_icon = args.app_show_icon == nil and true or args.app_show_icon
+ args.app_icon_halign = args.app_icon_halign or "center"
+ args.app_icon_width = args.app_icon_width or dpi(70)
+ args.app_icon_height = args.app_icon_height or dpi(70)
+ args.app_show_name = args.app_show_name == nil and true or args.app_show_name
+ args.app_name_generic_name_spacing = args.app_name_generic_name_spacing or dpi(0)
+ args.app_name_halign = args.app_name_halign or "center"
+ args.app_name_font = args.app_name_font or beautiful.font
+ args.app_name_normal_color = args.app_name_normal_color or beautiful.fg_normal or "#FFFFFF"
+ args.app_name_selected_color = args.app_name_selected_color or beautiful.bg_normal or "#000000"
+ args.app_show_generic_name = args.app_show_generic_name ~= nil and args.app_show_generic_name or false
+
+ local ret = gobject({})
+ ret._private = {}
+ ret._private.text = ""
+
+ gtable.crush(ret, app_launcher)
+ gtable.crush(ret, args)
+
+ -- Calculate the grid width and height
+ local grid_width = ret.shirnk_width == false
+ and dpi((ret.app_width * ret.apps_per_column) + ((ret.apps_per_column - 1) * ret.apps_spacing))
+ or nil
+ local grid_height = ret.shrink_height == false
+ and dpi((ret.app_height * ret.apps_per_row) + ((ret.apps_per_row - 1) * ret.apps_spacing))
+ or nil
+
+ -- These widgets need to be later accessed
+ ret._private.prompt = prompt
+ {
+ prompt = ret.prompt_text,
+ text = ret.prompt_start_text,
+ font = ret.prompt_font,
+ reset_on_stop = ret.reset_on_hide,
+ bg_cursor = ret.prompt_cursor_color,
+ history_path = ret.save_history == true and gfilesystem.get_cache_dir() .. "/history" or nil,
+ changed_callback = function(text)
+ if text == ret._private.text then
+ return
+ end
+
+ if ret._private.search_timer ~= nil and ret._private.search_timer.started then
+ ret._private.search_timer:stop()
+ end
+
+ ret._private.search_timer = gtimer {
+ timeout = 0.05,
+ autostart = true,
+ single_shot = true,
+ callback = function()
+ search(ret, text)
+ end
+ }
+
+ ret._private.text = text
+ end,
+ keypressed_callback = function(mod, key, cmd)
+ if key == "Escape" then
+ ret:hide()
+ end
+ if key == "Return" then
+ if ret._private.active_widget ~= nil then
+ ret._private.active_widget.spawn()
+ end
+ end
+ if key == "Up" then
+ scroll_up(ret)
+ end
+ if key == "Down" then
+ scroll_down(ret)
+ end
+ if key == "Left" then
+ scroll_left(ret)
+ end
+ if key == "Right" then
+ scroll_right(ret)
+ end
+ end
+ }
+ ret._private.grid = wibox.widget
+ {
+ layout = wibox.layout.grid,
+ forced_width = grid_width,
+ forced_height = grid_height,
+ orientation = "horizontal",
+ homogeneous = true,
+ expand = ret.expand_apps,
+ spacing = ret.apps_spacing,
+ forced_num_rows = ret.apps_per_row,
+ buttons =
+ {
+ awful.button({}, 4, function() scroll_up(ret) end),
+ awful.button({}, 5, function() scroll_down(ret) end)
+ }
+ }
+ ret._private.widget = awful.popup
+ {
+ type = "dock",
+ visible = false,
+ ontop = true,
+ placement = ret.placement,
+ shape = ret.shape,
+ bg = ret.background,
+ widget =
+ {
+ layout = wibox.layout.fixed.vertical,
+ {
+ widget = wibox.container.margin,
+ margins = ret.prompt_margins,
+ {
+ widget = wibox.container.background,
+ forced_height = ret.prompt_height,
+ shape = ret.prompt_shape,
+ bg = ret.prompt_color,
+ fg = ret.prompt_text_color,
+ border_width = ret.prompt_border_width,
+ border_color = ret.prompt_border_color,
+ {
+ widget = wibox.container.margin,
+ margins = ret.prompt_paddings,
+ {
+ widget = wibox.container.place,
+ halign = ret.prompt_text_halign,
+ valign = ret.prompt_text_valign,
+ {
+ layout = wibox.layout.fixed.horizontal,
+ spacing = ret.prompt_icon_text_spacing,
+ {
+ widget = wibox.widget.textbox,
+ font = ret.prompt_icon_font,
+ markup = ret.prompt_icon_markup
+ },
+ ret._private.prompt.textbox
+ }
+ }
+ }
+ }
+ },
+ {
+ widget = wibox.container.margin,
+ margins = ret.apps_margin,
+ ret._private.grid
+ }
+ }
+ }
+
+ -- Private variables to be used to be used by the scrolling and searching functions
+ ret._private.max_apps_per_page = ret.apps_per_column * ret.apps_per_row
+ ret._private.apps_per_page = ret._private.max_apps_per_page
+ ret._private.pages_count = 0
+ ret._private.current_page = 1
+
+ generate_apps(ret)
+ reset(ret)
+
+ if ret.rubato and ret.rubato.x then
+ ret.rubato.x:subscribe(function(pos)
+ ret._private.widget.x = pos
+ end)
+ end
+ if ret.rubato and ret.rubato.y then
+ ret.rubato.y:subscribe(function(pos)
+ ret._private.widget.y = pos
+ end)
+ end
+
+ if ret.hide_on_left_clicked_outside then
+ awful.mouse.append_client_mousebinding(
+ awful.button({ }, 1, function (c)
+ ret:hide()
+ end)
+ )
+
+ awful.mouse.append_global_mousebinding(
+ awful.button({ }, 1, function (c)
+ ret:hide()
+ end)
+ )
+ end
+ if ret.hide_on_right_clicked_outside then
+ awful.mouse.append_client_mousebinding(
+ awful.button({ }, 3, function (c)
+ ret:hide()
+ end)
+ )
+
+ awful.mouse.append_global_mousebinding(
+ awful.button({ }, 3, function (c)
+ ret:hide()
+ end)
+ )
+ end
+
+ local kill_old_inotify_process_script = [[ ps x | grep "inotifywait -e modify /usr/share/applications" | grep -v grep | awk '{print $1}' | xargs kill ]]
+ local subscribe_script = [[ bash -c "while (inotifywait -e modify /usr/share/applications -qq) do echo; done" ]]
+
+ awful.spawn.easy_async_with_shell(kill_old_inotify_process_script, function()
+ awful.spawn.with_line_callback(subscribe_script, {stdout = function(_)
+ generate_apps(ret)
+ end})
+ end)
+
+ return ret
+end
+
+function app_launcher.text(args)
+ args = args or {}
+
+ args.prompt_height = args.prompt_height or dpi(50)
+ args.prompt_margins = args.prompt_margins or dpi(30)
+ args.prompt_paddings = args.prompt_paddings or dpi(15)
+ args.app_width = args.app_width or dpi(400)
+ args.app_height = args.app_height or dpi(40)
+ args.apps_spacing = args.apps_spacing or dpi(10)
+ args.apps_per_row = args.apps_per_row or 15
+ args.apps_per_column = args.apps_per_column or 1
+ args.app_name_halign = args.app_name_halign or "left"
+ args.app_show_icon = args.app_show_icon ~= nil and args.app_show_icon or false
+ args.app_show_generic_name = args.app_show_generic_name == nil and true or args.app_show_generic_name
+ args.apps_margin = args.apps_margin or { left = dpi(40), right = dpi(40), bottom = dpi(30) }
+
+ return new(args)
+end
+
+function app_launcher.mt:__call(...)
+ return new(...)
+end
+
+return setmetatable(app_launcher, app_launcher.mt)
diff --git a/.config/awesome/extras/bling/widget/app_launcher/prompt.lua b/.config/awesome/extras/bling/widget/app_launcher/prompt.lua
new file mode 100755
index 0000000..fae3b86
--- /dev/null
+++ b/.config/awesome/extras/bling/widget/app_launcher/prompt.lua
@@ -0,0 +1,656 @@
+---------------------------------------------------------------------------
+--- Modified Prompt module.
+-- @author Julien Danjou <julien@danjou.info>
+-- @copyright 2008 Julien Danjou
+---------------------------------------------------------------------------
+
+local akey = require("awful.key")
+local keygrabber = require("awful.keygrabber")
+local gobject = require("gears.object")
+local gdebug = require('gears.debug')
+local gtable = require("gears.table")
+local gcolor = require("gears.color")
+local gstring = require("gears.string")
+local gfs = require("gears.filesystem")
+local wibox = require("wibox")
+local beautiful = require("beautiful")
+local io = io
+local table = table
+local math = math
+local ipairs = ipairs
+local unpack = unpack or table.unpack -- luacheck: globals unpack (compatibility with Lua 5.1)
+local capi = { selection = selection }
+
+local prompt = { mt = {} }
+
+--- Private data
+local data = {}
+data.history = {}
+
+local function itera(inc,a, i)
+ i = i + inc
+ local v = a[i]
+ if v then return i,v end
+end
+
+local function history_check_load(id, max)
+ if id and id ~= "" and not data.history[id] then
+ data.history[id] = { max = 50, table = {} }
+
+ if max then
+ data.history[id].max = max
+ end
+
+ local f = io.open(id, "r")
+ if not f then return end
+
+ -- Read history file
+ for line in f:lines() do
+ if gtable.hasitem(data.history[id].table, line) == nil then
+ table.insert(data.history[id].table, line)
+ if #data.history[id].table >= data.history[id].max then
+ break
+ end
+ end
+ end
+ f:close()
+ end
+end
+
+local function is_word_char(c)
+ if string.find(c, "[{[(,.:;_-+=@/ ]") then
+ return false
+ else
+ return true
+ end
+end
+
+local function cword_start(s, pos)
+ local i = pos
+ if i > 1 then
+ i = i - 1
+ end
+ while i >= 1 and not is_word_char(s:sub(i, i)) do
+ i = i - 1
+ end
+ while i >= 1 and is_word_char(s:sub(i, i)) do
+ i = i - 1
+ end
+ if i <= #s then
+ i = i + 1
+ end
+ return i
+end
+
+local function cword_end(s, pos)
+ local i = pos
+ while i <= #s and not is_word_char(s:sub(i, i)) do
+ i = i + 1
+ end
+ while i <= #s and is_word_char(s:sub(i, i)) do
+ i = i + 1
+ end
+ return i
+end
+
+local function history_save(id)
+ if data.history[id] then
+ gfs.make_parent_directories(id)
+ local f = io.open(id, "w")
+ if not f then
+ gdebug.print_warning("Failed to write the history to "..id)
+ return
+ end
+ for i = 1, math.min(#data.history[id].table, data.history[id].max) do
+ f:write(data.history[id].table[i] .. "\n")
+ end
+ f:close()
+ end
+end
+
+local function history_items(id)
+ if data.history[id] then
+ return #data.history[id].table
+ else
+ return -1
+ end
+end
+
+local function history_add(id, command)
+ if data.history[id] and command ~= "" then
+ local index = gtable.hasitem(data.history[id].table, command)
+ if index == nil then
+ table.insert(data.history[id].table, command)
+
+ -- Do not exceed our max_cmd
+ if #data.history[id].table > data.history[id].max then
+ table.remove(data.history[id].table, 1)
+ end
+
+ history_save(id)
+ else
+ -- Bump this command to the end of history
+ table.remove(data.history[id].table, index)
+ table.insert(data.history[id].table, command)
+ history_save(id)
+ end
+ end
+end
+
+local function have_multibyte_char_at(text, position)
+ return text:sub(position, position):wlen() == -1
+end
+
+local function prompt_text_with_cursor(args)
+ local char, spacer, text_start, text_end, ret
+ local text = args.text or ""
+ local _prompt = args.prompt or ""
+ local underline = args.cursor_ul or "none"
+
+ if args.select_all then
+ if #text == 0 then char = " " else char = gstring.xml_escape(text) end
+ spacer = " "
+ text_start = ""
+ text_end = ""
+ elseif #text < args.cursor_pos then
+ char = " "
+ spacer = ""
+ text_start = gstring.xml_escape(text)
+ text_end = ""
+ else
+ local offset = 0
+ if have_multibyte_char_at(text, args.cursor_pos) then
+ offset = 1
+ end
+ char = gstring.xml_escape(text:sub(args.cursor_pos, args.cursor_pos + offset))
+ spacer = " "
+ text_start = gstring.xml_escape(text:sub(1, args.cursor_pos - 1))
+ text_end = gstring.xml_escape(text:sub(args.cursor_pos + 1 + offset))
+ end
+
+ local cursor_color = gcolor.ensure_pango_color(args.cursor_color)
+ local text_color = gcolor.ensure_pango_color(args.text_color)
+
+ if args.highlighter then
+ text_start, text_end = args.highlighter(text_start, text_end)
+ end
+
+ ret = _prompt .. text_start .. "" .. char .. "" .. text_end .. spacer
+
+ return ret
+end
+
+local function update(self)
+ self.textbox:set_font(self.font)
+ self.textbox:set_markup(prompt_text_with_cursor{
+ text = self.command, text_color = self.fg_cursor, cursor_color = self.bg_cursor,
+ cursor_pos = self._private_cur_pos, cursor_ul = self.ul_cursor, select_all = self.select_all,
+ prompt = self.prompt, highlighter = self.highlighter })
+end
+
+local function exec(self, cb, command_to_history)
+ self.textbox:set_markup("")
+ history_add(self.history_path, command_to_history)
+ keygrabber.stop(self._private.grabber)
+ if cb then cb(self.command) end
+ if self.done_callback then
+ self.done_callback()
+ end
+end
+
+function prompt:start()
+ -- The cursor position
+ if self.reset_on_stop == true or self._private_cur_pos == nil then
+ self._private_cur_pos = (self.select_all and 1) or self.text:wlen() + 1
+ end
+ if self.reset_on_stop == true then self.text = "" self.command = "" end
+
+ self.textbox:set_font(self.font)
+ self.textbox:set_markup(prompt_text_with_cursor{
+ text = self.reset_on_stop and self.text or self.command, text_color = self.fg_cursor, cursor_color = self.bg_cursor,
+ cursor_pos = self._private_cur_pos, cursor_ul = self.ul_cursor, select_all = self.select_all,
+ prompt = self.prompt, highlighter = self.highlighter})
+
+ self._private.search_term = nil
+
+ history_check_load(self.history_path, self.history_max)
+ local history_index = history_items(self.history_path) + 1
+
+ -- The completion element to use on completion request.
+ local ncomp = 1
+
+ local command_before_comp
+ local cur_pos_before_comp
+
+ self._private.grabber = keygrabber.run(function(modifiers, key, event)
+ -- Convert index array to hash table
+ local mod = {}
+ for _, v in ipairs(modifiers) do mod[v] = true end
+
+ if event ~= "press" then
+ if self.keyreleased_callback then
+ self.keyreleased_callback(mod, key, self.command)
+ end
+ return
+ end
+
+ -- Call the user specified callback. If it returns true as
+ -- the first result then return from the function. Treat the
+ -- second and third results as a new command and new prompt
+ -- to be set (if provided)
+ if self.keypressed_callback then
+ local user_catched, new_command, new_prompt =
+ self.keypressed_callback(mod, key, self.command)
+ if new_command or new_prompt then
+ if new_command then
+ self.command = new_command
+ end
+ if new_prompt then
+ self.prompt = new_prompt
+ end
+ update(self)
+ end
+ if user_catched then
+ if self.changed_callback then
+ self.changed_callback(self.command)
+ end
+ return
+ end
+ end
+
+ local filtered_modifiers = {}
+
+ -- User defined cases
+ if self.hooks[key] then
+ -- Remove caps and num lock
+ for _, m in ipairs(modifiers) do
+ if not gtable.hasitem(akey.ignore_modifiers, m) then
+ table.insert(filtered_modifiers, m)
+ end
+ end
+
+ for _,v in ipairs(self.hooks[key]) do
+ if #filtered_modifiers == #v[1] then
+ local match = true
+ for _,v2 in ipairs(v[1]) do
+ match = match and mod[v2]
+ end
+ if match then
+ local cb
+ local ret, quit = v[3](self.command)
+ local original_command = self.command
+
+ -- Support both a "simple" and a "complex" way to
+ -- control if the prompt should quit.
+ quit = quit == nil and (ret ~= true) or (quit~=false)
+
+ -- Allow the callback to change the command
+ self.command = (ret ~= true) and ret or self.command
+
+ -- Quit by default, but allow it to be disabled
+ if ret and type(ret) ~= "boolean" then
+ cb = self.exe_callback
+ if not quit then
+ self._private_cur_pos = ret:wlen() + 1
+ update(self)
+ end
+ elseif quit then
+ -- No callback.
+ cb = function() end
+ end
+
+ -- Execute the callback
+ if cb then
+ exec(self, cb, original_command)
+ end
+
+ return
+ end
+ end
+ end
+ end
+
+ -- Get out cases
+ if (mod.Control and (key == "c" or key == "g"))
+ or (not mod.Control and key == "Escape") then
+ self:stop()
+ return false
+ elseif (mod.Control and (key == "j" or key == "m"))
+ -- or (not mod.Control and key == "Return")
+ -- or (not mod.Control and key == "KP_Enter")
+ then
+ exec(self, self.exe_callback, self.command)
+ -- We already unregistered ourselves so we don't want to return
+ -- true, otherwise we may unregister someone else.
+ return
+ end
+
+ -- Control cases
+ if mod.Control then
+ self.select_all = nil
+ if key == "v" then
+ local selection = capi.selection()
+ if selection then
+ -- Remove \n
+ local n = selection:find("\n")
+ if n then
+ selection = selection:sub(1, n - 1)
+ end
+ self.command = self.command:sub(1, self._private_cur_pos - 1) .. selection .. self.command:sub(self._private_cur_pos)
+ self._private_cur_pos = self._private_cur_pos + #selection
+ end
+ elseif key == "a" then
+ self._private_cur_pos = 1
+ elseif key == "b" then
+ if self._private_cur_pos > 1 then
+ self._private_cur_pos = self._private_cur_pos - 1
+ if have_multibyte_char_at(self.command, self._private_cur_pos) then
+ self._private_cur_pos = self._private_cur_pos - 1
+ end
+ end
+ elseif key == "d" then
+ if self._private_cur_pos <= #self.command then
+ self.command = self.command:sub(1, self._private_cur_pos - 1) .. self.command:sub(self._private_cur_pos + 1)
+ end
+ elseif key == "p" then
+ if history_index > 1 then
+ history_index = history_index - 1
+
+ self.command = data.history[self.history_path].table[history_index]
+ self._private_cur_pos = #self.command + 2
+ end
+ elseif key == "n" then
+ if history_index < history_items(self.history_path) then
+ history_index = history_index + 1
+
+ self.command = data.history[self.history_path].table[history_index]
+ self._private_cur_pos = #self.command + 2
+ elseif history_index == history_items(self.history_path) then
+ history_index = history_index + 1
+
+ self.command = ""
+ self._private_cur_pos = 1
+ end
+ elseif key == "e" then
+ self._private_cur_pos = #self.command + 1
+ elseif key == "r" then
+ self._private.search_term = self._private.search_term or self.command:sub(1, self._private_cur_pos - 1)
+ for i,v in (function(a,i) return itera(-1,a,i) end), data.history[self.history_path].table, history_index do
+ if v:find(self._private.search_term,1,true) ~= nil then
+ self.command=v
+ history_index=i
+ self._private_cur_pos=#self.command+1
+ break
+ end
+ end
+ elseif key == "s" then
+ self._private.search_term = self._private.search_term or self.command:sub(1, self._private_cur_pos - 1)
+ for i,v in (function(a,i) return itera(1,a,i) end), data.history[self.history_path].table, history_index do
+ if v:find(self._private.search_term,1,true) ~= nil then
+ self.command=v
+ history_index=i
+ self._private_cur_pos=#self.command+1
+ break
+ end
+ end
+ elseif key == "f" then
+ if self._private_cur_pos <= #self.command then
+ if have_multibyte_char_at(self.command, self._private_cur_pos) then
+ self._private_cur_pos = self._private_cur_pos + 2
+ else
+ self._private_cur_pos = self._private_cur_pos + 1
+ end
+ end
+ elseif key == "h" then
+ if self._private_cur_pos > 1 then
+ local offset = 0
+ if have_multibyte_char_at(self.command, self._private_cur_pos - 1) then
+ offset = 1
+ end
+ self.command = self.command:sub(1, self._private_cur_pos - 2 - offset) .. self.command:sub(self._private_cur_pos)
+ self._private_cur_pos = self._private_cur_pos - 1 - offset
+ end
+ elseif key == "k" then
+ self.command = self.command:sub(1, self._private_cur_pos - 1)
+ elseif key == "u" then
+ self.command = self.command:sub(self._private_cur_pos, #self.command)
+ self._private_cur_pos = 1
+ elseif key == "Prior" then
+ self._private.search_term = self.command:sub(1, self._private_cur_pos - 1) or ""
+ for i,v in (function(a,i) return itera(-1,a,i) end), data.history[self.history_path].table, history_index do
+ if v:find(self._private.search_term,1,true) == 1 then
+ self.command=v
+ history_index=i
+ break
+ end
+ end
+ elseif key == "Next" then
+ self._private.search_term = self.command:sub(1, self._private_cur_pos - 1) or ""
+ for i,v in (function(a,i) return itera(1,a,i) end), data.history[self.history_path].table, history_index do
+ if v:find(self._private.search_term,1,true) == 1 then
+ self.command=v
+ history_index=i
+ break
+ end
+ end
+ elseif key == "w" or key == "BackSpace" then
+ local wstart = 1
+ local wend = 1
+ local cword_start_pos = 1
+ local cword_end_pos = 1
+ while wend < self._private_cur_pos do
+ wend = self.command:find("[{[(,.:;_-+=@/ ]", wstart)
+ if not wend then wend = #self.command + 1 end
+ if self._private_cur_pos >= wstart and self._private_cur_pos <= wend + 1 then
+ cword_start_pos = wstart
+ cword_end_pos = self._private_cur_pos - 1
+ break
+ end
+ wstart = wend + 1
+ end
+ self.command = self.command:sub(1, cword_start_pos - 1) .. self.command:sub(cword_end_pos + 1)
+ self._private_cur_pos = cword_start_pos
+ elseif key == "Delete" then
+ -- delete from history only if:
+ -- we are not dealing with a new command
+ -- the user has not edited an existing entry
+ if self.command == data.history[self.history_path].table[history_index] then
+ table.remove(data.history[self.history_path].table, history_index)
+ if history_index <= history_items(self.history_path) then
+ self.command = data.history[self.history_path].table[history_index]
+ self._private_cur_pos = #self.command + 2
+ elseif history_index > 1 then
+ history_index = history_index - 1
+
+ self.command = data.history[self.history_path].table[history_index]
+ self._private_cur_pos = #self.command + 2
+ else
+ self.command = ""
+ self._private_cur_pos = 1
+ end
+ end
+ end
+ elseif mod.Mod1 or mod.Mod3 then
+ if key == "b" then
+ self._private_cur_pos = cword_start(self.command, self._private_cur_pos)
+ elseif key == "f" then
+ self._private_cur_pos = cword_end(self.command, self._private_cur_pos)
+ elseif key == "d" then
+ self.command = self.command:sub(1, self._private_cur_pos - 1) .. self.command:sub(cword_end(self.command, self._private_cur_pos))
+ elseif key == "BackSpace" then
+ local wstart = cword_start(self.command, self._private_cur_pos)
+ self.command = self.command:sub(1, wstart - 1) .. self.command:sub(self._private_cur_pos)
+ self._private_cur_pos = wstart
+ end
+ else
+ if self.completion_callback then
+ if key == "Tab" or key == "ISO_Left_Tab" then
+ if key == "ISO_Left_Tab" or mod.Shift then
+ if ncomp == 1 then return end
+ if ncomp == 2 then
+ self.command = command_before_comp
+ self.textbox:set_font(self.font)
+ self.textbox:set_markup(prompt_text_with_cursor{
+ text = command_before_comp, text_color = self.fg_cursor, cursor_color = self.bg_cursor,
+ cursor_pos = self._private_cur_pos, cursor_ul = self.ul_cursor, select_all = self.select_all,
+ prompt = self.prompt })
+ self._private_cur_pos = cur_pos_before_comp
+ ncomp = 1
+ return
+ end
+
+ ncomp = ncomp - 2
+ elseif ncomp == 1 then
+ command_before_comp = self.command
+ cur_pos_before_comp = self._private_cur_pos
+ end
+ local matches
+ self.command, self._private_cur_pos, matches = self.completion_callback(command_before_comp, cur_pos_before_comp, ncomp)
+ ncomp = ncomp + 1
+ key = ""
+ -- execute if only one match found and autoexec flag set
+ if matches and #matches == 1 and args.autoexec then
+ exec(self, self.exe_callback)
+ return
+ end
+ elseif key ~= "Shift_L" and key ~= "Shift_R" then
+ ncomp = 1
+ end
+ end
+
+ -- Typin cases
+ if mod.Shift and key == "Insert" then
+ local selection = capi.selection()
+ if selection then
+ -- Remove \n
+ local n = selection:find("\n")
+ if n then
+ selection = selection:sub(1, n - 1)
+ end
+ self.command = self.command:sub(1, self._private_cur_pos - 1) .. selection .. self.command:sub(self._private_cur_pos)
+ self._private_cur_pos = self._private_cur_pos + #selection
+ end
+ elseif key == "Home" then
+ self._private_cur_pos = 1
+ elseif key == "End" then
+ self._private_cur_pos = #self.command + 1
+ elseif key == "BackSpace" then
+ if self._private_cur_pos > 1 then
+ local offset = 0
+ if have_multibyte_char_at(self.command, self._private_cur_pos - 1) then
+ offset = 1
+ end
+ self.command = self.command:sub(1, self._private_cur_pos - 2 - offset) .. self.command:sub(self._private_cur_pos)
+ self._private_cur_pos = self._private_cur_pos - 1 - offset
+ end
+ elseif key == "Delete" then
+ self.command = self.command:sub(1, self._private_cur_pos - 1) .. self.command:sub(self._private_cur_pos + 1)
+ elseif key == "Left" then
+ self._private_cur_pos = self._private_cur_pos - 1
+ elseif key == "Right" then
+ self._private_cur_pos = self._private_cur_pos + 1
+ elseif key == "Prior" then
+ if history_index > 1 then
+ history_index = history_index - 1
+
+ self.command = data.history[self.history_path].table[history_index]
+ self._private_cur_pos = #self.command + 2
+ end
+ elseif key == "Next" then
+ if history_index < history_items(self.history_path) then
+ history_index = history_index + 1
+
+ self.command = data.history[self.history_path].table[history_index]
+ self._private_cur_pos = #self.command + 2
+ elseif history_index == history_items(self.history_path) then
+ history_index = history_index + 1
+
+ self.command = ""
+ self._private_cur_pos = 1
+ end
+ else
+ -- wlen() is UTF-8 aware but #key is not,
+ -- so check that we have one UTF-8 char but advance the cursor of # position
+ if key:wlen() == 1 then
+ if self.select_all then self.command = "" end
+ self.command = self.command:sub(1, self._private_cur_pos - 1) .. key .. self.command:sub(self._private_cur_pos)
+ self._private_cur_pos = self._private_cur_pos + #key
+ end
+ end
+ if self._private_cur_pos < 1 then
+ self._private_cur_pos = 1
+ elseif self._private_cur_pos > #self.command + 1 then
+ self._private_cur_pos = #self.command + 1
+ end
+ self.select_all = nil
+ end
+
+ update(self)
+ if self.changed_callback then
+ self.changed_callback(self.command)
+ end
+ end)
+end
+
+function prompt:stop()
+ keygrabber.stop(self._private.grabber)
+ history_save(self.history_path)
+ if self.done_callback then self.done_callback() end
+ return false
+end
+
+local function new(args)
+ args = args or {}
+
+ args.command = args.text or ""
+ args.prompt = args.prompt or ""
+ args.text = args.text or ""
+ args.font = args.font or beautiful.prompt_font or beautiful.font
+ args.bg_cursor = args.bg_cursor or beautiful.prompt_bg_cursor or beautiful.bg_focus or "white"
+ args.fg_cursor = args.fg_cursor or beautiful.prompt_fg_cursor or beautiful.fg_focus or "black"
+ args.ul_cursor = args.ul_cursor or nil
+ args.reset_on_stop = args.reset_on_stop == nil and true or args.reset_on_stop
+ args.select_all = args.select_all or nil
+ args.highlighter = args.highlighter or nil
+ args.hooks = args.hooks or {}
+ args.keypressed_callback = args.keypressed_callback or nil
+ args.changed_callback = args.changed_callback or nil
+ args.done_callback = args.done_callback or nil
+ args.history_max = args.history_max or nil
+ args.history_path = args.history_path or nil
+ args.completion_callback = args.completion_callback or nil
+ args.exe_callback = args.exe_callback or nil
+ args.textbox = args.textbox or wibox.widget.textbox()
+
+ -- Build the hook map
+ local hooks = {}
+ for _,v in ipairs(args.hooks) do
+ if #v == 3 then
+ local _,key,callback = unpack(v)
+ if type(callback) == "function" then
+ hooks[key] = hooks[key] or {}
+ hooks[key][#hooks[key]+1] = v
+ else
+ gdebug.print_warning("The hook's 3rd parameter has to be a function.")
+ end
+ else
+ gdebug.print_warning("The hook has to have 3 parameters.")
+ end
+ end
+ args.hooks = hooks
+
+ local ret = gobject({})
+ ret._private = {}
+ gtable.crush(ret, prompt)
+ gtable.crush(ret, args)
+
+ return ret
+end
+
+function prompt.mt:__call(...)
+ return new(...)
+end
+
+return setmetatable(prompt, prompt.mt)
\ No newline at end of file
diff --git a/.config/awesome/extras/bling/widget/init.lua b/.config/awesome/extras/bling/widget/init.lua
new file mode 100755
index 0000000..d3c6ebd
--- /dev/null
+++ b/.config/awesome/extras/bling/widget/init.lua
@@ -0,0 +1,7 @@
+return {
+ tag_preview = require(... .. ".tag_preview"),
+ task_preview = require(... .. ".task_preview"),
+ window_switcher = require(... .. ".window_switcher"),
+ tabbed_misc = require(... .. ".tabbed_misc"),
+ app_launcher = require(... .. ".app_launcher"),
+}
diff --git a/.config/awesome/extras/bling/widget/tabbar/boxes.lua b/.config/awesome/extras/bling/widget/tabbar/boxes.lua
new file mode 100755
index 0000000..720f420
--- /dev/null
+++ b/.config/awesome/extras/bling/widget/tabbar/boxes.lua
@@ -0,0 +1,57 @@
+local awful = require("awful")
+local gears = require("gears")
+local wibox = require("wibox")
+
+local beautiful = require("beautiful")
+
+local bg_normal = beautiful.tabbar_bg_normal or beautiful.bg_normal or "#ffffff"
+local fg_normal = beautiful.tabbar_fg_normal or beautiful.fg_normal or "#000000"
+local bg_focus = beautiful.tabbar_bg_focus or beautiful.bg_focus or "#000000"
+local fg_focus = beautiful.tabbar_fg_focus or beautiful.fg_focus or "#ffffff"
+local bg_focus_inactive = beautiful.tabbar_bg_focus_inactive or bg_focus
+local fg_focus_inactive = beautiful.tabbar_fg_focus_inactive or fg_focus
+local bg_normal_inactive = beautiful.tabbar_bg_normal_inactive or bg_normal
+local fg_normal_inactive = beautiful.tabbar_fg_normal_inactive or fg_normal
+local font = beautiful.tabbar_font or beautiful.font or "Hack 15"
+local size = beautiful.tabbar_size or 40
+local position = beautiful.tabbar_position or "bottom"
+
+local function create(c, focused_bool, buttons, inactive_bool)
+ local bg_temp = inactive_bool and bg_normal_inactive or bg_normal
+ local fg_temp = inactive_bool and fg_normal_inactive or fg_normal
+ if focused_bool then
+ bg_temp = inactive_bool and bg_focus_inactive or bg_focus
+ fg_temp = inactive_bool and fg_focus_inactive or fg_focus
+ end
+ local wid_temp = wibox.widget({
+ {
+ {
+ awful.widget.clienticon(c),
+ left = 10,
+ right = 10,
+ bottom = 10,
+ top = 10,
+ widget = wibox.container.margin(),
+ },
+ widget = wibox.container.place(),
+ },
+ buttons = buttons,
+ bg = bg_temp,
+ widget = wibox.container.background(),
+ })
+ return wid_temp
+end
+
+local layout = wibox.layout.fixed.horizontal
+if position == "left" or position == "right" then
+ layout = wibox.layout.fixed.vertical
+end
+
+return {
+ layout = layout,
+ create = create,
+ position = position,
+ size = size,
+ bg_normal = bg_normal,
+ bg_focus = bg_normal,
+}
diff --git a/.config/awesome/extras/bling/widget/tabbar/default.lua b/.config/awesome/extras/bling/widget/tabbar/default.lua
new file mode 100755
index 0000000..ad6b0b1
--- /dev/null
+++ b/.config/awesome/extras/bling/widget/tabbar/default.lua
@@ -0,0 +1,60 @@
+local gears = require("gears")
+local wibox = require("wibox")
+
+local beautiful = require("beautiful")
+
+local bg_normal = beautiful.tabbar_bg_normal or beautiful.bg_normal or "#ffffff"
+local fg_normal = beautiful.tabbar_fg_normal or beautiful.fg_normal or "#000000"
+local bg_focus = beautiful.tabbar_bg_focus or beautiful.bg_focus or "#000000"
+local fg_focus = beautiful.tabbar_fg_focus or beautiful.fg_focus or "#ffffff"
+local bg_focus_inactive = beautiful.tabbar_bg_focus_inactive or bg_focus
+local fg_focus_inactive = beautiful.tabbar_fg_focus_inactive or fg_focus
+local bg_normal_inactive = beautiful.tabbar_bg_normal_inactive or bg_normal
+local fg_normal_inactive = beautiful.tabbar_fg_normal_inactive or fg_normal
+local font = beautiful.tabbar_font or beautiful.font or "Hack 15"
+local size = beautiful.tabbar_size or 20
+local position = beautiful.tabbar_position or "top"
+
+local function create(c, focused_bool, buttons, inactive_bool)
+ local flexlist = wibox.layout.flex.horizontal()
+ local title_temp = c.name or c.class or "-"
+ local bg_temp = inactive_bool and bg_normal_inactive or bg_normal
+ local fg_temp = inactive_bool and fg_normal_inactive or fg_normal
+ if focused_bool then
+ bg_temp = inactive_bool and bg_focus_inactive or bg_focus
+ fg_temp = inactive_bool and fg_focus_inactive or fg_focus
+ end
+ local text_temp = wibox.widget.textbox()
+ text_temp.align = "center"
+ text_temp.valign = "center"
+ text_temp.font = font
+ text_temp.markup = ""
+ .. title_temp
+ .. ""
+ c:connect_signal("property::name", function(_)
+ local title_temp = c.name or c.class or "-"
+ text_temp.markup = ""
+ .. title_temp
+ .. ""
+ end)
+ local wid_temp = wibox.widget({
+ text_temp,
+ buttons = buttons,
+ bg = bg_temp,
+ widget = wibox.container.background(),
+ })
+ return wid_temp
+end
+
+return {
+ layout = wibox.layout.flex.horizontal,
+ create = create,
+ position = position,
+ size = size,
+ bg_normal = bg_normal,
+ bg_focus = bg_focus,
+}
diff --git a/.config/awesome/extras/bling/widget/tabbar/modern.lua b/.config/awesome/extras/bling/widget/tabbar/modern.lua
new file mode 100755
index 0000000..5f48066
--- /dev/null
+++ b/.config/awesome/extras/bling/widget/tabbar/modern.lua
@@ -0,0 +1,271 @@
+local awful = require("awful")
+local gears = require("gears")
+local wibox = require("wibox")
+local beautiful = require("beautiful")
+local xresources = require("beautiful.xresources")
+local dpi = xresources.apply_dpi
+local helpers = require(tostring(...):match(".*bling") .. ".helpers")
+
+local bg_normal = beautiful.tabbar_bg_normal or beautiful.bg_normal or "#ffffff"
+local fg_normal = beautiful.tabbar_fg_normal or beautiful.fg_normal or "#000000"
+local bg_focus = beautiful.tabbar_bg_focus or beautiful.bg_focus or "#000000"
+local fg_focus = beautiful.tabbar_fg_focus or beautiful.fg_focus or "#ffffff"
+local bg_focus_inactive = beautiful.tabbar_bg_focus_inactive or bg_focus
+local fg_focus_inactive = beautiful.tabbar_fg_focus_inactive or fg_focus
+local bg_normal_inactive = beautiful.tabbar_bg_normal_inactive or bg_normal
+local fg_normal_inactive = beautiful.tabbar_fg_normal_inactive or fg_normal
+local font = beautiful.tabbar_font or beautiful.font or "Hack 15"
+local size = beautiful.tabbar_size or dpi(40)
+local border_radius = beautiful.mstab_border_radius
+ or beautiful.border_radius
+ or 6
+local position = beautiful.tabbar_position or "top"
+local close_color = beautiful.tabbar_color_close
+ or beautiful.xcolor1
+ or "#f9929b"
+local min_color = beautiful.tabbar_color_min or beautiful.xcolor3 or "#fbdf90"
+local float_color = beautiful.tabbar_color_float
+ or beautiful.xcolor5
+ or "#ccaced"
+
+-- Helper to create buttons
+local function create_title_button(c, color_focus, color_unfocus)
+ local tb_color = wibox.widget({
+ wibox.widget.textbox(),
+ forced_width = dpi(8),
+ forced_height = dpi(8),
+ bg = color_focus,
+ shape = gears.shape.circle,
+ widget = wibox.container.background,
+ })
+
+ local tb = wibox.widget({
+ tb_color,
+ width = dpi(25),
+ height = dpi(25),
+ strategy = "min",
+ layout = wibox.layout.constraint,
+ })
+
+ local function update()
+ if client.focus == c then
+ tb_color.bg = color_focus
+ else
+ tb_color.bg = color_unfocus
+ end
+ end
+ update()
+ c:connect_signal("focus", update)
+ c:connect_signal("unfocus", update)
+
+ tb:connect_signal("mouse::enter", function()
+ tb_color.bg = color_focus .. "70"
+ end)
+
+ tb:connect_signal("mouse::leave", function()
+ tb_color.bg = color_focus
+ end)
+
+ tb.visible = true
+ return tb
+end
+
+local function create(c, focused_bool, buttons, inactive_bool)
+ -- local flexlist = wibox.layout.flex.horizontal()
+ local title_temp = c.name or c.class or "-"
+ local bg_temp = inactive_bool and bg_normal_inactive or bg_normal
+ local fg_temp = inactive_bool and fg_normal_inactive or fg_normal
+ if focused_bool then
+ bg_temp = inactive_bool and bg_focus_inactive or bg_focus
+ fg_temp = inactive_bool and fg_focus_inactive or fg_focus
+ end
+ local text_temp = wibox.widget.textbox()
+ text_temp.align = "center"
+ text_temp.valign = "center"
+ text_temp.font = font
+ text_temp.markup = ""
+ .. title_temp
+ .. ""
+ c:connect_signal("property::name", function(_)
+ local title_temp = c.name or c.class or "-"
+ text_temp.markup = ""
+ .. title_temp
+ .. ""
+ end)
+
+ local tab_content = wibox.widget({
+ {
+ awful.widget.clienticon(c),
+ top = dpi(6),
+ left = dpi(15),
+ bottom = dpi(6),
+ widget = wibox.container.margin,
+ },
+ text_temp,
+ nill,
+ expand = "none",
+ layout = wibox.layout.align.horizontal,
+ })
+
+ local close = create_title_button(c, close_color, bg_normal)
+ close:connect_signal("button::press", function()
+ c:kill()
+ end)
+
+ local floating = create_title_button(c, float_color, bg_normal)
+ floating:connect_signal("button::press", function()
+ c.floating = not c.floating
+ end)
+
+ local min = create_title_button(c, min_color, bg_normal)
+ min:connect_signal("button::press", function()
+ c.minimized = true
+ end)
+
+ if focused_bool then
+ tab_content = wibox.widget({
+ {
+ awful.widget.clienticon(c),
+ top = dpi(10),
+ left = dpi(15),
+ bottom = dpi(10),
+ widget = wibox.container.margin,
+ },
+ text_temp,
+ {
+ { min, floating, close, layout = wibox.layout.fixed.horizontal },
+ top = dpi(10),
+ right = dpi(10),
+ bottom = dpi(10),
+ widget = wibox.container.margin,
+ },
+ expand = "none",
+ layout = wibox.layout.align.horizontal,
+ })
+ end
+
+ local main_content = nil
+ local left_shape = nil
+ local right_shape = nil
+
+ if position == "top" then
+ main_content = wibox.widget({
+ {
+ tab_content,
+ bg = bg_temp,
+ shape = helpers.shape.prrect(
+ border_radius,
+ true,
+ true,
+ false,
+ false
+ ),
+ widget = wibox.container.background,
+ },
+ top = dpi(8),
+ widget = wibox.container.margin,
+ })
+
+ left_shape = helpers.shape.prrect(
+ border_radius,
+ false,
+ false,
+ true,
+ false
+ )
+ right_shape = helpers.shape.prrect(
+ border_radius,
+ false,
+ false,
+ false,
+ true
+ )
+ else
+ main_content = wibox.widget({
+ {
+ tab_content,
+ bg = bg_temp,
+ shape = helpers.shape.prrect(
+ border_radius,
+ false,
+ false,
+ true,
+ true
+ ),
+ widget = wibox.container.background,
+ },
+ bottom = dpi(8),
+ widget = wibox.container.margin,
+ })
+
+ left_shape = helpers.shape.prrect(
+ border_radius,
+ false,
+ true,
+ false,
+ false
+ )
+ right_shape = helpers.shape.prrect(
+ border_radius,
+ true,
+ false,
+ false,
+ false
+ )
+ end
+
+ local wid_temp = wibox.widget({
+ buttons = buttons,
+ {
+ {
+ {
+ wibox.widget.textbox(),
+ bg = bg_normal,
+ shape = left_shape,
+ widget = wibox.container.background,
+ },
+ bg = bg_temp,
+ shape = gears.rectangle,
+ widget = wibox.container.background,
+ },
+ width = border_radius + (border_radius / 2),
+ height = size,
+ strategy = "exact",
+ layout = wibox.layout.constraint,
+ },
+ main_content,
+ {
+ {
+ {
+ wibox.widget.textbox(),
+ bg = bg_normal,
+ shape = right_shape,
+ widget = wibox.container.background,
+ },
+ bg = bg_temp,
+ shape = gears.rectangle,
+ widget = wibox.container.background,
+ },
+ width = border_radius + (border_radius / 2),
+ height = size,
+ strategy = "exact",
+ layout = wibox.layout.constraint,
+ },
+
+ layout = wibox.layout.align.horizontal,
+ })
+ return wid_temp
+end
+
+return {
+ layout = wibox.layout.flex.horizontal,
+ create = create,
+ position = position,
+ size = size,
+ bg_normal = bg_normal,
+ bg_focus = bg_focus,
+}
diff --git a/.config/awesome/extras/bling/widget/tabbar/pure.lua b/.config/awesome/extras/bling/widget/tabbar/pure.lua
new file mode 100755
index 0000000..5be82e5
--- /dev/null
+++ b/.config/awesome/extras/bling/widget/tabbar/pure.lua
@@ -0,0 +1,81 @@
+local awful = require("awful")
+local gears = require("gears")
+local wibox = require("wibox")
+local gcolor = require("gears.color")
+local beautiful = require("beautiful")
+
+local bg_normal = beautiful.tabbar_bg_normal or beautiful.bg_normal or "#ffffff"
+local fg_normal = beautiful.tabbar_fg_normal or beautiful.fg_normal or "#000000"
+local bg_focus = beautiful.tabbar_bg_focus or beautiful.bg_focus or "#000000"
+local fg_focus = beautiful.tabbar_fg_focus or beautiful.fg_focus or "#ffffff"
+local bg_focus_inactive = beautiful.tabbar_bg_focus_inactive or bg_focus
+local fg_focus_inactive = beautiful.tabbar_fg_focus_inactive or fg_focus
+local bg_normal_inactive = beautiful.tabbar_bg_normal_inactive or bg_normal
+local fg_normal_inactive = beautiful.tabbar_fg_normal_inactive or fg_normal
+local font = beautiful.tabbar_font or beautiful.font or "Hack 15"
+local size = beautiful.tabbar_size or 20
+local position = beautiful.tabbar_position or "top"
+
+local function create(c, focused_bool, buttons, inactive_bool)
+ local bg_temp = inactive_bool and bg_normal_inactive or bg_normal
+ local fg_temp = inactive_bool and fg_normal_inactive or fg_normal
+ if focused_bool then
+ bg_temp = inactive_bool and bg_focus_inactive or bg_focus
+ fg_temp = inactive_bool and fg_focus_inactive or fg_focus
+ end
+
+ local wid_temp = wibox.widget({
+ {
+ { -- Left
+ wibox.widget.base.make_widget(
+ awful.titlebar.widget.iconwidget(c)
+ ),
+ buttons = buttons,
+ layout = wibox.layout.fixed.horizontal,
+ },
+ { -- Title
+ wibox.widget.base.make_widget(
+ awful.titlebar.widget.titlewidget(c)
+ ),
+ buttons = buttons,
+ widget = wibox.container.place,
+ },
+ { -- Right
+ focused_bool and wibox.widget.base.make_widget(
+ awful.titlebar.widget.floatingbutton(c)
+ ) or nil,
+ focused_bool and wibox.widget.base.make_widget(
+ awful.titlebar.widget.stickybutton(c)
+ ) or nil,
+ focused_bool and wibox.widget.base.make_widget(
+ awful.titlebar.widget.ontopbutton(c)
+ ) or nil,
+ focused_bool and wibox.widget.base.make_widget(
+ awful.titlebar.widget.maximizedbutton(c)
+ ) or nil,
+ focused_bool and wibox.widget.base.make_widget(
+ awful.titlebar.widget.minimizebutton(c)
+ ) or nil,
+ focused_bool and wibox.widget.base.make_widget(
+ awful.titlebar.widget.closebutton(c)
+ ) or nil,
+ layout = wibox.layout.fixed.horizontal,
+ },
+ layout = wibox.layout.align.horizontal,
+ },
+ bg = bg_temp,
+ fg = fg_temp,
+ widget = wibox.container.background,
+ })
+
+ return wid_temp
+end
+
+return {
+ layout = wibox.layout.flex.horizontal,
+ create = create,
+ position = position,
+ size = size,
+ bg_normal = bg_normal,
+ bg_focus = bg_focus,
+}
diff --git a/.config/awesome/extras/bling/widget/tabbed_misc/custom_tasklist.lua b/.config/awesome/extras/bling/widget/tabbed_misc/custom_tasklist.lua
new file mode 100755
index 0000000..ac22377
--- /dev/null
+++ b/.config/awesome/extras/bling/widget/tabbed_misc/custom_tasklist.lua
@@ -0,0 +1,51 @@
+local wibox = require("wibox")
+local awful = require("awful")
+local gears = require("gears")
+local beautiful = require("beautiful")
+local dpi = require("beautiful.xresources").apply_dpi
+
+local function tabobj_support(self, c, index, clients)
+ -- Self is the background widget in this context
+ if not c.bling_tabbed and #c.bling_tabbed.clients > 1 then
+ return
+ end
+
+ local group = c.bling_tabbed
+
+ -- TODO: Allow customization here
+ local layout_v = wibox.widget {
+ vertical_spacing = dpi(2),
+ horizontal_spacing = dpi(2),
+ layout = wibox.layout.grid.horizontal,
+ forced_num_rows = 2,
+ forced_num_cols = 2,
+ homogeneous = true
+ }
+
+ local wrapper = wibox.widget({
+ layout_v,
+ id = "click_role",
+ widget = wibox.container.margin,
+ margins = dpi(5),
+ })
+
+ -- To get the ball rolling.
+ for idx, c in ipairs(group.clients) do
+ if not (c and c.icon) then goto skip end
+
+ -- Add to the last layout
+ layout_v:add(wibox.widget {
+ {
+ widget = awful.widget.clienticon,
+ client = c
+ },
+ widget = wibox.container.constraint,
+ width = dpi(24),
+ height = dpi(24)
+ })
+ ::skip::
+ end
+ self.widget = wrapper
+end
+
+return tabobj_support
diff --git a/.config/awesome/extras/bling/widget/tabbed_misc/init.lua b/.config/awesome/extras/bling/widget/tabbed_misc/init.lua
new file mode 100755
index 0000000..1de3fbb
--- /dev/null
+++ b/.config/awesome/extras/bling/widget/tabbed_misc/init.lua
@@ -0,0 +1,9 @@
+return {
+ titlebar_indicator = require(
+ tostring(...):match(".*bling")
+ .. ".widget.tabbed_misc.titlebar_indicator"
+ ),
+ custom_tasklist = require(
+ tostring(...):match(".*bling") .. ".widget.tabbed_misc.custom_tasklist"
+ ),
+}
diff --git a/.config/awesome/extras/bling/widget/tabbed_misc/titlebar_indicator.lua b/.config/awesome/extras/bling/widget/tabbed_misc/titlebar_indicator.lua
new file mode 100755
index 0000000..46959cd
--- /dev/null
+++ b/.config/awesome/extras/bling/widget/tabbed_misc/titlebar_indicator.lua
@@ -0,0 +1,133 @@
+local wibox = require("wibox")
+local awful = require("awful")
+local gears = require("gears")
+local beautiful = require("beautiful")
+local dpi = require("beautiful.xresources").apply_dpi
+local tabbed_module = require(
+ tostring(...):match(".*bling") .. ".module.tabbed"
+)
+
+-- Just check if a table contains a value.
+local function tbl_contains(tbl, item)
+ for _, v in ipairs(tbl) do
+ if v == item then
+ return true
+ end
+ end
+ return false
+end
+
+-- Needs to be run, every time a new titlbear is created
+return function(c, opts)
+ -- Args & Fallback -- Widget templates are in their original loactions
+ opts = gears.table.crush({
+ layout_spacing = dpi(4),
+ icon_size = dpi(20),
+ icon_margin = dpi(4),
+ bg_color_focus = "#ff0000",
+ bg_color = "#00000000",
+ fg_color = "#fafafa",
+ fg_color_focus = "#e0e0e0",
+ icon_shape = function(cr, w, h)
+ gears.shape.rounded_rect(cr, w, h, 0)
+ end,
+ layout = wibox.layout.fixed.horizontal,
+ }, gears.table.join(
+ opts,
+ beautiful.bling_tabbed_misc_titlebar_indicator
+ ))
+
+ -- Container to store icons
+ local tabbed_icons = wibox.widget({
+ layout = opts.layout,
+ spacing = opts.layout_spacing,
+ })
+
+ awesome.connect_signal("bling::tabbed::client_removed", function(_, removed_c)
+ -- Remove from list
+ for idx, icon in ipairs(tabbed_icons.children) do
+ if icon._client == removed_c then
+ tabbed_icons:remove(idx)
+ end
+ end
+
+ -- Empty list
+ if removed_c == c then
+ tabbed_icons:reset()
+ end
+ end)
+
+ local function recreate(group)
+ if tbl_contains(group.clients, c) then
+ tabbed_icons:reset()
+ local focused = group.clients[group.focused_idx]
+
+ -- Autohide?
+ if #group.clients == 1 then
+ return
+ end
+
+ for idx, client in ipairs(group.clients) do
+ local widget = wibox.widget(
+ opts.widget_template or {
+ {
+ {
+ {
+ id = "icon_role",
+ forced_width = opts.icon_size,
+ forced_height = opts.icon_size,
+ widget = awful.widget.clienticon,
+ },
+ margins = opts.icon_margin,
+ widget = wibox.container.margin,
+ },
+ shape = opts.icon_shape,
+ id = "bg_role",
+ widget = wibox.container.background,
+ },
+ halign = "center",
+ valign = "center",
+ widget = wibox.container.place,
+ })
+
+ widget._client = client
+
+ -- No creation call back since this would be called on creation & every time the widget updated.
+ if opts.widget_template and opts.widget_template.update_callback then
+ opts.widget_template.update_callback(widget, client, group)
+ end
+
+ -- Add icons & etc
+ for _, w in ipairs(widget:get_children_by_id("icon_role")) do
+ -- TODO: Allow fallback icon?
+ w.image = client.icon
+ w.client = client
+ end
+
+ for _, w in ipairs(widget:get_children_by_id("bg_role")) do
+ w:add_button(awful.button({}, 1, function()
+ tabbed_module.switch_to(group, idx)
+ end))
+ if client == focused then
+ w.bg = opts.bg_color_focus
+ w.fg = opts.fg_color_focus
+ else
+ w.bg = opts.bg_color
+ w.fg = opts.fg_color
+ end
+ end
+
+ for _, w in ipairs(widget:get_children_by_id("text_role")) do
+ w.text = client.name
+ end
+
+ tabbed_icons:add(widget)
+ end
+ end
+ end
+
+ awesome.connect_signal("bling::tabbed::client_added", recreate)
+ awesome.connect_signal("bling::tabbed::changed_focus", recreate)
+
+ return tabbed_icons
+end
diff --git a/.config/awesome/extras/bling/widget/tag_preview.lua b/.config/awesome/extras/bling/widget/tag_preview.lua
new file mode 100755
index 0000000..2a181b4
--- /dev/null
+++ b/.config/awesome/extras/bling/widget/tag_preview.lua
@@ -0,0 +1,246 @@
+--
+-- Provides:
+-- bling::tag_preview::update -- first line is the signal
+-- t (tag) -- indented lines are function parameters
+-- bling::tag_preview::visibility
+-- s (screen)
+-- v (boolean)
+--
+local awful = require("awful")
+local wibox = require("wibox")
+local helpers = require(tostring(...):match(".*bling") .. ".helpers")
+local gears = require("gears")
+local beautiful = require("beautiful")
+local dpi = beautiful.xresources.apply_dpi
+local cairo = require("lgi").cairo
+
+local function draw_widget(
+ t,
+ tag_preview_image,
+ scale,
+ screen_radius,
+ client_radius,
+ client_opacity,
+ client_bg,
+ client_border_color,
+ client_border_width,
+ widget_bg,
+ widget_border_color,
+ widget_border_width,
+ geo,
+ margin,
+ background_image
+)
+ local client_list = wibox.layout.manual()
+ client_list.forced_height = geo.height
+ client_list.forced_width = geo.width
+ local tag_screen = t.screen
+ for i, c in ipairs(t:clients()) do
+ if not c.hidden and not c.minimized then
+
+
+ local img_box = wibox.widget ({
+ resize = true,
+ forced_height = 100 * scale,
+ forced_width = 100 * scale,
+ widget = wibox.widget.imagebox,
+ })
+
+ -- If fails to set image, fallback to a awesome icon
+ if not pcall(function() img_box.image = gears.surface.load(c.icon) end) then
+ img_box.image = beautiful.theme_assets.awesome_icon (24, "#222222", "#fafafa")
+ end
+
+ if tag_preview_image then
+ if c.prev_content or t.selected then
+ local content
+ if t.selected then
+ content = gears.surface(c.content)
+ else
+ content = gears.surface(c.prev_content)
+ end
+ local cr = cairo.Context(content)
+ local x, y, w, h = cr:clip_extents()
+ local img = cairo.ImageSurface.create(
+ cairo.Format.ARGB32,
+ w - x,
+ h - y
+ )
+ cr = cairo.Context(img)
+ cr:set_source_surface(content, 0, 0)
+ cr.operator = cairo.Operator.SOURCE
+ cr:paint()
+
+ img_box = wibox.widget({
+ image = gears.surface.load(img),
+ resize = true,
+ opacity = client_opacity,
+ forced_height = math.floor(c.height * scale),
+ forced_width = math.floor(c.width * scale),
+ widget = wibox.widget.imagebox,
+ })
+ end
+ end
+
+ local client_box = wibox.widget({
+ {
+ nil,
+ {
+ nil,
+ img_box,
+ nil,
+ expand = "outside",
+ layout = wibox.layout.align.horizontal,
+ },
+ nil,
+ expand = "outside",
+ widget = wibox.layout.align.vertical,
+ },
+ forced_height = math.floor(c.height * scale),
+ forced_width = math.floor(c.width * scale),
+ bg = client_bg,
+ shape_border_color = client_border_color,
+ shape_border_width = client_border_width,
+ shape = helpers.shape.rrect(client_radius),
+ widget = wibox.container.background,
+ })
+
+ client_box.point = {
+ x = math.floor((c.x - geo.x) * scale),
+ y = math.floor((c.y - geo.y) * scale),
+ }
+
+ client_list:add(client_box)
+ end
+ end
+
+ return wibox.widget {
+ {
+ background_image,
+ {
+ {
+ {
+ {
+ client_list,
+ forced_height = geo.height,
+ forced_width = geo.width,
+ widget = wibox.container.place,
+ },
+ layout = wibox.layout.align.horizontal,
+ },
+ layout = wibox.layout.align.vertical,
+ },
+ margins = margin,
+ widget = wibox.container.margin,
+ },
+ layout = wibox.layout.stack
+ },
+ bg = widget_bg,
+ shape_border_width = widget_border_width,
+ shape_border_color = widget_border_color,
+ shape = helpers.shape.rrect(screen_radius),
+ widget = wibox.container.background,
+ }
+end
+
+local enable = function(opts)
+ local opts = opts or {}
+
+ local tag_preview_image = opts.show_client_content or false
+ local widget_x = opts.x or dpi(20)
+ local widget_y = opts.y or dpi(20)
+ local scale = opts.scale or 0.2
+ local work_area = opts.honor_workarea or false
+ local padding = opts.honor_padding or false
+ local placement_fn = opts.placement_fn or nil
+ local background_image = opts.background_widget or nil
+
+ local margin = beautiful.tag_preview_widget_margin or dpi(0)
+ local screen_radius = beautiful.tag_preview_widget_border_radius or dpi(0)
+ local client_radius = beautiful.tag_preview_client_border_radius or dpi(0)
+ local client_opacity = beautiful.tag_preview_client_opacity or 0.5
+ local client_bg = beautiful.tag_preview_client_bg or "#000000"
+ local client_border_color = beautiful.tag_preview_client_border_color
+ or "#ffffff"
+ local client_border_width = beautiful.tag_preview_client_border_width
+ or dpi(3)
+ local widget_bg = beautiful.tag_preview_widget_bg or "#000000"
+ local widget_border_color = beautiful.tag_preview_widget_border_color
+ or "#ffffff"
+ local widget_border_width = beautiful.tag_preview_widget_border_width
+ or dpi(3)
+
+ local tag_preview_box = awful.popup({
+ type = "dropdown_menu",
+ visible = false,
+ ontop = true,
+ placement = placement_fn,
+ widget = wibox.container.background,
+ input_passthrough = true,
+ bg = "#00000000",
+ })
+
+ tag.connect_signal("property::selected", function(t)
+ -- Awesome switches up tags on startup really fast it seems, probably depends on what rules you have set
+ -- which can cause the c.content to not show the correct image
+ gears.timer
+ {
+ timeout = 0.1,
+ call_now = false,
+ autostart = true,
+ single_shot = true,
+ callback = function()
+ if t.selected == true then
+ for _, c in ipairs(t:clients()) do
+ c.prev_content = gears.surface.duplicate_surface(c.content)
+ end
+ end
+ end
+ }
+ end)
+
+ awesome.connect_signal("bling::tag_preview::update", function(t)
+ local geo = t.screen:get_bounding_geometry({
+ honor_padding = padding,
+ honor_workarea = work_area,
+ })
+
+ tag_preview_box.maximum_width = scale * geo.width + margin * 2
+ tag_preview_box.maximum_height = scale * geo.height + margin * 2
+
+
+ tag_preview_box.widget = draw_widget(
+ t,
+ tag_preview_image,
+ scale,
+ screen_radius,
+ client_radius,
+ client_opacity,
+ client_bg,
+ client_border_color,
+ client_border_width,
+ widget_bg,
+ widget_border_color,
+ widget_border_width,
+ geo,
+ margin,
+ background_image
+ )
+ end)
+
+ awesome.connect_signal("bling::tag_preview::visibility", function(s, v)
+ if not placement_fn then
+ tag_preview_box.x = s.geometry.x + widget_x
+ tag_preview_box.y = s.geometry.y + widget_y
+ end
+
+ if v == false then
+ tag_preview_box.widget = nil
+ collectgarbage("collect")
+ end
+
+ tag_preview_box.visible = v
+ end)
+end
+
+return {enable = enable, draw_widget = draw_widget}
diff --git a/.config/awesome/extras/bling/widget/task_preview.lua b/.config/awesome/extras/bling/widget/task_preview.lua
new file mode 100755
index 0000000..3b54867
--- /dev/null
+++ b/.config/awesome/extras/bling/widget/task_preview.lua
@@ -0,0 +1,199 @@
+--
+-- Provides:
+-- bling::task_preview::visibility
+-- s (screen)
+-- v (boolean)
+-- c (client)
+--
+local awful = require("awful")
+local wibox = require("wibox")
+local helpers = require(tostring(...):match(".*bling") .. ".helpers")
+local gears = require("gears")
+local beautiful = require("beautiful")
+local dpi = beautiful.xresources.apply_dpi
+local cairo = require("lgi").cairo
+
+-- TODO: rename structure to something better?
+local function draw_widget(
+ c,
+ widget_template,
+ screen_radius,
+ widget_bg,
+ widget_border_color,
+ widget_border_width,
+ margin,
+ widget_width,
+ widget_height
+)
+ if not pcall(function()
+ return type(c.content)
+ end) then
+ return
+ end
+
+ local content = nil
+ if c.active then
+ content = gears.surface(c.content)
+ elseif c.prev_content then
+ content = gears.surface(c.prev_content)
+ end
+
+ local img = nil
+ if content ~= nil then
+ local cr = cairo.Context(content)
+ local x, y, w, h = cr:clip_extents()
+ img = cairo.ImageSurface.create(cairo.Format.ARGB32, w - x, h - y)
+ cr = cairo.Context(img)
+ cr:set_source_surface(content, 0, 0)
+ cr.operator = cairo.Operator.SOURCE
+ cr:paint()
+ end
+
+ local widget = wibox.widget({
+ (widget_template or {
+ {
+ {
+ {
+ {
+ id = "icon_role",
+ resize = true,
+ forced_height = dpi(20),
+ forced_width = dpi(20),
+ widget = wibox.widget.imagebox,
+ },
+ {
+ {
+ id = "name_role",
+ align = "center",
+ widget = wibox.widget.textbox,
+ },
+ left = dpi(4),
+ right = dpi(4),
+ widget = wibox.container.margin,
+ },
+ layout = wibox.layout.align.horizontal,
+ },
+ {
+ {
+ {
+ id = "image_role",
+ resize = true,
+ clip_shape = helpers.shape.rrect(screen_radius),
+ widget = wibox.widget.imagebox,
+ },
+ valign = "center",
+ halign = "center",
+ widget = wibox.container.place,
+ },
+ top = margin * 0.25,
+ widget = wibox.container.margin,
+ },
+ fill_space = true,
+ layout = wibox.layout.fixed.vertical,
+ },
+ margins = margin,
+ widget = wibox.container.margin,
+ },
+ bg = widget_bg,
+ shape_border_width = widget_border_width,
+ shape_border_color = widget_border_color,
+ shape = helpers.shape.rrect(screen_radius),
+ widget = wibox.container.background,
+ }),
+ width = widget_width,
+ height = widget_height,
+ widget = wibox.container.constraint,
+ })
+
+ -- TODO: have something like a create callback here?
+
+ for _, w in ipairs(widget:get_children_by_id("image_role")) do
+ w.image = img -- TODO: copy it with gears.surface.xxx or something
+ end
+
+ for _, w in ipairs(widget:get_children_by_id("name_role")) do
+ w.text = c.name
+ end
+
+ for _, w in ipairs(widget:get_children_by_id("icon_role")) do
+ w.image = c.icon -- TODO: detect clienticon
+ end
+
+ return widget
+end
+
+local enable = function(opts)
+ local opts = opts or {}
+
+ local widget_x = opts.x or dpi(20)
+ local widget_y = opts.y or dpi(20)
+ local widget_height = opts.height or dpi(200)
+ local widget_width = opts.width or dpi(200)
+ local placement_fn = opts.placement_fn or nil
+
+ local margin = beautiful.task_preview_widget_margin or dpi(0)
+ local screen_radius = beautiful.task_preview_widget_border_radius or dpi(0)
+ local widget_bg = beautiful.task_preview_widget_bg or "#000000"
+ local widget_border_color = beautiful.task_preview_widget_border_color
+ or "#ffffff"
+ local widget_border_width = beautiful.task_preview_widget_border_width
+ or dpi(3)
+
+ local task_preview_box = awful.popup({
+ type = "dropdown_menu",
+ visible = false,
+ ontop = true,
+ placement = placement_fn,
+ widget = wibox.container.background, -- A dummy widget to make awful.popup not scream
+ input_passthrough = true,
+ bg = "#00000000",
+ })
+
+ tag.connect_signal("property::selected", function(t)
+ -- Awesome switches up tags on startup really fast it seems, probably depends on what rules you have set
+ -- which can cause the c.content to not show the correct image
+ gears.timer
+ {
+ timeout = 0.1,
+ call_now = false,
+ autostart = true,
+ single_shot = true,
+ callback = function()
+ if t.selected == true then
+ for _, c in ipairs(t:clients()) do
+ c.prev_content = gears.surface.duplicate_surface(c.content)
+ end
+ end
+ end
+ }
+ end)
+
+ awesome.connect_signal("bling::task_preview::visibility", function(s, v, c)
+ if v then
+ -- Update task preview contents
+ task_preview_box.widget = draw_widget(
+ c,
+ opts.structure,
+ screen_radius,
+ widget_bg,
+ widget_border_color,
+ widget_border_width,
+ margin,
+ widget_width,
+ widget_height
+ )
+ else
+ task_preview_box.widget = nil
+ collectgarbage("collect")
+ end
+
+ if not placement_fn then
+ task_preview_box.x = s.geometry.x + widget_x
+ task_preview_box.y = s.geometry.y + widget_y
+ end
+
+ task_preview_box.visible = v
+ end)
+end
+
+return { enable = enable, draw_widget = draw_widget }
diff --git a/.config/awesome/extras/bling/widget/window_switcher.lua b/.config/awesome/extras/bling/widget/window_switcher.lua
new file mode 100755
index 0000000..ac835a5
--- /dev/null
+++ b/.config/awesome/extras/bling/widget/window_switcher.lua
@@ -0,0 +1,454 @@
+local cairo = require("lgi").cairo
+local awful = require("awful")
+local gears = require("gears")
+local wibox = require("wibox")
+local beautiful = require("beautiful")
+local helpers = require(tostring(...):match(".*bling") .. ".helpers")
+local dpi = beautiful.xresources.apply_dpi
+
+local window_switcher_first_client -- The client that was focused when the window_switcher was activated
+local window_switcher_minimized_clients = {} -- The clients that were minimized when the window switcher was activated
+local window_switcher_grabber
+
+local get_num_clients = function()
+ local minimized_clients_in_tag = 0
+ local matcher = function(c)
+ return awful.rules.match(
+ c,
+ {
+ minimized = true,
+ skip_taskbar = false,
+ hidden = false,
+ first_tag = awful.screen.focused().selected_tag,
+ }
+ )
+ end
+ for c in awful.client.iterate(matcher) do
+ minimized_clients_in_tag = minimized_clients_in_tag + 1
+ end
+ return minimized_clients_in_tag + #awful.screen.focused().clients
+end
+
+local window_switcher_hide = function(window_switcher_box)
+ -- Add currently focused client to history
+ if client.focus then
+ local window_switcher_last_client = client.focus
+ awful.client.focus.history.add(window_switcher_last_client)
+ -- Raise client that was focused originally
+ -- Then raise last focused client
+ if
+ window_switcher_first_client and window_switcher_first_client.valid
+ then
+ window_switcher_first_client:raise()
+ window_switcher_last_client:raise()
+ end
+ end
+
+ -- Minimize originally minimized clients
+ local s = awful.screen.focused()
+ for _, c in pairs(window_switcher_minimized_clients) do
+ if c and c.valid and not (client.focus and client.focus == c) then
+ c.minimized = true
+ end
+ end
+ -- Reset helper table
+ window_switcher_minimized_clients = {}
+
+ -- Resume recording focus history
+ awful.client.focus.history.enable_tracking()
+ -- Stop and hide window_switcher
+ awful.keygrabber.stop(window_switcher_grabber)
+ window_switcher_box.visible = false
+ window_switcher_box.widget = nil
+ collectgarbage("collect")
+end
+
+local function draw_widget(
+ type,
+ background,
+ border_width,
+ border_radius,
+ border_color,
+ clients_spacing,
+ client_icon_horizontal_spacing,
+ client_width,
+ client_height,
+ client_margins,
+ thumbnail_margins,
+ thumbnail_scale,
+ name_margins,
+ name_valign,
+ name_forced_width,
+ name_font,
+ name_normal_color,
+ name_focus_color,
+ icon_valign,
+ icon_width,
+ mouse_keys
+)
+ local tasklist_widget = type == "thumbnail"
+ and awful.widget.tasklist({
+ screen = awful.screen.focused(),
+ filter = awful.widget.tasklist.filter.currenttags,
+ buttons = mouse_keys,
+ style = {
+ font = name_font,
+ fg_normal = name_normal_color,
+ fg_focus = name_focus_color,
+ },
+ layout = {
+ layout = wibox.layout.flex.horizontal,
+ spacing = clients_spacing,
+ },
+ widget_template = {
+ widget = wibox.container.background,
+ id = "bg_role",
+ forced_width = client_width,
+ forced_height = client_height,
+ create_callback = function(self, c, _, __)
+ local content = gears.surface(c.content)
+ local cr = cairo.Context(content)
+ local x, y, w, h = cr:clip_extents()
+ local img = cairo.ImageSurface.create(
+ cairo.Format.ARGB32,
+ w - x,
+ h - y
+ )
+ cr = cairo.Context(img)
+ cr:set_source_surface(content, 0, 0)
+ cr.operator = cairo.Operator.SOURCE
+ cr:paint()
+ self:get_children_by_id("thumbnail")[1].image =
+ gears.surface.load(
+ img
+ )
+ end,
+ {
+ {
+ {
+ horizontal_fit_policy = thumbnail_scale == true
+ and "fit"
+ or "auto",
+ vertical_fit_policy = thumbnail_scale == true
+ and "fit"
+ or "auto",
+ id = "thumbnail",
+ widget = wibox.widget.imagebox,
+ },
+ margins = thumbnail_margins,
+ widget = wibox.container.margin,
+ },
+ {
+ {
+ {
+ id = "icon_role",
+ widget = wibox.widget.imagebox,
+ },
+ forced_width = icon_width,
+ valign = icon_valign,
+ widget = wibox.container.place,
+ },
+ {
+ {
+ forced_width = name_forced_width,
+ valign = name_valign,
+ id = "text_role",
+ widget = wibox.widget.textbox,
+ },
+ margins = name_margins,
+ widget = wibox.container.margin,
+ },
+ spacing = client_icon_horizontal_spacing,
+ layout = wibox.layout.fixed.horizontal,
+ },
+ layout = wibox.layout.flex.vertical,
+ },
+ },
+ })
+ or awful.widget.tasklist({
+ screen = awful.screen.focused(),
+ filter = awful.widget.tasklist.filter.currenttags,
+ buttons = mouse_keys,
+ style = {
+ font = name_font,
+ fg_normal = name_normal_color,
+ fg_focus = name_focus_color,
+ },
+ layout = {
+ layout = wibox.layout.fixed.vertical,
+ spacing = clients_spacing,
+ },
+ widget_template = {
+ widget = wibox.container.background,
+ id = "bg_role",
+ forced_width = client_width,
+ forced_height = client_height,
+ {
+ {
+ {
+ id = "icon_role",
+ widget = wibox.widget.imagebox,
+ },
+ forced_width = icon_width,
+ valign = icon_valign,
+ widget = wibox.container.place,
+ },
+ {
+ {
+ forced_width = name_forced_width,
+ valign = name_valign,
+ id = "text_role",
+ widget = wibox.widget.textbox,
+ },
+ margins = name_margins,
+ widget = wibox.container.margin,
+ },
+ spacing = client_icon_horizontal_spacing,
+ layout = wibox.layout.fixed.horizontal,
+ },
+ },
+ })
+
+ return wibox.widget({
+ {
+ tasklist_widget,
+ margins = client_margins,
+ widget = wibox.container.margin,
+ },
+ shape_border_width = border_width,
+ shape_border_color = border_color,
+ bg = background,
+ shape = helpers.shape.rrect(border_radius),
+ widget = wibox.container.background,
+ })
+end
+
+local enable = function(opts)
+ local opts = opts or {}
+
+ local type = opts.type or "thumbnail"
+ local background = beautiful.window_switcher_widget_bg or "#000000"
+ local border_width = beautiful.window_switcher_widget_border_width or dpi(3)
+ local border_radius = beautiful.window_switcher_widget_border_radius
+ or dpi(0)
+ local border_color = beautiful.window_switcher_widget_border_color
+ or "#ffffff"
+ local clients_spacing = beautiful.window_switcher_clients_spacing or dpi(20)
+ local client_icon_horizontal_spacing = beautiful.window_switcher_client_icon_horizontal_spacing
+ or dpi(5)
+ local client_width = beautiful.window_switcher_client_width
+ or dpi(type == "thumbnail" and 150 or 500)
+ local client_height = beautiful.window_switcher_client_height
+ or dpi(type == "thumbnail" and 250 or 50)
+ local client_margins = beautiful.window_switcher_client_margins or dpi(10)
+ local thumbnail_margins = beautiful.window_switcher_thumbnail_margins
+ or dpi(5)
+ local thumbnail_scale = beautiful.thumbnail_scale or false
+ local name_margins = beautiful.window_switcher_name_margins or dpi(10)
+ local name_valign = beautiful.window_switcher_name_valign or "center"
+ local name_forced_width = beautiful.window_switcher_name_forced_width
+ or dpi(type == "thumbnail" and 200 or 550)
+ local name_font = beautiful.window_switcher_name_font or beautiful.font
+ local name_normal_color = beautiful.window_switcher_name_normal_color
+ or "#FFFFFF"
+ local name_focus_color = beautiful.window_switcher_name_focus_color
+ or "#FF0000"
+ local icon_valign = beautiful.window_switcher_icon_valign or "center"
+ local icon_width = beautiful.window_switcher_icon_width or dpi(40)
+
+ local hide_window_switcher_key = opts.hide_window_switcher_key or "Escape"
+
+ local select_client_key = opts.select_client_key or 1
+ local minimize_key = opts.minimize_key or "n"
+ local unminimize_key = opts.unminimize_key or "N"
+ local kill_client_key = opts.kill_client_key or "q"
+
+ local cycle_key = opts.cycle_key or "Tab"
+
+ local previous_key = opts.previous_key or "Left"
+ local next_key = opts.next_key or "Right"
+
+ local vim_previous_key = opts.vim_previous_key or "h"
+ local vim_next_key = opts.vim_next_key or "l"
+
+ local scroll_previous_key = opts.scroll_previous_key or 4
+ local scroll_next_key = opts.scroll_next_key or 5
+
+ local window_switcher_box = awful.popup({
+ bg = "#00000000",
+ visible = false,
+ ontop = true,
+ placement = awful.placement.centered,
+ screen = awful.screen.focused(),
+ widget = wibox.container.background, -- A dummy widget to make awful.popup not scream
+ widget = {
+ {
+ draw_widget(),
+ margins = client_margins,
+ widget = wibox.container.margin,
+ },
+ shape_border_width = border_width,
+ shape_border_color = border_color,
+ bg = background,
+ shape = helpers.shape.rrect(border_radius),
+ widget = wibox.container.background,
+ },
+ })
+
+ local mouse_keys = gears.table.join(
+ awful.button({
+ modifiers = { "Any" },
+ button = select_client_key,
+ on_press = function(c)
+ client.focus = c
+ end,
+ }),
+
+ awful.button({
+ modifiers = { "Any" },
+ button = scroll_previous_key,
+ on_press = function()
+ awful.client.focus.byidx(-1)
+ end,
+ }),
+
+ awful.button({
+ modifiers = { "Any" },
+ button = scroll_next_key,
+ on_press = function()
+ awful.client.focus.byidx(1)
+ end,
+ })
+ )
+
+ local keyboard_keys = {
+ [hide_window_switcher_key] = function()
+ window_switcher_hide(window_switcher_box)
+ end,
+
+ [minimize_key] = function()
+ if client.focus then
+ client.focus.minimized = true
+ end
+ end,
+ [unminimize_key] = function()
+ if awful.client.restore() then
+ client.focus = awful.client.restore()
+ end
+ end,
+ [kill_client_key] = function()
+ if client.focus then
+ client.focus:kill()
+ end
+ end,
+
+ [cycle_key] = function()
+ awful.client.focus.byidx(1)
+ end,
+
+ [previous_key] = function()
+ awful.client.focus.byidx(1)
+ end,
+ [next_key] = function()
+ awful.client.focus.byidx(-1)
+ end,
+
+ [vim_previous_key] = function()
+ awful.client.focus.byidx(1)
+ end,
+ [vim_next_key] = function()
+ awful.client.focus.byidx(-1)
+ end,
+ }
+
+ window_switcher_box:connect_signal("property::width", function()
+ if window_switcher_box.visible and get_num_clients() == 0 then
+ window_switcher_hide(window_switcher_box)
+ end
+ end)
+
+ window_switcher_box:connect_signal("property::height", function()
+ if window_switcher_box.visible and get_num_clients() == 0 then
+ window_switcher_hide(window_switcher_box)
+ end
+ end)
+
+ awesome.connect_signal("bling::window_switcher::turn_on", function()
+ local number_of_clients = get_num_clients()
+ if number_of_clients == 0 then
+ return
+ end
+
+ -- Store client that is focused in a variable
+ window_switcher_first_client = client.focus
+
+ -- Stop recording focus history
+ awful.client.focus.history.disable_tracking()
+
+ -- Go to previously focused client (in the tag)
+ awful.client.focus.history.previous()
+
+ -- Track minimized clients
+ -- Unminimize them
+ -- Lower them so that they are always below other
+ -- originally unminimized windows
+ local clients = awful.screen.focused().selected_tag:clients()
+ for _, c in pairs(clients) do
+ if c.minimized then
+ table.insert(window_switcher_minimized_clients, c)
+ c.minimized = false
+ c:lower()
+ end
+ end
+
+ -- Start the keygrabber
+ window_switcher_grabber = awful.keygrabber.run(function(_, key, event)
+ if event == "release" then
+ -- Hide if the modifier was released
+ -- We try to match Super or Alt or Control since we do not know which keybind is
+ -- used to activate the window switcher (the keybind is set by the user in keys.lua)
+ if
+ key:match("Super")
+ or key:match("Alt")
+ or key:match("Control")
+ then
+ window_switcher_hide(window_switcher_box)
+ end
+ -- Do nothing
+ return
+ end
+
+ -- Run function attached to key, if it exists
+ if keyboard_keys[key] then
+ keyboard_keys[key]()
+ end
+ end)
+
+ window_switcher_box.widget = draw_widget(
+ type,
+ background,
+ border_width,
+ border_radius,
+ border_color,
+ clients_spacing,
+ client_icon_horizontal_spacing,
+ client_width,
+ client_height,
+ client_margins,
+ thumbnail_margins,
+ thumbnail_scale,
+ name_margins,
+ name_valign,
+ name_forced_width,
+ name_font,
+ name_normal_color,
+ name_focus_color,
+ icon_valign,
+ icon_width,
+ mouse_keys
+ )
+ window_switcher_box.visible = true
+ end)
+end
+
+return { enable = enable }
diff --git a/.config/awesome/extras/nice b/.config/awesome/extras/nice
new file mode 160000
index 0000000..810aa72
--- /dev/null
+++ b/.config/awesome/extras/nice
@@ -0,0 +1 @@
+Subproject commit 810aa72bbebfee15d3375fdcb8d8a09f5c7741c8