Vendor dependencies for 0.3.0 release

This commit is contained in:
2025-09-27 10:29:08 -05:00
parent 0c8d39d483
commit 82ab7f317b
26803 changed files with 16134934 additions and 0 deletions

348
vendor/gilrs-core/src/lib.rs vendored Normal file
View File

@@ -0,0 +1,348 @@
#[macro_use]
extern crate log;
use std::fmt;
use std::fmt::Display;
use std::fmt::Formatter;
use std::error;
use std::time::Duration;
use std::time::SystemTime;
mod platform;
pub mod utils;
/// True, if Y axis of sticks commonly points downwards.
pub const IS_Y_AXIS_REVERSED: bool = platform::IS_Y_AXIS_REVERSED;
/// Allow control of gamepad's force feedback.
#[derive(Debug)]
pub struct FfDevice {
inner: platform::FfDevice,
}
impl FfDevice {
/// Sets magnitude for strong and weak ff motors.
pub fn set_ff_state(&mut self, strong: u16, weak: u16, min_duration: Duration) {
self.inner.set_ff_state(strong, weak, min_duration)
}
}
/// Holds information about gamepad event.
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
#[non_exhaustive]
pub struct Event {
/// Id of gamepad.
pub id: usize,
/// Event's data.
pub event: EventType,
/// Time when event was emitted.
pub time: SystemTime,
}
impl Event {
/// Creates new event with current time.
pub fn new(id: usize, event: EventType) -> Self {
let time = utils::time_now();
Event { id, event, time }
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
/// Gamepad event.
#[non_exhaustive]
pub enum EventType {
ButtonPressed(EvCode),
ButtonReleased(EvCode),
AxisValueChanged(i32, EvCode),
Connected,
Disconnected,
}
/// Holds information about expected axis range and deadzone.
#[derive(Copy, Clone, Debug)]
pub struct AxisInfo {
pub min: i32,
pub max: i32,
pub deadzone: Option<u32>,
}
/// State of device's power supply.
///
/// Battery level is reported as integer between 0 and 100.
///
/// ## Example
///
/// ```
/// use gilrs_core::PowerInfo;
/// # let gilrs = gilrs_core::Gilrs::new().unwrap();
///
/// match gilrs.gamepad(0).map(|g| g.power_info()) {
/// Some(PowerInfo::Discharging(lvl)) if lvl <= 10 => println!("Low battery level, you should \
/// plug your gamepad"),
/// _ => (),
/// };
/// ```
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum PowerInfo {
/// Failed to determine power status.
Unknown,
/// Device doesn't have battery.
Wired,
/// Device is running on the battery.
Discharging(u8),
/// Battery is charging.
Charging(u8),
/// Battery is charged.
Charged,
}
/// Struct used to manage gamepads and retrieve events.
#[derive(Debug)]
pub struct Gilrs {
inner: platform::Gilrs,
}
impl Gilrs {
pub fn new() -> Result<Self, Error> {
let inner = platform::Gilrs::new().map_err(|e| match e {
PlatformError::NotImplemented(inner) => Error::NotImplemented(Gilrs { inner }),
PlatformError::Other(e) => Error::Other(e),
})?;
Ok(Gilrs { inner })
}
/// Returns oldest event or `None` if all events were processed.
pub fn next_event(&mut self) -> Option<Event> {
self.inner.next_event()
}
/// Returns oldest event, waiting for new event if necessary.
pub fn next_event_blocking(&mut self, timeout: Option<Duration>) -> Option<Event> {
self.inner.next_event_blocking(timeout)
}
/// Borrows `Gamepad` or return `None` if index is invalid. Returned gamepad may be disconnected.
pub fn gamepad(&self, id: usize) -> Option<&Gamepad> {
unsafe {
let gp: Option<&platform::Gamepad> = self.inner.gamepad(id);
gp.map(|gp| &*(gp as *const _ as *const Gamepad))
}
}
/// Returns id greater than id of last connected gamepad. The returned value is only hint
/// and may be much larger than number of observed gamepads. For example, it may return maximum
/// number of connected gamepads on platforms when this limit is small.
///
/// `gamepad(id)` should return `Some` if using id that is smaller than value returned from this
/// function.
pub fn last_gamepad_hint(&self) -> usize {
self.inner.last_gamepad_hint()
}
}
/// Provides information about gamepad.
#[derive(Debug)]
#[repr(transparent)]
pub struct Gamepad {
inner: platform::Gamepad,
}
impl Gamepad {
/// Returns name of gamepad.
pub fn name(&self) -> &str {
self.inner.name()
}
/// Returns true if gamepad is connected.
pub fn is_connected(&self) -> bool {
self.inner.is_connected()
}
/// Returns UUID that represents gamepad model.
///
/// Returned UUID should be the same as SLD2 uses. If platform does not provide any method to
/// distinguish between gamepad models, nil UUID is returned.
///
/// It is recommended to process with the [UUID crate](https://crates.io/crates/uuid).
/// Use `Uuid::from_bytes` method to create a `Uuid` from the returned bytes.
pub fn uuid(&self) -> [u8; 16] {
*self.inner.uuid().as_bytes()
}
/// Returns the vendor ID, as assigned by the USB-IF, when available.
pub fn vendor_id(&self) -> Option<u16> {
self.inner.vendor_id()
}
/// Returns the product ID, as assigned by the vendor, when available.
pub fn product_id(&self) -> Option<u16> {
self.inner.product_id()
}
/// Returns device's power supply state.
pub fn power_info(&self) -> PowerInfo {
self.inner.power_info()
}
/// Returns true if force feedback is supported by device,
pub fn is_ff_supported(&self) -> bool {
self.inner.is_ff_supported()
}
/// Creates `FfDevice` corresponding to this gamepad.
pub fn ff_device(&self) -> Option<FfDevice> {
self.inner.ff_device().map(|inner| FfDevice { inner })
}
/// Returns slice with EvCodes that may appear in button related events.
pub fn buttons(&self) -> &[EvCode] {
unsafe {
let bt: &[platform::EvCode] = self.inner.buttons();
&*(bt as *const _ as *const [EvCode])
}
}
/// Returns slice with EvCodes that may appear in axis related events.
pub fn axes(&self) -> &[EvCode] {
unsafe {
let ax: &[platform::EvCode] = self.inner.axes();
&*(ax as *const _ as *const [EvCode])
}
}
/// Returns information about a specific axis. `None` may be returned if a device doesn't have an axis
/// with provided `EvCode`.
pub fn axis_info(&self, nec: EvCode) -> Option<&AxisInfo> {
self.inner.axis_info(nec.0)
}
}
#[cfg(feature = "serde-serialize")]
use serde::{Deserialize, Serialize};
/// Platform specific representation of axis or button.
#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
#[repr(transparent)]
pub struct EvCode(platform::EvCode);
impl EvCode {
pub fn into_u32(self) -> u32 {
self.0.into_u32()
}
}
impl Display for EvCode {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
self.0.fmt(f)
}
}
/// Error type which can be returned when creating `Gilrs`.
///
/// Private version of `Error` that use `platform::Gilrs`.
#[derive(Debug)]
enum PlatformError {
/// Gilrs does not support the current platform, but you can use dummy context from this error if
/// gamepad input is not essential.
#[allow(dead_code)]
NotImplemented(platform::Gilrs),
/// Platform specific error.
#[allow(dead_code)]
Other(Box<dyn error::Error + Send + Sync>),
}
impl Display for PlatformError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match *self {
PlatformError::NotImplemented(_) => {
f.write_str("Gilrs does not support current platform.")
}
PlatformError::Other(ref e) => e.fmt(f),
}
}
}
impl error::Error for PlatformError {
fn source(&self) -> Option<&(dyn error::Error + 'static)> {
match self {
PlatformError::Other(e) => Some(e.as_ref()),
_ => None,
}
}
}
/// Error type which can be returned when creating `Gilrs`.
#[non_exhaustive]
#[derive(Debug)]
pub enum Error {
/// Gilrs does not support current platform, but you can use dummy context from this error if
/// gamepad input is not essential.
NotImplemented(Gilrs),
/// Platform specific error.
Other(Box<dyn error::Error + Send + Sync + 'static>),
}
impl Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match *self {
Error::NotImplemented(_) => f.write_str("Gilrs does not support current platform."),
Error::Other(ref e) => e.fmt(f),
}
}
}
impl error::Error for Error {
fn source(&self) -> Option<&(dyn error::Error + 'static)> {
match self {
Error::Other(e) => Some(e.as_ref()),
_ => None,
}
}
}
/// Provides the most common mappings of physical location of gamepad elements to their EvCodes.
/// Some (or most) gamepads may use different mappings.
pub mod native_ev_codes {
use super::EvCode;
use crate::platform::native_ev_codes as nec;
pub const AXIS_LSTICKX: EvCode = EvCode(nec::AXIS_LSTICKX);
pub const AXIS_LSTICKY: EvCode = EvCode(nec::AXIS_LSTICKY);
pub const AXIS_LEFTZ: EvCode = EvCode(nec::AXIS_LEFTZ);
pub const AXIS_RSTICKX: EvCode = EvCode(nec::AXIS_RSTICKX);
pub const AXIS_RSTICKY: EvCode = EvCode(nec::AXIS_RSTICKY);
pub const AXIS_RIGHTZ: EvCode = EvCode(nec::AXIS_RIGHTZ);
pub const AXIS_DPADX: EvCode = EvCode(nec::AXIS_DPADX);
pub const AXIS_DPADY: EvCode = EvCode(nec::AXIS_DPADY);
pub const AXIS_RT: EvCode = EvCode(nec::AXIS_RT);
pub const AXIS_LT: EvCode = EvCode(nec::AXIS_LT);
pub const AXIS_RT2: EvCode = EvCode(nec::AXIS_RT2);
pub const AXIS_LT2: EvCode = EvCode(nec::AXIS_LT2);
pub const BTN_SOUTH: EvCode = EvCode(nec::BTN_SOUTH);
pub const BTN_EAST: EvCode = EvCode(nec::BTN_EAST);
pub const BTN_C: EvCode = EvCode(nec::BTN_C);
pub const BTN_NORTH: EvCode = EvCode(nec::BTN_NORTH);
pub const BTN_WEST: EvCode = EvCode(nec::BTN_WEST);
pub const BTN_Z: EvCode = EvCode(nec::BTN_Z);
pub const BTN_LT: EvCode = EvCode(nec::BTN_LT);
pub const BTN_RT: EvCode = EvCode(nec::BTN_RT);
pub const BTN_LT2: EvCode = EvCode(nec::BTN_LT2);
pub const BTN_RT2: EvCode = EvCode(nec::BTN_RT2);
pub const BTN_SELECT: EvCode = EvCode(nec::BTN_SELECT);
pub const BTN_START: EvCode = EvCode(nec::BTN_START);
pub const BTN_MODE: EvCode = EvCode(nec::BTN_MODE);
pub const BTN_LTHUMB: EvCode = EvCode(nec::BTN_LTHUMB);
pub const BTN_RTHUMB: EvCode = EvCode(nec::BTN_RTHUMB);
pub const BTN_DPAD_UP: EvCode = EvCode(nec::BTN_DPAD_UP);
pub const BTN_DPAD_DOWN: EvCode = EvCode(nec::BTN_DPAD_DOWN);
pub const BTN_DPAD_LEFT: EvCode = EvCode(nec::BTN_DPAD_LEFT);
pub const BTN_DPAD_RIGHT: EvCode = EvCode(nec::BTN_DPAD_RIGHT);
}

View File

@@ -0,0 +1,17 @@
// Copyright 2016-2018 Mateusz Sieczko and other GilRs Developers
//
// Licensed under the Apache License, Version 2.0, <LICENSE-APACHE or
// http://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
// http://opensource.org/licenses/MIT>, at your option. This file may not be
// copied, modified, or distributed except according to those terms.
use std::time::Duration;
#[derive(Debug)]
/// Represents gamepad. Reexported as FfDevice
pub struct Device;
impl Device {
/// Sets magnitude for strong and weak ff motors.
pub fn set_ff_state(&mut self, strong: u16, weak: u16, min_duration: Duration) {}
}

View File

@@ -0,0 +1,149 @@
// Copyright 2016-2018 Mateusz Sieczko and other GilRs Developers
//
// Licensed under the Apache License, Version 2.0, <LICENSE-APACHE or
// http://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
// http://opensource.org/licenses/MIT>, at your option. This file may not be
// copied, modified, or distributed except according to those terms.
#![allow(unused_variables)]
use super::FfDevice;
use crate::{AxisInfo, Event, PlatformError, PowerInfo};
use uuid::Uuid;
use std::fmt::{Display, Formatter, Result as FmtResult};
use std::time::Duration;
#[derive(Debug)]
pub struct Gilrs {}
impl Gilrs {
pub(crate) fn new() -> Result<Self, PlatformError> {
Err(PlatformError::NotImplemented(Gilrs {}))
}
pub(crate) fn next_event(&mut self) -> Option<Event> {
None
}
pub(crate) fn next_event_blocking(&mut self, timeout: Option<Duration>) -> Option<Event> {
None
}
pub fn gamepad(&self, id: usize) -> Option<&Gamepad> {
None
}
/// Returns index greater than index of last connected gamepad.
pub fn last_gamepad_hint(&self) -> usize {
0
}
}
#[derive(Debug)]
pub struct Gamepad {
_priv: u8, // required for `#[repr(transparent)]`
}
impl Gamepad {
pub fn name(&self) -> &str {
""
}
pub fn uuid(&self) -> Uuid {
Uuid::nil()
}
pub fn vendor_id(&self) -> Option<u16> {
None
}
pub fn product_id(&self) -> Option<u16> {
None
}
pub fn power_info(&self) -> PowerInfo {
PowerInfo::Unknown
}
pub fn is_ff_supported(&self) -> bool {
false
}
/// Creates Ffdevice corresponding to this gamepad.
pub fn ff_device(&self) -> Option<FfDevice> {
Some(FfDevice)
}
pub fn buttons(&self) -> &[EvCode] {
&[]
}
pub fn axes(&self) -> &[EvCode] {
&[]
}
pub(crate) fn axis_info(&self, nec: EvCode) -> Option<&AxisInfo> {
None
}
pub fn is_connected(&self) -> bool {
false
}
}
#[cfg(feature = "serde-serialize")]
use serde::{Deserialize, Serialize};
#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub struct EvCode(u16);
impl EvCode {
pub fn into_u32(self) -> u32 {
self.0 as u32
}
}
impl Display for EvCode {
fn fmt(&self, f: &mut Formatter) -> FmtResult {
self.0.fmt(f)
}
}
pub mod native_ev_codes {
use super::EvCode;
pub const AXIS_LSTICKX: EvCode = EvCode(0);
pub const AXIS_LSTICKY: EvCode = EvCode(1);
pub const AXIS_LEFTZ: EvCode = EvCode(2);
pub const AXIS_RSTICKX: EvCode = EvCode(3);
pub const AXIS_RSTICKY: EvCode = EvCode(4);
pub const AXIS_RIGHTZ: EvCode = EvCode(5);
pub const AXIS_DPADX: EvCode = EvCode(6);
pub const AXIS_DPADY: EvCode = EvCode(7);
pub const AXIS_RT: EvCode = EvCode(8);
pub const AXIS_LT: EvCode = EvCode(9);
pub const AXIS_RT2: EvCode = EvCode(10);
pub const AXIS_LT2: EvCode = EvCode(11);
pub const BTN_SOUTH: EvCode = EvCode(12);
pub const BTN_EAST: EvCode = EvCode(13);
pub const BTN_C: EvCode = EvCode(14);
pub const BTN_NORTH: EvCode = EvCode(15);
pub const BTN_WEST: EvCode = EvCode(16);
pub const BTN_Z: EvCode = EvCode(17);
pub const BTN_LT: EvCode = EvCode(18);
pub const BTN_RT: EvCode = EvCode(19);
pub const BTN_LT2: EvCode = EvCode(20);
pub const BTN_RT2: EvCode = EvCode(21);
pub const BTN_SELECT: EvCode = EvCode(22);
pub const BTN_START: EvCode = EvCode(23);
pub const BTN_MODE: EvCode = EvCode(24);
pub const BTN_LTHUMB: EvCode = EvCode(25);
pub const BTN_RTHUMB: EvCode = EvCode(26);
pub const BTN_DPAD_UP: EvCode = EvCode(27);
pub const BTN_DPAD_DOWN: EvCode = EvCode(28);
pub const BTN_DPAD_LEFT: EvCode = EvCode(29);
pub const BTN_DPAD_RIGHT: EvCode = EvCode(30);
}

View File

@@ -0,0 +1,14 @@
// Copyright 2016-2018 Mateusz Sieczko and other GilRs Developers
//
// Licensed under the Apache License, Version 2.0, <LICENSE-APACHE or
// http://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
// http://opensource.org/licenses/MIT>, at your option. This file may not be
// copied, modified, or distributed except according to those terms.
mod ff;
mod gamepad;
pub use self::ff::Device as FfDevice;
pub use self::gamepad::{native_ev_codes, EvCode, Gamepad, Gilrs};
// True, if Y axis of sticks points downwards.
pub const IS_Y_AXIS_REVERSED: bool = false;

View File

