feat(activity): Steam game detection on macOS (#1144) #8
Loading…
Reference in a new issue
No description provided.
Delete branch "feat/1144-activity-steam-game-detection-on-macos"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
rkd-activitynow detects Steam games on macOS, installs itself as a per-user launchd agent there, and is released and installed for Apple silicon. This is the plugin side of the Developer ID build's activity status. Linux behaviour is unchanged.Refs nanoya-biz/rkd#1144
macproc). It reads the same signal as on Linux:SteamAppIdin the game process's environment. On macOS that comes from theKERN_PROCARGS2sysctl, which is whatps -Ereads and needs no root for your own processes. Owner and start time come fromproc_pidinfo(PROC_PIDTBSDINFO):pbi_uid, andpbi_start_tvsec, which is the kernel'sp_starttime. libc has nokinfo_procon Apple, so I used this instead ofKERN_PROC_PID. The buffer is parsed as argc, then the exec path and its NUL padding, then argv, then env up to the empty string before the kernel'sapple[]strings. Reads are capped at 4 MiB, and a variable cut off at the cap is dropped. A process is looked at again after its arguments are read, so one that exits, or whose pid is reused, mid-scan is skipped. Other users' processes are never read.detect::RegistryFallback). When no process carriesSteamAppId, it uses Steam's ownregistry.vdfRunningAppID. That value is trusted only while theSteamPIDbeside it is still a live process of this user. The registry has no start time, sostarted_atis when the plugin first saw that game; it is kept across scans and reset when the game changes or stops.~/Library/Application Support/Steam, read with the existinglibraryfolders.vdf/appmanifest_<id>.acfcode.rkd-activity installon macOS writes~/Library/LaunchAgents/biz.nanoya.rkd-activity.plistand loads it with the same launchd sequence asrkd-terminal:print,bootouton reinstall,enable,bootstrap gui/<uid>retried after a bootout. Logs go to~/Library/Logs/rkd-activity/. The exe checks are the same too: a binary another user could replace is refused even with--force, and a temp or build folder is refused without it. Unlike rkd-terminal, the plist sets noPATH, since this plugin runs no programs.release.ymlbuildsrkd-activityand attachesrkd-activity-<tag>-aarch64-apple-darwin.install.shno longer refuses it on Apple silicon; asked for v0.6.0 or older on a Mac, it says those releases have no macOS build. No tag or release was made.Type of change
Screenshots / recording
No UI. Evidence is
rkd-activity serveplus a small Python client doingpair_begin→hello→pair→watch, then killing the game once it is watching. Both runs are on this Mac (arm64, macOS 26.5). BEFORE is a release build oforigin/main(c585b66) from agit archivescratch copy; AFTER is this branch's release build.Steam is installed here, but no game is: the library has no
appmanifest_*.acfandregistry.vdfhas noRunningAppID. So the game is a stand-in: a tiny native binary that sleeps,Hades.app/Contents/MacOS/Hades, started asenv SteamAppId=1145360 SECRET_TOKEN=hunter2 …, with Hades' manifest in a scratchHOME. This proves the macOS read path end to end on the real kernel. It does not prove that a real macOS Steam game carriesSteamAppId; see "Not verified" below.BEFORE — origin/main: nothing is detected on macOS.
AFTER — this PR: the stand-in started at 20:55:10 (1790355310).
A real launchd install on this Mac with the AFTER binary, uninstalled afterwards. BEFORE,
installrefused on macOS ("detects games on Linux only").Test plan
Automated:
cargo fmt --all --checkcargo clippy --workspace --all-targets --locked -- -D warningson macOS, and with--target x86_64-unknown-linux-gnuand--target x86_64-pc-windows-msvccargo test --workspace --lockedon macOS (66 inrkd-activity). The Linux-only code runs in CIscripts/test-install.shundershanddash: newact-mac,act-mac-oldandact-intel-maccases replace the old "refuses macOS" one. Alsoscripts/test-release.sh(includes the workflow lint) andshellcheck install.sh scripts/*.shNew tests:
macproc: argv/env/apple[] parsing, no padding, a cut-off buffer, other uid never read, exit/pid reuse mid-scan, and the realSystemreading this test processdetect: env scan, and the registry fallback (first seen kept, reset on change and stop, env wins, dead or foreignSteamPID, no manifest)steam:registry.vdfreading, including this Mac's real no-game shape, and the macOS rootservice: launchd install/reinstall/retry/no-start/uninstall/status, and the plistno_process_argument_or_environment_value_reaches_the_link_on_macos: the leak test over the macOS scan and the fallbackMutations applied by hand and confirmed red. 12 in the Rust code, all killed:
SteamPIDliveness check, first-seen keeping, the fallback call,SteamPID > 0state = runningprobeTwo in
install.sh, both killed: the Darwin target for rkd-activity, and the no-macOS-build message.Manual:
install→ running →uninstallvia launchd on this MacNot verified:
SteamAppIdin its environment. No game is installed here, so the env signal is unconfirmed on a real game. Whether the macOS client writesRunningAppIDtoregistry.vdfis also unconfirmed; this Mac's registry, with no game, hasSteamPIDunder HKLM and noRunningAppID. Both need a Mac with a game before a release is tagged.KERN_PROCARGS2even to its own user. It is then skipped, and only the registry fallback can report it.Review checklist
dbg!or commented-out code left behind; the evidence harness lives outside the repolibcwas already there for unixlocal/)Notes for the reviewer
macprociscfg(any(target_os = "macos", all(test, unix))), so a Linux release binary contains no macOS code and the other way round.appidholdsRunning,app_id_inandearliest, which both scanners share;procfsre-exports them, so the Linux path reads the same as before.systemctlcommands, same status line.service.rsgained aPlatformandLayout.uidfrom rkd-terminal. I left out rkd-terminal's "another plugin answers on the socket" warning, so Linux install output stays exactly the same.https://claude.ai/code/session_01S6WwLMTpMtw1RuCgBY8JGt