From a85f29a0fcff531afd8995945645efd9987e4126 Mon Sep 17 00:00:00 2001 From: Rick Peters Date: Sat, 3 Oct 2026 11:51:28 +0200 Subject: [PATCH] refactor: the menu's dependency rules as tables, shared by the menu and the app --- AGENTS.md | 2 +- CHANGELOG.md | 1 + lib/backend.sh | 19 +----------- lib/menu.sh | 79 +++++++++++++++++++++++++++++++++----------------- 4 files changed, 56 insertions(+), 45 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index c18c532..52ccaab 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -47,7 +47,7 @@ gamescope and the Plasma desktop. Primary target: the Valve Steam Machine idempotent. `_enable` is also used to re-apply. Optional `_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`. + `gaming` must stay first. Dependencies are the `REQUIRES`, `FOLLOWS` and `EXCLUDES` tables in `lib/menu.sh`, used by `toggle_component` (the menu) and `normalize_wanted` (the app). - **NVIDIA** (`lib/nvidia.sh`): gamescope's session is broken on NVIDIA, so the SteamOS conversion (`gaming`, `boot`, `single`, `glyphs`) is hidden there (unless already on) and `nvidia` + its sub-option `bigpicture` ("Gaming on NVIDIA") replace it: Steam on the Plasma desktop, started at login. Never reintroduce a gamescope session for NVIDIA without re-testing on the diff --git a/CHANGELOG.md b/CHANGELOG.md index 3a88835..9491575 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ one per merged pull request. ## Unreleased - **refactor: Valve's package mirror in one module (`lib/valve-mirror.sh`: newest repo, package database fields, checksummed download); the SteamOS extras, CEC driver, BIOS lookup and os-release version used four copies** +- **refactor: the rules between menu items are three tables (`REQUIRES`, `FOLLOWS`, `EXCLUDES` in `lib/menu.sh`) used by the menu's toggles and, as `normalize_wanted`, by the app's selection (it had its own copy of the rules). The app now also ticks the Steamify shortcut for "Add as non-Steam game" and unticks the game entry with it, like the menu** - **test: the menu rules, the plan and the app's and installer's JSON are checked against a golden file (`tests/menu-test.sh` in steamify-cachyos-dev); `make check`, `.editorconfig`, `.shellcheckrc`** - **docs: README lists the sources and projects Steamify builds on, with licences and thanks** diff --git a/lib/backend.sh b/lib/backend.sh index f205da8..16a6cb9 100644 --- a/lib/backend.sh +++ b/lib/backend.sh @@ -146,24 +146,7 @@ backend_apply() { WANTED[$c]=0 for id in "${want[@]}"; do [[ "$id" == "$c" ]] && WANTED[$c]=1; done done - # The same rules as the menu's toggles. - # (Single user mode only needs the conversion where it is offered: not on NVIDIA PCs.) - [[ "${WANTED[single]:-0}" == 1 ]] && component_available gaming && WANTED[gaming]=1 - if component_available gaming; then - [[ "${WANTED[gaming]:-0}" == 0 ]] && { WANTED[single]=0; WANTED[boot]=0; } - else - WANTED[boot]=0 - fi - [[ "${WANTED[bigpicture]:-0}" == 1 ]] && WANTED[nvidia]=1 - [[ "${WANTED[nvsilent]:-0}" == 1 ]] && WANTED[nvidia]=1 - [[ "${WANTED[bigpicture]:-0}" == 1 ]] && WANTED[nvsilent]=0 - [[ "${WANTED[nvidia]:-0}" == 0 ]] && { WANTED[bigpicture]=0; WANTED[nvsilent]=0; } - [[ "${WANTED[silent]:-0}" == 1 || "${WANTED[glyphs]:-0}" == 1 ]] && component_available gaming && WANTED[gaming]=1 - [[ "${WANTED[gaming]:-0}" == 0 ]] && { WANTED[silent]=0; WANTED[glyphs]=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 + normalize_wanted # Retired: only ever removed. WANTED[hdmi]=0 case "$boot" in desktop) WANTED[boot]=1; WANTED[gaming]=1 ;; gamescope) WANTED[boot]=0 ;; esac diff --git a/lib/menu.sh b/lib/menu.sh index 20a01b5..9f89309 100644 --- a/lib/menu.sh +++ b/lib/menu.sh @@ -11,6 +11,20 @@ COMPONENTS=(gaming boot silent glyphs nvidia bigpicture nvsilent theme single la ACTIONS=(bios) # Sub-options, shown indented under their parent and only while it's ticked. declare -A PARENT=([boot]=gaming [silent]=gaming [glyphs]=gaming [bigpicture]=nvidia [nvsilent]=nvidia [steamgame]=launcher [poweroff]=machine [kpin]=machine [hdmi]=machine [bios]=machine) +# What ticking one item does to another, for the menu's toggles (toggle_component) +# and the app's ticks (normalize_wanted). A sub-option needs its parent: ticking it +# ticks the parent, unticking the parent unticks it (Where to boot is chosen with +# --boot, so the app's ticks never bring the conversion for it; HDMI refresh boost is +# retired and never brings Steam Machine support). Single user mode only makes sense +# on top of the SteamOS conversion (where that is offered: on NVIDIA it logs in by +# itself, see lib/single-user.sh). +declare -A REQUIRES=([single]=gaming) +for _c in "${!PARENT[@]}"; do [[ "$_c" == hdmi ]] || REQUIRES[$_c]=${PARENT[$_c]}; done +unset _c +# Opt-out sub-options are ticked and unticked along with their parent. +declare -A FOLLOWS=([nvidia]=bigpicture [machine]=poweroff [launcher]=steamgame) +# Ticking one unticks the other; in normalize_wanted the first in COMPONENTS wins. +declare -A EXCLUDES=([bigpicture]=nvsilent [nvsilent]=bigpicture) # 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, @@ -291,35 +305,48 @@ defaults_list() { printf '[%s]\n' "$items" } +children_of() { + # children_of : the items that need (REQUIRES), one per line. + local c + for c in "${COMPONENTS[@]}"; do [[ "${REQUIRES[$c]:-}" == "$1" ]] && echo "$c"; done + return 0 +} + toggle_component() { - local c="$1" + local c="$1" p x component_selectable "$c" || return 1 WANTED[$c]=$(( 1 - WANTED[$c] )) - # Single user mode only makes sense on top of the SteamOS conversion. - # (On NVIDIA the conversion isn't offered: single user mode logs in by itself, see lib/single-user.sh.) - if [[ "$c" == single && "${WANTED[single]}" == 1 ]] && component_available gaming; then WANTED[gaming]=1; 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 - # Big Picture is opt-out: ticked along with "Gaming on NVIDIA". - if [[ "$c" == nvidia ]]; then WANTED[bigpicture]=${WANTED[nvidia]}; fi - if [[ "$c" == bigpicture && "${WANTED[bigpicture]}" == 1 ]]; then WANTED[nvidia]=1; WANTED[nvsilent]=0; fi - # Silent and Big Picture exclude each other. - if [[ "$c" == nvsilent && "${WANTED[nvsilent]}" == 1 ]]; then WANTED[nvidia]=1; WANTED[bigpicture]=0; fi - if [[ "$c" == nvidia && "${WANTED[nvidia]}" == 0 ]]; then WANTED[nvsilent]=0; fi - if [[ "$c" == gaming && "${WANTED[gaming]}" == 0 ]]; then WANTED[silent]=0; WANTED[glyphs]=0; fi - if [[ "$c" == silent && "${WANTED[silent]}" == 1 ]]; then WANTED[gaming]=1; fi - if [[ "$c" == glyphs && "${WANTED[glyphs]}" == 1 ]]; then WANTED[gaming]=1; fi - # 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; WANTED[bios]=0; fi - if [[ "$c" == kpin && "${WANTED[kpin]}" == 1 ]]; then WANTED[machine]=1; fi - if [[ "$c" == bios && "${WANTED[bios]}" == 1 ]]; then WANTED[machine]=1; fi - # Steam starts Steamify through the shortcut's start script. - # Opt-out, like the power-off fix: ticked along with the shortcut. - if [[ "$c" == launcher ]] && component_available steamgame; then WANTED[steamgame]=${WANTED[launcher]}; fi - if [[ "$c" == steamgame && "${WANTED[steamgame]}" == 1 ]]; then WANTED[launcher]=1; fi + if [[ "${WANTED[$c]}" == 1 ]]; then + p="${REQUIRES[$c]:-}" + [[ -n "$p" ]] && component_available "$p" && WANTED[$p]=1 + x="${EXCLUDES[$c]:-}" + [[ -n "$x" ]] && WANTED[$x]=0 + else + for x in $(children_of "$c"); do WANTED[$x]=0; done + fi + x="${FOLLOWS[$c]:-}" + [[ -n "$x" ]] && component_available "$x" && WANTED[$x]=${WANTED[$c]} +} + +normalize_wanted() { + # The same rules for a whole selection (the app ticks exactly the items it + # sends): a ticked sub-option brings its parent, two that exclude each other + # keep the first, and an unticked parent takes its sub-options with it + # (only where the parent is offered: single user mode stays on NVIDIA PCs). + local c p x + for c in "${COMPONENTS[@]}"; do + p="${REQUIRES[$c]:-}" + [[ -n "$p" && "$c" != boot && "${WANTED[$c]:-0}" == 1 ]] && component_available "$p" && WANTED[$p]=1 + done + for c in "${COMPONENTS[@]}"; do + x="${EXCLUDES[$c]:-}" + [[ -n "$x" && "${WANTED[$c]:-0}" == 1 ]] && WANTED[$x]=0 + done + for c in "${COMPONENTS[@]}"; do + p="${REQUIRES[$c]:-}" + [[ -n "$p" && "${WANTED[$p]:-0}" == 0 ]] && component_available "$p" && WANTED[$c]=0 + done + return 0 } show_menu() {