feat(vdisplay): finish Stage 4 — typed reject, Windows join-default, GameStream 503
Completes the mode-conflict admission surface deferred from the initial Stage 4: - REJECT now delivers the reason to the client: punktfunk/1 closes the QUIC connection with a distinct BUSY code (0x42) + the 'host busy: streaming WxH@Hz to <client>' string, which the client reads from ApplicationClosed (validated on loopback: the probe logs 'closed by peer: host busy … (code 66)'). - Windows default: separate (incl. the unconfigured default) resolves to JOIN — the Windows native host admits a second client at the live mode instead of the old silent last-wins reconfigure of the shared monitor (release-note behavior fix; the reconfigure is now opt-in as steal). separate stays multi-view on Linux. - GameStream 503: h_launch tracks the session owner fp (LaunchSession.owner_fp, kept [u8;32] for Copy) and applies the policy when a DIFFERENT paired client launches — reject → 503 (Moonlight 'host busy'), join → serve the live mode, steal/separate → take over. Same-client re-launch is never a conflict. Native reject-reason loopback-validated; Windows join-default pending .173 rebuild; GameStream 503 pending a Moonlight client (can't drive /launch autonomously). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -91,13 +91,33 @@ pub fn decide(
|
||||
}
|
||||
|
||||
/// Resolve the effective decision for a connecting session: read the console `mode_conflict` policy
|
||||
/// (default `Separate` when unconfigured — no behavior change) and [`decide`] against the live set.
|
||||
/// (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 {
|
||||
let conflict = policy::prefs()
|
||||
#[allow(unused_mut)]
|
||||
let mut conflict = policy::prefs()
|
||||
.configured_effective()
|
||||
.map(|e| e.mode_conflict)
|
||||
.unwrap_or(ModeConflict::Separate);
|
||||
decide(conflict, req_identity, &table().lock().unwrap())
|
||||
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;
|
||||
}
|
||||
decide(conflict, req_identity, &live)
|
||||
}
|
||||
|
||||
/// Register a now-admitted, live session; the returned guard removes it on drop (session end). Call
|
||||
|
||||
Reference in New Issue
Block a user