Compare commits

..
11 Commits
Author SHA1 Message Date
theupriser fe93dd3e67 Merge pull request #25 from theupriser/feat/fremont-poweroff
Feat/fremont poweroff
2026-09-27 12:41:38 +02:00
theupriser ef585c0db1 docs: Changelog entries per commit for 2.2.0 2026-09-27 12:36:25 +02:00
theupriser 32795bd5a5 feat(ui): Update badge on the progress screen 2026-09-27 12:35:40 +02:00
theupriser 3c8ca65902 fix(ui): Never write the sudo password to a file; Update label on the progress screen
The askpass helper reads the password from a named pipe that the app
feeds from memory each time sudo asks, instead of from a file.
2026-09-27 12:33:47 +02:00
theupriser 11a91ef85c feat: Update badge on components that get a newer version 2026-09-27 12:31:29 +02:00
theupriser 163f7908c4 feat: Feature versions are Steamify versions; Steam Machine support is 2.2.0 2026-09-27 12:27:55 +02:00
theupriser e121b06fd8 fix: Remove whatever is left of HDMI refresh boost, pin or no pin 2026-09-27 12:27:08 +02:00
theupriser 19ca966913 docs: Drop the kernel pin and HDMI refresh boost descriptions 2026-09-27 12:25:08 +02:00
theupriser 56fb9080a7 docs: Power-off fix texts cover every affected kernel, not just 7.2 2026-09-27 12:23:54 +02:00
theupriser 6df19eba3e feat: Power-off fix as its own sub-option, drop the kernel pin, feature versions
The power-off fix becomes an opt-out sub-option of Steam Machine
support. The kernel pin is no longer offered: an existing pin (and HDMI
refresh boost, which needs it) is unticked so a normal run removes it.
Components record a feature version on each successful run; installs
with an older version are ticked and updated, and new default
sub-options are ticked where their parent is on.
2026-09-27 12:20:56 +02:00
theupriser 7694c97d68 feat: Power-off fix for the Steam Machine; the kernel pin is optional
Kernels >= 7.2 keep the firmware's S4/S5 wake bit on GPIO pin 18, so the
Steam Machine starts again right after powering off. Steam Machine
support now builds a DKMS module (patches/steamify-fremont-poweroff.c)
that clears it before power-off, for every kernel. The kernel pin is no
longer ticked by default. Module sources and patches move to patches/,
embedded by the single-file build.
2026-09-27 12:13:23 +02:00
20 changed files with 500 additions and 162 deletions

No files matched your search

