diff --git a/stick/Cargo.toml b/stick/Cargo.toml index 418edfd..59db36f 100644 --- a/stick/Cargo.toml +++ b/stick/Cargo.toml @@ -2,7 +2,6 @@ name = "stick" version = "0.13.0" license = "Apache-2.0 OR BSL-1.0 OR MIT" - description = """ Platform-agnostic asynchronous gamepad, joystick, and flightstick library """ @@ -16,7 +15,8 @@ readme = "README.md" edition = "2021" [target.'cfg(all(not(target_arch="wasm32"),target_os="linux"))'.dependencies] -smelling_salts = "0.4" +smelling_salts = "0.6" +lookit = "0.1" [target.'cfg(windows)'.dependencies] winapi = { version = "0.3", features = ["libloaderapi", "xinput", "winerror"] } diff --git a/stick/build.rs b/stick/build.rs new file mode 100644 index 0000000..85e8303 --- /dev/null +++ b/stick/build.rs @@ -0,0 +1,19 @@ +use std::env; + +fn main() { + let target = &env::var("TARGET").unwrap(); + let target_family = &env::var("CARGO_CFG_TARGET_FAMILY").unwrap(); + let target_os = &env::var("CARGO_CFG_TARGET_OS").unwrap(); + let target_arch = &env::var("CARGO_CFG_TARGET_ARCH").unwrap(); + let target_vendor = &env::var("CARGO_CFG_TARGET_VENDOR").unwrap(); + let target_env = &env::var("CARGO_CFG_TARGET_ENV").unwrap(); + let unsupported = + format!( + "Target environment {} ({}, {}, {}, {}, {}) not suppported, please \ + consider opening an issue at https://github.com/libcala/stick/issues", + target, target_family, target_os, target_arch, target_vendor, target_env + ); + let mut out_file = env::var("OUT_DIR").unwrap(); + out_file.push_str("/unsupported.rs"); + std::fs::write(out_file, unsupported).unwrap(); +} diff --git a/stick/examples/haptic.rs b/stick/examples/haptic.rs index 9853daf..d67332a 100644 --- a/stick/examples/haptic.rs +++ b/stick/examples/haptic.rs @@ -2,13 +2,12 @@ use std::task::Poll::{self, Pending, Ready}; -use pasts::Loop; -use stick::{Controller, Event, Listener}; +use stick::{Connector, Controller, Event}; type Exit = usize; struct State { - listener: Listener, + connector: Connector, controllers: Vec, rumble: (f32, f32), } @@ -55,13 +54,13 @@ impl State { async fn event_loop() { let mut state = State { - listener: Listener::default(), + connector: Connector::default(), controllers: Vec::new(), rumble: (0.0, 0.0), }; let player_id = Loop::new(&mut state) - .when(|s| &mut s.listener, State::connect) + .when(|s| &mut s.connector, State::connect) .poll(|s| &mut s.controllers, State::event) .await; diff --git a/stick/src/connector.rs b/stick/src/connector.rs new file mode 100644 index 0000000..c3e8918 --- /dev/null +++ b/stick/src/connector.rs @@ -0,0 +1,51 @@ +use std::{ + future::Future, + pin::Pin, + task::{Context, Poll}, +}; + +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)] +pub struct Connector(Lookit, Remap); + +impl Default for Connector { + fn default() -> Self { + Self::new(Remap::default()) + } +} + +impl Connector { + /// Create a new controller connector + pub fn new(remap: Remap) -> Self { + Self(Lookit::with_input(), remap) + } +} + +impl Future for Connector { + type Output = Controller; + + fn poll( + mut self: Pin<&mut Self>, + cx: &mut Context<'_>, + ) -> Poll { + let a = Pin::new(&mut self.as_mut().0) + .poll(cx) + .map(|device| Controller::new(device, &self.1)); + match a { + Poll::Ready(Some(x)) => Poll::Ready(x), + Poll::Ready(None) => self.poll(cx), + Poll::Pending => Poll::Pending, + } + } +} diff --git a/stick/src/ctlr.rs b/stick/src/ctlr.rs index 701522b..81ca970 100644 --- a/stick/src/ctlr.rs +++ b/stick/src/ctlr.rs @@ -7,7 +7,24 @@ use std::{ task::{Context, Poll}, }; -use crate::Event; +use lookit::It; + +use crate::{ + 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 { @@ -266,13 +283,19 @@ pub struct Controller { // Shared remapping. remap: Arc, // - raw: Box, + raw: CtlrId, // Button states btns: u128, // Number button states nums: u128, // Axis states: axis: [f64; Axs::Count as usize], + // Unique platform-specific controller ID. + id: u64, + // Name of the controller. + name: String, + // Is the platform controller ready? If so, keep polling. + ready: bool, } impl Debug for Controller { @@ -283,31 +306,33 @@ impl Debug for Controller { impl Controller { #[allow(unused)] - pub(crate) fn new( - raw: Box, - remap: &Remap, - ) -> Self { + pub(crate) fn new(which: It, remap: &Remap) -> Option { let btns = 0; let nums = 0; let axis = [0.0; Axs::Count as usize]; - let remap = remap.0.get(&raw.id()).cloned().unwrap_or_default(); - Self { + let (id, name, raw) = platform().connect(which)?; + let remap = remap.0.get(&id).cloned().unwrap_or_default(); + let ready = true; + Some(Self { remap, raw, btns, nums, axis, - } + id, + name, + ready, + }) } /// Get a unique identifier for the specific model of gamepad. pub fn id(&self) -> u64 { - self.raw.id() + self.id } /// Get the name of this Pad. pub fn name(&self) -> &str { - self.raw.name() + &self.name } /// Turn on/off haptic force feedback. @@ -319,7 +344,7 @@ impl Controller { /// located on the left, and the second is typically high frequency and is /// located on the right (controllers may vary). pub fn rumble(&mut self, power: R) { - self.raw.rumble(power.left(), power.right()); + platform().rumble(&mut self.raw, power.left(), power.right()); } fn button(&mut self, b: Btn, f: fn(bool) -> Event, p: bool) -> Poll { @@ -363,7 +388,7 @@ impl Controller { - 1.0) .clamp(-1.0, 1.0) } else { - self.raw.axis(v).clamp(-1.0, 1.0) + v.clamp(-1.0, 1.0) }; if !map.deadzone.is_nan() && v.abs() <= map.deadzone { 0.0 @@ -371,7 +396,7 @@ impl Controller { v } } else { - self.raw.axis(v).clamp(-1.0, 1.0) + v.clamp(-1.0, 1.0) }; let axis = a as usize; if self.axis[axis] == v { @@ -396,7 +421,7 @@ impl Controller { ((v - f64::from(map.min)) / f64::from(map.max - map.min)) .clamp(0.0, 1.0) } else { - self.raw.pressure(v).clamp(0.0, 1.0) + v.clamp(0.0, 1.0) }; if !map.deadzone.is_nan() && v <= map.deadzone { 0.0 @@ -404,7 +429,7 @@ impl Controller { v } } else { - self.raw.pressure(v).clamp(0.0, 1.0) + v.clamp(0.0, 1.0) }; let axis = a as usize; if self.axis[axis] == v { @@ -567,16 +592,23 @@ impl Future for Controller { fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { let mut this = self.as_mut(); - if let Poll::Ready(event) = this.raw.poll(cx) { - let out = Self::process(&mut this, event); - if out.is_pending() { - Self::poll(self, cx) - } else { - out + if this.ready { + 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) + } else { + out + }; } - } else { - Poll::Pending } + + this.ready = Pin::new(&mut this.raw.device).poll(cx).is_ready(); + if !this.ready { + return Poll::Pending; + } + + Self::poll(self, cx) } } diff --git a/stick/src/focus.rs b/stick/src/focus.rs index 2da2f35..f1789e4 100644 --- a/stick/src/focus.rs +++ b/stick/src/focus.rs @@ -1,9 +1,11 @@ +use crate::platform::Support; + /// Window grab focus, re-enable events if they were disabled. pub fn focus() { - crate::raw::GLOBAL.with(|g| g.enable()); + crate::platform::platform().enable(); } /// Window ungrab focus, disable events. pub fn unfocus() { - crate::raw::GLOBAL.with(|g| g.disable()); + crate::platform::platform().disable(); } diff --git a/stick/src/lib.rs b/stick/src/lib.rs index a4ef87a..623199c 100644 --- a/stick/src/lib.rs +++ b/stick/src/lib.rs @@ -14,12 +14,12 @@ //! ```rust,no_run //! use pasts::Loop; //! use std::task::Poll::{self, Pending, Ready}; -//! use stick::{Controller, Event, Listener}; +//! use stick::{Controller, Event, Connector}; //! //! type Exit = usize; //! //! struct State { -//! listener: Listener, +//! connector: Connector, //! controllers: Vec, //! rumble: (f32, f32), //! } @@ -66,13 +66,13 @@ //! //! async fn event_loop() { //! let mut state = State { -//! listener: Listener::default(), +//! connector: Connector::default(), //! controllers: Vec::new(), //! rumble: (0.0, 0.0), //! }; //! //! let player_id = Loop::new(&mut state) -//! .when(|s| &mut s.listener, State::connect) +//! .when(|s| &mut s.connector, State::connect) //! .poll(|s| &mut s.controllers, State::event) //! .await; //! @@ -110,13 +110,25 @@ #[macro_use] extern crate log; +// Platform-specific implementation +mod platform { + #![allow(clippy::module_inception)] + + mod platform; + + pub(crate) use platform::{platform, CtlrId, Support}; +} + +mod connector; mod ctlr; mod event; mod focus; -mod listener; -mod raw; +// mod listener; +// mod raw; +pub use connector::Connector; pub use ctlr::{Controller, Remap}; pub use event::Event; pub use focus::{focus, unfocus}; -pub use listener::Listener; + +// pub use listener::Listener; diff --git a/stick/src/linux/controller.rs b/stick/src/linux/controller.rs new file mode 100644 index 0000000..cc4bda5 --- /dev/null +++ b/stick/src/linux/controller.rs @@ -0,0 +1,49 @@ +//! File I/O + +use std::io::{BufReader}; +use std::fs::File; +use lookit::It; +use std::os::unix::io::{AsRawFd}; +use smelling_salts::linux::{Device, Watcher}; + +use crate::Event; + +pub(crate) struct Controller { + pub(crate) device: Device, + pub(crate) queued: Option, + pub(crate) stream: BufReader, + pub(crate) abs_ranges: [super::evdev::AbsRange; super::evdev::ABS_MAX], + pub(crate) rumble: i16, +} + +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 + .ok()?; + let device = file.as_raw_fd(); + dbg!(device); + let stream = BufReader::new(file); + let abs_ranges = super::evdev::AbsRange::query(device); + let watcher = Watcher::new().input(); + + // Cache some information about the controller. + let id = super::evdev::hardware_id(device); + let name = super::evdev::hardware_name(device); + let rumble = super::haptic::joystick_haptic(device, -1, 0.0, 0.0); + + // Return controller information. + Some(( + id, + name, + Controller { + queued: None, + stream, + device: Device::new(device, watcher, true), + abs_ranges, + rumble, + } + )) +} diff --git a/stick/src/linux/evdev.rs b/stick/src/linux/evdev.rs new file mode 100644 index 0000000..a9e4802 --- /dev/null +++ b/stick/src/linux/evdev.rs @@ -0,0 +1,459 @@ +//! Evdev -> Stick event conversion + +use std::io::{Result, Read}; +use std::os::raw::{c_ushort, c_int, c_uint, c_long, c_ulong, c_void, c_char}; +use std::cmp::Ordering; +use std::os::unix::io::RawFd; +use std::mem::MaybeUninit; + +use crate::Event; +use super::controller::Controller; + +pub(crate) const EVENT_SIZE: usize = std::mem::size_of::(); + +// https://github.com/torvalds/linux/blob/master/include/uapi/linux/input-event-codes.h +pub(crate) const ABS_MAX: usize = 0x3F; + +extern "C" { + fn ioctl(fd: RawFd, request: c_ulong, v: *mut c_void) -> c_int; +} + +/// Query +pub(crate) unsafe fn haptic_query(fd: RawFd, b: *mut c_void) -> Option { + if ioctl(fd, 0x40304580, b) == -1 { + Some(-1) + } else { + None + } +} + +/// Get the hardware id of this controller. +pub(crate) fn hardware_id(fd: RawFd) -> u64 { + let mut id = MaybeUninit::::uninit(); + assert_ne!( + unsafe { ioctl(fd, 0x_8008_4502, id.as_mut_ptr().cast()) }, + -1 + ); + unsafe { id.assume_init() }.to_be() +} + +pub(crate) fn hardware_name(fd: RawFd) -> String { + let mut a = MaybeUninit::<[c_char; 256]>::uninit(); + assert_ne!( + unsafe { ioctl(fd, 0x80FF_4506, a.as_mut_ptr().cast()) }, + -1 + ); + let a = unsafe { a.assume_init() }; + let name = unsafe { std::ffi::CStr::from_ptr(a.as_ptr()) }; + + name.to_string_lossy().to_string() +} + +#[repr(C)] +struct AbsInfo { + // struct input_absinfo, from C. + value: i32, + minimum: u32, + maximum: u32, + fuzz: i32, + flat: i32, + resolution: i32, +} + +#[derive(Default, Copy, Clone, Debug)] +pub(crate) struct AbsRange { + // Minimum + min: c_uint, + // Flat + flat: c_uint, + // Normalization (2.0 / (maximum - minimum)) + norm: f64, +} + +impl AbsRange { + /// Normalize evdev event as f64. + fn normalize(&self, value: c_uint) -> f64 { + if (value as c_int).abs() < (self.flat as c_int) { + return 0.0; + } + + let unsigned = (value.wrapping_sub(self.min)) as f64; + unsigned * self.norm - 1.0 + } + + /// Query absolute axes. + pub(crate) fn query(fd: RawFd) -> [Self; ABS_MAX] { + let mut output = [Self::default(); ABS_MAX]; + + fn test_bit(index: usize, axis_list: &[u8; ABS_MAX / 8 + 1]) -> bool { + let byte = index / 8; + let bit = index % 8; + axis_list[byte] & (1 << bit) != 0 + } + + let mut axis_list = MaybeUninit::<[u8; ABS_MAX / 8 + 1]>::uninit(); + assert_ne!(unsafe { ioctl(fd, 0x_8008_4523, axis_list.as_mut_ptr().cast()) }, -1); + let axis_list = unsafe { axis_list.assume_init() }; + + for i in 0..ABS_MAX { + if test_bit(i, &axis_list) { + let mut info = MaybeUninit::::uninit(); + if unsafe { + ioctl(fd, 0x_8018_4540 + i as c_ulong, info.as_mut_ptr().cast()) + } == -1 { + continue; + } + let info = unsafe { info.assume_init() }; + let _value = info.value; // FIXME: Send event. + let min = info.minimum; + let norm = 2.0 / info.maximum.wrapping_sub(info.minimum) as f64; + let flat = info.flat as c_uint; + + output[i] = AbsRange { + min, + norm, + flat, + }; + } + } + + output + } +} + +#[repr(C)] +pub(crate) struct TimeVal { + // struct timeval, from C. + pub(crate) tv_sec: c_long, + pub(crate) tv_usec: c_long, +} + +#[repr(C)] +pub(crate) struct EvdevEv { + // struct input_event, from C. + pub(crate) ev_time: TimeVal, + pub(crate) ev_type: c_ushort, + pub(crate) ev_code: c_ushort, + pub(crate) ev_value: c_uint, +} + +// Event codes taken from +// https://github.com/torvalds/linux/blob/master/include/uapi/linux/input-event-codes.h + +// Convert Linux BTN press to stick Event. +fn to_stick_btn( + btn: c_ushort, + pushed: bool, +) -> Result> { + Ok(Some(match btn { + 0x08B /* KEY_MENU */ => Event::Context(pushed), + + 0x09E /* KEY_BACK */ => Event::PaddleLeft(pushed), + 0x09F /* KEY_FORWARD */ => Event::PaddleRight(pushed), + + 0x120 /* BTN_TRIGGER */ => Event::Trigger(pushed), + 0x121 /* BTN_THUMB */ => Event::ActionM(pushed), + 0x122 /* BTN_THUMB2 */ => Event::Bumper(pushed), + 0x123 /* BTN_TOP */ => Event::ActionR(pushed), + 0x124 /* BTN_TOP2 */ => Event::ActionL(pushed), + 0x125 /* BTN_PINKIE */ => Event::Pinky(pushed), + 0x126 /* BTN_BASE1 */ => Event::Number(1, pushed), + 0x127 /* BTN_BASE2 */ => Event::Number(2, pushed), + 0x128 /* BTN_BASE3 */ => Event::Number(3, pushed), + 0x129 /* BTN_BASE4 */ => Event::Number(4, pushed), + 0x12A /* BTN_BASE5 */ => Event::Number(5, pushed), + 0x12B /* BTN_BASE6 */ => Event::Number(6, pushed), + 0x12C /* BTN_BASE7 */ => Event::Number(7, pushed), + 0x12D /* BTN_BASE8 */ => Event::Number(8, pushed), + 0x12E /* BTN_BASE9 */ => Event::Number(9, pushed), + 0x12F /* BTN_BASE10 */ => Event::Number(10, pushed), + + 0x130 /* BTN_A / BTN_SOUTH */ => Event::ActionA(pushed), + 0x131 /* BTN_B / BTN_EAST */ => Event::ActionB(pushed), + 0x132 /* BTN_C */ => Event::ActionC(pushed), + 0x133 /* BTN_X / BTN_NORTH */ => Event::ActionV(pushed), + 0x134 /* BTN_Y / BTN_WEST */ => Event::ActionH(pushed), + 0x135 /* BTN_Z */ => Event::ActionD(pushed), + 0x136 /* BTN_TL */ => Event::BumperL(pushed), + 0x137 /* BTN_TR */ => Event::BumperR(pushed), + 0x138 /* BTN_TL2 */ => Event::TriggerL(f64::from(u8::from(pushed)) * 255.0), + 0x139 /* BTN_TR2 */ => Event::TriggerR(f64::from(u8::from(pushed)) * 255.0), + 0x13A /* BTN_SELECT */ => Event::MenuL(pushed), + 0x13B /* BTN_START */ => Event::MenuR(pushed), + 0x13C /* BTN_MODE */ => Event::Exit(pushed), + 0x13D /* BTN_THUMBL */ => Event::Joy(pushed), + 0x13E /* BTN_THUMBR */ => Event::Cam(pushed), + + 0x220 /* BTN_DPAD_UP */ => Event::Up(pushed), + 0x221 /* BTN_DPAD_DOWN */ => Event::Down(pushed), + 0x222 /* BTN_DPAD_LEFT */ => Event::Left(pushed), + 0x223 /* BTN_DPAD_RIGHT */ => Event::Right(pushed), + + 0x2C0 /* BTN_TRIGGER_HAPPY1 */ => Event::Number(11, pushed), + 0x2C1 /* BTN_TRIGGER_HAPPY2 */ => Event::Number(12, pushed), + 0x2C2 /* BTN_TRIGGER_HAPPY3 */ => Event::Number(13, pushed), + 0x2C3 /* BTN_TRIGGER_HAPPY4 */ => Event::Number(14, pushed), + 0x2C4 /* BTN_TRIGGER_HAPPY5 */ => Event::Number(15, pushed), + 0x2C5 /* BTN_TRIGGER_HAPPY6 */ => Event::Number(16, pushed), + 0x2C6 /* BTN_TRIGGER_HAPPY7 */ => Event::Number(17, pushed), + 0x2C7 /* BTN_TRIGGER_HAPPY8 */ => Event::Number(18, pushed), + 0x2C8 /* BTN_TRIGGER_HAPPY9 */ => Event::Number(19, pushed), + 0x2C9 /* BTN_TRIGGER_HAPPY10 */ => Event::Number(20, pushed), + 0x2CA /* BTN_TRIGGER_HAPPY11 */ => Event::Number(21, pushed), + 0x2CB /* BTN_TRIGGER_HAPPY12 */ => Event::Number(22, pushed), + 0x2CC /* BTN_TRIGGER_HAPPY13 */ => Event::Number(23, pushed), + 0x2CD /* BTN_TRIGGER_HAPPY14 */ => Event::Number(24, pushed), + 0x2CE /* BTN_TRIGGER_HAPPY15 */ => Event::Number(25, pushed), + 0x2CF /* BTN_TRIGGER_HAPPY16 */ => Event::Number(26, pushed), + 0x2D0 /* BTN_TRIGGER_HAPPY17 */ => Event::Number(27, pushed), + 0x2D1 /* BTN_TRIGGER_HAPPY18 */ => Event::Number(28, pushed), + 0x2D2 /* BTN_TRIGGER_HAPPY19 */ => Event::Number(29, pushed), + 0x2D3 /* BTN_TRIGGER_HAPPY20 */ => Event::Number(30, pushed), + 0x2D4 /* BTN_TRIGGER_HAPPY21 */ => Event::Number(31, pushed), + 0x2D5 /* BTN_TRIGGER_HAPPY22 */ => Event::Number(32, pushed), + 0x2D6 /* BTN_TRIGGER_HAPPY23 */ => Event::Number(33, pushed), + 0x2D7 /* BTN_TRIGGER_HAPPY24 */ => Event::Number(34, pushed), + 0x2D8 /* BTN_TRIGGER_HAPPY25 */ => Event::Number(35, pushed), + 0x2D9 /* BTN_TRIGGER_HAPPY26 */ => Event::Number(36, pushed), + 0x2DA /* BTN_TRIGGER_HAPPY27 */ => Event::Number(37, pushed), + 0x2DB /* BTN_TRIGGER_HAPPY28 */ => Event::Number(38, pushed), + 0x2DC /* BTN_TRIGGER_HAPPY29 */ => Event::Number(39, pushed), + 0x2DD /* BTN_TRIGGER_HAPPY30 */ => Event::Number(40, pushed), + 0x2DE /* BTN_TRIGGER_HAPPY31 */ => Event::Number(41, pushed), + 0x2DF /* BTN_TRIGGER_HAPPY32 */ => Event::Number(42, pushed), + 0x2E0 /* BTN_TRIGGER_HAPPY33 */ => Event::Number(43, pushed), + 0x2E1 /* BTN_TRIGGER_HAPPY34 */ => Event::Number(44, pushed), + 0x2E2 /* BTN_TRIGGER_HAPPY35 */ => Event::Number(45, pushed), + 0x2E3 /* BTN_TRIGGER_HAPPY36 */ => Event::Number(46, pushed), + 0x2E4 /* BTN_TRIGGER_HAPPY37 */ => Event::Number(47, pushed), + 0x2E5 /* BTN_TRIGGER_HAPPY38 */ => Event::Number(48, pushed), + 0x2E6 /* BTN_TRIGGER_HAPPY39 */ => Event::Number(49, pushed), + 0x2E7 /* BTN_TRIGGER_HAPPY40 */ => Event::Number(50, pushed), + + _unknown => { + eprintln!("Unknown Linux Button {}", _unknown); + eprintln!("Report at https://github.com/libcala/stick/issues"); + return Ok(None); + } + })) +} + +// Convert Linux REL axis to stick Event. +fn to_stick_rel( + axis: c_ushort, + value: c_uint, +) -> Result> { + Ok(Some(match axis { + 0x00 /* REL_X */ => Event::MouseX(value as f64), + 0x01 /* REL_Y */ => Event::MouseY(value as f64), + 0x02 /* REL_Z */ => { + eprintln!("FIXME: REL_Z"); + eprintln!("Report at https://github.com/libcala/stick/issues"); + return Ok(None) + } + 0x03 /* REL_RX */ => { + eprintln!("FIXME: REL_RX"); + eprintln!("Report at https://github.com/libcala/stick/issues"); + return Ok(None) + } + 0x04 /* REL_RY */ => { + eprintln!("FIXME: REL_RY"); + eprintln!("Report at https://github.com/libcala/stick/issues"); + return Ok(None) + } + 0x05 /* REL_RZ */ => { + eprintln!("FIXME: REL_RZ"); + eprintln!("Report at https://github.com/libcala/stick/issues"); + return Ok(None) + } + 0x06 /* REL_HWHEEL */ => { + eprintln!("FIXME: REL_HWHEEL"); + eprintln!("Report at https://github.com/libcala/stick/issues"); + return Ok(None) + } + 0x07 /* REL_DIAL */ => { + eprintln!("FIXME: REL_DIAL"); + eprintln!("Report at https://github.com/libcala/stick/issues"); + return Ok(None) + } + 0x08 /* REL_WHEEL */ => { + eprintln!("FIXME: REL_WHEEL"); + eprintln!("Report at https://github.com/libcala/stick/issues"); + return Ok(None) + } + 0x09 /* REL_MISC */ => { + eprintln!("FIXME: REL_MISC"); + eprintln!("Report at https://github.com/libcala/stick/issues"); + return Ok(None) + } + _unknown => { + eprintln!("Unknown Linux Axis {}", _unknown); + eprintln!("Report at https://github.com/libcala/stick/issues"); + return Ok(None) + } + })) +} + +// Convert Linux ABS axis to stick Event. +fn to_stick_abs( + controller: &mut Controller, + axis: c_ushort, + value: c_uint, +) -> Result> { + let value = controller.abs_ranges[usize::from(axis)].normalize(value); + Ok(Some(match axis { + 0x00 /* ABS_X */ => Event::JoyX(value), + 0x01 /* ABS_Y */ => Event::JoyY(value), + 0x02 /* ABS_Z */ => Event::JoyZ(value), + 0x03 /* ABS_RX */ => Event::CamX(value), + 0x04 /* ABS_RY */ => Event::CamY(value), + 0x05 /* ABS_RZ */ => Event::CamZ(value), + 0x06 /* ABS_THROTTLE */ => Event::Throttle(value), + 0x07 /* ABS_RUDDER */ => Event::Rudder(value), + 0x08 /* ABS_WHEEL */ => Event::Wheel(value), + 0x09 /* ABS_GAS */ => Event::Gas(value), + 0x0A /* ABS_BRAKE */ => Event::Brake(value), + 0x0B /* ABS_UNKNOWN0 */ => Event::Slew(value), + 0x0C /* ABS_UNKNOWN1 */ => Event::ThrottleL(value), + 0x0D /* ABS_UNKNOWN2 */ => Event::ThrottleR(value), + 0x0E /* ABS_UNKNOWN3 */ => Event::ScrollX(value), + 0x0F /* ABS_UNKNOWN4 */ => Event::ScrollY(value), + 0x10 /* ABS_HAT0X */ => match value.partial_cmp(&0.0).unwrap() { + Ordering::Greater => Event::PovRight(true), + Ordering::Less => Event::PovLeft(true), + Ordering::Equal => { + controller.queued = Some(Event::PovLeft(false)); + Event::PovRight(false) + } + }, + 0x11 /* ABS_HAT0Y */ => match value.partial_cmp(&0.0).unwrap() { + Ordering::Greater => Event::PovDown(true), + Ordering::Less => Event::PovUp(true), + Ordering::Equal => { + controller.queued = Some(Event::PovUp(false)); + Event::PovDown(false) + } + }, + 0x12 /* ABS_HAT1X */ => match value.partial_cmp(&0.0).unwrap() { + Ordering::Greater => Event::HatRight(true), + Ordering::Less => Event::HatLeft(true), + Ordering::Equal => { + controller.queued = Some(Event::HatLeft(false)); + Event::HatRight(false) + } + }, + 0x13 /* ABS_HAT1Y */ => match value.partial_cmp(&0.0).unwrap() { + Ordering::Greater => Event::HatDown(true), + Ordering::Less => Event::HatUp(true), + Ordering::Equal => { + controller.queued = Some(Event::HatUp(false)); + Event::HatDown(false) + } + }, + 0x14 /* ABS_HAT2X */ => match value.partial_cmp(&0.0).unwrap() { + Ordering::Greater => Event::TrimRight(true), + Ordering::Less => Event::TrimLeft(true), + Ordering::Equal => { + controller.queued = Some(Event::TrimLeft(false)); + Event::TrimRight(false) + } + }, + 0x15 /* ABS_HAT2Y */ => match value.partial_cmp(&0.0).unwrap() { + Ordering::Greater => Event::TrimDown(true), + Ordering::Less => Event::TrimUp(true), + Ordering::Equal => { + controller.queued = Some(Event::TrimUp(false)); + Event::TrimDown(false) + } + }, + 0x16 /* ABS_HAT3X */ => match value.partial_cmp(&0.0).unwrap() { + Ordering::Greater => Event::MicRight(true), + Ordering::Less => Event::MicLeft(true), + Ordering::Equal => { + controller.queued = Some(Event::MicLeft(false)); + Event::MicRight(false) + } + }, + 0x17 /* ABS_HAT3Y */ => match value.partial_cmp(&0.0).unwrap() { + Ordering::Greater => Event::MicDown(true), + Ordering::Less => Event::MicUp(true), + Ordering::Equal => { + controller.queued = Some(Event::MicUp(false)); + Event::MicDown(false) + } + }, + 0x18 /* ABS_PRESSURE */ => { + eprintln!("Unknown Event: ABS_PRESSURE"); + eprintln!("Report at https://github.com/libcala/stick/issues"); + return Ok(None) + } + 0x19 /* ABS_DISTANCE */ => { + eprintln!("Unknown Event: ABS_DISTANCE"); + eprintln!("Report at https://github.com/libcala/stick/issues"); + return Ok(None) + } + 0x1a /* ABS_TILT_X */ => { + eprintln!("Unknown Event: ABS_TILT_X"); + eprintln!("Report at https://github.com/libcala/stick/issues"); + return Ok(None) + } + 0x1b /* ABS_TILT_Y */ => { + eprintln!("Unknown Event: ABS_TILT_Y"); + eprintln!("Report at https://github.com/libcala/stick/issues"); + return Ok(None) + } + 0x1c /* ABS_TOOL_WIDTH */ => { + eprintln!("Unknown Event: ABS_TOOL_WIDTH"); + eprintln!("Report at https://github.com/libcala/stick/issues"); + return Ok(None) + } + 0x20 /* ABS_VOLUME */ => { + eprintln!("Unknown Event: ABS_VOLUME"); + eprintln!("Report at https://github.com/libcala/stick/issues"); + return Ok(None) + } + 0x28 /* ABS_MISC */ => { + eprintln!("Unknown Event: ABS_MISC"); + eprintln!("Report at https://github.com/libcala/stick/issues"); + return Ok(None) + } + _unknown => { + eprintln!("Unknown Linux Axis {}", _unknown); + eprintln!("Report at https://github.com/libcala/stick/issues"); + return Ok(None) + } + })) +} + +pub(crate) fn to_stick_events(controller: &mut Controller) -> Result> { + let mut event = [0u8; EVENT_SIZE]; + let v = controller.stream.read(&mut event[..])?; + assert_eq!(v, EVENT_SIZE); + + // If input is disabled, don't send events. + if !super::ENABLED.load(std::sync::atomic::Ordering::Relaxed) { + return Ok(None); + } + + let e: EvdevEv = unsafe { std::mem::transmute(event) }; + match e.ev_type { + 0x00 /* SYN */ => Ok(None), // Ignore Syn Input Events + 0x01 /* BTN */ => to_stick_btn(e.ev_code, e.ev_value != 0), + 0x02 /* REL */ => to_stick_rel(e.ev_code, e.ev_value), + 0x03 /* ABS */ => to_stick_abs(controller, e.ev_code, e.ev_value), + 0x04 /* MSC */ => { + if e.ev_code != 4 { // Ignore Misc./Scan Events + let (code, val) = (e.ev_code, e.ev_value); + eprintln!("Unknown Linux Misc Code: {}, Value: {}", code, val); + eprintln!("Report at https://github.com/libcala/stick/issues"); + } + Ok(None) + } + 0x15 /* FF */ => Ok(None), // Ignore Force Feedback Input Events + _unknown => { + eprintln!("Unknown Linux Event Type: {}", _unknown); + eprintln!("Report at https://github.com/libcala/stick/issues"); + Ok(None) + } + } +} diff --git a/stick/src/linux/haptic.rs b/stick/src/linux/haptic.rs new file mode 100644 index 0000000..70cf18a --- /dev/null +++ b/stick/src/linux/haptic.rs @@ -0,0 +1,162 @@ +use std::mem::size_of; +use std::convert::TryInto; +use std::os::unix::prelude::*; +use std::os::raw::{c_int, c_void}; + +extern "C" { + fn write(fd: RawFd, buf: *const c_void, count: usize) -> isize; + fn __errno_location() -> *mut c_int; +} + +// From: https://github.com/torvalds/linux/blob/master/include/uapi/linux/input.h + +#[repr(C)] +struct FfTrigger { + button: u16, + interval: u16, +} + +#[repr(C)] +struct FfReplay { + length: u16, + delay: u16, +} + +#[repr(C)] +#[derive(Copy, Clone)] +struct FfEnvelope { + attack_length: u16, + attack_level: u16, + fade_length: u16, + fade_level: u16, +} + +#[repr(C)] +#[derive(Copy, Clone)] +struct FfConstantEffect { + level: i16, + envelope: FfEnvelope, +} + +#[repr(C)] +#[derive(Copy, Clone)] +struct FfRampEffect { + start_level: i16, + end_level: i16, + envelope: FfEnvelope, +} + +#[repr(C)] +#[derive(Copy, Clone)] +struct FfPeriodicEffect { + waveform: u16, + period: u16, + magnitude: i16, + offset: i16, + phase: u16, + + envelope: FfEnvelope, + + custom_len: u32, + custom_data: *mut i16, +} + +#[repr(C)] +#[derive(Copy, Clone)] +struct FfConditionEffect { + right_saturation: u16, + left_saturation: u16, + + right_coeff: i16, + left_coeff: i16, + + deadband: u16, + center: i16, +} + +#[repr(C)] +#[derive(Copy, Clone)] +struct FfRumbleEffect { + strong_magnitude: u16, + weak_magnitude: u16, +} + +#[repr(C)] +union FfUnion { + constant: FfConstantEffect, // Not supported. + ramp: FfRampEffect, + periodic: FfPeriodicEffect, + condition: [FfConditionEffect; 2], /* One for each axis */ + rumble: FfRumbleEffect, // Not supported +} + +#[repr(C)] +struct FfEffect { + stype: u16, + id: i16, + direction: u16, + + trigger: FfTrigger, + replay: FfReplay, + + u: FfUnion, +} + +pub(crate) fn joystick_ff(fd: RawFd, code: i16, strong: f32, weak: f32) { + // Update haptic effect `code`. + if strong != 0.0 || weak != 0.0 { + joystick_haptic(fd, code, strong, weak); + } + // + let ev_code = code.try_into().unwrap(); + + let play = &super::evdev::EvdevEv { + ev_time: super::evdev::TimeVal { + tv_sec: 0, + tv_usec: 0, + }, + ev_type: 0x15, /*EV_FF*/ + ev_code, + ev_value: (strong > 0.0 || weak > 0.0) as _, + }; + let play: *const _ = play; + unsafe { + if write(fd, play.cast(), size_of::()) + != size_of::() as isize + { + let errno = *__errno_location(); + if errno != 19 && errno != 9 { + // 19 = device unplugged, ignore + // 9 = device openned read-only, ignore + panic!("Write exited with {}", *__errno_location()); + } + } + } +} + +// Get ID's for rumble and vibrate, if they're supported (otherwise, -1). +pub(crate) fn joystick_haptic(fd: RawFd, id: i16, strong: f32, weak: f32) -> i16 { + let a = &mut FfEffect { + stype: 0x50, + id, /*allocate new effect*/ + direction: 0, + trigger: FfTrigger { + button: 0, + interval: 0, + }, + replay: FfReplay { + length: 0, + delay: 0, + }, + u: FfUnion { + rumble: FfRumbleEffect { + strong_magnitude: (u16::MAX as f32 * strong) as u16, + weak_magnitude: (u16::MAX as f32 * weak) as u16, + }, + }, + }; + #[allow(trivial_casts)] + let v = unsafe { super::evdev::haptic_query(fd, (a as *mut FfEffect).cast()).unwrap_or(a.id) }; + drop(a); + v +} diff --git a/stick/src/linux/linux.rs b/stick/src/linux/linux.rs new file mode 100644 index 0000000..2ca9acf --- /dev/null +++ b/stick/src/linux/linux.rs @@ -0,0 +1,49 @@ +mod controller; +mod evdev; +mod haptic; + +use std::sync::atomic::{Ordering, AtomicBool}; +use std::io::{ErrorKind}; +use std::os::unix::io::AsRawFd; + +use super::Support; +use crate::Event; + +pub(crate) use controller::{Controller, connect}; + +static ENABLED: AtomicBool = AtomicBool::new(true); + +pub(super) struct Platform; + +impl Support for &Platform { + fn enable(self) { + ENABLED.store(true, Ordering::Relaxed); + } + + fn disable(self) { + ENABLED.store(false, Ordering::Relaxed); + } + + fn rumble(self, controller: &mut Controller, left: f32, right: f32) { + haptic::joystick_ff(controller.stream.get_ref().as_raw_fd(), controller.rumble, left, right); + } + + fn event(self, controller: &mut Controller) -> Option { + if let Some(event) = controller.queued.take() { + return Some(event); + } + match evdev::to_stick_events(controller) { + Ok(None) => self.event(controller), + Ok(Some(event)) => Some(event), + Err(e) => if e.kind() != ErrorKind::WouldBlock { + Some(Event::Disconnect) + } else { + None + }, + } + } +} + +pub(super) fn platform() -> &'static Platform { + &Platform +} diff --git a/stick/src/platform/platform.rs b/stick/src/platform/platform.rs new file mode 100644 index 0000000..3c7f818 --- /dev/null +++ b/stick/src/platform/platform.rs @@ -0,0 +1,36 @@ +#![allow(unsafe_code)] + +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; + +/// 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); + /// 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/platform/unsupported.rs b/stick/src/platform/unsupported.rs new file mode 100644 index 0000000..86bfd30 --- /dev/null +++ b/stick/src/platform/unsupported.rs @@ -0,0 +1,7 @@ +compile_error!(include_str!(concat!(env!("OUT_DIR"), "/unsupported.rs"))); + +pub(super) type Device = core::marker::PhantomData; + +pub(super) fn start() -> Device> { + core::marker::PhantomData +} diff --git a/stick/src/raw/linux.rs b/stick/src/raw/linux.rs deleted file mode 100644 index 231c5d6..0000000 --- a/stick/src/raw/linux.rs +++ /dev/null @@ -1,822 +0,0 @@ -use std::{ - cmp::Ordering, - fs::read_dir, - mem::{size_of, MaybeUninit}, - os::{ - raw::{c_char, c_int, c_long, c_uint, c_ulong, c_ushort, c_void}, - unix::io::RawFd, - }, - task::{Context, Poll}, -}; - -use smelling_salts::{Device, Watcher}; - -use crate::{Event, Remap}; - -// Event codes taken from -// https://github.com/torvalds/linux/blob/master/include/uapi/linux/input-event-codes.h - -// Convert Linux BTN press to stick Event. -fn linux_btn_to_stick_event( - pending: &mut Vec, - btn: c_ushort, - pushed: bool, -) { - pending.push(match btn { - 0x08B /* KEY_MENU */ => Event::Context(pushed), - - 0x09E /* KEY_BACK */ => Event::PaddleLeft(pushed), - 0x09F /* KEY_FORWARD */ => Event::PaddleRight(pushed), - - 0x120 /* BTN_TRIGGER */ => Event::Trigger(pushed), - 0x121 /* BTN_THUMB */ => Event::ActionM(pushed), - 0x122 /* BTN_THUMB2 */ => Event::Bumper(pushed), - 0x123 /* BTN_TOP */ => Event::ActionR(pushed), - 0x124 /* BTN_TOP2 */ => Event::ActionL(pushed), - 0x125 /* BTN_PINKIE */ => Event::Pinky(pushed), - 0x126 /* BTN_BASE1 */ => Event::Number(1, pushed), - 0x127 /* BTN_BASE2 */ => Event::Number(2, pushed), - 0x128 /* BTN_BASE3 */ => Event::Number(3, pushed), - 0x129 /* BTN_BASE4 */ => Event::Number(4, pushed), - 0x12A /* BTN_BASE5 */ => Event::Number(5, pushed), - 0x12B /* BTN_BASE6 */ => Event::Number(6, pushed), - 0x12C /* BTN_BASE7 */ => Event::Number(7, pushed), - 0x12D /* BTN_BASE8 */ => Event::Number(8, pushed), - 0x12E /* BTN_BASE9 */ => Event::Number(9, pushed), - 0x12F /* BTN_BASE10 */ => Event::Number(10, pushed), - - 0x130 /* BTN_A / BTN_SOUTH */ => Event::ActionA(pushed), - 0x131 /* BTN_B / BTN_EAST */ => Event::ActionB(pushed), - 0x132 /* BTN_C */ => Event::ActionC(pushed), - 0x133 /* BTN_X / BTN_NORTH */ => Event::ActionV(pushed), - 0x134 /* BTN_Y / BTN_WEST */ => Event::ActionH(pushed), - 0x135 /* BTN_Z */ => Event::ActionD(pushed), - 0x136 /* BTN_TL */ => Event::BumperL(pushed), - 0x137 /* BTN_TR */ => Event::BumperR(pushed), - 0x138 /* BTN_TL2 */ => Event::TriggerL(f64::from(u8::from(pushed)) * 255.0), - 0x139 /* BTN_TR2 */ => Event::TriggerR(f64::from(u8::from(pushed)) * 255.0), - 0x13A /* BTN_SELECT */ => Event::MenuL(pushed), - 0x13B /* BTN_START */ => Event::MenuR(pushed), - 0x13C /* BTN_MODE */ => Event::Exit(pushed), - 0x13D /* BTN_THUMBL */ => Event::Joy(pushed), - 0x13E /* BTN_THUMBR */ => Event::Cam(pushed), - 0x13F /* BTN_PINKYR */ => Event::PinkyRight(pushed), - 0x140 /* BTN_PINKYL */ => Event::PinkyLeft(pushed), - - 0x220 /* BTN_DPAD_UP */ => Event::Up(pushed), - 0x221 /* BTN_DPAD_DOWN */ => Event::Down(pushed), - 0x222 /* BTN_DPAD_LEFT */ => Event::Left(pushed), - 0x223 /* BTN_DPAD_RIGHT */ => Event::Right(pushed), - - 0x2C0 /* BTN_TRIGGER_HAPPY1 */ => Event::Number(11, pushed), - 0x2C1 /* BTN_TRIGGER_HAPPY2 */ => Event::Number(12, pushed), - 0x2C2 /* BTN_TRIGGER_HAPPY3 */ => Event::Number(13, pushed), - 0x2C3 /* BTN_TRIGGER_HAPPY4 */ => Event::Number(14, pushed), - 0x2C4 /* BTN_TRIGGER_HAPPY5 */ => Event::Number(15, pushed), - 0x2C5 /* BTN_TRIGGER_HAPPY6 */ => Event::Number(16, pushed), - 0x2C6 /* BTN_TRIGGER_HAPPY7 */ => Event::Number(17, pushed), - 0x2C7 /* BTN_TRIGGER_HAPPY8 */ => Event::Number(18, pushed), - 0x2C8 /* BTN_TRIGGER_HAPPY9 */ => Event::Number(19, pushed), - 0x2C9 /* BTN_TRIGGER_HAPPY10 */ => Event::Number(20, pushed), - 0x2CA /* BTN_TRIGGER_HAPPY11 */ => Event::Number(21, pushed), - 0x2CB /* BTN_TRIGGER_HAPPY12 */ => Event::Number(22, pushed), - 0x2CC /* BTN_TRIGGER_HAPPY13 */ => Event::Number(23, pushed), - 0x2CD /* BTN_TRIGGER_HAPPY14 */ => Event::Number(24, pushed), - 0x2CE /* BTN_TRIGGER_HAPPY15 */ => Event::Number(25, pushed), - 0x2CF /* BTN_TRIGGER_HAPPY16 */ => Event::Number(26, pushed), - 0x2D0 /* BTN_TRIGGER_HAPPY17 */ => Event::Number(27, pushed), - 0x2D1 /* BTN_TRIGGER_HAPPY18 */ => Event::Number(28, pushed), - 0x2D2 /* BTN_TRIGGER_HAPPY19 */ => Event::Number(29, pushed), - 0x2D3 /* BTN_TRIGGER_HAPPY20 */ => Event::Number(30, pushed), - 0x2D4 /* BTN_TRIGGER_HAPPY21 */ => Event::Number(31, pushed), - 0x2D5 /* BTN_TRIGGER_HAPPY22 */ => Event::Number(32, pushed), - 0x2D6 /* BTN_TRIGGER_HAPPY23 */ => Event::Number(33, pushed), - 0x2D7 /* BTN_TRIGGER_HAPPY24 */ => Event::Number(34, pushed), - 0x2D8 /* BTN_TRIGGER_HAPPY25 */ => Event::Number(35, pushed), - 0x2D9 /* BTN_TRIGGER_HAPPY26 */ => Event::Number(36, pushed), - 0x2DA /* BTN_TRIGGER_HAPPY27 */ => Event::Number(37, pushed), - 0x2DB /* BTN_TRIGGER_HAPPY28 */ => Event::Number(38, pushed), - 0x2DC /* BTN_TRIGGER_HAPPY29 */ => Event::Number(39, pushed), - 0x2DD /* BTN_TRIGGER_HAPPY30 */ => Event::Number(40, pushed), - 0x2DE /* BTN_TRIGGER_HAPPY31 */ => Event::Number(41, pushed), - 0x2DF /* BTN_TRIGGER_HAPPY32 */ => Event::Number(42, pushed), - 0x2E0 /* BTN_TRIGGER_HAPPY33 */ => Event::Number(43, pushed), - 0x2E1 /* BTN_TRIGGER_HAPPY34 */ => Event::Number(44, pushed), - 0x2E2 /* BTN_TRIGGER_HAPPY35 */ => Event::Number(45, pushed), - 0x2E3 /* BTN_TRIGGER_HAPPY36 */ => Event::Number(46, pushed), - 0x2E4 /* BTN_TRIGGER_HAPPY37 */ => Event::Number(47, pushed), - 0x2E5 /* BTN_TRIGGER_HAPPY38 */ => Event::Number(48, pushed), - 0x2E6 /* BTN_TRIGGER_HAPPY39 */ => Event::Number(49, pushed), - 0x2E7 /* BTN_TRIGGER_HAPPY40 */ => Event::Number(50, pushed), - - _unknown => { - eprintln!("Unknown Linux Button {}", _unknown); - eprintln!("Report at https://github.com/ardaku/stick/issues"); - return; - } - }) -} - -// Convert Linux REL axis to stick Event. -fn linux_rel_to_stick_event( - pending: &mut Vec, - axis: c_ushort, - value: c_int, -) { - match axis { - 0x00 /* REL_X */ => pending.push(Event::MouseX(value as f64)), - 0x01 /* REL_Y */ => pending.push(Event::MouseY(value as f64)), - 0x02 /* REL_Z */ => { - eprintln!("FIXME: REL_Z"); - eprintln!("Report at https://github.com/ardaku/stick/issues"); - } - 0x03 /* REL_RX */ => { - eprintln!("FIXME: REL_RX"); - eprintln!("Report at https://github.com/ardaku/stick/issues"); - } - 0x04 /* REL_RY */ => { - eprintln!("FIXME: REL_RY"); - eprintln!("Report at https://github.com/ardaku/stick/issues"); - } - 0x05 /* REL_RZ */ => { - eprintln!("FIXME: REL_RZ"); - eprintln!("Report at https://github.com/ardaku/stick/issues"); - } - 0x06 /* REL_HWHEEL */ => { - eprintln!("FIXME: REL_HWHEEL"); - eprintln!("Report at https://github.com/ardaku/stick/issues"); - } - 0x07 /* REL_DIAL */ => { - eprintln!("FIXME: REL_DIAL"); - eprintln!("Report at https://github.com/ardaku/stick/issues"); - } - 0x08 /* REL_WHEEL */ => { - eprintln!("FIXME: REL_WHEEL"); - eprintln!("Report at https://github.com/ardaku/stick/issues"); - } - 0x09 /* REL_MISC */ => { - eprintln!("FIXME: REL_MISC"); - eprintln!("Report at https://github.com/ardaku/stick/issues"); - } - _unknown => { - eprintln!("Unknown Linux Axis {}", _unknown); - eprintln!("Report at https://github.com/ardaku/stick/issues"); - } - } -} - -// Convert Linux ABS axis to stick Event. -fn linux_abs_to_stick_event( - pending: &mut Vec, - axis: c_ushort, - value: c_int, -) { - match axis { - 0x00 /* ABS_X */ => pending.push(Event::JoyX(value as f64)), - 0x01 /* ABS_Y */ => pending.push(Event::JoyY(value as f64)), - 0x02 /* ABS_Z */ => pending.push(Event::JoyZ(value as f64)), - 0x03 /* ABS_RX */ => pending.push(Event::CamX(value as f64)), - 0x04 /* ABS_RY */ => pending.push(Event::CamY(value as f64)), - 0x05 /* ABS_RZ */ => pending.push(Event::CamZ(value as f64)), - 0x06 /* ABS_THROTTLE */ => pending.push(Event::Throttle(value as f64)), - 0x07 /* ABS_RUDDER */ => pending.push(Event::Rudder(value as f64)), - 0x08 /* ABS_WHEEL */ => pending.push(Event::Wheel(value as f64)), - 0x09 /* ABS_GAS */ => pending.push(Event::Gas(value as f64)), - 0x0A /* ABS_BRAKE */ => pending.push(Event::Brake(value as f64)), - 0x0B /* ABS_UNKNOWN0 */ => pending.push(Event::Slew(value as f64)), - 0x0C /* ABS_UNKNOWN1 */ => pending.push(Event::ThrottleL(value as f64)), - 0x0D /* ABS_UNKNOWN2 */ => pending.push(Event::ThrottleR(value as f64)), - 0x0E /* ABS_UNKNOWN3 */ => pending.push(Event::ScrollX(value as f64)), - 0x0F /* ABS_UNKNOWN4 */ => pending.push(Event::ScrollY(value as f64)), - 0x10 /* ABS_HAT0X */ => match value.cmp(&0) { - Ordering::Greater => pending.push(Event::PovRight(true)), - Ordering::Less => pending.push(Event::PovLeft(true)), - Ordering::Equal => { - pending.push(Event::PovRight(false)); - pending.push(Event::PovLeft(false)); - } - }, - 0x11 /* ABS_HAT0Y */ => match value.cmp(&0) { - Ordering::Greater => pending.push(Event::PovDown(true)), - Ordering::Less => pending.push(Event::PovUp(true)), - Ordering::Equal => { - pending.push(Event::PovUp(false)); - pending.push(Event::PovDown(false)); - } - }, - 0x12 /* ABS_HAT1X */ => match value.cmp(&0) { - Ordering::Greater => pending.push(Event::HatRight(true)), - Ordering::Less => pending.push(Event::HatLeft(true)), - Ordering::Equal => { - pending.push(Event::HatRight(false)); - pending.push(Event::HatLeft(false)); - } - }, - 0x13 /* ABS_HAT1Y */ => match value.cmp(&0) { - Ordering::Greater => pending.push(Event::HatDown(true)), - Ordering::Less => pending.push(Event::HatUp(true)), - Ordering::Equal => { - pending.push(Event::HatUp(false)); - pending.push(Event::HatDown(false)); - } - }, - 0x14 /* ABS_HAT2X */ => match value.cmp(&0) { - Ordering::Greater => pending.push(Event::TrimRight(true)), - Ordering::Less => pending.push(Event::TrimLeft(true)), - Ordering::Equal => { - pending.push(Event::TrimRight(false)); - pending.push(Event::TrimLeft(false)); - } - }, - 0x15 /* ABS_HAT2Y */ => match value.cmp(&0) { - Ordering::Greater => pending.push(Event::TrimDown(true)), - Ordering::Less => pending.push(Event::TrimUp(true)), - Ordering::Equal => { - pending.push(Event::TrimUp(false)); - pending.push(Event::TrimDown(false)); - } - }, - 0x16 /* ABS_HAT3X */ => match value.cmp(&0) { - Ordering::Greater => pending.push(Event::MicRight(true)), - Ordering::Less => pending.push(Event::MicLeft(true)), - Ordering::Equal => { - pending.push(Event::MicRight(false)); - pending.push(Event::MicLeft(false)); - } - }, - 0x17 /* ABS_HAT3Y */ => match value.cmp(&0) { - Ordering::Greater => pending.push(Event::MicDown(true)), - Ordering::Less => pending.push(Event::MicUp(true)), - Ordering::Equal => { - pending.push(Event::MicUp(false)); - pending.push(Event::MicDown(false)); - } - }, - 0x18 /* ABS_PRESSURE */ => { - eprintln!("Unknown Event: ABS_PRESSURE"); - eprintln!("Report at https://github.com/ardaku/stick/issues"); - } - 0x19 /* ABS_DISTANCE */ => { - eprintln!("Unknown Event: ABS_DISTANCE"); - eprintln!("Report at https://github.com/ardaku/stick/issues"); - } - 0x1a /* ABS_TILT_X */ => { - eprintln!("Unknown Event: ABS_TILT_X"); - eprintln!("Report at https://github.com/ardaku/stick/issues"); - } - 0x1b /* ABS_TILT_Y */ => { - eprintln!("Unknown Event: ABS_TILT_Y"); - eprintln!("Report at https://github.com/ardaku/stick/issues"); - } - 0x1c /* ABS_TOOL_WIDTH */ => { - eprintln!("Unknown Event: ABS_TOOL_WIDTH"); - eprintln!("Report at https://github.com/ardaku/stick/issues"); - } - 0x20 /* ABS_VOLUME */ => { - eprintln!("Unknown Event: ABS_VOLUME"); - eprintln!("Report at https://github.com/ardaku/stick/issues"); - } - 0x28 /* ABS_MISC */ => { - eprintln!("Unknown Event: ABS_MISC"); - eprintln!("Report at https://github.com/ardaku/stick/issues"); - } - _unknown => { - eprintln!("Unknown Linux Axis {}", _unknown); - eprintln!("Report at https://github.com/ardaku/stick/issues"); - } - } -} - -fn linux_evdev_to_stick_event(pending: &mut Vec, e: EvdevEv) { - match e.ev_type { - 0x00 /* SYN */ => {}, // Ignore Syn Input Events - 0x01 /* BTN */ => linux_btn_to_stick_event(pending, e.ev_code, e.ev_value != 0), - 0x02 /* REL */ => linux_rel_to_stick_event(pending, e.ev_code, e.ev_value), - 0x03 /* ABS */ => linux_abs_to_stick_event(pending, e.ev_code, e.ev_value), - 0x04 /* MSC */ => { - if e.ev_code != 4 { // Ignore Misc./Scan Events - let (code, val) = (e.ev_code, e.ev_value); - eprintln!("Unknown Linux Misc Code: {}, Value: {}", code, val); - eprintln!("Report at https://github.com/ardaku/stick/issues"); - } - } - 0x15 /* FF */ => {}, // Ignore Force Feedback Input Events - _unknown => { - eprintln!("Unknown Linux Event Type: {}", _unknown); - eprintln!("Report at https://github.com/ardaku/stick/issues"); - } - } -} - -#[repr(C)] -struct InotifyEv { - // struct inotify_event, from C. - wd: c_int, /* Watch descriptor */ - mask: u32, /* Mask describing event */ - cookie: u32, /* Unique cookie associating related - events (for rename(2)) */ - len: u32, /* Size of name field */ - name: [u8; 256], /* Optional null-terminated name */ -} - -#[repr(C)] -struct TimeVal { - // struct timeval, from C. - tv_sec: c_long, - tv_usec: c_long, -} - -#[repr(C)] -struct EvdevEv { - // struct input_event, from C. - ev_time: TimeVal, - ev_type: c_ushort, - ev_code: c_ushort, - // Though in the C header it's defined as uint, define as int because - // that's how it's meant to be interpreted. - ev_value: c_int, -} - -#[repr(C)] -struct AbsInfo { - // struct input_absinfo, from C. - value: i32, - // Though in the C header it's defined as uint32, define as int32 because - // that's how it's meant to be interpreted. - minimum: i32, - // Though in the C header it's defined as uint32, define as int32 because - // that's how it's meant to be interpreted. - maximum: i32, - fuzz: i32, - flat: i32, - resolution: i32, -} - -extern "C" { - fn strlen(s: *const u8) -> usize; - - fn open(pathname: *const u8, flags: c_int) -> c_int; - fn read(fd: RawFd, buf: *mut c_void, count: usize) -> isize; - fn write(fd: RawFd, buf: *const c_void, count: usize) -> isize; - fn close(fd: RawFd) -> c_int; - fn fcntl(fd: RawFd, cmd: c_int, v: c_int) -> c_int; - fn ioctl(fd: RawFd, request: c_ulong, v: *mut c_void) -> c_int; - - fn inotify_init1(flags: c_int) -> c_int; - fn inotify_add_watch(fd: RawFd, path: *const u8, mask: u32) -> c_int; - - fn __errno_location() -> *mut c_int; -} - -// From: https://github.com/torvalds/linux/blob/master/include/uapi/linux/input.h - -#[repr(C)] -struct FfTrigger { - button: u16, - interval: u16, -} - -#[repr(C)] -struct FfReplay { - length: u16, - delay: u16, -} - -#[repr(C)] -#[derive(Copy, Clone)] -struct FfEnvelope { - attack_length: u16, - attack_level: u16, - fade_length: u16, - fade_level: u16, -} - -#[repr(C)] -#[derive(Copy, Clone)] -struct FfConstantEffect { - level: i16, - envelope: FfEnvelope, -} - -#[repr(C)] -#[derive(Copy, Clone)] -struct FfRampEffect { - start_level: i16, - end_level: i16, - envelope: FfEnvelope, -} - -#[repr(C)] -#[derive(Copy, Clone)] -struct FfPeriodicEffect { - waveform: u16, - period: u16, - magnitude: i16, - offset: i16, - phase: u16, - - envelope: FfEnvelope, - - custom_len: u32, - custom_data: *mut i16, -} - -#[repr(C)] -#[derive(Copy, Clone)] -struct FfConditionEffect { - right_saturation: u16, - left_saturation: u16, - - right_coeff: i16, - left_coeff: i16, - - deadband: u16, - center: i16, -} - -#[repr(C)] -#[derive(Copy, Clone)] -struct FfRumbleEffect { - strong_magnitude: u16, - weak_magnitude: u16, -} - -#[repr(C)] -union FfUnion { - constant: FfConstantEffect, // Not supported. - ramp: FfRampEffect, - periodic: FfPeriodicEffect, - condition: [FfConditionEffect; 2], /* One for each axis */ - rumble: FfRumbleEffect, // Not supported -} - -#[repr(C)] -struct FfEffect { - stype: u16, - id: i16, - direction: u16, - - trigger: FfTrigger, - replay: FfReplay, - - u: FfUnion, -} - -fn joystick_ff(fd: RawFd, code: i16, strong: f32, weak: f32) { - // Update haptic effect `code`. - if strong != 0.0 || weak != 0.0 { - joystick_haptic(fd, code, strong, weak); - } - // - let ev_code = code.try_into().unwrap(); - - let play = &EvdevEv { - ev_time: TimeVal { - tv_sec: 0, - tv_usec: 0, - }, - ev_type: 0x15, /* EV_FF */ - ev_code, - ev_value: (strong > 0.0 || weak > 0.0) as _, - }; - let play: *const _ = play; - unsafe { - if write(fd, play.cast(), size_of::()) - != size_of::() as isize - { - let errno = *__errno_location(); - if errno != 19 && errno != 9 { - // 19 = device unplugged, ignore - // 9 = device openned read-only, ignore - panic!("Write exited with {}", *__errno_location()); - } - } - } -} - -// Get ID's for rumble and vibrate, if they're supported (otherwise, -1). -fn joystick_haptic(fd: RawFd, id: i16, strong: f32, weak: f32) -> i16 { - let a = &mut FfEffect { - stype: 0x50, - id, /* allocate new effect */ - direction: 0, - trigger: FfTrigger { - button: 0, - interval: 0, - }, - replay: FfReplay { - length: 0, - delay: 0, - }, - u: FfUnion { - rumble: FfRumbleEffect { - strong_magnitude: (u16::MAX as f32 * strong) as u16, - weak_magnitude: (u16::MAX as f32 * weak) as u16, - }, - }, - }; - let b: *mut _ = a; - if unsafe { ioctl(fd, 0x40304580, b.cast()) } == -1 { - -1 - } else { - a.id - } -} - -//////////////////////////////////////////////////////////////////////////////// - -/// Gamepad / Other HID -struct Controller { - // Async device handle - device: Device, - // Hexadecimal controller type ID - id: u64, - // Rumble effect id. - rumble: i16, - /// Signed axis multiplier - norm: f64, - /// Signed axis zero - zero: f64, - /// Don't process near 0 - flat: f64, - /// - pending_events: Vec, - /// - name: String, -} - -impl Controller { - fn new(fd: c_int) -> Self { - // Enable evdev async. - assert_ne!(unsafe { fcntl(fd, 0x4, 0x800) }, -1); - - // Get the hardware id of this controller. - let mut id = MaybeUninit::::uninit(); - assert_ne!( - unsafe { ioctl(fd, 0x_8008_4502, id.as_mut_ptr().cast()) }, - -1 - ); - let id = unsafe { id.assume_init() }.to_be(); - - // Get the min and max absolute values for axis. - let mut a = MaybeUninit::::uninit(); - assert_ne!( - unsafe { ioctl(fd, 0x_8018_4540, a.as_mut_ptr().cast()) }, - -1 - ); - let a = unsafe { a.assume_init() }; - let norm = (a.maximum as f64 - a.minimum as f64) * 0.5; - let zero = a.minimum as f64 + norm; - // Invert so multiplication can be used instead of division - let norm = norm.recip(); - let flat = a.flat as f64 * norm; - - // Query the controller for haptic support. - let rumble = joystick_haptic(fd, -1, 0.0, 0.0); - // Construct device from fd, looking for input events. - let device = Device::new(fd, Watcher::new().input()); - // - let pending_events = Vec::new(); - - // Get Name - let fd = device.raw(); - let mut a = MaybeUninit::<[c_char; 256]>::uninit(); - assert_ne!( - unsafe { ioctl(fd, 0x80FF_4506, a.as_mut_ptr().cast()) }, - -1 - ); - let a = unsafe { a.assume_init() }; - let name = unsafe { std::ffi::CStr::from_ptr(a.as_ptr()) }; - let name = name.to_string_lossy().to_string(); - - // Return - Self { - device, - id, - rumble, - norm, - zero, - flat, - pending_events, - name, - } - } -} - -impl super::Controller for Controller { - fn id(&self) -> u64 { - self.id - } - - fn poll(&mut self, cx: &mut Context<'_>) -> Poll { - // Queue - if let Some(e) = self.pending_events.pop() { - return Poll::Ready(e); - } - - // Early return if a different device woke the executor. - if self.device.pending() { - return self.device.sleep(cx); - } - - // Read an event. - let mut ev = MaybeUninit::::uninit(); - let ev = { - let bytes = unsafe { - read( - self.device.raw(), - ev.as_mut_ptr().cast(), - size_of::(), - ) - }; - if bytes <= 0 { - let errno = unsafe { *__errno_location() }; - if errno == 19 { - return Poll::Ready(Event::Disconnect); - } - assert_eq!(errno, 11); - // If no new controllers found, return pending. - return self.device.sleep(cx); - } - assert_eq!(size_of::() as isize, bytes); - unsafe { ev.assume_init() } - }; - - // Convert the event (may produce multiple stick events). - linux_evdev_to_stick_event(&mut self.pending_events, ev); - - // Check if events should be dropped. - if !ENABLED.load(std::sync::atomic::Ordering::Relaxed) { - self.pending_events.clear(); - } - - // Tail call recursion! - self.poll(cx) - } - - fn name(&self) -> &str { - &self.name - } - - fn rumble(&mut self, left: f32, right: f32) { - if self.rumble >= 0 { - joystick_ff(self.device.raw(), self.rumble, left, right); - } - } - - /// Use default unsigned axis range - fn pressure(&self, input: f64) -> f64 { - input * (1.0 / 255.0) - } - - /// Use full joystick axis range. - fn axis(&self, input: f64) -> f64 { - let input = (input - self.zero) * self.norm; - if input.abs() <= self.flat { - 0.0 - } else { - input - } - } -} - -impl Drop for Controller { - fn drop(&mut self) { - assert_ne!(unsafe { close(self.device.stop()) }, -1); - } -} - -struct Listener { - device: Device, - read_dir: Option>, - remap: Remap, -} - -impl Listener { - fn new(remap: Remap) -> Self { - const CLOEXEC: c_int = 0o2000000; - const NONBLOCK: c_int = 0o0004000; - const ATTRIB: c_uint = 0x00000004; - const DIR: &[u8] = b"/dev/input/\0"; - - // Create an inotify. - let listen = unsafe { inotify_init1(NONBLOCK | CLOEXEC) }; - if listen == -1 { - panic!("Couldn't create inotify!"); - } - - // Start watching the controller directory. - if unsafe { inotify_add_watch(listen, DIR.as_ptr(), ATTRIB) } == -1 { - panic!("Couldn't add inotify watch!"); - } - - Self { - // Create watcher, and register with fd as a "device". - device: Device::new(listen, Watcher::new().input()), - // - read_dir: Some(Box::new(read_dir("/dev/input/").unwrap())), - // - remap, - } - } - - fn controller( - remap: &Remap, - mut filename: String, - ) -> Poll { - if filename.contains("event") { - filename.push('\0'); - // Try read & write first - let mut fd = unsafe { open(filename.as_ptr(), 2) }; - // Try readonly second (bluetooth controller - input device) - if fd == -1 { - fd = unsafe { open(filename.as_ptr(), 0) }; - } - // Try writeonly third (bluetooth haptic device) - if fd == -1 { - fd = unsafe { open(filename.as_ptr(), 1) }; - } - // If one succeeded, return that controller. - if fd != -1 { - return Poll::Ready(crate::Controller::new( - Box::new(Controller::new(fd)), - remap, - )); - } - } - Poll::Pending - } -} - -impl super::Listener for Listener { - fn poll(&mut self, cx: &mut Context<'_>) -> Poll { - // Read the directory for ctrls if initialization hasn't completed yet. - if let Some(ref mut read_dir) = &mut self.read_dir { - for dir_entry in read_dir.flatten() { - let file = dir_entry.path(); - let path = file.as_path().to_string_lossy().to_string(); - if let Poll::Ready(controller) = - Self::controller(&self.remap, path) - { - return Poll::Ready(controller); - } - } - self.read_dir = None; - } - - // Read the Inotify Event. - let mut ev = MaybeUninit::::zeroed(); - let read = unsafe { - read( - self.device.raw(), - ev.as_mut_ptr().cast(), - size_of::(), - ) - }; - if read > 0 { - let ev = unsafe { ev.assume_init() }; - let len = unsafe { strlen(&ev.name[0]) }; - let filename = String::from_utf8_lossy(&ev.name[..len]); - let path = format!("/dev/input/{}", filename); - if let Poll::Ready(controller) = Self::controller(&self.remap, path) - { - return Poll::Ready(controller); - } - } - - // Register waker & go to sleep for this device - self.device.sleep(cx) - } -} - -impl Drop for Listener { - fn drop(&mut self) { - assert_eq!(unsafe { close(self.device.stop()) }, 0); - } -} - -static ENABLED: std::sync::atomic::AtomicBool = - std::sync::atomic::AtomicBool::new(true); - -struct Global; - -impl super::Global for Global { - /// Enable all events (when window comes in focus). - fn enable(&self) { - ENABLED.store(true, std::sync::atomic::Ordering::Relaxed); - } - - /// Disable all events (when window leaves focus). - fn disable(&self) { - ENABLED.store(false, std::sync::atomic::Ordering::Relaxed); - } - - /// Create a new listener. - fn listener(&self, remap: Remap) -> Box { - Box::new(Listener::new(remap)) - } -} - -pub(super) fn global() -> Box { - Box::new(Global) -} 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 }