@@ -0,0 +1,124 @@
// Copyright 2016-2018 Mateusz Sieczko and other GilRs Developers
//
// Licensed under the Apache License, Version 2.0, <LICENSE-APACHE or
// http://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
// http://opensource.org/licenses/MIT>, at your option. This file may not be
// copied, modified, or distributed except according to those terms.
use std::fs::File;
use std::io::{Error as IoError, ErrorKind, Result as IoResult, Write};
use std::os::unix::io::AsRawFd;
use std::{mem, slice};
use super::ioctl::{self, ff_effect, ff_replay, ff_rumble_effect, input_event};
use nix::errno::Errno;
use std::time::Duration;
#[derive(Debug)]
pub struct Device {
effect: i16,
file: File,
}
impl Device {
pub(crate) fn new(path: &str) -> IoResult<Self> {
let file = File::create(path)?;
let mut effect = ff_effect {
type_: FF_RUMBLE,
id: -1,
direction: 0,
trigger: Default::default(),
replay: Default::default(),
u: Default::default(),
};
#[allow(clippy::unnecessary_mut_passed)]
let res = unsafe { ioctl::eviocsff(file.as_raw_fd(), &mut effect) };
if res.is_err() {
Err(IoError::new(ErrorKind::Other, "Failed to create effect"))
} else {
Ok(Device {
effect: effect.id,
file,
})
}
}
pub fn set_ff_state(&mut self, strong: u16, weak: u16, min_duration: Duration) {
let duration = min_duration.as_secs() * 1000 + u64::from(min_duration.subsec_millis());
let duration = if duration > u64::from(u16::MAX) {
u16::MAX
} else {
duration as u16
};
let mut effect = ff_effect {
type_: FF_RUMBLE,
id: self.effect,
direction: 0,
trigger: Default::default(),
replay: ff_replay {
delay: 0,
length: duration,
},
u: Default::default(),
};
unsafe {
let rumble = &mut effect.u as *mut _ as *mut ff_rumble_effect;
(*rumble).strong_magnitude = strong;
(*rumble).weak_magnitude = weak;
if let Err(err) = ioctl::eviocsff(self.file.as_raw_fd(), &effect) {
error!(
"Failed to modify effect of gamepad {:?}, error: {}",
self.file, err
);
return;
}
};
let time = libc::timeval {
tv_sec: 0,
tv_usec: 0,
};
let ev = input_event {
type_: EV_FF,
code: self.effect as u16,
value: 1,
time,
};
let size = mem::size_of::<input_event>();
let s = unsafe { slice::from_raw_parts(&ev as *const _ as *const u8, size) };
match self.file.write(s) {
Ok(s) if s == size => (),
Ok(_) => unreachable!(),
Err(e) => error!("Failed to set ff state: {}", e),
}
}
}
impl Drop for Device {
fn drop(&mut self) {
#[cfg(target_os = "linux")]
let effect = self.effect as ::libc::c_ulong;
#[cfg(not(target_os = "linux"))]
let effect = self.effect as ::libc::c_int;
if let Err(err) = unsafe { ioctl::eviocrmff(self.file.as_raw_fd(), effect) } {
if err != Errno::ENODEV {
error!(
"Failed to remove effect of gamepad {:?}: {}",
self.file, err
)
}
};
}
}
const EV_FF: u16 = 0x15;
const FF_RUMBLE: u16 = 0x50;

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,175 @@
// Copyright 2016-2018 Mateusz Sieczko and other GilRs Developers
//
// Licensed under the Apache License, Version 2.0, <LICENSE-APACHE or
// http://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
// http://opensource.org/licenses/MIT>, at your option. This file may not be
// copied, modified, or distributed except according to those terms.
// Some ioctls are exported by ioctl crate only for x86_64, so we have to define them anyway.
// Diffing linux/input.h across different architectures (i686, x86_64 and arm) didn't show any
// difference, so it looks like conditional compilation is not needed.
#![allow(dead_code)]
use nix::{ioctl_read, ioctl_read_buf, ioctl_write_int, ioctl_write_ptr, request_code_read};
use std::mem::MaybeUninit;
#[cfg(target_env = "musl")]
pub type IoctlRequest = libc::c_int;
#[cfg(not(target_env = "musl"))]
pub type IoctlRequest = libc::c_ulong;
ioctl_read!(eviocgid, b'E', 0x02, /*struct*/ input_id);
ioctl_write_int!(eviocrmff, b'E', 0x81);
ioctl_write_ptr!(eviocsff, b'E', 0x80, ff_effect);
ioctl_read_buf!(eviocgname, b'E', 0x06, MaybeUninit<u8>);
ioctl_read_buf!(eviocgkey, b'E', 0x18, u8);
pub unsafe fn eviocgbit(fd: libc::c_int, ev: u32, len: libc::c_int, buf: *mut u8) -> libc::c_int {
::nix::libc::ioctl(
fd,
request_code_read!(b'E', 0x20 + ev, len) as IoctlRequest,
buf,
)
}
pub unsafe fn eviocgabs(fd: ::libc::c_int, abs: u32, buf: *mut input_absinfo) -> libc::c_int {
::nix::libc::ioctl(
fd,
request_code_read!(b'E', 0x40 + abs, ::std::mem::size_of::<input_absinfo>())
as IoctlRequest,
buf,
)
}
#[derive(Copy, Clone)]
#[repr(C)]
pub struct input_event {
pub time: libc::timeval,
pub type_: u16,
pub code: u16,
pub value: i32,
}
impl ::std::default::Default for input_event {
fn default() -> Self {
unsafe { ::std::mem::zeroed() }
}
}
impl ::std::fmt::Debug for input_event {
fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
write!(
f,
"input_event {{ time: {{ tv_sec: {}, tv_usec: {} }}, type_: {}, code: {}, value: {}",
self.time.tv_sec, self.time.tv_usec, self.type_, self.code, self.value
)
}
}
#[derive(Copy, Clone)]
#[repr(C)]
pub struct input_id {
pub bustype: u16,
pub vendor: u16,
pub product: u16,
pub version: u16,
}
#[derive(Copy, Clone, Default, PartialEq, Eq, Debug)]
#[repr(C)]
pub struct input_absinfo {
pub value: i32,
pub minimum: i32,
pub maximum: i32,
pub fuzz: i32,
pub flat: i32,
pub resolution: i32,
}
#[derive(Copy, Clone, Default)]
#[repr(C)]
pub struct ff_replay {
pub length: u16,
pub delay: u16,
}
#[derive(Copy, Clone, Default)]
#[repr(C)]
pub struct ff_trigger {
pub button: u16,
pub interval: u16,
}
#[derive(Copy, Clone)]
#[repr(C)]
pub struct ff_envelope {
pub attack_length: u16,
pub attack_level: u16,
pub fade_length: u16,
pub fade_level: u16,
}
#[derive(Copy, Clone)]
#[repr(C)]
pub struct ff_constant_effect {
pub level: i16,
pub envelope: ff_envelope,
}
#[derive(Copy, Clone)]
#[repr(C)]
pub struct ff_ramp_effect {
pub start_level: i16,
pub end_level: i16,
pub envelope: ff_envelope,
}
#[derive(Copy, Clone)]
#[repr(C)]
pub struct ff_condition_effect {
pub right_saturation: u16,
pub left_saturation: u16,
pub right_coeff: i16,
pub left_coeff: i16,
pub deadband: u16,
pub center: i16,
}
#[derive(Copy, Clone)]
#[repr(C)]
pub struct ff_periodic_effect {
pub waveform: u16,
pub period: u16,
pub magnitude: i16,
pub offset: i16,
pub phase: u16,
pub envelope: ff_envelope,
pub custom_len: u32,
pub custom_data: *mut i16,
}
#[derive(Copy, Clone)]
#[repr(C)]
pub struct ff_rumble_effect {
pub strong_magnitude: u16,
pub weak_magnitude: u16,
}
#[derive(Copy, Clone)]
#[repr(C)]
pub struct ff_effect {
pub type_: u16,
pub id: i16,
pub direction: u16,
pub trigger: ff_trigger,
pub replay: ff_replay,
// FIXME this is actually a union
#[cfg(target_pointer_width = "64")]
pub u: [u64; 4],
#[cfg(target_pointer_width = "32")]
pub u: [u32; 7],
}

View File

@@ -0,0 +1,16 @@
// Copyright 2016-2018 Mateusz Sieczko and other GilRs Developers
//
// Licensed under the Apache License, Version 2.0, <LICENSE-APACHE or
// http://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
// http://opensource.org/licenses/MIT>, at your option. This file may not be
// copied, modified, or distributed except according to those terms.
// Copyright 2016 GilRs Developers
mod ff;
mod gamepad;
mod ioctl;
mod udev;
pub use self::ff::Device as FfDevice;
pub use self::gamepad::{native_ev_codes, EvCode, Gamepad, Gilrs};
pub const IS_Y_AXIS_REVERSED: bool = true;

View File

@@ -0,0 +1,254 @@
// Copyright 2016-2018 Mateusz Sieczko and other GilRs Developers
//
// Licensed under the Apache License, Version 2.0, <LICENSE-APACHE or
// http://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
// http://opensource.org/licenses/MIT>, at your option. This file may not be
// copied, modified, or distributed except according to those terms.
use libc as c;
use libudev_sys as ud;
use std::ffi::{CStr, CString};
use std::os::raw::c_char;
use std::ptr;
#[derive(Debug)]
pub struct Udev(*mut ud::udev);
impl Udev {
pub fn new() -> Option<Self> {
let u = unsafe { ud::udev_new() };
if u.is_null() {
None
} else {
Some(Udev(u))
}
}
pub fn enumerate(&self) -> Option<Enumerate> {
let en = unsafe { ud::udev_enumerate_new(self.0) };
if en.is_null() {
None
} else {
let en = Enumerate(en);
Some(en)
}
}
}
impl Drop for Udev {
fn drop(&mut self) {
unsafe {
ud::udev_unref(self.0);
}
}
}
impl Clone for Udev {
fn clone(&self) -> Self {
Udev(unsafe { ud::udev_ref(self.0) })
}
}
pub struct Enumerate(*mut ud::udev_enumerate);
impl Enumerate {
pub fn scan_devices(&self) {
// TODO: Check for error
let _ = unsafe { ud::udev_enumerate_scan_devices(self.0) };
}
pub fn add_match_property(&self, key: &CStr, val: &CStr) {
// TODO: Check for error
unsafe {
ud::udev_enumerate_add_match_property(self.0, key.as_ptr(), val.as_ptr());
}
}
pub fn add_match_subsystem(&self, subsystem: &CStr) {
// TODO: Check for error
unsafe {
ud::udev_enumerate_add_match_subsystem(self.0, subsystem.as_ptr());
}
}
pub fn iter(&self) -> DeviceIterator {
DeviceIterator(unsafe { ud::udev_enumerate_get_list_entry(self.0) })
}
}
impl Drop for Enumerate {
fn drop(&mut self) {
unsafe {
ud::udev_enumerate_unref(self.0);
}
}
}
pub struct DeviceIterator(*mut ud::udev_list_entry);
impl Iterator for DeviceIterator {
type Item = CString;
fn next(&mut self) -> Option<CString> {
if self.0.is_null() {
None
} else {
let p_name = unsafe { ud::udev_list_entry_get_name(self.0) };
let name = if p_name.is_null() {
return None;
} else {
unsafe { CStr::from_ptr(p_name).to_owned() }
};
self.0 = unsafe { ud::udev_list_entry_get_next(self.0) };
Some(name)
}
}
}
pub struct Device(*mut ud::udev_device);
impl Device {
pub fn from_syspath(udev: &Udev, path: &CStr) -> Option<Self> {
let dev = unsafe { ud::udev_device_new_from_syspath(udev.0, path.as_ptr()) };
if dev.is_null() {
None
} else {
Some(Device(dev))
}
}
pub fn syspath(&self) -> &CStr {
// Always returns cstring
unsafe { CStr::from_ptr(ud::udev_device_get_syspath(self.0)) }
}
pub fn devnode(&self) -> Option<&CStr> {
unsafe {
let s = ud::udev_device_get_devnode(self.0);
if s.is_null() {
None
} else {
Some(CStr::from_ptr(s))
}
}
}
#[allow(dead_code)]
pub fn properties(&self) -> PropertyIterator {
let prop = unsafe { ud::udev_device_get_properties_list_entry(self.0) };
PropertyIterator(prop)
}
pub fn action(&self) -> Option<&CStr> {
unsafe {
let s = ud::udev_device_get_action(self.0);
if s.is_null() {
None
} else {
Some(CStr::from_ptr(s))
}
}
}
pub fn property_value(&self, key: &CStr) -> Option<&CStr> {
unsafe {
let s = ud::udev_device_get_property_value(self.0, key.as_ptr());
if s.is_null() {
None
} else {
Some(CStr::from_ptr(s))
}
}
}
}
impl Clone for Device {
fn clone(&self) -> Self {
unsafe { Device(ud::udev_device_ref(self.0)) }
}
}
impl Drop for Device {
fn drop(&mut self) {
unsafe {
ud::udev_device_unref(self.0);
}
}
}
#[allow(dead_code)]
pub struct PropertyIterator(*mut ud::udev_list_entry);
impl Iterator for PropertyIterator {
type Item = (String, String);
fn next(&mut self) -> Option<(String, String)> {
if self.0.is_null() {
None
} else {
let p_name = unsafe { ud::udev_list_entry_get_name(self.0) };
let p_val = unsafe { ud::udev_list_entry_get_value(self.0) };
let name = if p_name.is_null() {
return None;
} else {
unsafe { CStr::from_ptr(p_name).to_string_lossy().into_owned() }
};
let value = if p_val.is_null() {
return None;
} else {
unsafe { CStr::from_ptr(p_val).to_string_lossy().into_owned() }
};
self.0 = unsafe { ud::udev_list_entry_get_next(self.0) };
Some((name, value))
}
}
}
#[derive(Debug)]
pub struct Monitor(*mut ud::udev_monitor);
impl Monitor {
pub fn new(udev: &Udev) -> Option<Self> {
unsafe {
let monitor =
ud::udev_monitor_new_from_netlink(udev.0, c"udev".as_ptr() as *const c_char);
if monitor.is_null() {
None
} else {
ud::udev_monitor_filter_add_match_subsystem_devtype(
monitor,
c"input".as_ptr() as *const c_char,
ptr::null(),
);
ud::udev_monitor_enable_receiving(monitor);
Some(Monitor(monitor))
}
}
}
pub fn wait_hotplug_available(&self) -> bool {
unsafe {
let mut fds = c::pollfd {
fd: ud::udev_monitor_get_fd(self.0),
events: c::POLLIN,
revents: 0,
};
(c::poll(&mut fds, 1, -1) == 1) && (fds.revents & c::POLLIN != 0)
}
}
pub fn device(&self) -> Device {
Device(unsafe { ud::udev_monitor_receive_device(self.0) })
}
}
impl Drop for Monitor {
fn drop(&mut self) {
unsafe {
ud::udev_monitor_unref(self.0);
}
}
}

View File

@@ -0,0 +1,17 @@
// Copyright 2016-2018 Mateusz Sieczko and other GilRs Developers
//
// Licensed under the Apache License, Version 2.0, <LICENSE-APACHE or
// http://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
// http://opensource.org/licenses/MIT>, at your option. This file may not be
// copied, modified, or distributed except according to those terms.
use std::time::Duration;
#[derive(Debug)]
/// Represents gamepad. Reexported as FfDevice
pub struct Device;
impl Device {
/// Sets magnitude for strong and weak ff motors.
pub fn set_ff_state(&mut self, _strong: u16, _weak: u16, _min_duration: Duration) {}
}

View File

