From 443dbcdfb81fbd889432563652d56af951a8f3b2 Mon Sep 17 00:00:00 2001 From: rickpeters Date: Sun, 27 Sep 2026 12:13:15 +0200 Subject: [PATCH 01/10] feat: Power-off fix for the Steam Machine; the kernel pin is optional Kernels >= 7.2 keep the firmware's S4/S5 wake bit on GPIO pin 18, so the Steam Machine starts again right after powering off. Steam Machine support now builds a DKMS module (patches/steamify-fremont-poweroff.c) that clears it before power-off, for every kernel. The kernel pin is no longer ticked by default. Module sources and patches move to patches/, embedded by the single-file build. --- .github/tools/bundle.sh | 17 ++++++ .github/workflows/bundle.yml | 4 +- AGENTS.md | 14 +++++ CHANGELOG.md | 17 ++++++ README.md | 12 ++-- TECHNICAL.md | 37 +++++++++++-- lib/cec.sh | 13 ++--- lib/common.sh | 3 + lib/fremont-poweroff.sh | 57 +++++++++++++++++++ lib/menu.sh | 10 ++-- lib/steam-machine.sh | 27 ++++++--- patches/README.md | 10 ++++ patches/cros-ec-cec-single-port.patch | 21 +++++++ patches/steamify-fremont-poweroff.c | 80 +++++++++++++++++++++++++++ steamify.sh | 4 +- ui/qml/Main.qml | 10 ++-- ui/steamify-app.sh | 2 +- 17 files changed, 297 insertions(+), 41 deletions(-) create mode 100644 lib/fremont-poweroff.sh create mode 100644 patches/README.md create mode 100644 patches/cros-ec-cec-single-port.patch create mode 100644 patches/steamify-fremont-poweroff.c diff --git a/.github/tools/bundle.sh b/.github/tools/bundle.sh index 1fddd13..5812723 100755 --- a/.github/tools/bundle.sh +++ b/.github/tools/bundle.sh @@ -31,6 +31,23 @@ version="v$(grep -oP '^VERSION=\K.*' "$entry"), $(git describe --always --dirty sed -e '1{/^#!/d}' -e '/^# Sourced by steamify.sh/d' "lib/$lib.sh" echo done + # patches/ embedded: replaces lib/common.sh's patch_file, which reads + # them from the checkout. + echo "# ----- patches/ -----" + echo "patch_file() {" + echo ' case "$1" in' + for f in patches/*; do + [[ "$f" == patches/README.md ]] && continue + grep -q '^STEAMIFY_PATCH_EOF$' "$f" && { echo "bundle: $f contains the heredoc delimiter" >&2; exit 1; } + echo " $(basename "$f")) cat << 'STEAMIFY_PATCH_EOF'" + cat "$f" + echo "STEAMIFY_PATCH_EOF" + echo " ;;" + done + echo ' *) return 1 ;;' + echo ' esac' + echo "}" + echo echo "# ----- steamify.sh -----" echo "main() {" # Everything after the source loop is the entry point's body. diff --git a/.github/workflows/bundle.yml b/.github/workflows/bundle.yml index f45e092..9700290 100644 --- a/.github/workflows/bundle.yml +++ b/.github/workflows/bundle.yml @@ -6,7 +6,7 @@ # curl -fsSL https://github.com/theupriser/steamify-cachyos/releases/latest/download/steamify.sh | bash # curl -fsSL https://github.com/theupriser/steamify-cachyos/releases/download/latest/steamify.sh | bash # Each release also has the app (v2): steamify-app.tar.gz (ui/, steamify.sh, -# lib/, the icon) and its launcher, run with: +# lib/, patches/, the icon) and its launcher, run with: # curl -fsSL https://github.com/theupriser/steamify-cachyos/releases/latest/download/steamify-app.sh | bash # A push to feat/v2-ui (the app, not released yet) updates the pre-release # v2-ui-preview instead, whose launcher gets that build: @@ -36,7 +36,7 @@ jobs: .github/tools/bundle.sh dist/steamify.sh # The app: sources, not the single-file build (its backend mode runs # steamify.sh from its own folder). - tar -czf dist/steamify-app.tar.gz steamify.sh lib ui assets/steam-gaming-settings.svg LICENSE.md + tar -czf dist/steamify-app.tar.gz steamify.sh lib patches ui assets/steam-gaming-settings.svg LICENSE.md cp ui/steamify-app.sh dist/steamify-app.sh - name: Check diff --git a/AGENTS.md b/AGENTS.md index 376e54f..d4a2f48 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -15,6 +15,10 @@ gamescope and the Plasma desktop. Primary target: the Valve Steam Machine - `steamify.sh` - the only entry point. Pre-flight checks, sources `lib/*.sh`, runs the menu and applies the plan. No component logic here. +- `patches/` - kernel module sources and patches the scripts build or + apply, never inline in the shell code. Read them with `patch_file + ` (`lib/common.sh`, from the checkout); the bundle embeds every file + in `patches/` and overrides `patch_file`. Add new ones to its README. - `lib/*.sh` - one file per responsibility, each defining functions only (no top-level side effects besides constants). See the table in `TECHNICAL.md`. @@ -204,6 +208,16 @@ gamescope and the Plasma desktop. Primary target: the Valve Steam Machine kernel only pairs that with a named lookup ("Port C") when the CEC driver registered first, which never happens since amdgpu loads from the initramfs. Without it `/dev/cec0` exists but stays at `f.f.f.f`. +- Power-off fix (`lib/fremont-poweroff.sh`, part of `machine`): kernels + >= 7.2 keep the firmware's S4/S5 wake bit on GPIO pin 18, so the Steam + Machine boots again right after powering off (Valve's kernel clears it at + probe, not for upstream). The DKMS module `steamify-fremont-poweroff` + (`patches/steamify-fremont-poweroff.c`) clears it in a power-off-prepare + handler, for every kernel. It made the kernel pin optional (`kpin` is in + `NO_PRESELECT`, no longer ticked with `machine`). `machine_repair` ticks + `machine` for setups without it. CachyOS's `linux-cachyos` is clang-built, + `-bore` GCC-built: let DKMS pick the compiler, never pass `LLVM=1`. Test + shutdown on the real machine for every new major kernel. - `Relogin=true` means a gamescope that fails to start is relaunched in a tight loop; keep that in mind when changing session handling. diff --git a/CHANGELOG.md b/CHANGELOG.md index b6b39fa..9d3d223 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,23 @@ All notable changes, per version and per commit. Versions follow `steamify.sh`. Versions before 0.7.0 were numbered afterwards, one per merged pull request. +## 2.2.0 - 2026-09-27 + +- **feat: Power-off fix for the Steam Machine; the kernel pin is optional** + - With kernels 7.2 and newer the Steam Machine started again right after + shutting down: Linux 7.2 keeps the wake bit the firmware sets on GPIO + pin 18, which Valve's own kernel clears in a patch that won't reach + CachyOS. Steam Machine support now builds a small module with DKMS for + every kernel that clears it right before power-off. Tested with + CachyOS 7.2.8: stays off, and without the module it rebooted. + - The kernel pin is no longer ticked with Steam Machine support or on a + first run; it stays available (HDMI refresh boost needs it). An + existing pin is kept. Steam Machine support from before 2.2.0 is + ticked in the menu to add the fix. + - Module sources and patches live in `patches/` instead of inside the + shell code; the single-file build embeds them. The Steam Machine CEC + driver change is now `patches/cros-ec-cec-single-port.patch`. + ## 2.1.0 - 2026-09-26 - **feat(ui): Manage button for HDMI refresh boost** diff --git a/README.md b/README.md index 460fa37..f918868 100644 --- a/README.md +++ b/README.md @@ -43,11 +43,13 @@ What Steamify can do on a Valve Steam Machine: TV's remote, and the TV turns on and off with the Steam Machine ([details](TECHNICAL.md#hdmi-cec)). - **Steam Machine support** - the front **LED bar** works, Steam's - **hardware settings** (fan, performance) work, and the power - button puts it to sleep like a console. - - **Pin the kernel to 7.1.6-1** - on by default: with newer CachyOS kernels - the Steam Machine reboots instead of shutting down. Untick it once CachyOS - fixes that. The kernel comes from its own + **hardware settings** (fan, performance) work, the power button puts it + to sleep like a console, and **shutting down really turns it off**, also + with the newest CachyOS kernel + ([details](TECHNICAL.md#power-off-fix-steam-machine)). + - **Pin the kernel to 7.1.6-1** - optional, off by default: stays on an + older kernel (HDMI refresh boost needs it). It used to be needed for + shutting down; the power-off fix above covers that now. The kernel comes from its own [release](https://github.com/theupriser/steamify-cachyos/releases/tag/kernel-7.1.6-1), checked against CachyOS's signature ([details](TECHNICAL.md#kernel-pin-steam-machine)). diff --git a/TECHNICAL.md b/TECHNICAL.md index 73d4285..22a2255 100644 --- a/TECHNICAL.md +++ b/TECHNICAL.md @@ -208,11 +208,36 @@ upset CEC for the TV's other devices Turning it off removes the packages again (`linuxconsole` only if the wizard installed it). +## Power-off fix (Steam Machine) + +With kernels 7.2 and newer a Steam Machine starts again right after +powering off. Linux 7.2 stopped clearing the S4/S5 wake bits at probe +(`pinctrl-amd: Don't clear S4 wake bits at probe`), 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. + +Steam Machine support 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. + +Steam Machine support set up before 2.2.0 has no power-off fix; the menu +ticks it, so a normal run adds it. + ## Kernel pin (Steam Machine) -With CachyOS kernels newer than 7.1.6 a Steam Machine reboots instead of -shutting down. The **Pin the kernel** sub-option (ticked along with Steam -Machine support) installs `linux-cachyos` and `linux-cachyos-headers` +With CachyOS kernels newer than 7.1.6 a Steam Machine rebooted instead of +shutting down; the power-off fix above handles that now, so the pin is +optional. The **Pin the kernel** sub-option (off by default; HDMI refresh +boost needs it) installs `linux-cachyos` and `linux-cachyos-headers` 7.1.6-1 and adds them to `IgnorePkg` in `/etc/pacman.conf`, so updates skip them. DKMS builds the LED driver for it; restart to boot it. @@ -356,8 +381,10 @@ immediately, which can turn into a loop - see | `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; kernel pin | -| `lib/hdmi-refresh.sh` | HDMI refresh boost (Steam Machine, pinned kernel): EDID over DDC, calculated steps, live test, `drm.edid_firmware` | -| `.github/tools/bundle.sh` | Builds the single-file version (`dist/steamify.sh`) | +| `lib/fremont-poweroff.sh` | Steam Machine support: the power-off fix (DKMS module from `patches/`) | +| `lib/hdmi-refresh.sh` | HDMI refresh boost (Steam Machine, pinned kernel): EDID over DDC, calculated steps, live test, per-display EDID hotplug script | +| `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/` embedded | | `.github/workflows/bundle.yml` | Builds and checks it on every push; publishes it on `main` | The single-file version is generated: on every push to `main`, GitHub diff --git a/lib/cec.sh b/lib/cec.sh index d6f40c2..e8e4724 100644 --- a/lib/cec.sh +++ b/lib/cec.sh @@ -60,8 +60,8 @@ cec_reload_driver() { cec_driver_enable() { detect_valve_fremont || return 0 local tmp k - if ! pacman -Q dkms >/dev/null 2>&1; then - sudo pacman -S --needed --noconfirm dkms || { err "Installing dkms failed."; return 1; } + if ! pacman -Q dkms patch >/dev/null 2>&1; then + sudo pacman -S --needed --noconfirm dkms patch || { err "Installing dkms failed."; return 1; } fi install_kernel_headers || return 1 tmp="$(mktemp -d)" @@ -72,13 +72,8 @@ cec_driver_enable() { err "Downloading Valve's CEC driver failed (or its checksum didn't match)." return 1 fi - # amdgpu registers its HDMI notifier without a port name, which only - # matches the driver's named lookup ("Port C") if the driver loaded - # first; amdgpu loads from the initramfs, so it never does. With a - # single CEC port, look it up by device alone. - sed -i 's/cec_notifier_cec_adap_register(hdmi_dev, conns\[port_num\],/cec_notifier_cec_adap_register(hdmi_dev, conns[1] ? conns[port_num] : NULL,/' \ - "$tmp/cros-ec-cec.c" - grep -q 'conns\[1\] ? conns\[port_num\] : NULL' "$tmp/cros-ec-cec.c" || + # So it finds amdgpu's HDMI port (see the patch for why). + patch_file cros-ec-cec-single-port.patch | patch -s -d "$tmp" -p1 || { rm -rf "$tmp"; err "Patching Valve's CEC driver failed."; return 1; } echo 'obj-m += cros-ec-cec.o' >"$tmp/Makefile" printf '%s\n' "# Written by Steamify: Valve's cros_ec_cec, which knows the Steam Machine." \ diff --git a/lib/common.sh b/lib/common.sh index 4ad8495..dfcf60e 100644 --- a/lib/common.sh +++ b/lib/common.sh @@ -6,6 +6,9 @@ c_reset="\033[0m"; c_bold="\033[1m"; c_green="\033[32m"; c_yellow="\033[33m"; c_ info() { echo -e "${c_cyan}[INFO]${c_reset} $*"; } ok() { echo -e "${c_green}[OK]${c_reset} $*"; } +# patch_file : a file from patches/ (module sources, patches). The +# single-file build replaces this with one that has them embedded. +patch_file() { cat "$SCRIPT_DIR/patches/$1"; } warn() { echo -e "${c_yellow}[WARN]${c_reset} $*"; } err() { echo -e "${c_red}[ERROR] $*${c_reset}" >&2; } ask_yn() { diff --git a/lib/fremont-poweroff.sh b/lib/fremont-poweroff.sh new file mode 100644 index 0000000..991dbba --- /dev/null +++ b/lib/fremont-poweroff.sh @@ -0,0 +1,57 @@ +#!/bin/bash +# Part of "Steam Machine support": the Steam Machine powers off instead of +# booting up again, on every kernel. Since Linux 7.2 ("pinctrl-amd: Don't +# clear S4 wake bits at probe") the S4/S5 wake bit the firmware leaves set +# on GPIO pin 18 stays set, and the machine starts again right after +# powering off (the reason for the kernel pin). Valve's kernel clears it at +# probe on Fremont, in a patch not meant for upstream, so CachyOS won't get +# it. A small module (patches/steamify-fremont-poweroff.c) clears it right +# before power-off; DKMS builds it for every kernel. On kernels that clear it themselves it does nothing. +# Sourced by steamify.sh; not meant to be run on its own. + +POWEROFF_DKMS_NAME=steamify-fremont-poweroff +POWEROFF_DKMS_VER=1 +POWEROFF_DKMS_SRC="/usr/src/$POWEROFF_DKMS_NAME-$POWEROFF_DKMS_VER" +POWEROFF_MODULES_LOAD=/etc/modules-load.d/steamify-fremont-poweroff.conf + +poweroff_fix_installed() { [[ -f "$POWEROFF_DKMS_SRC/dkms.conf" && -f "$POWEROFF_MODULES_LOAD" ]]; } + +poweroff_fix_enable() { + detect_valve_fremont || return 0 + local tmp k + if ! pacman -Q dkms >/dev/null 2>&1; then + sudo pacman -S --needed --noconfirm dkms || { err "Installing dkms failed."; return 1; } + fi + install_kernel_headers || return 1 + tmp="$(mktemp -d)" + patch_file "$POWEROFF_DKMS_NAME.c" > "$tmp/$POWEROFF_DKMS_NAME.c" + echo "obj-m += $POWEROFF_DKMS_NAME.o" >"$tmp/Makefile" + printf '%s\n' "# Written by Steamify: the Steam Machine powers off instead of booting up again." \ + "PACKAGE_NAME=\"$POWEROFF_DKMS_NAME\"" "PACKAGE_VERSION=\"$POWEROFF_DKMS_VER\"" \ + "BUILT_MODULE_NAME[0]=\"$POWEROFF_DKMS_NAME\"" 'DEST_MODULE_LOCATION[0]="/updates/dkms"' \ + 'AUTOINSTALL="yes"' >"$tmp/dkms.conf" + sudo dkms remove "$POWEROFF_DKMS_NAME/$POWEROFF_DKMS_VER" --all >/dev/null 2>&1 + sudo rm -rf "$POWEROFF_DKMS_SRC" + sudo install -d "$POWEROFF_DKMS_SRC" && + sudo install -m644 "$tmp"/{"$POWEROFF_DKMS_NAME.c",Makefile,dkms.conf} "$POWEROFF_DKMS_SRC/" + rm -rf "$tmp" + sudo dkms add "$POWEROFF_DKMS_NAME/$POWEROFF_DKMS_VER" >/dev/null || + { err "Adding the power-off fix to DKMS failed."; return 1; } + for k in /usr/lib/modules/*/build; do + k="$(basename "$(dirname "$k")")" + info "Building the power-off fix for $k..." + sudo dkms install "$POWEROFF_DKMS_NAME/$POWEROFF_DKMS_VER" -k "$k" >/dev/null || + warn "Building the power-off fix for $k failed; with that kernel it may start again after shutting down." + done + echo "$POWEROFF_DKMS_NAME" | sudo tee "$POWEROFF_MODULES_LOAD" >/dev/null + sudo modprobe "$POWEROFF_DKMS_NAME" 2>/dev/null + return 0 +} + +poweroff_fix_disable() { + sudo rm -f "$POWEROFF_MODULES_LOAD" + sudo modprobe -r "$POWEROFF_DKMS_NAME" 2>/dev/null + [[ -d "$POWEROFF_DKMS_SRC" ]] || return 0 + sudo dkms remove "$POWEROFF_DKMS_NAME/$POWEROFF_DKMS_VER" --all >/dev/null 2>&1 + sudo rm -rf "$POWEROFF_DKMS_SRC" +} diff --git a/lib/menu.sh b/lib/menu.sh index 9fe7e97..b3f4aaa 100644 --- a/lib/menu.sh +++ b/lib/menu.sh @@ -16,7 +16,7 @@ declare -A PARENT=([boot]=gaming [kpin]=machine [hdmi]=machine [bios]=machine) # upset other devices on the TV, even on SteamOS), except on a Steam Machine, # which has CEC like on SteamOS. HDMI refresh boost needs someone at the # screen to confirm each step. -NO_PRESELECT=(boot cec hdmi) +NO_PRESELECT=(boot cec kpin hdmi) declare -A LABEL=( [gaming]="SteamOS conversion: boot into gaming mode, Steam on the desktop" @@ -27,7 +27,7 @@ declare -A LABEL=( [launcher]="Steamify shortcut: the app on the desktop, Steamify Terminal in the launcher" [cec]="HDMI-CEC: use Steam with the TV remote, TV on/off with the PC (experimental)" [machine]="Steam Machine support: LED bar driver, hardware settings in Steam" - [kpin]="Pin the kernel to $PINNED_KERNEL_VER (fixes rebooting after shutdown)" + [kpin]="Pin the kernel to $PINNED_KERNEL_VER (optional, stays on an older kernel)" [hdmi]="HDMI refresh boost: highest refresh your HDMI display runs" [bios]="Update BIOS" ) @@ -72,6 +72,8 @@ detect_components() { done # HDMI-CEC set up by an older version: tick it, so a normal run fixes it. component_available cec && cec_repair && WANTED[cec]=1 + # Steam Machine support from before 2.2.0 lacks the power-off fix. + component_available machine && machine_repair && WANTED[machine]=1 # The terminal-only Steamify shortcut from before 2.0.1: tick it, so a # normal run replaces it with the app. launcher_repair && WANTED[launcher]=1 @@ -96,8 +98,8 @@ toggle_component() { if [[ "$c" == gaming && "${WANTED[gaming]}" == 0 ]]; then WANTED[single]=0; WANTED[boot]=0; fi # Where to boot to is part of the conversion, too. if [[ "$c" == boot && "${WANTED[boot]}" == 1 ]]; then WANTED[gaming]=1; fi - # The kernel pin is opt-out: ticked along with Steam Machine support. - if [[ "$c" == machine ]]; then WANTED[kpin]=${WANTED[machine]}; fi + # The kernel pin is opt-in (the power-off fix made it optional). + if [[ "$c" == machine && "${WANTED[machine]}" == 0 ]]; then WANTED[kpin]=0; fi if [[ "$c" == kpin && "${WANTED[kpin]}" == 1 ]]; then WANTED[machine]=1; fi # HDMI refresh boost and the BIOS update sit under Steam Machine support; # the boost also needs the pinned kernel (newer kernels don't need it). diff --git a/lib/steam-machine.sh b/lib/steam-machine.sh index 3a5ed5d..8f3d7e7 100644 --- a/lib/steam-machine.sh +++ b/lib/steam-machine.sh @@ -1,6 +1,7 @@ #!/bin/bash # "Steam Machine support" menu item, only on Valve Fremont hardware: the -# front LED bar driver, LED access for Steam, and steamos-manager. +# front LED bar driver, LED access for Steam, steamos-manager, and the +# power-off fix (lib/fremont-poweroff.sh). # Sourced by steamify.sh; not meant to be run on its own. detect_valve_fremont() { @@ -14,8 +15,8 @@ detect_valve_fremont() { [[ "$vendor" == "OEM" && "$product" == "F7F" ]] } -# Kernel pinned on a Steam Machine: with newer linux-cachyos releases it -# reboots instead of shutting down. The packages are kept in +# Kernel pinned on a Steam Machine (optional): newer linux-cachyos releases +# rebooted it instead of shutting down, which the power-off fix now handles. The packages are kept in # PINNED_KERNEL_DIR, so re-applying (or reinstalling after an update slipped # through) needs no download. PINNED_KERNEL_VER="7.1.6-1" @@ -334,13 +335,13 @@ reload_powerdevil() { kernel_overview() { # Per installed kernel: headers (needed to build DKMS modules), the # in-kernel Steam controller driver, and, on a Steam Machine, whether the - # LED driver is built for it. Printed in the menu so it's easy to verify + # LED driver and the power-off fix are built for it. Printed in the menu so it's easy to verify # a kernel update or a newly added kernel got everything. local mark="${c_green}yes${c_reset}" miss="${c_red}no ${c_reset}" leds=false detect_valve_fremont && command -v dkms >/dev/null 2>&1 && leds=true - local kdir k pkg headers hid led running line + local kdir k pkg headers hid led off running line local legend="> = running; controller = Steam controller driver" - [[ "$leds" == true ]] && legend+=", LEDs = LED bar driver built" + [[ "$leds" == true ]] && legend+=", LEDs = LED bar driver built, power-off = power-off fix built" echo -e " ${c_bold}Kernels${c_reset} ($legend)" for kdir in /usr/lib/modules/*/; do k="$(basename "$kdir")" @@ -352,7 +353,8 @@ kernel_overview() { line="$(printf '%-23s %-18s headers %b controller %b' "$k" "$pkg" "$headers" "$hid")" if [[ "$leds" == true ]]; then led="$miss"; dkms status -k "$k" leds-valve-dkms 2>/dev/null | grep -q installed && led="$mark" - line+="$(printf ' LEDs %b' "$led")" + off="$miss"; dkms status -k "$k" "$POWEROFF_DKMS_NAME" 2>/dev/null | grep -q installed && off="$mark" + line+="$(printf ' LEDs %b power-off %b' "$led" "$off")" fi echo -e " ${running}${line}" done @@ -369,7 +371,13 @@ kernel_overview() { machine_available() { detect_valve_fremont; } machine_status() { - pacman -Qi leds-valve-dkms-git >/dev/null 2>&1 && pacman -Qi steamos-manager >/dev/null 2>&1 + pacman -Qi leds-valve-dkms-git >/dev/null 2>&1 && pacman -Qi steamos-manager >/dev/null 2>&1 && + poweroff_fix_installed +} +# Set up before 2.2.0: no power-off fix yet; the menu ticks it to add it. +machine_repair() { + pacman -Qi leds-valve-dkms-git >/dev/null 2>&1 && pacman -Qi steamos-manager >/dev/null 2>&1 && + ! poweroff_fix_installed } machine_enable() { @@ -381,6 +389,8 @@ machine_enable() { ensure_aur_helper || { warn "Couldn't set up an AUR helper automatically. Install yay or paru, then run the wizard again."; return 1; } install_valve_led_driver || { warn "LED driver setup ran into a problem - see errors above."; return 1; } install_headers_boot_check + info "Installing the power-off fix (the machine stays off after shutting down)..." + poweroff_fix_enable || { err "Installing the power-off fix failed."; return 1; } # The LED files are root-only. Steam runs as the user; let it write them # in case it drives the bar directly (Valve's own privileged-write helper @@ -424,6 +434,7 @@ machine_disable() { sudo udevadm control --reload sudo modprobe -r leds-valve 2>/dev/null sudo pacman -Rns --noconfirm leds-valve-dkms-git 2>/dev/null + poweroff_fix_disable krevert machine reload_powerdevil sudo systemctl disable ensure-kernel-headers.service 2>/dev/null diff --git a/patches/README.md b/patches/README.md new file mode 100644 index 0000000..699c90d --- /dev/null +++ b/patches/README.md @@ -0,0 +1,10 @@ +# Patches and extra sources + +Files Steamify builds or applies, kept out of the shell code so they can be +read and reviewed on their own. `lib/*.sh` reads them with `patch_file +`; the single-file build (`.github/tools/bundle.sh`) embeds them. + +| File | Used by | What | +|---|---|---| +| `steamify-fremont-poweroff.c` | `lib/fremont-poweroff.sh` (Steam Machine support) | Kernel module (DKMS): clears GPIO 18's S4/S5 wake bit before power-off, so the Steam Machine stays off on kernels 7.2 and newer | +| `cros-ec-cec-single-port.patch` | `lib/cec.sh` (HDMI-CEC) | Makes Valve's Steam Machine CEC driver find amdgpu's HDMI port | diff --git a/patches/cros-ec-cec-single-port.patch b/patches/cros-ec-cec-single-port.patch new file mode 100644 index 0000000..3c21b3f --- /dev/null +++ b/patches/cros-ec-cec-single-port.patch @@ -0,0 +1,21 @@ +Steam Machine CEC: find amdgpu's HDMI port with a single CEC port. + +amdgpu registers its HDMI notifier without a port name, which only matches +the driver's named lookup ("Port C") if the CEC driver loaded first; amdgpu +loads from the initramfs, so it never does and /dev/cec0 stays at f.f.f.f. +With a single CEC port, look the notifier up by device alone. + +Applies to Valve's drivers/media/cec/platform/cros-ec/cros-ec-cec.c +(evlaV/linux-integration 10c8c880, see CEC_DRIVER_URL in lib/cec.sh). + +--- a/cros-ec-cec.c ++++ b/cros-ec-cec.c +@@ -485,7 +485,7 @@ + goto out_probe_adapter; + } + +- port->notify = cec_notifier_cec_adap_register(hdmi_dev, conns[port_num], ++ port->notify = cec_notifier_cec_adap_register(hdmi_dev, conns[1] ? conns[port_num] : NULL, + port->adap); + if (!port->notify) { + ret = -ENOMEM; diff --git a/patches/steamify-fremont-poweroff.c b/patches/steamify-fremont-poweroff.c new file mode 100644 index 0000000..f2c65b2 --- /dev/null +++ b/patches/steamify-fremont-poweroff.c @@ -0,0 +1,80 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Steam Machine (Fremont): power off instead of booting up again. + * + * Since Linux 7.2 ("pinctrl-amd: Don't clear S4 wake bits at probe") the + * S4/S5 wake bit the firmware leaves set on GPIO pin 18 (_SB.PCI0.GPP6) + * stays set, and the machine starts again right after powering off. + * Valve's kernel clears that bit at probe on Fremont (not for upstream); + * this does the same right before power-off, for kernels without it. + */ +#include +#include +#include +#include +#include +#include +#include + +#define FREMONT_GPP6_PIN 18 +#define WAKE_CNTRL_OFF_S4 15 + +static void __iomem *pin_reg; +static struct sys_off_handler *handler; + +static int fremont_poweroff_prepare(struct sys_off_data *data) +{ + u32 v = readl(pin_reg); + + if (v & BIT(WAKE_CNTRL_OFF_S4)) + writel(v & ~BIT(WAKE_CNTRL_OFF_S4), pin_reg); + return NOTIFY_DONE; +} + +static int __init fremont_poweroff_init(void) +{ + struct acpi_device *adev; + struct device *dev; + struct resource *res; + u32 v; + + if (!dmi_match(DMI_BOARD_NAME, "Fremont")) + return -ENODEV; + adev = acpi_dev_get_first_match_dev("AMDI0030", NULL, -1); + if (!adev) + return -ENODEV; + dev = bus_find_device_by_acpi_dev(&platform_bus_type, adev); + acpi_dev_put(adev); + if (!dev) + return -ENODEV; + res = platform_get_resource(to_platform_device(dev), IORESOURCE_MEM, 0); + put_device(dev); + if (!res || resource_size(res) < (FREMONT_GPP6_PIN + 1) * 4) + return -ENODEV; + /* pinctrl-amd owns the region; only this one register is touched. */ + pin_reg = ioremap(res->start + FREMONT_GPP6_PIN * 4, 4); + if (!pin_reg) + return -ENOMEM; + v = readl(pin_reg); + pr_info("GPIO %d register 0x%08x, S4/S5 wake %s\n", FREMONT_GPP6_PIN, v, + v & BIT(WAKE_CNTRL_OFF_S4) ? "set (cleared at power-off)" : "clear"); + handler = register_sys_off_handler(SYS_OFF_MODE_POWER_OFF_PREPARE, + SYS_OFF_PRIO_DEFAULT, fremont_poweroff_prepare, NULL); + if (IS_ERR(handler)) { + iounmap(pin_reg); + return PTR_ERR(handler); + } + return 0; +} + +static void __exit fremont_poweroff_exit(void) +{ + unregister_sys_off_handler(handler); + iounmap(pin_reg); +} + +module_init(fremont_poweroff_init); +module_exit(fremont_poweroff_exit); +MODULE_DESCRIPTION("Steam Machine: clear GPIO 18's S4 wake bit before power-off"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("dmi:*:rnFremont:*"); diff --git a/steamify.sh b/steamify.sh index 4eb66f4..dd87858 100755 --- a/steamify.sh +++ b/steamify.sh @@ -15,11 +15,11 @@ set -uo pipefail # Release version, see CHANGELOG.md. -VERSION=2.1.0 +VERSION=2.2.0 SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -for lib in common state packages login-manager single-user steam-desktop steam-machine hdmi-refresh cec boot-session vapor-theme steamos-extras bios desktop-shortcut wizard-shortcut menu backend; do +for lib in common state packages login-manager single-user steam-desktop steam-machine fremont-poweroff hdmi-refresh cec boot-session vapor-theme steamos-extras bios desktop-shortcut wizard-shortcut menu backend; do # shellcheck source=/dev/null source "$SCRIPT_DIR/lib/$lib.sh" done diff --git a/ui/qml/Main.qml b/ui/qml/Main.qml index fc8ce49..94a04b5 100644 --- a/ui/qml/Main.qml +++ b/ui/qml/Main.qml @@ -67,10 +67,10 @@ ApplicationWindow { body: "Use Steam with your TV remote, and the TV turns on and off with the PC. Turn on CEC on the TV too (Sony: BRAVIA Sync, Samsung: Anynet+, LG: SimpLink).", changes: ["Valve's cecd and cec-audio-control", "HDMI CEC section in Steam's Display settings", "Volume buttons for the TV in the Quick Access menu"] }, machine: { label: "Steam Machine support", hint: "LED bar, fan and performance settings in Steam", - body: "The front LED bar works, Steam's hardware settings work, and the power button puts it to sleep like a console.", - changes: ["leds-valve driver for every kernel (DKMS)", "steamos-manager for Steam's settings", "Console-like power handling"] }, - kpin: { label: "Pin the kernel", hint: "Fixes rebooting after shutdown", - body: "Newer CachyOS kernels make the Steam Machine reboot instead of shutting down. Untick once CachyOS fixes that.", + body: "The front LED bar works, Steam's hardware settings work, the power button puts it to sleep like a console, and shutting down really turns it off on every kernel.", + changes: ["leds-valve driver for every kernel (DKMS)", "steamos-manager for Steam's settings", "Console-like power handling", "Power-off fix: stays off after shutting down (DKMS)"] }, + kpin: { label: "Pin the kernel", hint: "Optional: stay on an older kernel", + body: "Keeps the Steam Machine on CachyOS kernel 7.1.6. Not needed for shutting down any more (Steam Machine support fixes that on every kernel); HDMI refresh boost needs it, since newer kernels do HDMI 2.1 themselves.", changes: ["linux-cachyos from Steamify's release (signature checked)", "Kept in /var/cache/steamify/kernel", "Added to IgnorePkg"] }, hdmi: { label: "HDMI refresh boost", hint: "Higher refresh rates over HDMI", body: "The pinned kernel keeps many HDMI displays at 60 Hz. Turning this on shows which refresh rates your display can run at the desktop resolution; you pick them, and each one is tried for 15 seconds so you can check the picture before it's installed.", @@ -247,7 +247,7 @@ ApplicationWindow { if (id === "hdmi" && !hdmiChoice && hdmiSaved.length) { openHdmiList(); return; } if (id === "hdmi" && w.hdmi && !nowOn("hdmi") && !hdmiChoice) { startHdmi(); return; } if (id === "hdmi" && !w.hdmi) hdmiChoice = ""; - if (id === "machine") w.kpin = w.machine; + if (id === "machine" && !w.machine) w.kpin = false; if (id === "kpin" && w.kpin) w.machine = true; if (id === "hdmi" && w.hdmi) { w.machine = true; w.kpin = true; } if (!w.machine || !w.kpin) w.hdmi = false; diff --git a/ui/steamify-app.sh b/ui/steamify-app.sh index 6cafcec..35a267b 100755 --- a/ui/steamify-app.sh +++ b/ui/steamify-app.sh @@ -2,7 +2,7 @@ # Steamify CachyOS - get and start the app (v2), for `curl | bash`: # curl -fsSL https://github.com/theupriser/steamify-cachyos/releases/latest/download/steamify-app.sh | bash # Downloads the newest release's app package (steamify-app.tar.gz: the app, -# steamify.sh and lib/) to ~/.local/share/steamify/app, installs PySide6 if +# steamify.sh, lib/ and patches/) to ~/.local/share/steamify/app, installs PySide6 if # it's missing, adds a launcher entry, and starts the app. # STEAMIFY_RELEASE= takes that release instead of the newest (the # v2-ui-preview build's copy of this script sets it). STEAMIFY_BRANCH= From 321bf9bfcb306c80b117b3ea5a872e14fe58295d Mon Sep 17 00:00:00 2001 From: rickpeters Date: Sun, 27 Sep 2026 12:20:56 +0200 Subject: [PATCH 02/10] feat: Power-off fix as its own sub-option, drop the kernel pin, feature versions The power-off fix becomes an opt-out sub-option of Steam Machine support. The kernel pin is no longer offered: an existing pin (and HDMI refresh boost, which needs it) is unticked so a normal run removes it. Components record a feature version on each successful run; installs with an older version are ticked and updated, and new default sub-options are ticked where their parent is on. --- AGENTS.md | 28 ++++++++++++------- CHANGELOG.md | 19 ++++++++----- README.md | 19 +++++++------ TECHNICAL.md | 37 ++++++++++++++++++++------ lib/backend.sh | 11 ++++++-- lib/fremont-poweroff.sh | 14 +++++++++- lib/menu.sh | 59 ++++++++++++++++++++++++++++++++++------- lib/steam-machine.sh | 19 +++++-------- steamify.sh | 1 + ui/qml/Main.qml | 21 +++++++++------ 10 files changed, 160 insertions(+), 68 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index d4a2f48..fded1dc 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -29,6 +29,12 @@ gamescope and the Plasma desktop. Primary target: the Valve Steam Machine `_available` is checked via `component_available` (e.g. `machine` only on Fremont). Turn-on order is `COMPONENTS` order, turn-off reverse; `gaming` must stay first. Dependencies live in `toggle_component`. +- **Feature versions.** Bump `FEATURE_VERSION[]` (`lib/menu.sh`) whenever + what `_enable` sets up changes: installs recorded with an older number + are ticked and re-applied ("update" in the plan and the app). New default + sub-options are ticked for installs whose parent is on (`feature_new`). + Status still comes from the system; don't add ad-hoc `_repair` checks + for new changes. - **Reversibility.** Every per-user KDE setting a component changes goes through `kset ` (`lib/state.sh`), which records the old value once; `_disable` calls @@ -208,16 +214,18 @@ gamescope and the Plasma desktop. Primary target: the Valve Steam Machine kernel only pairs that with a named lookup ("Port C") when the CEC driver registered first, which never happens since amdgpu loads from the initramfs. Without it `/dev/cec0` exists but stays at `f.f.f.f`. -- Power-off fix (`lib/fremont-poweroff.sh`, part of `machine`): kernels - >= 7.2 keep the firmware's S4/S5 wake bit on GPIO pin 18, so the Steam - Machine boots again right after powering off (Valve's kernel clears it at - probe, not for upstream). The DKMS module `steamify-fremont-poweroff` - (`patches/steamify-fremont-poweroff.c`) clears it in a power-off-prepare - handler, for every kernel. It made the kernel pin optional (`kpin` is in - `NO_PRESELECT`, no longer ticked with `machine`). `machine_repair` ticks - `machine` for setups without it. CachyOS's `linux-cachyos` is clang-built, - `-bore` GCC-built: let DKMS pick the compiler, never pass `LLVM=1`. Test - shutdown on the real machine for every new major kernel. +- Power-off fix (`poweroff`, `lib/fremont-poweroff.sh`, a default sub-option + of `machine`): kernels >= 7.2 keep the firmware's S4/S5 wake bit on GPIO + pin 18, so the Steam Machine boots again right after powering off + (Valve's kernel clears it at probe, not for upstream). The DKMS module + `steamify-fremont-poweroff` (`patches/steamify-fremont-poweroff.c`) clears + it in a power-off-prepare handler, for every kernel. It made the kernel pin + unnecessary: `kpin` is only available while on (`kpin_available`), and + `detect_components` unticks it (and `hdmi`, which needs the pinned + kernel), so a normal run removes an existing pin. CachyOS's + `linux-cachyos` is clang-built, `-bore` GCC-built: let DKMS pick the + compiler, never pass `LLVM=1`. Test shutdown on the real machine for every + new major kernel. - `Relogin=true` means a gamescope that fails to start is relaunched in a tight loop; keep that in mind when changing session handling. diff --git a/CHANGELOG.md b/CHANGELOG.md index 9d3d223..823903f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,12 +12,19 @@ one per merged pull request. shutting down: Linux 7.2 keeps the wake bit the firmware sets on GPIO pin 18, which Valve's own kernel clears in a patch that won't reach CachyOS. Steam Machine support now builds a small module with DKMS for - every kernel that clears it right before power-off. Tested with - CachyOS 7.2.8: stays off, and without the module it rebooted. - - The kernel pin is no longer ticked with Steam Machine support or on a - first run; it stays available (HDMI refresh boost needs it). An - existing pin is kept. Steam Machine support from before 2.2.0 is - ticked in the menu to add the fix. + every kernel that clears it right before power-off: the new **Power-off + fix** sub-option, ticked along with Steam Machine support (opt-out). + Tested with CachyOS 7.2.8: stays off, and without the module it + rebooted. + - The kernel pin is no longer offered. An existing pin is shown unticked, + so the next run removes it and brings back CachyOS's current kernel; + HDMI refresh boost, which needs the pinned kernel, goes with it (newer + kernels do HDMI 2.1 themselves). + - Feature versions: each component records the version of what it set + up; when a release changes a component, installs with an older version + are ticked and updated by a normal run ("update" in the plan and the + app), and new default sub-options (like the power-off fix) are ticked + where their parent is on. - Module sources and patches live in `patches/` instead of inside the shell code; the single-file build embeds them. The Steam Machine CEC driver change is now `patches/cros-ec-cec-single-port.patch`. diff --git a/README.md b/README.md index f918868..e1fff15 100644 --- a/README.md +++ b/README.md @@ -43,16 +43,15 @@ What Steamify can do on a Valve Steam Machine: TV's remote, and the TV turns on and off with the Steam Machine ([details](TECHNICAL.md#hdmi-cec)). - **Steam Machine support** - the front **LED bar** works, Steam's - **hardware settings** (fan, performance) work, the power button puts it - to sleep like a console, and **shutting down really turns it off**, also - with the newest CachyOS kernel - ([details](TECHNICAL.md#power-off-fix-steam-machine)). - - **Pin the kernel to 7.1.6-1** - optional, off by default: stays on an - older kernel (HDMI refresh boost needs it). It used to be needed for - shutting down; the power-off fix above covers that now. The kernel comes from its own - [release](https://github.com/theupriser/steamify-cachyos/releases/tag/kernel-7.1.6-1), - checked against CachyOS's signature - ([details](TECHNICAL.md#kernel-pin-steam-machine)). + **hardware settings** (fan, performance) work, and the power button puts + it to sleep like a console. + - **Power-off fix** - on by default: shutting down really turns the Steam + Machine off, also with the newest CachyOS kernel + ([details](TECHNICAL.md#power-off-fix-steam-machine)). + - **Pin the kernel to 7.1.6-1** - no longer offered: it was needed for + shutting down, which the power-off fix above covers now. An existing pin + is shown unticked, so the next run removes it and brings back CachyOS's + current kernel ([details](TECHNICAL.md#kernel-pin-steam-machine)). - **HDMI refresh boost** - only with the pinned kernel, never ticked by default: gets the highest refresh rate your HDMI display runs at the desktop resolution (e.g. 110 Hz instead of 60 on a 3440x1440 monitor). diff --git a/TECHNICAL.md b/TECHNICAL.md index 22a2255..96e227c 100644 --- a/TECHNICAL.md +++ b/TECHNICAL.md @@ -218,7 +218,8 @@ that bit set on GPIO pin 18 (`_SB.PCI0.GPP6`). Valve's own kernel for upstream ("until the firmware is fixed"), so CachyOS and mainline won't get it. -Steam Machine support builds a small module with DKMS for every installed +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 @@ -229,17 +230,37 @@ that already clears it, it does nothing. It logs the pin's state at load: 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. -Steam Machine support set up before 2.2.0 has no power-off fix; the menu -ticks it, so a normal run adds it. +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. + +## Feature versions (updates) + +Each component has a feature version (`FEATURE_VERSION` in `lib/menu.sh`, +1 when not listed). After a component is turned on successfully, the version +is recorded in `~/.local/state/cachyos-gamescope-boot/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 1. +- **A new default sub-option** (not in `NO_PRESELECT`) whose parent is on + 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. + +Bump a component's number whenever what its `_enable` sets up changes. ## Kernel pin (Steam Machine) With CachyOS kernels newer than 7.1.6 a Steam Machine rebooted instead of -shutting down; the power-off fix above handles that now, so the pin is -optional. The **Pin the kernel** sub-option (off by default; HDMI refresh -boost needs it) installs `linux-cachyos` and `linux-cachyos-headers` -7.1.6-1 and adds them to `IgnorePkg` in `/etc/pacman.conf`, so updates skip -them. DKMS builds the LED driver for it; restart to boot it. +shutting down; the power-off fix above handles that now. Since 2.2.0 the +**Pin the kernel** sub-option is no longer offered. A pin that's still +there is shown unticked, so a normal run (after the review) removes it and +brings back CachyOS's current kernel; HDMI refresh boost, which needs the +pinned kernel, is unticked with it. The pin installed `linux-cachyos` and +`linux-cachyos-headers` 7.1.6-1 and added them to `IgnorePkg` in +`/etc/pacman.conf`, so updates skipped them. The packages (and their signatures, which pacman checks) are kept in `/var/cache/steamify/kernel`, so re-applying needs no download. Missing diff --git a/lib/backend.sh b/lib/backend.sh index 776d91c..da6e867 100644 --- a/lib/backend.sh +++ b/lib/backend.sh @@ -62,6 +62,7 @@ backend_status() { items+=",\"kind\":\"$kind\",\"parent\":$(json_str "$parent")" items+=",\"on\":$( [[ "$now" == 1 ]] && echo true || echo false)" items+=",\"wanted\":$( [[ "${WANTED[$c]:-0}" == 1 ]] && echo true || echo false)" + items+=",\"update\":$(feature_outdated "$c" && echo true || echo false)" items+=",\"selectable\":$(component_selectable "$c" && echo true || echo false)}" done local cec="" f @@ -160,6 +161,8 @@ backend_apply() { # The same rules as the menu's toggles. [[ "${WANTED[single]:-0}" == 1 ]] && WANTED[gaming]=1 [[ "${WANTED[gaming]:-0}" == 0 ]] && { WANTED[single]=0; WANTED[boot]=0; } + [[ "${WANTED[poweroff]:-0}" == 1 ]] && WANTED[machine]=1 + [[ "${WANTED[machine]:-0}" == 0 ]] && WANTED[poweroff]=0 [[ "${WANTED[kpin]:-0}" == 1 ]] && WANTED[machine]=1 [[ "${WANTED[machine]:-0}" == 0 ]] && WANTED[kpin]=0 [[ "${WANTED[hdmi]:-0}" == 1 ]] && { WANTED[machine]=1; WANTED[kpin]=1; } @@ -179,8 +182,12 @@ backend_apply() { LOGIN_MANAGER=plasmalogin [[ "${WANTED[single]}" == 1 ]] && LOGIN_MANAGER=sddm local -a failed=() - for c in "${TO_DISABLE[@]}"; do backend_run_component "$c" disable || failed+=("$c"); done - for c in "${TO_ENABLE[@]}"; do backend_run_component "$c" enable || failed+=("$c"); done + for c in "${TO_DISABLE[@]}"; do + if backend_run_component "$c" disable; then feature_record "$c" disable; else failed+=("$c"); fi + done + for c in "${TO_ENABLE[@]}"; do + if backend_run_component "$c" enable; then is_action "$c" || feature_record "$c" enable; else failed+=("$c"); fi + done [[ " ${TO_DISABLE[*]} ${TO_ENABLE[*]} " =~ \ (gaming|single|boot|kpin)\ ]] && RESTART_FOR_LOGIN=true backend_event finished "\"failed\":$(json_list "${failed[@]}"),\"restart\":$(restart_needed && echo true || echo false)" } diff --git a/lib/fremont-poweroff.sh b/lib/fremont-poweroff.sh index 991dbba..6c49c46 100644 --- a/lib/fremont-poweroff.sh +++ b/lib/fremont-poweroff.sh @@ -1,5 +1,6 @@ #!/bin/bash -# Part of "Steam Machine support": the Steam Machine powers off instead of +# "Power-off fix" menu item (poweroff), a sub-option of Steam Machine support +# ticked along with it (opt-out): the Steam Machine powers off instead of # booting up again, on every kernel. Since Linux 7.2 ("pinctrl-amd: Don't # clear S4 wake bits at probe") the S4/S5 wake bit the firmware leaves set # on GPIO pin 18 stays set, and the machine starts again right after @@ -55,3 +56,14 @@ poweroff_fix_disable() { sudo dkms remove "$POWEROFF_DKMS_NAME/$POWEROFF_DKMS_VER" --all >/dev/null 2>&1 sudo rm -rf "$POWEROFF_DKMS_SRC" } + +poweroff_status() { poweroff_fix_installed; } +poweroff_enable() { + info "Installing the power-off fix (the machine stays off after shutting down)..." + poweroff_fix_enable || { err "Installing the power-off fix failed."; return 1; } + ok "Power-off fix on: the Steam Machine stays off after shutting down." +} +poweroff_disable() { + poweroff_fix_disable + ok "Power-off fix removed; with kernels 7.2 and newer the Steam Machine may start again after shutting down." +} diff --git a/lib/menu.sh b/lib/menu.sh index b3f4aaa..e46311a 100644 --- a/lib/menu.sh +++ b/lib/menu.sh @@ -5,18 +5,24 @@ # Menu order. Components are turned on in this order and off in reverse; # gaming must come first (single user builds on it). -COMPONENTS=(gaming boot theme glyphs single launcher cec machine kpin hdmi bios) +COMPONENTS=(gaming boot theme glyphs single launcher cec machine poweroff kpin hdmi bios) # One-off actions rather than on/off components: never preselected, never # re-applied, not listed as on or off. ACTIONS=(bios) # Sub-options, shown indented under their parent and only while it's ticked. -declare -A PARENT=([boot]=gaming [kpin]=machine [hdmi]=machine [bios]=machine) +declare -A PARENT=([boot]=gaming [poweroff]=machine [kpin]=machine [hdmi]=machine [bios]=machine) # Never preselected on a first run: booting into the desktop is a choice, # gamescope is the default; HDMI-CEC is opt-in (it can wake the machine or # upset other devices on the TV, even on SteamOS), except on a Steam Machine, # which has CEC like on SteamOS. HDMI refresh boost needs someone at the # screen to confirm each step. NO_PRESELECT=(boot cec kpin hdmi) +# Feature versions: bump a component's number whenever what its enable sets +# up changes. Each successful run records the number (state "features"); a +# component that's on with an older number is ticked and re-applied by a +# normal run, and the app shows it as an update. Unlisted = 1, and so is a +# setup from before these were recorded. +declare -A FEATURE_VERSION=() declare -A LABEL=( [gaming]="SteamOS conversion: boot into gaming mode, Steam on the desktop" @@ -27,7 +33,8 @@ declare -A LABEL=( [launcher]="Steamify shortcut: the app on the desktop, Steamify Terminal in the launcher" [cec]="HDMI-CEC: use Steam with the TV remote, TV on/off with the PC (experimental)" [machine]="Steam Machine support: LED bar driver, hardware settings in Steam" - [kpin]="Pin the kernel to $PINNED_KERNEL_VER (optional, stays on an older kernel)" + [poweroff]="Power-off fix: stays off after shutting down, on every kernel" + [kpin]="Pin the kernel to $PINNED_KERNEL_VER (no longer needed: untick for CachyOS's current kernel)" [hdmi]="HDMI refresh boost: highest refresh your HDMI display runs" [bios]="Update BIOS" ) @@ -35,7 +42,8 @@ declare -A CURRENT WANTED component_available() { case "$1" in - machine|kpin) machine_available ;; + machine|poweroff) machine_available ;; + kpin) kpin_available ;; hdmi) hdmi_available ;; bios) bios_available ;; esac @@ -56,6 +64,28 @@ menu_visible() { [[ -z "${PARENT[$1]:-}" || "${WANTED[${PARENT[$1]}]:-0}" == 1 ]] } +feature_record() { + # feature_record : after a successful run. + if [[ "$2" == enable ]]; then state_set features "$1" "${FEATURE_VERSION[$1]:-1}" + else state_set features "$1" off; fi +} + +feature_outdated() { + # On, but set up by an older version of that feature. + local have + [[ "${CURRENT[$1]:-0}" == 1 ]] || return 1 + have="$(state_get features "$1" 1)" + [[ "$have" =~ ^[0-9]+$ ]] && (( have < ${FEATURE_VERSION[$1]:-1} )) +} + +feature_new() { + # A default sub-option added after its parent was set up (e.g. the + # power-off fix under Steam Machine support): never turned on or off. + [[ -n "${PARENT[$1]:-}" && "${CURRENT[${PARENT[$1]}]:-0}" == 1 && "${CURRENT[$1]:-0}" == 0 ]] && + ! is_action "$1" && [[ " ${NO_PRESELECT[*]} " != *" $1 "* ]] && + [[ -z "$(state_get features "$1")" ]] +} + component_selectable() { # Greyed out and not tickable when it has nothing to do. case "$1" in @@ -72,8 +102,15 @@ detect_components() { done # HDMI-CEC set up by an older version: tick it, so a normal run fixes it. component_available cec && cec_repair && WANTED[cec]=1 - # Steam Machine support from before 2.2.0 lacks the power-off fix. - component_available machine && machine_repair && WANTED[machine]=1 + # Updated features and new default sub-options: ticked, so a normal run + # applies them. + for c in "${COMPONENTS[@]}"; do + component_available "$c" || continue + { feature_outdated "$c" || feature_new "$c"; } && WANTED[$c]=1 + done + # The kernel pin is no longer needed (the power-off fix): drop it, and + # HDMI refresh boost, which needs it, on a normal run. + if component_available kpin; then WANTED[kpin]=0; WANTED[hdmi]=0; fi # The terminal-only Steamify shortcut from before 2.0.1: tick it, so a # normal run replaces it with the app. launcher_repair && WANTED[launcher]=1 @@ -98,7 +135,9 @@ toggle_component() { if [[ "$c" == gaming && "${WANTED[gaming]}" == 0 ]]; then WANTED[single]=0; WANTED[boot]=0; fi # Where to boot to is part of the conversion, too. if [[ "$c" == boot && "${WANTED[boot]}" == 1 ]]; then WANTED[gaming]=1; fi - # The kernel pin is opt-in (the power-off fix made it optional). + # The power-off fix is opt-out: ticked along with Steam Machine support. + if [[ "$c" == machine ]]; then WANTED[poweroff]=${WANTED[machine]}; fi + if [[ "$c" == poweroff && "${WANTED[poweroff]}" == 1 ]]; then WANTED[machine]=1; fi if [[ "$c" == machine && "${WANTED[machine]}" == 0 ]]; then WANTED[kpin]=0; fi if [[ "$c" == kpin && "${WANTED[kpin]}" == 1 ]]; then WANTED[machine]=1; fi # HDMI refresh boost and the BIOS update sit under Steam Machine support; @@ -268,7 +307,7 @@ plan_changes() { component_available "$c" || continue [[ "${WANTED[$c]}" == 1 ]] || continue if is_action "$c"; then TO_ENABLE+=("$c"); continue; fi - if [[ "${CURRENT[$c]}" == 0 || "$REAPPLY" == true ]] || + if [[ "${CURRENT[$c]}" == 0 || "$REAPPLY" == true ]] || feature_outdated "$c" || [[ "$c" == gaming && "${CURRENT[single]}" != "${WANTED[single]}" ]]; then TO_ENABLE+=("$c") fi @@ -283,13 +322,13 @@ apply_changes() { for c in "${TO_DISABLE[@]}"; do if [[ "$c" == boot ]]; then echo; echo -e "${c_bold}Boot into: gamescope${c_reset}" else echo; echo -e "${c_bold}Turning off: ${LABEL[$c]}${c_reset}"; fi - "${c}_disable" || failed+=("$c") + if "${c}_disable"; then feature_record "$c" disable; else failed+=("$c"); fi done for c in "${TO_ENABLE[@]}"; do if [[ "$c" == boot ]]; then echo; echo -e "${c_bold}Boot into: desktop${c_reset}" elif is_action "$c"; then echo; echo -e "${c_bold}Running: ${LABEL[$c]%%:*}${c_reset}" else echo; echo -e "${c_bold}Turning on: ${LABEL[$c]}${c_reset}"; fi - "${c}_enable" || failed+=("$c") + if "${c}_enable"; then is_action "$c" || feature_record "$c" enable; else failed+=("$c"); fi done FAILED=("${failed[@]}") } diff --git a/lib/steam-machine.sh b/lib/steam-machine.sh index 8f3d7e7..f47ab42 100644 --- a/lib/steam-machine.sh +++ b/lib/steam-machine.sh @@ -1,7 +1,7 @@ #!/bin/bash # "Steam Machine support" menu item, only on Valve Fremont hardware: the -# front LED bar driver, LED access for Steam, steamos-manager, and the -# power-off fix (lib/fremont-poweroff.sh). +# front LED bar driver, LED access for Steam and steamos-manager. Its +# sub-option "Power-off fix" is lib/fremont-poweroff.sh. # Sourced by steamify.sh; not meant to be run on its own. detect_valve_fremont() { @@ -149,7 +149,9 @@ remove_kernel_pin() { RESTART_FOR_LOGIN=true } -# "Pin the kernel" menu item, a sub-option of Steam Machine support. +# "Pin the kernel" menu item, a sub-option of Steam Machine support. The +# power-off fix made it unnecessary: only shown while on, to remove it. +kpin_available() { detect_valve_fremont && kpin_status; } kpin_status() { pinned_kernel_installed && grep -Eq '^IgnorePkg\s*=.*\slinux-cachyos(\s|$)' /etc/pacman.conf } @@ -371,13 +373,7 @@ kernel_overview() { machine_available() { detect_valve_fremont; } machine_status() { - pacman -Qi leds-valve-dkms-git >/dev/null 2>&1 && pacman -Qi steamos-manager >/dev/null 2>&1 && - poweroff_fix_installed -} -# Set up before 2.2.0: no power-off fix yet; the menu ticks it to add it. -machine_repair() { - pacman -Qi leds-valve-dkms-git >/dev/null 2>&1 && pacman -Qi steamos-manager >/dev/null 2>&1 && - ! poweroff_fix_installed + pacman -Qi leds-valve-dkms-git >/dev/null 2>&1 && pacman -Qi steamos-manager >/dev/null 2>&1 } machine_enable() { @@ -389,8 +385,6 @@ machine_enable() { ensure_aur_helper || { warn "Couldn't set up an AUR helper automatically. Install yay or paru, then run the wizard again."; return 1; } install_valve_led_driver || { warn "LED driver setup ran into a problem - see errors above."; return 1; } install_headers_boot_check - info "Installing the power-off fix (the machine stays off after shutting down)..." - poweroff_fix_enable || { err "Installing the power-off fix failed."; return 1; } # The LED files are root-only. Steam runs as the user; let it write them # in case it drives the bar directly (Valve's own privileged-write helper @@ -434,7 +428,6 @@ machine_disable() { sudo udevadm control --reload sudo modprobe -r leds-valve 2>/dev/null sudo pacman -Rns --noconfirm leds-valve-dkms-git 2>/dev/null - poweroff_fix_disable krevert machine reload_powerdevil sudo systemctl disable ensure-kernel-headers.service 2>/dev/null diff --git a/steamify.sh b/steamify.sh index dd87858..ea40854 100755 --- a/steamify.sh +++ b/steamify.sh @@ -139,6 +139,7 @@ while true; do for c in "${TO_ENABLE[@]}"; do if [[ "$c" == boot ]]; then echo " - boot into: desktop (from the next boot)" elif is_action "$c"; then echo " - run: ${LABEL[$c]%%:*} at your own risk (checks, then asks twice more)" + elif feature_outdated "$c"; then echo " - update: ${LABEL[$c]} (changed in this version)" elif [[ "${CURRENT[$c]}" == 1 ]]; then echo " - re-apply: ${LABEL[$c]}"; else echo " - turn on: ${LABEL[$c]}"; fi done ask_yn "Go ahead?" y || { info "Nothing changed."; continue; } diff --git a/ui/qml/Main.qml b/ui/qml/Main.qml index 94a04b5..16781f9 100644 --- a/ui/qml/Main.qml +++ b/ui/qml/Main.qml @@ -67,10 +67,13 @@ ApplicationWindow { body: "Use Steam with your TV remote, and the TV turns on and off with the PC. Turn on CEC on the TV too (Sony: BRAVIA Sync, Samsung: Anynet+, LG: SimpLink).", changes: ["Valve's cecd and cec-audio-control", "HDMI CEC section in Steam's Display settings", "Volume buttons for the TV in the Quick Access menu"] }, machine: { label: "Steam Machine support", hint: "LED bar, fan and performance settings in Steam", - body: "The front LED bar works, Steam's hardware settings work, the power button puts it to sleep like a console, and shutting down really turns it off on every kernel.", - changes: ["leds-valve driver for every kernel (DKMS)", "steamos-manager for Steam's settings", "Console-like power handling", "Power-off fix: stays off after shutting down (DKMS)"] }, - kpin: { label: "Pin the kernel", hint: "Optional: stay on an older kernel", - body: "Keeps the Steam Machine on CachyOS kernel 7.1.6. Not needed for shutting down any more (Steam Machine support fixes that on every kernel); HDMI refresh boost needs it, since newer kernels do HDMI 2.1 themselves.", + body: "The front LED bar works, Steam's hardware settings work, and the power button puts it to sleep like a console.", + changes: ["leds-valve driver for every kernel (DKMS)", "steamos-manager for Steam's settings", "Console-like power handling"] }, + poweroff: { label: "Power-off fix", hint: "Stays off after shutting down, on every kernel", + body: "With kernels 7.2 and newer the Steam Machine starts again right after shutting down: the firmware leaves a wake bit set that newer kernels keep. Valve's own kernel clears it; this small module does the same right before power-off, for every CachyOS kernel.", + changes: ["steamify-fremont-poweroff module for every kernel (DKMS)", "Only on a Steam Machine, only touches that one wake bit", "Off: kernels 7.2 and newer may start it again after shutting down"] }, + kpin: { label: "Pin the kernel", hint: "No longer needed: untick for the current kernel", + body: "Keeps the Steam Machine on CachyOS kernel 7.1.6, which older versions of Steamify needed to shut down properly. Steam Machine support now fixes that on every kernel. Untick it to go back to CachyOS's current kernel (HDMI refresh boost goes with it: newer kernels read the display's fast modes and do HDMI 2.1 themselves).", changes: ["linux-cachyos from Steamify's release (signature checked)", "Kept in /var/cache/steamify/kernel", "Added to IgnorePkg"] }, hdmi: { label: "HDMI refresh boost", hint: "Higher refresh rates over HDMI", body: "The pinned kernel keeps many HDMI displays at 60 Hz. Turning this on shows which refresh rates your display can run at the desktop resolution; you pick them, and each one is tried for 15 seconds so you can check the picture before it's installed.", @@ -247,6 +250,8 @@ ApplicationWindow { if (id === "hdmi" && !hdmiChoice && hdmiSaved.length) { openHdmiList(); return; } if (id === "hdmi" && w.hdmi && !nowOn("hdmi") && !hdmiChoice) { startHdmi(); return; } if (id === "hdmi" && !w.hdmi) hdmiChoice = ""; + if (id === "machine") w.poweroff = w.machine; + if (id === "poweroff" && w.poweroff) w.machine = true; if (id === "machine" && !w.machine) w.kpin = false; if (id === "kpin" && w.kpin) w.machine = true; if (id === "hdmi" && w.hdmi) { w.machine = true; w.kpin = true; } @@ -261,7 +266,7 @@ ApplicationWindow { if (it.parent && !want[it.parent]) { if (it.on) p.push({ id: it.id, action: "off" }); continue; } if (want[it.id] && !it.on) p.push({ id: it.id, action: "on" }); else if (!want[it.id] && it.on) p.push({ id: it.id, action: "off" }); - else if (want[it.id] && reapply) p.push({ id: it.id, action: "again" }); + else if (want[it.id] && (reapply || it.update)) p.push({ id: it.id, action: reapply ? "again" : "update" }); } var bootNow = nowOn("boot") ? "desktop" : "gamescope"; if (want.gaming && boot !== bootNow) p.push({ id: "boot", action: boot === "desktop" ? "desktop" : "gaming" }); @@ -748,7 +753,7 @@ ApplicationWindow { model: plan ScrollBar.vertical: ScrollBar { policy: ScrollBar.AsNeeded } delegate: Rectangle { required property var modelData; width: 740; height: 56; radius: 12; color: t.card - readonly property var st: ({ on: ["Turn on", t.good, t.goodBg], off: ["Turn off", t.bad, t.badBg], again: ["Re-apply", "#7cc4ff", "#1b2b40"], + readonly property var st: ({ on: ["Turn on", t.good, t.goodBg], off: ["Turn off", t.bad, t.badBg], again: ["Re-apply", "#7cc4ff", "#1b2b40"], update: ["Update", t.warn, t.warnBg], desktop: ["Desktop", "#7cc4ff", "#1b2b40"], gaming: ["Gaming", "#7cc4ff", "#1b2b40"], check: ["Check", t.warn, t.warnBg], flash: ["Flash", t.bad, t.badBg] })[modelData.action] || ["", t.text, t.card] Row { anchors.fill: parent; anchors.leftMargin: 18; spacing: 14 @@ -761,10 +766,10 @@ ApplicationWindow { Text { anchors.verticalCenter: parent.verticalCenter; x: 18; text: "Your password is asked once. Changes to how the PC starts need a restart."; color: t.soft; font.family: t.body; font.pixelSize: 14 } } } Rectangle { - x: parent.width - 468; y: 32; width: 428; height: 210; radius: 16; color: t.card + x: parent.width - 468; y: 32; width: 428; height: 250; radius: 16; color: t.card Column { anchors.fill: parent; anchors.margins: 24; spacing: 12 Text { text: "SUMMARY"; color: t.faint; font.family: t.body; font.pixelSize: 12; font.weight: Font.DemiBold; font.letterSpacing: 0.8 } - Repeater { model: [["Turn on", "on"], ["Re-apply", "again"], ["Turn off", "off"]] + Repeater { model: [["Turn on", "on"], ["Update", "update"], ["Re-apply", "again"], ["Turn off", "off"]] Row { required property var modelData; width: 380 Text { text: parent.modelData[0]; color: t.soft; font.family: t.body; font.pixelSize: 15; width: 300 } Text { text: plan.filter(function (p) { return p.action === parent.modelData[1]; }).length; color: t.text; font.family: t.mono; font.pixelSize: 15; width: 80; horizontalAlignment: Text.AlignRight } } } From 28555175bb45abaec8139247efa5e495d490b830 Mon Sep 17 00:00:00 2001 From: rickpeters Date: Sun, 27 Sep 2026 12:23:54 +0200 Subject: [PATCH 03/10] docs: Power-off fix texts cover every affected kernel, not just 7.2 --- AGENTS.md | 5 +++-- CHANGELOG.md | 7 ++++--- TECHNICAL.md | 8 +++++--- lib/fremont-poweroff.sh | 18 ++++++++++-------- lib/menu.sh | 2 +- patches/README.md | 2 +- patches/steamify-fremont-poweroff.c | 6 +++--- ui/qml/Main.qml | 8 ++++---- 8 files changed, 31 insertions(+), 25 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index fded1dc..c9988b1 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -215,11 +215,12 @@ gamescope and the Plasma desktop. Primary target: the Valve Steam Machine registered first, which never happens since amdgpu loads from the initramfs. Without it `/dev/cec0` exists but stays at `f.f.f.f`. - Power-off fix (`poweroff`, `lib/fremont-poweroff.sh`, a default sub-option - of `machine`): kernels >= 7.2 keep the firmware's S4/S5 wake bit on GPIO + of `machine`): recent kernels (7.2 and the 6.x/7.0/7.1 updates with the + backport) keep the firmware's S4/S5 wake bit on GPIO pin 18, so the Steam Machine boots again right after powering off (Valve's kernel clears it at probe, not for upstream). The DKMS module `steamify-fremont-poweroff` (`patches/steamify-fremont-poweroff.c`) clears - it in a power-off-prepare handler, for every kernel. It made the kernel pin + it in a power-off-prepare handler, built for each kernel. It made the kernel pin unnecessary: `kpin` is only available while on (`kpin_available`), and `detect_components` unticks it (and `hdmi`, which needs the pinned kernel), so a normal run removes an existing pin. CachyOS's diff --git a/CHANGELOG.md b/CHANGELOG.md index 823903f..a4a6e10 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,11 +8,12 @@ one per merged pull request. ## 2.2.0 - 2026-09-27 - **feat: Power-off fix for the Steam Machine; the kernel pin is optional** - - With kernels 7.2 and newer the Steam Machine started again right after - shutting down: Linux 7.2 keeps the wake bit the firmware sets on GPIO + - With recent kernels (newer 6.x, 7.0 and 7.1 updates, 7.2 and probably + later ones) the Steam Machine started again right after shutting down: + they keep the wake bit the firmware sets on GPIO pin 18, which Valve's own kernel clears in a patch that won't reach CachyOS. Steam Machine support now builds a small module with DKMS for - every kernel that clears it right before power-off: the new **Power-off + each installed kernel that clears it right before power-off: the new **Power-off fix** sub-option, ticked along with Steam Machine support (opt-out). Tested with CachyOS 7.2.8: stays off, and without the module it rebooted. diff --git a/TECHNICAL.md b/TECHNICAL.md index 96e227c..c973843 100644 --- a/TECHNICAL.md +++ b/TECHNICAL.md @@ -210,9 +210,11 @@ wizard installed it). ## Power-off fix (Steam Machine) -With kernels 7.2 and newer a Steam Machine starts again right after -powering off. Linux 7.2 stopped clearing the S4/S5 wake bits at probe -(`pinctrl-amd: Don't clear S4 wake bits at probe`), and the firmware leaves +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 diff --git a/lib/fremont-poweroff.sh b/lib/fremont-poweroff.sh index 6c49c46..7619ead 100644 --- a/lib/fremont-poweroff.sh +++ b/lib/fremont-poweroff.sh @@ -1,13 +1,15 @@ #!/bin/bash # "Power-off fix" menu item (poweroff), a sub-option of Steam Machine support # ticked along with it (opt-out): the Steam Machine powers off instead of -# booting up again, on every kernel. Since Linux 7.2 ("pinctrl-amd: Don't -# clear S4 wake bits at probe") the S4/S5 wake bit the firmware leaves set -# on GPIO pin 18 stays set, and the machine starts again right after -# powering off (the reason for the kernel pin). Valve's kernel clears it at -# probe on Fremont, in a patch not meant for upstream, so CachyOS won't get -# it. A small module (patches/steamify-fremont-poweroff.c) clears it right -# before power-off; DKMS builds it for every kernel. On kernels that clear it themselves it does nothing. +# booting up again. Recent kernels (7.2, and the 6.x, 7.0 and 7.1 updates +# that got the change backported) keep the S4/S5 wake bit the firmware +# leaves set on GPIO pin 18 ("pinctrl-amd: Don't clear S4 wake bits at +# probe"), so the machine starts again right after powering off (the reason +# for the old kernel pin). Valve's kernel clears it at probe on Fremont, in +# a patch not meant for upstream, so CachyOS won't get it. A small module +# (patches/steamify-fremont-poweroff.c) clears it right before power-off; +# DKMS builds it for each installed kernel. On a kernel that clears it +# itself it does nothing. # Sourced by steamify.sh; not meant to be run on its own. POWEROFF_DKMS_NAME=steamify-fremont-poweroff @@ -65,5 +67,5 @@ poweroff_enable() { } poweroff_disable() { poweroff_fix_disable - ok "Power-off fix removed; with kernels 7.2 and newer the Steam Machine may start again after shutting down." + ok "Power-off fix removed; with recent kernels the Steam Machine may start again after shutting down." } diff --git a/lib/menu.sh b/lib/menu.sh index e46311a..cf623e6 100644 --- a/lib/menu.sh +++ b/lib/menu.sh @@ -33,7 +33,7 @@ declare -A LABEL=( [launcher]="Steamify shortcut: the app on the desktop, Steamify Terminal in the launcher" [cec]="HDMI-CEC: use Steam with the TV remote, TV on/off with the PC (experimental)" [machine]="Steam Machine support: LED bar driver, hardware settings in Steam" - [poweroff]="Power-off fix: stays off after shutting down, on every kernel" + [poweroff]="Power-off fix: the Steam Machine stays off after shutting down" [kpin]="Pin the kernel to $PINNED_KERNEL_VER (no longer needed: untick for CachyOS's current kernel)" [hdmi]="HDMI refresh boost: highest refresh your HDMI display runs" [bios]="Update BIOS" diff --git a/patches/README.md b/patches/README.md index 699c90d..23ee437 100644 --- a/patches/README.md +++ b/patches/README.md @@ -6,5 +6,5 @@ read and reviewed on their own. `lib/*.sh` reads them with `patch_file | File | Used by | What | |---|---|---| -| `steamify-fremont-poweroff.c` | `lib/fremont-poweroff.sh` (Steam Machine support) | Kernel module (DKMS): clears GPIO 18's S4/S5 wake bit before power-off, so the Steam Machine stays off on kernels 7.2 and newer | +| `steamify-fremont-poweroff.c` | `lib/fremont-poweroff.sh` (Steam Machine support) | Kernel module (DKMS): clears GPIO 18's S4/S5 wake bit before power-off, so the Steam Machine stays off with recent kernels | | `cros-ec-cec-single-port.patch` | `lib/cec.sh` (HDMI-CEC) | Makes Valve's Steam Machine CEC driver find amdgpu's HDMI port | diff --git a/patches/steamify-fremont-poweroff.c b/patches/steamify-fremont-poweroff.c index f2c65b2..b7c7c8a 100644 --- a/patches/steamify-fremont-poweroff.c +++ b/patches/steamify-fremont-poweroff.c @@ -2,9 +2,9 @@ /* * Steam Machine (Fremont): power off instead of booting up again. * - * Since Linux 7.2 ("pinctrl-amd: Don't clear S4 wake bits at probe") the - * S4/S5 wake bit the firmware leaves set on GPIO pin 18 (_SB.PCI0.GPP6) - * stays set, and the machine starts again right after powering off. + * Since "pinctrl-amd: Don't clear S4 wake bits at probe" (Linux 7.2, also + * backported to 6.x, 7.0 and 7.1 updates) the S4/S5 wake bit the firmware + * leaves set on GPIO pin 18 (_SB.PCI0.GPP6) stays set, and the machine starts again right after powering off. * Valve's kernel clears that bit at probe on Fremont (not for upstream); * this does the same right before power-off, for kernels without it. */ diff --git a/ui/qml/Main.qml b/ui/qml/Main.qml index 16781f9..d52c087 100644 --- a/ui/qml/Main.qml +++ b/ui/qml/Main.qml @@ -69,11 +69,11 @@ ApplicationWindow { machine: { label: "Steam Machine support", hint: "LED bar, fan and performance settings in Steam", body: "The front LED bar works, Steam's hardware settings work, and the power button puts it to sleep like a console.", changes: ["leds-valve driver for every kernel (DKMS)", "steamos-manager for Steam's settings", "Console-like power handling"] }, - poweroff: { label: "Power-off fix", hint: "Stays off after shutting down, on every kernel", - body: "With kernels 7.2 and newer the Steam Machine starts again right after shutting down: the firmware leaves a wake bit set that newer kernels keep. Valve's own kernel clears it; this small module does the same right before power-off, for every CachyOS kernel.", - changes: ["steamify-fremont-poweroff module for every kernel (DKMS)", "Only on a Steam Machine, only touches that one wake bit", "Off: kernels 7.2 and newer may start it again after shutting down"] }, + poweroff: { label: "Power-off fix", hint: "Stays off after shutting down", + body: "With recent kernels the Steam Machine starts again right after shutting down: the firmware leaves a wake bit set, and newer kernels (7.2, and updates of 6.x, 7.0 and 7.1) no longer clear it. Valve's own kernel clears it; this small module does the same right before power-off.", + changes: ["steamify-fremont-poweroff module for each installed kernel (DKMS)", "Only on a Steam Machine, only touches that one wake bit", "Off: recent kernels may start it again after shutting down"] }, kpin: { label: "Pin the kernel", hint: "No longer needed: untick for the current kernel", - body: "Keeps the Steam Machine on CachyOS kernel 7.1.6, which older versions of Steamify needed to shut down properly. Steam Machine support now fixes that on every kernel. Untick it to go back to CachyOS's current kernel (HDMI refresh boost goes with it: newer kernels read the display's fast modes and do HDMI 2.1 themselves).", + body: "Keeps the Steam Machine on CachyOS kernel 7.1.6, which older versions of Steamify needed to shut down properly. The power-off fix handles that now. Untick it to go back to CachyOS's current kernel (HDMI refresh boost goes with it: newer kernels read the display's fast modes and do HDMI 2.1 themselves).", changes: ["linux-cachyos from Steamify's release (signature checked)", "Kept in /var/cache/steamify/kernel", "Added to IgnorePkg"] }, hdmi: { label: "HDMI refresh boost", hint: "Higher refresh rates over HDMI", body: "The pinned kernel keeps many HDMI displays at 60 Hz. Turning this on shows which refresh rates your display can run at the desktop resolution; you pick them, and each one is tried for 15 seconds so you can check the picture before it's installed.", From 997cd0744d74d160a6f3dbe1a3654582b60b26b4 Mon Sep 17 00:00:00 2001 From: rickpeters Date: Sun, 27 Sep 2026 12:25:08 +0200 Subject: [PATCH 04/10] docs: Drop the kernel pin and HDMI refresh boost descriptions --- README.md | 10 ----- TECHNICAL.md | 85 +---------------------------------------- lib/fremont-poweroff.sh | 3 +- lib/menu.sh | 2 +- ui/qml/Main.qml | 4 +- 5 files changed, 5 insertions(+), 99 deletions(-) diff --git a/README.md b/README.md index e1fff15..eb08c43 100644 --- a/README.md +++ b/README.md @@ -48,16 +48,6 @@ What Steamify can do on a Valve Steam Machine: - **Power-off fix** - on by default: shutting down really turns the Steam Machine off, also with the newest CachyOS kernel ([details](TECHNICAL.md#power-off-fix-steam-machine)). - - **Pin the kernel to 7.1.6-1** - no longer offered: it was needed for - shutting down, which the power-off fix above covers now. An existing pin - is shown unticked, so the next run removes it and brings back CachyOS's - current kernel ([details](TECHNICAL.md#kernel-pin-steam-machine)). - - **HDMI refresh boost** - only with the pinned kernel, never ticked by - default: gets the highest refresh rate your HDMI display runs at the - desktop resolution (e.g. 110 Hz instead of 60 on a 3440x1440 monitor). - Each step is shown and you confirm it; no answer within 15 s switches - back. For more (e.g. 175 Hz), use DisplayPort - ([details](TECHNICAL.md#hdmi-refresh-boost-steam-machine)). - **Update BIOS** - installs Valve's newest Steam Machine BIOS. Never ticked by default, at your own risk, and only after two warnings ([details](TECHNICAL.md#bios-updates-steam-machine)). diff --git a/TECHNICAL.md b/TECHNICAL.md index c973843..3e2d66b 100644 --- a/TECHNICAL.md +++ b/TECHNICAL.md @@ -253,88 +253,6 @@ on the system itself; the version only decides about updates: Bump a component's number whenever what its `_enable` sets up changes. -## Kernel pin (Steam Machine) - -With CachyOS kernels newer than 7.1.6 a Steam Machine rebooted instead of -shutting down; the power-off fix above handles that now. Since 2.2.0 the -**Pin the kernel** sub-option is no longer offered. A pin that's still -there is shown unticked, so a normal run (after the review) removes it and -brings back CachyOS's current kernel; HDMI refresh boost, which needs the -pinned kernel, is unticked with it. The pin installed `linux-cachyos` and -`linux-cachyos-headers` 7.1.6-1 and added them to `IgnorePkg` in -`/etc/pacman.conf`, so updates skipped them. - -The packages (and their signatures, which pacman checks) are kept in -`/var/cache/steamify/kernel`, so re-applying needs no download. Missing -files are taken from pacman's cache, else downloaded from this repo's -`kernel-7.1.6-1` release, then `archive.cachyos.org`, then -`mirror.cachyos.org` (which only has the current kernel). Every file must -match the SHA-256 in the script and have a valid CachyOS signature; a bad -one is deleted, so the next run downloads it again. Set `PINNED_KERNEL_URL` -to a directory URL with the files to try another source first, or drop them -into the kernel directory yourself. - -Unticking it removes the pin and runs `sudo pacman -Syu`, which brings the -kernel back to CachyOS's current version; the files stay for next time. - -## HDMI refresh boost (Steam Machine) - -With the pinned kernel (7.1.6), HDMI displays often stay at 60 Hz. Two -reasons: - -- Monitors list their fast modes in an extra EDID block, announced by the - HDMI Forum EEODB data block. 7.1.6 only reads the first extension block, - so it never sees them. Newer kernels do. -- Their fastest modes need HDMI 2.1 (FRL). 7.1.6's amdgpu only does HDMI - 2.0 (TMDS, at most 600 MHz), but a mode with the display's own shortest - blanking at a slightly lower rate often fits. - -The menu item (only on a Steam Machine with the pinned kernel, never -ticked by default, run from the desktop in Konsole): - -1. Takes the desktop resolution from KDE (`kscreen-doctor -j`) and reads the - display's complete EDID over DDC (`i2ctransfer`, segment pointer 0x30). - A live EDID left by an earlier test is cleared first. -2. Calculates the highest rate that fits: the display's TMDS limit (HDMI - Forum VSDB, capped at amdgpu's 600 MHz), its shortest blanking at that - resolution and its maximum refresh (range limits, VRR maximum). Steps: - that rate rounded down to ten, and the hundred below it as a safe option. - Rates the display already lists, or that aren't faster than what works - now, are left out. -3. Builds the EDID: all of the display's blocks, the block count and EEODB - fixed, plus a DisplayID block with the steps. -4. Loads it live (debugfs `edid_override`, `trigger_hotplug`) and switches - to each step, lowest first. Each one needs a "y" within 15 s - (`WIZARD_HDMI_CONFIRM_SECONDS` for tests); anything else switches back - and stops. -5. Saves the confirmed steps for that display: the EDID as - `/usr/lib/firmware/edid/steamify-.bin`, where `` is the display's - manufacturer, model, serial and date (EDID bytes 8-17), and a line in - `/etc/steamify/hdmi-edid.conf` (id, name, mode, rates). Other saved - displays are kept. - -`steamify-edid.service` (at boot, before the login manager) and a udev rule -(`90-steamify-edid.rules`, every drm hotplug) run -`/usr/local/bin/steamify-edid-hotplug`. Per HDMI port it reads the connected -display's ID over DDC (the real display, even while an override is loaded) -and loads that display's saved EDID through debugfs, or resets the port to -the display's own EDID when there is none, or no display. What's loaded per -port is kept in `/run/steamify-edid`, so the hotplug the script triggers -itself doesn't loop. - -The item is on when the connected display runs on its saved EDID; with -another display it's off, and ticking it sets that one up. Turning it off -removes the connected display's EDID; the unit and rule go with the last -one. In the app the item is a **Set up…** button, and **Manage** once a -display is saved: it lists every saved display, removes any of them, and -sets up the connected display when it has none. Unpinning the kernel -removes them all. - -Versions before 2.1.0 used `drm.edid_firmware=` on the kernel command line -(and the initramfs), which applied to any display on that port; re-applying -saves such a setup per display and removes the parameter. Untick it before removing the -kernel pin: newer kernels read the EDID themselves and can do HDMI 2.1. - ## BIOS updates (Steam Machine) The **Update BIOS** item is only shown on a Steam Machine and is never ticked @@ -403,9 +321,8 @@ immediately, which can turn into a loop - see | `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; kernel pin | +| `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/hdmi-refresh.sh` | HDMI refresh boost (Steam Machine, pinned kernel): EDID over DDC, calculated steps, live test, per-display EDID hotplug script | | `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/` embedded | | `.github/workflows/bundle.yml` | Builds and checks it on every push; publishes it on `main` | diff --git a/lib/fremont-poweroff.sh b/lib/fremont-poweroff.sh index 7619ead..2ea8482 100644 --- a/lib/fremont-poweroff.sh +++ b/lib/fremont-poweroff.sh @@ -4,8 +4,7 @@ # booting up again. Recent kernels (7.2, and the 6.x, 7.0 and 7.1 updates # that got the change backported) keep the S4/S5 wake bit the firmware # leaves set on GPIO pin 18 ("pinctrl-amd: Don't clear S4 wake bits at -# probe"), so the machine starts again right after powering off (the reason -# for the old kernel pin). Valve's kernel clears it at probe on Fremont, in +# probe"), so the machine starts again right after powering off. Valve's kernel clears it at probe on Fremont, in # a patch not meant for upstream, so CachyOS won't get it. A small module # (patches/steamify-fremont-poweroff.c) clears it right before power-off; # DKMS builds it for each installed kernel. On a kernel that clears it diff --git a/lib/menu.sh b/lib/menu.sh index cf623e6..95f8123 100644 --- a/lib/menu.sh +++ b/lib/menu.sh @@ -34,7 +34,7 @@ declare -A LABEL=( [cec]="HDMI-CEC: use Steam with the TV remote, TV on/off with the PC (experimental)" [machine]="Steam Machine support: LED bar driver, hardware settings in Steam" [poweroff]="Power-off fix: the Steam Machine stays off after shutting down" - [kpin]="Pin the kernel to $PINNED_KERNEL_VER (no longer needed: untick for CachyOS's current kernel)" + [kpin]="Pin the kernel to $PINNED_KERNEL_VER (untick for CachyOS's current kernel)" [hdmi]="HDMI refresh boost: highest refresh your HDMI display runs" [bios]="Update BIOS" ) diff --git a/ui/qml/Main.qml b/ui/qml/Main.qml index d52c087..46dc0f3 100644 --- a/ui/qml/Main.qml +++ b/ui/qml/Main.qml @@ -72,8 +72,8 @@ ApplicationWindow { poweroff: { label: "Power-off fix", hint: "Stays off after shutting down", body: "With recent kernels the Steam Machine starts again right after shutting down: the firmware leaves a wake bit set, and newer kernels (7.2, and updates of 6.x, 7.0 and 7.1) no longer clear it. Valve's own kernel clears it; this small module does the same right before power-off.", changes: ["steamify-fremont-poweroff module for each installed kernel (DKMS)", "Only on a Steam Machine, only touches that one wake bit", "Off: recent kernels may start it again after shutting down"] }, - kpin: { label: "Pin the kernel", hint: "No longer needed: untick for the current kernel", - body: "Keeps the Steam Machine on CachyOS kernel 7.1.6, which older versions of Steamify needed to shut down properly. The power-off fix handles that now. Untick it to go back to CachyOS's current kernel (HDMI refresh boost goes with it: newer kernels read the display's fast modes and do HDMI 2.1 themselves).", + kpin: { label: "Pin the kernel", hint: "Untick for CachyOS's current kernel", + body: "Keeps the Steam Machine on CachyOS kernel 7.1.6. Untick it to go back to CachyOS's current kernel; HDMI refresh boost goes with it.", changes: ["linux-cachyos from Steamify's release (signature checked)", "Kept in /var/cache/steamify/kernel", "Added to IgnorePkg"] }, hdmi: { label: "HDMI refresh boost", hint: "Higher refresh rates over HDMI", body: "The pinned kernel keeps many HDMI displays at 60 Hz. Turning this on shows which refresh rates your display can run at the desktop resolution; you pick them, and each one is tried for 15 seconds so you can check the picture before it's installed.", From 38642dc6dbfb5fda11ffb021a8a7dae19fb564db Mon Sep 17 00:00:00 2001 From: rickpeters Date: Sun, 27 Sep 2026 12:27:08 +0200 Subject: [PATCH 05/10] fix: Remove whatever is left of HDMI refresh boost, pin or no pin --- AGENTS.md | 7 +++++-- CHANGELOG.md | 6 ++++-- lib/hdmi-refresh.sh | 35 ++++++++++++++--------------------- lib/menu.sh | 8 +++++--- 4 files changed, 28 insertions(+), 28 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index c9988b1..50c3983 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -222,8 +222,11 @@ gamescope and the Plasma desktop. Primary target: the Valve Steam Machine `steamify-fremont-poweroff` (`patches/steamify-fremont-poweroff.c`) clears it in a power-off-prepare handler, built for each kernel. It made the kernel pin unnecessary: `kpin` is only available while on (`kpin_available`), and - `detect_components` unticks it (and `hdmi`, which needs the pinned - kernel), so a normal run removes an existing pin. CachyOS's + `detect_components` unticks it, so a normal run removes an existing pin. + `hdmi` (it needed the pin) is retired the same way: `hdmi_status` is on + while anything of it is left (saved displays, hotplug unit, EDID files, + old kernel parameter), `hdmi_available` only then, it's always unticked, + and `hdmi_disable` removes all of it. CachyOS's `linux-cachyos` is clang-built, `-bore` GCC-built: let DKMS pick the compiler, never pass `LLVM=1`. Test shutdown on the real machine for every new major kernel. diff --git a/CHANGELOG.md b/CHANGELOG.md index a4a6e10..969785f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,8 +19,10 @@ one per merged pull request. rebooted. - The kernel pin is no longer offered. An existing pin is shown unticked, so the next run removes it and brings back CachyOS's current kernel; - HDMI refresh boost, which needs the pinned kernel, goes with it (newer - kernels do HDMI 2.1 themselves). + HDMI refresh boost, which needs the pinned kernel, is retired too + (newer kernels do HDMI 2.1 themselves): whatever is left of it (saved + displays, also ones not connected, its hotplug script, the old kernel + parameter) is unticked and removed by the next run, pin or no pin. - Feature versions: each component records the version of what it set up; when a release changes a component, installs with an older version are ticked and updated by a normal run ("update" in the plan and the diff --git a/lib/hdmi-refresh.sh b/lib/hdmi-refresh.sh index a2a8fd8..b3b8ea2 100644 --- a/lib/hdmi-refresh.sh +++ b/lib/hdmi-refresh.sh @@ -56,18 +56,17 @@ hdmi_cmdline_param() { } hdmi_available() { - # Stays available while on, so it can be turned off after the pin is gone. - detect_valve_fremont || return 1 - # Saved displays too, so they can be removed without the pin. - hdmi_status || [[ -n "$(hdmi_saved)" ]] || { pinned_kernel_installed && [[ -n "$(hdmi_connectors)" ]]; } + # No longer offered (it needed the pinned kernel): only shown while + # anything of it is left, so a normal run removes it. + detect_valve_fremont && hdmi_status } hdmi_status() { - # On = the connected display runs on its saved EDID. Another display on - # the port is off, so ticking it sets that one up. The command line: set - # up by an older version, re-applying moves it over. - [[ -n "$(hdmi_active_ids)" ]] || - { [[ -n "$(hdmi_cmdline_param)" ]] && compgen -G "$HDMI_FW_DIR/steamify-*.bin" >/dev/null; } + # On while anything of it is left: saved displays (connected or not), the + # hotplug script, an EDID file, or the kernel parameter of versions + # before 2.1.0. + [[ -n "$(hdmi_saved)" || -f "$HDMI_UNIT" || -n "$(hdmi_cmdline_param)" ]] || + compgen -G "$HDMI_FW_DIR/steamify-*.bin" >/dev/null } hdmi_saved() { [[ -f "$HDMI_MAP" ]] && grep -v '^#' "$HDMI_MAP"; } @@ -688,16 +687,10 @@ hdmi_enable() { } hdmi_disable() { - # For the connected display; other saved displays stay (the app lists - # them). Without the pinned kernel none may stay: newer kernels read the - # EDID themselves. - local id - if [[ -n "$(hdmi_cmdline_param)" ]]; then - hdmi_forget all; hdmi_remove_boot_param || return 1; state_clear hdmi - elif [[ "${WANTED[kpin]:-1}" == 0 ]] || ! pinned_kernel_installed; then - hdmi_forget all - else - for id in $(hdmi_active_ids); do hdmi_forget "$id"; done - fi - ok "HDMI refresh boost removed for the connected display; it uses its own EDID again." + # Everything, for every saved display: newer kernels read the whole EDID + # themselves, and an old one loaded at the next hotplug would be wrong. + hdmi_forget all + hdmi_remove_boot_param || return 1 + state_clear hdmi + ok "HDMI refresh boost removed; displays use their own EDID again." } diff --git a/lib/menu.sh b/lib/menu.sh index 95f8123..6e1742f 100644 --- a/lib/menu.sh +++ b/lib/menu.sh @@ -108,9 +108,11 @@ detect_components() { component_available "$c" || continue { feature_outdated "$c" || feature_new "$c"; } && WANTED[$c]=1 done - # The kernel pin is no longer needed (the power-off fix): drop it, and - # HDMI refresh boost, which needs it, on a normal run. - if component_available kpin; then WANTED[kpin]=0; WANTED[hdmi]=0; fi + # The kernel pin is no longer needed (the power-off fix), and HDMI + # refresh boost needed the pin: whatever is left of either is unticked, + # so a normal run removes it. + component_available kpin && WANTED[kpin]=0 + component_available hdmi && WANTED[hdmi]=0 # The terminal-only Steamify shortcut from before 2.0.1: tick it, so a # normal run replaces it with the app. launcher_repair && WANTED[launcher]=1 From 2fa347be73f718388a47f926b5a067d68ce0c68a Mon Sep 17 00:00:00 2001 From: rickpeters Date: Sun, 27 Sep 2026 12:27:55 +0200 Subject: [PATCH 06/10] feat: Feature versions are Steamify versions; Steam Machine support is 2.2.0 --- AGENTS.md | 5 +++-- TECHNICAL.md | 14 +++++++++----- lib/menu.sh | 31 ++++++++++++++++++++----------- 3 files changed, 32 insertions(+), 18 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 50c3983..63d2afd 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -29,8 +29,9 @@ gamescope and the Plasma desktop. Primary target: the Valve Steam Machine `_available` is checked via `component_available` (e.g. `machine` only on Fremont). Turn-on order is `COMPONENTS` order, turn-off reverse; `gaming` must stay first. Dependencies live in `toggle_component`. -- **Feature versions.** Bump `FEATURE_VERSION[]` (`lib/menu.sh`) whenever - what `_enable` sets up changes: installs recorded with an older number +- **Feature versions.** Set `FEATURE_VERSION[]` (`lib/menu.sh`) to the new + `VERSION` whenever what `_enable` sets up changes (a new component gets + one too): installs recorded with an older version are ticked and re-applied ("update" in the plan and the app). New default sub-options are ticked for installs whose parent is on (`feature_new`). Status still comes from the system; don't add ad-hoc `_repair` checks diff --git a/TECHNICAL.md b/TECHNICAL.md index 3e2d66b..2bb7b10 100644 --- a/TECHNICAL.md +++ b/TECHNICAL.md @@ -238,20 +238,24 @@ normal run adds it. ## Feature versions (updates) -Each component has a feature version (`FEATURE_VERSION` in `lib/menu.sh`, -1 when not listed). After a component is turned on successfully, the version -is recorded in `~/.local/state/cachyos-gamescope-boot/features.state` +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/cachyos-gamescope-boot/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 1. + "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 sub-option** (not in `NO_PRESELECT`) whose parent is on 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. -Bump a component's number whenever what its `_enable` sets up changes. +Set a component's entry to the new `VERSION` whenever what its +`_enable` sets up changes. ## BIOS updates (Steam Machine) diff --git a/lib/menu.sh b/lib/menu.sh index 6e1742f..6f64ecd 100644 --- a/lib/menu.sh +++ b/lib/menu.sh @@ -17,12 +17,18 @@ declare -A PARENT=([boot]=gaming [poweroff]=machine [kpin]=machine [hdmi]=machin # which has CEC like on SteamOS. HDMI refresh boost needs someone at the # screen to confirm each step. NO_PRESELECT=(boot cec kpin hdmi) -# Feature versions: bump a component's number whenever what its enable sets -# up changes. Each successful run records the number (state "features"); a -# component that's on with an older number is ticked and re-applied by a -# normal run, and the app shows it as an update. Unlisted = 1, and so is a -# setup from before these were recorded. -declare -A FEATURE_VERSION=() +# Feature versions: the Steamify version in which what a component's enable +# sets up last changed; set it to the new VERSION whenever you change one. +# Each successful run records it (state "features"); a component that's on +# with an older one is ticked and re-applied by a normal run, and the app +# shows it as an update. A setup from before these were recorded counts as +# FEATURE_BASELINE. +FEATURE_BASELINE=2.1.0 +declare -A FEATURE_VERSION=( + [gaming]=2.1.0 [boot]=2.1.0 [theme]=2.1.0 [glyphs]=2.1.0 [single]=2.1.0 + [launcher]=2.1.0 [cec]=2.1.0 [machine]=2.2.0 [poweroff]=2.2.0 + [kpin]=2.1.0 [hdmi]=2.1.0 +) declare -A LABEL=( [gaming]="SteamOS conversion: boot into gaming mode, Steam on the desktop" @@ -66,16 +72,19 @@ menu_visible() { feature_record() { # feature_record : after a successful run. - if [[ "$2" == enable ]]; then state_set features "$1" "${FEATURE_VERSION[$1]:-1}" + if [[ "$2" == enable ]]; then state_set features "$1" "${FEATURE_VERSION[$1]:-$FEATURE_BASELINE}" else state_set features "$1" off; fi } feature_outdated() { - # On, but set up by an older version of that feature. - local have + # On, but set up by an older version of that feature (compared as + # versions: 2.10.0 is newer than 2.9.0). + local have want [[ "${CURRENT[$1]:-0}" == 1 ]] || return 1 - have="$(state_get features "$1" 1)" - [[ "$have" =~ ^[0-9]+$ ]] && (( have < ${FEATURE_VERSION[$1]:-1} )) + have="$(state_get features "$1" "$FEATURE_BASELINE")" + [[ "$have" == off ]] && have="$FEATURE_BASELINE" + want="${FEATURE_VERSION[$1]:-$FEATURE_BASELINE}" + [[ "$have" != "$want" && "$(printf '%s\n' "$have" "$want" | sort -V | head -n 1)" == "$have" ]] } feature_new() { From 40a121f4d12723717bbeaa8fcfb6c1859c70034b Mon Sep 17 00:00:00 2001 From: rickpeters Date: Sun, 27 Sep 2026 12:31:29 +0200 Subject: [PATCH 07/10] feat: Update badge on components that get a newer version --- lib/menu.sh | 15 +++++++++++++-- ui/qml/Main.qml | 6 +++++- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/lib/menu.sh b/lib/menu.sh index 6f64ecd..2d8baec 100644 --- a/lib/menu.sh +++ b/lib/menu.sh @@ -87,6 +87,17 @@ feature_outdated() { [[ "$have" != "$want" && "$(printf '%s\n' "$have" "$want" | sort -V | head -n 1)" == "$have" ]] } +menu_label() { + # The label, with "(update)" after the name when a newer version of it + # will be applied. + local l="${LABEL[$1]}" + if feature_outdated "$1"; then + if [[ "$l" == *:* ]]; then l="${l%%:*} ${c_yellow}(update)${c_reset}:${l#*:}" + else l+=" ${c_yellow}(update)${c_reset}"; fi + fi + printf '%s' "$l" +} + feature_new() { # A default sub-option added after its parent was set up (e.g. the # power-off fix under Steam Machine support): never turned on or off. @@ -183,9 +194,9 @@ show_menu() { local tree=""; [[ -n "${PARENT[$c]:-}" ]] && tree=" └ " printf " %b%-3s %-6s %-6s %s%s (not available)%b\n" "$c_dim" "$i" "$now" "$want" "$tree" "${LABEL[$c]}" "$c_reset" elif [[ -n "${PARENT[$c]:-}" ]]; then - printf " %-3s %b %-6s └ %s\n" "$i" "$now" "$want" "${LABEL[$c]}" + printf " %-3s %b %-6s └ %b\n" "$i" "$now" "$want" "$(menu_label "$c")" else - printf " %-3s %b %-6s %s\n" "$i" "$now" "$want" "${LABEL[$c]}" + printf " %-3s %b %-6s %b\n" "$i" "$now" "$want" "$(menu_label "$c")" fi done echo diff --git a/ui/qml/Main.qml b/ui/qml/Main.qml index 46dc0f3..3752b61 100644 --- a/ui/qml/Main.qml +++ b/ui/qml/Main.qml @@ -627,7 +627,11 @@ ApplicationWindow { x: row.modelData.parent ? 46 : 16 width: controls.x - x - 16 opacity: row.modelData.kind === "action" && !(bios && bios.selectable) ? 0.6 : 1 - Text { text: label(row.modelData); color: t.textHi; font.family: t.body; font.pixelSize: 17; font.weight: Font.DemiBold; elide: Text.ElideRight; width: parent.width } + // The name, with an Update badge when a newer version of it will be applied. + Row { width: parent.width; spacing: 10 + Text { text: label(row.modelData); color: t.textHi; font.family: t.body; font.pixelSize: 17; font.weight: Font.DemiBold; elide: Text.ElideRight + width: Math.min(implicitWidth, parent.width - (upd.visible ? upd.width + parent.spacing : 0)) } + Chip { id: upd; visible: !!row.modelData.update; text: "Update"; fg: t.warn; bgc: t.warnBg; height: 20; anchors.verticalCenter: parent.verticalCenter } } Text { text: row.modelData.id === "bios" ? biosHint() : ((row.modelData.id === "hdmi" && hdmiRowHint()) || (texts[row.modelData.id] && texts[row.modelData.id].hint) || row.modelData.hint); color: t.mute; font.family: t.body; font.pixelSize: 13; elide: Text.ElideRight; width: parent.width } } // Right: every control ends on the same edge From dc1abd912b394ca09b9b5b7e4ad180ac44146b2a Mon Sep 17 00:00:00 2001 From: rickpeters Date: Sun, 27 Sep 2026 12:33:47 +0200 Subject: [PATCH 08/10] fix(ui): Never write the sudo password to a file; Update label on the progress screen The askpass helper reads the password from a named pipe that the app feeds from memory each time sudo asks, instead of from a file. --- ui/qml/Main.qml | 2 +- ui/steamify-ui | 42 ++++++++++++++++++++++++++++++++++-------- 2 files changed, 35 insertions(+), 9 deletions(-) diff --git a/ui/qml/Main.qml b/ui/qml/Main.qml index 3752b61..47a4829 100644 --- a/ui/qml/Main.qml +++ b/ui/qml/Main.qml @@ -858,7 +858,7 @@ ApplicationWindow { border.width: parent.parent.st === "wait" || parent.parent.st === "run" ? 2 : 0; border.color: parent.parent.st === "run" ? t.accent : "#343f50" Text { anchors.centerIn: parent; text: parent.parent.parent.st === "ok" ? "✓" : (parent.parent.parent.st === "fail" ? "!" : ""); color: parent.parent.parent.st === "ok" ? t.good : t.bad; font.pixelSize: 13; font.weight: Font.Bold } RotationAnimator on rotation { running: parent.parent.parent.st === "run"; from: 0; to: 360; duration: 1000; loops: Animation.Infinite } } - Text { text: ({ on: "Turn on ", off: "Turn off ", again: "Re-apply ", desktop: "Boot into ", gaming: "Boot into ", check: "Download and check the ", flash: "Hand to fwupd: the " })[parent.parent.modelData.action] + ((texts[parent.parent.modelData.id] || {}).label || parent.parent.modelData.id) + Text { text: (({ on: "Turn on ", off: "Turn off ", again: "Re-apply ", update: "Update ", desktop: "Boot into ", gaming: "Boot into ", check: "Download and check the ", flash: "Hand to fwupd: the " })[parent.parent.modelData.action] || "") + ((texts[parent.parent.modelData.id] || {}).label || parent.parent.modelData.id) color: parent.parent.st === "wait" ? t.faint : t.textHi; font.family: t.body; font.pixelSize: 16; font.weight: Font.DemiBold; anchors.verticalCenter: parent.verticalCenter; width: 380; elide: Text.ElideRight } Text { text: ({ wait: "Waiting", run: "Working…", ok: "Done", fail: "Problem" })[parent.parent.st]; color: parent.parent.st === "ok" ? t.good : (parent.parent.st === "fail" ? t.bad : "#b8c3d1"); font.family: t.body; font.pixelSize: 13; anchors.verticalCenter: parent.verticalCenter } } diff --git a/ui/steamify-ui b/ui/steamify-ui index f0913f4..0bc994e 100755 --- a/ui/steamify-ui +++ b/ui/steamify-ui @@ -197,19 +197,19 @@ class Backend(QObject): p.start() def _make_askpass(self, password): - """A private folder with the password, a helper that prints it for - `sudo -A`, and a `sudo` that always uses the helper (makepkg and - yay call plain sudo).""" + """A private folder with a helper that `sudo -A` runs for the + password, and a `sudo` that always uses it (makepkg and yay call + plain sudo). The password is never written to a file: the helper + reads it from a named pipe, and the app writes it into the pipe from + memory each time sudo asks, until _drop_askpass.""" base = os.environ.get("XDG_RUNTIME_DIR") or tempfile.gettempdir() d = tempfile.mkdtemp(prefix="steamify-", dir=base) os.chmod(d, stat.S_IRWXU) - secret = os.path.join(d, "secret") - fd = os.open(secret, os.O_WRONLY | os.O_CREAT | os.O_EXCL, 0o600) - with os.fdopen(fd, "w") as f: - f.write(password + "\n") + pipe = os.path.join(d, "pipe") + os.mkfifo(pipe, 0o600) askpass = os.path.join(d, "askpass") with open(askpass, "w") as f: - f.write("#!/bin/sh\nexec cat %s\n" % json.dumps(secret)) + f.write("#!/bin/sh\nexec cat %s\n" % json.dumps(pipe)) os.chmod(askpass, 0o700) bindir = os.path.join(d, "bin") os.mkdir(bindir) @@ -217,9 +217,35 @@ class Backend(QObject): with open(wrapper, "w") as f: f.write("#!/bin/sh\nexec /usr/bin/sudo -A \"$@\"\n") os.chmod(wrapper, 0o700) + stop = threading.Event() + data = (password + "\n").encode() + + def serve(): + while not stop.is_set(): + try: + fd = os.open(pipe, os.O_WRONLY) # waits for a reader + except OSError: + return + try: + if not stop.is_set(): + os.write(fd, data) + except OSError: + pass + finally: + os.close(fd) + threading.Thread(target=serve, daemon=True).start() + self._askpass_stop = (stop, pipe) return d, askpass, bindir def _drop_askpass(self): + if getattr(self, "_askpass_stop", None): + stop, pipe = self._askpass_stop + self._askpass_stop = None + stop.set() + try: # wakes the writer waiting for a reader, which then ends + os.close(os.open(pipe, os.O_RDONLY | os.O_NONBLOCK)) + except OSError: + pass if self._secret_dir: shutil.rmtree(self._secret_dir, ignore_errors=True) self._secret_dir = None From a252d38c7ca9c71b341fe402adfb2ad903bdd02b Mon Sep 17 00:00:00 2001 From: rickpeters Date: Sun, 27 Sep 2026 12:35:40 +0200 Subject: [PATCH 09/10] feat(ui): Update badge on the progress screen --- ui/qml/Main.qml | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/ui/qml/Main.qml b/ui/qml/Main.qml index 47a4829..9f2f700 100644 --- a/ui/qml/Main.qml +++ b/ui/qml/Main.qml @@ -858,8 +858,15 @@ ApplicationWindow { border.width: parent.parent.st === "wait" || parent.parent.st === "run" ? 2 : 0; border.color: parent.parent.st === "run" ? t.accent : "#343f50" Text { anchors.centerIn: parent; text: parent.parent.parent.st === "ok" ? "✓" : (parent.parent.parent.st === "fail" ? "!" : ""); color: parent.parent.parent.st === "ok" ? t.good : t.bad; font.pixelSize: 13; font.weight: Font.Bold } RotationAnimator on rotation { running: parent.parent.parent.st === "run"; from: 0; to: 360; duration: 1000; loops: Animation.Infinite } } - Text { text: (({ on: "Turn on ", off: "Turn off ", again: "Re-apply ", update: "Update ", desktop: "Boot into ", gaming: "Boot into ", check: "Download and check the ", flash: "Hand to fwupd: the " })[parent.parent.modelData.action] || "") + ((texts[parent.parent.modelData.id] || {}).label || parent.parent.modelData.id) - color: parent.parent.st === "wait" ? t.faint : t.textHi; font.family: t.body; font.pixelSize: 16; font.weight: Font.DemiBold; anchors.verticalCenter: parent.verticalCenter; width: 380; elide: Text.ElideRight } + // The step; an update shows the name with an Update badge, like the menu. + Item { width: 380; height: parent.height + readonly property var step: parent.parent.modelData + readonly property bool update: step.action === "update" + Row { anchors.verticalCenter: parent.verticalCenter; width: parent.width; spacing: 10 + Text { text: (({ on: "Turn on ", off: "Turn off ", again: "Re-apply ", desktop: "Boot into ", gaming: "Boot into ", check: "Download and check the ", flash: "Hand to fwupd: the " })[parent.parent.step.action] || "") + ((texts[parent.parent.step.id] || {}).label || parent.parent.step.id) + color: parent.parent.parent.parent.st === "wait" ? t.faint : t.textHi; font.family: t.body; font.pixelSize: 16; font.weight: Font.DemiBold; anchors.verticalCenter: parent.verticalCenter; elide: Text.ElideRight + width: Math.min(implicitWidth, parent.width - (stepUpd.visible ? stepUpd.width + parent.spacing : 0)) } + Chip { id: stepUpd; visible: parent.parent.update; text: "Update"; fg: t.warn; bgc: t.warnBg; height: 20; anchors.verticalCenter: parent.verticalCenter } } } Text { text: ({ wait: "Waiting", run: "Working…", ok: "Done", fail: "Problem" })[parent.parent.st]; color: parent.parent.st === "ok" ? t.good : (parent.parent.st === "fail" ? t.bad : "#b8c3d1"); font.family: t.body; font.pixelSize: 13; anchors.verticalCenter: parent.verticalCenter } } } From 24f15421fd1f121817495c768b181fe6f97070cb Mon Sep 17 00:00:00 2001 From: rickpeters Date: Sun, 27 Sep 2026 12:36:25 +0200 Subject: [PATCH 10/10] docs: Changelog entries per commit for 2.2.0 --- CHANGELOG.md | 58 +++++++++++++++++++++++++++++++++------------------- 1 file changed, 37 insertions(+), 21 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 969785f..f92b5fd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,30 +7,46 @@ one per merged pull request. ## 2.2.0 - 2026-09-27 -- **feat: Power-off fix for the Steam Machine; the kernel pin is optional** +- **docs: Changelog entries per commit for 2.2.0** +- `32795bd` **feat(ui): Update badge on the progress screen** +- `3c8ca65` **fix(ui): Never write the sudo password to a file; Update label on the progress screen** + - The app gave `sudo` the password through a short-lived file; the helper + now reads it from a named pipe the app feeds from memory each time sudo + asks. The progress screen showed "undefined" for an update. +- `11a91ef` **feat: Update badge on components that get a newer version** + - The menu (terminal and app) shows "(update)" / an Update badge after + the name of a component a normal run will update. +- `163f790` **feat: Feature versions are Steamify versions; Steam Machine support is 2.2.0** + - Each component records the Steamify version of what it set up; when a + release changes a component, installs with an older version are ticked + and updated by a normal run ("update" in the plan and the app's + review). A setup from before counts as 2.1.0, so Steam Machine support + (2.2.0) is updated on machines set up with 2.1.0. New default + sub-options (like the power-off fix) are ticked where their parent is on. +- `e121b06` **fix: Remove whatever is left of HDMI refresh boost, pin or no pin** + - HDMI refresh boost needed the pinned kernel and is retired (newer + kernels do HDMI 2.1 themselves): whatever is left of it (saved + displays, also ones not connected, its hotplug script, the old kernel + parameter) is unticked and removed by the next run. +- `19ca966` **docs: Drop the kernel pin and HDMI refresh boost descriptions** +- `56fb908` **docs: Power-off fix texts cover every affected kernel, not just 7.2** +- `6df19eb` **feat: Power-off fix as its own sub-option, drop the kernel pin, feature versions** + - The power-off fix is a sub-option of Steam Machine support, ticked + along with it (opt-out). The kernel pin is no longer offered: an + existing pin is shown unticked, so the next run removes it and brings + back CachyOS's current kernel. +- `7694c97` **feat: Power-off fix for the Steam Machine; the kernel pin is optional** - With recent kernels (newer 6.x, 7.0 and 7.1 updates, 7.2 and probably later ones) the Steam Machine started again right after shutting down: - they keep the wake bit the firmware sets on GPIO - pin 18, which Valve's own kernel clears in a patch that won't reach - CachyOS. Steam Machine support now builds a small module with DKMS for - each installed kernel that clears it right before power-off: the new **Power-off - fix** sub-option, ticked along with Steam Machine support (opt-out). - Tested with CachyOS 7.2.8: stays off, and without the module it - rebooted. - - The kernel pin is no longer offered. An existing pin is shown unticked, - so the next run removes it and brings back CachyOS's current kernel; - HDMI refresh boost, which needs the pinned kernel, is retired too - (newer kernels do HDMI 2.1 themselves): whatever is left of it (saved - displays, also ones not connected, its hotplug script, the old kernel - parameter) is unticked and removed by the next run, pin or no pin. - - Feature versions: each component records the version of what it set - up; when a release changes a component, installs with an older version - are ticked and updated by a normal run ("update" in the plan and the - app), and new default sub-options (like the power-off fix) are ticked - where their parent is on. + they keep the wake bit the firmware sets on GPIO pin 18, which Valve's + own kernel clears in a patch that won't reach CachyOS. A small module, + built with DKMS for each installed kernel, clears it right before + power-off. Tested with CachyOS 7.2.8: stays off, and without the module + it rebooted. - Module sources and patches live in `patches/` instead of inside the - shell code; the single-file build embeds them. The Steam Machine CEC - driver change is now `patches/cros-ec-cec-single-port.patch`. + shell code; the single-file build and the app package include them. The + Steam Machine CEC driver change is now + `patches/cros-ec-cec-single-port.patch`. ## 2.1.0 - 2026-09-26