mirror of
https://github.com/lhns/steam-frame-nix.git
synced 2026-10-06 01: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
@@ -18,87 +18,42 @@ for everything).
|
||||
All options live under `steamFrame.*`. The portal fix and clipboard sync are
|
||||
on by default; everything else is opt-in.
|
||||
|
||||
- [Features](#features) · [Install](#install) · [Two sessions](#two-sessions) ·
|
||||
[Usage](#usage) · [Options](#options)
|
||||
- [Fixes in detail](#fixes-in-detail):
|
||||
[session](#session-settings-and-background-services-sessionnix),
|
||||
[portal](#portal-sessionportalfix),
|
||||
[keyboard layout](#keyboard-layout-keyboardlayout-keyboardvariant),
|
||||
[VR keyboard extra keys](#steam-keyboard-patch-keyboardvrextrakeysenable),
|
||||
[swipe and suggestions](#vr-keyboard-swipe-and-suggestions-keyboardvr),
|
||||
[launcher menu](#launcher-menu-launchermenu),
|
||||
[hidden apps](#hidden-apps-launchermenuhiddenapps),
|
||||
[icon fallbacks](#icon-fallbacks-launchermenuiconfallbacks),
|
||||
[dashboard windows](#dashboard-windows-dashboardwindows),
|
||||
[Steam close button](#steam-close-button-dashboardsteamclosebuttonenable),
|
||||
[window curvature](#window-curvature-dashboardwindowcurvature),
|
||||
[window control bar](#window-control-bar-dashboardframecontrols),
|
||||
[SteamVR debugger](#steamvr-debugger-steamvrdebuggerenable),
|
||||
[clipboard sync](#clipboard-sync-clipboardsyncenable),
|
||||
[Firefox](#firefox-firefox),
|
||||
[Jellyfin hardware decoding](#jellyfin-hardware-decoding-jellyfinhardwaredecoding)
|
||||
- [UI patches](#ui-patches-uipatchespatches):
|
||||
[DevTools on the LAN](#devtools-on-the-lan),
|
||||
[after a Steam update](#after-a-steam-update)
|
||||
- [Changes outside Nix](#changes-outside-nix-exceptions) ·
|
||||
[Rollback](#rollback) · [Uninstall](#uninstall)
|
||||
|
||||
Technical documentation (how the patches work, writing your own, fixing them
|
||||
after a Steam update): [`docs/`](docs).
|
||||
|
||||
## Features
|
||||
|
||||
Session and desktop:
|
||||
One page per feature in [`docs/`](docs): problem, what you get,
|
||||
configuration, limitations and how it works.
|
||||
|
||||
- [Portal fix](#portal-sessionportalfix) (`session.portalFix`, on): apps in
|
||||
the Steam session can open links.
|
||||
- [Clipboard sync](#clipboard-sync-clipboardsyncenable) (`clipboardSync`,
|
||||
on): one clipboard for the Steam session and the nested desktop.
|
||||
- [Session settings](#session-settings-and-background-services-sessionnix)
|
||||
(`session.*`): outer bus and user services, for launchers and the other
|
||||
modules.
|
||||
**Session** ([docs/session.md](docs/session.md)):
|
||||
|
||||
Keyboard:
|
||||
- [Session settings](docs/session.md#session-settings-and-services) (`session.*`): outer bus and user services, for launchers and the other modules.
|
||||
- [Portal fix](docs/session.md#portal-fix) (`session.portalFix`, on): apps in the Steam session can open links.
|
||||
- [Keyboard layout](docs/session.md#keyboard-layout) (`keyboard.layout`, `keyboard.variant`): XKB layout for the Steam session.
|
||||
- [Clipboard sync](docs/session.md#clipboard-sync) (`clipboardSync`, on): one clipboard for the Steam session and the nested desktop.
|
||||
|
||||
- [Keyboard layout](#keyboard-layout-keyboardlayout-keyboardvariant)
|
||||
(`keyboard.layout`, `keyboard.variant`): XKB layout for the Steam session.
|
||||
- [Extra keys](#steam-keyboard-patch-keyboardvrextrakeysenable)
|
||||
(`keyboard.vr.extraKeys`): Esc/Ctrl/Alt, arrows, Delete, real chords and
|
||||
AltGr/non-ASCII characters on the VR keyboard.
|
||||
- [Swipe and suggestions](#vr-keyboard-swipe-and-suggestions-keyboardvr)
|
||||
(`keyboard.vr`): swipe typing, corrections, completions, Backspace drag.
|
||||
**VR keyboard** ([docs/keyboard.md](docs/keyboard.md)):
|
||||
|
||||
VR "+" menu:
|
||||
- [Extra keys](docs/keyboard.md#extra-keys) (`keyboard.vr.extraKeys`): Esc/Ctrl/Alt, arrows, Delete, real chords and AltGr/non-ASCII characters.
|
||||
- [Swipe and suggestions](docs/keyboard.md#swipe-and-suggestions) (`keyboard.vr`): swipe typing, corrections, completions, Backspace drag.
|
||||
|
||||
- [Launcher menu](#launcher-menu-launchermenu) (`launcherMenu.*`): sorted,
|
||||
Desktop pinned, closes on launch, no double launches, grid of tiles, all
|
||||
programs without Developer Mode.
|
||||
- [Hidden apps](#hidden-apps-launchermenuhiddenapps)
|
||||
(`launcherMenu.hiddenApps`) and
|
||||
[icon fallbacks](#icon-fallbacks-launchermenuiconfallbacks)
|
||||
(`launcherMenu.iconFallbacks`, on) for Konsole and KDE System Settings.
|
||||
**VR "+" menu** ([docs/launcher-menu.md](docs/launcher-menu.md)):
|
||||
|
||||
SteamVR dashboard (turns on the
|
||||
[SteamVR debugger](#steamvr-debugger-steamvrdebuggerenable) automatically):
|
||||
- [Launcher menu](docs/launcher-menu.md) (`launcherMenu.*`): sorted, Desktop pinned, closes on launch, no double launches, grid of tiles, all programs without Developer Mode.
|
||||
- [Hidden apps](docs/launcher-menu.md#hidden-apps) (`launcherMenu.hiddenApps`) and [icon fallbacks](docs/launcher-menu.md#icon-fallbacks) (`launcherMenu.iconFallbacks`, on) for Konsole and KDE System Settings.
|
||||
|
||||
- [Dashboard windows](#dashboard-windows-dashboardwindows)
|
||||
(`dashboard.windows`): larger max scale and push-back distance.
|
||||
- [Steam close button](#steam-close-button-dashboardsteamclosebuttonenable)
|
||||
(`dashboard.steamCloseButton`): an X that hides the Steam window.
|
||||
- [Window curvature](#window-curvature-dashboardwindowcurvature)
|
||||
(`dashboard.windowCurvature`): adjustable curvature per window.
|
||||
- [Window control bar](#window-control-bar-dashboardframecontrols)
|
||||
(`dashboard.frameControls`): move controls between bar and three-dot menu.
|
||||
**SteamVR dashboard** ([dashboard patches](docs/ui-patches.md#steamvr-dashboard-patches)):
|
||||
|
||||
Apps:
|
||||
- [Dashboard windows](docs/dashboard-windows.md) (`dashboard.windows`): larger max scale and push-back distance.
|
||||
- [Steam close button](docs/steam-close-button.md) (`dashboard.steamCloseButton`): an X that hides the Steam window.
|
||||
- [Window curvature](docs/window-curvature.md) (`dashboard.windowCurvature`): adjustable curvature per window.
|
||||
- [Window control bar](docs/window-control-bar.md) (`dashboard.frameControls`): move controls between bar and three-dot menu.
|
||||
- [SteamVR debugger](docs/steamvr-debugger.md) (`steamvrDebugger`, automatic): SteamVR's DevTools port for these, set only while SteamVR runs.
|
||||
|
||||
- [Firefox](#firefox-firefox) (`firefox`): launcher for the Flatpak with a VR
|
||||
fullscreen fix, optional AV1 off, a separate desktop profile.
|
||||
- [Jellyfin](#jellyfin-hardware-decoding-jellyfinhardwaredecoding)
|
||||
(`jellyfin.hardwareDecoding`): hardware video decoding in the Jellyfin
|
||||
Desktop Flatpak.
|
||||
**Apps:**
|
||||
|
||||
Your own patches of Steam's UI: [UI patches](#ui-patches-uipatchespatches).
|
||||
- [Firefox](docs/firefox.md) (`firefox`): launcher for the Flatpak with a VR fullscreen fix, optional AV1 off, a separate desktop profile.
|
||||
- [Jellyfin](docs/jellyfin.md) (`jellyfin.hardwareDecoding`): hardware video decoding in the Jellyfin Desktop Flatpak.
|
||||
|
||||
**Your own patches** of Steam's UI, and fixing patches after a Steam update: [UI patches](docs/ui-patches.md).
|
||||
|
||||
## Install
|
||||
|
||||
@@ -113,22 +68,13 @@ The short link redirects to
|
||||
[`install.sh`](https://raw.githubusercontent.com/lhns/steam-frame-nix/main/install.sh)
|
||||
on `main`. `sudo` needs a password: run `passwd` first if you never set one.
|
||||
|
||||
The installer:
|
||||
|
||||
1. installs Nix with the [NixOS nix-installer](https://github.com/NixOS/nix-installer)
|
||||
(`steam-deck` planner: store in `/home/nix`, survives SteamOS updates;
|
||||
flakes on), unlocking the read-only root only for the install. Skipped if
|
||||
Nix already works;
|
||||
2. uses `~/.config/home-manager` or `--flake <dir-or-flakeref>`; if there is
|
||||
none, creates `~/nix-config` (a git repo, linked to
|
||||
`~/.config/home-manager`) from the [template](template) with your user
|
||||
name;
|
||||
3. runs `home-manager switch`; conflicting dotfiles are renamed to
|
||||
`*.hm-backup-<time>`.
|
||||
|
||||
Re-running it just switches again (`--yes` answers every question).
|
||||
Afterwards edit `~/nix-config/home.nix` and run `home-manager switch` from a
|
||||
terminal in the nested desktop (see [Usage](#usage)).
|
||||
The installer installs Nix (skipped if Nix already works), uses
|
||||
`~/.config/home-manager` or `--flake <dir-or-flakeref>` (if there is none, it
|
||||
creates `~/nix-config` from the [template](template) with your user name) and
|
||||
runs `home-manager switch`; what it sets up is listed under
|
||||
[Set up by install.sh](#set-up-by-installsh). Re-running it just switches
|
||||
again (`--yes` answers every question). Afterwards edit
|
||||
`~/nix-config/home.nix` and switch (see [Usage](#usage)).
|
||||
|
||||
```sh
|
||||
curl -fsSL https://steam-frame-nix.lhns.de | bash -s -- status # Nix, generation, services
|
||||
@@ -137,9 +83,7 @@ curl -fsSL https://steam-frame-nix.lhns.de | bash -s -- cleanup --all # see Ch
|
||||
```
|
||||
|
||||
`bash -s -- --help` lists all commands and flags. See
|
||||
[Uninstall](#uninstall) for what `uninstall` removes and keeps, and
|
||||
[Set up by install.sh](#set-up-by-installsh) for everything the installer
|
||||
changes.
|
||||
[Uninstall](#uninstall) for what `uninstall` removes and keeps.
|
||||
|
||||
**Manual setup** (Nix with flakes and standalone home-manager):
|
||||
`nix flake init -t github:lhns/steam-frame-nix` creates a commented
|
||||
@@ -164,18 +108,18 @@ What this means for you:
|
||||
- **Switch from the nested desktop:** run `home-manager switch` in a
|
||||
terminal there, so clipboard-sync restarts with the desktop's
|
||||
environment. User services are handled for you
|
||||
([session settings](#session-settings-and-background-services-sessionnix)).
|
||||
([session settings](docs/session.md#session-settings-and-services)).
|
||||
- **Wallet:** there should be one `kwalletd6`, on the outer bus; apps started
|
||||
from the desktop would otherwise start a second one whose secrets VR can't
|
||||
see. Prefix such launchers' `Exec=` with `steamFrame.session.busEnv`
|
||||
(example under [Usage](#usage)).
|
||||
([example](docs/session.md#session-settings-and-services)).
|
||||
- **Launchers:** the "+" menu only sees `~/.local/share/applications` (not
|
||||
`~/.nix-profile/share`), so entries are written there, shadowing
|
||||
Flatpak/package entries with the same ID.
|
||||
- **Keyboard layout, clipboard, Firefox:** each session has its own; see
|
||||
[keyboard layout](#keyboard-layout-keyboardlayout-keyboardvariant),
|
||||
[clipboard sync](#clipboard-sync-clipboardsyncenable),
|
||||
[Firefox](#firefox-firefox) (desktop profile).
|
||||
[keyboard layout](docs/session.md#keyboard-layout),
|
||||
[clipboard sync](docs/session.md#clipboard-sync),
|
||||
[Firefox](docs/firefox.md#desktop-profile) (desktop profile).
|
||||
|
||||
## Usage
|
||||
|
||||
@@ -213,7 +157,7 @@ these two files ([`template/`](template), with more comments):
|
||||
```
|
||||
|
||||
```nix
|
||||
# home.nix
|
||||
# home.nix (the template has steamFrame commented out)
|
||||
{ config, pkgs, username, homeDirectory, ... }: {
|
||||
home.username = username;
|
||||
home.homeDirectory = homeDirectory;
|
||||
@@ -248,19 +192,10 @@ these two files ([`template/`](template), with more comments):
|
||||
firefox.disableAv1 = true;
|
||||
jellyfin.hardwareDecoding.enable = true; # install the Flatpak yourself
|
||||
};
|
||||
|
||||
# Example: an app that must use the single wallet on the outer bus.
|
||||
# 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
|
||||
# '';
|
||||
}
|
||||
```
|
||||
|
||||
Switch from a terminal **in the nested desktop** (so clipboard-sync restarts
|
||||
with the desktop's environment):
|
||||
Switch from a terminal in the nested desktop (see [Two sessions](#two-sessions)):
|
||||
|
||||
```sh
|
||||
home-manager switch # config in ~/.config/home-manager (installer)
|
||||
@@ -274,11 +209,6 @@ imports all modules; single ones:
|
||||
Every module imports `cleanup` (see
|
||||
[Changes outside Nix](#changes-outside-nix-exceptions)).
|
||||
|
||||
**Steam Developer Mode** (a Steam setting, not managed here) makes the "+"
|
||||
menu list every desktop entry; `launcherMenu.showAllApps` does the same
|
||||
without it. No feature needs Developer Mode; keep it off (see
|
||||
[DevTools on the LAN](#devtools-on-the-lan)).
|
||||
|
||||
## Options
|
||||
|
||||
| Option | Type | Default | Description |
|
||||
@@ -291,9 +221,9 @@ without it. No feature needs Developer Mode; keep it off (see
|
||||
| `steamFrame.session.services.stop` | list of str | `[ ]` | User units stopped on switch if running (e.g. of a disabled feature). |
|
||||
| `steamFrame.session.portalFix.enable` | bool | `true` | Working portal config (OpenURI) for the Steam session. |
|
||||
| `steamFrame.keyboard.layout` | null or str | `null` | XKB layout for the Steam session, e.g. `"de"`; `null`: US. |
|
||||
| `steamFrame.keyboard.variant` | null or str | `null` | XKB variant for the Steam session, e.g. `"nodeadkeys"`; see [Keyboard layout](#keyboard-layout-keyboardlayout-keyboardvariant). |
|
||||
| `steamFrame.keyboard.variant` | null or str | `null` | XKB variant for the Steam session, e.g. `"nodeadkeys"`; see [Keyboard layout](docs/session.md#keyboard-layout). |
|
||||
| `steamFrame.keyboard.vr.extraKeys.enable` | bool | `false` | VR keyboard with Esc/Ctrl/Alt, arrows, real chords, AltGr/non-ASCII. |
|
||||
| `steamFrame.keyboard.vr.enable` | bool | `false` | Swipe typing, suggestions and Backspace drag on the VR keyboard; the sub-features below are on by default, see [VR keyboard](#vr-keyboard-swipe-and-suggestions-keyboardvr). |
|
||||
| `steamFrame.keyboard.vr.enable` | bool | `false` | Swipe typing, suggestions and Backspace drag on the VR keyboard; the sub-features below are on by default, see [VR keyboard](docs/keyboard.md#swipe-and-suggestions). |
|
||||
| `steamFrame.keyboard.vr.swipe.enable` | bool | `true` | Swipe typing. |
|
||||
| `steamFrame.keyboard.vr.dictionary.languages` | list of submodules | layout language + English | `{ language; hunspell; words; frequencyOffset; keepFrequentAbove; }`: wordfreq language, `pkgs.hunspellDicts` name (or `null`), most frequent words taken, zipf offset, keep words Hunspell rejects from this zipf on (default `4.0`). Default: the `keyboard.layout` language (de, fr, es, it, nl, pt, sv; 60000) + English (40000, `-0.3`), else English (60000). |
|
||||
| `steamFrame.keyboard.vr.dictionary.contractions` | bool | `true` | Words with apostrophes (`couldn't`, `geht's`), swiped by their letters. |
|
||||
@@ -315,7 +245,7 @@ without it. No feature needs Developer Mode; keep it off (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](#ui-patches-uipatchespatches). |
|
||||
| `steamFrame.uiPatches.patches` | list of submodules | `[ ]` | Runtime patches of Steam's web UIs, see [UI patches](docs/ui-patches.md). |
|
||||
| `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. |
|
||||
@@ -324,14 +254,14 @@ without it. No feature needs Developer Mode; keep it off (see
|
||||
| `steamFrame.launcherMenu.grid.enable` | bool | `false` | Show the "+" menu's programs as a grid of tiles. |
|
||||
| `steamFrame.launcherMenu.grid.columns` | int, 1-8 | `4` | Tiles per row (3 ≈ 92 px, 4 ≈ 68 px, 5 ≈ 53 px). |
|
||||
| `steamFrame.launcherMenu.grid.maxRows` | null or positive int | `null` | Visible rows, the rest scrolls; `null`: up to 600 px. |
|
||||
| `steamFrame.launcherMenu.showAllApps` | bool | `false` | List all programs without Developer Mode, see [Launcher menu](#launcher-menu-launchermenu). |
|
||||
| `steamFrame.launcherMenu.iconFallbacks.enable` | bool | `true` | Breeze icons of Konsole and KDE System Settings in hicolor, so the "+" menu shows them, see [Icon fallbacks](#icon-fallbacks-launchermenuiconfallbacks). |
|
||||
| `steamFrame.launcherMenu.showAllApps` | bool | `false` | List all programs without Developer Mode, see [Launcher menu](docs/launcher-menu.md#all-programs-and-developer-mode). |
|
||||
| `steamFrame.launcherMenu.iconFallbacks.enable` | bool | `true` | Breeze icons of Konsole and KDE System Settings in hicolor, so the "+" menu shows them, see [Icon fallbacks](docs/launcher-menu.md#icon-fallbacks). |
|
||||
| `steamFrame.launcherMenu.iconFallbacks.extra` | list of str | `[ ]` | Further Breeze app icon names to provide (a name Breeze lacks fails the build). |
|
||||
| `steamFrame.launcherMenu.hiddenApps` | list of str | `[ ]` | Desktop entry ids (no `.desktop`) hidden from the "+" and KDE menus. |
|
||||
| `steamFrame.dashboard.windows.maxScale` | null or positive number | `null` | Max resize scale of dashboard windows; `null`: stock (2), see [Dashboard windows](#dashboard-windows-dashboardwindows). |
|
||||
| `steamFrame.dashboard.windows.maxScale` | null or positive number | `null` | Max resize scale of dashboard windows; `null`: stock (2), see [Dashboard windows](docs/dashboard-windows.md). |
|
||||
| `steamFrame.dashboard.windows.distance.{world,theater,dashboard}.{min,max}` | null or positive number (m) | `null` | Pull-in / push-back limits of grabbed windows; `null`: stock (world 0.25-5, theater 1-6, dashboard 0.3-4 m). |
|
||||
| `steamFrame.dashboard.steamCloseButton.enable` | bool | `false` | X button on the dashboard's Steam window, see [Steam close button](#steam-close-button-dashboardsteamclosebuttonenable). |
|
||||
| `steamFrame.dashboard.windowCurvature.enable` | bool | `false` | Adjustable curvature per window, see [Window curvature](#window-curvature-dashboardwindowcurvature). |
|
||||
| `steamFrame.dashboard.steamCloseButton.enable` | bool | `false` | X button on the dashboard's Steam window, see [Steam close button](docs/steam-close-button.md). |
|
||||
| `steamFrame.dashboard.windowCurvature.enable` | bool | `false` | Adjustable curvature per window, see [Window curvature](docs/window-curvature.md). |
|
||||
| `steamFrame.dashboard.windowCurvature.initial` | non-negative number | `1.0` | Curvature of curved world/hand windows without own value (1 = stock, 0 = flat). |
|
||||
| `steamFrame.dashboard.windowCurvature.max` | positive number | `3.0` | Largest curvature. |
|
||||
| `steamFrame.dashboard.windowCurvature.step` | positive number | `0.05` | Rounding step while dragging (at most `max`). |
|
||||
@@ -341,12 +271,12 @@ without it. No feature needs Developer Mode; keep it off (see
|
||||
| `steamFrame.dashboard.windowCurvature.dragPixelsPerUnit` | positive number (px) | `120` | Drag distance per 1.0 in the menu (6 px per 0.05 step). |
|
||||
| `steamFrame.dashboard.windowCurvature.barDragPixelsPerUnit` | positive number (px) | `60` | Drag distance per 1.0 on the bar button. |
|
||||
| `steamFrame.dashboard.windowCurvature.haptics` | bool | `true` | Controller haptics while dragging (steps, detents, edges); the dashboard's hover clicks are muted during a drag. |
|
||||
| `steamFrame.dashboard.frameControls.enable` | bool | `false` | Move window controls between bar and three-dot menu, see [Window control bar](#window-control-bar-dashboardframecontrols). |
|
||||
| `steamFrame.dashboard.frameControls.enable` | bool | `false` | Move window controls between bar and three-dot menu, see [Window control bar](docs/window-control-bar.md). |
|
||||
| `steamFrame.dashboard.frameControls.longPressMs` | int, 300-10000 (ms) | `1500` | Long-press duration. |
|
||||
| `steamFrame.dashboard.frameControls.inBar` | list of control names | `[ ]` | Controls that start in the bar: `keyboard`, `float`, `dashboard`, `theater`, `dockLeft`, `dockRight`, `close`, `curvature`, `"icon:<n>"`. |
|
||||
| `steamFrame.dashboard.frameControls.inMenu` | list of control names | `[ ]` | Controls that start in the three-dot menu. |
|
||||
| `steamFrame.dashboard.frameControls.floatInTheater` | bool | `false` | "Float" control on theater windows. |
|
||||
| `steamFrame.steamvrDebugger.enable` | bool | automatic | SteamVR dashboard DevTools on `127.0.0.1:8087` (set only while SteamVR runs); on when a dashboard patch is, see [SteamVR debugger](#steamvr-debugger-steamvrdebuggerenable). |
|
||||
| `steamFrame.steamvrDebugger.enable` | bool | automatic | SteamVR dashboard DevTools on `127.0.0.1:8087` (set only while SteamVR runs); on when a dashboard patch is, see [SteamVR debugger](docs/steamvr-debugger.md). |
|
||||
| `steamFrame.clipboardSync.enable` | bool | `true` | Clipboard bridge between the Steam session and the nested desktop. |
|
||||
| `steamFrame.clipboardSync.package` | package | built from `dnut/clipboard-sync` | The clipboard-sync package. |
|
||||
| `steamFrame.firefox.enable` | bool | `false` | Launcher for the Flathub Firefox Flatpak with the fixes below. |
|
||||
@@ -354,7 +284,7 @@ without it. No feature needs Developer Mode; keep it off (see
|
||||
| `steamFrame.firefox.disableAv1` | bool | `false` | Default `media.av1.enabled` to `false`: the Frame's decoder driver has no AV1, so sites send VP9/H.264, decoded in hardware. |
|
||||
| `steamFrame.firefox.prefs` | attrs of bool, int or str | `{ }` | Further `about:config` default values for every profile (override the fixes too). |
|
||||
| `steamFrame.firefox.desktopProfile` | null or str | `"desktop"` | Separate profile (directory name) for the nested desktop; `null`: the default profile in both sessions. |
|
||||
| `steamFrame.jellyfin.hardwareDecoding.enable` | bool | `false` | Hardware video decoding in the Jellyfin Desktop Flatpak, see [Jellyfin](#jellyfin-hardware-decoding-jellyfinhardwaredecoding). |
|
||||
| `steamFrame.jellyfin.hardwareDecoding.enable` | bool | `false` | Hardware video decoding in the Jellyfin Desktop Flatpak, see [Jellyfin](docs/jellyfin.md). |
|
||||
| `steamFrame.jellyfin.hardwareDecoding.hwdec` | str | `"v4l2m2m-copy,auto-copy"` | mpv `hwdec` used instead of Jellyfin's automatic one. |
|
||||
| `steamFrame.jellyfin.hardwareDecoding.command` | str, read-only | | The `flatpak run …` command line of the desktop entry, for a terminal. |
|
||||
| `steamFrame.cleanup.package` | package, read-only | | `steam-frame-nix-cleanup` (on `PATH` too), see [Changes outside Nix](#changes-outside-nix-exceptions). |
|
||||
@@ -376,496 +306,6 @@ Renamed options still work under their old names, with a warning:
|
||||
| `dashboard.windowCurvature.snapPixels`, `snapPoints` | `detentPixels`, `detentPoints` |
|
||||
| `dashboard.windowCurvature.dragThreshold` | `dragThresholdPixels` |
|
||||
|
||||
## Fixes in detail
|
||||
|
||||
### Session settings and background services (`session.nix`)
|
||||
|
||||
Used by the other modules; normally nothing to set.
|
||||
|
||||
- `session.runtimeDir` / `session.bus` point at the Steam session's runtime
|
||||
dir and bus (services, KDE wallet), which the nested desktop can't see;
|
||||
`session.busEnv` is a launcher prefix to reach them
|
||||
(`Exec=${config.steamFrame.session.busEnv} flatpak run …`, see
|
||||
[Usage](#usage)).
|
||||
- Switching from the nested desktop, Home Manager can't reach the service
|
||||
manager ("User systemd daemon not running") and skips `reloadSystemd`. So
|
||||
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).
|
||||
|
||||
### Portal (`session.portalFix`)
|
||||
|
||||
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.
|
||||
|
||||
**Fix:** 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`. The desktop's portal is unaffected.
|
||||
|
||||
**Remove when** SteamOS ships `gamescope-portals.conf`.
|
||||
|
||||
### Keyboard layout (`keyboard.layout`, `keyboard.variant`)
|
||||
|
||||
**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.
|
||||
|
||||
**Fix:** a drop-in on `gamescope-session.service` setting
|
||||
`XKB_DEFAULT_LAYOUT`/`VARIANT`; applies at the next Steam session start.
|
||||
|
||||
`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>`.
|
||||
|
||||
The layout also picks the [VR keyboard](#vr-keyboard-swipe-and-suggestions-keyboardvr)'s
|
||||
default dictionary language.
|
||||
|
||||
**Remove when** SteamOS applies a layout setting to gamescope.
|
||||
|
||||
### Steam keyboard patch (`keyboard.vr.extraKeys.enable`)
|
||||
|
||||
**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.
|
||||
- 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 (with `xdotool` on
|
||||
`:0`) 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 (see [After a Steam update](#after-a-steam-update)).
|
||||
Tested with Steam client 1790377368 (UI build 11041156).
|
||||
|
||||
How it works: [docs/keyboard.md](docs/keyboard.md#extra-keys).
|
||||
|
||||
**Remove when** Steam's VR keyboard gets these keys.
|
||||
|
||||
### VR keyboard: swipe and suggestions (`keyboard.vr.*`)
|
||||
|
||||
**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-steamvrdebuggerenable), turned on
|
||||
automatically.
|
||||
- **Haptics:** light ticks for drag steps and picks, a Snap at word detents.
|
||||
|
||||
**Dictionary** (`dictionary.*`): built from wordfreq frequency lists and
|
||||
Hunspell, both from nixpkgs. 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, see [Options](#options).
|
||||
|
||||
**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 [`keyboard.vr.extraKeys`](#steam-keyboard-patch-keyboardvrextrakeysenable)
|
||||
(with it, non-ASCII words are typed via its helper).
|
||||
|
||||
**Caveats:** depends on Steam/SteamVR UI internals; after an update that
|
||||
changes them the keyboard stays stock (see
|
||||
[After a Steam update](#after-a-steam-update)). Accented words of other
|
||||
languages are in the dictionary but only swipable where the layout has the
|
||||
letters.
|
||||
|
||||
How it works, tests, debugging: [docs/keyboard.md](docs/keyboard.md#swipe-and-suggestions).
|
||||
|
||||
**Remove when** Steam's VR keyboard gets swipe typing and suggestions.
|
||||
|
||||
### Launcher menu (`launcherMenu.*`)
|
||||
|
||||
**Problem:** the VR dashboard's "+" menu (non-Steam programs) 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):
|
||||
|
||||
- `sort`: programs sorted by name (case-insensitive), Desktop included.
|
||||
- `pinDesktop = "top"` / `"bottom"`: Desktop pinned above/below the list,
|
||||
always visible. **Limitation:** the pinned copy works with the laser but
|
||||
not with thumbstick / D-pad navigation.
|
||||
- `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. Only restyles Steam's items, so
|
||||
launching, sounds and controller navigation keep working. 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`: without Developer Mode Steam hides `konsole`,
|
||||
`systemsettings`, `dolphin`, `plasma-discover`, `vlc`, `firewall-config`,
|
||||
`cmake-gui`, `qrenderdoc`, `lxterminal` and `sh`; this lifts that filter
|
||||
only, so Developer Mode (sshd, xrdp, LAN DevTools forwards) can stay off.
|
||||
Hide single programs with [`hiddenApps`](#hidden-apps-launchermenuhiddenapps).
|
||||
|
||||
All revert when turned off (next switch). Tested with Steam client
|
||||
1790377368. How it works: [docs/launcher-menu.md](docs/launcher-menu.md).
|
||||
|
||||
#### Hidden apps (`launcherMenu.hiddenApps`)
|
||||
|
||||
**Problem:** with Developer Mode or `showAllApps`, the "+" menu lists every
|
||||
desktop entry, including system tools.
|
||||
|
||||
**Fix:** list desktop entry ids (no `.desktop`); a user entry with
|
||||
`Hidden=true` in `~/.local/share/applications` masks the system one (also in
|
||||
the KDE menu). The "+" menu always hides `steam` and `vrurlhandler`; for
|
||||
Konsole in VR use [`showAllApps`](#launcher-menu-launchermenu).
|
||||
|
||||
#### Icon fallbacks (`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.
|
||||
|
||||
**Fix:** links nixpkgs' Breeze SVGs of `utilities-terminal` and
|
||||
`preferences-system` (plus `extra`) into
|
||||
`~/.local/share/icons/hicolor/scalable/apps/`; a name Breeze doesn't have
|
||||
fails the build, `enable = false` provides none. 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.
|
||||
|
||||
`iconFallbacks` used to be a list; a list now fails with a hint (use
|
||||
`extra`, or `enable = false` for `[ ]`). How it works and migration from the
|
||||
2026-09 script: [docs/launcher-menu.md](docs/launcher-menu.md#icon-fallbacks).
|
||||
|
||||
### SteamVR dashboard patches
|
||||
|
||||
The next four features patch SteamVR's dashboard while it runs. Each turns
|
||||
on the [SteamVR debugger](#steamvr-debugger-steamvrdebuggerenable)
|
||||
(**the first time, restart SteamVR once**). They depend on SteamVR UI
|
||||
internals: 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.
|
||||
How they work and debugging: [docs/dashboard.md](docs/dashboard.md).
|
||||
|
||||
### Dashboard windows (`dashboard.windows.*`)
|
||||
|
||||
**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.
|
||||
|
||||
**Fix:** raises these 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.
|
||||
|
||||
```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;
|
||||
};
|
||||
```
|
||||
|
||||
**Caveat:** if SteamVR changes its stock grab distances, the distance options
|
||||
silently do nothing.
|
||||
|
||||
### Steam close button (`dashboard.steamCloseButton.enable`)
|
||||
|
||||
**Problem:** every dashboard window has a close (X) button except Steam's
|
||||
own, and with no other window open the dashboard always shows it.
|
||||
|
||||
**Fix:** gives the Steam window 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](#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.
|
||||
|
||||
**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.
|
||||
|
||||
### Window curvature (`dashboard.windowCurvature.*`)
|
||||
|
||||
**Problem:** SteamVR dashboard windows are either curved (fixed radius) or
|
||||
flat, and world windows start flat.
|
||||
|
||||
**Fix:** turns the "Toggle Curvature" row of a window's three-dot menu into
|
||||
a control showing the window's value; the same control in the bottom bar
|
||||
(see [window control bar](#window-control-bar-dashboardframecontrols)) 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.
|
||||
|
||||
**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.
|
||||
|
||||
### Window control bar (`dashboard.frameControls.*`)
|
||||
|
||||
**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".
|
||||
|
||||
**Fix:**
|
||||
|
||||
- **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](#changes-outside-nix-exceptions)).
|
||||
- `inBar` / `inMenu` set where controls start; a popup choice wins until
|
||||
that control's entry changes.
|
||||
- `floatInTheater` gives theater windows the "Float" control.
|
||||
|
||||
```nix
|
||||
steamFrame.dashboard.frameControls = {
|
||||
enable = true;
|
||||
# longPressMs = 1500;
|
||||
# inBar = [ "curvature" ]; inMenu = [ "theater" ];
|
||||
# floatInTheater = true;
|
||||
};
|
||||
```
|
||||
|
||||
With [window curvature](#window-curvature-dashboardwindowcurvature), 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.
|
||||
|
||||
**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.
|
||||
|
||||
### SteamVR debugger (`steamvrDebugger.enable`)
|
||||
|
||||
Dashboard patches (and the VR keyboard's strip below/above the keyboard)
|
||||
need SteamVR's DevTools port, which SteamVR opens only with its setting
|
||||
`VRWebHelper/DebuggerEnabled`. It is enabled automatically when any patch in
|
||||
`steamFrame.uiPatches.patches` uses port 8087; nothing to set.
|
||||
|
||||
The setting lives in `~/.config/openvr/config/steamvr.vrsettings`, which
|
||||
SteamVR rewrites, so it can't be a Nix link. It 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](#devtools-on-the-lan)).
|
||||
|
||||
How it works: [docs/steamvr-debugger.md](docs/steamvr-debugger.md).
|
||||
|
||||
### Clipboard sync (`clipboardSync.enable`)
|
||||
|
||||
On by default.
|
||||
|
||||
**Problem:** the Steam session's X displays and the nested desktop have
|
||||
separate clipboards.
|
||||
|
||||
**Fix:** [clipboard-sync](https://github.com/dnut/clipboard-sync), built from
|
||||
source (its flake is x86-only; `clipboardSync.package` to replace it),
|
||||
started via KDE autostart (the desktop can't reach the user manager, and
|
||||
`:2` must exist first). Each switch restarts it if outdated, so switch from
|
||||
a desktop terminal.
|
||||
|
||||
### Firefox (`firefox.*`)
|
||||
|
||||
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): gamescope never shows fullscreen windows, so
|
||||
Firefox looks frozen. `full-screen-api.ignore-widgets` makes fullscreen
|
||||
fill just the window. Not applied in the desktop profile. **Remove when**
|
||||
gamescope shows fullscreen X11 windows in VR.
|
||||
- **`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. **Remove
|
||||
when** a SteamOS kernel adds AV1 to `iris`.
|
||||
- **`prefs`:** further `about:config` values for every profile; they can
|
||||
also override the fixes above.
|
||||
- **`desktopProfile`** (`"desktop"`): the sessions can't see each other's
|
||||
Firefox, so a second instance stops at the locked profile; 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. 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.
|
||||
|
||||
How it works: [docs/firefox.md](docs/firefox.md).
|
||||
|
||||
### Jellyfin hardware decoding (`jellyfin.hardwareDecoding.*`)
|
||||
|
||||
For the Flathub [Jellyfin Desktop](https://github.com/jellyfin/jellyfin-desktop)
|
||||
Flatpak (`org.jellyfin.JellyfinDesktop`), which plays video with libmpv.
|
||||
|
||||
**Problem:** 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 mpv never tries (Jellyfin hard-sets `hwdec=auto-copy`, whose
|
||||
probing leaves out V4L2 M2M, and has no way to pass mpv options).
|
||||
|
||||
**Fix:** 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. Nothing is written to
|
||||
Flatpak's overrides: it applies to launches from that entry and is gone with
|
||||
it. Changes take effect at the next start of Jellyfin.
|
||||
|
||||
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" ];
|
||||
```
|
||||
|
||||
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: [docs/jellyfin.md](docs/jellyfin.md).
|
||||
|
||||
## UI patches (`uiPatches.patches`)
|
||||
|
||||
Steam's UI and SteamVR's dashboard are web pages; the launcher menu,
|
||||
dashboard and VR keyboard features patch them while they run, over their
|
||||
local DevTools ports. Steam's files are never modified, and 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`.
|
||||
|
||||
You can add your own patches with `steamFrame.uiPatches.patches`; see
|
||||
[docs/ui-patches.md](docs/ui-patches.md) (patch definition, persistent state,
|
||||
finders and signatures, `mkPatch`, shared hooks).
|
||||
|
||||
### 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.
|
||||
|
||||
### After a Steam update
|
||||
|
||||
The patches find Steam's code by signature. If a Steam or SteamVR update
|
||||
changes it so a signature no longer matches exactly once, that patch changes
|
||||
nothing: the feature stays stock and the journal (above) 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. Checking and fixing signatures:
|
||||
[docs/ui-patches.md](docs/ui-patches.md#after-a-steam-update).
|
||||
|
||||
## Changes outside Nix (exceptions)
|
||||
|
||||
Everything not listed here is a Home Manager link into the Nix store or
|
||||
@@ -873,29 +313,26 @@ lives in memory (the UI patches). These are written at runtime:
|
||||
|
||||
| Path | Feature | Lifetime | Removed by |
|
||||
|---|---|---|---|
|
||||
| `VRWebHelper.DebuggerEnabled` in `~/.config/openvr/config/steamvr.vrsettings` | [SteamVR debugger](#steamvr-debugger-steamvrdebuggerenable) | only while SteamVR runs | SteamVR stopping (runtime drop-in below); `steam-frame-nix-cleanup` while SteamVR is stopped |
|
||||
| `VRWebHelper.DebuggerEnabled` in `~/.config/openvr/config/steamvr.vrsettings` | [SteamVR debugger](docs/steamvr-debugger.md) | only while SteamVR runs | SteamVR stopping (runtime drop-in below); `steam-frame-nix-cleanup` while SteamVR is stopped |
|
||||
| `~/.local/state/steam-frame-nix/steamvr-debugger.armed` | SteamVR debugger: the key's previous value | while SteamVR runs; after a power loss until the next SteamVR start or cleanup | SteamVR stopping; `steam-frame-nix-cleanup` |
|
||||
| `/run/user/1000/systemd/user/steamvr.service.d/50-steam-frame-nix-debugger.conf`, `/run/user/1000/steam-frame-nix/steamvr-debugger-restore` | SteamVR debugger: puts the key back when SteamVR stops, without Nix | until reboot (tmpfs) | reboot; `steam-frame-nix-cleanup` while SteamVR is stopped and the debugger is off |
|
||||
| `~/.local/state/steam-frame-nix/ui-patches/<name>.json` | Saved choices of dashboard patches: window control bar placements (`frame-controls`), "Steam hidden" (`steam-close-button`). SteamOS's `steamvr.service` deletes `~/.cache/SteamVR` (the dashboard's own browser storage) on every SteamVR start. | until removed: kept when a patch is disabled (the choices come back when you enable it again) | `steam-frame-nix-cleanup --all`, `install.sh uninstall` |
|
||||
| mtime of `~/.local/share/icons/hicolor` | [icon fallbacks](#icon-fallbacks-launchermenuiconfallbacks): a running Steam rescans icons | only the directory's timestamp | nothing to remove |
|
||||
| `~/.local/state/steam-frame-nix/ui-patches/<name>.json` | Saved choices of dashboard patches ([persistent state](docs/ui-patches.md#persistent-state)): window control bar placements (`frame-controls`), "Steam hidden" (`steam-close-button`). SteamOS's `steamvr.service` deletes `~/.cache/SteamVR` (the dashboard's own browser storage) on every SteamVR start. | until removed: kept when a patch is disabled (the choices come back when you enable it again) | `steam-frame-nix-cleanup --all`, `install.sh uninstall` |
|
||||
| mtime of `~/.local/share/icons/hicolor` | [icon fallbacks](docs/launcher-menu.md#icon-fallbacks): a running Steam rescans icons | only the directory's timestamp | nothing to remove |
|
||||
|
||||
**`steam-frame-nix-cleanup`** (`install.sh cleanup`,
|
||||
`steamFrame.cleanup.package`) knows everything any version of
|
||||
steam-frame-nix wrote outside the store, removes only what is provably its
|
||||
own (everything else is reported as "left alone") and can be run again
|
||||
safely; `--dry-run` shows what it would do.
|
||||
safely; `--dry-run` shows what it would do. How it decides and what older
|
||||
versions left: [docs/cleanup.md](docs/cleanup.md).
|
||||
|
||||
- On every switch, `cleanup --orphans` removes what the configuration no
|
||||
longer uses (never the saved patch state).
|
||||
- `steam-frame-nix-cleanup --all` removes everything, also the saved patch
|
||||
state. SteamVR's key can't be changed while SteamVR runs: it is then left
|
||||
to the runtime drop-in (restored when SteamVR stops).
|
||||
- Without Nix or after a rollback it runs from the script
|
||||
(bash, coreutils, findutils, jq, all in SteamOS' `/usr/bin`):
|
||||
- Without Nix or after a rollback it runs from the script:
|
||||
`curl -fsSL https://steam-frame-nix.lhns.de | bash -s -- cleanup --all`.
|
||||
- It also removes what older versions left (icon fallback links, Firefox
|
||||
`user.js` files and their values, Jellyfin Flatpak override entries, ...;
|
||||
list: [docs/changes-outside-nix.md](docs/changes-outside-nix.md#left-by-older-versions)).
|
||||
|
||||
### Only while running
|
||||
|
||||
@@ -903,18 +340,30 @@ safely; `--dry-run` shows what it would do.
|
||||
memory; stopping `steam-ui-patches` / `steam-keyboard-patch` reverts them.
|
||||
- clipboard-sync runs from KDE autostart (a Home Manager link).
|
||||
- Firefox: the desktop profile's `user.js` link exists only while its
|
||||
Firefox runs (see [docs/firefox.md](docs/firefox.md#desktop-profile)).
|
||||
Firefox runs (see [Firefox](docs/firefox.md#how-it-works)).
|
||||
- Jellyfin: the hardware decoding permissions are `flatpak run` options of
|
||||
the desktop entry, not a Flatpak override.
|
||||
|
||||
### Set up by install.sh
|
||||
|
||||
`install.sh install` (the bootstrap, not the modules) also installs Nix
|
||||
(`/nix`, files in `/etc`), `~/nix-config` with the link
|
||||
`~/.config/home-manager`, Nix's and Home Manager's per-user state, and
|
||||
renames conflicting dotfiles to `*.hm-backup-<time>`; `install.sh uninstall`
|
||||
undoes it (full list:
|
||||
[docs/changes-outside-nix.md](docs/changes-outside-nix.md#set-up-by-installsh)).
|
||||
`install.sh install` (the bootstrap, not the modules) also changes these, 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`), its receipt `/nix/receipt.json`; the read-only root is
|
||||
unlocked only while it installs or uninstalls;
|
||||
- `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 that exists or `--flake` is given;
|
||||
uninstall removes the link, never the configuration;
|
||||
- dotfiles in Home Manager's way, renamed to `*.hm-backup-<time>` (kept);
|
||||
- `~/.local/state/home-manager`, `~/.local/state/nix` (profiles,
|
||||
generations), `~/.nix-profile`, `~/.nix-defexpr`, `~/.nix-channels`,
|
||||
`~/.cache/nix`.
|
||||
|
||||
### App data you create
|
||||
|
||||
|
||||
@@ -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.
|
||||
@@ -20,7 +20,7 @@
|
||||
# trace after a power loss) is resolved by the next start or
|
||||
# steam-frame-nix-cleanup.
|
||||
# Off: no unit and no drop-in; cleanup restores the key once SteamVR is
|
||||
# stopped. Developer Mode forwards the port to 0.0.0.0:8088 (README,
|
||||
# stopped. Developer Mode forwards the port to 0.0.0.0:8088 (docs/ui-patches.md,
|
||||
# "DevTools on the LAN"); our patches use 127.0.0.1 only.
|
||||
{ config, lib, pkgs, ... }:
|
||||
let
|
||||
|
||||
Reference in new issue
Block a user