@@ -0,0 +1,892 @@
// Copyright 2016-2018 Mateusz Sieczko and other GilRs Developers
//
// Licensed under the Apache License, Version 2.0, <LICENSE-APACHE or
// http://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
// http://opensource.org/licenses/MIT>, at your option. This file may not be
// copied, modified, or distributed except according to those terms.
use super::io_kit::*;
use super::FfDevice;
use crate::{AxisInfo, Event, EventType, PlatformError, PowerInfo};
use io_kit_sys::hid::usage_tables::kHIDPage_VendorDefinedStart;
use uuid::Uuid;
use core_foundation::runloop::{kCFRunLoopDefaultMode, CFRunLoop};
use io_kit_sys::hid::base::{IOHIDDeviceRef, IOHIDValueRef};
use io_kit_sys::hid::usage_tables::{
kHIDPage_GenericDesktop, kHIDUsage_GD_GamePad, kHIDUsage_GD_Joystick,
kHIDUsage_GD_MultiAxisController,
};
use io_kit_sys::ret::IOReturn;
use vec_map::VecMap;
use std::fmt::{Display, Formatter, Result as FmtResult};
use std::os::raw::c_void;
use std::sync::mpsc::{self, Receiver, Sender};
use std::sync::{Arc, Mutex};
use std::thread;
use std::time::Duration;
#[derive(Debug)]
pub struct Gilrs {
gamepads: Vec<Gamepad>,
device_infos: Arc<Mutex<Vec<DeviceInfo>>>,
rx: Receiver<(Event, Option<IOHIDDevice>)>,
}
impl Gilrs {
pub(crate) fn new() -> Result<Self, PlatformError> {
let gamepads = Vec::new();
let device_infos = Arc::new(Mutex::new(Vec::new()));
let (tx, rx) = mpsc::channel();
Self::spawn_thread(tx, device_infos.clone());
Ok(Gilrs {
gamepads,
device_infos,
rx,
})
}
fn spawn_thread(
tx: Sender<(Event, Option<IOHIDDevice>)>,
device_infos: Arc<Mutex<Vec<DeviceInfo>>>,
) {
thread::Builder::new()
.name("gilrs".to_owned())
.spawn(move || unsafe {
let mut manager = match IOHIDManager::new() {
Some(manager) => manager,
None => {
error!("Failed to create IOHIDManager object");
return;
}
};
manager.schedule_with_run_loop(CFRunLoop::get_current(), kCFRunLoopDefaultMode);
let context = &(tx.clone(), device_infos.clone()) as *const _ as *mut c_void;
manager.register_device_matching_callback(device_matching_cb, context);
let context = &(tx.clone(), device_infos.clone()) as *const _ as *mut c_void;
manager.register_device_removal_callback(device_removal_cb, context);
let context = &(tx, device_infos) as *const _ as *mut c_void;
manager.register_input_value_callback(input_value_cb, context);
CFRunLoop::run_current();
manager.unschedule_from_run_loop(CFRunLoop::get_current(), kCFRunLoopDefaultMode);
})
.expect("failed to spawn thread");
}
pub(crate) fn next_event(&mut self) -> Option<Event> {
let event = self.rx.try_recv().ok();
self.handle_event(event)
}
pub(crate) fn next_event_blocking(&mut self, timeout: Option<Duration>) -> Option<Event> {
let event = if let Some(timeout) = timeout {
self.rx.recv_timeout(timeout).ok()
} else {
self.rx.recv().ok()
};
self.handle_event(event)
}
fn handle_event(&mut self, event: Option<(Event, Option<IOHIDDevice>)>) -> Option<Event> {
match event {
Some((event, Some(device))) => {
if event.event == EventType::Connected {
if self.gamepads.get(event.id).is_some() {
self.gamepads[event.id].is_connected = true;
} else {
match Gamepad::open(device) {
Some(gamepad) => {
self.gamepads.push(gamepad);
}
None => {
error!("Failed to open gamepad: {:?}", event.id);
return None;
}
};
}
}
Some(event)
}
Some((event, None)) => {
if event.event == EventType::Disconnected {
match self.gamepads.get_mut(event.id) {
Some(gamepad) => {
match self.device_infos.lock().unwrap().get_mut(event.id) {
Some(device_info) => device_info.is_connected = false,
None => {
error!("Failed to find device_info: {:?}", event.id);
return None;
}
};
gamepad.is_connected = false;
}
None => {
error!("Failed to find gamepad: {:?}", event.id);
return None;
}
}
}
Some(event)
}
None => None,
}
}
pub fn gamepad(&self, id: usize) -> Option<&Gamepad> {
self.gamepads.get(id)
}
/// Returns index greater than index of last connected gamepad.
pub fn last_gamepad_hint(&self) -> usize {
self.gamepads.len()
}
}
#[derive(Debug)]
#[allow(dead_code)]
pub struct Gamepad {
name: String,
vendor: Option<u16>,
product: Option<u16>,
uuid: Uuid,
entry_id: u64,
location_id: u32,
page: u32,
usage: u32,
axes_info: VecMap<AxisInfo>,
axes: Vec<EvCode>,
hats: Vec<EvCode>,
buttons: Vec<EvCode>,
is_connected: bool,
}
impl Gamepad {
fn open(device: IOHIDDevice) -> Option<Gamepad> {
let io_service = match device.get_service() {
Some(io_service) => io_service,
None => {
error!("Failed to get device service");
return None;
}
};
let entry_id = match io_service.get_registry_entry_id() {
Some(entry_id) => entry_id,
None => {
error!("Failed to get entry id of device");
return None;
}
};
let location_id = match device.get_location_id() {
Some(location_id) => location_id,
None => {
error!("Failed to get location id of device");
return None;
}
};
let page = match device.get_page() {
Some(page) => {
if page >= kHIDPage_VendorDefinedStart {
error!("Device HID page is Vendor Defined. {device:?}");
return None;
}
if page == kHIDPage_GenericDesktop {
page
} else {
error!("Failed to get valid device. Expecting kHIDPage_GenericDesktop. Got 0x{:X?}", page);
return None;
}
}
None => {
error!("Failed to get page of device");
return None;
}
};
let usage = match device.get_usage() {
Some(usage) => {
if usage == kHIDUsage_GD_GamePad
|| usage == kHIDUsage_GD_Joystick
|| usage == kHIDUsage_GD_MultiAxisController
{
usage
} else {
error!("Failed to get valid device: {:?}", usage);
return None;
}
}
None => {
error!("Failed to get usage of device");
return None;
}
};
let name = device.get_name().unwrap_or_else(|| {
warn!("Failed to get name of device");
"Unknown".into()
});
let uuid = Self::create_uuid(&device).unwrap_or_default();
let mut gamepad = Gamepad {
name,
vendor: device.get_vendor_id(),
product: device.get_product_id(),
uuid,
entry_id,
location_id,
page,
usage,
axes_info: VecMap::with_capacity(8),
axes: Vec::with_capacity(8),
hats: Vec::with_capacity(4),
buttons: Vec::with_capacity(16),
is_connected: true,
};
gamepad.collect_axes_and_buttons(&device.get_elements());
Some(gamepad)
}
fn create_uuid(device: &IOHIDDevice) -> Option<Uuid> {
// SDL always uses USB bus for UUID
let bustype = u32::to_be(0x03);
let vendor_id = match device.get_vendor_id() {
Some(vendor_id) => vendor_id.to_be(),
None => {
warn!("Failed to get vendor id of device");
0
}
};
let product_id = match device.get_product_id() {
Some(product_id) => product_id.to_be(),
None => {
warn!("Failed to get product id of device");
0
}
};
let version = match device.get_version() {
Some(version) => version.to_be(),
None => {
warn!("Failed to get version of device");
0
}
};
if vendor_id == 0 && product_id == 0 && version == 0 {
None
} else {
Some(Uuid::from_fields(
bustype,
vendor_id,
0,
&[
(product_id >> 8) as u8,
product_id as u8,
0,
0,
(version >> 8) as u8,
version as u8,
0,
0,
],
))
}
}
pub fn name(&self) -> &str {
&self.name
}
pub fn vendor_id(&self) -> Option<u16> {
self.vendor
}
pub fn product_id(&self) -> Option<u16> {
self.product
}
pub fn uuid(&self) -> Uuid {
self.uuid
}
pub fn power_info(&self) -> PowerInfo {
PowerInfo::Unknown
}
pub fn is_ff_supported(&self) -> bool {
false
}
/// Creates Ffdevice corresponding to this gamepad.
pub fn ff_device(&self) -> Option<FfDevice> {
Some(FfDevice)
}
pub fn buttons(&self) -> &[EvCode] {
&self.buttons
}
pub fn axes(&self) -> &[EvCode] {
&self.axes
}
pub(crate) fn axis_info(&self, nec: EvCode) -> Option<&AxisInfo> {
self.axes_info.get(nec.usage as usize)
}
pub fn is_connected(&self) -> bool {
self.is_connected
}
fn collect_axes_and_buttons(&mut self, elements: &Vec<IOHIDElement>) {
let mut cookies = Vec::new();
self.collect_axes(elements, &mut cookies);
self.axes.sort_by_key(|axis| axis.usage);
self.hats.sort_by_key(|axis| axis.usage);
// Because "hat is axis" is a gilrs thing, we want to ensure that all hats are at the end of
// the axis vector, so the SDL mappings still work.
self.axes.extend(&self.hats);
self.collect_buttons(elements, &mut cookies);
self.buttons.sort_by_key(|button| button.usage);
}
fn collect_axes(&mut self, elements: &Vec<IOHIDElement>, cookies: &mut Vec<u32>) {
for element in elements {
let type_ = element.get_type();
let cookie = element.get_cookie();
let page = element.get_page();
let usage = element.get_usage();
if IOHIDElement::is_collection_type(type_) {
let children = element.get_children();
self.collect_axes(&children, cookies);
} else if IOHIDElement::is_axis(type_, page, usage) && !cookies.contains(&cookie) {
cookies.push(cookie);
self.axes_info.insert(
usage as usize,
AxisInfo {
min: element.get_logical_min() as _,
max: element.get_logical_max() as _,
deadzone: None,
},
);
self.axes.push(EvCode::new(page, usage));
} else if IOHIDElement::is_hat(type_, page, usage) && !cookies.contains(&cookie) {
cookies.push(cookie);
self.axes_info.insert(
usage as usize,
AxisInfo {
min: -1,
max: 1,
deadzone: None,
},
);
self.hats.push(EvCode::new(page, usage));
// All hat switches are translated into *two* axes
self.axes_info.insert(
(usage + 1) as usize, // "+ 1" is assumed for usage of 2nd hat switch axis
AxisInfo {
min: -1,
max: 1,
deadzone: None,
},
);
self.hats.push(EvCode::new(page, usage + 1));
}
}
}
fn collect_buttons(&mut self, elements: &Vec<IOHIDElement>, cookies: &mut Vec<u32>) {
for element in elements {
let type_ = element.get_type();
let cookie = element.get_cookie();
let page = element.get_page();
let usage = element.get_usage();
if IOHIDElement::is_collection_type(type_) {
let children = element.get_children();
self.collect_buttons(&children, cookies);
} else if IOHIDElement::is_button(type_, page, usage) && !cookies.contains(&cookie) {
cookies.push(cookie);
self.buttons.push(EvCode::new(page, usage));
}
}
}
}
#[derive(Debug)]
struct DeviceInfo {
entry_id: u64,
location_id: u32,
is_connected: bool,
}
#[cfg(feature = "serde-serialize")]
use serde::{Deserialize, Serialize};
#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub struct EvCode {
page: u32,
usage: u32,
}
impl EvCode {
fn new(page: u32, usage: u32) -> Self {
EvCode { page, usage }
}
pub fn into_u32(self) -> u32 {
(self.page << 16) | self.usage
}
}
impl From<IOHIDElement> for crate::EvCode {
fn from(e: IOHIDElement) -> Self {
crate::EvCode(EvCode {
page: e.get_page(),
usage: e.get_usage(),
})
}
}
impl Display for EvCode {
fn fmt(&self, f: &mut Formatter) -> FmtResult {
match self.page {
PAGE_GENERIC_DESKTOP => f.write_str("GENERIC_DESKTOP")?,
PAGE_BUTTON => f.write_str("BUTTON")?,
page => f.write_fmt(format_args!("PAGE_{}", page))?,
}
f.write_fmt(format_args!("({})", self.usage))
}
}
pub mod native_ev_codes {
use super::*;
pub const AXIS_LSTICKX: EvCode = EvCode {
page: super::PAGE_GENERIC_DESKTOP,
usage: super::USAGE_AXIS_LSTICKX,
};
pub const AXIS_LSTICKY: EvCode = EvCode {
page: super::PAGE_GENERIC_DESKTOP,
usage: super::USAGE_AXIS_LSTICKY,
};
pub const AXIS_LEFTZ: EvCode = EvCode {
page: super::PAGE_GENERIC_DESKTOP,
usage: super::USAGE_AXIS_LEFTZ,
};
pub const AXIS_RSTICKX: EvCode = EvCode {
page: super::PAGE_GENERIC_DESKTOP,
usage: super::USAGE_AXIS_RSTICKX,
};
pub const AXIS_RSTICKY: EvCode = EvCode {
page: super::PAGE_GENERIC_DESKTOP,
usage: super::USAGE_AXIS_RSTICKY,
};
pub const AXIS_RIGHTZ: EvCode = EvCode {
page: super::PAGE_GENERIC_DESKTOP,
usage: super::USAGE_AXIS_RIGHTZ,
};
pub const AXIS_DPADX: EvCode = EvCode {
page: super::PAGE_GENERIC_DESKTOP,
usage: super::USAGE_AXIS_DPADX,
};
pub const AXIS_DPADY: EvCode = EvCode {
page: super::PAGE_GENERIC_DESKTOP,
usage: super::USAGE_AXIS_DPADY,
};
pub const AXIS_RT: EvCode = EvCode {
page: super::PAGE_GENERIC_DESKTOP,
usage: super::USAGE_AXIS_RT,
};
pub const AXIS_LT: EvCode = EvCode {
page: super::PAGE_GENERIC_DESKTOP,
usage: super::USAGE_AXIS_LT,
};
pub const AXIS_RT2: EvCode = EvCode {
page: super::PAGE_GENERIC_DESKTOP,
usage: super::USAGE_AXIS_RT2,
};
pub const AXIS_LT2: EvCode = EvCode {
page: super::PAGE_GENERIC_DESKTOP,
usage: super::USAGE_AXIS_LT2,
};
pub const BTN_SOUTH: EvCode = EvCode {
page: super::PAGE_BUTTON,
usage: super::USAGE_BTN_SOUTH,
};
pub const BTN_EAST: EvCode = EvCode {
page: super::PAGE_BUTTON,
usage: super::USAGE_BTN_EAST,
};
pub const BTN_C: EvCode = EvCode {
page: super::PAGE_BUTTON,
usage: super::USAGE_BTN_C,
};
pub const BTN_NORTH: EvCode = EvCode {
page: super::PAGE_BUTTON,
usage: super::USAGE_BTN_NORTH,
};
pub const BTN_WEST: EvCode = EvCode {
page: super::PAGE_BUTTON,
usage: super::USAGE_BTN_WEST,
};
pub const BTN_Z: EvCode = EvCode {
page: super::PAGE_BUTTON,
usage: super::USAGE_BTN_Z,
};
pub const BTN_LT: EvCode = EvCode {
page: super::PAGE_BUTTON,
usage: super::USAGE_BTN_LT,
};
pub const BTN_RT: EvCode = EvCode {
page: super::PAGE_BUTTON,
usage: super::USAGE_BTN_RT,
};
pub const BTN_LT2: EvCode = EvCode {
page: super::PAGE_BUTTON,
usage: super::USAGE_BTN_LT2,
};
pub const BTN_RT2: EvCode = EvCode {
page: super::PAGE_BUTTON,
usage: super::USAGE_BTN_RT2,
};
pub const BTN_SELECT: EvCode = EvCode {
page: super::PAGE_BUTTON,
usage: super::USAGE_BTN_SELECT,
};
pub const BTN_START: EvCode = EvCode {
page: super::PAGE_BUTTON,
usage: super::USAGE_BTN_START,
};
pub const BTN_MODE: EvCode = EvCode {
page: super::PAGE_BUTTON,
usage: super::USAGE_BTN_MODE,
};
pub const BTN_LTHUMB: EvCode = EvCode {
page: super::PAGE_BUTTON,
usage: super::USAGE_BTN_LTHUMB,
};
pub const BTN_RTHUMB: EvCode = EvCode {
page: super::PAGE_BUTTON,
usage: super::USAGE_BTN_RTHUMB,
};
pub const BTN_DPAD_UP: EvCode = EvCode {
page: super::PAGE_BUTTON,
usage: super::USAGE_BTN_DPAD_UP,
};
pub const BTN_DPAD_DOWN: EvCode = EvCode {
page: super::PAGE_BUTTON,
usage: super::USAGE_BTN_DPAD_DOWN,
};
pub const BTN_DPAD_LEFT: EvCode = EvCode {
page: super::PAGE_BUTTON,
usage: super::USAGE_BTN_DPAD_LEFT,
};
pub const BTN_DPAD_RIGHT: EvCode = EvCode {
page: super::PAGE_BUTTON,
usage: super::USAGE_BTN_DPAD_RIGHT,
};
}
#[allow(clippy::type_complexity)]
extern "C" fn device_matching_cb(
context: *mut c_void,
_result: IOReturn,
_sender: *mut c_void,
value: IOHIDDeviceRef,
) {
let (tx, device_infos): &(
Sender<(Event, Option<IOHIDDevice>)>,
Arc<Mutex<Vec<DeviceInfo>>>,
) = unsafe { &*(context as *mut _) };
let device = match IOHIDDevice::new(value) {
Some(device) => device,
None => {
error!("Failed to get device");
return;
}
};
let io_service = match device.get_service() {
Some(io_service) => io_service,
None => {
error!("Failed to get device service");
return;
}
};
let entry_id = match io_service.get_registry_entry_id() {
Some(entry_id) => entry_id,
None => {
error!("Failed to get entry id of device");
return;
}
};
// Filter devices which will not succed in open(). If devices are added to the
// DeviceInfo vec, it will cause a mismatch of IDs as they're derived from
// the length of the DeviceInfo Vec, but devices which fail to open() will not
// be pushed into the Gilrs inner gamepads vec, and panics will ensue.
//
// Try to open the device early, and if it fails, do not add it to device_infos
match Gamepad::open(device.clone()) {
Some(gamepad) => drop(gamepad),
None => {
warn!("Failed to open device {device:?}. Skipping.");
return;
}
}
let mut device_infos = device_infos.lock().unwrap();
let id = match device_infos
.iter()
.position(|info| info.entry_id == entry_id && info.is_connected)
{
Some(id) => {
info!("Device is already registered: {:?}", entry_id);
id
}
None => {
let location_id = match device.get_location_id() {
Some(location_id) => location_id,
None => {
error!("Failed to get location id of device");
return;
}
};
device_infos.push(DeviceInfo {
entry_id,
location_id,
is_connected: true,
});
device_infos.len() - 1
}
};
let _ = tx.send((Event::new(id, EventType::Connected), Some(device)));
}
#[allow(clippy::type_complexity)]
extern "C" fn device_removal_cb(
context: *mut c_void,
_result: IOReturn,
_sender: *mut c_void,
value: IOHIDDeviceRef,
) {
let (tx, device_infos): &(
Sender<(Event, Option<IOHIDDevice>)>,
Arc<Mutex<Vec<DeviceInfo>>>,
) = unsafe { &*(context as *mut _) };
let device = match IOHIDDevice::new(value) {
Some(device) => device,
None => {
error!("Failed to get device");
return;
}
};
let location_id = match device.get_location_id() {
Some(location_id) => location_id,
None => {
error!("Failed to get location id of device");
return;
}
};
let device_infos = device_infos.lock().unwrap();
let id = match device_infos
.iter()
.position(|info| info.location_id == location_id && info.is_connected)
{
Some(id) => id,
None => {
warn!("Failed to find device: {:?}", location_id);
return;
}
};
let _ = tx.send((Event::new(id, EventType::Disconnected), None));
}
#[allow(clippy::type_complexity)]
extern "C" fn input_value_cb(
context: *mut c_void,
_result: IOReturn,
sender: *mut c_void,
value: IOHIDValueRef,
) {
let (tx, device_infos): &(
Sender<(Event, Option<IOHIDDevice>)>,
Arc<Mutex<Vec<DeviceInfo>>>,
) = unsafe { &*(context as *mut _) };
let device = match IOHIDDevice::new(sender as _) {
Some(device) => device,
None => {
error!("Failed to get device");
return;
}
};
let io_service = match device.get_service() {
Some(io_service) => io_service,
None => {
error!("Failed to get device service");
return;
}
};
let entry_id = match io_service.get_registry_entry_id() {
Some(entry_id) => entry_id,
None => {
error!("Failed to get entry id of device");
return;
}
};
let device_infos = device_infos.lock().unwrap();
let id = match device_infos
.iter()
.position(|info| info.entry_id == entry_id && info.is_connected)
{
Some(id) => id,
None => {
warn!("Failed to find device: {:?}", entry_id);
return;
}
};
let value = match IOHIDValue::new(value) {
Some(value) => value,
None => {
error!("Failed to get value");
return;
}
};
let element = match value.get_element() {
Some(element) => element,
None => {
error!("Failed to get element of value");
return;
}
};
let type_ = element.get_type();
let page = element.get_page();
let usage = element.get_usage();
if IOHIDElement::is_axis(type_, page, usage) {
let event = Event::new(
id,
EventType::AxisValueChanged(
value.get_value() as i32,
crate::EvCode(EvCode { page, usage }),
),
);
let _ = tx.send((event, None));
} else if IOHIDElement::is_button(type_, page, usage) {
if value.get_value() == 0 {
let event = Event::new(
id,
EventType::ButtonReleased(crate::EvCode(EvCode { page, usage })),
);
let _ = tx.send((event, None));
} else {
let event = Event::new(
id,
EventType::ButtonPressed(crate::EvCode(EvCode { page, usage })),
);
let _ = tx.send((event, None));
}
} else if IOHIDElement::is_hat(type_, page, usage) {
// Hat switch values are reported with a range of usually 8 numbers (sometimes 4). The logic
// below uses the reported min/max values of that range to map that onto a range of 0-7 for
// the directions (and any other value indicates the center position). Lucky for us, they
// always start with "up" as the lowest number and proceed clockwise. See similar handling
// here https://github.com/spurious/SDL-mirror/blob/094b2f68dd7fc9af167f905e10625e103a131459/src/joystick/darwin/SDL_sysjoystick.c#L976-L1028
//
// up
// 7 0 1
// \ | /
// left 6 - ? - 2 right (After mapping)
// / | \
// 5 4 3
// down
let range = element.get_logical_max() - element.get_logical_min() + 1;
let shifted_value = value.get_value() - element.get_logical_min();
let dpad_value = match range {
4 => shifted_value * 2, // 4-position hat switch - scale it up to 8
8 => shifted_value, // 8-position hat switch - no adjustment necessary
_ => -1, // Neither 4 nor 8 positions, we don't know what to do - default to centered
};
// At this point, the value should be normalized to the 0-7 directional values (or center
// for any other value). The dpad is a hat switch on macOS, but on other platforms dpads are
// either buttons or a pair of axes that get converted to button events by the
// `axis_dpad_to_button` filter. We will emulate axes here and let that filter do the
// button conversion, because it is safer and easier than making separate logic for button
// conversion that may diverge in subtle ways from the axis conversion logic. The most
// practical outcome of this conversion is that there are extra "released" axis events for
// the unused axis. For example, pressing just "up" will also give you a "released" event
// for either the left or right button, even if it wasn't pressed before pressing "up".
let x_axis_value = match dpad_value {
5..=7 => -1, // left
1..=3 => 1, // right
_ => 0,
};
// Since we're emulating an inverted macOS gamepad axis, down is positive and up is negative
let y_axis_value = match dpad_value {
3..=5 => 1, // down
0 | 1 | 7 => -1, // up
_ => 0,
};
let x_axis_event = Event::new(
id,
EventType::AxisValueChanged(
x_axis_value,
crate::EvCode(EvCode {
page,
usage: USAGE_AXIS_DPADX,
}),
),
);
let y_axis_event = Event::new(
id,
EventType::AxisValueChanged(
y_axis_value,
crate::EvCode(EvCode {
page,
usage: USAGE_AXIS_DPADY,
}),
),
);
let _ = tx.send((x_axis_event, None));
let _ = tx.send((y_axis_event, None));
}
}

