Press the browser key twice, get one browser

The application keys ran their command unconditionally, so pressing the
browser key twice gave you two browsers. That is the single most common
"Linux feels wrong" moment and it is a twenty-line fix: match the
window class, focus it if it is there, launch if it is not.

Two details the obvious version gets wrong. Patterns are anchored,
because an unanchored "mail" matches gmail-notifier and the mail key
would raise somebody's notifier. And class alone is not enough here:
the terminal and the editor are both kitty, so the editor bind narrows
by title or it raises whatever terminal happened to be open. The
contract's fixture contains both traps.

Found and fixed a bug this shipped with earlier today. Hyprland 0.56
dispatches through Lua, so `hyprctl dispatch focuswindow address:0x...`
is parsed as Lua source and fails with a syntax error -- which is what
the launcher's Switch Window command had been doing since it landed.
Its own contract stubbed hyprctl and recorded the arguments, so the
call looked correct and never ran. Both now use the selector form
hl.focus actually accepts, and the contract asserts the shape rather
than only that something was dispatched.
This commit is contained in:
Gabriel Brown
2026-08-22 06:02:47 -04:00
parent e1a04d2d70
commit 333ab9f6b7
7 changed files with 232 additions and 11 deletions
+33 -6
View File
@@ -136,13 +136,40 @@ local function write_categories()
file:close()
end
-- Focus it if it is already open, start it if it is not -- which is what the
-- application keys do on macOS and Windows, and what pressing the browser key
-- twice ought to do. bin/panama-launch matches on window class, narrowed by
-- title where the class alone cannot tell two things apart: the terminal and
-- the editor are both kitty here, so only the title distinguishes them.
--
-- Patterns are regular expressions and are anchored. An unanchored "mail"
-- would match gmail-notifier, and the mail key would raise somebody's notifier
-- instead. Single-quoted for the shell so a backslash reaches the matcher
-- rather than being eaten on the way.
local function shell_quote(value)
return "'" .. value:gsub("'", "'\\''") .. "'"
end
local launcher_bin = "$HOME/.local/share/Panama/bin/panama-launch"
local function launch_or_focus(class, command, title)
local parts = { launcher_bin, "--class", shell_quote(class) }
if title then
parts[#parts + 1] = "--title"
parts[#parts + 1] = shell_quote(title)
end
parts[#parts + 1] = "--"
parts[#parts + 1] = command
return table.concat(parts, " ")
end
category("Applications")
bind(mod .. " + T", hl.dsp.exec_cmd(terminal), { description = "Terminal" })
bind(mod .. " + N", hl.dsp.exec_cmd(editor), { description = "Neovim" })
bind(mod .. " + W", hl.dsp.exec_cmd(browser), { description = "Browser" })
bind(mod .. " + F", hl.dsp.exec_cmd(files), { description = "Files" })
bind(mod .. " + C", hl.dsp.exec_cmd(calculator), { description = "Calculator" })
bind(mod .. " + E", hl.dsp.exec_cmd(mail), { description = "Mail" })
bind(mod .. " + T", hl.dsp.exec_cmd(launch_or_focus("^kitty$", terminal)), { description = "Terminal" })
bind(mod .. " + N", hl.dsp.exec_cmd(launch_or_focus("^kitty$", editor, "nvim")), { description = "Neovim" })
bind(mod .. " + W", hl.dsp.exec_cmd(launch_or_focus("^helium", browser)), { description = "Browser" })
bind(mod .. " + F", hl.dsp.exec_cmd(launch_or_focus("^org\\.gnome\\.Nautilus$", files)), { description = "Files" })
bind(mod .. " + C", hl.dsp.exec_cmd(launch_or_focus("^org\\.gnome\\.Calculator$", calculator)), { description = "Calculator" })
bind(mod .. " + E", hl.dsp.exec_cmd(launch_or_focus("^org\\.mozilla\\.thunderbird", mail)), { description = "Mail" })
bind(mod .. " + I", hl.dsp.exec_cmd(settings), { description = "Settings" })
bind("CTRL + SHIFT + Escape", hl.dsp.exec_cmd(sysmonitor), { description = "System monitor" })