diff --git a/lib/menu.sh b/lib/menu.sh index 855b1a7..62f5326 100644 --- a/lib/menu.sh +++ b/lib/menu.sh @@ -9,7 +9,7 @@ COMPONENTS=(gaming theme glyphs single machine) declare -A LABEL=( [gaming]="SteamOS conversion: boot into gaming mode, Steam on the desktop" - [theme]="Install SteamOS theme: Vapor look, dark mode, SteamOS taskbar" + [theme]="Install SteamOS theme: Vapor look (cachyos-vapor)" [glyphs]="Install Steam Deck/Machine icons: Deck button icons in gaming mode" [single]="Single user mode: no password, lock screen or log out (SDDM)" [machine]="Steam Machine support: LED bar driver, hardware settings in Steam" @@ -56,12 +56,16 @@ show_menu() { printf " %-3s %-6b %-6s %s\n" "$i" "$now " "$want" "${LABEL[$c]}" done echo + echo -e "$KERNEL_OVERVIEW" + echo } run_menu() { # Sets REAPPLY. Returns 1 if the user quit. A checkbox list on a # terminal; a plain numbered prompt when input is piped (scripted runs). REAPPLY=false + # Built once: the TUI redraws on every key press. + KERNEL_OVERVIEW="$(kernel_overview)" if [[ -t 0 && -t 1 ]]; then run_menu_tui else @@ -83,13 +87,16 @@ draw_menu_tui() { state="off"; [[ "${CURRENT[$c]}" == 1 ]] && state="${c_green}on${c_reset}" line="$box ${LABEL[$c]}" if (( i == cursor )); then - echo -e " ${c_cyan}>${c_reset} ${c_bold}${line}${c_reset} (now: ${state})" + # The green x's reset would end the bold too: re-enable it after. + echo -e " ${c_cyan}>${c_reset} ${c_bold}${line//"$c_reset"/"$c_reset$c_bold"}${c_reset} (now: ${state})" else echo -e " ${line} (now: ${state})" fi i=$((i + 1)) done echo + echo -e "$KERNEL_OVERVIEW" + echo echo -e " ${c_bold}Up/Down${c_reset} move ${c_bold}Space${c_reset} select ${c_bold}Enter${c_reset} run ${c_bold}a${c_reset} run + re-apply what's on ${c_bold}q${c_reset} quit" } diff --git a/lib/steam-machine.sh b/lib/steam-machine.sh index 66c0242..3f7ce93 100644 --- a/lib/steam-machine.sh +++ b/lib/steam-machine.sh @@ -51,6 +51,14 @@ install_valve_led_driver() { # silently builds nothing, so modprobe later finds no module. install_kernel_headers || return 1 + # 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. + printf '%s\n' "# Written by cachyos-gamescope-boot: build for DKMS's target kernel." \ + 'MAKE[0]="make KVERSION=${kernelver}"' | sudo tee "$LED_DKMS_OVERRIDE" >/dev/null + if ! pacman -Qi leds-valve-dkms-git >/dev/null 2>&1; then info "Installing leds-valve-dkms-git from the AUR via $aur_helper (non-interactive)..." if ! "$aur_helper" -S "${flags[@]}" leds-valve-dkms-git; then @@ -61,19 +69,20 @@ install_valve_led_driver() { ok "leds-valve-dkms-git already installed." fi - # Build for the running kernel explicitly. The upstream Makefile uses - # `uname -r` rather than DKMS's target kernel, so building for the - # running kernel is the one case that reliably works. - local kver - kver="$(uname -r)" - if ! dkms status -k "$kver" leds-valve-dkms 2>/dev/null | grep -q installed; then - info "Building leds-valve for running kernel $kver..." + # 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 failed. See the build log:" + 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 - fi + done # Load now, and on every boot. echo leds-valve | sudo tee /etc/modules-load.d/leds-valve.conf >/dev/null @@ -96,6 +105,87 @@ install_valve_led_driver() { } LED_UDEV_RULE="/etc/udev/rules.d/70-valve-leds-user.rules" +LED_DKMS_OVERRIDE="/etc/dkms/leds-valve-dkms.conf" +HEADERS_SCRIPT="/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")" + sudo tee "$HEADERS_SCRIPT" > /dev/null << 'EOF' +#!/bin/bash +# Installed by cachyos-gamescope-boot: install missing -headers for every +# installed kernel, so DKMS builds leds-valve for it. +set -u +missing=() +for k in $(pacman -Qqo /usr/lib/modules/*/pkgbase 2>/dev/null | sort -u); do + pacman -Q "${k}-headers" >/dev/null 2>&1 && continue + pacman -Si "${k}-headers" >/dev/null 2>&1 && missing+=("${k}-headers") +done +[[ ${#missing[@]} -eq 0 ]] && exit 0 +# Another pacman is running (e.g. an update): try again next boot. +[[ -e /var/lib/pacman/db.lck ]] && { echo "pacman is busy, skipping"; exit 0; } +echo "Installing missing kernel headers: ${missing[*]}" +exec pacman -S --needed --noconfirm "${missing[@]}" +EOF + sudo chmod 755 "$HEADERS_SCRIPT" + sudo tee "$HEADERS_UNIT" > /dev/null << EOF +[Unit] +Description=Install missing kernel headers so DKMS builds leds-valve for every kernel +Wants=network-online.target +After=network-online.target + +[Service] +Type=oneshot +ExecStart=$HEADERS_SCRIPT + +[Install] +WantedBy=multi-user.target +EOF + 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 is 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 + echo -e " ${c_bold}Kernels${c_reset} (> = running; controller = Steam controller driver, LEDs = LED bar driver built)" + 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" + line+="$(printf ' LEDs %b' "$led")" + fi + echo -e " ${running}${line}" + done + 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; } @@ -106,6 +196,7 @@ machine_status() { 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 # 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 @@ -125,6 +216,15 @@ EOF sudo systemctl enable --now inputplumber.service sudo systemctl enable --now steamos-manager.service systemctl --user enable steamos-manager.service 2>/dev/null + + # 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." } @@ -138,5 +238,11 @@ 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 + krevert machine + reload_powerdevil + sudo systemctl disable ensure-kernel-headers.service 2>/dev/null + sudo rm -f "$LED_DKMS_OVERRIDE" "$HEADERS_UNIT" "$HEADERS_SCRIPT" + sudo rmdir "$(dirname "$HEADERS_SCRIPT")" 2>/dev/null + sudo systemctl daemon-reload ok "Steam Machine support removed (the AUR helper, if installed, is kept)." }