ef5808254a
The real Windows client is the spawned punktfunk-session Vulkan binary (pf-client-core); the in-process builtin GUI stream — reachable only via PUNKTFUNK_BUILTIN_STREAM=1 — was dead weight kept alive by nothing and a recurring source of wasted effort. Remove it: delete present/render/input/ audio.rs and the builtin remainder of session/video.rs, rip all the builtin wiring (app/mod, connect, stream), and make connect always spawn. Preserve the two shipped keepers that happened to live in those files by relocating them to a new probe.rs: run_speed_probe (the per-host network speed test used by the Settings speed page and --headless --speed-test) and decodable_codecs (the codec-capability advert on the probe connect). Trim gpu.rs to just the Settings adapter picker (adapter_names + helpers). --headless now supports only --speed-test — the in-process decode/frame-counter went with the pump. Drops the now-orphaned deps opus, wasapi, crossbeam-channel, anyhow; keeps ffmpeg-next (probe::decodable_codecs still needs it). Net 4432 deletions. Statically verified (module wiring, imports, orphaned symbols/deps all clean); the type-level compile runs on the windows-amd64 CI runner, which has the toolchain this non-Windows host lacks. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
137 lines
5.6 KiB
Rust
137 lines
5.6 KiB
Rust
//! The stream status page: streams run in the spawned `punktfunk-session` child's own window,
|
|
//! so the shell shows a status card in the app's card language — host header, the child's live
|
|
//! `stats:` line as a chip row + stage lines, the in-window shortcuts, and a Disconnect.
|
|
|
|
use super::style::{edges, uniform};
|
|
use std::sync::Arc;
|
|
use windows_reactor::*;
|
|
|
|
/// One HUD refresh: the session child's latest formatted `stats:` line, mirrored into root state
|
|
/// by the poll thread (`pf-hud`) and passed down as a prop.
|
|
#[derive(Clone, Default, PartialEq)]
|
|
pub(crate) struct HudSample {
|
|
/// The session child's latest formatted `stats:` line, for the status page. Empty before the
|
|
/// child's first stats window.
|
|
pub(crate) stats_line: String,
|
|
}
|
|
|
|
/// Spawn mode's Stream screen: the stream runs in the punktfunk-session child's own
|
|
/// window, so the shell shows a status card in the app's card language — monogram +
|
|
/// host header, the child's live `stats:` line as a chip row + stage lines, the
|
|
/// in-window shortcuts, and a Disconnect that kills the child (its exit event routes
|
|
/// the app back to the host list, same as the child's window closing). No hooks.
|
|
pub(crate) fn session_page(ctx: &Arc<super::AppCtx>, hud: &HudSample) -> Element {
|
|
use super::style::{avatar, card, pill, Pill};
|
|
let host = ctx.shared.target.lock().unwrap().name.clone();
|
|
let browse = ctx.shared.browse.load(std::sync::atomic::Ordering::SeqCst);
|
|
let title = match (browse, host.is_empty()) {
|
|
(true, true) => "Console library".to_string(),
|
|
(true, false) => format!("Console library \u{00B7} {host}"),
|
|
(false, true) => "Streaming".to_string(),
|
|
(false, false) => format!("Streaming to {host}"),
|
|
};
|
|
|
|
// Header: monogram + title + the one thing worth knowing (where the video went).
|
|
let header: Element = grid((
|
|
avatar(&host)
|
|
.grid_column(0)
|
|
.vertical_alignment(VerticalAlignment::Center),
|
|
vstack((
|
|
text_block(&title).font_size(18.0).semibold(),
|
|
text_block(
|
|
"The stream has its own window \u{2014} this one returns when the session ends.",
|
|
)
|
|
.font_size(12.0)
|
|
.foreground(ThemeRef::SecondaryText),
|
|
))
|
|
.spacing(2.0)
|
|
.grid_column(1)
|
|
.vertical_alignment(VerticalAlignment::Center)
|
|
.margin(edges(12.0, 0.0, 0.0, 0.0)),
|
|
))
|
|
.columns([GridLength::Auto, GridLength::Star(1.0)])
|
|
.into();
|
|
|
|
// The child prints one formatted stats line per 1 s window:
|
|
// "<mode> · <fps> · <Mb/s> · <path> [· HDR] | e2e … | …" — the first segment becomes
|
|
// a chip row (the decode path gets the status colour), the rest dim stage lines.
|
|
let mut body: Vec<Element> = vec![header];
|
|
if hud.stats_line.is_empty() {
|
|
// The child prints `stats:` lines only while its stats view is on (the Settings
|
|
// toggle / Ctrl+Alt+Shift+S) — say so instead of waiting forever. Browse idles in
|
|
// the library between launches, so no stats there is simply normal: no line.
|
|
if !browse {
|
|
let msg = if ctx.settings.lock().unwrap().show_stats {
|
|
"Waiting for the first stats window\u{2026}"
|
|
} else {
|
|
"Stats are off \u{2014} Ctrl+Alt+Shift+S in the stream window turns them on."
|
|
};
|
|
body.push(
|
|
text_block(msg)
|
|
.font_size(11.0)
|
|
.foreground(ThemeRef::SecondaryText)
|
|
.into(),
|
|
);
|
|
}
|
|
} else {
|
|
let mut segments = hud.stats_line.split(" | ");
|
|
if let Some(first) = segments.next() {
|
|
let chips: Vec<Element> = first
|
|
.split(" \u{00B7} ")
|
|
.map(str::trim)
|
|
.filter(|c| !c.is_empty())
|
|
.map(|c| {
|
|
let kind = match c {
|
|
"vulkan" | "vaapi" => Pill::Good,
|
|
"software" => Pill::Info,
|
|
_ => Pill::Neutral,
|
|
};
|
|
pill(c, kind).into()
|
|
})
|
|
.collect();
|
|
body.push(hstack(chips).spacing(6.0).into());
|
|
}
|
|
for seg in segments {
|
|
body.push(
|
|
text_block(seg.trim())
|
|
.font_size(11.0)
|
|
.foreground(ThemeRef::SecondaryText)
|
|
.into(),
|
|
);
|
|
}
|
|
}
|
|
|
|
body.push(
|
|
text_block(
|
|
"Ctrl+Alt+Shift+Q releases input \u{00B7} Ctrl+Alt+Shift+D disconnects \u{00B7} \
|
|
Ctrl+Alt+Shift+S stats \u{00B7} F11 fullscreen",
|
|
)
|
|
.font_size(11.0)
|
|
.wrap()
|
|
.foreground(ThemeRef::SecondaryText)
|
|
.margin(edges(0.0, 4.0, 0.0, 0.0))
|
|
.into(),
|
|
);
|
|
body.push({
|
|
let ctx = ctx.clone();
|
|
button("Disconnect")
|
|
.icon(Symbol::Cancel)
|
|
.on_click(move || {
|
|
// Kill the child; its exit event (the reader thread) navigates to the
|
|
// host list, exactly like the session window closing.
|
|
ctx.shared.session.lock().unwrap().kill();
|
|
})
|
|
.margin(edges(0.0, 6.0, 0.0, 0.0))
|
|
.into()
|
|
});
|
|
|
|
// One centred card, sized like the app's dialogs — Stretch + max_width (the `page`
|
|
// pattern) instead of a fixed width, so a narrow window shrinks the card instead of
|
|
// clipping it while a wide one still centres it at 520.
|
|
border(card(vstack(body).spacing(12.0)))
|
|
.max_width(520.0)
|
|
.margin(uniform(24.0))
|
|
.vertical_alignment(VerticalAlignment::Center)
|
|
.into()
|
|
}
|