`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>
49 lines
1.4 KiB
TypeScript
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
|
|
}
|
|
}
|