Commit Graph
3 Commits
Author SHA1 Message Date
enricobuehler f0e71e928a fix(gamestream): the resolved launch command is what the backend nests
plugin-kit-publish / publish (push) Failing after 34s
ci / web (push) Successful in 58s
ci / docs-site (push) Successful in 1m2s
apple / swift (push) Successful in 5m39s
ci / bench (push) Successful in 6m18s
deb / build-publish (push) Successful in 8m56s
decky / build-publish (push) Successful in 34s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 10s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 42s
android / android (push) Successful in 12m2s
deb / build-publish-client-arm64 (push) Successful in 7m35s
ci / rust-arm64 (push) Successful in 17m58s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 1m26s
windows-host / package (push) Successful in 19m41s
deb / build-publish-host (push) Successful in 18m45s
docker / build-push (ci, ci/rust-ci-noble.Dockerfile, punktfunk-rust-ci-noble) (push) Successful in 8m49s
arch / build-publish (push) Successful in 22m17s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 9m4s
ci / rust (push) Failing after 23m6s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 12m7s
docker / deploy-docs (push) Successful in 28s
docker / build-push-arm64cross (push) Successful in 4m20s
apple / screenshots (push) Successful in 24m30s
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Successful in 21m41s
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Successful in 21m18s
A Windows/macOS build compiled the resolved-launch parameter out entirely and
warned about it, because the two uses left were both Linux-gated. Both platforms
want the same value: off Linux `set_launch_command` is a backend no-op and a
library title resolves to no command at all (the interactive-session spawner
launches it by id), so the cfg split was carrying an older distinction that no
longer exists.

Also covers the Windows half of `process_name` with a test — the pure name match
plus a live scan for this test process, since `find`'s early return would
otherwise skip the signal entirely and the test would pass vacuously.

Gates: .133 check + clippy --all-targets clean with no warnings, 5/5 procscan
(incl. live); .21 299 tests + fmt.
2026-07-26 18:28:13 +02:00
enricobuehler 110eabf281 feat(library/providers): let a provider say how to recognize its games
A plugin's titles launch through the provider's own client, which hands off and
exits — so the host had nothing left to watch, and both lifetime behaviors went
quiet for exactly the entries a provider contributes. A `ProviderEntry` (and a
manual custom entry) may now carry an optional `detect` hint: install dir, exe,
or process name.

It is deliberately a subset of what the host tracks internally. A Steam appid or
a launcher's environment marker are things the host discovers for itself and
would be meaningless — or dangerous — to take on someone's word; where a title
is installed is something only the provider knows. The host's own findings win
where both exist, so a stale export can never redirect the matcher, and a blank
field is treated as absent rather than as "match everything" — an empty install
dir would otherwise prefix-match every process on the box, and this feature can
end processes.

`process_name` is the weakest of the three and the only one typed by hand, so it
is matched case-insensitively against the image's file name and nothing else:
`retroarch` finds RetroArch, not a helper whose name merely starts the same way,
and not a script that happens to live in a `retroarch/` directory. The
never-adopt-a-pre-existing-process rule still bounds it.

Also: the tray summary gains the running-game row (with the closing-in countdown
for a game whose client is gone — visible at the machine without opening the
console), the SDK mirrors the `game.*` events, and its generated client catches
up with the endpoints Phase 1 added.

Gates on .21: check + clippy --all-targets clean, 299 tests, fmt CI-parity,
openapi regenerated (GameEntry still carries no `detect` outbound); SDK tsc +
54 tests green.
2026-07-26 18:28:13 +02:00
enricobuehlerandClaude Fable 5 ed4e3d3ab6 feat(session): end-game/end-session lifetime on Windows
Phase 2. The lease, the settings, the events and the status surface were already
platform-neutral; what Windows lacked was a way to see processes and a way to ask
one to close.

`procscan` splits per-OS behind one contract. The Windows matcher is a Toolhelp
snapshot plus each process's full image path (`QueryFullProcessImageNameW`), with
creation times from `GetProcessTimes` enforcing the two rules the module exists
for: never adopt a process that predates the launch, never trust a bare pid. Both
matter more here than on Linux — the host is SYSTEM, so it can see and signal
everything, and Windows recycles pids briskly.

Path matching is case-insensitive and separator-aware, and normalizes the `\\?\`
prefix `canonicalize` adds, without which a store-derived path would never compare
equal to a live image path.

There is no reaper argv and no readable environment on Windows, so a spec carrying
only a Steam appid or an env marker matches nothing rather than falling through to
an empty predicate. Steam's appid instead feeds a **veto**: its per-app `Running`
flag in the user's hive can't be trusted to say a game IS running (Steam sets it
around updates too, and leaves it stale after a crash), but it is exactly right for
refusing to declare one gone. If the matcher can't see a game whose exe sits
outside its manifest's install dir, ending the session would be a false positive
the player feels immediately; honoring the veto only ever leaves a stream up.

Termination asks before it insists, which on Windows needs a detail that fails
silently if missed: `EnumWindows` only sees the calling thread's desktop, and the
host's is session 0, which holds none of the user's windows. So the terminating
thread binds to the input desktop first (the pattern `pf-inject`'s `sendinput.rs`
uses), posts `WM_CLOSE` to the game's visible top-level windows, waits, and only
then calls `TerminateProcess` on freshly re-verified pids. Without the desktop
bind the polite pass finds nothing and every game dies unsaved.

Job Objects, planned as WP2.3, are deferred with the reasoning in the design doc:
every Windows launch either hands off through a launcher or the shell — where a job
would wrap a shim that exits immediately — or is a direct exe whose path we already
know, and wiring one in would touch the `CreateProcessAsUserW` token path the UAC
and secure-desktop work depends on.

Also: the watcher's steady-state poll now re-verifies known pids and only re-scans
the whole table once they all vanish (which still catches a game that re-execs).
On Windows a full scan is an `OpenProcess` per process on the box, so this is the
difference between a negligible and a noticeable poll.

Gates: Linux .21 check/clippy/fmt/293 tests green; Windows .133 check + clippy
--all-targets clean, and the 4 procscan tests pass — including the live one that
finds the test process in the real process table and rejects a wrong creation time.
On-glass Windows (Steam/Epic/GOG/Xbox titles, the unsaved-progress check) still
owed; it needs a box with games on it.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-26 18:28:13 +02:00