feat(tray): surface kept virtual displays in the tray tooltip
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) <noreply@anthropic.com>
This commit is contained in:
+8
-1
@@ -2671,13 +2671,20 @@
|
|||||||
"paired_clients",
|
"paired_clients",
|
||||||
"native_paired_clients",
|
"native_paired_clients",
|
||||||
"pin_pending",
|
"pin_pending",
|
||||||
"pending_approvals"
|
"pending_approvals",
|
||||||
|
"kept_displays"
|
||||||
],
|
],
|
||||||
"properties": {
|
"properties": {
|
||||||
"audio_streaming": {
|
"audio_streaming": {
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
"description": "True while the audio stream thread is running."
|
"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": {
|
"native_paired_clients": {
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
"format": "int32",
|
"format": "int32",
|
||||||
|
|||||||
@@ -382,6 +382,10 @@ struct LocalSummary {
|
|||||||
pin_pending: bool,
|
pin_pending: bool,
|
||||||
/// Native pairing knocks awaiting the operator's approval (count only).
|
/// Native pairing knocks awaiting the operator's approval (count only).
|
||||||
pending_approvals: u32,
|
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.
|
/// A paired (certificate-pinned) Moonlight client.
|
||||||
@@ -1330,6 +1334,11 @@ async fn get_local_summary(State(st): State<Arc<MgmtState>>) -> Json<LocalSummar
|
|||||||
native_paired_clients,
|
native_paired_clients,
|
||||||
pin_pending: st.app.pairing.pin.awaiting_pin(),
|
pin_pending: st.app.pairing.pin.awaiting_pin(),
|
||||||
pending_approvals,
|
pending_approvals,
|
||||||
|
kept_displays: crate::vdisplay::registry::snapshot()
|
||||||
|
.displays
|
||||||
|
.iter()
|
||||||
|
.filter(|d| d.state == "lingering" || d.state == "pinned")
|
||||||
|
.count() as u32,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -33,6 +33,10 @@ pub struct Summary {
|
|||||||
pub native_paired_clients: u32,
|
pub native_paired_clients: u32,
|
||||||
pub pin_pending: bool,
|
pub pin_pending: bool,
|
||||||
pub pending_approvals: u32,
|
pub pending_approvals: u32,
|
||||||
|
/// Virtual displays kept with no live session (lingering/pinned). `#[serde(default)]` so an older
|
||||||
|
/// host that doesn't send it deserializes as 0.
|
||||||
|
#[serde(default)]
|
||||||
|
pub kept_displays: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, serde::Deserialize)]
|
#[derive(Clone, Copy, Debug, PartialEq, serde::Deserialize)]
|
||||||
@@ -71,6 +75,14 @@ impl TrayStatus {
|
|||||||
s.version, sess.width, sess.height, sess.fps
|
s.version, sess.width, sess.height, sess.fps
|
||||||
),
|
),
|
||||||
(_, true) => format!("punktfunk host {} — streaming", s.version),
|
(_, true) => 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),
|
_ => format!("punktfunk host {} — idle", s.version),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -432,6 +444,7 @@ mod tests {
|
|||||||
native_paired_clients: 2,
|
native_paired_clients: 2,
|
||||||
pin_pending: false,
|
pin_pending: false,
|
||||||
pending_approvals: 0,
|
pending_approvals: 0,
|
||||||
|
kept_displays: 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user