feat(activity): rkd-activity, Steam game detection on Linux (#1121) #6
Loading…
Reference in a new issue
No description provided.
Delete branch "feat/1121-plugins-rkd-activity-v1-steam-game-detec"
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
Adds
rkd-activity(local/activity), the local plugin that tells the RKD desktop app which Steam game is running on Linux, for the activity status in ADR-0005 (decision 3). It speaksmsg::activityfrom #5:watch/unwatchofactivity.game.watch@1, answered with{kind: "game", name, steam_app_id, started_at}ornull.Closes nanoya-biz/rkd#1121
Stacked on #5. Base is
feat/1119-plugins-split-rkd-local-link-handshake-fr, notmain. Retarget this PR tomainafter #5 merges. CI here only runspull_requestforbranches: [main], so this PR gets no CI runs until it is retargeted; the local gates are listed below.Type of change
Screenshots / recording
No UI. The pair is the plugin itself, before and after.
BEFORE (base
origin/feat/1119-…): there is no activity plugin.AFTER (this branch, in a Linux container,
rust:1.98arm64 under OrbStack, real/proc): a fake Steam install withPortal 2in a second library listed inlibraryfolders.vdf, a process started asenv SteamAppId=620 SECRET_TOKEN=hunter2 sleep 300, and a Python client doing pair_begin → hello → pair → watch. Killing the process sendsnull.Test plan
Automated:
cargo test --workspace --lockedpasses locally on macOS (45 inrkd-activity), andcargo test -p rkd-activity --lockedpasses on Linux in the container (44; the off-Linux test is cfg'd out there)cargo fmt --all --check,cargo clippy --workspace --all-targets --locked -- -D warnings(macOS)cargo clippy --target x86_64-pc-windows-msvc --workspace --all-targets --locked -- -D warnings(thewindows-checkjob)cargo clippy --target x86_64-unknown-linux-gnu -p rkd-activity --all-targets --locked -- -D warnings, so the Linux-only code is linted from the Mac as wellno_process_or_environment_value_reaches_the_link, which runs the real scan over a fake/procfull of other processes and secret-looking variables and checks every byte the plugin sends for them, andnothing_is_scanned_without_an_open_watchmin_intervalhold-back fails the two interval testsManual:
/proc,status, pair, watch, game quits →nulllibraryfolders.vdfand.acfshapes come from Steam's current formats and the oldLibraryFoldersone. A Proton game is expected to carrySteamAppIdin its wine processes too, but that has not been checkedReview checklist
dbg!or commented-out code left behindrkd-local-link,anyhow,serde,serde_json,tokio(unix socket, no TCP use) andlibc. No HTTP or network cratelocal/README.mdhas arkd-activitysection on what it reads and sends; the root README lists it)Notes for the reviewer
unwatchor when the connection drops). Each watch has its own sender task. It answers awatchat once, even inside the interval, then sends at most one change perMIN_INTERVAL(the latest value when the interval ends), so a game started and quit within the interval is never sent. A generation counter on the published snapshot stops a scanner that is being aborted from publishing after a new one has started./proc/<pid>is owned by our uid are read, and from each onlySteamAppIdand the stat start time are kept.procfsreturns nothing butRunning { app_id, started_at }.SteamAppId=0is ignored. With several games running, the most recently started one that has a manifest is reported. An id with no manifest →null. Names lose control characters and are cut toMAX_NAME_CHARS(128) chars.started_atis the earliest start of that app id's processes (btime+ stat field 22 /CLK_TCK).procfsiscfg(any(target_os = "linux", all(test, unix))), andsteam/vdfarecfg(any(target_os = "linux", test)), so a macOS or Windows release binary contains no scanning code, while the fixture tests still run on a Mac.detect::system_sourcereturnsNoneoff Linux. Windows builds a stubmainthat says it is unsupported. The server, CLI and service modules are unix-only.rkd-terminal's, trimmed (no pipe transport). I leftlocal/terminaluntouched so this PR does not conflict with #5's review. Extracting a shared plugin-host crate is a sensible follow-up.installis Linux-only (systemd user unit, same exe checks asrkd-terminal). On macOS it says detection is Linux-only; a launchd agent that always reports nothing would be pointless.install.shand release packaging forrkd-activity.install.sh,package-release.shandrelease.ymlare written aroundrkd-terminal(names,RKD_TERMINAL_*settings, the tarball layout and its test harness). A sha256-checked installer needs release assets to check, so it belongs with the release-pipeline change. Until then the README says to build from source (cargo install --git … rkd-activity). No systemd hardening either (RestrictAddressFamilies,PrivateNetwork): user units need user namespaces for those, and a private user namespace would stop the plugin reading other processes'environ. Both need checking on a real Linux desktop first.https://claude.ai/code/session_01S6WwLMTpMtw1RuCgBY8JGt
A second local plugin (ADR-0005 decision 3) that speaks msg::activity: watch/unwatch of activity.game.watch@1, answered with {kind: "game", name, steam_app_id, started_at} or null. Detection scans same-uid /proc/<pid>/environ for SteamAppId and names the game from appmanifest_<id>.acf, found via libraryfolders.vdf; an id with no manifest is not reported. One scanner runs only while a watch is open on some connection, and each watch sends at most one change per MIN_INTERVAL (the latest value), while a watch itself is answered at once. The detection code is compiled for Linux only (and for the tests on any unix, against fake /proc and Steam trees); elsewhere the source always reports null. A test drives the real scan over a fake /proc full of other processes and variables and checks every byte on the link for them. The socket, handshake and pairing are a copy of rkd-terminal's server, and serve/pair/status/unpair/install/uninstall mirror its CLI; install is a systemd user unit, Linux only. Claude-Session: https://claude.ai/code/session_01S6WwLMTpMtw1RuCgBY8JGt