mirror of
https://github.com/theupriser/steamify-cachyos.git
synced 2026-10-03 17:41:58 +02:00
The pinned 7.1.6 kernel doesn't read the extra EDID block (HDMI Forum EEODB) where monitors list their fast modes, and does no HDMI 2.1, so HDMI displays often stay at 60 Hz. The new menu item reads the display's EDID over DDC, calculates the highest rate that fits HDMI 2.0 at the desktop resolution (rounded down to ten, plus the hundred below as a safe option), tests each step live with a confirmation, and makes the confirmed ones permanent through drm.edid_firmware (Limine, sdboot-manage or GRUB).
207 lines
12 KiB
Markdown
207 lines
12 KiB
Markdown
# AGENTS.md
|
|
|
|
Guidance for AI coding agents working on this repository. See `README.md`
|
|
for what the script does from a user's point of view.
|
|
|
|
## Project overview
|
|
|
|
**Steamify CachyOS**: a bash wizard that makes CachyOS (KDE Plasma 6 + `plasma-login-manager`)
|
|
boot into a SteamOS-style gamescope session, with working switching between
|
|
gamescope and the Plasma desktop. Primary target: the Valve Steam Machine
|
|
(DMI `Valve`/`Fremont`) running CachyOS Desktop edition.
|
|
|
|
## Layout
|
|
|
|
- `steamify.sh` - the only entry point. Pre-flight checks,
|
|
sources `lib/*.sh`, runs the menu and applies the plan. No component
|
|
logic here.
|
|
- `lib/*.sh` - one file per responsibility, each defining functions only
|
|
(no top-level side effects besides constants). See the table in
|
|
`TECHNICAL.md`.
|
|
- **Components.** Every menu item `<id>` (listed in `COMPONENTS` and `LABEL`
|
|
in `lib/menu.sh`) provides `<id>_status` (return 0 if on, detected from
|
|
the system, no state file needed), `<id>_enable` and `<id>_disable`, all
|
|
idempotent. `<id>_enable` is also used to re-apply. Optional
|
|
`<id>_available` is checked via `component_available` (e.g. `machine`
|
|
only on Fremont). Turn-on order is `COMPONENTS` order, turn-off reverse;
|
|
`gaming` must stay first. Dependencies live in `toggle_component`.
|
|
- **Reversibility.** Every per-user KDE setting a component changes goes
|
|
through `kset <component> <file> <group|group> <key> <value>`
|
|
(`lib/state.sh`), which records the old value once; `<id>_disable` calls
|
|
`krevert <component>`. Never call `kwriteconfig6` directly for settings a
|
|
component owns. System files are backed up with `backup_file` and restored
|
|
on disable.
|
|
- **Single-file build.** `.github/tools/bundle.sh` inlines `lib/*.sh` in the order of
|
|
the entry point's `for lib in ...; do` source loop and wraps everything
|
|
after that loop in `main()`. Keep that loop on one line, keep all logic in
|
|
functions, and don't rely on `SCRIPT_DIR` for anything but sourcing.
|
|
CI (`.github/workflows/bundle.yml`) publishes the bundle as release
|
|
`v$VERSION` on pushes to `main`; an existing version is never overwritten,
|
|
so bump `VERSION` for every release.
|
|
|
|
## Conventions
|
|
|
|
- Bash, `set -uo pipefail` (no `-e`): check exit codes of steps that matter
|
|
explicitly (`|| { err ...; exit 1; }` or `|| return 1`).
|
|
- Use the helpers from `lib/common.sh` (`info`, `ok`, `warn`, `err`,
|
|
`ask_yn`, `backup_file`) for all output and prompts.
|
|
- Runs as the normal user; use `sudo` per command, never require root.
|
|
Per-user files go in `$HOME` of the invoking user (the script refuses to
|
|
configure another user).
|
|
- **Idempotent**: every step must be safe to re-run without duplicating
|
|
lines or settings. Back up system files with `backup_file` before the
|
|
first edit.
|
|
- Don't overwrite package-owned files in `/etc/xdg` or `/usr`. KDE settings
|
|
go into the user's config via `kwriteconfig6` (or `merge_kde_config` for
|
|
whole Valve ini files).
|
|
- Package installs after a user already confirmed use `--noconfirm`: a
|
|
second pacman prompt consumes the next scripted answer.
|
|
- Comments explain *why* (the CachyOS/Plasma quirk being worked around),
|
|
not what the next line does.
|
|
|
|
- **Versioning and releases.** SemVer in `VERSION` (`steamify.sh`,
|
|
shown in the menu header and the bundle header).
|
|
- Every commit gets an entry in `CHANGELOG.md` under its version (short hash
|
|
+ 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
|
|
changelog section) and marks it latest. An existing version is never
|
|
overwritten: without a bump nothing is released, and pull requests show
|
|
a warning.
|
|
- Users install through `releases/latest/download/steamify.sh`
|
|
(GitHub's newest release). The `latest` tag and release follow the newest
|
|
version tag too (moved, asset replaced, when a new version is released),
|
|
so the older URL `releases/download/latest/...` keeps working.
|
|
|
|
## Non-obvious behaviour to preserve
|
|
|
|
- `apply_changes` sets `$LOGIN_MANAGER` from the menu: `sddm` when single
|
|
user mode is wanted, else `plasmalogin`. Toggling single user re-applies
|
|
`gaming` so it moves to the other login manager. Single user mode hides
|
|
the launcher's Session dropdown via kickoff `primaryActions=3` (restricting
|
|
`action/logout` also hides Restart/Shut Down). Two login-manager paths:
|
|
**sddm** (like SteamOS: `steam-set-session` writes
|
|
`/etc/sddm.conf.d/zz-steamos-autologin.conf`, which SDDM honours; we add
|
|
`User=`/`Relogin=` in `10-gamescope-autologin.conf`, and `/etc/sddm.conf`
|
|
must not contain `[Autologin]` since it's read last) and **plasmalogin**
|
|
(needs the sync bridge and the shortcut's sudoers rule). Both must keep
|
|
working.
|
|
|
|
- `steam-set-session` only writes `/etc/plasmalogin.conf.d/zz-steamos-autologin.conf`;
|
|
the base `/etc/plasmalogin.conf` wins, hence the sync bridge. The sync
|
|
service needs `StartLimitIntervalSec=0`, or bursts of session switches
|
|
get it rate-limited and all later switches silently stop working.
|
|
- The sync script must only edit `Session=` inside `[Autologin]`.
|
|
- The Steam desktop autostart unit is guarded with
|
|
`ExecCondition=... XDG_CURRENT_DESKTOP = KDE`, so it doesn't start a
|
|
second Steam inside gamescope.
|
|
- Vapor theme: comes from the `cachyos-vapor` package (system-wide, in
|
|
`/usr/share`); only remove it on disable if we installed it. It is applied
|
|
with `lookandfeeltool --resetLayout` ("Desktop and window layout"), which
|
|
replaces the panel/desktop layout: the layout files are backed up to
|
|
`$STATE_DIR/theme-layout` and restored, and every key from Vapor's
|
|
`contents/defaults` goes through `kset` first. `plasma-apply-colorscheme`
|
|
runs with the `ColorScheme` key cleared, since it skips a scheme already
|
|
named there. The layout reset drops single user's launcher settings, so
|
|
`single_launcher` is re-applied.
|
|
- SteamOS extras (`lib/steamos-extras.sh`, part of the theme): downloaded from
|
|
Valve's newest `steamdeck-kde-presets` (repo db gives name + SHA-256) into
|
|
`/usr/local`, never `/usr`. `gaming-return.svg` is a symlink in the package:
|
|
install `steam-gaming-return.svg` under that name. The shortcut's icon is
|
|
switched with `set_shortcut_icon`, since the conversion is created before
|
|
the theme. An existing KWallet is never replaced or removed.
|
|
- Plasma 6 has no separate systemtray containment: tray settings live on
|
|
the systemtray applet itself (Valve's setup script targets Plasma 5).
|
|
- Restart plasmashell via `systemctl --user` (`plasma-plasmashell.service`),
|
|
not `plasmashell --replace` from the script's shell: a shell without the
|
|
session environment yields a light-themed desktop (context menus, apps it
|
|
launches).
|
|
- Live Plasma changes (`qdbus6 ... evaluateScript`, `lookandfeeltool`) only
|
|
run when `plasmashell` is running; config-file fallbacks cover the rest.
|
|
- plasmashell writes its in-memory config back on exit: edit panel/applet/
|
|
wallpaper files only between `stop_plasmashell_for_edit` and
|
|
`restart_plasmashell_if_stopped` (`lib/common.sh`).
|
|
- LED driver: `leds-valve-dkms-git`'s Makefile builds against `uname -r`,
|
|
not DKMS's target kernel: `/etc/dkms/leds-valve-dkms.conf` sets
|
|
`MAKE[0]="make KVERSION=${kernelver}"` (written before the AUR install).
|
|
Without it, other kernels build against the running kernel's tree and
|
|
fail (CachyOS kernels are clang-built; DKMS adds `LLVM=1` only for the
|
|
target's tree). Headers for every installed kernel are installed first,
|
|
and `ensure-kernel-headers.service` installs missing ones at boot (a
|
|
pacman hook can't run pacman), which triggers DKMS's install hook. The module creates
|
|
`/sys/class/leds/valve-leds*`.
|
|
- `boot` ("Boot into: [gamescope] / desktop") is a choice row, not a
|
|
checkbox: on = desktop. It's a sub-option of the conversion: `menu_visible`
|
|
only shows it (indented, `└`) while `gaming` is ticked, so menu numbers
|
|
after it shift by one when the conversion is unticked. It needs `gaming` (ticking it ticks gaming,
|
|
unticking gaming unticks it) and is never preselected (`NO_PRESELECT`), so
|
|
the conversion boots into gamescope by default. Desktop is
|
|
`steamify-boot-desktop.service`, ordered `Before=` the login managers:
|
|
`steam-set-session plasma.desktop` plus the plasmalogin sync bridge when it
|
|
exists, at every boot. CachyOS's `cachyos-gamescope-autologin` still sets
|
|
gamescope during each desktop session; the unit corrects it at boot.
|
|
- `bios` is an *action* (`ACTIONS` in `lib/menu.sh`), not an on/off
|
|
component: never preselected (not even on a first run), never re-applied
|
|
by `a`, not listed in the state overview, and `bios_status` is always off.
|
|
Only selectable when Valve's version differs from the installed one
|
|
(`component_selectable`, greyed out otherwise). Download, SHA-256 and the
|
|
fwupd device check (`get-details --json`: no `UpdateError`) come before the
|
|
warnings. The warning box uses `█` for its frame: Konsole draws a long
|
|
coloured row of `#` narrower, so the right edge wouldn't line up.
|
|
Keep both confirmations (y/N, then typing `UPDATE`) and the warnings; the
|
|
firmware comes from the newest `holo-X.Y` repo (`.files` db names the
|
|
`.cab`, `.db` gives the SHA-256), and fwupd itself refuses non-Fremont
|
|
hardware. In the VM, test it with `WIZARD_BIOS_DRY_RUN=1` (skips only the
|
|
device check, never flashes) and a faked version (dev-env
|
|
`BIOS_VERSION=F7F0107 ./run.sh --fremont`).
|
|
- The entry point loops: menu, run, "back to the menu" (or `[m]`/`[r]` when a
|
|
restart is needed), until `q`. When a restart is needed, `q` asks "Restart
|
|
now? [Y/n]" (`quit_prompt`): `n` goes back to the menu, the next `q` asks
|
|
again. Scripted input that runs out quits without restarting (never
|
|
restart on EOF: `ask_yn` would take its default).
|
|
- Steamify shortcut (`launcher`): the icon runs `curl | bash` of
|
|
`releases/latest/download/steamify.sh` in Konsole, so it's
|
|
always the newest release. Its icon, `assets/steam-gaming-settings.svg`
|
|
(Valve's GPL-2.0 return icon with a gear), is a release asset too, and is
|
|
downloaded from there (Steam's icon if that fails). Desktop files are
|
|
written with their mode already set (`install_executable`): Plasma opens a
|
|
desktop icon it first saw non-executable in an editor.
|
|
The start script closes its window after a 10-second countdown on success
|
|
and waits for Enter after an error; it uses `pipefail`, or a failed
|
|
download would run an empty script and count as success.
|
|
- HDMI refresh boost (`hdmi`, `lib/hdmi-refresh.sh`): only on Fremont with
|
|
the pinned kernel (stays visible while on). It needs someone at the
|
|
screen: the graphical app's backend (`BACKEND=true`) must refuse it, and every step needs a "y" within 15 s
|
|
(`WIZARD_HDMI_CONFIRM_SECONDS` for scripted tests). debugfs is root-only
|
|
(glob it under sudo), and `edid_override` takes exactly `reset` with no
|
|
newline. Build the EDID from the DDC read, not from sysfs: a live override
|
|
replaces the kernel's copy. The kernel command-line value holds a path, so
|
|
`sed` uses `|` as its delimiter.
|
|
- `Relogin=true` means a gamescope that fails to start is relaunched in a
|
|
tight loop; keep that in mind when changing session handling.
|
|
|
|
## Checking changes
|
|
|
|
The bundle puts every module in one file, so shellcheck sees all their
|
|
`local` variables together: don't reuse a name another module uses as an
|
|
array (e.g. `g` in `lib/state.sh`), or CI's shellcheck on the bundle fails.
|
|
|
|
|
|
There is no test suite. At minimum:
|
|
|
|
```bash
|
|
for f in steamify.sh lib/*.sh; do bash -n "$f"; done
|
|
shellcheck -S warning steamify.sh lib/*.sh # if available
|
|
```
|
|
|
|
Behaviour is verified in a CachyOS QEMU/KVM test VM. The VM scripts and a
|
|
Claude Code skill describing the whole test workflow (snapshots, SSH, running
|
|
the wizard with scripted menu input such as `printf '2\n\ny\nn\n'`, the
|
|
per-component checks; when stdin is not a terminal the menu falls back to a
|
|
numbered prompt, which is what scripted runs use, reboot checks, the full test matrix, `--fremont` to
|
|
fake Steam Machine hardware) live in
|
|
[steamify-cachyos-dev](https://github.com/theupriser/steamify-cachyos-dev).
|
|
Gamescope itself and the real LED bar can only be verified on hardware.
|