Trim: firefox launcher, debugger-arm migration, comments and docs

- firefox: the prefs.js patterns built once; the extension link condition
  without the redundant desktopFix (it implies a pref).
- install.sh steamvr-debugger-arm: no second copy of the old-marker
  migration (cleanup, run on every switch before the arm unit exists,
  already does it).
- steam-ui-patches: shorter state option description; lib's description
  mentions extraArgs.
- README: uiPatches.patches fields in the options table (moved from
  docs/ui-patches.md); docs/ui-patches.md: the finder helpers;
  docs/steamvr-debugger.md: repeated sentences removed.
This commit is contained in:
Pierre Kisters committed 2026-09-29 01:33:16 +02:00
1 parent a5ecbad76d
commit b3d93e8526
8 files changed
+43 -69

No files matched your search

+10 -4
View File
@@ -4,9 +4,9 @@
Valve Steam Frame (SteamOS, `aarch64-linux`, standalone home-manager). They Valve Steam Frame (SteamOS, `aarch64-linux`, standalone home-manager). They
work around quirks of the Frame's [two graphical sessions](#two-sessions) work around quirks of the Frame's [two graphical sessions](#two-sessions)
(portal, keyboard layout, clipboard, KDE wallet, Firefox), enable hardware (portal, keyboard layout, clipboard, KDE wallet, Firefox), enable hardware
video decoding in Jellyfin, run rootless Docker, and extend Steam's and SteamVR's UIs at runtime (VR keyboard, video decoding in Jellyfin, run rootless Docker, and extend Steam's and
"+" menu, dashboard windows, Steam close button, window curvature, window SteamVR's UIs at runtime (VR keyboard, "+" menu, dashboard windows, Steam
controls). close button, window curvature, window controls).
Everything is declarative: files are links into the Nix store, UI patches Everything is declarative: files are links into the Nix store, UI patches
live in memory. The few things that have to be written elsewhere at runtime live in memory. The few things that have to be written elsewhere at runtime
@@ -253,7 +253,13 @@ Every module imports `cleanup` (see
| `steamFrame.keyboard.vr.backspaceDrag.wordDetentPixels` | int | `90` | Extra travel across a word border (`0`: none). | | `steamFrame.keyboard.vr.backspaceDrag.wordDetentPixels` | int | `90` | Extra travel across a word border (`0`: none). |
| `steamFrame.keyboard.vr.haptics` | bool | `true` | Haptic ticks for drag steps, word detents and picks. | | `steamFrame.keyboard.vr.haptics` | bool | `true` | Haptic ticks for drag steps, word detents and picks. |
| `steamFrame.keyboard.vr.checks` | package, read-only | | The tests, built with the configured dictionary. | | `steamFrame.keyboard.vr.checks` | package, read-only | | The tests, built with the configured dictionary. |
| `steamFrame.uiPatches.patches` | list of submodules | `[ ]` | Runtime patches of Steam's web UIs, see [UI patches](docs/ui-patches.md). | | `steamFrame.uiPatches.patches` | list of submodules | `[ ]` | Runtime patches of Steam's web UIs, see [UI patches](docs/ui-patches.md#defining-a-patch). Fields below. |
| `steamFrame.uiPatches.patches.*.name` | str | required | Unique name (log). |
| `steamFrame.uiPatches.patches.*.endpoint` | str | `"http://127.0.0.1:8080"` | DevTools base URL, `/json/list` polled every 5 s: Steam 8080, SteamVR 8087. |
| `steamFrame.uiPatches.patches.*.target.{title,titleRegex,urlRegex}` | null or str | `null` | Pages to patch: exact title, JS regexes; all given ones must match. |
| `steamFrame.uiPatches.patches.*.patch` | path | required | JS evaluated (awaited) in every matching page. |
| `steamFrame.uiPatches.patches.*.unpatch` | null or path | `null` | JS evaluated when the service stops. |
| `steamFrame.uiPatches.patches.*.state` | bool | `false` | One [persistent JSON value](docs/ui-patches.md#persistent-state) for the patch. |
| `steamFrame.uiPatches.lib` | attrs, read-only | | Patch helpers (`mkPatch`), see [Finders and signatures](docs/ui-patches.md#mkpatch). | | `steamFrame.uiPatches.lib` | attrs, read-only | | Patch helpers (`mkPatch`), see [Finders and signatures](docs/ui-patches.md#mkpatch). |
| `steamFrame.launcherMenu.sort` | bool | `false` | Sort the "+" menu alphabetically. | | `steamFrame.launcherMenu.sort` | bool | `false` | Sort the "+" menu alphabetically. |
| `steamFrame.launcherMenu.pinDesktop` | null or `"top"` / `"bottom"` | `null` | Pin "Desktop" above/below the "+" menu's list; `null`: normal entry. | | `steamFrame.launcherMenu.pinDesktop` | null or `"top"` / `"bottom"` | `null` | Pin "Desktop" above/below the "+" menu's list; `null`: normal entry. |
+3 -9
View File
@@ -44,12 +44,6 @@ Port: `VRWebHelper/DebuggerPort`, default 8087.
- when SteamVR stops, the key goes back to its previous value (removed if - when SteamVR stops, the key goes back to its previous value (removed if
it wasn't there) and `.armed` is removed. it wasn't there) and `.armed` is removed.
The runtime files don't need Nix, which is why rollback and uninstall are The runtime files need no Nix (rollback, uninstall) and are gone at reboot;
covered; they are gone at reboot, and `.armed` (the only trace after a `.armed`, the only trace after a power loss, is resolved by the next
power loss) is resolved by the next SteamVR start or SteamVR start or `steam-frame-nix-cleanup`.
`steam-frame-nix-cleanup`. Disabled, there is no unit; the switch (cleanup)
restores the key if SteamVR is stopped, otherwise the runtime drop-in does
when it stops.
Until SteamVR has been restarted once with the drop-in, the port is closed
and `steam-ui-patches` keeps polling it.
+10 -17
View File
@@ -65,16 +65,8 @@ it runs. What they have in common:
## Defining a patch ## Defining a patch
`steamFrame.uiPatches.patches` entries: A `steamFrame.uiPatches.patches` entry (fields:
[README, Options](../README.md#options)):
| Attribute | Default | Description |
|---|---|---|
| `name` | | Unique name (log). |
| `endpoint` | `"http://127.0.0.1:8080"` | DevTools base URL; `/json/list` is polled every 5 s. |
| `target.title` / `target.titleRegex` / `target.urlRegex` | `null` | Pages to patch; all given criteria must match (JS regexes). |
| `patch` | | JS file evaluated in every matching page (awaited). |
| `unpatch` | `null` | JS file evaluated when the service stops. |
| `state` | `false` | Give the patch one [persistent JSON value](#persistent-state). |
```nix ```nix
steamFrame.uiPatches.patches = [ { steamFrame.uiPatches.patches = [ {
@@ -128,9 +120,11 @@ patches never use them. `modules/lib/finders.js` (like Decky Loader's
`findInReactTree`). `findInReactTree`).
Every signature must match exactly once, otherwise the patch changes nothing Every signature must match exactly once, otherwise the patch changes nothing
and reports it (e.g. `signature not found, Steam left unpatched: and reports it: `resolvePatch` returns e.g. `signature not found, Steam left
layouts.currentLayout (module 40222): ambiguous export, candidates r_, xy`). unpatched: layouts.currentLayout (module 40222): ambiguous export,
Results are cached per page. candidates r_, xy` as the patch's result. Results are cached per page. The
library also has `ensureStyle(doc, id, css)` and `logger(buffer)` (a capped
debug log).
Signatures live in `modules/lib/signatures.json`, shared by patches and the Signatures live in `modules/lib/signatures.json`, shared by patches and the
offline checker. An entry can also list `expects` (strings the patch relies offline checker. An entry can also list `expects` (strings the patch relies
@@ -164,10 +158,9 @@ steamFrame.uiPatches.patches = [ {
```js ```js
((find, sigs, opts, hooks) => { ((find, sigs, opts, hooks) => {
let mods; const mods = find.resolvePatch('webpackChunksteamui', sigs); // SteamVR dashboard: 'webpackChunkvrwebui'
try { mods = find.resolveAll(find.getWebpackRequire('webpackChunksteamui'), sigs); } if (typeof mods === 'string') return mods; // signature not found
catch (e) { return `not patched: ${e.message}`; } const Thing = mods.thing.exports.Thing;
const Thing = mods.thing.exports.Thing; // SteamVR dashboard: 'webpackChunkvrwebui'
… …
}) })
``` ```
-6
View File
@@ -591,12 +591,6 @@ cmd_debugger_arm() {
local cur tmp local cur tmp
need_not_root need_not_root
command -v jq >/dev/null 2>&1 || die "jq not found" command -v jq >/dev/null 2>&1 || die "jq not found"
if [[ -f $DEBUGGER_MARKER_V1 ]]; then # marker of older versions: key was absent
mkdir -p "$SFN_STATE"
[[ -f $DEBUGGER_ARMED ]] || printf 'absent\n' >"$DEBUGGER_ARMED"
rm -f -- "$DEBUGGER_MARKER_V1"
info "migrated $DEBUGGER_MARKER_V1 to $DEBUGGER_ARMED"
fi
if [[ ! -f $DEBUGGER_ARMED ]]; then if [[ ! -f $DEBUGGER_ARMED ]]; then
if [[ -f $VRSETTINGS ]]; then if [[ -f $VRSETTINGS ]]; then
cur="$(jq -r 'if (.VRWebHelper | type) == "object" and (.VRWebHelper | has("DebuggerEnabled")) cur="$(jq -r 'if (.VRWebHelper | type) == "object" and (.VRWebHelper | has("DebuggerEnabled"))
-2
View File
@@ -203,11 +203,9 @@ pkgs.runCommand "cleanup-check" { nativeBuildInputs = [ cleanup pkgs.jq ]; } ''
arm() { bash ${../../install.sh} steamvr-debugger-arm; } arm() { bash ${../../install.sh} steamvr-debugger-arm; }
fresh e fresh e
printf '{\n "steamvr" : {}\n}\n' > $V printf '{\n "steamvr" : {}\n}\n' > $V
touch $S/steamvr-debugger # old marker: migrated
echo activating > $STUB/state echo activating > $STUB/state
res=$(arm); echo "$res" res=$(arm); echo "$res"
[ "$(cat $S/steamvr-debugger.armed)" = absent ] || fail "armed" [ "$(cat $S/steamvr-debugger.armed)" = absent ] || fail "armed"
gone $S/steamvr-debugger
[ "$(jq -c .VRWebHelper $V)" = '{"DebuggerEnabled":true}' ] || fail "arm: $(cat $V)" [ "$(jq -c .VRWebHelper $V)" = '{"DebuggerEnabled":true}' ] || fail "arm: $(cat $V)"
there $root/run/steam-frame-nix/steamvr-debugger-restore there $root/run/steam-frame-nix/steamvr-debugger-restore
[ "$(grep -c daemon-reload $STUB/log)" = 1 ] || fail "arm reload" [ "$(grep -c daemon-reload $STUB/log)" = 1 ] || fail "arm reload"
+10 -17
View File
@@ -1,25 +1,18 @@
# Firefox Flatpak (org.mozilla.firefox) launcher. # Firefox Flatpak (org.mozilla.firefox) launcher.
# - prefs and vrFullscreenFix are default prefs (pref(), the default branch: # - prefs and vrFullscreenFix are default prefs (pref(): never written to
# never written to prefs.js, so removing one leaves nothing behind). Firefox # prefs.js, so removing one leaves nothing behind) in defaults/pref/ of the
# reads defaults/pref/*.js from its system config dir, /app/etc/firefox in # org.mozilla.firefox.systemconfig extension (/app/etc/firefox), provided
# the Flatpak, the mount point of the org.mozilla.firefox.systemconfig # as the user installation's "unmaintained extension"
# extension. The user installation's "unmaintained extension" dir # ($XDG_DATA_HOME/flatpak/extension/<id>/<arch>/<branch>): a Home Manager
# ($XDG_DATA_HOME/flatpak/extension/<id>/<arch>/<branch>) provides it: a # link to a store dir, which Flatpak mounts itself (the sandbox needn't see
# home-manager link to a store dir, which Flatpak mounts itself (the # /nix).
# sandbox needn't see /nix).
# - vrFullscreenFix: in the Steam session gamescope focuses a fullscreen X11 # - vrFullscreenFix: in the Steam session gamescope focuses a fullscreen X11
# window but never shows it (Firefox looks frozen). ignore-widgets keeps # window but never shows it (Firefox looks frozen). ignore-widgets keeps
# fullscreen inside the window. # fullscreen inside the window.
# - desktopProfile: the sessions have separate buses/displays, so a second # - desktopProfile: the sessions have separate buses/displays, so a second
# Firefox can't reach the running one and hits the profile lock; the nested # Firefox can't reach the running one and hits the profile lock; the nested
# desktop gets its own profile (a normal Firefox profile: browser data). # desktop gets its own profile, where fullscreen works: the launcher undoes
# Fullscreen works there, so the launcher undoes the fix in that profile # the fix there while its Firefox runs (firefox/launcher.nix).
# only while its Firefox runs: a user.js link to the extension's
# steam-frame-nix-desktop-user.js (a sandbox path; dangling on the host),
# made right before Firefox starts and removed, with the value Firefox
# stored from it in prefs.js, once it has exited (firefox/launcher.nix).
# Leftovers (a crash) and what older versions wrote into profiles (user.js
# copies and links) are removed by steam-frame-nix-cleanup (on switch).
# The entry shadows the Flatpak's (same ID), keeping MIME associations, and is # The entry shadows the Flatpak's (same ID), keeping MIME associations, and is
# seen by the "+" menu (which reads only ~/.local/share/applications). # seen by the "+" menu (which reads only ~/.local/share/applications).
# defaultBrowser: without a default the portal picks the first installed # defaultBrowser: without a default the portal picks the first installed
@@ -122,7 +115,7 @@ in {
# stable: the branch the launcher runs (the extension point has no # stable: the branch the launcher runs (the extension point has no
# version, so it takes the app's branch). # version, so it takes the app's branch).
xdg.dataFile = lib.optionalAttrs (defaultPrefs != { } || desktopFix) { xdg.dataFile = lib.optionalAttrs (defaultPrefs != { }) { # desktopFix implies a pref
"flatpak/extension/org.mozilla.firefox.systemconfig/aarch64/stable".source = sysconfig; "flatpak/extension/org.mozilla.firefox.systemconfig/aarch64/stable".source = sysconfig;
} // { } // {
"applications/org.mozilla.firefox.desktop".text = '' "applications/org.mozilla.firefox.desktop".text = ''
+3 -2
View File
@@ -26,14 +26,15 @@ writeShellScript "firefox-launcher" (''
'' + lib.optionalString desktopFix '' '' + lib.optionalString desktopFix ''
js=${lib.escapeShellArg desktopJs} js=${lib.escapeShellArg desktopJs}
u=$prof/user.js u=$prof/user.js
pats=${lib.escapeShellArg (lib.concatMapStringsSep "\n" (k: "user_pref(${builtins.toJSON k},") desktopKeys)}
# Firefox holds .parentlock open while it uses a profile (the sandbox sees # Firefox holds .parentlock open while it uses a profile (the sandbox sees
# the profile at the same path). # the profile at the same path).
inUse() { find /proc/[0-9]*/fd -lname "$prof/.parentlock" -print -quit 2>/dev/null | grep -q .; } inUse() { find /proc/[0-9]*/fd -lname "$prof/.parentlock" -print -quit 2>/dev/null | grep -q .; }
ours() { [ -L "$u" ] && [ "$(readlink "$u")" = "$js" ]; } ours() { [ -L "$u" ] && [ "$(readlink "$u")" = "$js" ]; }
sfn_unlink() { sfn_unlink() {
ours && ! inUse || return 0 ours && ! inUse || return 0
if [ -f "$prof/prefs.js" ] && grep -qF -f <(printf '%s\n' ${lib.escapeShellArgs (map (k: "user_pref(${builtins.toJSON k},") desktopKeys)}) "$prof/prefs.js"; then if [ -f "$prof/prefs.js" ] && grep -qF "$pats" "$prof/prefs.js"; then
grep -vF -f <(printf '%s\n' ${lib.escapeShellArgs (map (k: "user_pref(${builtins.toJSON k},") desktopKeys)}) "$prof/prefs.js" > "$prof/prefs.js.sfn" || true grep -vF "$pats" "$prof/prefs.js" > "$prof/prefs.js.sfn" || true
cat "$prof/prefs.js.sfn" > "$prof/prefs.js" cat "$prof/prefs.js.sfn" > "$prof/prefs.js"
rm -f "$prof/prefs.js.sfn" rm -f "$prof/prefs.js.sfn"
fi fi
+7 -12
View File
@@ -70,15 +70,10 @@ let
default = false; default = false;
description = '' description = ''
Give the patch one persistent JSON value (e.g. choices made in its Give the patch one persistent JSON value (e.g. choices made in its
UI), kept by the service in UI): `window.__sfuiStore.get(name)` / `.set(name, value)` in the
$XDG_STATE_HOME/steam-frame-nix/ui-patches/<name>.json across page, kept in $XDG_STATE_HOME/steam-frame-nix/ui-patches/<name>.json
SteamVR restarts and reboots: the page reads it with across SteamVR restarts, reboots and disabling the patch; removed
`window.__sfuiStore.get(name)` and writes it with by `steam-frame-nix-cleanup --all`.
`window.__sfuiStore.set(name, value)` (see injector.mjs). The file
is kept when the patch is disabled or removed (the choices come
back when it is enabled again) and removed by
`steam-frame-nix-cleanup --all` (also run by `install.sh
uninstall`).
''; '';
}; };
}; };
@@ -106,9 +101,9 @@ in {
defaultText = lib.literalMD "the helpers of `modules/lib`"; defaultText = lib.literalMD "the helpers of `modules/lib`";
description = '' description = ''
Patch helpers from modules/lib/default.nix: `mkPatch { name, src, Patch helpers from modules/lib/default.nix: `mkPatch { name, src,
signatures ? …, opts ? { } }` (calls `src` as signatures ? …, opts ? { }, extraArgs ? [ ] }` (calls `src` as
`(find, sigs, opts, hooks) => …`; see there), plus `finders`, `hooks` `(find, sigs, opts, hooks, ...extraArgs) => …`; see there), plus
(paths) and `signatures` (parsed signatures.json). `finders`, `hooks` (paths) and `signatures` (parsed signatures.json).
''; '';
}; };