diff --git a/AGENTS.md b/AGENTS.md index 63d2afd..9c1678b 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -33,7 +33,8 @@ gamescope and the Plasma desktop. Primary target: the Valve Steam Machine `VERSION` whenever what `_enable` sets up changes (a new component gets one too): installs recorded with an older version are ticked and re-applied ("update" in the plan and the app). New default - sub-options are ticked for installs whose parent is on (`feature_new`). + options are ticked for installs whose parent (top-level: `gaming`) is on + (`feature_new`, shown as "new"). Status still comes from the system; don't add ad-hoc `_repair` checks for new changes. - **Reversibility.** Every per-user KDE setting a component changes goes @@ -231,6 +232,14 @@ gamescope and the Plasma desktop. Primary target: the Valve Steam Machine `linux-cachyos` is clang-built, `-bore` GCC-built: let DKMS pick the compiler, never pass `LLVM=1`. Test shutdown on the real machine for every new major kernel. +- VRAM booster (`vram`, `lib/vram-booster.sh`, top-level, ticked by + default, 2.3.0; only available when `/sys/fs/cgroup/dmem.capacity` lists a + VRAM region of at least 2 GB; greyed out with an NVIDIA card, + `vram_selectable`, faked with `WIZARD_VRAM_FAKE_NVIDIA=1`): CachyOS's `dmemcg-booster` (system + user service) and + `plasma-foreground-booster`, like SteamOS 3.9's VRAM management. The + latter only starts with `kcgroupsrc [Foreground Booster] autostart=true` + (set with `kset`). Disable removes only the packages it installed + (`state_get vram installed_pkgs`). - `Relogin=true` means a gamescope that fails to start is relaunched in a tight loop; keep that in mind when changing session handling. diff --git a/CHANGELOG.md b/CHANGELOG.md index f92b5fd..3f746d4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,9 +5,17 @@ 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. +## 2.3.0 - 2026-09-27 + +- **feat: VRAM booster greyed out with an NVIDIA card, with why** +- `b0947d7` **feat: VRAM booster only for GPUs with at least 2 GB of their own VRAM** +- `3f97c85` **feat: VRAM booster for every supported GPU, not only the Steam Machine** +- `fd162c1` **feat: New badge for default sub-options added in this version** +- `54ebddf` **feat: VRAM booster for the Steam Machine** + ## 2.2.0 - 2026-09-27 -- **docs: Changelog entries per commit for 2.2.0** +- `ef585c0` **docs: Changelog entries per commit for 2.2.0** - `32795bd` **feat(ui): Update badge on the progress screen** - `3c8ca65` **fix(ui): Never write the sudo password to a file; Update label on the progress screen** - The app gave `sudo` the password through a short-lived file; the helper diff --git a/README.md b/README.md index eb08c43..124e888 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,12 @@ Steam Machine. 5. **Steamify shortcut** - "Steamify CachyOS" on the desktop and in the launcher opens the newest Steamify app, so you don't need the install command again. "Steamify Terminal" in the launcher opens the terminal menu. -6. **HDMI-CEC** (experimental; off by default, on by default on a Steam +6. **VRAM booster** - on by default: the game you're playing keeps its video + memory and background apps make room, like SteamOS 3.9. Shown for GPUs + whose driver supports it (AMD, Intel) on kernel 7.2 or newer; greyed out + with an NVIDIA card until NVIDIA's driver supports it + ([details](TECHNICAL.md#vram-booster)). +7. **HDMI-CEC** (experimental; off by default, on by default on a Steam Machine) - use Steam with your TV's remote, and the TV turns on and off with the PC. Needs a PC with CEC (like the Steam Machine) or a USB CEC adapter; most graphics cards, NVIDIA diff --git a/TECHNICAL.md b/TECHNICAL.md index 2bb7b10..9bbd3bc 100644 --- a/TECHNICAL.md +++ b/TECHNICAL.md @@ -236,6 +236,34 @@ Where Steam Machine support was set up before 2.2.0, the menu ticks the power-off fix as a new default sub-option (see feature versions below), so a normal run adds it. +## VRAM booster + +SteamOS 3.9 manages the dGPU's VRAM per cgroup: the game in front is +protected, background apps are evicted first. Without it a game that needs +most of the VRAM (8 GB on the Steam Machine) can be pushed into system RAM +by the desktop and other apps, and stutter. The kernel side is the `dmem` +cgroup controller (7.2); amdgpu and Intel's xe register their VRAM with it, +NVIDIA's driver doesn't: with an NVIDIA card the option is shown greyed +out, with why (`WIZARD_VRAM_FAKE_NVIDIA=1` fakes that for tests). Otherwise +the **VRAM booster** option is only shown when +`/sys/fs/cgroup/dmem.capacity` lists a VRAM region of at least 2 GB (a +dedicated GPU; an integrated one registers a small carve-out) or it's already on, and is +ticked by default; new in 2.3.0, so setups with the SteamOS conversion on +get it ticked. CachyOS packages the userspace side, which it installs: + +- `dmemcg-booster`: `dmemcg-booster-system.service` enables the `dmem` + controller and sets `dmem.low` for the cgroup in front; + `dmemcg-booster-user.service` runs in the user session. +- `plasma-foreground-booster`: tells it which window is in front on the + desktop. It only starts with `autostart=true` under `[Foreground Booster]` + in `kcgroupsrc`, which the wizard sets (and reverts). + +Check it with `cat /sys/fs/cgroup/cgroup.subtree_control` (lists `dmem`) and +the `dmem.low` of the slices under `user@.service`. Turning it off +disables the services and removes the packages the wizard installed. +Not added: a fixed `dmem.max` cap for `app.slice` (as some guides do); only +worth it if the booster itself turns out to cause stutter. + ## Feature versions (updates) Each component has a feature version (`FEATURE_VERSION` in `lib/menu.sh`): @@ -250,9 +278,11 @@ on the system itself; the version only decides about updates: "update". A setup from before versions were recorded counts as 2.1.0, so on a machine set up with 2.1.0, Steam Machine support (2.2.0) is updated. -- **A new default sub-option** (not in `NO_PRESELECT`) whose parent is on - and that was never turned on or off: ticked, so a normal run adds it. - Turning it off once records `off`, so it isn't ticked again. +- **A new default option** (not in `NO_PRESELECT`) whose parent is on (for + a top-level one: the SteamOS conversion) and that was never turned on or + off: ticked, so a normal run adds it. Turning it off once records `off`, + so it isn't ticked again. The menu and the app show it with a **New** + badge, and the plan says "new in this version". Set a component's entry to the new `VERSION` whenever what its `_enable` sets up changes. @@ -327,6 +357,7 @@ immediately, which can turn into a loop - see | `lib/cec.sh` | HDMI-CEC: Valve's `cecd` and friends from its `holo` repository | | `lib/steam-machine.sh` | Steam Machine support: LED driver, LED access, steamos-manager | | `lib/fremont-poweroff.sh` | Steam Machine support: the power-off fix (DKMS module from `patches/`) | +| `lib/vram-booster.sh` | VRAM booster (`dmemcg-booster`, `plasma-foreground-booster`) | | `patches/` | Module sources and patches the scripts build or apply (`patch_file`); see its README | | `.github/tools/bundle.sh` | Builds the single-file version (`dist/steamify.sh`), with `patches/` embedded | | `.github/workflows/bundle.yml` | Builds and checks it on every push; publishes it on `main` | diff --git a/lib/backend.sh b/lib/backend.sh index da6e867..326c763 100644 --- a/lib/backend.sh +++ b/lib/backend.sh @@ -63,6 +63,7 @@ backend_status() { items+=",\"on\":$( [[ "$now" == 1 ]] && echo true || echo false)" items+=",\"wanted\":$( [[ "${WANTED[$c]:-0}" == 1 ]] && echo true || echo false)" items+=",\"update\":$(feature_outdated "$c" && echo true || echo false)" + items+=",\"new\":$(feature_new "$c" && echo true || echo false)" items+=",\"selectable\":$(component_selectable "$c" && echo true || echo false)}" done local cec="" f diff --git a/lib/menu.sh b/lib/menu.sh index 2d8baec..2d8f2f2 100644 --- a/lib/menu.sh +++ b/lib/menu.sh @@ -5,7 +5,7 @@ # Menu order. Components are turned on in this order and off in reverse; # gaming must come first (single user builds on it). -COMPONENTS=(gaming boot theme glyphs single launcher cec machine poweroff kpin hdmi bios) +COMPONENTS=(gaming boot theme glyphs single launcher vram cec machine poweroff kpin hdmi bios) # One-off actions rather than on/off components: never preselected, never # re-applied, not listed as on or off. ACTIONS=(bios) @@ -26,7 +26,7 @@ NO_PRESELECT=(boot cec kpin hdmi) FEATURE_BASELINE=2.1.0 declare -A FEATURE_VERSION=( [gaming]=2.1.0 [boot]=2.1.0 [theme]=2.1.0 [glyphs]=2.1.0 [single]=2.1.0 - [launcher]=2.1.0 [cec]=2.1.0 [machine]=2.2.0 [poweroff]=2.2.0 + [launcher]=2.1.0 [cec]=2.1.0 [machine]=2.2.0 [poweroff]=2.2.0 [vram]=2.3.0 [kpin]=2.1.0 [hdmi]=2.1.0 ) @@ -37,6 +37,7 @@ declare -A LABEL=( [glyphs]="Install Steam Deck/Machine icons: Deck button icons in gaming mode" [single]="Single user mode: no password, lock screen or log out (SDDM)" [launcher]="Steamify shortcut: the app on the desktop, Steamify Terminal in the launcher" + [vram]="VRAM booster: the game in front keeps its VRAM, background apps make room" [cec]="HDMI-CEC: use Steam with the TV remote, TV on/off with the PC (experimental)" [machine]="Steam Machine support: LED bar driver, hardware settings in Steam" [poweroff]="Power-off fix: the Steam Machine stays off after shutting down" @@ -49,6 +50,7 @@ declare -A CURRENT WANTED component_available() { case "$1" in machine|poweroff) machine_available ;; + vram) vram_available ;; kpin) kpin_available ;; hdmi) hdmi_available ;; bios) bios_available ;; @@ -89,20 +91,25 @@ feature_outdated() { menu_label() { # The label, with "(update)" after the name when a newer version of it - # will be applied. - local l="${LABEL[$1]}" - if feature_outdated "$1"; then - if [[ "$l" == *:* ]]; then l="${l%%:*} ${c_yellow}(update)${c_reset}:${l#*:}" - else l+=" ${c_yellow}(update)${c_reset}"; fi + # will be applied, "(new)" for a default sub-option added since the + # last run. + local l="${LABEL[$1]}" b="" + if feature_outdated "$1"; then b="${c_yellow}(update)${c_reset}" + elif feature_new "$1"; then b="${c_green}(new)${c_reset}"; fi + if [[ -n "$b" ]]; then + if [[ "$l" == *:* ]]; then l="${l%%:*} $b:${l#*:}" + else l+=" $b"; fi fi printf '%s' "$l" } feature_new() { - # A default sub-option added after its parent was set up (e.g. the - # power-off fix under Steam Machine support): never turned on or off. - [[ -n "${PARENT[$1]:-}" && "${CURRENT[${PARENT[$1]}]:-0}" == 1 && "${CURRENT[$1]:-0}" == 0 ]] && - ! is_action "$1" && [[ " ${NO_PRESELECT[*]} " != *" $1 "* ]] && + # A default option added after its parent was set up (e.g. the power-off + # fix under Steam Machine support; a top-level one counts the SteamOS + # conversion as its parent): never turned on or off. + local p="${PARENT[$1]:-gaming}" + [[ "$1" != gaming && "${CURRENT[$p]:-0}" == 1 && "${CURRENT[$1]:-0}" == 0 ]] && + ! is_action "$1" && component_selectable "$1" && [[ " ${NO_PRESELECT[*]} " != *" $1 "* ]] && [[ -z "$(state_get features "$1")" ]] } @@ -110,6 +117,7 @@ component_selectable() { # Greyed out and not tickable when it has nothing to do. case "$1" in bios) bios_selectable ;; + vram) vram_selectable ;; esac } @@ -138,10 +146,12 @@ detect_components() { launcher_repair && WANTED[launcher]=1 # Shows the current and newest BIOS version. bios_available && { bios_lookup_newest; LABEL[bios]="$(bios_label)"; } + # Greyed out with an NVIDIA card: say why. + component_available vram && ! vram_selectable && LABEL[vram]="VRAM booster: not supported by NVIDIA's driver yet" # 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" && + component_available "$c" && component_selectable "$c" && ! is_action "$c" && [[ " ${NO_PRESELECT[*]} " != *" $c "* ]] && WANTED[$c]=1 done machine_available && WANTED[cec]=1 diff --git a/lib/vram-booster.sh b/lib/vram-booster.sh new file mode 100644 index 0000000..3941844 --- /dev/null +++ b/lib/vram-booster.sh @@ -0,0 +1,78 @@ +#!/bin/bash +# "VRAM booster" menu item (vram), ticked by default, like SteamOS 3.9's +# dGPU VRAM management: the game in front keeps its VRAM, background apps +# are evicted first, so a game that needs most of it doesn't spill into +# system RAM and stutter. The kernel side is the dmem cgroup controller +# (7.2; amdgpu and xe register their VRAM, NVIDIA's driver doesn't), so it's +# only offered when the kernel lists a VRAM region of at least 2 GB. CachyOS packages the userspace +# side: dmemcg-booster (a system service that enables the controller and +# sets the limits, plus a user service) and plasma-foreground-booster, which +# tells it which window is in front on the desktop. The latter only starts +# with "autostart" set in kcgroupsrc. +# Sourced by steamify.sh; not meant to be run on its own. + +VRAM_PKGS=(dmemcg-booster plasma-foreground-booster) +VRAM_MIN_BYTES=$((2 * 1024 * 1024 * 1024)) + +vram_supported() { + # WIZARD_VRAM_FAKE_NVIDIA=1 (tests): as with an NVIDIA card. + [[ -n "${WIZARD_VRAM_FAKE_NVIDIA:-}" ]] && return 1 + # Only for a GPU with its own VRAM: an integrated GPU registers a small + # carve-out too, but mostly uses system RAM, so there's little to boost. + awk -v min="$VRAM_MIN_BYTES" '$1 ~ /\/vram$/ && $2 >= min { found = 1 } END { exit !found }' \ + /sys/fs/cgroup/dmem.capacity 2>/dev/null +} + +vram_nvidia() { + # An NVIDIA display controller (PCI vendor 10de, class 03xxxx). + local d + [[ -n "${WIZARD_VRAM_FAKE_NVIDIA:-}" ]] && return 0 + for d in /sys/bus/pci/devices/*; do + [[ "$(cat "$d/vendor" 2>/dev/null)" == 0x10de && "$(cat "$d/class" 2>/dev/null)" == 0x03* ]] && return 0 + done + return 1 +} + +# Shown where it works, and greyed out with an NVIDIA card, whose driver +# doesn't register its VRAM with the kernel (yet): so it's clear why it +# can't be turned on. Hidden elsewhere (integrated graphics, older kernel). +vram_available() { vram_supported || vram_status || vram_nvidia; } +vram_selectable() { vram_supported || vram_status; } + +vram_status() { + pacman -Q "${VRAM_PKGS[@]}" >/dev/null 2>&1 && + systemctl is-enabled -q dmemcg-booster-system.service 2>/dev/null && + systemctl --user is-enabled -q dmemcg-booster-user.service 2>/dev/null +} + +vram_enable() { + local p missing=() + info "Installing the VRAM booster (the game in front keeps its VRAM)..." + for p in "${VRAM_PKGS[@]}"; do pacman -Q "$p" >/dev/null 2>&1 || missing+=("$p"); done + if ((${#missing[@]})); then + sudo pacman -S --needed --noconfirm "${missing[@]}" || + { err "Installing ${missing[*]} failed."; return 1; } + state_set vram installed_pkgs "${missing[*]}" + fi + sudo systemctl enable --now dmemcg-booster-system.service >/dev/null 2>&1 || + { err "Starting dmemcg-booster failed."; return 1; } + systemctl --user enable --now dmemcg-booster-user.service >/dev/null 2>&1 || + { err "Starting dmemcg-booster for $USER failed."; return 1; } + kset vram kcgroupsrc "Foreground Booster" autostart true + # Started by the Plasma session from now on; start it in this one too. + [[ "${XDG_CURRENT_DESKTOP:-}" == KDE ]] && + systemctl --user start plasma-foreground-booster.service >/dev/null 2>&1 + ok "VRAM booster on: the game in front keeps its VRAM." +} + +vram_disable() { + local pkgs + systemctl --user disable --now plasma-foreground-booster.service dmemcg-booster-user.service >/dev/null 2>&1 + sudo systemctl disable --now dmemcg-booster-system.service >/dev/null 2>&1 + krevert vram + pkgs="$(state_get vram installed_pkgs)" + # shellcheck disable=SC2086 # a list of package names + [[ -n "$pkgs" ]] && sudo pacman -Rns --noconfirm $pkgs >/dev/null 2>&1 + state_clear vram + ok "VRAM booster off." +} diff --git a/steamify.sh b/steamify.sh index ea40854..b34ef37 100755 --- a/steamify.sh +++ b/steamify.sh @@ -15,11 +15,11 @@ set -uo pipefail # Release version, see CHANGELOG.md. -VERSION=2.2.0 +VERSION=2.3.0 SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -for lib in common state packages login-manager single-user steam-desktop steam-machine fremont-poweroff hdmi-refresh cec boot-session vapor-theme steamos-extras bios desktop-shortcut wizard-shortcut menu backend; do +for lib in common state packages login-manager single-user steam-desktop steam-machine fremont-poweroff vram-booster hdmi-refresh cec boot-session vapor-theme steamos-extras bios desktop-shortcut wizard-shortcut menu backend; do # shellcheck source=/dev/null source "$SCRIPT_DIR/lib/$lib.sh" done @@ -140,7 +140,9 @@ while true; do 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 feature_outdated "$c"; then echo " - update: ${LABEL[$c]} (changed in this version)" - elif [[ "${CURRENT[$c]}" == 1 ]]; then echo " - re-apply: ${LABEL[$c]}"; else echo " - turn on: ${LABEL[$c]}"; fi + elif [[ "${CURRENT[$c]}" == 1 ]]; then echo " - re-apply: ${LABEL[$c]}" + elif feature_new "$c"; then echo " - turn on: ${LABEL[$c]} (new in this version)" + else echo " - turn on: ${LABEL[$c]}"; fi done ask_yn "Go ahead?" y || { info "Nothing changed."; continue; } diff --git a/ui/qml/Main.qml b/ui/qml/Main.qml index 9f2f700..1595498 100644 --- a/ui/qml/Main.qml +++ b/ui/qml/Main.qml @@ -45,6 +45,11 @@ ApplicationWindow { } // --- Texts per item (the backend's labels are the fallback) --- + // Replaces the explanation of an option that can't be turned on here. + readonly property var unsupported: ({ + vram: { body: "Not available with your NVIDIA graphics card: NVIDIA's driver doesn't tell Linux how its video memory is used yet, so there's nothing to steer. It works with AMD and Intel graphics cards, and shows up here as soon as NVIDIA's driver supports it.", + changes: ["Nothing: can't be turned on with NVIDIA's driver"] } + }) readonly property var texts: ({ gaming: { label: "SteamOS conversion", hint: "Boot into gaming mode, Steam on the desktop", body: "Boots straight into gaming mode. Switch to Desktop in Steam works, and Return to Gaming Mode on the desktop brings you back.", @@ -72,6 +77,9 @@ ApplicationWindow { poweroff: { label: "Power-off fix", hint: "Stays off after shutting down", body: "With recent kernels the Steam Machine starts again right after shutting down: the firmware leaves a wake bit set, and newer kernels (7.2, and updates of 6.x, 7.0 and 7.1) no longer clear it. Valve's own kernel clears it; this small module does the same right before power-off.", changes: ["steamify-fremont-poweroff module for each installed kernel (DKMS)", "Only on a Steam Machine, only touches that one wake bit", "Off: recent kernels may start it again after shutting down"] }, + vram: { label: "VRAM booster", hint: "The game in front keeps its VRAM", + body: "Like SteamOS 3.9: the game you're playing keeps its video memory, and background apps are moved out first. Without it a game that needs most of the video memory can spill into system RAM and stutter. Shown for GPUs whose driver supports it (AMD, Intel) on kernel 7.2 or newer.", + changes: ["dmemcg-booster (system and user service)", "plasma-foreground-booster, which tells it which window is in front", "Off: removed again, unless you had installed them yourself"] }, kpin: { label: "Pin the kernel", hint: "Untick for CachyOS's current kernel", body: "Keeps the Steam Machine on CachyOS kernel 7.1.6. Untick it to go back to CachyOS's current kernel; HDMI refresh boost goes with it.", changes: ["linux-cachyos from Steamify's release (signature checked)", "Kept in /var/cache/steamify/kernel", "Added to IgnorePkg"] }, @@ -241,6 +249,8 @@ ApplicationWindow { function toggle(id) { hdmiNote = ""; + var cur = items.find(function (i) { return i.id === id; }); + if (cur && cur.kind === "toggle" && cur.selectable === false) return; var w = Object.assign({}, want); w[id] = !w[id]; if (id === "gaming" && !w.gaming) { w.single = false; } @@ -264,7 +274,7 @@ ApplicationWindow { var it = items[i]; if (it.kind !== "toggle") continue; if (it.parent && !want[it.parent]) { if (it.on) p.push({ id: it.id, action: "off" }); continue; } - if (want[it.id] && !it.on) p.push({ id: it.id, action: "on" }); + if (want[it.id] && !it.on) p.push({ id: it.id, action: "on", isNew: !!it["new"] }); else if (!want[it.id] && it.on) p.push({ id: it.id, action: "off" }); else if (want[it.id] && (reapply || it.update)) p.push({ id: it.id, action: reapply ? "again" : "update" }); } @@ -626,17 +636,19 @@ ApplicationWindow { anchors.verticalCenter: parent.verticalCenter; spacing: 2 x: row.modelData.parent ? 46 : 16 width: controls.x - x - 16 - opacity: row.modelData.kind === "action" && !(bios && bios.selectable) ? 0.6 : 1 - // The name, with an Update badge when a newer version of it will be applied. + opacity: (row.modelData.kind === "action" && !(bios && bios.selectable)) || (row.modelData.kind === "toggle" && row.modelData.selectable === false) ? 0.6 : 1 + // The name, with an Update badge when a newer version of it will be + // applied, New for a default sub-option added since the last run. Row { width: parent.width; spacing: 10 Text { text: label(row.modelData); color: t.textHi; font.family: t.body; font.pixelSize: 17; font.weight: Font.DemiBold; elide: Text.ElideRight width: Math.min(implicitWidth, parent.width - (upd.visible ? upd.width + parent.spacing : 0)) } - Chip { id: upd; visible: !!row.modelData.update; text: "Update"; fg: t.warn; bgc: t.warnBg; height: 20; anchors.verticalCenter: parent.verticalCenter } } - Text { text: row.modelData.id === "bios" ? biosHint() : ((row.modelData.id === "hdmi" && hdmiRowHint()) || (texts[row.modelData.id] && texts[row.modelData.id].hint) || row.modelData.hint); color: t.mute; font.family: t.body; font.pixelSize: 13; elide: Text.ElideRight; width: parent.width } + Chip { id: upd; visible: !!row.modelData.update || !!row.modelData["new"]; text: row.modelData.update ? "Update" : "New"; fg: row.modelData.update ? t.warn : t.good; bgc: row.modelData.update ? t.warnBg : t.goodBg; height: 20; anchors.verticalCenter: parent.verticalCenter } } + Text { text: row.modelData.id === "bios" ? biosHint() : row.modelData.selectable === false ? row.modelData.hint : ((row.modelData.id === "hdmi" && hdmiRowHint()) || (texts[row.modelData.id] && texts[row.modelData.id].hint) || row.modelData.hint); color: t.mute; font.family: t.body; font.pixelSize: 13; elide: Text.ElideRight; width: parent.width } } // Right: every control ends on the same edge Item { id: controls + opacity: row.modelData.kind === "toggle" && row.modelData.selectable === false ? 0.4 : 1 anchors.right: parent.right; anchors.rightMargin: 16; anchors.verticalCenter: parent.verticalCenter width: 180; height: 32 // choice @@ -678,7 +690,7 @@ ApplicationWindow { id: detail width: parent.width; height: parent.height - sys.height - 16; radius: 16; color: t.card readonly property var it: sel < rows.length ? rows[sel] : null - readonly property var tx: it ? (texts[it.id] || {}) : {} + readonly property var tx: it ? (it.selectable === false && unsupported[it.id] ? Object.assign({}, texts[it.id], unsupported[it.id]) : (texts[it.id] || {})) : {} // Long texts scroll instead of running out of the card; // each item starts at the top. onItChanged: detailFlick.contentY = 0 @@ -763,6 +775,7 @@ ApplicationWindow { Row { anchors.fill: parent; anchors.leftMargin: 18; spacing: 14 Chip { text: parent.parent.st[0]; fg: parent.parent.st[1]; bgc: parent.parent.st[2]; width: 84; anchors.verticalCenter: parent.verticalCenter } Text { text: (texts[parent.parent.modelData.id] || {}).label || parent.parent.modelData.id; color: t.textHi; font.family: t.body; font.pixelSize: 17; font.weight: Font.DemiBold; anchors.verticalCenter: parent.verticalCenter } + Chip { visible: !!parent.parent.modelData.isNew; text: "New"; fg: t.good; bgc: t.goodBg; height: 20; anchors.verticalCenter: parent.verticalCenter } } } } @@ -858,7 +871,7 @@ ApplicationWindow { border.width: parent.parent.st === "wait" || parent.parent.st === "run" ? 2 : 0; border.color: parent.parent.st === "run" ? t.accent : "#343f50" Text { anchors.centerIn: parent; text: parent.parent.parent.st === "ok" ? "✓" : (parent.parent.parent.st === "fail" ? "!" : ""); color: parent.parent.parent.st === "ok" ? t.good : t.bad; font.pixelSize: 13; font.weight: Font.Bold } RotationAnimator on rotation { running: parent.parent.parent.st === "run"; from: 0; to: 360; duration: 1000; loops: Animation.Infinite } } - // The step; an update shows the name with an Update badge, like the menu. + // The step; an update or new sub-option shows its badge, like the menu. Item { width: 380; height: parent.height readonly property var step: parent.parent.modelData readonly property bool update: step.action === "update" @@ -866,7 +879,7 @@ ApplicationWindow { Text { text: (({ on: "Turn on ", off: "Turn off ", again: "Re-apply ", desktop: "Boot into ", gaming: "Boot into ", check: "Download and check the ", flash: "Hand to fwupd: the " })[parent.parent.step.action] || "") + ((texts[parent.parent.step.id] || {}).label || parent.parent.step.id) color: parent.parent.parent.parent.st === "wait" ? t.faint : t.textHi; font.family: t.body; font.pixelSize: 16; font.weight: Font.DemiBold; anchors.verticalCenter: parent.verticalCenter; elide: Text.ElideRight width: Math.min(implicitWidth, parent.width - (stepUpd.visible ? stepUpd.width + parent.spacing : 0)) } - Chip { id: stepUpd; visible: parent.parent.update; text: "Update"; fg: t.warn; bgc: t.warnBg; height: 20; anchors.verticalCenter: parent.verticalCenter } } } + Chip { id: stepUpd; visible: parent.parent.update || !!parent.parent.step.isNew; text: parent.parent.update ? "Update" : "New"; fg: parent.parent.update ? t.warn : t.good; bgc: parent.parent.update ? t.warnBg : t.goodBg; height: 20; anchors.verticalCenter: parent.verticalCenter } } } Text { text: ({ wait: "Waiting", run: "Working…", ok: "Done", fail: "Problem" })[parent.parent.st]; color: parent.parent.st === "ok" ? t.good : (parent.parent.st === "fail" ? t.bad : "#b8c3d1"); font.family: t.body; font.pixelSize: 13; anchors.verticalCenter: parent.verticalCenter } } }