Files
punktfunk/include/punktfunk_core.h
T
enricobuehler 1d605fb781 feat(gamepad): controller discovery + client-negotiated pad type + rich DualSense end to end
The Apple client grows full gamepad support and punktfunk/1 learns to negotiate
the virtual pad type:

- Protocol: Hello carries a GamepadPref byte (offset 21, the same trailing-byte
  back-compat pattern as the compositor; echoed resolved in Welcome at 54).
  Host precedence: explicit client choice > PUNKTFUNK_GAMEPAD env > Xbox 360,
  DualSense (UHID) only where available. ABI: punktfunk_connect_ex2 +
  punktfunk_connection_gamepad (connect_ex delegates; ABI_VERSION stays 2 — the
  trailing byte IS the compat mechanism). punktfunk-client-rs gets --gamepad.

- Swift client: GamepadManager (app-lifetime discovery + selection — Settings
  lists every controller with capabilities/battery/"In use"; exactly ONE pad
  forwards as pad 0, auto = most recently connected, or pinned), GamepadCapture
  (snapshot-diff button/axis events, DualSense touchpad + ~250 Hz motion on the
  rich-input plane, held state released on switch/deactivate/stop),
  GamepadFeedback (rumble → CoreHaptics per-handle engines; lightbar →
  GCDeviceLight; player LEDs → playerIndex; adaptive-trigger blocks → the
  table-driven DualSenseTriggerEffect parser → GCDualSenseAdaptiveTrigger,
  exact for the 10-zone positional modes). The pad type auto-resolves from the
  physical controller at connect time, user-overridable in Settings.

- Host DualSense fixes surfaced by adversarial review against hid-playstation /
  SDL / Nielk1 ground truth: input-report sensor/touch offsets were off by one
  (the kernel read garbage motion + phantom touches), the L2/R2 trigger blocks
  were swapped (the report is right-trigger-first), feedback now gates on the
  report's valid-flags (a plain rumble write no longer blanks lightbar/
  triggers), and the touchpad rescale clamps to the advertised ABS_MT extents.

- Tests: Hello/Welcome trailing-byte back-compat, pick_gamepad precedence,
  byte-exact input-report layout, valid-flag gating, per-mode trigger-parser
  table (incl. packed 3-bit zones), wire conversions, and a scripted loopback
  feedback burst (PUNKTFUNK_TEST_FEEDBACK=1) asserted through the xcframework
  on the rumble + HID-output planes.

Validated: cargo test/clippy/fmt green on macOS + Linux (61 host tests), swift
build/test green, test-loopback.sh green, tvOS/iOS targets compile. DualSense
motion sign/scale is derived from the calibration blob, not yet live-verified
(constants isolated in GamepadWire).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 16:28:33 +02:00

747 lines
30 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
// 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
// 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)
// 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 [`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).
#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
// 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.
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)
// 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;
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)
// 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
#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)
// 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`.
//
// 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)
// 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)
// 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)
// 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)
// 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 */