From fc30307a875455683b1423314853af7a0c45a594 Mon Sep 17 00:00:00 2001 From: enricobuehler Date: Sun, 14 Jun 2026 11:58:37 +0000 Subject: [PATCH] feat(abi): expose the host-resolved compositor to clients Add punktfunk_connection_compositor() (mirrors punktfunk_connection_gamepad): a client getter for the compositor the host actually resolved for the session, read from Welcome.compositor and threaded through NativeClient.resolved_compositor. The Apple/Linux clients use it to enable the client-side cursor by default on gamescope sessions, whose PipeWire capture carries no cursor (verified upstream). Header regenerated. Co-Authored-By: Claude Opus 4.8 (1M context) --- crates/punktfunk-core/src/abi.rs | 29 +++++++++++++++++++++ crates/punktfunk-core/src/client.rs | 40 +++++++++++++++++++---------- include/punktfunk_core.h | 13 ++++++++++ 3 files changed, 69 insertions(+), 13 deletions(-) diff --git a/crates/punktfunk-core/src/abi.rs b/crates/punktfunk-core/src/abi.rs index 9deb6d7..73525ae 100644 --- a/crates/punktfunk-core/src/abi.rs +++ b/crates/punktfunk-core/src/abi.rs @@ -1294,6 +1294,35 @@ pub unsafe extern "C" fn punktfunk_connection_gamepad( }) } +/// The compositor backend the host actually resolved for this session (one of the +/// `PUNKTFUNK_COMPOSITOR_*` values; the `Welcome`'s echo of the [`punktfunk_connect_ex`] +/// preference). `PUNKTFUNK_COMPOSITOR_AUTO` = an older host that didn't say. Clients use it for +/// compositor-specific behavior — e.g. a client-side cursor by default on +/// `PUNKTFUNK_COMPOSITOR_GAMESCOPE`, whose PipeWire capture carries no cursor. Safe any time after +/// connect. +/// +/// # Safety +/// `c` is a valid connection handle; `compositor` is writable (NULL is skipped). +#[cfg(feature = "quic")] +#[no_mangle] +pub unsafe extern "C" fn punktfunk_connection_compositor( + c: *const PunktfunkConnection, + compositor: *mut u32, +) -> PunktfunkStatus { + guard(|| { + let c = match unsafe { c.as_ref() } { + Some(c) => c, + None => return PunktfunkStatus::NullPointer, + }; + unsafe { + if !compositor.is_null() { + *compositor = c.inner.resolved_compositor.to_u8() as u32; + } + } + PunktfunkStatus::Ok + }) +} + /// The video encoder bitrate (kilobits per second) the host actually configured for this session /// — the [`punktfunk_connect_ex3`] request clamped to the host's range, or its default when `0` /// was requested. `0` = an older host that didn't report it. Safe any time after connect. diff --git a/crates/punktfunk-core/src/client.rs b/crates/punktfunk-core/src/client.rs index cd90498..20d3601 100644 --- a/crates/punktfunk-core/src/client.rs +++ b/crates/punktfunk-core/src/client.rs @@ -36,10 +36,10 @@ enum CtrlRequest { } /// What the worker reports to [`NativeClient::connect`] once the handshake lands: the negotiated -/// mode, the host-resolved gamepad backend, the host's certificate fingerprint, the resolved -/// encoder bitrate (kbps), and the host↔client clock offset (ns, host minus client; 0 = no skew -/// correction / an old host that didn't answer the handshake). -type Negotiated = (Mode, GamepadPref, [u8; 32], u32, i64); +/// mode, the host-resolved compositor backend, the host-resolved gamepad backend, the host's +/// certificate fingerprint, the resolved encoder bitrate (kbps), and the host↔client clock offset +/// (ns, host minus client; 0 = no skew correction / an old host that didn't answer the handshake). +type Negotiated = (Mode, CompositorPref, GamepadPref, [u8; 32], u32, i64); /// Accumulated state of an in-flight / finished speed test. The data-plane pump folds each /// received [`FLAG_PROBE`] access unit in; the control task records the host's [`ProbeResult`] @@ -135,6 +135,10 @@ pub struct NativeClient { /// SHA-256 fingerprint of the certificate the host actually presented. A TOFU caller /// (`pin = None`) persists this and passes it as the pin from then on. pub host_fingerprint: [u8; 32], + /// The compositor backend the host actually resolved for this session ([`Welcome::compositor`]). + /// `Auto` = an older host that didn't say. Clients use it for compositor-specific behavior (e.g. + /// drawing a client-side cursor by default on gamescope, whose capture carries no cursor). + pub resolved_compositor: CompositorPref, /// The virtual gamepad backend the host actually resolved ([`Welcome::gamepad`]). /// `Auto` = an older host that didn't say (assume X-Box 360, no DualSense feedback). pub resolved_gamepad: GamepadPref, @@ -228,15 +232,21 @@ impl NativeClient { }) .map_err(PunktfunkError::Io)?; - let (negotiated, resolved_gamepad, fingerprint, resolved_bitrate_kbps, clock_offset_ns) = - match ready_rx.recv_timeout(timeout) { - Ok(Ok(t)) => t, - Ok(Err(e)) => return Err(e), - Err(_) => { - shutdown.store(true, Ordering::SeqCst); - return Err(PunktfunkError::Timeout); - } - }; + let ( + negotiated, + resolved_compositor, + resolved_gamepad, + fingerprint, + resolved_bitrate_kbps, + clock_offset_ns, + ) = match ready_rx.recv_timeout(timeout) { + Ok(Ok(t)) => t, + Ok(Err(e)) => return Err(e), + Err(_) => { + shutdown.store(true, Ordering::SeqCst); + return Err(PunktfunkError::Timeout); + } + }; *mode_slot.lock().unwrap() = negotiated; Ok(NativeClient { frames: Mutex::new(frame_rx), @@ -252,6 +262,7 @@ impl NativeClient { worker: Some(worker), mode: mode_slot, host_fingerprint: fingerprint, + resolved_compositor, resolved_gamepad, resolved_bitrate_kbps, clock_offset_ns, @@ -661,6 +672,7 @@ async fn worker_main(args: WorkerArgs) { send, recv, welcome.mode, + welcome.compositor, welcome.gamepad, fingerprint, welcome.bitrate_kbps, @@ -674,6 +686,7 @@ async fn worker_main(args: WorkerArgs) { mut ctrl_send, mut ctrl_recv, negotiated, + resolved_compositor, resolved_gamepad, fingerprint, resolved_bitrate_kbps, @@ -687,6 +700,7 @@ async fn worker_main(args: WorkerArgs) { }; let _ = ready_tx.send(Ok(( negotiated, + resolved_compositor, resolved_gamepad, fingerprint, resolved_bitrate_kbps, diff --git a/include/punktfunk_core.h b/include/punktfunk_core.h index cef501e..7162288 100644 --- a/include/punktfunk_core.h +++ b/include/punktfunk_core.h @@ -801,6 +801,19 @@ PunktfunkStatus punktfunk_connection_mode(const PunktfunkConnection *c, PunktfunkStatus punktfunk_connection_gamepad(const PunktfunkConnection *c, uint32_t *gamepad); #endif +#if defined(PUNKTFUNK_FEATURE_QUIC) +// The compositor backend the host actually resolved for this session (one of the +// `PUNKTFUNK_COMPOSITOR_*` values; the `Welcome`'s echo of the [`punktfunk_connect_ex`] +// preference). `PUNKTFUNK_COMPOSITOR_AUTO` = an older host that didn't say. Clients use it for +// compositor-specific behavior — e.g. a client-side cursor by default on +// `PUNKTFUNK_COMPOSITOR_GAMESCOPE`, whose PipeWire capture carries no cursor. Safe any time after +// connect. +// +// # Safety +// `c` is a valid connection handle; `compositor` is writable (NULL is skipped). +PunktfunkStatus punktfunk_connection_compositor(const PunktfunkConnection *c, uint32_t *compositor); +#endif + #if defined(PUNKTFUNK_FEATURE_QUIC) // The video encoder bitrate (kilobits per second) the host actually configured for this session // — the [`punktfunk_connect_ex3`] request clamped to the host's range, or its default when `0`