mirror of
https://github.com/lhns/steam-frame-nix.git
synced 2026-10-06 03:00:13 +02:00
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:
1 parent
6a8924f219
commit
9f5538003f
8 files changed
+480
-6
No files matched your search
@@ -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
|
||||
|
||||
@@ -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`.
|
||||
|
||||
@@ -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;
|
||||
} ];
|
||||
};
|
||||
}
|
||||
@@ -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})`;
|
||||
})
|
||||
@@ -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';
|
||||
})()
|
||||
@@ -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\""] }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
'';
|
||||
};
|
||||
}
|
||||
@@ -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" ];
|
||||
|
||||
Reference in new issue
Block a user