fix(vdisplay): Windows admission default is reject, not join (single-capturer limit)

Two concurrent Windows sessions both drive the same pf-vdisplay monitor's
single-capturer IDD-push channel (newest-delivery-wins), which freezes the live
client and can wedge the driver (observed live: a concurrent-session test wedged
.173 → Moonlight 'no video'; needed a reboot). True multi-session capture is §6.6/
Stage 7. So on Windows 'separate' (incl. the unconfigured default) now resolves to
REJECT — a 2nd client gets a clean 503 and the live session is protected — instead
of join (which would freeze it). join/steal stay explicit opt-ins; Linux keeps
separate (real multi-view). Centralized as admission::effective_conflict(), shared
by the native handshake + GameStream h_launch.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-05 11:32:52 +00:00
parent 980939ed6b
commit 23446fa177
3 changed files with 54 additions and 29 deletions
+17 -22
View File
@@ -90,34 +90,29 @@ pub fn decide(
}
}
/// Resolve the effective decision for a connecting session: read the console `mode_conflict` policy
/// (default `Separate` when unconfigured) and [`decide`] against the live set.
///
/// **Windows** can't create SEPARATE virtual displays until the multi-monitor stage (§6.6), so a
/// `separate` outcome — including the **unconfigured default** — resolves to `join` (admit at the live
/// mode) rather than the old silent last-wins reconfigure of the shared monitor. This is the deliberate
/// Windows default change (release-note behavior fix); `steal` remains the way to force the new mode.
pub fn admit(req_identity: Option<[u8; 32]>) -> Admission {
#[allow(unused_mut)]
let mut conflict = policy::prefs()
/// The effective `mode_conflict` policy for THIS host: the console value (default `Separate` when
/// unconfigured), with the **Windows default applied**. On Windows `separate` — including the
/// unconfigured default — resolves to **`reject`**: two concurrent Windows sessions would both drive the
/// SAME pf-vdisplay monitor's single-capturer IDD-push channel ("newest-delivery-wins"), which freezes
/// the live client and can wedge the driver (true multi-session capture is §6.6 / Stage 7). So a 2nd
/// client gets a clean 503 and the live session is protected; `join`/`steal` stay as explicit opt-ins.
/// Linux keeps `separate` (real multi-view). Shared by the native + GameStream admission paths.
pub fn effective_conflict() -> ModeConflict {
let conflict = policy::prefs()
.configured_effective()
.map(|e| e.mode_conflict)
.unwrap_or(ModeConflict::Separate);
let live = table().lock().unwrap();
#[cfg(windows)]
if matches!(conflict, ModeConflict::Separate) {
if live
.iter()
.any(|s| !same_client(s.identity, req_identity))
{
tracing::warn!(
"mode_conflict=separate is not yet supported on Windows (multi-monitor is §6.6) — \
JOINing the live session's mode instead"
);
}
conflict = ModeConflict::Join;
return ModeConflict::Reject;
}
decide(conflict, req_identity, &live)
conflict
}
/// Resolve the admission decision for a connecting native session: [`effective_conflict`] + [`decide`]
/// against the live set.
pub fn admit(req_identity: Option<[u8; 32]>) -> Admission {
decide(effective_conflict(), req_identity, &table().lock().unwrap())
}
/// Register a now-admitted, live session; the returned guard removes it on drop (session end). Call