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>
This commit is contained in:
@@ -55,6 +55,21 @@
|
||||
// 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
|
||||
|
||||
@@ -94,6 +109,11 @@
|
||||
|
||||
#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
|
||||
|
||||
@@ -501,6 +521,7 @@ PunktfunkConnection *punktfunk_connect(const char *host,
|
||||
// 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`].
|
||||
@@ -517,6 +538,30 @@ PunktfunkConnection *punktfunk_connect_ex(const char *host,
|
||||
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
|
||||
@@ -659,6 +704,17 @@ PunktfunkStatus punktfunk_connection_mode(const PunktfunkConnection *c,
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user