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:
@@ -341,6 +341,11 @@ pub(crate) async fn serve(
|
||||
/// connects and never finishes the handshake would otherwise wedge the host for everyone.
|
||||
const HANDSHAKE_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(10);
|
||||
|
||||
/// QUIC application error code the host closes with on a `mode_conflict = reject` admission refusal,
|
||||
/// carrying the human-readable busy reason (live mode + client label) the client surfaces. A distinct
|
||||
/// code lets a client tell "host busy" apart from a transport failure.
|
||||
const REJECT_BUSY_CODE: u32 = 0x42;
|
||||
|
||||
/// Encoder bitrate (kbps) the host falls back to when the client expresses no preference
|
||||
/// (`Hello::bitrate_kbps == 0`) — the long-standing 20 Mbps default. A client that knows its
|
||||
/// link (e.g. after a speed test) requests an explicit rate instead.
|
||||
@@ -718,6 +723,11 @@ async fn serve_session(
|
||||
}
|
||||
Admission::Reject(reason) => {
|
||||
tracing::warn!("mode-conflict: REJECT — {reason}");
|
||||
// Deliver the reason to the client as a TYPED refusal: close the QUIC connection
|
||||
// with the BUSY application code + the reason bytes, which the client reads from
|
||||
// the `ApplicationClosed` error (so its UI can say "host is streaming X to <name>")
|
||||
// instead of seeing a bare connection drop. Then end the handshake.
|
||||
conn.close(REJECT_BUSY_CODE.into(), reason.as_bytes());
|
||||
anyhow::bail!("{reason}");
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user