99b4de32ee
ci / web (push) Failing after 40s
ci / rust (push) Successful in 1m6s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 13s
apple / swift (push) Successful in 1m20s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 4s
ci / docs-site (push) Failing after 46s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 18s
docker / deploy-docs (push) Successful in 16s
An identified-but-unpaired device that knocks on a pairing-required host is now
held as a pending request the operator approves from the web console — pairing it
with no PIN fetched out of band — instead of a flat reject.
- core: Hello gains an optional trailing device name (len u8 || UTF-8, ≤64,
same trailing-back-compat pattern as compositor/gamepad/bitrate). client-rs
--name sends it; the connector sends None (fingerprint-derived label).
- native_pairing: in-memory pending queue (note_pending dedups by fingerprint,
evicts the least-recently-active past a 32 cap, 10-min TTL); approve_pending
pins the fingerprint, deny drops it. Names are sanitized (strip control/ANSI/
bidi — untrusted wire input); add()/remove() roll back in-memory on a persist
failure; pairing clears any stale pending knock.
- m3: the require_pairing gate records the knock (sanitized label) before
rejecting; anonymous (certless) clients record nothing.
- mgmt: GET /native/pending, POST /native/pending/{id}/approve (optional {name})
and /deny; OpenAPI + tests; docs/api/openapi.json regenerated.
- web: a "Waiting for approval" section on the Pairing page (live-poll, Approve/
Deny, error-surfaced via QueryState); en+de strings.
- Also completes an in-progress NativeClient Sync refactor (receivers behind
per-plane mutexes) that was left half-applied in the tree.
Adversarially reviewed (4 lenses + 3-vote verify); the confirmed findings are
fixed here. Validated live on the GNOME box: knock (with a wire name, and a
malicious ANSI/bidi name that got neutralized) → pending → approve → the same
identity streams real video. Full workspace tests + clippy + fmt green; web tsc
clean. Roadmap §8b-1 marked done; §8b-2 (peer-push approval) is the client
follow-up. See docs-site pairing page.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
874 lines
36 KiB
C
874 lines
36 KiB
C
/* 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
|
||
|
||
// 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)
|
||
// 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)
|
||
// 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 [`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).
|
||
#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;
|
||
// 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)
|
||
// 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
|
||
// measured goodput to drive a bitrate choice from; `loss_pct` is the delivery loss at that rate.
|
||
typedef struct {
|
||
// 1 once the host's end-of-burst report arrived (measurement final); else 0 (partial).
|
||
uint8_t done;
|
||
// Probe payload bytes / packets the client received.
|
||
uint64_t recv_bytes;
|
||
uint32_t recv_packets;
|
||
// Probe payload bytes / packets the host reported sending.
|
||
uint64_t host_bytes;
|
||
uint32_t host_packets;
|
||
// Client-measured receive window (first→last probe AU), milliseconds.
|
||
uint32_t elapsed_ms;
|
||
// Measured goodput = `recv_bytes * 8 / elapsed_ms` (kilobits/second).
|
||
uint32_t throughput_kbps;
|
||
// Delivery loss `(host_bytes - recv_bytes) / host_bytes` as a percentage (0 if unknown).
|
||
float loss_pct;
|
||
} 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)
|
||
// 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)
|
||
// 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)
|
||
// 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)
|
||
// 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)
|
||
// 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 */
|