From 5d15e40a2b057ec7775f9884861fb462e637a59d Mon Sep 17 00:00:00 2001 From: Ivan Malison Date: Sun, 2 Aug 2026 13:34:05 -0700 Subject: [PATCH] fix(nix): restore desktop app icon matching (#2506) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix(nix): identify desktop app for icon matching * fix(desktop): ignore wrapper class in CLI passthrough * fix(nix): give the Electron launcher a real app root The Wayland app_id — what a bar or taskbar uses to find the .desktop entry, and therefore the icon — is fixed during Electron startup, so neither `--class=` nor main.ts's `app.setName()` reaches it on current Electron. Launching `electron path/to/main.js` gives Electron no package.json to read a name from, so the window published the default "electron" app_id and shells drew the generic Electron icon. Launch a one-file app root named "paseo-desktop" instead, matching the installed desktop entry and hicolor icon. Older Electron (38) derives the app_id from the runtime app name "Paseo", so also ship a NoDisplay alias entry under that name; the icon resolves either way and the launcher list still shows a single Paseo. Co-Authored-By: Claude Opus 5 --------- Co-authored-by: Claude Opus 5 --- nix/desktop-package.nix | 31 +++++++++++++++++-- .../src/daemon/cli/passthrough.test.ts | 17 ++++++++++ .../desktop/src/daemon/cli/passthrough.ts | 2 +- 3 files changed, 47 insertions(+), 3 deletions(-) diff --git a/nix/desktop-package.nix b/nix/desktop-package.nix index cadb074f0..4f061151d 100644 --- a/nix/desktop-package.nix +++ b/nix/desktop-package.nix @@ -187,11 +187,22 @@ buildNpmPackage { install -Dm644 packages/desktop/assets/icon.png \ $out/share/icons/hicolor/512x512/apps/paseo-desktop.png + # Electron derives Wayland's toplevel app_id from the package name in the + # app root it launches. Point it at a one-file app named "paseo-desktop" + # so shells can match the window to the desktop entry and hicolor icon. + mkdir -p $out/share/paseo-desktop/electron-app + printf '%s\n' "{ \"name\": \"paseo-desktop\", \"version\": \"$version\", \"main\": \"index.js\" }" \ + > $out/share/paseo-desktop/electron-app/package.json + printf '%s\n' 'require("../packages/desktop/dist/main.js");' \ + > $out/share/paseo-desktop/electron-app/index.js + # Chromium's setuid sandbox cannot live in the immutable Nix store. makeWrapper ${electron}/bin/electron $out/bin/paseo-desktop \ - --add-flags "$out/share/paseo-desktop/packages/desktop/dist/main.js" \ + --add-flags "$out/share/paseo-desktop/electron-app" \ --add-flags "--no-sandbox" \ - --set EXPO_DEV_URL "paseo://app/" + --add-flags "--class=paseo-desktop" \ + --set EXPO_DEV_URL "paseo://app/" \ + --set CHROME_DESKTOP "paseo-desktop.desktop" copyDesktopItems ''} @@ -219,7 +230,23 @@ buildNpmPackage { exec = "paseo-desktop"; icon = "paseo-desktop"; categories = ["Development"]; + startupWMClass = "paseo-desktop"; + }) + # Hidden alias entry. Which of the two names Electron ends up publishing as + # the Wayland app_id depends on the Electron version: 41 uses the app-root + # package.json `name` ("paseo-desktop"), 38 uses the runtime app name that + # main.ts sets ("Paseo"). Ship a NoDisplay entry for the second spelling so + # the icon resolves either way without a duplicate launcher item. + (makeDesktopItem { + name = "Paseo"; + desktopName = "Paseo"; + genericName = "AI Coding Agents"; + comment = "Self-hosted daemon for AI coding agents"; + exec = "paseo-desktop"; + icon = "paseo-desktop"; + categories = [ "Development" ]; startupWMClass = "Paseo"; + noDisplay = true; }) ]; diff --git a/packages/desktop/src/daemon/cli/passthrough.test.ts b/packages/desktop/src/daemon/cli/passthrough.test.ts index 84c728e40..e891f7175 100644 --- a/packages/desktop/src/daemon/cli/passthrough.test.ts +++ b/packages/desktop/src/daemon/cli/passthrough.test.ts @@ -65,6 +65,23 @@ describe("passthrough CLI", () => { ).toBeNull(); }); + it("ignores Linux desktop identity arguments injected by the Nix wrapper", () => { + expect( + parsePassthroughCliArgs({ + argv: [ + "/nix/store/electron/bin/electron", + "/nix/store/paseo-desktop/share/paseo-desktop/electron-app", + "--no-sandbox", + "--class=paseo-desktop", + "daemon", + "status", + ], + isDefaultApp: true, + forceCli: false, + }), + ).toEqual(["daemon", "status"]); + }); + it("ignores Electron remote debugging switches", () => { expect( parsePassthroughCliArgs({ diff --git a/packages/desktop/src/daemon/cli/passthrough.ts b/packages/desktop/src/daemon/cli/passthrough.ts index 17b3422c4..a315601d7 100644 --- a/packages/desktop/src/daemon/cli/passthrough.ts +++ b/packages/desktop/src/daemon/cli/passthrough.ts @@ -2,7 +2,7 @@ import { pathToFileURL } from "node:url"; import { resolvePassthroughCliEntrypoint } from "./entrypoints.js"; const DESKTOP_CLI_ENV = "PASEO_DESKTOP_CLI"; -const IGNORED_ARG_PREFIXES = ["-psn_", "--no-sandbox", "--remote-debugging-port="]; +const IGNORED_ARG_PREFIXES = ["-psn_", "--class=", "--no-sandbox", "--remote-debugging-port="]; export type PassthroughCliRunner = (argv: string[]) => Promise;