diff --git a/docs-site/content/docs/automation.md b/docs-site/content/docs/automation.md index e96e76db..daa46fd3 100644 --- a/docs-site/content/docs/automation.md +++ b/docs-site/content/docs/automation.md @@ -25,6 +25,8 @@ and nothing you configure here runs anywhere near the streaming path. | `client.connected` / `client.disconnected` | a client session is admitted / goes away | device name, cert fingerprint, plane (`native`/`gamestream`); disconnect adds `reason`: `quit` (user stop), `timeout` (vanished), `error` | | `session.started` / `session.ended` | an A/V session registers / ends | session id, client label, mode (`3840x2160@120`), HDR | | `stream.started` / `stream.stopped` | video actually starts / stops | mode, HDR, client name, launched app id/title (when one was requested), plane | +| `game.running` | a launched game's own process is seen running (not merely its launcher) | app id, title, store, client, plane | +| `game.exited` | a launched game is gone | the same, plus `reason`: `exited` (the player quit it) or `terminated` (the host closed it, per your [session⇄game settings](/docs/virtual-displays#when-a-game-ends-and-when-a-session-does)) | | `pairing.pending` | an unpaired device knocks (once per device, not per retry) | device name, fingerprint, plane | | `pairing.completed` / `pairing.denied` | a pairing is approved+stored / denied | device name, fingerprint, plane | | `display.created` / `display.released` | a virtual display is minted / kept displays are released | backend + mode / count | @@ -124,6 +126,29 @@ best-effort, even if the session crashed: A `do` that fails logs, keeps going, and its own `undo` is skipped (it never took effect). +## Reacting to a game, not a stream + +`stream.stopped` tells you the *stream* ended; `game.exited` tells you the *game* did. They are +often the same moment, but not always — a desktop stream has no game at all, and a stream can +outlive its game if you turned off "end the session when the game exits". + +If you have been polling the host to work out when a game finished, you don't need to any more: + +```json +{ "hooks": [ + { "on": "game.running", "run": "~/.config/punktfunk/scripts/game-up.sh" }, + { "on": "game.exited", "run": "~/.config/punktfunk/scripts/game-down.sh" } +] } +``` + +Both carry the title in `PF_EVENT_GAME_TITLE` / `PF_EVENT_GAME_APP`, and `game.exited` adds +`PF_EVENT_REASON` so a script can tell "the player quit" (`exited`) from "the host closed it" +(`terminated`) — worth checking before you, say, power the TV off. + +Ending the session yourself when a game exits needs no script at all: it is the default behavior, +under Host → *Virtual displays* → +[When a game or a session ends](/docs/virtual-displays#when-a-game-ends-and-when-a-session-does). + ## The event stream (`GET /api/v1/events`) For code, subscribe to the SSE stream on the management API (loopback + bearer token — the diff --git a/docs-site/content/docs/virtual-displays.md b/docs-site/content/docs/virtual-displays.md index cd324737..a7261ce8 100644 --- a/docs-site/content/docs/virtual-displays.md +++ b/docs-site/content/docs/virtual-displays.md @@ -136,14 +136,55 @@ Per-backend support: refresh**, with just the game inside. The game boots straight in — no Steam Big Picture to navigate, no game-mode desktop. Steam titles launch with the client hidden (`steam -silent`); non-Steam titles start almost instantly (gamescope up in ~1 s, then the game's own boot). Combined with **keep alive**, - the game keeps running when you disconnect and you re-attach straight back into it; when you quit the - game, the session ends cleanly and your client returns to its library. + the game keeps running when you disconnect and you re-attach straight back into it. Dedicated needs `gamescope` installed on the host; if it isn't, a launch falls back to **Auto** routing. This axis is independent of the preset — pick it under Host → *Virtual displays*. On a box that's already in Steam game mode, a dedicated Steam launch frees game mode's Steam first and restores it when the session ends. (GameStream / Moonlight launches follow the same routing.) +## When a game ends, and when a session does + +A streaming session and the game the host launched for it can share a fate. Two switches, under +Host → *Virtual displays* → **When a game or a session ends**. They apply to every store and both +protocols — and only ever to a game **this host launched for the session**: a game you started +yourself is never touched. + +### When the game exits + +**End the session** (default). Quit the game and your client goes back to its own library instead of +staring at your desktop. This is what a dedicated game session has always done; it now works on +every path — your live KDE/GNOME/Sway desktop, an attached gamescope, and Moonlight. + +**Keep streaming** if you stream the desktop and treat the game as incidental. + +### When the session ends + +Whether stopping — or losing — a session also closes the game. + +- **Leave it running** (default). Nothing is ever closed. Disconnect, and the game plays on for when + you come back. +- **Close it on Stop** — closing the client, or pressing *Stop* in the console, closes the game. + A network drop does not: you get your game back when you reconnect. +- **Always close it** — a drop closes it too, but only after a **reconnect window** (5 minutes by + default). Reconnect inside the window and nothing happens; the console shows the countdown while + it runs, with an **End now** button if you'd rather not wait. + +Closing a game costs whatever it hadn't saved, which is why nothing closes by default. The host asks +first — a polite close, the same thing clicking the window's X does, so the game runs its own +shutdown — and only forces the issue after ten seconds of being ignored. + +> **Keep alive and this setting are different clocks.** Keep-alive decides how long the *display* +> outlives a disconnect (10 s by default); the reconnect window decides how long the *game* does +> (5 minutes). A display set to **Forever** stays up regardless of what happens to the game — a +> pinned display is a deliberate "this box is a game host" choice, and closing a game doesn't undo it. + +### Automation + +The host publishes `game.running` and `game.exited` events (the latter says whether the player quit +it or the host closed it), so a hook or plugin can react without polling. See +[Automation](/docs/automation). + ## Persistent scaling Set your display **scaling** once and have it stick across reconnects. This works by giving each