feat(windows-host): launch the chosen library title into the interactive session
Make the no-op Windows `set_launch_command` real. New `windows/interactive.rs` `spawn_in_active_session` (WTSGetActiveConsoleSessionId → WTSQueryUserToken → CreateProcessAsUserW(winsta0\default) under the LOGGED-IN USER token, factored from the wgc_relay primitive) + `library::launch_title` resolving a store-qualified id to a concrete process via `windows_launch_for` (steam_appid → Steam.exe/explorer.exe steam:// URI; command → cmd.exe /c). Threaded as `SessionContext.launch` into both native data-plane paths (`virtual_stream`, `virtual_stream_relay`) and fired after capture is live so the title renders onto the captured desktop and grabs foreground. Security invariant intact: the client sends only the store-qualified id; the host resolves the recipe from its own library and the URI/flags are handed to a concrete EXE as plain args (never cmd /c of a client string). Linux unchanged (gamescope nesting via the handshake PUNKTFUNK_GAMESCOPE_APP path). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -571,6 +571,11 @@ async fn serve_session(
|
||||
// (`what's left` §3), resolve the command into the per-session VirtualDisplay via
|
||||
// `set_launch_command` (as the GameStream path now does) so sessions can't stomp each other.
|
||||
if let Some(id) = hello.launch.as_deref() {
|
||||
// Linux: resolve the id to a gamescope-nested command and stash it in the env the
|
||||
// gamescope backend reads. Windows has no gamescope to nest into — the data plane launches
|
||||
// the title into the interactive user session via `library::launch_title` once capture is
|
||||
// live (threaded as `SessionContext.launch` below), so there is nothing to do here.
|
||||
#[cfg(not(windows))]
|
||||
match crate::library::launch_command(id) {
|
||||
Some(cmd) => {
|
||||
tracing::info!(launch_id = id, command = %cmd, "launching library title");
|
||||
@@ -581,6 +586,8 @@ async fn serve_session(
|
||||
"client requested a launch id not in this host's library — ignoring"
|
||||
),
|
||||
}
|
||||
#[cfg(windows)]
|
||||
let _ = id;
|
||||
}
|
||||
|
||||
// Resolve the client's gamepad-backend preference (pure env/cfg check — no probing
|
||||
@@ -912,6 +919,10 @@ async fn serve_session(
|
||||
let source = opts.source;
|
||||
let (seconds, frames) = (opts.seconds, opts.frames);
|
||||
let mode = hello.mode;
|
||||
// Windows: the store-qualified launch id, threaded into the data plane so the title can be
|
||||
// launched into the interactive session once capture is live (no gamescope nesting on Windows).
|
||||
#[cfg(target_os = "windows")]
|
||||
let launch_for_dp = hello.launch.clone();
|
||||
let bitrate_kbps = welcome.bitrate_kbps; // resolved encoder bitrate (Hello clamped, or default)
|
||||
let bit_depth = welcome.bit_depth; // resolved encode bit depth (8, or 10 when negotiated)
|
||||
let stop_stream = stop.clone();
|
||||
@@ -971,6 +982,8 @@ async fn serve_session(
|
||||
probe_result_tx,
|
||||
fec_target: fec_target_dp,
|
||||
conn: conn_stream,
|
||||
#[cfg(target_os = "windows")]
|
||||
launch: launch_for_dp,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -2172,6 +2185,11 @@ struct SessionContext {
|
||||
fec_target: Arc<AtomicU8>,
|
||||
/// The QUIC control connection (carries host→client 0xCE source-HDR metadata mid-stream).
|
||||
conn: quinn::Connection,
|
||||
/// Windows: the store-qualified library id to launch into the interactive user session once
|
||||
/// capture is live (no gamescope nesting on Windows). `None` = no launch requested. Linux uses the
|
||||
/// gamescope `PUNKTFUNK_GAMESCOPE_APP` path resolved at handshake, so this field is Windows-only.
|
||||
#[cfg(target_os = "windows")]
|
||||
launch: Option<String>,
|
||||
}
|
||||
|
||||
fn virtual_stream(ctx: SessionContext) -> Result<()> {
|
||||
@@ -2208,6 +2226,8 @@ fn virtual_stream(ctx: SessionContext) -> Result<()> {
|
||||
probe_result_tx,
|
||||
fec_target,
|
||||
conn,
|
||||
#[cfg(target_os = "windows")]
|
||||
launch,
|
||||
} = ctx;
|
||||
tracing::info!(
|
||||
compositor = compositor.id(),
|
||||
@@ -2248,6 +2268,17 @@ fn virtual_stream(ctx: SessionContext) -> Result<()> {
|
||||
#[cfg(target_os = "windows")]
|
||||
let _composed_flip = crate::capture::composed_flip::ForceComposedFlip::start();
|
||||
|
||||
// Windows: capture is live (and composition forced) — launch the requested library title into the
|
||||
// interactive user session so it renders onto the captured desktop and grabs foreground. Linux
|
||||
// nests its launch in gamescope instead (the handshake `PUNKTFUNK_GAMESCOPE_APP` path). Best-effort:
|
||||
// a launch failure (no recipe for the kind, no interactive user) leaves the user on the desktop.
|
||||
#[cfg(target_os = "windows")]
|
||||
if let Some(id) = launch.as_deref() {
|
||||
if let Err(e) = crate::library::launch_title(id) {
|
||||
tracing::warn!(launch_id = id, error = %e, "could not launch requested library title");
|
||||
}
|
||||
}
|
||||
|
||||
let perf = crate::config::config().perf;
|
||||
// Microburst cap (applied in send_loop/paced_submit): a frame ≤ this bursts out immediately;
|
||||
// only a bigger frame's overflow is spread. PUNKTFUNK_PACE_BURST_KB overrides the 128 KB default.
|
||||
@@ -2600,6 +2631,7 @@ fn virtual_stream_relay(ctx: SessionContext) -> Result<()> {
|
||||
probe_result_tx,
|
||||
fec_target,
|
||||
conn: _conn,
|
||||
launch,
|
||||
} = ctx;
|
||||
tracing::info!(
|
||||
?mode,
|
||||
@@ -2657,6 +2689,15 @@ fn virtual_stream_relay(ctx: SessionContext) -> Result<()> {
|
||||
let (mut _keepalive, mut relay, mut target, mut effective_hz) = build(&mut vd, mode)?;
|
||||
let mut cur_mode = mode;
|
||||
|
||||
// Capture is live (the WGC helper is relaying) — launch the requested library title into the
|
||||
// interactive user session so it renders onto the captured desktop and grabs foreground.
|
||||
// Best-effort: a failure (no recipe for the kind, no interactive user) leaves the user on the desktop.
|
||||
if let Some(id) = launch.as_deref() {
|
||||
if let Err(e) = crate::library::launch_title(id) {
|
||||
tracing::warn!(launch_id = id, error = %e, "could not launch requested library title");
|
||||
}
|
||||
}
|
||||
|
||||
// O3.1: optionally observe the IDD-push ring alongside WGC (WGC = the presentation trigger) to
|
||||
// confirm the 0257 driver pushes frames into a HOST-created ring. Diagnostic only; gated.
|
||||
if std::env::var_os("PUNKTFUNK_IDD_PUSH_OBSERVE").is_some() {
|
||||
|
||||
Reference in New Issue
Block a user