feat(core+host+client): cursor channel — remote-desktop sweep M2a+M2b
The host cursor stops riding the video and becomes a real OS cursor on the client (the Parsec/RDP model): pointer feel no longer pays the capture→encode→network→decode→present round trip. Wire (M2a): - Hello grows a client_caps trailing byte (CLIENT_CAP_CURSOR) after the fixed display_hdr block — presence disambiguated by remaining length, which caps the post-HDR tail at 27 bytes (documented); Welcome answers HOST_CAP_CURSOR (capable-and-asked, the 444/clipboard precedent). - CursorShape (0x50, control stream): serial + dims + hotspot + straight RGBA, ≤120px/side so the u16 frame always fits (128² would overshoot); client caches by serial — re-showing a known shape costs 14 bytes, not a bitmap (RDP pointer-cache for free). - CursorState (0xD0 datagram): serial + visible/relative_hint flags + position, sent once per encode-loop tick — latest-wins, self-healing under loss, no refresh timer. relative_hint is reserved for M3. - Client core: two new planes (control-task + datagram-task arms) → next_cursor_shape/next_cursor_state; connect() grows client_caps (C ABI passes 0 until the v11 cursor poll fns exist). Host (M2b, Linux portal only): - handshake::cursor_forward is THE predicate (client asked ∧ Linux ∧ compositor ≠ gamescope) — Welcome bit and session wiring both read it. - SessionPlan.cursor_blend goes false for a forwarding session; the encode loop ticks a CursorForwarder every iteration: shape-serial diff → control-task bridge (mirrors probe_result), state datagram → conn. - CursorOverlay/capture CursorState carry the hotspot through (nearest-neighbor downscale backstop for XL cursors, unit-tested). Presenter: - CursorChannel drains both planes per loop iteration; shapes become SDL color cursors (from_surface + hotspot), applied while the desktop mouse model is engaged; visibility follows the host; capture/released hands back the system cursor. Sessions advertise the cap when they START in desktop mode. Verified on .21: fmt + clippy -D warnings (7 crates) + tests green (core 218 incl. new wire roundtrips, host 245 incl. e2e + forwarder downscale tests). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -201,6 +201,9 @@ pub extern "system" fn Java_io_unom_punktfunk_kit_NativeBridge_nativeConnect<'lo
|
||||
// No display-volume forwarding from Android yet (the panel tone-maps PQ itself via the
|
||||
// Surface dataspace + static metadata) — the host keeps its virtual-display EDID defaults.
|
||||
None,
|
||||
// No non-video caps: this client does not render the host cursor locally (no shape/state
|
||||
// planes in the jni surface), so advertising CLIENT_CAP_CURSOR would stream cursor-less.
|
||||
0,
|
||||
launch, // a store-qualified library id to boot into a game, or None for the desktop
|
||||
pin, // Some → Crypto on host-fp mismatch
|
||||
identity, // owned (cert, key) PEM, or None (anonymous)
|
||||
|
||||
@@ -547,6 +547,7 @@ impl AppModel {
|
||||
crate::video::decodable_codecs(), // codecs (unused by the probe, but honest)
|
||||
0, // preferred_codec: no preference
|
||||
None, // display_hdr: probe connect, nothing presents
|
||||
0, // client_caps: probe connect, nothing renders a cursor
|
||||
None, // launch: probe connect, no game
|
||||
pin,
|
||||
Some(identity),
|
||||
|
||||
@@ -523,6 +523,9 @@ async fn session(args: Args) -> Result<()> {
|
||||
// writes it into the virtual display's EDID (CTA HDR block), so the EDID-forwarding
|
||||
// path can be validated headlessly (check the host's monitor caps / ADD log line).
|
||||
display_hdr: punktfunk_core::client::display_hdr_env_override(),
|
||||
// No CLIENT_CAP_CURSOR: this headless tool renders nothing — advertising it would
|
||||
// just strip the pointer from the dumped bitstream.
|
||||
client_caps: 0,
|
||||
}
|
||||
.encode(),
|
||||
)
|
||||
|
||||
@@ -172,6 +172,11 @@ mod session_main {
|
||||
// defaults for Linux clients; `PUNKTFUNK_CLIENT_PEAK_NITS` (read in the session
|
||||
// pump) pins one manually.
|
||||
display_hdr: None,
|
||||
// The presenter renders the host cursor locally in desktop mouse mode (M2 cursor
|
||||
// channel); capture-mode sessions keep the composited cursor, so only advertise
|
||||
// when the session STARTS in desktop mode. The host gates further (Linux portal
|
||||
// compositors only).
|
||||
cursor_forward: settings.mouse_mode() == trust::MouseMode::Desktop,
|
||||
mic_enabled: settings.mic_enabled,
|
||||
clipboard,
|
||||
// The Settings preference (auto → VAAPI where it exists; the presenter
|
||||
|
||||
@@ -56,6 +56,7 @@ pub fn run_speed_probe(
|
||||
decodable_codecs(),
|
||||
0, // preferred_codec: no preference
|
||||
None, // display_hdr: probe connect, nothing presents
|
||||
0, // client_caps: probe connect, nothing renders a cursor
|
||||
None, // launch: no game
|
||||
pin,
|
||||
Some(identity),
|
||||
|
||||
Reference in New Issue
Block a user