Ein Grid Select Menu für den Awesome Windowmanager

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.

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)
Teile diesen Beitrag

Kommentar verfassen

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert

:bye: 
:good: 
:negative: 
:scratch: 
:wacko: 
:yahoo: 
B-) 
mehr …
 


Diese Seite verwendet Cookies. Mit der Nutzung von tuxlog erklärst Du Dich mit der Verwendung von Cookies einverstanden. Detaillierte Informationen über die Verwendung von Cookies auf dieser Website findest Du in der Datenschutzerklärung.

Nach oben scrollen