fix(windows/gamestream): create the virtual display on Windows (Moonlight black screen)

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 <noreply@anthropic.com>
This commit is contained in:
2026-06-30 12:11:11 +02:00
parent 915f11a712
commit b73d90efbb
+20 -8
View File
@@ -213,14 +213,26 @@ fn open_gs_virtual_source(
let compositor = if let Some(c) = app.and_then(|a| a.compositor) { let compositor = if let Some(c) = app.and_then(|a| a.compositor) {
c c
} else { } else {
let active = crate::vdisplay::detect_active_session(); // Windows has a single virtual-display backend (pf-vdisplay); `vdisplay::open` ignores the
crate::vdisplay::apply_session_env(&active); // compositor arg there, so short-circuit the Linux session-detection state machine with a
let c = crate::vdisplay::compositor_for_kind(active.kind) // placeholder — mirrors `punktfunk1::resolve_compositor`. Without this, the Linux `detect()`
.map(Ok) // below bails on Windows ("could not detect compositor … XDG_CURRENT_DESKTOP=''"), which
.unwrap_or_else(crate::vdisplay::detect) // killed the GameStream video thread → black screen (the native plane was already guarded).
.context("detect compositor")?; #[cfg(target_os = "windows")]
crate::vdisplay::apply_input_env(c); {
c 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")?; 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 // Carry the resolved launch command on the backend instance (per-session) rather than a