View File

@@ -0,0 +1,612 @@
// Copyright 2016-2018 Mateusz Sieczko and other GilRs Developers
//
// Licensed under the Apache License, Version 2.0, <LICENSE-APACHE or
// http://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
// http://opensource.org/licenses/MIT>, at your option. This file may not be
// copied, modified, or distributed except according to those terms.
#![allow(non_camel_case_types)]
#![allow(non_upper_case_globals)]
#![allow(non_snake_case)]
use core_foundation::array::{
kCFTypeArrayCallBacks, CFArray, CFArrayCallBacks, CFArrayGetCount, CFArrayGetValueAtIndex,
__CFArray,
};
use core_foundation::base::{
kCFAllocatorDefault, CFAllocatorRef, CFIndex, CFRelease, CFType, TCFType,
};
use core_foundation::dictionary::CFDictionary;
use core_foundation::impl_TCFType;
use core_foundation::number::CFNumber;
use core_foundation::runloop::{CFRunLoop, CFRunLoopMode};
use core_foundation::set::CFSetApplyFunction;
use core_foundation::string::{kCFStringEncodingUTF8, CFString, CFStringCreateWithCString};
use io_kit_sys::hid::base::{
IOHIDDeviceCallback, IOHIDDeviceRef, IOHIDElementRef, IOHIDValueCallback, IOHIDValueRef,
};
use io_kit_sys::hid::device::*;
use io_kit_sys::hid::element::*;
use io_kit_sys::hid::keys::*;
use io_kit_sys::hid::manager::*;
use io_kit_sys::hid::usage_tables::*;
use io_kit_sys::hid::value::{
IOHIDValueGetElement, IOHIDValueGetIntegerValue, IOHIDValueGetTypeID,
};
use io_kit_sys::ret::{kIOReturnSuccess, IOReturn};
use io_kit_sys::types::{io_service_t, IO_OBJECT_NULL};
use io_kit_sys::{IOObjectRelease, IOObjectRetain, IORegistryEntryGetRegistryEntryID};
use std::ffi::CStr;
use std::os::raw::{c_char, c_void};
use std::ptr;
#[repr(C)]
#[derive(Debug)]
pub struct IOHIDManager(IOHIDManagerRef);
pub type CFMutableArrayRef = *mut __CFArray;
extern "C" {
pub fn CFArrayCreateMutable(
allocator: CFAllocatorRef,
capacity: CFIndex,
callBacks: *const CFArrayCallBacks,
) -> CFMutableArrayRef;
pub fn CFArrayAppendValue(theArray: CFMutableArrayRef, value: *const c_void);
}
impl_TCFType!(IOHIDManager, IOHIDManagerRef, IOHIDManagerGetTypeID);
impl IOHIDManager {
pub fn new() -> Option<Self> {
let manager = unsafe { IOHIDManagerCreate(kCFAllocatorDefault, kIOHIDOptionsTypeNone) };
if manager.is_null() {
return None;
}
let matchers = CFArray::from_CFTypes(&[
create_hid_device_matcher(kHIDPage_GenericDesktop, kHIDUsage_GD_Joystick),
create_hid_device_matcher(kHIDPage_GenericDesktop, kHIDUsage_GD_GamePad),
create_hid_device_matcher(kHIDPage_GenericDesktop, kHIDUsage_GD_MultiAxisController),
]);
unsafe {
IOHIDManagerSetDeviceMatchingMultiple(manager, matchers.as_concrete_TypeRef());
};
let ret = unsafe { IOHIDManagerOpen(manager, kIOHIDOptionsTypeNone) };
if ret == kIOReturnSuccess {
Some(IOHIDManager(manager))
} else {
unsafe { CFRelease(manager as _) };
None
}
}
pub fn open(&mut self) -> IOReturn {
unsafe { IOHIDManagerOpen(self.0, kIOHIDOptionsTypeNone) }
}
pub fn close(&mut self) -> IOReturn {
unsafe { IOHIDManagerClose(self.0, kIOHIDOptionsTypeNone) }
}
pub fn schedule_with_run_loop(&mut self, run_loop: CFRunLoop, run_loop_mode: CFRunLoopMode) {
unsafe {
IOHIDManagerScheduleWithRunLoop(self.0, run_loop.as_concrete_TypeRef(), run_loop_mode)
}
}
pub fn unschedule_from_run_loop(&mut self, run_loop: CFRunLoop, run_loop_mode: CFRunLoopMode) {
unsafe {
IOHIDManagerUnscheduleFromRunLoop(self.0, run_loop.as_concrete_TypeRef(), run_loop_mode)
}
}
pub fn register_device_matching_callback(
&mut self,
callback: IOHIDDeviceCallback,
context: *mut c_void,
) {
unsafe { IOHIDManagerRegisterDeviceMatchingCallback(self.0, callback, context) }
}
pub fn register_device_removal_callback(
&mut self,
callback: IOHIDDeviceCallback,
context: *mut c_void,
) {
unsafe { IOHIDManagerRegisterDeviceRemovalCallback(self.0, callback, context) }
}
pub fn register_input_value_callback(
&mut self,
callback: IOHIDValueCallback,
context: *mut c_void,
) {
unsafe { IOHIDManagerRegisterInputValueCallback(self.0, callback, context) }
}
pub fn get_devices(&mut self) -> Vec<IOHIDDevice> {
let copied = unsafe { IOHIDManagerCopyDevices(self.0) };
if copied.is_null() {
return vec![];
}
let devices =
unsafe { CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks) };
if devices.is_null() {
unsafe { CFRelease(copied as _) };
return vec![];
}
unsafe { CFSetApplyFunction(copied, cf_set_applier, devices as _) };
unsafe { CFRelease(copied as _) };
let device_count = unsafe { CFArrayGetCount(devices) };
let mut vec = Vec::with_capacity(device_count as _);
for i in 0..device_count {
let device = unsafe { CFArrayGetValueAtIndex(devices, i) };
if device.is_null() {
continue;
}
if let Some(device) = IOHIDDevice::new(device as _) {
vec.push(device);
}
}
unsafe { CFRelease(devices as _) };
vec
}
}
impl Drop for IOHIDManager {
fn drop(&mut self) {
unsafe { CFRelease(self.as_CFTypeRef()) }
}
}
#[repr(C)]
#[derive(Debug)]
pub struct IOHIDDevice(IOHIDDeviceRef);
impl_TCFType!(IOHIDDevice, IOHIDDeviceRef, IOHIDDeviceGetTypeID);
impl IOHIDDevice {
pub fn new(device: IOHIDDeviceRef) -> Option<IOHIDDevice> {
if device.is_null() {
None
} else {
Some(IOHIDDevice(device))
}
}
pub fn get_name(&self) -> Option<String> {
self.get_string_property(kIOHIDProductKey)
.map(|name| name.to_string())
}
pub fn get_location_id(&self) -> Option<u32> {
self.get_number_property(kIOHIDLocationIDKey)
.and_then(|location_id| location_id.to_i32().map(|location_id| location_id as u32))
}
pub fn get_bustype(&self) -> Option<u16> {
match self.get_transport_key() {
Some(transport_key) => {
if transport_key == "USB" {
Some(0x03)
} else if transport_key == "Bluetooth" {
Some(0x05)
} else {
None
}
}
None => None,
}
}
pub fn get_transport_key(&self) -> Option<String> {
self.get_string_property(kIOHIDTransportKey)
.map(|transport_key| transport_key.to_string())
}
pub fn get_vendor_id(&self) -> Option<u16> {
self.get_number_property(kIOHIDVendorIDKey)
.and_then(|vendor_id| vendor_id.to_i32().map(|vendor_id| vendor_id as u16))
}
pub fn get_product_id(&self) -> Option<u16> {
self.get_number_property(kIOHIDProductIDKey)
.and_then(|product_id| product_id.to_i32().map(|product_id| product_id as u16))
}
pub fn get_version(&self) -> Option<u16> {
self.get_number_property(kIOHIDVersionNumberKey)
.and_then(|version| version.to_i32().map(|version| version as u16))
}
pub fn get_page(&self) -> Option<u32> {
self.get_number_property(kIOHIDPrimaryUsagePageKey)
.and_then(|page| page.to_i32().map(|page| page as u32))
}
pub fn get_usage(&self) -> Option<u32> {
self.get_number_property(kIOHIDPrimaryUsageKey)
.and_then(|usage| usage.to_i32().map(|usage| usage as u32))
}
pub fn get_service(&self) -> Option<IOService> {
unsafe { IOService::new(IOHIDDeviceGetService(self.0)) }
}
pub fn get_elements(&self) -> Vec<IOHIDElement> {
let elements =
unsafe { IOHIDDeviceCopyMatchingElements(self.0, ptr::null(), kIOHIDOptionsTypeNone) };
if elements.is_null() {
return vec![];
}
let element_count = unsafe { CFArrayGetCount(elements) };
let mut vec = Vec::with_capacity(element_count as _);
for i in 0..element_count {
let element = unsafe { CFArrayGetValueAtIndex(elements, i) };
if element.is_null() {
continue;
}
vec.push(IOHIDElement(element as _));
}
vec
}
}
impl Properties for IOHIDDevice {
fn get_property(&self, key: *const c_char) -> Option<CFType> {
let key =
unsafe { CFStringCreateWithCString(kCFAllocatorDefault, key, kCFStringEncodingUTF8) };
let value = unsafe { IOHIDDeviceGetProperty(self.0, key) };
if value.is_null() {
None
} else {
Some(unsafe { TCFType::wrap_under_get_rule(value) })
}
}
}
unsafe impl Send for IOHIDDevice {}
unsafe impl Sync for IOHIDDevice {}
#[repr(C)]
#[derive(Debug)]
pub struct IOHIDElement(IOHIDElementRef);
impl_TCFType!(IOHIDElement, IOHIDElementRef, IOHIDElementGetTypeID);
impl IOHIDElement {
pub fn is_collection_type(type_: u32) -> bool {
type_ == kIOHIDElementTypeCollection
}
pub fn is_axis(type_: u32, page: u32, usage: u32) -> bool {
match type_ {
kIOHIDElementTypeInput_Misc
| kIOHIDElementTypeInput_Button
| kIOHIDElementTypeInput_Axis => match page {
kHIDPage_GenericDesktop => {
matches!(
usage,
kHIDUsage_GD_X
| kHIDUsage_GD_Y
| kHIDUsage_GD_Z
| kHIDUsage_GD_Rx
| kHIDUsage_GD_Ry
| kHIDUsage_GD_Rz
| kHIDUsage_GD_Slider
| kHIDUsage_GD_Dial
| kHIDUsage_GD_Wheel
)
}
kHIDPage_Simulation => matches!(
usage,
kHIDUsage_Sim_Rudder
| kHIDUsage_Sim_Throttle
| kHIDUsage_Sim_Accelerator
| kHIDUsage_Sim_Brake
),
_ => false,
},
_ => false,
}
}
pub fn is_button(type_: u32, page: u32, usage: u32) -> bool {
match type_ {
kIOHIDElementTypeInput_Misc
| kIOHIDElementTypeInput_Button
| kIOHIDElementTypeInput_Axis => match page {
kHIDPage_GenericDesktop => matches!(
usage,
kHIDUsage_GD_DPadUp
| kHIDUsage_GD_DPadDown
| kHIDUsage_GD_DPadRight
| kHIDUsage_GD_DPadLeft
| kHIDUsage_GD_Start
| kHIDUsage_GD_Select
| kHIDUsage_GD_SystemMainMenu
),
kHIDPage_Button | kHIDPage_Consumer => true,
_ => false,
},
_ => false,
}
}
pub fn is_hat(type_: u32, page: u32, usage: u32) -> bool {
match type_ {
kIOHIDElementTypeInput_Misc
| kIOHIDElementTypeInput_Button
| kIOHIDElementTypeInput_Axis => match page {
kHIDPage_GenericDesktop => matches!(usage, USAGE_AXIS_DPADX | USAGE_AXIS_DPADY),
_ => false,
},
_ => false,
}
}
pub fn get_cookie(&self) -> u32 {
unsafe { IOHIDElementGetCookie(self.0) }
}
pub fn get_type(&self) -> u32 {
unsafe { IOHIDElementGetType(self.0) }
}
pub fn get_page(&self) -> u32 {
unsafe { IOHIDElementGetUsagePage(self.0) }
}
pub fn get_usage(&self) -> u32 {
unsafe { IOHIDElementGetUsage(self.0) }
}
pub fn get_logical_min(&self) -> i64 {
unsafe { IOHIDElementGetLogicalMin(self.0).try_into().unwrap() }
}
pub fn get_logical_max(&self) -> i64 {
unsafe { IOHIDElementGetLogicalMax(self.0).try_into().unwrap() }
}
pub fn get_calibration_dead_zone_min(&self) -> Option<i64> {
match self.get_number_property(kIOHIDElementCalibrationDeadZoneMinKey) {
Some(calibration_dead_zone_min) => calibration_dead_zone_min.to_i64(),
None => None,
}
}
pub fn get_calibration_dead_zone_max(&self) -> Option<i64> {
match self.get_number_property(kIOHIDElementCalibrationDeadZoneMaxKey) {
Some(calibration_dead_zone_max) => calibration_dead_zone_max.to_i64(),
None => None,
}
}
pub fn get_children(&self) -> Vec<IOHIDElement> {
let elements = unsafe { IOHIDElementGetChildren(self.0) };
if elements.is_null() {
return vec![];
}
let element_count = unsafe { CFArrayGetCount(elements) };
let mut vec = Vec::with_capacity(element_count as _);
for i in 0..element_count {
let element = unsafe { CFArrayGetValueAtIndex(elements, i) };
if element.is_null() {
continue;
}
vec.push(IOHIDElement(element as _));
}
vec
}
}
impl Properties for IOHIDElement {
fn get_property(&self, key: *const c_char) -> Option<CFType> {
let key =
unsafe { CFStringCreateWithCString(kCFAllocatorDefault, key, kCFStringEncodingUTF8) };
let value = unsafe { IOHIDElementGetProperty(self.0, key) };
if value.is_null() {
None
} else {
Some(unsafe { TCFType::wrap_under_get_rule(value) })
}
}
}
#[repr(C)]
#[derive(Debug)]
pub struct IOHIDValue(IOHIDValueRef);
impl_TCFType!(IOHIDValue, IOHIDValueRef, IOHIDValueGetTypeID);
impl IOHIDValue {
pub fn new(value: IOHIDValueRef) -> Option<IOHIDValue> {
if value.is_null() {
None
} else {
Some(IOHIDValue(value))
}
}
pub fn get_value(&self) -> i64 {
unsafe { IOHIDValueGetIntegerValue(self.0).try_into().unwrap() }
}
pub fn get_element(&self) -> Option<IOHIDElement> {
let element = unsafe { IOHIDValueGetElement(self.0) };
if element.is_null() {
None
} else {
Some(IOHIDElement(element))
}
}
}
#[repr(C)]
#[derive(Debug)]
pub struct IOService(io_service_t);
impl IOService {
pub fn new(io_service: io_service_t) -> Option<IOService> {
if io_service == IO_OBJECT_NULL {
return None;
}
let result = unsafe { IOObjectRetain(io_service) };
if result == kIOReturnSuccess {
Some(IOService(io_service))
} else {
None
}
}
pub fn get_registry_entry_id(&self) -> Option<u64> {
unsafe {
IOObjectRetain(self.0);
let mut entry_id = 0;
let result = IORegistryEntryGetRegistryEntryID(self.0, &mut entry_id);
IOObjectRelease(self.0);
if result == kIOReturnSuccess {
Some(entry_id)
} else {
None
}
}
}
}
impl Drop for IOService {
fn drop(&mut self) {
unsafe {
IOObjectRelease(self.0 as _);
}
}
}
trait Properties {
fn get_number_property(&self, key: *const c_char) -> Option<CFNumber> {
match self.get_property(key) {
Some(value) => {
if value.instance_of::<CFNumber>() {
Some(unsafe { CFNumber::wrap_under_get_rule(value.as_CFTypeRef() as _) })
} else {
None
}
}
None => None,
}
}
fn get_string_property(&self, key: *const c_char) -> Option<CFString> {
match self.get_property(key) {
Some(value) => {
if value.instance_of::<CFString>() {
Some(unsafe { CFString::wrap_under_get_rule(value.as_CFTypeRef() as _) })
} else {
None
}
}
None => None,
}
}
fn get_property(&self, key: *const c_char) -> Option<CFType>;
}
fn create_hid_device_matcher(page: u32, usage: u32) -> CFDictionary<CFString, CFNumber> {
let page_key = unsafe { CStr::from_ptr(kIOHIDDeviceUsagePageKey as _) };
let page_key = CFString::from(page_key.to_str().unwrap());
let page_value = CFNumber::from(page as i32);
let usage_key = unsafe { CStr::from_ptr(kIOHIDDeviceUsageKey as _) };
let usage_key = CFString::from(usage_key.to_str().unwrap());
let usage_value = CFNumber::from(usage as i32);
CFDictionary::from_CFType_pairs(&[(page_key, page_value), (usage_key, usage_value)])
}
extern "C" fn cf_set_applier(value: *const c_void, context: *const c_void) {
unsafe { CFArrayAppendValue(context as _, value) };
}
// Usage Pages
pub const PAGE_GENERIC_DESKTOP: u32 = kHIDPage_GenericDesktop;
pub const PAGE_BUTTON: u32 = kHIDPage_Button;
// GenericDesktop Page (0x01)
pub const USAGE_AXIS_LSTICKX: u32 = kHIDUsage_GD_X;
pub const USAGE_AXIS_LSTICKY: u32 = kHIDUsage_GD_Y;
#[allow(dead_code)]
pub const USAGE_AXIS_LEFTZ: u32 = 0;
pub const USAGE_AXIS_RSTICKX: u32 = kHIDUsage_GD_Rx;
pub const USAGE_AXIS_RSTICKY: u32 = kHIDUsage_GD_Ry;
#[allow(dead_code)]
pub const USAGE_AXIS_RIGHTZ: u32 = 0;
pub const USAGE_AXIS_DPADX: u32 = kHIDUsage_GD_Hatswitch;
pub const USAGE_AXIS_DPADY: u32 = kHIDUsage_GD_Hatswitch + 1; // This "+ 1" is assumed and hard-coded elsewhere
#[allow(dead_code)]
pub const USAGE_AXIS_RT: u32 = 0;
#[allow(dead_code)]
pub const USAGE_AXIS_LT: u32 = 0;
pub const USAGE_AXIS_RT2: u32 = kHIDUsage_GD_Z;
pub const USAGE_AXIS_LT2: u32 = kHIDUsage_GD_Rz;
// Button Page (0x09)
pub const USAGE_BTN_SOUTH: u32 = kHIDUsage_Button_1;
pub const USAGE_BTN_EAST: u32 = kHIDUsage_Button_1 + 1;
pub const USAGE_BTN_NORTH: u32 = kHIDUsage_Button_1 + 2;
pub const USAGE_BTN_WEST: u32 = kHIDUsage_Button_1 + 3;
pub const USAGE_BTN_LT: u32 = kHIDUsage_Button_1 + 4;
pub const USAGE_BTN_RT: u32 = kHIDUsage_Button_1 + 5;
pub const USAGE_BTN_LT2: u32 = kHIDUsage_Button_1 + 6;
pub const USAGE_BTN_RT2: u32 = kHIDUsage_Button_1 + 7;
pub const USAGE_BTN_START: u32 = kHIDUsage_Button_1 + 8;
pub const USAGE_BTN_SELECT: u32 = kHIDUsage_Button_1 + 9;
pub const USAGE_BTN_MODE: u32 = kHIDUsage_Button_1 + 10;
pub const USAGE_BTN_DPAD_UP: u32 = kHIDUsage_Button_1 + 11;
pub const USAGE_BTN_DPAD_DOWN: u32 = kHIDUsage_Button_1 + 12;
pub const USAGE_BTN_DPAD_LEFT: u32 = kHIDUsage_Button_1 + 13;
pub const USAGE_BTN_DPAD_RIGHT: u32 = kHIDUsage_Button_1 + 14;
#[allow(dead_code)]
pub const USAGE_BTN_C: u32 = kHIDUsage_Button_1 + 15;
#[allow(dead_code)]
pub const USAGE_BTN_Z: u32 = kHIDUsage_Button_1 + 16;
#[allow(dead_code)]
pub const USAGE_BTN_LTHUMB: u32 = kHIDUsage_Button_1 + 17;
#[allow(dead_code)]
pub const USAGE_BTN_RTHUMB: u32 = kHIDUsage_Button_1 + 18;

