Files
steamify-cachyos/lib/steam-machine.sh
T

340 lines
16 KiB
Bash

#!/bin/bash
# "Steam Machine support" menu item, only on Valve Fremont hardware: the
# 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() {
# Valve Steam Machine (Fremont) boards. The leds-valve driver's own DMI
# table also matches the OEM/F7F strings some early units report, so
# accept both -- otherwise we'd skip hardware the driver supports.
local vendor product
vendor="$(cat /sys/class/dmi/id/sys_vendor 2>/dev/null || true)"
product="$(cat /sys/class/dmi/id/product_name 2>/dev/null || true)"
[[ "$vendor" == "Valve" && "$product" == "Fremont" ]] ||
[[ "$vendor" == "OEM" && "$product" == "F7F" ]]
}
install_kernel_headers() {
# DKMS can only build leds-valve against kernels whose headers are
# installed. CachyOS ships several kernels (linux-cachyos, -lts, -bore,
# ...); install the matching -headers package for every one present.
local -a kernels headers=()
local k
mapfile -t kernels < <(pacman -Qqo /usr/lib/modules/*/pkgbase 2>/dev/null | sort -u)
# Installed headers are left alone: -S would update them past a pinned
# kernel (linux-cachyos-headers on a Steam Machine).
local have=0
for k in "${kernels[@]}"; do
pacman -Q "${k}-headers" >/dev/null 2>&1 && { have=$((have + 1)); continue; }
pacman -Si "${k}-headers" >/dev/null 2>&1 && headers+=("${k}-headers")
done
if [[ ${#headers[@]} -eq 0 && $have -gt 0 ]]; then
ok "Kernel headers already installed."
return 0
fi
if [[ ${#headers[@]} -eq 0 ]]; then
warn "Couldn't work out which kernel headers package you need."
warn "Install the -headers package for your kernel (e.g. linux-cachyos-headers) yourself."
return 1
fi
info "Making sure kernel headers are installed: ${headers[*]}"
sudo pacman -S --needed --noconfirm "${headers[@]}"
}
led_write_dkms_override() {
# The upstream Makefile builds against `uname -r` instead of the kernel
# DKMS targets, so every other installed kernel (e.g. linux-cachyos-lts)
# got the running kernel's tree and failed (wrong kernel, and gcc against
# a clang-built tree). DKMS reads this override after the package's
# dkms.conf; written before the install so its own build works too.
# On a fresh system dkms isn't installed yet, so /etc/dkms doesn't exist.
# Someone's own override is backed up and kept, with ours after it: it's
# sourced as bash, so our MAKE[0] wins.
sudo mkdir -p "$(dirname "$LED_DKMS_OVERRIDE")"
if ! led_dkms_override | sudo tee "$LED_DKMS_OVERRIDE.new" >/dev/null ||
! sudo mv -f "$LED_DKMS_OVERRIDE.new" "$LED_DKMS_OVERRIDE"; then
err "Couldn't write $LED_DKMS_OVERRIDE; without it the LED driver only builds for the running kernel."
return 1
fi
}
led_install_package() {
# led_install_package <aur helper>
local aur_helper="$1"
local -a flags
read -ra flags <<< "$(aur_noninteractive_flags "$aur_helper")"
if pacman -Qi leds-valve-dkms-git >/dev/null 2>&1; then
ok "leds-valve-dkms-git already installed."
return 0
fi
info "Installing leds-valve-dkms-git from the AUR via $aur_helper (non-interactive)..."
"$aur_helper" -S "${flags[@]}" leds-valve-dkms-git || { err "Installing leds-valve-dkms-git failed."; return 1; }
}
led_build_all_kernels() {
# Build for every installed kernel that has headers, so booting another
# one (e.g. the LTS kernel) still has the driver.
local kdir kver
for kdir in /usr/lib/modules/*/build; do
[[ -d "$kdir" ]] || continue
kver="$(basename "$(dirname "$kdir")")"
dkms status -k "$kver" leds-valve-dkms 2>/dev/null | grep -q installed && continue
info "Building leds-valve for kernel $kver..."
if ! sudo dkms install leds-valve-dkms/0.1 -k "$kver"; then
err "DKMS build for $kver failed. See the build log:"
err " /var/lib/dkms/leds-valve-dkms/0.1/build/make.log"
return 1
fi
done
}
led_load_module() {
# Load now, and on every boot.
echo leds-valve | sudo tee /etc/modules-load.d/leds-valve.conf >/dev/null
# The running kernel was just replaced (the kernel pin): its modules are
# gone, so the driver can only load after the restart.
if [[ ! -d "/usr/lib/modules/$(uname -r)/kernel" ]]; then
ok "The LED driver loads after the restart (into the new kernel)."
return 0
fi
info "Loading the leds-valve kernel module..."
if sudo modprobe leds-valve; then
ok "Module loaded."
else
warn "modprobe leds-valve failed. Kernel messages:"
sudo dmesg | grep -i 'valve' | tail -n 10
fi
if compgen -G "/sys/class/leds/valve-leds*" >/dev/null; then
ok "LED bar nodes detected: $(cd /sys/class/leds && echo valve-leds*)"
else
warn "No valve-leds nodes under /sys/class/leds/ yet. Reboot, then check with:"
warn " ls /sys/class/leds/ | grep valve ; sudo dmesg | grep -i valve"
fi
}
install_valve_led_driver() {
# Installs the leds-valve DKMS driver from the AUR so the front LED
# bar is exposed under /sys/class/leds/valve-leds[N].
local aur_helper
aur_helper="$(find_aur_helper)"
if [[ -z "$aur_helper" ]]; then
warn "No AUR helper available - can't install leds-valve-dkms-git."
warn "Install one yourself, then run: yay -S leds-valve-dkms-git"
return 1
fi
# Headers first: without them the package installs fine but DKMS
# silently builds nothing, so modprobe later finds no module.
install_kernel_headers || return 1
led_write_dkms_override || return 1
led_install_package "$aur_helper" || return 1
led_build_all_kernels || return 1
led_load_module
}
LED_UDEV_RULE="/etc/udev/rules.d/70-valve-leds-user.rules"
LED_DKMS_OVERRIDE="/etc/dkms/leds-valve-dkms.conf"
led_dkms_override() {
# The override to write: an existing one's own lines (backed up the first
# time), without ours from an earlier run, then ours. Before 2.5.0 the
# file was ours alone, with a different comment.
local ours
ours="$(patch_file leds-valve-dkms.conf)"
if [[ -f "$LED_DKMS_OVERRIDE" ]]; then
grep -qxF -f <(echo "$ours") "$LED_DKMS_OVERRIDE" ||
grep -q '^# Written by cachyos-gamescope-boot' "$LED_DKMS_OVERRIDE" ||
backup_file "$LED_DKMS_OVERRIDE" >&2
grep -vxF -f <(echo "$ours") "$LED_DKMS_OVERRIDE" | grep -v '^# Written by cachyos-gamescope-boot'
fi
echo "$ours"
}
led_dkms_override_remove() {
# Puts someone's own override back, or removes ours.
if [[ -f "$LED_DKMS_OVERRIDE.bak-gamescope-wizard" ]]; then
sudo mv -f "$LED_DKMS_OVERRIDE.bak-gamescope-wizard" "$LED_DKMS_OVERRIDE"
else
sudo rm -f "$LED_DKMS_OVERRIDE"
fi
}
HEADERS_SCRIPT="/usr/local/lib/steamify/ensure-kernel-headers"
# Before 2.5.0; removed when it's set up again or turned off.
HEADERS_SCRIPT_OLD="/usr/local/lib/cachyos-gamescope-boot/ensure-kernel-headers"
HEADERS_UNIT="/etc/systemd/system/ensure-kernel-headers.service"
install_headers_boot_check() {
# DKMS's pacman hook rebuilds leds-valve for every kernel whose headers
# get installed, but a newly added kernel (e.g. linux-cachyos-bore)
# comes without them, and a pacman hook can't install packages itself.
# So check at every boot and install missing headers, which triggers
# that hook and builds the driver for the new kernel.
sudo mkdir -p "$(dirname "$HEADERS_SCRIPT")"
patch_file ensure-kernel-headers.sh | sudo tee "$HEADERS_SCRIPT" > /dev/null
sudo chmod 755 "$HEADERS_SCRIPT"
sudo rm -rf "$(dirname "$HEADERS_SCRIPT_OLD")"
service_file ensure-kernel-headers.service HEADERS_SCRIPT="$HEADERS_SCRIPT" | sudo tee "$HEADERS_UNIT" > /dev/null
sudo systemctl daemon-reload
sudo systemctl enable ensure-kernel-headers.service
}
reload_powerdevil() {
qdbus6 org.kde.Solid.PowerManagement /org/kde/Solid/PowerManagement \
org.kde.Solid.PowerManagement.refreshStatus >/dev/null 2>&1 || true
}
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 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 off running line
local legend="> = running; controller = Steam controller driver"
[[ "$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")"
[[ -f "$kdir/pkgbase" ]] || continue
pkg="$(< "$kdir/pkgbase")"
headers="$miss"; [[ -d "$kdir/build" ]] && headers="$mark"
hid="$miss"; compgen -G "$kdir/kernel/drivers/hid/hid-steam.ko*" >/dev/null && hid="$mark"
running=" "; [[ "$k" == "$(uname -r)" ]] && running="${c_cyan}>${c_reset} "
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"
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
cec_installed && { echo; echo -e " $(cec_overview)"; }
if [[ "$leds" == true ]]; then
local loaded="$miss" nodes
lsmod | grep -q '^leds_valve' && loaded="$mark"
nodes="$(compgen -G '/sys/class/leds/valve-leds*' | wc -l)"
echo
echo -e " LED driver loaded: ${loaded} LED nodes: ${nodes} steamos-manager: $(systemctl is-active steamos-manager 2>/dev/null)"
fi
}
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
}
# Steam (steamwebhelper) reads the serial number for its System settings
# straight from DMI, which the kernel makes root-only; SteamOS makes it
# readable. A tmpfiles rule does that at every boot.
SERIAL_TMPFILES=/etc/tmpfiles.d/steamify-serial.conf
# Steam's Wi-Fi backend switch goes through steamos-manager, which reads and
# writes the backend in this file (SteamOS ships it); without it, it fails
# with "Wi-Fi backend not found in config". wpa_supplicant is what
# NetworkManager uses on CachyOS anyway. Not written when someone set a
# backend themselves.
WIFI_BACKEND_CONF=/etc/NetworkManager/conf.d/99-valve-wifi-backend.conf
wifi_backend_enable() {
[[ -f "$WIFI_BACKEND_CONF" ]] && return 0
grep -rqs '^wifi.backend=' /etc/NetworkManager/NetworkManager.conf /etc/NetworkManager/conf.d && return 0
printf '%s\n' "# Written by Steamify: Steam's Wi-Fi backend setting (steamos-manager)." \
'[device]' 'wifi.backend=wpa_supplicant' | sudo install -Dm644 /dev/stdin "$WIFI_BACKEND_CONF" ||
{ err "Writing the Wi-Fi backend setting failed."; return 1; }
state_set machine wifi_backend 1
}
wifi_backend_disable() {
[[ -n "$(state_get machine wifi_backend)" ]] || return 0
sudo rm -f "$WIFI_BACKEND_CONF"
state_clear machine
}
serial_enable() {
printf '%s\n' "# Written by Steamify: Steam shows the serial number, like on SteamOS." \
'z /sys/class/dmi/id/product_serial 0444 - - -' | sudo tee "$SERIAL_TMPFILES" > /dev/null ||
{ err "Making the serial number readable for Steam failed."; return 1; }
# The file applies at every boot; now too, unless /sys is read-only (the ISO's installer runs in a chroot).
best_effort sudo systemd-tmpfiles --create "$SERIAL_TMPFILES" ||
info "The serial number becomes readable for Steam at the next boot."
return 0
}
serial_disable() {
sudo rm -f "$SERIAL_TMPFILES"
best_effort sudo chmod 0400 /sys/class/dmi/id/product_serial
return 0
}
machine_enable() {
# The pinned kernel first when it's wanted too, so DKMS builds the LED
# driver for it once, instead of for the current kernel and then again.
if [[ "${WANTED[kpin]:-0}" == 1 ]]; then
install_pinned_kernel || { err "Couldn't set up kernel $PINNED_KERNEL_VER."; return 1; }
fi
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
# 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
# doesn't cover the Steam Machine's LED bar).
info "Letting $TARGET_USER control the LED bar..."
sudo tee "$LED_UDEV_RULE" > /dev/null << EOF
# Let the console user (and Steam) control the Steam Machine's front LED bar.
SUBSYSTEM=="leds", KERNEL=="valve-leds*", RUN+="/usr/bin/find /sys%p -maxdepth 1 -type f -exec /usr/bin/chown $TARGET_USER {} +"
EOF
sudo udevadm control --reload
sudo udevadm trigger --subsystem-match=leds --action=add
# steamos-manager and inputplumber are what Steam in gaming mode talks to
# for hardware and controller settings; it knows the Steam Machine from its DMI data.
info "Installing hardware manager packages (steamos-manager & inputplumber)..."
sudo pacman -S --needed --noconfirm steamos-manager inputplumber || { err "Installing hardware manager packages failed."; return 1; }
serial_enable || return 1
wifi_backend_enable || return 1
sudo systemctl enable --now inputplumber.service
sudo systemctl enable --now steamos-manager.service
best_effort user_systemctl enable steamos-manager.service
# HDMI-CEC ran before this and couldn't link cecd to steamos-manager yet.
cec_status && cec_link_steamos_manager
# Console-like power handling, as SteamOS's powerdevilrc (mains-power
# part; there is no battery): the power button sleeps instead of showing
# the logout screen, and the machine never suspends on its own (e.g.
# during a long download). The launcher's Shut Down still shuts down.
kset machine powerdevilrc "AC|SuspendAndShutdown" PowerButtonAction 1
kset machine powerdevilrc "AC|SuspendAndShutdown" AutoSuspendAction 0
kset machine powerdevilrc "AC|Display" LockBeforeTurnOffDisplay false
reload_powerdevil
ok "Steam Machine support on."
}
machine_disable() {
info "Removing Steam Machine support..."
best_effort user_systemctl disable --now steamos-manager.service
best_effort sudo systemctl disable --now steamos-manager.service
best_effort sudo systemctl disable --now inputplumber.service
best_effort sudo pacman -Rns --noconfirm steamos-manager inputplumber
serial_disable
wifi_backend_disable
sudo rm -f "$LED_UDEV_RULE" /etc/modules-load.d/leds-valve.conf
sudo udevadm control --reload
best_effort sudo modprobe -r leds-valve
best_effort sudo pacman -Rns --noconfirm leds-valve-dkms-git
krevert machine
reload_powerdevil
best_effort sudo systemctl disable ensure-kernel-headers.service
led_dkms_override_remove
sudo rm -f "$HEADERS_UNIT" "$HEADERS_SCRIPT"
sudo rm -rf "$(dirname "$HEADERS_SCRIPT")" "$(dirname "$HEADERS_SCRIPT_OLD")"
sudo systemctl daemon-reload
ok "Steam Machine support removed (the AUR helper, if installed, is kept)."
}