+17
View File
@@ -31,6 +31,23 @@ version="v$(grep -oP '^VERSION=\K.*' "$entry"), $(git describe --always --dirty
sed -e '1{/^#!/d}' -e '/^# Sourced by steamify.sh/d' "lib/$lib.sh" sed -e '1{/^#!/d}' -e '/^# Sourced by steamify.sh/d' "lib/$lib.sh"
echo echo
done done
# patches/ embedded: replaces lib/common.sh's patch_file, which reads
# them from the checkout.
echo "# ----- patches/ -----"
echo "patch_file() {"
echo ' case "$1" in'
for f in patches/*; do
[[ "$f" == patches/README.md ]] && continue
grep -q '^STEAMIFY_PATCH_EOF$' "$f" && { echo "bundle: $f contains the heredoc delimiter" >&2; exit 1; }
echo " $(basename "$f")) cat << 'STEAMIFY_PATCH_EOF'"
cat "$f"
echo "STEAMIFY_PATCH_EOF"
echo " ;;"
done
echo ' *) return 1 ;;'
echo ' esac'
echo "}"
echo
echo "# ----- steamify.sh -----" echo "# ----- steamify.sh -----"
echo "main() {" echo "main() {"
# Everything after the source loop is the entry point's body. # Everything after the source loop is the entry point's body.
+2 -2
View File
@@ -6,7 +6,7 @@
# curl -fsSL https://github.com/theupriser/steamify-cachyos/releases/latest/download/steamify.sh | bash # curl -fsSL https://github.com/theupriser/steamify-cachyos/releases/latest/download/steamify.sh | bash
# curl -fsSL https://github.com/theupriser/steamify-cachyos/releases/download/latest/steamify.sh | bash # curl -fsSL https://github.com/theupriser/steamify-cachyos/releases/download/latest/steamify.sh | bash
# Each release also has the app (v2): steamify-app.tar.gz (ui/, steamify.sh, # Each release also has the app (v2): steamify-app.tar.gz (ui/, steamify.sh,
# lib/, the icon) and its launcher, run with: # lib/, patches/, the icon) and its launcher, run with:
# curl -fsSL https://github.com/theupriser/steamify-cachyos/releases/latest/download/steamify-app.sh | bash # curl -fsSL https://github.com/theupriser/steamify-cachyos/releases/latest/download/steamify-app.sh | bash
# A push to feat/v2-ui (the app, not released yet) updates the pre-release # A push to feat/v2-ui (the app, not released yet) updates the pre-release
# v2-ui-preview instead, whose launcher gets that build: # v2-ui-preview instead, whose launcher gets that build:
@@ -36,7 +36,7 @@ jobs:
.github/tools/bundle.sh dist/steamify.sh .github/tools/bundle.sh dist/steamify.sh
# The app: sources, not the single-file build (its backend mode runs # The app: sources, not the single-file build (its backend mode runs
# steamify.sh from its own folder). # steamify.sh from its own folder).
tar -czf dist/steamify-app.tar.gz steamify.sh lib ui assets/steam-gaming-settings.svg LICENSE.md tar -czf dist/steamify-app.tar.gz steamify.sh lib patches ui assets/steam-gaming-settings.svg LICENSE.md
cp ui/steamify-app.sh dist/steamify-app.sh cp ui/steamify-app.sh dist/steamify-app.sh
- name: Check - name: Check
+27
View File
@@ -15,6 +15,10 @@ gamescope and the Plasma desktop. Primary target: the Valve Steam Machine
- `steamify.sh` - the only entry point. Pre-flight checks, - `steamify.sh` - the only entry point. Pre-flight checks,
sources `lib/*.sh`, runs the menu and applies the plan. No component sources `lib/*.sh`, runs the menu and applies the plan. No component
logic here. logic here.
- `patches/` - kernel module sources and patches the scripts build or
apply, never inline in the shell code. Read them with `patch_file
<name>` (`lib/common.sh`, from the checkout); the bundle embeds every file
in `patches/` and overrides `patch_file`. Add new ones to its README.
- `lib/*.sh` - one file per responsibility, each defining functions only - `lib/*.sh` - one file per responsibility, each defining functions only
(no top-level side effects besides constants). See the table in (no top-level side effects besides constants). See the table in
`TECHNICAL.md`. `TECHNICAL.md`.
@@ -25,6 +29,13 @@ gamescope and the Plasma desktop. Primary target: the Valve Steam Machine
`<id>_available` is checked via `component_available` (e.g. `machine` `<id>_available` is checked via `component_available` (e.g. `machine`
only on Fremont). Turn-on order is `COMPONENTS` order, turn-off reverse; 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 live in `toggle_component`.
- **Feature versions.** Set `FEATURE_VERSION[<id>]` (`lib/menu.sh`) to the new
`VERSION` whenever what `<id>_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`).
Status still comes from the system; don't add ad-hoc `<id>_repair` checks
for new changes.
- **Reversibility.** Every per-user KDE setting a component changes goes - **Reversibility.** Every per-user KDE setting a component changes goes
through `kset <component> <file> <group|group> <key> <value>` through `kset <component> <file> <group|group> <key> <value>`
(`lib/state.sh`), which records the old value once; `<id>_disable` calls (`lib/state.sh`), which records the old value once; `<id>_disable` calls
@@ -204,6 +215,22 @@ gamescope and the Plasma desktop. Primary target: the Valve Steam Machine
kernel only pairs that with a named lookup ("Port C") when the CEC driver kernel only pairs that with a named lookup ("Port C") when the CEC driver
registered first, which never happens since amdgpu loads from the registered first, which never happens since amdgpu loads from the
initramfs. Without it `/dev/cec0` exists but stays at `f.f.f.f`. initramfs. Without it `/dev/cec0` exists but stays at `f.f.f.f`.
- Power-off fix (`poweroff`, `lib/fremont-poweroff.sh`, a default sub-option
of `machine`): recent kernels (7.2 and the 6.x/7.0/7.1 updates with the
backport) keep the firmware's S4/S5 wake bit on GPIO
pin 18, so the Steam Machine boots again right after powering off
(Valve's kernel clears it at probe, not for upstream). The DKMS module
`steamify-fremont-poweroff` (`patches/steamify-fremont-poweroff.c`) clears
it in a power-off-prepare handler, built for each kernel. It made the kernel pin
unnecessary: `kpin` is only available while on (`kpin_available`), and
`detect_components` unticks it, so a normal run removes an existing pin.
`hdmi` (it needed the pin) is retired the same way: `hdmi_status` is on
while anything of it is left (saved displays, hotplug unit, EDID files,
old kernel parameter), `hdmi_available` only then, it's always unticked,
and `hdmi_disable` removes all of it. CachyOS's
`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.
- `Relogin=true` means a gamescope that fails to start is relaunched in a - `Relogin=true` means a gamescope that fails to start is relaunched in a
tight loop; keep that in mind when changing session handling. tight loop; keep that in mind when changing session handling.
+43
View File
@@ -5,6 +5,49 @@ All notable changes, per version and per commit. Versions follow
`steamify.sh`. Versions before 0.7.0 were numbered afterwards, `steamify.sh`. Versions before 0.7.0 were numbered afterwards,
one per merged pull request. one per merged pull request.
## 2.2.0 - 2026-09-27
- **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
now reads it from a named pipe the app feeds from memory each time sudo
asks. The progress screen showed "undefined" for an update.
- `11a91ef` **feat: Update badge on components that get a newer version**
- The menu (terminal and app) shows "(update)" / an Update badge after
the name of a component a normal run will update.
- `163f790` **feat: Feature versions are Steamify versions; Steam Machine support is 2.2.0**
- Each component records the Steamify version of what it set up; when a
release changes a component, installs with an older version are ticked
and updated by a normal run ("update" in the plan and the app's
review). A setup from before counts as 2.1.0, so Steam Machine support
(2.2.0) is updated on machines set up with 2.1.0. New default
sub-options (like the power-off fix) are ticked where their parent is on.
- `e121b06` **fix: Remove whatever is left of HDMI refresh boost, pin or no pin**
- HDMI refresh boost needed the pinned kernel and is retired (newer
kernels do HDMI 2.1 themselves): whatever is left of it (saved
displays, also ones not connected, its hotplug script, the old kernel
parameter) is unticked and removed by the next run.
- `19ca966` **docs: Drop the kernel pin and HDMI refresh boost descriptions**
- `56fb908` **docs: Power-off fix texts cover every affected kernel, not just 7.2**
- `6df19eb` **feat: Power-off fix as its own sub-option, drop the kernel pin, feature versions**
- The power-off fix is a sub-option of Steam Machine support, ticked
along with it (opt-out). The kernel pin is no longer offered: an
existing pin is shown unticked, so the next run removes it and brings
back CachyOS's current kernel.
- `7694c97` **feat: Power-off fix for the Steam Machine; the kernel pin is optional**
- With recent kernels (newer 6.x, 7.0 and 7.1 updates, 7.2 and probably
later ones) the Steam Machine started again right after shutting down:
they keep the wake bit the firmware sets on GPIO pin 18, which Valve's
own kernel clears in a patch that won't reach CachyOS. A small module,
built with DKMS for each installed kernel, clears it right before
power-off. Tested with CachyOS 7.2.8: stays off, and without the module
it rebooted.
- Module sources and patches live in `patches/` instead of inside the
shell code; the single-file build and the app package include them. The
Steam Machine CEC driver change is now
`patches/cros-ec-cec-single-port.patch`.
## 2.1.0 - 2026-09-26 ## 2.1.0 - 2026-09-26
- **feat(ui): Manage button for HDMI refresh boost** - **feat(ui): Manage button for HDMI refresh boost**
+5 -14
View File
@@ -43,20 +43,11 @@ What Steamify can do on a Valve Steam Machine:
TV's remote, and the TV turns on and off with the Steam Machine TV's remote, and the TV turns on and off with the Steam Machine
([details](TECHNICAL.md#hdmi-cec)). ([details](TECHNICAL.md#hdmi-cec)).
- **Steam Machine support** - the front **LED bar** works, Steam's - **Steam Machine support** - the front **LED bar** works, Steam's
**hardware settings** (fan, performance) work, and the power **hardware settings** (fan, performance) work, and the power button puts
button puts it to sleep like a console. it to sleep like a console.
- **Pin the kernel to 7.1.6-1** - on by default: with newer CachyOS kernels - **Power-off fix** - on by default: shutting down really turns the Steam
the Steam Machine reboots instead of shutting down. Untick it once CachyOS Machine off, also with the newest CachyOS kernel
fixes that. The kernel comes from its own ([details](TECHNICAL.md#power-off-fix-steam-machine)).
[release](https://github.com/theupriser/steamify-cachyos/releases/tag/kernel-7.1.6-1),
checked against CachyOS's signature
([details](TECHNICAL.md#kernel-pin-steam-machine)).
- **HDMI refresh boost** - only with the pinned kernel, never ticked by
default: gets the highest refresh rate your HDMI display runs at the
desktop resolution (e.g. 110 Hz instead of 60 on a 3440x1440 monitor).
Each step is shown and you confirm it; no answer within 15 s switches
back. For more (e.g. 175 Hz), use DisplayPort
([details](TECHNICAL.md#hdmi-refresh-boost-steam-machine)).
- **Update BIOS** - installs Valve's newest Steam Machine BIOS. Never ticked - **Update BIOS** - installs Valve's newest Steam Machine BIOS. Never ticked
by default, at your own risk, and only after two warnings by default, at your own risk, and only after two warnings
([details](TECHNICAL.md#bios-updates-steam-machine)). ([details](TECHNICAL.md#bios-updates-steam-machine)).
+45 -74
View File
@@ -208,84 +208,54 @@ upset CEC for the TV's other devices
Turning it off removes the packages again (`linuxconsole` only if the Turning it off removes the packages again (`linuxconsole` only if the
wizard installed it). wizard installed it).
## Kernel pin (Steam Machine) ## Power-off fix (Steam Machine)
With CachyOS kernels newer than 7.1.6 a Steam Machine reboots instead of With recent kernels a Steam Machine starts again right after powering off:
shutting down. The **Pin the kernel** sub-option (ticked along with Steam the newer 6.x, 7.0 and 7.1 updates and 7.2 (and probably every kernel after
Machine support) installs `linux-cachyos` and `linux-cachyos-headers` it). Linux stopped clearing the S4/S5 wake bits at probe
7.1.6-1 and adds them to `IgnorePkg` in `/etc/pacman.conf`, so updates skip (`pinctrl-amd: Don't clear S4 wake bits at probe`, in 7.2 and backported to
them. DKMS builds the LED driver for it; restart to boot it. stable kernels), and the firmware leaves
that bit set on GPIO pin 18 (`_SB.PCI0.GPP6`). Valve's own kernel
(`linux-neptune-72`) clears it at probe on Fremont, in a patch marked not
for upstream ("until the firmware is fixed"), so CachyOS and mainline won't
get it.
The packages (and their signatures, which pacman checks) are kept in The **Power-off fix** sub-option of Steam Machine support (ticked along with
`/var/cache/steamify/kernel`, so re-applying needs no download. Missing it, can be unticked) builds a small module with DKMS for every installed
files are taken from pacman's cache, else downloaded from this repo's kernel, `steamify-fremont-poweroff` (source in
`kernel-7.1.6-1` release, then `archive.cachyos.org`, then [`patches/steamify-fremont-poweroff.c`](patches/steamify-fremont-poweroff.c)).
`mirror.cachyos.org` (which only has the current kernel). Every file must It only loads on Fremont (DMI board name) and touches one register: right
match the SHA-256 in the script and have a valid CachyOS signature; a bad before power-off (a `SYS_OFF_MODE_POWER_OFF_PREPARE` handler, after the
one is deleted, so the next run downloads it again. Set `PINNED_KERNEL_URL` drivers have shut down) it clears that pin's S4/S5 wake bit. On a kernel
to a directory URL with the files to try another source first, or drop them that already clears it, it does nothing. It logs the pin's state at load:
into the kernel directory yourself. `dmesg | grep 'GPIO 18'`; `/sys/kernel/debug/gpio` shows the S4/S5 column.
Tested on a Steam Machine with `linux-cachyos-bore` 7.2.8: it stayed off
three times in a row, and rebooted right away with the module unloaded.
Unticking it removes the pin and runs `sudo pacman -Syu`, which brings the Where Steam Machine support was set up before 2.2.0, the menu ticks the
kernel back to CachyOS's current version; the files stay for next time. power-off fix as a new default sub-option (see feature versions below), so a
normal run adds it.
## HDMI refresh boost (Steam Machine) ## Feature versions (updates)
With the pinned kernel (7.1.6), HDMI displays often stay at 60 Hz. Two Each component has a feature version (`FEATURE_VERSION` in `lib/menu.sh`):
reasons: the Steamify version in which what it sets up last changed (2.1.0 for
everything that hasn't changed since). After a component is turned on
successfully, that version is recorded in `~/.local/state/cachyos-gamescope-boot/features.state`
(turning it off records `off`). Whether a component is on is always checked
on the system itself; the version only decides about updates:
- Monitors list their fast modes in an extra EDID block, announced by the - **On, recorded version older** than the current one: ticked and
HDMI Forum EEODB data block. 7.1.6 only reads the first extension block, re-applied by a normal run; the plan (and the app's review) says
so it never sees them. Newer kernels do. "update". A setup from before versions were recorded counts as 2.1.0,
- Their fastest modes need HDMI 2.1 (FRL). 7.1.6's amdgpu only does HDMI so on a machine set up with 2.1.0, Steam Machine support (2.2.0) is
2.0 (TMDS, at most 600 MHz), but a mode with the display's own shortest updated.
blanking at a slightly lower rate often fits. - **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.
The menu item (only on a Steam Machine with the pinned kernel, never Set a component's entry to the new `VERSION` whenever what its
ticked by default, run from the desktop in Konsole): `<id>_enable` sets up changes.
1. Takes the desktop resolution from KDE (`kscreen-doctor -j`) and reads the
display's complete EDID over DDC (`i2ctransfer`, segment pointer 0x30).
A live EDID left by an earlier test is cleared first.
2. Calculates the highest rate that fits: the display's TMDS limit (HDMI
Forum VSDB, capped at amdgpu's 600 MHz), its shortest blanking at that
resolution and its maximum refresh (range limits, VRR maximum). Steps:
that rate rounded down to ten, and the hundred below it as a safe option.
Rates the display already lists, or that aren't faster than what works
now, are left out.
3. Builds the EDID: all of the display's blocks, the block count and EEODB
fixed, plus a DisplayID block with the steps.
4. Loads it live (debugfs `edid_override`, `trigger_hotplug`) and switches
to each step, lowest first. Each one needs a "y" within 15 s
(`WIZARD_HDMI_CONFIRM_SECONDS` for tests); anything else switches back
and stops.
5. Saves the confirmed steps for that display: the EDID as
`/usr/lib/firmware/edid/steamify-<id>.bin`, where `<id>` is the display's
manufacturer, model, serial and date (EDID bytes 8-17), and a line in
`/etc/steamify/hdmi-edid.conf` (id, name, mode, rates). Other saved
displays are kept.
`steamify-edid.service` (at boot, before the login manager) and a udev rule
(`90-steamify-edid.rules`, every drm hotplug) run
`/usr/local/bin/steamify-edid-hotplug`. Per HDMI port it reads the connected
display's ID over DDC (the real display, even while an override is loaded)
and loads that display's saved EDID through debugfs, or resets the port to
the display's own EDID when there is none, or no display. What's loaded per
port is kept in `/run/steamify-edid`, so the hotplug the script triggers
itself doesn't loop.
The item is on when the connected display runs on its saved EDID; with
another display it's off, and ticking it sets that one up. Turning it off
removes the connected display's EDID; the unit and rule go with the last
one. In the app the item is a **Set up…** button, and **Manage** once a
display is saved: it lists every saved display, removes any of them, and
sets up the connected display when it has none. Unpinning the kernel
removes them all.
Versions before 2.1.0 used `drm.edid_firmware=` on the kernel command line
(and the initramfs), which applied to any display on that port; re-applying
saves such a setup per display and removes the parameter. Untick it before removing the
kernel pin: newer kernels read the EDID themselves and can do HDMI 2.1.
## BIOS updates (Steam Machine) ## BIOS updates (Steam Machine)
@@ -355,9 +325,10 @@ immediately, which can turn into a loop - see
| `lib/wizard-shortcut.sh` | Steamify shortcut: desktop icon and launcher entry that run the newest release | | `lib/wizard-shortcut.sh` | Steamify shortcut: desktop icon and launcher entry that run the newest release |
| `lib/bios.sh` | Update BIOS (Steam Machine, opt-in): current/newest version, double confirmation, fwupd | | `lib/bios.sh` | Update BIOS (Steam Machine, opt-in): current/newest version, double confirmation, fwupd |
| `lib/cec.sh` | HDMI-CEC: Valve's `cecd` and friends from its `holo` repository | | `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; kernel pin | | `lib/steam-machine.sh` | Steam Machine support: LED driver, LED access, steamos-manager |
| `lib/hdmi-refresh.sh` | HDMI refresh boost (Steam Machine, pinned kernel): EDID over DDC, calculated steps, live test, `drm.edid_firmware` | | `lib/fremont-poweroff.sh` | Steam Machine support: the power-off fix (DKMS module from `patches/`) |
| `.github/tools/bundle.sh` | Builds the single-file version (`dist/steamify.sh`) | | `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` | | `.github/workflows/bundle.yml` | Builds and checks it on every push; publishes it on `main` |
The single-file version is generated: on every push to `main`, GitHub The single-file version is generated: on every push to `main`, GitHub
+9 -2
View File
@@ -62,6 +62,7 @@ backend_status() {
items+=",\"kind\":\"$kind\",\"parent\":$(json_str "$parent")" items+=",\"kind\":\"$kind\",\"parent\":$(json_str "$parent")"
items+=",\"on\":$( [[ "$now" == 1 ]] && echo true || echo false)" items+=",\"on\":$( [[ "$now" == 1 ]] && echo true || echo false)"
items+=",\"wanted\":$( [[ "${WANTED[$c]:-0}" == 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+=",\"selectable\":$(component_selectable "$c" && echo true || echo false)}" items+=",\"selectable\":$(component_selectable "$c" && echo true || echo false)}"
done done
local cec="" f local cec="" f
@@ -160,6 +161,8 @@ backend_apply() {
# The same rules as the menu's toggles. # The same rules as the menu's toggles.
[[ "${WANTED[single]:-0}" == 1 ]] && WANTED[gaming]=1 [[ "${WANTED[single]:-0}" == 1 ]] && WANTED[gaming]=1
[[ "${WANTED[gaming]:-0}" == 0 ]] && { WANTED[single]=0; WANTED[boot]=0; } [[ "${WANTED[gaming]:-0}" == 0 ]] && { WANTED[single]=0; WANTED[boot]=0; }
[[ "${WANTED[poweroff]:-0}" == 1 ]] && WANTED[machine]=1
[[ "${WANTED[machine]:-0}" == 0 ]] && WANTED[poweroff]=0
[[ "${WANTED[kpin]:-0}" == 1 ]] && WANTED[machine]=1 [[ "${WANTED[kpin]:-0}" == 1 ]] && WANTED[machine]=1
[[ "${WANTED[machine]:-0}" == 0 ]] && WANTED[kpin]=0 [[ "${WANTED[machine]:-0}" == 0 ]] && WANTED[kpin]=0
[[ "${WANTED[hdmi]:-0}" == 1 ]] && { WANTED[machine]=1; WANTED[kpin]=1; } [[ "${WANTED[hdmi]:-0}" == 1 ]] && { WANTED[machine]=1; WANTED[kpin]=1; }
@@ -179,8 +182,12 @@ backend_apply() {
LOGIN_MANAGER=plasmalogin LOGIN_MANAGER=plasmalogin
[[ "${WANTED[single]}" == 1 ]] && LOGIN_MANAGER=sddm [[ "${WANTED[single]}" == 1 ]] && LOGIN_MANAGER=sddm
local -a failed=() local -a failed=()
for c in "${TO_DISABLE[@]}"; do backend_run_component "$c" disable || failed+=("$c"); done for c in "${TO_DISABLE[@]}"; do
for c in "${TO_ENABLE[@]}"; do backend_run_component "$c" enable || failed+=("$c"); done if backend_run_component "$c" disable; then feature_record "$c" disable; else failed+=("$c"); fi
done
for c in "${TO_ENABLE[@]}"; do
if backend_run_component "$c" enable; then is_action "$c" || feature_record "$c" enable; else failed+=("$c"); fi
done
[[ " ${TO_DISABLE[*]} ${TO_ENABLE[*]} " =~ \ (gaming|single|boot|kpin)\ ]] && RESTART_FOR_LOGIN=true [[ " ${TO_DISABLE[*]} ${TO_ENABLE[*]} " =~ \ (gaming|single|boot|kpin)\ ]] && RESTART_FOR_LOGIN=true
backend_event finished "\"failed\":$(json_list "${failed[@]}"),\"restart\":$(restart_needed && echo true || echo false)" backend_event finished "\"failed\":$(json_list "${failed[@]}"),\"restart\":$(restart_needed && echo true || echo false)"
} }
+4 -9
View File
@@ -60,8 +60,8 @@ cec_reload_driver() {
cec_driver_enable() { cec_driver_enable() {
detect_valve_fremont || return 0 detect_valve_fremont || return 0
local tmp k local tmp k
if ! pacman -Q dkms >/dev/null 2>&1; then if ! pacman -Q dkms patch >/dev/null 2>&1; then
sudo pacman -S --needed --noconfirm dkms || { err "Installing dkms failed."; return 1; } sudo pacman -S --needed --noconfirm dkms patch || { err "Installing dkms failed."; return 1; }
fi fi
install_kernel_headers || return 1 install_kernel_headers || return 1
tmp="$(mktemp -d)" tmp="$(mktemp -d)"
@@ -72,13 +72,8 @@ cec_driver_enable() {
err "Downloading Valve's CEC driver failed (or its checksum didn't match)." err "Downloading Valve's CEC driver failed (or its checksum didn't match)."
return 1 return 1
fi fi
# amdgpu registers its HDMI notifier without a port name, which only # So it finds amdgpu's HDMI port (see the patch for why).
# matches the driver's named lookup ("Port C") if the driver loaded patch_file cros-ec-cec-single-port.patch | patch -s -d "$tmp" -p1 ||
# first; amdgpu loads from the initramfs, so it never does. With a
# single CEC port, look it up by device alone.
sed -i 's/cec_notifier_cec_adap_register(hdmi_dev, conns\[port_num\],/cec_notifier_cec_adap_register(hdmi_dev, conns[1] ? conns[port_num] : NULL,/' \
"$tmp/cros-ec-cec.c"
grep -q 'conns\[1\] ? conns\[port_num\] : NULL' "$tmp/cros-ec-cec.c" ||
{ rm -rf "$tmp"; err "Patching Valve's CEC driver failed."; return 1; } { rm -rf "$tmp"; err "Patching Valve's CEC driver failed."; return 1; }
echo 'obj-m += cros-ec-cec.o' >"$tmp/Makefile" echo 'obj-m += cros-ec-cec.o' >"$tmp/Makefile"
printf '%s\n' "# Written by Steamify: Valve's cros_ec_cec, which knows the Steam Machine." \ printf '%s\n' "# Written by Steamify: Valve's cros_ec_cec, which knows the Steam Machine." \
+3
View File
@@ -6,6 +6,9 @@ c_reset="\033[0m"; c_bold="\033[1m"; c_green="\033[32m"; c_yellow="\033[33m"; c_
info() { echo -e "${c_cyan}[INFO]${c_reset} $*"; } info() { echo -e "${c_cyan}[INFO]${c_reset} $*"; }
ok() { echo -e "${c_green}[OK]${c_reset} $*"; } ok() { echo -e "${c_green}[OK]${c_reset} $*"; }
# patch_file <name>: a file from patches/ (module sources, patches). The
# single-file build replaces this with one that has them embedded.
patch_file() { cat "$SCRIPT_DIR/patches/$1"; }
warn() { echo -e "${c_yellow}[WARN]${c_reset} $*"; } warn() { echo -e "${c_yellow}[WARN]${c_reset} $*"; }
err() { echo -e "${c_red}[ERROR] $*${c_reset}" >&2; } err() { echo -e "${c_red}[ERROR] $*${c_reset}" >&2; }
ask_yn() { ask_yn() {
+70
View File
@@ -0,0 +1,70 @@
#!/bin/bash
# "Power-off fix" menu item (poweroff), a sub-option of Steam Machine support
# ticked along with it (opt-out): the Steam Machine powers off instead of
# booting up again. Recent kernels (7.2, and the 6.x, 7.0 and 7.1 updates
# that got the change backported) keep the S4/S5 wake bit the firmware
# leaves set on GPIO pin 18 ("pinctrl-amd: Don't clear S4 wake bits at
# probe"), so the machine starts again right after powering off. Valve's kernel clears it at probe on Fremont, in
# a patch not meant for upstream, so CachyOS won't get it. A small module
# (patches/steamify-fremont-poweroff.c) clears it right before power-off;
# DKMS builds it for each installed kernel. On a kernel that clears it
# itself it does nothing.
# Sourced by steamify.sh; not meant to be run on its own.
POWEROFF_DKMS_NAME=steamify-fremont-poweroff
POWEROFF_DKMS_VER=1
POWEROFF_DKMS_SRC="/usr/src/$POWEROFF_DKMS_NAME-$POWEROFF_DKMS_VER"
POWEROFF_MODULES_LOAD=/etc/modules-load.d/steamify-fremont-poweroff.conf
poweroff_fix_installed() { [[ -f "$POWEROFF_DKMS_SRC/dkms.conf" && -f "$POWEROFF_MODULES_LOAD" ]]; }
poweroff_fix_enable() {
detect_valve_fremont || return 0
local tmp k
if ! pacman -Q dkms >/dev/null 2>&1; then
sudo pacman -S --needed --noconfirm dkms || { err "Installing dkms failed."; return 1; }
fi
install_kernel_headers || return 1
tmp="$(mktemp -d)"
patch_file "$POWEROFF_DKMS_NAME.c" > "$tmp/$POWEROFF_DKMS_NAME.c"
echo "obj-m += $POWEROFF_DKMS_NAME.o" >"$tmp/Makefile"
printf '%s\n' "# Written by Steamify: the Steam Machine powers off instead of booting up again." \
"PACKAGE_NAME=\"$POWEROFF_DKMS_NAME\"" "PACKAGE_VERSION=\"$POWEROFF_DKMS_VER\"" \
"BUILT_MODULE_NAME[0]=\"$POWEROFF_DKMS_NAME\"" 'DEST_MODULE_LOCATION[0]="/updates/dkms"' \
'AUTOINSTALL="yes"' >"$tmp/dkms.conf"
sudo dkms remove "$POWEROFF_DKMS_NAME/$POWEROFF_DKMS_VER" --all >/dev/null 2>&1
sudo rm -rf "$POWEROFF_DKMS_SRC"
sudo install -d "$POWEROFF_DKMS_SRC" &&
sudo install -m644 "$tmp"/{"$POWEROFF_DKMS_NAME.c",Makefile,dkms.conf} "$POWEROFF_DKMS_SRC/"
rm -rf "$tmp"
sudo dkms add "$POWEROFF_DKMS_NAME/$POWEROFF_DKMS_VER" >/dev/null ||
{ err "Adding the power-off fix to DKMS failed."; return 1; }
for k in /usr/lib/modules/*/build; do
k="$(basename "$(dirname "$k")")"
info "Building the power-off fix for $k..."
sudo dkms install "$POWEROFF_DKMS_NAME/$POWEROFF_DKMS_VER" -k "$k" >/dev/null ||
warn "Building the power-off fix for $k failed; with that kernel it may start again after shutting down."
done
echo "$POWEROFF_DKMS_NAME" | sudo tee "$POWEROFF_MODULES_LOAD" >/dev/null
sudo modprobe "$POWEROFF_DKMS_NAME" 2>/dev/null
return 0
}
poweroff_fix_disable() {
sudo rm -f "$POWEROFF_MODULES_LOAD"
sudo modprobe -r "$POWEROFF_DKMS_NAME" 2>/dev/null
[[ -d "$POWEROFF_DKMS_SRC" ]] || return 0
sudo dkms remove "$POWEROFF_DKMS_NAME/$POWEROFF_DKMS_VER" --all >/dev/null 2>&1
sudo rm -rf "$POWEROFF_DKMS_SRC"
}
poweroff_status() { poweroff_fix_installed; }
poweroff_enable() {
info "Installing the power-off fix (the machine stays off after shutting down)..."
poweroff_fix_enable || { err "Installing the power-off fix failed."; return 1; }
ok "Power-off fix on: the Steam Machine stays off after shutting down."
}
poweroff_disable() {
poweroff_fix_disable
ok "Power-off fix removed; with recent kernels the Steam Machine may start again after shutting down."
}
+13 -20
View File
@@ -56,18 +56,17 @@ hdmi_cmdline_param() {
} }
hdmi_available() { hdmi_available() {
# Stays available while on, so it can be turned off after the pin is gone. # No longer offered (it needed the pinned kernel): only shown while
detect_valve_fremont || return 1 # anything of it is left, so a normal run removes it.
# Saved displays too, so they can be removed without the pin. detect_valve_fremont && hdmi_status
hdmi_status || [[ -n "$(hdmi_saved)" ]] || { pinned_kernel_installed && [[ -n "$(hdmi_connectors)" ]]; }
} }
hdmi_status() { hdmi_status() {
# On = the connected display runs on its saved EDID. Another display on # On while anything of it is left: saved displays (connected or not), the
# the port is off, so ticking it sets that one up. The command line: set # hotplug script, an EDID file, or the kernel parameter of versions
# up by an older version, re-applying moves it over. # before 2.1.0.
[[ -n "$(hdmi_active_ids)" ]] || [[ -n "$(hdmi_saved)" || -f "$HDMI_UNIT" || -n "$(hdmi_cmdline_param)" ]] ||
{ [[ -n "$(hdmi_cmdline_param)" ]] && compgen -G "$HDMI_FW_DIR/steamify-*.bin" >/dev/null; } compgen -G "$HDMI_FW_DIR/steamify-*.bin" >/dev/null
} }
hdmi_saved() { [[ -f "$HDMI_MAP" ]] && grep -v '^#' "$HDMI_MAP"; } hdmi_saved() { [[ -f "$HDMI_MAP" ]] && grep -v '^#' "$HDMI_MAP"; }
@@ -688,16 +687,10 @@ hdmi_enable() {
} }
hdmi_disable() { hdmi_disable() {
# For the connected display; other saved displays stay (the app lists # Everything, for every saved display: newer kernels read the whole EDID
# them). Without the pinned kernel none may stay: newer kernels read the # themselves, and an old one loaded at the next hotplug would be wrong.
# EDID themselves.
local id
if [[ -n "$(hdmi_cmdline_param)" ]]; then
hdmi_forget all; hdmi_remove_boot_param || return 1; state_clear hdmi
elif [[ "${WANTED[kpin]:-1}" == 0 ]] || ! pinned_kernel_installed; then
hdmi_forget all hdmi_forget all
else hdmi_remove_boot_param || return 1
for id in $(hdmi_active_ids); do hdmi_forget "$id"; done state_clear hdmi
fi ok "HDMI refresh boost removed; displays use their own EDID again."
ok "HDMI refresh boost removed for the connected display; it uses its own EDID again."
} }
+75 -12
View File
@@ -5,18 +5,30 @@
# Menu order. Components are turned on in this order and off in reverse; # Menu order. Components are turned on in this order and off in reverse;
# gaming must come first (single user builds on it). # gaming must come first (single user builds on it).
COMPONENTS=(gaming boot theme glyphs single launcher cec machine kpin hdmi bios) COMPONENTS=(gaming boot theme glyphs single launcher cec machine poweroff kpin hdmi bios)
# One-off actions rather than on/off components: never preselected, never # One-off actions rather than on/off components: never preselected, never
# re-applied, not listed as on or off. # re-applied, not listed as on or off.
ACTIONS=(bios) ACTIONS=(bios)
# Sub-options, shown indented under their parent and only while it's ticked. # Sub-options, shown indented under their parent and only while it's ticked.
declare -A PARENT=([boot]=gaming [kpin]=machine [hdmi]=machine [bios]=machine) declare -A PARENT=([boot]=gaming [poweroff]=machine [kpin]=machine [hdmi]=machine [bios]=machine)
# Never preselected on a first run: booting into the desktop is a choice, # 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 # 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, # upset other devices on the TV, even on SteamOS), except on a Steam Machine,
# which has CEC like on SteamOS. HDMI refresh boost needs someone at the # which has CEC like on SteamOS. HDMI refresh boost needs someone at the
# screen to confirm each step. # screen to confirm each step.
NO_PRESELECT=(boot cec hdmi) NO_PRESELECT=(boot cec kpin hdmi)
# Feature versions: the Steamify version in which what a component's enable
# sets up last changed; set it to the new VERSION whenever you change one.
# Each successful run records it (state "features"); a component that's on
# with an older one is ticked and re-applied by a normal run, and the app
# shows it as an update. A setup from before these were recorded counts as
# FEATURE_BASELINE.
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
[kpin]=2.1.0 [hdmi]=2.1.0
)
declare -A LABEL=( declare -A LABEL=(
[gaming]="SteamOS conversion: boot into gaming mode, Steam on the desktop" [gaming]="SteamOS conversion: boot into gaming mode, Steam on the desktop"
@@ -27,7 +39,8 @@ declare -A LABEL=(
[launcher]="Steamify shortcut: the app on the desktop, Steamify Terminal in the launcher" [launcher]="Steamify shortcut: the app on the desktop, Steamify Terminal in the launcher"
[cec]="HDMI-CEC: use Steam with the TV remote, TV on/off with the PC (experimental)" [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" [machine]="Steam Machine support: LED bar driver, hardware settings in Steam"
[kpin]="Pin the kernel to $PINNED_KERNEL_VER (fixes rebooting after shutdown)" [poweroff]="Power-off fix: the Steam Machine stays off after shutting down"
[kpin]="Pin the kernel to $PINNED_KERNEL_VER (untick for CachyOS's current kernel)"
[hdmi]="HDMI refresh boost: highest refresh your HDMI display runs" [hdmi]="HDMI refresh boost: highest refresh your HDMI display runs"
[bios]="Update BIOS" [bios]="Update BIOS"
) )
@@ -35,7 +48,8 @@ declare -A CURRENT WANTED
component_available() { component_available() {
case "$1" in case "$1" in
machine|kpin) machine_available ;; machine|poweroff) machine_available ;;
kpin) kpin_available ;;
hdmi) hdmi_available ;; hdmi) hdmi_available ;;
bios) bios_available ;; bios) bios_available ;;
esac esac
@@ -56,6 +70,42 @@ menu_visible() {
[[ -z "${PARENT[$1]:-}" || "${WANTED[${PARENT[$1]}]:-0}" == 1 ]] [[ -z "${PARENT[$1]:-}" || "${WANTED[${PARENT[$1]}]:-0}" == 1 ]]
} }
feature_record() {
# feature_record <component> <enable|disable>: after a successful run.
if [[ "$2" == enable ]]; then state_set features "$1" "${FEATURE_VERSION[$1]:-$FEATURE_BASELINE}"
else state_set features "$1" off; fi
}
feature_outdated() {
# On, but set up by an older version of that feature (compared as
# versions: 2.10.0 is newer than 2.9.0).
local have want
[[ "${CURRENT[$1]:-0}" == 1 ]] || return 1
have="$(state_get features "$1" "$FEATURE_BASELINE")"
[[ "$have" == off ]] && have="$FEATURE_BASELINE"
want="${FEATURE_VERSION[$1]:-$FEATURE_BASELINE}"
[[ "$have" != "$want" && "$(printf '%s\n' "$have" "$want" | sort -V | head -n 1)" == "$have" ]]
}
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
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 "* ]] &&
[[ -z "$(state_get features "$1")" ]]
}
component_selectable() { component_selectable() {
# Greyed out and not tickable when it has nothing to do. # Greyed out and not tickable when it has nothing to do.
case "$1" in case "$1" in
@@ -72,6 +122,17 @@ detect_components() {
done done
# HDMI-CEC set up by an older version: tick it, so a normal run fixes it. # HDMI-CEC set up by an older version: tick it, so a normal run fixes it.
component_available cec && cec_repair && WANTED[cec]=1 component_available cec && cec_repair && WANTED[cec]=1
# Updated features and new default sub-options: ticked, so a normal run
# applies them.
for c in "${COMPONENTS[@]}"; do
component_available "$c" || continue
{ feature_outdated "$c" || feature_new "$c"; } && WANTED[$c]=1
done
# The kernel pin is no longer needed (the power-off fix), and HDMI
# refresh boost needed the pin: whatever is left of either is unticked,
# so a normal run removes it.
component_available kpin && WANTED[kpin]=0
component_available hdmi && WANTED[hdmi]=0
# The terminal-only Steamify shortcut from before 2.0.1: tick it, so a # The terminal-only Steamify shortcut from before 2.0.1: tick it, so a
# normal run replaces it with the app. # normal run replaces it with the app.
launcher_repair && WANTED[launcher]=1 launcher_repair && WANTED[launcher]=1
@@ -96,8 +157,10 @@ toggle_component() {
if [[ "$c" == gaming && "${WANTED[gaming]}" == 0 ]]; then WANTED[single]=0; WANTED[boot]=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. # Where to boot to is part of the conversion, too.
if [[ "$c" == boot && "${WANTED[boot]}" == 1 ]]; then WANTED[gaming]=1; fi if [[ "$c" == boot && "${WANTED[boot]}" == 1 ]]; then WANTED[gaming]=1; fi
# The kernel pin is opt-out: ticked along with Steam Machine support. # The power-off fix is opt-out: ticked along with Steam Machine support.
if [[ "$c" == machine ]]; then WANTED[kpin]=${WANTED[machine]}; fi 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; fi
if [[ "$c" == kpin && "${WANTED[kpin]}" == 1 ]]; then WANTED[machine]=1; fi if [[ "$c" == kpin && "${WANTED[kpin]}" == 1 ]]; then WANTED[machine]=1; fi
# HDMI refresh boost and the BIOS update sit under Steam Machine support; # HDMI refresh boost and the BIOS update sit under Steam Machine support;
# the boost also needs the pinned kernel (newer kernels don't need it). # the boost also needs the pinned kernel (newer kernels don't need it).
@@ -131,9 +194,9 @@ show_menu() {
local tree=""; [[ -n "${PARENT[$c]:-}" ]] && tree=" └ " local tree=""; [[ -n "${PARENT[$c]:-}" ]] && tree=" └ "
printf " %b%-3s %-6s %-6s %s%s (not available)%b\n" "$c_dim" "$i" "$now" "$want" "$tree" "${LABEL[$c]}" "$c_reset" printf " %b%-3s %-6s %-6s %s%s (not available)%b\n" "$c_dim" "$i" "$now" "$want" "$tree" "${LABEL[$c]}" "$c_reset"
elif [[ -n "${PARENT[$c]:-}" ]]; then elif [[ -n "${PARENT[$c]:-}" ]]; then
printf " %-3s %b %-6s └ %s\n" "$i" "$now" "$want" "${LABEL[$c]}" printf " %-3s %b %-6s └ %b\n" "$i" "$now" "$want" "$(menu_label "$c")"
else else
printf " %-3s %b %-6s %s\n" "$i" "$now" "$want" "${LABEL[$c]}" printf " %-3s %b %-6s %b\n" "$i" "$now" "$want" "$(menu_label "$c")"
fi fi
done done
echo echo
@@ -266,7 +329,7 @@ plan_changes() {
component_available "$c" || continue component_available "$c" || continue
[[ "${WANTED[$c]}" == 1 ]] || continue [[ "${WANTED[$c]}" == 1 ]] || continue
if is_action "$c"; then TO_ENABLE+=("$c"); continue; fi if is_action "$c"; then TO_ENABLE+=("$c"); continue; fi
if [[ "${CURRENT[$c]}" == 0 || "$REAPPLY" == true ]] || if [[ "${CURRENT[$c]}" == 0 || "$REAPPLY" == true ]] || feature_outdated "$c" ||
[[ "$c" == gaming && "${CURRENT[single]}" != "${WANTED[single]}" ]]; then [[ "$c" == gaming && "${CURRENT[single]}" != "${WANTED[single]}" ]]; then
TO_ENABLE+=("$c") TO_ENABLE+=("$c")
fi fi
@@ -281,13 +344,13 @@ apply_changes() {
for c in "${TO_DISABLE[@]}"; do for c in "${TO_DISABLE[@]}"; do
if [[ "$c" == boot ]]; then echo; echo -e "${c_bold}Boot into: gamescope${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 else echo; echo -e "${c_bold}Turning off: ${LABEL[$c]}${c_reset}"; fi
"${c}_disable" || failed+=("$c") if "${c}_disable"; then feature_record "$c" disable; else failed+=("$c"); fi
done done
for c in "${TO_ENABLE[@]}"; do for c in "${TO_ENABLE[@]}"; do
if [[ "$c" == boot ]]; then echo; echo -e "${c_bold}Boot into: desktop${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}" 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 else echo; echo -e "${c_bold}Turning on: ${LABEL[$c]}${c_reset}"; fi
"${c}_enable" || failed+=("$c") if "${c}_enable"; then is_action "$c" || feature_record "$c" enable; else failed+=("$c"); fi
done done
FAILED=("${failed[@]}") FAILED=("${failed[@]}")
} }
+12 -8
View File
@@ -1,6 +1,7 @@
#!/bin/bash #!/bin/bash
# "Steam Machine support" menu item, only on Valve Fremont hardware: the # "Steam Machine support" menu item, only on Valve Fremont hardware: the
# front LED bar driver, LED access for Steam, and steamos-manager. # front LED bar driver, LED access for Steam and steamos-manager. Its
# sub-option "Power-off fix" is lib/fremont-poweroff.sh.
# Sourced by steamify.sh; not meant to be run on its own. # Sourced by steamify.sh; not meant to be run on its own.
detect_valve_fremont() { detect_valve_fremont() {
@@ -14,8 +15,8 @@ detect_valve_fremont() {
[[ "$vendor" == "OEM" && "$product" == "F7F" ]] [[ "$vendor" == "OEM" && "$product" == "F7F" ]]
} }
# Kernel pinned on a Steam Machine: with newer linux-cachyos releases it # Kernel pinned on a Steam Machine (optional): newer linux-cachyos releases
# reboots instead of shutting down. The packages are kept in # rebooted it instead of shutting down, which the power-off fix now handles. The packages are kept in
# PINNED_KERNEL_DIR, so re-applying (or reinstalling after an update slipped # PINNED_KERNEL_DIR, so re-applying (or reinstalling after an update slipped
# through) needs no download. # through) needs no download.
PINNED_KERNEL_VER="7.1.6-1" PINNED_KERNEL_VER="7.1.6-1"
@@ -148,7 +149,9 @@ remove_kernel_pin() {
RESTART_FOR_LOGIN=true RESTART_FOR_LOGIN=true
} }
# "Pin the kernel" menu item, a sub-option of Steam Machine support. # "Pin the kernel" menu item, a sub-option of Steam Machine support. The
# power-off fix made it unnecessary: only shown while on, to remove it.
kpin_available() { detect_valve_fremont && kpin_status; }
kpin_status() { kpin_status() {
pinned_kernel_installed && grep -Eq '^IgnorePkg\s*=.*\slinux-cachyos(\s|$)' /etc/pacman.conf pinned_kernel_installed && grep -Eq '^IgnorePkg\s*=.*\slinux-cachyos(\s|$)' /etc/pacman.conf
} }
@@ -334,13 +337,13 @@ reload_powerdevil() {
kernel_overview() { kernel_overview() {
# Per installed kernel: headers (needed to build DKMS modules), the # Per installed kernel: headers (needed to build DKMS modules), the
# in-kernel Steam controller driver, and, on a Steam Machine, whether 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 # LED driver and the power-off fix are built for it. Printed in the menu so it's easy to verify
# a kernel update or a newly added kernel got everything. # 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 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 detect_valve_fremont && command -v dkms >/dev/null 2>&1 && leds=true
local kdir k pkg headers hid led running line local kdir k pkg headers hid led off running line
local legend="> = running; controller = Steam controller driver" local legend="> = running; controller = Steam controller driver"
[[ "$leds" == true ]] && legend+=", LEDs = LED bar driver built" [[ "$leds" == true ]] && legend+=", LEDs = LED bar driver built, power-off = power-off fix built"
echo -e " ${c_bold}Kernels${c_reset} ($legend)" echo -e " ${c_bold}Kernels${c_reset} ($legend)"
for kdir in /usr/lib/modules/*/; do for kdir in /usr/lib/modules/*/; do
k="$(basename "$kdir")" k="$(basename "$kdir")"
@@ -352,7 +355,8 @@ kernel_overview() {
line="$(printf '%-23s %-18s headers %b controller %b' "$k" "$pkg" "$headers" "$hid")" line="$(printf '%-23s %-18s headers %b controller %b' "$k" "$pkg" "$headers" "$hid")"
if [[ "$leds" == true ]]; then if [[ "$leds" == true ]]; then
led="$miss"; dkms status -k "$k" leds-valve-dkms 2>/dev/null | grep -q installed && led="$mark" led="$miss"; dkms status -k "$k" leds-valve-dkms 2>/dev/null | grep -q installed && led="$mark"
line+="$(printf ' LEDs %b' "$led")" off="$miss"; dkms status -k "$k" "$POWEROFF_DKMS_NAME" 2>/dev/null | grep -q installed && off="$mark"
line+="$(printf ' LEDs %b power-off %b' "$led" "$off")"
fi fi
echo -e " ${running}${line}" echo -e " ${running}${line}"
done done
+10
View File
@@ -0,0 +1,10 @@
# Patches and extra sources
Files Steamify builds or applies, kept out of the shell code so they can be
read and reviewed on their own. `lib/*.sh` reads them with `patch_file
<name>`; the single-file build (`.github/tools/bundle.sh`) embeds them.
| File | Used by | What |
|---|---|---|
| `steamify-fremont-poweroff.c` | `lib/fremont-poweroff.sh` (Steam Machine support) | Kernel module (DKMS): clears GPIO 18's S4/S5 wake bit before power-off, so the Steam Machine stays off with recent kernels |
| `cros-ec-cec-single-port.patch` | `lib/cec.sh` (HDMI-CEC) | Makes Valve's Steam Machine CEC driver find amdgpu's HDMI port |
+21
View File
@@ -0,0 +1,21 @@
Steam Machine CEC: find amdgpu's HDMI port with a single CEC port.
amdgpu registers its HDMI notifier without a port name, which only matches
the driver's named lookup ("Port C") if the CEC driver loaded first; amdgpu
loads from the initramfs, so it never does and /dev/cec0 stays at f.f.f.f.
With a single CEC port, look the notifier up by device alone.
Applies to Valve's drivers/media/cec/platform/cros-ec/cros-ec-cec.c
(evlaV/linux-integration 10c8c880, see CEC_DRIVER_URL in lib/cec.sh).
--- a/cros-ec-cec.c
+++ b/cros-ec-cec.c
@@ -485,7 +485,7 @@
goto out_probe_adapter;
}
- port->notify = cec_notifier_cec_adap_register(hdmi_dev, conns[port_num],
+ port->notify = cec_notifier_cec_adap_register(hdmi_dev, conns[1] ? conns[port_num] : NULL,
port->adap);
if (!port->notify) {
ret = -ENOMEM;
+80
View File
@@ -0,0 +1,80 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Steam Machine (Fremont): power off instead of booting up again.
*
* Since "pinctrl-amd: Don't clear S4 wake bits at probe" (Linux 7.2, also
* backported to 6.x, 7.0 and 7.1 updates) the S4/S5 wake bit the firmware
* leaves set on GPIO pin 18 (_SB.PCI0.GPP6) stays set, and the machine starts again right after powering off.
* Valve's kernel clears that bit at probe on Fremont (not for upstream);
* this does the same right before power-off, for kernels without it.
*/
#include <linux/acpi.h>
#include <linux/bits.h>
#include <linux/dmi.h>
#include <linux/io.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/reboot.h>
#define FREMONT_GPP6_PIN 18
#define WAKE_CNTRL_OFF_S4 15
static void __iomem *pin_reg;
static struct sys_off_handler *handler;
static int fremont_poweroff_prepare(struct sys_off_data *data)
{
u32 v = readl(pin_reg);
if (v & BIT(WAKE_CNTRL_OFF_S4))
writel(v & ~BIT(WAKE_CNTRL_OFF_S4), pin_reg);
return NOTIFY_DONE;
}
static int __init fremont_poweroff_init(void)
{
struct acpi_device *adev;
struct device *dev;
struct resource *res;
u32 v;
if (!dmi_match(DMI_BOARD_NAME, "Fremont"))
return -ENODEV;
adev = acpi_dev_get_first_match_dev("AMDI0030", NULL, -1);
if (!adev)
return -ENODEV;
dev = bus_find_device_by_acpi_dev(&platform_bus_type, adev);
acpi_dev_put(adev);
if (!dev)
return -ENODEV;
res = platform_get_resource(to_platform_device(dev), IORESOURCE_MEM, 0);
put_device(dev);
if (!res || resource_size(res) < (FREMONT_GPP6_PIN + 1) * 4)
return -ENODEV;
/* pinctrl-amd owns the region; only this one register is touched. */
pin_reg = ioremap(res->start + FREMONT_GPP6_PIN * 4, 4);
if (!pin_reg)
return -ENOMEM;
v = readl(pin_reg);
pr_info("GPIO %d register 0x%08x, S4/S5 wake %s\n", FREMONT_GPP6_PIN, v,
v & BIT(WAKE_CNTRL_OFF_S4) ? "set (cleared at power-off)" : "clear");
handler = register_sys_off_handler(SYS_OFF_MODE_POWER_OFF_PREPARE,
SYS_OFF_PRIO_DEFAULT, fremont_poweroff_prepare, NULL);
if (IS_ERR(handler)) {
iounmap(pin_reg);
return PTR_ERR(handler);
}
return 0;
}
static void __exit fremont_poweroff_exit(void)
{
unregister_sys_off_handler(handler);
iounmap(pin_reg);
}
module_init(fremont_poweroff_init);
module_exit(fremont_poweroff_exit);
MODULE_DESCRIPTION("Steam Machine: clear GPIO 18's S4 wake bit before power-off");
MODULE_LICENSE("GPL");
MODULE_ALIAS("dmi:*:rnFremont:*");
+3 -2
View File
@@ -15,11 +15,11 @@
set -uo pipefail set -uo pipefail
# Release version, see CHANGELOG.md. # Release version, see CHANGELOG.md.
VERSION=2.1.0 VERSION=2.2.0
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
for lib in common state packages login-manager single-user steam-desktop steam-machine 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 hdmi-refresh cec boot-session vapor-theme steamos-extras bios desktop-shortcut wizard-shortcut menu backend; do
# shellcheck source=/dev/null # shellcheck source=/dev/null
source "$SCRIPT_DIR/lib/$lib.sh" source "$SCRIPT_DIR/lib/$lib.sh"
done done
@@ -139,6 +139,7 @@ while true; do
for c in "${TO_ENABLE[@]}"; do for c in "${TO_ENABLE[@]}"; do
if [[ "$c" == boot ]]; then echo " - boot into: desktop (from the next boot)" 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 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]}"; else echo " - turn on: ${LABEL[$c]}"; fi
done done
ask_yn "Go ahead?" y || { info "Nothing changed."; continue; } ask_yn "Go ahead?" y || { info "Nothing changed."; continue; }
+26 -10
View File
@@ -69,8 +69,11 @@ ApplicationWindow {
machine: { label: "Steam Machine support", hint: "LED bar, fan and performance settings in Steam", machine: { label: "Steam Machine support", hint: "LED bar, fan and performance settings in Steam",
body: "The front LED bar works, Steam's hardware settings work, and the power button puts it to sleep like a console.", body: "The front LED bar works, Steam's hardware settings work, and the power button puts it to sleep like a console.",
changes: ["leds-valve driver for every kernel (DKMS)", "steamos-manager for Steam's settings", "Console-like power handling"] }, changes: ["leds-valve driver for every kernel (DKMS)", "steamos-manager for Steam's settings", "Console-like power handling"] },
kpin: { label: "Pin the kernel", hint: "Fixes rebooting after shutdown", poweroff: { label: "Power-off fix", hint: "Stays off after shutting down",
body: "Newer CachyOS kernels make the Steam Machine reboot instead of shutting down. Untick once CachyOS fixes that.", 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"] },
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"] }, changes: ["linux-cachyos from Steamify's release (signature checked)", "Kept in /var/cache/steamify/kernel", "Added to IgnorePkg"] },
hdmi: { label: "HDMI refresh boost", hint: "Higher refresh rates over HDMI", hdmi: { label: "HDMI refresh boost", hint: "Higher refresh rates over HDMI",
body: "The pinned kernel keeps many HDMI displays at 60 Hz. Turning this on shows which refresh rates your display can run at the desktop resolution; you pick them, and each one is tried for 15 seconds so you can check the picture before it's installed.", body: "The pinned kernel keeps many HDMI displays at 60 Hz. Turning this on shows which refresh rates your display can run at the desktop resolution; you pick them, and each one is tried for 15 seconds so you can check the picture before it's installed.",
@@ -247,7 +250,9 @@ ApplicationWindow {
if (id === "hdmi" && !hdmiChoice && hdmiSaved.length) { openHdmiList(); return; } if (id === "hdmi" && !hdmiChoice && hdmiSaved.length) { openHdmiList(); return; }
if (id === "hdmi" && w.hdmi && !nowOn("hdmi") && !hdmiChoice) { startHdmi(); return; } if (id === "hdmi" && w.hdmi && !nowOn("hdmi") && !hdmiChoice) { startHdmi(); return; }
if (id === "hdmi" && !w.hdmi) hdmiChoice = ""; if (id === "hdmi" && !w.hdmi) hdmiChoice = "";
if (id === "machine") w.kpin = w.machine; if (id === "machine") w.poweroff = w.machine;
if (id === "poweroff" && w.poweroff) w.machine = true;
if (id === "machine" && !w.machine) w.kpin = false;
if (id === "kpin" && w.kpin) w.machine = true; if (id === "kpin" && w.kpin) w.machine = true;
if (id === "hdmi" && w.hdmi) { w.machine = true; w.kpin = true; } if (id === "hdmi" && w.hdmi) { w.machine = true; w.kpin = true; }
if (!w.machine || !w.kpin) w.hdmi = false; if (!w.machine || !w.kpin) w.hdmi = false;
@@ -261,7 +266,7 @@ ApplicationWindow {
if (it.parent && !want[it.parent]) { if (it.on) p.push({ id: it.id, action: "off" }); 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" });
else if (!want[it.id] && it.on) p.push({ id: it.id, action: "off" }); else if (!want[it.id] && it.on) p.push({ id: it.id, action: "off" });
else if (want[it.id] && reapply) p.push({ id: it.id, action: "again" }); else if (want[it.id] && (reapply || it.update)) p.push({ id: it.id, action: reapply ? "again" : "update" });
} }
var bootNow = nowOn("boot") ? "desktop" : "gamescope"; var bootNow = nowOn("boot") ? "desktop" : "gamescope";
if (want.gaming && boot !== bootNow) p.push({ id: "boot", action: boot === "desktop" ? "desktop" : "gaming" }); if (want.gaming && boot !== bootNow) p.push({ id: "boot", action: boot === "desktop" ? "desktop" : "gaming" });
@@ -622,7 +627,11 @@ ApplicationWindow {
x: row.modelData.parent ? 46 : 16 x: row.modelData.parent ? 46 : 16
width: controls.x - x - 16 width: controls.x - x - 16
opacity: row.modelData.kind === "action" && !(bios && bios.selectable) ? 0.6 : 1 opacity: row.modelData.kind === "action" && !(bios && bios.selectable) ? 0.6 : 1
Text { text: label(row.modelData); color: t.textHi; font.family: t.body; font.pixelSize: 17; font.weight: Font.DemiBold; elide: Text.ElideRight; width: parent.width } // The name, with an Update badge when a newer version of it will be applied.
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 } 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 }
} }
// Right: every control ends on the same edge // Right: every control ends on the same edge
@@ -748,7 +757,7 @@ ApplicationWindow {
model: plan model: plan
ScrollBar.vertical: ScrollBar { policy: ScrollBar.AsNeeded } ScrollBar.vertical: ScrollBar { policy: ScrollBar.AsNeeded }
delegate: Rectangle { required property var modelData; width: 740; height: 56; radius: 12; color: t.card delegate: Rectangle { required property var modelData; width: 740; height: 56; radius: 12; color: t.card
readonly property var st: ({ on: ["Turn on", t.good, t.goodBg], off: ["Turn off", t.bad, t.badBg], again: ["Re-apply", "#7cc4ff", "#1b2b40"], readonly property var st: ({ on: ["Turn on", t.good, t.goodBg], off: ["Turn off", t.bad, t.badBg], again: ["Re-apply", "#7cc4ff", "#1b2b40"], update: ["Update", t.warn, t.warnBg],
desktop: ["Desktop", "#7cc4ff", "#1b2b40"], gaming: ["Gaming", "#7cc4ff", "#1b2b40"], desktop: ["Desktop", "#7cc4ff", "#1b2b40"], gaming: ["Gaming", "#7cc4ff", "#1b2b40"],
check: ["Check", t.warn, t.warnBg], flash: ["Flash", t.bad, t.badBg] })[modelData.action] || ["", t.text, t.card] check: ["Check", t.warn, t.warnBg], flash: ["Flash", t.bad, t.badBg] })[modelData.action] || ["", t.text, t.card]
Row { anchors.fill: parent; anchors.leftMargin: 18; spacing: 14 Row { anchors.fill: parent; anchors.leftMargin: 18; spacing: 14
@@ -761,10 +770,10 @@ ApplicationWindow {
Text { anchors.verticalCenter: parent.verticalCenter; x: 18; text: "Your password is asked once. Changes to how the PC starts need a restart."; color: t.soft; font.family: t.body; font.pixelSize: 14 } } Text { anchors.verticalCenter: parent.verticalCenter; x: 18; text: "Your password is asked once. Changes to how the PC starts need a restart."; color: t.soft; font.family: t.body; font.pixelSize: 14 } }
} }
Rectangle { Rectangle {
x: parent.width - 468; y: 32; width: 428; height: 210; radius: 16; color: t.card x: parent.width - 468; y: 32; width: 428; height: 250; radius: 16; color: t.card
Column { anchors.fill: parent; anchors.margins: 24; spacing: 12 Column { anchors.fill: parent; anchors.margins: 24; spacing: 12
Text { text: "SUMMARY"; color: t.faint; font.family: t.body; font.pixelSize: 12; font.weight: Font.DemiBold; font.letterSpacing: 0.8 } Text { text: "SUMMARY"; color: t.faint; font.family: t.body; font.pixelSize: 12; font.weight: Font.DemiBold; font.letterSpacing: 0.8 }
Repeater { model: [["Turn on", "on"], ["Re-apply", "again"], ["Turn off", "off"]] Repeater { model: [["Turn on", "on"], ["Update", "update"], ["Re-apply", "again"], ["Turn off", "off"]]
Row { required property var modelData; width: 380 Row { required property var modelData; width: 380
Text { text: parent.modelData[0]; color: t.soft; font.family: t.body; font.pixelSize: 15; width: 300 } Text { text: parent.modelData[0]; color: t.soft; font.family: t.body; font.pixelSize: 15; width: 300 }
Text { text: plan.filter(function (p) { return p.action === parent.modelData[1]; }).length; color: t.text; font.family: t.mono; font.pixelSize: 15; width: 80; horizontalAlignment: Text.AlignRight } } } Text { text: plan.filter(function (p) { return p.action === parent.modelData[1]; }).length; color: t.text; font.family: t.mono; font.pixelSize: 15; width: 80; horizontalAlignment: Text.AlignRight } } }
@@ -849,8 +858,15 @@ ApplicationWindow {
border.width: parent.parent.st === "wait" || parent.parent.st === "run" ? 2 : 0; border.color: parent.parent.st === "run" ? t.accent : "#343f50" 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 } 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 } } RotationAnimator on rotation { running: parent.parent.parent.st === "run"; from: 0; to: 360; duration: 1000; loops: Animation.Infinite } }
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.modelData.action] + ((texts[parent.parent.modelData.id] || {}).label || parent.parent.modelData.id) // The step; an update shows the name with an Update badge, like the menu.
color: parent.parent.st === "wait" ? t.faint : t.textHi; font.family: t.body; font.pixelSize: 16; font.weight: Font.DemiBold; anchors.verticalCenter: parent.verticalCenter; width: 380; elide: Text.ElideRight } Item { width: 380; height: parent.height
readonly property var step: parent.parent.modelData
readonly property bool update: step.action === "update"
Row { anchors.verticalCenter: parent.verticalCenter; width: parent.width; spacing: 10
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 } } }
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 } 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 }
} }
} }
+1 -1
View File
@@ -2,7 +2,7 @@
# Steamify CachyOS - get and start the app (v2), for `curl | bash`: # Steamify CachyOS - get and start the app (v2), for `curl | bash`:
# curl -fsSL https://github.com/theupriser/steamify-cachyos/releases/latest/download/steamify-app.sh | bash # curl -fsSL https://github.com/theupriser/steamify-cachyos/releases/latest/download/steamify-app.sh | bash
# Downloads the newest release's app package (steamify-app.tar.gz: the app, # Downloads the newest release's app package (steamify-app.tar.gz: the app,
# steamify.sh and lib/) to ~/.local/share/steamify/app, installs PySide6 if # steamify.sh, lib/ and patches/) to ~/.local/share/steamify/app, installs PySide6 if
# it's missing, adds a launcher entry, and starts the app. # it's missing, adds a launcher entry, and starts the app.
# STEAMIFY_RELEASE=<tag> takes that release instead of the newest (the # STEAMIFY_RELEASE=<tag> takes that release instead of the newest (the
# v2-ui-preview build's copy of this script sets it). STEAMIFY_BRANCH=<branch> # v2-ui-preview build's copy of this script sets it). STEAMIFY_BRANCH=<branch>
+34 -8
View File
@@ -197,19 +197,19 @@ class Backend(QObject):
p.start() p.start()
def _make_askpass(self, password): def _make_askpass(self, password):
"""A private folder with the password, a helper that prints it for """A private folder with a helper that `sudo -A` runs for the
`sudo -A`, and a `sudo` that always uses the helper (makepkg and password, and a `sudo` that always uses it (makepkg and yay call
yay call plain sudo).""" plain sudo). The password is never written to a file: the helper
reads it from a named pipe, and the app writes it into the pipe from
memory each time sudo asks, until _drop_askpass."""
base = os.environ.get("XDG_RUNTIME_DIR") or tempfile.gettempdir() base = os.environ.get("XDG_RUNTIME_DIR") or tempfile.gettempdir()
d = tempfile.mkdtemp(prefix="steamify-", dir=base) d = tempfile.mkdtemp(prefix="steamify-", dir=base)
os.chmod(d, stat.S_IRWXU) os.chmod(d, stat.S_IRWXU)
secret = os.path.join(d, "secret") pipe = os.path.join(d, "pipe")
fd = os.open(secret, os.O_WRONLY | os.O_CREAT | os.O_EXCL, 0o600) os.mkfifo(pipe, 0o600)
with os.fdopen(fd, "w") as f:
f.write(password + "\n")
askpass = os.path.join(d, "askpass") askpass = os.path.join(d, "askpass")
with open(askpass, "w") as f: with open(askpass, "w") as f:
f.write("#!/bin/sh\nexec cat %s\n" % json.dumps(secret)) f.write("#!/bin/sh\nexec cat %s\n" % json.dumps(pipe))
os.chmod(askpass, 0o700) os.chmod(askpass, 0o700)
bindir = os.path.join(d, "bin") bindir = os.path.join(d, "bin")
os.mkdir(bindir) os.mkdir(bindir)
@@ -217,9 +217,35 @@ class Backend(QObject):
with open(wrapper, "w") as f: with open(wrapper, "w") as f:
f.write("#!/bin/sh\nexec /usr/bin/sudo -A \"$@\"\n") f.write("#!/bin/sh\nexec /usr/bin/sudo -A \"$@\"\n")
os.chmod(wrapper, 0o700) os.chmod(wrapper, 0o700)
stop = threading.Event()
data = (password + "\n").encode()
def serve():
while not stop.is_set():
try:
fd = os.open(pipe, os.O_WRONLY) # waits for a reader
except OSError:
return
try:
if not stop.is_set():
os.write(fd, data)
except OSError:
pass
finally:
os.close(fd)
threading.Thread(target=serve, daemon=True).start()
self._askpass_stop = (stop, pipe)
return d, askpass, bindir return d, askpass, bindir
def _drop_askpass(self): def _drop_askpass(self):
if getattr(self, "_askpass_stop", None):
stop, pipe = self._askpass_stop
self._askpass_stop = None
stop.set()
try: # wakes the writer waiting for a reader, which then ends
os.close(os.open(pipe, os.O_RDONLY | os.O_NONBLOCK))
except OSError:
pass
if self._secret_dir: if self._secret_dir:
shutil.rmtree(self._secret_dir, ignore_errors=True) shutil.rmtree(self._secret_dir, ignore_errors=True)
self._secret_dir = None self._secret_dir = None