View File

@@ -0,0 +1,15 @@
// Copyright 2016-2018 Mateusz Sieczko and other GilRs Developers
//
// Licensed under the Apache License, Version 2.0, <LICENSE-APACHE or
// http://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
// http://opensource.org/licenses/MIT>, at your option. This file may not be
// copied, modified, or distributed except according to those terms.
mod ff;
mod gamepad;
mod io_kit;
pub use self::ff::Device as FfDevice;
pub use self::gamepad::{native_ev_codes, EvCode, Gamepad, Gilrs};
// True, if Y axis of sticks points downwards.
pub const IS_Y_AXIS_REVERSED: bool = true;

60
vendor/gilrs-core/src/platform/mod.rs vendored Normal file
View File

@@ -0,0 +1,60 @@
// Copyright 2016-2018 Mateusz Sieczko and other GilRs Developers
//
// Licensed under the Apache License, Version 2.0, <LICENSE-APACHE or
// http://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
// http://opensource.org/licenses/MIT>, at your option. This file may not be
// copied, modified, or distributed except according to those terms.
//! Module which exports the platform-specific types.
//!
//! Each backend has to provide:
//!
//! * A `FfDevice` (a struct which handles force feedback)
//! * A `Gilrs` context
//! * A `Gamepad` struct
//! * A static `str` which specifies the name of the SDL input mapping
//! * A constant which define whether Y axis of sticks points upwards or downwards
//! * A module with the platform-specific constants for common gamepad buttons
//! called `native_ev_codes`
#![allow(clippy::module_inception)]
pub use self::platform::*;
#[cfg(target_os = "linux")]
#[path = "linux/mod.rs"]
mod platform;
#[cfg(target_os = "macos")]
#[path = "macos/mod.rs"]
mod platform;
#[cfg(all(not(feature = "xinput"), not(feature = "wgi")))]
compile_error!(
"Windows needs one of the features `gilrs/xinput` or `gilrs/wgi` enabled. \nEither don't use \
'default-features = false' or add one of the features back."
);
#[cfg(all(feature = "wgi", feature = "xinput"))]
compile_error!("features `gilrs/xinput` and `gilrs/wgi` are mutually exclusive");
#[cfg(all(target_os = "windows", feature = "xinput", not(feature = "wgi")))]
#[path = "windows_xinput/mod.rs"]
mod platform;
#[cfg(all(target_os = "windows", feature = "wgi"))]
#[path = "windows_wgi/mod.rs"]
mod platform;
#[cfg(target_arch = "wasm32")]
#[path = "wasm/mod.rs"]
mod platform;
#[cfg(all(
not(any(target_os = "linux")),
not(target_os = "macos"),
not(target_os = "windows"),
not(target_arch = "wasm32")
))]
#[path = "default/mod.rs"]
mod platform;

View File

@@ -0,0 +1,15 @@
// Copyright 2016-2018 Mateusz Sieczko and other GilRs Developers
//
// Licensed under the Apache License, Version 2.0, <LICENSE-APACHE or
// http://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
// http://opensource.org/licenses/MIT>, at your option. This file may not be
// copied, modified, or distributed except according to those terms.
use std::time::Duration;
#[derive(Debug)]
pub struct Device;
impl Device {
pub fn set_ff_state(&mut self, _strong: u16, _weak: u16, _min_duration: Duration) {}
}

View File

@@ -0,0 +1,486 @@
// Copyright 2016-2018 Mateusz Sieczko and other GilRs Developers
//
// Licensed under the Apache License, Version 2.0, <LICENSE-APACHE or
// http://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
// http://opensource.org/licenses/MIT>, at your option. This file may not be
// copied, modified, or distributed except according to those terms.
use std::collections::VecDeque;
use std::fmt::{Display, Formatter, Result as FmtResult};
use std::time::Duration;
use js_sys::RegExp;
use uuid::Uuid;
use wasm_bindgen::JsCast;
use web_sys::{DomException, Gamepad as WebGamepad, GamepadButton, GamepadMappingType};
use super::FfDevice;
use crate::platform::native_ev_codes::{BTN_LT2, BTN_RT2};
use crate::{AxisInfo, Event, EventType, PlatformError, PowerInfo};
#[cfg(feature = "serde-serialize")]
use serde::{Deserialize, Serialize};
#[derive(Debug)]
pub struct Gilrs {
event_cache: VecDeque<Event>,
gamepads: Vec<Gamepad>,
new_web_gamepads: Vec<WebGamepad>,
next_event_error_logged: bool,
}
impl Gilrs {
pub(crate) fn new() -> Result<Self, PlatformError> {
let window =
web_sys::window().ok_or_else(|| PlatformError::Other(Box::new(Error::NoWindow)))?;
if !window.is_secure_context() {
warn!("Context is not secure, gamepad API may not be available.")
}
Ok({
Gilrs {
event_cache: VecDeque::new(),
gamepads: Vec::new(),
new_web_gamepads: Vec::new(),
next_event_error_logged: false,
}
})
}
pub(crate) fn next_event(&mut self) -> Option<Event> {
// Don't duplicate the work of checking the diff between the old and new gamepads if
// there are still events to return
if !self.event_cache.is_empty() {
return self.event_cache.pop_front();
}
let gamepads = match web_sys::window()
.expect("no window")
.navigator()
.get_gamepads()
{
Ok(x) => {
self.next_event_error_logged = false;
x
}
Err(js) => {
if !self.next_event_error_logged {
self.next_event_error_logged = true;
let exception: DomException = match js.dyn_into() {
Ok(x) => x,
Err(e) => {
error!("getGamepads() failed with unknown error: {:?}", e);
return None;
}
};
error!("getGamepads(): {}", exception.message());
}
return None;
}
};
// Gather all non-null gamepads
for maybe_js_gamepad in gamepads {
if !maybe_js_gamepad.is_null() {
self.new_web_gamepads
.push(WebGamepad::from(maybe_js_gamepad));
}
}
// Update existing gamepads
for (id, gamepad) in self.gamepads.iter_mut().enumerate() {
let maybe_js_gamepad_index = self
.new_web_gamepads
.iter()
.position(|x| gamepad.gamepad.index() == x.index());
if let Some(js_gamepad_index) = maybe_js_gamepad_index {
gamepad.gamepad = self.new_web_gamepads.swap_remove(js_gamepad_index);
if !gamepad.connected {
self.event_cache
.push_back(Event::new(id, EventType::Connected));
gamepad.connected = true;
}
let buttons = gamepad.gamepad.buttons();
for btn_index in 0..gamepad
.mapping
.buttons()
.len()
.min(buttons.length() as usize)
{
let (old_pressed, old_value) = gamepad.mapping.buttons()[btn_index];
let ev_code = crate::EvCode(gamepad.button_code(btn_index));
let button_object = GamepadButton::from(buttons.get(btn_index as u32));
let new_pressed = button_object.pressed();
let new_value = button_object.value();
if [BTN_LT2, BTN_RT2].contains(&ev_code.0) && old_value != new_value {
// Treat left and right triggers as axes so we get non-binary values.
// Button Pressed/Changed events are generated from the axis changed
// events later.
let value = (new_value * i32::MAX as f64) as i32;
self.event_cache
.push_back(Event::new(id, EventType::AxisValueChanged(value, ev_code)));
} else {
match (old_pressed, new_pressed) {
(false, true) => self
.event_cache
.push_back(Event::new(id, EventType::ButtonPressed(ev_code))),
(true, false) => self
.event_cache
.push_back(Event::new(id, EventType::ButtonReleased(ev_code))),
_ => (),
}
}
gamepad.mapping.buttons_mut()[btn_index] = (new_pressed, new_value);
}
let axes = gamepad.gamepad.axes();
for axis_index in 0..gamepad.mapping.axes().len().min(axes.length() as usize) {
let old_value = gamepad.mapping.axes()[axis_index];
let new_value = axes
.get(axis_index as u32)
.as_f64()
.expect("axes() should be an array of f64");
if old_value != new_value {
let ev_code = crate::EvCode(gamepad.axis_code(axis_index));
let value = (new_value * i32::MAX as f64) as i32;
self.event_cache
.push_back(Event::new(id, EventType::AxisValueChanged(value, ev_code)));
}
gamepad.mapping.axes_mut()[axis_index] = new_value;
}
} else {
// Create a disconnect event
if gamepad.connected {
self.event_cache
.push_back(Event::new(id, EventType::Disconnected));
gamepad.connected = false;
}
}
}
// Add new gamepads
for js_gamepad in self.new_web_gamepads.drain(..) {
let id = self.gamepads.len();
self.gamepads.push(Gamepad::new(js_gamepad));
// Create a connected event
let event = Event::new(id, EventType::Connected);
self.event_cache.push_back(event);
}
self.event_cache.pop_front()
}
pub(crate) fn next_event_blocking(&mut self, _timeout: Option<Duration>) -> Option<Event> {
unimplemented!("next_event_blocking is not supported on web. Use next_event.")
}
pub fn gamepad(&self, id: usize) -> Option<&Gamepad> {
self.gamepads.get(id)
}
pub fn last_gamepad_hint(&self) -> usize {
self.gamepads.len()
}
}
#[derive(Debug)]
#[allow(clippy::large_enum_variant)]
enum Mapping {
Standard {
buttons: [(bool, f64); 17],
axes: [f64; 4],
},
NoMapping {
buttons: Vec<(bool, f64)>,
axes: Vec<f64>,
},
}
impl Mapping {
fn buttons(&self) -> &[(bool, f64)] {
match self {
Mapping::Standard { buttons, .. } => buttons,
Mapping::NoMapping { buttons, .. } => buttons,
}
}
fn buttons_mut(&mut self) -> &mut [(bool, f64)] {
match self {
Mapping::Standard { buttons, .. } => &mut *buttons,
Mapping::NoMapping { buttons, .. } => &mut *buttons,
}
}
fn axes(&self) -> &[f64] {
match self {
Mapping::Standard { axes, .. } => axes,
Mapping::NoMapping { axes, .. } => axes,
}
}
fn axes_mut(&mut self) -> &mut [f64] {
match self {
Mapping::Standard { axes, .. } => &mut *axes,
Mapping::NoMapping { axes, .. } => &mut *axes,
}
}
}
#[derive(Debug)]
pub struct Gamepad {
uuid: Uuid,
gamepad: WebGamepad,
name: String,
vendor: Option<u16>,
product: Option<u16>,
mapping: Mapping,
connected: bool,
}
impl Gamepad {
fn new(gamepad: WebGamepad) -> Gamepad {
let name = gamepad.id();
// This regular expression extracts the vendor and product ID from the gamepad "id".
// Firefox:
// 054c-05c4-Sony Computer Entertainment Wireless Controller
// Chrome:
// Sony Computer Entertainment Wireless Controller (STANDARD GAMEPAD Vendor: 054c Product: 05c4)
let regexp = RegExp::new(
r"(?:^([a-f0-9]{4})-([a-f0-9]{4})-)|(?:Vendor: ([a-f0-9]{4}) Product: ([a-f0-9]{4})\)$)",
"",
);
let (vendor, product) = if let Some(matches) = regexp.exec(&name) {
let parse_hex = |index| {
matches
.get(index)
.as_string()
.and_then(|id| u16::from_str_radix(&id, 16).ok())
};
(
parse_hex(1).or_else(|| parse_hex(3)),
parse_hex(2).or_else(|| parse_hex(4)),
)
} else {
(None, None)
};
let buttons = gamepad.buttons();
let button_iter = {
{
buttons.iter().map(GamepadButton::from)
}
};
let axes = gamepad.axes();
let axis_iter = {
{
axes.iter()
.map(|val| val.as_f64().expect("axes() should be an array of f64"))
}
};
let mapping = match gamepad.mapping() {
GamepadMappingType::Standard => {
let mut buttons = [(false, 0.0); 17];
let mut axes = [0.0; 4];
for (index, button) in button_iter.enumerate().take(buttons.len()) {
buttons[index] = (button.pressed(), button.value());
}
for (index, axis) in axis_iter.enumerate().take(axes.len()) {
axes[index] = axis;
}
Mapping::Standard { buttons, axes }
}
_ => {
let buttons = button_iter
.map(|button| (button.pressed(), button.value()))
.collect();
let axes = axis_iter.collect();
Mapping::NoMapping { buttons, axes }
}
};
Gamepad {
uuid: Uuid::nil(),
gamepad,
name,
vendor,
product,
mapping,
connected: true,
}
}
pub fn name(&self) -> &str {
&self.name
}
pub fn uuid(&self) -> Uuid {
self.uuid
}
pub fn vendor_id(&self) -> Option<u16> {
self.vendor
}
pub fn product_id(&self) -> Option<u16> {
self.product
}
pub fn is_connected(&self) -> bool {
self.gamepad.connected()
}
pub fn power_info(&self) -> PowerInfo {
PowerInfo::Unknown
}
pub fn is_ff_supported(&self) -> bool {
false
}
pub fn ff_device(&self) -> Option<FfDevice> {
None
}
pub fn buttons(&self) -> &[EvCode] {
&native_ev_codes::BUTTONS
}
pub fn axes(&self) -> &[EvCode] {
&native_ev_codes::AXES
}
fn button_code(&self, index: usize) -> EvCode {
self.buttons()
.get(index)
.copied()
.unwrap_or(EvCode(index as u8 + 31))
}
fn axis_code(&self, index: usize) -> EvCode {
self.axes()
.get(index)
.copied()
.unwrap_or_else(|| EvCode((index + self.mapping.buttons().len()) as u8 + 31))
}
pub(crate) fn axis_info(&self, _nec: EvCode) -> Option<&AxisInfo> {
if self.buttons().contains(&_nec) {
return Some(&AxisInfo {
min: 0,
max: i32::MAX,
deadzone: None,
});
}
Some(&AxisInfo {
min: i32::MIN,
max: i32::MAX,
deadzone: None,
})
}
}
#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub struct EvCode(u8);
impl EvCode {
pub fn into_u32(self) -> u32 {
self.0 as u32
}
}
impl Display for EvCode {
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
self.0.fmt(f)
}
}
#[derive(Debug, Copy, Clone)]
enum Error {
NoWindow,
}
impl Display for Error {
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
match *self {
Error::NoWindow => f.write_str("window is not available"),
}
}
}
impl std::error::Error for Error {}
pub mod native_ev_codes {
use super::EvCode;
pub const AXIS_LSTICKX: EvCode = EvCode(0);
pub const AXIS_LSTICKY: EvCode = EvCode(1);
pub const AXIS_LEFTZ: EvCode = EvCode(2);
pub const AXIS_RSTICKX: EvCode = EvCode(3);
pub const AXIS_RSTICKY: EvCode = EvCode(4);
pub const AXIS_RIGHTZ: EvCode = EvCode(5);
pub const AXIS_DPADX: EvCode = EvCode(6);
pub const AXIS_DPADY: EvCode = EvCode(7);
pub const AXIS_RT: EvCode = EvCode(8);
pub const AXIS_LT: EvCode = EvCode(9);
pub const AXIS_RT2: EvCode = EvCode(10);
pub const AXIS_LT2: EvCode = EvCode(11);
pub const BTN_SOUTH: EvCode = EvCode(12);
pub const BTN_EAST: EvCode = EvCode(13);
pub const BTN_C: EvCode = EvCode(14);
pub const BTN_NORTH: EvCode = EvCode(15);
pub const BTN_WEST: EvCode = EvCode(16);
pub const BTN_Z: EvCode = EvCode(17);
pub const BTN_LT: EvCode = EvCode(18);
pub const BTN_RT: EvCode = EvCode(19);
pub const BTN_LT2: EvCode = EvCode(20);
pub const BTN_RT2: EvCode = EvCode(21);
pub const BTN_SELECT: EvCode = EvCode(22);
pub const BTN_START: EvCode = EvCode(23);
pub const BTN_MODE: EvCode = EvCode(24);
pub const BTN_LTHUMB: EvCode = EvCode(25);
pub const BTN_RTHUMB: EvCode = EvCode(26);
pub const BTN_DPAD_UP: EvCode = EvCode(27);
pub const BTN_DPAD_DOWN: EvCode = EvCode(28);
pub const BTN_DPAD_LEFT: EvCode = EvCode(29);
pub const BTN_DPAD_RIGHT: EvCode = EvCode(30);
pub(super) static BUTTONS: [EvCode; 17] = [
BTN_SOUTH,
BTN_EAST,
BTN_WEST,
BTN_NORTH,
BTN_LT,
BTN_RT,
BTN_LT2,
BTN_RT2,
BTN_SELECT,
BTN_START,
BTN_LTHUMB,
BTN_RTHUMB,
BTN_DPAD_UP,
BTN_DPAD_DOWN,
BTN_DPAD_LEFT,
BTN_DPAD_RIGHT,
BTN_MODE,
];
pub(super) static AXES: [EvCode; 4] = [AXIS_LSTICKX, AXIS_LSTICKY, AXIS_RSTICKX, AXIS_RSTICKY];
}

