- finders.js (VERSION 2): resolvePatch (resolveAll or the "signature not found, ... left unpatched" status), ensureStyle and logger, used by the patches instead of their own copies. - hooks.js (VERSION 2) no longer unwinds per-patch wrappers of old patch versions; dashboard-windows and steam-close-button drop their handling of versions <= 2 / <= 6 (in-page only, gone with any SteamVR restart). - VERSIONs: dashboard-windows 4, steam-close-button 9, window-curvature 21, frame-controls 7, steam-keyboard-patch 22, launcher-menu grid 7, pinned-desktop 4, show-all 2.
steam-frame-nix
Home Manager modules for the
Valve Steam Frame (SteamOS, aarch64-linux, standalone home-manager). They
work around quirks of the Frame's two graphical sessions
(portal, keyboard layout, clipboard, KDE wallet, Firefox), enable hardware
video decoding in Jellyfin, run rootless Docker, and extend Steam's and SteamVR's UIs at runtime (VR keyboard,
"+" menu, dashboard windows, Steam close button, window curvature, window
controls).
Everything is declarative: files are links into the Nix store, UI patches
live in memory. The few things that have to be written elsewhere at runtime
are listed under Changes outside Nix, with
their lifetime and what removes them; steam-frame-nix-cleanup removes every
one of them (on each switch what the configuration no longer uses, --all
for everything).
All options live under steamFrame.*. The portal fix and clipboard sync are
on by default; everything else is opt-in.
Features
One page per feature in docs/: problem, what you get,
configuration, limitations and how it works.
Session (docs/session.md):
- Session settings (
session.*): outer bus and user services, for launchers and the other modules. - Portal fix (
session.portalFix, on): apps in the Steam session can open links. - Keyboard layout (
keyboard.layout,keyboard.variant): XKB layout for the Steam session. - Clipboard sync (
clipboardSync, on): one clipboard for the Steam session and the nested desktop.
VR keyboard (docs/keyboard.md):
- Extra keys (
keyboard.vr.extraKeys): Esc/Ctrl/Alt, arrows, Delete, real chords and AltGr/non-ASCII characters. - Swipe and suggestions (
keyboard.vr): swipe typing, corrections, completions, Backspace drag.
VR "+" menu (docs/launcher-menu.md):
- Launcher menu (
launcherMenu.*): sorted, Desktop pinned, closes on launch, no double launches, grid of tiles, all programs without Developer Mode. - Hidden apps (
launcherMenu.hiddenApps) and icon fallbacks (launcherMenu.iconFallbacks, on) for Konsole and KDE System Settings.
SteamVR dashboard (dashboard patches):
- Dashboard windows (
dashboard.windows): larger max scale and push-back distance. - Steam close button (
dashboard.steamCloseButton): an X that hides the Steam window. - Window curvature (
dashboard.windowCurvature): adjustable curvature per window. - Window control bar (
dashboard.frameControls): move controls between bar and three-dot menu. - SteamVR debugger (
steamvrDebugger, automatic): SteamVR's DevTools port for these, set only while SteamVR runs.
Apps:
- Firefox (
firefox): launcher for the Flatpak with a VR fullscreen fix, optional AV1 off, a separate desktop profile, optionally the default browser. - Jellyfin (
jellyfin.hardwareDecoding): hardware video decoding in the Jellyfin Desktop Flatpak. - Keyring launchers (
keyring.flatpaks,keyring.programs): apps (Flatpak or Nix, Electron too) keep their KDE wallet logins in both sessions. - Docker (
docker): rootless Docker as a user service, CLI working in both sessions.
Your own patches of Steam's UI, and fixing patches after a Steam update: UI patches.
Install
On the Frame (or a Steam Deck), in a terminal (Konsole in desktop mode or the nested desktop):
curl -fsSL https://steam-frame-nix.lhns.de | bash -s -- install
The short link redirects to
install.sh
on main. sudo needs a password: run passwd first if you never set one.
The installer installs Nix (skipped if Nix already works), uses
~/.config/home-manager or --flake <dir-or-flakeref> (if there is none, it
creates ~/nix-config from the template with your user name) and
runs home-manager switch; what it sets up is listed under
Set up by install.sh. Re-running it just switches
again (--yes answers every question). Afterwards edit
~/nix-config/home.nix and switch (see Usage).
curl -fsSL https://steam-frame-nix.lhns.de | bash -s -- status # Nix, generation, services
curl -fsSL https://steam-frame-nix.lhns.de | bash -s -- uninstall # --keep-nix keeps Nix
curl -fsSL https://steam-frame-nix.lhns.de | bash -s -- cleanup --all # see Changes outside Nix
bash -s -- --help lists all commands and flags. See
Uninstall for what uninstall removes and keeps.
Manual setup (Nix with flakes and standalone home-manager):
nix flake init -t github:lhns/steam-frame-nix creates a commented
flake.nix and home.nix (template/, shown under
Usage), or add the input to your own flake.
Two sessions
The Frame runs two graphical sessions at once; most workarounds exist because of their differences:
| Steam / VR session | Nested Plasma desktop | |
|---|---|---|
| Compositor | gamescope | KWin (nested, shown as a VR window) |
| Displays | X display :0 (apps show as floating VR windows) |
own Wayland + Xwayland :2 |
| D-Bus | the outer session bus, /run/user/1000/bus |
a private bus |
XDG_RUNTIME_DIR |
/run/user/1000 |
its own |
| systemd user manager | yes | not reachable |
What this means for you:
- Switch from the nested desktop: run
home-manager switchin a terminal there, so clipboard-sync restarts with the desktop's environment. User services are handled for you (session settings). - Wallet: there should be one
kwalletd6, on the outer bus; apps started from the desktop would otherwise start a second one whose secrets VR can't see. List such apps in keyring. - Launchers: the "+" menu only sees
~/.local/share/applications(not~/.nix-profile/share), so entries are written there, shadowing Flatpak/package entries with the same ID. - Keyboard layout, clipboard, Firefox: each session has its own; see keyboard layout, clipboard sync, Firefox (desktop profile).
Usage
nix flake init -t github:lhns/steam-frame-nix (or the installer) creates
these two files (template/, with more comments):
# flake.nix
{
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
home-manager = {
url = "github:nix-community/home-manager";
inputs.nixpkgs.follows = "nixpkgs";
};
steam-frame-nix = {
url = "github:lhns/steam-frame-nix";
inputs.nixpkgs.follows = "nixpkgs";
};
};
outputs = { nixpkgs, home-manager, steam-frame-nix, ... }:
let
username = "steamos"; # filled in by install.sh
homeDirectory = "/home/steamos";
system = "aarch64-linux"; # Steam Deck: "x86_64-linux"
in {
homeConfigurations.${username} = home-manager.lib.homeManagerConfiguration {
pkgs = nixpkgs.legacyPackages.${system};
extraSpecialArgs = { inherit username homeDirectory; };
modules = [ steam-frame-nix.homeManagerModules.default ./home.nix ];
};
};
}
# home.nix (the template has steamFrame commented out)
{ config, pkgs, username, homeDirectory, ... }: {
home.username = username;
home.homeDirectory = homeDirectory;
home.stateVersion = "26.05";
targets.genericLinux.enable = true;
programs.home-manager.enable = true;
steamFrame = {
keyboard.layout = "de"; # XKB layout, Steam session
keyboard.vr.extraKeys.enable = true; # Esc/Ctrl/Alt/arrows in VR
keyboard.vr.enable = true; # swipe, suggestions, Backspace drag
launcherMenu = {
sort = true;
pinDesktop = "bottom";
closeOnLaunch = true;
launchDebounceSeconds = 10;
grid = { enable = true; columns = 4; maxRows = 4; };
showAllApps = true;
# iconFallbacks.extra = [ "system-file-manager" ];
# Listed in the "+" menu only with showAllApps or Steam Developer Mode:
hiddenApps = [ "lxterminal" "cmake-gui" "firewall-config" "renderdoc" ];
};
dashboard = {
windows.maxScale = 4.0;
windows.distance.world.max = 10.0;
windows.distance.theater.max = 12.0;
steamCloseButton.enable = true;
windowCurvature.enable = true;
frameControls.enable = true;
};
firefox.enable = true;
firefox.disableAv1 = true;
firefox.defaultBrowser = true;
jellyfin.hardwareDecoding.enable = true; # install the Flatpak yourself
keyring.flatpaks."im.riot.Riot" = { # Element: logins in both sessions
name = "Element";
electron = true;
schemeHandlers = [ "element" "io.element.desktop" ];
};
docker.enable = true; # rootless
};
}
Switch from a terminal in the nested desktop (see Two sessions):
home-manager switch # config in ~/.config/home-manager (installer)
home-manager switch --flake .#steamos # manual setup, from the flake's directory
In your own flake, add the input as above and
steam-frame-nix.homeManagerModules.default to the modules. default
imports all modules; single ones:
homeManagerModules.{session,portal,keyboard-layout,steam-keyboard-patch,vr-keyboard,hidden-apps,steam-ui-patches,launcher-menu,steamvr-debugger,cleanup,dashboard-windows,steam-close-button,window-curvature,frame-controls,clipboard-sync,firefox,jellyfin,keyring,docker}.
Every module imports cleanup (see
Changes outside Nix).
Options
| Option | Type | Default | Description |
|---|---|---|---|
steamFrame.session.runtimeDir |
str | "/run/user/1000" |
XDG_RUNTIME_DIR of the outer (Steam/VR) session. |
steamFrame.session.bus |
str | "unix:path=${runtimeDir}/bus" |
Outer session D-Bus (user manager, kwalletd6). |
steamFrame.session.busEnv |
str, read-only | "env DBUS_SESSION_BUS_ADDRESS=${bus}" |
Prefix for launchers that must use the outer bus. |
steamFrame.session.services.start |
list of str | [ ] |
User units started on switch if not running. |
steamFrame.session.services.restart |
list of str | [ ] |
User units restarted on every switch. |
steamFrame.session.services.stop |
list of str | [ ] |
User units stopped on switch if running (e.g. of a disabled feature). |
steamFrame.session.portalFix.enable |
bool | true |
Working portal config (OpenURI) for the Steam session. |
steamFrame.keyboard.layout |
null or str | null |
XKB layout for the Steam session, e.g. "de"; null: US. |
steamFrame.keyboard.variant |
null or str | null |
XKB variant for the Steam session, e.g. "nodeadkeys"; see Keyboard layout. |
steamFrame.keyboard.vr.extraKeys.enable |
bool | false |
VR keyboard with Esc/Ctrl/Alt, arrows, real chords, AltGr/non-ASCII. |
steamFrame.keyboard.vr.enable |
bool | false |
Swipe typing, suggestions and Backspace drag on the VR keyboard; the sub-features below are on by default, see VR keyboard. |
steamFrame.keyboard.vr.swipe.enable |
bool | true |
Swipe typing. |
steamFrame.keyboard.vr.dictionary.languages |
list of submodules | layout language + English | { language; hunspell; words; frequencyOffset; keepFrequentAbove; }: wordfreq language, pkgs.hunspellDicts name (or null), most frequent words taken, zipf offset, keep words Hunspell rejects from this zipf on (default 4.0). Default: the keyboard.layout language (de, fr, es, it, nl, pt, sv; 60000) + English (40000, -0.3), else English (60000). |
steamFrame.keyboard.vr.dictionary.contractions |
bool | true |
Words with apostrophes (couldn't, geht's), swiped by their letters. |
steamFrame.keyboard.vr.dictionary.extraWords |
list of str | [ ] |
Words always included, casing as given. |
steamFrame.keyboard.vr.dictionary.extraWordsFrequency |
number | 5.0 |
Zipf frequency of extra words. |
steamFrame.keyboard.vr.dictionary.extraWordFiles |
list of paths | [ ] |
Word lists, word or word<TAB>zipf per line. |
steamFrame.keyboard.vr.dictionary.excludeWords |
list of str | [ ] |
Words never suggested. |
steamFrame.keyboard.vr.text.bufferChars |
int | 128 |
Characters of typed text the keyboard remembers. |
steamFrame.keyboard.vr.text.resetAfterIdleSeconds |
int | 30 |
Forget it after this long without typing (0: never). |
steamFrame.keyboard.vr.text.autoSpace |
bool | true |
Space before a swiped word after a known non-space character. |
steamFrame.keyboard.vr.suggestions.position |
"below", "above", "inside" |
"above" |
Suggestion strip: SteamVR panel below/above the keyboard, or over its number row. |
steamFrame.keyboard.vr.suggestions.count |
int | 6 |
Suggestions shown. |
steamFrame.keyboard.vr.autocorrect.enable |
bool | true |
Correction suggestions for finished tapped words not in the dictionary. |
steamFrame.keyboard.vr.autocorrect.maxEditDistance |
int | 2 |
Largest edit distance (neighbouring keys and swaps count 0.5). |
steamFrame.keyboard.vr.completions.enable |
bool | true |
Completions of the tapped word. |
steamFrame.keyboard.vr.completions.minPrefix |
int | 2 |
Letters typed before completions show. |
steamFrame.keyboard.vr.backspaceDrag.enable |
bool | true |
Backspace drag: left deletes, back right retypes. |
steamFrame.keyboard.vr.backspaceDrag.pixelsPerChar |
int | 25 |
Travel per character (keyboard px; a key is ~60). |
steamFrame.keyboard.vr.backspaceDrag.wordDetentPixels |
int | 90 |
Extra travel across a word border (0: none). |
steamFrame.keyboard.vr.haptics |
bool | true |
Haptic ticks for drag steps, word detents and picks. |
steamFrame.keyboard.vr.checks |
package, read-only | The tests, built with the configured dictionary. | |
steamFrame.uiPatches.patches |
list of submodules | [ ] |
Runtime patches of Steam's web UIs, see UI patches. |
steamFrame.uiPatches.lib |
attrs, read-only | Patch helpers (mkPatch), see Finders and signatures. |
|
steamFrame.launcherMenu.sort |
bool | false |
Sort the "+" menu alphabetically. |
steamFrame.launcherMenu.pinDesktop |
null or "top" / "bottom" |
null |
Pin "Desktop" above/below the "+" menu's list; null: normal entry. |
steamFrame.launcherMenu.closeOnLaunch |
bool | false |
Close the "+" menu when a program is clicked. |
steamFrame.launcherMenu.launchDebounceSeconds |
unsigned int (s) | 0 |
Ignore repeat launches of a program within this time; 0: off. |
steamFrame.launcherMenu.grid.enable |
bool | false |
Show the "+" menu's programs as a grid of tiles. |
steamFrame.launcherMenu.grid.columns |
int, 1-8 | 4 |
Tiles per row (3 ≈ 92 px, 4 ≈ 68 px, 5 ≈ 53 px). |
steamFrame.launcherMenu.grid.maxRows |
null or positive int | null |
Visible rows, the rest scrolls; null: up to 600 px. |
steamFrame.launcherMenu.showAllApps |
bool | false |
List all programs without Developer Mode, see Launcher menu. |
steamFrame.launcherMenu.iconFallbacks.enable |
bool | true |
Breeze icons of Konsole and KDE System Settings in hicolor, so the "+" menu shows them, see Icon fallbacks. |
steamFrame.launcherMenu.iconFallbacks.extra |
list of str | [ ] |
Further Breeze app icon names to provide (a name Breeze lacks fails the build). |
steamFrame.launcherMenu.hiddenApps |
list of str | [ ] |
Desktop entry ids (no .desktop) hidden from the "+" and KDE menus. |
steamFrame.dashboard.windows.maxScale |
null or positive number | null |
Max resize scale of dashboard windows; null: stock (2), see Dashboard windows. |
steamFrame.dashboard.windows.distance.{world,theater,dashboard}.{min,max} |
null or positive number (m) | null |
Pull-in / push-back limits of grabbed windows; null: stock (world 0.25-5, theater 1-6, dashboard 0.3-4 m). |
steamFrame.dashboard.steamCloseButton.enable |
bool | false |
X button on the dashboard's Steam window, see Steam close button. |
steamFrame.dashboard.windowCurvature.enable |
bool | false |
Adjustable curvature per window, see Window curvature. |
steamFrame.dashboard.windowCurvature.initial |
non-negative number | 1.0 |
Curvature of curved world/hand windows without own value (1 = stock, 0 = flat). |
steamFrame.dashboard.windowCurvature.max |
positive number | 3.0 |
Largest curvature. |
steamFrame.dashboard.windowCurvature.step |
positive number | 0.05 |
Rounding step while dragging (at most max). |
steamFrame.dashboard.windowCurvature.detentPixels |
unsigned int (px) | 24 |
Detent at each detent point in drag pixels: the value holds there, then continues (nothing skipped); 0: none. |
steamFrame.dashboard.windowCurvature.detentPoints |
list of non-negative numbers | [ 0 1.0 ] |
Detent points (flat, stock), at most max. |
steamFrame.dashboard.windowCurvature.dragThresholdPixels |
unsigned int (px) | 8 |
Vertical travel before a press becomes a drag. |
steamFrame.dashboard.windowCurvature.dragPixelsPerUnit |
positive number (px) | 120 |
Drag distance per 1.0 in the menu (6 px per 0.05 step). |
steamFrame.dashboard.windowCurvature.barDragPixelsPerUnit |
positive number (px) | 60 |
Drag distance per 1.0 on the bar button. |
steamFrame.dashboard.windowCurvature.haptics |
bool | true |
Controller haptics while dragging (steps, detents, edges); the dashboard's hover clicks are muted during a drag. |
steamFrame.dashboard.frameControls.enable |
bool | false |
Move window controls between bar and three-dot menu, see Window control bar. |
steamFrame.dashboard.frameControls.longPressMs |
int, 300-10000 (ms) | 1500 |
Long-press duration. |
steamFrame.dashboard.frameControls.inBar |
list of control names | [ ] |
Controls that start in the bar: keyboard, float, dashboard, theater, dockLeft, dockRight, close, curvature, "icon:<n>". |
steamFrame.dashboard.frameControls.inMenu |
list of control names | [ ] |
Controls that start in the three-dot menu. |
steamFrame.dashboard.frameControls.floatInTheater |
bool | false |
"Float" control on theater windows. |
steamFrame.steamvrDebugger.enable |
bool | automatic | SteamVR dashboard DevTools on 127.0.0.1:8087 (set only while SteamVR runs); on when a dashboard patch is, see SteamVR debugger. |
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. |
steamFrame.firefox.enable |
bool | false |
Launcher for the Flathub Firefox Flatpak with the fixes below. |
steamFrame.firefox.vrFullscreenFix |
bool | true |
Default full-screen-api.ignore-widgets to true (not in the desktop profile). |
steamFrame.firefox.disableAv1 |
bool | false |
Default media.av1.enabled to false: the Frame's decoder driver has no AV1, so sites send VP9/H.264, decoded in hardware. |
steamFrame.firefox.prefs |
attrs of bool, int or str | { } |
Further about:config default values for every profile (override the fixes too). |
steamFrame.firefox.desktopProfile |
null or str | "desktop" |
Separate profile (directory name) for the nested desktop; null: the default profile in both sessions. |
steamFrame.firefox.defaultBrowser |
bool | false |
Default for http, https, text/html (xdg.mimeApps). |
steamFrame.jellyfin.hardwareDecoding.enable |
bool | false |
Hardware video decoding in the Jellyfin Desktop Flatpak, see Jellyfin. |
steamFrame.jellyfin.hardwareDecoding.hwdec |
str | "v4l2m2m-copy,auto-copy" |
mpv hwdec used instead of Jellyfin's automatic one. |
steamFrame.jellyfin.hardwareDecoding.command |
str, read-only | The flatpak run … command line of the desktop entry, for a terminal. |
|
steamFrame.keyring.flatpaks |
attrs of submodules | { } |
Flatpaks by app ID getting a wallet launcher (outer bus, wallet D-Bus names), see Keyring launchers. Fields below. |
steamFrame.keyring.programs |
attrs of submodules | { } |
Other programs by desktop ID (without .desktop) getting a wallet launcher (outer bus). Fields below. |
steamFrame.keyring.{flatpaks,programs}.<id>.name |
str | required | Name=. |
steamFrame.keyring.{flatpaks,programs}.<id>.genericName, .comment, .startupWMClass |
null or str | null |
GenericName=, Comment=, StartupWMClass=. |
steamFrame.keyring.{flatpaks,programs}.<id>.icon |
null or str | flatpaks: the app ID; programs: null |
Icon=. |
steamFrame.keyring.{flatpaks,programs}.<id>.categories |
list of str | [ ] |
Categories=. |
steamFrame.keyring.{flatpaks,programs}.<id>.schemeHandlers |
list of str | [ ] |
URL schemes (e.g. login callbacks) the app handles and is made the default and recommended handler for. |
steamFrame.keyring.{flatpaks,programs}.<id>.mimeTypes |
list of str | [ ] |
Further MimeType= entries, not made default. |
steamFrame.keyring.{flatpaks,programs}.<id>.electron |
bool | false |
Pass --password-store=kwallet6 (Electron apps). |
steamFrame.keyring.{flatpaks,programs}.<id>.args |
list of str | [ ] |
Further app arguments (desktop entry syntax). |
steamFrame.keyring.{flatpaks,programs}.<id>.fieldCode |
"%U", "%u", "%F", "%f", "" |
"%U" |
How the entry passes URLs/files. |
steamFrame.keyring.{flatpaks,programs}.<id>.actions |
attrs of { name; args; } |
{ } |
Desktop actions, each running the command with its args. |
steamFrame.keyring.{flatpaks,programs}.<id>.settings |
attrs of str | { } |
Further [Desktop Entry] keys. |
steamFrame.keyring.{flatpaks,programs}.<id>.command |
str, read-only | The command line without args, for a terminal. | |
steamFrame.keyring.flatpaks.<id>.flatpakArgs |
list of str | [ ] |
Further flatpak run options. |
steamFrame.keyring.programs.<id>.executable |
str | required | The program to run, e.g. "${pkgs.claude-desktop}/bin/claude-desktop". |
steamFrame.docker.enable |
bool | false |
Rootless Docker as a user service, CLI for both sessions, see Docker. |
steamFrame.docker.package |
package | pkgs.docker |
Docker package (daemon and CLI). |
steamFrame.docker.host |
str, read-only | "unix://${runtimeDir}/docker.sock" |
The daemon's DOCKER_HOST (the CLI's default). |
steamFrame.cleanup.package |
package, read-only | steam-frame-nix-cleanup (on PATH too), see Changes outside Nix. |
Renamed options still work under their old names, with a warning:
| Old | New |
|---|---|
keyboardLayout, keyboardVariant |
keyboard.layout, keyboard.variant |
steamKeyboardPatch.enable |
keyboard.vr.extraKeys.enable |
hiddenApps |
launcherMenu.hiddenApps |
launcherMenu.launchDebounce |
launcherMenu.launchDebounceSeconds |
runtimeDir, userBus, outerBusEnv |
session.runtimeDir, session.bus, session.busEnv |
userServices.{start,restart,stop} |
session.services.{start,restart,stop} |
portalFix.enable |
session.portalFix.enable |
dashboard.windowMaxScale |
dashboard.windows.maxScale |
dashboard.windowDistance.* |
dashboard.windows.distance.* |
dashboard.windowCurvature.default |
dashboard.windowCurvature.initial |
dashboard.windowCurvature.snapPixels, snapPoints |
detentPixels, detentPoints |
dashboard.windowCurvature.dragThreshold |
dragThresholdPixels |
Changes outside Nix (exceptions)
Everything not listed here is a Home Manager link into the Nix store or lives in memory (the UI patches). These are written at runtime:
| Path | Feature | Lifetime | Removed by |
|---|---|---|---|
VRWebHelper.DebuggerEnabled in ~/.config/openvr/config/steamvr.vrsettings |
SteamVR debugger | only while SteamVR runs | SteamVR stopping (runtime drop-in below); steam-frame-nix-cleanup while SteamVR is stopped |
~/.local/state/steam-frame-nix/steamvr-debugger.armed |
SteamVR debugger: the key's previous value | while SteamVR runs; after a power loss until the next SteamVR start or cleanup | SteamVR stopping; steam-frame-nix-cleanup |
/run/user/1000/systemd/user/steamvr.service.d/50-steam-frame-nix-debugger.conf, /run/user/1000/steam-frame-nix/steamvr-debugger-restore |
SteamVR debugger: puts the key back when SteamVR stops, without Nix | until reboot (tmpfs) | reboot; steam-frame-nix-cleanup while SteamVR is stopped and the debugger is off |
~/.local/state/steam-frame-nix/ui-patches/<name>.json |
Saved choices of dashboard patches (persistent state): window control bar placements (frame-controls), "Steam hidden" (steam-close-button). SteamOS's steamvr.service deletes ~/.cache/SteamVR (the dashboard's own browser storage) on every SteamVR start. |
until removed: kept when a patch is disabled (the choices come back when you enable it again) | steam-frame-nix-cleanup --all, install.sh uninstall |
mtime of ~/.local/share/icons/hicolor |
icon fallbacks: a running Steam rescans icons | only the directory's timestamp | nothing to remove |
steam-frame-nix-cleanup (install.sh cleanup,
steamFrame.cleanup.package) knows everything any version of
steam-frame-nix wrote outside the store, removes only what is provably its
own (everything else is reported as "left alone") and can be run again
safely; --dry-run shows what it would do. How it decides and what older
versions left: docs/cleanup.md.
- On every switch,
cleanup --orphansremoves what the configuration no longer uses (never the saved patch state). steam-frame-nix-cleanup --allremoves everything, also the saved patch state. SteamVR's key can't be changed while SteamVR runs: it is then left to the runtime drop-in (restored when SteamVR stops).- Without Nix or after a rollback it runs from the script:
curl -fsSL https://steam-frame-nix.lhns.de | bash -s -- cleanup --all.
Only while running
- The UI patches (Steam, SteamVR dashboard, VR keyboard) live in the pages'
memory; stopping
steam-ui-patches/steam-keyboard-patchreverts them. - clipboard-sync runs from KDE autostart (a Home Manager link).
- Firefox: the desktop profile's
user.jslink exists only while its Firefox runs (see Firefox). - Jellyfin, keyring launchers: the permissions are
flatpak runoptions of the desktop entries, not Flatpak overrides. - Docker: the daemon's socket in
/run/user/1000(tmpfs) exists whiledocker.serviceruns.
Set up by install.sh
install.sh install (the bootstrap, not the modules) also changes these, and
install.sh uninstall undoes it:
- Nix via nix-installer
(
steam-deckplanner, flakes on):/nix(bind mount of/home/nix, survives SteamOS updates), files in/etc(systemd units, profile scripts,nix.conf), its receipt/nix/receipt.json; the read-only root is unlocked only while it installs or uninstalls; experimental-features = nix-command flakesin~/.config/nix/nix.confif Nix was already there without flakes;~/nix-config(your configuration, a git repository, from the template with your user name filled intoflake.nix) and the link~/.config/home-managerto it, unless that exists or--flakeis given; uninstall removes the link, never the configuration;- dotfiles in Home Manager's way, renamed to
*.hm-backup-<time>(kept); ~/.local/state/home-manager,~/.local/state/nix(profiles, generations),~/.nix-profile,~/.nix-defexpr,~/.nix-channels,~/.cache/nix.
App data you create
Not steam-frame-nix's to remove: the Firefox desktop profile
(~/.var/app/org.mozilla.firefox/config/mozilla/firefox/desktop, browser
data), secrets apps stored in the KDE wallet, and whatever apps keep in
~/.var/app/*, Flatpak apps and their runtimes.
Docker's images, containers and volumes (~/.local/share/docker) are partly
owned by the subordinate UIDs of your containers, so a plain rm fails.
With the daemon running, then stopped:
docker system prune -a --volumes
systemctl --user stop docker
nix shell nixpkgs#rootlesskit -c rootlesskit rm -rf ~/.local/share/docker
Rollback
home-manager generations lists previous generations; run
<store path>/activate of the one you want. Generations with
steam-frame-nix clean up after themselves on activation (orphans). After
rolling back to a generation without steam-frame-nix, or to one older
than steam-frame-nix-cleanup (2026-09-29), remove what the newer one wrote
outside the store:
curl -fsSL https://steam-frame-nix.lhns.de | bash -s -- cleanup --all
# or: nix run github:lhns/steam-frame-nix#cleanup -- --all
Uninstall
curl -fsSL https://steam-frame-nix.lhns.de | bash -s -- uninstall # --keep-nix keeps Nix
stops Home Manager's user services (reverting the UI patches), runs
cleanup --all, runs home-manager uninstall, then removes Nix and the
per-user Nix state (see Set up by install.sh). If
SteamVR is running, its key is restored when SteamVR stops (the closing
message says so). Your configuration, *.hm-backup-* files, app data and
Flatpaks stay.
To drop steam-frame-nix from a Home Manager configuration you keep, first
run steam-frame-nix-cleanup --all, then remove it and switch. Or set Home
Manager's uninstall = true; in the configuration that still imports
steam-frame-nix and switch: its activation runs cleanup --all while Home
Manager removes its files. (home-manager uninstall alone doesn't load
steam-frame-nix's modules, so it can't clean up after them.)