PunktfunkPunktfunk

Host events

Every event the host publishes, the fields each one carries, and the event stream that scripts and plugins read.

The host publishes an event each time something happens on it, such as a device connecting or a game exiting: look one up here with the fields it carries, or read them live from a script. To run a command or call a webhook on an event, see Events & hooks.

The events

KindFires whenCarries
client.connected / client.disconnecteda device connects / goes awayname, fingerprint, plane (native, gamestream, or web for a browser), the profile it plays as (id, display_name; not on gamestream); disconnect adds reason: quit, timeout or error
session.started / session.endeda session starts / endssession id, client, fingerprint, plane, mode (3840x2160@120), HDR, profile. session.ended adds a summary (duration, codec, bitrate span, frames, bring-up time, and ended: local, game_exited, host_ended, host_error, lost, stopped_by_operator or stolen), the same shape as GET /api/v1/session/last
stream.started / stream.stoppedvideo starts / stopsmode, HDR, client, fingerprint, launched app, plane, profile
game.launchingthe host is about to start a launched game; not when it picks up one still runningapp id, title, store, client, fingerprint, plane, preset, profile
game.runninga launched game's own process runs (not just its launcher)app id, title, store, client, fingerprint, plane, profile
game.windowthe game's window reaches the screen, often 5–40 s after game.runningthe same, plus the window's title and app_id
game.exiteda launched game is gonethe same, plus reason: exited (the player quit or it crashed) or terminated (the host closed it, per these settings)
pairing.pendingan unpaired device knocks, once per devicename, fingerprint, plane (native for a browser too: it pairs into the same store)
pairing.completed / pairing.denieda pairing is approved / deniedname, fingerprint, plane
access.granted / access.changedyou pick a device's access when pairing / edit it laterdevice, grants bits, expires_unix (absent: no expiry)
access.expireda streaming device's access runs outdevice
display.created / display.releaseda virtual display is created / kept displays are releasedbackend and mode / count
library.changedthe game library changessource: manual or the provider id
downloads.changeda title's download starts, pauses or ends, or its files are removedapp, title, state (queued, paused, done, failed, cancelled, removed, …)
emulators.changeda managed emulator is installed or removedemulator id
update.availablea newer release is found, once per versionversion, channel, install kind (apt, windows-installer, …)
update.appliedthe updated host first startsfrom, to
action.invokeda host power action or Next monitor is accepted, or failsid (power.sleep, power.reboot, power.shutdown, host.restart, display.next), device (absent for the console), outcome
plugins.changeda plugin registers, restarts or goes awayplugin id
store.changeda plugin install or uninstall finished, or a catalog refreshednothing: re-read the store
settings.changedhost settings changed in the consolethe setting ids
host.started / host.stoppingthe host comes up / shuts downversion, whether GameStream is on

Each event is one JSON object with a rising seq, a ts_ms timestamp and a schema version. Fields are only ever added, never renamed.

{ "seq": 42, "ts_ms": 1784227449526, "schema": 1,
  "kind": "stream.started",
  "stream": { "mode": "2560x1440@120", "hdr": true,
              "client": "Living Room TV", "fingerprint": "9f86d081…",
              "app": "steam:570", "plane": "native",
              "preset": { "id": "3f9a0c11e2b4", "name": "Docked" } } }

preset names the settings preset the device streamed with; it is absent when the device used its plain settings, and on Moonlight. profile names who plays (id, display_name); it is absent on Moonlight.

Reacting to a game, not a stream

stream.stopped means the stream ended; game.exited means the game did. They differ on a desktop stream with no game, or when the session outlives its game. Dim the lights on game.running, when the game's process runs; send "ready" on game.window, when its window is on screen, which can be half a minute later on a cold Proton prefix. game.window doesn't fire on KWin without its window-list permission or on GNOME without the Punktfunk extension.

Scripts read PF_EVENT_GAME_TITLE, PF_EVENT_GAME_APP and, on game.exited, PF_EVENT_REASON: exited when the player quit, terminated when the host closed it.

The event stream

For a shell script, punktfunk-host ctl watch prints one JSON object per line, and resumes and reconnects by itself:

punktfunk-host ctl watch --kinds pairing.pending,stream.'*'

A {"kind":"ctl.resync"} line means events were missed: re-read the state you need.

To read Server-Sent Events directly, call the management API on the host with its token:

. ~/.config/punktfunk/mgmt-token   # sets PUNKTFUNK_MGMT_TOKEN
curl -Nk -H "Authorization: Bearer $PUNKTFUNK_MGMT_TOKEN" \
  "https://127.0.0.1:47990/api/v1/events?kinds=pairing.*,stream.*"
  • Each frame carries id: (the seq), event: (the kind) and data: (the event JSON).
  • ?kinds= filters by kind or domain.*, comma-separated.
  • Reconnect with Last-Event-ID or ?since=<seq> to replay what you missed from the last ~1024 events. If they're gone, an event: dropped frame comes first: re-read /status, /clients and the rest.
  • Without a cursor the stream replays what it holds, then sends event: live. Stay quiet until it arrives.
  • At most 32 streams at once; more get 503.

On this page