Files
punktfunk/include/punktfunk_core.h
T
enricobuehler 3e6c9f6060 feat(gamepad): add virtual Xbox One/Series + DualShock 4 pad types
Extends virtual-controller support beyond Xbox 360 + DualSense. Goal: a
physical Xbox One or PS4 pad on the client gets a near-native matching virtual
pad on the host, auto-resolved from the controller type.

Protocol/core:
- GamepadPref gains XboxOne (wire 3) + DualShock4 (wire 4); to_u8/from_u8/
  from_name/as_str + C ABI PUNKTFUNK_GAMEPAD_XBOXONE/_DUALSHOCK4 constants
  (compile-time guard ties them to the enum). Single-byte wire form is
  unchanged, so it's forward-compatible (older peers degrade to Auto).

Host (Linux):
- New UHID DualShock 4 backend (inject/dualshock4.rs) bound by hid-playstation:
  lightbar, touchpad, motion, rumble — DualSense minus adaptive triggers /
  player LEDs / mute. Reuses the DualSense pure state + button mapping; only the
  report byte layout, the real-DS4 HID descriptor, the GET_REPORT handshake
  (0x12 MAC mandatory; 0x02 calibration; 0xa3 firmware) and the touchpad
  resolution (1920x942) differ. Touchpad/motion ride the existing 0xCC plane,
  lightbar the 0xCD Led plane (deduped); rumble the universal 0xCA plane.
- Xbox One/Series is the uinput Xbox-360 backend parameterized with the One S
  USB identity (045e:02ea) for matching glyphs — XInput-identical otherwise.
- PadBackend dispatch + resolver handle both; off Linux the UHID pads and
  One/Series fold into Xbox 360. Windows-host DS4 (ViGEm) deferred.

Clients (auto-resolve physical pad -> virtual type, plus manual settings):
- Linux/Windows (SDL3): SDL_GAMEPAD_TYPE_PS4 -> DualShock 4, _XBOXONE ->
  Xbox One; PadInfo carries the resolved pref; DS4 touchpad/motion capture +
  lightbar already type-agnostic. Linux settings combo + label updated.
- Apple (GameController): GCDualShockGamepad/GCXboxGamepad detection, DS4
  touchpad capture, settings picker entries.
- Android (Kotlin): InputDevice VID/PID auto-detect (matching the other
  clients) + settings entries.
- probe: --gamepad help/aliases.

Also hardens the Android JNI boundary: wrap the teardown + poll-thread shims in
catch_unwind so a panic degrades to a logged no-op instead of aborting the app.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-21 13:34:44 +00:00

