mirror of
https://github.com/lhns/steam-frame-nix.git
synced 2026-10-06 01:00:13 +02:00
Every module imports cleanup.nix (also exported as homeManagerModules.cleanup), which - puts steam-frame-nix-cleanup (install.sh cleanup) on PATH; - runs `cleanup --orphans --quiet` after linkGeneration on every switch, keeping what the configuration still uses (steamFrame.cleanup.keep, set by the modules: debugger while steamvrDebugger is on, the Firefox desktop profile's user.js while its fix applies); in a dry run it runs with --dry-run; - runs `cleanup --all` instead when Home Manager's `uninstall = true;` is set (the manual uninstall route); - removes links of older versions at paths Home Manager is about to own before checkLinkTargets (steamFrame.cleanup.migrateLinks), which would count as collisions otherwise. install.sh cleanup gains --quiet (only actions, deferrals and warnings; the header only when something is printed). The Jellyfin module's own removal of the old shim copy goes: cleanup does it.
160 lines
5.5 KiB
Nix
160 lines
5.5 KiB
Nix
# Runtime patches of Steam's and SteamVR's web UIs over local CEF DevTools
|
|
# (Steam 127.0.0.1:8080; SteamVR's vrwebhelper 127.0.0.1:8087, see
|
|
# steamvr-debugger.nix). The steam-ui-patches service (injector.mjs) injects
|
|
# each registered patch into its target pages, re-injects on reload and every
|
|
# 15 s, and runs the unpatches on stop; Steam's files are untouched.
|
|
# It exists while `patches` is non-empty, restarts on every switch (the old
|
|
# instance reverts removed patches) and is stopped once the list is empty.
|
|
# Patches with `state = true` get a persistent JSON value (injector.mjs:
|
|
# "Persistent state"). Patch calling convention (mkPatch): lib/default.nix.
|
|
{ config, pkgs, lib, ... }:
|
|
let
|
|
cfg = config.steamFrame.uiPatches;
|
|
inherit (lib) mkOption types;
|
|
|
|
patchType = types.submodule {
|
|
options = {
|
|
name = mkOption {
|
|
type = types.strMatching "[A-Za-z0-9_.-]+";
|
|
example = "launcher-menu-order";
|
|
description = "Unique name, used in the service's log.";
|
|
};
|
|
endpoint = mkOption {
|
|
type = types.str;
|
|
default = "http://127.0.0.1:8080";
|
|
example = "http://127.0.0.1:8087";
|
|
description = ''
|
|
DevTools base URL (its /json/list is polled): Steam 8080, SteamVR
|
|
8087.
|
|
'';
|
|
};
|
|
target = {
|
|
title = mkOption {
|
|
type = types.nullOr types.str;
|
|
default = null;
|
|
example = "SharedJSContext";
|
|
description = "Exact page title of the targets to patch.";
|
|
};
|
|
titleRegex = mkOption {
|
|
type = types.nullOr types.str;
|
|
default = null;
|
|
description = "JavaScript regex the page title must match.";
|
|
};
|
|
urlRegex = mkOption {
|
|
type = types.nullOr types.str;
|
|
default = null;
|
|
description = "JavaScript regex the page URL must match.";
|
|
};
|
|
};
|
|
patch = mkOption {
|
|
type = types.path;
|
|
description = ''
|
|
JavaScript evaluated (awaited) in every matching page after reloads
|
|
and every 15 s, so it must be idempotent. Its result is logged when
|
|
it changes ("unchanged" never is). Build with `lib.mkPatch` to get
|
|
the finder library.
|
|
'';
|
|
};
|
|
unpatch = mkOption {
|
|
type = types.nullOr types.path;
|
|
default = null;
|
|
description = ''
|
|
JavaScript that reverts the patch when the service stops; must be
|
|
safe on an unpatched page.
|
|
'';
|
|
};
|
|
state = mkOption {
|
|
type = types.bool;
|
|
default = false;
|
|
description = ''
|
|
Give the patch one persistent JSON value (e.g. choices made in its
|
|
UI), kept by the service in
|
|
$XDG_STATE_HOME/steam-frame-nix/ui-patches/<name>.json across
|
|
SteamVR restarts and reboots: the page reads it with
|
|
`window.__sfuiStore.get(name)` and writes it with
|
|
`window.__sfuiStore.set(name, value)` (see injector.mjs). The file
|
|
is user data and stays when the patch is removed.
|
|
'';
|
|
};
|
|
};
|
|
};
|
|
|
|
entry = p: {
|
|
inherit (p) name endpoint patch unpatch state;
|
|
target = lib.filterAttrs (_: v: v != null) p.target;
|
|
};
|
|
|
|
configFile = pkgs.writeText "steam-ui-patches.json" (builtins.toJSON {
|
|
pollMs = 5000;
|
|
reinjectMs = 15000;
|
|
debounceMs = 3000;
|
|
unpatchTimeoutMs = 2000;
|
|
patches = map entry cfg.patches;
|
|
});
|
|
in {
|
|
imports = [ ./session.nix ./cleanup.nix ];
|
|
|
|
options.steamFrame.uiPatches.lib = mkOption {
|
|
type = types.attrsOf types.raw;
|
|
readOnly = true;
|
|
default = import ./lib { inherit pkgs; };
|
|
defaultText = lib.literalMD "the helpers of `modules/lib`";
|
|
description = ''
|
|
Patch helpers from modules/lib/default.nix: `mkPatch { name, src,
|
|
signatures ? …, opts ? { } }` (calls `src` as
|
|
`(find, sigs, opts, hooks) => …`; see there), plus `finders`, `hooks`
|
|
(paths) and `signatures` (parsed signatures.json).
|
|
'';
|
|
};
|
|
|
|
options.steamFrame.uiPatches.patches = mkOption {
|
|
type = types.listOf patchType;
|
|
default = [ ];
|
|
example = lib.literalExpression ''
|
|
[ {
|
|
name = "my-patch";
|
|
target.title = "SharedJSContext";
|
|
patch = ./my-patch/patch.js;
|
|
unpatch = ./my-patch/unpatch.js;
|
|
} ]
|
|
'';
|
|
description = ''
|
|
Runtime patches of Steam's web UIs, kept injected by the
|
|
steam-ui-patches service and reverted when it stops. Every page
|
|
matching all given target criteria is patched.
|
|
'';
|
|
};
|
|
|
|
config = lib.mkMerge [
|
|
(lib.mkIf (cfg.patches != [ ]) {
|
|
assertions = [ {
|
|
assertion = lib.allUnique (map (p: p.name) cfg.patches);
|
|
message = "steamFrame.uiPatches.patches: names must be unique.";
|
|
} ];
|
|
|
|
systemd.user.services.steam-ui-patches = {
|
|
Unit.Description = "Runtime patches of Steam's UI (CEF DevTools)";
|
|
Service = {
|
|
ExecStart = lib.escapeShellArgs [
|
|
"${pkgs.nodejs}/bin/node"
|
|
"${./steam-ui-patches/injector.mjs}"
|
|
"${configFile}"
|
|
];
|
|
Restart = "always";
|
|
RestartSec = 5;
|
|
TimeoutStopSec = 5; # the injector unpatches Steam's UI on SIGTERM
|
|
};
|
|
Install.WantedBy = [ "default.target" ];
|
|
};
|
|
|
|
# Restart on switch: re-inject changed patches; the old instance reverts
|
|
# removed ones.
|
|
steamFrame.session.services.restart = [ "steam-ui-patches.service" ];
|
|
})
|
|
# No patches: stopping the injector reverts them right away.
|
|
(lib.mkIf (cfg.patches == [ ]) {
|
|
steamFrame.session.services.stop = [ "steam-ui-patches.service" ];
|
|
})
|
|
];
|
|
}
|