refactor(inject/host/windows): hoist the shared SwCreateCtx into gamepad_raii (G14)
The `SwDeviceCreate` completion-callback context (`SwCreateCtx`, the `sw_create_cb` extern callback, and the `instance_id()` accessor) was copy-pasted byte-for-byte in the XUSB (`gamepad_windows.rs`) and DualSense/DS4 (`dualsense_windows.rs`) backends. Hoist the one copy into `gamepad_raii.rs` as `pub(super)`; both `create_swdevice` bodies now build the shared type and pass the shared callback. Prunes the now-orphaned HRESULT/SetEvent/HANDLE imports from the two siblings. Pure move + dedup, no behavior change. Windows-verified with the rest of Phase 3 (clippy --all-targets -D warnings). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -21,19 +21,19 @@ use super::dualsense_proto::{
|
|||||||
parse_ds_output, serialize_state, DsFeedback, DsState, DS_INPUT_REPORT_LEN, DS_TOUCH_H,
|
parse_ds_output, serialize_state, DsFeedback, DsState, DS_INPUT_REPORT_LEN, DS_TOUCH_H,
|
||||||
DS_TOUCH_W,
|
DS_TOUCH_W,
|
||||||
};
|
};
|
||||||
use super::gamepad_raii::PadChannel;
|
use super::gamepad_raii::{sw_create_cb, PadChannel, SwCreateCtx};
|
||||||
use crate::gamestream::gamepad::{GamepadEvent, MAX_PADS};
|
use crate::gamestream::gamepad::{GamepadEvent, MAX_PADS};
|
||||||
use crate::inject::pad_gate::PadGate;
|
use crate::inject::pad_gate::PadGate;
|
||||||
use anyhow::{anyhow, Result};
|
use anyhow::{anyhow, Result};
|
||||||
use punktfunk_core::quic::{HidOutput, RichInput};
|
use punktfunk_core::quic::{HidOutput, RichInput};
|
||||||
use std::ffi::c_void;
|
use std::ffi::c_void;
|
||||||
use std::time::{Duration, Instant};
|
use std::time::{Duration, Instant};
|
||||||
use windows::core::{w, GUID, HRESULT, PCWSTR};
|
use windows::core::{w, GUID, PCWSTR};
|
||||||
use windows::Win32::Devices::Enumeration::Pnp::{
|
use windows::Win32::Devices::Enumeration::Pnp::{
|
||||||
SwDeviceClose, SwDeviceCreate, HSWDEVICE, SW_DEVICE_CREATE_INFO,
|
SwDeviceClose, SwDeviceCreate, HSWDEVICE, SW_DEVICE_CREATE_INFO,
|
||||||
};
|
};
|
||||||
use windows::Win32::Foundation::{CloseHandle, E_FAIL, HANDLE, WAIT_OBJECT_0};
|
use windows::Win32::Foundation::{CloseHandle, E_FAIL, WAIT_OBJECT_0};
|
||||||
use windows::Win32::System::Threading::{CreateEventW, SetEvent, WaitForSingleObject};
|
use windows::Win32::System::Threading::{CreateEventW, WaitForSingleObject};
|
||||||
|
|
||||||
/// Shared-section layout — the single source of truth is [`pf_driver_proto::gamepad::PadShm`] (offset
|
/// Shared-section layout — the single source of truth is [`pf_driver_proto::gamepad::PadShm`] (offset
|
||||||
/// asserts pin every field; the `pf_dualsense` driver maps the same struct). Derive the size/offsets/magic
|
/// asserts pin every field; the `pf_dualsense` driver maps the same struct). Derive the size/offsets/magic
|
||||||
@@ -72,50 +72,6 @@ struct DsWinPad {
|
|||||||
last_out_seq: u32,
|
last_out_seq: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Context for the `SwDeviceCreate` completion callback: an event to signal, the HRESULT it reports,
|
|
||||||
/// and the PnP instance id PnP assigned (captured for devnode health diagnostics).
|
|
||||||
#[repr(C)]
|
|
||||||
struct SwCreateCtx {
|
|
||||||
event: HANDLE,
|
|
||||||
result: HRESULT,
|
|
||||||
instance_id: [u16; 128],
|
|
||||||
}
|
|
||||||
|
|
||||||
/// `SwDeviceCreate` fires this once PnP has enumerated the device; stash the result and wake the
|
|
||||||
/// creator, which blocks on the event (so there's no concurrent access to `*ctx`).
|
|
||||||
unsafe extern "system" fn sw_create_cb(
|
|
||||||
_dev: HSWDEVICE,
|
|
||||||
result: HRESULT,
|
|
||||||
ctx: *const c_void,
|
|
||||||
id: PCWSTR,
|
|
||||||
) {
|
|
||||||
if !ctx.is_null() {
|
|
||||||
// SAFETY: ctx is the &mut SwCreateCtx the creator passed; it outlives this callback (the
|
|
||||||
// creator blocks on the event). `id` is a NUL-terminated string for the callback's duration.
|
|
||||||
unsafe {
|
|
||||||
let c = ctx as *mut SwCreateCtx;
|
|
||||||
(*c).result = result;
|
|
||||||
if !id.is_null() {
|
|
||||||
for i in 0..(*c).instance_id.len() - 1 {
|
|
||||||
let ch = *id.0.add(i);
|
|
||||||
(*c).instance_id[i] = ch;
|
|
||||||
if ch == 0 {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let _ = SetEvent((*c).event);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SwCreateCtx {
|
|
||||||
fn instance_id(&self) -> Option<String> {
|
|
||||||
let len = self.instance_id.iter().position(|&c| c == 0)?;
|
|
||||||
(len > 0).then(|| String::from_utf16_lossy(&self.instance_id[..len]))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The PnP identity for a virtual controller devnode — varies by controller type so the same
|
/// The PnP identity for a virtual controller devnode — varies by controller type so the same
|
||||||
/// [`create_swdevice`] builds a DualSense (`VID_054C&PID_0CE6`) or a DualShock 4
|
/// [`create_swdevice`] builds a DualSense (`VID_054C&PID_0CE6`) or a DualShock 4
|
||||||
/// (`VID_054C&PID_09CC`). The fields map onto the `SW_DEVICE_CREATE_INFO` identity discussed below.
|
/// (`VID_054C&PID_09CC`). The fields map onto the `SW_DEVICE_CREATE_INFO` identity discussed below.
|
||||||
|
|||||||
@@ -22,11 +22,12 @@
|
|||||||
|
|
||||||
use anyhow::{anyhow, bail, Context, Result};
|
use anyhow::{anyhow, bail, Context, Result};
|
||||||
use pf_driver_proto::gamepad::{PadBootstrap, BOOT_MAGIC, GAMEPAD_PROTO_VERSION};
|
use pf_driver_proto::gamepad::{PadBootstrap, BOOT_MAGIC, GAMEPAD_PROTO_VERSION};
|
||||||
|
use std::ffi::c_void;
|
||||||
use std::os::windows::io::{AsRawHandle, FromRawHandle, OwnedHandle};
|
use std::os::windows::io::{AsRawHandle, FromRawHandle, OwnedHandle};
|
||||||
use std::sync::atomic::{fence, AtomicU32, AtomicU64, Ordering};
|
use std::sync::atomic::{fence, AtomicU32, AtomicU64, Ordering};
|
||||||
use std::sync::OnceLock;
|
use std::sync::OnceLock;
|
||||||
use std::time::{Duration, Instant};
|
use std::time::{Duration, Instant};
|
||||||
use windows::core::{w, HSTRING, PCWSTR};
|
use windows::core::{w, HRESULT, HSTRING, PCWSTR};
|
||||||
use windows::Win32::Devices::DeviceAndDriverInstallation::{
|
use windows::Win32::Devices::DeviceAndDriverInstallation::{
|
||||||
CM_Get_DevNode_Status, CM_Locate_DevNodeW, CM_DEVNODE_STATUS_FLAGS, CM_LOCATE_DEVNODE_NORMAL,
|
CM_Get_DevNode_Status, CM_Locate_DevNodeW, CM_DEVNODE_STATUS_FLAGS, CM_LOCATE_DEVNODE_NORMAL,
|
||||||
CM_PROB, CR_SUCCESS, DN_DRIVER_LOADED, DN_HAS_PROBLEM, DN_STARTED,
|
CM_PROB, CR_SUCCESS, DN_DRIVER_LOADED, DN_HAS_PROBLEM, DN_STARTED,
|
||||||
@@ -45,7 +46,7 @@ use windows::Win32::System::Memory::{
|
|||||||
MEMORY_MAPPED_VIEW_ADDRESS, PAGE_READWRITE,
|
MEMORY_MAPPED_VIEW_ADDRESS, PAGE_READWRITE,
|
||||||
};
|
};
|
||||||
use windows::Win32::System::Threading::{
|
use windows::Win32::System::Threading::{
|
||||||
GetCurrentProcess, OpenProcess, PROCESS_DUP_HANDLE, PROCESS_QUERY_LIMITED_INFORMATION,
|
GetCurrentProcess, OpenProcess, SetEvent, PROCESS_DUP_HANDLE, PROCESS_QUERY_LIMITED_INFORMATION,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Least access the pad driver needs on the duplicated DATA section: it only MAPS it read/write, so
|
/// Least access the pad driver needs on the duplicated DATA section: it only MAPS it read/write, so
|
||||||
@@ -403,6 +404,53 @@ impl PadChannel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Context for the `SwDeviceCreate` completion callback: an event to signal, the HRESULT it reports,
|
||||||
|
/// and the PnP instance id PnP assigned (captured for devnode health diagnostics). Shared by every
|
||||||
|
/// Windows companion backend (XUSB / DualSense / DS4): each `create_swdevice` builds one, hands it to
|
||||||
|
/// `SwDeviceCreate` alongside [`sw_create_cb`], and reads [`instance_id`](Self::instance_id) once the
|
||||||
|
/// callback has signalled.
|
||||||
|
#[repr(C)]
|
||||||
|
pub(super) struct SwCreateCtx {
|
||||||
|
pub(super) event: HANDLE,
|
||||||
|
pub(super) result: HRESULT,
|
||||||
|
pub(super) instance_id: [u16; 128],
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `SwDeviceCreate` fires this once PnP has enumerated the device; stash the result and wake the
|
||||||
|
/// creator, which blocks on the event (so there's no concurrent access to `*ctx`).
|
||||||
|
pub(super) unsafe extern "system" fn sw_create_cb(
|
||||||
|
_dev: HSWDEVICE,
|
||||||
|
result: HRESULT,
|
||||||
|
ctx: *const c_void,
|
||||||
|
id: PCWSTR,
|
||||||
|
) {
|
||||||
|
if !ctx.is_null() {
|
||||||
|
// SAFETY: ctx is the &mut SwCreateCtx the creator passed; it outlives this callback (the
|
||||||
|
// creator blocks on the event). `id` is a NUL-terminated string for the callback's duration.
|
||||||
|
unsafe {
|
||||||
|
let c = ctx as *mut SwCreateCtx;
|
||||||
|
(*c).result = result;
|
||||||
|
if !id.is_null() {
|
||||||
|
for i in 0..(*c).instance_id.len() - 1 {
|
||||||
|
let ch = *id.0.add(i);
|
||||||
|
(*c).instance_id[i] = ch;
|
||||||
|
if ch == 0 {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let _ = SetEvent((*c).event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SwCreateCtx {
|
||||||
|
pub(super) fn instance_id(&self) -> Option<String> {
|
||||||
|
let len = self.instance_id.iter().position(|&c| c == 0)?;
|
||||||
|
(len > 0).then(|| String::from_utf16_lossy(&self.instance_id[..len]))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// A `SwDeviceCreate`'d software devnode; drop removes it via `SwDeviceClose`. Replaces the manual
|
/// A `SwDeviceCreate`'d software devnode; drop removes it via `SwDeviceClose`. Replaces the manual
|
||||||
/// `SwDeviceClose` each backend used to call in its `Drop`.
|
/// `SwDeviceClose` each backend used to call in its `Drop`.
|
||||||
pub(super) struct SwDevice(HSWDEVICE);
|
pub(super) struct SwDevice(HSWDEVICE);
|
||||||
|
|||||||
@@ -12,18 +12,18 @@
|
|||||||
//! parses the `SET_STATE` packet into the shared section, and [`GamepadManager::pump_rumble`] relays
|
//! parses the `SET_STATE` packet into the shared section, and [`GamepadManager::pump_rumble`] relays
|
||||||
//! level changes to the client (the universal 0xCA plane), mirroring the Linux `EV_FF` read path.
|
//! level changes to the client (the universal 0xCA plane), mirroring the Linux `EV_FF` read path.
|
||||||
|
|
||||||
use super::gamepad_raii::PadChannel;
|
use super::gamepad_raii::{sw_create_cb, PadChannel, SwCreateCtx};
|
||||||
use crate::gamestream::gamepad::{GamepadEvent, MAX_PADS};
|
use crate::gamestream::gamepad::{GamepadEvent, MAX_PADS};
|
||||||
use crate::inject::pad_gate::PadGate;
|
use crate::inject::pad_gate::PadGate;
|
||||||
use anyhow::{anyhow, Result};
|
use anyhow::{anyhow, Result};
|
||||||
use std::ffi::c_void;
|
use std::ffi::c_void;
|
||||||
use std::time::{Duration, Instant};
|
use std::time::{Duration, Instant};
|
||||||
use windows::core::{w, GUID, HRESULT, PCWSTR};
|
use windows::core::{w, GUID, PCWSTR};
|
||||||
use windows::Win32::Devices::Enumeration::Pnp::{
|
use windows::Win32::Devices::Enumeration::Pnp::{
|
||||||
SwDeviceClose, SwDeviceCreate, HSWDEVICE, SW_DEVICE_CREATE_INFO,
|
SwDeviceClose, SwDeviceCreate, HSWDEVICE, SW_DEVICE_CREATE_INFO,
|
||||||
};
|
};
|
||||||
use windows::Win32::Foundation::{CloseHandle, E_FAIL, HANDLE, WAIT_OBJECT_0};
|
use windows::Win32::Foundation::{CloseHandle, E_FAIL, WAIT_OBJECT_0};
|
||||||
use windows::Win32::System::Threading::{CreateEventW, SetEvent, WaitForSingleObject};
|
use windows::Win32::System::Threading::{CreateEventW, WaitForSingleObject};
|
||||||
|
|
||||||
// Shared-section layout — the single source of truth is `pf_driver_proto::gamepad::XusbShm` (offset
|
// Shared-section layout — the single source of truth is `pf_driver_proto::gamepad::XusbShm` (offset
|
||||||
// asserts pin every field; the `pf_xusb` driver maps the same struct). Derive the size/offsets/magic from
|
// asserts pin every field; the `pf_xusb` driver maps the same struct). Derive the size/offsets/magic from
|
||||||
@@ -44,49 +44,6 @@ const OFF_RUMBLE: usize = core::mem::offset_of!(XusbShm, rumble_large); // large
|
|||||||
const OFF_DRIVER_PROTO: usize = core::mem::offset_of!(XusbShm, driver_proto);
|
const OFF_DRIVER_PROTO: usize = core::mem::offset_of!(XusbShm, driver_proto);
|
||||||
const OFF_PAD_INDEX: usize = core::mem::offset_of!(XusbShm, pad_index);
|
const OFF_PAD_INDEX: usize = core::mem::offset_of!(XusbShm, pad_index);
|
||||||
|
|
||||||
/// Context for the `SwDeviceCreate` completion callback: an event to signal, the HRESULT it reports,
|
|
||||||
/// and the PnP instance id PnP assigned (captured for devnode health diagnostics).
|
|
||||||
#[repr(C)]
|
|
||||||
struct SwCreateCtx {
|
|
||||||
event: HANDLE,
|
|
||||||
result: HRESULT,
|
|
||||||
instance_id: [u16; 128],
|
|
||||||
}
|
|
||||||
|
|
||||||
/// `SwDeviceCreate` fires this once PnP has enumerated the device; stash the result + wake the creator.
|
|
||||||
unsafe extern "system" fn sw_create_cb(
|
|
||||||
_dev: HSWDEVICE,
|
|
||||||
result: HRESULT,
|
|
||||||
ctx: *const c_void,
|
|
||||||
id: PCWSTR,
|
|
||||||
) {
|
|
||||||
if !ctx.is_null() {
|
|
||||||
// SAFETY: ctx is the &mut SwCreateCtx the creator passed; it outlives this callback (the
|
|
||||||
// creator blocks on the event). `id` is a NUL-terminated string for the callback's duration.
|
|
||||||
unsafe {
|
|
||||||
let c = ctx as *mut SwCreateCtx;
|
|
||||||
(*c).result = result;
|
|
||||||
if !id.is_null() {
|
|
||||||
for i in 0..(*c).instance_id.len() - 1 {
|
|
||||||
let ch = *id.0.add(i);
|
|
||||||
(*c).instance_id[i] = ch;
|
|
||||||
if ch == 0 {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let _ = SetEvent((*c).event);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SwCreateCtx {
|
|
||||||
fn instance_id(&self) -> Option<String> {
|
|
||||||
let len = self.instance_id.iter().position(|&c| c == 0)?;
|
|
||||||
(len > 0).then(|| String::from_utf16_lossy(&self.instance_id[..len]))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Spawn the `pf_xusb_<index>` companion devnode (hardware id `pf_xusb`, enumerator `punktfunk`). The
|
/// Spawn the `pf_xusb_<index>` companion devnode (hardware id `pf_xusb`, enumerator `punktfunk`). The
|
||||||
/// INF (System class) binds our UMDF driver, which registers the XUSB interface. Unlike the HID pads,
|
/// INF (System class) binds our UMDF driver, which registers the XUSB interface. Unlike the HID pads,
|
||||||
/// no USB compatible-ids are needed — XInput finds the device by the interface GUID, not VID/PID — but
|
/// no USB compatible-ids are needed — XInput finds the device by the interface GUID, not VID/PID — but
|
||||||
|
|||||||
Reference in New Issue
Block a user