From f82a6430ea1aecfdbf658ccedd82d49435e889aa Mon Sep 17 00:00:00 2001 From: rope Date: Sun, 16 Aug 2026 19:18:32 +0100 Subject: [PATCH] hyprland: match scratchpads by dedicated ghostty class, not title --- settings/hyprland.nix | 42 +++++++++++++++++++++++++----------------- 1 file changed, 25 insertions(+), 17 deletions(-) diff --git a/settings/hyprland.nix b/settings/hyprland.nix index 4d74837..4c9fb94 100644 --- a/settings/hyprland.nix +++ b/settings/hyprland.nix @@ -263,17 +263,25 @@ in hl.layer_rule({ match = { namespace = "quickshell-bar" }, blur = true, ignore_alpha = 0.3 }) hl.layer_rule({ match = { namespace = "quickshell-osd" }, blur = true, ignore_alpha = 0.3 }) - -- Scratchpad respawn: both special-workspace terminals are meant to - -- exist for the whole session, so SUPER+Q on them reads as "reset - -- this thing", not "close it". Relaunch on close. + -- Both special-workspace terminals get their own --class, which does + -- two things ghostty's --title cannot. First, gtk-single-instance + -- means a second `ghostty` hands its request to the running instance + -- and exits; forwarded requests keep -e but DROP --title, so the + -- window ends up named by the shell and no title rule matches it. + -- A distinct class forces a separate instance. Second, the class is + -- fixed at surface creation, so the shell's own title escapes can't + -- desync the window rules or the respawn below. + -- + -- Respawn: these are meant to exist for the whole session, so SUPER+Q + -- on them reads as "reset this thing", not "close it". local scratchpads = { - ["scratchpad"] = "ghostty --title=scratchpad", - ["spotify-player"] = "ghostty --title=spotify-player -e spotify_player", + ["dev.fred.scratchpad"] = "ghostty --class=dev.fred.scratchpad --title=scratchpad", + ["dev.fred.spotify"] = "ghostty --class=dev.fred.spotify --title=spotify-player -e spotify_player", } local shutting_down = false hl.on("hyprland.shutdown", function() shutting_down = true end) hl.on("window.close", function(win) - local cmd = scratchpads[win.title] + local cmd = scratchpads[win.class] if cmd and not shutting_down then hl.exec_cmd(cmd) end end) @@ -295,11 +303,11 @@ in -- Warms spotify-player's Spotify Connect device while you're -- doing something else; the window rule above swallows it onto -- special:music silently, so it never steals focus. - hl.exec_cmd("ghostty --title=spotify-player -e spotify_player") + hl.exec_cmd(scratchpads["dev.fred.spotify"]) -- Scratchpad terminal, same trick: parked on special:scratch at -- login so SUPER+Return is always an instant toggle and the shell -- keeps its history/cwd between peeks. - hl.exec_cmd("ghostty --title=scratchpad") + hl.exec_cmd(scratchpads["dev.fred.scratchpad"]) -- swayosd removed; volume/brightness OSD handled by quickshell ${lib.optionalString isMacbook ''hl.exec_cmd("hypridle")''} end) @@ -343,38 +351,38 @@ in -- Spotify's backend, and that cost is per *process*, so relaunching -- a terminal for it means waiting every time. Parking one instance -- here pays it once at login; SUPER+M slides it in and out. - -- Matched on title, not class: gtk-single-instance means every - -- ghostty window is the same app-id, and --title is authoritative - -- (ghostty ignores the program's own title escapes once it is set). + -- Matched on the dedicated --class set above, not on title: a shell + -- prompt rewrites its window title, and forwarded single-instance + -- launches never carry --title in the first place. hl.window_rule({ - match = { title = "^spotify-player$" }, + match = { class = "^dev.fred.spotify$" }, workspace = "special:music silent", }) -- Inset it rather than filling the screen, so the workspace -- underneath still frames it. Floating is what makes a percentage -- size stick: a tiled window is sized by the layout, which would - -- just expand it back to the full area. Matched on title for the + -- just expand it back to the full area. Matched on class for the -- same reason as above, and because matching on the special -- workspace would depend on the assignment rule running first. hl.window_rule({ name = "spotify-player-inset", - match = { title = "^spotify-player$" }, + match = { class = "^dev.fred.spotify$" }, float = true, size = "80% 80%", center = true, }) - -- Scratchpad terminal — same title-match + float-to-inset pattern as + -- Scratchpad terminal — same class-match + float-to-inset pattern as -- spotify-player above. hl.window_rule({ - match = { title = "^scratchpad$" }, + match = { class = "^dev.fred.scratchpad$" }, workspace = "special:scratch silent", }) hl.window_rule({ name = "scratchpad-inset", - match = { title = "^scratchpad$" }, + match = { class = "^dev.fred.scratchpad$" }, float = true, size = "80% 80%", center = true,