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
+83 -70
View File
@@ -20,15 +20,17 @@ hl.monitor({ output = "DP-3", mode = "highres", position = "auto", scale = "auto
---------------------
-- Set programs that you use
local terminal = "ghostty"
local fileManager = "nautilus"
local browser = "brave"
local menu = "wofi --show drun"
local pwManager = "bitwarden-desktop"
local terminal = "ghostty"
local multiplexer = os.getenv("HOME") .. "/.local/bin/herdr"
local fileManager = "nautilus"
local browser = "brave"
local menu = "wofi --show drun"
local pwManager = "bitwarden-desktop"
-- Hyprland's session PATH is /usr/local/bin:/usr/bin — no ~/.local/bin —
-- 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"
-------------------
@@ -43,18 +45,21 @@ hl.on("hyprland.start", function()
-- hl.exec_cmd("wl-paste --type image --watch cliphist store") -- Stores only image data
hl.exec_cmd("nextcloud --background")
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("handy --start-hidden") -- English instance (parakeet)
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)
hl.exec_cmd("ghostty -e tmux")
hl.exec_cmd("ghostty -e " .. multiplexer)
hl.exec_cmd("brave")
hl.exec_cmd("steam")
end)
hl.window_rule({ match = { class = "^(com.mitchellh.ghostty)$" }, workspace = "1 silent" })
hl.window_rule({ match = { class = "^(brave-browser)$" }, workspace = "2 silent" })
hl.window_rule({ match = { class = "^(steam)$" }, workspace = "3 silent" })
hl.window_rule({ match = { class = "^(brave-browser)$" }, workspace = "2 silent" })
hl.window_rule({ match = { class = "^(steam)$" }, workspace = "3 silent" })
-------------------------------
@@ -75,12 +80,12 @@ hl.env("__GLX_VENDOR_LIBRARY_NAME", "nvidia")
-- Refer to https://wiki.hypr.land/Configuring/Basics/Variables/
hl.config({
general = {
gaps_in = 5,
gaps_out = 20,
gaps_in = 5,
gaps_out = 20,
border_size = 2,
border_size = 2,
col = {
col = {
active_border = { colors = { "rgba(33ccffee)", "rgba(00ff99ee)" }, angle = 45 },
inactive_border = "rgba(595959aa)",
},
@@ -89,27 +94,27 @@ hl.config({
resize_on_border = true,
-- Please see https://wiki.hypr.land/Configuring/Advanced-and-Cool/Tearing/ before you turn this on
allow_tearing = false,
allow_tearing = false,
layout = "dwindle",
layout = "dwindle",
},
decoration = {
rounding = 10,
rounding_power = 2,
rounding = 10,
rounding_power = 2,
-- Change transparency of focused and unfocused windows
active_opacity = 1.0,
inactive_opacity = 1.0,
shadow = {
shadow = {
enabled = true,
range = 4,
render_power = 3,
color = "rgba(1a1a1aee)",
},
blur = {
blur = {
enabled = true,
size = 3,
passes = 1,
@@ -124,27 +129,27 @@ hl.config({
})
-- Default curves and animations, see https://wiki.hypr.land/Configuring/Advanced-and-Cool/Animations/
hl.curve("easeOutQuint", { type = "bezier", points = { {0.23, 1}, {0.32, 1} } })
hl.curve("easeInOutCubic", { type = "bezier", points = { {0.65, 0.05}, {0.36, 1} } })
hl.curve("linear", { type = "bezier", points = { {0, 0}, {1, 1} } })
hl.curve("almostLinear", { type = "bezier", points = { {0.5, 0.5}, {0.75, 1} } })
hl.curve("quick", { type = "bezier", points = { {0.15, 0}, {0.1, 1} } })
hl.curve("easeOutQuint", { type = "bezier", points = { { 0.23, 1 }, { 0.32, 1 } } })
hl.curve("easeInOutCubic", { type = "bezier", points = { { 0.65, 0.05 }, { 0.36, 1 } } })
hl.curve("linear", { type = "bezier", points = { { 0, 0 }, { 1, 1 } } })
hl.curve("almostLinear", { type = "bezier", points = { { 0.5, 0.5 }, { 0.75, 1 } } })
hl.curve("quick", { type = "bezier", points = { { 0.15, 0 }, { 0.1, 1 } } })
hl.animation({ leaf = "global", enabled = true, speed = 10, bezier = "default" })
hl.animation({ leaf = "border", enabled = true, speed = 5.39, bezier = "easeOutQuint" })
hl.animation({ leaf = "windows", enabled = true, speed = 4.79, bezier = "easeOutQuint" })
hl.animation({ leaf = "windowsIn", enabled = true, speed = 4.1, bezier = "easeOutQuint", style = "popin 87%" })
hl.animation({ leaf = "windowsOut", enabled = true, speed = 1.49, bezier = "linear", style = "popin 87%" })
hl.animation({ leaf = "fadeIn", enabled = true, speed = 1.73, bezier = "almostLinear" })
hl.animation({ leaf = "fadeOut", enabled = true, speed = 1.46, bezier = "almostLinear" })
hl.animation({ leaf = "fade", enabled = true, speed = 3.03, bezier = "quick" })
hl.animation({ leaf = "layers", enabled = true, speed = 3.81, bezier = "easeOutQuint" })
hl.animation({ leaf = "layersIn", enabled = true, speed = 4, bezier = "easeOutQuint", style = "fade" })
hl.animation({ leaf = "layersOut", enabled = true, speed = 1.5, bezier = "linear", style = "fade" })
hl.animation({ leaf = "fadeLayersIn", enabled = true, speed = 1.79, bezier = "almostLinear" })
hl.animation({ leaf = "global", enabled = true, speed = 10, bezier = "default" })
hl.animation({ leaf = "border", enabled = true, speed = 5.39, bezier = "easeOutQuint" })
hl.animation({ leaf = "windows", enabled = true, speed = 4.79, bezier = "easeOutQuint" })
hl.animation({ leaf = "windowsIn", enabled = true, speed = 4.1, bezier = "easeOutQuint", style = "popin 87%" })
hl.animation({ leaf = "windowsOut", enabled = true, speed = 1.49, bezier = "linear", style = "popin 87%" })
hl.animation({ leaf = "fadeIn", enabled = true, speed = 1.73, bezier = "almostLinear" })
hl.animation({ leaf = "fadeOut", enabled = true, speed = 1.46, bezier = "almostLinear" })
hl.animation({ leaf = "fade", enabled = true, speed = 3.03, bezier = "quick" })
hl.animation({ leaf = "layers", enabled = true, speed = 3.81, bezier = "easeOutQuint" })
hl.animation({ leaf = "layersIn", enabled = true, speed = 4, bezier = "easeOutQuint", style = "fade" })
hl.animation({ leaf = "layersOut", enabled = true, speed = 1.5, bezier = "linear", style = "fade" })
hl.animation({ leaf = "fadeLayersIn", enabled = true, speed = 1.79, bezier = "almostLinear" })
hl.animation({ leaf = "fadeLayersOut", enabled = true, speed = 1.39, bezier = "almostLinear" })
hl.animation({ leaf = "workspaces", enabled = true, speed = 1.94, bezier = "almostLinear", style = "fade" })
hl.animation({ leaf = "workspacesIn", enabled = true, speed = 1.21, bezier = "almostLinear", style = "fade" })
hl.animation({ leaf = "workspaces", enabled = true, speed = 1.94, bezier = "almostLinear", style = "fade" })
hl.animation({ leaf = "workspacesIn", enabled = true, speed = 1.21, bezier = "almostLinear", style = "fade" })
hl.animation({ leaf = "workspacesOut", enabled = true, speed = 1.94, bezier = "almostLinear", style = "fade" })
-- Ref https://wiki.hypr.land/Configuring/Basics/Workspace-Rules/
@@ -180,17 +185,17 @@ hl.config({
hl.config({
input = {
kb_layout = "us,no",
kb_variant = "",
kb_model = "",
kb_options = "caps:escape,grp:alt_shift_toggle",
kb_rules = "",
kb_layout = "us,no",
kb_variant = "",
kb_model = "",
kb_options = "caps:escape,grp:alt_shift_toggle",
kb_rules = "",
follow_mouse = 1,
sensitivity = 0, -- -1.0 - 1.0, 0 means no modification.
sensitivity = 0, -- -1.0 - 1.0, 0 means no modification.
touchpad = {
touchpad = {
natural_scroll = false,
},
},
@@ -211,14 +216,14 @@ hl.device({
local mainMod = "SUPER" -- Sets "Windows" key as main modifier
-- See https://wiki.hypr.land/Configuring/Basics/Binds/ for more
hl.bind(mainMod .. " + C", hl.dsp.window.close())
hl.bind(mainMod .. " + M", hl.dsp.exec_cmd("uwsm stop")) -- graceful uwsm session teardown instead of hl.dsp.exit()
hl.bind(mainMod .. " + F", hl.dsp.window.fullscreen())
hl.bind(mainMod .. " + C", hl.dsp.window.close())
hl.bind(mainMod .. " + M", hl.dsp.exec_cmd("uwsm stop")) -- graceful uwsm session teardown instead of hl.dsp.exit()
hl.bind(mainMod .. " + F", hl.dsp.window.fullscreen())
hl.bind(mainMod .. " + Return", hl.dsp.exec_cmd(terminal))
hl.bind(mainMod .. " + E", hl.dsp.exec_cmd(fileManager))
hl.bind(mainMod .. " + space", hl.dsp.exec_cmd(menu))
hl.bind(mainMod .. " + B", hl.dsp.exec_cmd(browser))
hl.bind(mainMod .. " + P", hl.dsp.exec_cmd(pwManager))
hl.bind(mainMod .. " + E", hl.dsp.exec_cmd(fileManager))
hl.bind(mainMod .. " + space", hl.dsp.exec_cmd(menu))
hl.bind(mainMod .. " + B", hl.dsp.exec_cmd(browser))
hl.bind(mainMod .. " + P", hl.dsp.exec_cmd(pwManager))
hl.bind(mainMod .. " + A", hl.dsp.exec_cmd("grimblast copy area"))
hl.bind(mainMod .. " + S", hl.dsp.exec_cmd("grimblast save area"))
@@ -237,14 +242,18 @@ hl.bind(mainMod .. " + T", hl.dsp.exec_cmd("handy --toggle-transcription"))
hl.bind(mainMod .. " + T", hl.dsp.exec_cmd("handy --toggle-transcription"), { release = true })
hl.bind(mainMod .. " + Y", hl.dsp.exec_cmd(handyNo .. " toggle"))
hl.bind(mainMod .. " + Y", hl.dsp.exec_cmd(handyNo .. " toggle"), { release = true })
hl.bind(mainMod .. " + G", hl.dsp.exec_cmd("handy"))
hl.bind(mainMod .. " + SHIFT + G", hl.dsp.exec_cmd(handyNo .. " gui"))
hl.bind(mainMod .. " + G", hl.dsp.exec_cmd("handy"))
hl.bind(mainMod .. " + SHIFT + G", hl.dsp.exec_cmd(handyNo .. " gui"))
hl.bind("SUPER + SHIFT + L", hl.dsp.exec_cmd("hyprlock"))
-- 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 .. " + R", hl.dsp.exec_cmd([=[if [[ $(wofi --dmenu --prompt="Reboot?" --width=200 --height=100 <<< $'Yes\nNo') == "Yes" ]]; then pkill brave; systemctl reboot; fi]=]))
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 .. " + 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
hl.bind(mainMod .. " + h", hl.dsp.focus({ direction = "left" }))
@@ -256,7 +265,7 @@ hl.bind(mainMod .. " + j", hl.dsp.focus({ direction = "down" }))
-- Move active window to a workspace with mainMod + SHIFT + [0-9]
for i = 1, 10 do
local key = i % 10 -- 10 maps to key 0
hl.bind(mainMod .. " + " .. key, hl.dsp.focus({ workspace = i }))
hl.bind(mainMod .. " + " .. key, hl.dsp.focus({ workspace = i }))
hl.bind(mainMod .. " + SHIFT + " .. key, hl.dsp.window.move({ workspace = i }))
end
@@ -266,25 +275,29 @@ end
-- Scroll through existing workspaces with mainMod + scroll
hl.bind(mainMod .. " + mouse_down", hl.dsp.focus({ workspace = "e+1" }))
hl.bind(mainMod .. " + mouse_up", hl.dsp.focus({ workspace = "e-1" }))
hl.bind(mainMod .. " + mouse_up", hl.dsp.focus({ workspace = "e-1" }))
-- Move/resize windows with mainMod + LMB/RMB and dragging
hl.bind(mainMod .. " + mouse:272", hl.dsp.window.drag(), { mouse = true })
hl.bind(mainMod .. " + mouse:272", hl.dsp.window.drag(), { mouse = true })
hl.bind(mainMod .. " + mouse:273", hl.dsp.window.resize(), { mouse = true })
-- 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("XF86AudioLowerVolume", hl.dsp.exec_cmd("wpctl set-volume @DEFAULT_AUDIO_SINK@ 5%-"), { 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("XF86MonBrightnessDown", hl.dsp.exec_cmd("brightnessctl s 10%-"), { locked = true, repeating = true })
hl.bind("XF86AudioRaiseVolume", hl.dsp.exec_cmd("wpctl set-volume -l 1 @DEFAULT_AUDIO_SINK@ 5%+"),
{ locked = true, repeating = true })
hl.bind("XF86AudioLowerVolume", hl.dsp.exec_cmd("wpctl set-volume @DEFAULT_AUDIO_SINK@ 5%-"),
{ 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("XF86MonBrightnessDown", hl.dsp.exec_cmd("brightnessctl s 10%-"), { locked = true, repeating = true })
-- Requires playerctl
hl.bind("XF86AudioNext", hl.dsp.exec_cmd("playerctl next"), { locked = true })
hl.bind("XF86AudioNext", hl.dsp.exec_cmd("playerctl next"), { locked = true })
hl.bind("XF86AudioPause", hl.dsp.exec_cmd("playerctl play-pause"), { locked = true })
hl.bind("XF86AudioPlay", hl.dsp.exec_cmd("playerctl play-pause"), { locked = true })
hl.bind("XF86AudioPrev", hl.dsp.exec_cmd("playerctl previous"), { locked = true })
hl.bind("XF86AudioPlay", hl.dsp.exec_cmd("playerctl play-pause"), { locked = true })
hl.bind("XF86AudioPrev", hl.dsp.exec_cmd("playerctl previous"), { locked = true })
--------------------------------