fix(gamepad): working per-session SwDeviceCreate for the Windows DualSense

create_swdevice now succeeds. The two requirements (each E_INVALIDARG otherwise): the
enumerator name must have no underscore (use "punktfunk"), and the completion callback is
mandatory (the docs mark pCallback [in], not optional -- NULL is rejected). Back on the
typed windows-rs SwDeviceCreate (a raw-FFI diagnosis confirmed it's the OS, not the
binding), parameterized by pad index (instance pf_pad_<index>), waiting on the callback.
Per-session device: created on connect, SwDeviceClose'd on drop -- no leftovers, no phantom.

Live-verified on the RTX box: device materializes, the UMDF driver binds, SDL3 identifies it
as a PS5 ("DualSense Wireless Controller"), input flows; removed on disconnect. The
dualsense-windows-test CLI now cycles input + prints any 0x02 feedback for diagnosis.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-22 10:34:58 +00:00
parent 6a501f484a
commit 6db3525e29
2 changed files with 70 additions and 48 deletions
+31 -16
View File
@@ -224,26 +224,41 @@ fn real_main() -> Result<()> {
kind: 2,
capabilities: 0,
});
// ls_x 16384 → report byte1 0xC0; BTN_A (Cross) → report byte8 0x28.
mgr.handle(&GamepadEvent::State(GamepadFrame {
index: 0,
active_mask: 1,
buttons: punktfunk_core::input::gamepad::BTN_A,
left_trigger: 0,
right_trigger: 0,
ls_x: 16384,
ls_y: 0,
rs_x: 0,
rs_y: 0,
}));
println!(
"virtual DualSense created via SwDeviceCreate (VID 054C/PID 0CE6). Holding {secs}s \
verify Get-PnpDevice VID_054C + a HID read (expect byte1=0xC0, byte8=0x28)."
"virtual DualSense up — cycling Cross + sweeping the left stick for {secs}s. Watch it \
in joy.cpl / Steam / a game; any rumble / lightbar / trigger the game sends prints below."
);
let deadline = Instant::now() + Duration::from_secs(secs);
let (mut i, mut last) = (0i32, Instant::now());
while Instant::now() < deadline {
mgr.pump(|_, _, _| {}, |_| {});
std::thread::sleep(Duration::from_millis(50));
// Surface a game's feedback: rumble (universal) + lightbar / player-LED / adaptive
// triggers (DualSense-only) coming back over the shared section.
mgr.pump(
|pad, lo, hi| println!(" rumble from game: pad={pad} low={lo} high={hi}"),
|o| println!(" hid output from game: {o:?}"),
);
if last.elapsed() >= Duration::from_millis(400) {
last = Instant::now();
i += 1;
let buttons = if i % 2 == 0 {
punktfunk_core::input::gamepad::BTN_A // Cross
} else {
0
};
let lx = (((i % 64) - 32) * 1024) as i16; // sweep left stick X
mgr.handle(&GamepadEvent::State(GamepadFrame {
index: 0,
active_mask: 1,
buttons,
left_trigger: 0,
right_trigger: 0,
ls_x: lx,
ls_y: 0,
rs_x: 0,
rs_y: 0,
}));
}
std::thread::sleep(Duration::from_millis(15));
}
println!("dualsense-windows-test: done (devnode removed)");
Ok(())