This commit is contained in:
Ryan Thomas 2022-02-11 02:03:04 -05:00
parent e2f420d934
commit 537619514f
2 changed files with 108 additions and 121 deletions

View File

@ -1,12 +1,12 @@
We are surprised at the time of this writing that the system tray remains such an obvious bottleneck in the nevertheless ubiquitous fight against mouse-dependency among neckbeards the world over. We present accordingly, systray_hints. We are surprised at the time of this writing that the system tray remains such an obvious bottleneck in the nevertheless ubiquitous fight against mouse-dependency among neckbeards the world over. We present accordingly: systray_hints.
# Overview # Overview
When `systray_hints.run()` is executed, a popup widget displays numbers next to each icon in the system tray while keygrabber listens for input. The number entered on the keyboard sends a right click to the corresponding icon and the original position of the mouse pointer is restored. For most programs this opens a context menu the user may then navigate using arrow keys and pressing Return. When `systray_hints.run()` is executed, a popup widget displays a number next to each icon in the system tray while keygrabber listens for input. The number entered on the keyboard sends a right click to the corresponding icon and the original position of the mouse pointer is restored. For most programs this opens a context menu the user may then navigate using arrows and the Return key. To cancel, submit any invalid entry; in practice this includes the Escape key as well as whatever keybinding was configured to launch the function.
The default option of a right click may be overriden on the fly by pressing the Left or Right arrow key before making a selection for a left or right click, respectively. Alternatively, pressing either the Up or Down key will bypass all clicking and the pointer is simply moved to the location of the selected icon to display its on-hover tooltip, if any. The configured mouse button (right-click by default) can be overridden before entering a selection by pressing the Left or Right arrow key for a left or right click, respectively. Alternatively, pressing the Up arrow key will substitute hovering for clicking to display a given icon's tooltip, if any.
If ten or more icons are displayed, the function will interpret the "1" key as the first digit of the selection and wait for the second digit, or wait for the Return key. Up to 19 icons are supported. If ten or more icons are displayed, the keygrabber will interpret the "1" key as the first digit of the selection and wait for the second digit, or for the Return key to select the first icon. Up to 19 icons are supported.
# Install # Install
@ -17,6 +17,13 @@ Add `systray_hints = require("systray_hints")` to rc.lua. Note this may work bet
# Configuration # Configuration
Configure by overriding systray_hints table values after the require statement. For example, set the font, left-click by default, and redefine the keybindings for left-click [1], hover [2], and right-click [3].
systray_hints = require("systray_hints")
systray_hints.font = "Iosevka Bold 16"
systray_hints.default_button = 1
systray_hints.mouse_buttons = { "h", "j", "l" }
If the system tray is normally hidden in your environment and toggled as needed with a keybinding, you can replace that keybinding with something like this: If the system tray is normally hidden in your environment and toggled as needed with a keybinding, you can replace that keybinding with something like this:
awful.key({ modkey }, "s", function () awful.key({ modkey }, "s", function ()
@ -34,7 +41,7 @@ If the system tray is normally hidden in your environment and toggled as needed
end, {description="toggle systray with hints", group="awesome"}), end, {description="toggle systray with hints", group="awesome"}),
The `systray_hints.run()` function will automatically unhide the system tray as needed and will return it to its original visiblity state whenever a selection is not made. The `systray_hints.run()` function will automatically unhide the system tray as needed and will return it to its original visiblity state whenever a selection is not made, such as when the key combination is pressed again.
If your system tray is always displayed, simply create a keybinding like the following: If your system tray is always displayed, simply create a keybinding like the following:
@ -53,6 +60,4 @@ If your system tray is always displayed, simply create a keybinding like the fol
The ability to obtain the geometry of the system tray is not referenced in the awesome API for a reason; in theory it may occasionally return incorrect data, requiring an additional execution of the keybinding. The ability to obtain the geometry of the system tray is not referenced in the awesome API for a reason; in theory it may occasionally return incorrect data, requiring an additional execution of the keybinding.
This project was previously assembled as a quick hack in the form of a shell script requiring iocane, rofi, and xdotool. It has been rewritten as a native lua module for the latest stable release with no external dependencies. Final testing and clean-up in progress. This project was previously assembled as a quick hack in the form of a shell script requiring iocane, rofi, and xdotool. It has been rewritten as a native lua module for the latest stable release with no external dependencies. While deprecated functions were avoided, we have not yet tested it in the development version.
While deprecated functions were avoided, we have not yet tested it in the development version.

