From 69f4c987f6916c9a2cf8f174d1ddd709fd4210e2 Mon Sep 17 00:00:00 2001 From: enricobuehler Date: Sun, 5 Jul 2026 17:54:44 +0000 Subject: [PATCH] feat(tray): surface kept virtual displays in the tray tooltip MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Stage 8 polish. `GET /api/v1/local/summary` (the tray's loopback-only unauthenticated status source) gains `kept_displays` — the count of lingering/pinned virtual displays (held with no live session), over the already-validated `registry::snapshot()`. The tray shows it in the idle tooltip ("idle · 1 display kept"), so a user knows a display — and, under exclusive topology, their physical monitors — is being held (e.g. a gaming-rig `forever` pin). Release stays via the console: a state-changing release can't be an unauthenticated endpoint, and the non-elevated Windows tray can't read the SYSTEM-DACL'd mgmt token, so a tray release button isn't cleanly cross-platform. `#[serde(default)]` on the tray side keeps it compatible with an older host. Tray tests green. Co-Authored-By: Claude Opus 4.8 (1M context) --- api/openapi.json | 9 ++++++++- crates/punktfunk-host/src/mgmt.rs | 9 +++++++++ crates/punktfunk-tray/src/status.rs | 13 +++++++++++++ 3 files changed, 30 insertions(+), 1 deletion(-) diff --git a/api/openapi.json b/api/openapi.json index 4bc886f..0abb080 100644 --- a/api/openapi.json +++ b/api/openapi.json @@ -2671,13 +2671,20 @@ "paired_clients", "native_paired_clients", "pin_pending", - "pending_approvals" + "pending_approvals", + "kept_displays" ], "properties": { "audio_streaming": { "type": "boolean", "description": "True while the audio stream thread is running." }, + "kept_displays": { + "type": "integer", + "format": "int32", + "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.", + "minimum": 0 + }, "native_paired_clients": { "type": "integer", "format": "int32", diff --git a/crates/punktfunk-host/src/mgmt.rs b/crates/punktfunk-host/src/mgmt.rs index 00f4b4f..7002849 100644 --- a/crates/punktfunk-host/src/mgmt.rs +++ b/crates/punktfunk-host/src/mgmt.rs @@ -382,6 +382,10 @@ struct LocalSummary { pin_pending: bool, /// Native pairing knocks awaiting the operator's approval (count only). pending_approvals: u32, + /// Virtual displays being KEPT with no live session — lingering (keep-alive window) or pinned + /// (`keep_alive: forever`). Non-zero means a display (and, exclusive, your physical monitors) is + /// held; the tray surfaces it + a one-click release. Active (in-use) displays are not counted. + kept_displays: u32, } /// A paired (certificate-pinned) Moonlight client. @@ -1330,6 +1334,11 @@ async fn get_local_summary(State(st): State>) -> Json format!("punktfunk host {} — streaming", s.version), + // Idle, but surface a kept (lingering/pinned) display: it — and, under an exclusive + // topology, your physical monitors — is being held. Release it from the console. + _ if s.kept_displays > 0 => format!( + "punktfunk host {} — idle · {} display{} kept", + s.version, + s.kept_displays, + if s.kept_displays == 1 { "" } else { "s" } + ), _ => format!("punktfunk host {} — idle", s.version), }, } @@ -432,6 +444,7 @@ mod tests { native_paired_clients: 2, pin_pending: false, pending_approvals: 0, + kept_displays: 0, } }