Add dashboard-windows (resize/grab-distance limits) and steamvr-debugger

dashboard-windows: steamFrame.dashboard.windowMaxScale and
windowDistance.{world,theater,dashboard}.{min,max}, one runtime patch of the
SteamVR dashboard (systemui, 127.0.0.1:8087) that rewrites
frame-resize-scale-max and grab min/max-distance in the scene graph sent to
vrcompositor, then asks for one resend so changes apply immediately.
Registered only when an option is set. The mailbox class and the
scene-graph scheduler are found by signature (mkPatch, "dashboard-windows"
in lib/signatures.json, bundle vrwebui-systemui), so
scripts/check-signatures.mjs checks them offline too.

steamvr-debugger: steamFrame.steamvrDebugger.enable, defaulting to true when
a UI patch uses port 8087; merges VRWebHelper.DebuggerEnabled into
steamvr.vrsettings before every SteamVR start, and reverts it (only if it
set it) when turned off.
This commit is contained in:
Pierre Kisters committed 2026-09-27 20:09:47 +02:00
1 parent 6a8924f219
commit 9f5538003f
8 files changed
+480 -6

No files matched your search

+94 -6
View File
@@ -3,8 +3,8 @@
[Home Manager](https://github.com/nix-community/home-manager) modules for the
Valve Steam Frame: SteamOS on `aarch64-linux`, standalone home-manager on a
non-NixOS system. They work around quirks of the Frame's two graphical
sessions (portal config, keyboard layout, VR keyboard, "+" menu, clipboard,
Firefox)
sessions (portal config, keyboard layout, VR keyboard, "+" menu, VR
dashboard windows, clipboard, Firefox)
declaratively, so every change can be reverted by activating an older
home-manager generation.
@@ -133,6 +133,10 @@ the two files below).
closeOnLaunch = true;
launchDebounce = 10;
};
dashboard = {
windowMaxScale = 4.0;
windowDistance.world.max = 10.0;
};
firefox.enable = true;
# Only relevant with Steam Developer Mode on (see hidden-apps below).
hiddenApps = [ "lxterminal" "cmake-gui" "firewall-config" "renderdoc" ];
@@ -156,7 +160,7 @@ home-manager switch --flake .#steamos
```
Individual modules are available as
`homeManagerModules.{session,portal,keyboard-layout,steam-keyboard-patch,hidden-apps,steam-ui-patches,launcher-menu,clipboard-sync,firefox}`;
`homeManagerModules.{session,portal,keyboard-layout,steam-keyboard-patch,hidden-apps,steam-ui-patches,launcher-menu,steamvr-debugger,dashboard-windows,clipboard-sync,firefox}`;
`default` imports all of them.
**Steam Developer Mode** (a Steam setting, not managed here) makes the "+"
@@ -182,6 +186,9 @@ menu list every desktop entry, including terminals such as Konsole.
| `steamFrame.launcherMenu.pinDesktop` | null or `"top"` / `"bottom"` | `null` | Pin "Desktop" above or below the "+" menu's scrolling list (always visible). `null`: a normal list entry. |
| `steamFrame.launcherMenu.closeOnLaunch` | bool | `false` | Close the "+" menu as soon as a program in it is clicked. |
| `steamFrame.launcherMenu.launchDebounce` | unsigned int (seconds) | `0` | Ignore repeated launches of the same program from the "+" menu within this time. `0`: off. |
| `steamFrame.dashboard.windowMaxScale` | null or number | `null` | Largest resize-handle scale of SteamVR dashboard windows, relative to their default size. `null`: stock (2). See [Dashboard windows](#dashboard-windows-dashboard). |
| `steamFrame.dashboard.windowDistance.{world,theater,dashboard}.{min,max}` | null or number (m) | `null` | How close / far grabbed windows can be pulled in / pushed back. `null`: stock (world 0.25-5, theater 1-6, dashboard 0.3-4 m). |
| `steamFrame.steamvrDebugger.enable` | bool | `true` if a UI patch uses port 8087, else `false` | SteamVR dashboard DevTools on `127.0.0.1:8087` (`VRWebHelper/DebuggerEnabled`), needed by dashboard patches. See [SteamVR debugger](#steamvr-debugger-steamvrdebuggerenable). |
| `steamFrame.hiddenApps` | list of str | `[ ]` | Desktop entry ids (without `.desktop`) to hide from the "+" and KDE menus. |
| `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. |
@@ -306,7 +313,7 @@ Steam's client UI (and SteamVR's dashboard, `vrwebhelper`) are web pages in
CEF with a local DevTools port: `127.0.0.1:8080` for Steam (SteamOS starts
it with `-cef-enable-debugging`), `127.0.0.1:8087` for SteamVR when its
debugger is enabled (`VRWebHelper/DebuggerEnabled` in
`steamvr.vrsettings`). The `steam-ui-patches` user service (`injector.mjs`,
`steamvr.vrsettings`, see [SteamVR debugger](#steamvr-debugger-steamvrdebuggerenable)). The `steam-ui-patches` user service (`injector.mjs`,
Node) uses them to patch the running UI; Steam's files are never modified.
Each entry of `steamFrame.uiPatches.patches`:
@@ -423,7 +430,9 @@ nix shell nixpkgs#nodejs -c node scripts/check-signatures.mjs
```
It reads the bundles Steam's UI page loads (`~/.local/share/Steam/steamui`:
the scripts of `index.html` and every chunk its webpack runtime can load),
the scripts of `index.html` and every chunk its webpack runtime can load; for
[Dashboard windows](#dashboard-windows-dashboard) also SteamVR's dashboard,
`/opt/steamvr/resources/webinterface/dashboard/systemui.html`),
extracts all webpack module factories and evaluates every signature of
`modules/lib/signatures.json` against them with the same finder code the
patches use (exports are checked by running the matched module in an inert
@@ -445,7 +454,7 @@ If something is missing, look at the module that used to match (the new
bundle's module sources: `scripts/webpack-modules.mjs`) and adjust the
signature in `signatures.json` (bump the patch's `VERSION` if its code
changes). Options: `--signatures FILE` adds your own signatures (same
format; the bundle `vrwebui-systemui` is SteamVR's dashboard),
format; bundles `steamui` and `vrwebui-systemui`, SteamVR's dashboard),
`--dir steamui=DIR` checks another copy, `--patch NAME`, `--strict` (fail on
warnings), `--json`. Live, `journalctl --user -u steam-keyboard-patch -u
steam-ui-patches` shows what each patch reported.
@@ -492,6 +501,85 @@ patches use Steam APIs (`SteamClient.Apps`) and React props rather than
webpack modules; the offline checker verifies those anchors too.
Tested with Steam client 1790377368.
### Dashboard windows (`dashboard.*`)
**Problem:** SteamVR dashboard windows (Steam, app windows, overlays, the
theater screen) can only be enlarged to twice their default size, and
grabbed windows can only be pushed back to 5 m (6 m in theater mode), too
close for a big virtual screen.
**Fix:** a UI patch (see above) of SteamVR's dashboard page (`vrwebhelper`,
`127.0.0.1:8087`, title `systemui`); it turns on the
[SteamVR debugger](#steamvr-debugger-steamvrdebuggerenable). The dashboard
describes its windows to `vrcompositor` as a scene graph
(`update_scene_graph` messages over its mailbox WebSocket), and the
compositor enforces the limits it finds there. They are constants in the
dashboard's JS, so the patch wraps the mailbox's `SendMessage` and rewrites
them in every outgoing scene graph:
| Option | Scene-graph property | Stock |
|---|---|---|
| `windowMaxScale` | `frame-resize-scale-max` of every window frame | 2 (range 0.25-2, relative to the window's default size; the theater screen's default is 2.8x larger) |
| `windowDistance.world.{min,max}` | `min-distance` / `max-distance` of `grab-scale` nodes: windows placed in the world | 0.25-5 m |
| `windowDistance.theater.{min,max}` | same, `grab-transform` of the theater screen | 1-6 m |
| `windowDistance.dashboard.{min,max}` | same, `grab-transform` of the dashboard itself | 0.3-4 m |
| (not patched) | `grab-transform` of the keyboard | 0.2-1 m |
Distances are how close / far a grabbed window can be pulled in / pushed
back (thumbstick or scroll while dragging). `null` (default) keeps the stock
value; unset `min`/`max` of a set range stay stock. The patch is only
registered when at least one option is set.
After patching, the dashboard is asked to send its (unchanged) scene graph
once more, so changes take effect immediately, without touching any window;
turning the options off reverts to stock the same way (next switch).
```nix
steamFrame.dashboard = {
windowMaxScale = 4.0; # resize up to 4x (theater: 11.2x)
windowDistance.world.max = 10.0; # push windows back up to 10 m
windowDistance.theater.max = 12.0;
};
```
**Caveats:** depends on SteamVR UI internals: the mailbox class and the
scene-graph scheduler are found by signature (`dashboard-windows` in
`modules/lib/signatures.json`, checked offline by
`scripts/check-signatures.mjs` after a SteamVR update); if one doesn't match,
the patch reports it and leaves the dashboard stock. Grab nodes are recognized by their type *and* their
exact stock values, so if a SteamVR update changes those, the distance
options silently do nothing (the scale option still applies). The patch's
state, including counters of rewritten nodes per kind, is
`window.__sfuiDashboardWindows` in the `systemui` page (DevTools).
### SteamVR debugger (`steamvrDebugger.enable`)
Patches of the SteamVR dashboard need its DevTools port, which SteamVR only
opens with the setting `VRWebHelper/DebuggerEnabled` (port
`VRWebHelper/DebuggerPort`, default 8087). It is enabled automatically
(`mkDefault`) as soon as a patch in `steamFrame.uiPatches.patches` uses port
8087 (e.g. [Dashboard windows](#dashboard-windows-dashboard)).
SteamVR rewrites `~/.config/openvr/config/steamvr.vrsettings` while it runs
and on exit, so the file can't be managed by Home Manager, and edits while it
runs are lost. Instead the `steamvr-webhelper-debugger` oneshot merges just
that key (with `jq`, other settings untouched) before every SteamVR start (a
drop-in on SteamOS's `steamvr.service` makes it Want/After the oneshot).
SteamVR reads the setting only at startup: **the first time, restart SteamVR
once** (e.g. reboot) before dashboard patches work; until then the
`steam-ui-patches` service keeps polling `127.0.0.1:8087`.
Turning it off sets the key back to `false` at the next SteamVR start, but
only if this module set it (marker in
`~/.local/state/steam-frame-nix/`); a setting you made yourself is left
alone.
**Security:** SteamOS's `steamvr-web-debug-portforward.service` forwards
`0.0.0.0:8088` to this port, so with the debugger on, anyone on the same
network could run code in the SteamVR dashboard. Masking it is recommended
(see "DevTools on the LAN" in [UI patches](#ui-patches-uipatchespatches));
the patches only use `127.0.0.1`.
### Hidden apps (`hiddenApps`)
**Problem:** with Steam Developer Mode on, the "+" menu lists every desktop
+2
View File
@@ -19,6 +19,8 @@
hidden-apps = ./modules/hidden-apps.nix;
steam-ui-patches = ./modules/steam-ui-patches.nix;
launcher-menu = ./modules/launcher-menu.nix;
steamvr-debugger = ./modules/steamvr-debugger.nix;
dashboard-windows = ./modules/dashboard-windows.nix;
# Built with the consumer's pkgs; only the source comes from our input.
# The key lets the module system deduplicate it when it is imported both
# directly and through `default`.
+101
View File
@@ -0,0 +1,101 @@
# Resize and grab-distance limits of SteamVR dashboard windows (Steam, app
# windows, overlays, the theater screen, the dashboard itself), through a
# runtime patch of SteamVR's dashboard page (vrwebhelper "systemui", DevTools
# 127.0.0.1:8087; see dashboard-windows/patch.js for how it works). It is
# registered with steam-ui-patches.nix only when an option is set, and turns
# on the SteamVR web helper debugger (steamvr-debugger.nix), which needs one
# SteamVR restart the first time. Changes apply immediately (the dashboard
# resends its scene graph); unsetting all options reverts to stock.
# Depends on SteamVR UI internals, found by signature (lib/signatures.json,
# "dashboard-windows"; scripts/check-signatures.mjs checks them offline).
{ config, lib, pkgs, ... }:
let
cfg = config.steamFrame.dashboard;
inherit (lib) mkOption types;
inherit (import ./lib { inherit pkgs; }) mkPatch;
# Stock grab distance ranges (meters), matched by the patch.
stockDistance = {
world = { min = 0.25; max = 5; };
theater = { min = 1; max = 6; };
dashboard = { min = 0.3; max = 4; };
};
rangeOption = kind: what: {
min = mkOption {
type = types.nullOr types.number;
default = null;
description = ''
Closest distance in meters ${what} can be pulled in to while grabbed.
null = stock (${toString stockDistance.${kind}.min} m).
'';
};
max = mkOption {
type = types.nullOr types.number;
default = null;
example = stockDistance.${kind}.max * 2;
description = ''
Farthest distance in meters ${what} can be pushed back to while
grabbed. null = stock (${toString stockDistance.${kind}.max} m).
'';
};
};
effective = kind: {
min = if cfg.windowDistance.${kind}.min != null then cfg.windowDistance.${kind}.min else stockDistance.${kind}.min;
max = if cfg.windowDistance.${kind}.max != null then cfg.windowDistance.${kind}.max else stockDistance.${kind}.max;
};
distanceSet = lib.any (r: r.min != null || r.max != null) (lib.attrValues cfg.windowDistance);
enabled = cfg.windowMaxScale != null || distanceSet;
in {
imports = [ ./steam-ui-patches.nix ./steamvr-debugger.nix ];
options.steamFrame.dashboard = {
windowMaxScale = mkOption {
type = types.nullOr types.number;
default = null;
example = 4.0;
description = ''
Largest size, relative to their default size, that SteamVR dashboard
windows (Steam, app windows, overlays, the theater screen) can be
enlarged to with the resize handle. null = stock (2; the smallest is
0.25). The theater screen's default size is 2.8x that of a normal
window, so its limit is 2.8x this value. Runtime patch of the SteamVR
dashboard (steamFrame.uiPatches), applied immediately.
'';
};
windowDistance = {
world = rangeOption "world" "windows placed in the world (not attached to the dashboard)";
theater = rangeOption "theater" "the theater-mode screen";
dashboard = rangeOption "dashboard" "the dashboard itself";
};
};
config = lib.mkIf enabled {
assertions =
[ {
assertion = cfg.windowMaxScale == null || cfg.windowMaxScale > 0;
message = "steamFrame.dashboard.windowMaxScale must be positive.";
} ]
++ map (kind: let r = effective kind; in {
assertion = r.min > 0 && r.max >= r.min;
message = "steamFrame.dashboard.windowDistance.${kind}: need 0 < min <= max (stock values fill in unset ones), got ${toString r.min}-${toString r.max}.";
}) (lib.attrNames stockDistance);
steamFrame.uiPatches.patches = [ {
name = "dashboard-windows";
endpoint = "http://127.0.0.1:8087";
target.title = "systemui";
patch = mkPatch {
name = "dashboard-windows";
src = ./dashboard-windows/patch.js;
opts = {
maxScale = cfg.windowMaxScale;
distance = cfg.windowDistance;
};
};
unpatch = ./dashboard-windows/unpatch.js;
} ];
};
}
+125
View File
@@ -0,0 +1,125 @@
// dashboard-windows: resize and grab-distance limits of SteamVR dashboard
// windows (Steam, app windows, overlays, the theater screen, the dashboard
// itself). Target: SteamVR's dashboard page (vrwebhelper, DevTools
// 127.0.0.1:8087, title "systemui").
//
// This file is a function expression, called by the file lib/default.nix
// (mkPatch) generates: (<this file>)(find, sigs, opts), with find the finder
// library (lib/finders.js), sigs this patch's module signatures
// (lib/signatures.json, "dashboard-windows") and opts the options from
// dashboard-windows.nix (null = stock), e.g.
// { maxScale: 4.0,
// distance: { world: {min: null, max: 10}, theater: {...}, dashboard: {...} } }
//
// How it works: systemui builds a scene graph from its DOM and sends it to
// vrcompositor through its mailbox WebSocket,
// mailbox.SendMessage("vrcompositor_systemlayer", {type: "update_scene_graph", scene_graph})
// and vrcompositor enforces the limits it finds there:
// - "frame-resize-scale-min"/"-max" on every window frame (stock 0.25 / 2,
// relative to the window's default size; the theater screen's default is
// 2.8x larger): range of the resize handle;
// - "min-distance"/"max-distance" (meters) on grab nodes: how close / far a
// grabbed window can be pulled in / pushed back (thumbstick / scroll):
// world "grab-scale" 0.25 / 5 windows placed in the world
// theater "grab-transform" 1 / 6 the theater screen
// dashboard "grab-transform" 0.3 / 4 the dashboard itself
// (the keyboard's grab-transform, 0.2 / 1, is left alone).
// These are constants/literals in systemui's bundle (not patchable at the
// source), so this patch wraps the mailbox class's SendMessage (on its
// prototype, which existing instances use) and rewrites them in outgoing
// scene graphs (a fresh object per update, so editing it in place is safe).
// Grab nodes are identified by node type plus their exact stock values: after
// a SteamVR update that changes them, the distance rewrite is a no-op.
// Then systemui is asked for one scene-graph resend (its own debounced
// scheduler: rebuilds the unchanged graph, no visible UI change), so new
// limits apply immediately.
//
// State: window.__sfuiDashboardWindows (mailbox prototype, resend function,
// the active rewrite, per-kind rewrite counters in .hits). The wrapper
// (__sfuiPatch = NAME, original in __sfuiOrig) looks the rewrite up per
// call; unpatch.js restores the original and resends the stock graph.
// The mailbox class and the scheduler are found by signature, not by
// webpack module id or minified export name; if one doesn't match, the patch
// returns an error and changes nothing. Idempotent: same VERSION and options
// -> "unchanged"; otherwise the rewrite is replaced in place.
((find, sigs, opts) => {
const NAME = 'dashboard-windows';
const VERSION = 2;
const GRAB = {
world: { type: 'grab-scale', min: 0.25, max: 5 },
theater: { type: 'grab-transform', min: 1, max: 6 },
dashboard: { type: 'grab-transform', min: 0.3, max: 4 },
};
const maxScale = opts?.maxScale ?? null;
if (maxScale !== null && !(maxScale > 0)) return `invalid maxScale ${maxScale}`;
const rules = [];
for (const [kind, stock] of Object.entries(GRAB)) {
const o = opts?.distance?.[kind] ?? {};
const min = o.min ?? stock.min, max = o.max ?? stock.max;
if (!(min > 0 && max >= min)) return `invalid ${kind} distance range ${min}-${max}`;
if (min !== stock.min || max !== stock.max) rules.push({ kind, ...stock, newMin: min, newMax: max });
}
const id = JSON.stringify([VERSION, maxScale, rules]);
let st = window.__sfuiDashboardWindows;
if (!st) {
let mods;
try {
mods = find.resolveAll(find.getWebpackRequire('webpackChunkvrwebui'), sigs);
} catch (e) {
return `signature not found, dashboard left unpatched: ${e.message}`;
}
// Mailbox: SendMessage(target, msg) -> WebSocketSend("mailbox_send ...").
// resend: debounced "send the scene graph again" (0 args, NextSGID(), setTimeout).
st = window.__sfuiDashboardWindows = {
proto: mods.mailbox.exports.Mailbox.prototype, resend: mods.sceneGraph.exports.resend,
id: null, rewrite: null, hits: {},
};
}
const cur = st.proto.SendMessage;
const installed = cur.__sfuiPatch === NAME && cur.__sfuiVersion === VERSION;
if (installed && st.id === id) return 'unchanged';
if (!installed) {
const orig = cur.__sfuiPatch === NAME ? cur.__sfuiOrig : cur;
const f = function SendMessage(target, msg, ...rest) {
const rewrite = window.__sfuiDashboardWindows?.rewrite;
if (rewrite && msg?.type === 'update_scene_graph') {
try { rewrite(msg.scene_graph); } catch { /* never break the dashboard */ }
}
return orig.call(this, target, msg, ...rest);
};
Object.assign(f, { __sfuiPatch: NAME, __sfuiVersion: VERSION, __sfuiOrig: orig });
st.proto.SendMessage = f;
}
const hits = st.hits = {};
const hit = (k) => { hits[k] = (hits[k] ?? 0) + 1; };
const walk = (n) => {
if (!n || typeof n !== 'object') return;
if (Array.isArray(n)) { n.forEach(walk); return; }
const p = n.properties;
if (p) {
if (maxScale !== null && typeof p['frame-resize-scale-max'] === 'number') {
// Never below the frame's own minimum.
p['frame-resize-scale-max'] = Math.max(maxScale, p['frame-resize-scale-min'] ?? 0);
hit('scale');
}
if (typeof p['max-distance'] === 'number') {
const r = rules.find((r) => r.type === n.type && r.min === p['min-distance'] && r.max === p['max-distance']);
if (r) { p['min-distance'] = r.newMin; p['max-distance'] = r.newMax; hit(r.kind); }
}
}
if (n.children) walk(n.children);
};
st.id = id;
st.rewrite = maxScale === null && rules.length === 0 ? null : walk;
try { st.resend(); } catch { /* applies with the next scene-graph update */ }
const desc = [
...(maxScale !== null ? [`max scale ${maxScale}`] : []),
...rules.map((r) => `${r.kind} ${r.newMin}-${r.newMax} m`),
].join(', ') || 'all stock';
return `patched (${desc})`;
})
+15
View File
@@ -0,0 +1,15 @@
// Reverts dashboard-windows/patch.js: restores the mailbox SendMessage and
// resends the scene graph, so vrcompositor gets the stock limits again right
// away. If something else has wrapped SendMessage since, our wrapper stays in
// its chain but is inert (it looks the rewrite up in the removed state).
// Safe when not patched.
(() => {
const NAME = 'dashboard-windows';
const st = window.__sfuiDashboardWindows;
if (!st) return 'not patched';
delete window.__sfuiDashboardWindows;
const f = st.proto.SendMessage;
if (f?.__sfuiPatch === NAME) st.proto.SendMessage = f.__sfuiOrig;
try { st.resend?.(); } catch { /* ignore */ }
return 'unpatched';
})()
+26
View File
@@ -99,6 +99,32 @@
"module": { "includes": ["\"valve.steam.gamepadui.barpopup\""] }
}
}
},
"dashboard-windows": {
"bundle": "vrwebui-systemui",
"modules": {
"mailbox": {
"module": { "includes": ["\"mailbox_send \"", "WebSocketSend(", "\"vrcompositor_systemlayer\""] },
"exports": {
"Mailbox": { "type": "class", "protoMethods": ["SendMessage", "WebSocketSend"] }
}
},
"sceneGraph": {
"module": { "includes": ["\"update_scene_graph\"", "NextSGID()", "scene_graph:"] },
"exports": {
"resend": { "type": "function", "length": 0, "includes": ["NextSGID()", "setTimeout"] }
},
"expects": ["\"vrcompositor_systemlayer\"", ".SendMessage("]
},
"windowFrame": {
"checkOnly": true,
"module": { "includes": ["\"frame-resize-scale-max\"", "\"frame-resize-scale-min\""] }
},
"grabScale": {
"checkOnly": true,
"module": { "includes": ["\"grab-scale\"", "\"min-distance\"", "\"max-distance\""] }
}
}
}
}
}
+110
View File
@@ -0,0 +1,110 @@
# Enables the SteamVR web helper debugger: Chromium DevTools of vrwebhelper,
# which renders the SteamVR dashboard, on 127.0.0.1:8087, so steam-ui-patches
# can patch the dashboard the way it patches Steam's UI on 127.0.0.1:8080.
# Turned on automatically (mkDefault) when a UI patch targets port 8087.
#
# SteamVR only opens the port when /settings/VRWebHelper/DebuggerEnabled is
# true (settingsschema.vrsettings: requires_restart; port from
# VRWebHelper/DebuggerPort, default 8087). User settings live in
# ~/.config/openvr/config/steamvr.vrsettings, which SteamVR rewrites at
# runtime and on exit (from memory), so it can't be a read-only store link,
# and editing it while SteamVR runs would be overwritten.
# steamvr-webhelper-debugger.service merges just that key with jq (atomic
# write, other keys untouched, file created if missing), before every start
# of steamvr.service (SteamVR's systemd user unit): a drop-in makes it
# Want + start After the oneshot. (Its own ExecStartPre= chain already starts
# vrserver, so an added ExecStartPre= would run too late.) Hence the setting
# takes effect with the next SteamVR start, once.
#
# The service is installed even when disabled: if this module set the key
# before (marker in $XDG_STATE_HOME/steam-frame-nix), it sets it back to
# false at the next SteamVR start and removes the marker; otherwise it
# leaves the file alone (a setting made by hand is kept).
#
# Security: SteamOS's steamvr-web-debug-portforward.service forwards
# 0.0.0.0:8088 to this port (see README, "DevTools on the LAN"); masking it
# is recommended. Our patches only use 127.0.0.1.
{ config, lib, pkgs, ... }:
let
cfg = config.steamFrame.steamvrDebugger;
usesDebugger = p: builtins.match "[a-z]+://[^/]*:8087(/.*)?" p.endpoint != null;
merge = pkgs.writeShellApplication {
name = "steamvr-webhelper-debugger";
runtimeInputs = [ pkgs.jq pkgs.coreutils ];
text = ''
f="$HOME/.config/openvr/config/steamvr.vrsettings"
marker="''${XDG_STATE_HOME:-$HOME/.local/state}/steam-frame-nix/steamvr-debugger"
${if cfg.enable then ''
want=true
'' else ''
if [ ! -e "$marker" ]; then
echo "not managed (VRWebHelper.DebuggerEnabled left as is)"
exit 0
fi
want=false
''}
mkdir -p "$(dirname "$f")"
[ -s "$f" ] || printf '{}\n' > "$f"
if [ "$(jq '.VRWebHelper.DebuggerEnabled' "$f")" = "$want" ]; then
echo "VRWebHelper.DebuggerEnabled already $want"
else
tmp="$(mktemp "$f.XXXXXX")"
trap 'rm -f "$tmp"' EXIT
jq --indent 3 --argjson v "$want" '.VRWebHelper.DebuggerEnabled = $v' "$f" > "$tmp"
chmod --reference="$f" "$tmp"
mv "$tmp" "$f"
echo "VRWebHelper.DebuggerEnabled set to $want (effective from this SteamVR start)"
fi
if [ "$want" = true ]; then
mkdir -p "$(dirname "$marker")"
touch "$marker"
else
rm -f "$marker"
fi
'';
};
in {
imports = [ ./steam-ui-patches.nix ];
options.steamFrame.steamvrDebugger.enable = lib.mkOption {
type = lib.types.bool;
default = false;
defaultText = lib.literalMD "`true` if a patch in `steamFrame.uiPatches.patches` uses port 8087, else `false`";
description = ''
Enable SteamVR's web helper debugger (DevTools of the SteamVR dashboard
on 127.0.0.1:8087, setting VRWebHelper/DebuggerEnabled in
~/.config/openvr/config/steamvr.vrsettings), needed by patches of the
SteamVR dashboard. Set before each SteamVR start, so it takes effect
after SteamVR is restarted once. Turning it off sets the key back to
false at the next SteamVR start. Note: SteamOS's
steamvr-web-debug-portforward.service exposes the port on 0.0.0.0:8088;
masking it is recommended.
'';
};
config = {
steamFrame.steamvrDebugger.enable =
lib.mkDefault (lib.any usesDebugger config.steamFrame.uiPatches.patches);
systemd.user.services.steamvr-webhelper-debugger = {
Unit = {
Description = "Set SteamVR VRWebHelper/DebuggerEnabled (steam-frame-nix)";
Before = [ "steamvr.service" ];
};
Service = {
Type = "oneshot";
ExecStart = "${merge}/bin/steamvr-webhelper-debugger";
};
};
# Wants= (not Requires=): if the merge fails (e.g. broken JSON), SteamVR
# still starts, just without the debugger.
xdg.configFile."systemd/user/steamvr.service.d/webhelper-debugger.conf".text = ''
[Unit]
Wants=steamvr-webhelper-debugger.service
After=steamvr-webhelper-debugger.service
'';
};
}
+7
View File
@@ -25,6 +25,13 @@
# closeOnLaunch = true;
# launchDebounce = 10;
# };
# # SteamVR dashboard windows: resizable up to 4x (stock 2x), pushed
# # back up to 10 m in the world / 12 m in theater mode (stock 5 / 6 m):
# dashboard = {
# windowMaxScale = 4.0;
# windowDistance.world.max = 10.0;
# windowDistance.theater.max = 12.0;
# };
# firefox.enable = true; # launcher for the Firefox Flatpak
# # Hidden from the "+" menu (with Steam Developer Mode on):
# hiddenApps = [ "lxterminal" "cmake-gui" "firewall-config" "renderdoc" ];