/** * Generated by orval v8.22.0 ๐Ÿบ * Do not edit manually. * punktfunk management API * Control-plane API for managing a punktfunk streaming host: host capabilities, runtime status, paired clients, the pairing PIN flow, and session control. Authentication: HTTP bearer token, enforced on every route except `/api/v1/health` when the host is started with a management token (mandatory for non-loopback binds). * OpenAPI spec version: 0.12.0 */ import { Schema as S } from 'effect'; /** * @summary List paired clients */ export const ListPairedClientsResponseItem = S.Struct({ "fingerprint": S.String.annotations({ description: 'Lowercase hex SHA-256 of the client certificate DER โ€” the client\'s stable id here.' }), "not_after_unix": S.optional(S.NullOr(S.Number)).annotations({ description: 'Certificate validity end (unix seconds).' }), "not_before_unix": S.optional(S.NullOr(S.Number)).annotations({ description: 'Certificate validity start (unix seconds).' }), "subject": S.optional(S.NullOr(S.String)).annotations({ description: 'Certificate subject (e.g. `CN=NVIDIA GameStream Client`), if the DER parses.' }) }).annotations({ description: 'A paired (certificate-pinned) Moonlight client.' }) export const ListPairedClientsResponse = S.Array(ListPairedClientsResponseItem) /** * Removes the client's certificate from the pairing store. Caveat: the nvhttp TLS layer * does not yet reject unlisted certificates (`gamestream/tls.rs` accepts any well-formed * client cert โ€” a planned hardening step), so until that lands this removes the client * from the listing without severing its ability to reconnect. * @summary Unpair a client */ export const UnpairClientParams = S.Struct({ "fingerprint": S.String.annotations({ description: 'Hex SHA-256 fingerprint of the client certificate DER (64 chars, case-insensitive)' }) }) /** * Lists every backend the host knows how to drive, flags which are usable right now, and marks * the one an unspecified (`Auto`) client request resolves to. Clients pass an `id` to their * `--compositor` flag (or `PUNKTFUNK_COMPOSITOR_*` over the C ABI) to request it. * @summary Available compositor backends */ export const ListCompositorsResponseItem = S.Struct({ "available": S.Boolean.annotations({ description: 'Usable on this host right now: the live session\'s own compositor, or gamescope wherever\nits binary is installed.' }), "default": S.Boolean.annotations({ description: 'True for the backend an `Auto` (unspecified) request resolves to right now.' }), "id": S.String.annotations({ description: 'Stable identifier (`\"kwin\"` | `\"wlroots\"` | `\"mutter\"` | `\"gamescope\"`) โ€” pass this to a\nclient\'s `--compositor` flag.' }), "label": S.String.annotations({ description: 'Human-readable label for UIs.' }) }).annotations({ description: 'A compositor backend the host can drive a virtual output on, and whether it\'s usable now.' }) export const ListCompositorsResponse = S.Array(ListCompositorsResponseItem) /** * Set the **manual** desktop arrangement โ€” per-identity-slot `(x, y)` offsets so a multi-monitor * group (ยง6A/ยง6B) comes back where the operator placed it. Persisted into the policy's layout block * and switched to manual mode; applied from the next connect (a live group re-applies on its next * acquire). Locks in the current effective behavior as explicit fields, so arranging displays never * silently changes keep-alive/topology/conflict/identity. See `design/display-management.md` ยง6.2. * @summary Arrange virtual displays */ export const SetDisplayLayoutBody = S.Struct({ "positions": S.optional(S.Record({ key: S.String, value: S.Struct({ "x": S.Number, "y": S.Number }).annotations({ description: 'A desktop-space offset for a display (top-left origin).' }) })).annotations({ description: '`{\"\": {\"x\": โ€ฆ, \"y\": โ€ฆ}}` โ€” where each arranged display\'s top-left sits.' }) }).annotations({ description: 'Request body for `setDisplayLayout`: per-identity-slot desktop offsets, keyed by the identity-slot\nid as a string (the same id `\/display\/state` reports as `identity_slot`).' }) export const setDisplayLayoutResponseCustomPresetsItemFieldsKeepAliveTwoSecondsMin = 0; export const setDisplayLayoutResponseCustomPresetsItemFieldsMaxDisplaysMin = 0; export const setDisplayLayoutResponseEffectiveKeepAliveTwoSecondsMin = 0; export const setDisplayLayoutResponseEffectiveMaxDisplaysMin = 0; export const setDisplayLayoutResponsePresetsItemFieldsKeepAliveTwoSecondsMin = 0; export const setDisplayLayoutResponsePresetsItemFieldsMaxDisplaysMin = 0; export const setDisplayLayoutResponseSettingsKeepAliveTwoSecondsMin = 0; export const setDisplayLayoutResponseSettingsMaxDisplaysMin = 0; export const setDisplayLayoutResponseSettingsVersionMin = 0; export const SetDisplayLayoutResponse = S.Struct({ "configured": S.Boolean.annotations({ description: 'True once a `display-settings.json` exists (the console has configured this host).' }), "custom_presets": S.Array(S.Struct({ "fields": S.Struct({ "identity": S.Literal('shared', 'per-client', 'per-client-mode').annotations({ description: 'Stable display identity, so desktop environments persist per-display config (KDE scaling). Stored\nat Stage 0; carriers wired from the identity stage.' }), "keep_alive": S.Union(S.Struct({ "mode": S.Literal('off') }).annotations({ description: 'Tear the display down at session end (today\'s default on every backend but Windows, which\nlingers 10 s).' }), S.Struct({ "mode": S.Literal('duration'), "seconds": S.Number.pipe(S.greaterThanOrEqualTo(setDisplayLayoutResponseCustomPresetsItemFieldsKeepAliveTwoSecondsMin)).annotations({ description: 'Linger window in seconds.' }) }).annotations({ description: 'Keep the display for `seconds` after the last session leaves, then tear it down; a reconnect\ninside the window reuses it.' }), S.Struct({ "mode": S.Literal('forever') }).annotations({ description: 'Keep the display until host shutdown or an explicit release (the `Pinned` lifecycle state).\n\*\*Not honored until the display-lifecycle stage\*\* โ€” rejected by the mgmt PUT at Stage 0.' })).annotations({ description: 'How long a virtual display (and, on gamescope\'s bare spawn, the nested session + its game)\nsurvives after the last client session detaches. Serialized as an object tagged on `mode`\n(`{\"mode\":\"off\"}` \/ `{\"mode\":\"duration\",\"seconds\":300}` \/ `{\"mode\":\"forever\"}`) so the web form\nand the OpenAPI schema stay simple.' }), "layout": S.Struct({ "mode": S.optional(S.Literal('auto-row', 'manual')).annotations({ description: 'How group members are arranged in the desktop coordinate space. Stored at Stage 0; applied from\nthe multi-monitor stage.' }), "positions": S.optional(S.Record({ key: S.String, value: S.Struct({ "x": S.Number, "y": S.Number }).annotations({ description: 'A desktop-space offset for a display (top-left origin).' }) })) }).annotations({ description: 'Group layout: the arrangement mode plus, for [`LayoutMode::Manual`], per-slot offsets keyed by\nidentity-slot id (string keys for stable JSON).' }), "max_displays": S.Number.pipe(S.greaterThanOrEqualTo(setDisplayLayoutResponseCustomPresetsItemFieldsMaxDisplaysMin)), "mode_conflict": S.Literal('separate', 'steal', 'join', 'reject').annotations({ description: 'Admission when a \*different\* client connects while a display\/session is already live and asks for\na different mode. Stored at Stage 0; enforced from the mode-conflict admission stage.' }), "topology": S.Literal('auto', 'extend', 'primary', 'exclusive').annotations({ description: 'What the host does to the box\'s display topology while managed virtual displays are up.' }) }).annotations({ description: 'The six display-behavior axes this preset applies (the same shape a built-in preset expands to).' }), "game_session": S.optional(S.Literal('auto', 'dedicated')).annotations({ description: 'The game-session routing this preset applies (orthogonal to the six axes; see [`GameSession`]).\nA custom preset captures the operator\'s \*full\* setup, so โ€” unlike a built-in preset โ€” applying\none does set this axis.' }), "id": S.String.annotations({ description: 'Host-assigned, stable for the life of the entry (the `{id}` in the CRUD path).' }), "name": S.String.annotations({ description: 'User-facing name shown on the preset card; editable.' }) }).annotations({ description: 'A user-defined named preset: a saved bundle of the six display-behavior axes (exactly what a\nbuilt-in [`Preset`] expands to) plus the orthogonal game-session axis, that the operator names\nand applies from the console.\n\nUnlike the built-in [`Preset`]s (a closed enum), custom presets are \*\*data\*\* โ€” a catalog stored in\n`\/display-presets.json`. Applying one writes a `Custom` [`DisplayPolicy`] carrying these\nfields (the console reuses `PUT \/display\/settings`), so [`DisplayPolicy::effective`] stays pure and\nthe built-in set is never touched. The catalog is decoupled from the active `display-settings.json`:\nediting or deleting a preset never mutates the running policy (re-apply to adopt a change).' })).annotations({ description: 'The operator\'s saved custom presets (`display-presets.json`) โ€” named field-bundles rendered\nalongside the built-ins. Managed via `POST\/PUT\/DELETE \/display\/presets`; applied by writing a\n`Custom` policy carrying the preset\'s fields.' }), "effective": S.Struct({ "identity": S.Literal('shared', 'per-client', 'per-client-mode').annotations({ description: 'Stable display identity, so desktop environments persist per-display config (KDE scaling). Stored\nat Stage 0; carriers wired from the identity stage.' }), "keep_alive": S.Union(S.Struct({ "mode": S.Literal('off') }).annotations({ description: 'Tear the display down at session end (today\'s default on every backend but Windows, which\nlingers 10 s).' }), S.Struct({ "mode": S.Literal('duration'), "seconds": S.Number.pipe(S.greaterThanOrEqualTo(setDisplayLayoutResponseEffectiveKeepAliveTwoSecondsMin)).annotations({ description: 'Linger window in seconds.' }) }).annotations({ description: 'Keep the display for `seconds` after the last session leaves, then tear it down; a reconnect\ninside the window reuses it.' }), S.Struct({ "mode": S.Literal('forever') }).annotations({ description: 'Keep the display until host shutdown or an explicit release (the `Pinned` lifecycle state).\n\*\*Not honored until the display-lifecycle stage\*\* โ€” rejected by the mgmt PUT at Stage 0.' })).annotations({ description: 'How long a virtual display (and, on gamescope\'s bare spawn, the nested session + its game)\nsurvives after the last client session detaches. Serialized as an object tagged on `mode`\n(`{\"mode\":\"off\"}` \/ `{\"mode\":\"duration\",\"seconds\":300}` \/ `{\"mode\":\"forever\"}`) so the web form\nand the OpenAPI schema stay simple.' }), "layout": S.Struct({ "mode": S.optional(S.Literal('auto-row', 'manual')).annotations({ description: 'How group members are arranged in the desktop coordinate space. Stored at Stage 0; applied from\nthe multi-monitor stage.' }), "positions": S.optional(S.Record({ key: S.String, value: S.Struct({ "x": S.Number, "y": S.Number }).annotations({ description: 'A desktop-space offset for a display (top-left origin).' }) })) }).annotations({ description: 'Group layout: the arrangement mode plus, for [`LayoutMode::Manual`], per-slot offsets keyed by\nidentity-slot id (string keys for stable JSON).' }), "max_displays": S.Number.pipe(S.greaterThanOrEqualTo(setDisplayLayoutResponseEffectiveMaxDisplaysMin)), "mode_conflict": S.Literal('separate', 'steal', 'join', 'reject').annotations({ description: 'Admission when a \*different\* client connects while a display\/session is already live and asks for\na different mode. Stored at Stage 0; enforced from the mode-conflict admission stage.' }), "topology": S.Literal('auto', 'extend', 'primary', 'exclusive').annotations({ description: 'What the host does to the box\'s display topology while managed virtual displays are up.' }) }).annotations({ description: 'The effective (preset-expanded) policy currently in force.' }), "enforced": S.Array(S.String).annotations({ description: 'Option names this build enforces right now. All five axes are now acted on (keep_alive +\ntopology since Stage 0-2, identity Stage 3, mode_conflict Stage 4, layout Stage 5) โ€” the console\nreads this to know which controls are live vs. \"coming soon\" (per-backend nuance, e.g. layout\nposition apply being KWin-only, is reported per display in `\/display\/state`).' }), "presets": S.Array(S.Struct({ "fields": S.Struct({ "identity": S.Literal('shared', 'per-client', 'per-client-mode').annotations({ description: 'Stable display identity, so desktop environments persist per-display config (KDE scaling). Stored\nat Stage 0; carriers wired from the identity stage.' }), "keep_alive": S.Union(S.Struct({ "mode": S.Literal('off') }).annotations({ description: 'Tear the display down at session end (today\'s default on every backend but Windows, which\nlingers 10 s).' }), S.Struct({ "mode": S.Literal('duration'), "seconds": S.Number.pipe(S.greaterThanOrEqualTo(setDisplayLayoutResponsePresetsItemFieldsKeepAliveTwoSecondsMin)).annotations({ description: 'Linger window in seconds.' }) }).annotations({ description: 'Keep the display for `seconds` after the last session leaves, then tear it down; a reconnect\ninside the window reuses it.' }), S.Struct({ "mode": S.Literal('forever') }).annotations({ description: 'Keep the display until host shutdown or an explicit release (the `Pinned` lifecycle state).\n\*\*Not honored until the display-lifecycle stage\*\* โ€” rejected by the mgmt PUT at Stage 0.' })).annotations({ description: 'How long a virtual display (and, on gamescope\'s bare spawn, the nested session + its game)\nsurvives after the last client session detaches. Serialized as an object tagged on `mode`\n(`{\"mode\":\"off\"}` \/ `{\"mode\":\"duration\",\"seconds\":300}` \/ `{\"mode\":\"forever\"}`) so the web form\nand the OpenAPI schema stay simple.' }), "layout": S.Struct({ "mode": S.optional(S.Literal('auto-row', 'manual')).annotations({ description: 'How group members are arranged in the desktop coordinate space. Stored at Stage 0; applied from\nthe multi-monitor stage.' }), "positions": S.optional(S.Record({ key: S.String, value: S.Struct({ "x": S.Number, "y": S.Number }).annotations({ description: 'A desktop-space offset for a display (top-left origin).' }) })) }).annotations({ description: 'Group layout: the arrangement mode plus, for [`LayoutMode::Manual`], per-slot offsets keyed by\nidentity-slot id (string keys for stable JSON).' }), "max_displays": S.Number.pipe(S.greaterThanOrEqualTo(setDisplayLayoutResponsePresetsItemFieldsMaxDisplaysMin)), "mode_conflict": S.Literal('separate', 'steal', 'join', 'reject').annotations({ description: 'Admission when a \*different\* client connects while a display\/session is already live and asks for\na different mode. Stored at Stage 0; enforced from the mode-conflict admission stage.' }), "topology": S.Literal('auto', 'extend', 'primary', 'exclusive').annotations({ description: 'What the host does to the box\'s display topology while managed virtual displays are up.' }) }).annotations({ description: 'The effective policy this preset expands to (the same fields a `custom` policy carries).' }), "id": S.String.annotations({ description: 'The preset id (`default` | `gaming-rig` | `shared-desktop` | `hotdesk` | `workstation`).' }), "summary": S.String.annotations({ description: 'One-line story shown next to the option.' }) }).annotations({ description: 'One preset\'s human-facing description + the fields it expands to, so the console can render a\npreset picker with an accurate \"what this does\" preview without hardcoding the expansion.' })).annotations({ description: 'Every named preset and what it expands to (for the picker\'s preview).' }), "settings": S.Struct({ "ddc_power_off": S.optional(S.Boolean).annotations({ description: 'EXPERIMENTAL (Windows): command physical monitors\' panels off over DDC\/CI (VCP 0xD6 โ†’\nDPMS off) right before an `Exclusive` isolate deactivates them, and back on at restore.\nTargets the \"connected-but-dark head\" periodic-stutter class (monitor standby\nauto-input-scan \/ DP link churn while the virtual display is the sole active display) at\nthe monitor-firmware level. Best-effort โ€” monitors without DDC\/CI (or with it disabled in\nthe OSD) are skipped. Orthogonal to `preset` (like `game_session`): preserved across\npreset changes; `#[serde(default)]` = off so existing `display-settings.json` files are\nuntouched.' }), "game_session": S.optional(S.Literal('auto', 'dedicated')).annotations({ description: 'How a game-launching session is served (`design\/gamemode-and-dedicated-sessions.md` ยง5.2).\nOrthogonal to `preset`\/lifecycle โ€” preserved across preset changes; `#[serde(default)]` = `Auto`\nso existing `display-settings.json` files are untouched.' }), "identity": S.optional(S.Literal('shared', 'per-client', 'per-client-mode')).annotations({ description: 'Stable display identity, so desktop environments persist per-display config (KDE scaling). Stored\nat Stage 0; carriers wired from the identity stage.' }), "keep_alive": S.optional(S.Union(S.Struct({ "mode": S.Literal('off') }).annotations({ description: 'Tear the display down at session end (today\'s default on every backend but Windows, which\nlingers 10 s).' }), S.Struct({ "mode": S.Literal('duration'), "seconds": S.Number.pipe(S.greaterThanOrEqualTo(setDisplayLayoutResponseSettingsKeepAliveTwoSecondsMin)).annotations({ description: 'Linger window in seconds.' }) }).annotations({ description: 'Keep the display for `seconds` after the last session leaves, then tear it down; a reconnect\ninside the window reuses it.' }), S.Struct({ "mode": S.Literal('forever') }).annotations({ description: 'Keep the display until host shutdown or an explicit release (the `Pinned` lifecycle state).\n\*\*Not honored until the display-lifecycle stage\*\* โ€” rejected by the mgmt PUT at Stage 0.' }))).annotations({ description: 'How long a virtual display (and, on gamescope\'s bare spawn, the nested session + its game)\nsurvives after the last client session detaches. Serialized as an object tagged on `mode`\n(`{\"mode\":\"off\"}` \/ `{\"mode\":\"duration\",\"seconds\":300}` \/ `{\"mode\":\"forever\"}`) so the web form\nand the OpenAPI schema stay simple.' }), "layout": S.optional(S.Struct({ "mode": S.optional(S.Literal('auto-row', 'manual')).annotations({ description: 'How group members are arranged in the desktop coordinate space. Stored at Stage 0; applied from\nthe multi-monitor stage.' }), "positions": S.optional(S.Record({ key: S.String, value: S.Struct({ "x": S.Number, "y": S.Number }).annotations({ description: 'A desktop-space offset for a display (top-left origin).' }) })) })).annotations({ description: 'Group layout: the arrangement mode plus, for [`LayoutMode::Manual`], per-slot offsets keyed by\nidentity-slot id (string keys for stable JSON).' }), "max_displays": S.optional(S.Number.pipe(S.greaterThanOrEqualTo(setDisplayLayoutResponseSettingsMaxDisplaysMin))).annotations({ description: 'Upper bound on simultaneously-live virtual displays (clamped to `1..=16` on write).' }), "mode_conflict": S.optional(S.Literal('separate', 'steal', 'join', 'reject')).annotations({ description: 'Admission when a \*different\* client connects while a display\/session is already live and asks for\na different mode. Stored at Stage 0; enforced from the mode-conflict admission stage.' }), "pnp_disable_monitors": S.optional(S.Boolean).annotations({ description: 'EXPERIMENTAL (Windows): DISABLE physical monitors\' PnP device nodes for the stream\'s\nduration (persistently, so a standby monitor\/TV whose hot-plug events re-arrive stays\ndisabled) and re-enable them at teardown. Two selectors: the monitors an `Exclusive`\nisolate deactivated, plus โ€” in ANY topology โ€” external monitors that are connected but not\npart of the desktop (the standby TV that was never active, whose input auto-scan \/\ninstant-on HPD cycling re-probes the link every few seconds). Targets the same\n\"connected-but-dark head\" periodic-stutter class as [`Self::ddc_power_off`], but at the\nWindows-reaction level: a disabled devnode\'s wake events trigger no PnP arrival, no CCD\nre-evaluation, no DWM invalidation. A crash-recovery journal re-enables leftovers on host\nstartup. Orthogonal to `preset` (like `game_session`); `#[serde(default)]` = off.' }), "preset": S.optional(S.Literal('custom', 'default', 'gaming-rig', 'shared-desktop', 'hotdesk', 'workstation')).annotations({ description: 'A named bundle of the fields below. `Custom` (the default) means the explicit fields rule; any\nother preset ignores the stored fields and expands to its own ([`DisplayPolicy::effective`]).' }), "topology": S.optional(S.Literal('auto', 'extend', 'primary', 'exclusive')).annotations({ description: 'What the host does to the box\'s display topology while managed virtual displays are up.' }), "version": S.optional(S.Number.pipe(S.greaterThanOrEqualTo(setDisplayLayoutResponseSettingsVersionMin))).annotations({ description: 'Schema version (currently 1) โ€” lets a future field addition migrate rather than reject.' }) }).annotations({ description: 'The stored policy (preset + custom fields), or the built-in default when unconfigured.' }) }).annotations({ description: 'Full display-management state for the console: the stored policy, every preset\'s expansion, the\nresolved effective policy, and which options this build actually enforces yet (Stage 0 wires\nkeep-alive linger + topology; the rest are stored but not yet acted on).' }) /** * The operator's named field-bundles (`display-presets.json`). These also ride the * `GET /display/settings` response (`custom_presets`), so the console rarely needs this directly. * @summary List the saved custom presets */ export const listCustomPresetsResponseFieldsKeepAliveTwoSecondsMin = 0; export const listCustomPresetsResponseFieldsMaxDisplaysMin = 0; export const ListCustomPresetsResponseItem = S.Struct({ "fields": S.Struct({ "identity": S.Literal('shared', 'per-client', 'per-client-mode').annotations({ description: 'Stable display identity, so desktop environments persist per-display config (KDE scaling). Stored\nat Stage 0; carriers wired from the identity stage.' }), "keep_alive": S.Union(S.Struct({ "mode": S.Literal('off') }).annotations({ description: 'Tear the display down at session end (today\'s default on every backend but Windows, which\nlingers 10 s).' }), S.Struct({ "mode": S.Literal('duration'), "seconds": S.Number.pipe(S.greaterThanOrEqualTo(listCustomPresetsResponseFieldsKeepAliveTwoSecondsMin)).annotations({ description: 'Linger window in seconds.' }) }).annotations({ description: 'Keep the display for `seconds` after the last session leaves, then tear it down; a reconnect\ninside the window reuses it.' }), S.Struct({ "mode": S.Literal('forever') }).annotations({ description: 'Keep the display until host shutdown or an explicit release (the `Pinned` lifecycle state).\n\*\*Not honored until the display-lifecycle stage\*\* โ€” rejected by the mgmt PUT at Stage 0.' })).annotations({ description: 'How long a virtual display (and, on gamescope\'s bare spawn, the nested session + its game)\nsurvives after the last client session detaches. Serialized as an object tagged on `mode`\n(`{\"mode\":\"off\"}` \/ `{\"mode\":\"duration\",\"seconds\":300}` \/ `{\"mode\":\"forever\"}`) so the web form\nand the OpenAPI schema stay simple.' }), "layout": S.Struct({ "mode": S.optional(S.Literal('auto-row', 'manual')).annotations({ description: 'How group members are arranged in the desktop coordinate space. Stored at Stage 0; applied from\nthe multi-monitor stage.' }), "positions": S.optional(S.Record({ key: S.String, value: S.Struct({ "x": S.Number, "y": S.Number }).annotations({ description: 'A desktop-space offset for a display (top-left origin).' }) })) }).annotations({ description: 'Group layout: the arrangement mode plus, for [`LayoutMode::Manual`], per-slot offsets keyed by\nidentity-slot id (string keys for stable JSON).' }), "max_displays": S.Number.pipe(S.greaterThanOrEqualTo(listCustomPresetsResponseFieldsMaxDisplaysMin)), "mode_conflict": S.Literal('separate', 'steal', 'join', 'reject').annotations({ description: 'Admission when a \*different\* client connects while a display\/session is already live and asks for\na different mode. Stored at Stage 0; enforced from the mode-conflict admission stage.' }), "topology": S.Literal('auto', 'extend', 'primary', 'exclusive').annotations({ description: 'What the host does to the box\'s display topology while managed virtual displays are up.' }) }).annotations({ description: 'The six display-behavior axes this preset applies (the same shape a built-in preset expands to).' }), "game_session": S.optional(S.Literal('auto', 'dedicated')).annotations({ description: 'The game-session routing this preset applies (orthogonal to the six axes; see [`GameSession`]).\nA custom preset captures the operator\'s \*full\* setup, so โ€” unlike a built-in preset โ€” applying\none does set this axis.' }), "id": S.String.annotations({ description: 'Host-assigned, stable for the life of the entry (the `{id}` in the CRUD path).' }), "name": S.String.annotations({ description: 'User-facing name shown on the preset card; editable.' }) }).annotations({ description: 'A user-defined named preset: a saved bundle of the six display-behavior axes (exactly what a\nbuilt-in [`Preset`] expands to) plus the orthogonal game-session axis, that the operator names\nand applies from the console.\n\nUnlike the built-in [`Preset`]s (a closed enum), custom presets are \*\*data\*\* โ€” a catalog stored in\n`\/display-presets.json`. Applying one writes a `Custom` [`DisplayPolicy`] carrying these\nfields (the console reuses `PUT \/display\/settings`), so [`DisplayPolicy::effective`] stays pure and\nthe built-in set is never touched. The catalog is decoupled from the active `display-settings.json`:\nediting or deleting a preset never mutates the running policy (re-apply to adopt a change).' }) export const ListCustomPresetsResponse = S.Array(ListCustomPresetsResponseItem) /** * Stores a named bundle of the display-behavior axes (+ the game-session axis) the operator can * apply later. The host assigns a stable id, returned in the body. Applying a preset is a * `PUT /display/settings` with a `Custom` policy carrying its `fields` โ€” no separate apply route. * @summary Save a custom preset */ export const createCustomPresetBodyFieldsKeepAliveTwoSecondsMin = 0; export const createCustomPresetBodyFieldsMaxDisplaysMin = 0; export const CreateCustomPresetBody = S.Struct({ "fields": S.Struct({ "identity": S.Literal('shared', 'per-client', 'per-client-mode').annotations({ description: 'Stable display identity, so desktop environments persist per-display config (KDE scaling). Stored\nat Stage 0; carriers wired from the identity stage.' }), "keep_alive": S.Union(S.Struct({ "mode": S.Literal('off') }).annotations({ description: 'Tear the display down at session end (today\'s default on every backend but Windows, which\nlingers 10 s).' }), S.Struct({ "mode": S.Literal('duration'), "seconds": S.Number.pipe(S.greaterThanOrEqualTo(createCustomPresetBodyFieldsKeepAliveTwoSecondsMin)).annotations({ description: 'Linger window in seconds.' }) }).annotations({ description: 'Keep the display for `seconds` after the last session leaves, then tear it down; a reconnect\ninside the window reuses it.' }), S.Struct({ "mode": S.Literal('forever') }).annotations({ description: 'Keep the display until host shutdown or an explicit release (the `Pinned` lifecycle state).\n\*\*Not honored until the display-lifecycle stage\*\* โ€” rejected by the mgmt PUT at Stage 0.' })).annotations({ description: 'How long a virtual display (and, on gamescope\'s bare spawn, the nested session + its game)\nsurvives after the last client session detaches. Serialized as an object tagged on `mode`\n(`{\"mode\":\"off\"}` \/ `{\"mode\":\"duration\",\"seconds\":300}` \/ `{\"mode\":\"forever\"}`) so the web form\nand the OpenAPI schema stay simple.' }), "layout": S.Struct({ "mode": S.optional(S.Literal('auto-row', 'manual')).annotations({ description: 'How group members are arranged in the desktop coordinate space. Stored at Stage 0; applied from\nthe multi-monitor stage.' }), "positions": S.optional(S.Record({ key: S.String, value: S.Struct({ "x": S.Number, "y": S.Number }).annotations({ description: 'A desktop-space offset for a display (top-left origin).' }) })) }).annotations({ description: 'Group layout: the arrangement mode plus, for [`LayoutMode::Manual`], per-slot offsets keyed by\nidentity-slot id (string keys for stable JSON).' }), "max_displays": S.Number.pipe(S.greaterThanOrEqualTo(createCustomPresetBodyFieldsMaxDisplaysMin)), "mode_conflict": S.Literal('separate', 'steal', 'join', 'reject').annotations({ description: 'Admission when a \*different\* client connects while a display\/session is already live and asks for\na different mode. Stored at Stage 0; enforced from the mode-conflict admission stage.' }), "topology": S.Literal('auto', 'extend', 'primary', 'exclusive').annotations({ description: 'What the host does to the box\'s display topology while managed virtual displays are up.' }) }).annotations({ description: 'The six resolved fields after preset expansion โ€” what the lifecycle\/registry and the Stage-0 call\nsites read, and what the mgmt API echoes as the \"currently in force\" policy. Pure output of\n[`DisplayPolicy::effective`].' }), "game_session": S.optional(S.Literal('auto', 'dedicated')).annotations({ description: 'How a session that \*\*launches a game\*\* (a library id on the Hello \/ apps.json \/ Decky pin) is\nserved (`design\/gamemode-and-dedicated-sessions.md` ยง5.2). Orthogonal to the preset\/lifecycle axes\nโ€” a top-level [`DisplayPolicy`] field, NOT part of [`EffectivePolicy`], so a preset never clobbers\nit. Linux-only in effect (a launching Windows session opens into the one desktop).' }), "name": S.String }).annotations({ description: 'Request body to create or replace a custom preset (no `id` โ€” the host owns it).' }) /** * @summary Update a custom preset */ export const UpdateCustomPresetParams = S.Struct({ "id": S.String.annotations({ description: 'The custom preset id' }) }) export const updateCustomPresetBodyFieldsKeepAliveTwoSecondsMin = 0; export const updateCustomPresetBodyFieldsMaxDisplaysMin = 0; export const UpdateCustomPresetBody = S.Struct({ "fields": S.Struct({ "identity": S.Literal('shared', 'per-client', 'per-client-mode').annotations({ description: 'Stable display identity, so desktop environments persist per-display config (KDE scaling). Stored\nat Stage 0; carriers wired from the identity stage.' }), "keep_alive": S.Union(S.Struct({ "mode": S.Literal('off') }).annotations({ description: 'Tear the display down at session end (today\'s default on every backend but Windows, which\nlingers 10 s).' }), S.Struct({ "mode": S.Literal('duration'), "seconds": S.Number.pipe(S.greaterThanOrEqualTo(updateCustomPresetBodyFieldsKeepAliveTwoSecondsMin)).annotations({ description: 'Linger window in seconds.' }) }).annotations({ description: 'Keep the display for `seconds` after the last session leaves, then tear it down; a reconnect\ninside the window reuses it.' }), S.Struct({ "mode": S.Literal('forever') }).annotations({ description: 'Keep the display until host shutdown or an explicit release (the `Pinned` lifecycle state).\n\*\*Not honored until the display-lifecycle stage\*\* โ€” rejected by the mgmt PUT at Stage 0.' })).annotations({ description: 'How long a virtual display (and, on gamescope\'s bare spawn, the nested session + its game)\nsurvives after the last client session detaches. Serialized as an object tagged on `mode`\n(`{\"mode\":\"off\"}` \/ `{\"mode\":\"duration\",\"seconds\":300}` \/ `{\"mode\":\"forever\"}`) so the web form\nand the OpenAPI schema stay simple.' }), "layout": S.Struct({ "mode": S.optional(S.Literal('auto-row', 'manual')).annotations({ description: 'How group members are arranged in the desktop coordinate space. Stored at Stage 0; applied from\nthe multi-monitor stage.' }), "positions": S.optional(S.Record({ key: S.String, value: S.Struct({ "x": S.Number, "y": S.Number }).annotations({ description: 'A desktop-space offset for a display (top-left origin).' }) })) }).annotations({ description: 'Group layout: the arrangement mode plus, for [`LayoutMode::Manual`], per-slot offsets keyed by\nidentity-slot id (string keys for stable JSON).' }), "max_displays": S.Number.pipe(S.greaterThanOrEqualTo(updateCustomPresetBodyFieldsMaxDisplaysMin)), "mode_conflict": S.Literal('separate', 'steal', 'join', 'reject').annotations({ description: 'Admission when a \*different\* client connects while a display\/session is already live and asks for\na different mode. Stored at Stage 0; enforced from the mode-conflict admission stage.' }), "topology": S.Literal('auto', 'extend', 'primary', 'exclusive').annotations({ description: 'What the host does to the box\'s display topology while managed virtual displays are up.' }) }).annotations({ description: 'The six resolved fields after preset expansion โ€” what the lifecycle\/registry and the Stage-0 call\nsites read, and what the mgmt API echoes as the \"currently in force\" policy. Pure output of\n[`DisplayPolicy::effective`].' }), "game_session": S.optional(S.Literal('auto', 'dedicated')).annotations({ description: 'How a session that \*\*launches a game\*\* (a library id on the Hello \/ apps.json \/ Decky pin) is\nserved (`design\/gamemode-and-dedicated-sessions.md` ยง5.2). Orthogonal to the preset\/lifecycle axes\nโ€” a top-level [`DisplayPolicy`] field, NOT part of [`EffectivePolicy`], so a preset never clobbers\nit. Linux-only in effect (a launching Windows session opens into the one desktop).' }), "name": S.String }).annotations({ description: 'Request body to create or replace a custom preset (no `id` โ€” the host owns it).' }) export const updateCustomPresetResponseFieldsKeepAliveTwoSecondsMin = 0; export const updateCustomPresetResponseFieldsMaxDisplaysMin = 0; export const UpdateCustomPresetResponse = S.Struct({ "fields": S.Struct({ "identity": S.Literal('shared', 'per-client', 'per-client-mode').annotations({ description: 'Stable display identity, so desktop environments persist per-display config (KDE scaling). Stored\nat Stage 0; carriers wired from the identity stage.' }), "keep_alive": S.Union(S.Struct({ "mode": S.Literal('off') }).annotations({ description: 'Tear the display down at session end (today\'s default on every backend but Windows, which\nlingers 10 s).' }), S.Struct({ "mode": S.Literal('duration'), "seconds": S.Number.pipe(S.greaterThanOrEqualTo(updateCustomPresetResponseFieldsKeepAliveTwoSecondsMin)).annotations({ description: 'Linger window in seconds.' }) }).annotations({ description: 'Keep the display for `seconds` after the last session leaves, then tear it down; a reconnect\ninside the window reuses it.' }), S.Struct({ "mode": S.Literal('forever') }).annotations({ description: 'Keep the display until host shutdown or an explicit release (the `Pinned` lifecycle state).\n\*\*Not honored until the display-lifecycle stage\*\* โ€” rejected by the mgmt PUT at Stage 0.' })).annotations({ description: 'How long a virtual display (and, on gamescope\'s bare spawn, the nested session + its game)\nsurvives after the last client session detaches. Serialized as an object tagged on `mode`\n(`{\"mode\":\"off\"}` \/ `{\"mode\":\"duration\",\"seconds\":300}` \/ `{\"mode\":\"forever\"}`) so the web form\nand the OpenAPI schema stay simple.' }), "layout": S.Struct({ "mode": S.optional(S.Literal('auto-row', 'manual')).annotations({ description: 'How group members are arranged in the desktop coordinate space. Stored at Stage 0; applied from\nthe multi-monitor stage.' }), "positions": S.optional(S.Record({ key: S.String, value: S.Struct({ "x": S.Number, "y": S.Number }).annotations({ description: 'A desktop-space offset for a display (top-left origin).' }) })) }).annotations({ description: 'Group layout: the arrangement mode plus, for [`LayoutMode::Manual`], per-slot offsets keyed by\nidentity-slot id (string keys for stable JSON).' }), "max_displays": S.Number.pipe(S.greaterThanOrEqualTo(updateCustomPresetResponseFieldsMaxDisplaysMin)), "mode_conflict": S.Literal('separate', 'steal', 'join', 'reject').annotations({ description: 'Admission when a \*different\* client connects while a display\/session is already live and asks for\na different mode. Stored at Stage 0; enforced from the mode-conflict admission stage.' }), "topology": S.Literal('auto', 'extend', 'primary', 'exclusive').annotations({ description: 'What the host does to the box\'s display topology while managed virtual displays are up.' }) }).annotations({ description: 'The six display-behavior axes this preset applies (the same shape a built-in preset expands to).' }), "game_session": S.optional(S.Literal('auto', 'dedicated')).annotations({ description: 'The game-session routing this preset applies (orthogonal to the six axes; see [`GameSession`]).\nA custom preset captures the operator\'s \*full\* setup, so โ€” unlike a built-in preset โ€” applying\none does set this axis.' }), "id": S.String.annotations({ description: 'Host-assigned, stable for the life of the entry (the `{id}` in the CRUD path).' }), "name": S.String.annotations({ description: 'User-facing name shown on the preset card; editable.' }) }).annotations({ description: 'A user-defined named preset: a saved bundle of the six display-behavior axes (exactly what a\nbuilt-in [`Preset`] expands to) plus the orthogonal game-session axis, that the operator names\nand applies from the console.\n\nUnlike the built-in [`Preset`]s (a closed enum), custom presets are \*\*data\*\* โ€” a catalog stored in\n`\/display-presets.json`. Applying one writes a `Custom` [`DisplayPolicy`] carrying these\nfields (the console reuses `PUT \/display\/settings`), so [`DisplayPolicy::effective`] stays pure and\nthe built-in set is never touched. The catalog is decoupled from the active `display-settings.json`:\nediting or deleting a preset never mutates the running policy (re-apply to adopt a change).' }) /** * Removes it from the catalog. The active policy is untouched โ€” if this preset was the one applied, * the running behavior stays exactly as it was (the catalog and `display-settings.json` are decoupled). * @summary Delete a custom preset */ export const DeleteCustomPresetParams = S.Struct({ "id": S.String.annotations({ description: 'The custom preset id' }) }) /** * Tear down lingering/pinned displays now โ€” so a physical-screen user gets their screen back * without waiting out the linger. `slot` releases one; omit it to release all kept displays. * Active (streaming) displays are never torn down here (that is session control). * @summary Release kept virtual displays */ export const releaseDisplayBodySlotMin = 0; export const ReleaseDisplayBody = S.Struct({ "slot": S.optional(S.NullOr(S.Number.pipe(S.greaterThanOrEqualTo(releaseDisplayBodySlotMin)))).annotations({ description: 'Slot to release (see `state`); omit to release \*\*all\*\* kept displays.' }) }).annotations({ description: 'Request body for `releaseDisplay`.' }) export const releaseDisplayResponseReleasedMin = 0; export const ReleaseDisplayResponse = S.Struct({ "released": S.Number.pipe(S.greaterThanOrEqualTo(releaseDisplayResponseReleasedMin)).annotations({ description: 'Number of kept displays torn down.' }) }).annotations({ description: 'Result of a `\/display\/release`.' }) /** * The stored virtual-display policy (lifecycle, topology, conflict handling, identity, layout), * every preset's expansion, and which options this build enforces yet. See * `design/display-management.md`. * @summary Display-management policy */ export const getDisplaySettingsResponseCustomPresetsItemFieldsKeepAliveTwoSecondsMin = 0; export const getDisplaySettingsResponseCustomPresetsItemFieldsMaxDisplaysMin = 0; export const getDisplaySettingsResponseEffectiveKeepAliveTwoSecondsMin = 0; export const getDisplaySettingsResponseEffectiveMaxDisplaysMin = 0; export const getDisplaySettingsResponsePresetsItemFieldsKeepAliveTwoSecondsMin = 0; export const getDisplaySettingsResponsePresetsItemFieldsMaxDisplaysMin = 0; export const getDisplaySettingsResponseSettingsKeepAliveTwoSecondsMin = 0; export const getDisplaySettingsResponseSettingsMaxDisplaysMin = 0; export const getDisplaySettingsResponseSettingsVersionMin = 0; export const GetDisplaySettingsResponse = S.Struct({ "configured": S.Boolean.annotations({ description: 'True once a `display-settings.json` exists (the console has configured this host).' }), "custom_presets": S.Array(S.Struct({ "fields": S.Struct({ "identity": S.Literal('shared', 'per-client', 'per-client-mode').annotations({ description: 'Stable display identity, so desktop environments persist per-display config (KDE scaling). Stored\nat Stage 0; carriers wired from the identity stage.' }), "keep_alive": S.Union(S.Struct({ "mode": S.Literal('off') }).annotations({ description: 'Tear the display down at session end (today\'s default on every backend but Windows, which\nlingers 10 s).' }), S.Struct({ "mode": S.Literal('duration'), "seconds": S.Number.pipe(S.greaterThanOrEqualTo(getDisplaySettingsResponseCustomPresetsItemFieldsKeepAliveTwoSecondsMin)).annotations({ description: 'Linger window in seconds.' }) }).annotations({ description: 'Keep the display for `seconds` after the last session leaves, then tear it down; a reconnect\ninside the window reuses it.' }), S.Struct({ "mode": S.Literal('forever') }).annotations({ description: 'Keep the display until host shutdown or an explicit release (the `Pinned` lifecycle state).\n\*\*Not honored until the display-lifecycle stage\*\* โ€” rejected by the mgmt PUT at Stage 0.' })).annotations({ description: 'How long a virtual display (and, on gamescope\'s bare spawn, the nested session + its game)\nsurvives after the last client session detaches. Serialized as an object tagged on `mode`\n(`{\"mode\":\"off\"}` \/ `{\"mode\":\"duration\",\"seconds\":300}` \/ `{\"mode\":\"forever\"}`) so the web form\nand the OpenAPI schema stay simple.' }), "layout": S.Struct({ "mode": S.optional(S.Literal('auto-row', 'manual')).annotations({ description: 'How group members are arranged in the desktop coordinate space. Stored at Stage 0; applied from\nthe multi-monitor stage.' }), "positions": S.optional(S.Record({ key: S.String, value: S.Struct({ "x": S.Number, "y": S.Number }).annotations({ description: 'A desktop-space offset for a display (top-left origin).' }) })) }).annotations({ description: 'Group layout: the arrangement mode plus, for [`LayoutMode::Manual`], per-slot offsets keyed by\nidentity-slot id (string keys for stable JSON).' }), "max_displays": S.Number.pipe(S.greaterThanOrEqualTo(getDisplaySettingsResponseCustomPresetsItemFieldsMaxDisplaysMin)), "mode_conflict": S.Literal('separate', 'steal', 'join', 'reject').annotations({ description: 'Admission when a \*different\* client connects while a display\/session is already live and asks for\na different mode. Stored at Stage 0; enforced from the mode-conflict admission stage.' }), "topology": S.Literal('auto', 'extend', 'primary', 'exclusive').annotations({ description: 'What the host does to the box\'s display topology while managed virtual displays are up.' }) }).annotations({ description: 'The six display-behavior axes this preset applies (the same shape a built-in preset expands to).' }), "game_session": S.optional(S.Literal('auto', 'dedicated')).annotations({ description: 'The game-session routing this preset applies (orthogonal to the six axes; see [`GameSession`]).\nA custom preset captures the operator\'s \*full\* setup, so โ€” unlike a built-in preset โ€” applying\none does set this axis.' }), "id": S.String.annotations({ description: 'Host-assigned, stable for the life of the entry (the `{id}` in the CRUD path).' }), "name": S.String.annotations({ description: 'User-facing name shown on the preset card; editable.' }) }).annotations({ description: 'A user-defined named preset: a saved bundle of the six display-behavior axes (exactly what a\nbuilt-in [`Preset`] expands to) plus the orthogonal game-session axis, that the operator names\nand applies from the console.\n\nUnlike the built-in [`Preset`]s (a closed enum), custom presets are \*\*data\*\* โ€” a catalog stored in\n`\/display-presets.json`. Applying one writes a `Custom` [`DisplayPolicy`] carrying these\nfields (the console reuses `PUT \/display\/settings`), so [`DisplayPolicy::effective`] stays pure and\nthe built-in set is never touched. The catalog is decoupled from the active `display-settings.json`:\nediting or deleting a preset never mutates the running policy (re-apply to adopt a change).' })).annotations({ description: 'The operator\'s saved custom presets (`display-presets.json`) โ€” named field-bundles rendered\nalongside the built-ins. Managed via `POST\/PUT\/DELETE \/display\/presets`; applied by writing a\n`Custom` policy carrying the preset\'s fields.' }), "effective": S.Struct({ "identity": S.Literal('shared', 'per-client', 'per-client-mode').annotations({ description: 'Stable display identity, so desktop environments persist per-display config (KDE scaling). Stored\nat Stage 0; carriers wired from the identity stage.' }), "keep_alive": S.Union(S.Struct({ "mode": S.Literal('off') }).annotations({ description: 'Tear the display down at session end (today\'s default on every backend but Windows, which\nlingers 10 s).' }), S.Struct({ "mode": S.Literal('duration'), "seconds": S.Number.pipe(S.greaterThanOrEqualTo(getDisplaySettingsResponseEffectiveKeepAliveTwoSecondsMin)).annotations({ description: 'Linger window in seconds.' }) }).annotations({ description: 'Keep the display for `seconds` after the last session leaves, then tear it down; a reconnect\ninside the window reuses it.' }), S.Struct({ "mode": S.Literal('forever') }).annotations({ description: 'Keep the display until host shutdown or an explicit release (the `Pinned` lifecycle state).\n\*\*Not honored until the display-lifecycle stage\*\* โ€” rejected by the mgmt PUT at Stage 0.' })).annotations({ description: 'How long a virtual display (and, on gamescope\'s bare spawn, the nested session + its game)\nsurvives after the last client session detaches. Serialized as an object tagged on `mode`\n(`{\"mode\":\"off\"}` \/ `{\"mode\":\"duration\",\"seconds\":300}` \/ `{\"mode\":\"forever\"}`) so the web form\nand the OpenAPI schema stay simple.' }), "layout": S.Struct({ "mode": S.optional(S.Literal('auto-row', 'manual')).annotations({ description: 'How group members are arranged in the desktop coordinate space. Stored at Stage 0; applied from\nthe multi-monitor stage.' }), "positions": S.optional(S.Record({ key: S.String, value: S.Struct({ "x": S.Number, "y": S.Number }).annotations({ description: 'A desktop-space offset for a display (top-left origin).' }) })) }).annotations({ description: 'Group layout: the arrangement mode plus, for [`LayoutMode::Manual`], per-slot offsets keyed by\nidentity-slot id (string keys for stable JSON).' }), "max_displays": S.Number.pipe(S.greaterThanOrEqualTo(getDisplaySettingsResponseEffectiveMaxDisplaysMin)), "mode_conflict": S.Literal('separate', 'steal', 'join', 'reject').annotations({ description: 'Admission when a \*different\* client connects while a display\/session is already live and asks for\na different mode. Stored at Stage 0; enforced from the mode-conflict admission stage.' }), "topology": S.Literal('auto', 'extend', 'primary', 'exclusive').annotations({ description: 'What the host does to the box\'s display topology while managed virtual displays are up.' }) }).annotations({ description: 'The effective (preset-expanded) policy currently in force.' }), "enforced": S.Array(S.String).annotations({ description: 'Option names this build enforces right now. All five axes are now acted on (keep_alive +\ntopology since Stage 0-2, identity Stage 3, mode_conflict Stage 4, layout Stage 5) โ€” the console\nreads this to know which controls are live vs. \"coming soon\" (per-backend nuance, e.g. layout\nposition apply being KWin-only, is reported per display in `\/display\/state`).' }), "presets": S.Array(S.Struct({ "fields": S.Struct({ "identity": S.Literal('shared', 'per-client', 'per-client-mode').annotations({ description: 'Stable display identity, so desktop environments persist per-display config (KDE scaling). Stored\nat Stage 0; carriers wired from the identity stage.' }), "keep_alive": S.Union(S.Struct({ "mode": S.Literal('off') }).annotations({ description: 'Tear the display down at session end (today\'s default on every backend but Windows, which\nlingers 10 s).' }), S.Struct({ "mode": S.Literal('duration'), "seconds": S.Number.pipe(S.greaterThanOrEqualTo(getDisplaySettingsResponsePresetsItemFieldsKeepAliveTwoSecondsMin)).annotations({ description: 'Linger window in seconds.' }) }).annotations({ description: 'Keep the display for `seconds` after the last session leaves, then tear it down; a reconnect\ninside the window reuses it.' }), S.Struct({ "mode": S.Literal('forever') }).annotations({ description: 'Keep the display until host shutdown or an explicit release (the `Pinned` lifecycle state).\n\*\*Not honored until the display-lifecycle stage\*\* โ€” rejected by the mgmt PUT at Stage 0.' })).annotations({ description: 'How long a virtual display (and, on gamescope\'s bare spawn, the nested session + its game)\nsurvives after the last client session detaches. Serialized as an object tagged on `mode`\n(`{\"mode\":\"off\"}` \/ `{\"mode\":\"duration\",\"seconds\":300}` \/ `{\"mode\":\"forever\"}`) so the web form\nand the OpenAPI schema stay simple.' }), "layout": S.Struct({ "mode": S.optional(S.Literal('auto-row', 'manual')).annotations({ description: 'How group members are arranged in the desktop coordinate space. Stored at Stage 0; applied from\nthe multi-monitor stage.' }), "positions": S.optional(S.Record({ key: S.String, value: S.Struct({ "x": S.Number, "y": S.Number }).annotations({ description: 'A desktop-space offset for a display (top-left origin).' }) })) }).annotations({ description: 'Group layout: the arrangement mode plus, for [`LayoutMode::Manual`], per-slot offsets keyed by\nidentity-slot id (string keys for stable JSON).' }), "max_displays": S.Number.pipe(S.greaterThanOrEqualTo(getDisplaySettingsResponsePresetsItemFieldsMaxDisplaysMin)), "mode_conflict": S.Literal('separate', 'steal', 'join', 'reject').annotations({ description: 'Admission when a \*different\* client connects while a display\/session is already live and asks for\na different mode. Stored at Stage 0; enforced from the mode-conflict admission stage.' }), "topology": S.Literal('auto', 'extend', 'primary', 'exclusive').annotations({ description: 'What the host does to the box\'s display topology while managed virtual displays are up.' }) }).annotations({ description: 'The effective policy this preset expands to (the same fields a `custom` policy carries).' }), "id": S.String.annotations({ description: 'The preset id (`default` | `gaming-rig` | `shared-desktop` | `hotdesk` | `workstation`).' }), "summary": S.String.annotations({ description: 'One-line story shown next to the option.' }) }).annotations({ description: 'One preset\'s human-facing description + the fields it expands to, so the console can render a\npreset picker with an accurate \"what this does\" preview without hardcoding the expansion.' })).annotations({ description: 'Every named preset and what it expands to (for the picker\'s preview).' }), "settings": S.Struct({ "ddc_power_off": S.optional(S.Boolean).annotations({ description: 'EXPERIMENTAL (Windows): command physical monitors\' panels off over DDC\/CI (VCP 0xD6 โ†’\nDPMS off) right before an `Exclusive` isolate deactivates them, and back on at restore.\nTargets the \"connected-but-dark head\" periodic-stutter class (monitor standby\nauto-input-scan \/ DP link churn while the virtual display is the sole active display) at\nthe monitor-firmware level. Best-effort โ€” monitors without DDC\/CI (or with it disabled in\nthe OSD) are skipped. Orthogonal to `preset` (like `game_session`): preserved across\npreset changes; `#[serde(default)]` = off so existing `display-settings.json` files are\nuntouched.' }), "game_session": S.optional(S.Literal('auto', 'dedicated')).annotations({ description: 'How a game-launching session is served (`design\/gamemode-and-dedicated-sessions.md` ยง5.2).\nOrthogonal to `preset`\/lifecycle โ€” preserved across preset changes; `#[serde(default)]` = `Auto`\nso existing `display-settings.json` files are untouched.' }), "identity": S.optional(S.Literal('shared', 'per-client', 'per-client-mode')).annotations({ description: 'Stable display identity, so desktop environments persist per-display config (KDE scaling). Stored\nat Stage 0; carriers wired from the identity stage.' }), "keep_alive": S.optional(S.Union(S.Struct({ "mode": S.Literal('off') }).annotations({ description: 'Tear the display down at session end (today\'s default on every backend but Windows, which\nlingers 10 s).' }), S.Struct({ "mode": S.Literal('duration'), "seconds": S.Number.pipe(S.greaterThanOrEqualTo(getDisplaySettingsResponseSettingsKeepAliveTwoSecondsMin)).annotations({ description: 'Linger window in seconds.' }) }).annotations({ description: 'Keep the display for `seconds` after the last session leaves, then tear it down; a reconnect\ninside the window reuses it.' }), S.Struct({ "mode": S.Literal('forever') }).annotations({ description: 'Keep the display until host shutdown or an explicit release (the `Pinned` lifecycle state).\n\*\*Not honored until the display-lifecycle stage\*\* โ€” rejected by the mgmt PUT at Stage 0.' }))).annotations({ description: 'How long a virtual display (and, on gamescope\'s bare spawn, the nested session + its game)\nsurvives after the last client session detaches. Serialized as an object tagged on `mode`\n(`{\"mode\":\"off\"}` \/ `{\"mode\":\"duration\",\"seconds\":300}` \/ `{\"mode\":\"forever\"}`) so the web form\nand the OpenAPI schema stay simple.' }), "layout": S.optional(S.Struct({ "mode": S.optional(S.Literal('auto-row', 'manual')).annotations({ description: 'How group members are arranged in the desktop coordinate space. Stored at Stage 0; applied from\nthe multi-monitor stage.' }), "positions": S.optional(S.Record({ key: S.String, value: S.Struct({ "x": S.Number, "y": S.Number }).annotations({ description: 'A desktop-space offset for a display (top-left origin).' }) })) })).annotations({ description: 'Group layout: the arrangement mode plus, for [`LayoutMode::Manual`], per-slot offsets keyed by\nidentity-slot id (string keys for stable JSON).' }), "max_displays": S.optional(S.Number.pipe(S.greaterThanOrEqualTo(getDisplaySettingsResponseSettingsMaxDisplaysMin))).annotations({ description: 'Upper bound on simultaneously-live virtual displays (clamped to `1..=16` on write).' }), "mode_conflict": S.optional(S.Literal('separate', 'steal', 'join', 'reject')).annotations({ description: 'Admission when a \*different\* client connects while a display\/session is already live and asks for\na different mode. Stored at Stage 0; enforced from the mode-conflict admission stage.' }), "pnp_disable_monitors": S.optional(S.Boolean).annotations({ description: 'EXPERIMENTAL (Windows): DISABLE physical monitors\' PnP device nodes for the stream\'s\nduration (persistently, so a standby monitor\/TV whose hot-plug events re-arrive stays\ndisabled) and re-enable them at teardown. Two selectors: the monitors an `Exclusive`\nisolate deactivated, plus โ€” in ANY topology โ€” external monitors that are connected but not\npart of the desktop (the standby TV that was never active, whose input auto-scan \/\ninstant-on HPD cycling re-probes the link every few seconds). Targets the same\n\"connected-but-dark head\" periodic-stutter class as [`Self::ddc_power_off`], but at the\nWindows-reaction level: a disabled devnode\'s wake events trigger no PnP arrival, no CCD\nre-evaluation, no DWM invalidation. A crash-recovery journal re-enables leftovers on host\nstartup. Orthogonal to `preset` (like `game_session`); `#[serde(default)]` = off.' }), "preset": S.optional(S.Literal('custom', 'default', 'gaming-rig', 'shared-desktop', 'hotdesk', 'workstation')).annotations({ description: 'A named bundle of the fields below. `Custom` (the default) means the explicit fields rule; any\nother preset ignores the stored fields and expands to its own ([`DisplayPolicy::effective`]).' }), "topology": S.optional(S.Literal('auto', 'extend', 'primary', 'exclusive')).annotations({ description: 'What the host does to the box\'s display topology while managed virtual displays are up.' }), "version": S.optional(S.Number.pipe(S.greaterThanOrEqualTo(getDisplaySettingsResponseSettingsVersionMin))).annotations({ description: 'Schema version (currently 1) โ€” lets a future field addition migrate rather than reject.' }) }).annotations({ description: 'The stored policy (preset + custom fields), or the built-in default when unconfigured.' }) }).annotations({ description: 'Full display-management state for the console: the stored policy, every preset\'s expansion, the\nresolved effective policy, and which options this build actually enforces yet (Stage 0 wires\nkeep-alive linger + topology; the rest are stored but not yet acted on).' }) /** * Persists a new policy (validated + clamped) and applies it from the next connect/teardown โ€” a * running session keeps the display it opened on. `keep_alive: forever` (the gaming-rig preset) is * honored (the display is Pinned; free it via `POST /display/release`). * @summary Set the display-management policy */ export const setDisplaySettingsBodyKeepAliveTwoSecondsMin = 0; export const setDisplaySettingsBodyMaxDisplaysMin = 0; export const setDisplaySettingsBodyVersionMin = 0; export const SetDisplaySettingsBody = S.Struct({ "ddc_power_off": S.optional(S.Boolean).annotations({ description: 'EXPERIMENTAL (Windows): command physical monitors\' panels off over DDC\/CI (VCP 0xD6 โ†’\nDPMS off) right before an `Exclusive` isolate deactivates them, and back on at restore.\nTargets the \"connected-but-dark head\" periodic-stutter class (monitor standby\nauto-input-scan \/ DP link churn while the virtual display is the sole active display) at\nthe monitor-firmware level. Best-effort โ€” monitors without DDC\/CI (or with it disabled in\nthe OSD) are skipped. Orthogonal to `preset` (like `game_session`): preserved across\npreset changes; `#[serde(default)]` = off so existing `display-settings.json` files are\nuntouched.' }), "game_session": S.optional(S.Literal('auto', 'dedicated')).annotations({ description: 'How a game-launching session is served (`design\/gamemode-and-dedicated-sessions.md` ยง5.2).\nOrthogonal to `preset`\/lifecycle โ€” preserved across preset changes; `#[serde(default)]` = `Auto`\nso existing `display-settings.json` files are untouched.' }), "identity": S.optional(S.Literal('shared', 'per-client', 'per-client-mode')).annotations({ description: 'Stable display identity, so desktop environments persist per-display config (KDE scaling). Stored\nat Stage 0; carriers wired from the identity stage.' }), "keep_alive": S.optional(S.Union(S.Struct({ "mode": S.Literal('off') }).annotations({ description: 'Tear the display down at session end (today\'s default on every backend but Windows, which\nlingers 10 s).' }), S.Struct({ "mode": S.Literal('duration'), "seconds": S.Number.pipe(S.greaterThanOrEqualTo(setDisplaySettingsBodyKeepAliveTwoSecondsMin)).annotations({ description: 'Linger window in seconds.' }) }).annotations({ description: 'Keep the display for `seconds` after the last session leaves, then tear it down; a reconnect\ninside the window reuses it.' }), S.Struct({ "mode": S.Literal('forever') }).annotations({ description: 'Keep the display until host shutdown or an explicit release (the `Pinned` lifecycle state).\n\*\*Not honored until the display-lifecycle stage\*\* โ€” rejected by the mgmt PUT at Stage 0.' }))).annotations({ description: 'How long a virtual display (and, on gamescope\'s bare spawn, the nested session + its game)\nsurvives after the last client session detaches. Serialized as an object tagged on `mode`\n(`{\"mode\":\"off\"}` \/ `{\"mode\":\"duration\",\"seconds\":300}` \/ `{\"mode\":\"forever\"}`) so the web form\nand the OpenAPI schema stay simple.' }), "layout": S.optional(S.Struct({ "mode": S.optional(S.Literal('auto-row', 'manual')).annotations({ description: 'How group members are arranged in the desktop coordinate space. Stored at Stage 0; applied from\nthe multi-monitor stage.' }), "positions": S.optional(S.Record({ key: S.String, value: S.Struct({ "x": S.Number, "y": S.Number }).annotations({ description: 'A desktop-space offset for a display (top-left origin).' }) })) })).annotations({ description: 'Group layout: the arrangement mode plus, for [`LayoutMode::Manual`], per-slot offsets keyed by\nidentity-slot id (string keys for stable JSON).' }), "max_displays": S.optional(S.Number.pipe(S.greaterThanOrEqualTo(setDisplaySettingsBodyMaxDisplaysMin))).annotations({ description: 'Upper bound on simultaneously-live virtual displays (clamped to `1..=16` on write).' }), "mode_conflict": S.optional(S.Literal('separate', 'steal', 'join', 'reject')).annotations({ description: 'Admission when a \*different\* client connects while a display\/session is already live and asks for\na different mode. Stored at Stage 0; enforced from the mode-conflict admission stage.' }), "pnp_disable_monitors": S.optional(S.Boolean).annotations({ description: 'EXPERIMENTAL (Windows): DISABLE physical monitors\' PnP device nodes for the stream\'s\nduration (persistently, so a standby monitor\/TV whose hot-plug events re-arrive stays\ndisabled) and re-enable them at teardown. Two selectors: the monitors an `Exclusive`\nisolate deactivated, plus โ€” in ANY topology โ€” external monitors that are connected but not\npart of the desktop (the standby TV that was never active, whose input auto-scan \/\ninstant-on HPD cycling re-probes the link every few seconds). Targets the same\n\"connected-but-dark head\" periodic-stutter class as [`Self::ddc_power_off`], but at the\nWindows-reaction level: a disabled devnode\'s wake events trigger no PnP arrival, no CCD\nre-evaluation, no DWM invalidation. A crash-recovery journal re-enables leftovers on host\nstartup. Orthogonal to `preset` (like `game_session`); `#[serde(default)]` = off.' }), "preset": S.optional(S.Literal('custom', 'default', 'gaming-rig', 'shared-desktop', 'hotdesk', 'workstation')).annotations({ description: 'A named bundle of the fields below. `Custom` (the default) means the explicit fields rule; any\nother preset ignores the stored fields and expands to its own ([`DisplayPolicy::effective`]).' }), "topology": S.optional(S.Literal('auto', 'extend', 'primary', 'exclusive')).annotations({ description: 'What the host does to the box\'s display topology while managed virtual displays are up.' }), "version": S.optional(S.Number.pipe(S.greaterThanOrEqualTo(setDisplaySettingsBodyVersionMin))).annotations({ description: 'Schema version (currently 1) โ€” lets a future field addition migrate rather than reject.' }) }).annotations({ description: 'The user-facing display-management policy โ€” what `display-settings.json` holds and what the mgmt\nAPI GETs\/PUTs. When [`preset`](Self::preset) is not [`Preset::Custom`] the explicit fields are\nignored (the console writes one or the other); [`effective`](Self::effective) resolves both to a\nsingle [`EffectivePolicy`].' }) export const setDisplaySettingsResponseCustomPresetsItemFieldsKeepAliveTwoSecondsMin = 0; export const setDisplaySettingsResponseCustomPresetsItemFieldsMaxDisplaysMin = 0; export const setDisplaySettingsResponseEffectiveKeepAliveTwoSecondsMin = 0; export const setDisplaySettingsResponseEffectiveMaxDisplaysMin = 0; export const setDisplaySettingsResponsePresetsItemFieldsKeepAliveTwoSecondsMin = 0; export const setDisplaySettingsResponsePresetsItemFieldsMaxDisplaysMin = 0; export const setDisplaySettingsResponseSettingsKeepAliveTwoSecondsMin = 0; export const setDisplaySettingsResponseSettingsMaxDisplaysMin = 0; export const setDisplaySettingsResponseSettingsVersionMin = 0; export const SetDisplaySettingsResponse = S.Struct({ "configured": S.Boolean.annotations({ description: 'True once a `display-settings.json` exists (the console has configured this host).' }), "custom_presets": S.Array(S.Struct({ "fields": S.Struct({ "identity": S.Literal('shared', 'per-client', 'per-client-mode').annotations({ description: 'Stable display identity, so desktop environments persist per-display config (KDE scaling). Stored\nat Stage 0; carriers wired from the identity stage.' }), "keep_alive": S.Union(S.Struct({ "mode": S.Literal('off') }).annotations({ description: 'Tear the display down at session end (today\'s default on every backend but Windows, which\nlingers 10 s).' }), S.Struct({ "mode": S.Literal('duration'), "seconds": S.Number.pipe(S.greaterThanOrEqualTo(setDisplaySettingsResponseCustomPresetsItemFieldsKeepAliveTwoSecondsMin)).annotations({ description: 'Linger window in seconds.' }) }).annotations({ description: 'Keep the display for `seconds` after the last session leaves, then tear it down; a reconnect\ninside the window reuses it.' }), S.Struct({ "mode": S.Literal('forever') }).annotations({ description: 'Keep the display until host shutdown or an explicit release (the `Pinned` lifecycle state).\n\*\*Not honored until the display-lifecycle stage\*\* โ€” rejected by the mgmt PUT at Stage 0.' })).annotations({ description: 'How long a virtual display (and, on gamescope\'s bare spawn, the nested session + its game)\nsurvives after the last client session detaches. Serialized as an object tagged on `mode`\n(`{\"mode\":\"off\"}` \/ `{\"mode\":\"duration\",\"seconds\":300}` \/ `{\"mode\":\"forever\"}`) so the web form\nand the OpenAPI schema stay simple.' }), "layout": S.Struct({ "mode": S.optional(S.Literal('auto-row', 'manual')).annotations({ description: 'How group members are arranged in the desktop coordinate space. Stored at Stage 0; applied from\nthe multi-monitor stage.' }), "positions": S.optional(S.Record({ key: S.String, value: S.Struct({ "x": S.Number, "y": S.Number }).annotations({ description: 'A desktop-space offset for a display (top-left origin).' }) })) }).annotations({ description: 'Group layout: the arrangement mode plus, for [`LayoutMode::Manual`], per-slot offsets keyed by\nidentity-slot id (string keys for stable JSON).' }), "max_displays": S.Number.pipe(S.greaterThanOrEqualTo(setDisplaySettingsResponseCustomPresetsItemFieldsMaxDisplaysMin)), "mode_conflict": S.Literal('separate', 'steal', 'join', 'reject').annotations({ description: 'Admission when a \*different\* client connects while a display\/session is already live and asks for\na different mode. Stored at Stage 0; enforced from the mode-conflict admission stage.' }), "topology": S.Literal('auto', 'extend', 'primary', 'exclusive').annotations({ description: 'What the host does to the box\'s display topology while managed virtual displays are up.' }) }).annotations({ description: 'The six display-behavior axes this preset applies (the same shape a built-in preset expands to).' }), "game_session": S.optional(S.Literal('auto', 'dedicated')).annotations({ description: 'The game-session routing this preset applies (orthogonal to the six axes; see [`GameSession`]).\nA custom preset captures the operator\'s \*full\* setup, so โ€” unlike a built-in preset โ€” applying\none does set this axis.' }), "id": S.String.annotations({ description: 'Host-assigned, stable for the life of the entry (the `{id}` in the CRUD path).' }), "name": S.String.annotations({ description: 'User-facing name shown on the preset card; editable.' }) }).annotations({ description: 'A user-defined named preset: a saved bundle of the six display-behavior axes (exactly what a\nbuilt-in [`Preset`] expands to) plus the orthogonal game-session axis, that the operator names\nand applies from the console.\n\nUnlike the built-in [`Preset`]s (a closed enum), custom presets are \*\*data\*\* โ€” a catalog stored in\n`\/display-presets.json`. Applying one writes a `Custom` [`DisplayPolicy`] carrying these\nfields (the console reuses `PUT \/display\/settings`), so [`DisplayPolicy::effective`] stays pure and\nthe built-in set is never touched. The catalog is decoupled from the active `display-settings.json`:\nediting or deleting a preset never mutates the running policy (re-apply to adopt a change).' })).annotations({ description: 'The operator\'s saved custom presets (`display-presets.json`) โ€” named field-bundles rendered\nalongside the built-ins. Managed via `POST\/PUT\/DELETE \/display\/presets`; applied by writing a\n`Custom` policy carrying the preset\'s fields.' }), "effective": S.Struct({ "identity": S.Literal('shared', 'per-client', 'per-client-mode').annotations({ description: 'Stable display identity, so desktop environments persist per-display config (KDE scaling). Stored\nat Stage 0; carriers wired from the identity stage.' }), "keep_alive": S.Union(S.Struct({ "mode": S.Literal('off') }).annotations({ description: 'Tear the display down at session end (today\'s default on every backend but Windows, which\nlingers 10 s).' }), S.Struct({ "mode": S.Literal('duration'), "seconds": S.Number.pipe(S.greaterThanOrEqualTo(setDisplaySettingsResponseEffectiveKeepAliveTwoSecondsMin)).annotations({ description: 'Linger window in seconds.' }) }).annotations({ description: 'Keep the display for `seconds` after the last session leaves, then tear it down; a reconnect\ninside the window reuses it.' }), S.Struct({ "mode": S.Literal('forever') }).annotations({ description: 'Keep the display until host shutdown or an explicit release (the `Pinned` lifecycle state).\n\*\*Not honored until the display-lifecycle stage\*\* โ€” rejected by the mgmt PUT at Stage 0.' })).annotations({ description: 'How long a virtual display (and, on gamescope\'s bare spawn, the nested session + its game)\nsurvives after the last client session detaches. Serialized as an object tagged on `mode`\n(`{\"mode\":\"off\"}` \/ `{\"mode\":\"duration\",\"seconds\":300}` \/ `{\"mode\":\"forever\"}`) so the web form\nand the OpenAPI schema stay simple.' }), "layout": S.Struct({ "mode": S.optional(S.Literal('auto-row', 'manual')).annotations({ description: 'How group members are arranged in the desktop coordinate space. Stored at Stage 0; applied from\nthe multi-monitor stage.' }), "positions": S.optional(S.Record({ key: S.String, value: S.Struct({ "x": S.Number, "y": S.Number }).annotations({ description: 'A desktop-space offset for a display (top-left origin).' }) })) }).annotations({ description: 'Group layout: the arrangement mode plus, for [`LayoutMode::Manual`], per-slot offsets keyed by\nidentity-slot id (string keys for stable JSON).' }), "max_displays": S.Number.pipe(S.greaterThanOrEqualTo(setDisplaySettingsResponseEffectiveMaxDisplaysMin)), "mode_conflict": S.Literal('separate', 'steal', 'join', 'reject').annotations({ description: 'Admission when a \*different\* client connects while a display\/session is already live and asks for\na different mode. Stored at Stage 0; enforced from the mode-conflict admission stage.' }), "topology": S.Literal('auto', 'extend', 'primary', 'exclusive').annotations({ description: 'What the host does to the box\'s display topology while managed virtual displays are up.' }) }).annotations({ description: 'The effective (preset-expanded) policy currently in force.' }), "enforced": S.Array(S.String).annotations({ description: 'Option names this build enforces right now. All five axes are now acted on (keep_alive +\ntopology since Stage 0-2, identity Stage 3, mode_conflict Stage 4, layout Stage 5) โ€” the console\nreads this to know which controls are live vs. \"coming soon\" (per-backend nuance, e.g. layout\nposition apply being KWin-only, is reported per display in `\/display\/state`).' }), "presets": S.Array(S.Struct({ "fields": S.Struct({ "identity": S.Literal('shared', 'per-client', 'per-client-mode').annotations({ description: 'Stable display identity, so desktop environments persist per-display config (KDE scaling). Stored\nat Stage 0; carriers wired from the identity stage.' }), "keep_alive": S.Union(S.Struct({ "mode": S.Literal('off') }).annotations({ description: 'Tear the display down at session end (today\'s default on every backend but Windows, which\nlingers 10 s).' }), S.Struct({ "mode": S.Literal('duration'), "seconds": S.Number.pipe(S.greaterThanOrEqualTo(setDisplaySettingsResponsePresetsItemFieldsKeepAliveTwoSecondsMin)).annotations({ description: 'Linger window in seconds.' }) }).annotations({ description: 'Keep the display for `seconds` after the last session leaves, then tear it down; a reconnect\ninside the window reuses it.' }), S.Struct({ "mode": S.Literal('forever') }).annotations({ description: 'Keep the display until host shutdown or an explicit release (the `Pinned` lifecycle state).\n\*\*Not honored until the display-lifecycle stage\*\* โ€” rejected by the mgmt PUT at Stage 0.' })).annotations({ description: 'How long a virtual display (and, on gamescope\'s bare spawn, the nested session + its game)\nsurvives after the last client session detaches. Serialized as an object tagged on `mode`\n(`{\"mode\":\"off\"}` \/ `{\"mode\":\"duration\",\"seconds\":300}` \/ `{\"mode\":\"forever\"}`) so the web form\nand the OpenAPI schema stay simple.' }), "layout": S.Struct({ "mode": S.optional(S.Literal('auto-row', 'manual')).annotations({ description: 'How group members are arranged in the desktop coordinate space. Stored at Stage 0; applied from\nthe multi-monitor stage.' }), "positions": S.optional(S.Record({ key: S.String, value: S.Struct({ "x": S.Number, "y": S.Number }).annotations({ description: 'A desktop-space offset for a display (top-left origin).' }) })) }).annotations({ description: 'Group layout: the arrangement mode plus, for [`LayoutMode::Manual`], per-slot offsets keyed by\nidentity-slot id (string keys for stable JSON).' }), "max_displays": S.Number.pipe(S.greaterThanOrEqualTo(setDisplaySettingsResponsePresetsItemFieldsMaxDisplaysMin)), "mode_conflict": S.Literal('separate', 'steal', 'join', 'reject').annotations({ description: 'Admission when a \*different\* client connects while a display\/session is already live and asks for\na different mode. Stored at Stage 0; enforced from the mode-conflict admission stage.' }), "topology": S.Literal('auto', 'extend', 'primary', 'exclusive').annotations({ description: 'What the host does to the box\'s display topology while managed virtual displays are up.' }) }).annotations({ description: 'The effective policy this preset expands to (the same fields a `custom` policy carries).' }), "id": S.String.annotations({ description: 'The preset id (`default` | `gaming-rig` | `shared-desktop` | `hotdesk` | `workstation`).' }), "summary": S.String.annotations({ description: 'One-line story shown next to the option.' }) }).annotations({ description: 'One preset\'s human-facing description + the fields it expands to, so the console can render a\npreset picker with an accurate \"what this does\" preview without hardcoding the expansion.' })).annotations({ description: 'Every named preset and what it expands to (for the picker\'s preview).' }), "settings": S.Struct({ "ddc_power_off": S.optional(S.Boolean).annotations({ description: 'EXPERIMENTAL (Windows): command physical monitors\' panels off over DDC\/CI (VCP 0xD6 โ†’\nDPMS off) right before an `Exclusive` isolate deactivates them, and back on at restore.\nTargets the \"connected-but-dark head\" periodic-stutter class (monitor standby\nauto-input-scan \/ DP link churn while the virtual display is the sole active display) at\nthe monitor-firmware level. Best-effort โ€” monitors without DDC\/CI (or with it disabled in\nthe OSD) are skipped. Orthogonal to `preset` (like `game_session`): preserved across\npreset changes; `#[serde(default)]` = off so existing `display-settings.json` files are\nuntouched.' }), "game_session": S.optional(S.Literal('auto', 'dedicated')).annotations({ description: 'How a game-launching session is served (`design\/gamemode-and-dedicated-sessions.md` ยง5.2).\nOrthogonal to `preset`\/lifecycle โ€” preserved across preset changes; `#[serde(default)]` = `Auto`\nso existing `display-settings.json` files are untouched.' }), "identity": S.optional(S.Literal('shared', 'per-client', 'per-client-mode')).annotations({ description: 'Stable display identity, so desktop environments persist per-display config (KDE scaling). Stored\nat Stage 0; carriers wired from the identity stage.' }), "keep_alive": S.optional(S.Union(S.Struct({ "mode": S.Literal('off') }).annotations({ description: 'Tear the display down at session end (today\'s default on every backend but Windows, which\nlingers 10 s).' }), S.Struct({ "mode": S.Literal('duration'), "seconds": S.Number.pipe(S.greaterThanOrEqualTo(setDisplaySettingsResponseSettingsKeepAliveTwoSecondsMin)).annotations({ description: 'Linger window in seconds.' }) }).annotations({ description: 'Keep the display for `seconds` after the last session leaves, then tear it down; a reconnect\ninside the window reuses it.' }), S.Struct({ "mode": S.Literal('forever') }).annotations({ description: 'Keep the display until host shutdown or an explicit release (the `Pinned` lifecycle state).\n\*\*Not honored until the display-lifecycle stage\*\* โ€” rejected by the mgmt PUT at Stage 0.' }))).annotations({ description: 'How long a virtual display (and, on gamescope\'s bare spawn, the nested session + its game)\nsurvives after the last client session detaches. Serialized as an object tagged on `mode`\n(`{\"mode\":\"off\"}` \/ `{\"mode\":\"duration\",\"seconds\":300}` \/ `{\"mode\":\"forever\"}`) so the web form\nand the OpenAPI schema stay simple.' }), "layout": S.optional(S.Struct({ "mode": S.optional(S.Literal('auto-row', 'manual')).annotations({ description: 'How group members are arranged in the desktop coordinate space. Stored at Stage 0; applied from\nthe multi-monitor stage.' }), "positions": S.optional(S.Record({ key: S.String, value: S.Struct({ "x": S.Number, "y": S.Number }).annotations({ description: 'A desktop-space offset for a display (top-left origin).' }) })) })).annotations({ description: 'Group layout: the arrangement mode plus, for [`LayoutMode::Manual`], per-slot offsets keyed by\nidentity-slot id (string keys for stable JSON).' }), "max_displays": S.optional(S.Number.pipe(S.greaterThanOrEqualTo(setDisplaySettingsResponseSettingsMaxDisplaysMin))).annotations({ description: 'Upper bound on simultaneously-live virtual displays (clamped to `1..=16` on write).' }), "mode_conflict": S.optional(S.Literal('separate', 'steal', 'join', 'reject')).annotations({ description: 'Admission when a \*different\* client connects while a display\/session is already live and asks for\na different mode. Stored at Stage 0; enforced from the mode-conflict admission stage.' }), "pnp_disable_monitors": S.optional(S.Boolean).annotations({ description: 'EXPERIMENTAL (Windows): DISABLE physical monitors\' PnP device nodes for the stream\'s\nduration (persistently, so a standby monitor\/TV whose hot-plug events re-arrive stays\ndisabled) and re-enable them at teardown. Two selectors: the monitors an `Exclusive`\nisolate deactivated, plus โ€” in ANY topology โ€” external monitors that are connected but not\npart of the desktop (the standby TV that was never active, whose input auto-scan \/\ninstant-on HPD cycling re-probes the link every few seconds). Targets the same\n\"connected-but-dark head\" periodic-stutter class as [`Self::ddc_power_off`], but at the\nWindows-reaction level: a disabled devnode\'s wake events trigger no PnP arrival, no CCD\nre-evaluation, no DWM invalidation. A crash-recovery journal re-enables leftovers on host\nstartup. Orthogonal to `preset` (like `game_session`); `#[serde(default)]` = off.' }), "preset": S.optional(S.Literal('custom', 'default', 'gaming-rig', 'shared-desktop', 'hotdesk', 'workstation')).annotations({ description: 'A named bundle of the fields below. `Custom` (the default) means the explicit fields rule; any\nother preset ignores the stored fields and expands to its own ([`DisplayPolicy::effective`]).' }), "topology": S.optional(S.Literal('auto', 'extend', 'primary', 'exclusive')).annotations({ description: 'What the host does to the box\'s display topology while managed virtual displays are up.' }), "version": S.optional(S.Number.pipe(S.greaterThanOrEqualTo(setDisplaySettingsResponseSettingsVersionMin))).annotations({ description: 'Schema version (currently 1) โ€” lets a future field addition migrate rather than reject.' }) }).annotations({ description: 'The stored policy (preset + custom fields), or the built-in default when unconfigured.' }) }).annotations({ description: 'Full display-management state for the console: the stored policy, every preset\'s expansion, the\nresolved effective policy, and which options this build actually enforces yet (Stage 0 wires\nkeep-alive linger + topology; the rest are stored but not yet acted on).' }) /** * The host's managed virtual displays right now โ€” active (streaming), lingering (kept after * disconnect, counting down to teardown), or pinned (kept indefinitely). See * `design/display-management.md`. * @summary Live virtual displays */ export const getDisplayStateResponseDisplaysItemDisplayIndexMin = 0; export const getDisplayStateResponseDisplaysItemExpiresInMsMin = 0; export const getDisplayStateResponseDisplaysItemGroupMin = 0; export const getDisplayStateResponseDisplaysItemIdentitySlotMin = 0; export const getDisplayStateResponseDisplaysItemSessionsMin = 0; export const getDisplayStateResponseDisplaysItemSlotMin = 0; export const GetDisplayStateResponse = S.Struct({ "displays": S.Array(S.Struct({ "backend": S.String.annotations({ description: 'Backend name (`pf-vdisplay`, `kwin`, โ€ฆ).' }), "client": S.optional(S.NullOr(S.String)).annotations({ description: 'Short client label, when the owner tracks it.' }), "display_index": S.Number.pipe(S.greaterThanOrEqualTo(getDisplayStateResponseDisplaysItemDisplayIndexMin)).annotations({ description: 'This display\'s ordinal within its group, in acquire order (0-based).' }), "expires_in_ms": S.optional(S.NullOr(S.Number.pipe(S.greaterThanOrEqualTo(getDisplayStateResponseDisplaysItemExpiresInMsMin)))).annotations({ description: 'Milliseconds until a lingering display is torn down (absent when active\/pinned).' }), "group": S.Number.pipe(S.greaterThanOrEqualTo(getDisplayStateResponseDisplaysItemGroupMin)).annotations({ description: 'Display group (shared desktop) id โ€” several displays with the same group form one desktop (ยง6A).' }), "identity_slot": S.optional(S.NullOr(S.Number.pipe(S.greaterThanOrEqualTo(getDisplayStateResponseDisplaysItemIdentitySlotMin)))).annotations({ description: 'Stable per-client identity slot keying persistent config + manual layout (absent = shared\/anonymous).' }), "mode": S.String.annotations({ description: '`WIDTHxHEIGHT@HZ`.' }), "sessions": S.Number.pipe(S.greaterThanOrEqualTo(getDisplayStateResponseDisplaysItemSessionsMin)).annotations({ description: 'Live sessions holding the display.' }), "slot": S.Number.pipe(S.greaterThanOrEqualTo(getDisplayStateResponseDisplaysItemSlotMin)).annotations({ description: 'Stable-enough id for the `\/display\/release` `slot` argument.' }), "state": S.String.annotations({ description: '`active` | `lingering` | `pinned`.' }), "topology": S.String.annotations({ description: 'Effective topology for this display\'s group (`extend` | `primary` | `exclusive`).' }), "x": S.Number.annotations({ description: 'Desktop-space top-left `x` (auto-row or the console\'s manual arrangement, ยง6.2).' }), "y": S.Number.annotations({ description: 'Desktop-space top-left `y`.' }) }).annotations({ description: 'One live or kept virtual display.' })) }).annotations({ description: 'The host\'s managed virtual displays right now.' }) /** * Server-Sent Events stream of the host's lifecycle events: client connect/disconnect, session * and stream start/end, pairing decisions, display create/release, library changes, host * start/stop โ€” both protocol planes. Frames carry `id:` = the event's monotonic `seq`, * `event:` = its kind, and `data:` = the event JSON (schema-versioned, additive-only). * * Resume: standard `Last-Event-ID` (or `?since=`) replays from the in-memory ring; a consumer * that fell off the ring receives an `event: dropped` frame first and should resync via the * REST snapshots. Keep-alive comments are sent every 15 s. * @summary Stream host lifecycle events (SSE) */ export const streamEventsQuerySinceMin = 0; export const StreamEventsQueryParams = S.Struct({ "since": S.optional(S.Number.pipe(S.greaterThanOrEqualTo(streamEventsQuerySinceMin))).annotations({ description: 'Resume cursor: only events with `seq` greater than this are sent (the ring keeps the newest ~1024). `Last-Event-ID` takes precedence.' }), "kinds": S.optional(S.String).annotations({ description: 'Comma-separated server-side kind filter: exact kinds (`pairing.pending`) or `domain.\*` prefixes (`stream.\*`).' }) }) export const streamEventsHeaderLastEventIDMin = 0; export const StreamEventsHeader = S.Struct({ "Last-Event-ID": S.optional(S.NullOr(S.Number.pipe(S.greaterThanOrEqualTo(streamEventsHeaderLastEventIDMin)))).annotations({ description: 'SSE auto-reconnect cursor โ€” the `id:` of the last received frame.' }) }) /** * Lists the host's hardware GPUs, the persisted auto/manual preference, the GPU the next session * will use (and why), and the GPU live sessions encode on right now. * @summary GPU inventory and selection */ export const listGpusResponseActiveTwoSessionsMin = 0; export const listGpusResponseGpusItemVramMbMin = 0; export const ListGpusResponse = S.Struct({ "active": S.optional(S.Union(S.Null, S.Struct({ "backend": S.String.annotations({ description: 'The encode backend in use (`nvenc` | `amf` | `qsv` | `vaapi` | `software`).' }), "id": S.String.annotations({ description: 'Stable id matching an entry of `gpus` (empty for the CPU\/software encoder).' }), "name": S.String, "sessions": S.Number.pipe(S.greaterThanOrEqualTo(listGpusResponseActiveTwoSessionsMin)).annotations({ description: 'Number of live encode sessions on it.' }), "vendor": S.String.annotations({ description: '`nvidia` | `amd` | `intel` | `other`.' }) }).annotations({ description: 'The GPU live sessions use right now (absent while nothing is streaming).' }))), "env_override": S.optional(S.NullOr(S.String)).annotations({ description: '`PUNKTFUNK_RENDER_ADAPTER` (the host.env pin), when set โ€” it applies while `mode` is\n`auto`; a manual preference overrides it.' }), "gpus": S.Array(S.Struct({ "id": S.String.annotations({ description: 'Stable identifier (`vendorid-deviceid-occurrence`, hex PCI ids) โ€” pass to `setGpuPreference`.\nStable across reboots and driver updates, unlike an adapter index or LUID.' }), "name": S.String.annotations({ description: 'Adapter\/marketing name.' }), "vendor": S.String.annotations({ description: '`nvidia` | `amd` | `intel` | `other`.' }), "vram_mb": S.Number.pipe(S.greaterThanOrEqualTo(listGpusResponseGpusItemVramMbMin)).annotations({ description: 'Dedicated VRAM in MiB (0 where the platform doesn\'t expose it).' }) }).annotations({ description: 'One hardware GPU on the host (software\/WARP adapters are never listed).' })).annotations({ description: 'The host\'s hardware GPUs.' }), "mode": S.String.annotations({ description: '`auto` or `manual`.' }), "preferred_available": S.Boolean.annotations({ description: 'Whether the preferred GPU is currently present.' }), "preferred_id": S.optional(S.NullOr(S.String)).annotations({ description: 'The manually preferred GPU\'s stable id, when one is stored (kept while `mode` is `auto` so\na console can offer returning to it). May reference a GPU that is currently absent.' }), "preferred_name": S.optional(S.NullOr(S.String)).annotations({ description: 'The stored name of the preferred GPU (a usable label even when it is absent).' }), "selected": S.optional(S.Union(S.Null, S.Struct({ "id": S.String, "name": S.String, "source": S.String.annotations({ description: 'Why this GPU was selected: `preference` (the manual choice), `env`\n(`PUNKTFUNK_RENDER_ADAPTER`), `auto` (max dedicated VRAM \/ platform default), or\n`preference_missing` (a manual choice is set but that GPU is absent โ€” auto-selected\ninstead so the host keeps streaming).' }), "vendor": S.String.annotations({ description: '`nvidia` | `amd` | `intel` | `other`.' }) }).annotations({ description: 'The GPU the next session will use.' }))) }).annotations({ description: 'Full GPU-selection state for the console: inventory, the persisted preference, what the next\nsession will use, and what is in use right now.' }) /** * `auto` restores automatic selection (`PUNKTFUNK_RENDER_ADAPTER` pin, else max dedicated VRAM); * `manual` pins capture + encode to the given GPU. Persisted across restarts; applies to the * **next** session (a running session keeps its GPU). If the preferred GPU is absent at session * start the host falls back to automatic selection rather than failing. * @summary Set the GPU preference */ export const SetGpuPreferenceBody = S.Struct({ "gpu_id": S.optional(S.NullOr(S.String)).annotations({ description: 'Required when `mode` is `manual`: the stable `id` of a currently listed GPU\n(see `listGpus`).' }), "mode": S.String.annotations({ description: '`auto` (env pin, else max dedicated VRAM โ€” the default) or `manual`.' }) }).annotations({ description: 'Request body for `setGpuPreference`.' }) export const setGpuPreferenceResponseActiveTwoSessionsMin = 0; export const setGpuPreferenceResponseGpusItemVramMbMin = 0; export const SetGpuPreferenceResponse = S.Struct({ "active": S.optional(S.Union(S.Null, S.Struct({ "backend": S.String.annotations({ description: 'The encode backend in use (`nvenc` | `amf` | `qsv` | `vaapi` | `software`).' }), "id": S.String.annotations({ description: 'Stable id matching an entry of `gpus` (empty for the CPU\/software encoder).' }), "name": S.String, "sessions": S.Number.pipe(S.greaterThanOrEqualTo(setGpuPreferenceResponseActiveTwoSessionsMin)).annotations({ description: 'Number of live encode sessions on it.' }), "vendor": S.String.annotations({ description: '`nvidia` | `amd` | `intel` | `other`.' }) }).annotations({ description: 'The GPU live sessions use right now (absent while nothing is streaming).' }))), "env_override": S.optional(S.NullOr(S.String)).annotations({ description: '`PUNKTFUNK_RENDER_ADAPTER` (the host.env pin), when set โ€” it applies while `mode` is\n`auto`; a manual preference overrides it.' }), "gpus": S.Array(S.Struct({ "id": S.String.annotations({ description: 'Stable identifier (`vendorid-deviceid-occurrence`, hex PCI ids) โ€” pass to `setGpuPreference`.\nStable across reboots and driver updates, unlike an adapter index or LUID.' }), "name": S.String.annotations({ description: 'Adapter\/marketing name.' }), "vendor": S.String.annotations({ description: '`nvidia` | `amd` | `intel` | `other`.' }), "vram_mb": S.Number.pipe(S.greaterThanOrEqualTo(setGpuPreferenceResponseGpusItemVramMbMin)).annotations({ description: 'Dedicated VRAM in MiB (0 where the platform doesn\'t expose it).' }) }).annotations({ description: 'One hardware GPU on the host (software\/WARP adapters are never listed).' })).annotations({ description: 'The host\'s hardware GPUs.' }), "mode": S.String.annotations({ description: '`auto` or `manual`.' }), "preferred_available": S.Boolean.annotations({ description: 'Whether the preferred GPU is currently present.' }), "preferred_id": S.optional(S.NullOr(S.String)).annotations({ description: 'The manually preferred GPU\'s stable id, when one is stored (kept while `mode` is `auto` so\na console can offer returning to it). May reference a GPU that is currently absent.' }), "preferred_name": S.optional(S.NullOr(S.String)).annotations({ description: 'The stored name of the preferred GPU (a usable label even when it is absent).' }), "selected": S.optional(S.Union(S.Null, S.Struct({ "id": S.String, "name": S.String, "source": S.String.annotations({ description: 'Why this GPU was selected: `preference` (the manual choice), `env`\n(`PUNKTFUNK_RENDER_ADAPTER`), `auto` (max dedicated VRAM \/ platform default), or\n`preference_missing` (a manual choice is set but that GPU is absent โ€” auto-selected\ninstead so the host keeps streaming).' }), "vendor": S.String.annotations({ description: '`nvidia` | `amd` | `intel` | `other`.' }) }).annotations({ description: 'The GPU the next session will use.' }))) }).annotations({ description: 'Full GPU-selection state for the console: inventory, the persisted preference, what the next\nsession will use, and what is in use right now.' }) /** * Always available without authentication. * @summary Liveness probe */ export const getHealthResponseAbiVersionMin = 0; export const GetHealthResponse = S.Struct({ "abi_version": S.Number.pipe(S.greaterThanOrEqualTo(getHealthResponseAbiVersionMin)).annotations({ description: '`punktfunk-core` C ABI version.' }), "status": S.String.annotations({ description: 'Always `\"ok\"` when the host responds.' }), "version": S.String.annotations({ description: '`punktfunk-host` crate version.' }) }).annotations({ description: 'Liveness + version probe.' }) /** * The operator's `hooks.json`: commands and webhooks fired on host lifecycle events. Empty * when unconfigured. * @summary Get the hook configuration */ export const getHooksResponseHooksItemDebounceMsMin = 0; export const getHooksResponseHooksItemTimeoutSMin = 0; export const GetHooksResponse = S.Struct({ "hooks": S.optional(S.Array(S.Struct({ "debounce_ms": S.optional(S.Number.pipe(S.greaterThanOrEqualTo(getHooksResponseHooksItemDebounceMsMin))).annotations({ description: 'Minimum interval between firings of this hook, in milliseconds. 0 = fire every time.' }), "filter": S.optional(S.Union(S.Null, S.Struct({ "app": S.optional(S.NullOr(S.String)).annotations({ description: 'Launched app id\/title (`stream.\*` events).' }), "client": S.optional(S.NullOr(S.String)).annotations({ description: 'Client\/device name (for `session.\*`: the short client label the Dashboard shows).' }), "fingerprint": S.optional(S.NullOr(S.String)).annotations({ description: 'Certificate fingerprint (hex, case-insensitive).' }), "plane": S.optional(S.Union(S.Null, S.Literal('native', 'gamestream').annotations({ description: 'Protocol plane (`native` \/ `gamestream`).' }))) }).annotations({ description: 'Exact-match constraints on the event\'s fields; every present field must match.' }))), "hmac_secret_file": S.optional(S.NullOr(S.String)).annotations({ description: 'File holding the webhook HMAC secret (`X-Punktfunk-Signature: sha256=`). The file\nshould be operator-owned and private; a world-readable secret is warned about.' }), "on": S.String.annotations({ description: 'Which events fire this hook: an exact kind (`stream.started`) or a `domain.\*` prefix\n(`pairing.\*`) โ€” the same vocabulary as the SSE `?kinds=` filter.' }), "run": S.optional(S.NullOr(S.String)).annotations({ description: 'Shell command to execute (detached, event JSON on stdin + `PF_EVENT_\*` env).' }), "timeout_s": S.optional(S.Number.pipe(S.greaterThanOrEqualTo(getHooksResponseHooksItemTimeoutSMin))).annotations({ description: 'Exec timeout in seconds (1โ€“600, default 30); the process group is killed on expiry.' }), "webhook": S.optional(S.NullOr(S.String)).annotations({ description: 'URL to POST the event JSON to.' }) }).annotations({ description: 'One hook: fire `run` and\/or `webhook` when an event matching `on` (+ `filter`) occurs.' }))) }).annotations({ description: 'The operator\'s hook configuration โ€” the `hooks.json` document and the `\/api\/v1\/hooks` body.' }) /** * Validates and persists a full `hooks.json` document (this is a whole-document PUT, not a * patch). Applies from the next event โ€” no restart. Hook commands run as the host user * (interactive user session on Windows): treat this configuration as operator-privileged. * @summary Replace the hook configuration */ export const setHooksBodyHooksItemDebounceMsMin = 0; export const setHooksBodyHooksItemTimeoutSMin = 0; export const SetHooksBody = S.Struct({ "hooks": S.optional(S.Array(S.Struct({ "debounce_ms": S.optional(S.Number.pipe(S.greaterThanOrEqualTo(setHooksBodyHooksItemDebounceMsMin))).annotations({ description: 'Minimum interval between firings of this hook, in milliseconds. 0 = fire every time.' }), "filter": S.optional(S.Union(S.Null, S.Struct({ "app": S.optional(S.NullOr(S.String)).annotations({ description: 'Launched app id\/title (`stream.\*` events).' }), "client": S.optional(S.NullOr(S.String)).annotations({ description: 'Client\/device name (for `session.\*`: the short client label the Dashboard shows).' }), "fingerprint": S.optional(S.NullOr(S.String)).annotations({ description: 'Certificate fingerprint (hex, case-insensitive).' }), "plane": S.optional(S.Union(S.Null, S.Literal('native', 'gamestream').annotations({ description: 'Protocol plane (`native` \/ `gamestream`).' }))) }).annotations({ description: 'Exact-match constraints on the event\'s fields; every present field must match.' }))), "hmac_secret_file": S.optional(S.NullOr(S.String)).annotations({ description: 'File holding the webhook HMAC secret (`X-Punktfunk-Signature: sha256=`). The file\nshould be operator-owned and private; a world-readable secret is warned about.' }), "on": S.String.annotations({ description: 'Which events fire this hook: an exact kind (`stream.started`) or a `domain.\*` prefix\n(`pairing.\*`) โ€” the same vocabulary as the SSE `?kinds=` filter.' }), "run": S.optional(S.NullOr(S.String)).annotations({ description: 'Shell command to execute (detached, event JSON on stdin + `PF_EVENT_\*` env).' }), "timeout_s": S.optional(S.Number.pipe(S.greaterThanOrEqualTo(setHooksBodyHooksItemTimeoutSMin))).annotations({ description: 'Exec timeout in seconds (1โ€“600, default 30); the process group is killed on expiry.' }), "webhook": S.optional(S.NullOr(S.String)).annotations({ description: 'URL to POST the event JSON to.' }) }).annotations({ description: 'One hook: fire `run` and\/or `webhook` when an event matching `on` (+ `filter`) occurs.' }))) }).annotations({ description: 'The operator\'s hook configuration โ€” the `hooks.json` document and the `\/api\/v1\/hooks` body.' }) export const setHooksResponseHooksItemDebounceMsMin = 0; export const setHooksResponseHooksItemTimeoutSMin = 0; export const SetHooksResponse = S.Struct({ "hooks": S.optional(S.Array(S.Struct({ "debounce_ms": S.optional(S.Number.pipe(S.greaterThanOrEqualTo(setHooksResponseHooksItemDebounceMsMin))).annotations({ description: 'Minimum interval between firings of this hook, in milliseconds. 0 = fire every time.' }), "filter": S.optional(S.Union(S.Null, S.Struct({ "app": S.optional(S.NullOr(S.String)).annotations({ description: 'Launched app id\/title (`stream.\*` events).' }), "client": S.optional(S.NullOr(S.String)).annotations({ description: 'Client\/device name (for `session.\*`: the short client label the Dashboard shows).' }), "fingerprint": S.optional(S.NullOr(S.String)).annotations({ description: 'Certificate fingerprint (hex, case-insensitive).' }), "plane": S.optional(S.Union(S.Null, S.Literal('native', 'gamestream').annotations({ description: 'Protocol plane (`native` \/ `gamestream`).' }))) }).annotations({ description: 'Exact-match constraints on the event\'s fields; every present field must match.' }))), "hmac_secret_file": S.optional(S.NullOr(S.String)).annotations({ description: 'File holding the webhook HMAC secret (`X-Punktfunk-Signature: sha256=`). The file\nshould be operator-owned and private; a world-readable secret is warned about.' }), "on": S.String.annotations({ description: 'Which events fire this hook: an exact kind (`stream.started`) or a `domain.\*` prefix\n(`pairing.\*`) โ€” the same vocabulary as the SSE `?kinds=` filter.' }), "run": S.optional(S.NullOr(S.String)).annotations({ description: 'Shell command to execute (detached, event JSON on stdin + `PF_EVENT_\*` env).' }), "timeout_s": S.optional(S.Number.pipe(S.greaterThanOrEqualTo(setHooksResponseHooksItemTimeoutSMin))).annotations({ description: 'Exec timeout in seconds (1โ€“600, default 30); the process group is killed on expiry.' }), "webhook": S.optional(S.NullOr(S.String)).annotations({ description: 'URL to POST the event JSON to.' }) }).annotations({ description: 'One hook: fire `run` and\/or `webhook` when an event matching `on` (+ `filter`) occurs.' }))) }).annotations({ description: 'The operator\'s hook configuration โ€” the `hooks.json` document and the `\/api\/v1\/hooks` body.' }) /** * @summary Host identity and capabilities */ export const getHostInfoResponseAbiVersionMin = 0; export const getHostInfoResponsePortsAudioMin = 0; export const getHostInfoResponsePortsControlMin = 0; export const getHostInfoResponsePortsHttpMin = 0; export const getHostInfoResponsePortsHttpsMin = 0; export const getHostInfoResponsePortsMgmtMin = 0; export const getHostInfoResponsePortsRtspMin = 0; export const getHostInfoResponsePortsVideoMin = 0; export const GetHostInfoResponse = S.Struct({ "abi_version": S.Number.pipe(S.greaterThanOrEqualTo(getHostInfoResponseAbiVersionMin)).annotations({ description: '`punktfunk-core` C ABI version.' }), "app_version": S.String.annotations({ description: 'GameStream host version advertised to Moonlight clients.' }), "codecs": S.Array(S.Literal('h264', 'hevc', 'av1', 'pyrowave').annotations({ description: 'Video codec identifier. The wire token matches the codec\'s canonical name used across the\nstack (SDP\/GameStream advertisement, the stats-capture `CaptureMeta.codec`, and the encoder\'s\n[`Codec::label`]) โ€” notably `H.265` serializes as `\"hevc\"`, not `\"h265\"`, so the same codec\nreads identically on every console page.' })).annotations({ description: 'Codecs the host can encode (NVENC).' }), "gamestream": S.Boolean.annotations({ description: 'Whether the GameStream\/Moonlight-compat planes are running (`--gamestream`). `false` on the\nsecure default (native punktfunk\/1 only) โ€” a console can hide Moonlight-only UI (e.g. the\nMoonlight PIN pairing card, which could never receive a PIN when this is `false`).' }), "gfe_version": S.String.annotations({ description: 'GFE version advertised to Moonlight clients.' }), "hostname": S.String, "local_ip": S.String.annotations({ description: 'Best-effort primary LAN IP.' }), "ports": S.Struct({ "audio": S.Number.pipe(S.greaterThanOrEqualTo(getHostInfoResponsePortsAudioMin)), "control": S.Number.pipe(S.greaterThanOrEqualTo(getHostInfoResponsePortsControlMin)), "http": S.Number.pipe(S.greaterThanOrEqualTo(getHostInfoResponsePortsHttpMin)).annotations({ description: 'nvhttp plain HTTP (serverinfo, pairing).' }), "https": S.Number.pipe(S.greaterThanOrEqualTo(getHostInfoResponsePortsHttpsMin)).annotations({ description: 'nvhttp mutual-TLS HTTPS (post-pairing).' }), "mgmt": S.Number.pipe(S.greaterThanOrEqualTo(getHostInfoResponsePortsMgmtMin)).annotations({ description: 'This management API.' }), "rtsp": S.Number.pipe(S.greaterThanOrEqualTo(getHostInfoResponsePortsRtspMin)), "video": S.Number.pipe(S.greaterThanOrEqualTo(getHostInfoResponsePortsVideoMin)) }).annotations({ description: 'Every port a client integration may need (Moonlight derives the stream ports from the\nHTTP base; a control pane should not have to).' }), "uniqueid": S.String.annotations({ description: 'Stable per-host id (persisted across restarts), matched on pairing.' }), "version": S.String.annotations({ description: '`punktfunk-host` crate version.' }) }).annotations({ description: 'Host identity and advertised capabilities (static for the life of the process).' }) /** * Every installed-store title (Steam, read from the host's local files โ€” no Steam API key) * merged with the user's custom entries, sorted by title. Artwork fields are URLs the client * fetches directly (the public Steam CDN for Steam titles). * @summary List the game library */ export const GetLibraryResponseItem = S.Struct({ "art": S.Struct({ "header": S.optional(S.NullOr(S.String)).annotations({ description: 'Horizontal header (Steam `header.jpg`) โ€” the universal fallback.' }), "hero": S.optional(S.NullOr(S.String)).annotations({ description: 'Wide background (Steam `library_hero.jpg`).' }), "logo": S.optional(S.NullOr(S.String)).annotations({ description: 'Transparent title logo (Steam `logo.png`).' }), "portrait": S.optional(S.NullOr(S.String)).annotations({ description: 'Vertical capsule \/ poster (Steam `library_600x900.jpg`). Best for a grid.' }) }).annotations({ description: 'Cover art for a title. All fields are URLs (the Steam CDN for Steam titles, user-supplied for\ncustom). The client prefers `portrait` for a grid and falls back to `header` when a title has\nno 600ร—900 capsule (common for older Steam apps).' }), "id": S.String.annotations({ description: 'Stable, store-qualified id: `steam:` or `custom:`.' }), "launch": S.optional(S.Union(S.Null, S.Struct({ "kind": S.String.annotations({ description: '`\"steam_appid\"` or `\"command\"`.' }), "value": S.String.annotations({ description: 'The appid (for `steam_appid`) or the shell command (for `command`).' }) }).annotations({ description: 'How the host would launch it, when known.' }))), "store": S.String.annotations({ description: 'Which store surfaced it: `\"steam\"` or `\"custom\"`.' }), "title": S.String }).annotations({ description: 'One title in the unified library, regardless of which store it came from.' }) export const GetLibraryResponse = S.Array(GetLibraryResponseItem) /** * Resolves `kind` (`portrait` | `hero` | `logo` | `header`) for the given library id and streams * the image bytes. For a Steam title, the host's own local Steam cache is tried first (exact โ€” * it's what the user's Steam client already shows for it), the public Steam CDN's flat URL * convention as a fallback (newer titles' CDN assets can live at a per-asset-hash path the host * can't predict, in which case this 404s and the client falls through to its next art candidate). * Only Steam ids are backed today; any other store 404s. * @summary Fetch one cover-art image for a library entry */ export const GetLibraryArtParams = S.Struct({ "id": S.String.annotations({ description: 'The store-qualified library id, e.g. `steam:570`' }), "kind": S.String.annotations({ description: '`portrait` | `hero` | `logo` | `header`' }) }) /** * Creates a user-curated title (e.g. a non-Steam game, an emulator, a ROM) with caller-supplied * artwork URLs. The host assigns a stable id, returned in the body. * @summary Add a custom library entry */ export const CreateCustomGameBody = S.Struct({ "art": S.optional(S.Struct({ "header": S.optional(S.NullOr(S.String)).annotations({ description: 'Horizontal header (Steam `header.jpg`) โ€” the universal fallback.' }), "hero": S.optional(S.NullOr(S.String)).annotations({ description: 'Wide background (Steam `library_hero.jpg`).' }), "logo": S.optional(S.NullOr(S.String)).annotations({ description: 'Transparent title logo (Steam `logo.png`).' }), "portrait": S.optional(S.NullOr(S.String)).annotations({ description: 'Vertical capsule \/ poster (Steam `library_600x900.jpg`). Best for a grid.' }) })).annotations({ description: 'Cover art for a title. All fields are URLs (the Steam CDN for Steam titles, user-supplied for\ncustom). The client prefers `portrait` for a grid and falls back to `header` when a title has\nno 600ร—900 capsule (common for older Steam apps).' }), "launch": S.optional(S.Union(S.Null, S.Struct({ "kind": S.String.annotations({ description: '`\"steam_appid\"` or `\"command\"`.' }), "value": S.String.annotations({ description: 'The appid (for `steam_appid`) or the shell command (for `command`).' }) }).annotations({ description: 'How the host would launch a title (consumed by the session launcher in a later step). Kept\nopen-ended so new stores slot in: `steam_appid` โ†’ `steam steam:\/\/rungameid\/`;\n`command` โ†’ run `` nested in a gamescope session.' }))), "prep": S.optional(S.Array(S.Struct({ "do": S.String.annotations({ description: 'Command run before launch. Same execution recipe and ownership checks as hook `run`\ncommands (event-less: stdin is empty JSON, env carries the `PF_APP_\*` context).' }), "undo": S.optional(S.NullOr(S.String)).annotations({ description: 'Command run after the session ends. Skipped when its `do` failed (it never took effect).' }) }).annotations({ description: 'One per-app preparation step (RFC ยง6 โ€” deliberate Sunshine `prep-cmd` parity): `do` runs\n\*\*synchronously before the app launches\*\* (an HDR toggle or a MangoHud env change must land\nfirst), `undo` runs at session end โ€” reverse order across steps, best-effort, on every exit\npath including a crash-unwind (RAII via [`PrepGuard`]).' }))).annotations({ description: 'Per-title prep\/undo steps โ€” commands run as the host user; operator-privileged config.' }), "title": S.String }).annotations({ description: 'Request body to create or replace a custom entry (no `id` โ€” the host owns it).' }) /** * @summary Update a custom library entry */ export const UpdateCustomGameParams = S.Struct({ "id": S.String.annotations({ description: 'The custom entry id (without the `custom:` prefix)' }) }) export const UpdateCustomGameBody = S.Struct({ "art": S.optional(S.Struct({ "header": S.optional(S.NullOr(S.String)).annotations({ description: 'Horizontal header (Steam `header.jpg`) โ€” the universal fallback.' }), "hero": S.optional(S.NullOr(S.String)).annotations({ description: 'Wide background (Steam `library_hero.jpg`).' }), "logo": S.optional(S.NullOr(S.String)).annotations({ description: 'Transparent title logo (Steam `logo.png`).' }), "portrait": S.optional(S.NullOr(S.String)).annotations({ description: 'Vertical capsule \/ poster (Steam `library_600x900.jpg`). Best for a grid.' }) })).annotations({ description: 'Cover art for a title. All fields are URLs (the Steam CDN for Steam titles, user-supplied for\ncustom). The client prefers `portrait` for a grid and falls back to `header` when a title has\nno 600ร—900 capsule (common for older Steam apps).' }), "launch": S.optional(S.Union(S.Null, S.Struct({ "kind": S.String.annotations({ description: '`\"steam_appid\"` or `\"command\"`.' }), "value": S.String.annotations({ description: 'The appid (for `steam_appid`) or the shell command (for `command`).' }) }).annotations({ description: 'How the host would launch a title (consumed by the session launcher in a later step). Kept\nopen-ended so new stores slot in: `steam_appid` โ†’ `steam steam:\/\/rungameid\/`;\n`command` โ†’ run `` nested in a gamescope session.' }))), "prep": S.optional(S.Array(S.Struct({ "do": S.String.annotations({ description: 'Command run before launch. Same execution recipe and ownership checks as hook `run`\ncommands (event-less: stdin is empty JSON, env carries the `PF_APP_\*` context).' }), "undo": S.optional(S.NullOr(S.String)).annotations({ description: 'Command run after the session ends. Skipped when its `do` failed (it never took effect).' }) }).annotations({ description: 'One per-app preparation step (RFC ยง6 โ€” deliberate Sunshine `prep-cmd` parity): `do` runs\n\*\*synchronously before the app launches\*\* (an HDR toggle or a MangoHud env change must land\nfirst), `undo` runs at session end โ€” reverse order across steps, best-effort, on every exit\npath including a crash-unwind (RAII via [`PrepGuard`]).' }))).annotations({ description: 'Per-title prep\/undo steps โ€” commands run as the host user; operator-privileged config.' }), "title": S.String }).annotations({ description: 'Request body to create or replace a custom entry (no `id` โ€” the host owns it).' }) export const UpdateCustomGameResponse = S.Struct({ "art": S.optional(S.Struct({ "header": S.optional(S.NullOr(S.String)).annotations({ description: 'Horizontal header (Steam `header.jpg`) โ€” the universal fallback.' }), "hero": S.optional(S.NullOr(S.String)).annotations({ description: 'Wide background (Steam `library_hero.jpg`).' }), "logo": S.optional(S.NullOr(S.String)).annotations({ description: 'Transparent title logo (Steam `logo.png`).' }), "portrait": S.optional(S.NullOr(S.String)).annotations({ description: 'Vertical capsule \/ poster (Steam `library_600x900.jpg`). Best for a grid.' }) })).annotations({ description: 'Cover art for a title. All fields are URLs (the Steam CDN for Steam titles, user-supplied for\ncustom). The client prefers `portrait` for a grid and falls back to `header` when a title has\nno 600ร—900 capsule (common for older Steam apps).' }), "id": S.String.annotations({ description: 'Host-assigned, stable for the life of the entry (the `{id}` in the CRUD path).' }), "launch": S.optional(S.Union(S.Null, S.Struct({ "kind": S.String.annotations({ description: '`\"steam_appid\"` or `\"command\"`.' }), "value": S.String.annotations({ description: 'The appid (for `steam_appid`) or the shell command (for `command`).' }) }).annotations({ description: 'How the host would launch a title (consumed by the session launcher in a later step). Kept\nopen-ended so new stores slot in: `steam_appid` โ†’ `steam steam:\/\/rungameid\/`;\n`command` โ†’ run `` nested in a gamescope session.' }))), "prep": S.optional(S.Array(S.Struct({ "do": S.String.annotations({ description: 'Command run before launch. Same execution recipe and ownership checks as hook `run`\ncommands (event-less: stdin is empty JSON, env carries the `PF_APP_\*` context).' }), "undo": S.optional(S.NullOr(S.String)).annotations({ description: 'Command run after the session ends. Skipped when its `do` failed (it never took effect).' }) }).annotations({ description: 'One per-app preparation step (RFC ยง6 โ€” deliberate Sunshine `prep-cmd` parity): `do` runs\n\*\*synchronously before the app launches\*\* (an HDR toggle or a MangoHud env change must land\nfirst), `undo` runs at session end โ€” reverse order across steps, best-effort, on every exit\npath including a crash-unwind (RAII via [`PrepGuard`]).' }))).annotations({ description: 'Per-title prep\/undo steps (RFC ยง6): each `do` runs before this title launches, each\n`undo` at session end in reverse order (see [`crate::hooks::run_prep`]).' }), "title": S.String }).annotations({ description: 'A user-added title, persisted in `~\/.config\/punktfunk\/library.json`. Same shape the API\nreturns and the web console edits.' }) /** * @summary Delete a custom library entry */ export const DeleteCustomGameParams = S.Struct({ "id": S.String.annotations({ description: 'The custom entry id (without the `custom:` prefix)' }) }) /** * Non-sensitive status (counts and booleans only โ€” no PIN values, no fingerprints, no device * names). Unauthenticated, but served to loopback peers only. * @summary Local status summary for the tray icon */ export const getLocalSummaryResponseKeptDisplaysMin = 0; export const getLocalSummaryResponseNativePairedClientsMin = 0; export const getLocalSummaryResponsePairedClientsMin = 0; export const getLocalSummaryResponsePendingApprovalsMin = 0; export const getLocalSummaryResponseSessionTwoFpsMin = 0; export const getLocalSummaryResponseSessionTwoHeightMin = 0; export const getLocalSummaryResponseSessionTwoWidthMin = 0; export const GetLocalSummaryResponse = S.Struct({ "audio_streaming": S.Boolean.annotations({ description: 'True while the audio stream thread is running.' }), "conflicts": S.optional(S.Array(S.String)).annotations({ description: 'Other Moonlight-compatible hosts (Sunshine\/Apollo\/โ€ฆ) detected on this machine at startup โ€”\nrunning one alongside Punktfunk is unsupported. Compact labels (e.g. `Sunshine (running)`);\nthe tray\/console surface them so the clash is visible before pairing silently fails.' }), "kept_displays": S.Number.pipe(S.greaterThanOrEqualTo(getLocalSummaryResponseKeptDisplaysMin)).annotations({ description: 'Virtual displays being KEPT with no live session โ€” lingering (keep-alive window) or pinned\n(`keep_alive: forever`). Non-zero means a display (and, exclusive, your physical monitors) is\nheld; the tray surfaces it + a one-click release. Active (in-use) displays are not counted.' }), "native_paired_clients": S.Number.pipe(S.greaterThanOrEqualTo(getLocalSummaryResponseNativePairedClientsMin)).annotations({ description: 'Number of paired native (punktfunk\/1) devices.' }), "paired_clients": S.Number.pipe(S.greaterThanOrEqualTo(getLocalSummaryResponsePairedClientsMin)).annotations({ description: 'Number of pinned (paired) GameStream client certificates.' }), "pending_approvals": S.Number.pipe(S.greaterThanOrEqualTo(getLocalSummaryResponsePendingApprovalsMin)).annotations({ description: 'Native pairing knocks awaiting the operator\'s approval (count only).' }), "pin_pending": S.Boolean.annotations({ description: 'True while a GameStream pairing handshake is parked waiting for the user\'s PIN.' }), "session": S.optional(S.Union(S.Null, S.Struct({ "fps": S.Number.pipe(S.greaterThanOrEqualTo(getLocalSummaryResponseSessionTwoFpsMin)), "height": S.Number.pipe(S.greaterThanOrEqualTo(getLocalSummaryResponseSessionTwoHeightMin)), "width": S.Number.pipe(S.greaterThanOrEqualTo(getLocalSummaryResponseSessionTwoWidthMin)) }).annotations({ description: 'The active launch session (set by Moonlight\'s `\/launch`, cleared on cancel\/stop).' }))), "version": S.String.annotations({ description: 'Host version (mirrors `\/health`).' }), "video_streaming": S.Boolean.annotations({ description: 'True while the video stream thread is running.' }) }).annotations({ description: 'Non-sensitive host status for the local tray icon: counts and booleans only โ€” no PIN values,\nno fingerprints, no device names. Served unauthenticated to LOOPBACK peers only (see\n`require_auth`): the bearer-token file is SYSTEM\/Administrators-DACL\'d on Windows, so the\nper-user tray process cannot authenticate โ€” this narrow read-only route is its status source.' }) /** * The host's recent log entries โ€” an in-memory ring of the newest few thousand, captured at * DEBUG and above regardless of `RUST_LOG`. Follow live by polling with `after` set to the last * response's `next` cursor; a `dropped: true` means entries were evicted between polls (the ring * wrapped). Bearer-only: logs can reference client identities and host paths, so this is part of * the loopback-only admin surface, never the LAN-readable mTLS one. * @summary Host logs */ export const logsGetQueryAfterMin = 0; export const logsGetQueryLimitMin = 0; export const LogsGetQueryParams = S.Struct({ "after": S.optional(S.Number.pipe(S.greaterThanOrEqualTo(logsGetQueryAfterMin))).annotations({ description: 'Return entries with seq greater than this (omitted\/0 = oldest retained)' }), "limit": S.optional(S.Number.pipe(S.greaterThanOrEqualTo(logsGetQueryLimitMin))).annotations({ description: 'Max entries per response (default and cap 1000)' }) }) export const logsGetResponseEntriesItemSeqMin = 0; export const logsGetResponseEntriesItemTsMsMin = 0; export const logsGetResponseNextMin = 0; export const LogsGetResponse = S.Struct({ "dropped": S.Boolean.annotations({ description: 'True when entries between `after` and the first returned one were already evicted.' }), "entries": S.Array(S.Struct({ "level": S.String.annotations({ description: '`ERROR` | `WARN` | `INFO` | `DEBUG` | `TRACE`.' }), "msg": S.String.annotations({ description: 'The formatted message, structured fields appended as `key=value`.' }), "seq": S.Number.pipe(S.greaterThanOrEqualTo(logsGetResponseEntriesItemSeqMin)).annotations({ description: 'Monotonic sequence number (1-based) โ€” pass the last one back as the `after` cursor.' }), "target": S.String.annotations({ description: 'The emitting module path (tracing target).' }), "ts_ms": S.Number.pipe(S.greaterThanOrEqualTo(logsGetResponseEntriesItemTsMsMin)).annotations({ description: 'Unix timestamp in milliseconds.' }) }).annotations({ description: 'One captured log event.' })), "next": S.Number.pipe(S.greaterThanOrEqualTo(logsGetResponseNextMin)).annotations({ description: 'Cursor for the next poll (the last returned seq, or the request\'s `after` when empty).' }) }).annotations({ description: 'One poll\'s worth of log entries.' }) /** * @summary List native paired clients */ export const ListNativeClientsResponseItem = S.Struct({ "fingerprint": S.String.annotations({ description: 'Hex SHA-256 of the client certificate โ€” its stable id here.' }), "name": S.String.annotations({ description: 'The name the client supplied when pairing.' }) }).annotations({ description: 'A paired native (punktfunk\/1) client.' }) export const ListNativeClientsResponse = S.Array(ListNativeClientsResponseItem) /** * Removes a punktfunk/1 client from the native trust store by fingerprint. * @summary Unpair a native client */ export const UnpairNativeClientParams = S.Struct({ "fingerprint": S.String.annotations({ description: 'Hex SHA-256 of the client certificate (case-insensitive)' }) }) /** * The native (punktfunk/1) pairing window. Poll while armed to show the PIN + countdown. * `enabled: false` means this host runs GameStream only (no `--native`). * @summary Native pairing status */ export const getNativePairingResponseExpiresInSecsMin = 0; export const getNativePairingResponsePairedClientsMin = 0; export const GetNativePairingResponse = S.Struct({ "armed": S.Boolean.annotations({ description: 'True while a pairing window is open.' }), "enabled": S.Boolean.annotations({ description: 'Whether the native host is running (the unified host started with `--native`).' }), "expires_in_secs": S.optional(S.NullOr(S.Number.pipe(S.greaterThanOrEqualTo(getNativePairingResponseExpiresInSecsMin)))).annotations({ description: 'Seconds left in the window (null = disarmed, or armed with no expiry via the CLI flag).' }), "paired_clients": S.Number.pipe(S.greaterThanOrEqualTo(getNativePairingResponsePairedClientsMin)).annotations({ description: 'Number of paired native clients.' }), "pin": S.optional(S.NullOr(S.String)).annotations({ description: 'The PIN to display while armed (null when disarmed).' }) }).annotations({ description: 'Native (punktfunk\/1) pairing status. Unlike GameStream, the \*\*host\*\* mints the PIN (the SPAKE2\nceremony needs it client-side first), so the console \*\*displays\*\* `pin` for the user to enter on\ntheir device โ€” armed on demand for a short window.' }) /** * Opens a pairing window and mints a fresh PIN to display. The user enters it on their device * within `ttl_secs`; the device then appears in the native client list. * @summary Arm native pairing */ export const armNativePairingBodyTtlSecsMin = 0; export const ArmNativePairingBody = S.Struct({ "fingerprint": S.optional(S.NullOr(S.String)).annotations({ description: 'Optional: bind the window to ONE device fingerprint (hex SHA-256, e.g. from a pending knock).\nWhen set, only a pairing attempt from that fingerprint consumes the window โ€” so an unpaired\nLAN peer can neither pair nor burn a window armed for a specific device (security-review #9).\nOmit for an unbound window (any device may use the PIN โ€” trusted-LAN only).' }), "ttl_secs": S.optional(S.NullOr(S.Number.pipe(S.greaterThanOrEqualTo(armNativePairingBodyTtlSecsMin)))).annotations({ description: 'Window length in seconds (default 120; clamped to 15โ€“600).' }) }).annotations({ description: 'Arm-native-pairing request body.' }) export const armNativePairingResponseExpiresInSecsMin = 0; export const armNativePairingResponsePairedClientsMin = 0; export const ArmNativePairingResponse = S.Struct({ "armed": S.Boolean.annotations({ description: 'True while a pairing window is open.' }), "enabled": S.Boolean.annotations({ description: 'Whether the native host is running (the unified host started with `--native`).' }), "expires_in_secs": S.optional(S.NullOr(S.Number.pipe(S.greaterThanOrEqualTo(armNativePairingResponseExpiresInSecsMin)))).annotations({ description: 'Seconds left in the window (null = disarmed, or armed with no expiry via the CLI flag).' }), "paired_clients": S.Number.pipe(S.greaterThanOrEqualTo(armNativePairingResponsePairedClientsMin)).annotations({ description: 'Number of paired native clients.' }), "pin": S.optional(S.NullOr(S.String)).annotations({ description: 'The PIN to display while armed (null when disarmed).' }) }).annotations({ description: 'Native (punktfunk\/1) pairing status. Unlike GameStream, the \*\*host\*\* mints the PIN (the SPAKE2\nceremony needs it client-side first), so the console \*\*displays\*\* `pin` for the user to enter on\ntheir device โ€” armed on demand for a short window.' }) /** * Unpaired devices that tried to connect while the host requires pairing. Approve one to pair * it without a PIN (delegated approval); entries expire after ~10 minutes. * @summary List devices awaiting pairing approval */ export const listPendingDevicesResponseAgeSecsMin = 0; export const listPendingDevicesResponseIdMin = 0; export const ListPendingDevicesResponseItem = S.Struct({ "age_secs": S.Number.pipe(S.greaterThanOrEqualTo(listPendingDevicesResponseAgeSecsMin)).annotations({ description: 'Seconds since the device last knocked.' }), "fingerprint": S.String.annotations({ description: 'Hex SHA-256 of the device\'s certificate โ€” what approval pins.' }), "id": S.Number.pipe(S.greaterThanOrEqualTo(listPendingDevicesResponseIdMin)).annotations({ description: 'Id to address approve\/deny (per-process; entries expire after ~10 minutes).' }), "name": S.String.annotations({ description: 'Best-effort device label (the client\'s own name, else fingerprint-derived).' }) }).annotations({ description: 'An unpaired device that tried to connect while the host requires pairing โ€” awaiting\n\*\*delegated approval\*\* (approve it here instead of fetching the host PIN out of band).' }) export const ListPendingDevicesResponse = S.Array(ListPendingDevicesResponseItem) /** * Pairs the device's certificate fingerprint โ€” it can connect immediately (no PIN). Optionally * relabel it via the body; send `{}` to keep the name it knocked with. * @summary Approve a pending device */ export const approvePendingDevicePathIdMin = 0; export const ApprovePendingDeviceParams = S.Struct({ "id": S.Number.pipe(S.greaterThanOrEqualTo(approvePendingDevicePathIdMin)).annotations({ description: 'Pending-request id from the pending list' }) }) export const ApprovePendingDeviceBody = S.Struct({ "name": S.optional(S.NullOr(S.String)).annotations({ description: 'Operator-chosen label for the device (defaults to the name it knocked with).' }) }).annotations({ description: 'Approve-pending-device request body. Send `{}` to keep the device\'s own name.' }) export const ApprovePendingDeviceResponse = S.Struct({ "fingerprint": S.String.annotations({ description: 'Hex SHA-256 of the client certificate โ€” its stable id here.' }), "name": S.String.annotations({ description: 'The name the client supplied when pairing.' }) }).annotations({ description: 'A paired native (punktfunk\/1) client.' }) /** * Drops the request. Not a blocklist โ€” the device's next attempt knocks again. * @summary Deny a pending device */ export const denyPendingDevicePathIdMin = 0; export const DenyPendingDeviceParams = S.Struct({ "id": S.Number.pipe(S.greaterThanOrEqualTo(denyPendingDevicePathIdMin)).annotations({ description: 'Pending-request id from the pending list' }) }) /** * Poll this to know when to prompt the user for the PIN Moonlight displays. * @summary Pairing-flow status */ export const GetPairingStatusResponse = S.Struct({ "pin_pending": S.Boolean.annotations({ description: 'True while a pairing handshake is parked waiting for the user\'s PIN.' }) }).annotations({ description: 'Pairing-flow status.' }) /** * Delivers the PIN the Moonlight client is displaying, completing the out-of-band half * of the pairing handshake. * @summary Submit the pairing PIN */ export const SubmitPairingPinBody = S.Struct({ "pin": S.String.annotations({ description: '1โ€“16 ASCII digits (Moonlight shows 4).' }) }).annotations({ description: 'The PIN Moonlight displays during pairing.' }) /** * The full sample time-series of the capture currently recording, for live graphing. `404` when * nothing is armed. * @summary Live in-progress capture */ export const statsCaptureLiveResponseMetaDurationMsMin = 0; export const statsCaptureLiveResponseMetaFpsMin = 0; export const statsCaptureLiveResponseMetaHeightMin = 0; export const statsCaptureLiveResponseMetaSampleCountMin = 0; export const statsCaptureLiveResponseMetaStartedUnixMsMin = 0; export const statsCaptureLiveResponseMetaWidthMin = 0; export const statsCaptureLiveResponseSamplesItemBitrateKbpsMin = 0; export const statsCaptureLiveResponseSamplesItemFecRecoveredMin = 0; export const statsCaptureLiveResponseSamplesItemFramesDroppedMin = 0; export const statsCaptureLiveResponseSamplesItemPacketsDroppedMin = 0; export const statsCaptureLiveResponseSamplesItemSendDroppedMin = 0; export const statsCaptureLiveResponseSamplesItemSessionIdMin = 0; export const statsCaptureLiveResponseSamplesItemTMsMin = 0; export const StatsCaptureLiveResponse = S.Struct({ "meta": S.Struct({ "client": S.String.annotations({ description: 'Short label \/ fingerprint prefix, or `\"\"` if unknown.' }), "codec": S.String.annotations({ description: '`\"h264\" | \"hevc\" | \"av1\"`.' }), "duration_ms": S.Number.pipe(S.greaterThanOrEqualTo(statsCaptureLiveResponseMetaDurationMsMin)), "fps": S.Number.pipe(S.greaterThanOrEqualTo(statsCaptureLiveResponseMetaFpsMin)), "height": S.Number.pipe(S.greaterThanOrEqualTo(statsCaptureLiveResponseMetaHeightMin)), "id": S.String.annotations({ description: 'e.g. `\"2026-06-26T20-14-03Z_5120x1440\"` โ€” also the filename stem.' }), "kind": S.String.annotations({ description: '`\"native\" | \"gamestream\"`.' }), "sample_count": S.Number.pipe(S.greaterThanOrEqualTo(statsCaptureLiveResponseMetaSampleCountMin)), "started_unix_ms": S.Number.pipe(S.greaterThanOrEqualTo(statsCaptureLiveResponseMetaStartedUnixMsMin)), "width": S.Number.pipe(S.greaterThanOrEqualTo(statsCaptureLiveResponseMetaWidthMin)) }).annotations({ description: 'Capture summary โ€” the filename stem plus the negotiated mode\/codec\/client. Stored at the head\nof each on-disk recording and listed standalone (without the sample body) by\n[`StatsRecorder::list`].' }), "samples": S.Array(S.Struct({ "bitrate_kbps": S.Number.pipe(S.greaterThanOrEqualTo(statsCaptureLiveResponseSamplesItemBitrateKbpsMin)).annotations({ description: 'Configured target bitrate.' }), "fec_recovered": S.Number.pipe(S.greaterThanOrEqualTo(statsCaptureLiveResponseSamplesItemFecRecoveredMin)).annotations({ description: 'FEC shards recovered this window (delta).' }), "fps": S.Number.annotations({ description: 'Genuine NEW frames\/s from the source.' }), "frames_dropped": S.Number.pipe(S.greaterThanOrEqualTo(statsCaptureLiveResponseSamplesItemFramesDroppedMin)).annotations({ description: 'Frames dropped this window (delta).' }), "mbps": S.Number.annotations({ description: 'Transmit goodput (Mb\/s).' }), "packets_dropped": S.Number.pipe(S.greaterThanOrEqualTo(statsCaptureLiveResponseSamplesItemPacketsDroppedMin)).annotations({ description: 'Packets dropped this window (receiver-side \/ reassembler, where known).' }), "repeat_fps": S.Number.annotations({ description: 'Re-encoded holds\/s (source-starvation indicator).' }), "send_dropped": S.Number.pipe(S.greaterThanOrEqualTo(statsCaptureLiveResponseSamplesItemSendDroppedMin)).annotations({ description: 'Host send-buffer overflow \/ EAGAIN this window (delta).' }), "session_id": S.Number.pipe(S.greaterThanOrEqualTo(statsCaptureLiveResponseSamplesItemSessionIdMin)).annotations({ description: 'Disambiguates concurrent sessions (usually constant).' }), "stages": S.Array(S.Struct({ "name": S.String.annotations({ description: '`\"capture\" | \"submit\" | \"encode\" | \"packetize\" | \"send\"` (path-dependent).' }), "p50_us": S.Number, "p99_us": S.Number }).annotations({ description: 'One pipeline stage\'s latency in an aggregation window (microseconds).' })).annotations({ description: 'Ordered pipeline stages for this path.' }), "t_ms": S.Number.pipe(S.greaterThanOrEqualTo(statsCaptureLiveResponseSamplesItemTMsMin)).annotations({ description: 'Milliseconds since capture start (monotonic; stamped by [`StatsRecorder::push_sample`]).' }) }).annotations({ description: 'One aggregated sample (~ every 2 s native, ~ every 1 s GameStream).' })) }).annotations({ description: 'A full capture: summary + the sample time-series. The wire + on-disk shape.' }) /** * Arms a new performance-stats capture. Idempotent: if a capture is already running this returns * the current status unchanged. While armed, the streaming loops emit aggregated samples (~ every * 1โ€“2 s) into the in-progress capture, readable live via `GET /stats/capture/live`. * @summary Start a stats capture */ export const statsCaptureStartResponseElapsedMsMin = 0; export const statsCaptureStartResponseSampleCountMin = 0; export const statsCaptureStartResponseStartedUnixMsMin = 0; export const StatsCaptureStartResponse = S.Struct({ "armed": S.Boolean.annotations({ description: 'Capture currently running.' }), "elapsed_ms": S.Number.pipe(S.greaterThanOrEqualTo(statsCaptureStartResponseElapsedMsMin)).annotations({ description: 'Host-measured elapsed time of the in-progress capture, in ms (`0` if idle). Computed from the\nhost\'s MONOTONIC clock, so a console can show elapsed time without subtracting `started_unix_ms`\nfrom its own (possibly skewed) wall clock.' }), "kind": S.String.annotations({ description: 'Path of the in-progress capture (`\"\"` if idle).' }), "sample_count": S.Number.pipe(S.greaterThanOrEqualTo(statsCaptureStartResponseSampleCountMin)).annotations({ description: 'Samples in the in-progress capture.' }), "started_unix_ms": S.Number.pipe(S.greaterThanOrEqualTo(statsCaptureStartResponseStartedUnixMsMin)).annotations({ description: 'Unix start time of the in-progress capture (`0` if idle).' }) }).annotations({ description: 'Snapshot of the in-progress capture for the management API.' }) /** * Whether a capture is armed, its sample count, and start time. Poll this (e.g. every 2 s) to * drive the capture-control UI. * @summary Stats capture status */ export const statsCaptureStatusResponseElapsedMsMin = 0; export const statsCaptureStatusResponseSampleCountMin = 0; export const statsCaptureStatusResponseStartedUnixMsMin = 0; export const StatsCaptureStatusResponse = S.Struct({ "armed": S.Boolean.annotations({ description: 'Capture currently running.' }), "elapsed_ms": S.Number.pipe(S.greaterThanOrEqualTo(statsCaptureStatusResponseElapsedMsMin)).annotations({ description: 'Host-measured elapsed time of the in-progress capture, in ms (`0` if idle). Computed from the\nhost\'s MONOTONIC clock, so a console can show elapsed time without subtracting `started_unix_ms`\nfrom its own (possibly skewed) wall clock.' }), "kind": S.String.annotations({ description: 'Path of the in-progress capture (`\"\"` if idle).' }), "sample_count": S.Number.pipe(S.greaterThanOrEqualTo(statsCaptureStatusResponseSampleCountMin)).annotations({ description: 'Samples in the in-progress capture.' }), "started_unix_ms": S.Number.pipe(S.greaterThanOrEqualTo(statsCaptureStatusResponseStartedUnixMsMin)).annotations({ description: 'Unix start time of the in-progress capture (`0` if idle).' }) }).annotations({ description: 'Snapshot of the in-progress capture for the management API.' }) /** * Disarms the in-progress capture and writes it to disk atomically, returning its summary. If * nothing was recording, returns `204 No Content`. * @summary Stop the stats capture */ export const statsCaptureStopResponseDurationMsMin = 0; export const statsCaptureStopResponseFpsMin = 0; export const statsCaptureStopResponseHeightMin = 0; export const statsCaptureStopResponseSampleCountMin = 0; export const statsCaptureStopResponseStartedUnixMsMin = 0; export const statsCaptureStopResponseWidthMin = 0; export const StatsCaptureStopResponse = S.Struct({ "client": S.String.annotations({ description: 'Short label \/ fingerprint prefix, or `\"\"` if unknown.' }), "codec": S.String.annotations({ description: '`\"h264\" | \"hevc\" | \"av1\"`.' }), "duration_ms": S.Number.pipe(S.greaterThanOrEqualTo(statsCaptureStopResponseDurationMsMin)), "fps": S.Number.pipe(S.greaterThanOrEqualTo(statsCaptureStopResponseFpsMin)), "height": S.Number.pipe(S.greaterThanOrEqualTo(statsCaptureStopResponseHeightMin)), "id": S.String.annotations({ description: 'e.g. `\"2026-06-26T20-14-03Z_5120x1440\"` โ€” also the filename stem.' }), "kind": S.String.annotations({ description: '`\"native\" | \"gamestream\"`.' }), "sample_count": S.Number.pipe(S.greaterThanOrEqualTo(statsCaptureStopResponseSampleCountMin)), "started_unix_ms": S.Number.pipe(S.greaterThanOrEqualTo(statsCaptureStopResponseStartedUnixMsMin)), "width": S.Number.pipe(S.greaterThanOrEqualTo(statsCaptureStopResponseWidthMin)) }).annotations({ description: 'Capture summary โ€” the filename stem plus the negotiated mode\/codec\/client. Stored at the head\nof each on-disk recording and listed standalone (without the sample body) by\n[`StatsRecorder::list`].' }) /** * Every saved capture's summary (the `meta` head only โ€” not the sample body), newest first. * @summary List saved recordings */ export const statsRecordingsListResponseDurationMsMin = 0; export const statsRecordingsListResponseFpsMin = 0; export const statsRecordingsListResponseHeightMin = 0; export const statsRecordingsListResponseSampleCountMin = 0; export const statsRecordingsListResponseStartedUnixMsMin = 0; export const statsRecordingsListResponseWidthMin = 0; export const StatsRecordingsListResponseItem = S.Struct({ "client": S.String.annotations({ description: 'Short label \/ fingerprint prefix, or `\"\"` if unknown.' }), "codec": S.String.annotations({ description: '`\"h264\" | \"hevc\" | \"av1\"`.' }), "duration_ms": S.Number.pipe(S.greaterThanOrEqualTo(statsRecordingsListResponseDurationMsMin)), "fps": S.Number.pipe(S.greaterThanOrEqualTo(statsRecordingsListResponseFpsMin)), "height": S.Number.pipe(S.greaterThanOrEqualTo(statsRecordingsListResponseHeightMin)), "id": S.String.annotations({ description: 'e.g. `\"2026-06-26T20-14-03Z_5120x1440\"` โ€” also the filename stem.' }), "kind": S.String.annotations({ description: '`\"native\" | \"gamestream\"`.' }), "sample_count": S.Number.pipe(S.greaterThanOrEqualTo(statsRecordingsListResponseSampleCountMin)), "started_unix_ms": S.Number.pipe(S.greaterThanOrEqualTo(statsRecordingsListResponseStartedUnixMsMin)), "width": S.Number.pipe(S.greaterThanOrEqualTo(statsRecordingsListResponseWidthMin)) }).annotations({ description: 'Capture summary โ€” the filename stem plus the negotiated mode\/codec\/client. Stored at the head\nof each on-disk recording and listed standalone (without the sample body) by\n[`StatsRecorder::list`].' }) export const StatsRecordingsListResponse = S.Array(StatsRecordingsListResponseItem) /** * The full capture (meta + samples) for `id`, for graphing or download. * @summary Get a saved recording */ export const StatsRecordingGetParams = S.Struct({ "id": S.String.annotations({ description: 'The recording id (its filename stem)' }) }) export const statsRecordingGetResponseMetaDurationMsMin = 0; export const statsRecordingGetResponseMetaFpsMin = 0; export const statsRecordingGetResponseMetaHeightMin = 0; export const statsRecordingGetResponseMetaSampleCountMin = 0; export const statsRecordingGetResponseMetaStartedUnixMsMin = 0; export const statsRecordingGetResponseMetaWidthMin = 0; export const statsRecordingGetResponseSamplesItemBitrateKbpsMin = 0; export const statsRecordingGetResponseSamplesItemFecRecoveredMin = 0; export const statsRecordingGetResponseSamplesItemFramesDroppedMin = 0; export const statsRecordingGetResponseSamplesItemPacketsDroppedMin = 0; export const statsRecordingGetResponseSamplesItemSendDroppedMin = 0; export const statsRecordingGetResponseSamplesItemSessionIdMin = 0; export const statsRecordingGetResponseSamplesItemTMsMin = 0; export const StatsRecordingGetResponse = S.Struct({ "meta": S.Struct({ "client": S.String.annotations({ description: 'Short label \/ fingerprint prefix, or `\"\"` if unknown.' }), "codec": S.String.annotations({ description: '`\"h264\" | \"hevc\" | \"av1\"`.' }), "duration_ms": S.Number.pipe(S.greaterThanOrEqualTo(statsRecordingGetResponseMetaDurationMsMin)), "fps": S.Number.pipe(S.greaterThanOrEqualTo(statsRecordingGetResponseMetaFpsMin)), "height": S.Number.pipe(S.greaterThanOrEqualTo(statsRecordingGetResponseMetaHeightMin)), "id": S.String.annotations({ description: 'e.g. `\"2026-06-26T20-14-03Z_5120x1440\"` โ€” also the filename stem.' }), "kind": S.String.annotations({ description: '`\"native\" | \"gamestream\"`.' }), "sample_count": S.Number.pipe(S.greaterThanOrEqualTo(statsRecordingGetResponseMetaSampleCountMin)), "started_unix_ms": S.Number.pipe(S.greaterThanOrEqualTo(statsRecordingGetResponseMetaStartedUnixMsMin)), "width": S.Number.pipe(S.greaterThanOrEqualTo(statsRecordingGetResponseMetaWidthMin)) }).annotations({ description: 'Capture summary โ€” the filename stem plus the negotiated mode\/codec\/client. Stored at the head\nof each on-disk recording and listed standalone (without the sample body) by\n[`StatsRecorder::list`].' }), "samples": S.Array(S.Struct({ "bitrate_kbps": S.Number.pipe(S.greaterThanOrEqualTo(statsRecordingGetResponseSamplesItemBitrateKbpsMin)).annotations({ description: 'Configured target bitrate.' }), "fec_recovered": S.Number.pipe(S.greaterThanOrEqualTo(statsRecordingGetResponseSamplesItemFecRecoveredMin)).annotations({ description: 'FEC shards recovered this window (delta).' }), "fps": S.Number.annotations({ description: 'Genuine NEW frames\/s from the source.' }), "frames_dropped": S.Number.pipe(S.greaterThanOrEqualTo(statsRecordingGetResponseSamplesItemFramesDroppedMin)).annotations({ description: 'Frames dropped this window (delta).' }), "mbps": S.Number.annotations({ description: 'Transmit goodput (Mb\/s).' }), "packets_dropped": S.Number.pipe(S.greaterThanOrEqualTo(statsRecordingGetResponseSamplesItemPacketsDroppedMin)).annotations({ description: 'Packets dropped this window (receiver-side \/ reassembler, where known).' }), "repeat_fps": S.Number.annotations({ description: 'Re-encoded holds\/s (source-starvation indicator).' }), "send_dropped": S.Number.pipe(S.greaterThanOrEqualTo(statsRecordingGetResponseSamplesItemSendDroppedMin)).annotations({ description: 'Host send-buffer overflow \/ EAGAIN this window (delta).' }), "session_id": S.Number.pipe(S.greaterThanOrEqualTo(statsRecordingGetResponseSamplesItemSessionIdMin)).annotations({ description: 'Disambiguates concurrent sessions (usually constant).' }), "stages": S.Array(S.Struct({ "name": S.String.annotations({ description: '`\"capture\" | \"submit\" | \"encode\" | \"packetize\" | \"send\"` (path-dependent).' }), "p50_us": S.Number, "p99_us": S.Number }).annotations({ description: 'One pipeline stage\'s latency in an aggregation window (microseconds).' })).annotations({ description: 'Ordered pipeline stages for this path.' }), "t_ms": S.Number.pipe(S.greaterThanOrEqualTo(statsRecordingGetResponseSamplesItemTMsMin)).annotations({ description: 'Milliseconds since capture start (monotonic; stamped by [`StatsRecorder::push_sample`]).' }) }).annotations({ description: 'One aggregated sample (~ every 2 s native, ~ every 1 s GameStream).' })) }).annotations({ description: 'A full capture: summary + the sample time-series. The wire + on-disk shape.' }) /** * Removes the recording `id` from disk. `404` if there is no such recording. * @summary Delete a saved recording */ export const StatsRecordingDeleteParams = S.Struct({ "id": S.String.annotations({ description: 'The recording id (its filename stem)' }) }) /** * @summary Live host status */ export const getStatusResponseActiveSessionsMin = 0; export const getStatusResponsePairedClientsMin = 0; export const getStatusResponseSessionTwoFpsMin = 0; export const getStatusResponseSessionTwoHeightMin = 0; export const getStatusResponseSessionTwoWidthMin = 0; export const getStatusResponseStreamTwoBitrateKbpsMin = 0; export const getStatusResponseStreamTwoFpsMin = 0; export const getStatusResponseStreamTwoHeightMin = 0; export const getStatusResponseStreamTwoMinFecMin = 0; export const getStatusResponseStreamTwoPacketSizeMin = 0; export const getStatusResponseStreamTwoWidthMin = 0; export const GetStatusResponse = S.Struct({ "active_sessions": S.Number.pipe(S.greaterThanOrEqualTo(getStatusResponseActiveSessionsMin)).annotations({ description: 'Number of live streaming sessions across BOTH planes (GameStream + native punktfunk\/1). The\nnative server admits concurrent sessions, so this can exceed 1; `session`\/`stream` below\ndescribe a single representative session for the detail card.' }), "audio_streaming": S.Boolean.annotations({ description: 'True while the audio stream thread is running.' }), "paired_clients": S.Number.pipe(S.greaterThanOrEqualTo(getStatusResponsePairedClientsMin)).annotations({ description: 'Number of pinned (paired) client certificates.' }), "pin_pending": S.Boolean.annotations({ description: 'True while a pairing handshake is parked waiting for the user\'s PIN\n(submit it via `POST \/api\/v1\/pair\/pin`).' }), "session": S.optional(S.Union(S.Null, S.Struct({ "fps": S.Number.pipe(S.greaterThanOrEqualTo(getStatusResponseSessionTwoFpsMin)), "height": S.Number.pipe(S.greaterThanOrEqualTo(getStatusResponseSessionTwoHeightMin)), "width": S.Number.pipe(S.greaterThanOrEqualTo(getStatusResponseSessionTwoWidthMin)) }).annotations({ description: 'A representative active session. GameStream\'s launch (Moonlight `\/launch`) when present, else\nthe first live native session. `null` when nothing is streaming.' }))), "stream": S.optional(S.Union(S.Null, S.Struct({ "bitrate_kbps": S.Number.pipe(S.greaterThanOrEqualTo(getStatusResponseStreamTwoBitrateKbpsMin)), "codec": S.Literal('h264', 'hevc', 'av1', 'pyrowave').annotations({ description: 'Video codec identifier. The wire token matches the codec\'s canonical name used across the\nstack (SDP\/GameStream advertisement, the stats-capture `CaptureMeta.codec`, and the encoder\'s\n[`Codec::label`]) โ€” notably `H.265` serializes as `\"hevc\"`, not `\"h265\"`, so the same codec\nreads identically on every console page.' }), "fps": S.Number.pipe(S.greaterThanOrEqualTo(getStatusResponseStreamTwoFpsMin)), "height": S.Number.pipe(S.greaterThanOrEqualTo(getStatusResponseStreamTwoHeightMin)), "min_fec": S.Number.pipe(S.greaterThanOrEqualTo(getStatusResponseStreamTwoMinFecMin)).annotations({ description: 'Client\'s parity floor per FEC block (`minRequiredFecPackets`).' }), "packet_size": S.Number.pipe(S.greaterThanOrEqualTo(getStatusResponseStreamTwoPacketSizeMin)).annotations({ description: 'Video payload size per packet (bytes).' }), "width": S.Number.pipe(S.greaterThanOrEqualTo(getStatusResponseStreamTwoWidthMin)) }).annotations({ description: 'The active stream\'s parameters โ€” RTSP-negotiated for GameStream, or the live native session\'s\nmode\/codec\/bitrate. `null` when nothing is streaming.' }))), "video_streaming": S.Boolean.annotations({ description: 'True while the video stream thread is running.' }) }).annotations({ description: 'Live host status (changes as clients launch\/end sessions).' })