208
init.lua
View File

@ -1,45 +1,42 @@
-- S Y S T R A Y H I N T S -- S Y S T R A Y H I N T S
-- rts/oarion7 - ryanthomas.org -- rts/oarion7 - ryanthomas.org
-- Module to control the awesomewm systray from the keyboard using -- Control the awesomewm systray from the keyboard using vimium-like
-- vimium-like number hints. Developed and tested on awesome v4.3. -- number hints. Developed and tested on awesome v4.3.
-- TO DO: move widget to table (remove globals)
-- cleanup
local awful = require("awful") local awful = require("awful")
local gears = require("gears") local gears = require("gears")
local b = require("beautiful") local b = require("beautiful")
local wibox = require("wibox") local wibox = require("wibox")
local s local s
local font = b.systray_hints_font or b.taglist_font or b.font awful.screen.connect_for_each_screen(function(screen)
local bgcolor = b.systray_hints_bg or b.taglist_bg_occupied or "#55465a" if screen.systray then s = screen end
local highlight = b.systray_hints_bg_highlight or "#aa53aa" end)
local highlight_alt = b.systray_hints_bg_highlight_alt or "#426f5a"
local fgcolor = b.systray_hints_fg or b.taglist_fg_occupied or "#fdf6e3"
local bordercolor = "#fdf6e333"
awful.screen.connect_for_each_screen(function(screen) if screen.systray then s = screen end end)
if s == nil then return nil end if s == nil then return nil end
local systray_hints = { local systray_hints = {
arrows = { "Left", "Down", "Up", "Right" },
--arrows = { "h", "j", "k", "l" }, font = b.systray_hints_font or b.taglist_font or b.font,
default_button = 3, bgcolor = b.systray_hints_bg or b.taglist_bg_occupied or "#55465a",
--default_button = 1, highlight = b.systray_hints_bg_highlight or "#aa53aa",
hints = hints,
systray = s.systray, highlight_alt = b.systray_hints_bg_highlight_alt or "#426f5a",
wibox = s.mywibox, --where the system tray is located color = b.systray_hints_fg or b.taglist_fg_occupied or "#fdf6e3",
run = run, bordercolor = "#fdf6e333",
spacing = 1,
mouse_buttons = { "Left", "Up", "Right" },
default_button = 3,
popup = popup,
systray = s.systray,
wibox = s.mywibox, --wibox in which to locate the system tray
run = run,
} }
local total local total
local was_hidden local was_hidden
local icon_count
local icon_width local icon_width
local half_icon local icons_x
local first_icon_x
local icons_y local icons_y
local function delay(time, cmd) local function delay(time, cmd)
@ -47,48 +44,47 @@ local function delay(time, cmd)
callback = function () cmd () end, } ) callback = function () cmd () end, } )
end end
local function execute(choice, mouse_button) local function execute(choice, mouse_button)
local target local saved
local factor local factor
local saved_coords local target
saved_coords = mouse.coords({x = x, y = y}) saved = mouse.coords({x = x, y = y})
if choice == 1 then factor = 0 else factor = choice - 1 end if choice == 1 then factor = 0 else factor = choice - 1 end
target = first_icon_x + ( icon_width * factor ) target = icons_x + ( icon_width * factor )
mouse.coords { x = target , y = icons_y } mouse.coords { x = target , y = icons_y }
if mouse_button ~= 2 then if mouse_button ~= 2 then
root.fake_input("button_press" , tostring(mouse_button)) root.fake_input("button_press" , tostring(mouse_button))
root.fake_input("button_release", tostring(mouse_button)) root.fake_input("button_release", tostring(mouse_button))
delay(0.05, function () mouse.coords({x = saved_coords.x, y = saved_coords.y}, true) end) delay(0.05, function () mouse.coords({x = saved.x, y = saved.y}, true) end)
end end
if systray_hints.hints then systray_hints.hints.visible = false end if systray_hints.popup then systray_hints.popup.visible = false end
end end
local function highlight_options(total) local function highlight_multidigits(total)
local color local color
for i = 9, total do for i = 9, total do
color = highlight_alt color = systray_hints.highlight_alt
if i == 9 then if i == 9 then
i = 1 i = 1
color = highlight color = systray_hints.highlight
end end
systray_hints.hints.widget:get_children()[1]:get_children()[i].widget:set_bg(color) systray_hints.popup.widget:get_children()[1]:get_children()[i].widget:set_bg(color)
end end
end end
local function get_key_input(total) local function get_key_input(total)
local grabber local grabber
local mouse_button local mouse_button
local mouse_button = systray_hints.default_button
local function conc(n) return tonumber( 1 .. n ) end local function conc(n) return tonumber( 1 .. n ) end
mouse_button = systray_hints.default_button
grabber = awful.keygrabber { grabber = awful.keygrabber {
mask_modkeys = true, mask_modkeys = true,
@ -97,9 +93,10 @@ local function get_key_input(total)
if key == '1' and total > 9 then if key == '1' and total > 9 then
if systray_hints.hints then if systray_hints.popup then
highlight_options(total) highlight_multidigits(total)
end end
grabber.keypressed_callback = function(self, mod, key, cmd) grabber.keypressed_callback = function(self, mod, key, cmd)
if key == "Return" then if key == "Return" then
execute(1, mouse_button) execute(1, mouse_button)
@ -109,20 +106,20 @@ local function get_key_input(total)
grabber:stop() grabber:stop()
else else
grabber:stop() grabber:stop()
if was_hidden then s.systray.visible = false end if was_hidden then systray_hints.systray.visible = false end
if systray_hints.hints then systray_hints.hints.visible = false end if systray_hints.popup then systray_hints.popup.visible = false end
end end
end end
elseif key == systray_hints.arrows[1] then mouse_button = 1 elseif key == systray_hints.mouse_buttons[1] then mouse_button = 1
elseif key == systray_hints.arrows[4] then mouse_button = 3 elseif key == systray_hints.mouse_buttons[2] then mouse_button = 2
elseif key == systray_hints.arrows[2] or key == systray_hints.arrows[3] then mouse_button = 2 elseif key == systray_hints.mouse_buttons[3] then mouse_button = 3
elseif not key:match("%D") and tonumber(key) <= total then elseif not key:match("%D") and tonumber(key) <= total then
execute(tonumber(key), mouse_button) execute(tonumber(key), mouse_button)
grabber:stop() grabber:stop()
else else
grabber:stop() grabber:stop()
if was_hidden then s.systray.visible = false end if was_hidden then systray_hints.systray.visible = false end
if systray_hints.hints then systray_hints.hints.visible = false end if systray_hints.popup then systray_hints.popup.visible = false end
end end
end, end,
@ -131,79 +128,67 @@ local function get_key_input(total)
end end
local function show_hints(x, y, w, total, s)
local hints = {}
local hint_width
local function show_hints(sys_hints_geo_x, sys_hints_geo_y, w, sys_hints_icon_count, s) hint_width = w - ( systray_hints.spacing * 2 )
local sys_hints_icon_width = w - 2 --subtract for margins --Decide whether hints should display above or below systray icons.
if y >= 100 then y = y - hint_width else y = y + hint_width end
if sys_hints_geo_y >= 100 then --Hide if already displayed
sys_hints_geo_y = sys_hints_geo_y - sys_hints_icon_width if systray_hints.popup then systray_hints.popup.visible = false end
else sys_hints_geo_y = sys_hints_geo_y + sys_hints_icon_width
--Decide if hints should display above or below systray icons.
end
--hide if already displayed
if systray_hints.hints then systray_hints.hints.visible = false end
local num_rows = sys_hints_icon_count
local sys_hints_list = {}
local systray_widget_list = {}
local widget_shape = function(cr, width, height) local widget_shape = function(cr, width, height)
gears.shape.rounded_rect(cr, width, height, 5) gears.shape.rounded_rect(cr, width, height, 5)
end end
local var = {} for i = 1, total do
for i = 1, num_rows do table.insert(sys_hints_list, tostring(i)) end
for k, v in pairs(sys_hints_list) do
var["text_" .. v] = wibox.widget.textbox(tostring(v)) local text
local text_alias = var["text_" .. v] local placement = {}
text_alias.font = font -- "Sans 14" local background = {}
text_alias.markup = '<span color="' .. fgcolor .. local margins = {}
'">' .. v .. '</span>'
local item_place = {} text = wibox.widget.textbox(tostring(i))
table.insert(item_place, text_alias) text.font = systray_hints.font
text.markup = '<span color="' .. systray_hints.color .. '">' ..
i .. '</span>'
item_place.widget = wibox.container.place table.insert(placement, text)
placement.widget = wibox.container.place
table.insert(background, placement)
local item_background = {} background.widget = wibox.container.background
table.insert(item_background, item_place) background.bg = systray_hints.bgcolor
background.forced_width = hint_width
background.shape = widget_shape
background.shape_border_width = 2
background.shape_border_color = systray_hints.bordercolor
item_background.widget = wibox.container.background table.insert(margins, background)
item_background.bg = bgcolor -- "#ff00ff"
item_background.forced_width = sys_hints_icon_width
item_background.shape = widget_shape
item_background.shape_border_width = 2
item_background.shape_border_color = bordercolor
tostring(v)
local item_margin = {} margins.widget = wibox.container.margin
table.insert(item_margin, item_background) margins.right = systray_hints.spacing
margins.left = systray_hints.spacing
item_margin.widget = wibox.container.margin table.insert(hints, margins)
item_margin.right = 1
item_margin.left = 1
local complete_widget = item_margin
table.insert(systray_widget_list, complete_widget)
end end
systray_widget_list.layout = wibox.layout.fixed.horizontal hints.layout = wibox.layout.fixed.horizontal
systray_hints.hints = awful.popup { systray_hints.popup = awful.popup {
widget = { widget = {
screen = s, screen = s,
systray_widget_list, hints,
layout = wibox.layout.fixed.horizontal,
layout = wibox.layout.fixed.horizontal,
}, },
x = sys_hints_geo_x, x = x,
y = sys_hints_geo_y, y = y,
visible = true, visible = true,
ontop = true, ontop = true,
bg = "#00000000", bg = "#00000000",
} }
end end
@ -214,14 +199,13 @@ local function find_widget_in_wibox(wb, wdg)
local g = gears.matrix.transform_rectangle local g = gears.matrix.transform_rectangle
local x, y, w, h = g(hi:get_matrix_to_device(), 0, 0, hi:get_size()) local x, y, w, h = g(hi:get_matrix_to_device(), 0, 0, hi:get_size())
icon_count = math.floor( ( w - ( w % h) ) / h + 1 ) total = math.floor( ( w - ( w % h) ) / h + 1 )
icon_width = math.floor(w / icon_count ) icon_width = math.floor(w / total )
half_icon = math.floor( icon_width / 2) icons_x = math.floor( x + icon_width / 2)
first_icon_x = math.floor( x + half_icon) icons_y = math.floor(y + icon_width / 2)
icons_y = math.floor(y + half_icon)
show_hints( math.floor(x), math.floor(y), icon_width, icon_count, s ) show_hints( math.floor(x), math.floor(y), icon_width, total, s )
get_key_input(icon_count) get_key_input(total)
end end
@ -231,9 +215,7 @@ local function find_widget_in_wibox(wb, wdg)
return return
end end
for _, child in ipairs(hi:get_children()) do for _, child in ipairs(hi:get_children()) do
-- return traverse(child)
traverse(child) traverse(child)
-- allow for additional round of recursion across container widgets.
end end
end end
return traverse(wb._drawable._widget_hierarchy) return traverse(wb._drawable._widget_hierarchy)
@ -241,9 +223,9 @@ end
systray_hints.run = function () systray_hints.run = function ()
if not s.systray.visible then if not systray_hints.systray.visible then
was_hidden = true was_hidden = true
s.systray.visible = true systray_hints.systray.visible = true
delay(0.05, function () find_widget_in_wibox(systray_hints.wibox, systray_hints.systray) end) delay(0.05, function () find_widget_in_wibox(systray_hints.wibox, systray_hints.systray) end)
else else
find_widget_in_wibox(systray_hints.wibox, systray_hints.systray) find_widget_in_wibox(systray_hints.wibox, systray_hints.systray)