diff --git a/stick/src/connector.rs b/stick/src/connector.rs index e238fd5..c3e8918 100644 --- a/stick/src/connector.rs +++ b/stick/src/connector.rs @@ -8,6 +8,12 @@ use lookit::Lookit; use crate::{Controller, Remap}; +#[cfg(windows)] +mod lookit { + #[derive(Debug)] + pub(crate) struct Lookit {} +} + /// Future that you can `.await` to connect to /// [`Controller`](crate::Controller)s #[derive(Debug)] diff --git a/stick/src/ctlr.rs b/stick/src/ctlr.rs index 57b04cc..d4b75c4 100644 --- a/stick/src/ctlr.rs +++ b/stick/src/ctlr.rs @@ -10,10 +10,22 @@ use std::{ use lookit::It; use crate::{ - platform::{connect, platform, PlatformController, Support}, + platform::{platform, CtlrId, Support}, Event, }; +#[cfg(windows)] +pub(crate) mod lookit { + #[derive(Debug)] + pub(crate) struct It {} + + impl It { + pub fn id(&self) -> u8 { + todo!() + } + } +} + #[repr(i8)] enum Btn { Exit = 0, @@ -271,7 +283,7 @@ pub struct Controller { // Shared remapping. remap: Arc, // - raw: PlatformController, + raw: CtlrId, // Button states btns: u128, // Number button states @@ -298,7 +310,7 @@ impl Controller { let btns = 0; let nums = 0; let axis = [0.0; Axs::Count as usize]; - let (id, name, raw) = connect(which)?; + let (id, name, raw) = platform().connect(which)?; let remap = remap.0.get(&id).cloned().unwrap_or_default(); let ready = true; Some(Self { @@ -581,7 +593,7 @@ impl Future for Controller { let mut this = self.as_mut(); if this.ready { - if let Some(event) = platform().event(&mut this.raw) { + if let Poll::Ready(event) = platform().event(&mut this.raw) { let out = Self::process(&mut *this, event); return if out.is_pending() { Self::poll(self, cx) diff --git a/stick/src/lib.rs b/stick/src/lib.rs index 1e090c5..623199c 100644 --- a/stick/src/lib.rs +++ b/stick/src/lib.rs @@ -116,7 +116,7 @@ mod platform { mod platform; - pub(crate) use platform::{connect, platform, PlatformController, Support}; + pub(crate) use platform::{platform, CtlrId, Support}; } mod connector; diff --git a/stick/src/linux/controller.rs b/stick/src/linux/controller.rs index a182d0c..caf06e1 100644 --- a/stick/src/linux/controller.rs +++ b/stick/src/linux/controller.rs @@ -17,6 +17,8 @@ pub(crate) struct Controller { } pub(crate) fn connect(it: It) -> Option<(u64, String, Controller)> { + // Some controllers may not have haptic force feedback while others might + // ONLY have haptic force feedback and no controls. let file = it.file_open() // Try Read & Write first .or_else(|it| it.file_open_r()) // Then Readonly second .or_else(|it| it.file_open_w()) // Then Writeonly third diff --git a/stick/src/platform/platform.rs b/stick/src/platform/platform.rs index d5ba561..3c7f818 100644 --- a/stick/src/platform/platform.rs +++ b/stick/src/platform/platform.rs @@ -1,28 +1,36 @@ #![allow(unsafe_code)] -use lookit::It; +use std::task::{Context, Poll}; + +use crate::Event; // Choose platform driver implementation. #[allow(unused_attributes)] #[cfg_attr(target_os = "linux", path = "../linux/linux.rs")] +#[cfg_attr(target_os = "windows", path = "../windows/windows.rs")] #[path = "unsupported.rs"] mod driver; -// Import the device type from the target platform. -pub(crate) use driver::Controller as PlatformController; - -// Single required method for each platform. -pub(crate) fn connect(it: It) -> Option<(u64, String, PlatformController)> { - driver::connect(it) -} +/// Controller ID. +pub(crate) struct CtlrId(u32); +/// Required platform support trait. pub(crate) trait Support { + /// Window gained focus, start receiving events. fn enable(self); + /// Window lost focus, stop receiving events. fn disable(self); - fn rumble(self, controller: &mut PlatformController, left: f32, right: f32); - fn event(self, device: &mut PlatformController) -> Option; + /// Set left and right rumble value for controller. + fn rumble(self, ctlr: &CtlrId, left: f32, right: f32); + /// Attempt getting a new event from a connected controller. + fn event(self, ctlr: &CtlrId, cx: &mut Context<'_>) -> Poll; + /// Attempt connecting to a new controller. + fn connect(self, cx: &mut Context<'_>) -> Poll<(u64, String, CtlrId)>; } +/// Get platform support implementation. +/// +/// Each platform must implement this function to work. pub(crate) fn platform() -> impl Support { driver::platform() } diff --git a/stick/src/windows/controller.rs b/stick/src/windows/controller.rs new file mode 100644 index 0000000..5e08fd2 --- /dev/null +++ b/stick/src/windows/controller.rs @@ -0,0 +1,41 @@ +use std::rc::Rc; +use crate::Event; +use winapi::shared::minwindef::DWORD; +use super::XInputHandle; +use crate::ctlr::lookit::It; + +pub(crate) struct Controller { + pub(crate) xinput: Rc, + pub(crate) device_id: u8, + pub(crate) pending_events: Vec, + pub(crate) last_packet: DWORD, +} + +impl Controller { + #[allow(unused)] + fn new(device_id: u8, xinput: Rc) -> Self { + Self { + xinput, + device_id, + pending_events: Vec::new(), + last_packet: 0, + } + } + + /// Stereo rumble effect (left is low frequency, right is high frequency). + pub(super) fn rumble(&mut self, left: f32, right: f32) { + self.xinput + .set_state( + self.device_id as u32, + (u16::MAX as f32 * left) as u16, + (u16::MAX as f32 * right) as u16, + ) + .unwrap() + } +} + +pub(crate) fn connect(it: It) -> Option<(u64, String, Controller)> { + let name = "XInput Controller"; + let controller = Controller::new(it.id(), todo!()); + Some((0, name.to_string(), controller)) +} diff --git a/stick/src/windows/windows.rs b/stick/src/windows/windows.rs new file mode 100644 index 0000000..48bed59 --- /dev/null +++ b/stick/src/windows/windows.rs @@ -0,0 +1,83 @@ +use self::controller::Controller; +use self::xinput::XInputHandle; +use super::{CtlrId, Support}; +use crate::Event; +use std::mem::MaybeUninit; +use std::sync::Once; +use std::sync::atomic::{AtomicU8, Ordering}; +use std::task::{Context, Poll}; +use std::rc::Rc; + +mod controller; +mod xinput; + +static mut PLATFORM: MaybeUninit = MaybeUninit::uninit(); +static ONCE: Once = Once::new(); +static CONNECTED: AtomicU8 = AtomicU8::new(0); +static READY: AtomicU8 = AtomicU8::new(0); + +pub(super) struct Platform(Option>); + +impl Support for &Platform { + fn enable(self) { + if let Some(ref xinput) = self.0 { + unsafe { (xinput.xinput_enable)(true as _) }; + } + } + + fn disable(self) { + if let Some(ref xinput) = self.0 { + unsafe { (xinput.xinput_enable)(false as _) }; + } + } + + fn rumble(self, ctlr: &CtlrId, left: f32, right: f32) { + todo!() + } + + fn event(self, ctlr: &CtlrId, cx: &mut Context<'_>) -> Poll { + todo!() + } + + fn connect(self, cx: &mut Context<'_>) -> Poll<(u64, String, CtlrId)> { + // Early return optimization if timeout hasn't passed yet. + // FIXME + + // DirectInput only allows for 4 controllers + let connected = CONNECTED.load(Ordering::Relaxed); + for id in 0..4 { + let mask = 1 << id; + let was_connected = (connected & mask) != 0; + if let Some(ref xinput) = self.0 { + if xinput.get_state(id).is_ok() { + CONNECTED.fetch_or(mask, Ordering::Relaxed); + if !was_connected { + // we have a new device! + return Poll::Ready(CtlrId(id)); + } + } else { + // set deviceto unplugged + CONNECTED.fetch_and(!mask, Ordering::Relaxed); + } + } + } + + xinput::register_wake_timeout(100, cx.waker()); + + Poll::Pending + } +} + +pub(super) fn platform() -> &'static Platform { + ONCE.call_once(|| unsafe { + PLATFORM = MaybeUninit::new(Platform(if let Ok(xinput) = XInputHandle::load_default() { + Some(xinput) + } else { + None + })); + }); + + unsafe { + PLATFORM.assume_init_ref() + } +} diff --git a/stick/src/raw/windows.rs b/stick/src/windows/xinput.rs similarity index 73% rename from stick/src/raw/windows.rs rename to stick/src/windows/xinput.rs index 727caeb..dd3514e 100644 --- a/stick/src/raw/windows.rs +++ b/stick/src/windows/xinput.rs @@ -63,9 +63,9 @@ impl Drop for ScopedHMODULE { /// A handle to a loaded XInput DLL. #[derive(Clone)] -struct XInputHandle { +pub(super) struct XInputHandle { handle: Arc, - xinput_enable: XInputEnableFunc, + pub(super) xinput_enable: XInputEnableFunc, xinput_get_state: XInputGetStateFunc, xinput_set_state: XInputSetStateFunc, xinput_get_capabilities: XInputGetCapabilitiesFunc, @@ -622,7 +622,7 @@ extern "C" fn waker_callback( } } -fn register_wake_timeout(delay: u32, waker: &Waker) { +pub(super) fn register_wake_timeout(delay: u32, waker: &Waker) { unsafe { let waker = std::mem::transmute::<&Waker, usize>(waker); @@ -630,243 +630,120 @@ fn register_wake_timeout(delay: u32, waker: &Waker) { } } -//////////////////////////////////////////////////////////////////////////////// - -pub(crate) struct Controller { - xinput: Arc, - device_id: u8, - pending_events: Vec, - last_packet: DWORD, -} - -impl Controller { - #[allow(unused)] - fn new(device_id: u8, xinput: Arc) -> Self { - Self { - xinput, - device_id, - pending_events: Vec::new(), - last_packet: 0, - } - } -} - -impl super::Controller for Controller { - fn id(&self) -> u64 { - 0 // FIXME - } - - /// Poll for events. - fn poll(&mut self, cx: &mut Context<'_>) -> Poll { - if let Some(e) = self.pending_events.pop() { - return Poll::Ready(e); - } - - if let Ok(state) = self.xinput.get_state(self.device_id as u32) { - if state.raw.dwPacketNumber != self.last_packet { - // we have a new packet from the controller - self.last_packet = state.raw.dwPacketNumber; - - let (nx, ny) = XInputState::normalize_raw_stick_value( - (state.raw.Gamepad.sThumbRX, state.raw.Gamepad.sThumbRY), - xinput::XINPUT_GAMEPAD_RIGHT_THUMB_DEADZONE, - ); - - self.pending_events.push(Event::CamX(nx)); - self.pending_events.push(Event::CamY(ny)); - - let (nx, ny) = XInputState::normalize_raw_stick_value( - (state.raw.Gamepad.sThumbLX, state.raw.Gamepad.sThumbLY), - xinput::XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE, - ); - - self.pending_events.push(Event::JoyX(nx)); - self.pending_events.push(Event::JoyY(ny)); - - let t = if state.raw.Gamepad.bLeftTrigger - > xinput::XINPUT_GAMEPAD_TRIGGER_THRESHOLD - { - state.raw.Gamepad.bLeftTrigger - } else { - 0 - }; - - self.pending_events.push(Event::TriggerL(t as f64 / 255.0)); - - let t = if state.raw.Gamepad.bRightTrigger - > xinput::XINPUT_GAMEPAD_TRIGGER_THRESHOLD - { - state.raw.Gamepad.bRightTrigger - } else { - 0 - }; - - self.pending_events.push(Event::TriggerR(t as f64 / 255.0)); - - while let Ok(Some(keystroke)) = - self.xinput.get_keystroke(self.device_id as u32) - { - // Ignore key repeat events - if keystroke.Flags & xinput::XINPUT_KEYSTROKE_REPEAT != 0 { - continue; - } - - let held = - keystroke.Flags & xinput::XINPUT_KEYSTROKE_KEYDOWN != 0; - - match keystroke.VirtualKey { - xinput::VK_PAD_START => { - self.pending_events.push(Event::MenuR(held)) - } - xinput::VK_PAD_BACK => { - self.pending_events.push(Event::MenuL(held)) - } - xinput::VK_PAD_A => { - self.pending_events.push(Event::ActionA(held)) - } - xinput::VK_PAD_B => { - self.pending_events.push(Event::ActionB(held)) - } - xinput::VK_PAD_X => { - self.pending_events.push(Event::ActionH(held)) - } - xinput::VK_PAD_Y => { - self.pending_events.push(Event::ActionV(held)) - } - xinput::VK_PAD_LSHOULDER => { - self.pending_events.push(Event::BumperL(held)) - } - xinput::VK_PAD_RSHOULDER => { - self.pending_events.push(Event::BumperR(held)) - } - xinput::VK_PAD_LTHUMB_PRESS => { - self.pending_events.push(Event::Joy(held)) - } - xinput::VK_PAD_RTHUMB_PRESS => { - self.pending_events.push(Event::Cam(held)) - } - xinput::VK_PAD_DPAD_UP => { - self.pending_events.push(Event::Up(held)) - } - xinput::VK_PAD_DPAD_DOWN => { - self.pending_events.push(Event::Down(held)) - } - xinput::VK_PAD_DPAD_LEFT => { - self.pending_events.push(Event::Left(held)) - } - xinput::VK_PAD_DPAD_RIGHT => { - self.pending_events.push(Event::Right(held)) - } - _ => (), - } - } - - if let Some(event) = self.pending_events.pop() { - return Poll::Ready(event); - } - } - } else { - // the device has gone - return Poll::Ready(Event::Disconnect); - } - - register_wake_timeout(10, cx.waker()); - Poll::Pending - } - - /// Stereo rumble effect (left is low frequency, right is high frequency). - fn rumble(&mut self, left: f32, right: f32) { - self.xinput - .set_state( - self.device_id as u32, - (u16::MAX as f32 * left) as u16, - (u16::MAX as f32 * right) as u16, - ) - .unwrap() - } - - /// Get the name of this controller. - fn name(&self) -> &str { - "XInput Controller" - } -} - -pub(crate) struct Listener { - xinput: Arc, - connected: u64, - to_check: u8, - remap: Remap, -} - -impl Listener { - fn new(remap: Remap, xinput: Arc) -> Self { - Self { - xinput, - connected: 0, - to_check: 0, - remap, - } - } -} - -impl super::Listener for Listener { - fn poll(&mut self, cx: &mut Context<'_>) -> Poll { - let id = self.to_check; - let mask = 1 << id; - self.to_check += 1; - // direct input only allows for 4 controllers - if self.to_check > 3 { - self.to_check = 0; - } - let was_connected = (self.connected & mask) != 0; - - if self.xinput.get_state(id as u32).is_ok() { - if !was_connected { - // we have a new device! - self.connected |= mask; - - return Poll::Ready(crate::Controller::new( - Box::new(Controller::new(id, self.xinput.clone())), - &self.remap, - )); - } - } else if was_connected { - // a device has been unplugged - self.connected &= !mask; - } - - register_wake_timeout(100, cx.waker()); - - Poll::Pending - } -} - -struct Global { - xinput: Arc, -} - -impl super::Global for Global { - /// Enable all events (when window comes in focus). - fn enable(&self) { - unsafe { (self.xinput.xinput_enable)(true as _) }; - } - - /// Disable all events (when window leaves focus). - fn disable(&self) { - unsafe { (self.xinput.xinput_enable)(false as _) }; - } - - /// Create a new listener. - fn listener(&self, remap: Remap) -> Box { - Box::new(Listener::new(remap, self.xinput.clone())) - } -} - -pub(super) fn global() -> Box { - // Windows implementation may fail. - if let Ok(xinput) = XInputHandle::load_default() { - Box::new(Global { xinput }) - } else { - Box::new(super::FakeGlobal) - } +/// Poll for events. +pub(super) fn poll(controller: &mut Controller, cx: &mut Context<'_>) -> Poll { + if let Some(e) = controller.pending_events.pop() { + return Poll::Ready(e); + } + + if let Ok(state) = controller.xinput.get_state(controller.device_id as u32) { + if state.raw.dwPacketNumber != controller.last_packet { + // we have a new packet from the controller + controller.last_packet = state.raw.dwPacketNumber; + + let (nx, ny) = XInputState::normalize_raw_stick_value( + (state.raw.Gamepad.sThumbRX, state.raw.Gamepad.sThumbRY), + xinput::XINPUT_GAMEPAD_RIGHT_THUMB_DEADZONE, + ); + + controller.pending_events.push(Event::CamX(nx)); + controller.pending_events.push(Event::CamY(ny)); + + let (nx, ny) = XInputState::normalize_raw_stick_value( + (state.raw.Gamepad.sThumbLX, state.raw.Gamepad.sThumbLY), + xinput::XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE, + ); + + controller.pending_events.push(Event::JoyX(nx)); + controller.pending_events.push(Event::JoyY(ny)); + + let t = if state.raw.Gamepad.bLeftTrigger + > xinput::XINPUT_GAMEPAD_TRIGGER_THRESHOLD + { + state.raw.Gamepad.bLeftTrigger + } else { + 0 + }; + + controller.pending_events.push(Event::TriggerL(t as f64 / 255.0)); + + let t = if state.raw.Gamepad.bRightTrigger + > xinput::XINPUT_GAMEPAD_TRIGGER_THRESHOLD + { + state.raw.Gamepad.bRightTrigger + } else { + 0 + }; + + controller.pending_events.push(Event::TriggerR(t as f64 / 255.0)); + + while let Ok(Some(keystroke)) = + controller.xinput.get_keystroke(controller.device_id as u32) + { + // Ignore key repeat events + if keystroke.Flags & xinput::XINPUT_KEYSTROKE_REPEAT != 0 { + continue; + } + + let held = + keystroke.Flags & xinput::XINPUT_KEYSTROKE_KEYDOWN != 0; + + match keystroke.VirtualKey { + xinput::VK_PAD_START => { + controller.pending_events.push(Event::MenuR(held)) + } + xinput::VK_PAD_BACK => { + controller.pending_events.push(Event::MenuL(held)) + } + xinput::VK_PAD_A => { + controller.pending_events.push(Event::ActionA(held)) + } + xinput::VK_PAD_B => { + controller.pending_events.push(Event::ActionB(held)) + } + xinput::VK_PAD_X => { + controller.pending_events.push(Event::ActionH(held)) + } + xinput::VK_PAD_Y => { + controller.pending_events.push(Event::ActionV(held)) + } + xinput::VK_PAD_LSHOULDER => { + controller.pending_events.push(Event::BumperL(held)) + } + xinput::VK_PAD_RSHOULDER => { + controller.pending_events.push(Event::BumperR(held)) + } + xinput::VK_PAD_LTHUMB_PRESS => { + controller.pending_events.push(Event::Joy(held)) + } + xinput::VK_PAD_RTHUMB_PRESS => { + controller.pending_events.push(Event::Cam(held)) + } + xinput::VK_PAD_DPAD_UP => { + controller.pending_events.push(Event::Up(held)) + } + xinput::VK_PAD_DPAD_DOWN => { + controller.pending_events.push(Event::Down(held)) + } + xinput::VK_PAD_DPAD_LEFT => { + controller.pending_events.push(Event::Left(held)) + } + xinput::VK_PAD_DPAD_RIGHT => { + controller.pending_events.push(Event::Right(held)) + } + _ => (), + } + } + + if let Some(event) = controller.pending_events.pop() { + return Poll::Ready(event); + } + } + } else { + // the device has gone + return Poll::Ready(Event::Disconnect); + } + + register_wake_timeout(10, cx.waker()); + Poll::Pending }