Update 09.10.2022: fixed fehlende Farbe beim letzten Knopf wenn die Anzahl der Anwendungen, die Anzahl der hinterlegten Farben übersteigt
Nach dem Umstieg auf den awesome Windowmanager, bin ich echt begeistert, nur eins fehlte mir, ein Grid Select Menu, wie ich es von xmonad kannte. Ein Grid Select Menu zeigt mir als Overlay eine tabellarische Anordnung von Menüeinträgen / Anwendungsnamen aus denen ich dann auswählen kann und durch Klick oder Enter auf dem jeweiligen Eintrag die Anwendung starten kann.
![](https://cdn1.tuxlog.de/wp-content/uploads/grid-select-1024x440.jpg)
Der awesome Windowmanager läßt sich in der Programmiersprache Lua erweitern und so habe ich eine kleine Routine geschrieben, mit der man awesome um ein Grid Select erweitern kann.
Dieses awesome widget kann bis zu 50 Menueinträge anzeigen und wird direkt über die rc.lua (Konfigurationsdatei von awesome und normalerweise im Verzeichnis ~/.config/awesome abgelegt).
Zuerst definiert man sich die gewünschten Menüeinträge und zugehörige Programme. Dabei ist der erste String der Text, der im Menü angezeigt wird und der zweite Stringe das Kommando, das bei Auswahl des Menüeintrages ausgeführt wird.
Das sieht dann z.B. so aus:
local my_appgrid = {
{"Chromium", "chromium"}, {"Thunderbird", "thunderbird"}
, {"Firefox", "firefox"}, {"Urxvt", "urxvt"}
, {"LibreOffice Calc", "localc"}, {"LibreOffice Writer", "lowriter"}
, {"PCManFM", "pcmanfm"}
}
Code-Sprache: JavaScript (javascript)
Um das Menü auch aufrufen zu können verknüpft man es dann mit einer Tastenkombination. Im folgenden Beispiel ist das <Mod4> + g.
awful.key({ modkey }, "g", function()
grid_select.launch({appgrid = my_appgrid}) end,
{description = "Show grid selection menu", group = "custom"}
)
Code-Sprache: JavaScript (javascript)
Dies Verknüpfung mit der Tastenkombination wird in rc.lua im Abschnitt globalkeys = mytable.join( … ) eingebaut.
Wer möchte kann sich auch eine andere Menge an Farben definieren. Dazu definiert man sich eine Tabelle von Farben und gibt diese als Parameter an das Widget mit.
local myColors = { „#9BB7D4“, „#C74375“, „#BF1932“, „#7BC4C4“, „#E2583E“, „#53B0AE“ }
Die Verknüpfung mit der Tastenkombination sieht dann so aus:
awful.key({ modkey }, "g", function()
grid_select.launch({appgrid = my_appgrid, colors = myColors}) end,
{description = "Show grid selection menu", group = "custom"}
)
Code-Sprache: JavaScript (javascript)
Das Widget wird wie folgt in rc.lua eingebunden:
local grid_select = require("grid-select-widget.grid-select")
Code-Sprache: JavaScript (javascript)
Zum Abschluss der Sourcecode des Widgets, den man z.B. im Verzeichnis ~/.config/awesoem/grid-select/grid-select.lua ablegt.
-------------------------------------------------
-- Grid select widget for Awesome Window Manager
-- @author tuxlogger
-- @copyright 2022 Tuxlogger
-------------------------------------------------
--
-- To use this grid select widget, edit rc.lua
-- define your own commands in a table
--[[[
local HOME_DIR = os.getenv("HOME")
local my_appgrid = {
{"Chromium", "chromium"}
, {"Thunderbird", "thunderbird"}
, {"Firefox", "firefox"}
, {"Urxvt", "urxvt"}
, {"LibreOffice Calc", "localc"}
, {"LibreOffice Writer", "lowriter"}
, {"PCManFM", "pcmanfm"}
}
and add a keybinding
awful.key({ modkey }, "g", function() grid_select.launch({appgrid = my_appgrid}) end, {description = "Show grid selection menu", group = "custom"})
somewhere in the
globalkeys = mytable.join( ... )
section in rc.lua
You can also change the colors defining your own color table
local myColors = { "#9BB7D4", "#C74375", "#BF1932", "#7BC4C4", "#E2583E", "#53B0AE" }
and change the keybinding accordingly to
awful.key({ modkey }, "g", function() grid_select.launch({appgrid = my_appgrid, colors = myColors}) end, {description = "Show grid selection menu", group = "custom"})
You can either define the selected entry by setting default_app to the number of the entry in my_appgrid, counting start at 1
local my_appgrid_default = 9
and change the keybindings according to
awful.key({ modkey }, "g", function() grid_select.launch({appgrid = my_appgrid, colors = myColors, default_app = my_appgrid_default}) end, {description = "Show grid selection menu", group = "custom"})
--]]
local awful = require("awful")
local capi = {keygrabber = keygrabber }
local wibox = require("wibox")
local gears = require("gears")
local HOME_DIR = os.getenv("HOME")
-- default appgrid entries to have something to start with
local myAppGrid = {
{"Chromium", "chromium"}
, {"Thunderbird", "thunderbird"}
, {"Firefox", "firefox"}
, {"Urxvt", "urxvt"}
, {"LibreOffice Calc", "localc"}
, {"LibreOffice Writer", "lowriter"}
, {"PCManFM", "pcmanfm"}
}
-- default color palette from the pantene colors of the year
local myColors = { "#9BB7D4", "#C74375", "#BF1932", "#7BC4C4", "#E2583E", "#53B0AE",
"#9B1B30", "#5A5B9F", "#F0C05A", "#45B5AA", "#D94F70", "#DD4124",
"#009473", "#B163A3", "#955251", "#92A8D1", "#88B04B", "#5F4B8B",
"#FF6F61", "#0F4C81"
}
-- create overlay parent widget - default
local w = wibox {}
-- create button widget with text label, connected to command with background color bg_color
local function create_button(label, command, bg_color)
-- create button
local but = wibox.widget{
{
{
markup = '<span size="14000" >' .. label ..'</span>', -- font size 14
align = 'center',
valign = 'center',
widget = wibox.widget.textbox
},
top = 8, bottom = 8, left = 8, right = 8,
widget = wibox.container.margin
},
shape = function(cr, width, height) gears.shape.rounded_rect(cr, width, height, 4) end,
widget = wibox.container.background
}
-- set colors of button
but:set_bg(bg_color)
but:set_fg("#222222")
-- change button layout on enter and leave mouse focus
but:connect_signal("mouse::enter", function(c)
c:set_fg("#ffffff")
but:set_shape( function(cr, width, height) gears.shape.hexagon(cr, width, height) end)
end)
but:connect_signal("mouse::leave", function(c)
c:set_fg("#222222")
but:set_shape( function(cr, width, height) gears.shape.rounded_rect(cr, width, height, 4) end)
end)
but:connect_signal("button::press", function()
awful.spawn(command)
w.visible = false
capi.keygrabber.stop()
end)
return but
end
-- calculate the distriution of the entries to the columns
-- currently this works with a max of 50 entries
local function calc_entry_distribution( count )
local ca= {}
-- pattern of the grid select layout, giving the max entries for eacht col
local col_patterns = {
{ 1 }
,{ 2 }
,{ 1, 3, 1 }
,{ 2, 4, 2 }
,{ 1, 3, 5, 3, 1 }
,{ 2, 4, 6, 4, 2 }
,{ 1, 3, 5, 7, 5, 3, 1 }
,{ 2, 4, 6, 8, 6, 4, 2 }
,{ 1, 3, 5, 7, 9, 7, 5, 3, 1 }
,{ 2, 4, 6, 8, 10, 8, 6, 4, 2 }
}
-- max number of entries for the patterns defined in col_patterns
local max_entries = { 1, 2, 5, 8, 13, 18, 25, 32, 41, 50 }
-- get the index of col_patterns for the given number of entries in count
local i = 1
while(max_entries[i] < count)
do
i = i + 1
end
-- return the selected pattern
return col_patterns[i]
end
-- launch grid select menu
local function launch(args)
args = args or {}
local bg_color = args.bg_color or "#333333"
local appgrid = args.appgrid or myAppGrid
local colors = args.colors or myColors
local default_app = args.default_app or nil
-- maximum elements in the middle stack of the gridselect, dirty have to fix it
local maxstack = math.floor( math.log(#appgrid/2) / math.log(2) )
-- update overlay parent widget, doing this here because we have to change the size
w = wibox {
--opacity = 0.8,
max_widget_size = 1000,
ontop = true,
width = 130 * math.pow(maxstack,2),
height = 100 * maxstack,
shape = function(cr, width, height)
gears.shape.rounded_rect(cr, width, height, 8)
end
}
w:set_bg(bg_color)
-- build button widget array
local button_array = {}
for i = 1, #appgrid do
table.insert( button_array,
create_button(appgrid[i][1], appgrid[i][2], colors[ (i % #colors) + 1])
)
end
-- create container widget
local l = wibox.widget {
homogeneous = true,
spacing = 15,
min_cols_size = 10,
min_rows_size = 10,
expand = true,
layout = wibox.layout.grid,
}
-- get entry distribution
local cols = calc_entry_distribution(#appgrid)
-- add buttons to the container widget using the given pattern
local cc = 1
local offset = 0
local defx = 0
local defy = 0
for i = 1, #cols do -- loop for columns
offset = math.floor( ( math.max(table.unpack(cols)) - cols[i]) /2)
for j = 1, cols[i] do -- loop for rows
if (button_array[cc] ~= nil) then
l:add_widget_at(button_array[cc], j + offset, i)
if (cc == default_app) then
defx = j + offset
defy = i
end
end
cc = cc + 1
end
end
-- show it
w:set_widget(l)
w.screen = mouse.screen
w.visible = true
awful.placement.centered(w)
-- focus on middle button
if (default_app == nil) then
x = math.ceil(#cols / 2)
y = math.floor(cols[x] / 2)
else
x = defx
y = defy
end
curr_button = l:get_widgets_at(x,y)
curr_button[1]:emit_signal('mouse::enter')
-- connect to every button but default button to reformat default button on enter
for i = 1, #button_array do
if (i ~= default_app) then
button_array[i]:connect_signal("mouse::enter", function(c)
c:set_fg("#222222")
button_array[default_app]:set_shape( function(cr, width, height) gears.shape.rounded_rect(cr, width, height, 4) end)
end)
end
end
-- add keyboard grabber
capi.keygrabber.run(function(_, key, event)
if event == "release" then return end
if key then
if key == 'Escape' then
capi.keygrabber.stop()
w.visible = false
elseif key == 'Return' then
curr_button[1]:emit_signal( 'button::press' )
elseif key == 'Up' then
local new_x = x - 1
if new_x < 1 then new_x = 1 end
local new_curr_button = l:get_widgets_at(new_x,y)
if new_curr_button and new_curr_button[1] then
curr_button[1]:emit_signal('mouse::leave')
new_curr_button[1]:emit_signal('mouse::enter')
curr_button = new_curr_button
x = new_x
end
elseif key == 'Down' then
local cmax = math.max(table.unpack(cols))
local new_x = x + 1
if new_x > cmax then new_x = cmax end
local new_curr_button = l:get_widgets_at(new_x,y)
if new_curr_button and new_curr_button[1] then
curr_button[1]:emit_signal('mouse::leave')
new_curr_button[1]:emit_signal('mouse::enter')
curr_button = new_curr_button
x = new_x
end
elseif key == 'Left' then
local new_y = y - 1
if new_y < 1 then new_y = 1 end
local new_curr_button = l:get_widgets_at(x,new_y)
if new_curr_button and new_curr_button[1] then
curr_button[1]:emit_signal('mouse::leave')
new_curr_button[1]:emit_signal('mouse::enter')
curr_button = new_curr_button
y = new_y
end
elseif key == 'Right' then
local new_y = y + 1
if new_y > #cols then new_y = #cols end
local new_curr_button = l:get_widgets_at(x,new_y)
if new_curr_button and new_curr_button[1] then
curr_button[1]:emit_signal('mouse::leave')
new_curr_button[1]:emit_signal('mouse::enter')
curr_button = new_curr_button
y = new_y
end
end
if key == 'Escape' or string.match("Return", key) then
capi.keygrabber.stop()
w.visible = false
end
end
end)
end
-- start it
return {
launch = launch
}
Code-Sprache: PHP (php)