Files
punktfunk/clients/windows/src/app/stream.rs
T
enricobuehler ef5808254a refactor(windows): remove the legacy in-process builtin stream path
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>
2026-07-13 01:22:09 +02:00

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()
}