feat(gamepad): DualSense Edge backend — Linux UHID + Windows UMDF (N1)
The plain-DualSense transport + report codec under the Edge USB identity (054C:0DF2, verbatim 389-byte real-device descriptor cross-checked against the raw usbmon capture + hhd's production virtual Edge), so the wire back grips (BTN_PADDLE1..4: Deck L4/L5/R4/R5, Elite P1-P4) land on the Edge's NATIVE buttons[2] bits instead of the fold/drop policy: PADDLE1/2 -> the right/left back buttons, PADDLE3/4 -> the right/left Fn buttons (kernel BTN_TRIGGER_HAPPY1..4 on >= 7.2; SDL/Steam read hidraw on any kernel). - proto: Edge descriptor + btn2 bits + edge_paddle_bits(), pinned against hid-playstation DS_EDGE_BUTTONS_* and SDL_hidapi_ps5 (tests). - Linux: DsUhidIdentity parameterizes the UHID create; DsEdgeLinuxProto / DualSenseEdgeManager. Headless-validated on .21 (7.1): driver=playstation binds 0DF2, all 4 input devices created, probe lightbar/player-LED feedback round-trips; dualsense-test grew --edge (cycles all 4 paddles). - Windows: UMDF driver serves device_type=2 (Edge descriptor/attrs/strings, DS feature blobs); WinDsIdentity parameterizes the SwDevice profile + devtype stamp; DsEdgeWinProto / DualSenseEdgeWindowsManager; INF gains pf_dualsenseedge. Driver change => resign + reinstall before on-glass. - Router: DualSenseEdge arms in route_handle/apply_rich/pump/heartbeat; pick_gamepad folds Edge -> itself on linux||windows; degrade_if_no_uhid covers it. - Client (SDL): 054C:0DF2 declares DualSenseEdge (no distinct SDL type); Edge physical pads take the raw DS5 effects path; console-UI glyphs = Shapes. Apple/Android pickers follow separately. Verified: .21 clippy -D warnings + 292/0 host tests + on-box UHID bind smoke; .133 clippy pending in this push. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -255,19 +255,28 @@ fn real_main() -> Result<()> {
|
||||
// Create a virtual DualSense via UHID and exercise it (validation, no streaming session):
|
||||
// toggles the Cross button, sweeps the left stick, and prints any HID output the kernel
|
||||
// sends back. Verify with `evtest` / `ls /dev/input/by-id/*Punktfunk*` / `wpctl status`.
|
||||
// `--edge` creates a DualSense **Edge** (054C:0DF2) instead and additionally cycles the
|
||||
// four back/Fn buttons (kernel ≥ 7.2 exposes them as BTN_TRIGGER_HAPPY1..4; on older
|
||||
// kernels verify the bind + `hidraw` byte 10 instead).
|
||||
#[cfg(target_os = "linux")]
|
||||
Some("dualsense-test") => {
|
||||
use inject::dualsense::DualSensePad;
|
||||
use inject::dualsense_proto::DsState;
|
||||
use inject::dualsense::{DsUhidIdentity, DualSensePad};
|
||||
use inject::dualsense_proto::{edge_paddle_bits, DsState};
|
||||
let secs: u64 = args
|
||||
.iter()
|
||||
.skip_while(|a| *a != "--seconds")
|
||||
.nth(1)
|
||||
.and_then(|s| s.parse().ok())
|
||||
.unwrap_or(20);
|
||||
let edge = args.iter().any(|a| a == "--edge");
|
||||
let (identity, label) = if edge {
|
||||
(DsUhidIdentity::dualsense_edge(), "DualSense Edge")
|
||||
} else {
|
||||
(DsUhidIdentity::dualsense(), "DualSense")
|
||||
};
|
||||
use std::time::{Duration, Instant};
|
||||
let mut pad =
|
||||
DualSensePad::open(0).context("create virtual DualSense via /dev/uhid")?;
|
||||
let mut pad = DualSensePad::open(0, &identity)
|
||||
.with_context(|| format!("create virtual {label} via /dev/uhid"))?;
|
||||
// Answer the kernel's init GET_REPORTs promptly so hid-playstation creates the input
|
||||
// devices before we start streaming state.
|
||||
let init = Instant::now() + Duration::from_millis(800);
|
||||
@@ -276,7 +285,7 @@ fn real_main() -> Result<()> {
|
||||
std::thread::sleep(Duration::from_millis(10));
|
||||
}
|
||||
println!(
|
||||
"virtual DualSense created — check `evtest`, `ls /dev/input/by-id/*Punktfunk*`, \
|
||||
"virtual {label} created — check `evtest`, `ls /dev/input/by-id/*Punktfunk*`, \
|
||||
`ls /sys/class/leds/`. Cycling Cross + sweeping LS for {secs}s."
|
||||
);
|
||||
let deadline = Instant::now() + Duration::from_secs(secs);
|
||||
@@ -292,14 +301,22 @@ fn real_main() -> Result<()> {
|
||||
if last_write.elapsed() >= Duration::from_millis(300) {
|
||||
last_write = Instant::now();
|
||||
i += 1;
|
||||
let buttons = if i % 2 == 0 {
|
||||
let mut buttons = if i % 2 == 0 {
|
||||
punktfunk_core::input::gamepad::BTN_A
|
||||
} else {
|
||||
0
|
||||
};
|
||||
if edge {
|
||||
// Cycle one paddle per beat (R4 → L4 → R5 → L5) so all four Edge slots
|
||||
// are visible in evtest / hidraw.
|
||||
buttons |= punktfunk_core::input::gamepad::BTN_PADDLE1 << (i % 4);
|
||||
}
|
||||
let lx = (((i % 64) - 32) * 1024) as i16; // sweep left stick X
|
||||
let st = DsState::from_gamepad(buttons, lx, 0, 0, 0, 0, 0);
|
||||
pad.write_state(&st).context("write DualSense report")?;
|
||||
let mut st = DsState::from_gamepad(buttons, lx, 0, 0, 0, 0, 0);
|
||||
if edge {
|
||||
st.buttons[2] |= edge_paddle_bits(buttons);
|
||||
}
|
||||
pad.write_state(&st).context("write report")?;
|
||||
}
|
||||
std::thread::sleep(Duration::from_millis(15));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user