Tell the host which games are running, from Playnite's own events (0.4.5, exporter 0.4.0) #11

Merged
enricobuehler merged 1 commits from feat/report-running-state into main 2026-08-20 19:28:09 +00:00
Owner

Recognising a running game is guesswork the host has to do from the outside: it scans for an install directory, an executable, a process name. A title with none of those — an emulated game, a manually added one, a library plugin that records no install directory — could not be recognised at all, and Playnite's launch is a playnite:// hand-off, so the host held no process either. Quitting such a game never ended the streaming session, and "End game" had nothing to aim at.

PR #7 narrowed this by sending every detect signal Playnite had. It could not close it: some games genuinely have none. Playnite, meanwhile, has known the whole time — it starts the game, tracks it in whatever mode the person configured, and raises an event on both edges carrying the pid.

Exporter 0.4.0

Hooks OnGameStarted / OnGameStopped / OnGameStartupCancelled and writes punktfunk-running.json beside the library export, in both drop points:

{ "schema": 1,
  "generatedAt": "2026-08-20T21:00:00Z",   // re-stamped every 30s while anything runs
  "running": [ { "id": "e3b0c442-…", "pid": 4242 } ] }
  • Its own document, not a field on each game. The library export is the whole catalogue — rewriting a thousand games on every launch would be absurd — and the plugin fingerprints that document to decide whether to reconcile, so folding in a fact that changes on every launch would turn each launch into a full library PUT. A new file also needs no Schema bump, where a bumped library schema is refused outright by every installed plugin.
  • Never debounced (a stop delayed is a session delayed), unlike the library export.
  • Seeded at startup from Game.IsRunning, so a Playnite restart — or installing this extension — mid-game converges instead of claiming everything stopped.
  • Deleted when Playnite closes, not emptied. A game Playnite launched keeps running perfectly well after Playnite quits, so "nothing is running" would be a lie; no document at all is honestly "no information".

The plugin (0.4.5)

Polls that file every 3 s and PUTs the set to PUT /library/provider/playnite/running, restating it every 30 s so the host's claim stays alive.

The distinction the whole thing turns on: an empty report says "everything stopped" and ends the streaming session; silence says "no information" and lets the host's claim expire so it falls back to process scanning. Every unusable document — missing, malformed, too new, or older than 90 s with nobody restating it — produces silence. Pinned by tests.

sync.reportRunning (default on) turns it off, surfaced in Settings → Sync rather than left config-file-only: the host acts on this, so the way back out has to be findable by whoever hits the problem.

