Compare commits

...
27 Commits
Author SHA1 Message Date
theupriser b01e2c5221 Merge pull request #35 from theupriser/release/2.7.0
Release 2.7.0
2026-09-28 10:57:45 +02:00
theupriser affb5f79b0 Merge pull request #34 from theupriser/bugfix/theme-off-single-launcher
fix: theme off no longer brings back single user's launcher restriction
2026-09-28 10:53:19 +02:00
theupriser 58e43d4c4a fix: turning the theme off no longer brings back single user's launcher restriction when single user was turned off in between 2026-09-28 10:52:28 +02:00
theupriser 23f265b02f Merge pull request #33 from theupriser/feature/remove-v2-ui-preview
chore: remove the v2 app preview
2026-09-28 10:47:10 +02:00
theupriser ced4a45d2f chore: the v2 app preview is gone (it is the released app): no preview pre-release step in CI, no mention in steamify-app.sh 2026-09-28 10:46:23 +02:00
theupriser 6ad686a8b2 fix: Merge branch 'feature/gh-required' into release/2.7.0 2026-09-28 10:19:06 +02:00
theupriser b1adacbcb3 docs: gh is required for the release-branch pull requests; ask the user to install it 2026-09-28 10:18:59 +02:00
theupriser 8dd2db3a97 fix: Merge branch 'feature/release-branches' into release/2.7.0 2026-09-28 10:18:06 +02:00
theupriser 501c0ba425 docs: gh or git for merging into the release branch 2026-09-28 10:17:59 +02:00
theupriser bee18b76a8 docs: the agent merges feature/bugfix branches into the release branch; the release into main stays the user's 2026-09-28 10:17:41 +02:00
theupriser eadbdda271 docs: release branches (release/X.Y.Z, feature/ and bugfix/ branches into it, the release into main); CI builds release/** pushes 2026-09-28 10:14:20 +02:00
theupriser c4ec755a4a feat: --defaults --options <ids> (exactly these on) replaces --skip; steamify.sh --boot gamescope|desktop on its own switches where an installed conversion starts 2026-09-28 10:06:45 +02:00
theupriser 14aa7c0b54 fix: the app's progress screen says "Boot into desktop", not "Boot into Boot into" 2026-09-28 10:06:32 +02:00
theupriser 5bf9da411d fix: HDMI-CEC enables cec-audio-control.socket (the TV remote's volume keys); existing installs get it as an update 2026-09-28 10:06:23 +02:00
theupriser 4f6e6fd0aa feat: --defaults takes --skip <ids> and --boot gamescope|desktop, for the Steam Machine ISO's installer pages 2026-09-27 21:59:08 +02:00
theupriser a79eeedbc4 Merge pull request #32 from theupriser/feat/defaults-mode
feat: --defaults applies the default setup without the menu, also fro…
2026-09-27 21:25:03 +02:00
theupriser b948c2020b feat: --defaults applies the default setup without the menu, also from an installer (no session): the base for a Steam Machine ISO
systemctl --user goes through user_systemctl, which only changes unit files
without a session; the theme lets Plasma build Vapor's layout at the first
login when the layout is still /etc/skel's; a one-time first-login autostart
sets up single user's launcher and opens the app.
2026-09-27 19:21:28 +02:00
theupriser 0c96bed448 Merge pull request #31 from theupriser/docs/screenshot-2.5.2
docs: README screenshot at 2.5.2: everything on, nothing to update
2026-09-27 18:53:56 +02:00
theupriser e3f7cc9773 docs: README screenshot at 2.5.2: everything on, nothing to update 2026-09-27 18:53:23 +02:00
theupriser 353de489d7 Merge pull request #30 from theupriser/feat/apply-disabled
2.5.1: Add as non-Steam game, controller and apply button
2026-09-27 18:49:20 +02:00
theupriser d8b2e812c5 fix: Add as non-Steam game asks Steam to close again every 15 s (a Steam that was still starting ignored it) 2026-09-27 18:42:12 +02:00
theupriser 3a0692fdf2 docs: README screenshot at 2.5.1 with Add as non-Steam game 2026-09-27 18:17:41 +02:00
theupriser e86ddcc4af feat: Add as non-Steam game: Steamify in the Steam library, under the shortcut; controller works when started from Steam 2026-09-27 18:16:14 +02:00
theupriser 389bc3346e fix(ui): B never quits (Steam sends Esc for it), Ctrl+Q does; started from Steam, the hints show the Steam Controller's buttons 2026-09-27 17:56:57 +02:00
theupriser c6c1150836 fix(ui): Enter selects (a Steam Controller's A in Steam's desktop layout); Ctrl+Enter reviews and applies 2026-09-27 17:54:55 +02:00
theupriser 98e2effcff fix(ui): Started from Steam, the app runs without Steam's overlay (Qt aborted) and stays in the foreground, so Steam Input gives it the controller 2026-09-27 17:44:38 +02:00
theupriser 431515b60f feat(ui): Review & apply greyed out while nothing would change; the Done screen shows why a run stopped 2026-09-27 17:11:46 +02:00
33 changed files with 790 additions and 88 deletions

No files matched your search

+3 -25
View File
@@ -5,18 +5,16 @@
# release are moved to it, so both of these always get the newest version:
# 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,
# Each release also has the app: steamify-app.tar.gz (ui/, steamify.sh,
# 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
# A push to feat/v2-ui (the app, not released yet) updates the pre-release
# v2-ui-preview instead, whose launcher gets that build:
# curl -fsSL https://github.com/theupriser/steamify-cachyos/releases/download/v2-ui-preview/steamify-app.sh | bash
# Pushes to release/** build and check it like pull requests; only main publishes.
# Pull requests only build and check it, and warn when VERSION is already released.
name: Single-file build
on:
push:
branches: [main, feat/v2-ui]
branches: [main, 'release/**']
pull_request:
workflow_dispatch:
@@ -106,23 +104,3 @@ jobs:
gh release create latest --title "Steamify CachyOS - latest (v$VERSION)" --notes "$latest_notes" --latest=false
fi
gh release upload latest dist/steamify.sh dist/steamify-app.sh dist/steamify-app.tar.gz assets/steam-gaming-settings.svg --clobber
- name: Publish the v2-ui-preview pre-release
if: github.event_name == 'push' && github.ref == 'refs/heads/feat/v2-ui'
env:
GH_TOKEN: ${{ github.token }}
run: |
# Its launcher downloads this pre-release, not the newest release.
sed -i 's/STEAMIFY_RELEASE:-latest/STEAMIFY_RELEASE:-v2-ui-preview/' dist/steamify-app.sh
grep -q 'STEAMIFY_RELEASE:-v2-ui-preview' dist/steamify-app.sh
git tag -f v2-ui-preview "$GITHUB_SHA" && git push -f origin v2-ui-preview
notes="Preview of the Steamify app (v2) from feat/v2-ui ($(git describe --always), $(git log -1 --format='%cs %s')). Not a release.
Run it with:
curl -fsSL https://github.com/${{ github.repository }}/releases/download/v2-ui-preview/steamify-app.sh | bash"
if gh release view v2-ui-preview >/dev/null 2>&1; then
gh release edit v2-ui-preview --title "Steamify app (v2) preview" --notes "$notes" --prerelease --latest=false
else
gh release create v2-ui-preview --target "$GITHUB_SHA" --title "Steamify app (v2) preview" --notes "$notes" --prerelease --latest=false
fi
gh release upload v2-ui-preview dist/steamify-app.sh dist/steamify-app.tar.gz --clobber
+54 -3
View File
@@ -98,11 +98,36 @@ gamescope and the Plasma desktop. Primary target: the Valve Steam Machine
+ subject; a commit can't contain its own hash, so fill it in with the
next commit). The section heading must be `## <VERSION> - <date>`: CI
cuts the release notes out of the changelog by that heading.
- Every PR that should be released bumps `VERSION` and adds its section.
A push to `main` publishes release `v$VERSION` (tag + bundle + that
- **Branches: one release branch per version.** Work for a release goes
into `release/X.Y.Z` (e.g. `release/2.7.0`), branched from `main` when
that version starts. That branch bumps `VERSION` to `X.Y.Z` and adds the
`## X.Y.Z - <date>` changelog section (first commit on it).
- Every change gets its own branch from the release branch:
`feature/<name>` (new behaviour) or `bugfix/<name>` (a fix), and a
pull request **into the release branch**, not into `main`. Its commits
add their changelog lines to that version's section.
- When the release is done and tested (the test plan in
steamify-cachyos-dev, `TESTPLAN.md`), the release branch gets a pull
request **into `main`**. Merging it publishes the release (below).
- A fix for a released version: a new `release/X.Y.Z+1` from `main`,
with the `bugfix/` branch into it; don't reopen an old release branch.
- Up to the release branch, the agent manages it: create the
`feature/`/`bugfix/` branches, commit, push, and merge them into the
release branch itself once tested (with `gh`: `gh pr create --base
release/X.Y.Z` + `gh pr merge --merge`; no review needed). Without
`gh` (`command -v gh`), ask the user to install it (Ubuntu:
`sudo apt install gh`, then `gh auth login`) instead of working around
it. Only the
release branch's pull request into `main` is the user's: never commit
or merge to `main`, the user merges that one.
Keep a feature/bugfix branch up to date by merging (or rebasing on)
its release branch, the release branch by merging `main` when that
moved.
- A push to `main` publishes release `v$VERSION` (tag + bundle + that
changelog section) and marks it latest. An existing version is never
overwritten: without a bump nothing is released, and pull requests show
a warning.
a warning when `VERSION` is a version that's already released (so a
release branch that forgot its bump shows it on every PR).
- 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
@@ -243,6 +268,15 @@ gamescope and the Plasma desktop. Primary target: the Valve Steam Machine
`linux-cachyos` is clang-built, `-bore` GCC-built: let DKMS pick the
compiler, never pass `LLVM=1`. Test shutdown on the real machine for every
new major kernel.
- Add as non-Steam game (`steamgame`, `lib/steam-game.sh`, sub-option of
`launcher`, ticked along with it, 2.5.1): edits Steam's `shortcuts.vdf`
with `patches/steam-shortcuts.py` only while Steam is closed (Steam
rewrites it on exit), then starts Steam again. Never close Steam in gaming
mode or when `SteamGameId` is set (the run is a Steam game). An existing
entry for the start script is updated, not doubled; removal takes every
entry for it. Controller input through Steam's desktop layout arrives as
keys: Enter must select (A), Esc must never quit (B); the app re-execs
without Steam's overlay preload.
- 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
@@ -260,6 +294,23 @@ gamescope and the Plasma desktop. Primary target: the Valve Steam Machine
latter only starts with `kcgroupsrc [Foreground Booster] autostart=true`
(set with `kset`). Disable removes only the packages it installed
(`state_get vram installed_pkgs`).
- Install-time mode (`steamify.sh --defaults`, 2.6.0): applies what the menu
would preselect, no menu or prompts (needs passwordless sudo). The Steam
Machine ISO ([steammachine-cachyos-live-iso](https://github.com/theupriser/steammachine-cachyos-live-iso))
runs it in the installer for a user who has never logged in: no session
bus, user systemd or plasmashell. So every `systemctl --user` goes through
`user_systemctl` (`lib/common.sh`; without a session only unit files change,
via `--root=/`), and the theme, when the layout is still `/etc/skel`'s,
removes it so Plasma builds Vapor's layout at the first login. What needs
that layout (single user's launcher) runs then, from a one-time autostart
(`lib/first-login.sh`, `--first-login`) that also opens the app.
`--options <id>,...` (exactly these on, the rest off; an item on brings its
parent) and `--boot gamescope|desktop` (desktop only with `gaming`) (2.7.0,
`defaults_options` in `lib/menu.sh`); the ISO's installer page passes them.
Items this PC can't use are left off with a warning, not an error.
`steamify.sh --boot gamescope|desktop` on its own (2.7.0, for scripts) only
switches `boot` on an installed conversion: `WANTED` is `CURRENT` plus
that, so no updates or removals a normal run would pick.
- `Relogin=true` means a gamescope that fails to start is relaunched in a
tight loop; keep that in mind when changing session handling.
+31
View File
@@ -5,6 +5,37 @@ 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.7.0 - 2026-09-28
- `4f6e6fd` **feat: --defaults takes --skip <ids> and --boot gamescope|desktop, for the Steam Machine ISO's installer pages**
- `5bf9da4` **fix: HDMI-CEC enables cec-audio-control.socket (the TV remote's volume keys); existing installs get it as an update**
- `14aa7c0` **fix: the app's progress screen says "Boot into desktop", not "Boot into Boot into"**
- `c4ec755` **feat: --defaults --options <ids> (exactly these on) replaces --skip; steamify.sh --boot gamescope|desktop on its own switches where an installed conversion starts**
- `eadbdda` **docs: release branches (release/X.Y.Z, feature/ and bugfix/ branches into it, the release into main); CI builds release/** pushes**
- `bee18b7` **docs: the agent merges feature/bugfix branches into the release branch; the release into main stays the user's**
- `501c0ba` **docs: gh or git for merging into the release branch**
- `b1adacb` **docs: gh is required for the release-branch pull requests; ask the user to install it**
- `ced4a45` **chore: the v2 app preview is gone (it is the released app): no preview pre-release step in CI, no mention in steamify-app.sh**
- **fix: turning the theme off no longer brings back single user's launcher restriction when single user was turned off in between**
## 2.6.0 - 2026-09-27
- `b948c20` **feat: --defaults applies the default setup without the menu, also from an installer (no session): the base for a Steam Machine ISO**
## 2.5.2 - 2026-09-27
- `0c96bed` **docs: README screenshot at 2.5.2: everything on, nothing to update**
## 2.5.1 - 2026-09-27
- **fix: Add as non-Steam game asks Steam to close again every 15 s (a Steam that was still starting ignored it)**
- `3a0692f` **docs: README screenshot at 2.5.1 with Add as non-Steam game**
- `e86ddcc` **feat: Add as non-Steam game: Steamify in the Steam library, under the shortcut; controller works when started from Steam**
- `389bc33` **fix(ui): B never quits (Steam sends Esc for it), Ctrl+Q does; started from Steam, the hints show the Steam Controller's buttons**
- `c6c1150` **fix(ui): Enter selects (a Steam Controller's A in Steam's desktop layout); Ctrl+Enter reviews and applies**
- `98e2eff` **fix(ui): Started from Steam, the app runs without Steam's overlay (Qt aborted) and stays in the foreground, so Steam Input gives it the controller**
- `431515b` **feat(ui): Review & apply greyed out while nothing would change; the Done screen shows why a run stopped**
## 2.5.0 - 2026-09-27
- **docs: README: what the VRAM booster says with NVIDIA's closed driver; shorter unsupported text in the app**
+12
View File
@@ -30,6 +30,10 @@ Steam Machine.
5. **Steamify shortcut** - "Steamify CachyOS" on the desktop and in the
launcher opens the newest Steamify app, so you don't need the install
command again. "Steamify Terminal" in the launcher opens the terminal menu.
With **Add as non-Steam game** (on by default) it's in your Steam library
too: started from Steam it gets your controller (a Steam Controller only
talks to Steam), in gaming mode as well. Steam closes for a moment while
it's added, so that's done from the desktop.
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
@@ -121,6 +125,14 @@ steamos-session-select persistent # start where you left off last time
steamos-session-select oneshot # always start in gaming mode (default)
```
Where the PC starts after a restart can also be set from a script, once the
SteamOS conversion is on (only that changes; `sudo` may ask for your password):
```bash
steamify.sh --boot desktop # start in the desktop from the next boot on
steamify.sh --boot gamescope # start in gaming mode again (default)
```
## Something wrong?
Stuck on a black screen in gaming mode: press **Ctrl+Alt+F3**, log in, and run
+58 -1
View File
@@ -180,7 +180,9 @@ SHA-256 in Valve's package index:
- `cecd`, Valve's CEC daemon (a user service started with the desktop and
gaming mode): TV remote keys become normal key presses (arrows, Enter,
Back), and it turns the TV on and off with the PC;
- `cec-audio-control`: volume of the TV or receiver;
- `cec-audio-control`: volume of the TV or receiver (the TV remote's volume
keys); its user socket is enabled by Steamify, since SteamOS enables it
through a preset that pacman doesn't apply;
- `inputattach-cec-units` (with `linuxconsole` from CachyOS for
`inputattach`): attaches USB CEC adapters (Pulse-Eight, RainShadow).
@@ -236,6 +238,35 @@ 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.
## Add as non-Steam game
A sub-option of the Steamify shortcut (`steamgame`, `lib/steam-game.sh`, new
in 2.5.1, ticked along with the shortcut): Steamify CachyOS in every Steam
account's non-Steam games (`~/.local/share/Steam/userdata/<id>/config/shortcuts.vdf`,
binary VDF, edited by `patches/steam-shortcuts.py`), starting the shortcut's
`run-app`, with the icon rendered as PNG (`~/.local/share/steamify/steamify.png`;
Steam shows no SVG). Why: while Steam runs, a Steam Controller only talks to
Steam, which gives its input to the game it started (Steam Input); on the
desktop through its desktop layout (A = Enter, B = Esc, Y = Space, X =
Steam's keyboard), in gaming mode as a gamepad.
Steam reads the list only at startup and writes it back on exit, so Steam is
closed (`steam -shutdown`) for the edit and started again (through
`steam-desktop-autostart.service` when it's there). That's refused in gaming
mode and when Steamify itself was started from Steam (`SteamGameId`), since
closing Steam would end it. An entry for the start script that's already
there (added by hand, or with the path from before 2.5.0) is updated, not
added twice; what was done is recorded per account (state `steamgame`:
`<account>=<appid> added|updated`) and a copy of each list is kept in the
state directory before the first change. Turning it off (or the shortcut)
removes every entry for the start script, since it can't start anything
without it.
Started from Steam, the app re-executes itself without Steam's overlay
(`LD_PRELOAD` of `gameoverlayrenderer.so` makes Qt abort) and
`steamify-app.sh` keeps it in the foreground (`SteamGameId`), or Steam
counts the game as ended right away and stops giving it the controller.
## Update notifications
The **Update notifications** option (new in 2.5.0, ticked by default)
@@ -389,6 +420,30 @@ steamos-session-select persistent # remember the last-used session across reboo
steamos-session-select oneshot # always start in gamescope (default, like a Deck)
```
`steamify.sh --boot gamescope|desktop` sets where the PC starts after a
restart (the menu's Boot into row) from a script: it needs the SteamOS
conversion and changes nothing else, not even the updates a normal run would
apply. Exit code 1 without the conversion or with another value.
## Install-time mode (`--defaults`)
For an installer (the Steam Machine ISO), without a session or prompts
(passwordless sudo required):
```bash
steamify.sh --defaults # what the menu would preselect
steamify.sh --defaults --options gaming,theme,cec,machine --boot desktop
```
`--options` turns on exactly the listed items (the menu's ids: `gaming`,
`theme`, `glyphs`, `single`, `launcher`, `notify`, `vram`, `cec`, `machine`,
`poweroff`), everything else off; an item that's on brings its parent
(single user the conversion, the power-off fix Steam Machine support). An
item this PC can't use (e.g. `vram` without a VRAM region) is left off with
a warning. `--boot desktop` needs `gaming`. Unknown options, ids, `boot` or
`bios` in the list: exit code 1 before anything changes. What needs a
Plasma session waits for the first login (`lib/first-login.sh`).
With `Relogin=true`, a gamescope session that fails to start is restarted
immediately, which can turn into a loop - see
[TROUBLESHOOTING.md](TROUBLESHOOTING.md) for the way out.
@@ -414,7 +469,9 @@ immediately, which can turn into a loop - see
| `lib/cec.sh` | HDMI-CEC: Valve's `cecd` and friends from its `holo` repository |
| `lib/steam-machine.sh` | Steam Machine support: LED driver, LED access, steamos-manager |
| `lib/fremont-poweroff.sh` | Steam Machine support: the power-off fix (DKMS module from `patches/`) |
| `lib/steam-game.sh` | Add as non-Steam game: Steamify in the Steam library (`patches/steam-shortcuts.py`) |
| `lib/update-notifier.sh` | Update notifications: the notifier from `patches/` and its user timer |
| `lib/first-login.sh` | `--defaults` without a session (the Steam Machine ISO's installer): the one-time first-login step that sets up single user's launcher on Plasma's new layout and opens the app |
| `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 |
Binary file not shown.

Before

Width:  |  Height:  |  Size: 492 KiB

After

Width:  |  Height:  |  Size: 519 KiB

+18 -15
View File
@@ -34,11 +34,11 @@ cec_link_steamos_manager() {
# called by Steam Machine support, which installs steamos-manager after
# this item ran.
pacman -Q steamos-manager >/dev/null 2>&1 || return 0
systemctl --user daemon-reload
systemctl --user enable steamos-manager-configure-cecd.service 2>/dev/null
systemctl --user start steamos-manager-configure-cecd.service 2>/dev/null
systemctl --user restart cecd.service 2>/dev/null
systemctl --user restart steamos-manager.service 2>/dev/null
user_systemctl daemon-reload
user_systemctl enable steamos-manager-configure-cecd.service 2>/dev/null
user_systemctl start steamos-manager-configure-cecd.service 2>/dev/null
user_systemctl restart cecd.service 2>/dev/null
user_systemctl restart steamos-manager.service 2>/dev/null
return 0
}
@@ -51,10 +51,10 @@ cec_status() { cec_installed && [[ -f "$CEC_STEAM_DROPIN" ]] && cec_driver_ok; }
cec_reload_driver() {
# cecd keeps /dev/cec0 open, so the old module can't be unloaded under it.
systemctl --user stop cecd.service 2>/dev/null
user_systemctl stop cecd.service 2>/dev/null
sudo modprobe -r cros_ec_cec 2>/dev/null
sudo modprobe cros_ec_cec 2>/dev/null
systemctl --user start cecd.service 2>/dev/null
user_systemctl start cecd.service 2>/dev/null
}
cec_driver_enable() {
@@ -154,10 +154,13 @@ cec_enable() {
sudo tee "$CEC_STEAM_ENV" >/dev/null
printf '%s\n' "# Written by Steamify: overrides STEAM_ENABLE_CEC=0 from the gaming mode script." \
'[Service]' "EnvironmentFile=-$CEC_STEAM_ENV" | sudo tee "$CEC_STEAM_DROPIN" >/dev/null
systemctl --user daemon-reload
user_systemctl daemon-reload
# The TV remote's volume keys: SteamOS enables this socket through its
# preset, which pacman doesn't apply, so nothing else does on CachyOS.
user_systemctl enable --now cec-audio-control.socket 2>/dev/null
cec_link_steamos_manager
if compgen -G "/dev/cec*" >/dev/null; then
systemctl --user restart cecd.service 2>/dev/null
user_systemctl restart cecd.service 2>/dev/null
ok "HDMI-CEC on ($(cd /dev && echo cec*)); Steam shows its settings from the next gaming mode start."
info "Turn on CEC on your TV too (e.g. Sony: BRAVIA Sync, Samsung: Anynet+, LG: SimpLink)."
else
@@ -168,20 +171,20 @@ cec_enable() {
cec_disable() {
info "Removing HDMI-CEC..."
systemctl --user disable --now cecd.service cec-audio-control.socket cec-audio-control.service 2>/dev/null
systemctl --user disable steamos-manager-configure-cecd.service 2>/dev/null
user_systemctl disable --now cecd.service cec-audio-control.socket cec-audio-control.service 2>/dev/null
user_systemctl disable steamos-manager-configure-cecd.service 2>/dev/null
sudo pacman -Rns --noconfirm "${CEC_PKGS[@]}" 2>/dev/null
cec_driver_disable
sudo rm -f "$CEC_ORDER_DROPIN" "$CEC_STEAM_DROPIN" "$CEC_STEAM_ENV"
sudo rmdir "$(dirname "$CEC_ORDER_DROPIN")" "$(dirname "$CEC_STEAM_DROPIN")" "$(dirname "$CEC_STEAM_ENV")" 2>/dev/null
systemctl --user daemon-reload
user_systemctl daemon-reload
if [[ -n "$(state_get cec installed_linuxconsole)" ]]; then
sudo pacman -Rns --noconfirm linuxconsole 2>/dev/null
state_clear cec
fi
sudo udevadm control --reload
systemctl --user is-active -q steamos-manager.service 2>/dev/null &&
systemctl --user restart steamos-manager.service
user_systemctl is-active -q steamos-manager.service 2>/dev/null &&
user_systemctl restart steamos-manager.service
ok "HDMI-CEC removed."
}
@@ -189,5 +192,5 @@ cec_overview() {
# For the menu's kernel overview.
local dev="none"
compgen -G "/dev/cec*" >/dev/null && dev="$(cd /dev && echo cec*)"
echo "CEC devices: $dev cecd: $(systemctl --user is-active cecd 2>/dev/null)"
echo "CEC devices: $dev cecd: $(user_systemctl is-active cecd 2>/dev/null)"
}
+24
View File
@@ -44,6 +44,30 @@ require_root_helper() {
fi
}
user_session() {
# A running user systemd and session bus. Not in the installer's chroot,
# where the ISO applies Steamify for a user who has never logged in.
[[ -S "${XDG_RUNTIME_DIR:-/nonexistent}/bus" ]]
}
user_systemctl() {
# systemctl --user, also without a session: then only the unit files
# change (enable/disable through --root); starting and stopping is left
# to the first login, which starts what's enabled anyway.
if user_session; then systemctl --user "$@"; return; fi
local a verb="" args=()
for a in "$@"; do
[[ "$a" == --now ]] && continue
[[ -z "$verb" && "$a" != -* ]] && verb="$a"
args+=("$a")
done
case "$verb" in
enable|disable|is-enabled|mask|unmask) systemctl --user --root=/ "${args[@]}" ;;
is-active) return 1 ;;
*) return 0 ;;
esac
}
plasma_applets() {
# Print "containment:applet" for every applet of plugin $1 in the
# user's Plasma layout, for kwriteconfig6 --group paths.
+40
View File
@@ -0,0 +1,40 @@
#!/bin/bash
# The ISO applies Steamify in the installer, before the user ever logged
# in (steamify.sh --defaults without a session). What needs Plasma's first
# layout is finished at the first desktop login, by a one-time autostart
# that then opens the app, next to CachyOS Hello.
# Sourced by steamify.sh; not meant to be run on its own.
FIRST_LOGIN_ENTRY="${XDG_CONFIG_HOME:-$HOME/.config}/autostart/steamify-first-login.desktop"
FIRST_LOGIN_SCRIPT="$STEAMIFY_BIN/first-login"
first_login_schedule() {
install_executable "$FIRST_LOGIN_SCRIPT" 755 << EOF2
#!/bin/bash
# Installed by Steamify CachyOS: runs once, at the first desktop login.
set -o pipefail
curl -fsSL --max-time 30 "$WIZARD_URL" | bash -s -- --first-login
rm -f "$FIRST_LOGIN_ENTRY" "\$0"
[[ -x "$WIZARD_APP_LAUNCHER" ]] && exec "$WIZARD_APP_LAUNCHER"
EOF2
printf '%s\n' '[Desktop Entry]' 'Type=Application' 'Name=Steamify first login' \
"Exec=$FIRST_LOGIN_SCRIPT" 'OnlyShowIn=KDE;' 'X-KDE-autostart-phase=2' 'NoDisplay=true' |
install_executable "$FIRST_LOGIN_ENTRY" 644
}
first_login_run() {
# plasmashell has just laid out the desktop from the look-and-feel
# package; wait for its config, then put single user's launcher
# settings on the new launcher.
local _
for _ in $(seq 30); do
[[ -n "$(plasma_applets org.kde.plasma.kickoff)" ]] && break
sleep 2
done
if single_status; then
stop_plasmashell_for_edit
single_launcher
restart_plasmashell_if_stopped
fi
return 0
}
+66 -3
View File
@@ -5,12 +5,12 @@
# 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 notify vram cec machine poweroff kpin hdmi bios)
COMPONENTS=(gaming boot theme glyphs single launcher steamgame notify vram cec machine poweroff kpin hdmi bios)
# One-off actions rather than on/off components: never preselected, never
# re-applied, not listed as on or off.
ACTIONS=(bios)
# Sub-options, shown indented under their parent and only while it's ticked.
declare -A PARENT=([boot]=gaming [poweroff]=machine [kpin]=machine [hdmi]=machine [bios]=machine)
declare -A PARENT=([boot]=gaming [steamgame]=launcher [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,
@@ -26,7 +26,7 @@ NO_PRESELECT=(boot cec kpin hdmi)
FEATURE_BASELINE=2.1.0
declare -A FEATURE_VERSION=(
[gaming]=2.1.0 [boot]=2.1.0 [theme]=2.1.0 [glyphs]=2.1.0 [single]=2.1.0
[launcher]=2.1.0 [cec]=2.1.0 [machine]=2.2.0 [poweroff]=2.2.0 [vram]=2.3.0 [notify]=2.5.0
[launcher]=2.1.0 [cec]=2.7.0 [machine]=2.2.0 [poweroff]=2.2.0 [vram]=2.3.0 [notify]=2.5.0 [steamgame]=2.5.1
[kpin]=2.1.0 [hdmi]=2.1.0
)
@@ -37,6 +37,7 @@ declare -A LABEL=(
[glyphs]="Install Steam Deck/Machine icons: Deck button icons in gaming mode"
[single]="Single user mode: no password, lock screen or log out (SDDM)"
[launcher]="Steamify shortcut: the app on the desktop, Steamify Terminal in the launcher"
[steamgame]="Add as non-Steam game: Steamify in your Steam library, for the controller and gaming mode"
[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"
[cec]="HDMI-CEC: use Steam with the TV remote, TV on/off with the PC (experimental)"
@@ -52,6 +53,7 @@ component_available() {
case "$1" in
machine|poweroff) machine_available ;;
vram) vram_available ;;
steamgame) steamgame_available ;;
kpin) kpin_available ;;
hdmi) hdmi_available ;;
bios) bios_available ;;
@@ -177,6 +179,63 @@ detect_components() {
fi
}
defaults_options() {
# defaults_options [--options <id>[,<id>...]] [--boot gamescope|desktop]:
# what --defaults sets up, from the Steam Machine ISO's installer page.
# --options: exactly these items on, every other off (without it, the
# menu's preselection). An item that's on brings its parent (single user
# the conversion, like ticking it in the menu); one this PC can't use is
# left off with a warning. --boot desktop needs the conversion. Returns 1
# on an unknown option, item or value.
local c ids list=false boot=""
while [[ $# -gt 0 ]]; do
# Both take a value: without one, shift 2 fails and never shifts, so
# the loop would spin forever.
[[ "$1" == --options || "$1" == --boot ]] && [[ $# -lt 2 ]] && { err "$1 needs a value"; return 1; }
case "$1" in
--options)
list=true
IFS=, read -ra ids <<< "$2"
shift 2 ;;
--boot)
case "$2" in
gamescope|desktop) boot="$2" ;;
*) err "--boot takes gamescope or desktop"; return 1 ;;
esac
shift 2 ;;
*) err "Unknown option: $1"; return 1 ;;
esac
done
if [[ "$list" == true ]]; then
for c in "${ids[@]}"; do
[[ -n "$c" ]] || continue
[[ -n "${LABEL[$c]:-}" ]] || { err "Unknown item: $c"; return 1; }
[[ "$c" == boot ]] && { err "Where to start is --boot, not an item."; return 1; }
is_action "$c" && { err "$c can't be part of the install."; return 1; }
done
for c in "${COMPONENTS[@]}"; do WANTED[$c]=0; done
for c in "${ids[@]}"; do
[[ -n "$c" ]] || continue
if component_available "$c" && component_selectable "$c"; then
WANTED[$c]=1
else
warn "Leaving out ${LABEL[$c]%%:*}: not available on this PC."
fi
done
[[ "${WANTED[single]}" == 1 ]] && WANTED[gaming]=1
for c in "${COMPONENTS[@]}"; do
[[ "${WANTED[$c]}" == 1 && -n "${PARENT[$c]:-}" ]] && WANTED[${PARENT[$c]}]=1
done
fi
case "$boot" in
desktop)
[[ "${WANTED[gaming]}" == 1 ]] || { err "--boot desktop needs the SteamOS conversion (gaming)."; return 1; }
WANTED[boot]=1 ;;
gamescope) WANTED[boot]=0 ;;
esac
return 0
}
toggle_component() {
local c="$1"
component_selectable "$c" || return 1
@@ -192,6 +251,10 @@ toggle_component() {
if [[ "$c" == machine && "${WANTED[machine]}" == 0 ]]; then WANTED[kpin]=0; WANTED[bios]=0; fi
if [[ "$c" == kpin && "${WANTED[kpin]}" == 1 ]]; then WANTED[machine]=1; fi
if [[ "$c" == bios && "${WANTED[bios]}" == 1 ]]; then WANTED[machine]=1; fi
# Steam starts Steamify through the shortcut's start script.
# Opt-out, like the power-off fix: ticked along with the shortcut.
if [[ "$c" == launcher ]] && component_available steamgame; then WANTED[steamgame]=${WANTED[launcher]}; fi
if [[ "$c" == steamgame && "${WANTED[steamgame]}" == 1 ]]; then WANTED[launcher]=1; fi
}
show_menu() {
+18
View File
@@ -101,6 +101,24 @@ single_launcher() {
done
}
single_launcher_drop() {
# The theme's layout backup is taken when it's turned on: with single
# user on then, it holds single's launcher keys, and restoring it after
# single was turned off brings them back. Drop exactly single's values
# (a launcher set up differently by the user is left alone).
local applet
local -a kick_args
for applet in $(plasma_applets org.kde.plasma.kickoff); do
kick_args=(--file plasma-org.kde.plasma.desktop-appletsrc --group Containments --group "${applet%%:*}"
--group Applets --group "${applet#*:}" --group Configuration --group General)
[[ "$(kreadconfig6 "${kick_args[@]}" --key primaryActions)" == 3 ]] &&
kwriteconfig6 "${kick_args[@]}" --key primaryActions --delete
[[ "$(kreadconfig6 "${kick_args[@]}" --key systemFavorites)" == 'suspend,reboot,shutdown' ]] &&
kwriteconfig6 "${kick_args[@]}" --key systemFavorites --delete
done
return 0
}
single_enable() {
info "Turning off the lock screen, user switching and logging out..."
stop_plasmashell_for_edit
+1 -1
View File
@@ -123,7 +123,7 @@ migrate_layout() {
"$(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
user_systemctl 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
+4 -4
View File
@@ -22,18 +22,18 @@ steam_enable() {
rm -f "$HOME/.config/autostart/steam.desktop"
# Reload user systemd context and activate the background loop
systemctl --user daemon-reload
systemctl --user enable --now steam-desktop-autostart.service
user_systemctl daemon-reload
user_systemctl enable --now steam-desktop-autostart.service
ok "Steam UI parameters and keyboard autostart deployed successfully."
}
steam_disable() {
info "Removing Steam desktop autostart..."
systemctl --user disable --now steam-desktop-autostart.service 2>/dev/null
user_systemctl disable --now steam-desktop-autostart.service 2>/dev/null
rm -f "$HOME/.config/systemd/user/steam-desktop-autostart.service" \
"$HOME/.config/environment.d/99-kde-virtual-keyboard.conf"
systemctl --user daemon-reload
user_systemctl daemon-reload
ok "Steam desktop settings removed (takes full effect at next login)."
}
+133
View File
@@ -0,0 +1,133 @@
#!/bin/bash
# "Add as non-Steam game" (steamgame), a sub-option of the Steamify shortcut:
# Steamify CachyOS in the Steam library, so Steam can start it: with Steam
# Input it then gets the controller (a Steam Controller only talks to Steam
# while Steam runs), in gaming mode too. Steam reads its non-Steam games
# (userdata/<id>/config/shortcuts.vdf, binary; patches/steam-shortcuts.py)
# only at startup and writes them back on exit, so Steam is closed for the
# edit and started again. That's refused while a Steam session depends on it:
# in gaming mode, or with this run started from Steam itself. What it did is
# recorded per Steam account (state steamgame, "<account>": "<appid> added"
# or "<appid> updated", for an entry that was there already): Steam rewrites
# the file on exit, so a mark inside the entry could be lost; the start
# script's path in it is the other sign it's ours.
# Sourced by steamify.sh; not meant to be run on its own.
STEAM_ROOT="${XDG_DATA_HOME:-$HOME/.local/share}/Steam"
STEAMGAME_NAME="Steamify CachyOS"
# Steam shows no SVG icons: the shortcut's icon rendered as PNG.
STEAMGAME_ICON="$STEAMIFY_DATA/steamify.png"
steamgame_files() {
# Every Steam account's shortcuts.vdf (the file may not exist yet).
local d
for d in "$STEAM_ROOT"/userdata/*/config; do
[[ -d "$d" && "$(basename "$(dirname "$d")")" != 0 ]] && echo "$d/shortcuts.vdf"
done
}
steamgame_tool() {
python3 <(patch_file steam-shortcuts.py) "$@"
}
steamgame_available() { [[ -n "$(steamgame_files)" ]]; }
steamgame_status() {
local f
while IFS= read -r f; do
[[ -f "$f" ]] && steamgame_tool find "$f" "$WIZARD_APP_LAUNCHER" >/dev/null && return 0
done < <(steamgame_files)
return 1
}
steamgame_steam_busy() {
# Closing Steam would end what's running: gaming mode is Steam's own
# session, and a run started from Steam is one of its games.
if [[ -n "${SteamGameId:-}" ]]; then
err "Steamify was started from Steam, which has to close for this. Start it from the desktop icon, or run Steamify Terminal."
return 0
fi
if pgrep -x gamescope >/dev/null || pgrep -f gamescope-session >/dev/null; then
err "Steam has to close for this, which would end gaming mode. Do it from the desktop."
return 0
fi
return 1
}
steamgame_edit() {
# steamgame_edit <function>: runs it with Steam closed, then starts Steam
# again if it was running (through our autostart unit when that's there).
local was_running=false i
if pgrep -x steam >/dev/null; then
steamgame_steam_busy && return 1
was_running=true
info "Closing Steam for a moment (it only reads its games list at startup)..."
# A Steam that's still starting ignores the request: ask again.
for ((i = 0; i < 90; i++)); do
((i % 15 == 0)) && steam -shutdown >/dev/null 2>&1
pgrep -x steam >/dev/null || break
sleep 1
done
pgrep -x steam >/dev/null && { err "Steam didn't close; nothing changed."; return 1; }
fi
"$1"; local rc=$?
if [[ "$was_running" == true ]]; then
if user_systemctl is-enabled -q steam-desktop-autostart.service 2>/dev/null; then
user_systemctl start steam-desktop-autostart.service
else
systemd-run --user --collect -q steam -silent >/dev/null 2>&1
fi
info "Steam is starting again."
fi
return "$rc"
}
steamgame_icon() {
# The PNG, or the SVG when it can't be rendered (librsvg comes with KDE).
if [[ -f "$WIZARD_ICON" ]] && command -v rsvg-convert >/dev/null &&
mkdir -p "$STEAMIFY_DATA" && rsvg-convert -w 256 -h 256 -o "$STEAMGAME_ICON" "$WIZARD_ICON" 2>/dev/null; then
echo "$STEAMGAME_ICON"
else
echo "$WIZARD_ICON"
fi
}
steamgame_add_all() {
local f out icon
icon="$(steamgame_icon)"
while IFS= read -r f; do
# A copy of each account's list before the first change.
[[ -f "$f" && ! -f "$STATE_DIR/shortcuts-$(basename "$(dirname "$(dirname "$f")")").vdf" ]] &&
mkdir -p "$STATE_DIR" && cp -a "$f" "$STATE_DIR/shortcuts-$(basename "$(dirname "$(dirname "$f")")").vdf"
out="$(steamgame_tool add "$f" "$STEAMGAME_NAME" "$WIZARD_APP_LAUNCHER" "$icon" "$WIZARD_APP_ENTRY")" ||
{ err "Couldn't add Steamify to $f."; return 1; }
state_set steamgame "$(basename "$(dirname "$(dirname "$f")")")" "${out#* } ${out%% *}"
info "Steam library ($(basename "$(dirname "$(dirname "$f")")")): ${out%% *} $STEAMGAME_NAME."
done < <(steamgame_files)
}
steamgame_remove_all() {
local f id
while IFS= read -r f; do
[[ -f "$f" ]] || continue
# Any entry for our start script: it can't start anything without it.
while id="$(steamgame_tool find "$f" "$WIZARD_APP_LAUNCHER")"; do
steamgame_tool remove "$f" "$id" || return 1
done
done < <(steamgame_files)
}
steamgame_enable() {
[[ -x "$WIZARD_APP_LAUNCHER" ]] || { err "The Steamify shortcut isn't set up; turn that on first."; return 1; }
info "Adding $STEAMGAME_NAME to your Steam library..."
steamgame_edit steamgame_add_all || return 1
ok "Steamify is in your Steam library (non-Steam games): start it from there for the controller, in gaming mode too."
}
steamgame_disable() {
info "Removing $STEAMGAME_NAME from your Steam library..."
steamgame_edit steamgame_remove_all || return 1
state_clear steamgame
rm -f "$STEAMGAME_ICON"
ok "Steamify removed from your Steam library."
}
+2 -2
View File
@@ -405,7 +405,7 @@ EOF
sudo pacman -S --needed --noconfirm steamos-manager inputplumber || { err "Installing hardware manager packages failed."; return 1; }
sudo systemctl enable --now inputplumber.service
sudo systemctl enable --now steamos-manager.service
systemctl --user enable steamos-manager.service 2>/dev/null
user_systemctl enable steamos-manager.service 2>/dev/null
# HDMI-CEC ran before this and couldn't link cecd to steamos-manager yet.
cec_status && cec_link_steamos_manager
@@ -422,7 +422,7 @@ EOF
machine_disable() {
info "Removing Steam Machine support..."
systemctl --user disable --now steamos-manager.service 2>/dev/null
user_systemctl disable --now steamos-manager.service 2>/dev/null
sudo systemctl disable --now steamos-manager.service 2>/dev/null
sudo systemctl disable --now inputplumber.service 2>/dev/null
sudo pacman -Rns --noconfirm steamos-manager inputplumber 2>/dev/null
+7 -7
View File
@@ -12,8 +12,8 @@ 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
user_systemctl is-enabled -q "$NOTIFY_NAME.timer" 2>/dev/null &&
user_systemctl is-enabled -q "$NOTIFY_NAME.service" 2>/dev/null
}
notify_seen() {
@@ -40,19 +40,19 @@ notify_enable() {
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 ||
user_systemctl daemon-reload
user_systemctl enable "$NOTIFY_NAME.service" >/dev/null 2>&1 &&
user_systemctl 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
user_systemctl 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
user_systemctl daemon-reload
state_clear notify
ok "Update notifications off."
}
+19 -1
View File
@@ -54,10 +54,18 @@ theme_enable() {
# Only remove it again on disable if we were the ones installing it.
state_set theme installed_pkg 1
fi
# Without a session (the ISO's installer), for a user who has never
# logged in: the layout still CachyOS's default from /etc/skel is
# removed (after the backup below), and Plasma builds its first layout
# from the look-and-feel package, as --resetLayout does below.
local offline=false layout=~/.config/plasma-org.kde.plasma.desktop-appletsrc
if [[ -z "${WAYLAND_DISPLAY:-}${DISPLAY:-}" ]]; then
if [[ -e "$layout" ]] && ! cmp -s "$layout" "/etc/skel/.config/${layout##*/}"; then
err "Run this from the Plasma desktop: applying the Vapor layout needs a running Plasma session."
return 1
fi
offline=true
fi
# Only the first time, so a re-apply doesn't overwrite the originals.
local backup="$STATE_DIR/theme-layout" f
@@ -87,6 +95,12 @@ theme_enable() {
kset theme kdeglobals General ColorScheme --delete
plasma-apply-colorscheme Vapor >/dev/null 2>&1 || true
if [[ "$offline" == true ]]; then
rm -f ~/.config/plasma-org.kde.plasma.desktop-appletsrc ~/.config/plasmashellrc
extras_enable || warn "SteamOS desktop extras not installed (see above); the theme itself is on."
ok "Vapor theme set; Plasma lays out the Vapor desktop at the first login."
return 0
fi
info "Applying the Vapor theme with its desktop and window layout..."
# --resetLayout is "Desktop and window layout" in System Settings: the
# SteamOS panel, launcher icon and wallpaper. It needs plasmashell up.
@@ -120,7 +134,11 @@ theme_disable() {
fi
# After the tools above, so keys that weren't set before are removed again.
krevert theme
single_status && single_launcher
if single_status; then
single_launcher
else
single_launcher_drop
fi
restart_plasmashell_if_stopped
qdbus6 org.kde.KWin /KWin org.kde.KWin.reconfigure >/dev/null 2>&1 || true
+4 -4
View File
@@ -101,7 +101,7 @@ vram_selectable() { vram_supported || vram_status; }
vram_status() {
pacman -Q "${VRAM_PKGS[@]}" >/dev/null 2>&1 &&
systemctl is-enabled -q dmemcg-booster-system.service 2>/dev/null &&
systemctl --user is-enabled -q dmemcg-booster-user.service 2>/dev/null
user_systemctl is-enabled -q dmemcg-booster-user.service 2>/dev/null
}
vram_enable() {
@@ -115,18 +115,18 @@ vram_enable() {
fi
sudo systemctl enable --now dmemcg-booster-system.service >/dev/null 2>&1 ||
{ err "Starting dmemcg-booster failed."; return 1; }
systemctl --user enable --now dmemcg-booster-user.service >/dev/null 2>&1 ||
user_systemctl enable --now dmemcg-booster-user.service >/dev/null 2>&1 ||
{ err "Starting dmemcg-booster for $USER failed."; return 1; }
kset vram kcgroupsrc "Foreground Booster" autostart true
# Started by the Plasma session from now on; start it in this one too.
[[ "${XDG_CURRENT_DESKTOP:-}" == KDE ]] &&
systemctl --user start plasma-foreground-booster.service >/dev/null 2>&1
user_systemctl start plasma-foreground-booster.service >/dev/null 2>&1
ok "VRAM booster on: the game in front keeps its VRAM."
}
vram_disable() {
local pkgs
systemctl --user disable --now plasma-foreground-booster.service dmemcg-booster-user.service >/dev/null 2>&1
user_systemctl disable --now plasma-foreground-booster.service dmemcg-booster-user.service >/dev/null 2>&1
sudo systemctl disable --now dmemcg-booster-system.service >/dev/null 2>&1
krevert vram
pkgs="$(state_get vram installed_pkgs)"
+1
View File
@@ -11,5 +11,6 @@ read and reviewed on their own. `lib/*.sh` reads them with `patch_file
| `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=...`) |
| `steam-shortcuts.py` | `lib/steam-game.sh` (Add as non-Steam game) | Reads and writes Steam's binary `shortcuts.vdf` (find, add or update, remove) |
| `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 |
+159
View File
@@ -0,0 +1,159 @@
#!/usr/bin/env python3
# Steamify CachyOS: Steam's non-Steam games list (userdata/<id>/config/
# shortcuts.vdf, binary VDF), for "Add as non-Steam game". Steam reads it only
# at startup and writes it back on exit, so it's edited while Steam is closed.
# steam-shortcuts.py find <vdf> <exe> -> appid of an entry for exe
# steam-shortcuts.py add <vdf> <name> <exe> <icon> <desktop file>
# -> "added <appid>" / "updated <appid>"
# steam-shortcuts.py remove <vdf> <appid>
# An entry counts as ours when its Exe is <exe>, or a Steamify start script
# (run-app, also under the pre-2.5.0 path): that one is updated, not doubled.
import os
import struct
import sys
import zlib
MAP, STR, INT, END = 0, 1, 2, 8
def parse(data):
pos = 0
def cstr():
nonlocal pos
end = data.index(b"\0", pos)
s = data[pos:end].decode("utf-8", "surrogateescape")
pos = end + 1
return s
def block():
nonlocal pos
items = []
while True:
t = data[pos]
pos += 1
if t == END:
return items
key = cstr()
if t == MAP:
items.append((key, block()))
elif t == STR:
items.append((key, cstr()))
elif t == INT:
items.append((key, struct.unpack_from("<I", data, pos)[0]))
pos += 4
else:
raise ValueError(f"unknown VDF type {t} at {pos}")
if not data:
return [("shortcuts", [])]
root = block()
return root
def dump(items):
out = bytearray()
for key, value in items:
k = key.encode("utf-8", "surrogateescape") + b"\0"
if isinstance(value, list):
out += bytes([MAP]) + k + dump(value)
elif isinstance(value, int):
out += bytes([INT]) + k + struct.pack("<I", value & 0xFFFFFFFF)
else:
out += bytes([STR]) + k + value.encode("utf-8", "surrogateescape") + b"\0"
return bytes(out) + bytes([END])
def get(entry, key, default=None):
# Steam's key names vary in case between versions (appid/AppName/Exe).
for k, v in entry:
if k.lower() == key.lower():
return v
return default
def put(entry, key, value):
for i, (k, _) in enumerate(entry):
if k.lower() == key.lower():
entry[i] = (k, value)
return
entry.append((key, value))
def shortcuts(root):
for k, v in root:
if k.lower() == "shortcuts":
return v
raise ValueError("no shortcuts block")
def ours(entry, exe):
have = get(entry, "Exe", "").strip('"')
return have == exe or (have.endswith("/run-app") and
("/steamify/" in have or "/cachyos-gamescope-boot/" in have))
def load(path):
try:
with open(path, "rb") as f:
return parse(f.read())
except FileNotFoundError:
return [("shortcuts", [])]
def save(path, root):
tmp = path + ".steamify-tmp"
with open(tmp, "wb") as f:
f.write(dump(root))
os.replace(tmp, path)
def main(argv):
cmd, path = argv[1], argv[2]
root = load(path)
entries = shortcuts(root)
if cmd == "find":
for _, e in entries:
if ours(e, argv[3]):
print(get(e, "appid", 0))
return 0
return 1
if cmd == "add":
name, exe, icon, desktop = argv[3:7]
for _, e in entries:
if ours(e, exe):
put(e, "AppName", name)
put(e, "Exe", f'"{exe}"')
put(e, "StartDir", f'"{os.path.dirname(exe)}/"')
put(e, "icon", icon)
put(e, "ShortcutPath", desktop)
save(path, root)
print("updated", get(e, "appid", 0))
return 0
# Steam's own id for a shortcut: CRC32 of exe and name, top bit set.
appid = (zlib.crc32(f'"{exe}"{name}'.encode()) | 0x80000000) & 0xFFFFFFFF
entry = [("appid", appid), ("AppName", name), ("Exe", f'"{exe}"'),
("StartDir", f'"{os.path.dirname(exe)}/"'), ("icon", icon),
("ShortcutPath", desktop), ("LaunchOptions", ""), ("IsHidden", 0),
("AllowDesktopConfig", 1), ("AllowOverlay", 1), ("OpenVR", 0),
("Devkit", 0), ("DevkitGameID", ""), ("DevkitOverrideAppID", 0),
("LastPlayTime", 0), ("FlatpakAppID", ""), ("tags", [])]
entries.append((str(len(entries)), entry))
if os.path.dirname(path):
os.makedirs(os.path.dirname(path), exist_ok=True)
save(path, root)
print("added", appid)
return 0
if cmd == "remove":
keep = [e for _, e in entries if str(get(e, "appid", "")) != argv[3]]
if len(keep) == len(entries):
return 0
entries[:] = [(str(i), e) for i, e in enumerate(keep)]
save(path, root)
return 0
print(f"unknown command {cmd}", file=sys.stderr)
return 2
if __name__ == "__main__":
sys.exit(main(sys.argv))
+55 -2
View File
@@ -15,11 +15,11 @@
set -uo pipefail
# Release version, see CHANGELOG.md.
VERSION=2.5.0
VERSION=2.7.0
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
for lib in common state packages login-manager single-user steam-desktop steam-machine fremont-poweroff vram-booster hdmi-refresh cec boot-session vapor-theme steamos-extras bios desktop-shortcut wizard-shortcut update-notifier 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 steam-game update-notifier first-login menu backend; do
# shellcheck source=/dev/null
source "$SCRIPT_DIR/lib/$lib.sh"
done
@@ -28,6 +28,15 @@ require_root_helper
BACKEND=false
[[ "${1:-}" == --backend ]] && BACKEND=true
# --defaults [--options <id>,...] [--boot gamescope|desktop]: apply what the
# menu would preselect (or exactly the listed items), without the menu or any
# prompt (the Steam Machine ISO's first login runs this; sudo must not ask).
DEFAULTS=false
[[ "${1:-}" == --defaults ]] && DEFAULTS=true
# --boot gamescope|desktop: only change where an installed conversion
# starts, everything else stays as it is (for scripts; no menu).
BOOT_ONLY=false
[[ "${1:-}" == --boot ]] && BOOT_ONLY=true
if [[ "$BACKEND" == false ]]; then
echo -e "${c_bold}Steamify CachyOS${c_reset} v$VERSION"
@@ -114,6 +123,50 @@ if [[ "$BACKEND" == true ]]; then
exit $?
fi
if [[ "${1:-}" == --first-login ]]; then
first_login_run
exit 0
fi
if [[ "$DEFAULTS" == true ]]; then
RESTART_FOR_LOGIN=false
REAPPLY=false
sudo -n true 2>/dev/null || { err "--defaults needs sudo without a password."; exit 1; }
detect_components
defaults_options "${@:2}" || exit 1
plan_changes
apply_changes
# Run by the installer: the rest waits for the first desktop login.
user_session || first_login_schedule
[[ ${#FAILED[@]} -eq 0 ]] || { warn "These had problems (see above): ${FAILED[*]}"; exit 1; }
ok "Done; the changes take effect after a restart."
exit 0
fi
if [[ "$BOOT_ONLY" == true ]]; then
RESTART_FOR_LOGIN=false
REAPPLY=false
case "${2:-}" in
gamescope|desktop) ;;
*) err "--boot takes gamescope or desktop"; exit 1 ;;
esac
detect_components
[[ "${CURRENT[gaming]}" == 1 ]] || { err "--boot needs the SteamOS conversion; turn it on first."; exit 1; }
# What is on stays on, and nothing else changes: not the updates or
# removals a normal run would pick.
for c in "${COMPONENTS[@]}"; do WANTED[$c]=${CURRENT[$c]:-0}; done
WANTED[boot]=0; [[ "$2" == desktop ]] && WANTED[boot]=1
plan_changes
if [[ ${#TO_DISABLE[@]} -eq 0 && ${#TO_ENABLE[@]} -eq 0 ]]; then
ok "Already starting in $2."
exit 0
fi
apply_changes
[[ ${#FAILED[@]} -eq 0 ]] || { warn "These had problems (see above): ${FAILED[*]}"; exit 1; }
ok "Starts in $2 from the next boot on."
exit 0
fi
# Menu loop: after each run the menu comes back with the new state, until
# the user quits; the restart question comes then, once, for everything.
RESTART_FOR_LOGIN=false
+22 -5
View File
@@ -18,6 +18,10 @@ Item {
property var steps: ({}) // id -> "wait"|"run"|"ok"|"fail"
property var failed: []
property bool restartNeeded: false
// Review & apply only when something would change; "Re-apply what's on"
// is for when nothing did.
readonly property bool canApply: computePlan().length > 0
onCanApplyChanged: if (!canApply && sel === rows.length) sel = Math.max(0, rows.length - 1)
property string runError: ""
property bool wrongPassword: false
property bool typing: false // a text field has the keys
@@ -77,6 +81,8 @@ Item {
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 === "launcher" && items.some(function (i) { return i.id === "steamgame"; })) w.steamgame = w.launcher;
if (id === "steamgame" && w.steamgame) w.launcher = true;
want = w;
}
function computePlan() {
@@ -93,7 +99,10 @@ Item {
if (want.gaming && boot !== bootNow) p.push({ id: "boot", action: boot === "desktop" ? "desktop" : "gaming" });
return p;
}
function goReview(again) { reapply = again; plan = computePlan(); screen = "review"; }
function goReview(again) {
if (!again && !canApply) return;
reapply = again; plan = computePlan(); screen = "review";
}
function wantedIds() {
var ids = [];
for (var i = 0; i < items.length; i++)
@@ -195,7 +204,8 @@ Item {
if (screen === "menu") {
if (biosChecking) return;
if (a === "up") sel = Math.max(0, sel - 1);
else if (a === "down") sel = Math.min(rows.length, sel + 1);
// The greyed-out Apply button can't be selected.
else if (a === "down") sel = Math.min(canApply ? rows.length : rows.length - 1, sel + 1);
else if (a === "accept") {
if (sel === rows.length) { goReview(false); return; }
var r = rows[sel];
@@ -207,7 +217,9 @@ Item {
boot = a === "left" ? "gamescope" : "desktop";
else if (a === "apply") goReview(false);
else if (a === "reapply") goReview(true);
else if (a === "back") Qt.quit();
// B/Esc/Back don't quit: Steam's desktop layout sends Esc for a
// Steam Controller's B, which should only ever go back.
else if (a === "quit") Qt.quit();
} else if (screen === "review") {
if (a === "accept" || a === "apply") onApplyPressed();
else if (a === "back") { reapply = false; screen = "menu"; }
@@ -246,16 +258,21 @@ Item {
if (screen === "bios2" && Input.type !== "keyboard" && (e.key === Qt.Key_Return || e.key === Qt.Key_Enter)) {
if (!e.isAutoRepeat) act("hold"); e.accepted = true; return;
}
if (!fromRemote) Input.type = "keyboard";
if (!fromRemote) Input.type = Input.fromSteam ? "steam" : "keyboard";
var k = e.key;
if (k === Qt.Key_Up) act("up");
else if (k === Qt.Key_Down) act("down");
else if (k === Qt.Key_Left) act("left");
else if (k === Qt.Key_Right) act("right");
else if (k === Qt.Key_Space) act("accept");
else if (k === Qt.Key_Return || k === Qt.Key_Enter) act(Input.type === "remote" || screen !== "menu" ? "accept" : "apply");
// Enter selects, like A on a controller: Steam's desktop layout sends
// Enter for a Steam Controller's A (and Space for Y). Ctrl+Enter
// goes straight to Review & apply.
else if ((k === Qt.Key_Return || k === Qt.Key_Enter) && (e.modifiers & Qt.ControlModifier)) act("apply");
else if (k === Qt.Key_Return || k === Qt.Key_Enter) act("accept");
else if (k === Qt.Key_Escape || k === Qt.Key_Back || k === Qt.Key_Backspace) { if (!e.isAutoRepeat) act("back"); }
else if (k === Qt.Key_R) act("reapply");
else if (k === Qt.Key_Q && (e.modifiers & Qt.ControlModifier)) act("quit");
else return;
e.accepted = true;
}
+3 -1
View File
@@ -37,7 +37,9 @@ Item {
font.pixelSize: 13; font.weight: Font.Bold }
RotationAnimator on rotation { running: step.st === "run"; from: 0; to: 360; duration: 1000; loops: Animation.Infinite } }
Row { width: 380; anchors.verticalCenter: parent.verticalCenter; spacing: 10
Text { text: (Texts.actions[step.modelData.action] || [])[3] + Texts.label(step.modelData.id)
// Boot into's label is its prefix already: "Boot into desktop", not "Boot into Boot into".
Text { text: step.modelData.id === "boot" ? (Texts.actions[step.modelData.action] || [])[3] + Texts.actions[step.modelData.action][0].toLowerCase()
: (Texts.actions[step.modelData.action] || [])[3] + Texts.label(step.modelData.id)
color: step.st === "wait" ? Theme.faint : Theme.textHi; font.family: Theme.body; font.pixelSize: 16; font.weight: Font.DemiBold
anchors.verticalCenter: parent.verticalCenter; elide: Text.ElideRight
width: Math.min(implicitWidth, parent.width - (badge.visible ? badge.width + parent.spacing : 0)) }
+4 -2
View File
@@ -10,11 +10,13 @@ Rectangle {
signal clicked
height: 48; radius: 12; width: row.implicitWidth + 36
color: primary ? Theme.accent : Theme.button
border.width: focusRing ? 2 : 0; border.color: Theme.textHi
border.width: focusRing && enabled ? 2 : 0; border.color: Theme.textHi
// Greyed out while there's nothing to do.
opacity: enabled ? 1 : 0.4
Row { id: row; anchors.centerIn: parent; spacing: 10
Glyph { k: btn.k; visible: btn.k !== ""; dark: btn.primary; anchors.verticalCenter: parent.verticalCenter }
Text { text: btn.text; color: btn.primary ? Theme.ink : Theme.text; font.family: Theme.body; font.pixelSize: 15; font.weight: Font.DemiBold
anchors.verticalCenter: parent.verticalCenter }
}
MouseArea { anchors.fill: parent; cursorShape: Qt.PointingHandCursor; onClicked: btn.clicked() }
MouseArea { anchors.fill: parent; enabled: btn.enabled; cursorShape: Qt.PointingHandCursor; onClicked: btn.clicked() }
}
+12 -2
View File
@@ -1,5 +1,8 @@
// The result of a run: all done, a problem, or nothing changed.
// The result of a run: all done, a problem, or nothing changed. With a
// problem, the run's output is shown below it (a BIOS check has no progress
// screen, so this is the only place to read why it stopped).
import QtQuick
import QtQuick.Controls.Basic
Item {
required property AppState app
@@ -20,7 +23,14 @@ Item {
Text { width: parent.width; horizontalAlignment: Text.AlignHCenter; wrapMode: Text.WordWrap; color: Theme.soft; font.family: Theme.body; font.pixelSize: 17; text: message }
Rectangle { visible: app.failed.length > 0; anchors.horizontalCenter: parent.horizontalCenter; width: ft.implicitWidth + 32; height: 44; radius: 12; color: Theme.warnBg
Text { id: ft; anchors.centerIn: parent; color: "#f2c27a"; font.family: Theme.body; font.pixelSize: 14
text: "Had a problem: " + app.failed.map(function (id) { return Texts.label(id); }).join(", ") + ". See the details." } }
text: "Had a problem: " + app.failed.map(function (id) { return Texts.label(id); }).join(", ") + (app.log.count ? ". Details below." : ".") } }
Rectangle { visible: app.failed.length > 0 && app.log.count > 0; width: parent.width; height: 190; radius: 14; color: Theme.card
ListView { id: details; anchors.fill: parent; anchors.margins: 16; clip: true; model: app.log
ScrollBar.vertical: ScrollBar { policy: ScrollBar.AsNeeded }
// The end is where it stopped.
onCountChanged: positionViewAtEnd()
onVisibleChanged: if (visible) positionViewAtEnd()
delegate: Text { required property string line; width: details.width - 12; text: line; color: "#aab5c4"; font.family: Theme.mono; font.pixelSize: 13; wrapMode: Text.WrapAnywhere } } }
Row { anchors.horizontalCenter: parent.horizontalCenter; spacing: 14; topPadding: 8
Btn { k: Input.g.back; text: "Back to the menu"; height: 52; onClicked: app.act("back") }
Btn { visible: app.restartNeeded; k: Input.g.ok; text: "Restart now"; primary: true; height: 52; onClicked: app.backend.restart() } }
+1 -1
View File
@@ -6,7 +6,7 @@ Rectangle {
property string k
property bool dark: false
height: 26; width: Math.max(26, gt.implicitWidth + 14)
radius: Input.type === "controller" && k.length === 1 ? 13 : 7
radius: (Input.type === "controller" || Input.type === "steam") && k.length === 1 ? 13 : 7
color: dark ? Theme.ink : Theme.key
Text { id: gt; anchors.centerIn: parent; text: glyph.k; color: glyph.dark ? Theme.accent : Theme.textHi
font.family: Theme.body; font.pixelSize: 12; font.weight: Font.DemiBold }
+8 -3
View File
@@ -5,10 +5,15 @@ import QtQuick
QtObject {
property string type: "keyboard"
// Started from Steam: keys come from a Steam Controller through Steam's
// desktop layout (A = Enter, B = Esc, Y = Space, X = Steam's keyboard),
// shown as "steam" with the controller's buttons.
property bool fromSteam: false
readonly property var glyphs: ({
controller: { toggle: "A", choose: "◀ ▶", reapply: "Y", quit: "B", apply: "X", ok: "A", back: "B" },
keyboard: { toggle: "Space", choose: "← →", reapply: "R", quit: "Esc", apply: "Enter", ok: "Enter", back: "Esc" },
remote: { toggle: "OK", choose: "◀ ▶", reapply: "Red", quit: "Back", apply: "↓ Apply", ok: "OK", back: "Back" }
controller: { toggle: "A", choose: "◀ ▶", reapply: "Y", quit: "", apply: "X", ok: "A", back: "B" },
keyboard: { toggle: "Enter", choose: "← →", reapply: "R", quit: "Ctrl+Q", apply: "Ctrl+Enter", ok: "Enter", back: "Esc" },
steam: { toggle: "A", choose: "◀ ▶", reapply: "", quit: "", apply: "↓ A", ok: "A", back: "B" },
remote: { toggle: "OK", choose: "◀ ▶", reapply: "Red", quit: "", apply: "↓ Apply", ok: "OK", back: "Back" }
})
readonly property var g: glyphs[type]
}
+2
View File
@@ -12,6 +12,8 @@ ApplicationWindow {
required property var gamepad
required property bool fullscreen
required property string iconUrl
required property bool fromSteam
Component.onCompleted: Input.fromSteam = fromSteam
width: 1280; height: 720
minimumWidth: 960; minimumHeight: 540
+5 -1
View File
@@ -59,7 +59,10 @@ Item {
Row {
anchors.left: parent.left; anchors.leftMargin: 40; anchors.verticalCenter: parent.verticalCenter; spacing: 28
Repeater {
// Quit only has a key on a keyboard (Ctrl+Q), and re-apply has
// none through Steam's layout; a controller quits through Steam.
model: [[screen.g.toggle, "Toggle"], [screen.g.choose, "Choose"], [screen.g.reapply, "Re-apply what's on"], [screen.g.quit, "Quit"]]
.filter(function (h) { return h[0] !== ""; })
Row { required property var modelData; spacing: 8
Glyph { k: parent.modelData[0]; anchors.verticalCenter: parent.verticalCenter }
Text { text: parent.modelData[1]; color: Theme.label; font.family: Theme.body; font.pixelSize: 14; anchors.verticalCenter: parent.verticalCenter } }
@@ -70,7 +73,8 @@ Item {
Text { readonly property int n: app.computePlan().length
text: n === 0 ? "Everything is the way you want it" : n + (n === 1 ? " change" : " changes")
color: Theme.faint; font.family: Theme.body; font.pixelSize: 14; anchors.verticalCenter: parent.verticalCenter }
Btn { k: screen.g.apply; text: "Review & apply"; primary: true; focusRing: app.sel === app.rows.length; height: 44; onClicked: app.goReview(false) }
Btn { k: screen.g.apply; text: "Review & apply"; primary: true; enabled: app.canApply
focusRing: app.sel === app.rows.length; height: 44; onClicked: app.goReview(false) }
}
}
}
+1 -1
View File
@@ -47,7 +47,7 @@ Rectangle {
Text { visible: app.wrongPassword; text: "That password didn't work. Try again."; color: Theme.bad; font.family: Theme.body; font.pixelSize: 14 }
Rectangle { width: parent.width; height: 46; radius: 12; color: Theme.note
Text { anchors.verticalCenter: parent.verticalCenter; x: 14; color: Theme.soft; font.family: Theme.body; font.pixelSize: 14
text: ({ controller: "Press Steam + X for the on-screen keyboard.", keyboard: "Type your password and press Enter.", remote: "Select the field for the on-screen keyboard." })[Input.type] } }
text: ({ controller: "Press Steam + X for the on-screen keyboard.", keyboard: "Type your password and press Enter.", steam: "Press X for Steam's on-screen keyboard, type your password and press A.", remote: "Select the field for the on-screen keyboard." })[Input.type] } }
Item { width: parent.width; height: 48
Btn { k: Input.g.back; text: "Back"; onClicked: app.act("back") }
Btn { anchors.right: parent.right; k: Input.g.ok; text: "Apply"; primary: true; onClicked: screen.submit() } }
+3
View File
@@ -21,6 +21,9 @@ QtObject {
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.",
changes: ["Steamify CachyOS on the desktop and in the launcher (the app)", "Steamify Terminal in the launcher"] },
steamgame: { label: "Add as non-Steam game", hint: "Steamify in your Steam library",
body: "Adds Steamify to your Steam library as a non-Steam game. Started from Steam it gets your controller (a Steam Controller only talks to Steam while Steam runs), and you can open it in gaming mode. Steam closes for a moment and starts again, since it only reads its games list at startup: so this is done from the desktop, not from gaming mode or with Steamify started from Steam.",
changes: ["Steamify CachyOS in your Steam library, with its icon", "An entry that's already there is updated, not added twice", "Off (or the shortcut off): removed from the library again"] },
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"] },
+9 -4
View File
@@ -1,12 +1,12 @@
#!/bin/bash
# Steamify CachyOS - get and start the app (v2), for `curl | bash`:
# Steamify CachyOS - get and start the app, 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, lib/, patches/ and services/) 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>
# takes that branch's source instead (for testing before a build).
# STEAMIFY_RELEASE=<tag> takes that release instead of the newest.
# STEAMIFY_BRANCH=<branch> takes that branch's source instead (for testing
# before a build).
set -euo pipefail
REPO="theupriser/steamify-cachyos"
@@ -71,4 +71,9 @@ EOF
command -v update-desktop-database >/dev/null && update-desktop-database "$APPS" 2>/dev/null || true
say "Starting Steamify..."
# Started from Steam: stay in the foreground, or Steam counts the game as
# ended right away and stops giving it the controller.
if [[ -n "${SteamGameId:-}" ]]; then
exec "$DEST/ui/steamify-ui" < /dev/null
fi
setsid "$DEST/ui/steamify-ui" >/dev/null 2>&1 < /dev/null &
+11
View File
@@ -20,6 +20,14 @@ import tempfile
import threading
import time
# Started from Steam (a non-Steam game, so Steam Input hands it the
# controller): Steam preloads its overlay into every game, and Qt aborts with
# it loaded. Start again without it, before Qt is imported.
if "gameoverlayrenderer" in os.environ.get("LD_PRELOAD", ""):
os.environ["LD_PRELOAD"] = ":".join(p for p in os.environ["LD_PRELOAD"].split(":")
if p and "gameoverlayrenderer" not in p)
os.execv(sys.executable, [sys.executable] + sys.argv)
HERE = os.path.dirname(os.path.realpath(__file__))
try:
@@ -365,6 +373,9 @@ def main():
engine = QQmlApplicationEngine()
engine.setInitialProperties({"backend": backend, "gamepad": pad,
"fullscreen": fullscreen,
# Started from Steam: its keys are a Steam
# Controller's buttons (desktop layout).
"fromSteam": bool(os.environ.get("SteamGameId")),
"iconUrl": QUrl.fromLocalFile(os.path.realpath(icon)).toString()})
engine.load(os.path.join(HERE, "qml", "Main.qml"))
if not engine.rootObjects():