feat(core+host+driver+client): mid-stream cursor-render flip — host composites in the capture model
The cursor channel excluded the host pointer from the video for the whole session, but the client only draws it under the DESKTOP mouse model — flipping ⌃⌥⇧M to capture mid-stream left the session pointer-blind (user-found). The fix makes render ownership LIVE state: - wire: CursorRenderMode 0x51 (client→host, control stream) — client_draws: true = desktop model (host excludes + forwards), false = capture model / released (host composites, full fidelity — DWM on Windows incl. real XOR inversion, encoder blend on Linux). Sessions start client_draws=true (the pre-message behavior). - host: control task stores the flag; the encode loop edge-detects it — forwarding + frame.cursor strip while the client draws, quiet forwarder + overlay-into-blend while composited. SessionPlan now grants blend CAPABILITY wherever the capture has a pointer (dropping the !cursor_forward gate) so the composite side needs no encoder rebuild; per-tick frame.cursor decides what is drawn. - Windows: Capturer::set_cursor_forward → retained IOCTL_SET_CURSOR_FORWARD sender (proto v6) → driver (un)declares the IddCx hardware cursor on the LIVE monitor. Un-declare re-issues the setup with EMPTY caps (candidate mechanism — the DDI has no documented un-setup); resetup-on-commit is skipped while off, so a mode commit's software-cursor default is the fallback path. Failure against a pre-v6 driver logs and keeps declared-at-ADD behavior. - SDL client: one edge-detected reconciler at the cursor pump site — desktop-active = client draws; capture model or released = host composites. Covers the chord, the M3 auto-flip, and engage/release. - C ABI v12: punktfunk_connection_set_cursor_render (additive; wire version unchanged — pre-§8 hosts ignore the unknown message type). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -28,6 +28,10 @@ pub(crate) enum CtrlRequest {
|
||||
/// Announce that the local clipboard changed — the lazy format-list offer (bytes cross later on
|
||||
/// a fetch stream). Symmetric message; the host may send one too.
|
||||
ClipOffer(ClipOffer),
|
||||
/// Who renders the pointer (cursor-forward sessions): `true` = client draws locally (the
|
||||
/// desktop mouse model — host excludes + forwards), `false` = host composites into the
|
||||
/// video (the capture model). Sent on every mouse-model flip; idempotent, latest-wins.
|
||||
CursorRender(crate::quic::CursorRenderMode),
|
||||
}
|
||||
|
||||
/// What the worker reports to [`NativeClient::connect`] once the handshake lands: the
|
||||
|
||||
@@ -573,6 +573,20 @@ impl NativeClient {
|
||||
.map_err(|_| PunktfunkError::Closed)
|
||||
}
|
||||
|
||||
/// Tell the host who renders the pointer (cursor-forward sessions —
|
||||
/// design/remote-desktop-sweep.md §8): `true` = this client draws it locally (the desktop
|
||||
/// mouse model; the host excludes the pointer from the video and forwards shape/state),
|
||||
/// `false` = the host composites it into the video (the capture model — full fidelity,
|
||||
/// the pre-channel behavior). Call on every mouse-model flip; idempotent, latest-wins,
|
||||
/// no-op on hosts without [`HOST_CAP_CURSOR`](crate::quic::HOST_CAP_CURSOR).
|
||||
pub fn set_cursor_render(&self, client_draws: bool) -> Result<()> {
|
||||
self.ctrl_tx
|
||||
.try_send(CtrlRequest::CursorRender(crate::quic::CursorRenderMode {
|
||||
client_draws,
|
||||
}))
|
||||
.map_err(|_| PunktfunkError::Closed)
|
||||
}
|
||||
|
||||
/// Ask the host's encoder to emit a fresh IDR keyframe now (client recovery on a stalled
|
||||
/// decode). Non-blocking, fire-and-forget — the recovered keyframe is the only ack. The
|
||||
/// caller should throttle (the decode stays wedged across several frames until the IDR
|
||||
|
||||
@@ -71,6 +71,7 @@ impl ControlTask {
|
||||
}
|
||||
CtrlRequest::ClipControl(c) => c.encode(),
|
||||
CtrlRequest::ClipOffer(o) => o.encode(),
|
||||
CtrlRequest::CursorRender(m) => m.encode(),
|
||||
};
|
||||
if io::write_msg(&mut ctrl_send, &bytes).await.is_err() {
|
||||
break;
|
||||
|
||||
Reference in New Issue
Block a user