fix(tray): count native sessions as streaming, and stop hiding "Open web console"
apple / swift (push) Successful in 1m30s
ci / web (push) Successful in 1m1s
windows-host / package (push) Failing after 4m40s
ci / docs-site (push) Successful in 1m8s
ci / bench (push) Successful in 5m31s
decky / build-publish (push) Successful in 29s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 11s
arch / build-publish (push) Successful in 11m30s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 11s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 41s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 11s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 9s
android / android (push) Successful in 15m55s
docker / build-push (ci, ci/rust-ci-noble.Dockerfile, punktfunk-rust-ci-noble) (push) Successful in 5m36s
deb / build-publish (push) Successful in 9m54s
deb / build-publish-host (push) Successful in 9m42s
ci / rust (push) Failing after 21m48s
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Failing after 7m45s
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Failing after 9m57s
apple / screenshots (push) Successful in 22m35s
docker / deploy-docs (push) Successful in 25s
apple / swift (push) Successful in 1m30s
ci / web (push) Successful in 1m1s
windows-host / package (push) Failing after 4m40s
ci / docs-site (push) Successful in 1m8s
ci / bench (push) Successful in 5m31s
decky / build-publish (push) Successful in 29s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 11s
arch / build-publish (push) Successful in 11m30s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 11s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 41s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 11s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 9s
android / android (push) Successful in 15m55s
docker / build-push (ci, ci/rust-ci-noble.Dockerfile, punktfunk-rust-ci-noble) (push) Successful in 5m36s
deb / build-publish (push) Successful in 9m54s
deb / build-publish-host (push) Successful in 9m42s
ci / rust (push) Failing after 21m48s
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Failing after 7m45s
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Failing after 9m57s
apple / screenshots (push) Successful in 22m35s
docker / deploy-docs (push) Successful in 25s
The tray sat at "Idle" through an entire stream, and its Windows context menu could come up without its main action. Two separate causes. `GET /api/v1/local/summary` reported `video_streaming`/`audio_streaming` straight from `AppState.streaming` — flags only the GameStream media pipeline raises. The native punktfunk/1 plane is the DEFAULT (GameStream is opt-in) and never touches them, so every native session read as idle: idle icon, and a tooltip that printed that session's own resolution next to the word "idle" (the `session` field was already native-aware). This is the blind spot `/status` was fixed for — see the `session_status` module doc — which the summary kept. Both flags now OR in a live native session, and the tray additionally treats a reported session as streaming, so a new tray reads an older host correctly too. The menu built "Open web console" (and the pairing entry) only while a live loopback probe of the console had just succeeded. A console still starting, or an SSR render slower than the 2 s probe timeout, therefore deleted the tray's most-wanted action outright — indistinguishable from a tray that never had one, with left-clicking the icon as the only, undiscoverable, way in. The entry is now always present and the default item; a failed probe changes its label to "(not responding)" instead of hiding it, and takes two consecutive misses to say so, since one timeout is not "down". While here: a "Release kept display…" entry when displays are held (the summary field's own doc promised a one-click release that did not exist), and entries deep-link to /pairing and /displays instead of all landing on the dashboard. The Linux SNI menu mirrors all of it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -286,11 +286,74 @@ async fn health_is_open_and_versioned() {
|
||||
assert_eq!(body["abi_version"], punktfunk_core::ABI_VERSION);
|
||||
}
|
||||
|
||||
/// Serializes the tests that read (or write) the process-global live-session registry
|
||||
/// ([`crate::session_status`]): a session registered by one test would otherwise make a
|
||||
/// concurrently running one see a stream it never started.
|
||||
static SESSION_REGISTRY_LOCK: tokio::sync::Mutex<()> = tokio::sync::Mutex::const_new(());
|
||||
|
||||
/// A `/local/summary` request from a loopback peer (the tray's own).
|
||||
fn summary_req() -> axum::http::Request<Body> {
|
||||
let mut req = get_req("/api/v1/local/summary");
|
||||
req.extensions_mut()
|
||||
.insert(PeerAddr("127.0.0.1:40000".parse().unwrap()));
|
||||
req
|
||||
}
|
||||
|
||||
/// Registers a stand-in live native session; the returned guard removes it on drop.
|
||||
fn fake_native_session(
|
||||
width: u32,
|
||||
height: u32,
|
||||
fps: u32,
|
||||
) -> crate::session_status::LiveSessionGuard {
|
||||
let packed = ((width as u64) << 32) | ((height as u64) << 16) | fps as u64;
|
||||
crate::session_status::register(
|
||||
Arc::new(std::sync::atomic::AtomicU64::new(packed)),
|
||||
Arc::new(std::sync::atomic::AtomicU32::new(20_000)),
|
||||
Codec::H265,
|
||||
Arc::new(std::sync::atomic::AtomicBool::new(false)),
|
||||
Arc::new(std::sync::atomic::AtomicBool::new(false)),
|
||||
"test-client".into(),
|
||||
false,
|
||||
Arc::new(std::sync::atomic::AtomicU32::new(0)),
|
||||
Arc::new(std::sync::atomic::AtomicU32::new(0)),
|
||||
)
|
||||
}
|
||||
|
||||
/// A native (punktfunk/1) session — the DEFAULT plane — must read as streaming in the tray's
|
||||
/// summary. The GameStream `streaming` flag stays false throughout such a session, and reading it
|
||||
/// alone left the tray showing "idle" (with the idle icon) for the whole stream: exactly the blind
|
||||
/// spot `/status` was fixed for in [`crate::session_status`], which `/local/summary` still had.
|
||||
#[tokio::test]
|
||||
async fn local_summary_reports_a_native_session_as_streaming() {
|
||||
let _serial = SESSION_REGISTRY_LOCK.lock().await;
|
||||
let app = test_app(test_state(), None);
|
||||
|
||||
let (status, body) = send(&app, summary_req()).await;
|
||||
assert_eq!(status, StatusCode::OK);
|
||||
assert_eq!(body["video_streaming"], false);
|
||||
assert_eq!(body["session"], serde_json::Value::Null);
|
||||
|
||||
let session = fake_native_session(3840, 2160, 120);
|
||||
let (_, body) = send(&app, summary_req()).await;
|
||||
assert_eq!(body["video_streaming"], true, "native session: {body}");
|
||||
assert_eq!(body["audio_streaming"], true, "native session: {body}");
|
||||
assert_eq!(body["session"]["width"], 3840);
|
||||
assert_eq!(body["session"]["height"], 2160);
|
||||
assert_eq!(body["session"]["fps"], 120);
|
||||
|
||||
// Session over → back to idle.
|
||||
drop(session);
|
||||
let (_, body) = send(&app, summary_req()).await;
|
||||
assert_eq!(body["video_streaming"], false);
|
||||
assert_eq!(body["session"], serde_json::Value::Null);
|
||||
}
|
||||
|
||||
/// The tray's `/local/summary` is unauthenticated for LOOPBACK peers only — a LAN peer is
|
||||
/// rejected even though the route needs no bearer token, and the body never carries secret
|
||||
/// material (no PIN values, no fingerprints, no device names — counts/booleans only).
|
||||
#[tokio::test]
|
||||
async fn local_summary_is_loopback_only_and_non_sensitive() {
|
||||
let _serial = SESSION_REGISTRY_LOCK.lock().await;
|
||||
let np = Arc::new(
|
||||
crate::native_pairing::NativePairing::load_with(
|
||||
Some(std::env::temp_dir().join(format!("pf-mgmt-summary-{}.json", std::process::id()))),
|
||||
@@ -600,6 +663,7 @@ async fn compositors_lists_all_backends_with_flags() {
|
||||
|
||||
#[tokio::test]
|
||||
async fn status_reflects_runtime_state() {
|
||||
let _serial = SESSION_REGISTRY_LOCK.lock().await;
|
||||
let state = test_state();
|
||||
let app = test_app(state.clone(), None);
|
||||
|
||||
@@ -756,6 +820,8 @@ async fn stop_session_clears_runtime_state() {
|
||||
|
||||
#[tokio::test]
|
||||
async fn idr_requires_an_active_stream() {
|
||||
// A live native session (registered by a sibling test) is an active stream to this route.
|
||||
let _serial = SESSION_REGISTRY_LOCK.lock().await;
|
||||
let state = test_state();
|
||||
let app = test_app(state.clone(), None);
|
||||
let post = || {
|
||||
|
||||
Reference in New Issue
Block a user