mirror of
https://github.com/theupriser/steamify-cachyos.git
synced 2026-10-03 17:41:58 +02:00
feat: Add as non-Steam game: Steamify in the Steam library, under the shortcut; controller works when started from Steam
This commit is contained in:
1 parent
389bc3346e
commit
e86ddcc4af
11 files changed
+356
-5
No files matched your search
@@ -243,6 +243,15 @@ 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.
|
||||||
|
- 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,
|
- Update notifications (`notify`, `lib/update-notifier.sh`, top-level,
|
||||||
ticked by default, 2.5.0): a user timer (daily, and the service is wanted
|
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
|
by `plasma-workspace.target`) runs `patches/steamify-notifier.py`, which
|
||||||
|
|||||||
@@ -5,6 +5,14 @@ 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.1 - 2026-09-27
|
||||||
|
|
||||||
|
- **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
|
## 2.5.0 - 2026-09-27
|
||||||
|
|
||||||
- **docs: README: what the VRAM booster says with NVIDIA's closed driver; shorter unsupported text in the app**
|
- **docs: README: what the VRAM booster says with NVIDIA's closed driver; shorter unsupported text in the app**
|
||||||
|
|||||||
@@ -30,6 +30,10 @@ 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.
|
||||||
|
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
|
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
|
when there's a new Steamify, with Open Steamify and Skip this version. It
|
||||||
never updates anything by itself
|
never updates anything by itself
|
||||||
|
|||||||
@@ -236,6 +236,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
|
power-off fix as a new default sub-option (see feature versions below), so a
|
||||||
normal run adds it.
|
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
|
## Update notifications
|
||||||
|
|
||||||
The **Update notifications** option (new in 2.5.0, ticked by default)
|
The **Update notifications** option (new in 2.5.0, ticked by default)
|
||||||
@@ -414,6 +443,7 @@ 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/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/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 |
|
| `services/` | The systemd units the scripts install (`service_file`, `@KEY@` placeholders); see its README |
|
||||||
|
|||||||
+9
-3
@@ -5,12 +5,12 @@
|
|||||||
|
|
||||||
# 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 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
|
# One-off actions rather than on/off components: never preselected, never
|
||||||
# re-applied, not listed as on or off.
|
# re-applied, not listed as on or off.
|
||||||
ACTIONS=(bios)
|
ACTIONS=(bios)
|
||||||
# Sub-options, shown indented under their parent and only while it's ticked.
|
# Sub-options, shown indented under their parent and only while it's ticked.
|
||||||
declare -A PARENT=([boot]=gaming [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,
|
# Never preselected on a first run: booting into the desktop is a choice,
|
||||||
# gamescope is the default; HDMI-CEC is opt-in (it can wake the machine or
|
# gamescope is the default; HDMI-CEC is opt-in (it can wake the machine or
|
||||||
# upset other devices on the TV, even on SteamOS), except on a Steam Machine,
|
# upset other devices on the TV, even on SteamOS), except on a Steam Machine,
|
||||||
@@ -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 [notify]=2.5.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 [steamgame]=2.5.1
|
||||||
[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"
|
||||||
|
[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"
|
[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)"
|
||||||
@@ -52,6 +53,7 @@ component_available() {
|
|||||||
case "$1" in
|
case "$1" in
|
||||||
machine|poweroff) machine_available ;;
|
machine|poweroff) machine_available ;;
|
||||||
vram) vram_available ;;
|
vram) vram_available ;;
|
||||||
|
steamgame) steamgame_available ;;
|
||||||
kpin) kpin_available ;;
|
kpin) kpin_available ;;
|
||||||
hdmi) hdmi_available ;;
|
hdmi) hdmi_available ;;
|
||||||
bios) bios_available ;;
|
bios) bios_available ;;
|
||||||
@@ -192,6 +194,10 @@ toggle_component() {
|
|||||||
if [[ "$c" == machine && "${WANTED[machine]}" == 0 ]]; then WANTED[kpin]=0; WANTED[bios]=0; fi
|
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" == kpin && "${WANTED[kpin]}" == 1 ]]; then WANTED[machine]=1; fi
|
||||||
if [[ "$c" == bios && "${WANTED[bios]}" == 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() {
|
show_menu() {
|
||||||
|
|||||||
@@ -0,0 +1,129 @@
|
|||||||
|
#!/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)..."
|
||||||
|
steam -shutdown >/dev/null 2>&1
|
||||||
|
for ((i = 0; i < 60; i++)); do 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 systemctl --user is-enabled -q steam-desktop-autostart.service 2>/dev/null; then
|
||||||
|
systemctl --user 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."
|
||||||
|
}
|
||||||
@@ -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 |
|
| `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 |
|
| `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-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 |
|
| `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 |
|
||||||
@@ -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))
|
||||||
+2
-2
@@ -15,11 +15,11 @@
|
|||||||
set -uo pipefail
|
set -uo pipefail
|
||||||
|
|
||||||
# Release version, see CHANGELOG.md.
|
# Release version, see CHANGELOG.md.
|
||||||
VERSION=2.5.0
|
VERSION=2.5.1
|
||||||
|
|
||||||
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 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 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
|
||||||
|
|||||||
@@ -81,6 +81,8 @@ Item {
|
|||||||
if (id === "poweroff" && w.poweroff) w.machine = true;
|
if (id === "poweroff" && w.poweroff) w.machine = true;
|
||||||
if (id === "machine" && !w.machine) w.kpin = false;
|
if (id === "machine" && !w.machine) w.kpin = false;
|
||||||
if (id === "kpin" && w.kpin) w.machine = true;
|
if (id === "kpin" && w.kpin) w.machine = true;
|
||||||
|
if (id === "launcher" && items.some(function (i) { return i.id === "steamgame"; })) w.steamgame = w.launcher;
|
||||||
|
if (id === "steamgame" && w.steamgame) w.launcher = true;
|
||||||
want = w;
|
want = w;
|
||||||
}
|
}
|
||||||
function computePlan() {
|
function computePlan() {
|
||||||
|
|||||||
@@ -21,6 +21,9 @@ 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"] },
|
||||||
|
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",
|
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.",
|
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"] },
|
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"] },
|
||||||
|
|||||||
Reference in new issue
Block a user