prefs and vrFullscreenFix become default prefs (pref()) in
defaults/pref/steam-frame-nix.js of /app/etc/firefox, the mount point of
the org.mozilla.firefox.systemconfig extension point. home-manager
provides the extension as a user "unmaintained extension" link
($XDG_DATA_HOME/flatpak/extension/<id>/aarch64/stable) to a store dir,
which Flatpak mounts itself: the sandbox doesn't see /nix. Default prefs
are never written to prefs.js, so removing one leaves nothing behind.
The desktop profile undoes the fullscreen fix with a user.js linked to
/app/etc/firefox/steam-frame-nix-desktop-user.js. The sync (on switch,
and before each launch) removes the user.js copies and store links of
older versions and takes their values out of prefs.js, for each profile
not in use (Firefox holds .parentlock open).
Jellyfin Desktop hard-sets mpv's hwdec=auto-copy, whose probe list leaves
out V4L2 M2M, and the Flatpak can't see the Frame's V4L2 decoder. An
LD_PRELOAD shim (preloaded straight from the store, only that path exposed
read-only) rewrites hwdec to v4l2m2m-copy,auto-copy; devices=all makes the
decoder visible. Overrides via nix-flatpak, or a home-manager-owned override
file without it.
When the ring shows, frame-controls no longer takes the press over via
cancelPress(); it raises the press's drag threshold to 3x via the new
window.__sfuiWindowCurvature.scalePressDragThreshold(factor) (measured from
the press start). Laser drift keeps the long press, a deliberate drag starts
the curvature drag and cancels it. Completion still calls cancelPress().
Stock calls Frame.SetControlsItems once per control on every re-render
of a window's controls (~12 identical calls per window and scene-graph
update); each replaces three MobX arrays and re-renders the bar. The
wrapper now skips the stock setter when the (re-partitioned) lists equal
the frame's current ones, and reads each action's icon once per call
(protoForSteam is recomputed on every untracked read).
The Flatpak sandbox doesn't see /nix, so the linked user.js dangled and
Firefox never read it (vrFullscreenFix had no effect). The copy starts with a
marker line, so the module still recognises its own file.
Opt-in with steamFrame.keyboard.vr.enable; swipe, autocorrect and
completion suggestions, Backspace drag and haptics are on under it.
- vr-keyboard/patch.js (Steam UI): swipe decoder, a model of what the
keyboard typed, suggestions that only replace what they typed, Backspace
drag with word detents and retyping; checks its Steam internals by
signature (signatures.json: vr-keyboard) and stays stock otherwise.
- vr-keyboard/panel.js (SteamVR systemui) + relay.mjs (user service
vr-keyboard-relay): the suggestion strip as a dashboard panel below or
above the keyboard (signatures: vr-keyboard-panel).
- Dictionary built from nixpkgs' wordfreq and hunspellDicts; default: the
keyboard.layout language (de, fr, es, it, nl, pt, sv) plus English.
- Tests: keyboard.vr.checks and the flake check vr-keyboard.
US, Dvorak, Colemak, Bulgarian, Chinese, Japanese and Korean have no AltGr
key, so AltGr + Delete and the AltGr arrows were unreachable. They get
Steam's own AltGr toggle key, labelled Fn, right of the space bar like
German AltGr.
The Delete key's hint without AltGr is now a third layout entry (empty
key, label Entf), rendered by Steam exactly like its other AltGr hints
(e.g. } on 0) instead of a stylesheet pseudo-element.
The key that becomes Delete on AltGr shows a small hint (the arrow hints'
7 px) on the baseline of Steam's AltGr hints, while the layout has such a
free key.
Steam draws a [normal, shifted] entry as a bare empty key on AltGr and
drops its key type, so the Half-width ^ grew to a full key. Keep the type
with an empty AltGr entry. The AltGr Delete key gets Backspace's key type
(the dark special-key face), the replaced key's flexible width and a
centred label.
While AltGr is active, the key left of Backspace (German ´/`, empty on
AltGr) becomes Delete, labelled like Steam's Delete key (Entf; Del when
longer), sent by the helper; repeats while held like Backspace.
- dashboard.windowMaxScale / windowDistance -> dashboard.windows.maxScale /
distance
- windowCurvature: default -> initial, snapPixels/snapPoints ->
detentPixels/detentPoints, dragThreshold -> dragThresholdPixels (also the
patch's option keys; window-curvature VERSION 19); removed `snap` now
points to detentPixels
- types.numbers.positive/nonnegative instead of number + range assertions
(step <= max, initial/detentPoints <= max and distance min <= max stay)
- frame-controls patch accepts longPressMs from 300, like the option
(VERSION 2)
- "SteamVR dashboard patch" in descriptions, which fit into 80 columns
Old names keep working with a rename warning.
Closing Steam with X now marks it hidden (state steamHidden). Before,
bar-only ended as soon as any window became active, so closing a docked
window afterwards let stock SteamVR fall back to Steam.
While hidden, the stock fallbacks go to the most recently active other
docked window with a tab, else bar-only:
- autoSwitchOverlayIfNeeded with no active frame;
- the dashboard mailbox's dashboard_overlay_destroyed handler (stock:
switchToHomeOverlay -> Steam);
- show/switch requests for Steam with reason "theater frame destroyed"
(key removed) or "SetDockLocation" (echo of X docking Steam: dropped).
Steam becoming the active frame (tab click, Steam menu pick,
SwitchToDashboardOverlay) ends hidden.
The hidden state replaces the rule that swallowed the first
ShowOverlay(main) from Steam after each dashboard open in bar-only (Steam
doesn't send one there; its only effect was eating a real Steam menu
pick). Version 7; state of versions 5/6 is taken over (bar-only implies
hidden), their fields are left for a rollback. Signatures: mailbox and
dashboard handler anchors, the ShowOverlay ones dropped.
The bar panel is no longer padded during a bar drag (its new size and the
compensated origin reached the compositor at different times, so the bar
flickered). SteamVR keeps sending the pressed bar's coordinates past its
edge, so the drag simply uses them. `barDragRoom` is removed.
- Snap points are detents in drag distance (snapPixels, default 24): the
value holds at the point, then continues, so no value is skipped.
`snap` is removed in favour of `snapPixels`.
- The dashboard's own hover haptics are muted during a drag, so only the
value's ticks and detents are felt.
- Longer drag per step by default: 120 px per 1.0 in the menu, 60 on the
bar button (step stays 0.05).
Document the patch calling convention once in lib/default.nix; patch
headers say what, target, hook, state and contracts. Comments only, no
code changes.
Explain the steamvrDebugger default in plain words, and replace the
"mask the port forwards" advice: SteamOS only enables them with Steam's
Developer Mode, which none of the patches need.
- A short contents list; the intro names all features.
- "UI patches" (with finders/signatures and after-a-Steam-update) is its
own section before "Fixes in detail" instead of sitting between the
keyboard patch and the launcher menu; anchors are unchanged.
- The usage example says how it relates to template/ and uses the
template's stateVersion.
- Developer Mode notes (usage, hidden apps) mention launcherMenu.showAllApps;
the launcher section no longer says the patches use no webpack module;
window curvature points to the window control bar for the bar button.
- pinned-desktop (VERSION 3): drop the stop() of a predecessor global that
never existed in this repo; log errors as "sfui <NAME>:" through one
guard helper, like the grid patch.
- steamCloseButton.enable is a mkEnableOption like the other feature
switches; enable descriptions no longer end in a stray newline.
- launcher-menu.nix header lists show-all and no longer claims the patches
use no webpack module; hiddenApps mentions showAllApps.
- Patch comments: frame-controls ring timing matches the code (half of
longPressMs, at most 1 s), window-curvature names frame-controls as what
moves its control into the bar, steam-close-button no longer refers to an
unpublished version.
steamFrame.dashboard.frameControls: a long press (longPressMs, default
1.5 s; a ring shows the progress from half of it) on a control under a
dashboard window or on a More Options (three-dot) menu row opens a popup
with "Show in bar", which moves that control between the window's bottom
bar and the menu, for all windows. Placement is keyed by the control's
action icon, kept in the page and its localStorage (survives SteamVR
restarts); inBar / inMenu set defaults. floatInTheater gives theater
windows the Float control back.
The popup is a scene-graph panel of its own, attached to the pressed
button's (or the menu's) anchor like SteamVR's tooltips and menu, so the
bar and menu panels don't change; the menu's focus auto-close is held back
while its popup is open. Coordinates with window-curvature through its
sfui-curv-* contract. Lookups by signature ("frame-controls" in
lib/signatures.json, check-only anchors for the rest).
Without Steam's Developer Mode the "+" menu hides konsole, systemsettings,
dolphin, plasma-discover, vlc and a few more by executable name. The patch
empties only that list's iterator, leaving the setting itself untouched.
steamFrame.dashboard.windowCurvature: the "Toggle Curvature" row of a
window's More Options menu becomes a control (click: curved -> flat,
flat -> stock; drag up/down with the laser: curvature relative to
SteamVR's stock curve, snapping to 0 and 1). The patch moves each frame's
curvature-origin node to stock distance / value in outgoing scene graphs;
per-window values live in the systemui page. Lookups by signature
("window-curvature" in signatures.json, incl. check-only anchors).
Shared method hooks (lib/hooks.js, passed by mkPatch as 4th argument):
one SendMessage wrapper for dashboard-windows (now VERSION 3) and
window-curvature instead of a chain of per-patch wrappers; legacy
wrappers on top are unwound.
With maxRows capping the scroller, the inner grid panel scrolls (as in
Steam's own design, where the outer element holds the fixed edge shadows).
The fade/shadow state is now read from whichever of the two overflows and
updated on the panel's scroll events too, so the shadows show only where
more content is.
Besides the ScrollFade mask, the scroll region has ::before/::after shadows
toggled by stale can-scroll classes; with everything visible the bottom
shadow still covered the last row. Checker covers the shadow CSS.
steamFrame.dashboard.steamCloseButton.enable (default false) gives the
SteamVR dashboard's main Steam window a close button: it docks the window
back into the dashboard, then switches to the previously active window or,
with none, leaves just the dashboard bar. Bar-only survives dashboard
reopens and patch restarts (state in window.__sfuiSteamCloseState, taken
over from the earlier personal version 4 of this patch); the stock
"no active frame -> show Steam" paths are suppressed while it lasts.
MobX, the dock-location enum and the closing component class are found by
signature; check-only signatures cover the dashboard internals it uses.
launcherMenu.grid = { enable; columns; maxRows; } restyles the "+" menu's
programs section as a grid of tiles (icon, name below): Steam's own items
stay in place, maxRows limits the visible rows, and Steam's scroll fade is
recomputed so it only shows where there is more to scroll. The pinned
Desktop row is slim and centred in tile mode.
launcherMenu.iconFallbacks (default utilities-terminal, preferences-system)
installs hicolor copies of Breeze app icons, since Steam resolves program
icons only in hicolor; Konsole and KDE System Settings had none.
The checker gains checkOnly "stylesheet" signatures (matched against the
bundle's CSS files) to cover the scroll fade's gradient; launcher-menu-grid
signatures cover the section keys and the ScrollFade classes.
WebSocketSend is a MobX @action.bound method, a getter on the prototype in
the page (a plain method only in the offline checker's sandbox), so the
Mailbox signature never matched live. Use SendMessage,
SendMessageAndWaitForResponse and RegisterHandler instead; note the pitfall
at protoMethods in finders.js.
dashboard-windows: steamFrame.dashboard.windowMaxScale and
windowDistance.{world,theater,dashboard}.{min,max}, one runtime patch of the
SteamVR dashboard (systemui, 127.0.0.1:8087) that rewrites
frame-resize-scale-max and grab min/max-distance in the scene graph sent to
vrcompositor, then asks for one resend so changes apply immediately.
Registered only when an option is set. The mailbox class and the
scene-graph scheduler are found by signature (mkPatch, "dashboard-windows"
in lib/signatures.json, bundle vrwebui-systemui), so
scripts/check-signatures.mjs checks them offline too.
steamvr-debugger: steamFrame.steamvrDebugger.enable, defaulting to true when
a UI patch uses port 8087; merges VRWebHelper.DebuggerEnabled into
steamvr.vrsettings before every SteamVR start, and reverts it (only if it
set it) when turned off.