View File

@@ -0,0 +1,7 @@
mod ff;
mod gamepad;
pub use self::ff::Device as FfDevice;
pub use self::gamepad::{native_ev_codes, EvCode, Gamepad, Gilrs};
pub const IS_Y_AXIS_REVERSED: bool = true;

View File

@@ -0,0 +1,37 @@
// Copyright 2016-2018 Mateusz Sieczko and other GilRs Developers
//
// Licensed under the Apache License, Version 2.0, <LICENSE-APACHE or
// http://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
// http://opensource.org/licenses/MIT>, at your option. This file may not be
// copied, modified, or distributed except according to those terms.
use std::time::Duration;
use windows::Gaming::Input::Gamepad as WgiGamepad;
use windows::Gaming::Input::GamepadVibration;
#[derive(Debug)]
pub struct Device {
id: u32,
wgi_gamepad: Option<WgiGamepad>,
}
impl Device {
pub(crate) fn new(id: u32, wgi_gamepad: Option<WgiGamepad>) -> Self {
Device { id, wgi_gamepad }
}
pub fn set_ff_state(&mut self, strong: u16, weak: u16, _min_duration: Duration) {
if let Some(wgi_gamepad) = &self.wgi_gamepad {
if let Err(err) = wgi_gamepad.SetVibration(GamepadVibration {
LeftMotor: (strong as f64) / (u16::MAX as f64),
RightMotor: (weak as f64) / (u16::MAX as f64),
LeftTrigger: 0.0,
RightTrigger: 0.0,
}) {
error!(
"Failed to change FF state unknown error. ID = {}, error = {:?}.",
self.id, err
);
}
}
}
}

View File

@@ -0,0 +1,957 @@
// Copyright 2016-2018 Mateusz Sieczko and other GilRs Developers
//
// Licensed under the Apache License, Version 2.0, <LICENSE-APACHE or
// http://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
// http://opensource.org/licenses/MIT>, at your option. This file may not be
// copied, modified, or distributed except according to those terms.
use super::FfDevice;
use crate::native_ev_codes as nec;
use crate::{utils, AxisInfo, Event, EventType, PlatformError, PowerInfo};
#[cfg(feature = "serde-serialize")]
use serde::{Deserialize, Serialize};
use std::fmt::{Display, Formatter, Result as FmtResult};
use std::sync::mpsc::{self, Receiver, Sender, TryRecvError};
use std::thread;
use std::thread::JoinHandle;
use std::time::{Duration, Instant, SystemTime};
use uuid::Uuid;
use windows::core::HSTRING;
use windows::Devices::Power::BatteryReport;
use windows::Foundation::EventHandler;
use windows::Gaming::Input::{
GameControllerSwitchPosition, Gamepad as WgiGamepad, GamepadButtons, GamepadReading,
RawGameController,
};
use windows::System::Power::BatteryStatus;
const SDL_HARDWARE_BUS_USB: u32 = 0x03;
// const SDL_HARDWARE_BUS_BLUETOOTH: u32 = 0x05;
// The general consensus is that standard xbox controllers poll at ~125 hz which
// means 8 ms between updates.
// Seems like a good target for how often we update the background thread.
const EVENT_THREAD_SLEEP_TIME: u64 = 8;
const WGI_TO_GILRS_BUTTON_MAP: [(GamepadButtons, crate::EvCode); 14] = [
(GamepadButtons::DPadUp, nec::BTN_DPAD_UP),
(GamepadButtons::DPadDown, nec::BTN_DPAD_DOWN),
(GamepadButtons::DPadLeft, nec::BTN_DPAD_LEFT),
(GamepadButtons::DPadRight, nec::BTN_DPAD_RIGHT),
(GamepadButtons::Menu, nec::BTN_START),
(GamepadButtons::View, nec::BTN_SELECT),
(GamepadButtons::LeftThumbstick, nec::BTN_LTHUMB),
(GamepadButtons::RightThumbstick, nec::BTN_RTHUMB),
(GamepadButtons::LeftShoulder, nec::BTN_LT),
(GamepadButtons::RightShoulder, nec::BTN_RT),
(GamepadButtons::A, nec::BTN_SOUTH),
(GamepadButtons::B, nec::BTN_EAST),
(GamepadButtons::X, nec::BTN_WEST),
(GamepadButtons::Y, nec::BTN_NORTH),
];
/// This is similar to `gilrs_core::Event` but has a raw_game_controller that still needs to be
/// converted to a gilrs gamepad id.
#[derive(Debug)]
struct WgiEvent {
raw_game_controller: RawGameController,
event: EventType,
pub time: SystemTime,
}
impl WgiEvent {
fn new(raw_game_controller: RawGameController, event: EventType) -> Self {
let time = utils::time_now();
WgiEvent {
raw_game_controller,
event,
time,
}
}
}
#[derive(Debug)]
pub struct Gilrs {
gamepads: Vec<Gamepad>,
rx: Receiver<WgiEvent>,
join_handle: Option<JoinHandle<()>>,
stop_tx: Sender<()>,
}
impl Gilrs {
pub(crate) fn new() -> Result<Self, PlatformError> {
let raw_game_controllers = RawGameController::RawGameControllers()
.map_err(|e| PlatformError::Other(Box::new(e)))?;
let count = raw_game_controllers
.Size()
.map_err(|e| PlatformError::Other(Box::new(e)))?;
// Intentionally avoiding using RawGameControllers.into_iter() as it triggers a crash when
// the app is run through steam.
// https://gitlab.com/gilrs-project/gilrs/-/issues/132
let gamepads = (0..count)
.map(|i| {
let controller = raw_game_controllers
.GetAt(i)
.map_err(|e| PlatformError::Other(Box::new(e)))?;
Ok(Gamepad::new(i, controller))
})
.collect::<Result<Vec<_>, _>>()?;
let (tx, rx) = mpsc::channel();
let (stop_tx, stop_rx) = mpsc::channel();
let join_handle = Some(Self::spawn_thread(tx, stop_rx));
Ok(Gilrs {
gamepads,
rx,
join_handle,
stop_tx,
})
}
fn spawn_thread(tx: Sender<WgiEvent>, stop_rx: Receiver<()>) -> JoinHandle<()> {
let added_tx = tx.clone();
let added_handler = EventHandler::<RawGameController>::new(move |_, g| {
if let Some(g) = g.as_ref() {
added_tx
.send(WgiEvent::new(g.clone(), EventType::Connected))
.expect("should be able to send to main thread");
}
Ok(())
});
let controller_added_token =
RawGameController::RawGameControllerAdded(&added_handler).unwrap();
let removed_tx = tx.clone();
let removed_handler = EventHandler::<RawGameController>::new(move |_, g| {
if let Some(g) = g.as_ref() {
removed_tx
.send(WgiEvent::new(g.clone(), EventType::Disconnected))
.expect("should be able to send to main thread");
}
Ok(())
});
let controller_removed_token =
RawGameController::RawGameControllerRemoved(&removed_handler).unwrap();
std::thread::Builder::new()
.name("gilrs".to_owned())
.spawn(move || {
let mut controllers: Vec<RawGameController> = Vec::new();
// To avoid allocating every update, store old and new readings for every controller
// and swap their memory
let mut readings: Vec<(HSTRING, Reading, Reading)> = Vec::new();
let mut last_failed_get_id: Option<Instant> = None;
loop {
match stop_rx.try_recv() {
Ok(_) => break,
Err(TryRecvError::Disconnected) => {
warn!("stop_rx channel disconnected prematurely");
break;
}
Err(TryRecvError::Empty) => {}
}
controllers.clear();
// Avoiding using RawGameControllers().into_iter() here due to it causing an
// unhandled exception when the app is running through steam.
// https://gitlab.com/gilrs-project/gilrs/-/issues/132
if let Ok(raw_game_controllers) = RawGameController::RawGameControllers() {
let count = raw_game_controllers.Size().unwrap_or_default();
for index in 0..count {
if let Ok(controller) = raw_game_controllers.GetAt(index) {
controllers.push(controller);
}
}
}
for controller in controllers.iter() {
let id: HSTRING = match controller.NonRoamableId() {
Ok(id) => id,
Err(e) => {
if last_failed_get_id.map_or(true, |x| x.elapsed().as_secs() > 59) {
error!(
"Failed to get gamepad id: {e}! Skipping reading events \
for this gamepad."
);
last_failed_get_id = Some(Instant::now());
}
continue;
}
};
// Find readings for this controller or insert new ones.
let index = match readings.iter().position(|(other_id, ..)| id == *other_id)
{
None => {
let reading = match WgiGamepad::FromGameController(controller) {
Ok(wgi_gamepad) => {
Reading::Gamepad(wgi_gamepad.GetCurrentReading().unwrap())
}
_ => Reading::Raw(RawGamepadReading::new(controller).unwrap()),
};
readings.push((id, reading.clone(), reading));
readings.len() - 1
}
Some(i) => i,
};
let (_, old_reading, new_reading) = &mut readings[index];
// Make last update's reading the old reading and get a new one.
std::mem::swap(old_reading, new_reading);
if let Err(e) = new_reading.update(controller) {
if e.code().is_err() {
error!("Reading::update() function failed with {e}");
}
}
// Skip if this is the same reading as the last one.
if old_reading.time() == new_reading.time() {
continue;
}
Reading::send_events_for_differences(
old_reading,
new_reading,
controller,
&tx,
);
}
thread::sleep(Duration::from_millis(EVENT_THREAD_SLEEP_TIME));
}
if let Err(e) =
RawGameController::RemoveRawGameControllerAdded(controller_added_token)
{
error!("Failed to remove RawGameControllerAdded event handler: {e}");
}
if let Err(e) =
RawGameController::RemoveRawGameControllerRemoved(controller_removed_token)
{
error!("Failed to remove RawGameControllerRemoved event handler: {e}");
}
})
.expect("failed to spawn thread")
}
pub(crate) fn next_event(&mut self) -> Option<Event> {
self.rx
.try_recv()
.ok()
.map(|wgi_event: WgiEvent| self.handle_event(wgi_event))
}
pub(crate) fn next_event_blocking(&mut self, timeout: Option<Duration>) -> Option<Event> {
if let Some(timeout) = timeout {
self.rx
.recv_timeout(timeout)
.ok()
.map(|wgi_event: WgiEvent| self.handle_event(wgi_event))
} else {
self.rx
.recv()
.ok()
.map(|wgi_event: WgiEvent| self.handle_event(wgi_event))
}
}
fn handle_event(&mut self, wgi_event: WgiEvent) -> Event {
// Find the index of the gamepad in our vec or insert it
let id = self
.gamepads
.iter()
.position(
|gamepad| match wgi_event.raw_game_controller.NonRoamableId() {
Ok(id) => id == gamepad.non_roamable_id,
_ => false,
},
)
.unwrap_or_else(|| {
self.gamepads.push(Gamepad::new(
self.gamepads.len() as u32,
wgi_event.raw_game_controller,
));
self.gamepads.len() - 1
});
match wgi_event.event {
EventType::Connected => self.gamepads[id].is_connected = true,
EventType::Disconnected => self.gamepads[id].is_connected = false,
_ => (),
}
Event {
id,
event: wgi_event.event,
time: wgi_event.time,
}
}
pub fn gamepad(&self, id: usize) -> Option<&Gamepad> {
self.gamepads.get(id)
}
pub fn last_gamepad_hint(&self) -> usize {
self.gamepads.len()
}
}
impl Drop for Gilrs {
fn drop(&mut self) {
if let Err(e) = self.stop_tx.send(()) {
warn!("Failed to send stop signal to thread: {e:?}");
}
if let Err(e) = self.join_handle.take().unwrap().join() {
warn!("Failed to join thread: {e:?}");
}
}
}
#[derive(Debug, Clone)]
struct RawGamepadReading {
axes: Vec<f64>,
buttons: Vec<bool>,
switches: Vec<GameControllerSwitchPosition>,
time: u64,
}
impl RawGamepadReading {
fn new(raw_game_controller: &RawGameController) -> windows::core::Result<Self> {
let axis_count = raw_game_controller.AxisCount()? as usize;
let button_count = raw_game_controller.ButtonCount()? as usize;
let switch_count = raw_game_controller.SwitchCount()? as usize;
let mut new = Self {
axes: vec![0.0; axis_count],
buttons: vec![false; button_count],
switches: vec![GameControllerSwitchPosition::default(); switch_count],
time: 0,
};
new.time = raw_game_controller.GetCurrentReading(
&mut new.buttons,
&mut new.switches,
&mut new.axes,
)?;
Ok(new)
}
fn update(&mut self, raw_game_controller: &RawGameController) -> windows::core::Result<()> {
self.time = raw_game_controller.GetCurrentReading(
&mut self.buttons,
&mut self.switches,
&mut self.axes,
)?;
Ok(())
}
}
/// Treats switches like a two axes similar to a Directional pad.
/// Returns a tuple containing the values of the x and y axis.
/// Value's range is -1 to 1.
fn direction_from_switch(switch: GameControllerSwitchPosition) -> (i32, i32) {
match switch {
GameControllerSwitchPosition::Up => (0, 1),
GameControllerSwitchPosition::Down => (0, -1),
GameControllerSwitchPosition::Right => (1, 0),
GameControllerSwitchPosition::Left => (-1, 0),
GameControllerSwitchPosition::UpLeft => (-1, 1),
GameControllerSwitchPosition::UpRight => (1, 1),
GameControllerSwitchPosition::DownLeft => (-1, -1),
GameControllerSwitchPosition::DownRight => (1, -1),
_ => (0, 0),
}
}
#[derive(Clone)]
enum Reading {
Raw(RawGamepadReading),
Gamepad(GamepadReading),
}
impl Reading {
fn time(&self) -> u64 {
match self {
Reading::Raw(r) => r.time,
Reading::Gamepad(r) => r.Timestamp,
}
}
fn update(&mut self, controller: &RawGameController) -> windows::core::Result<()> {
match self {
Reading::Raw(raw_reading) => {
raw_reading.update(controller)?;
}
Reading::Gamepad(gamepad_reading) => {
let gamepad: WgiGamepad = WgiGamepad::FromGameController(controller)?;
*gamepad_reading = gamepad.GetCurrentReading()?;
}
}
Ok(())
}
fn send_events_for_differences(
old: &Self,
new: &Self,
controller: &RawGameController,
tx: &Sender<WgiEvent>,
) {
match (old, new) {
// WGI RawGameController
(Reading::Raw(old), Reading::Raw(new)) => {
// Axis changes
for index in 0..new.axes.len() {
if old.axes.get(index) != new.axes.get(index) {
// https://github.com/libsdl-org/SDL/blob/6af17369ca773155bd7f39b8801725c4a6d52e4f/src/joystick/windows/SDL_windows_gaming_input.c#L863
let value = ((new.axes[index] * 65535.0) - 32768.0) as i32;
let event_type = EventType::AxisValueChanged(
value,
crate::EvCode(EvCode {
kind: EvCodeKind::Axis,
index: index as u32,
}),
);
tx.send(WgiEvent::new(controller.clone(), event_type))
.unwrap()
}
}
for index in 0..new.buttons.len() {
if old.buttons.get(index) != new.buttons.get(index) {
let event_type = match new.buttons[index] {
true => EventType::ButtonPressed(crate::EvCode(EvCode {
kind: EvCodeKind::Button,
index: index as u32,
})),
false => EventType::ButtonReleased(crate::EvCode(EvCode {
kind: EvCodeKind::Button,
index: index as u32,
})),
};
tx.send(WgiEvent::new(controller.clone(), event_type))
.unwrap()
}
}
for index in 0..old.switches.len() {
let (old_x, old_y) = direction_from_switch(old.switches[index]);
let (new_x, new_y) = direction_from_switch(new.switches[index]);
if old_x != new_x {
let event_type = EventType::AxisValueChanged(
new_x,
crate::EvCode(EvCode {
kind: EvCodeKind::Switch,
index: (index * 2) as u32,
}),
);
tx.send(WgiEvent::new(controller.clone(), event_type))
.unwrap()
}
if old_y != new_y {
let event_type = EventType::AxisValueChanged(
-new_y,
crate::EvCode(EvCode {
kind: EvCodeKind::Switch,
index: (index * 2) as u32 + 1,
}),
);
tx.send(WgiEvent::new(controller.clone(), event_type))
.unwrap()
}
}
}
// WGI Gamepad
(Reading::Gamepad(old), Reading::Gamepad(new)) => {
#[rustfmt::skip]
let axes = [
(new.LeftTrigger, old.LeftTrigger, nec::AXIS_LT2, 1.0),
(new.RightTrigger, old.RightTrigger, nec::AXIS_RT2, 1.0),
(new.LeftThumbstickX, old.LeftThumbstickX, nec::AXIS_LSTICKX, 1.0),
(new.LeftThumbstickY, old.LeftThumbstickY, nec::AXIS_LSTICKY, -1.0),
(new.RightThumbstickX, old.RightThumbstickX, nec::AXIS_RSTICKX, 1.0),
(new.RightThumbstickY, old.RightThumbstickY, nec::AXIS_RSTICKY, -1.0),
];
for (new, old, code, multiplier) in axes {
if new != old {
let _ = tx.send(WgiEvent::new(
controller.clone(),
EventType::AxisValueChanged(
(multiplier * new * i32::MAX as f64) as i32,
code,
),
));
}
}
for (current_button, ev_code) in WGI_TO_GILRS_BUTTON_MAP {
if (new.Buttons & current_button) != (old.Buttons & current_button) {
let _ = match new.Buttons & current_button != GamepadButtons::None {
true => tx.send(WgiEvent::new(
controller.clone(),
EventType::ButtonPressed(ev_code),
)),
false => tx.send(WgiEvent::new(
controller.clone(),
EventType::ButtonReleased(ev_code),
)),
};
}
}
}
(a, b) => {
warn!(
"WGI Controller changed from gamepad: {} to gamepad: {}. Could not compare \
last update.",
a.is_gamepad(),
b.is_gamepad()
);
#[cfg(debug_assertions)]
panic!(
"Controllers shouldn't change type between updates, likely programmer error"
);
}
}
}
fn is_gamepad(&self) -> bool {
matches!(self, Reading::Gamepad(_))
}
}
#[derive(Debug)]
pub struct Gamepad {
id: u32,
name: String,
uuid: Uuid,
is_connected: bool,
/// This is the generic controller handle without any mappings
/// https://learn.microsoft.com/en-us/uwp/api/windows.gaming.input.rawgamecontroller
raw_game_controller: RawGameController,
/// An ID for this device that will survive disconnects and restarts.
/// [NonRoamableIds](https://learn.microsoft.com/en-us/uwp/api/windows.gaming.input.rawgamecontroller.nonroamableid)
///
/// Changes if plugged into a different port and is not the same between different applications
/// or PCs.
non_roamable_id: HSTRING,
/// If the controller has a [Gamepad](https://learn.microsoft.com/en-us/uwp/api/windows.gaming.input.gamepad?view=winrt-22621)
/// mapping, this is used to access the mapped values.
wgi_gamepad: Option<WgiGamepad>,
axes: Option<Vec<EvCode>>,
buttons: Option<Vec<EvCode>>,
}
impl Gamepad {
fn new(id: u32, raw_game_controller: RawGameController) -> Gamepad {
let is_connected = true;
let non_roamable_id = raw_game_controller.NonRoamableId().unwrap();
// See if we can cast this to a windows definition of a gamepad
let wgi_gamepad = WgiGamepad::FromGameController(&raw_game_controller).ok();
let name = match raw_game_controller.DisplayName() {
Ok(hstring) => hstring.to_string_lossy(),
Err(_) => "unknown".to_string(),
};
let uuid = match wgi_gamepad.is_some() {
true => Uuid::nil(),
false => {
let vendor_id = raw_game_controller.HardwareVendorId().unwrap_or(0).to_be();
let product_id = raw_game_controller.HardwareProductId().unwrap_or(0).to_be();
let version = 0;
// SDL uses the SDL_HARDWARE_BUS_BLUETOOTH bustype for IsWireless devices:
// https://github.com/libsdl-org/SDL/blob/294ccba0a23b37fffef62189423444f93732e565/src/joystick/windows/SDL_windows_gaming_input.c#L335-L338
// In my testing though, it caused my controllers to not find mappings.
// SDL only uses their WGI implementation for UWP apps so I guess it hasn't been
// used enough for people to submit mappings with the different bustype.
let bustype = SDL_HARDWARE_BUS_USB.to_be();
Uuid::from_fields(
bustype,
vendor_id,
0,
&[
(product_id >> 8) as u8,
product_id as u8,
0,
0,
(version >> 8) as u8,
version as u8,
0,
0,
],
)
}
};
let mut gamepad = Gamepad {
id,
name,
uuid,
is_connected,
raw_game_controller,
non_roamable_id,
wgi_gamepad,
axes: None,
buttons: None,
};
if gamepad.wgi_gamepad.is_none() {
gamepad.collect_axes_and_buttons();
}
gamepad
}
pub fn name(&self) -> &str {
&self.name
}
pub fn uuid(&self) -> Uuid {
self.uuid
}
pub fn vendor_id(&self) -> Option<u16> {
self.raw_game_controller.HardwareVendorId().ok()
}
pub fn product_id(&self) -> Option<u16> {
self.raw_game_controller.HardwareProductId().ok()
}
pub fn is_connected(&self) -> bool {
self.is_connected
}
pub fn power_info(&self) -> PowerInfo {
self.power_info_err().unwrap_or(PowerInfo::Unknown)
}
/// Using this function so we can easily map errors to unknown
fn power_info_err(&self) -> windows::core::Result<PowerInfo> {
if !self.raw_game_controller.IsWireless()? {
return Ok(PowerInfo::Wired);
}
let report: BatteryReport = self.raw_game_controller.TryGetBatteryReport()?;
let status: BatteryStatus = report.Status()?;
let power_info = match status {
BatteryStatus::Discharging | BatteryStatus::Charging => {
let full = report.FullChargeCapacityInMilliwattHours()?.GetInt32()? as f32;
let remaining = report.RemainingCapacityInMilliwattHours()?.GetInt32()? as f32;
let percent: u8 = ((remaining / full) * 100.0) as u8;
match status {
_ if percent == 100 => PowerInfo::Charged,
BatteryStatus::Discharging => PowerInfo::Discharging(percent),
BatteryStatus::Charging => PowerInfo::Charging(percent),
_ => unreachable!(),
}
}
BatteryStatus::NotPresent => PowerInfo::Wired,
BatteryStatus::Idle => PowerInfo::Charged,
BatteryStatus(_) => PowerInfo::Unknown,
};
Ok(power_info)
}
pub fn is_ff_supported(&self) -> bool {
self.wgi_gamepad.is_some()
&& self
.raw_game_controller
.ForceFeedbackMotors()
.ok()
.map(|motors| motors.First())
.is_some()
}
pub fn ff_device(&self) -> Option<FfDevice> {
Some(FfDevice::new(self.id, self.wgi_gamepad.clone()))
}
pub fn buttons(&self) -> &[EvCode] {
match &self.buttons {
None => &native_ev_codes::BUTTONS,
Some(buttons) => buttons,
}
}
pub fn axes(&self) -> &[EvCode] {
match &self.axes {
None => &native_ev_codes::AXES,
Some(axes) => axes,
}
}
pub(crate) fn axis_info(&self, nec: EvCode) -> Option<&AxisInfo> {
// If it isn't a Windows "Gamepad" then return what we want SDL mappings to be able to use
if self.wgi_gamepad.is_none() {
return match nec.kind {
EvCodeKind::Button => None,
EvCodeKind::Axis => Some(&AxisInfo {
min: i16::MIN as i32,
max: i16::MAX as i32,
deadzone: None,
}),
EvCodeKind::Switch => Some(&AxisInfo {
min: -1,
max: 1,
deadzone: None,
}),
};
}
// For Windows Gamepads, the triggers are 0.0 to 1.0 and the thumbsticks are -1.0 to 1.0
// https://learn.microsoft.com/en-us/uwp/api/windows.gaming.input.gamepadreading#fields
// Since Gilrs processes axis data as integers, the input has already been multiplied by
// i32::MAX in the joy_value method.
match nec {
native_ev_codes::AXIS_LT2 | native_ev_codes::AXIS_RT2 => Some(&AxisInfo {
min: 0,
max: i32::MAX,
deadzone: None,
}),
_ => Some(&AxisInfo {
min: i32::MIN,
max: i32::MAX,
deadzone: None,
}),
}
}
fn collect_axes_and_buttons(&mut self) {
let axis_count = self.raw_game_controller.AxisCount().unwrap() as u32;
let button_count = self.raw_game_controller.ButtonCount().unwrap() as u32;
let switch_count = self.raw_game_controller.SwitchCount().unwrap() as u32;
self.buttons = Some(
(0..button_count)
.map(|index| EvCode {
kind: EvCodeKind::Button,
index,
})
.collect(),
);
self.axes = Some(
(0..axis_count)
.map(|index| EvCode {
kind: EvCodeKind::Axis,
index,
})
.chain(
// Treat switches as two axes
(0..switch_count).flat_map(|index| {
[
EvCode {
kind: EvCodeKind::Switch,
index: index * 2,
},
EvCode {
kind: EvCodeKind::Switch,
index: (index * 2) + 1,
},
]
}),
)
.collect(),
);
}
}
#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
enum EvCodeKind {
Button = 0,
Axis,
Switch,
}
impl Display for EvCodeKind {
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
match self {
EvCodeKind::Button => "Button",
EvCodeKind::Axis => "Axis",
EvCodeKind::Switch => "Switch",
}
.fmt(f)
}
}
#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub struct EvCode {
kind: EvCodeKind,
index: u32,
}
impl EvCode {
pub fn into_u32(self) -> u32 {
((self.kind as u32) << 16) | self.index
}
}
impl Display for EvCode {
fn fmt(&self, f: &mut Formatter) -> FmtResult {
write!(f, "{}({})", self.kind, self.index)
}
}
pub mod native_ev_codes {
use super::{EvCode, EvCodeKind};
pub const AXIS_LSTICKX: EvCode = EvCode {
kind: EvCodeKind::Axis,
index: 0,
};
pub const AXIS_LSTICKY: EvCode = EvCode {
kind: EvCodeKind::Axis,
index: 1,
};
pub const AXIS_RSTICKX: EvCode = EvCode {
kind: EvCodeKind::Axis,
index: 2,
};
pub const AXIS_LT2: EvCode = EvCode {
kind: EvCodeKind::Axis,
index: 3,
};
pub const AXIS_RT2: EvCode = EvCode {
kind: EvCodeKind::Axis,
index: 4,
};
pub const AXIS_RSTICKY: EvCode = EvCode {
kind: EvCodeKind::Axis,
index: 5,
};
pub const AXIS_RT: EvCode = EvCode {
kind: EvCodeKind::Axis,
index: 6,
};
pub const AXIS_LT: EvCode = EvCode {
kind: EvCodeKind::Axis,
index: 7,
};
pub const AXIS_LEFTZ: EvCode = EvCode {
kind: EvCodeKind::Axis,
index: 8,
};
pub const AXIS_RIGHTZ: EvCode = EvCode {
kind: EvCodeKind::Axis,
index: 9,
};
pub const AXIS_DPADX: EvCode = EvCode {
kind: EvCodeKind::Switch,
index: 0,
};
pub const AXIS_DPADY: EvCode = EvCode {
kind: EvCodeKind::Switch,
index: 1,
};
pub const BTN_WEST: EvCode = EvCode {
kind: EvCodeKind::Button,
index: 0,
};
pub const BTN_SOUTH: EvCode = EvCode {
kind: EvCodeKind::Button,
index: 1,
};
pub const BTN_EAST: EvCode = EvCode {
kind: EvCodeKind::Button,
index: 2,
};
pub const BTN_NORTH: EvCode = EvCode {
kind: EvCodeKind::Button,
index: 3,
};
pub const BTN_LT: EvCode = EvCode {
kind: EvCodeKind::Button,
index: 4,
};
pub const BTN_RT: EvCode = EvCode {
kind: EvCodeKind::Button,
index: 5,
};
pub const BTN_LT2: EvCode = EvCode {
kind: EvCodeKind::Button,
index: 6,
};
pub const BTN_RT2: EvCode = EvCode {
kind: EvCodeKind::Button,
index: 7,
};
pub const BTN_SELECT: EvCode = EvCode {
kind: EvCodeKind::Button,
index: 8,
};
pub const BTN_START: EvCode = EvCode {
kind: EvCodeKind::Button,
index: 9,
};
pub const BTN_LTHUMB: EvCode = EvCode {
kind: EvCodeKind::Button,
index: 10,
};
pub const BTN_RTHUMB: EvCode = EvCode {
kind: EvCodeKind::Button,
index: 11,
};
pub const BTN_MODE: EvCode = EvCode {
kind: EvCodeKind::Button,
index: 12,
};
pub const BTN_C: EvCode = EvCode {
kind: EvCodeKind::Button,
index: 13,
};
pub const BTN_Z: EvCode = EvCode {
kind: EvCodeKind::Button,
index: 14,
};
// The DPad for DS4 controllers is a hat/switch that gets mapped to the DPad native event
// code buttons. These "buttons" don't exist on the DS4 controller, so it doesn't matter
// what the index is, but if it overlaps with an existing button it will send the event
// for the overlapping button as a dpad button instead of unknown.
// By using a large index it should avoid this.
pub const BTN_DPAD_UP: EvCode = EvCode {
kind: EvCodeKind::Button,
index: u32::MAX - 3,
};
pub const BTN_DPAD_RIGHT: EvCode = EvCode {
kind: EvCodeKind::Button,
index: u32::MAX - 2,
};
pub const BTN_DPAD_DOWN: EvCode = EvCode {
kind: EvCodeKind::Button,
index: u32::MAX - 1,
};
pub const BTN_DPAD_LEFT: EvCode = EvCode {
kind: EvCodeKind::Button,
index: u32::MAX,
};
pub(super) static BUTTONS: [EvCode; 14] = [
BTN_WEST,
BTN_SOUTH,
BTN_EAST,
BTN_NORTH,
BTN_LT,
BTN_RT,
BTN_SELECT,
BTN_START,
BTN_LTHUMB,
BTN_RTHUMB,
BTN_DPAD_UP,
BTN_DPAD_RIGHT,
BTN_DPAD_DOWN,
BTN_DPAD_LEFT,
];
pub(super) static AXES: [EvCode; 6] = [
AXIS_LSTICKX,
AXIS_LSTICKY,
AXIS_RSTICKX,
AXIS_LT2,
AXIS_RT2,
AXIS_RSTICKY,
];
}

