diff --git a/AGENTS.md b/AGENTS.md index 16fa88a..d109e00 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -132,6 +132,16 @@ gamescope and the Plasma desktop. Primary target: the Valve Steam Machine and `ensure-kernel-headers.service` installs missing ones at boot (a pacman hook can't run pacman), which triggers DKMS's install hook. The module creates `/sys/class/leds/valve-leds*`. +- `boot` ("Boot into: [gamescope] / desktop") is a choice row, not a + checkbox: on = desktop. It's a sub-option of the conversion: `menu_visible` + only shows it (indented, `└`) while `gaming` is ticked, so menu numbers + after it shift by one when the conversion is unticked. It needs `gaming` (ticking it ticks gaming, + unticking gaming unticks it) and is never preselected (`NO_PRESELECT`), so + the conversion boots into gamescope by default. Desktop is + `steamify-boot-desktop.service`, ordered `Before=` the login managers: + `steam-set-session plasma.desktop` plus the plasmalogin sync bridge when it + exists, at every boot. CachyOS's `cachyos-gamescope-autologin` still sets + gamescope during each desktop session; the unit corrects it at boot. - `bios` is an *action* (`ACTIONS` in `lib/menu.sh`), not an on/off component: never preselected (not even on a first run), never re-applied by `a`, not listed in the state overview, and `bios_status` is always off. diff --git a/CHANGELOG.md b/CHANGELOG.md index 6ca9f6d..b5b7054 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,14 +5,35 @@ 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. -## 0.9.1 - 2026-09-24 +## 0.10.0 - 2026-09-24 + +- `0f503dd` **feat: Boot into gamescope or the desktop** + - New menu row under the conversion, "Boot into: [gamescope] / desktop"; + gamescope stays the default and is never changed on a first run. + - Desktop: `steamify-boot-desktop.service` sets the autologin session to + Plasma at every boot, before the login manager starts; Return to Gaming + Mode and Steam's Switch to Desktop keep working. Back to gamescope removes + it. Choosing desktop turns the conversion on; turning the conversion off + resets it to gamescope. + - The later menu items move down one (BIOS is now 8 on a Steam Machine). +- `4895de3` **feat: "Boot into" is a sub-option of the conversion** + - Shown indented under the SteamOS conversion, and only while it's ticked. +- `bd90e29` **feat: Left/Right arrows choose on the "Boot into" row** + - Left = gamescope, right = desktop (Space still switches); the row says + "←/→ choose" and the key help lists Left/Right. +- **fix: Messages say which way it boots** + - "This will: boot into: gamescope (from the next boot)" instead of "turn + off: Boot into the desktop instead of gaming mode", and the same in the + progress line and the "Done" overview. + +## 0.9.1 - 2026-09-24 (#10) - `2e107e5` **fix: LED driver for every kernel on a fresh install** - The DKMS override was written to `/etc/dkms` before `dkms` was installed, when that folder doesn't exist yet, so it silently failed and the build for other kernels (e.g. LTS) failed as in 0.6. The folder is created first now, and a failed write stops with an error. -- **feat: Restart question on quit is "Restart now? [Y/n]"** +- `5ab83d6` **feat: Restart question on quit is "Restart now? [Y/n]"** - When something needs a restart, `q` asks with yes as the default; `n` goes back to the menu and the next `q` asks again. Ctrl+C quits without restarting; scripted input that runs out never restarts. diff --git a/README.md b/README.md index 9498e31..3e15de8 100644 --- a/README.md +++ b/README.md @@ -14,19 +14,23 @@ Everything you turn off is put back the way it was. Desktop** in Steam works and so does going back (the **Return to Gaming Mode** icon on the desktop), you're back in gaming mode after a restart, and Steam's on-screen keyboard (Steam + X) also works on the desktop. -2. **SteamOS theme** - the Vapor look for the desktop (CachyOS's +2. **Boot into: [gamescope] / desktop** - a sub-option of 1, shown under it + while it's ticked: where the PC starts. Gamescope (gaming mode) is the + default; choose desktop to start in KDE Plasma instead, with Return to + Gaming Mode one double-click away. +3. **SteamOS theme** - the Vapor look for the desktop (CachyOS's `cachyos-vapor` package). -3. **Steam Deck/Machine icons** - Steam Deck button icons in gaming mode. -4. **Single user mode** - like SteamOS: never a login or lock screen, no +4. **Steam Deck/Machine icons** - Steam Deck button icons in gaming mode. +5. **Single user mode** - like SteamOS: never a login or lock screen, no user switching or logging out. Typing a password with a controller is no fun. (Needs 1.) -5. **Steamify shortcut** - a "Steamify CachyOS" icon on the desktop and in +6. **Steamify shortcut** - a "Steamify CachyOS" icon on the desktop and in the app launcher (the Steam logo with a gear) that opens the newest version of Steamify in Konsole, so you don't need the install command again. -6. **Steam Machine support** - only shown on a Valve Steam Machine: the +7. **Steam Machine support** - only shown on a Valve Steam Machine: the driver for the front LED bar, and the hardware settings in Steam (fan, TV control over HDMI-CEC). -7. **Update BIOS** - only on a Steam Machine, opt-in and at your own risk: +8. **Update BIOS** - only on a Steam Machine, opt-in and at your own risk: installs the newest Steam Machine BIOS from Valve (see [BIOS updates](#bios-updates-steam-machine)). @@ -70,7 +74,8 @@ Run it as yourself, not as root. You'll see a checklist: Up/Down move Space select Enter run a run + re-apply what's on q quit ``` -Move with the **arrow keys**, tick or untick with **Space**, and press +Move with the **arrow keys**, tick or untick with **Space** (on the **Boot +into** row, **Left/Right** choose gamescope or desktop), and press **Enter** to run. "now:" shows what's on at the moment. Below the list, every installed kernel is shown with its headers and Steam controller driver; on a Steam Machine also whether the LED bar driver is built for it, and whether @@ -97,9 +102,12 @@ files in `lib/` next to it. **Power > Switch to Desktop**. - **Back to gaming mode:** double-click **Return to Gaming Mode** on the desktop (or log out, if you're not using single user mode). -- After a restart you always start in gaming mode. +- After a restart you start in gaming mode, or on the desktop if you chose + **Boot into: desktop** in the menu. Switching back and forth works the same + either way. -Prefer to decide yourself where your PC starts? Run one of these in Konsole: +Prefer to start where you left off last time? Run one of these in Konsole +(with **Boot into: gamescope**): ```bash steamos-session-select persistent # start where you left off last time @@ -356,6 +364,7 @@ gamescope-session, ...) stay installed. | `lib/state.sh` | Undo journal for KDE settings (`kset`/`krevert`) | | `lib/common.sh` | Output helpers, prompts, backups, plasmashell handling | | `lib/packages.sh` | Required packages, AUR helper (yay/paru) | +| `lib/boot-session.sh` | Boot into gamescope or the desktop (`steamify-boot-desktop.service`) | | `lib/login-manager.sh` | SteamOS conversion: SDDM or plasmalogin autologin, sync bridge | | `lib/steam-desktop.sh` | Steam in the Plasma session; Steam Deck/Machine icons | | `lib/desktop-shortcut.sh` | Return to Gaming Mode shortcut and its sudoers rule | diff --git a/lib/boot-session.sh b/lib/boot-session.sh new file mode 100644 index 0000000..5e5d565 --- /dev/null +++ b/lib/boot-session.sh @@ -0,0 +1,62 @@ +#!/bin/bash +# "Boot into: gamescope / desktop" menu item, on top of the SteamOS +# conversion. Gamescope is the default (the conversion's autologin, and +# CachyOS's cachyos-gamescope-autologin resetting to it after a desktop +# session). Desktop: a unit that sets the autologin session to Plasma at +# every boot, before the login manager starts. Switching between the two in +# a running system (Return to Gaming Mode, Steam's Switch to Desktop) works +# the same either way. +# Sourced by steamify.sh; not meant to be run on its own. + +BOOT_UNIT_NAME="steamify-boot-desktop.service" +BOOT_UNIT="/etc/systemd/system/$BOOT_UNIT_NAME" +SESSION_SYNC="/usr/local/bin/sync-steamos-session.sh" + +boot_status() { + # On = boots into the desktop. + [[ -f "$BOOT_UNIT" ]] && systemctl is-enabled -q "$BOOT_UNIT_NAME" 2>/dev/null +} + +boot_enable() { + info "Setting the PC to boot into the desktop..." + # The sync bridge (plasma-login-manager only; not there with SDDM) copies + # the session into the base config; run it right away rather than rely + # on its path unit, which could still be pending when the login manager + # starts. + sudo tee "$BOOT_UNIT" > /dev/null << EOF +[Unit] +Description=Steamify CachyOS: boot into the desktop instead of gaming mode +Before=display-manager.service sddm.service plasmalogin.service + +[Service] +Type=oneshot +ExecStart=/usr/lib/steamos/steam-set-session plasma.desktop +ExecStart=/bin/sh -c '[ -x $SESSION_SYNC ] && exec $SESSION_SYNC || true' + +[Install] +WantedBy=graphical.target +EOF + sudo systemctl daemon-reload + sudo systemctl enable "$BOOT_UNIT_NAME" || { err "Enabling $BOOT_UNIT_NAME failed."; return 1; } + ok "Boots into the desktop from the next boot on (Return to Gaming Mode still works)." +} + +boot_disable() { + info "Setting the PC to boot into gaming mode again..." + sudo systemctl disable "$BOOT_UNIT_NAME" 2>/dev/null + sudo rm -f "$BOOT_UNIT" + sudo systemctl daemon-reload + # Next boot into gamescope, as the conversion sets it up. + sudo /usr/lib/steamos/steam-set-session gamescope-session.desktop + [[ -x "$SESSION_SYNC" ]] && sudo "$SESSION_SYNC" + ok "Boots into gaming mode from the next boot on." +} + +boot_choice() { + # The menu row: the chosen option in brackets. + if [[ "$1" == 1 ]]; then + echo "Boot into: gamescope [desktop]" + else + echo "Boot into: [gamescope] desktop" + fi +} diff --git a/lib/menu.sh b/lib/menu.sh index ab45c60..c859488 100644 --- a/lib/menu.sh +++ b/lib/menu.sh @@ -5,13 +5,17 @@ # Menu order. Components are turned on in this order and off in reverse; # gaming must come first (single user builds on it). -COMPONENTS=(gaming theme glyphs single launcher machine bios) +COMPONENTS=(gaming boot theme glyphs single launcher machine bios) # One-off actions rather than on/off components: never preselected, never # re-applied, not listed as on or off. ACTIONS=(bios) +# Never preselected on a first run: booting into the desktop is a choice, +# gamescope is the default. +NO_PRESELECT=(boot) declare -A LABEL=( [gaming]="SteamOS conversion: boot into gaming mode, Steam on the desktop" + [boot]="Boot into the desktop instead of gaming mode" [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)" @@ -30,6 +34,19 @@ component_available() { is_action() { [[ " ${ACTIONS[*]} " == *" $1 "* ]]; } +boot_mode() { + # boot_mode 0|1: the "Boot into" choice as a word, for messages; "turn + # off boot into the desktop" reads as the opposite of what was chosen. + if [[ "$1" == 1 ]]; then echo desktop; else echo gamescope; fi +} + +menu_visible() { + # Shown in the menu. "Boot into" is a sub-option of the conversion: only + # while the conversion is ticked. + component_available "$1" || return 1 + [[ "$1" != boot || "${WANTED[gaming]:-0}" == 1 ]] +} + component_selectable() { # Greyed out and not tickable when it has nothing to do. case "$1" in @@ -49,7 +66,8 @@ detect_components() { # First run: preselect the full SteamOS experience (never an action). if [[ "$any" == false ]]; then for c in "${COMPONENTS[@]}"; do - component_available "$c" && ! is_action "$c" && WANTED[$c]=1 + component_available "$c" && ! is_action "$c" && + [[ " ${NO_PRESELECT[*]} " != *" $c "* ]] && WANTED[$c]=1 done fi } @@ -60,7 +78,9 @@ toggle_component() { WANTED[$c]=$(( 1 - WANTED[$c] )) # Single user mode only makes sense on top of the SteamOS conversion. if [[ "$c" == single && "${WANTED[single]}" == 1 ]]; then WANTED[gaming]=1; fi - if [[ "$c" == gaming && "${WANTED[gaming]}" == 0 ]]; then WANTED[single]=0; fi + 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 } show_menu() { @@ -69,7 +89,7 @@ show_menu() { printf " ${c_bold}%-3s %-6s %-6s %s${c_reset}\n" "#" "Now" "Want" "Component" MENU_ITEMS=() for c in "${COMPONENTS[@]}"; do - component_available "$c" || continue + menu_visible "$c" || continue i=$((i + 1)); MENU_ITEMS[$i]=$c # Pad the plain word, then colour it: colour codes would count as width. now="off"; [[ "${CURRENT[$c]}" == 1 ]] && now="on" @@ -77,7 +97,13 @@ show_menu() { now="$(printf '%-6s' "$now")" [[ "${CURRENT[$c]}" == 1 ]] && now="${now/on/${c_green}on${c_reset}}" want="[ ]"; [[ "${WANTED[$c]}" == 1 ]] && want="[x]" - if component_selectable "$c"; then + if [[ "$c" == boot ]]; then + # A choice rather than a checkbox: Now/Want show the mode. + now="gaming"; [[ "${CURRENT[boot]}" == 1 ]] && now="desk" + [[ "${CURRENT[gaming]}" == 1 ]] || now="-" + want="gaming"; [[ "${WANTED[boot]}" == 1 ]] && want="desk" + printf " %-3s %-6s %-6s └ %s\n" "$i" "$now" "$want" "$(boot_choice "${WANTED[boot]}")" + elif component_selectable "$c"; then printf " %-3s %b %-6s %s\n" "$i" "$now" "$want" "${LABEL[$c]}" else printf " %b%-3s %-6s %-6s %s (not available)%b\n" "$c_dim" "$i" "$now" "$want" "${LABEL[$c]}" "$c_reset" @@ -109,12 +135,21 @@ draw_menu_tui() { echo MENU_ITEMS=() for c in "${COMPONENTS[@]}"; do - component_available "$c" || continue + menu_visible "$c" || continue MENU_ITEMS[$i]=$c box="[ ]"; [[ "${WANTED[$c]}" == 1 ]] && box="[${c_green}x${c_reset}]" state=" (now: off)"; [[ "${CURRENT[$c]}" == 1 ]] && state=" (now: ${c_green}on${c_reset})" is_action "$c" && state=" (opt-in, runs once)" line="$box ${LABEL[$c]}" + if [[ "$c" == boot ]]; then + # A choice rather than a checkbox; Space switches it. + # Indented under the conversion, whose sub-option it is. + line="$(boot_choice "${WANTED[boot]}")" + line=" └ ${line/\[/[${c_green}}"; line="${line/\]/${c_reset}]}" + local mode=gamescope; [[ "${CURRENT[boot]}" == 1 ]] && mode=desktop + state=" (${c_bold}←/→${c_reset} choose)" + [[ "${CURRENT[gaming]}" == 1 ]] && state=" (now: $mode; ${c_bold}←/→${c_reset} choose)" + fi if ! component_selectable "$c"; then # Greyed out: nothing to do (e.g. BIOS already up to date). local mark=" "; (( i == cursor )) && mark=" ${c_cyan}>${c_reset} " @@ -130,7 +165,8 @@ draw_menu_tui() { 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" + echo -e " ${c_bold}Up/Down${c_reset} move ${c_bold}Space${c_reset} select ${c_bold}Left/Right${c_reset} choose ${c_bold}Enter${c_reset} run" + echo -e " ${c_bold}a${c_reset} run + re-apply what's on ${c_bold}q${c_reset} quit" } run_menu_tui() { @@ -140,6 +176,8 @@ run_menu_tui() { while true; do draw_menu_tui "$cursor" count=${#MENU_ITEMS[@]} + # The list shrinks when the conversion (and its sub-option) is unticked. + if (( cursor >= count )); then cursor=$(( count - 1 )); draw_menu_tui "$cursor"; fi IFS= read -rsn1 key if [[ "$key" == $'\e' ]]; then IFS= read -rsn2 -t 0.05 rest @@ -149,6 +187,9 @@ run_menu_tui() { $'\e[A'|k) (( cursor = (cursor + count - 1) % count )) ;; $'\e[B'|j) (( cursor = (cursor + 1) % count )) ;; " ") toggle_component "${MENU_ITEMS[$cursor]}" ;; + # On the "Boot into" row: left = gamescope, right = desktop. + $'\e[D'|h) [[ "${MENU_ITEMS[$cursor]}" == boot && "${WANTED[boot]}" == 1 ]] && toggle_component boot ;; + $'\e[C'|l) [[ "${MENU_ITEMS[$cursor]}" == boot && "${WANTED[boot]}" == 0 ]] && toggle_component boot ;; "") break ;; a|A) REAPPLY=true; break ;; q|Q) tput cnorm 2>/dev/null; echo; return 1 ;; @@ -208,11 +249,13 @@ apply_changes() { [[ "${WANTED[single]}" == 1 ]] && LOGIN_MANAGER=sddm for c in "${TO_DISABLE[@]}"; do - echo; echo -e "${c_bold}Turning off: ${LABEL[$c]}${c_reset}" + 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") done for c in "${TO_ENABLE[@]}"; do - if is_action "$c"; then echo; echo -e "${c_bold}Running: ${LABEL[$c]%%:*}${c_reset}" + 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") done diff --git a/steamify.sh b/steamify.sh index f305511..313bb2b 100755 --- a/steamify.sh +++ b/steamify.sh @@ -15,11 +15,11 @@ set -uo pipefail # Release version, see CHANGELOG.md. -VERSION=0.9.1 +VERSION=0.10.0 SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -for lib in common state packages login-manager single-user steam-desktop steam-machine vapor-theme steamos-extras bios desktop-shortcut wizard-shortcut menu; do +for lib in common state packages login-manager single-user steam-desktop steam-machine boot-session vapor-theme steamos-extras bios desktop-shortcut wizard-shortcut menu; do # shellcheck source=/dev/null source "$SCRIPT_DIR/lib/$lib.sh" done @@ -118,9 +118,13 @@ while true; do echo echo -e "${c_bold}This will:${c_reset}" - for c in "${TO_DISABLE[@]}"; do echo " - turn off: ${LABEL[$c]}"; done + for c in "${TO_DISABLE[@]}"; do + if [[ "$c" == boot ]]; then echo " - boot into: gamescope (from the next boot)" + else echo " - turn off: ${LABEL[$c]}"; fi + done for c in "${TO_ENABLE[@]}"; do - if is_action "$c"; then echo " - run: ${LABEL[$c]%%:*} at your own risk (checks, then asks twice more)" + 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 [[ "${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; } @@ -135,7 +139,7 @@ while true; do apply_changes # Login manager changes only take effect after a restart. - [[ " ${TO_DISABLE[*]} ${TO_ENABLE[*]} " == *" gaming "* || " ${TO_DISABLE[*]} ${TO_ENABLE[*]} " == *" single "* ]] && + [[ " ${TO_DISABLE[*]} ${TO_ENABLE[*]} " =~ \ (gaming|single|boot)\ ]] && RESTART_FOR_LOGIN=true echo @@ -143,7 +147,9 @@ while true; do echo -e "${c_bold}Done. Current state:${c_reset}" for c in "${COMPONENTS[@]}"; do component_available "$c" && ! is_action "$c" || continue - if [[ "${CURRENT[$c]}" == 1 ]]; then echo -e " ${c_green}on ${c_reset} ${LABEL[$c]}"; else echo " off ${LABEL[$c]}"; fi + if [[ "$c" == boot ]]; then + [[ "${CURRENT[gaming]}" == 1 ]] && echo " └ boots into: $(boot_mode "${CURRENT[boot]}")" + elif [[ "${CURRENT[$c]}" == 1 ]]; then echo -e " ${c_green}on ${c_reset} ${LABEL[$c]}"; else echo " off ${LABEL[$c]}"; fi done if [[ ${#FAILED[@]} -gt 0 ]]; then warn "These had problems (see above): ${FAILED[*]}"