handy: hide force-shown windows at boot

Handy ignores --start-hidden whenever the tray icon is disabled: without
a tray it force-shows the main window as the only way back in
(lib.rs: `if should_force_show || !should_hide || !tray_available`).
Since both instances run tray-less (GUI access is via the SUPER+G /
SUPER+SHIFT+G binds instead), both windows opened on workspace 1 at
every boot.

Add handy-boot-hide: polls for the first two Handy windows after boot
and dispatches a close on each — Handy intercepts close and hides the
window instead of quitting, which is exactly the hidden state
--start-hidden would have produced. Wired into the Hyprland autostart
after both instances launch.

Note: the Lua config provider parses `hyprctl dispatch` args as a Lua
expression, so the script uses the dispatcher form
`hl.dsp.window.close({ window = 'address:…' })`.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016vLKT7j9KJEsJ4mEVbGyqp
This commit is contained in:
2026-08-14 06:50:29 +02:00
parent 7b5b2f6b7f
commit ec0c06d491
2 changed files with 104 additions and 70 deletions
+20 -7
View File
@@ -21,6 +21,7 @@ hl.monitor({ output = "DP-3", mode = "highres", position = "auto", scale = "auto
-- Set programs that you use -- Set programs that you use
local terminal = "ghostty" local terminal = "ghostty"
local multiplexer = os.getenv("HOME") .. "/.local/bin/herdr"
local fileManager = "nautilus" local fileManager = "nautilus"
local browser = "brave" local browser = "brave"
local menu = "wofi --show drun" local menu = "wofi --show drun"
@@ -29,6 +30,7 @@ local pwManager = "bitwarden-desktop"
-- Hyprland's session PATH is /usr/local/bin:/usr/bin — no ~/.local/bin — -- Hyprland's session PATH is /usr/local/bin:/usr/bin — no ~/.local/bin —
-- so user scripts need their absolute path in autostart and binds. -- so user scripts need their absolute path in autostart and binds.
local handyNo = os.getenv("HOME") .. "/.local/bin/handy-no" local handyNo = os.getenv("HOME") .. "/.local/bin/handy-no"
local handyBootHide = os.getenv("HOME") .. "/.local/bin/handy-boot-hide"
------------------- -------------------
@@ -45,9 +47,12 @@ hl.on("hyprland.start", function()
hl.exec_cmd("nextcloud --background") hl.exec_cmd("nextcloud --background")
hl.exec_cmd("handy --start-hidden") -- English instance (parakeet) hl.exec_cmd("handy --start-hidden") -- English instance (parakeet)
hl.exec_cmd(handyNo .. " start") -- Norwegian instance (whisper-large-v3), private D-Bus + own data dir hl.exec_cmd(handyNo .. " start") -- Norwegian instance (whisper-large-v3), private D-Bus + own data dir
-- Handy ignores --start-hidden when the tray icon is off (it force-shows
-- the window as its only way back in), so hide the first two windows here.
hl.exec_cmd(handyBootHide)
-- Launch apps on specific workspaces (window rules below) -- Launch apps on specific workspaces (window rules below)
hl.exec_cmd("ghostty -e tmux") hl.exec_cmd("ghostty -e " .. multiplexer)
hl.exec_cmd("brave") hl.exec_cmd("brave")
hl.exec_cmd("steam") hl.exec_cmd("steam")
end) end)
@@ -243,8 +248,12 @@ hl.bind(mainMod .. " + SHIFT + G", hl.dsp.exec_cmd(handyNo .. " gui"))
hl.bind("SUPER + SHIFT + L", hl.dsp.exec_cmd("hyprlock")) hl.bind("SUPER + SHIFT + L", hl.dsp.exec_cmd("hyprlock"))
-- System shutdown and reboot with confirmation -- System shutdown and reboot with confirmation
hl.bind(mainMod .. " + Q", hl.dsp.exec_cmd([=[if [[ $(wofi --dmenu --prompt="Shutdown?" --width=200 --height=100 <<< $'Yes\nNo') == "Yes" ]]; then pkill brave; systemctl poweroff; fi]=])) hl.bind(mainMod .. " + Q",
hl.bind(mainMod .. " + R", hl.dsp.exec_cmd([=[if [[ $(wofi --dmenu --prompt="Reboot?" --width=200 --height=100 <<< $'Yes\nNo') == "Yes" ]]; then pkill brave; systemctl reboot; fi]=])) hl.dsp.exec_cmd(
[=[if [[ $(wofi --dmenu --prompt="Shutdown?" --width=200 --height=100 <<< $'Yes\nNo') == "Yes" ]]; then pkill brave; systemctl poweroff; fi]=]))
hl.bind(mainMod .. " + R",
hl.dsp.exec_cmd(
[=[if [[ $(wofi --dmenu --prompt="Reboot?" --width=200 --height=100 <<< $'Yes\nNo') == "Yes" ]]; then pkill brave; systemctl reboot; fi]=]))
-- Move focus with mainMod + hjkl -- Move focus with mainMod + hjkl
hl.bind(mainMod .. " + h", hl.dsp.focus({ direction = "left" })) hl.bind(mainMod .. " + h", hl.dsp.focus({ direction = "left" }))
@@ -273,10 +282,14 @@ hl.bind(mainMod .. " + mouse:272", hl.dsp.window.drag(), { mouse = true })
hl.bind(mainMod .. " + mouse:273", hl.dsp.window.resize(), { mouse = true }) hl.bind(mainMod .. " + mouse:273", hl.dsp.window.resize(), { mouse = true })
-- Laptop multimedia keys for volume and LCD brightness -- Laptop multimedia keys for volume and LCD brightness
hl.bind("XF86AudioRaiseVolume", hl.dsp.exec_cmd("wpctl set-volume -l 1 @DEFAULT_AUDIO_SINK@ 5%+"), { locked = true, repeating = true }) hl.bind("XF86AudioRaiseVolume", hl.dsp.exec_cmd("wpctl set-volume -l 1 @DEFAULT_AUDIO_SINK@ 5%+"),
hl.bind("XF86AudioLowerVolume", hl.dsp.exec_cmd("wpctl set-volume @DEFAULT_AUDIO_SINK@ 5%-"), { locked = true, repeating = true }) { locked = true, repeating = true })
hl.bind("XF86AudioMute", hl.dsp.exec_cmd("wpctl set-mute @DEFAULT_AUDIO_SINK@ toggle"), { locked = true, repeating = true }) hl.bind("XF86AudioLowerVolume", hl.dsp.exec_cmd("wpctl set-volume @DEFAULT_AUDIO_SINK@ 5%-"),
hl.bind("XF86AudioMicMute", hl.dsp.exec_cmd("wpctl set-mute @DEFAULT_AUDIO_SOURCE@ toggle"), { locked = true, repeating = true }) { locked = true, repeating = true })
hl.bind("XF86AudioMute", hl.dsp.exec_cmd("wpctl set-mute @DEFAULT_AUDIO_SINK@ toggle"),
{ locked = true, repeating = true })
hl.bind("XF86AudioMicMute", hl.dsp.exec_cmd("wpctl set-mute @DEFAULT_AUDIO_SOURCE@ toggle"),
{ locked = true, repeating = true })
hl.bind("XF86MonBrightnessUp", hl.dsp.exec_cmd("brightnessctl s 10%+"), { locked = true, repeating = true }) hl.bind("XF86MonBrightnessUp", hl.dsp.exec_cmd("brightnessctl s 10%+"), { locked = true, repeating = true })
hl.bind("XF86MonBrightnessDown", hl.dsp.exec_cmd("brightnessctl s 10%-"), { locked = true, repeating = true }) hl.bind("XF86MonBrightnessDown", hl.dsp.exec_cmd("brightnessctl s 10%-"), { locked = true, repeating = true })
+21
View File
@@ -0,0 +1,21 @@
#!/usr/bin/env bash
# Handy force-shows its main window at startup whenever the tray icon is
# disabled (lib.rs: "If start_hidden but tray is disabled, we must show
# the window anyway" — it can't know the SUPER+G/SHIFT+G binds are the
# way back in). So --start-hidden alone can't keep the two instances
# hidden at boot. Instead: watch for the first two Handy windows and
# dispatch a close — Handy intercepts close and hides the window.
set -euo pipefail
hidden=0
for _ in $(seq 1 150); do # ~30 s window after boot
while read -r addr; do
# Lua-form dispatch: the Lua config provider parses hyprctl
# dispatch args as a Lua expression.
hyprctl dispatch "hl.dsp.window.close({ window = 'address:$addr' })" >/dev/null
hidden=$((hidden + 1))
done < <(hyprctl clients -j | jq -r '.[] | select(.class == "handy") | .address')
[ "$hidden" -ge 2 ] && exit 0
sleep 0.2
done
exit 0