From 4306d4f91406028d98ba8480dd05205e41adde1d Mon Sep 17 00:00:00 2001 From: enricobuehler Date: Tue, 30 Jun 2026 12:11:11 +0200 Subject: [PATCH] fix(windows/gamestream): create the virtual display on Windows (Moonlight black screen) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The GameStream video path (open_gs_virtual_source) ran the Linux compositor- detection state machine on every platform. On Windows detect_active_session() returns None and vdisplay::detect() bails ("could not detect compositor ... XDG_CURRENT_DESKTOP=''"), killing the video thread right after RTSP PLAY — so a Moonlight client paired, negotiated, then black-screened and dropped. The native punktfunk/1 path already guards this (resolve_compositor returns a placeholder Compositor on Windows, since vdisplay::open ignores the compositor arg there and always uses the pf-vdisplay IddCx backend). Mirror that guard in the GameStream path: short-circuit to a placeholder on Windows, keep the Linux session detection (apply_session_env/apply_input_env) under cfg(not(windows)). Validated live: Moonlight -> this box now creates the pf-vdisplay virtual monitor, attaches the IDD-push ring, and NVENC streams 5120x1440@240. Co-Authored-By: Claude Opus 4.8 --- .../punktfunk-host/src/gamestream/stream.rs | 28 +++++++++++++------ 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/crates/punktfunk-host/src/gamestream/stream.rs b/crates/punktfunk-host/src/gamestream/stream.rs index 1696ca5..8796692 100644 --- a/crates/punktfunk-host/src/gamestream/stream.rs +++ b/crates/punktfunk-host/src/gamestream/stream.rs @@ -213,14 +213,26 @@ fn open_gs_virtual_source( let compositor = if let Some(c) = app.and_then(|a| a.compositor) { c } else { - let active = crate::vdisplay::detect_active_session(); - crate::vdisplay::apply_session_env(&active); - let c = crate::vdisplay::compositor_for_kind(active.kind) - .map(Ok) - .unwrap_or_else(crate::vdisplay::detect) - .context("detect compositor")?; - crate::vdisplay::apply_input_env(c); - c + // Windows has a single virtual-display backend (pf-vdisplay); `vdisplay::open` ignores the + // compositor arg there, so short-circuit the Linux session-detection state machine with a + // placeholder — mirrors `punktfunk1::resolve_compositor`. Without this, the Linux `detect()` + // below bails on Windows ("could not detect compositor … XDG_CURRENT_DESKTOP=''"), which + // killed the GameStream video thread → black screen (the native plane was already guarded). + #[cfg(target_os = "windows")] + { + crate::vdisplay::Compositor::Kwin + } + #[cfg(not(target_os = "windows"))] + { + let active = crate::vdisplay::detect_active_session(); + crate::vdisplay::apply_session_env(&active); + let c = crate::vdisplay::compositor_for_kind(active.kind) + .map(Ok) + .unwrap_or_else(crate::vdisplay::detect) + .context("detect compositor")?; + crate::vdisplay::apply_input_env(c); + c + } }; let mut vd = crate::vdisplay::open(compositor).context("open virtual display")?; // Carry the resolved launch command on the backend instance (per-session) rather than a