feat(dualsense): Phase C/D/E — virtual DualSense routing + 0xCC/0xCD planes + C ABI
ci / rust (push) Has been cancelled
ci / rust (push) Has been cancelled
PUNKTFUNK_GAMEPAD=dualsense now routes a session's gamepad through a real virtual
DualSense (UHID + hid-playstation) end to end:
- host: a `PadBackend` enum (m3.rs) selects `GamepadManager` (uinput xpad, default)
or the new `DualSenseManager` (dualsense.rs) per session. The manager keeps each
pad's full DsState so touchpad + motion (rich-input plane) persist across
button/stick frames, and services the !Send /dev/uhid fd only on the input thread
(which cycles <=4ms, so the GET_REPORT init handshake completes).
- feedback: `service()` now returns `DsFeedback { hidout, rumble }`. Motor rumble
stays on the universal 0xCA plane (so non-DualSense clients still feel it; manager
dedups change); lightbar / player LEDs / adaptive-trigger effects ride the new
0xCD HID-output plane (host->client) as `HidOutput`.
- rich input: touchpad contacts + motion ride the 0xCC plane (client->host) as
`RichInput`, applied via `DualSenseManager::apply_rich` (merged with button state;
touch normalized 0..65535 -> the touchpad resolution).
- connector + C ABI: `NativeClient::next_hidout` / `send_rich_input`, exported as
`punktfunk_connection_next_hidout` (-> PunktfunkHidOutput) and
`punktfunk_connection_send_rich_input` (<- PunktfunkRichInput); header regenerated.
- reference client: `--rich-input-test` drives the DualSense touchpad + motion and
logs the 0xCD feedback that comes back.
Validated live on-box: a synthetic-source m3-host + client-rs created the real
kernel DualSense, drove 0xCC, and decoded 12 live 0xCD events (the kernel's actual
lightbar/trigger init reports) with the data plane unaffected (600/600 frames).
Adversarial review fixes folded in: the input loop no longer skips the rich drain +
feedback pump on a dropped gamepad event, and the touch contact id is clamped to its
slot. Remaining: the Apple client renders triggers/rumble on a real DualSense.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -19,6 +19,24 @@
|
||||
// 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
|
||||
@@ -319,6 +337,57 @@ typedef struct {
|
||||
} 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
|
||||
@@ -528,6 +597,20 @@ PunktfunkStatus punktfunk_connection_next_rumble(PunktfunkConnection *c,
|
||||
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).
|
||||
//
|
||||
@@ -552,6 +635,18 @@ PunktfunkStatus punktfunk_connection_send_mic(PunktfunkConnection *c,
|
||||
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.
|
||||
|
||||
Reference in New Issue
Block a user