Files
hermes-agent/apps/desktop/electron/linux-launcher-ready.ts
teknium1 169c48fa13 fix(desktop): app-grid launches write hermes.desktop only after the window is on screen
`hermes desktop` used to create/refresh `~/.local/share/applications/hermes.desktop`
synchronously before spawning Electron. When the entry is ABSENT (first run after an
update, deleted by a cleaner, tombstoned by AV) that write lands while gnome-shell still
has the grid-launched ShellApp in STARTING; unpatched shells (before GNOME MR !4428)
drop the app's last strong reference on `installed_changed` and the next idle GC kills
the whole Wayland session, minutes to an hour later (#111906, residual after #111396).

Now a launch that carries `DESKTOP_STARTUP_ID` (app grid / menu) defers the write:
the launcher opens a pipe, hands Electron its write end as `HERMES_DESKTOP_READY_FD`
(pass_fds), and a worker thread installs the entry once Electron reports the main
window revealed (`onRevealed` in createWindow, via the new
`apps/desktop/electron/linux-launcher-ready.ts`), plus a 2 s settle so the compositor
has mapped the surface. If Electron exits without ever revealing a window, `finish()`
installs the entry after the exit (STOPPED app, no STARTING object) — self-heal
semantics survive. Terminal launches, the updater's detached relaunch and
`--build-only` have no DESKTOP_STARTUP_ID / spawn no app and keep writing immediately.

Why not simply write after Electron exits (#111915's approach): a daemon thread
started right before `sys.exit` is killed with the interpreter, so the heal is lost,
and a heal that waits for the user to quit the app leaves the menu entry missing for
the whole session.

Co-authored-by: KoNit-K <124019182+KoNit-K@users.noreply.github.com>
2026-09-15 18:29:37 -07:00

49 lines
1.4 KiB
TypeScript

/**
* Tells the `hermes desktop` launcher that the main window is on screen.
*
* On Linux an app-grid launch defers writing the app's own `.desktop` entry
* until the window has mapped: unpatched gnome-shell (before GNOME MR !4428)
* drops a STARTING ShellApp's last reference when its entry changes, and the
* next idle GC takes down the whole Wayland session (#111906). The launcher
* hands us the write end of a pipe in `HERMES_DESKTOP_READY_FD`; one byte means
* "mapped, safe to heal". A launch without the variable is a no-op.
*/
import fs from 'node:fs'
export const LAUNCHER_READY_FD_ENV = 'HERMES_DESKTOP_READY_FD'
type ReadyFdIo = {
writeSync: (fd: number, data: string) => number
closeSync: (fd: number) => void
}
/**
* Signal the launcher once. The fd is closed after the write and the variable
* removed from `env`, so a second reveal (or a child inheriting the env) can
* never write into whatever file later reuses that descriptor number.
*/
export function notifyLauncherWindowRevealed(env: NodeJS.ProcessEnv = process.env, io: ReadyFdIo = fs): boolean {
const raw = env[LAUNCHER_READY_FD_ENV]
if (raw === undefined) {
return false
}
delete env[LAUNCHER_READY_FD_ENV]
const fd = Number(raw)
if (!Number.isInteger(fd) || fd < 0) {
return false
}
try {
io.writeSync(fd, 'r')
io.closeSync(fd)
return true
} catch {
return false
}
}