feat(clients/windows): single-window handoff, shared settings store, console-UI surfacing
UX polish batch 1 (clients/windows/ui-polish-plan.md, workstreams W0/A1/B/C/E):
- W0: the shell's duplicated trust/settings structs are gone — src/trust.rs
re-exports pf-client-core's, so the shell and the spawned session binary share
ONE Settings shape over client-windows-settings.json. Fixes real bugs: the
shell's saves no longer drop session-side fields; the stats-overlay toggle
(show_hud -> show_stats, serde alias migrates old files) and the forwarded-
controller pick now actually reach the spawned session. The "Streaming engine"
Settings combo is gone (PUNKTFUNK_BUILTIN_STREAM=1 stays as the dev A/B knob).
- Forwarded-controller pinning by stable key (vid:pid:name, pf-client-core's
format): persisted as forward_pad, applied by the shell's own service at
startup AND by the session binary in session_params (both OSes — the session
never applied the pin before).
- A1 single window: the shell hides itself when the spawned session window
presents its first frame ({"ready":true}) and restores + foregrounds on the
child's exit — exactly one visible Punktfunk window at any time. Restore runs
before the request-access cancel gate so a Ready/Cancel race can't strand the
shell hidden.
- C console UI: punktfunk-session --browse gains --json-status (ready when the
library window presents; error line on a failed start), and the shell
surfaces it — "Open console UI" on paired hosts' menus, plus a controller-
detected hint card targeting the most recent paired host (x64 only; aarch64
ships no skia ui feature). Browse spawns hide/restore the shell like streams.
- B responsive: minimum window size (420x360); the hosts header collapses to
icon-only buttons below 700 px; the session status card shrinks instead of
clipping (max_width); busy pages, help actions, licenses and long labels wrap.
- E polish: session exe gets the app icon (winresource + WM_SETICON via the new
pf-presenter win32.rs); both exes share an explicit AppUserModelID on
unpackaged runs (packaged identity wins) so the windows group as one taskbar
app across the visibility handoff; "Start streams fullscreen" toggle passes
--fullscreen (GTK parity); connects stamp KnownHost::last_used; the connect
target is stashed for every route so "Connecting to X" always names the host.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -7,9 +7,9 @@
|
||||
//! `{"ready":true}`, banner from the `{"error"|"ended": …}` line, `trust_rejected`
|
||||
//! routed to the re-pair PIN ceremony, `stats:` lines to the session status page.
|
||||
//!
|
||||
//! The legacy in-process D3D11VA presenter remains reachable via the Settings
|
||||
//! "Streaming engine" pick or `PUNKTFUNK_BUILTIN_STREAM=1` (`app::use_builtin_stream`) —
|
||||
//! the A/B baseline until its deletion.
|
||||
//! The legacy in-process D3D11VA presenter remains reachable via the
|
||||
//! `PUNKTFUNK_BUILTIN_STREAM=1` env override (`app::use_builtin_stream`) — the
|
||||
//! developer A/B baseline until its deletion.
|
||||
|
||||
use std::io::BufRead as _;
|
||||
use std::process::{Child, Command, Stdio};
|
||||
@@ -86,34 +86,75 @@ pub(crate) fn session_binary() -> std::path::PathBuf {
|
||||
|
||||
/// Spawn the session binary for a connect with `fp_hex` pinned and feed its lifecycle to
|
||||
/// `on_event` from a reader thread. The child is parked in `slot` so Disconnect/Cancel
|
||||
/// can kill it. `Err` = the spawn itself failed (binary missing?) — surfaced as a
|
||||
/// connect error by the caller.
|
||||
/// can kill it. `fullscreen` starts the stream window fullscreen (the Settings "Start
|
||||
/// streams fullscreen" toggle); `launch` carries a library title id for the host to
|
||||
/// launch during the handshake. `Err` = the spawn itself failed (binary missing?) —
|
||||
/// surfaced as a connect error by the caller.
|
||||
pub(crate) fn spawn_session(
|
||||
addr: &str,
|
||||
port: u16,
|
||||
fp_hex: &str,
|
||||
connect_timeout_secs: u64,
|
||||
fullscreen: bool,
|
||||
launch: Option<&str>,
|
||||
slot: SessionChild,
|
||||
mut on_event: impl FnMut(SpawnEvent) + Send + 'static,
|
||||
on_event: impl FnMut(SpawnEvent) + Send + 'static,
|
||||
) -> Result<(), String> {
|
||||
use std::os::windows::process::CommandExt as _;
|
||||
const CREATE_NO_WINDOW: u32 = 0x0800_0000;
|
||||
|
||||
let mut cmd = Command::new(session_binary());
|
||||
cmd.arg("--connect")
|
||||
.arg(format!("{addr}:{port}"))
|
||||
.arg("--fp")
|
||||
.arg(fp_hex)
|
||||
.arg("--connect-timeout")
|
||||
.arg(connect_timeout_secs.to_string())
|
||||
.stdin(Stdio::null())
|
||||
.arg(connect_timeout_secs.to_string());
|
||||
if fullscreen {
|
||||
cmd.arg("--fullscreen");
|
||||
}
|
||||
if let Some(id) = launch {
|
||||
cmd.arg("--launch").arg(id);
|
||||
}
|
||||
spawn_with(cmd, &format!("{addr}:{port}"), slot, on_event)
|
||||
}
|
||||
|
||||
/// Spawn the session binary in `--browse` mode: the console (gamepad) library for a
|
||||
/// PAIRED host, in the session window — launches run as streams in that same window.
|
||||
/// The same stdout contract as a connect (`--json-status`): `ready` when the library
|
||||
/// window presents, `error` on a failed start, EOF on quit.
|
||||
pub(crate) fn spawn_browse(
|
||||
addr: &str,
|
||||
port: u16,
|
||||
fullscreen: bool,
|
||||
slot: SessionChild,
|
||||
on_event: impl FnMut(SpawnEvent) + Send + 'static,
|
||||
) -> Result<(), String> {
|
||||
let mut cmd = Command::new(session_binary());
|
||||
cmd.arg("--browse")
|
||||
.arg(format!("{addr}:{port}"))
|
||||
.arg("--json-status");
|
||||
if fullscreen {
|
||||
cmd.arg("--fullscreen");
|
||||
}
|
||||
spawn_with(cmd, &format!("{addr}:{port}"), slot, on_event)
|
||||
}
|
||||
|
||||
/// The shared spawn + stdout-contract reader behind [`spawn_session`]/[`spawn_browse`].
|
||||
fn spawn_with(
|
||||
mut cmd: Command,
|
||||
host_label: &str,
|
||||
slot: SessionChild,
|
||||
mut on_event: impl FnMut(SpawnEvent) + Send + 'static,
|
||||
) -> Result<(), String> {
|
||||
use std::os::windows::process::CommandExt as _;
|
||||
const CREATE_NO_WINDOW: u32 = 0x0800_0000;
|
||||
|
||||
cmd.stdin(Stdio::null())
|
||||
.stdout(Stdio::piped())
|
||||
.stderr(Stdio::inherit()) // session logs interleave with the shell's (dev runs)
|
||||
.creation_flags(CREATE_NO_WINDOW);
|
||||
let mut child = cmd
|
||||
.spawn()
|
||||
.map_err(|e| format!("couldn't start punktfunk-session: {e}"))?;
|
||||
tracing::info!(host = %addr, port, "session binary spawned");
|
||||
tracing::info!(host = %host_label, "session binary spawned");
|
||||
|
||||
let stdout = child.stdout.take().expect("piped stdout");
|
||||
// Park the child where the kill handle (and the reader, for the final reap) reach it.
|
||||
|
||||
Reference in New Issue
Block a user