1139 lines
50 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* punktfunk-core C ABI — see crates/punktfunk-core/src/abi.rs */
#ifndef PUNKTFUNK_CORE_H
#define PUNKTFUNK_CORE_H
#pragma once
/* Generated by cbindgen from punktfunk-core. Do not edit by hand. */
#include <stdarg.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>
// Bump on any breaking change to the [C ABI](crate::abi). Mirrors
// `punktfunk_abi_version()` and is checked by clients before use.
//
// v2: `punktfunk_connect` gained `client_cert_pem`/`client_key_pem` (pairing identities);
// added `punktfunk_pair` / `punktfunk_generate_identity` / `punktfunk_connection_request_mode`.
#define ABI_VERSION 2
// `PunktfunkHidOutput::kind` — lightbar RGB (`r`/`g`/`b` valid).
#define PUNKTFUNK_HIDOUT_LED 1
// `PunktfunkHidOutput::kind` — player-indicator LEDs (`player_bits` valid, low 5 bits).
#define PUNKTFUNK_HIDOUT_PLAYER_LEDS 2
// `PunktfunkHidOutput::kind` — one adaptive-trigger effect (`which` + `effect`/`effect_len` valid).
#define PUNKTFUNK_HIDOUT_TRIGGER 3
// Capacity of `PunktfunkHidOutput::effect` (the DualSense trigger parameter block).
#define PUNKTFUNK_HID_EFFECT_MAX 11
// `PunktfunkRichInput::kind` — a touchpad contact (`finger`/`active`/`x`/`y` valid).
#define PUNKTFUNK_RICH_TOUCHPAD 1
// `PunktfunkRichInput::kind` — a motion sample (`gyro`/`accel` valid).
#define PUNKTFUNK_RICH_MOTION 2
// Compositor preference for [`punktfunk_connect_ex`] (`compositor` arg). `AUTO` lets the host
// pick (auto-detect from its running desktop); a concrete value is honored only if that backend
// is available on the host right now, else the host falls back to auto-detect. The resolved
// choice is reported back over the protocol (see `punktfunk/1` `Welcome`).
#define PUNKTFUNK_COMPOSITOR_AUTO 0
// KWin / KDE Plasma.
#define PUNKTFUNK_COMPOSITOR_KWIN 1
// wlroots (Sway / Hyprland).
#define PUNKTFUNK_COMPOSITOR_WLROOTS 2
// Mutter / GNOME.
#define PUNKTFUNK_COMPOSITOR_MUTTER 3
// gamescope (spawned nested).
#define PUNKTFUNK_COMPOSITOR_GAMESCOPE 4
// Gamepad-backend preference for [`punktfunk_connect_ex2`] (`gamepad` arg): which virtual pad
// the host creates for this session's controllers. Precedence host-side: an explicit client
// choice > the host's `PUNKTFUNK_GAMEPAD` env var > X-Box 360. `AUTO` (or any unrecognized
// value) = host decides. The resolved choice is echoed over the protocol (`Welcome`) and
// readable via [`punktfunk_connection_gamepad`].
#define PUNKTFUNK_GAMEPAD_AUTO 0
// uinput X-Box 360 pad (the universal default — every game speaks XInput).
#define PUNKTFUNK_GAMEPAD_XBOX360 1
// UHID DualSense (kernel `hid-playstation`): adaptive triggers, lightbar, touchpad, motion —
// feedback arrives on the HID-output plane ([`punktfunk_connection_next_hidout`]). Honored
// only where available (Linux hosts); otherwise the host falls back to X-Box 360.
#define PUNKTFUNK_GAMEPAD_DUALSENSE 2
// uinput X-Box One / Series pad — the X-Box 360 backend with the One/Series USB identity, so
// games show One/Series glyphs. XInput-identical to `XBOX360` otherwise (no game-visible gain;
// impulse-trigger rumble is unreachable through a virtual pad). Useful for glyph-matching a
// physical X-Box One/Series controller on the client.
#define PUNKTFUNK_GAMEPAD_XBOXONE 3
// UHID DualShock 4 (kernel `hid-playstation` ≥ 6.2): lightbar, touchpad, motion, rumble — the
// touchpad/motion arrive over the rich-input plane and lightbar over the HID-output plane, like
// DualSense (minus adaptive triggers / player LEDs / mute). Honored only where available (Linux
// hosts); otherwise the host falls back to X-Box 360.
#define PUNKTFUNK_GAMEPAD_DUALSHOCK4 4
// Connect to a `punktfunk/1` host and start a session at `width`x`height`@`refresh_hz`.
// Blocks up to `timeout_ms` for the handshake. Returns NULL on failure. Equivalent to
// [`punktfunk_connect_ex`] with `compositor = PUNKTFUNK_COMPOSITOR_AUTO`.
//
// Video-capability bit for [`punktfunk_connect_ex5`] (`video_caps`): the client can decode a
// 10-bit (Main10) HEVC stream. (Mirrors `quic::VIDEO_CAP_10BIT`.)
#define PUNKTFUNK_VIDEO_CAP_10BIT 1
// Video-capability bit for [`punktfunk_connect_ex5`] (`video_caps`): the client can present
// BT.2020 PQ HDR10 (implies 10-bit). (Mirrors `quic::VIDEO_CAP_HDR`.)
#define PUNKTFUNK_VIDEO_CAP_HDR 2
// 16-byte AEAD authentication tag appended by GCM.
#define TAG_LEN 16
// Wire tag distinguishing an input datagram from a video packet.
#define INPUT_MAGIC 200
// Fixed serialized size of an [`InputEvent`] on the wire (tag + fields).
#define INPUT_WIRE_LEN (((((1 + 1) + 4) + 4) + 4) + 4)
#define PUNKTFUNK_BTN_DPAD_UP 1
#define PUNKTFUNK_BTN_DPAD_DOWN 2
#define PUNKTFUNK_BTN_DPAD_LEFT 4
#define PUNKTFUNK_BTN_DPAD_RIGHT 8
#define PUNKTFUNK_BTN_START 16
#define PUNKTFUNK_BTN_BACK 32
#define PUNKTFUNK_BTN_LS_CLICK 64
#define PUNKTFUNK_BTN_RS_CLICK 128
#define PUNKTFUNK_BTN_LB 256
#define PUNKTFUNK_BTN_RB 512
#define PUNKTFUNK_BTN_GUIDE 1024
#define PUNKTFUNK_BTN_A 4096
#define PUNKTFUNK_BTN_B 8192
#define PUNKTFUNK_BTN_X 16384
#define PUNKTFUNK_BTN_Y 32768
// DualSense touchpad click. Moonlight's extended-button position (`buttonFlags2`
// merges in at `<< 16`, see `gamestream/gamepad.rs`), so GameStream clients land on
// the same bit. Only the DualSense backend renders it; the xpad has no such button.
#define PUNKTFUNK_BTN_TOUCHPAD 1048576
// Axis ids for `InputKind::GamepadAxis`.
#define PUNKTFUNK_AXIS_LS_X 0
#define PUNKTFUNK_AXIS_LS_Y 1
#define PUNKTFUNK_AXIS_RS_X 2
#define PUNKTFUNK_AXIS_RS_Y 3
// Triggers: value range 0..255.
#define PUNKTFUNK_AXIS_LT 4
#define PUNKTFUNK_AXIS_RT 5
// Identifies a punktfunk video packet (vs. an input datagram, see [`crate::input`]).
#define PUNKTFUNK_MAGIC 201
#define FLAG_PIC 1
#define FLAG_EOF 2
#define FLAG_SOF 4
// Bandwidth-probe filler, not decodable video: a [`crate::quic::ProbeRequest`] speed test makes
// the host burst access units carrying this flag so the client measures throughput/loss without
// feeding them to the decoder. Punktfunk/1 only (GameStream never sets it).
#define FLAG_PROBE 8
// Largest UDP datagram the core will send or accept. `Config::validate` bounds
// `shard_payload` so `HEADER_LEN + shard_payload + CRYPTO_OVERHEAD ≤ MAX_DATAGRAM_BYTES`.
#define MAX_DATAGRAM_BYTES 2048
#if defined(PUNKTFUNK_FEATURE_QUIC)
// [`Hello::video_caps`] bit: the client can decode a 10-bit (Main10) HEVC stream.
#define VIDEO_CAP_10BIT 1
#endif
#if defined(PUNKTFUNK_FEATURE_QUIC)
// [`Hello::video_caps`] bit: the client can present BT.2020 PQ HDR10 (implies 10-bit).
#define VIDEO_CAP_HDR 2
#endif
#if defined(PUNKTFUNK_FEATURE_QUIC)
// Longest device name carried in a [`Hello`] (bytes of UTF-8; longer names are truncated on
// encode, rejected on decode — a one-byte length prefix caps it at 255 anyway).
#define HELLO_NAME_MAX 64
#endif
#if defined(PUNKTFUNK_FEATURE_QUIC)
// Longest library id carried in a [`Hello::launch`] (bytes of UTF-8). Ids are short
// (`steam:<appid>` / `custom:<12 hex>`); the cap just bounds an attacker-controlled field.
#define HELLO_LAUNCH_MAX 128
#endif
#if defined(PUNKTFUNK_FEATURE_QUIC)
// Type byte of [`Reconfigure`] (first byte after the magic).
#define MSG_RECONFIGURE 1
#endif
#if defined(PUNKTFUNK_FEATURE_QUIC)
// Type byte of [`Reconfigured`].
#define MSG_RECONFIGURED 2
#endif
#if defined(PUNKTFUNK_FEATURE_QUIC)
// Type byte of [`RequestKeyframe`].
#define MSG_REQUEST_KEYFRAME 3
#endif
#if defined(PUNKTFUNK_FEATURE_QUIC)
// Type byte of [`LossReport`].
#define MSG_LOSS_REPORT 4
#endif
#if defined(PUNKTFUNK_FEATURE_QUIC)
// Type byte of [`ProbeRequest`].
#define MSG_PROBE_REQUEST 32
#endif
#if defined(PUNKTFUNK_FEATURE_QUIC)
// Type byte of [`ProbeResult`].
#define MSG_PROBE_RESULT 33
#endif
#if defined(PUNKTFUNK_FEATURE_QUIC)
// Type byte of [`ClockProbe`].
#define MSG_CLOCK_PROBE 48
#endif
#if defined(PUNKTFUNK_FEATURE_QUIC)
// Type byte of [`ClockEcho`].
#define MSG_CLOCK_ECHO 49
#endif
#if defined(PUNKTFUNK_FEATURE_QUIC)
// Type byte of [`PairRequest`].
#define MSG_PAIR_REQUEST 16
#endif
#if defined(PUNKTFUNK_FEATURE_QUIC)
// Type byte of [`PairChallenge`].
#define MSG_PAIR_CHALLENGE 17
#endif
#if defined(PUNKTFUNK_FEATURE_QUIC)
// Type byte of [`PairProof`].
#define MSG_PAIR_PROOF 18
#endif
#if defined(PUNKTFUNK_FEATURE_QUIC)
// Type byte of [`PairResult`].
#define MSG_PAIR_RESULT 19
#endif
#if defined(PUNKTFUNK_FEATURE_QUIC)
// Datagram wire tags. Video rides UDP; everything low-rate rides QUIC datagrams,
// demultiplexed by the first byte: input = [`crate::input::INPUT_MAGIC`] (0xC8, client→host),
// audio = [`AUDIO_MAGIC`] (0xC9, host→client), rumble = [`RUMBLE_MAGIC`] (0xCA, host→client),
// mic = [`MIC_MAGIC`] (0xCB, client→host), rich-input = [`RICH_INPUT_MAGIC`] (0xCC, client→host),
// HID-output = [`HIDOUT_MAGIC`] (0xCD, host→client), HDR metadata = [`HDR_META_MAGIC`]
// (0xCE, host→client).
#define PUNKTFUNK_AUDIO_MAGIC 201
#endif
#if defined(PUNKTFUNK_FEATURE_QUIC)
#define PUNKTFUNK_RUMBLE_MAGIC 202
#endif
#if defined(PUNKTFUNK_FEATURE_QUIC)
// Microphone uplink: the client's mic, Opus-encoded, client → host (the inverse of
// [`AUDIO_MAGIC`]). The host feeds it into a virtual PipeWire source so its apps can record it.
#define MIC_MAGIC 203
#endif
#if defined(PUNKTFUNK_FEATURE_QUIC)
// Rich client→host input: events too big for the fixed 18-byte [`InputEvent`]
// (crate::input::InputEvent) — the DualSense touchpad and motion sensors. Variable-length,
// kind-tagged (see [`RichInput`]).
#define RICH_INPUT_MAGIC 204
#endif
#if defined(PUNKTFUNK_FEATURE_QUIC)
// HID output, host → client: DualSense feedback a game wrote to the host's virtual controller
// (lightbar, player LEDs, adaptive triggers) — the rich analog of [`RUMBLE_MAGIC`]. See
// [`HidOutput`].
#define HIDOUT_MAGIC 205
#endif
#if defined(PUNKTFUNK_FEATURE_QUIC)
// HDR static-metadata datagram tag, host → client (the static analog of the per-frame VUI;
// see [`HdrMeta`]). Next tag after [`HIDOUT_MAGIC`].
#define HDR_META_MAGIC 206
#endif
#if defined(PUNKTFUNK_FEATURE_QUIC)
// CICP colour-primaries code point: BT.709.
#define ColorInfo_CP_BT709 1
#endif
#if defined(PUNKTFUNK_FEATURE_QUIC)
// CICP colour-primaries code point: BT.2020.
#define ColorInfo_CP_BT2020 9
#endif
#if defined(PUNKTFUNK_FEATURE_QUIC)
// CICP transfer code point: BT.709.
#define ColorInfo_TRC_BT709 1
#endif
#if defined(PUNKTFUNK_FEATURE_QUIC)
// CICP transfer code point: PQ (SMPTE ST.2084).
#define ColorInfo_TRC_PQ 16
#endif
#if defined(PUNKTFUNK_FEATURE_QUIC)
// CICP transfer code point: HLG (ARIB STD-B67 / BT.2100).
#define ColorInfo_TRC_HLG 18
#endif
#if defined(PUNKTFUNK_FEATURE_QUIC)
// CICP matrix code point: BT.709.
#define ColorInfo_MC_BT709 1
#endif
#if defined(PUNKTFUNK_FEATURE_QUIC)
// CICP matrix code point: BT.2020 non-constant-luminance. (Never emit 10 / constant-luminance —
// no client decodes it.)
#define ColorInfo_MC_BT2020_NCL 9
#endif
// Stable C ABI status codes. `Ok` is 0; all errors are negative so callers can
// test `rc < 0`. Do not renumber existing variants — only append.
enum PunktfunkStatus
#if defined(__cplusplus) || __STDC_VERSION__ >= 202311L
: int32_t
#endif // defined(__cplusplus) || __STDC_VERSION__ >= 202311L
{
PUNKTFUNK_STATUS_OK = 0,
PUNKTFUNK_STATUS_INVALID_ARG = -1,
PUNKTFUNK_STATUS_FEC = -2,
PUNKTFUNK_STATUS_CRYPTO = -3,
PUNKTFUNK_STATUS_BAD_PACKET = -4,
PUNKTFUNK_STATUS_NO_FRAME = -5,
PUNKTFUNK_STATUS_UNSUPPORTED = -6,
PUNKTFUNK_STATUS_IO = -7,
PUNKTFUNK_STATUS_NULL_POINTER = -8,
PUNKTFUNK_STATUS_TIMEOUT = -9,
PUNKTFUNK_STATUS_CLOSED = -10,
PUNKTFUNK_STATUS_PANIC = -99,
};
#ifndef __cplusplus
#if __STDC_VERSION__ >= 202311L
typedef enum PunktfunkStatus PunktfunkStatus;
#else
typedef int32_t PunktfunkStatus;
#endif // __STDC_VERSION__ >= 202311L
#endif // __cplusplus
// Kinds of input event. `#[repr(u8)]` so it crosses the C ABI as a byte tag.
enum PunktfunkInputKind
#if defined(__cplusplus) || __STDC_VERSION__ >= 202311L
: uint8_t
#endif // defined(__cplusplus) || __STDC_VERSION__ >= 202311L
{
PUNKTFUNK_INPUT_KIND_KEY_DOWN = 0,
PUNKTFUNK_INPUT_KIND_KEY_UP = 1,
// Relative motion: `x`/`y` carry `dx`/`dy`.
PUNKTFUNK_INPUT_KIND_MOUSE_MOVE = 2,
// Absolute motion: `x`/`y` carry pixel coordinates and `flags` packs the client's
// coordinate-space size as `(width << 16) | height` (the same contract as
// [`TouchDown`](Self::TouchDown)) — injectors normalize against it before mapping
// into the output region and **drop the event when it is zero**.
PUNKTFUNK_INPUT_KIND_MOUSE_MOVE_ABS = 3,
PUNKTFUNK_INPUT_KIND_MOUSE_BUTTON_DOWN = 4,
PUNKTFUNK_INPUT_KIND_MOUSE_BUTTON_UP = 5,
// `x` carries the (signed) scroll delta.
PUNKTFUNK_INPUT_KIND_MOUSE_SCROLL = 6,
// `code` = button bit ([`gamepad`] `BTN_*`), `x` ≠ 0 = pressed, `flags` = pad index.
PUNKTFUNK_INPUT_KIND_GAMEPAD_BUTTON = 7,
// `code` = axis id ([`gamepad`] `AXIS_*`), `x` = axis value, `flags` = pad index.
// Sticks are i16 range (32768..32767) in the XInput/Moonlight convention — **+y =
// up** (unlike mouse coordinates); triggers 0..255.
PUNKTFUNK_INPUT_KIND_GAMEPAD_AXIS = 8,
// Touch begins. `code` = touch id (which finger; reusable after `TouchUp`), `x`/`y` =
// pixel coordinates and `flags` = `(width << 16) | height` of the client's touch surface
// — the same absolute mapping as [`MouseMoveAbs`](Self::MouseMoveAbs).
PUNKTFUNK_INPUT_KIND_TOUCH_DOWN = 9,
// Touch moves. Same field meaning as [`TouchDown`](Self::TouchDown).
PUNKTFUNK_INPUT_KIND_TOUCH_MOVE = 10,
// Touch ends. Only `code` (the touch id) is used.
PUNKTFUNK_INPUT_KIND_TOUCH_UP = 11,
};
#ifndef __cplusplus
#if __STDC_VERSION__ >= 202311L
typedef enum PunktfunkInputKind PunktfunkInputKind;
#else
typedef uint8_t PunktfunkInputKind;
#endif // __STDC_VERSION__ >= 202311L
#endif // __cplusplus
#if defined(PUNKTFUNK_FEATURE_QUIC)
// Per-session colour signalling (CICP / ITU-T H.273 code points) the host resolved for the
// encoded video, carried on [`Welcome`]. A client configures its decoder/presenter from these
// instead of inferring them from the bitstream VUI. An older host omits the bytes on the wire →
// [`ColorInfo::SDR_BT709`] (the 8-bit BT.709 limited stream every pre-HDR build produced).
//
// The *static* HDR mastering metadata (ST.2086 + content light level) is larger and can change
// mid-stream, so it rides the [`HDR_META_MAGIC`] datagram rather than this fixed struct.
typedef struct ColorInfo ColorInfo;
#endif
#if defined(PUNKTFUNK_FEATURE_QUIC)
// Opaque handle to a live `punktfunk/1` connection (QUIC control plane + UDP data plane, all
// pumped on internal threads).
//
// Thread contract: each plane (video `next_au`, audio `next_audio`, rumble `next_rumble`)
// may be pulled from its own thread, at most one thread per plane. The accessors only
// take shared references internally (per-plane mutexed borrow slots), so cross-plane
// concurrency is sound — never two threads on the *same* plane.
typedef struct PunktfunkConnection PunktfunkConnection;
#endif
// Opaque session handle. Pointer-only from C.
typedef struct PunktfunkSession PunktfunkSession;
// Forward-compatible session configuration. The caller MUST set `struct_size` to
// `sizeof(PunktfunkConfig)`; the core uses it to detect ABI skew.
typedef struct {
uint32_t struct_size;
// 0 = host, 1 = client.
uint32_t role;
// 1 = P1 (GameStream-compatible), 2 = P2 (`punktfunk/1`).
uint32_t phase;
// 0 = GF(2⁸), 1 = GF(2¹⁶).
uint32_t fec_scheme;
uint32_t fec_percent;
uint32_t max_data_per_block;
uint32_t shard_payload;
// Non-zero enables AES-128-GCM.
uint32_t encrypt;
uint8_t key[16];
uint8_t salt[4];
// Test hook for the loopback transport; 0 in production.
uint32_t loopback_drop_period;
// Largest encoded access unit the receiver will accept (bounds reassembler memory).
uint64_t max_frame_bytes;
} PunktfunkConfig;
// A reassembled access unit. `data`/`len` borrow session-owned memory valid until the
// next `punktfunk_client_poll_frame`/`punktfunk_session_free` on the same session.
typedef struct {
const uint8_t *data;
uintptr_t len;
uint32_t frame_index;
uint64_t pts_ns;
uint32_t flags;
} PunktfunkFrame;
// A single input event. `#[repr(C)]` — shared verbatim with the C ABI as
// `PunktfunkInputEvent`.
typedef struct {
PunktfunkInputKind kind;
uint8_t _pad[3];
// keycode / button id / axis id, depending on `kind`.
uint32_t code;
// x / dx / abs-x / axis-value / scroll-delta, depending on `kind`.
int32_t x;
// y / dy / abs-y, depending on `kind`.
int32_t y;
// modifier bitmask or gamepad index.
uint32_t flags;
} PunktfunkInputEvent;
// Snapshot of session counters.
typedef struct {
uint64_t frames_submitted;
uint64_t frames_completed;
uint64_t frames_dropped;
uint64_t packets_sent;
uint64_t packets_received;
uint64_t packets_dropped;
// Packets dropped on the host send path because the kernel buffer was full (WouldBlock) — the
// dominant loss mode at very high bitrate; distinct from `packets_dropped` (recv-side).
uint64_t packets_send_dropped;
uint64_t fec_recovered_shards;
uint64_t bytes_sent;
uint64_t bytes_received;
} PunktfunkStats;
#if defined(PUNKTFUNK_FEATURE_QUIC)
// One Opus audio packet pulled off a `punktfunk/1` connection (48 kHz stereo, 5 ms frames).
// `data` borrows connection memory until the next `punktfunk_connection_next_audio` call.
typedef struct {
const uint8_t *data;
uintptr_t len;
uint32_t seq;
uint64_t pts_ns;
} PunktfunkAudioPacket;
#endif
#if defined(PUNKTFUNK_FEATURE_QUIC)
// One DualSense HID-output feedback event a game wrote to the host's virtual pad
// ([`punktfunk_connection_next_hidout`]). `kind` selects which fields are meaningful — replay it
// on a real DualSense (lightbar color, player LEDs, or an adaptive-trigger effect via the
// platform's `GCDualSenseAdaptiveTrigger`-style API).
typedef struct {
// One of `PUNKTFUNK_HIDOUT_*`.
uint8_t kind;
// Gamepad index.
uint8_t pad;
// LED: lightbar red.
uint8_t r;
// LED: lightbar green.
uint8_t g;
// LED: lightbar blue.
uint8_t b;
// PlayerLeds: lit player indicators (low 5 bits).
uint8_t player_bits;
// Trigger: 0 = L2, 1 = R2.
uint8_t which;
// Trigger: number of valid bytes in `effect` (≤ `PUNKTFUNK_HID_EFFECT_MAX`).
uint8_t effect_len;
// Trigger: the raw DualSense trigger parameter block (mode + params).
uint8_t effect[11];
} PunktfunkHidOutput;
#endif
#if defined(PUNKTFUNK_FEATURE_QUIC)
// Static HDR metadata for an HDR session ([`punktfunk_connection_next_hdr_meta`]): SMPTE ST.2086
// mastering display colour volume + CEA-861.3 content light level. All fields are in the standard
// HDR10 SEI fixed-point units (primaries/white in 1/50000, luminance in 0.0001 cd/m²), ready for
// DXGI `DXGI_HDR_METADATA_HDR10` / Apple `CAEDRMetadata` / Android `KEY_HDR_STATIC_INFO`.
typedef struct {
// Display-primaries x-chromaticities in 1/50000 units, ST.2086 order [green, blue, red].
uint16_t display_primaries_x[3];
// Display-primaries y-chromaticities in 1/50000 units, ST.2086 order [green, blue, red].
uint16_t display_primaries_y[3];
// White-point x-chromaticity, 1/50000 units.
uint16_t white_point_x;
// White-point y-chromaticity, 1/50000 units.
uint16_t white_point_y;
// Max display mastering luminance, 0.0001 cd/m² units.
uint32_t max_display_mastering_luminance;
// Min display mastering luminance, 0.0001 cd/m² units.
uint32_t min_display_mastering_luminance;
// Maximum content light level (MaxCLL), nits. 0 = unknown.
uint16_t max_cll;
// Maximum frame-average light level (MaxFALL), nits. 0 = unknown.
uint16_t max_fall;
} PunktfunkHdrMeta;
#endif
#if defined(PUNKTFUNK_FEATURE_QUIC)
// One rich client→host input for the host's virtual DualSense
// ([`punktfunk_connection_send_rich_input`]): a touchpad contact or a motion sample. Set `kind`
// and the matching fields; the others are ignored.
typedef struct {
// One of `PUNKTFUNK_RICH_*`.
uint8_t kind;
// Gamepad index.
uint8_t pad;
// Touchpad: contact id (0 or 1).
uint8_t finger;
// Touchpad: 1 = finger down, 0 = lifted.
uint8_t active;
// Touchpad: normalized x, 0..=65535 across the touchpad.
uint16_t x;
// Touchpad: normalized y, 0..=65535 across the touchpad.
uint16_t y;
// Motion: gyro (pitch, yaw, roll), raw signed-16.
int16_t gyro[3];
// Motion: accelerometer (x, y, z), raw signed-16.
int16_t accel[3];
} PunktfunkRichInput;
#endif
// A speed-test measurement, filled by [`punktfunk_connection_probe_result`]. `done` is 0 until
// the host's end-of-burst report lands, then 1 (the numbers are final). `throughput_kbps` is the
// delivered wire throughput to drive a bitrate choice from; `loss_pct` is the link loss and
// `host_drop_pct` the host-side send-buffer drop (raise `net.core.wmem_max`) — they're measured
// separately so a host that can't keep up reads differently from a lossy link.
typedef struct {
// 1 once the host's end-of-burst report arrived (measurement final); else 0 (partial).
uint8_t done;
// Delivered wire bytes (header + shard) / packets the client received during the burst.
uint64_t recv_bytes;
uint32_t recv_packets;
// Application goodput bytes / access units the host offered.
uint64_t host_bytes;
uint32_t host_packets;
// The host's measured burst duration, milliseconds (the throughput denominator).
uint32_t elapsed_ms;
// Delivered wire throughput = `recv_bytes * 8 / elapsed_ms` (kilobits/second).
uint32_t throughput_kbps;
// Link loss `(wire_packets_sent recv_packets) / wire_packets_sent` as a percentage.
float loss_pct;
// Host-side send-buffer drop `send_dropped / (wire_packets_sent + send_dropped)`, percent.
float host_drop_pct;
// Wire packets the host put on the link, and the ones its send buffer dropped (raw counts).
uint32_t wire_packets_sent;
uint32_t send_dropped;
} PunktfunkProbeResult;
#ifdef __cplusplus
extern "C" {
#endif // __cplusplus
// Current ABI version. Mismatch with [`crate::ABI_VERSION`] means incompatible core.
uint32_t punktfunk_abi_version(void);
// Create a session over a real UDP transport (`local`/`peer` are `host:port` strings).
// Returns NULL on error.
//
// # Safety
// `cfg`, `local`, `peer` must be valid pointers; the strings must be NUL-terminated.
PunktfunkSession *punktfunk_session_new(const PunktfunkConfig *cfg,
const char *local,
const char *peer);
// Create a connected host+client session pair sharing an in-process loopback
// transport. Test/dev only — exercises the full FEC + framing path without a network.
//
// # Safety
// All four pointers must be valid; the two out-params receive owned handles.
PunktfunkStatus punktfunk_test_loopback_pair(const PunktfunkConfig *host_cfg,
const PunktfunkConfig *client_cfg,
PunktfunkSession **out_host,
PunktfunkSession **out_client);
// Free a session handle. Safe to call with NULL.
//
// # Safety
// `s` must be a handle from `punktfunk_session_new`/`punktfunk_test_loopback_pair`, freed once.
void punktfunk_session_free(PunktfunkSession *s);
// Host: FEC-protect, packetize, seal and send one encoded access unit.
//
// # Safety
// `s` is a valid host handle; `data` points to `len` readable bytes (or `len == 0`).
PunktfunkStatus punktfunk_host_submit_frame(PunktfunkSession *s,
const uint8_t *data,
uintptr_t len,
uint64_t pts_ns,
uint32_t flags);
// Client: poll for the next reassembled access unit. Returns [`PunktfunkStatus::NoFrame`]
// when nothing is ready yet. On `Ok`, `*out` borrows session memory until the next poll.
//
// # Safety
// `s` is a valid client handle; `out` points to a writable `PunktfunkFrame`.
PunktfunkStatus punktfunk_client_poll_frame(PunktfunkSession *s, PunktfunkFrame *out);
// Client: serialize and send one input event to the host.
//
// # Safety
// `s` is a valid client handle; `ev` points to a valid [`InputEvent`].
PunktfunkStatus punktfunk_send_input(PunktfunkSession *s, const PunktfunkInputEvent *ev);
// Register the host-side input callback (pass a NULL fn pointer to clear). The callback
// fires from within [`punktfunk_host_poll_input`], on the calling thread.
//
// # Safety
// `s` is a valid host handle; `user` is passed back verbatim to `cb`.
PunktfunkStatus punktfunk_set_input_callback(PunktfunkSession *s,
void (*cb)(const PunktfunkInputEvent *event, void *user),
void *user);
// Host: drain all pending input events, invoking the registered callback for each.
// Returns the count dispatched (≥ 0), or a negative [`PunktfunkStatus`] on error.
//
// # Safety
// `s` is a valid host handle.
int32_t punktfunk_host_poll_input(PunktfunkSession *s);
// Copy session counters into `*out`.
//
// # Safety
// `s` is a valid handle; `out` points to a writable `PunktfunkStats`.
PunktfunkStatus punktfunk_get_stats(PunktfunkSession *s, PunktfunkStats *out);
#if defined(PUNKTFUNK_FEATURE_QUIC)
// Trust: `pin_sha256` (NULL or 32 bytes) is the expected SHA-256 fingerprint of the host's
// certificate — a mismatching host is rejected. NULL = trust on first use; persist the
// fingerprint written to `observed_sha256_out` (NULL or 32 bytes, filled on success) and
// pass it as the pin on every later connect.
//
// Identity: `client_cert_pem`/`client_key_pem` (both NULL, or both NUL-terminated PEM
// strings — see [`punktfunk_generate_identity`]) are presented via TLS client auth so a
// host can recognize this client once paired ([`punktfunk_pair`]). NULL = anonymous;
// hosts running `--require-pairing` reject anonymous sessions.
//
// # Safety
// `host` is a NUL-terminated UTF-8 string (IP or hostname resolvable by the platform);
// `pin_sha256`/`observed_sha256_out` are each NULL or valid for 32 bytes;
// `client_cert_pem`/`client_key_pem` are each NULL or NUL-terminated UTF-8.
PunktfunkConnection *punktfunk_connect(const char *host,
uint16_t port,
uint32_t width,
uint32_t height,
uint32_t refresh_hz,
const uint8_t *pin_sha256,
uint8_t *observed_sha256_out,
const char *client_cert_pem,
const char *client_key_pem,
uint32_t timeout_ms);
#endif
#if defined(PUNKTFUNK_FEATURE_QUIC)
// Like [`punktfunk_connect`], but requests a specific `compositor` backend on the host (one of
// the `PUNKTFUNK_COMPOSITOR_*` values). `PUNKTFUNK_COMPOSITOR_AUTO` (or any unrecognized value)
// lets the host decide; a concrete value is honored only if available, else the host falls back
// to auto-detect. The resolved choice is logged host-side and returned over the protocol.
// Equivalent to [`punktfunk_connect_ex2`] with `gamepad = PUNKTFUNK_GAMEPAD_AUTO`.
//
// # Safety
// Same as [`punktfunk_connect`].
PunktfunkConnection *punktfunk_connect_ex(const char *host,
uint16_t port,
uint32_t width,
uint32_t height,
uint32_t refresh_hz,
uint32_t compositor,
const uint8_t *pin_sha256,
uint8_t *observed_sha256_out,
const char *client_cert_pem,
const char *client_key_pem,
uint32_t timeout_ms);
#endif
#if defined(PUNKTFUNK_FEATURE_QUIC)
// Like [`punktfunk_connect_ex`], but additionally requests which virtual `gamepad` backend the
// host creates for this session's pads (one of the `PUNKTFUNK_GAMEPAD_*` values).
// `PUNKTFUNK_GAMEPAD_AUTO` (or any unrecognized value) lets the host decide (its
// `PUNKTFUNK_GAMEPAD` env var, else X-Box 360); a concrete value is honored only if that
// backend is available on the host. The resolved choice is readable via
// [`punktfunk_connection_gamepad`] — only a DualSense session emits HID-output feedback.
//
// # Safety
// Same as [`punktfunk_connect`].
PunktfunkConnection *punktfunk_connect_ex2(const char *host,
uint16_t port,
uint32_t width,
uint32_t height,
uint32_t refresh_hz,
uint32_t compositor,
uint32_t gamepad,
const uint8_t *pin_sha256,
uint8_t *observed_sha256_out,
const char *client_cert_pem,
const char *client_key_pem,
uint32_t timeout_ms);
#endif
#if defined(PUNKTFUNK_FEATURE_QUIC)
// Like [`punktfunk_connect_ex2`], but additionally requests the video encoder `bitrate_kbps`
// (kilobits per second). `0` lets the host pick its default; any other value is clamped to the
// host's supported range. After a speed test ([`punktfunk_connection_speed_test`]) a client can
// reconnect (or pick at connect time) with the measured rate. The value the host actually
// configured is readable via [`punktfunk_connection_bitrate`].
//
// # Safety
// Same as [`punktfunk_connect`].
PunktfunkConnection *punktfunk_connect_ex3(const char *host,
uint16_t port,
uint32_t width,
uint32_t height,
uint32_t refresh_hz,
uint32_t compositor,
uint32_t gamepad,
uint32_t bitrate_kbps,
const uint8_t *pin_sha256,
uint8_t *observed_sha256_out,
const char *client_cert_pem,
const char *client_key_pem,
uint32_t timeout_ms);
#endif
#if defined(PUNKTFUNK_FEATURE_QUIC)
// Like [`punktfunk_connect_ex3`], but additionally asks the host to launch a library title in
// this session. `launch_id` is a store-qualified [`crate::library`-style] id as returned by the
// host's `GET /api/v1/library` (`steam:<appid>` / `custom:<id>`); the host resolves it against
// its OWN library and runs the matching recipe — the client never sends a raw command. `NULL`
// (or an empty / unknown id) ⇒ the host's default session, no game launched.
//
// # Safety
// Same as [`punktfunk_connect`]; `launch_id`, when non-NULL, must be a NUL-terminated C string.
PunktfunkConnection *punktfunk_connect_ex4(const char *host,
uint16_t port,
uint32_t width,
uint32_t height,
uint32_t refresh_hz,
uint32_t compositor,
uint32_t gamepad,
uint32_t bitrate_kbps,
const char *launch_id,
const uint8_t *pin_sha256,
uint8_t *observed_sha256_out,
const char *client_cert_pem,
const char *client_key_pem,
uint32_t timeout_ms);
#endif
#if defined(PUNKTFUNK_FEATURE_QUIC)
// Like [`punktfunk_connect_ex4`], but additionally advertises the embedder's video decode/present
// capabilities as `video_caps` — a bitfield of `PUNKTFUNK_VIDEO_CAP_10BIT` (can decode 10-bit
// Main10) and `PUNKTFUNK_VIDEO_CAP_HDR` (can present BT.2020 PQ HDR10). The host upgrades to a
// 10-bit / HDR encode ONLY when the matching bit is set (and the host opted in); `0` keeps the
// 8-bit BT.709 SDR stream. After connecting, read the resolved colour via
// [`punktfunk_connection_color_info`] and drain the mastering metadata via
// [`punktfunk_connection_next_hdr_meta`].
//
// # Safety
// Same as [`punktfunk_connect`]; `launch_id`, when non-NULL, must be a NUL-terminated C string.
PunktfunkConnection *punktfunk_connect_ex5(const char *host,
uint16_t port,
uint32_t width,
uint32_t height,
uint32_t refresh_hz,
uint32_t compositor,
uint32_t gamepad,
uint32_t bitrate_kbps,
uint8_t video_caps,
const char *launch_id,
const uint8_t *pin_sha256,
uint8_t *observed_sha256_out,
const char *client_cert_pem,
const char *client_key_pem,
uint32_t timeout_ms);
#endif
#if defined(PUNKTFUNK_FEATURE_QUIC)
// Generate a persistent client identity: a self-signed certificate + private key, both
// PEM, NUL-terminated, written into the caller's buffers. Generate ONCE, store both
// strings (Keychain etc.), pass them to [`punktfunk_pair`] and every
// [`punktfunk_connect`] — the certificate's fingerprint is how hosts recognize this
// client. 4096-byte buffers are ample.
//
// # Safety
// `cert_pem_out` is writable for `cert_cap` bytes; `key_pem_out` for `key_cap`.
PunktfunkStatus punktfunk_generate_identity(char *cert_pem_out,
uintptr_t cert_cap,
char *key_pem_out,
uintptr_t key_cap);
#endif
#if defined(PUNKTFUNK_FEATURE_QUIC)
// Run the PIN pairing ceremony against a host (see the protocol docs in punktfunk-core):
// the host displays a short PIN; the user types it into the client app, which passes it
// here. On success the host has stored this client's identity, the now-verified host
// fingerprint is written to `host_sha256_out` (32 bytes) — persist it and pass it as
// `pin_sha256` to [`punktfunk_connect`] from then on. Returns
// [`PunktfunkStatus::Crypto`] for a wrong PIN.
//
// # Safety
// `host`/`client_cert_pem`/`client_key_pem`/`pin`/`name` are NUL-terminated UTF-8;
// `host_sha256_out` is writable for 32 bytes.
PunktfunkStatus punktfunk_pair(const char *host,
uint16_t port,
const char *client_cert_pem,
const char *client_key_pem,
const char *pin,
const char *name,
uint8_t *host_sha256_out,
uint32_t timeout_ms);
#endif
#if defined(PUNKTFUNK_FEATURE_QUIC)
// Pull the next reassembled access unit, waiting up to `timeout_ms`. Returns
// [`PunktfunkStatus::NoFrame`] on timeout and [`PunktfunkStatus::Closed`] once the session ended.
// On `Ok`, `*out` borrows connection memory **until the next `next_au` call** on this
// handle (the audio/rumble planes do not invalidate it).
//
// # Safety
// `c` is a valid connection handle; `out` is writable. At most one thread pulls video —
// it may run concurrently with one audio-pulling and one rumble-pulling thread.
PunktfunkStatus punktfunk_connection_next_au(PunktfunkConnection *c,
PunktfunkFrame *out,
uint32_t timeout_ms);
#endif
#if defined(PUNKTFUNK_FEATURE_QUIC)
// Pull the next Opus audio packet, waiting up to `timeout_ms`. Returns
// [`PunktfunkStatus::NoFrame`] on timeout and [`PunktfunkStatus::Closed`] once the session ended.
// On `Ok`, `out->data` borrows connection memory **until the next audio call** on this
// handle (independent of the video slot). Drain from a dedicated audio thread — packets
// arrive every 5 ms and the internal queue holds 320 ms.
//
// # Safety
// `c` is a valid connection handle; `out` is writable. At most one thread pulls audio —
// it may run concurrently with the video/rumble pullers.
PunktfunkStatus punktfunk_connection_next_audio(PunktfunkConnection *c,
PunktfunkAudioPacket *out,
uint32_t timeout_ms);
#endif
#if defined(PUNKTFUNK_FEATURE_QUIC)
// Pull the next rumble (force-feedback) update, waiting up to `timeout_ms`. Amplitudes
// are 0..0xFFFF (`low` = low-frequency motor, `high` = high-frequency), `(0, 0)` = stop.
// Same timeout/closed semantics as [`punktfunk_connection_next_audio`].
//
// # Safety
// `c` is a valid connection handle; out pointers are writable (NULLs are skipped). At
// most one thread pulls rumble — it may run concurrently with the video/audio pullers.
PunktfunkStatus punktfunk_connection_next_rumble(PunktfunkConnection *c,
uint16_t *pad,
uint16_t *low,
uint16_t *high,
uint32_t timeout_ms);
#endif
#if defined(PUNKTFUNK_FEATURE_QUIC)
// Pull the next DualSense HID-output feedback event (lightbar / player LEDs / adaptive trigger)
// the host's virtual pad received from a game, into `*out`. [`PunktfunkStatus::NoFrame`] on
// timeout, [`PunktfunkStatus::Closed`] once the session ended. Only the DualSense host backend
// emits these. Same threading rules as [`punktfunk_connection_next_rumble`] (one puller, may run
// alongside the other planes).
//
// # Safety
// `c` is a valid connection handle; `out` is writable for one `PunktfunkHidOutput`.
PunktfunkStatus punktfunk_connection_next_hidout(PunktfunkConnection *c,
PunktfunkHidOutput *out,
uint32_t timeout_ms);
#endif
#if defined(PUNKTFUNK_FEATURE_QUIC)
// Pull the next static HDR metadata update (ST.2086 mastering display + content light level) for
// an HDR session, into `*out`. [`PunktfunkStatus::NoFrame`] on timeout, [`PunktfunkStatus::Closed`]
// once the session ended. The host sends one near session start and re-sends it on mastering
// changes / keyframes; apply the latest to the display (`SetHDRMetaData` / `CAEDRMetadata` /
// `KEY_HDR_STATIC_INFO`). Only an HDR session (`punktfunk_connection_color_info` reports a PQ
// transfer) ever emits these. Same threading rules as [`punktfunk_connection_next_rumble`] (one
// puller, may run alongside the other planes).
//
// # Safety
// `c` is a valid connection handle; `out` is writable for one `PunktfunkHdrMeta`.
PunktfunkStatus punktfunk_connection_next_hdr_meta(PunktfunkConnection *c,
PunktfunkHdrMeta *out,
uint32_t timeout_ms);
#endif
#if defined(PUNKTFUNK_FEATURE_QUIC)
// Read the session's resolved colour signalling + encode bit depth (from the host's Welcome).
// Each out pointer is filled when non-NULL: `primaries`/`transfer`/`matrix` are CICP code points
// (BT.709 = 1; BT.2020 = 9; PQ transfer = 16, HLG = 18; BT.2020-NCL matrix = 9), `full_range` is
// 0 (limited) or 1 (full), `bit_depth` is 8 or 10. A `transfer` of 16/18 means HDR — configure an
// HDR present path and drain [`punktfunk_connection_next_hdr_meta`]. Available immediately after a
// successful connect (these don't change without a reconfigure).
//
// # Safety
// `c` is a valid connection handle; each out pointer is NULL or writable for its scalar.
PunktfunkStatus punktfunk_connection_color_info(PunktfunkConnection *c,
uint8_t *primaries,
uint8_t *transfer,
uint8_t *matrix,
uint8_t *full_range,
uint8_t *bit_depth);
#endif
#if defined(PUNKTFUNK_FEATURE_QUIC)
// Send one input event to the host as a QUIC datagram (non-blocking enqueue).
//
// # Safety
// `c` is a valid connection handle; `ev` points to a valid [`InputEvent`].
PunktfunkStatus punktfunk_connection_send_input(PunktfunkConnection *c,
const PunktfunkInputEvent *ev);
#endif
#if defined(PUNKTFUNK_FEATURE_QUIC)
// Send one Opus mic frame to the host as a QUIC datagram (48 kHz; the host decodes it into a
// virtual microphone source its apps can record). Non-blocking enqueue; the host uses `seq`/
// `pts_ns` (the caller's own counters) only for diagnostics. `opus_data`/`len` may be empty
// (a DTX silence frame). The data is copied; the caller may reuse the buffer after this returns.
//
// # Safety
// `c` is a valid connection handle; `opus_data` is valid for `len` bytes (or `len == 0`).
PunktfunkStatus punktfunk_connection_send_mic(PunktfunkConnection *c,
const uint8_t *opus_data,
uintptr_t len,
uint32_t seq,
uint64_t pts_ns);
#endif
#if defined(PUNKTFUNK_FEATURE_QUIC)
// Send one rich input event (DualSense touchpad contact or motion sample) to the host as a QUIC
// datagram (non-blocking enqueue). The host applies it to its virtual DualSense pad — a no-op
// unless the host runs the DualSense gamepad backend. [`PunktfunkStatus::InvalidArg`] on an
// unknown `kind`.
//
// # Safety
// `c` is a valid connection handle; `rich` points to a valid [`PunktfunkRichInput`].
PunktfunkStatus punktfunk_connection_send_rich_input(PunktfunkConnection *c,
const PunktfunkRichInput *rich);
#endif
#if defined(PUNKTFUNK_FEATURE_QUIC)
// The currently active session mode — the Welcome's, until an accepted
// [`punktfunk_connection_request_mode`] switches it. Safe any time after connect.
//
// # Safety
// `c` is a valid connection handle; out pointers are writable (NULLs are skipped).
PunktfunkStatus punktfunk_connection_mode(const PunktfunkConnection *c,
uint32_t *width,
uint32_t *height,
uint32_t *refresh_hz);
#endif
#if defined(PUNKTFUNK_FEATURE_QUIC)
// The virtual gamepad backend the host actually resolved for this session (one of the
// `PUNKTFUNK_GAMEPAD_*` values; the `Welcome`'s echo of the [`punktfunk_connect_ex2`]
// preference). `PUNKTFUNK_GAMEPAD_AUTO` = an older host that didn't say — assume X-Box 360,
// no HID-output feedback. Safe any time after connect.
//
// # Safety
// `c` is a valid connection handle; `gamepad` is writable (NULL is skipped).
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`
// was requested. `0` = an older host that didn't report it. Safe any time after connect.
//
// # Safety
// `c` is a valid connection handle; `bitrate_kbps` is writable (NULL is skipped).
PunktfunkStatus punktfunk_connection_bitrate(const PunktfunkConnection *c, uint32_t *bitrate_kbps);
#endif
#if defined(PUNKTFUNK_FEATURE_QUIC)
// The host↔client wall-clock offset (nanoseconds, **host minus client**) measured by the
// connect-time skew handshake. Add it to a local receive/present timestamp (same realtime clock,
// `CLOCK_REALTIME` / `gettimeofday`-epoch nanoseconds) to express that instant in the host's
// capture clock — the clock the per-access-unit `pts_ns` is stamped in — so glass-to-glass latency
// (e.g. present-time minus `pts_ns`) is valid across machines. `0` = no correction: either an older
// host that didn't answer the handshake, or genuinely synchronized clocks. Safe any time after
// connect.
//
// # Safety
// `c` is a valid connection handle; `offset_ns` is writable (NULL is skipped).
PunktfunkStatus punktfunk_connection_clock_offset_ns(const PunktfunkConnection *c,
int64_t *offset_ns);
#endif
#if defined(PUNKTFUNK_FEATURE_QUIC)
// Ask the host to switch the live session to `width`x`height`@`refresh_hz` without
// reconnecting (window resized, refresh changed). Non-blocking enqueue: on acceptance the
// stream continues at the new mode — the first new-mode access unit is an IDR with
// in-band parameter sets (rebuild the decoder from it) — and
// [`punktfunk_connection_mode`] reflects the switch. A rejected request leaves the
// session unchanged.
//
// # Safety
// `c` is a valid connection handle.
PunktfunkStatus punktfunk_connection_request_mode(const PunktfunkConnection *c,
uint32_t width,
uint32_t height,
uint32_t refresh_hz);
#endif
#if defined(PUNKTFUNK_FEATURE_QUIC)
// Ask the host's encoder to emit a fresh IDR keyframe now — client recovery when the
// decoder has stalled (the infinite-GOP stream sends one opening IDR then P-frames only, so
// a wedged decoder would otherwise freeze until the next loss-triggered recovery keyframe).
// Non-blocking, fire-and-forget; the recovered keyframe is the only ack. The caller should
// THROTTLE — the decode stays wedged for several frames until the IDR lands, so requesting
// every frame would flood the control stream.
//
// # Safety
// `c` is a valid connection handle.
PunktfunkStatus punktfunk_connection_request_keyframe(const PunktfunkConnection *c);
#endif
#if defined(PUNKTFUNK_FEATURE_QUIC)
// Cumulative access units the host→client reassembler dropped as unrecoverable (FEC couldn't
// rebuild them). A video loop polls this and calls [`punktfunk_connection_request_keyframe`]
// when it climbs — the correct loss trigger under the host's infinite GOP, where unrecoverable
// loss yields reference-missing delta frames the decoder *silently conceals* (frozen / garbage
// picture, no decode error), so a decode-error trigger rarely fires. Monotonic for the session;
// compare against the last observed value. Writes 0 to `out` on a NULL connection.
//
// # Safety
// `c` is a valid connection handle; `out` is writable (NULL is skipped).
PunktfunkStatus punktfunk_connection_frames_dropped(const PunktfunkConnection *c, uint64_t *out);
#endif
#if defined(PUNKTFUNK_FEATURE_QUIC)
// Start a bandwidth speed test: ask the host to burst filler over the data plane at
// `target_kbps` of goodput for `duration_ms` (each clamped host-side to ≤ 3 Gbps / ≤ 5 s),
// *briefly pausing video*. Non-blocking — poll [`punktfunk_connection_probe_result`] until its
// `done` field is 1. Starting a probe resets any prior measurement.
//
// # Safety
// `c` is a valid connection handle.
PunktfunkStatus punktfunk_connection_speed_test(const PunktfunkConnection *c,
uint32_t target_kbps,
uint32_t duration_ms);
#endif
#if defined(PUNKTFUNK_FEATURE_QUIC)
// Read the current speed-test measurement into `*out` (partial until `out->done == 1`). Safe to
// poll repeatedly after [`punktfunk_connection_speed_test`]; before any probe it reports zeros.
//
// # Safety
// `c` is a valid connection handle; `out` is writable for one `PunktfunkProbeResult` (NULL is an
// error).
PunktfunkStatus punktfunk_connection_probe_result(const PunktfunkConnection *c,
PunktfunkProbeResult *out);
#endif
#if defined(PUNKTFUNK_FEATURE_QUIC)
// Close the connection and free the handle (joins the internal threads). NULL is a no-op.
//
// # Safety
// `c` was returned by [`punktfunk_connect`] and is not used after this call.
void punktfunk_connection_close(PunktfunkConnection *c);
#endif
#ifdef __cplusplus
} // extern "C"
#endif // __cplusplus
#endif /* PUNKTFUNK_CORE_H */