From b942352663a967ef937817f28acc324c698fbe12 Mon Sep 17 00:00:00 2001 From: Pierre Kisters <1524059+lhns@users.noreply.github.com> Date: Tue, 29 Sep 2026 01:32:31 +0200 Subject: [PATCH] Injectors: no re-injection once the unpatch has started On SIGTERM the periodic (and debounced) re-injection of injector.mjs and helper.mjs could still run while or after the unpatch was evaluated, leaving the page patched after the service was gone (e.g. a feature just turned off). helper.mjs also gets a timeout on /json/list, like the injector. --- modules/steam-keyboard-patch/helper.mjs | 6 +++++- modules/steam-ui-patches/injector.mjs | 4 +++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/modules/steam-keyboard-patch/helper.mjs b/modules/steam-keyboard-patch/helper.mjs index ceceeb9..46b94af 100644 --- a/modules/steam-keyboard-patch/helper.mjs +++ b/modules/steam-keyboard-patch/helper.mjs @@ -11,6 +11,7 @@ const [, , patchPath, unpatchPath, xdotool = 'xdotool'] = process.argv; const PATCH = readFileSync(patchPath, 'utf8'); const UNPATCH = readFileSync(unpatchPath, 'utf8'); let current = null; // CDP `call` of the live session, if any +let stopping = false; const CDP = 'http://127.0.0.1:8080/json/list'; const sleep = (ms) => new Promise((r) => setTimeout(r, ms)); // :0 is the Steam session's Xwayland, where the VR app windows live. @@ -64,7 +65,7 @@ function handle(msg) { } async function session() { - const list = await (await fetch(CDP)).json(); + const list = await (await fetch(CDP, { signal: AbortSignal.timeout(3000) })).json(); const t = list.find((x) => x.title === 'SharedJSContext'); if (!t) throw new Error('SharedJSContext not found'); const ws = new WebSocket(t.webSocketDebuggerUrl); @@ -75,6 +76,7 @@ async function session() { }); let last; // logged when it changes (e.g. a signature error once, not every 15 s) const inject = async () => { + if (stopping) return; // never re-patch after the unpatch started const r = await call('Runtime.evaluate', { expression: PATCH, returnByValue: true }); const v = r.result?.result?.value ?? r.result?.exceptionDetails?.exception?.description; if (v !== last || process.env.VRKBD_VERBOSE) console.log('inject:', v); @@ -114,6 +116,8 @@ async function session() { } for (const sig of ['SIGTERM', 'SIGINT']) process.on(sig, async () => { + if (stopping) return; + stopping = true; releaseAll(); if (current) { const r = await Promise.race([current('Runtime.evaluate', { expression: UNPATCH, returnByValue: true }), sleep(2000)]); diff --git a/modules/steam-ui-patches/injector.mjs b/modules/steam-ui-patches/injector.mjs index 9daab8e..af8c2a0 100644 --- a/modules/steam-ui-patches/injector.mjs +++ b/modules/steam-ui-patches/injector.mjs @@ -20,7 +20,8 @@ // Patches must be idempotent expressions; their (awaited) result value is // logged when it changes, e.g. "patched" once, then "unchanged" silently. // On SIGTERM/SIGINT every live session evaluates its patches' unpatch -// expressions (if any) (each with a timeout), then the process exits. +// expressions (if any, each with a timeout) and injects nothing after that, +// then the process exits. // // Persistent state ("state": true): one JSON value per patch, kept in // $XDG_STATE_HOME/steam-frame-nix/ui-patches/.json (default @@ -118,6 +119,7 @@ function open(endpoint, target, list) { const evaluate = (expression) => call('Runtime.evaluate', { expression, returnByValue: true, awaitPromise: true }); const inject = async () => { for (const p of list) { + if (stopping) return; // never re-patch after the unpatch started if (p.state) await evaluate(prelude(p.name)); const v = String(value(await evaluate(p.patch))); if (last.get(p.name) !== v && v !== 'unchanged') console.log(`${tag} ${p.name}: ${v}`);