diff --git a/crates/punktfunk-host/src/inject/windows/dualsense_windows.rs b/crates/punktfunk-host/src/inject/windows/dualsense_windows.rs index 9f1b3659..1b960c2b 100644 --- a/crates/punktfunk-host/src/inject/windows/dualsense_windows.rs +++ b/crates/punktfunk-host/src/inject/windows/dualsense_windows.rs @@ -21,19 +21,19 @@ use super::dualsense_proto::{ parse_ds_output, serialize_state, DsFeedback, DsState, DS_INPUT_REPORT_LEN, DS_TOUCH_H, 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::inject::pad_gate::PadGate; use anyhow::{anyhow, Result}; use punktfunk_core::quic::{HidOutput, RichInput}; use std::ffi::c_void; use std::time::{Duration, Instant}; -use windows::core::{w, GUID, HRESULT, PCWSTR}; +use windows::core::{w, GUID, PCWSTR}; use windows::Win32::Devices::Enumeration::Pnp::{ SwDeviceClose, SwDeviceCreate, HSWDEVICE, SW_DEVICE_CREATE_INFO, }; -use windows::Win32::Foundation::{CloseHandle, E_FAIL, HANDLE, WAIT_OBJECT_0}; -use windows::Win32::System::Threading::{CreateEventW, SetEvent, WaitForSingleObject}; +use windows::Win32::Foundation::{CloseHandle, E_FAIL, WAIT_OBJECT_0}; +use windows::Win32::System::Threading::{CreateEventW, WaitForSingleObject}; /// 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 @@ -72,50 +72,6 @@ struct DsWinPad { 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 { - 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 /// [`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. diff --git a/crates/punktfunk-host/src/inject/windows/gamepad_raii.rs b/crates/punktfunk-host/src/inject/windows/gamepad_raii.rs index 5effe302..d41f76e1 100644 --- a/crates/punktfunk-host/src/inject/windows/gamepad_raii.rs +++ b/crates/punktfunk-host/src/inject/windows/gamepad_raii.rs @@ -22,11 +22,12 @@ use anyhow::{anyhow, bail, Context, Result}; 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::sync::atomic::{fence, AtomicU32, AtomicU64, Ordering}; use std::sync::OnceLock; use std::time::{Duration, Instant}; -use windows::core::{w, HSTRING, PCWSTR}; +use windows::core::{w, HRESULT, HSTRING, PCWSTR}; use windows::Win32::Devices::DeviceAndDriverInstallation::{ 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, @@ -45,7 +46,7 @@ use windows::Win32::System::Memory::{ MEMORY_MAPPED_VIEW_ADDRESS, PAGE_READWRITE, }; 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 @@ -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 { + 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 /// `SwDeviceClose` each backend used to call in its `Drop`. pub(super) struct SwDevice(HSWDEVICE); diff --git a/crates/punktfunk-host/src/inject/windows/gamepad_windows.rs b/crates/punktfunk-host/src/inject/windows/gamepad_windows.rs index 5d38c747..10cc8638 100644 --- a/crates/punktfunk-host/src/inject/windows/gamepad_windows.rs +++ b/crates/punktfunk-host/src/inject/windows/gamepad_windows.rs @@ -12,18 +12,18 @@ //! 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. -use super::gamepad_raii::PadChannel; +use super::gamepad_raii::{sw_create_cb, PadChannel, SwCreateCtx}; use crate::gamestream::gamepad::{GamepadEvent, MAX_PADS}; use crate::inject::pad_gate::PadGate; use anyhow::{anyhow, Result}; use std::ffi::c_void; use std::time::{Duration, Instant}; -use windows::core::{w, GUID, HRESULT, PCWSTR}; +use windows::core::{w, GUID, PCWSTR}; use windows::Win32::Devices::Enumeration::Pnp::{ SwDeviceClose, SwDeviceCreate, HSWDEVICE, SW_DEVICE_CREATE_INFO, }; -use windows::Win32::Foundation::{CloseHandle, E_FAIL, HANDLE, WAIT_OBJECT_0}; -use windows::Win32::System::Threading::{CreateEventW, SetEvent, WaitForSingleObject}; +use windows::Win32::Foundation::{CloseHandle, E_FAIL, WAIT_OBJECT_0}; +use windows::Win32::System::Threading::{CreateEventW, WaitForSingleObject}; // 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 @@ -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_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 { - 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_` 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, /// no USB compatible-ids are needed — XInput finds the device by the interface GUID, not VID/PID — but