Compatibility

  • The call goes through the kit's untyped host seam, not ProviderClient.reportRunning. That method landed in plugin-kit 0.4.4 and depending on it makes this repo bun install-broken until the kit publishes (No version matching "^0.4.4"), for the identical request. Kit dep stays ^0.4.3.
  • An older host has no such route: it 404s, the plugin logs it at debug and carries on with detect-hint tracking. So minHost does not need to move — 0.25.0 still holds.
  • Needs a host carrying the route (punktfunk PR #361) for the feature to do anything.

Gates (all green, against the published kit 0.4.3)

bun run typecheck ×3 · bun test 70 pass / 0 fail · bunx biome check · bun run build (backend + SPA) · dotnet build exporter -c Release 0 warnings, 0 errors.

The staleness guard was proved non-vacuous the honest way: backing it out turns the stale-document test red.

No on-glass yet — this needs a Windows box with real Playnite. The exporter half in particular has only been compiled, not run.

Recognising a running game is guesswork the host has to do from the outside: it scans for an install directory, an executable, a process name. A title with **none** of those — an emulated game, a manually added one, a library plugin that records no install directory — could not be recognised at all, and Playnite's launch is a `playnite://` hand-off, so the host held no process either. Quitting such a game never ended the streaming session, and "End game" had nothing to aim at. PR #7 narrowed this by sending every detect signal Playnite had. It could not close it: some games genuinely have none. Playnite, meanwhile, has known the whole time — it starts the game, tracks it in whatever mode the person configured, and raises an event on both edges carrying the pid. ## Exporter 0.4.0 Hooks `OnGameStarted` / `OnGameStopped` / `OnGameStartupCancelled` and writes `punktfunk-running.json` beside the library export, in both drop points: ```jsonc { "schema": 1, "generatedAt": "2026-08-20T21:00:00Z", // re-stamped every 30s while anything runs "running": [ { "id": "e3b0c442-…", "pid": 4242 } ] } ``` - **Its own document, not a field on each game.** The library export is the whole catalogue — rewriting a thousand games on every launch would be absurd — and the plugin *fingerprints* that document to decide whether to reconcile, so folding in a fact that changes on every launch would turn each launch into a full library PUT. A new file also needs no `Schema` bump, where a bumped library schema is refused outright by every installed plugin. - **Never debounced** (a stop delayed is a session delayed), unlike the library export. - **Seeded at startup** from `Game.IsRunning`, so a Playnite restart — or installing this extension — mid-game converges instead of claiming everything stopped. - **Deleted when Playnite closes**, not emptied. A game Playnite launched keeps running perfectly well after Playnite quits, so "nothing is running" would be a lie; no document at all is honestly "no information". ## The plugin (0.4.5) Polls that file every 3 s and PUTs the set to `PUT /library/provider/playnite/running`, restating it every 30 s so the host's claim stays alive. ⭐ **The distinction the whole thing turns on:** an **empty** report says "everything stopped" and ends the streaming session; **silence** says "no information" and lets the host's claim expire so it falls back to process scanning. Every unusable document — missing, malformed, too new, or older than 90 s with nobody restating it — produces **silence**. Pinned by tests. `sync.reportRunning` (default on) turns it off, surfaced in **Settings → Sync** rather than left config-file-only: the host *acts* on this, so the way back out has to be findable by whoever hits the problem. ## Compatibility - The call goes through the kit's **untyped host seam**, not `ProviderClient.reportRunning`. That method landed in plugin-kit 0.4.4 and depending on it makes this repo `bun install`-broken until the kit publishes (`No version matching "^0.4.4"`), for the identical request. Kit dep stays `^0.4.3`. - An older host has no such route: it 404s, the plugin logs it at debug and carries on with detect-hint tracking. So **`minHost` does not need to move** — 0.25.0 still holds. - Needs a host carrying the route (punktfunk PR #361) for the feature to do anything. ## Gates (all green, against the published kit 0.4.3) `bun run typecheck` ×3 · `bun test` **70 pass / 0 fail** · `bunx biome check` · `bun run build` (backend + SPA) · `dotnet build exporter -c Release` **0 warnings, 0 errors**. The staleness guard was proved non-vacuous the honest way: backing it out turns the stale-document test red. ⚠ **No on-glass yet** — this needs a Windows box with real Playnite. The exporter half in particular has only been compiled, not run.
enricobuehler added 1 commit 2026-08-20 18:40:20 +00:00
feat: tell the host which games are running, from Playnite's own events (0.4.5)
CI / exporter (pull_request) Successful in 20s
CI / build (pull_request) Successful in 1m28s
CI / publish (pull_request) Skipped
3cfe78466e
Recognising a running game is guesswork the host has to do from the outside:
it scans for an install directory, an executable, a process name. A title with
none of those — an emulated game, a manually added one, a library plugin that
records no install directory — could not be recognised at all, and Playnite's
launch is a `playnite://` hand-off, so the host held no process either. Quitting
such a game never ended the streaming session, and "End game" had nothing to
aim at. Playnite knew the whole time: it starts the game, tracks it in whatever
mode the person configured, and raises an event on both edges carrying the pid.

**Exporter 0.4.0** hooks `OnGameStarted` / `OnGameStopped` /
`OnGameStartupCancelled` and writes `punktfunk-running.json` beside the library
export, in both drop points. Its own document, not a field on each game: the
library export is the whole catalogue, the plugin fingerprints it to decide
whether to reconcile, and a new file needs no `Schema` bump — where a bumped
library schema would be refused outright by every installed plugin.

Never debounced (a stop delayed is a session delayed), seeded at startup from
`Game.IsRunning` so a restart mid-game converges instead of claiming everything
stopped, re-stamped every 30s while anything runs, and DELETED when Playnite
closes. Deleting rather than emptying is the point: a game Playnite launched
keeps running after Playnite quits, so "nothing is running" would be a lie,
where no document at all is honestly "no information".

**The plugin** polls that file every 3s and PUTs the set to
`/library/provider/playnite/running` when it changes, restating it every 30s so
the host's claim stays alive. An unusable document — missing, malformed, too
new, or older than 90s with nobody restating it — makes it go QUIET rather than
report an empty set: silence lets the host's claim expire and hand tracking back
to its process scan, where an empty report would assert every game had stopped
and end a live session. `sync.reportRunning` (Settings → Sync) turns it off.

The call goes through the kit's untyped host seam rather than
`ProviderClient.reportRunning`, which is the same request: that landed in
plugin-kit 0.4.4 and depending on it would make this plugin unbuildable until
the kit publishes, for no gain.
enricobuehler merged commit 2eaa504fbc into main 2026-08-20 19:28:09 +00:00
enricobuehler deleted branch feat/report-running-state 2026-08-20 19:28:11 +00:00
Sign in to join this conversation.
No Reviewers
No labels
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: unom/punktfunk-plugin-playnite#11