feat(gamepad): pure-user-mode Windows DualShock 4 + Xbox 360 (drop ViGEm) + installer + multi-pad
audit / cargo-audit (push) Successful in 17s
apple / swift (push) Successful in 57s
android / android (push) Successful in 4m36s
ci / web (push) Successful in 34s
ci / docs-site (push) Successful in 52s
release / apple (push) Successful in 7m31s
ci / rust (push) Successful in 8m37s
ci / bench (push) Successful in 4m39s
decky / build-publish (push) Successful in 11s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 7s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 4s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 4s
deb / build-publish (push) Successful in 2m35s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 5s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 2m18s
flatpak / build-publish (push) Successful in 4m0s
rpm / build-publish (bazzite, punktfunk-fedora-rpm) (push) Successful in 8m31s
docker / deploy-docs (push) Successful in 19s
rpm / build-publish (fedora-44, punktfunk-fedora44-rpm) (push) Successful in 8m22s
windows-host / package (push) Successful in 2m56s
windows-msix / package (arm64, C:\Users\Public\ffmpeg-arm64, aarch64-pc-windows-msvc, C:\t-a64) (push) Successful in 1m13s
windows-msix / package (x64, C:\Users\Public\ffmpeg, x86_64-pc-windows-msvc, C:\t) (push) Successful in 1m15s
windows / build (aarch64-pc-windows-msvc) (push) Successful in 59s
windows / build (x86_64-pc-windows-msvc) (push) Successful in 1m3s
audit / cargo-audit (push) Successful in 17s
apple / swift (push) Successful in 57s
android / android (push) Successful in 4m36s
ci / web (push) Successful in 34s
ci / docs-site (push) Successful in 52s
release / apple (push) Successful in 7m31s
ci / rust (push) Successful in 8m37s
ci / bench (push) Successful in 4m39s
decky / build-publish (push) Successful in 11s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 7s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 4s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 4s
deb / build-publish (push) Successful in 2m35s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 5s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 2m18s
flatpak / build-publish (push) Successful in 4m0s
rpm / build-publish (bazzite, punktfunk-fedora-rpm) (push) Successful in 8m31s
docker / deploy-docs (push) Successful in 19s
rpm / build-publish (fedora-44, punktfunk-fedora44-rpm) (push) Successful in 8m22s
windows-host / package (push) Successful in 2m56s
windows-msix / package (arm64, C:\Users\Public\ffmpeg-arm64, aarch64-pc-windows-msvc, C:\t-a64) (push) Successful in 1m13s
windows-msix / package (x64, C:\Users\Public\ffmpeg, x86_64-pc-windows-msvc, C:\t) (push) Successful in 1m15s
windows / build (aarch64-pc-windows-msvc) (push) Successful in 59s
windows / build (x86_64-pc-windows-msvc) (push) Successful in 1m3s
Windows virtual gamepads now have zero external dependencies - ViGEmBus is removed. - DualShock 4: Windows UMDF backend (inject/dualshock4_windows.rs + dualshock4_proto.rs), reusing the DualSense SwDeviceCreate game-detection identity fix. The one UMDF driver serves the DS5 or DS4 identity/descriptor/features/strings per a device_type byte the host stamps into shared memory. Driver also gains IOCTL_HID_GET_STRING and a 41-byte calibration feature. - Xbox 360: a new UMDF2 XUSB companion driver (packaging/windows/xusb-driver/) that registers GUID_DEVINTERFACE_XUSB and answers the buffered XInput IOCTLs from a shared section, so classic XInputGetState/SetState work with no kernel bus driver. inject/gamepad_windows.rs is rewritten to drive it and the vigem-client dependency is removed. Xbox One folds to the 360 XInput path. - Installer: vendor + pnputil-install the three UMDF drivers (packaging/windows/gamepad-drivers/ + install-gamepad-drivers.ps1, wired into pack-host-installer.ps1 + punktfunk-host.iss). - Multi-pad: the host stamps each pad index into the device Location (pszDeviceLocation); the driver reads it via WdfDeviceAllocAndQueryProperty to map its own *-shm-<index>, with UmdfHostProcessSharing=ProcessSharingDisabled giving each pad its own host (per-pad statics). Validated live on the Windows host: Cyberpunk native DualSense detection, DS4 identity + descriptor, XInputGetState + rumble round-trip, two pads -> two distinct XInput slots, and a full installer build. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -209,7 +209,6 @@ fn real_main() -> Result<()> {
|
||||
#[cfg(target_os = "windows")]
|
||||
Some("dualsense-windows-test") => {
|
||||
use crate::gamestream::gamepad::{GamepadEvent, GamepadFrame};
|
||||
use inject::dualsense_windows::DualSenseWindowsManager;
|
||||
use std::time::{Duration, Instant};
|
||||
let secs: u64 = args
|
||||
.iter()
|
||||
@@ -217,38 +216,95 @@ fn real_main() -> Result<()> {
|
||||
.nth(1)
|
||||
.and_then(|s| s.parse().ok())
|
||||
.unwrap_or(20);
|
||||
let mut mgr = DualSenseWindowsManager::new();
|
||||
// Arrival creates the pad (SwDeviceCreate + section); State pushes the report.
|
||||
mgr.handle(&GamepadEvent::Arrival {
|
||||
index: 0,
|
||||
kind: 2,
|
||||
capabilities: 0,
|
||||
});
|
||||
println!(
|
||||
"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 {
|
||||
// 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:?}"),
|
||||
// `--index N` creates pad `pf_pad_N` (default 0) — use a spare index (e.g. 1) to test
|
||||
// alongside a running host that already holds pad 0. `--ds4` drives the DualShock 4
|
||||
// backend instead of the DualSense one.
|
||||
let idx: u8 = args
|
||||
.iter()
|
||||
.skip_while(|a| *a != "--index")
|
||||
.nth(1)
|
||||
.and_then(|s| s.parse().ok())
|
||||
.unwrap_or(0);
|
||||
let ds4 = args.iter().any(|a| a == "--ds4");
|
||||
let xbox = args.iter().any(|a| a == "--xbox");
|
||||
// Same drive loop for either backend (identical method surface): Arrival creates the pad,
|
||||
// State pushes a cycling report, pump surfaces a game's rumble/lightbar feedback.
|
||||
macro_rules! drive {
|
||||
($mgr:expr, $label:expr) => {{
|
||||
let mut mgr = $mgr;
|
||||
mgr.handle(&GamepadEvent::Arrival {
|
||||
index: idx,
|
||||
kind: 2,
|
||||
capabilities: 0,
|
||||
});
|
||||
println!(
|
||||
"virtual {} up — cycling Cross + sweeping the left stick for {secs}s. Watch \
|
||||
it in joy.cpl / Steam / a game; any feedback the game sends prints below.",
|
||||
$label
|
||||
);
|
||||
let deadline = Instant::now() + Duration::from_secs(secs);
|
||||
let (mut i, mut last) = (0i32, Instant::now());
|
||||
while Instant::now() < deadline {
|
||||
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: idx as i16,
|
||||
active_mask: 1 << idx,
|
||||
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));
|
||||
}
|
||||
}};
|
||||
}
|
||||
if xbox {
|
||||
// Xbox 360 via the XUSB companion: a different surface (handle + pump_rumble, no
|
||||
// HID-output plane), so drive it inline rather than via the macro.
|
||||
let mut mgr = inject::gamepad::GamepadManager::new();
|
||||
mgr.handle(&GamepadEvent::Arrival {
|
||||
index: idx,
|
||||
kind: 1,
|
||||
capabilities: 0,
|
||||
});
|
||||
println!(
|
||||
"virtual Xbox 360 (XUSB) up — sweeping LS + toggling A for {secs}s. Check with \
|
||||
an XInput game or xinputtest.exe."
|
||||
);
|
||||
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
|
||||
let deadline = Instant::now() + Duration::from_secs(secs);
|
||||
let mut t = 0i32;
|
||||
while Instant::now() < deadline {
|
||||
mgr.pump_rumble(|pad, lo, hi| {
|
||||
println!(" rumble from game: pad={pad} low={lo} high={hi}")
|
||||
});
|
||||
t += 1;
|
||||
let lx = (((t % 200) - 100) * 327).clamp(-32768, 32767) as i16; // sweep ±32700
|
||||
let buttons = if (t / 67) % 2 == 0 {
|
||||
punktfunk_core::input::gamepad::BTN_A
|
||||
} else {
|
||||
0
|
||||
};
|
||||
let lx = (((i % 64) - 32) * 1024) as i16; // sweep left stick X
|
||||
mgr.handle(&GamepadEvent::State(GamepadFrame {
|
||||
index: 0,
|
||||
active_mask: 1,
|
||||
index: idx as i16,
|
||||
active_mask: 1 << idx,
|
||||
buttons,
|
||||
left_trigger: 0,
|
||||
right_trigger: 0,
|
||||
@@ -257,8 +313,18 @@ fn real_main() -> Result<()> {
|
||||
rs_x: 0,
|
||||
rs_y: 0,
|
||||
}));
|
||||
std::thread::sleep(Duration::from_millis(15));
|
||||
}
|
||||
std::thread::sleep(Duration::from_millis(15));
|
||||
} else if ds4 {
|
||||
drive!(
|
||||
inject::dualshock4_windows::DualShock4WindowsManager::new(),
|
||||
"DualShock 4"
|
||||
);
|
||||
} else {
|
||||
drive!(
|
||||
inject::dualsense_windows::DualSenseWindowsManager::new(),
|
||||
"DualSense"
|
||||
);
|
||||
}
|
||||
println!("dualsense-windows-test: done (devnode removed)");
|
||||
Ok(())
|
||||
|
||||
Reference in New Issue
Block a user