Files
lhns--steam-frame-nix/modules/session.nix
T
Pierre Kisters 6b12d5329f Install: name what waits for a restart; uninstall survives a failed Nix uninstall
A fresh install into a running session left the keyboard layout and the
SteamVR dashboard patches (VR pet, the VR keyboard's suggestion strip, ...)
silently off until a reboot: gamescope reads XKB_DEFAULT_* only at its
start, and SteamVR opens its DevTools port (VRWebHelper.DebuggerEnabled)
only at its start. Neither can be applied at runtime.

- install.sh restart-check (run on every switch by session.nix, and by
  install at its end): compares the keyboard-layout drop-in with the
  running gamescope session's environment and checks whether SteamVR runs
  without port 8087; install ends with "Restart once" listing them.
- uninstall: a failing nix-installer uninstall (e.g. /nix busy) no longer
  aborts the script before the ~/.config/home-manager link and Home
  Manager state are removed; it says to reboot and run uninstall again.
- cleanup check: restart-check on fake cgroup/proc dirs.
2026-10-01 18:06:50 +02:00

125 lines
4.9 KiB
Nix

# Settings shared by both sessions, and user-service handling on switch.
#
# The Steam/VR session owns the systemd user manager and the outer D-Bus
# (<runtimeDir>/bus); the nested Plasma desktop has its own XDG_RUNTIME_DIR
# and private bus. `switch` usually runs from the nested desktop, where
# home-manager's reloadSystemd is skipped ("User systemd daemon not running"),
# so `steamFrameUserServices` does it against the outer session: always
# daemon-reload, then start/stop/restart the units in `session.services`.
# Then `steamFrameRestartCheck` (`install.sh restart-check`) names what the
# running session can't pick up (read only at a process start): the keyboard
# layout, the SteamVR dashboard patches' DevTools port.
{ config, lib, pkgs, ... }:
let
cfg = config.steamFrame.session;
units = lib.escapeShellArgs;
restartCheck = pkgs.callPackage ./cleanup/package.nix {
name = "steam-frame-nix-restart-check";
command = "restart-check";
};
rename = from: to: lib.mkRenamedOptionModule ([ "steamFrame" ] ++ from) [ "steamFrame" "session" to ];
in {
imports = [
(rename [ "runtimeDir" ] "runtimeDir")
(rename [ "userBus" ] "bus")
] ++ map (n: lib.mkRenamedOptionModule
[ "steamFrame" "userServices" n ] [ "steamFrame" "session" "services" n ])
[ "start" "restart" "stop" ];
# Read-only, so no mkRenamedOptionModule (its alias definition would be a
# second one); reading the old name warns.
options.steamFrame.outerBusEnv = lib.mkOption {
type = lib.types.str;
readOnly = true;
visible = false;
default = lib.warn
"The option `steamFrame.outerBusEnv' has been renamed to `steamFrame.session.busEnv'."
cfg.busEnv;
defaultText = lib.literalExpression "config.steamFrame.session.busEnv";
description = "Alias of {option}`steamFrame.session.busEnv`.";
};
options.steamFrame.session = {
runtimeDir = lib.mkOption {
type = lib.types.str;
default = "/run/user/1000";
description = "XDG_RUNTIME_DIR of the outer (Steam/VR) session.";
};
bus = lib.mkOption {
type = lib.types.str;
default = "unix:path=${cfg.runtimeDir}/bus";
defaultText = lib.literalExpression
''"unix:path=''${config.steamFrame.session.runtimeDir}/bus"'';
description = ''
Outer session D-Bus address; the only bus that reaches the user
systemd manager and the running kwalletd6.
'';
};
busEnv = lib.mkOption {
type = lib.types.str;
readOnly = true;
default = "env DBUS_SESSION_BUS_ADDRESS=${cfg.bus}";
defaultText = lib.literalExpression
''"env DBUS_SESSION_BUS_ADDRESS=''${config.steamFrame.session.bus}"'';
description = ''
Exec= prefix for launchers that must use the outer bus, e.g. so apps
in the nested desktop use the running kwalletd6 instead of a second
one.
'';
};
services = {
start = lib.mkOption {
type = lib.types.listOf lib.types.str;
default = [ ];
example = [ "docker.service" ];
description = ''
User units started on switch if not already running (outer user
manager).
'';
};
restart = lib.mkOption {
type = lib.types.listOf lib.types.str;
default = [ ];
example = [ "steam-keyboard-patch.service" ];
description = ''
User units restarted on every switch (outer user manager).
'';
};
stop = lib.mkOption {
type = lib.types.listOf lib.types.str;
default = [ ];
description = ''
User units stopped on switch if running (e.g. of a just-disabled
feature).
'';
};
};
};
# In a subshell: the outer session's environment must not leak into
# activation steps that run later.
# Read-only; a warning, never a failed switch.
config.home.activation.steamFrameRestartCheck = lib.hm.dag.entryAfter [ "steamFrameUserServices" ] ''
STEAM_FRAME_NIX_RUNTIME_DIR=${lib.escapeShellArg cfg.runtimeDir} XDG_CONFIG_HOME=${lib.escapeShellArg config.xdg.configHome} \
${lib.getExe restartCheck} || true
'';
config.home.activation.steamFrameUserServices = lib.hm.dag.entryAfter [ "reloadSystemd" ] (''
(
export XDG_RUNTIME_DIR=${lib.escapeShellArg cfg.runtimeDir} DBUS_SESSION_BUS_ADDRESS=${lib.escapeShellArg cfg.bus}
run /usr/bin/systemctl --user daemon-reload
'' + lib.optionalString (cfg.services.start != [ ]) ''
run /usr/bin/systemctl --user start ${units cfg.services.start}
'' + lib.optionalString (cfg.services.stop != [ ]) ''
# Skip inactive ones: stopping a unit that isn't loaded fails noisily.
for unit in ${units cfg.services.stop}; do
[[ $(/usr/bin/systemctl --user show -p ActiveState --value "$unit") == inactive ]] \
|| run /usr/bin/systemctl --user stop "$unit" || true
done
'' + lib.optionalString (cfg.services.restart != [ ]) ''
run /usr/bin/systemctl --user restart ${units cfg.services.restart}
'' + ''
)
'');
}