fix(nix): restore desktop app icon matching (#2506)

* 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 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Ivan Malison
2026-08-03 04:34:05 +08:00
committed by GitHub
co-authored by Claude Opus 5
parent fdb39d2111
commit 5d15e40a2b
3 changed files with 47 additions and 3 deletions
+29 -2
View File
@@ -187,11 +187,22 @@ buildNpmPackage {
install -Dm644 packages/desktop/assets/icon.png \ install -Dm644 packages/desktop/assets/icon.png \
$out/share/icons/hicolor/512x512/apps/paseo-desktop.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. # Chromium's setuid sandbox cannot live in the immutable Nix store.
makeWrapper ${electron}/bin/electron $out/bin/paseo-desktop \ 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" \ --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 copyDesktopItems
''} ''}
@@ -219,7 +230,23 @@ buildNpmPackage {
exec = "paseo-desktop"; exec = "paseo-desktop";
icon = "paseo-desktop"; icon = "paseo-desktop";
categories = ["Development"]; 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"; startupWMClass = "Paseo";
noDisplay = true;
}) })
]; ];
@@ -65,6 +65,23 @@ describe("passthrough CLI", () => {
).toBeNull(); ).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", () => { it("ignores Electron remote debugging switches", () => {
expect( expect(
parsePassthroughCliArgs({ parsePassthroughCliArgs({
@@ -2,7 +2,7 @@ import { pathToFileURL } from "node:url";
import { resolvePassthroughCliEntrypoint } from "./entrypoints.js"; import { resolvePassthroughCliEntrypoint } from "./entrypoints.js";
const DESKTOP_CLI_ENV = "PASEO_DESKTOP_CLI"; 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<number>; export type PassthroughCliRunner = (argv: string[]) => Promise<number>;