mirror of
https://github.com/theupriser/steamify-cachyos.git
synced 2026-10-03 17:41:58 +02:00
Merge pull request #25 from theupriser/feat/fremont-poweroff
Feat/fremont poweroff
This commit is contained in:
20 files changed
+500
-162
No files matched your search
@@ -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"
|
||||
echo
|
||||
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 "main() {"
|
||||
# Everything after the source loop is the entry point's body.
|
||||
|
||||
@@ -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/download/latest/steamify.sh | bash
|
||||
# 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
|
||||
# A push to feat/v2-ui (the app, not released yet) updates the pre-release
|
||||
# v2-ui-preview instead, whose launcher gets that build:
|
||||
@@ -36,7 +36,7 @@ jobs:
|
||||
.github/tools/bundle.sh dist/steamify.sh
|
||||
# The app: sources, not the single-file build (its backend mode runs
|
||||
# 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
|
||||
|
||||
- name: Check
|
||||
|
||||
@@ -15,6 +15,10 @@ gamescope and the Plasma desktop. Primary target: the Valve Steam Machine
|
||||
- `steamify.sh` - the only entry point. Pre-flight checks,
|
||||
sources `lib/*.sh`, runs the menu and applies the plan. No component
|
||||
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
|
||||
(no top-level side effects besides constants). See the table in
|
||||
`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`
|
||||
only on Fremont). Turn-on order is `COMPONENTS` order, turn-off reverse;
|
||||
`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
|
||||
through `kset <component> <file> <group|group> <key> <value>`
|
||||
(`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
|
||||
registered first, which never happens since amdgpu loads from the
|
||||
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
|
||||
tight loop; keep that in mind when changing session handling.
|
||||
|
||||
|
||||
@@ -5,6 +5,49 @@ 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.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
|
||||
|
||||
- **feat(ui): Manage button for HDMI refresh boost**
|
||||
|
||||
@@ -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
|
||||
([details](TECHNICAL.md#hdmi-cec)).
|
||||
- **Steam Machine support** - the front **LED bar** works, Steam's
|
||||
**hardware settings** (fan, performance) work, and the power
|
||||
button puts it to sleep like a console.
|
||||
- **Pin the kernel to 7.1.6-1** - on by default: with newer CachyOS kernels
|
||||
the Steam Machine reboots instead of shutting down. Untick it once CachyOS
|
||||
fixes that. The kernel comes from its own
|
||||
[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)).
|
||||
**hardware settings** (fan, performance) work, and the power button puts
|
||||
it to sleep like a console.
|
||||
- **Power-off fix** - on by default: shutting down really turns the Steam
|
||||
Machine off, also with the newest CachyOS kernel
|
||||
([details](TECHNICAL.md#power-off-fix-steam-machine)).
|
||||
- **Update BIOS** - installs Valve's newest Steam Machine BIOS. Never ticked
|
||||
by default, at your own risk, and only after two warnings
|
||||
([details](TECHNICAL.md#bios-updates-steam-machine)).
|
||||
|
||||
+45
-74
@@ -208,84 +208,54 @@ upset CEC for the TV's other devices
|
||||
Turning it off removes the packages again (`linuxconsole` only if the
|
||||
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
|
||||
shutting down. The **Pin the kernel** sub-option (ticked along with Steam
|
||||
Machine support) installs `linux-cachyos` and `linux-cachyos-headers`
|
||||
7.1.6-1 and adds them to `IgnorePkg` in `/etc/pacman.conf`, so updates skip
|
||||
them. DKMS builds the LED driver for it; restart to boot it.
|
||||
With recent kernels a Steam Machine starts again right after powering off:
|
||||
the newer 6.x, 7.0 and 7.1 updates and 7.2 (and probably every kernel after
|
||||
it). Linux stopped clearing the S4/S5 wake bits at probe
|
||||
(`pinctrl-amd: Don't clear S4 wake bits at probe`, in 7.2 and backported to
|
||||
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
|
||||
`/var/cache/steamify/kernel`, so re-applying needs no download. Missing
|
||||
files are taken from pacman's cache, else downloaded from this repo's
|
||||
`kernel-7.1.6-1` release, then `archive.cachyos.org`, then
|
||||
`mirror.cachyos.org` (which only has the current kernel). Every file must
|
||||
match the SHA-256 in the script and have a valid CachyOS signature; a bad
|
||||
one is deleted, so the next run downloads it again. Set `PINNED_KERNEL_URL`
|
||||
to a directory URL with the files to try another source first, or drop them
|
||||
into the kernel directory yourself.
|
||||
The **Power-off fix** sub-option of Steam Machine support (ticked along with
|
||||
it, can be unticked) builds a small module with DKMS for every installed
|
||||
kernel, `steamify-fremont-poweroff` (source in
|
||||
[`patches/steamify-fremont-poweroff.c`](patches/steamify-fremont-poweroff.c)).
|
||||
It only loads on Fremont (DMI board name) and touches one register: right
|
||||
before power-off (a `SYS_OFF_MODE_POWER_OFF_PREPARE` handler, after the
|
||||
drivers have shut down) it clears that pin's S4/S5 wake bit. On a kernel
|
||||
that already clears it, it does nothing. It logs the pin's state at load:
|
||||
`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
|
||||
kernel back to CachyOS's current version; the files stay for next time.
|
||||
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.
|
||||
|
||||
## HDMI refresh boost (Steam Machine)
|
||||
## Feature versions (updates)
|
||||
|
||||
With the pinned kernel (7.1.6), HDMI displays often stay at 60 Hz. Two
|
||||
reasons:
|
||||
Each component has a feature version (`FEATURE_VERSION` in `lib/menu.sh`):
|
||||
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
|
||||
HDMI Forum EEODB data block. 7.1.6 only reads the first extension block,
|
||||
so it never sees them. Newer kernels do.
|
||||
- Their fastest modes need HDMI 2.1 (FRL). 7.1.6's amdgpu only does HDMI
|
||||
2.0 (TMDS, at most 600 MHz), but a mode with the display's own shortest
|
||||
blanking at a slightly lower rate often fits.
|
||||
- **On, recorded version older** than the current one: ticked and
|
||||
re-applied by a normal run; the plan (and the app's review) says
|
||||
"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.
|
||||
|
||||
The menu item (only on a Steam Machine with the pinned kernel, never
|
||||
ticked by default, run from the desktop in Konsole):
|
||||
|
||||
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.
|
||||
Set a component's entry to the new `VERSION` whenever what its
|
||||
`<id>_enable` sets up changes.
|
||||
|
||||
## 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/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/steam-machine.sh` | Steam Machine support: LED driver, LED access, steamos-manager; kernel pin |
|
||||
| `lib/hdmi-refresh.sh` | HDMI refresh boost (Steam Machine, pinned kernel): EDID over DDC, calculated steps, live test, `drm.edid_firmware` |
|
||||
| `.github/tools/bundle.sh` | Builds the single-file version (`dist/steamify.sh`) |
|
||||
| `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/`) |
|
||||
| `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` |
|
||||
|
||||
The single-file version is generated: on every push to `main`, GitHub
|
||||
|
||||
+9
-2
@@ -62,6 +62,7 @@ backend_status() {
|
||||
items+=",\"kind\":\"$kind\",\"parent\":$(json_str "$parent")"
|
||||
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+=",\"selectable\":$(component_selectable "$c" && echo true || echo false)}"
|
||||
done
|
||||
local cec="" f
|
||||
@@ -160,6 +161,8 @@ backend_apply() {
|
||||
# The same rules as the menu's toggles.
|
||||
[[ "${WANTED[single]:-0}" == 1 ]] && WANTED[gaming]=1
|
||||
[[ "${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[machine]:-0}" == 0 ]] && WANTED[kpin]=0
|
||||
[[ "${WANTED[hdmi]:-0}" == 1 ]] && { WANTED[machine]=1; WANTED[kpin]=1; }
|
||||
@@ -179,8 +182,12 @@ backend_apply() {
|
||||
LOGIN_MANAGER=plasmalogin
|
||||
[[ "${WANTED[single]}" == 1 ]] && LOGIN_MANAGER=sddm
|
||||
local -a failed=()
|
||||
for c in "${TO_DISABLE[@]}"; do backend_run_component "$c" disable || failed+=("$c"); done
|
||||
for c in "${TO_ENABLE[@]}"; do backend_run_component "$c" enable || failed+=("$c"); done
|
||||
for c in "${TO_DISABLE[@]}"; do
|
||||
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
|
||||
backend_event finished "\"failed\":$(json_list "${failed[@]}"),\"restart\":$(restart_needed && echo true || echo false)"
|
||||
}
|
||||
|
||||
+4
-9
@@ -60,8 +60,8 @@ cec_reload_driver() {
|
||||
cec_driver_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; }
|
||||
if ! pacman -Q dkms patch >/dev/null 2>&1; then
|
||||
sudo pacman -S --needed --noconfirm dkms patch || { err "Installing dkms failed."; return 1; }
|
||||
fi
|
||||
install_kernel_headers || return 1
|
||||
tmp="$(mktemp -d)"
|
||||
@@ -72,13 +72,8 @@ cec_driver_enable() {
|
||||
err "Downloading Valve's CEC driver failed (or its checksum didn't match)."
|
||||
return 1
|
||||
fi
|
||||
# amdgpu registers its HDMI notifier without a port name, which only
|
||||
# matches the driver's named lookup ("Port C") if the driver loaded
|
||||
# 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" ||
|
||||
# So it finds amdgpu's HDMI port (see the patch for why).
|
||||
patch_file cros-ec-cec-single-port.patch | patch -s -d "$tmp" -p1 ||
|
||||
{ rm -rf "$tmp"; err "Patching Valve's CEC driver failed."; return 1; }
|
||||
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." \
|
||||
|
||||
@@ -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} $*"; }
|
||||
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} $*"; }
|
||||
err() { echo -e "${c_red}[ERROR] $*${c_reset}" >&2; }
|
||||
ask_yn() {
|
||||
|
||||
@@ -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
@@ -56,18 +56,17 @@ hdmi_cmdline_param() {
|
||||
}
|
||||
|
||||
hdmi_available() {
|
||||
# Stays available while on, so it can be turned off after the pin is gone.
|
||||
detect_valve_fremont || return 1
|
||||
# Saved displays too, so they can be removed without the pin.
|
||||
hdmi_status || [[ -n "$(hdmi_saved)" ]] || { pinned_kernel_installed && [[ -n "$(hdmi_connectors)" ]]; }
|
||||
# No longer offered (it needed the pinned kernel): only shown while
|
||||
# anything of it is left, so a normal run removes it.
|
||||
detect_valve_fremont && hdmi_status
|
||||
}
|
||||
|
||||
hdmi_status() {
|
||||
# On = the connected display runs on its saved EDID. Another display on
|
||||
# the port is off, so ticking it sets that one up. The command line: set
|
||||
# up by an older version, re-applying moves it over.
|
||||
[[ -n "$(hdmi_active_ids)" ]] ||
|
||||
{ [[ -n "$(hdmi_cmdline_param)" ]] && compgen -G "$HDMI_FW_DIR/steamify-*.bin" >/dev/null; }
|
||||
# On while anything of it is left: saved displays (connected or not), the
|
||||
# hotplug script, an EDID file, or the kernel parameter of versions
|
||||
# before 2.1.0.
|
||||
[[ -n "$(hdmi_saved)" || -f "$HDMI_UNIT" || -n "$(hdmi_cmdline_param)" ]] ||
|
||||
compgen -G "$HDMI_FW_DIR/steamify-*.bin" >/dev/null
|
||||
}
|
||||
|
||||
hdmi_saved() { [[ -f "$HDMI_MAP" ]] && grep -v '^#' "$HDMI_MAP"; }
|
||||
@@ -688,16 +687,10 @@ hdmi_enable() {
|
||||
}
|
||||
|
||||
hdmi_disable() {
|
||||
# For the connected display; other saved displays stay (the app lists
|
||||
# them). Without the pinned kernel none may stay: newer kernels read the
|
||||
# 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
|
||||
# Everything, for every saved display: newer kernels read the whole EDID
|
||||
# themselves, and an old one loaded at the next hotplug would be wrong.
|
||||
hdmi_forget all
|
||||
else
|
||||
for id in $(hdmi_active_ids); do hdmi_forget "$id"; done
|
||||
fi
|
||||
ok "HDMI refresh boost removed for the connected display; it uses its own EDID again."
|
||||
hdmi_remove_boot_param || return 1
|
||||
state_clear hdmi
|
||||
ok "HDMI refresh boost removed; displays use their own EDID again."
|
||||
}
|
||||
+75
-12
@@ -5,18 +5,30 @@
|
||||
|
||||
# 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 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
|
||||
# re-applied, not listed as on or off.
|
||||
ACTIONS=(bios)
|
||||
# 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,
|
||||
# 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,
|
||||
# which has CEC like on SteamOS. HDMI refresh boost needs someone at the
|
||||
# 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=(
|
||||
[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"
|
||||
[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"
|
||||
[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"
|
||||
[bios]="Update BIOS"
|
||||
)
|
||||
@@ -35,7 +48,8 @@ declare -A CURRENT WANTED
|
||||
|
||||
component_available() {
|
||||
case "$1" in
|
||||
machine|kpin) machine_available ;;
|
||||
machine|poweroff) machine_available ;;
|
||||
kpin) kpin_available ;;
|
||||
hdmi) hdmi_available ;;
|
||||
bios) bios_available ;;
|
||||
esac
|
||||
@@ -56,6 +70,42 @@ menu_visible() {
|
||||
[[ -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() {
|
||||
# Greyed out and not tickable when it has nothing to do.
|
||||
case "$1" in
|
||||
@@ -72,6 +122,17 @@ detect_components() {
|
||||
done
|
||||
# HDMI-CEC set up by an older version: tick it, so a normal run fixes it.
|
||||
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
|
||||
# normal run replaces it with the app.
|
||||
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
|
||||
# Where to boot to is part of the conversion, too.
|
||||
if [[ "$c" == boot && "${WANTED[boot]}" == 1 ]]; then WANTED[gaming]=1; fi
|
||||
# The kernel pin is opt-out: ticked along with Steam Machine support.
|
||||
if [[ "$c" == machine ]]; then WANTED[kpin]=${WANTED[machine]}; fi
|
||||
# The power-off fix is opt-out: ticked along with Steam Machine support.
|
||||
if [[ "$c" == machine ]]; then WANTED[poweroff]=${WANTED[machine]}; fi
|
||||
if [[ "$c" == poweroff && "${WANTED[poweroff]}" == 1 ]]; then WANTED[machine]=1; fi
|
||||
if [[ "$c" == machine && "${WANTED[machine]}" == 0 ]]; then WANTED[kpin]=0; fi
|
||||
if [[ "$c" == kpin && "${WANTED[kpin]}" == 1 ]]; then WANTED[machine]=1; fi
|
||||
# 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).
|
||||
@@ -131,9 +194,9 @@ show_menu() {
|
||||
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"
|
||||
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
|
||||
printf " %-3s %b %-6s %s\n" "$i" "$now" "$want" "${LABEL[$c]}"
|
||||
printf " %-3s %b %-6s %b\n" "$i" "$now" "$want" "$(menu_label "$c")"
|
||||
fi
|
||||
done
|
||||
echo
|
||||
@@ -266,7 +329,7 @@ plan_changes() {
|
||||
component_available "$c" || continue
|
||||
[[ "${WANTED[$c]}" == 1 ]] || continue
|
||||
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
|
||||
TO_ENABLE+=("$c")
|
||||
fi
|
||||
@@ -281,13 +344,13 @@ apply_changes() {
|
||||
for c in "${TO_DISABLE[@]}"; do
|
||||
if [[ "$c" == boot ]]; then echo; echo -e "${c_bold}Boot into: gamescope${c_reset}"
|
||||
else echo; echo -e "${c_bold}Turning off: ${LABEL[$c]}${c_reset}"; fi
|
||||
"${c}_disable" || failed+=("$c")
|
||||
if "${c}_disable"; then feature_record "$c" disable; else failed+=("$c"); fi
|
||||
done
|
||||
for c in "${TO_ENABLE[@]}"; do
|
||||
if [[ "$c" == boot ]]; then echo; echo -e "${c_bold}Boot into: desktop${c_reset}"
|
||||
elif is_action "$c"; then echo; echo -e "${c_bold}Running: ${LABEL[$c]%%:*}${c_reset}"
|
||||
else echo; echo -e "${c_bold}Turning on: ${LABEL[$c]}${c_reset}"; fi
|
||||
"${c}_enable" || failed+=("$c")
|
||||
if "${c}_enable"; then is_action "$c" || feature_record "$c" enable; else failed+=("$c"); fi
|
||||
done
|
||||
FAILED=("${failed[@]}")
|
||||
}
|
||||
+12
-8
@@ -1,6 +1,7 @@
|
||||
#!/bin/bash
|
||||
# "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.
|
||||
|
||||
detect_valve_fremont() {
|
||||
@@ -14,8 +15,8 @@ detect_valve_fremont() {
|
||||
[[ "$vendor" == "OEM" && "$product" == "F7F" ]]
|
||||
}
|
||||
|
||||
# Kernel pinned on a Steam Machine: with newer linux-cachyos releases it
|
||||
# reboots instead of shutting down. The packages are kept in
|
||||
# Kernel pinned on a Steam Machine (optional): newer linux-cachyos releases
|
||||
# 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
|
||||
# through) needs no download.
|
||||
PINNED_KERNEL_VER="7.1.6-1"
|
||||
@@ -148,7 +149,9 @@ remove_kernel_pin() {
|
||||
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() {
|
||||
pinned_kernel_installed && grep -Eq '^IgnorePkg\s*=.*\slinux-cachyos(\s|$)' /etc/pacman.conf
|
||||
}
|
||||
@@ -334,13 +337,13 @@ reload_powerdevil() {
|
||||
kernel_overview() {
|
||||
# Per installed kernel: headers (needed to build DKMS modules), 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.
|
||||
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
|
||||
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"
|
||||
[[ "$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)"
|
||||
for kdir in /usr/lib/modules/*/; do
|
||||
k="$(basename "$kdir")"
|
||||
@@ -352,7 +355,8 @@ kernel_overview() {
|
||||
line="$(printf '%-23s %-18s headers %b controller %b' "$k" "$pkg" "$headers" "$hid")"
|
||||
if [[ "$leds" == true ]]; then
|
||||
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
|
||||
echo -e " ${running}${line}"
|
||||
done
|
||||
|
||||
@@ -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 |
|
||||
@@ -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;
|
||||
@@ -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
@@ -15,11 +15,11 @@
|
||||
set -uo pipefail
|
||||
|
||||
# Release version, see CHANGELOG.md.
|
||||
VERSION=2.1.0
|
||||
VERSION=2.2.0
|
||||
|
||||
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
|
||||
source "$SCRIPT_DIR/lib/$lib.sh"
|
||||
done
|
||||
@@ -139,6 +139,7 @@ while true; do
|
||||
for c in "${TO_ENABLE[@]}"; 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
|
||||
done
|
||||
ask_yn "Go ahead?" y || { info "Nothing changed."; continue; }
|
||||
|
||||
+26
-10
@@ -69,8 +69,11 @@ ApplicationWindow {
|
||||
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.",
|
||||
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",
|
||||
body: "Newer CachyOS kernels make the Steam Machine reboot instead of shutting down. Untick once CachyOS fixes that.",
|
||||
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"] },
|
||||
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"] },
|
||||
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.",
|
||||
@@ -247,7 +250,9 @@ ApplicationWindow {
|
||||
if (id === "hdmi" && !hdmiChoice && hdmiSaved.length) { openHdmiList(); return; }
|
||||
if (id === "hdmi" && w.hdmi && !nowOn("hdmi") && !hdmiChoice) { startHdmi(); return; }
|
||||
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 === "hdmi" && w.hdmi) { w.machine = true; w.kpin = true; }
|
||||
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 (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] && 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";
|
||||
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
|
||||
width: controls.x - x - 16
|
||||
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 }
|
||||
}
|
||||
// Right: every control ends on the same edge
|
||||
@@ -748,7 +757,7 @@ ApplicationWindow {
|
||||
model: plan
|
||||
ScrollBar.vertical: ScrollBar { policy: ScrollBar.AsNeeded }
|
||||
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"],
|
||||
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
|
||||
@@ -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 } }
|
||||
}
|
||||
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
|
||||
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
|
||||
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 } } }
|
||||
@@ -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"
|
||||
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 } }
|
||||
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)
|
||||
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 }
|
||||
// The step; an update shows the name with an Update badge, like the menu.
|
||||
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 }
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -2,7 +2,7 @@
|
||||
# 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
|
||||
# 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.
|
||||
# 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>
|
||||
|
||||
+34
-8
@@ -197,19 +197,19 @@ class Backend(QObject):
|
||||
p.start()
|
||||
|
||||
def _make_askpass(self, password):
|
||||
"""A private folder with the password, a helper that prints it for
|
||||
`sudo -A`, and a `sudo` that always uses the helper (makepkg and
|
||||
yay call plain sudo)."""
|
||||
"""A private folder with a helper that `sudo -A` runs for the
|
||||
password, and a `sudo` that always uses it (makepkg and yay call
|
||||
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()
|
||||
d = tempfile.mkdtemp(prefix="steamify-", dir=base)
|
||||
os.chmod(d, stat.S_IRWXU)
|
||||
secret = os.path.join(d, "secret")
|
||||
fd = os.open(secret, os.O_WRONLY | os.O_CREAT | os.O_EXCL, 0o600)
|
||||
with os.fdopen(fd, "w") as f:
|
||||
f.write(password + "\n")
|
||||
pipe = os.path.join(d, "pipe")
|
||||
os.mkfifo(pipe, 0o600)
|
||||
askpass = os.path.join(d, "askpass")
|
||||
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)
|
||||
bindir = os.path.join(d, "bin")
|
||||
os.mkdir(bindir)
|
||||
@@ -217,9 +217,35 @@ class Backend(QObject):
|
||||
with open(wrapper, "w") as f:
|
||||
f.write("#!/bin/sh\nexec /usr/bin/sudo -A \"$@\"\n")
|
||||
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
|
||||
|
||||
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:
|
||||
shutil.rmtree(self._secret_dir, ignore_errors=True)
|
||||
self._secret_dir = None
|
||||
|
||||
Reference in new issue
Block a user