diff --git a/README.md b/README.md index 098a154..014dfe0 100644 --- a/README.md +++ b/README.md @@ -4,9 +4,9 @@ Valve Steam Frame (SteamOS, `aarch64-linux`, standalone home-manager). They work around quirks of the Frame's [two graphical sessions](#two-sessions) (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, -"+" menu, dashboard windows, Steam close button, window curvature, window -controls). +video decoding in Jellyfin, run rootless Docker, and extend Steam's and +SteamVR's UIs at runtime (VR keyboard, "+" menu, dashboard windows, Steam +close button, window curvature, window controls). 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 @@ -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.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.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.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. | diff --git a/docs/steamvr-debugger.md b/docs/steamvr-debugger.md index bf096bf..beba6e4 100644 --- a/docs/steamvr-debugger.md +++ b/docs/steamvr-debugger.md @@ -44,12 +44,6 @@ Port: `VRWebHelper/DebuggerPort`, default 8087. - when SteamVR stops, the key goes back to its previous value (removed if it wasn't there) and `.armed` is removed. -The runtime files don't need Nix, which is why rollback and uninstall are -covered; they are gone at reboot, and `.armed` (the only trace after a -power loss) is resolved by the next SteamVR start or -`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. +The runtime files need no Nix (rollback, uninstall) and are gone at reboot; +`.armed`, the only trace after a power loss, is resolved by the next +SteamVR start or `steam-frame-nix-cleanup`. diff --git a/docs/ui-patches.md b/docs/ui-patches.md index 5da8367..0d863c9 100644 --- a/docs/ui-patches.md +++ b/docs/ui-patches.md @@ -65,16 +65,8 @@ it runs. What they have in common: ## Defining a patch -`steamFrame.uiPatches.patches` entries: - -| 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). | +A `steamFrame.uiPatches.patches` entry (fields: +[README, Options](../README.md#options)): ```nix steamFrame.uiPatches.patches = [ { @@ -128,9 +120,11 @@ patches never use them. `modules/lib/finders.js` (like Decky Loader's `findInReactTree`). Every signature must match exactly once, otherwise the patch changes nothing -and reports it (e.g. `signature not found, Steam left unpatched: -layouts.currentLayout (module 40222): ambiguous export, candidates r_, xy`). -Results are cached per page. +and reports it: `resolvePatch` returns e.g. `signature not found, Steam left +unpatched: layouts.currentLayout (module 40222): ambiguous export, +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 offline checker. An entry can also list `expects` (strings the patch relies @@ -164,10 +158,9 @@ steamFrame.uiPatches.patches = [ { ```js ((find, sigs, opts, hooks) => { - let mods; - try { mods = find.resolveAll(find.getWebpackRequire('webpackChunksteamui'), sigs); } - catch (e) { return `not patched: ${e.message}`; } - const Thing = mods.thing.exports.Thing; // SteamVR dashboard: 'webpackChunkvrwebui' + const mods = find.resolvePatch('webpackChunksteamui', sigs); // SteamVR dashboard: 'webpackChunkvrwebui' + if (typeof mods === 'string') return mods; // signature not found + const Thing = mods.thing.exports.Thing; … }) ``` diff --git a/install.sh b/install.sh index fcd0610..51456e5 100755 --- a/install.sh +++ b/install.sh @@ -591,12 +591,6 @@ cmd_debugger_arm() { local cur tmp need_not_root 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 $VRSETTINGS ]]; then cur="$(jq -r 'if (.VRWebHelper | type) == "object" and (.VRWebHelper | has("DebuggerEnabled")) diff --git a/modules/cleanup/check.nix b/modules/cleanup/check.nix index c8b881d..41a0d15 100644 --- a/modules/cleanup/check.nix +++ b/modules/cleanup/check.nix @@ -203,11 +203,9 @@ pkgs.runCommand "cleanup-check" { nativeBuildInputs = [ cleanup pkgs.jq ]; } '' arm() { bash ${../../install.sh} steamvr-debugger-arm; } fresh e printf '{\n "steamvr" : {}\n}\n' > $V - touch $S/steamvr-debugger # old marker: migrated echo activating > $STUB/state res=$(arm); echo "$res" [ "$(cat $S/steamvr-debugger.armed)" = absent ] || fail "armed" - gone $S/steamvr-debugger [ "$(jq -c .VRWebHelper $V)" = '{"DebuggerEnabled":true}' ] || fail "arm: $(cat $V)" there $root/run/steam-frame-nix/steamvr-debugger-restore [ "$(grep -c daemon-reload $STUB/log)" = 1 ] || fail "arm reload" diff --git a/modules/firefox.nix b/modules/firefox.nix index 19c0217..39fdff6 100644 --- a/modules/firefox.nix +++ b/modules/firefox.nix @@ -1,25 +1,18 @@ # Firefox Flatpak (org.mozilla.firefox) launcher. -# - prefs and vrFullscreenFix are default prefs (pref(), the default branch: -# never written to prefs.js, so removing one leaves nothing behind). Firefox -# reads defaults/pref/*.js from its system config dir, /app/etc/firefox in -# the Flatpak, the mount point of the org.mozilla.firefox.systemconfig -# extension. The user installation's "unmaintained extension" dir -# ($XDG_DATA_HOME/flatpak/extension///) provides it: a -# home-manager link to a store dir, which Flatpak mounts itself (the -# sandbox needn't see /nix). +# - prefs and vrFullscreenFix are default prefs (pref(): never written to +# prefs.js, so removing one leaves nothing behind) in defaults/pref/ of the +# org.mozilla.firefox.systemconfig extension (/app/etc/firefox), provided +# as the user installation's "unmaintained extension" +# ($XDG_DATA_HOME/flatpak/extension///): a Home Manager +# link to a store dir, which Flatpak mounts itself (the sandbox needn't see +# /nix). # - vrFullscreenFix: in the Steam session gamescope focuses a fullscreen X11 # window but never shows it (Firefox looks frozen). ignore-widgets keeps # fullscreen inside the window. # - desktopProfile: the sessions have separate buses/displays, so a second # 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). -# Fullscreen works there, so the launcher undoes the fix in that profile -# 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). +# desktop gets its own profile, where fullscreen works: the launcher undoes +# the fix there while its Firefox runs (firefox/launcher.nix). # The entry shadows the Flatpak's (same ID), keeping MIME associations, and is # seen by the "+" menu (which reads only ~/.local/share/applications). # 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 # 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; } // { "applications/org.mozilla.firefox.desktop".text = '' diff --git a/modules/firefox/launcher.nix b/modules/firefox/launcher.nix index 8436440..29ce57f 100644 --- a/modules/firefox/launcher.nix +++ b/modules/firefox/launcher.nix @@ -26,14 +26,15 @@ writeShellScript "firefox-launcher" ('' '' + lib.optionalString desktopFix '' js=${lib.escapeShellArg desktopJs} 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 # the profile at the same path). inUse() { find /proc/[0-9]*/fd -lname "$prof/.parentlock" -print -quit 2>/dev/null | grep -q .; } ours() { [ -L "$u" ] && [ "$(readlink "$u")" = "$js" ]; } sfn_unlink() { 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 - grep -vF -f <(printf '%s\n' ${lib.escapeShellArgs (map (k: "user_pref(${builtins.toJSON k},") desktopKeys)}) "$prof/prefs.js" > "$prof/prefs.js.sfn" || true + if [ -f "$prof/prefs.js" ] && grep -qF "$pats" "$prof/prefs.js"; then + grep -vF "$pats" "$prof/prefs.js" > "$prof/prefs.js.sfn" || true cat "$prof/prefs.js.sfn" > "$prof/prefs.js" rm -f "$prof/prefs.js.sfn" fi diff --git a/modules/steam-ui-patches.nix b/modules/steam-ui-patches.nix index 5f8be44..dde44dc 100644 --- a/modules/steam-ui-patches.nix +++ b/modules/steam-ui-patches.nix @@ -70,15 +70,10 @@ let default = false; description = '' Give the patch one persistent JSON value (e.g. choices made in its - UI), kept by the service in - $XDG_STATE_HOME/steam-frame-nix/ui-patches/.json across - SteamVR restarts and reboots: the page reads it with - `window.__sfuiStore.get(name)` and writes it with - `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`). + UI): `window.__sfuiStore.get(name)` / `.set(name, value)` in the + page, kept in $XDG_STATE_HOME/steam-frame-nix/ui-patches/.json + across SteamVR restarts, reboots and disabling the patch; removed + by `steam-frame-nix-cleanup --all`. ''; }; }; @@ -106,9 +101,9 @@ in { defaultText = lib.literalMD "the helpers of `modules/lib`"; description = '' Patch helpers from modules/lib/default.nix: `mkPatch { name, src, - signatures ? …, opts ? { } }` (calls `src` as - `(find, sigs, opts, hooks) => …`; see there), plus `finders`, `hooks` - (paths) and `signatures` (parsed signatures.json). + signatures ? …, opts ? { }, extraArgs ? [ ] }` (calls `src` as + `(find, sigs, opts, hooks, ...extraArgs) => …`; see there), plus + `finders`, `hooks` (paths) and `signatures` (parsed signatures.json). ''; };