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
@@ -213,6 +213,17 @@ fn open_gs_virtual_source(
let compositor = if let Some(c) = app.and_then(|a| a.compositor) {
c
} else {
// 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)
@@ -221,6 +232,7 @@ fn open_gs_virtual_source(
.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