diff --git a/TODO.md b/TODO.md index 2968af2..d97cd73 100644 --- a/TODO.md +++ b/TODO.md @@ -2,29 +2,26 @@ ## Open -1. **Steamify installer page with checkboxes** (next, 2026-09-28). Tested - in the ISO VM: the current packagechooser page ("Steam Machine", mode - `optionalmultiple`) only lets you select one item, not several. Wanted: - - a page titled **"Steamify"** (not "Steam Machine"); - - **toggle switches like the Steamify app's** (preferred, so the page - looks like Steamify itself; checkboxes only as a fallback) (`ui/qml` in - steamify-cachyos: the rows with "now on" and a switch), one per Steamify - option, **all on by default** (opt-out: switch off what you don't - want), several at a time; a QML page could reuse the app's look - (`Theme`, its toggle rows) and texts (`Texts`); - - the "Start in" page (gaming mode / desktop) can stay. - Calamares has no ready checkbox page whose ticks come out as plain ids - (netinstall's checkboxes are package installs), so this needs our own page: - a QML page (adapt CachyOS's `packagechooserq`) or a small Calamares - module. The page passes the **ticked** ids to `steamify-install`, which - runs `--defaults --options --boot gamescope|desktop` (Steamify 2.7.0: - exactly these on; Boot into can be a row under the conversion like in the - app, or stay its own page). Rows and texts can come from - `steamify.sh --backend status` (+ the app's `Texts.qml`). Until then - `steamify-install` turns the current page's "leave out" ids into - `--options`. "Add as non-Steam game" needs a Steam account: not at install. - Also still to check with it: that `${gs[...]}` reaches shellprocess (the - install log shows "Unknown item: ${gs[..." if not). +1. **Steamify installer page** (in progress 2026-09-28; next ISO build after 13:00). + Done in the tree, not yet tried in Calamares: + - `packagechooserq@steamifypage` right after the packages page, titled + "Steamify"; the old skip/boot pages are gone. + - `usr/local/share/steamify-installer/SteamifyPage.qml`: CachyOS installer + look (palette, turquoise #00CED1, standard Switch/RadioButton), rows left, + explanation right, Boot into under the conversion. Stores the on-ids + (+ `boot` for desktop, `none`) as `packagechooser_steamifypage`; + `steamify-install` turns that into `--options`/`--boot`. + - Rows from `steamify.sh --defaults --list` (Steamify 2.8.0, + `feature/defaults-list`): `Items.qml` in `usr/local/share/steamify/qml` + (fallback: steamify-installer/Items.qml, copied by steamify-prepare.sh). + - **Next:** copy `Theme.qml` too (Texts.qml needs it); fix the RowLayout + "recursive rearrange" warning; `calamares-online.sh` downloads the newest + steamify.sh + Texts/Theme before Calamares (fallback: the ISO's) and writes + Items.qml from `--defaults --list`. Build with `steamify-prepare.sh <2.8.0 + checkout>` (`vmisobuild.sh --steamify /mnt`), test in the ISO VM + (`vmisoboot.sh --fresh --fremont`), check `/var/log/steamify-install.log`. + - Quick page test without a build: `qml6` in the test VM with a stub + `config` and stub io.calamares modules (QT_FORCE_STDERR_LOGGING=1). 2. **Check the live name** (the build tree has it right: `build/x86_64/airootfs/etc/os-release`) after the next build: Hello's subtitle should say "Steamify CachyOS, based on CachyOS rolling" (`steamify-customize.sh`, diff --git a/archiso/airootfs/etc/calamares/modules/packagechooser_steamifyboot.conf b/archiso/airootfs/etc/calamares/modules/packagechooser_steamifyboot.conf deleted file mode 100644 index b0ff2dd..0000000 --- a/archiso/airootfs/etc/calamares/modules/packagechooser_steamifyboot.conf +++ /dev/null @@ -1,16 +0,0 @@ -# Steamify: where the Steam Machine starts (steamify-install passes --boot). ---- -mode: required -method: legacy -labels: - step: "Start in" -default: gamescope -items: - - id: gamescope - name: "Gaming mode" - description: "Start straight into Steam's gaming mode, like SteamOS. Switch to Desktop in Steam's power menu brings you to the desktop." - screenshot: ":/images/no-selection.png" - - id: desktop - name: "Desktop" - description: "Start in the Plasma desktop. Return to Gaming Mode on the desktop brings you to Steam's gaming mode." - screenshot: ":/images/no-selection.png" diff --git a/archiso/airootfs/etc/calamares/modules/packagechooser_steamifyskip.conf b/archiso/airootfs/etc/calamares/modules/packagechooser_steamifyskip.conf deleted file mode 100644 index f2fcac2..0000000 --- a/archiso/airootfs/etc/calamares/modules/packagechooser_steamifyskip.conf +++ /dev/null @@ -1,37 +0,0 @@ -# Steamify: which parts of the Steam Machine setup to leave out. Nothing -# selected means Steamify's full default setup; the ids are Steamify's menu -# items (steamify-install passes the others as --options). ---- -mode: optionalmultiple -method: legacy -labels: - step: "Steam Machine" -items: - - id: "" - name: "Everything" - description: "Select what to leave out of the Steam Machine setup (Steamify). Leave everything unselected for the full SteamOS-style setup; everything can be changed later in the Steamify app." - screenshot: ":/images/no-selection.png" - - id: theme - name: "Leave out: SteamOS theme" - description: "The Vapor look for the desktop, with SteamOS's panel layout and Add to Steam." - - id: glyphs - name: "Leave out: Steam Deck/Machine icons" - description: "Steam Deck button icons in gaming mode." - - id: single - name: "Leave out: single user mode" - description: "No password, lock screen, user switching or log out, like SteamOS." - - id: launcher - name: "Leave out: Steamify shortcut" - description: "The Steamify app on the desktop and in the launcher, to change these choices later." - - id: notify - name: "Leave out: update notifications" - description: "A notification when there's a new Steamify. It never updates by itself." - - id: vram - name: "Leave out: VRAM booster" - description: "The game in front keeps its video memory; background apps make room." - - id: cec - name: "Leave out: HDMI-CEC" - description: "Use Steam with the TV remote, TV on and off with the Steam Machine." - - id: machine - name: "Leave out: Steam Machine support" - description: "The LED bar driver, hardware settings in Steam and the power-off fix." diff --git a/archiso/airootfs/etc/calamares/modules/packagechooserq_steamifypage.conf b/archiso/airootfs/etc/calamares/modules/packagechooserq_steamifypage.conf new file mode 100644 index 0000000..b699cb9 --- /dev/null +++ b/archiso/airootfs/etc/calamares/modules/packagechooserq_steamifypage.conf @@ -0,0 +1,10 @@ +# Steamify: its page (the app's switches, /usr/local/share/steamify-installer/ +# SteamifyPage.qml). Legacy method: the page's choice (the ids that are on, +# plus "boot" for Boot into Desktop) goes to global storage as +# packagechooser_steamifypage, for shellprocess_steamify.conf. +--- +method: legacy +qmlFilename: /usr/local/share/steamify-installer/SteamifyPage.qml +labels: + step: "Steamify" +packageChoice: gaming,theme,glyphs,single,launcher,notify,vram,cec,machine,poweroff diff --git a/archiso/airootfs/etc/calamares/modules/shellprocess_steamify.conf b/archiso/airootfs/etc/calamares/modules/shellprocess_steamify.conf index bf86d9c..24dfd01 100644 --- a/archiso/airootfs/etc/calamares/modules/shellprocess_steamify.conf +++ b/archiso/airootfs/etc/calamares/modules/shellprocess_steamify.conf @@ -4,7 +4,7 @@ dontChroot: true timeout: 3600 script: - - "-/usr/local/bin/steamify-install ${ROOT} ${USER} \"${gs[packagechooser_steamifyskip]}\" \"${gs[packagechooser_steamifyboot]}\"" + - "-/usr/local/bin/steamify-install ${ROOT} ${USER} \"${gs[packagechooser_steamifypage]}\"" i18n: name: "Setting up the Steam Machine (Steamify)" diff --git a/archiso/airootfs/usr/local/bin/calamares-online.sh b/archiso/airootfs/usr/local/bin/calamares-online.sh index aaa71d8..9f4fcad 100755 --- a/archiso/airootfs/usr/local/bin/calamares-online.sh +++ b/archiso/airootfs/usr/local/bin/calamares-online.sh @@ -45,10 +45,11 @@ main() { EOF sudo cp "/usr/share/calamares/settings_${mode}.conf" /etc/calamares/settings.conf - # Steamify: its step (modules/shellprocess_steamify.conf) before the - # installer cleans up after itself. - sudo sed -i -e 's/^- id: cleanup_calamares$/- id: steamifyskip\n module: packagechooser\n config: packagechooser_steamifyskip.conf\n\n- id: steamifyboot\n module: packagechooser\n config: packagechooser_steamifyboot.conf\n\n- id: steamify\n module: shellprocess\n config: shellprocess_steamify.conf\n\n&/' \ - -e 's/^ - packagechooser@desktop$/&\n - packagechooser@steamifyskip\n - packagechooser@steamifyboot/' \ + # Steamify: its page (packagechooserq@steamifypage, right after the + # packages page) and its step (modules/shellprocess_steamify.conf) before + # the installer cleans up after itself. + sudo sed -i -e 's/^- id: cleanup_calamares$/- id: steamifypage\n module: packagechooserq\n config: packagechooserq_steamifypage.conf\n\n- id: steamify\n module: shellprocess\n config: shellprocess_steamify.conf\n\n&/' \ + -e 's/^ - netinstall$/&\n - packagechooserq@steamifypage/' \ -e 's/^ - shellprocess@cleanup_calamares$/ - shellprocess@steamify\n&/' /etc/calamares/settings.conf exec pkexec-wrapper calamares -D6 >> $log } diff --git a/archiso/airootfs/usr/local/bin/steamify-install b/archiso/airootfs/usr/local/bin/steamify-install index 65bf882..5544fb9 100755 --- a/archiso/airootfs/usr/local/bin/steamify-install +++ b/archiso/airootfs/usr/local/bin/steamify-install @@ -1,29 +1,30 @@ #!/bin/bash -# steamify-install [] [gamescope|desktop]: -# runs Steamify's default setup (minus what the installer's Steam Machine -# page leaves out, where the boot page says) for +# steamify-install : runs Steamify's setup for # the new user inside the installed system (Calamares, after the users and -# services steps). Steamify refuses root and asks sudo per command, so the +# services steps), as chosen on the installer's Steamify page: is the +# ids that are on, comma-separated, plus "boot" for Boot into Desktop, or +# "none" (packagechooser_steamifypage). Steamify refuses root and asks sudo +# per command, so the # user gets a NOPASSWD rule for this run only, named to be read last: sudo # takes the last matching rule, and a later %wheel rule would ask again. Never fails the install: a # problem leaves the log and Steamify can be run again from the desktop. -root="$1" user="$2" -# The pages' ids, comma-separated; the "Everything" entry is an empty id. -skip="$(tr ',' '\n' <<< "${3:-}" | grep -v '^$' | paste -sd, -)" -boot="${4:-gamescope}" -# Steamify 2.7.0 takes the items to turn on (--options), not the ones to -# leave out: every item of the default setup minus the skipped ones (the -# power-off fix goes with Steam Machine support). Until the Steamify page -# passes the "on" list itself. -options="" -for id in gaming theme glyphs single launcher notify vram cec machine poweroff; do - [[ ",$skip," == *",$id,"* ]] && continue - [[ "$id" == poweroff && ",$skip," == *",machine,"* ]] && continue - options+="${options:+,}$id" +root="$1" user="$2" choice="${3:-}" +# Not filled in by Calamares (empty, or the literal ${gs[...]}): the page's +# default, everything on. +[[ -z "$choice" || "$choice" == *'${'* ]] && choice=gaming,theme,glyphs,single,launcher,notify,vram,cec,machine,poweroff +boot=gamescope options="" +for id in ${choice//,/ }; do + case "$id" in + boot) boot=desktop ;; + none) ;; + *) options+="${options:+,}$id" ;; + esac done log="$root/var/log/steamify-install.log" rule="$root/etc/sudoers.d/zz-steamify-install" [[ -d "$root" && -n "$user" ]] || exit 0 +# Everything switched off on the page: nothing to set up. +[[ -n "$options" ]] || { echo "Nothing chosen on the Steamify page." > "$log"; exit 0; } install -Dm755 /usr/local/share/steamify/steamify.sh "$root/var/tmp/steamify.sh" printf '%s ALL=(ALL) NOPASSWD: ALL\n' "$user" > "$rule" diff --git a/archiso/airootfs/usr/local/share/steamify-installer/Items.qml b/archiso/airootfs/usr/local/share/steamify-installer/Items.qml new file mode 100644 index 0000000..582574e --- /dev/null +++ b/archiso/airootfs/usr/local/share/steamify-installer/Items.qml @@ -0,0 +1,20 @@ +// The Steamify page's rows when `steamify.sh --defaults --list` can't be run +// (see calamares-online.sh, which writes the real list over this copy in +// /usr/local/share/steamify/qml at the start of the installer). +pragma Singleton +import QtQuick + +QtObject { + readonly property var rows: [ + { id: "gaming", label: "SteamOS conversion", hint: "boot into gaming mode, Steam on the desktop", kind: "toggle", parent: "", on: true, selectable: true }, + { id: "boot", label: "Boot into", hint: "", kind: "choice", parent: "gaming", on: false, selectable: true }, + { id: "theme", label: "Install SteamOS theme", hint: "Vapor look (cachyos-vapor)", kind: "toggle", parent: "", on: true, selectable: true }, + { id: "glyphs", label: "Install Steam Deck/Machine icons", hint: "Deck button icons in gaming mode", kind: "toggle", parent: "", on: true, selectable: true }, + { id: "single", label: "Single user mode", hint: "no password, lock screen or log out (SDDM)", kind: "toggle", parent: "", on: true, selectable: true }, + { id: "launcher", label: "Steamify shortcut", hint: "the app on the desktop, Steamify Terminal in the launcher", kind: "toggle", parent: "", on: true, selectable: true }, + { id: "notify", label: "Update notifications", hint: "a notification when there's a new Steamify", kind: "toggle", parent: "", on: true, selectable: true }, + { id: "cec", label: "HDMI-CEC", hint: "use Steam with the TV remote, TV on/off with the PC", kind: "toggle", parent: "", on: true, selectable: true }, + { id: "machine", label: "Steam Machine support", hint: "LED bar driver, hardware settings in Steam", kind: "toggle", parent: "", on: true, selectable: true }, + { id: "poweroff", label: "Power-off fix", hint: "the Steam Machine stays off after shutting down", kind: "toggle", parent: "machine", on: true, selectable: true } + ] +} diff --git a/archiso/airootfs/usr/local/share/steamify-installer/SteamifyPage.qml b/archiso/airootfs/usr/local/share/steamify-installer/SteamifyPage.qml new file mode 100644 index 0000000..144553b --- /dev/null +++ b/archiso/airootfs/usr/local/share/steamify-installer/SteamifyPage.qml @@ -0,0 +1,185 @@ +// Steamify's page in Calamares (packagechooserq@steamifypage, method legacy), +// styled like the rest of CachyOS's installer: its palette, standard +// controls, CachyOS's turquoise for the selected row. Left the options, +// right what the selected (or hovered) one does. +// The rows come from Steamify itself: calamares-online.sh writes +// ../steamify/qml/Items.qml from `steamify.sh --defaults --list` (the newest +// Steamify, else the ISO's), so new options show up without a new ISO; the +// explanations are the app's (Texts.qml, same folder). +// The choice goes to Calamares' global storage as packagechooser_steamifypage: +// the ids that are on, comma-separated, plus "boot" for Boot into Desktop +// ("none" when nothing is on); steamify-install passes it to +// `steamify.sh --defaults --options ... --boot ...`. +import io.calamares.core 1.0 +import io.calamares.ui 1.0 +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import "../steamify/qml" + +Item { + id: page + width: parent.width + height: parent.height + + SystemPalette { id: pal; colorGroup: SystemPalette.Active } + readonly property color accent: "#00CED1" + + // Only what can be set up here (Steamify leaves out the rest). + readonly property var rows: Items.rows.filter(function (r) { return r.selectable; }) + property var want: ({}) + property string boot: "gamescope" + property int sel: 0 + + function label(r) { return (Texts.items[r.id] || {}).label || r.label; } + function hint(r) { return (Texts.items[r.id] || {}).hint || r.hint; } + function shown(r) { return !r.parent || !!want[r.parent]; } + + // Steamify's own rules: a sub-option needs its parent (turning a parent + // off turns its sub-options off); single user mode needs the conversion. + function toggle(id) { + var w = Object.assign({}, want); + w[id] = !w[id]; + for (var i = 0; i < rows.length; i++) { + var r = rows[i]; + if (w[id] && r.id === id && r.parent) w[r.parent] = true; + if (!w[id] && r.parent === id && r.kind === "toggle") w[r.id] = false; + if (w[id] && r.parent === id && r.kind === "toggle") w[r.id] = true; // opt-out sub-options + } + if (id === "single" && w.single) w.gaming = true; + if (id === "gaming" && !w.gaming) { w.single = false; boot = "gamescope"; } + want = w; + } + + function choice() { + var on = []; + for (var i = 0; i < rows.length; i++) + if (rows[i].kind === "toggle" && want[rows[i].id]) on.push(rows[i].id); + if (want.gaming && boot === "desktop") on.push("boot"); + return on.length ? on.join(",") : "none"; + } + onWantChanged: config.packageChoice = choice() + onBootChanged: config.packageChoice = choice() + Component.onCompleted: { + // Preselected like Steamify's first run. + var w = {}; + for (var i = 0; i < rows.length; i++) if (rows[i].kind === "toggle") w[rows[i].id] = rows[i].on; + want = w; + config.packageChoice = choice(); + } + + ColumnLayout { + anchors.fill: parent + anchors.margins: 16 + spacing: 12 + + Label { + Layout.fillWidth: true + text: "Steamify sets up the SteamOS experience while CachyOS installs. Everything can be changed later with the Steamify app." + wrapMode: Text.WordWrap + color: pal.windowText + } + + RowLayout { + Layout.fillWidth: true + Layout.fillHeight: true + spacing: 16 + + ListView { + id: list + Layout.fillHeight: true + Layout.preferredWidth: parent.width * 0.55 + clip: true + spacing: 4 + boundsBehavior: Flickable.StopAtBounds + model: page.rows + ScrollBar.vertical: ScrollBar { policy: ScrollBar.AsNeeded } + delegate: Rectangle { + id: row + required property var modelData + required property int index + readonly property var it: modelData + readonly property bool vis: page.shown(it) + readonly property bool selected: vis && index === page.sel + width: list.width - 12 + height: vis ? 52 : 0 + visible: vis + radius: 4 + color: selected ? Qt.rgba(page.accent.r, page.accent.g, page.accent.b, 0.18) : pal.base + border.width: 1 + border.color: selected ? page.accent : Qt.rgba(pal.windowText.r, pal.windowText.g, pal.windowText.b, 0.15) + + MouseArea { + anchors.fill: parent; hoverEnabled: true + // Hovering explains a row too, like selecting it. + onEntered: page.sel = row.index + onClicked: { page.sel = row.index; if (row.it.kind === "toggle") page.toggle(row.it.id); } + } + RowLayout { + anchors.fill: parent + anchors.leftMargin: row.it.parent ? 32 : 12 + anchors.rightMargin: 12 + spacing: 12 + ColumnLayout { + Layout.fillWidth: true + spacing: 0 + Label { Layout.fillWidth: true; text: page.label(row.it); font.bold: true; elide: Text.ElideRight; color: pal.text } + Label { Layout.fillWidth: true; text: page.hint(row.it); elide: Text.ElideRight; color: pal.text; opacity: 0.7; font.pointSize: 8.5 } + } + Switch { + visible: row.it.kind === "toggle" + checked: !!page.want[row.it.id] + onToggled: { page.sel = row.index; page.toggle(row.it.id); } + } + RowLayout { + visible: row.it.kind === "choice" + spacing: 4 + RadioButton { text: "Gaming mode"; checked: page.boot === "gamescope" + onClicked: { page.sel = row.index; page.boot = "gamescope"; } } + RadioButton { text: "Desktop"; checked: page.boot === "desktop" + onClicked: { page.sel = row.index; page.boot = "desktop"; } } + } + } + } + } + + // What the selected (or hovered) option does. + Rectangle { + id: detail + readonly property var it: page.rows[page.sel] || ({}) + readonly property var tx: Texts.items[it.id] || ({}) + Layout.fillWidth: true + Layout.fillHeight: true + radius: 4 + color: pal.base + border.width: 1 + border.color: Qt.rgba(pal.windowText.r, pal.windowText.g, pal.windowText.b, 0.15) + onItChanged: flick.contentY = 0 + + Flickable { + id: flick + anchors.fill: parent + anchors.margins: 16 + clip: true + contentHeight: col.implicitHeight + boundsBehavior: Flickable.StopAtBounds + ScrollBar.vertical: ScrollBar { policy: ScrollBar.AsNeeded } + ColumnLayout { + id: col + width: flick.width - 12 + spacing: 10 + Label { Layout.fillWidth: true; text: detail.it.id ? page.label(detail.it) : ""; font.bold: true; font.pointSize: 13 + wrapMode: Text.WordWrap; color: pal.text } + Label { Layout.fillWidth: true; text: detail.tx.body || page.hint(detail.it) || ""; wrapMode: Text.WordWrap; color: pal.text } + Label { visible: (detail.tx.changes || []).length > 0; text: "What it changes"; font.bold: true; color: pal.text; topPadding: 4 } + Repeater { + model: detail.tx.changes || [] + Label { required property string modelData; Layout.fillWidth: true; text: "• " + modelData + wrapMode: Text.WordWrap; color: pal.text } + } + } + } + } + } + } +} diff --git a/steamify-prepare.sh b/steamify-prepare.sh index 9adede4..1d7154e 100755 --- a/steamify-prepare.sh +++ b/steamify-prepare.sh @@ -1,7 +1,7 @@ #!/bin/bash # steamify-prepare.sh [steamify checkout]: puts the Steamify bundle on the # ISO (archiso/airootfs/usr/local/share/steamify/steamify.sh), used by the -# installer's Steamify step. Built from a checkout when given, else the +# installer's Steamify step, and what its Steamify page shows (qml/). Built from a checkout when given, else the # newest release. Run before buildiso.sh. set -euo pipefail dest="$(cd "$(dirname "$0")" && pwd)/archiso/airootfs/usr/local/share/steamify/steamify.sh" @@ -18,6 +18,20 @@ if [[ -n "${1:-}" ]]; then else curl -fsSL https://raw.githubusercontent.com/theupriser/steamify-cachyos/main/patches/steamify-fremont-poweroff.c -o "$poweroff" fi +# The installer's Steamify page (/usr/local/share/steamify-installer/ +# SteamifyPage.qml) shows the app's explanations (Texts.qml, from the +# checkout, else the newest release's app) and its rows (Items.qml: this +# fallback until calamares-online.sh writes the real list). +qml="$(dirname "$dest")/qml" +rm -rf "$qml"; mkdir -p "$qml" +if [[ -n "${1:-}" ]]; then + cp "$1/ui/qml/Texts.qml" "$qml/" +else + curl -fsSL https://github.com/theupriser/steamify-cachyos/releases/latest/download/steamify-app.tar.gz | + tar -xz -C "$qml" --strip-components=2 ui/qml/Texts.qml +fi +cp "$(cd "$(dirname "$0")" && pwd)/archiso/airootfs/usr/local/share/steamify-installer/Items.qml" "$qml/" +printf '%s\n' 'singleton Texts 1.0 Texts.qml' 'singleton Items 1.0 Items.qml' > "$qml/qmldir" grep -q -- '--defaults' "$dest" || { echo "This Steamify has no --defaults (needs 2.6.0 or newer)." >&2; exit 1; } chmod 755 "$dest" # Temporary: cachyos-calamares-next 3.4.2-13 is linked against Boost 1.91