View File

@@ -0,0 +1,13 @@
// Copyright 2016-2018 Mateusz Sieczko and other GilRs Developers
//
// Licensed under the Apache License, Version 2.0, <LICENSE-APACHE or
// http://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
// http://opensource.org/licenses/MIT>, at your option. This file may not be
// copied, modified, or distributed except according to those terms.
mod ff;
mod gamepad;
pub use self::ff::Device as FfDevice;
pub use self::gamepad::{native_ev_codes, EvCode, Gamepad, Gilrs};
pub const IS_Y_AXIS_REVERSED: bool = true;

View File

@@ -0,0 +1,40 @@
// Copyright 2016-2018 Mateusz Sieczko and other GilRs Developers
//
// Licensed under the Apache License, Version 2.0, <LICENSE-APACHE or
// http://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
// http://opensource.org/licenses/MIT>, at your option. This file may not be
// copied, modified, or distributed except according to those terms.
use rusty_xinput::{self, XInputHandle, XInputUsageError};
use std::sync::Arc;
use std::time::Duration;
#[derive(Debug)]
pub struct Device {
id: u32,
xinput_handle: Arc<XInputHandle>,
}
impl Device {
pub(crate) fn new(id: u32, xinput_handle: Arc<XInputHandle>) -> Self {
Device { id, xinput_handle }
}
pub fn set_ff_state(&mut self, strong: u16, weak: u16, _min_duration: Duration) {
match self.xinput_handle.set_state(self.id, strong, weak) {
Ok(()) => (),
Err(XInputUsageError::DeviceNotConnected) => {
error!(
"Failed to change FF state gamepad with id {} is no longer connected.",
self.id
);
}
Err(err) => {
error!(
"Failed to change FF state unknown error. ID = {}, error = {:?}.",
self.id, err
);
}
}
}
}

View File

