diff --git a/bling-scm-5.rockspec b/bling-dev-1.rockspec
similarity index 66%
rename from bling-scm-5.rockspec
rename to bling-dev-1.rockspec
index 2dbb36c..575d3f0 100644
--- a/bling-scm-5.rockspec
+++ b/bling-dev-1.rockspec
@@ -1,19 +1,19 @@
package = "bling"
-version = "scm-5"
+version = "dev-1"
source = {
- url = "git://github.com/Nooo37/bling",
+ url = "git://github.com/BlingCorp/bling",
branch = "master",
}
description = {
summary = "Utilities for the AwesomeWM",
detailed = [[
- This module extends the Awesome window manager with alternative layouts,
- flash focus, tabbing, a simple tiling wallpaper generator, a declarative
+ This module extends the Awesome window manager with alternative layouts,
+ flash focus, tabbing, a simple tiling wallpaper generator, a declarative
wallpaper setter, window swallowing and a playerctl signal.
]],
- homepage = "https://github.com/Nooo37/bling",
+ homepage = "https://github.com/BlingCorp/bling",
license = "MIT",
}
@@ -23,32 +23,41 @@ dependencies = {
build = {
type = "builtin",
- modules = {
+ modules = {
["bling"] = "init.lua",
- ["bling.layout"] = "layout/init.lua",
- ["bling.layout.centered"] = "layout/centered.lua",
- ["bling.layout.equalarea"] = "layout/equalarea.lua",
- ["bling.layout.horizontal"] = "layout/horizontal.lua",
- ["bling.layout.mstab"] = "layout/mstab.lua",
- ["bling.layout.vertical"] = "layout/vertical.lua",
["bling.helpers"] = "helpers/init.lua",
["bling.helpers.client"] = "helpers/client.lua",
["bling.helpers.color"] = "helpers/color.lua",
["bling.helpers.filesystem"] = "helpers/filesystem.lua",
["bling.helpers.shape"] = "helpers/shape.lua",
["bling.helpers.time"] = "helpers/time.lua",
+ ["bling.layout"] = "layout/init.lua",
+ ["bling.layout.centered"] = "layout/centered.lua",
+ ["bling.layout.deck"] = "layout/deck.lua",
+ ["bling.layout.equalarea"] = "layout/equalarea.lua",
+ ["bling.layout.horizontal"] = "layout/horizontal.lua",
+ ["bling.layout.mstab"] = "layout/mstab.lua",
+ ["bling.layout.vertical"] = "layout/vertical.lua",
["bling.module"] = "module/init.lua",
["bling.module.flash_focus"] = "module/flash_focus.lua",
+ ["bling.module.scratchpad"] = "module/scratchpad.lua",
["bling.module.tabbed"] = "module/tabbed.lua",
["bling.module.tiled_wallpaper"] = "module/tiled_wallpaper.lua",
["bling.module.wallpaper"] = "module/wallpaper.lua",
["bling.module.window_swallowing"] = "module/window_swallowing.lua",
["bling.signal"] = "signal/init.lua",
- ["bling.signal.playerctl"] = "signal/playerctl.lua",
+ ["bling.signal.playerctl"] = "signal/playerctl/init.lua",
+ ["bling.signal.playerctl.playerctl_cli"] = "signal/playerctl/playerctl_cli.lua",
+ ["bling.signal.playerctl.playerctl_lib"] = "signal/playerctl/playerctl_lib.lua",
+ ["bling.widget"] = "widget/init.lua",
["bling.widget.tabbar.boxes"] = "widget/tabbar/boxes.lua",
["bling.widget.tabbar.default"] = "widget/tabbar/default.lua",
["bling.widget.tabbar.modern"] = "widget/tabbar/modern.lua",
+ ["bling.widget.tabbed_misc"] = "widget/tabbed_misc/init.lua",
+ ["bling.widget.tabbed_misc.custom_tasklist"] = "widget/tabbed_misc/custom_tasklist.lua",
+ ["bling.widget.tabbed_misc.titlebar_indicator"] = "widget/tabbed_misc/titlebar_indicator.lua",
["bling.widget.tag_preview"] = "widget/tag_preview.lua",
- ["bling.widget"] = "widget/init.lua",
+ ["bling.widget.task_preview"] = "widget/task_preview.lua",
+ ["bling.widget.window_switcher"] = "widget/window_switcher.lua",
},
}
diff --git a/docs/_sidebar.md b/docs/_sidebar.md
index c7fc2b1..9216d34 100644
--- a/docs/_sidebar.md
+++ b/docs/_sidebar.md
@@ -17,6 +17,7 @@
- [Tag Preview](widgets/tag_preview.md)
- [Task Preview](widgets/task_preview.md)
- [Tabbed Misc](widgets/tabbed_misc.md)
+ - [Window Switcher](widgets/window_switcher.md)
- Extra
- [Theme Variable Template](theme.md)
diff --git a/docs/layouts/layout.md b/docs/layouts/layout.md
index a3a697d..5a9bcd6 100644
--- a/docs/layouts/layout.md
+++ b/docs/layouts/layout.md
@@ -6,7 +6,7 @@ Everyone of them supports multiple master clients and master width factor making
The mstab layout uses the tab theme from the tabbed module.
-```Lua
+```lua
bling.layout.mstab
bling.layout.centered
bling.layout.vertical
@@ -16,36 +16,40 @@ bling.layout.deck
```
### Theme Variables
+
```lua
-- mstab
-theme.mstab_bar_ontop = false -- whether you want to allow the bar to be ontop of clients
-theme.mstab_dont_resize_slaves = false -- whether the tabbed stack windows should be smaller than the
- -- currently focused stack window (set it to true if you use
- -- transparent terminals. False if you use shadows on solid ones
-theme.mstab_bar_padding = "default" -- how much padding there should be between clients and your tabbar
- -- by default it will adjust based on your useless gaps.
- -- If you want a custom value. Set it to the number of pixels (int)
-theme.mstab_border_radius = 0 -- border radius of the tabbar
-theme.mstab_bar_height = 40 -- height of the tabbar
-theme.mstab_tabbar_position = "top" -- position of the tabbar (mstab currently does not support left,right)
-theme.mstab_tabbar_style = "default" -- style of the tabbar ("default", "boxes" or "modern")
- -- defaults to the tabbar_style so only change if you want a
- -- different style for mstab and tabbed
+theme.mstab_bar_ontop = false -- whether you want to allow the bar to be ontop of clients
+theme.mstab_dont_resize_slaves = false -- whether the tabbed stack windows should be smaller than the
+ -- currently focused stack window (set it to true if you use
+ -- transparent terminals. False if you use shadows on solid ones
+theme.mstab_bar_padding = "default" -- how much padding there should be between clients and your tabbar
+ -- by default it will adjust based on your useless gaps.
+ -- If you want a custom value. Set it to the number of pixels (int)
+theme.mstab_border_radius = 0 -- border radius of the tabbar
+theme.mstab_bar_height = 40 -- height of the tabbar
+theme.mstab_tabbar_position = "top" -- position of the tabbar (mstab currently does not support left,right)
+theme.mstab_tabbar_style = "default" -- style of the tabbar ("default", "boxes" or "modern")
+ -- defaults to the tabbar_style so only change if you want a
+ -- different style for mstab and tabbed
```
### Previews
#### Mstab (dynamic tabbing layout)
+
![](https://imgur.com/HZRgApE.png)
-*screenshot by [javacafe](https://github.com/JavaCafe01)*
+*screenshot by [JavaCafe01](https://github.com/JavaCafe01)*
#### Centered
+
![](https://media.discordapp.net/attachments/769673106842845194/780095998239834142/unknown.png)
-*screenshot by [branwright](https://github.com/branwright1)*
+*screenshot by [HeavyRain266](https://github.com/HeavyRain266)*
#### Equal area
+
![](https://imgur.com/JCFFywv.png)
*screenshot by [bysmutheye](https://github.com/bysmutheye)*
@@ -56,4 +60,5 @@ The left area shows the deck layout in action. In this screenshot it is used tog
![](https://cdn.discordapp.com/attachments/635625954219261982/877957824225894430/unknown.png)
-*screenshot by [javacafe](https://github.com/JavaCafe01)*
+*screenshot by [JavaCafe01](https://github.com/JavaCafe01)*
+
diff --git a/docs/module/flash.md b/docs/module/flash.md
index 00f1582..a12b78d 100644
--- a/docs/module/flash.md
+++ b/docs/module/flash.md
@@ -22,8 +22,8 @@ awful.key({modkey}, "Up",
### Theme Variables
```lua
-theme.flash_focus_start_opacity = 0.6 -- the starting opacity
-theme.flash_focus_step = 0.01 -- the step of animation
+theme.flash_focus_start_opacity = 0.6 -- the starting opacity
+theme.flash_focus_step = 0.01 -- the step of animation
```
### Preview
diff --git a/docs/module/scratch.md b/docs/module/scratch.md
index 8259870..54f8ddd 100644
--- a/docs/module/scratch.md
+++ b/docs/module/scratch.md
@@ -18,7 +18,7 @@ To initalize a scratchpad you can do something like the following:
```lua
local bling = require("bling")
-local rubato = require("rubato") -- Totally optional, only required if you are using animations.
+local rubato = require("rubato") -- Totally optional, only required if you are using animations.
-- These are example rubato tables. You can use one for just y, just x, or both.
-- The duration and easing is up to you. Please check out the rubato docs to learn more.
@@ -28,7 +28,7 @@ local anim_y = rubato.timed {
easing = rubato.quadratic,
intro = 0.1,
duration = 0.3,
- awestore_compat = true -- This option must be set to true.
+ awestore_compat = true -- This option must be set to true.
}
local anim_x = rubato.timed {
@@ -37,7 +37,7 @@ local anim_x = rubato.timed {
easing = rubato.quadratic,
intro = 0.1,
duration = 0.3,
- awestore_compat = true -- This option must be set to true.
+ awestore_compat = true -- This option must be set to true.
}
local term_scratch = bling.module.scratchpad {
@@ -49,7 +49,7 @@ local term_scratch = bling.module.scratchpad {
geometry = {x=360, y=90, height=900, width=1200}, -- The geometry in a floating state
reapply = true, -- Whether all those properties should be reapplied on every new opening of the scratchpad (MUST BE TRUE FOR ANIMATIONS)
dont_focus_before_close = false, -- When set to true, the scratchpad will be closed by the toggle function regardless of whether its focused or not. When set to false, the toggle function will first bring the scratchpad into focus and only close it on a second call
- rubato = {x = anim_x, y = anim_y} -- Optional. This is how you can pass in the rubato tables for animations. If you don't want animations, you can ignore this option.
+ rubato = {x = anim_x, y = anim_y} -- Optional. This is how you can pass in the rubato tables for animations. If you don't want animations, you can ignore this option.
}
```
diff --git a/docs/module/swal.md b/docs/module/swal.md
index ad40c0f..ae339fc 100644
--- a/docs/module/swal.md
+++ b/docs/module/swal.md
@@ -6,15 +6,15 @@ Can your window manager swallow? It probably can...
To activate and deactivate window swallowing here are the following functions. If you want to activate it, just call the `start` function once in your `rc.lua`.
```lua
-bling.module.window_swallowing.start() -- activates window swallowing
-bling.module.window_swallowing.stop() -- deactivates window swallowing
-bling.module.window_swallowing.toggle() -- toggles window swallowing
+bling.module.window_swallowing.start() -- activates window swallowing
+bling.module.window_swallowing.stop() -- deactivates window swallowing
+bling.module.window_swallowing.toggle() -- toggles window swallowing
```
### Theme Variables
```lua
-theme.dont_swallow_classname_list = {"firefox", "Gimp"} -- list of class names that should not be swallowed
-theme.dont_swallow_filter_activated = true -- whether the filter above should be active
+theme.dont_swallow_classname_list = {"firefox", "Gimp"} -- list of class names that should not be swallowed
+theme.dont_swallow_filter_activated = true -- whether the filter above should be active
```
### Preview
diff --git a/docs/module/tabbed.md b/docs/module/tabbed.md
index 985fca3..e705357 100644
--- a/docs/module/tabbed.md
+++ b/docs/module/tabbed.md
@@ -6,10 +6,10 @@ Tabbed implements a tab container. There are also different themes for the tabs.
You should bind these functions to keys in order to use the tabbed module effectively:
```lua
-bling.module.tabbed.pick() -- picks a client with your cursor to add to the tabbing group
-bling.module.tabbed.pop() -- removes the focused client from the tabbing group
-bling.module.tabbed.iter() -- iterates through the currently focused tabbing group
-bling.module.tabbed.pick_with_dmenu() -- picks a client with a dmenu application (defaults to rofi, other options can be set with a string parameter like "dmenu")
+bling.module.tabbed.pick() -- picks a client with your cursor to add to the tabbing group
+bling.module.tabbed.pop() -- removes the focused client from the tabbing group
+bling.module.tabbed.iter() -- iterates through the currently focused tabbing group
+bling.module.tabbed.pick_with_dmenu() -- picks a client with a dmenu application (defaults to rofi, other options can be set with a string parameter like "dmenu")
bling.module.tabbed.pick_by_direction(dir) -- picks a client based on direction ("up", "down", "left" or "right")
```
@@ -17,25 +17,29 @@ bling.module.tabbed.pick_by_direction(dir) -- picks a client based on direction
```lua
-- For tabbed only
-theme.tabbed_spawn_in_tab = false -- whether a new client should spawn into the focused tabbing container
+theme.tabbed_spawn_in_tab = false -- whether a new client should spawn into the focused tabbing container
-- For tabbar in general
theme.tabbar_ontop = false
-theme.tabbar_radius = 0 -- border radius of the tabbar
-theme.tabbar_style = "default" -- style of the tabbar ("default", "boxes" or "modern")
-theme.tabbar_font = "Sans 11" -- font of the tabbar
-theme.tabbar_size = 40 -- size of the tabbar
-theme.tabbar_position = "top" -- position of the tabbar
-theme.tabbar_bg_normal = "#000000" -- background color of the focused client on the tabbar
-theme.tabbar_fg_normal = "#ffffff" -- foreground color of the focused client on the tabbar
-theme.tabbar_bg_focus = "#1A2026" -- background color of unfocused clients on the tabbar
-theme.tabbar_fg_focus = "#ff0000" -- foreground color of unfocused clients on the tabbar
-theme.tabbar_disable = false -- disable the tab bar entirely
+theme.tabbar_radius = 0 -- border radius of the tabbar
+theme.tabbar_style = "default" -- style of the tabbar ("default", "boxes" or "modern")
+theme.tabbar_font = "Sans 11" -- font of the tabbar
+theme.tabbar_size = 40 -- size of the tabbar
+theme.tabbar_position = "top" -- position of the tabbar
+theme.tabbar_bg_normal = "#000000" -- background color of the focused client on the tabbar
+theme.tabbar_fg_normal = "#ffffff" -- foreground color of the focused client on the tabbar
+theme.tabbar_bg_focus = "#1A2026" -- background color of unfocused clients on the tabbar
+theme.tabbar_fg_focus = "#ff0000" -- foreground color of unfocused clients on the tabbar
+theme.tabbar_bg_focus_inactive = nil -- background color of the focused client on the tabbar when inactive
+theme.tabbar_fg_focus_inactive = nil -- foreground color of the focused client on the tabbar when inactive
+theme.tabbar_bg_normal_inactive = nil -- background color of unfocused clients on the tabbar when inactive
+theme.tabbar_fg_normal_inactive = nil -- foreground color of unfocused clients on the tabbar when inactive
+theme.tabbar_disable = false -- disable the tab bar entirely
-- the following variables are currently only for the "modern" tabbar style
-theme.tabbar_color_close = "#f9929b" -- chnges the color of the close button
-theme.tabbar_color_min = "#fbdf90" -- chnges the color of the minimize button
-theme.tabbar_color_float = "#ccaced" -- chnges the color of the float button
+theme.tabbar_color_close = "#f9929b" -- chnges the color of the close button
+theme.tabbar_color_min = "#fbdf90" -- chnges the color of the minimize button
+theme.tabbar_color_float = "#ccaced" -- chnges the color of the float button
```
### Preview
@@ -44,19 +48,19 @@ Modern theme:
-*screenshot by [javacafe](https://github.com/JavaCafe01)*
+*screenshot by [JavaCafe01](https://github.com/JavaCafe01)*
### Signals
The tabbed module emits a few signals for the purpose of integration,
```lua
-- bling::tabbed::update -- triggered whenever a tabbed object is updated
--- tabobj -- the object that caused the update
+-- tabobj -- the object that caused the update
-- bling::tabbed::client_added -- triggered whenever a new client is added to a tab group
--- tabobj -- the object that the client was added to
--- client -- the client that added
+-- tabobj -- the object that the client was added to
+-- client -- the client that added
-- bling::tabbed::client_removed -- triggered whenever a client is removed from a tab group
--- tabobj -- the object that the client was removed from
--- client -- the client that was removed
+-- tabobj -- the object that the client was removed from
+-- client -- the client that was removed
-- bling::tabbed::changed_focus -- triggered whenever a tab group's focus is changed
--- tabobj -- the modified tab group
+-- tabobj -- the modified tab group
```
diff --git a/docs/module/twall.md b/docs/module/twall.md
index a6d31af..69c09c3 100644
--- a/docs/module/twall.md
+++ b/docs/module/twall.md
@@ -4,16 +4,16 @@
The function to set an automatically created tiled wallpaper can be called the following way (you don't need to set every option in the table):
```lua
-awful.screen.connect_for_each_screen(function(s) -- that way the wallpaper is applied to every screen
- bling.module.tiled_wallpaper("x", s, { -- call the actual function ("x" is the string that will be tiled)
- fg = "#ff0000", -- define the foreground color
- bg = "#00ffff", -- define the background color
- offset_y = 25, -- set a y offset
- offset_x = 25, -- set a x offset
- font = "Hack", -- set the font (without the size)
- font_size = 14, -- set the font size
- padding = 100, -- set padding (default is 100)
- zickzack = true -- rectangular pattern or criss cross
+awful.screen.connect_for_each_screen(function(s) -- that way the wallpaper is applied to every screen
+ bling.module.tiled_wallpaper("x", s, { -- call the actual function ("x" is the string that will be tiled)
+ fg = "#ff0000", -- define the foreground color
+ bg = "#00ffff", -- define the background color
+ offset_y = 25, -- set a y offset
+ offset_x = 25, -- set a x offset
+ font = "Hack", -- set the font (without the size)
+ font_size = 14, -- set the font size
+ padding = 100, -- set padding (default is 100)
+ zickzack = true -- rectangular pattern or criss cross
})
end)
```
@@ -23,3 +23,4 @@ end)
![](https://media.discordapp.net/attachments/702548913999314964/773887721294135296/tiled-wallpapers.png?width=1920&height=1080)
*screenshots by [Nooo37](https://github.com/Nooo37)*
+
diff --git a/docs/module/wall.md b/docs/module/wall.md
index ea87f52..0869166 100644
--- a/docs/module/wall.md
+++ b/docs/module/wall.md
@@ -18,7 +18,7 @@ bling.module.wallpaper.setup {
bling.module.wallpaper.setup {
set_function = bling.module.wallpaper.setters.random,
wallpaper = {"/path/to/a/folder", "/path/to/another/folder"},
- change_timer = 631, -- prime numbers are better for timers
+ change_timer = 631, -- prime numbers are better for timers
position = "fit",
background = "#424242"
}
@@ -99,14 +99,14 @@ Here are the defaults:
```lua
-- Default parameters
bling.module.wallpaper.setup {
- screen = nil, -- the screen to apply the wallpaper, as seen in gears.wallpaper functions
- change_timer = nil, -- the timer in seconds. If set, call the set_function every change_timer seconds
- set_function = nil, -- the setter function
+ screen = nil, -- the screen to apply the wallpaper, as seen in gears.wallpaper functions
+ change_timer = nil, -- the timer in seconds. If set, call the set_function every change_timer seconds
+ set_function = nil, -- the setter function
-- parameters used by bling.module.wallpaper.prepare_list
- wallpaper = nil, -- the wallpaper object, see simple or simple_schedule documentation
- image_formats = {"jpg", "jpeg", "png", "bmp"}, -- when searching in folder, consider these files only
- recursive = true, -- when searching in folder, search also in subfolders
+ wallpaper = nil, -- the wallpaper object, see simple or simple_schedule documentation
+ image_formats = {"jpg", "jpeg", "png", "bmp"}, -- when searching in folder, consider these files only
+ recursive = true, -- when searching in folder, search also in subfolders
-- parameters used by bling.module.wallpaper.apply
position = nil, -- use a function of gears.wallpaper when applicable ("centered", "fit", "maximized", "tiled")
@@ -116,8 +116,8 @@ bling.module.wallpaper.setup {
scale = 1, -- see gears.wallpaper.centered
-- parameters that only apply to bling.module.wallpaper.setter.awesome (as a setter or as a wallpaper function)
- colors = { -- see beautiful.theme_assets.wallpaper
- bg = beautiful.bg_color, -- the actual default is this color but darkened or lightned
+ colors = { -- see beautiful.theme_assets.wallpaper
+ bg = beautiful.bg_color, -- the actual default is this color but darkened or lightned
fg = beautiful.fg_color,
alt_fg = beautiful.fg_focus
}
diff --git a/docs/signals/pctl.md b/docs/signals/pctl.md
index edce9b0..9fb09c6 100644
--- a/docs/signals/pctl.md
+++ b/docs/signals/pctl.md
@@ -24,20 +24,20 @@ To disable: `bling.signal.playerctl.disable()`
Here are the signals available:
```lua
--- bling::playerctl::status -- first line is the signal
--- playing (boolean) -- indented lines are function parameters
--- player_name (string)
+-- bling::playerctl::status -- first line is the signal
+-- playing (boolean) -- indented lines are function parameters
+-- player_name (string)
-- bling::playerctl::title_artist_album
--- title (string)
--- artist (string)
--- album_path (string)
--- player_name (string)
+-- title (string)
+-- artist (string)
+-- album_path (string)
+-- player_name (string)
-- bling::playerctl::position
--- interval_sec (number)
--- length_sec (number)
--- player_name (string)
+-- interval_sec (number)
+-- length_sec (number)
+-- player_name (string)
-- bling::playerctl::no_players
--- (No parameters)
+-- (No parameters)
```
### Example Implementation
@@ -122,8 +122,8 @@ By default, this module will output signals from the most recently active player
These options can be set through a call to `bling.signal.playerctl.enable()` or these theme variables:
```lua
theme.playerctl_backend = "playerctl_cli"
-theme.playerctl_ignore = {}
-theme.playerctl_player = {}
+theme.playerctl_ignore = {}
+theme.playerctl_player = {}
theme.playerctl_update_on_activity = true
theme.playerctl_position_update_interval = 1
```
@@ -140,12 +140,12 @@ bling.signal.playerctl.enable {
-- OR in your theme file:
-- Same config as above but with theme variables
theme.playerctl_backend = "playerctl_lib"
-theme.playerctl_ignore = "firefox"
-theme.playerctl_player = {"ncspot", "%any"}
+theme.playerctl_ignore = "firefox"
+theme.playerctl_player = {"ncspot", "%any"}
-- Prioritize vlc over all other players and deprioritize spotify
theme.playerctl_backend = "playerctl_lib"
-theme.playerctl_player = {"vlc", "%any", "spotify"}
+theme.playerctl_player = {"vlc", "%any", "spotify"}
-- Disable priority of most recently active players
theme.playerctl_backend = "playerctl_lib"
diff --git a/docs/theme.md b/docs/theme.md
index 916f839..ea885ff 100644
--- a/docs/theme.md
+++ b/docs/theme.md
@@ -26,15 +26,20 @@ theme.tabbed_spawn_in_tab = false -- whether a new client should spawn
-- tabbar general
theme.tabbar_ontop = false
-theme.tabbar_radius = 0 -- border radius of the tabbar
-theme.tabbar_style = "default" -- style of the tabbar ("default", "boxes" or "modern")
-theme.tabbar_font = "Sans 11" -- font of the tabbar
-theme.tabbar_size = 40 -- size of the tabbar
-theme.tabbar_position = "top" -- position of the tabbar
-theme.tabbar_bg_normal = "#000000" -- background color of the focused client on the tabbar
-theme.tabbar_fg_normal = "#ffffff" -- foreground color of the focused client on the tabbar
-theme.tabbar_bg_focus = "#1A2026" -- background color of unfocused clients on the tabbar
-theme.tabbar_fg_focus = "#ff0000" -- foreground color of unfocused clients on the tabbar
+theme.tabbar_radius = 0 -- border radius of the tabbar
+theme.tabbar_style = "default" -- style of the tabbar ("default", "boxes" or "modern")
+theme.tabbar_font = "Sans 11" -- font of the tabbar
+theme.tabbar_size = 40 -- size of the tabbar
+theme.tabbar_position = "top" -- position of the tabbar
+theme.tabbar_bg_normal = "#000000" -- background color of the focused client on the tabbar
+theme.tabbar_fg_normal = "#ffffff" -- foreground color of the focused client on the tabbar
+theme.tabbar_bg_focus = "#1A2026" -- background color of unfocused clients on the tabbar
+theme.tabbar_fg_focus = "#ff0000" -- foreground color of unfocused clients on the tabbar
+theme.tabbar_bg_focus_inactive = nil -- background color of the focused client on the tabbar when inactive
+theme.tabbar_fg_focus_inactive = nil -- foreground color of the focused client on the tabbar when inactive
+theme.tabbar_bg_normal_inactive = nil -- background color of unfocused clients on the tabbar when inactive
+theme.tabbar_fg_normal_inactive = nil -- foreground color of unfocused clients on the tabbar when inactive
+theme.tabbar_disable = false -- disable the tab bar entirely
-- mstab
theme.mstab_bar_ontop = false -- whether you want to allow the bar to be ontop of clients
@@ -87,4 +92,25 @@ theme.bling_tabbed_misc_titlebar_indicator = {
end,
layout = wibox.layout.fixed.horizontal
}
+
+-- window switcher widget
+theme.window_switcher_widget_bg = "#000000" -- The bg color of the widget
+theme.window_switcher_widget_border_width = 3 -- The border width of the widget
+theme.window_switcher_widget_border_radius = 0 -- The border radius of the widget
+theme.window_switcher_widget_border_color = "#ffffff" -- The border color of the widget
+theme.window_switcher_clients_spacing = 20 -- The space between each client item
+theme.window_switcher_client_icon_horizontal_spacing = 5 -- The space between client icon and text
+theme.window_switcher_client_width = 150 -- The width of one client widget
+theme.window_switcher_client_height = 250 -- The height of one client widget
+theme.window_switcher_client_margins = 10 -- The margin between the content and the border of the widget
+theme.window_switcher_thumbnail_margins = 10 -- The margin between one client thumbnail and the rest of the widget
+theme.thumbnail_scale = false -- If set to true, the thumbnails fit policy will be set to "fit" instead of "auto"
+theme.window_switcher_name_margins = 10 -- The margin of one clients title to the rest of the widget
+theme.window_switcher_name_valign = "center" -- How to vertically align one clients title
+theme.window_switcher_name_forced_width = 200 -- The width of one title
+theme.window_switcher_name_font = "sans 11" -- The font of all titles
+theme.window_switcher_name_normal_color = "#ffffff" -- The color of one title if the client is unfocused
+theme.window_switcher_name_focus_color = "#ff0000" -- The color of one title if the client is focused
+theme.window_switcher_icon_valign = "center" -- How to vertically align the one icon
+theme.window_switcher_icon_width = 40 -- The width of one icon
```
diff --git a/docs/widgets/tabbed_misc.md b/docs/widgets/tabbed_misc.md
index 2d5f788..89c4557 100644
--- a/docs/widgets/tabbed_misc.md
+++ b/docs/widgets/tabbed_misc.md
@@ -18,8 +18,8 @@ To use the task list indicator:
bling.widget.tabbed_misc.titlebar_indicator(client, {
layout = wibox.layout.fixed.vertical,
layout_spacing = dpi(5), -- Set spacing in between items
- icon_size = dpi(24),
- icon_margin = 0,
+ icon_size = dpi(24), -- Set icon size
+ icon_margin = 0, -- Set icon margin
fg_color = "#cccccc", -- Normal color for text
fg_color_focus = "#ffffff", -- Color for focused text
bg_color_focus = "#282828", -- Color for the focused items
@@ -80,32 +80,32 @@ The module exports a function that can be added to your tasklist as a `update_ca
### Usage
```lua
awful.widget.tasklist({
- screen = s,
- filter = awful.widget.tasklist.filter.currenttags,
- layout = {
- spacing = dpi(10),
- layout = wibox.layout.fixed.vertical,
- },
- style = {
- bg_normal = "#00000000",
- },
- widget_template = {
- {
- {
- widget = wibox.widget.imagebox,
- id = "icon_role",
- align = "center",
- valign = "center",
- },
- width = dpi(24),
- height = dpi(24),
- widget = wibox.container.constraint,
- },
- widget = wibox.container.background, -- IT MUST BE A CONTAINER WIDGET AS THAT IS WHAT THE FUNCTION EXPECTS
- update_callback = require("bling.widget.tabbed_misc").custom_tasklist,
- id = "background_role",
- },
- })
+ screen = s,
+ filter = awful.widget.tasklist.filter.currenttags,
+ layout = {
+ spacing = dpi(10),
+ layout = wibox.layout.fixed.vertical,
+ },
+ style = {
+ bg_normal = "#00000000",
+ },
+ widget_template = {
+ {
+ {
+ widget = wibox.widget.imagebox,
+ id = "icon_role",
+ align = "center",
+ valign = "center",
+ },
+ width = dpi(24),
+ height = dpi(24),
+ widget = wibox.container.constraint,
+ },
+ widget = wibox.container.background, -- IT MUST BE A CONTAINER WIDGET AS THAT IS WHAT THE FUNCTION EXPECTS
+ update_callback = require("bling.widget.tabbed_misc").custom_tasklist,
+ id = "background_role",
+ },
+})
```
If you need to do something else, it can be used like so
diff --git a/docs/widgets/tag_preview.md b/docs/widgets/tag_preview.md
index 9251253..bdf033d 100644
--- a/docs/widgets/tag_preview.md
+++ b/docs/widgets/tag_preview.md
@@ -2,7 +2,7 @@
This is a popup widget that will show a preview of a specified tag that illustrates the position, size, content, and icon of all clients.
-![](https://imgur.com/3nYe1e8.gif)
+![](https://imgur.com/zFdvs4K.gif)
*gif by [javacafe](https://github.com/JavaCafe01)*
@@ -24,19 +24,25 @@ bling.widget.tag_preview.enable {
top = 30,
left = 30
}
- })
- end
-}
+ })
+ end,
+ background_widget = wibox.widget { -- Set a background image (like a wallpaper) for the widget
+ image = beautiful.wallpaper,
+ horizontal_fit_policy = "fit",
+ vertical_fit_policy = "fit",
+ widget = wibox.widget.imagebox
+ }
+}
```
Here are the signals available:
```lua
--- bling::tag_preview::update -- first line is the signal
--- t (tag) -- indented lines are function parameters
+-- bling::tag_preview::update -- first line is the signal
+-- t (tag) -- indented lines are function parameters
-- bling::tag_preview::visibility
--- s (screen)
--- v (boolean)
+-- s (screen)
+-- v (boolean)
```
By default, the widget is not visible. You must implement when it will update and when it will show.
@@ -100,7 +106,7 @@ s.mytaglist = awful.widget.taglist {
create_callback = function(self, c3, index, objects) --luacheck: no unused args
self:get_children_by_id('index_role')[1].markup = ' '..index..' '
self:connect_signal('mouse::enter', function()
-
+
-- BLING: Only show widget when there are clients in the tag
if #c3:clients() > 0 then
-- BLING: Update the widget with the new tag
@@ -119,7 +125,7 @@ s.mytaglist = awful.widget.taglist {
-- BLING: Turn the widget off
awesome.emit_signal("bling::tag_preview::visibility", s, false)
-
+
if self.has_backup then self.bg = self.backup end
end)
end,
@@ -132,17 +138,18 @@ s.mytaglist = awful.widget.taglist {
```
### Theme Variables
+
```lua
-theme.tag_preview_widget_border_radius = 0 -- Border radius of the widget (With AA)
-theme.tag_preview_client_border_radius = 0 -- Border radius of each client in the widget (With AA)
-theme.tag_preview_client_opacity = 0.5 -- Opacity of each client
-theme.tag_preview_client_bg = "#000000" -- The bg color of each client
-theme.tag_preview_client_border_color = "#ffffff" -- The border color of each client
-theme.tag_preview_client_border_width = 3 -- The border width of each client
-theme.tag_preview_widget_bg = "#000000" -- The bg color of the widget
-theme.tag_preview_widget_border_color = "#ffffff" -- The border color of the widget
-theme.tag_preview_widget_border_width = 3 -- The border width of the widget
-theme.tag_preview_widget_margin = 0 -- The margin of the widget
+theme.tag_preview_widget_border_radius = 0 -- Border radius of the widget (With AA)
+theme.tag_preview_client_border_radius = 0 -- Border radius of each client in the widget (With AA)
+theme.tag_preview_client_opacity = 0.5 -- Opacity of each client
+theme.tag_preview_client_bg = "#000000" -- The bg color of each client
+theme.tag_preview_client_border_color = "#ffffff" -- The border color of each client
+theme.tag_preview_client_border_width = 3 -- The border width of each client
+theme.tag_preview_widget_bg = "#000000" -- The bg color of the widget
+theme.tag_preview_widget_border_color = "#ffffff" -- The border color of the widget
+theme.tag_preview_widget_border_width = 3 -- The border width of the widget
+theme.tag_preview_widget_margin = 0 -- The margin of the widget
```
NOTE: I recommend to only use the widget border radius theme variable when not using shadows with a compositor, as anti-aliased rounding with the outer widgets made with AwesomeWM rely on the actual bg being transparent. If you want rounding with shadows on the widget, use a compositor like [jonaburg's fork](https://github.com/jonaburg/picom).
diff --git a/docs/widgets/task_preview.md b/docs/widgets/task_preview.md
index 136a207..da81f5e 100644
--- a/docs/widgets/task_preview.md
+++ b/docs/widgets/task_preview.md
@@ -12,46 +12,46 @@ To enable:
```lua
bling.widget.task_preview.enable {
- x = 20, -- The x-coord of the popup
- y = 20, -- The y-coord of the popup
- height = 200, -- The height of the popup
- width = 200, -- The width of the popup
- placement_fn = function(c) -- Place the widget using awful.placement (this overrides x & y)
+ x = 20, -- The x-coord of the popup
+ y = 20, -- The y-coord of the popup
+ height = 200, -- The height of the popup
+ width = 200, -- The width of the popup
+ placement_fn = function(c) -- Place the widget using awful.placement (this overrides x & y)
awful.placement.bottom(c, {
margins = {
bottom = 30
}
- })
+ })
end
-}
+}
```
To allow for more customization, there is also a `widget_structure` property (as seen in some default awesome widgets) which is optional. An example is as follows -
```lua
bling.widget.task_preview.enable {
- x = 20, -- The x-coord of the popup
- y = 20, -- The y-coord of the popup
- height = 200, -- The height of the popup
- width = 200, -- The width of the popup
- placement_fn = function(c) -- Place the widget using awful.placement (this overrides x & y)
+ x = 20, -- The x-coord of the popup
+ y = 20, -- The y-coord of the popup
+ height = 200, -- The height of the popup
+ width = 200, -- The width of the popup
+ placement_fn = function(c) -- Place the widget using awful.placement (this overrides x & y)
awful.placement.bottom(c, {
margins = {
bottom = 30
}
- })
+ })
end,
-- Your widget will automatically conform to the given size due to a constraint container.
widget_structure = {
{
{
{
- id = 'icon_role',
+ id = 'icon_role',
widget = awful.widget.clienticon, -- The client icon
},
{
- id = 'name_role' -- The client name / title
+ id = 'name_role', -- The client name / title
widget = wibox.widget.textbox,
- }
+ },
layout = wibox.layout.flex.horizontal
},
widget = wibox.container.margin,
@@ -66,16 +66,16 @@ bling.widget.task_preview.enable {
},
layout = wibox.layout.fixed.vertical
}
-}
+}
```
Here are the signals available:
```lua
-- bling::task_preview::visibility -- first line is the signal
--- s (screen) -- indented lines are function parameters
--- v (boolean)
--- c (client)
+-- s (screen) -- indented lines are function parameters
+-- v (boolean)
+-- c (client)
```
By default, the widget is not visible. You must implement when it will update and when it will show.
@@ -124,7 +124,7 @@ s.mytasklist = awful.widget.tasklist {
nil,
create_callback = function(self, c, index, objects) --luacheck: no unused args
self:get_children_by_id('clienticon')[1].client = c
-
+
-- BLING: Toggle the popup on hover and disable it off hover
self:connect_signal('mouse::enter', function()
awesome.emit_signal("bling::task_preview::visibility", s,
@@ -142,11 +142,11 @@ s.mytasklist = awful.widget.tasklist {
### Theme Variables
```lua
-theme.task_preview_widget_border_radius = 0 -- Border radius of the widget (With AA)
-theme.task_preview_widget_bg = "#000000" -- The bg color of the widget
-theme.task_preview_widget_border_color = "#ffffff" -- The border color of the widget
-theme.task_preview_widget_border_width = 3 -- The border width of the widget
-theme.task_preview_widget_margin = 0 -- The margin of the widget
+theme.task_preview_widget_border_radius = 0 -- Border radius of the widget (With AA)
+theme.task_preview_widget_bg = "#000000" -- The bg color of the widget
+theme.task_preview_widget_border_color = "#ffffff" -- The border color of the widget
+theme.task_preview_widget_border_width = 3 -- The border width of the widget
+theme.task_preview_widget_margin = 0 -- The margin of the widget
```
NOTE: I recommend to only use the widget border radius theme variable when not using shadows with a compositor, as anti-aliased rounding with the outer widgets made with AwesomeWM rely on the actual bg being transparent. If you want rounding with shadows on the widget, use a compositor like [jonaburg's fork](https://github.com/jonaburg/picom).
diff --git a/docs/widgets/window_switcher.md b/docs/widgets/window_switcher.md
new file mode 100644
index 0000000..46b3843
--- /dev/null
+++ b/docs/widgets/window_switcher.md
@@ -0,0 +1,64 @@
+## 🎨 Window Switcher
+
+A popup with client previews that allows you to switch clients similar to the alt-tab menu in MacOS, GNOME, and Windows.
+
+![](https://user-images.githubusercontent.com/70270606/133311802-8aef1012-346f-4f4c-843d-10d9de54ffeb.png)
+
+*image by [No37](https://github.com/Nooo37)*
+
+### Usage
+
+To enable:
+
+```lua
+bling.widget.window_switcher.enable {
+ type = "thumbnail", -- set to anything other than "thumbnail" to disable client previews
+
+ -- keybindings (the examples provided are also the default if kept unset)
+ hide_window_switcher_key = "Escape", -- The key on which to close the popup
+ minimize_key = "n", -- The key on which to minimize the selected client
+ unminimize_key = "N", -- The key on which to unminimize all clients
+ kill_client_key = "q", -- The key on which to close the selected client
+ cycle_key = "Tab", -- The key on which to cycle through all clients
+ previous_key = "Left", -- The key on which to select the previous client
+ next_key = "Right", -- The key on which to select the next client
+ vim_previous_key = "h", -- Alternative key on which to select the previous client
+ vim_next_key = "l", -- Alternative key on which to select the next client
+}
+```
+
+To run the window swicher you have to emit this signal from within your configuration (usually using a keybind).
+
+```lua
+awesome.emit_signal("bling::window_switcher::turn_on")
+```
+
+For example:
+```lua
+ awful.key({Mod1}, "Tab", function()
+ awesome.emit_signal("bling::window_switcher::turn_on")
+ end, {description = "Window Switcher", group = "bling"})
+```
+
+### Theme Variables
+```lua
+theme.window_switcher_widget_bg = "#000000" -- The bg color of the widget
+theme.window_switcher_widget_border_width = 3 -- The border width of the widget
+theme.window_switcher_widget_border_radius = 0 -- The border radius of the widget
+theme.window_switcher_widget_border_color = "#ffffff" -- The border color of the widget
+theme.window_switcher_clients_spacing = 20 -- The space between each client item
+theme.window_switcher_client_icon_horizontal_spacing = 5 -- The space between client icon and text
+theme.window_switcher_client_width = 150 -- The width of one client widget
+theme.window_switcher_client_height = 250 -- The height of one client widget
+theme.window_switcher_client_margins = 10 -- The margin between the content and the border of the widget
+theme.window_switcher_thumbnail_margins = 10 -- The margin between one client thumbnail and the rest of the widget
+theme.thumbnail_scale = false -- If set to true, the thumbnails fit policy will be set to "fit" instead of "auto"
+theme.window_switcher_name_margins = 10 -- The margin of one clients title to the rest of the widget
+theme.window_switcher_name_valign = "center" -- How to vertically align one clients title
+theme.window_switcher_name_forced_width = 200 -- The width of one title
+theme.window_switcher_name_font = "sans 11" -- The font of all titles
+theme.window_switcher_name_normal_color = "#ffffff" -- The color of one title if the client is unfocused
+theme.window_switcher_name_focus_color = "#ff0000" -- The color of one title if the client is focused
+theme.window_switcher_icon_valign = "center" -- How to vertically align the one icon
+theme.window_switcher_icon_width = 40 -- The width of one icon
+```
diff --git a/helpers/color.lua b/helpers/color.lua
index 22d6501..4042360 100644
--- a/helpers/color.lua
+++ b/helpers/color.lua
@@ -1,3 +1,12 @@
+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.
@@ -13,6 +22,14 @@ function _color.is_dark(color)
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"`.
@@ -49,4 +66,93 @@ function _color.darken(color, amount)
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/helpers/icon_theme.lua b/helpers/icon_theme.lua
new file mode 100644
index 0000000..4a1db92
--- /dev/null
+++ b/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/layout/centered.lua b/layout/centered.lua
index c1f79c2..4e1ad24 100644
--- a/layout/centered.lua
+++ b/layout/centered.lua
@@ -1,7 +1,4 @@
local awful = require("awful")
-local gears = require("gears")
-local gcolor = require("gears.color")
-local beautiful = require("beautiful")
local math = math
local mylayout = {}
@@ -58,6 +55,7 @@ function mylayout.arrange(p)
-- 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,
@@ -83,20 +81,4 @@ function mylayout.arrange(p)
end
end
-local icon_raw = gears.filesystem.get_configuration_dir()
- .. tostring(...):match("^.*bling"):gsub("%.", "/")
- .. "/icons/layouts/centered.png"
-
-local function get_icon()
- if icon_raw ~= nil then
- return gcolor.recolor_image(icon_raw, beautiful.fg_normal)
- else
- return nil
- end
-end
-
-return {
- layout = mylayout,
- icon_raw = icon_raw,
- get_icon = get_icon,
-}
+return mylayout
diff --git a/layout/deck.lua b/layout/deck.lua
index a1e7546..e0500b9 100644
--- a/layout/deck.lua
+++ b/layout/deck.lua
@@ -1,8 +1,3 @@
-local awful = require("awful")
-local gears = require("gears")
-local gcolor = require("gears.color")
-local beautiful = require("beautiful")
-
local mylayout = {}
mylayout.name = "deck"
@@ -39,20 +34,4 @@ function mylayout.arrange(p)
end
end
-local icon_raw = gears.filesystem.get_configuration_dir()
- .. tostring(...):match("^.*bling"):gsub("%.", "/")
- .. "/icons/layouts/deck.png"
-
-local function get_icon()
- if icon_raw ~= nil then
- return gcolor.recolor_image(icon_raw, beautiful.fg_normal)
- else
- return nil
- end
-end
-
-return {
- layout = mylayout,
- icon_raw = icon_raw,
- get_icon = get_icon,
-}
+return mylayout
diff --git a/layout/equalarea.lua b/layout/equalarea.lua
index eab2acc..37e972d 100644
--- a/layout/equalarea.lua
+++ b/layout/equalarea.lua
@@ -1,6 +1,3 @@
-local gears = require("gears")
-local gcolor = require("gears.color")
-local beautiful = require("beautiful")
local math = math
local screen = screen
local mylayout = {}
@@ -77,20 +74,4 @@ function mylayout.arrange(p)
divide(p, g, 1, #cls, cls, mwfact, mcount)
end
-local icon_raw = gears.filesystem.get_configuration_dir()
- .. tostring(...):match("^.*bling"):gsub("%.", "/")
- .. "/icons/layouts/equalarea.png"
-
-local function get_icon()
- if icon_raw ~= nil then
- return gcolor.recolor_image(icon_raw, beautiful.fg_normal)
- else
- return nil
- end
-end
-
-return {
- layout = mylayout,
- icon_raw = icon_raw,
- get_icon = get_icon,
-}
+return mylayout
diff --git a/layout/horizontal.lua b/layout/horizontal.lua
index 353b4a8..23f9c9e 100644
--- a/layout/horizontal.lua
+++ b/layout/horizontal.lua
@@ -1,7 +1,3 @@
-local awful = require("awful")
-local gears = require("gears")
-local gcolor = require("gears.color")
-local beautiful = require("beautiful")
local math = math
local mylayout = {}
@@ -57,20 +53,4 @@ function mylayout.arrange(p)
end
end
-local icon_raw = gears.filesystem.get_configuration_dir()
- .. tostring(...):match("^.*bling"):gsub("%.", "/")
- .. "/icons/layouts/horizontal.png"
-
-local function get_icon()
- if icon_raw ~= nil then
- return gcolor.recolor_image(icon_raw, beautiful.fg_normal)
- else
- return nil
- end
-end
-
-return {
- layout = mylayout,
- icon_raw = icon_raw,
- get_icon = get_icon,
-}
+return mylayout
diff --git a/layout/init.lua b/layout/init.lua
index f7ed971..223d9d4 100644
--- a/layout/init.lua
+++ b/layout/init.lua
@@ -1,30 +1,44 @@
local beautiful = require("beautiful")
+local gears = require("gears")
-local mstab = require(... .. ".mstab")
-beautiful.layout_mstab = mstab.get_icon()
+local M = {}
+local relative_lua_path = tostring(...)
-local vertical = require(... .. ".vertical")
-beautiful.layout_vertical = vertical.get_icon()
+local function get_layout_icon_path(name)
+ local relative_icon_path = relative_lua_path
+ :match("^.*bling"):gsub("%.", "/")
+ .. "/icons/layouts/" .. name .. ".png"
-local horizontal = require(... .. ".horizontal")
-beautiful.layout_horizontal = horizontal.get_icon()
+ 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 centered = require(... .. ".centered")
-beautiful.layout_centered = centered.get_icon()
+local function get_icon(icon_raw)
+ if icon_raw ~= nil then
+ return gears.color.recolor_image(icon_raw, beautiful.fg_normal)
+ else
+ return nil
+ end
+end
-local equalarea = require(... .. ".equalarea")
-beautiful.layout_equalarea = equalarea.get_icon()
-
-local deck = require(... .. ".deck")
-beautiful.layout_deck = deck.get_icon()
-
-local layout = {
- mstab = mstab.layout,
- centered = centered.layout,
- vertical = vertical.layout,
- horizontal = horizontal.layout,
- equalarea = equalarea.layout,
- deck = deck.layout,
+local layouts = {
+ "mstab",
+ "vertical",
+ "horizontal",
+ "centered",
+ "equalarea",
+ "deck"
}
-return layout
+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/layout/mstab.lua b/layout/mstab.lua
index 6cbdb84..639275f 100644
--- a/layout/mstab.lua
+++ b/layout/mstab.lua
@@ -1,7 +1,6 @@
local awful = require("awful")
local gears = require("gears")
local wibox = require("wibox")
-local gcolor = require("gears.color")
local beautiful = require("beautiful")
local mylayout = {}
@@ -242,16 +241,4 @@ function mylayout.arrange(p)
)
end
-local icon_raw = gears.filesystem.get_configuration_dir()
- .. tostring(...):match("^.*bling"):gsub("%.", "/")
- .. "/icons/layouts/mstab.png"
-
-local function get_icon()
- if icon_raw ~= nil then
- return gcolor.recolor_image(icon_raw, beautiful.fg_normal)
- else
- return nil
- end
-end
-
-return { layout = mylayout, icon_raw = icon_raw, get_icon = get_icon }
+return mylayout
diff --git a/layout/vertical.lua b/layout/vertical.lua
index f0eb8aa..8b6811e 100644
--- a/layout/vertical.lua
+++ b/layout/vertical.lua
@@ -1,7 +1,3 @@
-local awful = require("awful")
-local gears = require("gears")
-local gcolor = require("gears.color")
-local beautiful = require("beautiful")
local math = math
local mylayout = {}
@@ -57,20 +53,4 @@ function mylayout.arrange(p)
end
end
-local icon_raw = gears.filesystem.get_configuration_dir()
- .. tostring(...):match("^.*bling"):gsub("%.", "/")
- .. "/icons/layouts/vertical.png"
-
-local function get_icon()
- if icon_raw ~= nil then
- return gcolor.recolor_image(icon_raw, beautiful.fg_normal)
- else
- return nil
- end
-end
-
-return {
- layout = mylayout,
- icon_raw = icon_raw,
- get_icon = get_icon,
-}
+return mylayout
diff --git a/module/tabbed.lua b/module/tabbed.lua
index b7ae67d..c53ec03 100644
--- a/module/tabbed.lua
+++ b/module/tabbed.lua
@@ -23,6 +23,14 @@ local bar = require(
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
@@ -50,6 +58,8 @@ tabbed.remove = function(c)
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
@@ -67,6 +77,8 @@ 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
@@ -218,12 +230,15 @@ 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))
- wid_temp = bar.create(c, (idx == tabobj.focused_idx), buttons)
+ 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)
@@ -240,6 +255,8 @@ 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
diff --git a/theme-var-template.lua b/theme-var-template.lua
index ad65463..421d37c 100644
--- a/theme-var-template.lua
+++ b/theme-var-template.lua
@@ -36,6 +36,10 @@ theme.tabbar_bg_normal = "#000000" -- background color of the focused client on
theme.tabbar_fg_normal = "#ffffff" -- foreground color of the focused client on the tabbar
theme.tabbar_bg_focus = "#1A2026" -- background color of unfocused clients on the tabbar
theme.tabbar_fg_focus = "#ff0000" -- foreground color of unfocused clients on the tabbar
+theme.tabbar_bg_focus_inactive = nil -- background color of the focused client on the tabbar when inactive
+theme.tabbar_fg_focus_inactive = nil -- foreground color of the focused client on the tabbar when inactive
+theme.tabbar_bg_normal_inactive = nil -- background color of unfocused clients on the tabbar when inactive
+theme.tabbar_fg_normal_inactive = nil -- foreground color of unfocused clients on the tabbar when inactive
-- mstab
theme.mstab_bar_ontop = false -- whether you want to allow the bar to be ontop of clients
@@ -76,4 +80,25 @@ theme.task_preview_widget_border_color = "#ffffff" -- The border color of the wi
theme.task_preview_widget_border_width = 3 -- The border width of the widget
theme.task_preview_widget_margin = 0 -- The margin of the widget
+-- window switcher
+theme.window_switcher_widget_bg = "#000000" -- The bg color of the widget
+theme.window_switcher_widget_border_width = 3 -- The border width of the widget
+theme.window_switcher_widget_border_radius = 0 -- The border radius of the widget
+theme.window_switcher_widget_border_color = "#ffffff" -- The border color of the widget
+theme.window_switcher_clients_spacing = 20 -- The space between each client item
+theme.window_switcher_client_icon_horizontal_spacing = 5 -- The space between client icon and text
+theme.window_switcher_client_width = 150 -- The width of one client widget
+theme.window_switcher_client_height = 250 -- The height of one client widget
+theme.window_switcher_client_margins = 10 -- The margin between the content and the border of the widget
+theme.window_switcher_thumbnail_margins = 10 -- The margin between one client thumbnail and the rest of the widget
+theme.thumbnail_scale = false -- If set to true, the thumbnails fit policy will be set to "fit" instead of "auto"
+theme.window_switcher_name_margins = 10 -- The margin of one clients title to the rest of the widget
+theme.window_switcher_name_valign = "center" -- How to vertically align one clients title
+theme.window_switcher_name_forced_width = 200 -- The width of one title
+theme.window_switcher_name_font = "Sans 11" -- The font of all titles
+theme.window_switcher_name_normal_color = "#ffffff" -- The color of one title if the client is unfocused
+theme.window_switcher_name_focus_color = "#ff0000" -- The color of one title if the client is focused
+theme.window_switcher_icon_valign = "center" -- How to vertically align the one icon
+theme.window_switcher_icon_width = 40 -- The width of one icon
+
-- LuaFormatter on
diff --git a/widget/app_launcher/init.lua b/widget/app_launcher/init.lua
new file mode 100644
index 0000000..7eeaf17
--- /dev/null
+++ b/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/widget/app_launcher/prompt.lua b/widget/app_launcher/prompt.lua
new file mode 100644
index 0000000..fae3b86
--- /dev/null
+++ b/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/widget/init.lua b/widget/init.lua
index df8bfe9..d3c6ebd 100644
--- a/widget/init.lua
+++ b/widget/init.lua
@@ -3,4 +3,5 @@ return {
task_preview = require(... .. ".task_preview"),
window_switcher = require(... .. ".window_switcher"),
tabbed_misc = require(... .. ".tabbed_misc"),
+ app_launcher = require(... .. ".app_launcher"),
}
diff --git a/widget/tabbar/boxes.lua b/widget/tabbar/boxes.lua
index d0619da..720f420 100644
--- a/widget/tabbar/boxes.lua
+++ b/widget/tabbar/boxes.lua
@@ -8,16 +8,20 @@ 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)
- local bg_temp = bg_normal
- local fg_temp = fg_normal
+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 = bg_focus
- fg_temp = fg_focus
+ 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({
{
diff --git a/widget/tabbar/default.lua b/widget/tabbar/default.lua
index 2774d6f..ad6b0b1 100644
--- a/widget/tabbar/default.lua
+++ b/widget/tabbar/default.lua
@@ -7,18 +7,22 @@ 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)
+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 = bg_normal
- local fg_temp = fg_normal
+ 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 = bg_focus
- fg_temp = fg_focus
+ 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"
diff --git a/widget/tabbar/modern.lua b/widget/tabbar/modern.lua
index 11f5e44..5f48066 100644
--- a/widget/tabbar/modern.lua
+++ b/widget/tabbar/modern.lua
@@ -10,6 +10,10 @@ 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
@@ -66,14 +70,14 @@ local function create_title_button(c, color_focus, color_unfocus)
return tb
end
-local function create(c, focused_bool, buttons)
+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 = bg_normal
- local fg_temp = fg_normal
+ 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 = bg_focus
- fg_temp = fg_focus
+ 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"
diff --git a/widget/tabbar/pure.lua b/widget/tabbar/pure.lua
index 588e59f..5be82e5 100644
--- a/widget/tabbar/pure.lua
+++ b/widget/tabbar/pure.lua
@@ -8,13 +8,21 @@ 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)
- local bg_temp = focused_bool and bg_focus or bg_normal
- local fg_temp = focused_bool and fg_focus or fg_normal
+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({
{
diff --git a/widget/tag_preview.lua b/widget/tag_preview.lua
index d73d42a..2a181b4 100644
--- a/widget/tag_preview.lua
+++ b/widget/tag_preview.lua
@@ -28,7 +28,8 @@ local function draw_widget(
widget_border_color,
widget_border_width,
geo,
- margin
+ margin,
+ background_image
)
local client_list = wibox.layout.manual()
client_list.forced_height = geo.height
@@ -36,15 +37,20 @@ local function draw_widget(
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({
- image = gears.surface.load(c.icon),
+
+
+ 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
@@ -108,22 +114,26 @@ local function draw_widget(
end
end
- return {
+ return wibox.widget {
{
+ background_image,
{
{
{
- client_list,
- forced_height = geo.height,
- forced_width = geo.width,
- widget = wibox.container.place,
+ {
+ client_list,
+ forced_height = geo.height,
+ forced_width = geo.width,
+ widget = wibox.container.place,
+ },
+ layout = wibox.layout.align.horizontal,
},
- layout = wibox.layout.align.horizontal,
+ layout = wibox.layout.align.vertical,
},
- layout = wibox.layout.align.vertical,
+ margins = margin,
+ widget = wibox.container.margin,
},
- margins = margin,
- widget = wibox.container.margin,
+ layout = wibox.layout.stack
},
bg = widget_bg,
shape_border_width = widget_border_width,
@@ -143,6 +153,7 @@ local enable = function(opts)
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)
@@ -170,9 +181,22 @@ local enable = function(opts)
})
tag.connect_signal("property::selected", function(t)
- for _, c in ipairs(t:clients()) do
- c.prev_content = gears.surface.duplicate_surface(c.content)
- end
+ -- 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)
@@ -184,14 +208,24 @@ local enable = function(opts)
tag_preview_box.maximum_width = scale * geo.width + margin * 2
tag_preview_box.maximum_height = scale * geo.height + margin * 2
- -- TODO: Use a table here
- tag_preview_box:setup(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))
+
+ 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)
@@ -200,6 +234,11 @@ local enable = function(opts)
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
diff --git a/widget/task_preview.lua b/widget/task_preview.lua
index 603e6db..3b54867 100644
--- a/widget/task_preview.lua
+++ b/widget/task_preview.lua
@@ -30,14 +30,24 @@ local function draw_widget(
end) then
return
end
- 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()
+
+ 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 {
@@ -139,6 +149,25 @@ local enable = function(opts)
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
@@ -153,6 +182,9 @@ local enable = function(opts)
widget_width,
widget_height
)
+ else
+ task_preview_box.widget = nil
+ collectgarbage("collect")
end
if not placement_fn then
diff --git a/widget/window_switcher.lua b/widget/window_switcher.lua
index 46a303b..ac835a5 100644
--- a/widget/window_switcher.lua
+++ b/widget/window_switcher.lua
@@ -59,6 +59,8 @@ local window_switcher_hide = function(window_switcher_box)
-- 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(