feat: Update notifications: a notification and tray icon when there's a new Steamify

This commit is contained in:
theupriser committed 2026-09-27 14:06:48 +02:00
1 parent 7268c77c80
commit 1d98aa3ee0
10 files changed
+264 -6

No files matched your search

+7
View File
@@ -223,6 +223,13 @@ 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.
- Update notifications (`notify`, `lib/update-notifier.sh`, top-level,
ticked by default, 2.5.0): a user timer (daily, and the service is wanted
by `plasma-workspace.target`) runs `patches/steamify-notifier.py`, which
compares GitHub's newest release with the `seen` version every run records
(`notify_seen` in the entry point, only while it's on). It must never
update anything itself: notification + tray icon, Open Steamify / Skip
this version (`skipped`). Desktop only (exits without `plasmashell`).
- VRAM booster (`vram`, `lib/vram-booster.sh`, top-level, ticked by
default, 2.3.0; only available when `/sys/fs/cgroup/dmem.capacity` lists a
VRAM region of at least 2 GB; greyed out with an NVIDIA card,
+4
View File
@@ -5,6 +5,10 @@ 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.5.0 - 2026-09-27
- **feat: Update notifications: a notification and tray icon when there's a new Steamify**
## 2.4.1 - 2026-09-27
- **chore: Version 2.4.1**
+6 -2
View File
@@ -30,12 +30,16 @@ 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.
6. **VRAM booster** - on by default: the game you're playing keeps its video
6. **Update notifications** - on by default: a notification and a tray icon
when there's a new Steamify, with Open Steamify and Skip this version. It
never updates anything by itself
([details](TECHNICAL.md#update-notifications)).
7. **VRAM booster** - on by default: the game you're playing keeps its video
memory and background apps make room, like SteamOS 3.9. Shown for GPUs
whose driver supports it (AMD, Intel) on kernel 7.2 or newer; greyed out
with an NVIDIA card until NVIDIA's driver supports it
([details](TECHNICAL.md#vram-booster)).
7. **HDMI-CEC** (experimental; off by default, on by default on a Steam
8. **HDMI-CEC** (experimental; off by default, on by default on a Steam
Machine) - use Steam with your TV's
remote, and the TV turns on and off with the PC. Needs a PC with CEC (like
the Steam Machine) or a USB CEC adapter; most graphics cards, NVIDIA
+22
View File
@@ -236,6 +236,27 @@ 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.
## Update notifications
The **Update notifications** option (new in 2.5.0, ticked by default)
installs `patches/steamify-notifier.py` as
`~/.local/share/cachyos-gamescope-boot/steamify-notifier` with two user
units in `~/.config/systemd/user`: `steamify-update-check.timer` (daily,
`Persistent=`) and `steamify-update-check.service` (also wanted by
`plasma-workspace.target`, so it checks a minute after each desktop login).
Nothing stays running while there's no update.
Every Steamify run records its version as `seen` (`notify.state`). The check
asks GitHub for the newest release; when it's newer than `seen` and not the
`skipped` version, it shows a notification (`notify-send` with the buttons
**Open Steamify** and **Skip this version**) and a tray icon (click: open;
menu: open, skip, remind me later). Closing the notification keeps the
icon; opening Steamify in any way records the new version and ends it. It
only runs on the Plasma desktop: gamescope shows no notifications or tray,
so an update found in gaming mode waits for the next desktop login. It never
installs anything: the shortcut always starts the newest release, which then
shows what's new or updated for this install.
## VRAM booster
SteamOS 3.9 manages the dGPU's VRAM per cgroup: the game in front is
@@ -357,6 +378,7 @@ 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/update-notifier.sh` | Update notifications: the notifier from `patches/` and its user timer |
| `lib/vram-booster.sh` | VRAM booster (`dmemcg-booster`, `plasma-foreground-booster`) |
| `patches/` | Module sources and patches the scripts build or apply (`patch_file`); see its README |
| `.github/tools/bundle.sh` | Builds the single-file version (`dist/steamify.sh`), with `patches/` embedded |
+3 -2
View File
@@ -5,7 +5,7 @@
# 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 vram cec machine poweroff kpin hdmi bios)
COMPONENTS=(gaming boot theme glyphs single launcher notify vram cec machine poweroff kpin hdmi bios)
# One-off actions rather than on/off components: never preselected, never
# re-applied, not listed as on or off.
ACTIONS=(bios)
@@ -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
[launcher]=2.1.0 [cec]=2.1.0 [machine]=2.2.0 [poweroff]=2.2.0 [vram]=2.3.0 [notify]=2.5.0
[kpin]=2.1.0 [hdmi]=2.1.0
)
@@ -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"
[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)"
[machine]="Steam Machine support: LED bar driver, hardware settings in Steam"
+82
View File
@@ -0,0 +1,82 @@
#!/bin/bash
# "Update notifications" menu item (notify), ticked by default: a systemd
# user timer checks GitHub's newest release once a day and at each Plasma
# login. When it's newer than the last Steamify that ran here, it shows a
# notification (Open Steamify / Skip this version) and a tray icon until
# then; it never updates anything itself (patches/steamify-notifier.py).
# Sourced by steamify.sh; not meant to be run on its own.
NOTIFY_DIR="${XDG_DATA_HOME:-$HOME/.local/share}/cachyos-gamescope-boot"
NOTIFY_SCRIPT="$NOTIFY_DIR/steamify-notifier"
NOTIFY_UNITS="${XDG_CONFIG_HOME:-$HOME/.config}/systemd/user"
NOTIFY_NAME=steamify-update-check
notify_status() {
[[ -f "$NOTIFY_SCRIPT" ]] &&
systemctl --user is-enabled -q "$NOTIFY_NAME.timer" 2>/dev/null &&
systemctl --user is-enabled -q "$NOTIFY_NAME.service" 2>/dev/null
}
notify_seen() {
# Every run records its version: the notifier compares the newest
# release with it, so opening Steamify ends a reminder.
notify_status && state_set notify seen "$VERSION"
}
notify_enable() {
local p missing=()
info "Adding update notifications..."
# PySide6 for the tray icon, notify-send for the notification's buttons.
python3 -c 'import PySide6.QtWidgets' 2>/dev/null || missing+=(pyside6)
command -v notify-send >/dev/null || missing+=(libnotify)
if ((${#missing[@]})); then
sudo pacman -S --needed --noconfirm "${missing[@]}" ||
{ err "Installing ${missing[*]} failed."; return 1; }
fi
patch_file steamify-notifier.py | install_executable "$NOTIFY_SCRIPT" 755 ||
{ err "Couldn't write $NOTIFY_SCRIPT."; return 1; }
mkdir -p "$NOTIFY_UNITS"
# PartOf the graphical session: the tray icon goes with a logout. The
# short wait at login lets the tray come up first (in the script: an
# ExecStartPre sleep runs into the start timeout).
cat > "$NOTIFY_UNITS/$NOTIFY_NAME.service" << EOF
[Unit]
Description=Steamify: check for a new release
After=plasma-workspace.target
PartOf=graphical-session.target
[Service]
Type=simple
ExecStart=$NOTIFY_SCRIPT --delay 60
[Install]
WantedBy=plasma-workspace.target
EOF
cat > "$NOTIFY_UNITS/$NOTIFY_NAME.timer" << EOF
[Unit]
Description=Steamify: check for a new release daily
[Timer]
OnCalendar=daily
RandomizedDelaySec=1h
Persistent=true
[Install]
WantedBy=timers.target
EOF
systemctl --user daemon-reload
systemctl --user enable "$NOTIFY_NAME.service" >/dev/null 2>&1 &&
systemctl --user enable --now "$NOTIFY_NAME.timer" >/dev/null 2>&1 ||
{ err "Enabling the update check failed."; return 1; }
state_set notify seen "$VERSION"
ok "Update notifications on: you'll get a notification when there's a new Steamify."
}
notify_disable() {
systemctl --user disable --now "$NOTIFY_NAME.timer" "$NOTIFY_NAME.service" >/dev/null 2>&1
rm -f "$NOTIFY_UNITS/$NOTIFY_NAME.service" "$NOTIFY_UNITS/$NOTIFY_NAME.timer" "$NOTIFY_SCRIPT"
rmdir "$NOTIFY_DIR" 2>/dev/null || true
systemctl --user daemon-reload
state_clear notify
ok "Update notifications off."
}
+1
View File
@@ -7,4 +7,5 @@ read and reviewed on their own. `lib/*.sh` reads them with `patch_file
| File | Used by | What |
|---|---|---|
| `steamify-fremont-poweroff.c` | `lib/fremont-poweroff.sh` (Steam Machine support) | Kernel module (DKMS): clears GPIO 18's S4/S5 wake bit before power-off, so the Steam Machine stays off with recent kernels |
| `steamify-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 |
+133
View File
@@ -0,0 +1,133 @@
#!/usr/bin/env python3
# Steamify CachyOS: update notifications (installed by the "Update
# notifications" menu item, run by a systemd user timer and at each Plasma
# login). Never updates anything itself: when GitHub has a newer release
# than the last Steamify that ran here, it shows a notification (Open
# Steamify / Skip this version) and a tray icon until you open the app,
# skip the version or pick "Remind me later". Nothing runs in between.
import configparser
import json
import os
import subprocess
import sys
import threading
import time
import urllib.request
REPO = "theupriser/steamify-cachyos"
STATE_DIR = os.path.join(os.environ.get("XDG_STATE_HOME") or os.path.expanduser("~/.local/state"),
"cachyos-gamescope-boot")
STATE = os.path.join(STATE_DIR, "notify.state")
DATA = os.environ.get("XDG_DATA_HOME") or os.path.expanduser("~/.local/share")
RUN_APP = os.path.join(DATA, "cachyos-gamescope-boot", "run-app")
APP_URL = f"https://github.com/{REPO}/releases/latest/download/steamify-app.sh"
def version(v):
try:
return tuple(int(p) for p in v.lstrip("v").split("."))
except ValueError:
return None
def state(key):
cp = configparser.ConfigParser(interpolation=None)
cp.read(STATE)
return cp.get("State", key, fallback="")
def set_state(key, value):
# Same ini as lib/state.sh's state_set writes.
subprocess.run(["kwriteconfig6", "--file", STATE, "--group", "State", "--key", key, value], check=False)
def latest():
req = urllib.request.Request(f"https://api.github.com/repos/{REPO}/releases/latest",
headers={"User-Agent": "steamify-notifier", "Accept": "application/vnd.github+json"})
with urllib.request.urlopen(req, timeout=30) as r:
return json.load(r)["tag_name"].lstrip("v")
def pending(new):
# Newer than the last Steamify that ran here, and not skipped.
seen = version(state("seen") or "0")
return seen is not None and version(new) > seen and new != state("skipped")
def desktop():
# Only on the Plasma desktop: gamescope doesn't show notifications or a
# tray, so an update found in gaming mode waits for the next desktop login.
return subprocess.run(["pgrep", "-u", str(os.getuid()), "-x", "plasmashell"],
stdout=subprocess.DEVNULL).returncode == 0
def main():
if "--delay" in sys.argv:
time.sleep(int(sys.argv[sys.argv.index("--delay") + 1]))
if not desktop():
return 0
try:
new = latest()
except Exception as e: # offline, rate limited: try again next time
print(f"Couldn't check for a new Steamify: {e}", file=sys.stderr)
return 0
if version(new) is None or not pending(new):
return 0
from PySide6.QtCore import QObject, QTimer, Signal
from PySide6.QtGui import QAction, QIcon
from PySide6.QtWidgets import QApplication, QMenu, QSystemTrayIcon
app = QApplication(sys.argv[:1])
app.setApplicationName("Steamify")
app.setDesktopFileName("steamify-ui")
app.setQuitOnLastWindowClosed(False)
icon = QIcon.fromTheme("cachyos-gamescope-boot-wizard", QIcon.fromTheme("steam"))
def open_app():
# The app records its version as seen, which ends the reminder.
cmd = [RUN_APP] if os.access(RUN_APP, os.X_OK) else \
["bash", "-c", f"set -o pipefail; curl -fsSL --max-time 30 {APP_URL} | bash"]
subprocess.Popen(cmd, start_new_session=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
app.quit()
def skip():
set_state("skipped", new)
app.quit()
tray = QSystemTrayIcon(icon)
tray.setToolTip(f"Steamify {new} is available")
menu = QMenu()
for text, fn in ((f"Open Steamify {new}", open_app), ("Skip this version", skip), ("Remind me later", app.quit)):
a = QAction(text, menu)
a.triggered.connect(fn)
menu.addAction(a)
tray.setContextMenu(menu)
tray.activated.connect(lambda reason: open_app() if reason == QSystemTrayIcon.ActivationReason.Trigger else None)
tray.show()
# The notification's buttons: notify-send waits for the click, so it
# runs beside the event loop. Closing it without a click keeps the icon.
class Relay(QObject):
picked = Signal(str)
relay = Relay()
relay.picked.connect(lambda a: open_app() if a == "open" else skip() if a == "skip" else None)
def notify():
r = subprocess.run(["notify-send", "--app-name=Steamify", "--icon=cachyos-gamescope-boot-wizard",
"-A", "open=Open Steamify", "-A", "skip=Skip this version",
f"Steamify {new} is available",
"Open Steamify to see what's new and choose what to update."],
capture_output=True, text=True)
relay.picked.emit(r.stdout.strip())
threading.Thread(target=notify, daemon=True).start()
# Opened some other way (the shortcut, the terminal): the reminder is done.
poll = QTimer()
poll.timeout.connect(lambda: None if pending(new) else app.quit())
poll.start(60_000)
return app.exec()
if __name__ == "__main__":
sys.exit(main())
+3 -2
View File
@@ -15,11 +15,11 @@
set -uo pipefail
# Release version, see CHANGELOG.md.
VERSION=2.4.1
VERSION=2.5.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 menu backend; do
for lib in common state packages login-manager single-user steam-desktop steam-machine fremont-poweroff vram-booster hdmi-refresh cec boot-session vapor-theme steamos-extras bios desktop-shortcut wizard-shortcut update-notifier menu backend; do
# shellcheck source=/dev/null
source "$SCRIPT_DIR/lib/$lib.sh"
done
@@ -43,6 +43,7 @@ fi
# Everything per-user (autologin user, Steam, theme, shortcut) is for the
# user running the script.
TARGET_USER="$(id -un)"
notify_seen
restart_needed() { [[ -n "${BIOS_NEEDS_RESTART:-}" || "$RESTART_FOR_LOGIN" == true ]]; }
+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"] },
notify: { label: "Update notifications", hint: "A notification when there's a new Steamify",
body: "Checks once a day and when you log in to the desktop. When there's a new Steamify you get a notification and a tray icon: open the app to see what's new and choose what to update, or skip that version. It never updates anything by itself.",
changes: ["A daily check (systemd user timer), only on the desktop", "Tray icon only while an update is waiting", "Skip this version: quiet until the next one"] },
vram: { label: "VRAM booster", hint: "The game in front keeps its VRAM",
body: "Like SteamOS 3.9: the game you're playing keeps its video memory, and background apps are moved out first. Without it a game that needs most of the video memory can spill into system RAM and stutter. Shown for GPUs whose driver supports it (AMD, Intel) on kernel 7.2 or newer.",
changes: ["dmemcg-booster (system and user service)", "plasma-foreground-booster, which tells it which window is in front", "Off: removed again, unless you had installed them yourself"] },