@@ -0,0 +1,641 @@
// Copyright 2016-2018 Mateusz Sieczko and other GilRs Developers
//
// Licensed under the Apache License, Version 2.0, <LICENSE-APACHE or
// http://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
// http://opensource.org/licenses/MIT>, at your option. This file may not be
// copied, modified, or distributed except according to those terms.
use super::FfDevice;
use crate::{AxisInfo, Event, EventType, PlatformError, PowerInfo};
use std::error::Error as StdError;
use std::fmt::{Display, Formatter, Result as FmtResult};
use std::sync::{
mpsc::{self, Receiver, Sender},
Arc,
};
use std::time::Duration;
use std::{mem, thread};
use rusty_xinput::{
BatteryLevel, BatteryType, XInputHandle, XInputLoadingFailure, XInputState, XInputUsageError,
};
use uuid::Uuid;
use winapi::um::xinput::{
XINPUT_GAMEPAD as XGamepad, XINPUT_GAMEPAD_A, XINPUT_GAMEPAD_B, XINPUT_GAMEPAD_BACK,
XINPUT_GAMEPAD_DPAD_DOWN, XINPUT_GAMEPAD_DPAD_LEFT, XINPUT_GAMEPAD_DPAD_RIGHT,
XINPUT_GAMEPAD_DPAD_UP, XINPUT_GAMEPAD_LEFT_SHOULDER, XINPUT_GAMEPAD_LEFT_THUMB,
XINPUT_GAMEPAD_RIGHT_SHOULDER, XINPUT_GAMEPAD_RIGHT_THUMB, XINPUT_GAMEPAD_START,
XINPUT_GAMEPAD_X, XINPUT_GAMEPAD_Y, XINPUT_STATE as XState,
};
// Chosen by dice roll ;)
const EVENT_THREAD_SLEEP_TIME: u64 = 10;
const ITERATIONS_TO_CHECK_IF_CONNECTED: u64 = 100;
const MAX_XINPUT_CONTROLLERS: usize = 4;
#[derive(Debug)]
pub struct Gilrs {
gamepads: [Gamepad; MAX_XINPUT_CONTROLLERS],
rx: Receiver<Event>,
}
impl Gilrs {
pub(crate) fn new() -> Result<Self, PlatformError> {
let xinput_handle = XInputHandle::load_default()
.map_err(|e| PlatformError::Other(Box::new(Error::FailedToLoadDll(e))))?;
let xinput_handle = Arc::new(xinput_handle);
let gamepad_ids: [usize; MAX_XINPUT_CONTROLLERS] = std::array::from_fn(|idx| idx);
// Map controller IDs to Gamepads
let gamepads = gamepad_ids.map(|id| Gamepad::new(id as u32, xinput_handle.clone()));
let mut connected: [bool; MAX_XINPUT_CONTROLLERS] = Default::default();
// Iterate through each controller ID and set connected state
for id in 0..MAX_XINPUT_CONTROLLERS {
connected[id] = gamepads[id].is_connected;
}
let (tx, rx) = mpsc::channel();
Self::spawn_thread(tx, connected, xinput_handle.clone());
// Coerce gamepads vector to slice
Ok(Gilrs { gamepads, rx })
}
pub(crate) fn next_event(&mut self) -> Option<Event> {
let ev = self.rx.try_recv().ok();
self.handle_evevnt(ev);
ev
}
pub(crate) fn next_event_blocking(&mut self, timeout: Option<Duration>) -> Option<Event> {
let ev = if let Some(tiemout) = timeout {
self.rx.recv_timeout(tiemout).ok()
} else {
self.rx.recv().ok()
};
self.handle_evevnt(ev);
ev
}
fn handle_evevnt(&mut self, ev: Option<Event>) {
if let Some(ev) = ev {
match ev.event {
EventType::Connected => self.gamepads[ev.id].is_connected = true,
EventType::Disconnected => self.gamepads[ev.id].is_connected = false,
_ => (),
}
}
}
pub fn gamepad(&self, id: usize) -> Option<&Gamepad> {
self.gamepads.get(id)
}
pub fn last_gamepad_hint(&self) -> usize {
self.gamepads.len()
}
fn spawn_thread(
tx: Sender<Event>,
connected: [bool; MAX_XINPUT_CONTROLLERS],
xinput_handle: Arc<XInputHandle>,
) {
std::thread::Builder::new()
.name("gilrs".to_owned())
.spawn(move || unsafe {
// Issue #70 fix - Maintain a prev_state per controller id. Otherwise the loop will compare the prev_state of a different controller.
let mut prev_states: [XState; MAX_XINPUT_CONTROLLERS] =
[mem::zeroed::<XState>(); MAX_XINPUT_CONTROLLERS];
let mut connected = connected;
let mut counter = 0;
loop {
for id in 0..MAX_XINPUT_CONTROLLERS {
if *connected.get_unchecked(id)
|| counter % ITERATIONS_TO_CHECK_IF_CONNECTED == 0
{
match xinput_handle.get_state(id as u32) {
Ok(XInputState { raw: state }) => {
if !connected[id] {
connected[id] = true;
let _ = tx.send(Event::new(id, EventType::Connected));
}
if state.dwPacketNumber != prev_states[id].dwPacketNumber {
Self::compare_state(
id,
&state.Gamepad,
&prev_states[id].Gamepad,
&tx,
);
prev_states[id] = state;
}
}
Err(XInputUsageError::DeviceNotConnected) if connected[id] => {
connected[id] = false;
let _ = tx.send(Event::new(id, EventType::Disconnected));
}
Err(XInputUsageError::DeviceNotConnected) => (),
Err(e) => error!("Failed to get gamepad state: {:?}", e),
}
}
}
counter = counter.wrapping_add(1);
thread::sleep(Duration::from_millis(EVENT_THREAD_SLEEP_TIME));
}
})
.expect("failed to spawn thread");
}
fn compare_state(id: usize, g: &XGamepad, pg: &XGamepad, tx: &Sender<Event>) {
if g.bLeftTrigger != pg.bLeftTrigger {
let _ = tx.send(Event::new(
id,
EventType::AxisValueChanged(
g.bLeftTrigger as i32,
crate::native_ev_codes::AXIS_LT2,
),
));
}
if g.bRightTrigger != pg.bRightTrigger {
let _ = tx.send(Event::new(
id,
EventType::AxisValueChanged(
g.bRightTrigger as i32,
crate::native_ev_codes::AXIS_RT2,
),
));
}
if g.sThumbLX != pg.sThumbLX {
let _ = tx.send(Event::new(
id,
EventType::AxisValueChanged(
g.sThumbLX as i32,
crate::native_ev_codes::AXIS_LSTICKX,
),
));
}
if g.sThumbLY != pg.sThumbLY {
let _ = tx.send(Event::new(
id,
EventType::AxisValueChanged(
g.sThumbLY as i32,
crate::native_ev_codes::AXIS_LSTICKY,
),
));
}
if g.sThumbRX != pg.sThumbRX {
let _ = tx.send(Event::new(
id,
EventType::AxisValueChanged(
g.sThumbRX as i32,
crate::native_ev_codes::AXIS_RSTICKX,
),
));
}
if g.sThumbRY != pg.sThumbRY {
let _ = tx.send(Event::new(
id,
EventType::AxisValueChanged(
g.sThumbRY as i32,
crate::native_ev_codes::AXIS_RSTICKY,
),
));
}
if !is_mask_eq(g.wButtons, pg.wButtons, XINPUT_GAMEPAD_DPAD_UP) {
let _ = match g.wButtons & XINPUT_GAMEPAD_DPAD_UP != 0 {
true => tx.send(Event::new(
id,
EventType::ButtonPressed(crate::native_ev_codes::BTN_DPAD_UP),
)),
false => tx.send(Event::new(
id,
EventType::ButtonReleased(crate::native_ev_codes::BTN_DPAD_UP),
)),
};
}
if !is_mask_eq(g.wButtons, pg.wButtons, XINPUT_GAMEPAD_DPAD_DOWN) {
let _ = match g.wButtons & XINPUT_GAMEPAD_DPAD_DOWN != 0 {
true => tx.send(Event::new(
id,
EventType::ButtonPressed(crate::native_ev_codes::BTN_DPAD_DOWN),
)),
false => tx.send(Event::new(
id,
EventType::ButtonReleased(crate::native_ev_codes::BTN_DPAD_DOWN),
)),
};
}
if !is_mask_eq(g.wButtons, pg.wButtons, XINPUT_GAMEPAD_DPAD_LEFT) {
let _ = match g.wButtons & XINPUT_GAMEPAD_DPAD_LEFT != 0 {
true => tx.send(Event::new(
id,
EventType::ButtonPressed(crate::native_ev_codes::BTN_DPAD_LEFT),
)),
false => tx.send(Event::new(
id,
EventType::ButtonReleased(crate::native_ev_codes::BTN_DPAD_LEFT),
)),
};
}
if !is_mask_eq(g.wButtons, pg.wButtons, XINPUT_GAMEPAD_DPAD_RIGHT) {
let _ = match g.wButtons & XINPUT_GAMEPAD_DPAD_RIGHT != 0 {
true => tx.send(Event::new(
id,
EventType::ButtonPressed(crate::native_ev_codes::BTN_DPAD_RIGHT),
)),
false => tx.send(Event::new(
id,
EventType::ButtonReleased(crate::native_ev_codes::BTN_DPAD_RIGHT),
)),
};
}
if !is_mask_eq(g.wButtons, pg.wButtons, XINPUT_GAMEPAD_START) {
let _ = match g.wButtons & XINPUT_GAMEPAD_START != 0 {
true => tx.send(Event::new(
id,
EventType::ButtonPressed(crate::native_ev_codes::BTN_START),
)),
false => tx.send(Event::new(
id,
EventType::ButtonReleased(crate::native_ev_codes::BTN_START),
)),
};
}
if !is_mask_eq(g.wButtons, pg.wButtons, XINPUT_GAMEPAD_BACK) {
let _ = match g.wButtons & XINPUT_GAMEPAD_BACK != 0 {
true => tx.send(Event::new(
id,
EventType::ButtonPressed(crate::native_ev_codes::BTN_SELECT),
)),
false => tx.send(Event::new(
id,
EventType::ButtonReleased(crate::native_ev_codes::BTN_SELECT),
)),
};
}
if !is_mask_eq(g.wButtons, pg.wButtons, XINPUT_GAMEPAD_LEFT_THUMB) {
let _ = match g.wButtons & XINPUT_GAMEPAD_LEFT_THUMB != 0 {
true => tx.send(Event::new(
id,
EventType::ButtonPressed(crate::native_ev_codes::BTN_LTHUMB),
)),
false => tx.send(Event::new(
id,
EventType::ButtonReleased(crate::native_ev_codes::BTN_LTHUMB),
)),
};
}
if !is_mask_eq(g.wButtons, pg.wButtons, XINPUT_GAMEPAD_RIGHT_THUMB) {
let _ = match g.wButtons & XINPUT_GAMEPAD_RIGHT_THUMB != 0 {
true => tx.send(Event::new(
id,
EventType::ButtonPressed(crate::native_ev_codes::BTN_RTHUMB),
)),
false => tx.send(Event::new(
id,
EventType::ButtonReleased(crate::native_ev_codes::BTN_RTHUMB),
)),
};
}
if !is_mask_eq(g.wButtons, pg.wButtons, XINPUT_GAMEPAD_LEFT_SHOULDER) {
let _ = match g.wButtons & XINPUT_GAMEPAD_LEFT_SHOULDER != 0 {
true => tx.send(Event::new(
id,
EventType::ButtonPressed(crate::native_ev_codes::BTN_LT),
)),
false => tx.send(Event::new(
id,
EventType::ButtonReleased(crate::native_ev_codes::BTN_LT),
)),
};
}
if !is_mask_eq(g.wButtons, pg.wButtons, XINPUT_GAMEPAD_RIGHT_SHOULDER) {
let _ = match g.wButtons & XINPUT_GAMEPAD_RIGHT_SHOULDER != 0 {
true => tx.send(Event::new(
id,
EventType::ButtonPressed(crate::native_ev_codes::BTN_RT),
)),
false => tx.send(Event::new(
id,
EventType::ButtonReleased(crate::native_ev_codes::BTN_RT),
)),
};
}
if !is_mask_eq(g.wButtons, pg.wButtons, XINPUT_GAMEPAD_A) {
let _ = match g.wButtons & XINPUT_GAMEPAD_A != 0 {
true => tx.send(Event::new(
id,
EventType::ButtonPressed(crate::native_ev_codes::BTN_SOUTH),
)),
false => tx.send(Event::new(
id,
EventType::ButtonReleased(crate::native_ev_codes::BTN_SOUTH),
)),
};
}
if !is_mask_eq(g.wButtons, pg.wButtons, XINPUT_GAMEPAD_B) {
let _ = match g.wButtons & XINPUT_GAMEPAD_B != 0 {
true => tx.send(Event::new(
id,
EventType::ButtonPressed(crate::native_ev_codes::BTN_EAST),
)),
false => tx.send(Event::new(
id,
EventType::ButtonReleased(crate::native_ev_codes::BTN_EAST),
)),
};
}
if !is_mask_eq(g.wButtons, pg.wButtons, XINPUT_GAMEPAD_X) {
let _ = match g.wButtons & XINPUT_GAMEPAD_X != 0 {
true => tx.send(Event::new(
id,
EventType::ButtonPressed(crate::native_ev_codes::BTN_WEST),
)),
false => tx.send(Event::new(
id,
EventType::ButtonReleased(crate::native_ev_codes::BTN_WEST),
)),
};
}
if !is_mask_eq(g.wButtons, pg.wButtons, XINPUT_GAMEPAD_Y) {
let _ = match g.wButtons & XINPUT_GAMEPAD_Y != 0 {
true => tx.send(Event::new(
id,
EventType::ButtonPressed(crate::native_ev_codes::BTN_NORTH),
)),
false => tx.send(Event::new(
id,
EventType::ButtonReleased(crate::native_ev_codes::BTN_NORTH),
)),
};
}
}
}
#[derive(Debug)]
pub struct Gamepad {
uuid: Uuid,
id: u32,
is_connected: bool,
xinput_handle: Arc<XInputHandle>,
}
impl Gamepad {
fn new(id: u32, xinput_handle: Arc<XInputHandle>) -> Gamepad {
let is_connected = xinput_handle.get_state(id).is_ok();
Gamepad {
uuid: Uuid::nil(),
id,
is_connected,
xinput_handle,
}
}
pub fn name(&self) -> &str {
"Xbox Controller"
}
pub fn uuid(&self) -> Uuid {
self.uuid
}
pub fn vendor_id(&self) -> Option<u16> {
None
}
pub fn product_id(&self) -> Option<u16> {
None
}
pub fn is_connected(&self) -> bool {
self.is_connected
}
pub fn power_info(&self) -> PowerInfo {
match self.xinput_handle.get_gamepad_battery_information(self.id) {
Ok(binfo) => match binfo.battery_type {
BatteryType::WIRED => PowerInfo::Wired,
BatteryType::ALKALINE | BatteryType::NIMH => {
let lvl = match binfo.battery_level {
BatteryLevel::EMPTY => 0,
BatteryLevel::LOW => 33,
BatteryLevel::MEDIUM => 67,
BatteryLevel::FULL => 100,
lvl => {
trace!("Unexpected battery level: {}", lvl.0);
100
}
};
if lvl == 100 {
PowerInfo::Charged
} else {
PowerInfo::Discharging(lvl)
}
}
_ => PowerInfo::Unknown,
},
Err(e) => {
debug!("Failed to get battery info: {:?}", e);
PowerInfo::Unknown
}
}
}
pub fn is_ff_supported(&self) -> bool {
true
}
pub fn ff_device(&self) -> Option<FfDevice> {
Some(FfDevice::new(self.id, self.xinput_handle.clone()))
}
pub fn buttons(&self) -> &[EvCode] {
&native_ev_codes::BUTTONS
}
pub fn axes(&self) -> &[EvCode] {
&native_ev_codes::AXES
}
pub(crate) fn axis_info(&self, nec: EvCode) -> Option<&AxisInfo> {
native_ev_codes::AXES_INFO
.get(nec.0 as usize)
.and_then(|o| o.as_ref())
}
}
#[inline(always)]
fn is_mask_eq(l: u16, r: u16, mask: u16) -> bool {
(l & mask != 0) == (r & mask != 0)
}
#[cfg(feature = "serde-serialize")]
use serde::{Deserialize, Serialize};
#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub struct EvCode(u8);
impl EvCode {
pub fn into_u32(self) -> u32 {
self.0 as u32
}
}
impl Display for EvCode {
fn fmt(&self, f: &mut Formatter) -> FmtResult {
self.0.fmt(f)
}
}
#[derive(Debug)]
enum Error {
FailedToLoadDll(XInputLoadingFailure),
}
impl StdError for Error {}
impl Display for Error {
fn fmt(&self, f: &mut Formatter) -> FmtResult {
match self {
Error::FailedToLoadDll(e) => {
f.write_fmt(format_args!("Failed to load XInput DLL {:?}", e))
}
}
}
}
pub mod native_ev_codes {
use winapi::um::xinput::{
XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE, XINPUT_GAMEPAD_RIGHT_THUMB_DEADZONE,
XINPUT_GAMEPAD_TRIGGER_THRESHOLD,
};
use super::EvCode;
use crate::AxisInfo;
pub const AXIS_LSTICKX: EvCode = EvCode(0);
pub const AXIS_LSTICKY: EvCode = EvCode(1);
pub const AXIS_LEFTZ: EvCode = EvCode(2);
pub const AXIS_RSTICKX: EvCode = EvCode(3);
pub const AXIS_RSTICKY: EvCode = EvCode(4);
pub const AXIS_RIGHTZ: EvCode = EvCode(5);
pub const AXIS_DPADX: EvCode = EvCode(6);
pub const AXIS_DPADY: EvCode = EvCode(7);
pub const AXIS_RT: EvCode = EvCode(8);
pub const AXIS_LT: EvCode = EvCode(9);
pub const AXIS_RT2: EvCode = EvCode(10);
pub const AXIS_LT2: EvCode = EvCode(11);
pub const BTN_SOUTH: EvCode = EvCode(12);
pub const BTN_EAST: EvCode = EvCode(13);
pub const BTN_C: EvCode = EvCode(14);
pub const BTN_NORTH: EvCode = EvCode(15);
pub const BTN_WEST: EvCode = EvCode(16);
pub const BTN_Z: EvCode = EvCode(17);
pub const BTN_LT: EvCode = EvCode(18);
pub const BTN_RT: EvCode = EvCode(19);
pub const BTN_LT2: EvCode = EvCode(20);
pub const BTN_RT2: EvCode = EvCode(21);
pub const BTN_SELECT: EvCode = EvCode(22);
pub const BTN_START: EvCode = EvCode(23);
pub const BTN_MODE: EvCode = EvCode(24);
pub const BTN_LTHUMB: EvCode = EvCode(25);
pub const BTN_RTHUMB: EvCode = EvCode(26);
pub const BTN_DPAD_UP: EvCode = EvCode(27);
pub const BTN_DPAD_DOWN: EvCode = EvCode(28);
pub const BTN_DPAD_LEFT: EvCode = EvCode(29);
pub const BTN_DPAD_RIGHT: EvCode = EvCode(30);
pub(super) static BUTTONS: [EvCode; 15] = [
BTN_SOUTH,
BTN_EAST,
BTN_NORTH,
BTN_WEST,
BTN_LT,
BTN_RT,
BTN_SELECT,
BTN_START,
BTN_MODE,
BTN_LTHUMB,
BTN_RTHUMB,
BTN_DPAD_UP,
BTN_DPAD_DOWN,
BTN_DPAD_LEFT,
BTN_DPAD_RIGHT,
];
pub(super) static AXES: [EvCode; 6] = [
AXIS_LSTICKX,
AXIS_LSTICKY,
AXIS_RSTICKX,
AXIS_RSTICKY,
AXIS_RT2,
AXIS_LT2,
];
pub(super) static AXES_INFO: [Option<AxisInfo>; 12] = [
// LeftStickX
Some(AxisInfo {
min: i16::MIN as i32,
max: i16::MAX as i32,
deadzone: Some(XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE as u32),
}),
// LeftStickY
Some(AxisInfo {
min: i16::MIN as i32,
max: i16::MAX as i32,
deadzone: Some(XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE as u32),
}),
// LeftZ
None,
// RightStickX
Some(AxisInfo {
min: i16::MIN as i32,
max: i16::MAX as i32,
deadzone: Some(XINPUT_GAMEPAD_RIGHT_THUMB_DEADZONE as u32),
}),
// RightStickY
Some(AxisInfo {
min: i16::MIN as i32,
max: i16::MAX as i32,
deadzone: Some(XINPUT_GAMEPAD_RIGHT_THUMB_DEADZONE as u32),
}),
// RightZ
None,
// DPadX
None,
// DPadY
None,
// RightTrigger
None,
// LeftTrigger
None,
// RightTrigger2
Some(AxisInfo {
min: u8::MIN as i32,
max: u8::MAX as i32,
deadzone: Some(XINPUT_GAMEPAD_TRIGGER_THRESHOLD as u32),
}),
// LeftTrigger2
Some(AxisInfo {
min: u8::MIN as i32,
max: u8::MAX as i32,
deadzone: Some(XINPUT_GAMEPAD_TRIGGER_THRESHOLD as u32),
}),
];
}

View File

@@ -0,0 +1,13 @@
// Copyright 2016-2018 Mateusz Sieczko and other GilRs Developers
//
// Licensed under the Apache License, Version 2.0, <LICENSE-APACHE or
// http://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
// http://opensource.org/licenses/MIT>, at your option. This file may not be
// copied, modified, or distributed except according to those terms.
mod ff;
mod gamepad;
pub use self::ff::Device as FfDevice;
pub use self::gamepad::{native_ev_codes, EvCode, Gamepad, Gilrs};
pub const IS_Y_AXIS_REVERSED: bool = false;

21
vendor/gilrs-core/src/utils.rs vendored Normal file
View File

@@ -0,0 +1,21 @@
use std::time::SystemTime;
/// Returns true if nth bit in array is 1.
#[allow(dead_code)]
pub(crate) fn test_bit(n: u16, array: &[u8]) -> bool {
(array[(n / 8) as usize] >> (n % 8)) & 1 != 0
}
#[cfg(not(target_arch = "wasm32"))]
pub fn time_now() -> SystemTime {
SystemTime::now()
}
#[cfg(target_arch = "wasm32")]
pub fn time_now() -> SystemTime {
use js_sys::Date;
use std::time::Duration;
let offset = Duration::from_millis(Date::now() as u64);
SystemTime::UNIX_EPOCH + offset
}