feat(abi): expose the host-resolved compositor to clients
ci / docs-site (push) Successful in 30s
apple / swift (push) Successful in 1m13s
ci / bench (push) Successful in 1m39s
ci / web (push) Successful in 30s
ci / rust (push) Successful in 2m3s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 6s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 4s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 4s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 5s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 4s
deb / build-publish (push) Successful in 2m24s
rpm / build-publish (bazzite, punktfunk-fedora-rpm) (push) Successful in 4m46s
docker / deploy-docs (push) Successful in 17s
rpm / build-publish (fedora-44, punktfunk-fedora44-rpm) (push) Successful in 4m23s

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) <noreply@anthropic.com>
This commit is contained in:
2026-06-14 11:58:37 +00:00
parent c548155dd9
commit fc30307a87
3 changed files with 69 additions and 13 deletions
+29
View File
@@ -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.
+27 -13
View File
@@ -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,
+13
View File
@@ -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`