Files
lhns--steam-frame-nix/modules/frame-controls.nix
T
Pierre Kisters 7fe68e055c Dashboard patches: persist frame-controls placements and "Steam hidden" on disk
SteamOS's steamvr.service runs `rm -rf ~/.cache/SteamVR` on every SteamVR
start, which deletes vrwebhelper's browser profile and with it the
localStorage frame-controls kept its placements in, so they were lost on
every SteamVR restart and reboot.

steam-ui-patches: per-patch persistent state (`state = true`). The injector
keeps ~/.local/state/steam-frame-nix/ui-patches/<name>.json, seeds
window.__sfuiStore in a fresh page before the patch runs and writes the file
(atomically, on change, name-checked, max 64 KiB) when the page calls the
CDP binding __sfuiStoreSave.

frame-controls v5: placements saved there (v4 localStorage copy migrated
once, then removed). steam-close-button v8: "Steam hidden" saved there; a
restored hidden state hides Steam once if SteamVR already shows it when the
patch attaches (or first shows it before the patch sees Steam's frame).

README: "Changes outside Nix" lists the directory; install.sh uninstall
removes it.
2026-09-28 23:33:12 +02:00

79 lines
3.0 KiB
Nix

# Move window controls between a dashboard window's bottom bar and its
# three-dot menu (long press). SteamVR dashboard patch
# (frame-controls/patch.js; "systemui" on 127.0.0.1:8087), registered only
# when enabled; enables steamvr-debugger.nix (one SteamVR restart the first
# time).
{ config, lib, pkgs, ... }:
let
cfg = config.steamFrame.dashboard.frameControls;
inherit (lib) mkOption types;
inherit (import ./lib { inherit pkgs; }) mkPatch;
# Control names -> SteamVR action icon enums (the patch keys controls by icon).
names = [ "keyboard" "float" "dashboard" "theater" "dockLeft" "dockRight" "close" "curvature" ];
controlType = types.either (types.enum names) (types.strMatching "icon:[0-9]+");
controlList = description: mkOption {
type = types.listOf controlType;
default = [ ];
example = [ "curvature" ];
inherit description;
};
in {
imports = [ ./steam-ui-patches.nix ./steamvr-debugger.nix ];
options.steamFrame.dashboard.frameControls = {
enable = lib.mkEnableOption ''
moving SteamVR dashboard window controls between the bottom bar and the
three-dot menu: long press an icon or menu row for a "Show in bar"
popup. Applies to that control in all windows, kept across SteamVR
restarts and reboots (in
~/.local/state/steam-frame-nix/ui-patches/frame-controls.json).
SteamVR dashboard patch; off restores stock (next switch)'';
longPressMs = mkOption {
type = types.ints.between 300 10000;
default = 1500;
description = ''
Long-press time (ms) to open the popup. A progress ring appears after
half of it (at most 1 s), so ordinary clicks show nothing.
'';
};
inBar = controlList ''
Controls that start in the bar (default: SteamVR's placement):
${lib.concatStringsSep ", " names}, or "icon:<n>" (see
window.__sfuiFrameControls.dump() in the dashboard's DevTools). A popup
choice wins until the control's entry here changes.
'';
inMenu = controlList ''
Controls that start in the three-dot menu (same names as inBar).
'';
floatInTheater = mkOption {
type = types.bool;
default = false;
description = ''
Give theater windows the "Float" control too (stock: dashboard-docked
windows only). Follows Float's placement.
'';
};
};
config = lib.mkIf cfg.enable {
assertions = [ {
assertion = lib.intersectLists cfg.inBar cfg.inMenu == [ ];
message = "steamFrame.dashboard.frameControls: ${builtins.toJSON (lib.intersectLists cfg.inBar cfg.inMenu)} in both inBar and inMenu.";
} ];
steamFrame.uiPatches.patches = [ {
name = "frame-controls";
endpoint = "http://127.0.0.1:8087";
target.title = "systemui";
patch = mkPatch {
name = "frame-controls";
src = ./frame-controls/patch.js;
opts = removeAttrs cfg [ "enable" ];
};
unpatch = ./frame-controls/unpatch.js;
state = true; # popup placements, kept across restarts
} ];
};
}