This repository has no description
0

Configure Feed

Select the types of activity you want to include in your feed.

desktops.sway: fix firefox daemon rebuild restarts, screencast, phantom windows

- firefox.service: restartIfChanged/stopIfChanged = false so nixos-rebuild
switch no longer kills all open browser windows
- xdg-desktop-portal-wlr: retry until system-wide pipewire is reachable
(RestartSec=2, startLimitIntervalSec=0) instead of tripping the start-limit
at boot and staying dead, which silently broke screensharing
- workspace-tabs: register the anchor-window scratchpad rule statically in the
sway config, only persist/restore http(s) URLs, and skip anchor-titled
windows in the sway mapping, so the anchor no longer leaks into workspace
files and reappears as a phantom window after a restart

Assisted-by: Claude Code (claude-opus-4-8[1m])

+47 -4
+9 -2
tools/workspace-manager/workspace-tabs-host.py
··· 93 93 if node_type == "workspace": 94 94 workspace = node.get("name", workspace) 95 95 app_id = node.get("app_id", "") or "" 96 - if "firefox" in app_id.lower() and node_type == "con": 96 + name = node.get("name", "") or "" 97 + # Skip the hidden workspace-manager anchor window — it must never be 98 + # mapped to a real workspace, or it gets saved as a phantom tab. 99 + if ( 100 + "firefox" in app_id.lower() 101 + and node_type == "con" 102 + and not name.startswith("workspace-anchor") 103 + ): 97 104 results.append( 98 105 { 99 106 "workspace": workspace, 100 - "windowTitle": node.get("name", ""), 107 + "windowTitle": name, 101 108 } 102 109 ) 103 110 for child in node.get("nodes", []) + node.get("floating_nodes", []):
+13
2configs/desktops/lib/wayland.nix
··· 45 45 xdg.portal.config.common.default = "wlr"; 46 46 fonts.enableDefaultPackages = true; 47 47 48 + # PipeWire runs system-wide and is usually not ready yet when sway starts the 49 + # screencast portal at login. The default unit retries every 100ms and trips 50 + # the start-limit within a second, then stays dead for the whole session — 51 + # which silently breaks screensharing. Keep retrying with a sane delay and no 52 + # give-up so it comes up as soon as PipeWire accepts connections. 53 + systemd.user.services.xdg-desktop-portal-wlr = { 54 + startLimitIntervalSec = 0; 55 + serviceConfig = { 56 + Restart = lib.mkForce "on-failure"; 57 + RestartSec = lib.mkForce 2; 58 + }; 59 + }; 60 + 48 61 security.polkit.enable = true; 49 62 security.pam.services.swaylock = { }; 50 63 security.pam.services.swaylock-plugin = { };
+11
2configs/desktops/sway/default.nix
··· 38 38 "sway-session.target" 39 39 "workspace-manager.service" 40 40 ]; 41 + # Never restart on `nixos-rebuild switch` — that would kill all open 42 + # browser windows. A new Firefox build is only picked up at the next 43 + # sway session start. 44 + restartIfChanged = false; 45 + stopIfChanged = false; 41 46 serviceConfig = { 42 47 Type = "simple"; 43 48 ExecStart = lib.getExe self.packages.${pkgs.system}.firefox; ··· 441 446 xkb_variant altgr-intl 442 447 xkb_options caps:hyper 443 448 } 449 + 450 + # Hide the workspace-manager Firefox anchor window. Registered statically 451 + # here (not just by the daemon at runtime) so the rule exists before the 452 + # firefox.service starts, otherwise the anchor flashes onto the current 453 + # workspace and gets captured as a phantom tab. 454 + for_window [title="^workspace-anchor"] move scratchpad 444 455 445 456 # flameshot 446 457 for_window [app_id="flameshot"] border pixel 0, floating enable, fullscreen disable, move absolute position 0 0
+14 -2
tools/workspace-manager/firefox-extension/background.js
··· 9 9 const HOST_NAME = "workspace_tabs"; 10 10 const ANCHOR_TITLE = "workspace-anchor"; 11 11 12 + // Only real web pages belong in a workspace file. Internal pages — most 13 + // importantly the anchor window's moz-extension:// page, but also about:* and 14 + // blank tabs — must never be persisted or restored, otherwise a transient 15 + // startup window gets saved and later reappears as a phantom browser window. 16 + function isRestorableUrl(url) { 17 + return ( 18 + !!url && (url.startsWith("http://") || url.startsWith("https://")) 19 + ); 20 + } 21 + 12 22 let port = null; 13 23 let pendingRequests = new Map(); 14 24 let requestId = 0; ··· 179 189 try { 180 190 const tabs = await browser.tabs.query({ windowId }); 181 191 const tabData = tabs 182 - .filter((t) => t.url && !t.url.startsWith("about:")) 192 + .filter((t) => isRestorableUrl(t.url)) 183 193 .map((t) => ({ 184 194 url: t.url, 185 195 pinned: t.pinned, ··· 237 247 238 248 try { 239 249 const resp = await sendNative("get_tabs", { workspace }); 240 - const tabs = resp.tabs || []; 250 + // Drop any non-web entries (e.g. a previously-saved anchor page) so stale 251 + // bad data in existing workspace files can't resurrect a phantom window. 252 + const tabs = (resp.tabs || []).filter((t) => isRestorableUrl(t.url)); 241 253 if (tabs.length === 0) return; 242 254 243 255 // Create window with the first tab to avoid a blank "new tab"