mirror of
https://github.com/theupriser/steamify-cachyos.git
synced 2026-10-03 17:41:58 +02:00
605 lines
36 KiB
Markdown
605 lines
36 KiB
Markdown
# Technical details
|
|
|
|
How Steamify CachyOS works under the hood. For using it, see
|
|
[README.md](README.md); for problems, [TROUBLESHOOTING.md](TROUBLESHOOTING.md).
|
|
|
|
## How turning things on and off works
|
|
|
|
At startup every component checks whether it is on (`*_status` in `lib/`).
|
|
You choose; the wizard then turns off what you unticked (in reverse order)
|
|
and turns on what you ticked. To be able to undo:
|
|
|
|
- **System files** are backed up once, next to the original, with a
|
|
`.bak-gamescope-wizard` suffix, and restored when you turn the component
|
|
off.
|
|
- **KDE settings** in your home directory are recorded with their previous
|
|
value the first time the wizard changes them, in an undo journal under
|
|
`~/.local/state/steamify/`. Turning the component off writes
|
|
the old values back (or removes keys that didn't exist before). Setups
|
|
made by older versions of the script, without a journal, fall back to
|
|
KDE's defaults.
|
|
- **Packages** installed for the conversion (Steam, gamescope-session, ...)
|
|
are kept when you turn it off; Steam Machine support removes its own.
|
|
|
|
## Single user mode: SDDM, no locking
|
|
|
|
Turning it on switches to **SDDM** and turns off everything that asks for a
|
|
password or another user, per user: `action/lock_screen`, `switch_user` and
|
|
`start_new_session` restrictions in `kdeglobals`, no automatic locking
|
|
(`kscreenlockerrc`), Meta+L and Ctrl+Alt+Del unbound, and the launcher shows
|
|
only Sleep / Restart / Shut Down (kickoff `primaryActions=3`), which hides
|
|
the Session dropdown with Log Out. (Restricting `action/logout` would also
|
|
hide Restart and Shut Down.) Turning it off restores all of that and moves
|
|
the conversion back to plasma-login-manager.
|
|
|
|
**KDE wallet.** KDE normally unlocks your wallet with the password you log in
|
|
with; with autologin nobody types it, so apps that keep passwords in it (Brave,
|
|
for example) ask for the wallet password. Single user mode does what SteamOS
|
|
does: it uses Valve's empty wallet without a password (from
|
|
`steamdeck-kde-presets`, checksum verified). Your own wallet in
|
|
`~/.local/share/kwalletd/` is moved to `kdewallet.kwl.bak-steamify` (and
|
|
`.salt`). Turning single user mode off puts it back, unchanged; the single
|
|
user wallet, with anything saved in it meanwhile, is kept as
|
|
`kdewallet.kwl.steamify-single-user` and used again the next time it's on.
|
|
Passwords aren't shared between the two wallets.
|
|
|
|
**Without the conversion (NVIDIA PCs, where gamescope's session is broken)** nothing else logs in by itself, so single user mode
|
|
does that too: `single_login_enable` (`lib/single-user.sh`) switches to SDDM and writes
|
|
`/etc/sddm.conf.d/zzz-steamify-autologin.conf` (`User=`, `Session=plasma.desktop`, `Relogin=true`; named to sort after
|
|
`steam-set-session`'s `zz-steamos-autologin.conf`, which may still say gamescope from an earlier conversion), and drops any
|
|
`[Autologin]` from `/etc/sddm.conf`. Turning it off removes the file and goes back to plasma-login-manager. With the
|
|
conversion on, the conversion does the login as before. Ticking single user mode only ticks the conversion where the
|
|
conversion is offered.
|
|
|
|
**SDDM** is what SteamOS uses, and CachyOS's `steam-set-session` supports it
|
|
directly: it writes `/etc/sddm.conf.d/zz-steamos-autologin.conf`, which SDDM
|
|
honours. The script installs and enables `sddm` (active from the next boot),
|
|
writes `User=`, `Session=` and `Relogin=true` to
|
|
`/etc/sddm.conf.d/10-gamescope-autologin.conf`, and removes any
|
|
`[Autologin]` from `/etc/sddm.conf` (read last, so it would override both).
|
|
No sync bridge or sudoers rule is needed; the shortcut just runs
|
|
`steamos-session-select gamescope`, which logs out, and `Relogin=true` logs
|
|
straight back in to gamescope.
|
|
|
|
Without single user mode, the conversion uses **plasma-login-manager**
|
|
(CachyOS's default since March 2026), which needs the workarounds below.
|
|
|
|
## Why this is needed (plasma-login-manager)
|
|
|
|
CachyOS's `gamescope-session-cachyos` package ships `steamos-session-select`,
|
|
which is meant to work exactly like it does on real SteamOS. Since the March
|
|
2026 release, CachyOS uses `plasma-login-manager` instead of SDDM, and three
|
|
things get in the way:
|
|
|
|
1. **No `User=` written to the autologin config.**
|
|
`steam-set-session` (called by `steamos-session-select`) only writes a
|
|
`Session=` key. `plasma-login-manager` needs both `Session=` and `User=`
|
|
to autologin at all; without `User=` it shows the greeter every boot.
|
|
|
|
2. **Missing `/etc/plasmalogin.conf.d` directory.**
|
|
Without it, `steam-set-session` fails, and Steam's "Switch to Desktop"
|
|
hangs forever. *(Fixed upstream in `gamescope-session-cachyos` 1.1.6; the
|
|
script still creates the directory for older versions.)*
|
|
|
|
3. **The base `/etc/plasmalogin.conf` wins over conf.d, and CachyOS ships it
|
|
with `Session=plasma`.**
|
|
Every tool that changes sessions (`steamos-session-select`, Steam's power
|
|
menu, and `cachyos-gamescope-autologin.service`, which resets you to
|
|
gamescope after a desktop session) only writes
|
|
`/etc/plasmalogin.conf.d/zz-steamos-autologin.conf`. The base file takes
|
|
priority, so none of those switches stick.
|
|
|
|
The script sets `Session=`, `User=` and `Relogin=true` in the base config,
|
|
and installs a small systemd path watcher that copies whatever CachyOS's
|
|
tools write to the conf.d fragment into the base config.
|
|
|
|
## What each component changes
|
|
|
|
**SteamOS conversion** (`lib/login-manager.sh`, `lib/steam-desktop.sh`,
|
|
`lib/desktop-shortcut.sh`):
|
|
|
|
- installs missing packages: `gamescope-session-cachyos`, `steam`,
|
|
`mangohud`, `xterm`, `ttf-liberation`, `wqy-zenhei`, `plasma-keyboard`;
|
|
- *SDDM (single user mode):* installs/enables SDDM and writes its autologin
|
|
config;
|
|
- *plasma-login-manager:* creates `/etc/plasmalogin.conf.d`, backs up and
|
|
rewrites the `[Autologin]` section of `/etc/plasmalogin.conf`, removes
|
|
stray `zzz-steamos-autologin*.conf` files from manual troubleshooting
|
|
(never `zz-steamos-autologin.conf`, which CachyOS's tools own), and
|
|
installs `/usr/local/bin/sync-steamos-session.sh` plus
|
|
`sync-steamos-session.path`/`.service`;
|
|
- `KWIN_IM_SHOW_ALWAYS=1` for the virtual keyboard; the sub-option `silent`
|
|
("Start Steam silently in desktop mode", opt-in, always shown) writes
|
|
`~/.config/autostart/steam.desktop` (the system steam.desktop with `-silent` in its Exec line). That is the
|
|
file Steam's own setting "Run Steam when my computer starts" creates and removes, so the setting shows it. An older
|
|
`steam-desktop-autostart` user unit is removed;
|
|
- the **Return to Gaming Mode** shortcut; on plasma-login-manager with a
|
|
narrow sudoers rule (`/etc/sudoers.d/gamescope-session-switch`) so it can
|
|
restart the login manager without a password prompt.
|
|
|
|
Turning it off restores `/etc/plasmalogin.conf` from its backup, removes the
|
|
SDDM autologin, sync bridge, shortcut, sudoers rule and Steam autostart, and
|
|
switches back to plasma-login-manager.
|
|
|
|
The conversion also sets what Steam's System settings show, see
|
|
[below](#what-steams-system-settings-show).
|
|
|
|
**Steam Deck/Machine icons:** `STEAM_GAMEPADUI_ARGS="-gamepadui -steamos3"`
|
|
in `~/.config/environment.d/` (and gamescope-session's own environment
|
|
file), which makes Steam show Steam Deck button glyphs in gaming mode. It is a sub-option of the SteamOS conversion (`gaming`):
|
|
it does nothing without gaming mode, ticking it ticks the conversion and turning the conversion off turns it off.
|
|
|
|
## SteamOS desktop look
|
|
|
|
Installs CachyOS's `cachyos-vapor` package (the SteamOS Vapor theme the
|
|
CachyOS handheld edition uses) from the CachyOS repository and switches your
|
|
desktop to its Vapor global theme, including its desktop and window layout
|
|
(like ticking "Desktop and window layout" in System Settings): Vapor colors
|
|
and Plasma style, the SteamOS panel and launcher icon, and the Steam Deck
|
|
wallpaper. Run it from the Plasma desktop: applying the layout needs a
|
|
running Plasma session.
|
|
|
|
It also adds the SteamOS desktop extras that `cachyos-vapor` doesn't ship,
|
|
taken from the newest `steamdeck-kde-presets` on Valve's SteamOS mirror
|
|
(checksum verified) and installed to `/usr/local`:
|
|
|
|
- **Add to Steam** in the right-click menu of apps, AppImages and `.exe`
|
|
files, and in the launcher: adds them to Steam as non-Steam games;
|
|
- **Nested Desktop**: add it to Steam from the launcher, then start it in
|
|
gaming mode for a Plasma desktop inside gaming mode;
|
|
- the SteamOS **Return to Gaming Mode** icon (Steam logo with a return arrow);
|
|
- a window rule that keeps the **Steam keyboard** above other windows.
|
|
|
|
Turning it off restores your previous look and panel layout, and removes `cachyos-vapor` again
|
|
if the wizard installed it (and nothing else, like `cachyos-handheld`, needs it).
|
|
|
|
## What Steam's System settings show
|
|
|
|
With the SteamOS conversion on, `/etc/os-release` gets:
|
|
|
|
| Key | Value | Shown in Steam as |
|
|
|---|---|---|
|
|
| `VERSION_ID` | `steamos-3.9`: the newest SteamOS release in Valve's repo (offline: the previous value) | OS Version |
|
|
| `VERSION_CODENAME` | `steam-machine` | OS Codename |
|
|
| `VARIANT` / `VARIANT_ID` | `Steamify 2.9.0` / `steamify-2.9.0` | OS Variant (`VARIANT_ID`) |
|
|
|
|
The OS name stays CachyOS everywhere: Steam's OS Name comes from
|
|
`lsb_release -d`, which isn't touched, and `NAME`/`PRETTY_NAME` stay
|
|
CachyOS's. limine-snapper-sync finds the boot entries by that name, so
|
|
changing it would break snapshot boot entries; `/etc/default/limine` gets
|
|
`TARGET_OS_NAME="CachyOS"` as a guard when it has none.
|
|
|
|
KDE's About this System would show `VERSION_ID` after the name
|
|
("CachyOS Linux steamos-3.9"); `UseOSReleaseVersion=true` in
|
|
`~/.config/kcm-about-distrorc` makes it show `VERSION`, which CachyOS doesn't
|
|
have, so it keeps saying "CachyOS Linux".
|
|
|
|
CachyOS's own hooks (`cachyos-hooks`) reset os-release on updates of
|
|
`filesystem`; the pacman hook `zz-steamify-os-release` runs after them and
|
|
sets the keys again (`/usr/local/libexec/steamify-os-release`). The values are
|
|
refreshed after every Steamify run that changes something. Turning the
|
|
conversion off removes the hook and runs CachyOS's `cachyos-branding` again.
|
|
|
|
## Front LED bar (Steam Machine)
|
|
|
|
The **Steam Machine support** component, only shown on Fremont hardware
|
|
(DMI `Valve`/`Fremont`, or `OEM`/`F7F` on early units):
|
|
|
|
- installs an AUR helper (`yay`) if neither `yay` nor `paru` is present;
|
|
- installs the kernel headers for every installed kernel;
|
|
- installs `leds-valve-dkms-git` from the AUR - Valve's own driver from the
|
|
SteamOS kernel - builds it for every installed kernel (so the LTS kernel
|
|
works too), and loads it at every boot (`/etc/modules-load.d/leds-valve.conf`);
|
|
- adds a udev rule (`/etc/udev/rules.d/70-valve-leds-user.rules`) that gives
|
|
your user the LED files, so Steam, which runs as you, can drive the bar;
|
|
- console-like power button: it puts the machine to sleep, and it never
|
|
suspends by itself on mains power (the launcher's Shut Down still shuts down);
|
|
- installs and enables `steamos-manager`, the service Steam in gaming mode
|
|
uses for hardware settings (fan, performance, and the HDMI-CEC settings
|
|
when [HDMI-CEC](#hdmi-cec) is on); it recognises the Steam Machine from its
|
|
DMI data;
|
|
- makes the serial number readable for Steam, which shows it in its System
|
|
settings (`/etc/tmpfiles.d/steamify-serial.conf`: `product_serial` is
|
|
root-only by default);
|
|
- makes Steam's Wi-Fi backend setting work: steamos-manager switches it in
|
|
`/etc/NetworkManager/conf.d/99-valve-wifi-backend.conf`, which is created
|
|
with `wifi.backend=wpa_supplicant` (NetworkManager's default) when no
|
|
backend is set anywhere yet.
|
|
|
|
Turning it off removes all of that again (the AUR helper is kept).
|
|
|
|
The LEDs appear as `/sys/class/leds/valve-leds*`. The driver's Makefile
|
|
builds against the running kernel (`uname -r`) instead of the kernel DKMS
|
|
builds for, so the wizard adds a DKMS override
|
|
(`/etc/dkms/leds-valve-dkms.conf`) that passes DKMS's target kernel in. With
|
|
it, DKMS rebuilds the driver whenever a kernel or its headers are installed
|
|
or upgraded. A newly added kernel doesn't come with its headers, so
|
|
`ensure-kernel-headers.service` checks at every boot and installs any
|
|
missing `-headers` package, which makes DKMS build the driver for it.
|
|
|
|
## Extended controller support
|
|
|
|
`extended_controller_support` (`lib/controllers.sh`, top-level, off by default, 2.10.0) installs two kernel drivers from CachyOS's own
|
|
repo (no AUR): `xone-dkms` with `xone-dongle-firmware` for the Xbox wireless dongle (the kernel has no driver for it; wired
|
|
Xbox controllers go through the same driver), and `xpadneo-dkms` for Xbox One/Series controllers over Bluetooth (without it
|
|
they work as plain gamepads but miss rumble including the triggers, the right button mapping and the battery level). Both are
|
|
DKMS modules, built for every installed kernel, so `install_kernel_headers` (`lib/steam-machine.sh`) makes sure each kernel has its
|
|
headers first. A PC that already has the AUR's `xone-dkms-git` keeps it: it provides the same driver and conflicts with the repo's
|
|
`xone-dkms`. Status is the three packages being installed. Off removes only what Steamify installed (state
|
|
`extended_controller_support`, `installed_pkgs`); the headers stay. The drivers load when the device shows up: the dongle is
|
|
plugged in again, or the controller reconnected. Its test is in steamify-cachyos-dev (`tests/controllers-test.sh`).
|
|
|
|
## Gaming on NVIDIA
|
|
|
|
gamescope's own gaming mode shows a corrupted picture on NVIDIA graphics cards: NVIDIA's open bug 5240452 (flicker and
|
|
artifacts in the DRM session at modes above 2560x1440@120, seen up to driver 595 and on RTX 3070 Ti, 4090, 5090 and 5080;
|
|
no fix known). The kernel parameters `nvidia-drm.modeset=1`/`fbdev=1` and loading the NVIDIA modules early don't change it
|
|
(the driver already reports both `Y`), and gamescope started nested inside KDE artifacts too when KWin hands its fullscreen
|
|
window straight to the display. What does work on an RTX 5080: Steam's Big Picture (`steam -gamepadui`) as a normal window of
|
|
the Plasma desktop session, smooth and clean.
|
|
|
|
So with an NVIDIA GPU (PCI vendor 0x10de, display class, its driver providing `nvidia_drm`: `nvidia_present`) the SteamOS
|
|
conversion (`gaming`, and its options `boot`, `single`, `glyphs`) isn't offered, unless it is already on (so it can be turned
|
|
off). `nvidia` ("Gaming on NVIDIA", `lib/nvidia.sh`) takes its place, hidden while the conversion is on (both would start
|
|
Steam at login): it installs `steam` when missing (recorded in state `nvidia`, removed again only then) and enables the user
|
|
unit `steamify-steam-autostart.service` (`services/`, `ExecStart=/usr/bin/steam @ARGS@`, Plasma only). Its sub-option
|
|
`bigpicture` ("Steam starts in Big Picture", ticked along with it) rewrites the unit with `-gamepadui` and sets
|
|
`ksmserverrc [General] loginMode=emptySession` (`kset`, undone with `krevert bigpicture`), so windows of the last session
|
|
(Discord, a browser) aren't restored on top of Big Picture; unticked, Steam starts in its normal window. The opt-in sub-option `nvsilent` ("Start Steam silently") rewrites the unit with `-silent`
|
|
(tray only); it and `bigpicture` exclude each other. The PC always boots into the desktop: there is no gamescope session. Takes effect at the next
|
|
login.
|
|
|
|
On a supported card (RTX 20 series or newer: not on chwd's legacy lists `/var/lib/chwd/ids/nvidia-*.ids`, the VRAM booster's
|
|
check, `vram_nvidia_legacy_id`; `nvidia_supported`) enabling it also does what NVIDIA's DRM stack wants, though it didn't cure
|
|
gamescope: `nvidia-drm.modeset=1 nvidia-drm.fbdev=1` on the kernel command line (Limine, systemd-boot or GRUB; the file is
|
|
backed up first; on Limine into the existing `KERNEL_CMDLINE[default]="..."` or `+="..."` line, never an extra appended line,
|
|
which ends up as text; the kernel treats `nvidia_drm` and `nvidia-drm` alike) and
|
|
`/etc/mkinitcpio.conf.d/90-steamify-nvidia.conf` (`MODULES+=(nvidia nvidia_modeset nvidia_uvm nvidia_drm)`), then the initramfs
|
|
and boot entries are rebuilt and checked for the parameters. The early-load drop-in is only there while every installed kernel
|
|
has the NVIDIA modules (mkinitcpio fails on a missing module, and `limine-mkinitcpio` then skips that kernel's boot entry):
|
|
a pacman hook (`/etc/pacman.d/hooks/85-steamify-nvidia-initramfs.hook`, after DKMS, before the initramfs is built) runs
|
|
`/usr/local/libexec/steamify-nvidia-initramfs` at every kernel or driver change and decides again. Turning it off removes
|
|
all of it. The tests are in steamify-cachyos-dev: `tests/nvidia-test.sh` (a fake GPU, a stub `pacman` and `systemctl`, a temp home) and `tests/nvidia-hardware-test.sh` (a real NVIDIA PC).
|
|
|
|
Tried and dropped (notes in steamify-cachyos-dev, `nvidia/HARDWARE-RESULTS.md`): a gamescope session that opens Big Picture in Plasma (it needed the `start-gamescope-session`
|
|
command shadowed in `/usr/local/bin`, and showed the desktop for a few seconds, then a Steam window with a black border);
|
|
a KWin session without Plasma (the mouse stuttered every second or two).
|
|
|
|
## HDMI-CEC
|
|
|
|
The **HDMI-CEC** item (on every PC; ticked by default only on a Steam
|
|
Machine) installs
|
|
Valve's CEC stack from its SteamOS `holo` repository, the newest `holo-X.Y`
|
|
on `steamdeck-packages.steamos.cloud`, each package checked against the
|
|
SHA-256 in Valve's package index:
|
|
|
|
- `cecd`, Valve's CEC daemon (a user service started with the desktop and
|
|
gaming mode): TV remote keys become normal key presses (arrows, Enter,
|
|
Back), and it turns the TV on and off with the PC;
|
|
- `cec-audio-control`: volume of the TV or receiver (the TV remote's volume
|
|
keys); its user socket is enabled by Steamify, since SteamOS enables it
|
|
through a preset that pacman doesn't apply;
|
|
- `inputattach-cec-units` (with `linuxconsole` from CachyOS for
|
|
`inputattach`): attaches USB CEC adapters (Pulse-Eight, RainShadow).
|
|
|
|
It works with any `/dev/cec*`: a GPU that has CEC on its HDMI port (the
|
|
Steam Machine; the CachyOS kernel has DisplayPort CEC built in) or a USB
|
|
adapter ([CEC.md](CEC.md) lists which PCs have one). The menu lists the CEC
|
|
devices found. Also turn on CEC on the TV
|
|
(Sony: BRAVIA Sync, Samsung: Anynet+, LG: SimpLink). On a Steam Machine
|
|
`steamos-manager` writes cecd's settings from Steam's (wake the TV, put it to
|
|
sleep), in `~/.config/cecd/config.d/`, and Steam shows its HDMI-CEC
|
|
settings; the wizard restarts steamos-manager so that happens right away.
|
|
CachyOS's gaming mode script (`/usr/lib/steamos/gamescope-session`) sets
|
|
`STEAM_ENABLE_CEC=0`, which hides those settings; Steam reads the script's
|
|
variables from `$XDG_RUNTIME_DIR/gamescope-environment`. HDMI-CEC adds a
|
|
drop-in for `steam-launcher.service`
|
|
(`/etc/systemd/user/steam-launcher.service.d/10-steamify-cec.conf`) with a
|
|
second `EnvironmentFile=` (`/etc/steamify/steam-cec.env`,
|
|
`STEAM_ENABLE_CEC=1`), which overrides it from the next gaming mode start.
|
|
|
|
It's experimental: on SteamOS itself CEC can wake the Steam Machine right
|
|
after it goes to sleep (Samsung TVs,
|
|
[SteamOS#2626](https://github.com/ValveSoftware/SteamOS/issues/2626)) and
|
|
upset CEC for the TV's other devices
|
|
([SteamOS#2817](https://github.com/ValveSoftware/SteamOS/issues/2817)).
|
|
Turning it off removes the packages again (`linuxconsole` only if the
|
|
wizard installed it).
|
|
|
|
## Power-off fix (Steam Machine)
|
|
|
|
With recent kernels a Steam Machine starts again right after powering off:
|
|
the newer 6.x, 7.0 and 7.1 updates and 7.2 (and probably every kernel after
|
|
it). Linux stopped clearing the S4/S5 wake bits at probe
|
|
(`pinctrl-amd: Don't clear S4 wake bits at probe`, in 7.2 and backported to
|
|
stable kernels), and the firmware leaves
|
|
that bit set on GPIO pin 18 (`_SB.PCI0.GPP6`). Valve's own kernel
|
|
(`linux-neptune-72`) clears it at probe on Fremont, in a patch marked not
|
|
for upstream ("until the firmware is fixed"), so CachyOS and mainline won't
|
|
get it.
|
|
|
|
The **Power-off fix** sub-option of Steam Machine support (ticked along with
|
|
it, can be unticked) builds a small module with DKMS for every installed
|
|
kernel, `steamify-fremont-poweroff` (source in
|
|
[`patches/steamify-fremont-poweroff.c`](patches/steamify-fremont-poweroff.c)).
|
|
It only loads on Fremont (DMI board name) and touches one register: right
|
|
before power-off (a `SYS_OFF_MODE_POWER_OFF_PREPARE` handler, after the
|
|
drivers have shut down) it clears that pin's S4/S5 wake bit. On a kernel
|
|
that already clears it, it does nothing. It logs the pin's state at load:
|
|
`dmesg | grep 'GPIO 18'`; `/sys/kernel/debug/gpio` shows the S4/S5 column.
|
|
Tested on a Steam Machine with `linux-cachyos-bore` 7.2.8: it stayed off
|
|
three times in a row, and rebooted right away with the module unloaded.
|
|
|
|
Where Steam Machine support was set up before 2.2.0, the menu ticks the
|
|
power-off fix as a new default sub-option (see feature versions below), so a
|
|
normal run adds it.
|
|
|
|
## Add as non-Steam game
|
|
|
|
A sub-option of the Steamify shortcut (`steamgame`, `lib/steam-game.sh`, new
|
|
in 2.5.1, ticked along with the shortcut): Steamify CachyOS in every Steam
|
|
account's non-Steam games (`~/.local/share/Steam/userdata/<id>/config/shortcuts.vdf`,
|
|
binary VDF, edited by `patches/steam-shortcuts.py`), starting the shortcut's
|
|
`run-app`, with the icon rendered as PNG (`~/.local/share/steamify/steamify.png`;
|
|
Steam shows no SVG). Why: while Steam runs, a Steam Controller only talks to
|
|
Steam, which gives its input to the game it started (Steam Input); on the
|
|
desktop through its desktop layout (A = Enter, B = Esc, Y = Space, X =
|
|
Steam's keyboard), in gaming mode as a gamepad.
|
|
|
|
Steam reads the list only at startup and writes it back on exit, so Steam is
|
|
closed (`steam -shutdown`) for the edit and started again (`steam -silent`). That's refused in gaming
|
|
mode and when Steamify itself was started from Steam (`SteamGameId`), since
|
|
closing Steam would end it. An entry for the start script that's already
|
|
there (added by hand, or with the path from before 2.5.0) is updated, not
|
|
added twice; what was done is recorded per account (state `steamgame`:
|
|
`<account>=<appid> added|updated`) and a copy of each list is kept in the
|
|
state directory before the first change. Turning it off (or the shortcut)
|
|
removes every entry for the start script, since it can't start anything
|
|
without it.
|
|
|
|
Started from Steam, the app re-executes itself without Steam's overlay
|
|
(`LD_PRELOAD` of `gameoverlayrenderer.so` makes Qt abort) and
|
|
`steamify-app.sh` keeps it in the foreground (`SteamGameId`), or Steam
|
|
counts the game as ended right away and stops giving it the controller.
|
|
|
|
## Update notifications
|
|
|
|
The **Update notifications** option (new in 2.5.0, ticked by default)
|
|
installs `patches/steamify-notifier.py` as
|
|
`~/.local/share/steamify/bin/steamify-notifier` with two user
|
|
units in `~/.config/systemd/user`: `steamify-update-check.timer` (daily,
|
|
`Persistent=`) and `steamify-update-check.service` (also wanted by
|
|
`plasma-workspace.target`, so it checks a minute after each desktop login).
|
|
Nothing stays running while there's no update.
|
|
|
|
Every Steamify run records its version as `seen` (`notify.state`). The check
|
|
asks GitHub for the newest release; when it's newer than `seen` and not the
|
|
`skipped` version, it shows a notification (`notify-send` with the buttons
|
|
**Open Steamify** and **Skip this version**) and a tray icon (click: open;
|
|
menu: open, skip, remind me later). Closing the notification keeps the
|
|
icon; opening Steamify in any way records the new version and ends it. It
|
|
only runs on the Plasma desktop: gamescope shows no notifications or tray,
|
|
so an update found in gaming mode waits for the next desktop login. It never
|
|
installs anything: the shortcut always starts the newest release, which then
|
|
shows what's new or updated for this install.
|
|
|
|
## VRAM booster
|
|
|
|
SteamOS 3.9 manages the dGPU's VRAM per cgroup: the game in front is
|
|
protected, background apps are evicted first. Without it a game that needs
|
|
most of the VRAM (8 GB on the Steam Machine) can be pushed into system RAM
|
|
by the desktop and other apps, and stutter. The kernel side is the `dmem`
|
|
cgroup controller (7.2); amdgpu and Intel's xe register their VRAM with it
|
|
(`drm/<pci>/vram`), NVIDIA's open kernel modules from driver 615 too
|
|
(`nvidia/<pci>/vidmem`); `dmemcg-booster` protects every region it lists,
|
|
whatever its name. NVIDIA's closed modules don't: with such a card the option is shown greyed
|
|
out with what to do (`vram_nvidia_case`): open modules older than 615 ->
|
|
update; the closed driver on a card the open one supports (not in chwd's
|
|
legacy lists `/var/lib/chwd/ids/nvidia-{580,470,390}.ids`) -> the `chwd`
|
|
commands to switch, never switched by Steamify itself (a failed switch
|
|
means a black screen, and it can't be tested here); a legacy card or
|
|
nouveau -> not supported. Shown greyed
|
|
out, with why, until its driver lists a region: then it's offered like any
|
|
other (`WIZARD_VRAM_FAKE_NVIDIA=1` fakes the grey-out, `WIZARD_VRAM_CAPACITY=<file>`
|
|
reads the regions from a copy, for tests). Otherwise
|
|
the **VRAM booster** option is only shown when
|
|
`/sys/fs/cgroup/dmem.capacity` lists a VRAM region (`vram`, or `vidmem`
|
|
as other drivers name it, numbered with several) of at least 2 GB (a
|
|
dedicated GPU; an integrated one registers a small carve-out) or it's already on, and is
|
|
ticked by default; new in 2.3.0, so setups with the SteamOS conversion on
|
|
get it ticked. CachyOS packages the userspace side, which it installs:
|
|
|
|
- `dmemcg-booster`: `dmemcg-booster-system.service` enables the `dmem`
|
|
controller and sets `dmem.low` for the cgroup in front;
|
|
`dmemcg-booster-user.service` runs in the user session.
|
|
- `plasma-foreground-booster`: tells it which window is in front on the
|
|
desktop. It only starts with `autostart=true` under `[Foreground Booster]`
|
|
in `kcgroupsrc`, which the wizard sets (and reverts).
|
|
|
|
Check it with `cat /sys/fs/cgroup/cgroup.subtree_control` (lists `dmem`) and
|
|
the `dmem.low` of the slices under `user@<uid>.service`. Turning it off
|
|
disables the services and removes the packages the wizard installed.
|
|
Not added: a fixed `dmem.max` cap for `app.slice` (as some guides do); only
|
|
worth it if the booster itself turns out to cause stutter.
|
|
|
|
## Feature versions (updates)
|
|
|
|
Each component has a feature version (`FEATURE_VERSION` in `lib/menu.sh`):
|
|
the Steamify version in which what it sets up last changed (2.1.0 for
|
|
everything that hasn't changed since). After a component is turned on
|
|
successfully, that version is recorded in `~/.local/state/steamify/features.state`
|
|
(turning it off records `off`). Whether a component is on is always checked
|
|
on the system itself; the version only decides about updates:
|
|
|
|
- **On, recorded version older** than the current one: ticked and
|
|
re-applied by a normal run; the plan (and the app's review) says
|
|
"update". A setup from before versions were recorded counts as 2.1.0,
|
|
so on a machine set up with 2.1.0, Steam Machine support (2.2.0) is
|
|
updated.
|
|
- **A new default option** (not in `NO_PRESELECT`) whose parent is on (for
|
|
a top-level one: the SteamOS conversion) and that was never turned on or
|
|
off: ticked, so a normal run adds it. Turning it off once records `off`,
|
|
so it isn't ticked again. The menu and the app show it with a **New**
|
|
badge, and the plan says "new in this version".
|
|
|
|
Set a component's entry to the new `VERSION` whenever what its
|
|
`<id>_enable` sets up changes.
|
|
|
|
## BIOS updates (Steam Machine)
|
|
|
|
The **Update BIOS** item is only shown on a Steam Machine and is never ticked
|
|
by default. It shows the BIOS version you have now and the newest one Valve
|
|
ships (the `.cab` file in its `fremont-hw-support` package, looked up on
|
|
Valve's SteamOS mirror):
|
|
|
|
```
|
|
[ ] Update BIOS: now F7F0107, newest F7F0108 (own risk) (opt-in, runs once)
|
|
```
|
|
|
|
It can only be ticked when Valve has a newer BIOS than yours; when you're up
|
|
to date, when the newest version can't be looked up (offline), or when an
|
|
update is already waiting for a restart, it's greyed out.
|
|
|
|
When you run it, the wizard:
|
|
|
|
1. downloads Valve's package and checks its **SHA-256** against Valve's
|
|
repository, so it's exactly Valve's file;
|
|
2. asks **fwupd** whether the firmware is for this very machine (fwupd compares
|
|
the firmware's hardware IDs with the device) and stops if it isn't;
|
|
3. shows a large red **warning** with the current and the new version, and asks
|
|
whether you understand the risks (default: no);
|
|
4. shows the warning **again** and only continues when you type `UPDATE`;
|
|
5. hands the firmware to fwupd, which writes it during the **next restart**:
|
|
choose "restart now" or restart later yourself.
|
|
|
|
**At your own risk:** a failed or interrupted BIOS update can leave the machine
|
|
unable to start. Keep it on mains power, and never turn off the power, unplug
|
|
it or press the power button while it updates, including during the restart;
|
|
the screen can stay black for several minutes.
|
|
|
|
To walk through it without flashing anything, run the wizard with
|
|
`WIZARD_BIOS_DRY_RUN=1`: it downloads and checks the package and shows both
|
|
warnings, skips fwupd's device check, and only prints the install command.
|
|
|
|
## Where Steamify keeps its files
|
|
|
|
In the home folder, everything is under `steamify`:
|
|
|
|
| Where | What |
|
|
|---|---|
|
|
| `~/.local/state/steamify/` | State (`*.state`), undo journals (`*.journal`), the saved panel layout (`theme-layout/`) |
|
|
| `~/.local/share/steamify/app/` | The app (`steamify-app.sh` downloads it here) |
|
|
| `~/.local/share/steamify/bin/` | The shortcut's start scripts (`run-app`, `run-wizard`) and `steamify-notifier` |
|
|
| `~/.local/share/icons/hicolor/scalable/apps/steamify.svg` | The shortcut's icon |
|
|
| `~/.local/share/applications/steamify-*.desktop` | The launcher entries |
|
|
| `~/.config/autostart/steam.desktop` | the `silent` option (Steam's "Run Steam when my computer starts") |
|
|
| `~/.config/systemd/user/` | `steamify-update-check.*` |
|
|
|
|
System-wide: `/usr/local/lib/steamify/` (kernel headers script),
|
|
`/usr/src/steamify-*` (DKMS modules), `/var/cache/steamify/kernel` (the old
|
|
kernel pin's packages), and `*.bak-gamescope-wizard` backups next to the
|
|
system files that were edited (that name stays: it's how they're found again).
|
|
|
|
Before 2.5.0 the home folder paths used the project's old name
|
|
(`cachyos-gamescope-boot`). `migrate_layout` (`lib/state.sh`) moves them at
|
|
every start of a newer version, once, and leaves symlinks under the old names
|
|
so an older release still finds the same state; it fixes the paths in the
|
|
launcher entries and the update check's unit, and merges when both exist.
|
|
The headers script moves when Steam Machine support is set up again.
|
|
|
|
## Manual session control
|
|
|
|
```bash
|
|
steamos-session-select gamescope # switch to gamescope right now
|
|
steamos-session-select plasma # switch to desktop right now
|
|
steamos-session-select persistent # remember the last-used session across reboots
|
|
steamos-session-select oneshot # always start in gamescope (default, like a Deck)
|
|
```
|
|
|
|
`steamify.sh --boot gamescope|desktop` sets where the PC starts after a
|
|
restart (the menu's Boot into row) from a script: it needs the SteamOS
|
|
conversion and changes nothing else, not even the updates a normal run would
|
|
apply. Exit code 1 without the conversion or with another value.
|
|
|
|
## Install-time mode (`--defaults`)
|
|
|
|
For an installer (the Steam Machine ISO), without a session or prompts
|
|
(passwordless sudo required):
|
|
|
|
```bash
|
|
steamify.sh --defaults # what the menu would preselect
|
|
steamify.sh --defaults --options gaming,theme,cec,machine --boot desktop
|
|
steamify.sh --defaults --list # what --defaults can set up here, as JSON
|
|
```
|
|
|
|
`--options` turns on exactly the listed items (the menu's ids: `gaming`,
|
|
`theme`, `glyphs`, `single`, `launcher`, `notify`, `vram`, `cec`, `machine`,
|
|
`poweroff`), everything else off; an item that's on brings its parent
|
|
(single user the conversion, the power-off fix Steam Machine support). An
|
|
item this PC can't use (e.g. `vram` without a VRAM region) is left off with
|
|
a warning. `--boot desktop` needs `gaming`. Unknown options, ids, `boot` or
|
|
`bios` in the list: exit code 1 before anything changes. What needs a
|
|
Plasma session waits for the first login (`lib/first-login.sh`). The same step (and the online theme path) replaces Plasma's default taskbar pins, which include Discover
|
|
(not installed on CachyOS: a blank icon), by CachyOS's own (`taskbar_drop_discover` in `lib/vapor-theme.sh`; skipped when Discover is
|
|
installed or the taskbar already has its own `launchers`).
|
|
|
|
With `Relogin=true`, a gamescope session that fails to start is restarted
|
|
immediately, which can turn into a loop - see
|
|
[TROUBLESHOOTING.md](TROUBLESHOOTING.md) for the way out.
|
|
|
|
## Project layout
|
|
|
|
| File | Responsibility |
|
|
|---|---|
|
|
| `steamify.sh` | Entry point: checks, menu, apply, summary, restart |
|
|
| `lib/menu.sh` | The menu: detect, toggle, plan and apply changes |
|
|
| `lib/state.sh` | Undo journal for KDE settings (`kset`/`krevert`) |
|
|
| `lib/common.sh` | Output helpers, prompts, backups, plasmashell handling |
|
|
| `lib/packages.sh` | Required packages, AUR helper (yay/paru) |
|
|
| `lib/boot-session.sh` | Boot into gamescope or the desktop (`steamify-boot-desktop.service`) |
|
|
| `lib/login-manager.sh` | SteamOS conversion: SDDM or plasmalogin autologin, sync bridge |
|
|
| `lib/steam-desktop.sh` | Steam in the Plasma session; Steam Deck/Machine icons |
|
|
| `lib/desktop-shortcut.sh` | Return to Gaming Mode shortcut and its sudoers rule |
|
|
| `lib/vapor-theme.sh` | SteamOS theme: installs and switches to `cachyos-vapor` |
|
|
| `lib/steamos-extras.sh` | SteamOS desktop extras from Valve's package (Add to Steam, Nested Desktop, icon, keyboard rule, KWallet) |
|
|
| `lib/single-user.sh` | Single user mode: no lock screen, user switching or log out |
|
|
| `lib/wizard-shortcut.sh` | Steamify shortcut: desktop icon and launcher entry that run the newest release |
|
|
| `lib/bios.sh` | Update BIOS (Steam Machine, opt-in): current/newest version, double confirmation, fwupd |
|
|
| `lib/cec.sh` | HDMI-CEC: Valve's `cecd` and friends from its `holo` repository |
|
|
| `lib/steam-machine.sh` | Steam Machine support: LED driver, LED access, steamos-manager |
|
|
| `lib/fremont-poweroff.sh` | Steam Machine support: the power-off fix (DKMS module from `patches/`) |
|
|
| `lib/steam-game.sh` | Add as non-Steam game: Steamify in the Steam library (`patches/steam-shortcuts.py`) |
|
|
| `lib/update-notifier.sh` | Update notifications: the notifier from `patches/` and its user timer |
|
|
| `lib/first-login.sh` | `--defaults` without a session (the Steam Machine ISO's installer): the one-time first-login step that sets up single user's launcher on Plasma's new layout and opens the app |
|
|
| `lib/controllers.sh` | Extended controller support: Xbox wireless dongle (xone) and Xbox Bluetooth (xpadneo) from the CachyOS repo |
|
|
| `lib/nvidia.sh` | Gaming on NVIDIA: Steam on the desktop, started at login, optionally in Big Picture (replaces the SteamOS conversion there) |
|
|
| `lib/vram-booster.sh` | VRAM booster (`dmemcg-booster`, `plasma-foreground-booster`) |
|
|
| `services/` | The systemd units the scripts install (`service_file`, `@KEY@` placeholders); see its README |
|
|
| `patches/` | Module sources and patches the scripts build or apply (`patch_file`); see its README |
|
|
| `.github/tools/bundle.sh` | Builds the single-file version (`dist/steamify.sh`), with `patches/` and `services/` embedded |
|
|
| `.github/workflows/bundle.yml` | Builds and checks it on every push (GitHub and a Gitea mirror); publishes it on `main`, and starts the Steamify ISO's release |
|
|
|
|
The single-file version is generated: on every push to `main`, GitHub
|
|
Actions runs `.github/tools/bundle.sh`, checks the result with `bash -n` and
|
|
shellcheck, and publishes it as a release per version (tag `v<VERSION>`,
|
|
never overwritten; the newest is marked latest, and the `latest` tag follows
|
|
it). It inlines
|
|
`lib/*.sh` and wraps the entry point in `main()`, so bash has read the whole
|
|
file before anything runs; when stdin is a pipe (`curl | bash`) it reattaches
|
|
the terminal for the menu (set `WIZARD_KEEP_STDIN=1` to keep piped input).
|
|
|
|
Notes for contributors and AI coding agents are in [`AGENTS.md`](AGENTS.md).
|
|
|
|
## Caveats
|
|
|
|
- This works around CachyOS/`plasma-login-manager` behavior as of September
|
|
2026. If CachyOS fixes `steam-set-session` upstream, parts of this script
|
|
become unnecessary - please open an issue or PR if you notice that.
|
|
- Tested on a Valve Steam Machine running CachyOS Desktop edition. It should
|
|
work on any CachyOS desktop install using `plasma-login-manager`, but
|
|
hasn't been tested on Steam Deck/Legion Go hardware.
|
|
|
|
## Related upstream reports
|
|
|
|
- [CachyOS/gamescope-session#9](https://github.com/CachyOS/gamescope-session/issues/9) - "Switch to Desktop" hang due to missing `/etc/plasmalogin.conf.d`
|