mirror of
https://github.com/lhns/steam-frame-nix.git
synced 2026-10-06 02:00:13 +02:00
docs: README as overview + options, one docs page per feature
README keeps intro, feature list linking docs/, install, two sessions, usage, the complete options table, changes outside Nix, rollback, uninstall. Each feature's details (problem, what you get, configuration, limitations, how it works) move to its own page in docs/; docs/dashboard.md is split per feature and docs/changes-outside-nix.md becomes docs/cleanup.md.
This commit is contained in:
1 parent
5a3d0b9d05
commit
aa9ca183f1
16 files changed
+948
-922
No files matched your search
@@ -1,67 +0,0 @@
|
||||
# Cleanup and installer: how they work
|
||||
|
||||
What steam-frame-nix writes outside the Nix store, how to remove it, and
|
||||
rollback/uninstall: README,
|
||||
[Changes outside Nix](../README.md#changes-outside-nix-exceptions),
|
||||
[Rollback](../README.md#rollback), [Uninstall](../README.md#uninstall).
|
||||
|
||||
## Cleanup
|
||||
|
||||
`steam-frame-nix-cleanup` (`steamFrame.cleanup.package`, module `cleanup`,
|
||||
imported by every module) is `install.sh cleanup`, so the same code runs
|
||||
with Nix (on every switch: `cleanup --orphans --keep <what the configuration
|
||||
still uses>`) and without it, from the script (bash, coreutils, findutils,
|
||||
jq, all in SteamOS' `/usr/bin`). Home Manager's `uninstall = true;` runs
|
||||
`cleanup --all` from the activation.
|
||||
|
||||
It knows everything any version wrote outside the store and removes an
|
||||
artifact only when it is proven to be its own; anything else is reported as
|
||||
"left alone" and never touched:
|
||||
|
||||
| Artifact | Proof / rule |
|
||||
|---|---|
|
||||
| `debugger`: `VRWebHelper.DebuggerEnabled` in `steamvr.vrsettings` | `~/.local/state/steam-frame-nix/steamvr-debugger.armed` holds the value before (older versions: the empty marker `steamvr-debugger`, value before = absent). Restored once SteamVR is stopped; while it runs the runtime drop-in restores it when SteamVR stops. Also the runtime drop-in and restore script themselves. |
|
||||
| `icons`: `hicolor/scalable/apps/<name>.svg` | links to Breeze in the store, listed in `~/.local/state/steam-frame-nix/icon-fallbacks` (the icon-fallbacks script of 2026-09) |
|
||||
| `firefox`: `user.js` in Firefox profiles | links to `/app/etc/firefox/steam-frame-nix-desktop-user.js` (left alone while the profile is in use), older links to `*-firefox-*user.js` and copies starting with the steam-frame-nix marker comment, and the values they left in `prefs.js` (only with Firefox closed) |
|
||||
| `jellyfin` | the hwdec shim entries in the Jellyfin Flatpak's user override `~/.local/share/flatpak/overrides/org.jellyfin.JellyfinDesktop` (nix-flatpak), an empty override file, and the shim copy of earlier versions in `~/.var/app/org.jellyfin.JellyfinDesktop` (marker `~/.local/state/steam-frame-nix/jellyfin-hwdec-shim`) |
|
||||
| `ui-state`: `~/.local/state/steam-frame-nix/ui-patches/<name>.json` | the dashboard patches' saved choices; `--all` only, never `--orphans`; stray `*.json.tmp` files |
|
||||
| `dirs` | `~/.local/state/steam-frame-nix` and `/run/user/1000/steam-frame-nix` when empty |
|
||||
|
||||
### Left by older versions
|
||||
|
||||
The rows above include what only older versions wrote: the icon fallback
|
||||
links and their list (`~/.local/state/steam-frame-nix/icon-fallbacks`), the
|
||||
debugger marker `steamvr-debugger`, Firefox `user.js` copies and links and
|
||||
their `prefs.js` values, the Jellyfin hwdec entries of
|
||||
`~/.local/share/flatpak/overrides/org.jellyfin.JellyfinDesktop` (an empty
|
||||
override file too) and the old shim copy in
|
||||
`~/.var/app/org.jellyfin.JellyfinDesktop`.
|
||||
|
||||
## Set up by install.sh
|
||||
|
||||
`install.sh install` (the bootstrap, not the modules) changes more, and
|
||||
`install.sh uninstall` undoes it:
|
||||
|
||||
- Nix via [nix-installer](https://github.com/NixOS/nix-installer)
|
||||
(`steam-deck` planner, flakes on): `/nix` (bind mount of `/home/nix`,
|
||||
survives SteamOS updates), files in `/etc` (systemd units, profile
|
||||
scripts, `nix.conf`) and its receipt `/nix/receipt.json`; the read-only
|
||||
root is unlocked only while it installs or uninstalls. Skipped if Nix
|
||||
already works;
|
||||
- `experimental-features = nix-command flakes` in `~/.config/nix/nix.conf`
|
||||
if Nix was already there without flakes;
|
||||
- `~/nix-config` (your configuration, a git repository, from the template
|
||||
with your user name filled into `flake.nix`) and the link
|
||||
`~/.config/home-manager` to it, unless `~/.config/home-manager` exists or
|
||||
`--flake <dir-or-flakeref>` is given; uninstall removes the link, never
|
||||
the configuration;
|
||||
- dotfiles Home Manager found in its way, renamed to `*.hm-backup-<time>`
|
||||
(kept by uninstall);
|
||||
- `~/.local/state/home-manager` and `~/.local/state/nix` (profiles,
|
||||
generations), `~/.nix-profile`, `~/.nix-defexpr`, `~/.nix-channels`,
|
||||
`~/.cache/nix`.
|
||||
|
||||
The installer runs `systemctl --user` against the outer session's user
|
||||
manager (the nested desktop can't reach it with its own environment) and
|
||||
uses the installed `home-manager` if there is one, else Home Manager's
|
||||
`master`.
|
||||
@@ -0,0 +1,43 @@
|
||||
# Cleanup and installer
|
||||
|
||||
How `steam-frame-nix-cleanup` and `install.sh` work. What steam-frame-nix
|
||||
writes outside the Nix store, how to remove it, rollback and uninstall:
|
||||
[README, Changes outside Nix](../README.md#changes-outside-nix-exceptions),
|
||||
[Rollback](../README.md#rollback), [Uninstall](../README.md#uninstall).
|
||||
|
||||
## Cleanup
|
||||
|
||||
`steam-frame-nix-cleanup` (`steamFrame.cleanup.package`, module `cleanup`,
|
||||
imported by every module) is `install.sh cleanup`, so the same code runs
|
||||
with Nix (on every switch: `cleanup --orphans --keep <what the configuration
|
||||
still uses>`) and without it, from the script (bash, coreutils, findutils,
|
||||
jq, all in SteamOS' `/usr/bin`). Home Manager's `uninstall = true;` runs
|
||||
`cleanup --all` from the activation. `--quiet` prints only actions,
|
||||
deferrals and warnings.
|
||||
|
||||
It removes an artifact only when it is proven to be its own; anything else
|
||||
is reported as "left alone" and never touched:
|
||||
|
||||
| Artifact | Proof / rule |
|
||||
|---|---|
|
||||
| `debugger`: `VRWebHelper.DebuggerEnabled` in `steamvr.vrsettings` | `~/.local/state/steam-frame-nix/steamvr-debugger.armed` holds the value before (older versions: the empty marker `steamvr-debugger`, value before = absent). Restored once SteamVR is stopped; while it runs the runtime drop-in restores it when SteamVR stops. Also the runtime drop-in and restore script themselves. |
|
||||
| `icons`: `hicolor/scalable/apps/<name>.svg` | links to Breeze in the store, listed in `~/.local/state/steam-frame-nix/icon-fallbacks` (the icon-fallbacks script of 2026-09) |
|
||||
| `firefox`: `user.js` in Firefox profiles | links to `/app/etc/firefox/steam-frame-nix-desktop-user.js` (left alone while the profile is in use), older links to `*-firefox-*user.js` and copies starting with the steam-frame-nix marker comment, and the values they left in `prefs.js` (only with Firefox closed) |
|
||||
| `jellyfin` | the hwdec shim entries in the Jellyfin Flatpak's user override `~/.local/share/flatpak/overrides/org.jellyfin.JellyfinDesktop` (nix-flatpak), an empty override file, and the shim copy of earlier versions in `~/.var/app/org.jellyfin.JellyfinDesktop` (marker `~/.local/state/steam-frame-nix/jellyfin-hwdec-shim`) |
|
||||
| `ui-state`: `~/.local/state/steam-frame-nix/ui-patches/<name>.json` | the dashboard patches' saved choices; `--all` only, never `--orphans`; stray `*.json.tmp` files |
|
||||
| `dirs` | `~/.local/state/steam-frame-nix` and `/run/user/1000/steam-frame-nix` when empty |
|
||||
|
||||
### Left by older versions
|
||||
|
||||
Of the rows above, only older versions wrote: the icon fallback links and
|
||||
their list, the debugger marker `steamvr-debugger`, Firefox `user.js` copies
|
||||
and links and their `prefs.js` values, the Jellyfin hwdec override entries
|
||||
(and an empty override file) and the old shim copy.
|
||||
|
||||
## Installer
|
||||
|
||||
What `install.sh install` sets up is listed in the README under
|
||||
[Set up by install.sh](../README.md#set-up-by-installsh). The installer runs
|
||||
`systemctl --user` against the outer session's user manager (the nested
|
||||
desktop can't reach it with its own environment) and uses the installed
|
||||
`home-manager` if there is one, else Home Manager's `master`.
|
||||
@@ -0,0 +1,61 @@
|
||||
# Dashboard windows
|
||||
|
||||
`dashboard.windows.*`, module `dashboard-windows` ([options](../README.md#options)).
|
||||
|
||||
A [SteamVR dashboard patch](ui-patches.md#steamvr-dashboard-patches).
|
||||
|
||||
## Problem
|
||||
|
||||
SteamVR dashboard windows can only be enlarged to 2x, and grabbed windows
|
||||
pushed back only to 5 m (6 m in theater), too close for a big screen.
|
||||
|
||||
## What you get
|
||||
|
||||
Higher limits (`null` keeps stock):
|
||||
|
||||
| Option | Stock |
|
||||
|---|---|
|
||||
| `maxScale` | 2 (relative to the window's default size; the theater screen's default is 2.8x larger) |
|
||||
| `distance.world.{min,max}` | 0.25-5 m |
|
||||
| `distance.theater.{min,max}` | 1-6 m |
|
||||
| `distance.dashboard.{min,max}` | 0.3-4 m |
|
||||
|
||||
Distances limit pulling in / pushing back a grabbed window (thumbstick or
|
||||
scroll while dragging). Changes apply immediately, and turning options off
|
||||
reverts on the next switch. The keyboard's range is not patched.
|
||||
|
||||
## Configuration
|
||||
|
||||
```nix
|
||||
steamFrame.dashboard.windows = {
|
||||
maxScale = 4.0; # resize up to 4x (theater: 11.2x)
|
||||
distance.world.max = 10.0; # push windows back up to 10 m
|
||||
distance.theater.max = 12.0;
|
||||
};
|
||||
```
|
||||
|
||||
## Caveats
|
||||
|
||||
If SteamVR changes its stock grab distances, the distance options silently
|
||||
do nothing.
|
||||
|
||||
## How it works
|
||||
|
||||
`systemui` sends its scene graph to vrcompositor via
|
||||
`mailbox.SendMessage("vrcompositor_systemlayer", { type: "update_scene_graph", scene_graph })`,
|
||||
and vrcompositor enforces the limits in it:
|
||||
|
||||
- `frame-resize-scale-min` / `-max` on window frames;
|
||||
- `min-distance` / `max-distance` on grab nodes: `grab-scale` for world
|
||||
windows, `grab-transform` for the theater screen and the dashboard (the
|
||||
keyboard's `grab-transform`, 0.2 / 1 m, is left alone).
|
||||
|
||||
They are literals in the bundle, so the patch hooks `SendMessage` on the
|
||||
mailbox prototype ([shared hooks](ui-patches.md#shared-method-hooks), shared
|
||||
with [window curvature](window-curvature.md)) and edits outgoing scene graphs
|
||||
in place. Grab nodes are matched by type plus exact stock values, which is
|
||||
why a change of them makes the distance rewrite a no-op. A scene-graph
|
||||
resend applies new limits at once.
|
||||
|
||||
State: `window.__sfuiDashboardWindows` in the `systemui` page (counters in
|
||||
`.hits`).
|
||||
@@ -1,105 +0,0 @@
|
||||
# SteamVR dashboard patches: how they work
|
||||
|
||||
Technical details of the four dashboard patches. What they do and how to
|
||||
configure them: README, [dashboard windows](../README.md#dashboard-windows-dashboardwindows),
|
||||
[Steam close button](../README.md#steam-close-button-dashboardsteamclosebuttonenable),
|
||||
[window curvature](../README.md#window-curvature-dashboardwindowcurvature),
|
||||
[window control bar](../README.md#window-control-bar-dashboardframecontrols).
|
||||
|
||||
All four are `mkPatch` [UI patches](ui-patches.md) of SteamVR's dashboard
|
||||
(`vrwebhelper`, page title `systemui`, DevTools `127.0.0.1:8087`, webpack
|
||||
chunk `webpackChunkvrwebui`), registered only when enabled; they turn on the
|
||||
[SteamVR debugger](steamvr-debugger.md). Each is found by signature (its
|
||||
entry in `modules/lib/signatures.json` has the patch's name); on mismatch
|
||||
the dashboard stays stock. Sources: `modules/<name>/patch.js`, whose header
|
||||
comments go into more detail.
|
||||
|
||||
## Dashboard windows
|
||||
|
||||
`dashboard-windows`. `systemui` sends its scene graph to vrcompositor via
|
||||
`mailbox.SendMessage("vrcompositor_systemlayer", { type: "update_scene_graph", scene_graph })`,
|
||||
and vrcompositor enforces the limits in it:
|
||||
|
||||
- `frame-resize-scale-min` / `-max` on window frames;
|
||||
- `min-distance` / `max-distance` on grab nodes: `grab-scale` for world
|
||||
windows, `grab-transform` for the theater screen and the dashboard (the
|
||||
keyboard's `grab-transform`, 0.2 / 1 m, is left alone).
|
||||
|
||||
They are literals in the bundle, so the patch hooks `SendMessage` on the
|
||||
mailbox prototype ([shared hooks](ui-patches.md#shared-method-hooks), shared
|
||||
with window curvature) and edits outgoing scene graphs in place. Grab nodes
|
||||
are matched by type plus exact stock values, so if SteamVR changes them the
|
||||
distance rewrite becomes a no-op. A scene-graph resend applies new limits at
|
||||
once.
|
||||
|
||||
State: `window.__sfuiDashboardWindows` (counters in `.hits`).
|
||||
|
||||
## Steam close button
|
||||
|
||||
`steam-close-button`, with [persistent state](ui-patches.md#persistent-state)
|
||||
(`{ schema: 1, steamHidden }`).
|
||||
|
||||
- **Button:** the frame's `closing` component shows X when
|
||||
`componentProps.onCloseRequested` exists; the Steam window's instance
|
||||
(overlay `valve.steam.gamepadui.main`) gets one and its props are
|
||||
re-assigned so the MobX computed re-evaluates.
|
||||
- **Staying hidden:** while hidden, stock fallbacks to Steam go to the
|
||||
previous window or bar-only instead: `Dashboard.autoSwitchOverlayIfNeeded`
|
||||
(instance override) and the mailbox handler `dashboard_overlay_destroyed`.
|
||||
Mailbox show/switch requests for Steam with reason `SetDockLocation` (the
|
||||
echo of X docking Steam) are dropped, "theater frame destroyed" ones are
|
||||
passed on without the Steam key. Explicit requests (tab click, Steam menu
|
||||
pick, `SwitchToDashboardOverlay`) show Steam again.
|
||||
- **Restarts:** a restored hidden state stays pending (`restorePending`)
|
||||
until Steam's frame is seen, since the injector attaches within ~5 s of
|
||||
the page appearing.
|
||||
|
||||
Debugging: `window.__sfuiSteamClose.plan()` / `homePlan()`; state in
|
||||
`window.__sfuiSteamCloseState` (survives re-injection; teardown restores all
|
||||
overrides and never switches frames).
|
||||
|
||||
## Window curvature
|
||||
|
||||
`window-curvature`. Stock curvature: the frame renders a transform
|
||||
`frame:<id>:curvature-origin` at `z = DashboardStore.curvatureDistance` when
|
||||
curved, else 1000; its panels reference it as `curvature-origin-id` and
|
||||
vrcompositor bends them onto a cylinder around it (curvature = 1/radius).
|
||||
Those MobX properties are non-configurable, so the patch hooks the mailbox
|
||||
`SendMessage` (shared with dashboard windows) and rewrites the origin's z
|
||||
in outgoing scene graphs to stock / value. On/off stays the stock toggle
|
||||
state, so stock toggle and wheel always agree.
|
||||
|
||||
Values are kept per window (key: first overlay key) in
|
||||
`window.__sfuiWindowCurvatureState`, across re-patch and unpatch, not across
|
||||
a dashboard reload. The bar button uses the coordinates SteamVR keeps
|
||||
sending past the pressed panel's edge while the trigger is held; the bar
|
||||
panel is never resized.
|
||||
|
||||
Debugging: `window.__sfuiWindowCurvature.dump()` (`.log` recent events).
|
||||
|
||||
### Contract for other patches
|
||||
|
||||
For patches handling presses on the curvature controls (e.g. the window
|
||||
control bar's long press): every element the patch drives has class
|
||||
`sfui-curv-ctl`; when a press becomes a drag, a bubbling `CustomEvent`
|
||||
`sfui-curv-dragstart` (detail `{ frameID, where: 'menu' | 'bar' }`) is
|
||||
dispatched on it, and `sfui-curv-dragend` when it ends;
|
||||
`window.__sfuiWindowCurvature.scalePressDragThreshold(factor)` sets the
|
||||
current press's drag threshold to `factor` × `dragThresholdPixels` (from the
|
||||
press start; returns whether it applied, i.e. a press that is not yet a
|
||||
drag); `window.__sfuiWindowCurvature.cancelPress()` ends a press without its
|
||||
click. A patch with its own gesture lets `mousemove` through while
|
||||
undecided, drops its gesture on `sfui-curv-dragstart`, may raise the
|
||||
threshold while its gesture is under way, and calls `cancelPress()` when it
|
||||
takes the press over. The window control bar uses factor 3 once its
|
||||
progress ring shows.
|
||||
|
||||
## Window control bar
|
||||
|
||||
`frame-controls`, with [persistent state](ui-patches.md#persistent-state)
|
||||
(placements). Short presses stay stock; the three-dot button can't be
|
||||
moved. Works with window curvature through the
|
||||
[contract](#contract-for-other-patches) above.
|
||||
|
||||
Debugging: `window.__sfuiFrameControls.dump()`, `.placement()`, `.reset()`
|
||||
(forget choices), `.log`, `window.__sfuiFrameControlsState`.
|
||||
+68
-23
@@ -1,36 +1,81 @@
|
||||
# Firefox: how it works
|
||||
# Firefox
|
||||
|
||||
`firefox.*` (module `firefox`). What it does and how to configure it:
|
||||
README, [Firefox](../README.md#firefox-firefox).
|
||||
`firefox.*`, module `firefox`. Options:
|
||||
[README, Options](../README.md#options).
|
||||
|
||||
## Default prefs
|
||||
## Problem
|
||||
|
||||
`prefs` and the fixes are *default* values (`pref()`), not user values:
|
||||
`about:config` can still change them per profile, and nothing is written to
|
||||
`prefs.js`, so removing one leaves nothing behind. Firefox reads default
|
||||
prefs from `defaults/pref/*.js` in its system config dir, which in the
|
||||
Flatpak is `/app/etc/firefox`, the mount point of the
|
||||
In the Steam session gamescope never shows fullscreen windows, so Firefox
|
||||
looks frozen when a page goes fullscreen; sites send AV1, which the Frame
|
||||
decodes in software; and the two sessions can't see each other's Firefox,
|
||||
so a second instance stops at the locked profile.
|
||||
|
||||
## What you get
|
||||
|
||||
A launcher for the Flathub Firefox Flatpak (`org.mozilla.firefox`, stable;
|
||||
install it yourself). The launcher shadows the Flatpak's own entry (same
|
||||
ID), so default-browser associations keep working.
|
||||
|
||||
- **`vrFullscreenFix`** (on): `full-screen-api.ignore-widgets` makes
|
||||
fullscreen fill just the window. Not applied in the desktop profile.
|
||||
- **`disableAv1`** (off): `media.av1.enabled = false`. The Frame's decoder
|
||||
driver (`iris`) has no AV1, only H.264, HEVC and VP9, so YouTube and co.
|
||||
send VP9/H.264, decoded in hardware, instead of software AV1.
|
||||
- **`prefs`:** further `about:config` values for every profile; they can
|
||||
also override the fixes above.
|
||||
- **`desktopProfile`** (`"desktop"`): in the nested desktop the launcher
|
||||
uses this separate profile (a normal Firefox profile with its own browser
|
||||
data, created on first use). `null`: the default profile in both sessions.
|
||||
|
||||
`prefs` and the fixes are *default* values, not user values: `about:config`
|
||||
can still change them per profile, and removing one leaves nothing behind.
|
||||
Changes take effect at the next start of Firefox.
|
||||
|
||||
## Configuration
|
||||
|
||||
```nix
|
||||
steamFrame.firefox = {
|
||||
enable = true;
|
||||
disableAv1 = true;
|
||||
prefs."browser.startup.page" = 3; # restore the previous session
|
||||
};
|
||||
```
|
||||
|
||||
## Caveats
|
||||
|
||||
- A `user.js` of your own in the desktop profile is never touched (the
|
||||
fullscreen fix then stays on there).
|
||||
- A `org.mozilla.firefox.systemconfig` Flatpak extension of your own would
|
||||
conflict with the one this module provides.
|
||||
- **Remove** `vrFullscreenFix` **when** gamescope shows fullscreen X11
|
||||
windows in VR, `disableAv1` **when** a SteamOS kernel adds AV1 to `iris`.
|
||||
|
||||
## How it works
|
||||
|
||||
### Default prefs
|
||||
|
||||
The prefs are written with `pref()`, and nothing goes to `prefs.js`.
|
||||
Firefox reads default prefs from `defaults/pref/*.js` in its system config
|
||||
dir, which in the Flatpak is `/app/etc/firefox`, the mount point of the
|
||||
`org.mozilla.firefox.systemconfig` extension. home-manager provides that
|
||||
extension as a link from
|
||||
`~/.local/share/flatpak/extension/org.mozilla.firefox.systemconfig/aarch64/stable`
|
||||
to a store directory; Flatpak mounts it itself, so the sandbox doesn't get
|
||||
`/nix`. (A systemconfig extension of your own would conflict with it.)
|
||||
`/nix`.
|
||||
|
||||
## Desktop profile
|
||||
### Desktop profile
|
||||
|
||||
The launcher (a desktop entry with the Flatpak's ID, shadowing its entry)
|
||||
picks `desktopProfile` when started in the nested desktop. The desktop
|
||||
profile undoes the fullscreen fix only while its Firefox runs: the launcher
|
||||
links the profile's `user.js` to
|
||||
`/app/etc/firefox/steam-frame-nix-desktop-user.js` (a sandbox path) right
|
||||
before starting Firefox, waits for it, and once it has exited and the
|
||||
profile is no longer in use removes the link and the value Firefox stored
|
||||
from it in `prefs.js`. A second launch that just hands a URL to the running
|
||||
Firefox leaves both in place. A `user.js` of your own is never touched (the
|
||||
fix then stays on in that profile). After a crash, the next launch or
|
||||
`steam-frame-nix-cleanup` (on switch) removes them.
|
||||
The launcher (a desktop entry with the Flatpak's ID) picks `desktopProfile`
|
||||
when started in the nested desktop. The desktop profile undoes the
|
||||
fullscreen fix only while its Firefox runs: the launcher links the
|
||||
profile's `user.js` to `/app/etc/firefox/steam-frame-nix-desktop-user.js`
|
||||
(a sandbox path) right before starting Firefox, waits for it, and once it
|
||||
has exited and the profile is no longer in use removes the link and the
|
||||
value Firefox stored from it in `prefs.js`. A second launch that just hands
|
||||
a URL to the running Firefox leaves both in place. After a crash, the next
|
||||
launch or `steam-frame-nix-cleanup` (on switch) removes them.
|
||||
|
||||
## Older versions
|
||||
### Older versions
|
||||
|
||||
Older versions linked or copied a `user.js` into every profile; those and
|
||||
the values they left in `prefs.js` are removed by `steam-frame-nix-cleanup`
|
||||
|
||||
+56
-20
@@ -1,13 +1,15 @@
|
||||
# Jellyfin hardware decoding: how it works
|
||||
# Jellyfin hardware decoding
|
||||
|
||||
`jellyfin.hardwareDecoding.*` (module `jellyfin`). What it does, how to
|
||||
configure it and its caveats: README,
|
||||
[Jellyfin hardware decoding](../README.md#jellyfin-hardware-decoding-jellyfinhardwaredecoding).
|
||||
`jellyfin.hardwareDecoding.*`, module `jellyfin`, for the Flathub
|
||||
[Jellyfin Desktop](https://github.com/jellyfin/jellyfin-desktop) Flatpak
|
||||
(`org.jellyfin.JellyfinDesktop`), which plays video with libmpv. Options:
|
||||
[README, Options](../README.md#options).
|
||||
|
||||
## Why mpv doesn't use the decoder
|
||||
## Problem
|
||||
|
||||
The Frame's hardware decoder is a V4L2 memory-to-memory device
|
||||
(`qcom-iris`, `/dev/video*`), which
|
||||
Video is decoded in software (1080p H.264: ~40 % CPU). The Frame's hardware
|
||||
decoder is a V4L2 memory-to-memory device (`qcom-iris`, `/dev/video*`),
|
||||
which
|
||||
|
||||
- the Flatpak can't open: its `devices=dri` covers only the GPU, and Flatpak
|
||||
has nothing between that and `devices=all`;
|
||||
@@ -15,23 +17,58 @@ The Frame's hardware decoder is a V4L2 memory-to-memory device
|
||||
probing leaves out V4L2 M2M on purpose (its quality varies by SoC).
|
||||
Jellyfin has no way to pass mpv options.
|
||||
|
||||
## The shim
|
||||
## What you get
|
||||
|
||||
The Jellyfin desktop entry (same ID as the Flatpak's, so the KDE menu and
|
||||
the "+" menu start it) runs the Flatpak with device access (`devices=all`)
|
||||
and makes mpv use `hwdec` (default `v4l2m2m-copy,auto-copy`); explicit
|
||||
values such as `no` stay. mpv tries the listed decoders in order and falls
|
||||
back to software decoding per stream. With the default, 1080p H.264 plays
|
||||
at ~15-20 % CPU. Changes take effect at the next start of Jellyfin.
|
||||
|
||||
## Configuration
|
||||
|
||||
Installing the Flatpak is up to you, e.g.
|
||||
`flatpak install --user flathub org.jellyfin.JellyfinDesktop`, or with
|
||||
nix-flatpak:
|
||||
|
||||
```nix
|
||||
services.flatpak.packages = [ "org.jellyfin.JellyfinDesktop" ];
|
||||
steamFrame.jellyfin.hardwareDecoding.enable = true;
|
||||
```
|
||||
|
||||
From a terminal, start it with the command line of
|
||||
`steamFrame.jellyfin.hardwareDecoding.command` (or `grep ^Exec=
|
||||
~/.local/share/applications/org.jellyfin.JellyfinDesktop.desktop`); its
|
||||
output shows `mpv-hwdec-shim: hwdec "auto-copy" -> "v4l2m2m-copy,auto-copy"`,
|
||||
then mpv's `Using hardware decoding (v4l2m2m-copy)`.
|
||||
|
||||
## Caveats
|
||||
|
||||
- `devices=all` gives the app all of `/dev` (cameras, input devices, ...),
|
||||
not just the decoder.
|
||||
- V4L2 M2M decoding quality varies with drivers and codecs. Tested: 8-bit
|
||||
H.264; 10-bit HEVC is untested. mpv falls back to software only when the
|
||||
decoder fails; for streams that decode with artifacts, disable the option
|
||||
(or set `hwdec = "auto-copy"`, Jellyfin's own value).
|
||||
|
||||
## How it works
|
||||
|
||||
### The shim
|
||||
|
||||
An `LD_PRELOAD` shim (`modules/jellyfin/mpv-hwdec-shim.c`, a few libmpv
|
||||
wrappers, only libc) rewrites an `hwdec` value starting with `auto` (set
|
||||
through libmpv's `mpv_set_*` functions) to `$SFN_MPV_HWDEC` (the `hwdec`
|
||||
option); explicit values such as `no` stay. It is preloaded from the Nix
|
||||
store; only its store path is exposed (read-only) to the sandbox. It would
|
||||
work for any libmpv app that sets `hwdec=auto*`, but only Jellyfin Desktop
|
||||
is set up here.
|
||||
option). It is preloaded from the Nix store; only its store path is exposed
|
||||
(read-only) to the sandbox. It would work for any libmpv app that sets
|
||||
`hwdec=auto*`, but only Jellyfin Desktop is set up here.
|
||||
|
||||
## The desktop entry
|
||||
### The desktop entry
|
||||
|
||||
Nothing is written to Flatpak's overrides: a desktop entry shadowing the
|
||||
Flatpak's (`~/.local/share/applications/org.jellyfin.JellyfinDesktop.desktop`,
|
||||
same ID, so the KDE menu and the "+" menu start it) passes device access and
|
||||
the shim as `flatpak run` options, so they apply to launches from that entry
|
||||
and are gone with it:
|
||||
Nothing is written to Flatpak's overrides: the entry
|
||||
(`~/.local/share/applications/org.jellyfin.JellyfinDesktop.desktop`) passes
|
||||
device access and the shim as `flatpak run` options, so they apply to
|
||||
launches from that entry and are gone with it:
|
||||
|
||||
```sh
|
||||
flatpak run --branch=stable --arch=aarch64 --command=jellyfin-desktop \
|
||||
@@ -40,8 +77,7 @@ flatpak run --branch=stable --arch=aarch64 --command=jellyfin-desktop \
|
||||
--env=SFN_MPV_HWDEC=v4l2m2m-copy,auto-copy org.jellyfin.JellyfinDesktop
|
||||
```
|
||||
|
||||
(The exact line with the store path is the read-only option
|
||||
`steamFrame.jellyfin.hardwareDecoding.command`.)
|
||||
(`command` is this line with the store path.)
|
||||
|
||||
Older versions used a Flatpak override (via nix-flatpak or a Home Manager
|
||||
link) and a shim copy in `~/.var/app/org.jellyfin.JellyfinDesktop`;
|
||||
|
||||
+122
-25
@@ -1,52 +1,149 @@
|
||||
# VR keyboard: how it works
|
||||
# VR keyboard
|
||||
|
||||
Technical details of the two VR keyboard patches. What they do and how to
|
||||
configure them: README, [extra keys](../README.md#steam-keyboard-patch-keyboardvrextrakeysenable)
|
||||
and [swipe and suggestions](../README.md#vr-keyboard-swipe-and-suggestions-keyboardvr).
|
||||
Two patches of Steam's VR keyboard: [extra keys](#extra-keys) and
|
||||
[swipe and suggestions](#swipe-and-suggestions). They work together or
|
||||
alone. Options: [README, Options](../README.md#options) (`keyboard.vr.*`).
|
||||
The keyboard layout of the Steam session itself is a separate fix
|
||||
([keyboard layout](session.md#keyboard-layout)).
|
||||
|
||||
## Extra keys
|
||||
|
||||
`keyboard.vr.extraKeys.enable`, module `steam-keyboard-patch`.
|
||||
|
||||
**Problem:** Steam's VR keyboard has no Ctrl, Alt or Esc, can't press real
|
||||
keys, and its text emulation only maps plain ASCII: non-ASCII and
|
||||
AltGr/dead-key characters on the German keymap (`| @ { [ ] } \ ~ ^`,
|
||||
backtick, `ä ö ü €`) come out as `1`.
|
||||
|
||||
**What you get:**
|
||||
|
||||
- Bottom row: `Esc Ctrl Alt [Space] AltGr ← ↑ ↓ → Close`, stable with Shift
|
||||
or AltGr. Esc, Ctrl and Alt have Steam's dark special-key face, like its
|
||||
AltGr and layout keys.
|
||||
- AltGr + arrows: Home, End, Page Up, Page Down (hinted on the keys);
|
||||
Shift + arrows select text.
|
||||
- AltGr + the key left of Backspace (`´` on German, `=` on US): Delete,
|
||||
labelled like Steam's Delete key in its language (`Entf`; `Del` if that is
|
||||
longer), hinted on the key without AltGr; repeats while held. Layouts with
|
||||
an AltGr character on that key get none.
|
||||
- Layouts without AltGr (US, Dvorak, Colemak, Bulgarian, Chinese, Japanese,
|
||||
Korean) get an `Fn` key right of the space bar: Steam's AltGr toggle
|
||||
(tap: once, tap twice: locked, hold), for Delete and Home/End/Page Up/Down.
|
||||
- Ctrl/Alt chords and Esc are real key presses in the VR-selected window; a
|
||||
toggled Ctrl/Alt is held down while the keyboard is open (e.g.
|
||||
Ctrl+scroll).
|
||||
- Characters Steam would type as `1` are typed correctly; everything else
|
||||
goes through Steam as before.
|
||||
- Enter always types Return (stock Steam may send it to a Steam search box).
|
||||
|
||||
Applies right away: no reboot or Steam restart needed, and it is re-applied
|
||||
after Steam restarts. Turning it off reverts the keyboard.
|
||||
|
||||
**Layouts:** the character routing targets the German keymap; on others it
|
||||
is harmless, and Esc/Ctrl/Alt/arrows work regardless.
|
||||
|
||||
**Security:** the helper service that presses the keys only accepts
|
||||
single-key Ctrl/Alt chords, the extra keys, Ctrl/Alt hold/release and single
|
||||
non-ASCII/AltGr characters; it cannot type ASCII text or press Enter.
|
||||
|
||||
**Caveat:** depends on Steam UI internals; after a Steam update that changes
|
||||
them the keyboard stays stock and the journal says why (see
|
||||
[after a Steam update](ui-patches.md#after-a-steam-update)). Tested with
|
||||
Steam client 1790377368 (UI build 11041156).
|
||||
|
||||
**Remove when** Steam's VR keyboard gets these keys.
|
||||
|
||||
### How it works
|
||||
|
||||
- The `steam-keyboard-patch` user service (`helper.mjs`) injects a patch
|
||||
into Steam's `SharedJSContext` over DevTools (`127.0.0.1:8080`) and
|
||||
re-injects it after Steam restarts; stopping it (or disabling the option)
|
||||
runs the unpatch, so no reboot or Steam restart is needed.
|
||||
runs the unpatch.
|
||||
- Ctrl/Alt chords and Esc are sent with `xdotool key` on `:0` (focus follows
|
||||
the VR-selected window); a toggled Ctrl/Alt is held down with `xdotool`
|
||||
while the keyboard is open.
|
||||
the VR-selected window); a toggled Ctrl/Alt is held down with `xdotool`.
|
||||
- Problem characters (non-ASCII, AltGr/dead-key characters on the German
|
||||
keymap) are typed with `xdotool type`; everything else goes through
|
||||
Steam's own text emulation. On other keymaps those characters are still
|
||||
typed by xdotool, which is why the routing is harmless there.
|
||||
- The helper's command set is deliberately narrow (see the README's
|
||||
security note): single-key Ctrl/Alt chords, the extra keys, Ctrl/Alt
|
||||
hold/release and single non-ASCII/AltGr characters.
|
||||
|
||||
Found by signature (see [finders and signatures](ui-patches.md#finders-and-signatures));
|
||||
if one stops matching, the keyboard stays stock and the journal says why
|
||||
(see [after a Steam update](ui-patches.md#after-a-steam-update)).
|
||||
- The new keys use Steam's key type Meta (the dark face); the Delete key
|
||||
takes Backspace's key type.
|
||||
- Found by signature (see
|
||||
[finders and signatures](ui-patches.md#finders-and-signatures)).
|
||||
|
||||
## Swipe and suggestions
|
||||
|
||||
`keyboard.vr.*`, module `vr-keyboard`.
|
||||
|
||||
**Problem:** Steam's VR keyboard is tap-only: no swipe typing, no
|
||||
suggestions, and deleting more than a few characters means many Backspace
|
||||
taps.
|
||||
|
||||
**What you get** with `keyboard.vr.enable` (the sub-features `swipe`,
|
||||
`autocorrect`, `completions`, `backspaceDrag` and `haptics` are on by
|
||||
default):
|
||||
|
||||
- **Swipe:** press the trigger on the first letter, sweep over the others,
|
||||
release on the last. The word is typed with a space before it if needed
|
||||
(`text.autoSpace`); alternatives show in the strip. `'` and `-` are typed,
|
||||
not swiped.
|
||||
- **Suggestions** never change text by themselves: a finished tapped word
|
||||
that isn't in the dictionary gets corrections (itself first;
|
||||
`autocorrect`), a word being tapped gets completions (the typed letters
|
||||
first; `completions`). A pick replaces exactly what it typed and can be
|
||||
switched again.
|
||||
- **Backspace drag:** drag Backspace left to delete one character per
|
||||
`pixelsPerChar`, with a detent (`wordDetentPixels`) at each word border
|
||||
and at the start of what the keyboard typed; drag back right to retype.
|
||||
- **Strip** (`suggestions.position`): a SteamVR dashboard panel below or
|
||||
above the keyboard, or inside the keyboard over its number row. Its
|
||||
buttons take the keyboard's key style. Below/above uses the
|
||||
[SteamVR debugger](steamvr-debugger.md), turned on automatically.
|
||||
- **Haptics:** light ticks for drag steps and picks, a Snap at word detents.
|
||||
|
||||
**Dictionary** (`dictionary.*`): by default the `keyboard.layout` language
|
||||
(de, fr, es, it, nl, pt, sv) plus English, else English only. Add words
|
||||
(`extraWords`, `extraWordFiles`), remove some (`excludeWords`) or configure
|
||||
the languages:
|
||||
|
||||
```nix
|
||||
steamFrame.keyboard.vr = {
|
||||
enable = true;
|
||||
suggestions.position = "inside"; # no SteamVR panel
|
||||
dictionary.extraWords = [ "SteamOS" "Nix" ];
|
||||
dictionary.excludeWords = [ "teh" ];
|
||||
backspaceDrag.pixelsPerChar = 20;
|
||||
};
|
||||
```
|
||||
|
||||
**Text memory:** the keyboard can't read the text field, so it remembers
|
||||
what it typed itself (`text.bufferChars`); anything it can't follow (Enter,
|
||||
arrows, extraKeys' keys, another field, `text.resetAfterIdleSeconds`) resets
|
||||
that, and suggestions only replace text the memory proves intact. Works with
|
||||
and without [extra keys](#extra-keys) (with it, non-ASCII words are typed
|
||||
via its xdotool helper).
|
||||
|
||||
**Caveats:** depends on Steam/SteamVR UI internals; after an update that
|
||||
changes them the keyboard stays stock (see
|
||||
[after a Steam update](ui-patches.md#after-a-steam-update)). Accented words
|
||||
of other languages are in the dictionary but only swipable where the layout
|
||||
has the letters.
|
||||
|
||||
**Remove when** Steam's VR keyboard gets swipe typing and suggestions.
|
||||
|
||||
### How it works
|
||||
|
||||
- A Steam UI patch (`vr-keyboard`, injected by `steam-ui-patches` like the
|
||||
other [UI patches](ui-patches.md)).
|
||||
- Swiped words are matched by shape (SHARK2-style template matching)
|
||||
against a dictionary built at build time from wordfreq frequency lists
|
||||
and Hunspell, both from nixpkgs (`dictionary.*`: per language the `words`
|
||||
most frequent wordfreq entries, shifted by `frequencyOffset`, filtered by
|
||||
Hunspell except words at or above `keepFrequentAbove`).
|
||||
and Hunspell, both from nixpkgs (per language the `words` most frequent
|
||||
wordfreq entries, shifted by `frequencyOffset`, filtered by Hunspell
|
||||
except words at or above `keepFrequentAbove`).
|
||||
- The strip below/above the keyboard is a SteamVR dashboard panel
|
||||
(`panel.js`, a patch of SteamVR's `systemui` page on port 8087, via the
|
||||
[SteamVR debugger](steamvr-debugger.md)), fed by the `vr-keyboard-relay`
|
||||
user service (`relay.mjs`) between the two pages. With `inside` neither
|
||||
the panel nor the relay runs.
|
||||
- With `extraKeys`, non-ASCII words are typed via its xdotool helper.
|
||||
|
||||
Found by signature (entries `vr-keyboard`, `vr-keyboard-panel`); if one
|
||||
stops matching the keyboard stays stock.
|
||||
(`panel.js`, a patch of SteamVR's `systemui` page on port 8087), fed by
|
||||
the `vr-keyboard-relay` user service (`relay.mjs`) between the two pages.
|
||||
With `inside` neither the panel nor the relay runs.
|
||||
- Found by signature (entries `vr-keyboard`, `vr-keyboard-panel`).
|
||||
|
||||
**Tests:** `nix flake check` (checks `vr-keyboard`: text model, corrector,
|
||||
decoder accuracy on German + English) and `keyboard.vr.checks` for the
|
||||
|
||||
+104
-14
@@ -1,15 +1,73 @@
|
||||
# Launcher menu: how it works
|
||||
# Launcher menu
|
||||
|
||||
Technical details of the "+" menu features. What they do and how to
|
||||
configure them: README, [launcher menu](../README.md#launcher-menu-launchermenu),
|
||||
[hidden apps](../README.md#hidden-apps-launchermenuhiddenapps),
|
||||
[icon fallbacks](../README.md#icon-fallbacks-launchermenuiconfallbacks).
|
||||
`launcherMenu.*`: the VR dashboard's "+" menu (non-Steam programs), with
|
||||
[hidden apps](#hidden-apps) and [icon fallbacks](#icon-fallbacks). Options:
|
||||
[README, Options](../README.md#options).
|
||||
|
||||
## Menu patches
|
||||
## Problem
|
||||
|
||||
`launcherMenu.*` (module `launcher-menu`): [UI patches](ui-patches.md) in
|
||||
Steam's `SharedJSContext` (`modules/launcher-menu/`), each registered only
|
||||
when its option is set and reverted by its unpatch when unset (next switch):
|
||||
The "+" menu is in random order with "Desktop" somewhere in a scrolling
|
||||
list, and a click shows no feedback until the window appears, so programs
|
||||
often get started twice.
|
||||
|
||||
## What you get
|
||||
|
||||
Patches of Steam's UI, each on its own option:
|
||||
|
||||
- `sort`: programs sorted by name (case-insensitive), Desktop included.
|
||||
- `pinDesktop = "top"` / `"bottom"`: Desktop pinned above/below the list,
|
||||
always visible.
|
||||
- `closeOnLaunch`: the menu closes on click.
|
||||
- `launchDebounceSeconds = <seconds>`: a repeat launch of the same command
|
||||
within that time is ignored (and logged); a program that exits right away
|
||||
can only be restarted once the time is up.
|
||||
- `grid.enable`: the programs section becomes a grid of tiles (icon, name
|
||||
below); "Add desktop window" stays a list. The popup is 300 px wide, so
|
||||
`grid.columns` sets the tile size (3 ≈ 92 px, 4 ≈ 68 px, 5 ≈ 53 px);
|
||||
`grid.maxRows` limits visible rows, the rest scrolls.
|
||||
- `showAllApps`: every program, see
|
||||
[below](#all-programs-and-developer-mode).
|
||||
|
||||
All revert when turned off (next switch).
|
||||
|
||||
## Configuration
|
||||
|
||||
```nix
|
||||
steamFrame.launcherMenu = {
|
||||
sort = true;
|
||||
pinDesktop = "bottom";
|
||||
closeOnLaunch = true;
|
||||
launchDebounceSeconds = 10;
|
||||
grid = { enable = true; columns = 4; maxRows = 4; };
|
||||
showAllApps = true;
|
||||
hiddenApps = [ "lxterminal" "cmake-gui" "firewall-config" "renderdoc" ];
|
||||
};
|
||||
```
|
||||
|
||||
### All programs and Developer Mode
|
||||
|
||||
Without Developer Mode Steam hides `konsole`, `systemsettings`, `dolphin`,
|
||||
`plasma-discover`, `vlc`, `firewall-config`, `cmake-gui`, `qrenderdoc`,
|
||||
`lxterminal` and `sh`. **Steam Developer Mode** (a Steam setting, not managed
|
||||
here) makes the "+" menu list every desktop entry; `showAllApps` lifts that
|
||||
filter only, so Developer Mode (sshd, xrdp, LAN DevTools forwards) can stay
|
||||
off. No feature needs Developer Mode; keep it off (see
|
||||
[DevTools on the LAN](ui-patches.md#devtools-on-the-lan)). Hide single
|
||||
programs with [`hiddenApps`](#hidden-apps).
|
||||
|
||||
## Limitations
|
||||
|
||||
- The pinned Desktop works with the laser but not with thumbstick / D-pad
|
||||
navigation.
|
||||
- Tested with Steam client 1790377368. A Steam update can break the
|
||||
patches; the menu then stays stock
|
||||
([after a Steam update](ui-patches.md#after-a-steam-update)).
|
||||
|
||||
## How it works
|
||||
|
||||
[UI patches](ui-patches.md) in Steam's `SharedJSContext`
|
||||
(`modules/launcher-menu/`, module `launcher-menu`), each registered only when
|
||||
its option is set and reverted by its unpatch when unset:
|
||||
|
||||
- `order/`: sorts `ScanForInstalledNonSteamApps()` by name (Steam lists the
|
||||
programs in GLib hash-table order);
|
||||
@@ -26,14 +84,46 @@ and verified by the [offline checker](ui-patches.md#after-a-steam-update).
|
||||
|
||||
## Hidden apps
|
||||
|
||||
`launcherMenu.hiddenApps` (module `hidden-apps`): a Home Manager-linked
|
||||
desktop entry with `Hidden=true` per id in `~/.local/share/applications`,
|
||||
which masks the system entry of the same id for Steam and KDE alike.
|
||||
`launcherMenu.hiddenApps`, module `hidden-apps`.
|
||||
|
||||
**Problem:** with Developer Mode or `showAllApps`, the "+" menu lists every
|
||||
desktop entry, including system tools.
|
||||
|
||||
**What you get:** the listed desktop entry ids (no `.desktop`) are hidden
|
||||
from the "+" menu and the KDE menu. The "+" menu always hides `steam` and
|
||||
`vrurlhandler`; for Konsole in VR use
|
||||
[`showAllApps`](#all-programs-and-developer-mode).
|
||||
|
||||
**How it works:** a Home Manager-linked desktop entry with `Hidden=true` per
|
||||
id in `~/.local/share/applications`, which masks the system entry of the
|
||||
same id for Steam and KDE alike.
|
||||
|
||||
## Icon fallbacks
|
||||
|
||||
`launcherMenu.iconFallbacks.*`: Home Manager links nixpkgs' Breeze SVGs
|
||||
into `~/.local/share/icons/hicolor/scalable/apps/`. When the set of links
|
||||
`launcherMenu.iconFallbacks.*`. On by default.
|
||||
|
||||
**Problem:** Steam resolves `Icon=` only in the hicolor theme (and
|
||||
`pixmaps`), so Konsole and KDE System Settings, whose icons only Breeze has,
|
||||
show without icon.
|
||||
|
||||
**What you get:** Breeze's `utilities-terminal` and `preferences-system`
|
||||
icons (plus `extra`) in hicolor, so the "+" menu shows them. A running Steam
|
||||
picks up changes without a restart. Each switch also prints hints: icons of
|
||||
programs Steam can't find that Breeze has (add them to `extra`), and
|
||||
fallbacks hicolor has anyway.
|
||||
|
||||
**Configuration:** `extra` adds Breeze icon names; a name Breeze doesn't have
|
||||
fails the build, `enable = false` provides none.
|
||||
|
||||
```nix
|
||||
steamFrame.launcherMenu.iconFallbacks.extra = [ "system-file-manager" ];
|
||||
```
|
||||
|
||||
`iconFallbacks` used to be a list; a list now fails with a hint (use
|
||||
`extra`, or `enable = false` for `[ ]`).
|
||||
|
||||
**How it works:** Home Manager links nixpkgs' Breeze SVGs into
|
||||
`~/.local/share/icons/hicolor/scalable/apps/`. When the set of links
|
||||
changes, the switch bumps the mtime of `~/.local/share/icons/hicolor`, so a
|
||||
running Steam rescans (GTK only rereads a theme whose directory changed).
|
||||
The switch's hints come from `icon-fallbacks.sh`.
|
||||
|
||||
+101
@@ -0,0 +1,101 @@
|
||||
# Session fixes
|
||||
|
||||
Four fixes for the Frame's [two graphical sessions](../README.md#two-sessions):
|
||||
session settings and services, the portal, the keyboard layout and the
|
||||
clipboard. Options: [README, Options](../README.md#options) (`session.*`,
|
||||
`keyboard.layout`, `keyboard.variant`, `clipboardSync.*`).
|
||||
|
||||
## Session settings and services
|
||||
|
||||
Module `session` (`session.nix`), used by the other modules; normally
|
||||
nothing to set.
|
||||
|
||||
**Problem:** the nested desktop can't see the Steam session's runtime dir
|
||||
and bus (services, KDE wallet), and switching from the nested desktop,
|
||||
Home Manager can't reach the service manager ("User systemd daemon not
|
||||
running") and skips `reloadSystemd`.
|
||||
|
||||
**What you get:**
|
||||
|
||||
- `session.runtimeDir` / `session.bus` point at the Steam session's runtime
|
||||
dir and bus; `session.busEnv` is a launcher prefix to reach them.
|
||||
- After every switch this module reloads the Steam session's user manager
|
||||
and applies `session.services.start` / `stop` / `restart`, which other
|
||||
modules fill (you can add your own units).
|
||||
|
||||
**Configuration:** a launcher for an app that must use the single wallet on
|
||||
the outer bus (see [Two sessions](../README.md#two-sessions)):
|
||||
|
||||
```nix
|
||||
{ config, ... }: {
|
||||
xdg.dataFile."applications/org.example.App.desktop".text = ''
|
||||
[Desktop Entry]
|
||||
Type=Application
|
||||
Name=Example
|
||||
Exec=${config.steamFrame.session.busEnv} flatpak run org.example.App %U
|
||||
'';
|
||||
}
|
||||
```
|
||||
|
||||
## Portal fix
|
||||
|
||||
`session.portalFix.enable`, module `portal`. On by default.
|
||||
|
||||
**Problem:** the Frame image (SteamOS 0.3.0, build 20260922) points the Steam
|
||||
session's `xdg-desktop-portal` at `/usr/share/xdg-desktop-portal/gamescope-portals`,
|
||||
which lacks `gamescope-portals.conf`: no backend, no OpenURI, so no app in
|
||||
the Steam session can open links.
|
||||
|
||||
**What you get:** a working OpenURI portal in the Steam session; the
|
||||
desktop's portal is unaffected.
|
||||
|
||||
**Remove when** SteamOS ships `gamescope-portals.conf`.
|
||||
|
||||
**How it works:** a portal dir in `~/.local/share` linking Valve's `.portal`
|
||||
files plus a config (`default=holo;gamescope`), and a drop-in on
|
||||
`xdg-desktop-portal.service`.
|
||||
|
||||
## Keyboard layout
|
||||
|
||||
`keyboard.layout`, `keyboard.variant`, module `keyboard-layout`.
|
||||
|
||||
**Problem:** gamescope and its Xwayland displays use US unless
|
||||
`XKB_DEFAULT_*` is set; KDE's layout only affects the nested desktop, and
|
||||
`~/.config/environment.d` isn't read on the Frame.
|
||||
|
||||
**What you get:** the layout in the Steam session, from the next Steam
|
||||
session start. The layout also picks the
|
||||
[VR keyboard](keyboard.md#swipe-and-suggestions)'s default dictionary
|
||||
language.
|
||||
|
||||
**Configuration:** `keyboard.variant` picks a variant of the layout, e.g. for
|
||||
`de`: `null` (standard, with dead keys: `^`, `` ` ``, `´` wait for the next
|
||||
key), `"nodeadkeys"` (those are typed immediately), `"mac"`, `"neo"`, `"e1"`,
|
||||
`"us"` (German letters on a US layout). List them with
|
||||
`localectl list-x11-keymap-variants <layout>`.
|
||||
|
||||
```nix
|
||||
steamFrame.keyboard = { layout = "de"; variant = "nodeadkeys"; };
|
||||
```
|
||||
|
||||
**Remove when** SteamOS applies a layout setting to gamescope.
|
||||
|
||||
**How it works:** a drop-in on `gamescope-session.service` setting
|
||||
`XKB_DEFAULT_LAYOUT`/`VARIANT`.
|
||||
|
||||
## Clipboard sync
|
||||
|
||||
`clipboardSync.enable`, module `clipboard-sync`. On by default.
|
||||
|
||||
**Problem:** the Steam session's X displays and the nested desktop have
|
||||
separate clipboards.
|
||||
|
||||
**What you get:** one clipboard: copy in one session, paste in the other.
|
||||
|
||||
**Configuration:** `clipboardSync.package` replaces the package. Switch from
|
||||
a desktop terminal: each switch restarts clipboard-sync if outdated, and it
|
||||
must restart with the desktop's environment.
|
||||
|
||||
**How it works:** [clipboard-sync](https://github.com/dnut/clipboard-sync),
|
||||
built from source (its flake is x86-only), started via KDE autostart (the
|
||||
desktop can't reach the user manager, and `:2` must exist first).
|
||||
@@ -0,0 +1,66 @@
|
||||
# Steam close button
|
||||
|
||||
`dashboard.steamCloseButton.enable`, module `steam-close-button` ([options](../README.md#options)).
|
||||
|
||||
A [SteamVR dashboard patch](ui-patches.md#steamvr-dashboard-patches).
|
||||
|
||||
## Problem
|
||||
|
||||
Every dashboard window has a close (X) button except Steam's own, and with
|
||||
no other window open the dashboard always shows it.
|
||||
|
||||
## What you get
|
||||
|
||||
The Steam window gets an X that hides Steam: it docks the window back if it
|
||||
was in the world, theater or on a hand, then shows the most recently active
|
||||
other dashboard window, or **just the dashboard bar** if there is none.
|
||||
|
||||
Steam stays hidden until you bring it back (Steam tab, a Steam menu pick,
|
||||
SteamVR asking for it): closing the active window, or a theater window,
|
||||
then goes to the previous window or the bar instead of Steam. This survives
|
||||
dashboard reopens, patch-service restarts, SteamVR restarts and reboots
|
||||
("Steam hidden" is saved in
|
||||
`~/.local/state/steam-frame-nix/ui-patches/steam-close-button.json`, see
|
||||
[Changes outside Nix](../README.md#changes-outside-nix-exceptions)). After a
|
||||
restart the patch attaches a few seconds after the dashboard appears,
|
||||
possibly after SteamVR has already shown Steam: if Steam is (or first
|
||||
becomes) the active window then, it is hidden once like with X (only the bar
|
||||
at that point); otherwise it just stays hidden.
|
||||
|
||||
## Configuration
|
||||
|
||||
```nix
|
||||
steamFrame.dashboard.steamCloseButton.enable = true;
|
||||
```
|
||||
|
||||
## Limitations
|
||||
|
||||
- SteamVR's rarer "go home" paths (Now Playing after a game quits, message
|
||||
overlays) still show Steam.
|
||||
- No effect with a VRLink remote dashboard.
|
||||
- Turning the option off while bar-only leaves no active window until the
|
||||
next tab click or dashboard open.
|
||||
|
||||
## How it works
|
||||
|
||||
`steam-close-button`, with [persistent state](ui-patches.md#persistent-state)
|
||||
(`{ schema: 1, steamHidden }`).
|
||||
|
||||
- **Button:** the frame's `closing` component shows X when
|
||||
`componentProps.onCloseRequested` exists; the Steam window's instance
|
||||
(overlay `valve.steam.gamepadui.main`) gets one and its props are
|
||||
re-assigned so the MobX computed re-evaluates.
|
||||
- **Staying hidden:** while hidden, stock fallbacks to Steam go to the
|
||||
previous window or bar-only instead: `Dashboard.autoSwitchOverlayIfNeeded`
|
||||
(instance override) and the mailbox handler `dashboard_overlay_destroyed`.
|
||||
Mailbox show/switch requests for Steam with reason `SetDockLocation` (the
|
||||
echo of X docking Steam) are dropped, "theater frame destroyed" ones are
|
||||
passed on without the Steam key. Explicit requests (tab click, Steam menu
|
||||
pick, `SwitchToDashboardOverlay`) show Steam again.
|
||||
- **Restarts:** a restored hidden state stays pending (`restorePending`)
|
||||
until Steam's frame is seen, since the injector attaches within ~5 s of
|
||||
the page appearing.
|
||||
|
||||
Debugging: `window.__sfuiSteamClose.plan()` / `homePlan()` in the `systemui`
|
||||
page; state in `window.__sfuiSteamCloseState` (survives re-injection;
|
||||
teardown restores all overrides and never switches frames).
|
||||
+36
-17
@@ -1,18 +1,37 @@
|
||||
# SteamVR debugger: how it works
|
||||
# SteamVR debugger
|
||||
|
||||
`steamvrDebugger.enable` (module `steamvr-debugger`). What it is for and
|
||||
what you need to do: README,
|
||||
[SteamVR debugger](../README.md#steamvr-debugger-steamvrdebuggerenable).
|
||||
`steamvrDebugger.enable`, module `steamvr-debugger`. Automatic; nothing to
|
||||
set. Options: [README, Options](../README.md#options).
|
||||
|
||||
Enabled automatically when any patch in `steamFrame.uiPatches.patches` has
|
||||
an `endpoint` on port 8087 (all [dashboard](dashboard.md) patches and the VR
|
||||
keyboard's strip below/above the keyboard).
|
||||
## Problem
|
||||
|
||||
SteamVR opens its DevTools port only with `VRWebHelper/DebuggerEnabled`
|
||||
(port `VRWebHelper/DebuggerPort`, default 8087). SteamVR rewrites
|
||||
`~/.config/openvr/config/steamvr.vrsettings` from memory, so the key can't
|
||||
be a link and can't be edited while SteamVR runs. It is set only while
|
||||
SteamVR runs:
|
||||
The [dashboard patches](ui-patches.md#steamvr-dashboard-patches) (and the
|
||||
[VR keyboard](keyboard.md#swipe-and-suggestions)'s strip below/above the
|
||||
keyboard) need SteamVR's DevTools port, which SteamVR opens only with its
|
||||
setting `VRWebHelper/DebuggerEnabled`. That setting lives in
|
||||
`~/.config/openvr/config/steamvr.vrsettings`, which SteamVR rewrites from
|
||||
memory, so it can't be a Nix link and can't be edited while SteamVR runs.
|
||||
|
||||
## What you get
|
||||
|
||||
The port, enabled automatically when any patch in
|
||||
`steamFrame.uiPatches.patches` has an `endpoint` on port 8087. The setting
|
||||
is **set only while SteamVR runs**: set before every SteamVR start, put back
|
||||
to its previous value when SteamVR stops, also after a rollback or uninstall
|
||||
(without Nix). A value you set to `true` yourself is never touched.
|
||||
|
||||
**The first time, restart SteamVR once** (e.g. reboot); until then the
|
||||
dashboard patches wait. Turned off, the setting is restored at the switch
|
||||
(or when SteamVR stops, if it runs).
|
||||
|
||||
## Security
|
||||
|
||||
The port listens on `127.0.0.1` only; keep Developer Mode off (see
|
||||
[DevTools on the LAN](ui-patches.md#devtools-on-the-lan)).
|
||||
|
||||
## How it works
|
||||
|
||||
Port: `VRWebHelper/DebuggerPort`, default 8087.
|
||||
|
||||
- before every SteamVR start, the `steamvr-webhelper-debugger` oneshot
|
||||
(a drop-in on `steamvr.service`, running `install.sh steamvr-debugger-arm`)
|
||||
@@ -25,12 +44,12 @@ SteamVR runs:
|
||||
- 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, so this also works after a rollback or
|
||||
uninstall; they are gone at reboot, and `.armed` (the only trace after a
|
||||
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`. A key set to `true` by the user is never touched.
|
||||
Disabled, there is no unit; the switch (cleanup) restores the key if SteamVR
|
||||
is stopped, otherwise the runtime drop-in does when it stops.
|
||||
`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.
|
||||
+73
-24
@@ -1,8 +1,15 @@
|
||||
# UI patches
|
||||
|
||||
For patch authors, and for fixing patches after a Steam update. User-level
|
||||
overview: README, [UI patches](../README.md#ui-patches-uipatchespatches);
|
||||
options `steamFrame.uiPatches.patches` and `steamFrame.uiPatches.lib`.
|
||||
`uiPatches.patches`, `uiPatches.lib` (modules `steam-ui-patches`,
|
||||
`steamvr-debugger`). For patch authors, and for fixing patches after a Steam
|
||||
update. Options: [README, Options](../README.md#options).
|
||||
|
||||
## Problem
|
||||
|
||||
Steam's UI and SteamVR's dashboard can't be extended: their files belong to
|
||||
Steam, and Steam overwrites them on every update.
|
||||
|
||||
## What you get
|
||||
|
||||
Steam's UI and SteamVR's dashboard (`vrwebhelper`) are CEF web pages with a
|
||||
local DevTools port: `127.0.0.1:8080` for Steam (SteamOS passes
|
||||
@@ -10,15 +17,51 @@ local DevTools port: `127.0.0.1:8080` for Steam (SteamOS passes
|
||||
[its debugger](steamvr-debugger.md) is on. The `steam-ui-patches` user
|
||||
service (`injector.mjs`) patches the running pages through them; Steam's
|
||||
files are never modified. The [launcher menu](launcher-menu.md),
|
||||
[dashboard](dashboard.md) and [VR keyboard](keyboard.md) features are such
|
||||
patches (the extra keys have their own injector, `steam-keyboard-patch`),
|
||||
and you can add your own.
|
||||
[dashboard](#steamvr-dashboard-patches) and [VR keyboard](keyboard.md)
|
||||
features are such patches (the extra keys have their own injector,
|
||||
`steam-keyboard-patch`), and you can add your own.
|
||||
|
||||
Don't expose these ports: see
|
||||
[DevTools on the LAN](../README.md#devtools-on-the-lan).
|
||||
Turning a feature off (or stopping the `steam-ui-patches` /
|
||||
`steam-keyboard-patch` user services) restores the stock UI without a Steam
|
||||
restart. Log: `journalctl --user -u steam-ui-patches -u steam-keyboard-patch`.
|
||||
|
||||
**Caveat:** patches depend on Steam UI internals and can break with an
|
||||
update; find modules by signature, not id ([below](#finders-and-signatures)).
|
||||
## Caveats and security
|
||||
|
||||
- Patches depend on Steam UI internals and can break with an update; find
|
||||
modules by signature, not id ([below](#finders-and-signatures)).
|
||||
- Don't expose the DevTools ports ([DevTools on the LAN](#devtools-on-the-lan)).
|
||||
|
||||
### DevTools on the LAN
|
||||
|
||||
Steam's Developer Mode enables `steam-web-debug-portforward`
|
||||
(`0.0.0.0:8081` → `8080`) and `steamvr-web-debug-portforward`
|
||||
(`0.0.0.0:8088` → `8087`), and firewalld allows ports 1024-65535, so anyone
|
||||
on the network could run code in Steam's UI. No patch needs Developer Mode,
|
||||
so keep it off. If it was on while those units were masked, they may stay
|
||||
enabled: check with
|
||||
`systemctl is-enabled steam-web-debug-portforward steamvr-web-debug-portforward`
|
||||
and `sudo systemctl disable` them.
|
||||
|
||||
## SteamVR dashboard patches
|
||||
|
||||
[Dashboard windows](dashboard-windows.md),
|
||||
[Steam close button](steam-close-button.md),
|
||||
[window curvature](window-curvature.md) and
|
||||
[window control bar](window-control-bar.md) patch SteamVR's dashboard while
|
||||
it runs. What they have in common:
|
||||
|
||||
- Each turns on the [SteamVR debugger](steamvr-debugger.md) (**the first
|
||||
time, restart SteamVR once**).
|
||||
- They depend on SteamVR UI internals: each is found by signature (its entry
|
||||
in `modules/lib/signatures.json` has the patch's name); after an update
|
||||
that changes them the dashboard stays stock (see
|
||||
[after a Steam update](#after-a-steam-update)). Tested with SteamVR build
|
||||
11008059.
|
||||
- All are laser-only: gamepad navigation sees the stock dashboard.
|
||||
- All are `mkPatch` patches of `vrwebhelper` (page title `systemui`,
|
||||
DevTools `127.0.0.1:8087`, webpack chunk `webpackChunkvrwebui`),
|
||||
registered only when enabled. Sources: `modules/<name>/patch.js`, whose
|
||||
header comments go into more detail.
|
||||
|
||||
## Defining a patch
|
||||
|
||||
@@ -46,8 +89,8 @@ Patches are evaluated on attach, after new JS contexts (reloads) and every
|
||||
15 s, so they must be idempotent: return e.g. `"patched"` once, then
|
||||
`"unchanged"` (not logged); other results are logged when they change
|
||||
(`journalctl --user -u steam-ui-patches`). On stop, `unpatch` restores the
|
||||
stock UI without a Steam restart. The service exists only while the list is
|
||||
non-empty and is restarted on every switch.
|
||||
stock UI. The service exists only while the list is non-empty and is
|
||||
restarted on every switch.
|
||||
|
||||
## Persistent state
|
||||
|
||||
@@ -66,12 +109,11 @@ per such patch in `~/.local/state/steam-frame-nix/ui-patches/<name>.json`
|
||||
service writes the file atomically, only on change, only for that page's
|
||||
`state` patches, at most 64 KiB.
|
||||
|
||||
Used by the [window control bar](dashboard.md#window-control-bar) and the
|
||||
[Steam close button](dashboard.md#steam-close-button). The file is user
|
||||
data, not generated by Nix: it is kept when the patch is disabled or removed
|
||||
(the choices come back when you enable it again) and removed only by
|
||||
`steam-frame-nix-cleanup --all` or `install.sh uninstall`; see
|
||||
[changes outside Nix](../README.md#changes-outside-nix-exceptions).
|
||||
Used by the [window control bar](window-control-bar.md) and the
|
||||
[Steam close button](steam-close-button.md). The file is user data, not
|
||||
generated by Nix: it is kept when the patch is disabled or removed and
|
||||
removed only by `steam-frame-nix-cleanup --all` or `install.sh uninstall`;
|
||||
see [changes outside Nix](../README.md#changes-outside-nix-exceptions).
|
||||
|
||||
## Finders and signatures
|
||||
|
||||
@@ -134,9 +176,9 @@ steamFrame.uiPatches.patches = [ {
|
||||
|
||||
`modules/lib/hooks.js`, argument `hooks`, also `window.__sfuiHooks`: patches
|
||||
intercepting the same method (e.g. the dashboard mailbox's `SendMessage`,
|
||||
used by [dashboard windows](dashboard.md#dashboard-windows) and
|
||||
[window curvature](dashboard.md#window-curvature)) register named hooks; one
|
||||
wrapper per method runs them in registration order, so patches can be
|
||||
used by [dashboard windows](dashboard-windows.md#how-it-works) and
|
||||
[window curvature](window-curvature.md#how-it-works)) register named hooks;
|
||||
one wrapper per method runs them in registration order, so patches can be
|
||||
injected, upgraded and reverted in any order.
|
||||
|
||||
```js
|
||||
@@ -147,10 +189,18 @@ hooks.remove(Mailbox.prototype, 'SendMessage', 'my-patch'); // in unpatch
|
||||
```
|
||||
|
||||
Patches reacting to presses on the curvature controls follow the
|
||||
[window curvature contract](dashboard.md#contract-for-other-patches) (`sfui-curv-*`).
|
||||
[window curvature contract](window-curvature.md#contract-for-other-patches)
|
||||
(`sfui-curv-*`).
|
||||
|
||||
## After a Steam update
|
||||
|
||||
If a Steam or SteamVR update changes Steam's code so a signature no longer
|
||||
matches exactly once, that patch changes nothing: the feature stays stock
|
||||
and the journal (see [above](#what-you-get)) says why, e.g.
|
||||
`signature not found, Steam left unpatched: …`. Update steam-frame-nix
|
||||
(`nix flake update steam-frame-nix`, then switch) once it supports the new
|
||||
build.
|
||||
|
||||
Check the signatures offline (Steam need not run) from a checkout:
|
||||
|
||||
```sh
|
||||
@@ -178,5 +228,4 @@ To fix a signature, inspect the new module sources
|
||||
(`scripts/webpack-modules.mjs`), adjust `signatures.json` and bump the
|
||||
patch's `VERSION` if its code changes. Flags: `--signatures FILE` (your own,
|
||||
bundles `steamui` / `vrwebui-systemui`), `--dir steamui=DIR`,
|
||||
`--patch NAME`, `--strict` (fail on warnings), `--json`. Live:
|
||||
`journalctl --user -u steam-keyboard-patch -u steam-ui-patches`.
|
||||
`--patch NAME`, `--strict` (fail on warnings), `--json`.
|
||||
@@ -0,0 +1,57 @@
|
||||
# Window control bar
|
||||
|
||||
`dashboard.frameControls.*`, module `frame-controls` ([options](../README.md#options)).
|
||||
|
||||
A [SteamVR dashboard patch](ui-patches.md#steamvr-dashboard-patches).
|
||||
|
||||
## Problem
|
||||
|
||||
The controls under a dashboard window are fixed: some in the bottom bar,
|
||||
others only in the three-dot menu (curvature, dock to a controller), and
|
||||
theater windows have no "Float".
|
||||
|
||||
## What you get
|
||||
|
||||
- **long press** a bar icon or menu row (`longPressMs`; a progress ring
|
||||
shows from half the time, at most after 1 s), then **Show in bar** in the
|
||||
popup moves that control between bar and menu **for all windows**.
|
||||
Placements survive SteamVR restarts and reboots (saved in
|
||||
`~/.local/state/steam-frame-nix/ui-patches/frame-controls.json`, see
|
||||
[Changes outside Nix](../README.md#changes-outside-nix-exceptions)).
|
||||
Short presses stay stock.
|
||||
- `inBar` / `inMenu` set where controls start; a popup choice wins until
|
||||
that control's entry changes.
|
||||
- `floatInTheater` gives theater windows the "Float" control.
|
||||
|
||||
With [window curvature](window-curvature.md), a drag on the curvature
|
||||
control adjusts curvature and cancels the long press, also after the ring
|
||||
shows; once the ring shows, the drag needs 3× the usual travel
|
||||
(`dragThresholdPixels`, counted from where the press started), so laser
|
||||
drift during the hold doesn't cancel it.
|
||||
|
||||
## Configuration
|
||||
|
||||
```nix
|
||||
steamFrame.dashboard.frameControls = {
|
||||
enable = true;
|
||||
# longPressMs = 1500;
|
||||
# inBar = [ "curvature" ]; inMenu = [ "theater" ];
|
||||
# floatInTheater = true;
|
||||
};
|
||||
```
|
||||
|
||||
## Limitations
|
||||
|
||||
- Laser only: no right-click or thumbstick click reaches the dashboard;
|
||||
gamepad navigation sees stock controls.
|
||||
- Placements are per control type, not per window.
|
||||
- The three-dot button itself can't be moved.
|
||||
|
||||
## How it works
|
||||
|
||||
`frame-controls`, with [persistent state](ui-patches.md#persistent-state)
|
||||
(placements). Works with window curvature through its
|
||||
[contract](window-curvature.md#contract-for-other-patches).
|
||||
|
||||
Debugging: `window.__sfuiFrameControls.dump()`, `.placement()`, `.reset()`
|
||||
(forget choices), `.log`, `window.__sfuiFrameControlsState`.
|
||||
@@ -0,0 +1,85 @@
|
||||
# Window curvature
|
||||
|
||||
`dashboard.windowCurvature.*`, module `window-curvature` ([options](../README.md#options)).
|
||||
|
||||
A [SteamVR dashboard patch](ui-patches.md#steamvr-dashboard-patches).
|
||||
|
||||
## Problem
|
||||
|
||||
SteamVR dashboard windows are either curved (fixed radius) or flat, and
|
||||
world windows start flat.
|
||||
|
||||
## What you get
|
||||
|
||||
The "Toggle Curvature" row of a window's three-dot menu becomes a control
|
||||
showing the window's value; the same control in the bottom bar (see
|
||||
[window control bar](window-control-bar.md)) works without the value, with
|
||||
haptic steps.
|
||||
|
||||
- **click:** curved → flat, flat → stock (1);
|
||||
- **drag up/down** with the laser: curvature from 0 (flat) to `max`,
|
||||
relative to stock (2 = half the radius), rounded to `step`, with a detent
|
||||
of `detentPixels` of drag at each of `detentPoints` (no values skipped),
|
||||
and haptics (`haptics`) for detents, edges and steps. Drag distance:
|
||||
`dragPixelsPerUnit` in the menu, `barDragPixelsPerUnit` on the bar button,
|
||||
after `dragThresholdPixels`.
|
||||
|
||||
A window without its own value is shown at `initial` once curved in the
|
||||
world or on a hand, at 1 in the dashboard or theater. Values are kept per
|
||||
window until SteamVR restarts.
|
||||
|
||||
## Configuration
|
||||
|
||||
```nix
|
||||
steamFrame.dashboard.windowCurvature = {
|
||||
enable = true;
|
||||
# initial = 1.0; max = 3.0; step = 0.05;
|
||||
# detentPoints = [ 0 1.0 ]; detentPixels = 24;
|
||||
# dragPixelsPerUnit = 60; # full range in one drag (see below)
|
||||
};
|
||||
```
|
||||
|
||||
## Limitations
|
||||
|
||||
- Laser only; with gamepad navigation the row is the stock toggle.
|
||||
- No thumbstick scrolling (SteamVR sends no wheel events to the menu).
|
||||
- The laser stops at the menu's edge (~190 px above the row): with the
|
||||
default 120 px per 1.0, 0 → 1 fits into one drag, 0 → 3 takes two. Lower
|
||||
`dragPixelsPerUnit` (≤ 60) for the full range in one drag.
|
||||
|
||||
## How it works
|
||||
|
||||
Stock curvature: the frame renders a transform `frame:<id>:curvature-origin`
|
||||
at `z = DashboardStore.curvatureDistance` when curved, else 1000; its panels
|
||||
reference it as `curvature-origin-id` and vrcompositor bends them onto a
|
||||
cylinder around it (curvature = 1/radius). Those MobX properties are
|
||||
non-configurable, so the patch hooks the mailbox `SendMessage`
|
||||
([shared hooks](ui-patches.md#shared-method-hooks), shared with
|
||||
[dashboard windows](dashboard-windows.md)) and rewrites the origin's z in
|
||||
outgoing scene graphs to stock / value. On/off stays the stock toggle state,
|
||||
so stock toggle and wheel always agree.
|
||||
|
||||
Values are kept per window (key: first overlay key) in
|
||||
`window.__sfuiWindowCurvatureState`, across re-patch and unpatch, not across
|
||||
a dashboard reload. The bar button uses the coordinates SteamVR keeps
|
||||
sending past the pressed panel's edge while the trigger is held; the bar
|
||||
panel is never resized.
|
||||
|
||||
Debugging: `window.__sfuiWindowCurvature.dump()` (`.log` recent events).
|
||||
|
||||
### Contract for other patches
|
||||
|
||||
For patches handling presses on the curvature controls (e.g. the window
|
||||
control bar's long press): every element the patch drives has class
|
||||
`sfui-curv-ctl`; when a press becomes a drag, a bubbling `CustomEvent`
|
||||
`sfui-curv-dragstart` (detail `{ frameID, where: 'menu' | 'bar' }`) is
|
||||
dispatched on it, and `sfui-curv-dragend` when it ends;
|
||||
`window.__sfuiWindowCurvature.scalePressDragThreshold(factor)` sets the
|
||||
current press's drag threshold to `factor` × `dragThresholdPixels` (from the
|
||||
press start; returns whether it applied, i.e. a press that is not yet a
|
||||
drag); `window.__sfuiWindowCurvature.cancelPress()` ends a press without its
|
||||
click. A patch with its own gesture lets `mousemove` through while
|
||||
undecided, drops its gesture on `sfui-curv-dragstart`, may raise the
|
||||
threshold while its gesture is under way, and calls `cancelPress()` when it
|
||||
takes the press over. The window control bar uses factor 3 once its
|
||||
progress ring shows.
|
||||
Reference in new issue
Block a user