Merge pull request #29 from theupriser/feat/update-notifier

Feat/update notifier
This commit is contained in:
theupriser authored and GitHub committed 2026-09-27 17:02:15 +02:00
commit 66dd04799b
38 files changed
+718 -156

No files matched your search

+11 -6
View File
@@ -31,13 +31,15 @@ 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 # patches/ and services/ embedded: replace lib/common.sh's patch_file
# them from the checkout. # and service_raw, which read them from the checkout.
echo "# ----- patches/ -----" embed() {
echo "patch_file() {" local fn="$1" dir="$2" f
echo "# ----- $dir/ -----"
echo "$fn() {"
echo ' case "$1" in' echo ' case "$1" in'
for f in patches/*; do for f in "$dir"/*; do
[[ "$f" == patches/README.md ]] && continue [[ "$f" == "$dir/README.md" ]] && continue
grep -q '^STEAMIFY_PATCH_EOF$' "$f" && { echo "bundle: $f contains the heredoc delimiter" >&2; exit 1; } grep -q '^STEAMIFY_PATCH_EOF$' "$f" && { echo "bundle: $f contains the heredoc delimiter" >&2; exit 1; }
echo " $(basename "$f")) cat << 'STEAMIFY_PATCH_EOF'" echo " $(basename "$f")) cat << 'STEAMIFY_PATCH_EOF'"
cat "$f" cat "$f"
@@ -48,6 +50,9 @@ version="v$(grep -oP '^VERSION=\K.*' "$entry"), $(git describe --always --dirty
echo ' esac' echo ' esac'
echo "}" echo "}"
echo echo
}
embed patch_file patches
embed service_raw services
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/, patches/, the icon) and its launcher, run with: # lib/, patches/, services/, 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 patches ui assets/steam-gaming-settings.svg LICENSE.md tar -czf dist/steamify-app.tar.gz steamify.sh lib patches services 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
+32 -3
View File
@@ -19,6 +19,12 @@ gamescope and the Plasma desktop. Primary target: the Valve Steam Machine
apply, never inline in the shell code. Read them with `patch_file apply, never inline in the shell code. Read them with `patch_file
<name>` (`lib/common.sh`, from the checkout); the bundle embeds every 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. in `patches/` and overrides `patch_file`. Add new ones to its README.
- `services/` - the systemd units the scripts install (`.service`,
`.timer`, `.path`), never inline in the shell code. Read them with
`service_file <name> [KEY=value...]` (`lib/common.sh`), which fills in
`@KEY@` placeholders; the bundle embeds every file in `services/` too
(`service_raw`). Add new ones to its README. Small drop-ins for other
packages' units stay inline.
- `ui/` - the app: `steamify-ui` (PySide6; runs `steamify.sh --backend`, - `ui/` - the app: `steamify-ui` (PySide6; runs `steamify.sh --backend`,
`lib/backend.sh`) and `ui/qml/`: `Main.qml` (window, header, which screen `lib/backend.sh`) and `ui/qml/`: `Main.qml` (window, header, which screen
shows), `AppState.qml` (all state and logic, input actions), one shows), `AppState.qml` (all state and logic, input actions), one
@@ -26,6 +32,12 @@ gamescope and the Plasma desktop. Primary target: the Valve Steam Machine
the singletons `Theme` (colours, fonts), `Texts` (item texts) and `Input` the singletons `Theme` (colours, fonts), `Texts` (item texts) and `Input`
(controller/keyboard/remote and its button names), listed in `qmldir`. (controller/keyboard/remote and its button names), listed in `qmldir`.
Screens get the `AppState` as `app` and only show it or call its functions. Screens get the `AppState` as `app` and only show it or call its functions.
- **Paths.** Everything in the home folder lives under `steamify`:
`$STATE_DIR` (`~/.local/state/steamify`), `$STEAMIFY_DATA`/`$STEAMIFY_BIN`
(`~/.local/share/steamify/{app,bin}`), `lib/state.sh`. Never add files
under the old name `cachyos-gamescope-boot`; `migrate_layout` moves those
of older versions (symlinks keep older releases working). Keep the
`.bak-gamescope-wizard` backup suffix: existing backups are found by it.
- `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`.
@@ -42,6 +54,8 @@ gamescope and the Plasma desktop. Primary target: the Valve Steam Machine
are ticked and re-applied ("update" in the plan and the app). New default are ticked and re-applied ("update" in the plan and the app). New default
options are ticked for installs whose parent (top-level: `gaming`) is on options are ticked for installs whose parent (top-level: `gaming`) is on
(`feature_new`, shown as "new"). (`feature_new`, shown as "new").
Options shown but left unticked in a confirmed run are recorded as
`off` (`feature_record_unticked`), or `feature_new` would tick them again.
Status still comes from the system; don't add ad-hoc `<id>_repair` checks Status still comes from the system; don't add ad-hoc `<id>_repair` checks
for new changes. for new changes.
- **Reversibility.** Every per-user KDE setting a component changes goes - **Reversibility.** Every per-user KDE setting a component changes goes
@@ -89,6 +103,10 @@ gamescope and the Plasma desktop. Primary target: the Valve Steam Machine
changelog section) and marks it latest. An existing version is never changelog section) and marks it latest. An existing version is never
overwritten: without a bump nothing is released, and pull requests show overwritten: without a bump nothing is released, and pull requests show
a warning. a warning.
- A release that adds, removes or renames a menu row also retakes the
README screenshot (`assets/screenshot-menu.png`): the app from the
branch on a Steam Machine, every row visible (window 1280 wide, tall
enough), the header showing the new `VERSION`, scaled to 1600 px wide.
- Users install through `releases/latest/download/steamify.sh` - Users install through `releases/latest/download/steamify.sh`
(GitHub's newest release). The `latest` tag and release follow the newest (GitHub's newest release). The `latest` tag and release follow the newest
version tag too (moved, asset replaced, when a new version is released), version tag too (moved, asset replaced, when a new version is released),
@@ -144,7 +162,9 @@ gamescope and the Plasma desktop. Primary target: the Valve Steam Machine
`restart_plasmashell_if_stopped` (`lib/common.sh`). `restart_plasmashell_if_stopped` (`lib/common.sh`).
- LED driver: `leds-valve-dkms-git`'s Makefile builds against `uname -r`, - LED driver: `leds-valve-dkms-git`'s Makefile builds against `uname -r`,
not DKMS's target kernel: `/etc/dkms/leds-valve-dkms.conf` sets not DKMS's target kernel: `/etc/dkms/leds-valve-dkms.conf` sets
`MAKE[0]="make KVERSION=${kernelver}"` (written before the AUR install). `MAKE[0]="make KVERSION=${kernelver}"` (written before the AUR install;
someone's own override there is backed up, kept with ours appended, and
restored on disable).
Without it, other kernels build against the running kernel's tree and Without it, other kernels build against the running kernel's tree and
fail (CachyOS kernels are clang-built; DKMS adds `LLVM=1` only for the fail (CachyOS kernels are clang-built; DKMS adds `LLVM=1` only for the
target's tree). Headers for every installed kernel are installed first, target's tree). Headers for every installed kernel are installed first,
@@ -223,10 +243,19 @@ gamescope and the Plasma desktop. Primary target: the Valve Steam Machine
`linux-cachyos` is clang-built, `-bore` GCC-built: let DKMS pick the `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 compiler, never pass `LLVM=1`. Test shutdown on the real machine for every
new major kernel. new major kernel.
- Update notifications (`notify`, `lib/update-notifier.sh`, top-level,
ticked by default, 2.5.0): a user timer (daily, and the service is wanted
by `plasma-workspace.target`) runs `patches/steamify-notifier.py`, which
compares GitHub's newest release with the `seen` version every run records
(`notify_seen` in the entry point, only while it's on, with `frontend`: app or terminal, which Open Steamify starts again, in a `systemd-run --scope` since the check's own unit is stopped when it exits). It must never
update anything itself: notification + tray icon, Open Steamify / Skip
this version (`skipped`). Desktop only (exits without `plasmashell`).
- VRAM booster (`vram`, `lib/vram-booster.sh`, top-level, ticked by - VRAM booster (`vram`, `lib/vram-booster.sh`, top-level, ticked by
default, 2.3.0; only available when `/sys/fs/cgroup/dmem.capacity` lists a default, 2.3.0; only available when `/sys/fs/cgroup/dmem.capacity` lists a
VRAM region of at least 2 GB; greyed out with an NVIDIA card, `vram`/`vidmem` region (numbered too) of at least 2 GB, whatever the brand;
`vram_selectable`, faked with `WIZARD_VRAM_FAKE_NVIDIA=1`): CachyOS's `dmemcg-booster` (system + user service) and greyed out with an NVIDIA card whose driver lists none, `vram_selectable`,
faked with `WIZARD_VRAM_FAKE_NVIDIA=1`; `WIZARD_VRAM_CAPACITY=<file>` reads
the regions from a copy): CachyOS's `dmemcg-booster` (system + user service) and
`plasma-foreground-booster`, like SteamOS 3.9's VRAM management. The `plasma-foreground-booster`, like SteamOS 3.9's VRAM management. The
latter only starts with `kcgroupsrc [Foreground Booster] autostart=true` latter only starts with `kcgroupsrc [Foreground Booster] autostart=true`
(set with `kset`). Disable removes only the packages it installed (set with `kset`). Disable removes only the packages it installed
+17
View File
@@ -5,6 +5,23 @@ 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.5.0 - 2026-09-27
- **docs: README: what the VRAM booster says with NVIDIA's closed driver; shorter unsupported text in the app**
- `32c33e4` **fix: An option left unticked stays off: it came back ticked as "new" on the next run (since 2.3.0)**
- `2dd0f3a` **feat: VRAM booster with NVIDIA says what to do: update, switch to the open driver (chwd command), or not supported by the card**
- `57995bc` **docs: VRAM booster works with NVIDIA's open kernel modules (driver 615+); only the closed driver is greyed out**
- `d57e8d8` **docs: AGENTS.md and changelog for the vidmem detection**
- `5a34633` **feat: VRAM booster also for drivers that name their region vidmem (or numbered), NVIDIA included once its driver registers it**
- `479b70b` **feat: Everything in the home folder under steamify (state, scripts, icon), moved once from the old name**
- `2e0881b` **fix: fill takes values literally; the notifier path is quoted in its unit**
- `cce31e1` **refactor: DKMS confs in patches/; an existing leds-valve DKMS override is kept and restored**
- `bd4fa3a` **refactor: The session sync and kernel headers scripts in patches/**
- `8ffa0f2` **refactor: systemd units in services/, read with service_file like patches/**
- `8a2601d` **docs: README screenshot at 2.5.0 with Update notifications; AGENTS.md: retake it when rows change**
- `572417b` **fix: Open Steamify from the notification starts the app (own scope), the same way it was last used (app or terminal)**
- `cf73229` **feat: Update notifications: a notification and tray icon when there's a new Steamify**
## 2.4.1 - 2026-09-27 ## 2.4.1 - 2026-09-27
- **chore: Version 2.4.1** - **chore: Version 2.4.1**
+11 -4
View File
@@ -30,12 +30,19 @@ Steam Machine.
5. **Steamify shortcut** - "Steamify CachyOS" on the desktop and in the 5. **Steamify shortcut** - "Steamify CachyOS" on the desktop and in the
launcher opens the newest Steamify app, so you don't need the install launcher opens the newest Steamify app, so you don't need the install
command again. "Steamify Terminal" in the launcher opens the terminal menu. command again. "Steamify Terminal" in the launcher opens the terminal menu.
6. **VRAM booster** - on by default: the game you're playing keeps its video 6. **Update notifications** - on by default: a notification and a tray icon
when there's a new Steamify, with Open Steamify and Skip this version. It
never updates anything by itself
([details](TECHNICAL.md#update-notifications)).
7. **VRAM booster** - on by default: the game you're playing keeps its video
memory and background apps make room, like SteamOS 3.9. Shown for GPUs memory and background apps make room, like SteamOS 3.9. Shown for GPUs
whose driver supports it (AMD, Intel) on kernel 7.2 or newer; greyed out whose driver supports it (AMD, Intel, NVIDIA with its open kernel modules
with an NVIDIA card until NVIDIA's driver supports it from driver 615) on kernel 7.2 or newer. With NVIDIA's closed driver it's
greyed out and says what to do: switch to the open driver when your card
supports it (RTX 20 series and newer; Steamify shows the command, it
doesn't switch drivers itself)
([details](TECHNICAL.md#vram-booster)). ([details](TECHNICAL.md#vram-booster)).
7. **HDMI-CEC** (experimental; off by default, on by default on a Steam 8. **HDMI-CEC** (experimental; off by default, on by default on a Steam
Machine) - use Steam with your TV's Machine) - use Steam with your TV's
remote, and the TV turns on and off with the PC. Needs a PC with CEC (like remote, and the TV turns on and off with the PC. Needs a PC with CEC (like
the Steam Machine) or a USB CEC adapter; most graphics cards, NVIDIA the Steam Machine) or a USB CEC adapter; most graphics cards, NVIDIA
+66 -7
View File
@@ -14,7 +14,7 @@ and turns on what you ticked. To be able to undo:
off. off.
- **KDE settings** in your home directory are recorded with their previous - **KDE settings** in your home directory are recorded with their previous
value the first time the wizard changes them, in an undo journal under value the first time the wizard changes them, in an undo journal under
`~/.local/state/cachyos-gamescope-boot/`. Turning the component off writes `~/.local/state/steamify/`. Turning the component off writes
the old values back (or removes keys that didn't exist before). Setups the old values back (or removes keys that didn't exist before). Setups
made by older versions of the script, without a journal, fall back to made by older versions of the script, without a journal, fall back to
KDE's defaults. KDE's defaults.
@@ -236,17 +236,49 @@ 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 power-off fix as a new default sub-option (see feature versions below), so a
normal run adds it. normal run adds it.
## Update notifications
The **Update notifications** option (new in 2.5.0, ticked by default)
installs `patches/steamify-notifier.py` as
`~/.local/share/steamify/bin/steamify-notifier` with two user
units in `~/.config/systemd/user`: `steamify-update-check.timer` (daily,
`Persistent=`) and `steamify-update-check.service` (also wanted by
`plasma-workspace.target`, so it checks a minute after each desktop login).
Nothing stays running while there's no update.
Every Steamify run records its version as `seen` (`notify.state`). The check
asks GitHub for the newest release; when it's newer than `seen` and not the
`skipped` version, it shows a notification (`notify-send` with the buttons
**Open Steamify** and **Skip this version**) and a tray icon (click: open;
menu: open, skip, remind me later). Closing the notification keeps the
icon; opening Steamify in any way records the new version and ends it. It
only runs on the Plasma desktop: gamescope shows no notifications or tray,
so an update found in gaming mode waits for the next desktop login. It never
installs anything: the shortcut always starts the newest release, which then
shows what's new or updated for this install.
## VRAM booster ## VRAM booster
SteamOS 3.9 manages the dGPU's VRAM per cgroup: the game in front is SteamOS 3.9 manages the dGPU's VRAM per cgroup: the game in front is
protected, background apps are evicted first. Without it a game that needs protected, background apps are evicted first. Without it a game that needs
most of the VRAM (8 GB on the Steam Machine) can be pushed into system RAM most of the VRAM (8 GB on the Steam Machine) can be pushed into system RAM
by the desktop and other apps, and stutter. The kernel side is the `dmem` by the desktop and other apps, and stutter. The kernel side is the `dmem`
cgroup controller (7.2); amdgpu and Intel's xe register their VRAM with it, cgroup controller (7.2); amdgpu and Intel's xe register their VRAM with it
NVIDIA's driver doesn't: with an NVIDIA card the option is shown greyed (`drm/<pci>/vram`), NVIDIA's open kernel modules from driver 615 too
out, with why (`WIZARD_VRAM_FAKE_NVIDIA=1` fakes that for tests). Otherwise (`nvidia/<pci>/vidmem`); `dmemcg-booster` protects every region it lists,
whatever its name. NVIDIA's closed modules don't: with such a card the option is shown greyed
out with what to do (`vram_nvidia_case`): open modules older than 615 ->
update; the closed driver on a card the open one supports (not in chwd's
legacy lists `/var/lib/chwd/ids/nvidia-{580,470,390}.ids`) -> the `chwd`
commands to switch, never switched by Steamify itself (a failed switch
means a black screen, and it can't be tested here); a legacy card or
nouveau -> not supported. Shown greyed
out, with why, until its driver lists a region: then it's offered like any
other (`WIZARD_VRAM_FAKE_NVIDIA=1` fakes the grey-out, `WIZARD_VRAM_CAPACITY=<file>`
reads the regions from a copy, for tests). Otherwise
the **VRAM booster** option is only shown when the **VRAM booster** option is only shown when
`/sys/fs/cgroup/dmem.capacity` lists a VRAM region of at least 2 GB (a `/sys/fs/cgroup/dmem.capacity` lists a VRAM region (`vram`, or `vidmem`
as other drivers name it, numbered with several) of at least 2 GB (a
dedicated GPU; an integrated one registers a small carve-out) or it's already on, and is dedicated GPU; an integrated one registers a small carve-out) or it's already on, and is
ticked by default; new in 2.3.0, so setups with the SteamOS conversion on ticked by default; new in 2.3.0, so setups with the SteamOS conversion on
get it ticked. CachyOS packages the userspace side, which it installs: get it ticked. CachyOS packages the userspace side, which it installs:
@@ -269,7 +301,7 @@ worth it if the booster itself turns out to cause stutter.
Each component has a feature version (`FEATURE_VERSION` in `lib/menu.sh`): 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 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 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` successfully, that version is recorded in `~/.local/state/steamify/features.state`
(turning it off records `off`). Whether a component is on is always checked (turning it off records `off`). Whether a component is on is always checked
on the system itself; the version only decides about updates: on the system itself; the version only decides about updates:
@@ -323,6 +355,31 @@ To walk through it without flashing anything, run the wizard with
`WIZARD_BIOS_DRY_RUN=1`: it downloads and checks the package and shows both `WIZARD_BIOS_DRY_RUN=1`: it downloads and checks the package and shows both
warnings, skips fwupd's device check, and only prints the install command. warnings, skips fwupd's device check, and only prints the install command.
## Where Steamify keeps its files
In the home folder, everything is under `steamify`:
| Where | What |
|---|---|
| `~/.local/state/steamify/` | State (`*.state`), undo journals (`*.journal`), the saved panel layout (`theme-layout/`) |
| `~/.local/share/steamify/app/` | The app (`steamify-app.sh` downloads it here) |
| `~/.local/share/steamify/bin/` | The shortcut's start scripts (`run-app`, `run-wizard`) and `steamify-notifier` |
| `~/.local/share/icons/hicolor/scalable/apps/steamify.svg` | The shortcut's icon |
| `~/.local/share/applications/steamify-*.desktop` | The launcher entries |
| `~/.config/systemd/user/` | `steamify-update-check.*`, `steam-desktop-autostart.service` |
System-wide: `/usr/local/lib/steamify/` (kernel headers script),
`/usr/src/steamify-*` (DKMS modules), `/var/cache/steamify/kernel` (the old
kernel pin's packages), and `*.bak-gamescope-wizard` backups next to the
system files that were edited (that name stays: it's how they're found again).
Before 2.5.0 the home folder paths used the project's old name
(`cachyos-gamescope-boot`). `migrate_layout` (`lib/state.sh`) moves them at
every start of a newer version, once, and leaves symlinks under the old names
so an older release still finds the same state; it fixes the paths in the
launcher entries and the update check's unit, and merges when both exist.
The headers script moves when Steam Machine support is set up again.
## Manual session control ## Manual session control
```bash ```bash
@@ -357,9 +414,11 @@ immediately, which can turn into a loop - see
| `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 | | `lib/steam-machine.sh` | Steam Machine support: LED driver, LED access, steamos-manager |
| `lib/fremont-poweroff.sh` | Steam Machine support: the power-off fix (DKMS module from `patches/`) | | `lib/fremont-poweroff.sh` | Steam Machine support: the power-off fix (DKMS module from `patches/`) |
| `lib/update-notifier.sh` | Update notifications: the notifier from `patches/` and its user timer |
| `lib/vram-booster.sh` | VRAM booster (`dmemcg-booster`, `plasma-foreground-booster`) | | `lib/vram-booster.sh` | VRAM booster (`dmemcg-booster`, `plasma-foreground-booster`) |
| `services/` | The systemd units the scripts install (`service_file`, `@KEY@` placeholders); see its README |
| `patches/` | Module sources and patches the scripts build or apply (`patch_file`); see its README | | `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/tools/bundle.sh` | Builds the single-file version (`dist/steamify.sh`), with `patches/` and `services/` 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
Binary file not shown.

Before

Width:  |  Height:  |  Size: 484 KiB

After

Width:  |  Height:  |  Size: 492 KiB

+7 -1
View File
@@ -54,7 +54,11 @@ backend_status() {
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+=",\"update\":$(feature_outdated "$c" && echo true || echo false)"
items+=",\"new\":$(feature_new "$c" && echo true || echo false)" items+=",\"new\":$(feature_new "$c" && echo true || echo false)"
items+=",\"selectable\":$(component_selectable "$c" && echo true || echo false)}" items+=",\"selectable\":$(component_selectable "$c" && echo true || echo false)"
# Why it can't be turned on, for the app's explanation.
[[ "$c" == vram && -n "${VRAM_NVIDIA_CASE:-}" ]] &&
items+=",\"note\":$(json_str "$(vram_nvidia_note "$VRAM_NVIDIA_CASE")")"
items+="}"
done done
local cec="" f local cec="" f
for f in /dev/cec*; do [[ -e "$f" ]] && cec+="${cec:+ }$(basename "$f")"; done for f in /dev/cec*; do [[ -e "$f" ]] && cec+="${cec:+ }$(basename "$f")"; done
@@ -156,6 +160,7 @@ backend_apply() {
plan_changes plan_changes
backend_event plan "\"disable\":$(json_list "${TO_DISABLE[@]}"),\"enable\":$(json_list "${TO_ENABLE[@]}")" backend_event plan "\"disable\":$(json_list "${TO_DISABLE[@]}"),\"enable\":$(json_list "${TO_ENABLE[@]}")"
if [[ ${#TO_DISABLE[@]} -eq 0 && ${#TO_ENABLE[@]} -eq 0 ]]; then if [[ ${#TO_DISABLE[@]} -eq 0 && ${#TO_ENABLE[@]} -eq 0 ]]; then
feature_record_unticked
backend_event finished '"failed":[],"restart":false,"nothing":true' backend_event finished '"failed":[],"restart":false,"nothing":true'
return 0 return 0
fi fi
@@ -171,6 +176,7 @@ backend_apply() {
for c in "${TO_ENABLE[@]}"; do for c in "${TO_ENABLE[@]}"; do
if backend_run_component "$c" enable; then is_action "$c" || feature_record "$c" enable; else failed+=("$c"); fi if backend_run_component "$c" enable; then is_action "$c" || feature_record "$c" enable; else failed+=("$c"); fi
done done
feature_record_unticked
[[ " ${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)"
} }
+1 -13
View File
@@ -23,19 +23,7 @@ boot_enable() {
# the session into the base config; run it right away rather than rely # the session into the base config; run it right away rather than rely
# on its path unit, which could still be pending when the login manager # on its path unit, which could still be pending when the login manager
# starts. # starts.
sudo tee "$BOOT_UNIT" > /dev/null << EOF service_file "$BOOT_UNIT_NAME" SESSION_SYNC="$SESSION_SYNC" | sudo tee "$BOOT_UNIT" > /dev/null
[Unit]
Description=Steamify CachyOS: boot into the desktop instead of gaming mode
Before=display-manager.service sddm.service plasmalogin.service
[Service]
Type=oneshot
ExecStart=/usr/lib/steamos/steam-set-session plasma.desktop
ExecStart=/bin/sh -c '[ -x $SESSION_SYNC ] && exec $SESSION_SYNC || true'
[Install]
WantedBy=graphical.target
EOF
sudo systemctl daemon-reload sudo systemctl daemon-reload
sudo systemctl enable "$BOOT_UNIT_NAME" || { err "Enabling $BOOT_UNIT_NAME failed."; return 1; } sudo systemctl enable "$BOOT_UNIT_NAME" || { err "Enabling $BOOT_UNIT_NAME failed."; return 1; }
ok "Boots into the desktop from the next boot on (Return to Gaming Mode still works)." ok "Boots into the desktop from the next boot on (Return to Gaming Mode still works)."
+1 -4
View File
@@ -76,10 +76,7 @@ cec_driver_enable() {
patch_file cros-ec-cec-single-port.patch | patch -s -d "$tmp" -p1 || 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; } { 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." \ patch_file "$CEC_DKMS_NAME.dkms.conf" | fill NAME="$CEC_DKMS_NAME" VERSION="$CEC_DKMS_VER" >"$tmp/dkms.conf"
"PACKAGE_NAME=\"$CEC_DKMS_NAME\"" "PACKAGE_VERSION=\"$CEC_DKMS_VER\"" \
'BUILT_MODULE_NAME[0]="cros-ec-cec"' 'DEST_MODULE_LOCATION[0]="/updates/dkms"' \
'AUTOINSTALL="yes"' >"$tmp/dkms.conf"
sudo dkms remove "$CEC_DKMS_NAME/$CEC_DKMS_VER" --all >/dev/null 2>&1 sudo dkms remove "$CEC_DKMS_NAME/$CEC_DKMS_VER" --all >/dev/null 2>&1
sudo rm -rf "$CEC_DKMS_SRC" sudo rm -rf "$CEC_DKMS_SRC"
sudo install -d "$CEC_DKMS_SRC" && sudo install -m644 "$tmp"/{cros-ec-cec.c,Makefile,dkms.conf} "$CEC_DKMS_SRC/" sudo install -d "$CEC_DKMS_SRC" && sudo install -m644 "$tmp"/{cros-ec-cec.c,Makefile,dkms.conf} "$CEC_DKMS_SRC/"
+15
View File
@@ -9,6 +9,21 @@ ok() { echo -e "${c_green}[OK]${c_reset} $*"; }
# patch_file <name>: a file from patches/ (module sources, patches). The # patch_file <name>: a file from patches/ (module sources, patches). The
# single-file build replaces this with one that has them embedded. # single-file build replaces this with one that has them embedded.
patch_file() { cat "$SCRIPT_DIR/patches/$1"; } patch_file() { cat "$SCRIPT_DIR/patches/$1"; }
# service_file <name> [KEY=value...]: a systemd unit from services/, filled
# in. The single-file build replaces service_raw like patch_file.
service_raw() { cat "$SCRIPT_DIR/services/$1"; }
service_file() { local name="$1"; shift; service_raw "$name" | fill "$@"; }
# fill [KEY=value...]: stdin with each @KEY@ replaced by its value, taken
# literally (a home folder may contain sed's "&", "\" or "|").
fill() {
local kv v
local -a subst=(-e '')
for kv in "$@"; do
v="${kv#*=}"; v="${v//\\/\\\\}"; v="${v//&/\\&}"; v="${v//|/\\|}"
subst+=(-e "s|@${kv%%=*}@|$v|g")
done
sed "${subst[@]}"
}
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() {
+1 -4
View File
@@ -28,10 +28,7 @@ poweroff_fix_enable() {
tmp="$(mktemp -d)" tmp="$(mktemp -d)"
patch_file "$POWEROFF_DKMS_NAME.c" > "$tmp/$POWEROFF_DKMS_NAME.c" patch_file "$POWEROFF_DKMS_NAME.c" > "$tmp/$POWEROFF_DKMS_NAME.c"
echo "obj-m += $POWEROFF_DKMS_NAME.o" >"$tmp/Makefile" 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." \ patch_file "$POWEROFF_DKMS_NAME.dkms.conf" | fill NAME="$POWEROFF_DKMS_NAME" VERSION="$POWEROFF_DKMS_VER" >"$tmp/dkms.conf"
"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 dkms remove "$POWEROFF_DKMS_NAME/$POWEROFF_DKMS_VER" --all >/dev/null 2>&1
sudo rm -rf "$POWEROFF_DKMS_SRC" sudo rm -rf "$POWEROFF_DKMS_SRC"
sudo install -d "$POWEROFF_DKMS_SRC" && sudo install -d "$POWEROFF_DKMS_SRC" &&
+3 -39
View File
@@ -185,24 +185,7 @@ install_session_sync() {
local SYNC_SCRIPT="/usr/local/bin/sync-steamos-session.sh" local SYNC_SCRIPT="/usr/local/bin/sync-steamos-session.sh"
info "Installing session sync bridge at $SYNC_SCRIPT" info "Installing session sync bridge at $SYNC_SCRIPT"
sudo tee "$SYNC_SCRIPT" > /dev/null << 'EOF' patch_file sync-steamos-session.sh | sudo tee "$SYNC_SCRIPT" > /dev/null
#!/bin/bash
SRC="/etc/plasmalogin.conf.d/zz-steamos-autologin.conf"
DEST="/etc/plasmalogin.conf"
[[ -f "$SRC" ]] || exit 0
SESSION=$(grep -oP '^Session=\K.*' "$SRC")
[[ -z "$SESSION" ]] && exit 0
# Only touch Session= inside [Autologin]; other sections may have their own.
if sed -n '/^\[Autologin\]/,/^\[/p' "$DEST" | grep -q '^Session='; then
sed -i "/^\[Autologin\]/,/^\[/ s|^Session=.*|Session=$SESSION|" "$DEST"
elif grep -q '^\[Autologin\]' "$DEST"; then
sed -i "/^\[Autologin\]/a Session=$SESSION" "$DEST"
else
printf '\n[Autologin]\nSession=%s\n' "$SESSION" >> "$DEST"
fi
EOF
sudo chmod +x "$SYNC_SCRIPT" sudo chmod +x "$SYNC_SCRIPT"
ok "Sync script installed." ok "Sync script installed."
@@ -211,28 +194,9 @@ EOF
# 7. systemd path watcher + service # 7. systemd path watcher + service
info "Installing systemd path watcher so session switches take effect immediately" info "Installing systemd path watcher so session switches take effect immediately"
sudo tee /etc/systemd/system/sync-steamos-session.path > /dev/null << 'EOF' service_file sync-steamos-session.path | sudo tee /etc/systemd/system/sync-steamos-session.path > /dev/null
[Unit]
Description=Watch for steamos session changes
[Path] service_file sync-steamos-session.service | sudo tee /etc/systemd/system/sync-steamos-session.service > /dev/null
PathModified=/etc/plasmalogin.conf.d/zz-steamos-autologin.conf
[Install]
WantedBy=multi-user.target
EOF
sudo tee /etc/systemd/system/sync-steamos-session.service > /dev/null << 'EOF'
[Unit]
Description=Sync steamos session selection into plasmalogin.conf
# Session switches can come in bursts (switch + autologin reset); never
# let systemd's start rate limit silently stop the bridge.
StartLimitIntervalSec=0
[Service]
Type=oneshot
ExecStart=/usr/local/bin/sync-steamos-session.sh
EOF
sudo systemctl daemon-reload sudo systemctl daemon-reload
sudo systemctl enable --now sync-steamos-session.path sudo systemctl enable --now sync-steamos-session.path
+24 -4
View File
@@ -5,7 +5,7 @@
# 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 vram cec machine poweroff kpin hdmi bios) COMPONENTS=(gaming boot theme glyphs single launcher notify vram 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)
@@ -26,7 +26,7 @@ NO_PRESELECT=(boot cec kpin hdmi)
FEATURE_BASELINE=2.1.0 FEATURE_BASELINE=2.1.0
declare -A FEATURE_VERSION=( declare -A FEATURE_VERSION=(
[gaming]=2.1.0 [boot]=2.1.0 [theme]=2.1.0 [glyphs]=2.1.0 [single]=2.1.0 [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 [vram]=2.3.0 [launcher]=2.1.0 [cec]=2.1.0 [machine]=2.2.0 [poweroff]=2.2.0 [vram]=2.3.0 [notify]=2.5.0
[kpin]=2.1.0 [hdmi]=2.1.0 [kpin]=2.1.0 [hdmi]=2.1.0
) )
@@ -37,6 +37,7 @@ declare -A LABEL=(
[glyphs]="Install Steam Deck/Machine icons: Deck button icons in gaming mode" [glyphs]="Install Steam Deck/Machine icons: Deck button icons in gaming mode"
[single]="Single user mode: no password, lock screen or log out (SDDM)" [single]="Single user mode: no password, lock screen or log out (SDDM)"
[launcher]="Steamify shortcut: the app on the desktop, Steamify Terminal in the launcher" [launcher]="Steamify shortcut: the app on the desktop, Steamify Terminal in the launcher"
[notify]="Update notifications: a notification when there's a new Steamify, never updates by itself"
[vram]="VRAM booster: the game in front keeps its VRAM, background apps make room" [vram]="VRAM booster: the game in front keeps its VRAM, background apps make room"
[cec]="HDMI-CEC: use Steam with the TV remote, TV on/off with the PC (experimental)" [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"
@@ -72,6 +73,21 @@ menu_visible() {
[[ -z "${PARENT[$1]:-}" || "${WANTED[${PARENT[$1]}]:-0}" == 1 ]] [[ -z "${PARENT[$1]:-}" || "${WANTED[${PARENT[$1]}]:-0}" == 1 ]]
} }
feature_record_unticked() {
# After a run the user confirmed: options shown but left unticked that
# have no record yet count as turned off, or the next run would offer a
# default one again as "new" (feature_new) and tick it: e.g. single user
# mode unticked on a first run came back ticked when anything else
# changed. Hidden sub-options weren't a choice, so they're left alone.
local c
for c in "${COMPONENTS[@]}"; do
is_action "$c" && continue
menu_visible "$c" || continue
[[ "${WANTED[$c]:-0}" == 0 && -z "$(state_get features "$c")" ]] && state_set features "$c" off
done
return 0
}
feature_record() { feature_record() {
# feature_record <component> <enable|disable>: after a successful run. # feature_record <component> <enable|disable>: after a successful run.
if [[ "$2" == enable ]]; then state_set features "$1" "${FEATURE_VERSION[$1]:-$FEATURE_BASELINE}" if [[ "$2" == enable ]]; then state_set features "$1" "${FEATURE_VERSION[$1]:-$FEATURE_BASELINE}"
@@ -146,8 +162,11 @@ detect_components() {
launcher_repair && WANTED[launcher]=1 launcher_repair && WANTED[launcher]=1
# Shows the current and newest BIOS version. # Shows the current and newest BIOS version.
bios_available && { bios_lookup_newest; LABEL[bios]="$(bios_label)"; } bios_available && { bios_lookup_newest; LABEL[bios]="$(bios_label)"; }
# Greyed out with an NVIDIA card: say why. # Greyed out with an NVIDIA card: say why, and what to do.
component_available vram && ! vram_selectable && LABEL[vram]="VRAM booster: not supported by NVIDIA's driver yet" if component_available vram && ! vram_selectable; then
VRAM_NVIDIA_CASE="$(vram_nvidia_case)"
LABEL[vram]="VRAM booster: $(vram_nvidia_hint "$VRAM_NVIDIA_CASE")"
fi
# First run: preselect the full SteamOS experience (never an action). # First run: preselect the full SteamOS experience (never an action).
if [[ "$any" == false ]]; then if [[ "$any" == false ]]; then
for c in "${COMPONENTS[@]}"; do for c in "${COMPONENTS[@]}"; do
@@ -357,5 +376,6 @@ apply_changes() {
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
if "${c}_enable"; then is_action "$c" || feature_record "$c" enable; else failed+=("$c"); fi if "${c}_enable"; then is_action "$c" || feature_record "$c" enable; else failed+=("$c"); fi
done done
feature_record_unticked
FAILED=("${failed[@]}") FAILED=("${failed[@]}")
} }
+59 -1
View File
@@ -5,7 +5,11 @@
# deletes the key if it didn't exist before). # deletes the key if it didn't exist before).
# Sourced by steamify.sh; not meant to be run on its own. # Sourced by steamify.sh; not meant to be run on its own.
STATE_DIR="${XDG_STATE_HOME:-$HOME/.local/state}/cachyos-gamescope-boot" STATE_DIR="${XDG_STATE_HOME:-$HOME/.local/state}/steamify"
# Steamify's own files in the home folder: the app (app/, from
# steamify-app.sh) and the shortcut's and notifier's scripts (bin/).
STEAMIFY_DATA="${XDG_DATA_HOME:-$HOME/.local/share}/steamify"
STEAMIFY_BIN="$STEAMIFY_DATA/bin"
UNSET=$'\x01' UNSET=$'\x01'
kset() { kset() {
@@ -77,3 +81,57 @@ state_clear() {
current_display_manager() { current_display_manager() {
systemctl show -p Id --value display-manager 2>/dev/null | sed 's/\.service$//' systemctl show -p Id --value display-manager 2>/dev/null | sed 's/\.service$//'
} }
migrate_dir() {
# migrate_dir <old> <new>: moves <old> to <new> and leaves a symlink.
# When both exist (an older release ran after a newer one), what <new>
# lacks is moved over; a file in both keeps <new>'s copy and <old> stays
# as it is. Returns 0 when something moved.
local old="$1" new="$2" f moved=1
[[ -d "$old" && ! -L "$old" ]] || return 1
if [[ ! -e "$new" ]]; then
mkdir -p "$(dirname "$new")"
mv "$old" "$new" && ln -s "$new" "$old"
return 0
fi
for f in "$old"/* "$old"/.[!.]*; do
[[ -e "$f" || -L "$f" ]] || continue
[[ -e "$new/${f##*/}" ]] && continue
mv "$f" "$new/" && moved=0
done
rmdir "$old" 2>/dev/null && ln -s "$new" "$old"
return "$moved"
}
migrate_layout() {
# Before 2.5.0 the state and scripts were under the project's old name
# (cachyos-gamescope-boot). Moved once; the old names stay as symlinks,
# so an older release that still runs finds the same state.
local old_state="${XDG_STATE_HOME:-$HOME/.local/state}/cachyos-gamescope-boot"
local old_bin="${XDG_DATA_HOME:-$HOME/.local/share}/cachyos-gamescope-boot"
local old_icon="${XDG_DATA_HOME:-$HOME/.local/share}/icons/hicolor/scalable/apps/cachyos-gamescope-boot-wizard.svg"
local f
# Links left pointing at nothing once their component was turned off.
for f in "$old_state" "$old_bin"; do [[ -L "$f" && ! -e "$f" ]] && rm -f "$f"; done
migrate_dir "$old_state" "$STATE_DIR"
if migrate_dir "$old_bin" "$STEAMIFY_BIN"; then
# The notifier had the old paths built in; its unit and the
# shortcut's entries point at the scripts.
[[ -f "$STEAMIFY_BIN/steamify-notifier" ]] &&
patch_file steamify-notifier.py | install_executable "$STEAMIFY_BIN/steamify-notifier" 755
for f in "$NOTIFY_UNITS/$NOTIFY_NAME.service" "$WIZARD_APP_ENTRY" "$WIZARD_TERMINAL_ENTRY" \
"$(wizard_desktop_dir)/$WIZARD_DESKTOP_NAME"; do
[[ -f "$f" ]] && sed -i "s|$old_bin/|$STEAMIFY_BIN/|g" "$f"
done
systemctl --user daemon-reload 2>/dev/null
fi
if [[ -f "$old_icon" ]]; then
if [[ -e "$WIZARD_ICON" ]]; then rm -f "$old_icon"; else mv "$old_icon" "$WIZARD_ICON"; fi
gtk-update-icon-cache -q -f -t "$(dirname "$(dirname "$(dirname "$WIZARD_ICON")")")" 2>/dev/null || true
fi
for f in "$WIZARD_APP_ENTRY" "$WIZARD_TERMINAL_ENTRY" "$(wizard_desktop_dir)/$WIZARD_DESKTOP_NAME"; do
[[ -f "$f" ]] && grep -q '^Icon=cachyos-gamescope-boot-wizard$' "$f" &&
sed -i 's|^Icon=cachyos-gamescope-boot-wizard$|Icon=steamify|' "$f"
done
return 0
}
+1 -17
View File
@@ -16,23 +16,7 @@ steam_enable() {
systemd_user_dir="$HOME/.config/systemd/user" systemd_user_dir="$HOME/.config/systemd/user"
mkdir -p "$systemd_user_dir" mkdir -p "$systemd_user_dir"
cat << 'EOF' > "$systemd_user_dir/steam-desktop-autostart.service" service_file steam-desktop-autostart.service > "$systemd_user_dir/steam-desktop-autostart.service"
[Unit]
Description=Steam Background Autostart for Virtual Keyboard
After=graphical-session.target
PartOf=graphical-session.target
[Service]
Type=simple
# Only in the Plasma desktop session; gamescope-session starts its own Steam.
ExecCondition=/bin/sh -c '[ "$XDG_CURRENT_DESKTOP" = KDE ]'
ExecStart=/usr/bin/steam -silent
Restart=on-failure
RestartSec=5
[Install]
WantedBy=graphical-session.target
EOF
# Clean out old .desktop shortcut so they don't fight # Clean out old .desktop shortcut so they don't fight
rm -f "$HOME/.config/autostart/steam.desktop" rm -f "$HOME/.config/autostart/steam.desktop"
+37 -34
View File
@@ -224,9 +224,11 @@ install_valve_led_driver() {
# a clang-built tree). DKMS reads this override after the package's # a clang-built tree). DKMS reads this override after the package's
# dkms.conf; written before the install so its own build works too. # dkms.conf; written before the install so its own build works too.
# On a fresh system dkms isn't installed yet, so /etc/dkms doesn't exist. # On a fresh system dkms isn't installed yet, so /etc/dkms doesn't exist.
# Someone's own override is backed up and kept, with ours after it: it's
# sourced as bash, so our MAKE[0] wins.
sudo mkdir -p "$(dirname "$LED_DKMS_OVERRIDE")" sudo mkdir -p "$(dirname "$LED_DKMS_OVERRIDE")"
if ! printf '%s\n' "# Written by cachyos-gamescope-boot: build for DKMS's target kernel." \ if ! led_dkms_override | sudo tee "$LED_DKMS_OVERRIDE.new" >/dev/null ||
'MAKE[0]="make KVERSION=${kernelver}"' | sudo tee "$LED_DKMS_OVERRIDE" >/dev/null; then ! sudo mv -f "$LED_DKMS_OVERRIDE.new" "$LED_DKMS_OVERRIDE"; then
err "Couldn't write $LED_DKMS_OVERRIDE; without it the LED driver only builds for the running kernel." err "Couldn't write $LED_DKMS_OVERRIDE; without it the LED driver only builds for the running kernel."
return 1 return 1
fi fi
@@ -285,7 +287,33 @@ install_valve_led_driver() {
LED_UDEV_RULE="/etc/udev/rules.d/70-valve-leds-user.rules" LED_UDEV_RULE="/etc/udev/rules.d/70-valve-leds-user.rules"
LED_DKMS_OVERRIDE="/etc/dkms/leds-valve-dkms.conf" LED_DKMS_OVERRIDE="/etc/dkms/leds-valve-dkms.conf"
HEADERS_SCRIPT="/usr/local/lib/cachyos-gamescope-boot/ensure-kernel-headers"
led_dkms_override() {
# The override to write: an existing one's own lines (backed up the first
# time), without ours from an earlier run, then ours. Before 2.5.0 the
# file was ours alone, with a different comment.
local ours
ours="$(patch_file leds-valve-dkms.conf)"
if [[ -f "$LED_DKMS_OVERRIDE" ]]; then
grep -qxF -f <(echo "$ours") "$LED_DKMS_OVERRIDE" ||
grep -q '^# Written by cachyos-gamescope-boot' "$LED_DKMS_OVERRIDE" ||
backup_file "$LED_DKMS_OVERRIDE" >&2
grep -vxF -f <(echo "$ours") "$LED_DKMS_OVERRIDE" | grep -v '^# Written by cachyos-gamescope-boot'
fi
echo "$ours"
}
led_dkms_override_remove() {
# Puts someone's own override back, or removes ours.
if [[ -f "$LED_DKMS_OVERRIDE.bak-gamescope-wizard" ]]; then
sudo mv -f "$LED_DKMS_OVERRIDE.bak-gamescope-wizard" "$LED_DKMS_OVERRIDE"
else
sudo rm -f "$LED_DKMS_OVERRIDE"
fi
}
HEADERS_SCRIPT="/usr/local/lib/steamify/ensure-kernel-headers"
# Before 2.5.0; removed when it's set up again or turned off.
HEADERS_SCRIPT_OLD="/usr/local/lib/cachyos-gamescope-boot/ensure-kernel-headers"
HEADERS_UNIT="/etc/systemd/system/ensure-kernel-headers.service" HEADERS_UNIT="/etc/systemd/system/ensure-kernel-headers.service"
install_headers_boot_check() { install_headers_boot_check() {
@@ -295,36 +323,10 @@ install_headers_boot_check() {
# So check at every boot and install missing headers, which triggers # So check at every boot and install missing headers, which triggers
# that hook and builds the driver for the new kernel. # that hook and builds the driver for the new kernel.
sudo mkdir -p "$(dirname "$HEADERS_SCRIPT")" sudo mkdir -p "$(dirname "$HEADERS_SCRIPT")"
sudo tee "$HEADERS_SCRIPT" > /dev/null << 'EOF' patch_file ensure-kernel-headers.sh | sudo tee "$HEADERS_SCRIPT" > /dev/null
#!/bin/bash
# Installed by cachyos-gamescope-boot: install missing -headers for every
# installed kernel, so DKMS builds leds-valve for it.
set -u
missing=()
for k in $(pacman -Qqo /usr/lib/modules/*/pkgbase 2>/dev/null | sort -u); do
pacman -Q "${k}-headers" >/dev/null 2>&1 && continue
pacman -Si "${k}-headers" >/dev/null 2>&1 && missing+=("${k}-headers")
done
[[ ${#missing[@]} -eq 0 ]] && exit 0
# Another pacman is running (e.g. an update): try again next boot.
[[ -e /var/lib/pacman/db.lck ]] && { echo "pacman is busy, skipping"; exit 0; }
echo "Installing missing kernel headers: ${missing[*]}"
exec pacman -S --needed --noconfirm "${missing[@]}"
EOF
sudo chmod 755 "$HEADERS_SCRIPT" sudo chmod 755 "$HEADERS_SCRIPT"
sudo tee "$HEADERS_UNIT" > /dev/null << EOF sudo rm -rf "$(dirname "$HEADERS_SCRIPT_OLD")"
[Unit] service_file ensure-kernel-headers.service HEADERS_SCRIPT="$HEADERS_SCRIPT" | sudo tee "$HEADERS_UNIT" > /dev/null
Description=Install missing kernel headers so DKMS builds leds-valve for every kernel
Wants=network-online.target
After=network-online.target
[Service]
Type=oneshot
ExecStart=$HEADERS_SCRIPT
[Install]
WantedBy=multi-user.target
EOF
sudo systemctl daemon-reload sudo systemctl daemon-reload
sudo systemctl enable ensure-kernel-headers.service sudo systemctl enable ensure-kernel-headers.service
} }
@@ -431,8 +433,9 @@ machine_disable() {
krevert machine krevert machine
reload_powerdevil reload_powerdevil
sudo systemctl disable ensure-kernel-headers.service 2>/dev/null sudo systemctl disable ensure-kernel-headers.service 2>/dev/null
sudo rm -f "$LED_DKMS_OVERRIDE" "$HEADERS_UNIT" "$HEADERS_SCRIPT" led_dkms_override_remove
sudo rmdir "$(dirname "$HEADERS_SCRIPT")" 2>/dev/null sudo rm -f "$HEADERS_UNIT" "$HEADERS_SCRIPT"
sudo rm -rf "$(dirname "$HEADERS_SCRIPT")" "$(dirname "$HEADERS_SCRIPT_OLD")"
sudo systemctl daemon-reload sudo systemctl daemon-reload
ok "Steam Machine support removed (the AUR helper, if installed, is kept)." ok "Steam Machine support removed (the AUR helper, if installed, is kept)."
} }
+58
View File
@@ -0,0 +1,58 @@
#!/bin/bash
# "Update notifications" menu item (notify), ticked by default: a systemd
# user timer checks GitHub's newest release once a day and at each Plasma
# login. When it's newer than the last Steamify that ran here, it shows a
# notification (Open Steamify / Skip this version) and a tray icon until
# then; it never updates anything itself (patches/steamify-notifier.py).
# Sourced by steamify.sh; not meant to be run on its own.
NOTIFY_SCRIPT="$STEAMIFY_BIN/steamify-notifier"
NOTIFY_UNITS="${XDG_CONFIG_HOME:-$HOME/.config}/systemd/user"
NOTIFY_NAME=steamify-update-check
notify_status() {
[[ -f "$NOTIFY_SCRIPT" ]] &&
systemctl --user is-enabled -q "$NOTIFY_NAME.timer" 2>/dev/null &&
systemctl --user is-enabled -q "$NOTIFY_NAME.service" 2>/dev/null
}
notify_seen() {
# Every run records its version: the notifier compares the newest
# release with it, so opening Steamify ends a reminder. Also whether it
# was the app or the terminal menu: the notification opens the same.
notify_status || return 0
state_set notify seen "$VERSION"
if [[ "$BACKEND" == true ]]; then state_set notify frontend app; else state_set notify frontend terminal; fi
}
notify_enable() {
local p missing=()
info "Adding update notifications..."
# PySide6 for the tray icon, notify-send for the notification's buttons.
python3 -c 'import PySide6.QtWidgets' 2>/dev/null || missing+=(pyside6)
command -v notify-send >/dev/null || missing+=(libnotify)
if ((${#missing[@]})); then
sudo pacman -S --needed --noconfirm "${missing[@]}" ||
{ err "Installing ${missing[*]} failed."; return 1; }
fi
patch_file steamify-notifier.py | install_executable "$NOTIFY_SCRIPT" 755 ||
{ err "Couldn't write $NOTIFY_SCRIPT."; return 1; }
mkdir -p "$NOTIFY_UNITS"
service_file "$NOTIFY_NAME.service" NOTIFY_SCRIPT="$NOTIFY_SCRIPT" > "$NOTIFY_UNITS/$NOTIFY_NAME.service"
service_file "$NOTIFY_NAME.timer" > "$NOTIFY_UNITS/$NOTIFY_NAME.timer"
systemctl --user daemon-reload
systemctl --user enable "$NOTIFY_NAME.service" >/dev/null 2>&1 &&
systemctl --user enable --now "$NOTIFY_NAME.timer" >/dev/null 2>&1 ||
{ err "Enabling the update check failed."; return 1; }
state_set notify seen "$VERSION"
ok "Update notifications on: you'll get a notification when there's a new Steamify."
}
notify_disable() {
systemctl --user disable --now "$NOTIFY_NAME.timer" "$NOTIFY_NAME.service" >/dev/null 2>&1
rm -f "$NOTIFY_UNITS/$NOTIFY_NAME.service" "$NOTIFY_UNITS/$NOTIFY_NAME.timer" "$NOTIFY_SCRIPT"
rmdir "$STEAMIFY_BIN" 2>/dev/null || true
systemctl --user daemon-reload
state_clear notify
ok "Update notifications off."
}
+64 -5
View File
@@ -4,7 +4,7 @@
# are evicted first, so a game that needs most of it doesn't spill into # are evicted first, so a game that needs most of it doesn't spill into
# system RAM and stutter. The kernel side is the dmem cgroup controller # system RAM and stutter. The kernel side is the dmem cgroup controller
# (7.2; amdgpu and xe register their VRAM, NVIDIA's driver doesn't), so it's # (7.2; amdgpu and xe register their VRAM, NVIDIA's driver doesn't), so it's
# only offered when the kernel lists a VRAM region of at least 2 GB. CachyOS packages the userspace # only offered when the kernel lists a VRAM region (vram or vidmem) of at least 2 GB. CachyOS packages the userspace
# side: dmemcg-booster (a system service that enables the controller and # side: dmemcg-booster (a system service that enables the controller and
# sets the limits, plus a user service) and plasma-foreground-booster, which # sets the limits, plus a user service) and plasma-foreground-booster, which
# tells it which window is in front on the desktop. The latter only starts # tells it which window is in front on the desktop. The latter only starts
@@ -13,14 +13,21 @@
VRAM_PKGS=(dmemcg-booster plasma-foreground-booster) VRAM_PKGS=(dmemcg-booster plasma-foreground-booster)
VRAM_MIN_BYTES=$((2 * 1024 * 1024 * 1024)) VRAM_MIN_BYTES=$((2 * 1024 * 1024 * 1024))
# The regions the kernel's dmem controller lists (a file under /sys/fs/cgroup;
# a variable so tests can point it at a copy).
VRAM_CAPACITY="${WIZARD_VRAM_CAPACITY:-/sys/fs/cgroup/dmem.capacity}"
vram_supported() { vram_supported() {
# WIZARD_VRAM_FAKE_NVIDIA=1 (tests): as with an NVIDIA card. # WIZARD_VRAM_FAKE_NVIDIA (tests): as with an NVIDIA card whose driver
# doesn't register its VRAM; see vram_nvidia_case.
[[ -n "${WIZARD_VRAM_FAKE_NVIDIA:-}" ]] && return 1 [[ -n "${WIZARD_VRAM_FAKE_NVIDIA:-}" ]] && return 1
# Only for a GPU with its own VRAM: an integrated GPU registers a small # Only for a GPU with its own VRAM: an integrated GPU registers a small
# carve-out too, but mostly uses system RAM, so there's little to boost. # carve-out too, but mostly uses system RAM, so there's little to boost.
awk -v min="$VRAM_MIN_BYTES" '$1 ~ /\/vram$/ && $2 >= min { found = 1 } END { exit !found }' \ # By what the driver registers, not the brand: amdgpu and xe name the
/sys/fs/cgroup/dmem.capacity 2>/dev/null # region "vram", other drivers "vidmem" (numbered with several), so an
# NVIDIA driver that registers its memory is offered right away.
awk -v min="$VRAM_MIN_BYTES" '$1 ~ /\/(vram|vidmem)[0-9]*$/ && $2 >= min { found = 1 } END { exit !found }' \
"$VRAM_CAPACITY" 2>/dev/null
} }
vram_nvidia() { vram_nvidia() {
@@ -33,7 +40,59 @@ vram_nvidia() {
return 1 return 1
} }
# Shown where it works, and greyed out with an NVIDIA card, whose driver # chwd's lists of NVIDIA cards that need a closed legacy branch (580xx,
# 470xx, 390xx); every other NVIDIA card runs the open kernel modules.
VRAM_CHWD_IDS=/var/lib/chwd/ids
vram_nvidia_case() {
# Why an NVIDIA card's VRAM isn't registered, so the menu can say what to
# do: "update" (open modules older than 615), "switch <chwd profile>"
# (closed driver on a card the open one supports), "legacy" (card only
# runs a closed legacy branch), "nouveau" (no NVIDIA module).
# WIZARD_VRAM_FAKE_NVIDIA=1 fakes "switch nvidia-dkms-580xx", or name a case.
local fake="${WIZARD_VRAM_FAKE_NVIDIA:-}" license d id p
if [[ -n "$fake" ]]; then
[[ "$fake" == 1 ]] && fake="switch nvidia-dkms-580xx"
echo "$fake"; return
fi
license="$(modinfo -F license nvidia 2>/dev/null)"
case "$license" in
"Dual MIT/GPL") echo update; return ;;
"") echo nouveau; return ;;
esac
for d in /sys/bus/pci/devices/*; do
[[ "$(cat "$d/vendor" 2>/dev/null)" == 0x10de && "$(cat "$d/class" 2>/dev/null)" == 0x03* ]] || continue
id="$(cat "$d/device")"; id="${id#0x}"
grep -qwi "$id" "$VRAM_CHWD_IDS"/nvidia-*.ids 2>/dev/null && { echo legacy; return; }
done
for p in 580xx 470xx 390xx; do
pacman -Q "nvidia-$p-dkms" >/dev/null 2>&1 && { echo "switch nvidia-dkms-$p"; return; }
done
echo switch
}
vram_nvidia_hint() {
# vram_nvidia_hint <case>: the short reason (terminal menu, the app's row).
case "$1" in
update) echo "update NVIDIA's driver to 615 or newer (sudo pacman -Syu)" ;;
switch*) echo "needs NVIDIA's open driver, which your card supports" ;;
legacy) echo "your NVIDIA card's driver doesn't support it (older than RTX 20)" ;;
*) echo "the nouveau driver doesn't support it yet" ;;
esac
}
vram_nvidia_note() {
# vram_nvidia_note <case>: the full explanation, with what to do.
local profile="${1#switch}"; profile="${profile# }"
case "$1" in
update) echo "NVIDIA's open driver tells Linux how its video memory is used from version 615 on; yours is older ($(cat /sys/module/nvidia/version 2>/dev/null || echo unknown)). Update your system (sudo pacman -Syu), restart, and it's available here." ;;
switch*) echo "Your card also runs NVIDIA's open driver, the one CachyOS installs by default, and only that one tells Linux how its video memory is used. You're using NVIDIA's closed driver. Switch in a terminal with: ${profile:+sudo chwd -r $profile && }sudo chwd -i nvidia-open-dkms, then restart: it's available here after that. Steamify doesn't switch it for you: if something goes wrong, the screen stays black after the restart." ;;
legacy) echo "Your NVIDIA card is older than the RTX 20 series and only runs NVIDIA's closed driver, which doesn't tell Linux how its video memory is used. Newer NVIDIA cards (RTX 20 series and up), and AMD and Intel graphics cards, support it." ;;
*) echo "The open-source nouveau driver doesn't tell Linux how its video memory is used yet (a patch is on its way into the kernel). NVIDIA's own open driver does, from version 615 on (sudo chwd -a installs the right one)." ;;
esac
}
# Shown where it works, and greyed out with an NVIDIA card whose driver
# doesn't register its VRAM with the kernel (yet): so it's clear why it # doesn't register its VRAM with the kernel (yet): so it's clear why it
# can't be turned on. Hidden elsewhere (integrated graphics, older kernel). # can't be turned on. Hidden elsewhere (integrated graphics, older kernel).
vram_available() { vram_supported || vram_status || vram_nvidia; } vram_available() { vram_supported || vram_status || vram_nvidia; }
+4 -4
View File
@@ -10,9 +10,9 @@ WIZARD_RELEASE="https://github.com/theupriser/steamify-cachyos/releases/latest/d
WIZARD_URL="$WIZARD_RELEASE/steamify.sh" WIZARD_URL="$WIZARD_RELEASE/steamify.sh"
WIZARD_APP_URL="$WIZARD_RELEASE/steamify-app.sh" WIZARD_APP_URL="$WIZARD_RELEASE/steamify-app.sh"
# Steam logo with a gear (assets/ in the repo, published with every release). # Steam logo with a gear (assets/ in the repo, published with every release).
WIZARD_ICON="${XDG_DATA_HOME:-$HOME/.local/share}/icons/hicolor/scalable/apps/cachyos-gamescope-boot-wizard.svg" WIZARD_ICON="${XDG_DATA_HOME:-$HOME/.local/share}/icons/hicolor/scalable/apps/steamify.svg"
WIZARD_LAUNCHER="${XDG_DATA_HOME:-$HOME/.local/share}/cachyos-gamescope-boot/run-wizard" WIZARD_LAUNCHER="$STEAMIFY_BIN/run-wizard"
WIZARD_APP_LAUNCHER="${XDG_DATA_HOME:-$HOME/.local/share}/cachyos-gamescope-boot/run-app" WIZARD_APP_LAUNCHER="$STEAMIFY_BIN/run-app"
WIZARD_APPS="${XDG_DATA_HOME:-$HOME/.local/share}/applications" WIZARD_APPS="${XDG_DATA_HOME:-$HOME/.local/share}/applications"
# steamify-ui: the id the app gives itself, so its window belongs to this # steamify-ui: the id the app gives itself, so its window belongs to this
# entry. steamify-app.sh leaves an entry with X-Steamify-Shortcut alone. # entry. steamify-app.sh leaves an entry with X-Steamify-Shortcut alone.
@@ -91,7 +91,7 @@ EOF
tmp="$(mktemp)" tmp="$(mktemp)"
if curl -fsSL --max-time 20 "$WIZARD_RELEASE/steam-gaming-settings.svg" -o "$tmp" && grep -q '<svg' "$tmp"; then if curl -fsSL --max-time 20 "$WIZARD_RELEASE/steam-gaming-settings.svg" -o "$tmp" && grep -q '<svg' "$tmp"; then
install -D -m 644 "$tmp" "$WIZARD_ICON" install -D -m 644 "$tmp" "$WIZARD_ICON"
icon=cachyos-gamescope-boot-wizard icon=steamify
gtk-update-icon-cache -q -f -t "${XDG_DATA_HOME:-$HOME/.local/share}/icons/hicolor" 2>/dev/null || true gtk-update-icon-cache -q -f -t "${XDG_DATA_HOME:-$HOME/.local/share}/icons/hicolor" 2>/dev/null || true
else else
warn "Couldn't download the shortcut's icon; using Steam's icon instead." warn "Couldn't download the shortcut's icon; using Steam's icon instead."
+6 -1
View File
@@ -2,9 +2,14 @@
Files Steamify builds or applies, kept out of the shell code so they can be 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 read and reviewed on their own. `lib/*.sh` reads them with `patch_file
<name>`; the single-file build (`.github/tools/bundle.sh`) embeds them. <name>` (piped through `fill KEY=value...` for `@KEY@` placeholders); the single-file build (`.github/tools/bundle.sh`) embeds them.
| File | Used by | What | | 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 | | `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 |
| `sync-steamos-session.sh` | `lib/login-manager.sh` (SteamOS conversion, plasma-login-manager) | The sync bridge: copies Steam's session choice from `zz-steamos-autologin.conf` into `plasmalogin.conf`'s `[Autologin]` |
| `ensure-kernel-headers.sh` | `lib/steam-machine.sh` (Steam Machine support) | Run at boot: installs missing headers for every installed kernel |
| `leds-valve-dkms.conf` | `lib/steam-machine.sh` (Steam Machine support) | `/etc/dkms` override: build the LED driver for DKMS's target kernel; appended to someone's own override, which is backed up and restored |
| `steamify-fremont-poweroff.dkms.conf`, `steamify-cros-ec-cec.dkms.conf` | `lib/fremont-poweroff.sh`, `lib/cec.sh` | The DKMS modules' `dkms.conf` (`fill NAME=... VERSION=...`) |
| `steamify-notifier.py` | `lib/update-notifier.sh` (Update notifications) | The daily check: notification and tray icon when there's a new release |
| `cros-ec-cec-single-port.patch` | `lib/cec.sh` (HDMI-CEC) | Makes Valve's Steam Machine CEC driver find amdgpu's HDMI port | | `cros-ec-cec-single-port.patch` | `lib/cec.sh` (HDMI-CEC) | Makes Valve's Steam Machine CEC driver find amdgpu's HDMI port |
+14
View File
@@ -0,0 +1,14 @@
#!/bin/bash
# Installed by Steamify: install missing -headers for every
# installed kernel, so DKMS builds leds-valve for it.
set -u
missing=()
for k in $(pacman -Qqo /usr/lib/modules/*/pkgbase 2>/dev/null | sort -u); do
pacman -Q "${k}-headers" >/dev/null 2>&1 && continue
pacman -Si "${k}-headers" >/dev/null 2>&1 && missing+=("${k}-headers")
done
[[ ${#missing[@]} -eq 0 ]] && exit 0
# Another pacman is running (e.g. an update): try again next boot.
[[ -e /var/lib/pacman/db.lck ]] && { echo "pacman is busy, skipping"; exit 0; }
echo "Installing missing kernel headers: ${missing[*]}"
exec pacman -S --needed --noconfirm "${missing[@]}"
+2
View File
@@ -0,0 +1,2 @@
# Written by Steamify: build leds-valve for DKMS's target kernel, not the running one.
MAKE[0]="make KVERSION=${kernelver}"
+6
View File
@@ -0,0 +1,6 @@
# Written by Steamify: Valve's cros_ec_cec, which knows the Steam Machine.
PACKAGE_NAME="@NAME@"
PACKAGE_VERSION="@VERSION@"
BUILT_MODULE_NAME[0]="cros-ec-cec"
DEST_MODULE_LOCATION[0]="/updates/dkms"
AUTOINSTALL="yes"
@@ -0,0 +1,6 @@
# Written by Steamify: the Steam Machine powers off instead of booting up again.
PACKAGE_NAME="@NAME@"
PACKAGE_VERSION="@VERSION@"
BUILT_MODULE_NAME[0]="@NAME@"
DEST_MODULE_LOCATION[0]="/updates/dkms"
AUTOINSTALL="yes"
+145
View File
@@ -0,0 +1,145 @@
#!/usr/bin/env python3
# Steamify CachyOS: update notifications (installed by the "Update
# notifications" menu item, run by a systemd user timer and at each Plasma
# login). Never updates anything itself: when GitHub has a newer release
# than the last Steamify that ran here, it shows a notification (Open
# Steamify / Skip this version) and a tray icon until you open the app,
# skip the version or pick "Remind me later". Nothing runs in between.
import configparser
import json
import os
import subprocess
import sys
import threading
import time
import urllib.request
REPO = "theupriser/steamify-cachyos"
STATE_DIR = os.path.join(os.environ.get("XDG_STATE_HOME") or os.path.expanduser("~/.local/state"),
"steamify")
STATE = os.path.join(STATE_DIR, "notify.state")
DATA = os.environ.get("XDG_DATA_HOME") or os.path.expanduser("~/.local/share")
RUN_APP = os.path.join(DATA, "steamify", "bin", "run-app")
RUN_TERMINAL = os.path.join(DATA, "steamify", "bin", "run-wizard")
RELEASE = f"https://github.com/{REPO}/releases/latest/download"
def version(v):
try:
return tuple(int(p) for p in v.lstrip("v").split("."))
except ValueError:
return None
def state(key):
cp = configparser.ConfigParser(interpolation=None)
cp.read(STATE)
return cp.get("State", key, fallback="")
def set_state(key, value):
# Same ini as lib/state.sh's state_set writes.
subprocess.run(["kwriteconfig6", "--file", STATE, "--group", "State", "--key", key, value], check=False)
def latest():
req = urllib.request.Request(f"https://api.github.com/repos/{REPO}/releases/latest",
headers={"User-Agent": "steamify-notifier", "Accept": "application/vnd.github+json"})
with urllib.request.urlopen(req, timeout=30) as r:
return json.load(r)["tag_name"].lstrip("v")
def pending(new):
# Newer than the last Steamify that ran here, and not skipped.
seen = version(state("seen") or "0")
return seen is not None and version(new) > seen and new != state("skipped")
def desktop():
# Only on the Plasma desktop: gamescope doesn't show notifications or a
# tray, so an update found in gaming mode waits for the next desktop login.
return subprocess.run(["pgrep", "-u", str(os.getuid()), "-x", "plasmashell"],
stdout=subprocess.DEVNULL).returncode == 0
def main():
if "--delay" in sys.argv:
time.sleep(int(sys.argv[sys.argv.index("--delay") + 1]))
if not desktop():
return 0
try:
new = latest()
except Exception as e: # offline, rate limited: try again next time
print(f"Couldn't check for a new Steamify: {e}", file=sys.stderr)
return 0
if version(new) is None or not pending(new):
return 0
from PySide6.QtCore import QObject, QTimer, Signal
from PySide6.QtGui import QAction, QIcon
from PySide6.QtWidgets import QApplication, QMenu, QSystemTrayIcon
app = QApplication(sys.argv[:1])
app.setApplicationName("Steamify")
app.setDesktopFileName("steamify-ui")
app.setQuitOnLastWindowClosed(False)
icon = QIcon.fromTheme("steamify", QIcon.fromTheme("steam"))
def open_app():
# The way Steamify was last used (the app or the terminal menu), the
# newest release either way; it records its version as seen, which
# ends the reminder. In its own unit: this one's processes are
# stopped when the check exits.
if state("frontend") == "terminal":
cmd = ["konsole", "-e", RUN_TERMINAL] if os.access(RUN_TERMINAL, os.X_OK) else \
["konsole", "-e", "bash", "-c",
f"set -o pipefail; curl -fsSL --max-time 30 {RELEASE}/steamify.sh | bash; read -rp 'Press Enter to close. ' _"]
else:
cmd = [RUN_APP] if os.access(RUN_APP, os.X_OK) else \
["bash", "-c", f"set -o pipefail; curl -fsSL --max-time 30 {RELEASE}/steamify-app.sh | bash"]
# A scope lives as long as anything in it: the start script puts the
# app in the background and exits.
subprocess.Popen(["systemd-run", "--user", "--scope", "--collect", "--quiet", *cmd],
start_new_session=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
app.quit()
def skip():
set_state("skipped", new)
app.quit()
tray = QSystemTrayIcon(icon)
tray.setToolTip(f"Steamify {new} is available")
menu = QMenu()
for text, fn in ((f"Open Steamify {new}", open_app), ("Skip this version", skip), ("Remind me later", app.quit)):
a = QAction(text, menu)
a.triggered.connect(fn)
menu.addAction(a)
tray.setContextMenu(menu)
tray.activated.connect(lambda reason: open_app() if reason == QSystemTrayIcon.ActivationReason.Trigger else None)
tray.show()
# The notification's buttons: notify-send waits for the click, so it
# runs beside the event loop. Closing it without a click keeps the icon.
class Relay(QObject):
picked = Signal(str)
relay = Relay()
relay.picked.connect(lambda a: open_app() if a == "open" else skip() if a == "skip" else None)
def notify():
r = subprocess.run(["notify-send", "--app-name=Steamify", "--icon=steamify",
"-A", "open=Open Steamify", "-A", "skip=Skip this version",
f"Steamify {new} is available",
"Open Steamify to see what's new and choose what to update."],
capture_output=True, text=True)
relay.picked.emit(r.stdout.strip())
threading.Thread(target=notify, daemon=True).start()
# Opened some other way (the shortcut, the terminal): the reminder is done.
poll = QTimer()
poll.timeout.connect(lambda: None if pending(new) else app.quit())
poll.start(60_000)
return app.exec()
if __name__ == "__main__":
sys.exit(main())
+16
View File
@@ -0,0 +1,16 @@
#!/bin/bash
SRC="/etc/plasmalogin.conf.d/zz-steamos-autologin.conf"
DEST="/etc/plasmalogin.conf"
[[ -f "$SRC" ]] || exit 0
SESSION=$(grep -oP '^Session=\K.*' "$SRC")
[[ -z "$SESSION" ]] && exit 0
# Only touch Session= inside [Autologin]; other sections may have their own.
if sed -n '/^\[Autologin\]/,/^\[/p' "$DEST" | grep -q '^Session='; then
sed -i "/^\[Autologin\]/,/^\[/ s|^Session=.*|Session=$SESSION|" "$DEST"
elif grep -q '^\[Autologin\]' "$DEST"; then
sed -i "/^\[Autologin\]/a Session=$SESSION" "$DEST"
else
printf '\n[Autologin]\nSession=%s\n' "$SESSION" >> "$DEST"
fi
+16
View File
@@ -0,0 +1,16 @@
# Services
The systemd units Steamify installs, kept out of the shell code so they can
be read and reviewed on their own. `lib/*.sh` reads them with `service_file
<name> [KEY=value...]`, which fills in `@KEY@` placeholders (paths the
scripts define); the single-file build (`.github/tools/bundle.sh`) embeds
them. Small drop-ins that only add a line or two to someone else's unit stay
in the shell code.
| File | Used by | What |
|---|---|---|
| `steamify-boot-desktop.service` | `lib/boot-session.sh` (Boot into: desktop) | Sets the Plasma session before the login manager starts |
| `sync-steamos-session.path`, `.service` | `lib/login-manager.sh` (SteamOS conversion, plasma-login-manager) | The sync bridge: copies Steam's session choice into `plasmalogin.conf` |
| `steam-desktop-autostart.service` | `lib/steam-desktop.sh` (SteamOS conversion) | Steam in the background on the Plasma desktop (user unit) |
| `ensure-kernel-headers.service` | `lib/steam-machine.sh` (Steam Machine support) | Installs missing kernel headers at boot, so DKMS builds for every kernel |
| `steamify-update-check.service`, `.timer` | `lib/update-notifier.sh` (Update notifications) | The daily and login check for a new release (user units) |
+11
View File
@@ -0,0 +1,11 @@
[Unit]
Description=Install missing kernel headers so DKMS builds leds-valve for every kernel
Wants=network-online.target
After=network-online.target
[Service]
Type=oneshot
ExecStart=@HEADERS_SCRIPT@
[Install]
WantedBy=multi-user.target
+15
View File
@@ -0,0 +1,15 @@
[Unit]
Description=Steam Background Autostart for Virtual Keyboard
After=graphical-session.target
PartOf=graphical-session.target
[Service]
Type=simple
# Only in the Plasma desktop session; gamescope-session starts its own Steam.
ExecCondition=/bin/sh -c '[ "$XDG_CURRENT_DESKTOP" = KDE ]'
ExecStart=/usr/bin/steam -silent
Restart=on-failure
RestartSec=5
[Install]
WantedBy=graphical-session.target
+11
View File
@@ -0,0 +1,11 @@
[Unit]
Description=Steamify CachyOS: boot into the desktop instead of gaming mode
Before=display-manager.service sddm.service plasmalogin.service
[Service]
Type=oneshot
ExecStart=/usr/lib/steamos/steam-set-session plasma.desktop
ExecStart=/bin/sh -c '[ -x @SESSION_SYNC@ ] && exec @SESSION_SYNC@ || true'
[Install]
WantedBy=graphical.target
+15
View File
@@ -0,0 +1,15 @@
[Unit]
Description=Steamify: check for a new release
After=plasma-workspace.target
# The tray icon goes with a logout.
PartOf=graphical-session.target
[Service]
Type=simple
# The short wait at login lets the tray come up first (in the script: an
# ExecStartPre sleep runs into the start timeout).
# Quoted: the path is in the home folder, which may contain a space.
ExecStart="@NOTIFY_SCRIPT@" --delay 60
[Install]
WantedBy=plasma-workspace.target
+10
View File
@@ -0,0 +1,10 @@
[Unit]
Description=Steamify: check for a new release daily
[Timer]
OnCalendar=daily
RandomizedDelaySec=1h
Persistent=true
[Install]
WantedBy=timers.target
+8
View File
@@ -0,0 +1,8 @@
[Unit]
Description=Watch for steamos session changes
[Path]
PathModified=/etc/plasmalogin.conf.d/zz-steamos-autologin.conf
[Install]
WantedBy=multi-user.target
+9
View File
@@ -0,0 +1,9 @@
[Unit]
Description=Sync steamos session selection into plasmalogin.conf
# Session switches can come in bursts (switch + autologin reset); never
# let systemd's start rate limit silently stop the bridge.
StartLimitIntervalSec=0
[Service]
Type=oneshot
ExecStart=/usr/local/bin/sync-steamos-session.sh
+5 -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.4.1 VERSION=2.5.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 fremont-poweroff vram-booster hdmi-refresh cec boot-session vapor-theme steamos-extras bios desktop-shortcut wizard-shortcut menu backend; do for lib in common state packages login-manager single-user steam-desktop steam-machine fremont-poweroff vram-booster hdmi-refresh cec boot-session vapor-theme steamos-extras bios desktop-shortcut wizard-shortcut update-notifier 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
@@ -43,6 +43,8 @@ fi
# Everything per-user (autologin user, Steam, theme, shortcut) is for the # Everything per-user (autologin user, Steam, theme, shortcut) is for the
# user running the script. # user running the script.
TARGET_USER="$(id -un)" TARGET_USER="$(id -un)"
migrate_layout
notify_seen
restart_needed() { [[ -n "${BIOS_NEEDS_RESTART:-}" || "$RESTART_FOR_LOGIN" == true ]]; } restart_needed() { [[ -n "${BIOS_NEEDS_RESTART:-}" || "$RESTART_FOR_LOGIN" == true ]]; }
@@ -125,6 +127,7 @@ while true; do
plan_changes plan_changes
if [[ ${#TO_DISABLE[@]} -eq 0 && ${#TO_ENABLE[@]} -eq 0 ]]; then if [[ ${#TO_DISABLE[@]} -eq 0 && ${#TO_ENABLE[@]} -eq 0 ]]; then
feature_record_unticked
ok "Everything is already the way you want it." ok "Everything is already the way you want it."
read -rp "Press Enter to go back to the menu... " _ || break read -rp "Press Enter to go back to the menu... " _ || break
continue continue
+8 -4
View File
@@ -21,8 +21,11 @@ QtObject {
launcher: { label: "Steamify shortcut", hint: "The app on the desktop and in the launcher", launcher: { label: "Steamify shortcut", hint: "The app on the desktop and in the launcher",
body: "Opens the newest Steamify app, so you never need the install command again. Steamify Terminal in the launcher opens the terminal menu.", body: "Opens the newest Steamify app, so you never need the install command again. Steamify Terminal in the launcher opens the terminal menu.",
changes: ["Steamify CachyOS on the desktop and in the launcher (the app)", "Steamify Terminal in the launcher"] }, changes: ["Steamify CachyOS on the desktop and in the launcher (the app)", "Steamify Terminal in the launcher"] },
notify: { label: "Update notifications", hint: "A notification when there's a new Steamify",
body: "Checks once a day and when you log in to the desktop. When there's a new Steamify you get a notification and a tray icon: open the app to see what's new and choose what to update, or skip that version. It never updates anything by itself.",
changes: ["A daily check (systemd user timer), only on the desktop", "Tray icon only while an update is waiting", "Skip this version: quiet until the next one"] },
vram: { label: "VRAM booster", hint: "The game in front keeps its VRAM", vram: { label: "VRAM booster", hint: "The game in front keeps its VRAM",
body: "Like SteamOS 3.9: the game you're playing keeps its video memory, and background apps are moved out first. Without it a game that needs most of the video memory can spill into system RAM and stutter. Shown for GPUs whose driver supports it (AMD, Intel) on kernel 7.2 or newer.", body: "Like SteamOS 3.9: the game you're playing keeps its video memory, and background apps are moved out first. Without it a game that needs most of the video memory can spill into system RAM and stutter. Shown for GPUs whose driver supports it (AMD, Intel, NVIDIA with its open kernel modules from driver 615) on kernel 7.2 or newer.",
changes: ["dmemcg-booster (system and user service)", "plasma-foreground-booster, which tells it which window is in front", "Off: removed again, unless you had installed them yourself"] }, changes: ["dmemcg-booster (system and user service)", "plasma-foreground-booster, which tells it which window is in front", "Off: removed again, unless you had installed them yourself"] },
cec: { label: "HDMI-CEC", hint: "Use Steam with the TV remote, TV on/off with the PC", experimental: true, cec: { label: "HDMI-CEC", hint: "Use Steam with the TV remote, TV on/off with the PC", experimental: true,
body: "Use Steam with your TV remote, and the TV turns on and off with the PC. Turn on CEC on the TV too (Sony: BRAVIA Sync, Samsung: Anynet+, LG: SimpLink).", body: "Use Steam with your TV remote, and the TV turns on and off with the PC. Turn on CEC on the TV too (Sony: BRAVIA Sync, Samsung: Anynet+, LG: SimpLink).",
@@ -45,8 +48,8 @@ QtObject {
}) })
// Replaces the explanation of an option that can't be turned on here. // Replaces the explanation of an option that can't be turned on here.
readonly property var unsupported: ({ readonly property var unsupported: ({
vram: { body: "Not available with your NVIDIA graphics card: NVIDIA's driver doesn't tell Linux how its video memory is used yet, so there's nothing to steer. It works with AMD and Intel graphics cards, and shows up here as soon as NVIDIA's driver supports it.", // The body comes from the backend (item.note): it depends on the driver.
changes: ["Nothing: can't be turned on with NVIDIA's driver"] } vram: { changes: ["Nothing until then"] }
}) })
// Per plan action: the review's chip [text, colour, background] and the // Per plan action: the review's chip [text, colour, background] and the
@@ -60,7 +63,8 @@ QtObject {
function of(item) { function of(item) {
var tx = items[item.id] || {}; var tx = items[item.id] || {};
return item.selectable === false && unsupported[item.id] ? Object.assign({}, tx, unsupported[item.id]) : tx; if (item.selectable !== false || !unsupported[item.id]) return tx;
return Object.assign({}, tx, unsupported[item.id], item.note ? { body: item.note } : {});
} }
function label(id, fallback) { return (items[id] || {}).label || fallback || id; } function label(id, fallback) { return (items[id] || {}).label || fallback || id; }
} }
+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, lib/ and patches/) to ~/.local/share/steamify/app, installs PySide6 if # steamify.sh, lib/, patches/ and services/) 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>