use super::ff::Device as FfDevice;
use super::ioctl;
use super::ioctl::{input_absinfo, input_event};
use super::udev::*;
use crate::utils;
use crate::{AxisInfo, Event, EventType};
use crate::{PlatformError, PowerInfo};
use libc as c;
use uuid::Uuid;
use vec_map::VecMap;
use std::{error, thread};
use std::ffi::CStr;
use std::fmt::{Display, Formatter, Result as FmtResult};
use std::mem::{self, MaybeUninit};
use std::ops::Index;
use std::os::raw::c_char;
use std::str;
use std::sync::mpsc;
use std::sync::mpsc::{Receiver, Sender};
use std::time::{Duration, SystemTime, UNIX_EPOCH};
#[derive(Debug)]
pub struct Gilrs {
gamepads: Vec<Gamepad>,
event_counter: usize,
hotplug_rx: Receiver<HotplugEvent>,
}
impl Gilrs {
pub(crate) fn new() -> Result<Self, PlatformError> {
let mut gamepads = Vec::new();
let udev = match Udev::new() {
Some(udev) => udev,
None => {
return Err(PlatformError::Other(Box::new(Error::UdevCtx)));
}
};
let en = match udev.enumerate() {
Some(en) => en,
None => {
return Err(PlatformError::Other(Box::new(Error::UdevEnumerate)));
}
};
unsafe { en.add_match_property(cstr_new(b"ID_INPUT_JOYSTICK\0"), cstr_new(b"1\0")) }
en.scan_devices();
for dev in en.iter() {
if let Some(dev) = Device::from_syspath(&udev, &dev) {
if let Some(gamepad) = Gamepad::open(&dev) {
gamepads.push(gamepad);
}
}
}
let (hotplug_tx, hotplug_rx) = mpsc::channel();
thread::spawn(move || {
let udev = match Udev::new() {
Some(udev) => udev,
None => {
error!("Failed to create udev for hot plug thread!");
return;
}
};
let monitor = match Monitor::new(&udev) {
Some(m) => m,
None => {
error!("Failed to create udev monitor for hot plug thread!");
return;
},
};
handle_hotplug(hotplug_tx, monitor)
});
Ok(Gilrs {
gamepads,
event_counter: 0,
hotplug_rx,
})
}
pub(crate) fn next_event(&mut self) -> Option<Event> {
if let Some(event) = self.handle_hotplug() {
return Some(event);
}
loop {
let gamepad = match self.gamepads.get_mut(self.event_counter) {
Some(gp) => gp,
None => {
self.event_counter = 0;
return None;
}
};
if !gamepad.is_connected {
self.event_counter += 1;
continue;
}
match gamepad.event() {
Some((event, time)) => {
return Some(Event {
id: self.event_counter,
event,
time,
});
}
None => {
self.event_counter += 1;
continue;
}
};
}
}
pub fn gamepad(&self, id: usize) -> Option<&Gamepad> {
self.gamepads.get(id)
}
pub fn last_gamepad_hint(&self) -> usize {
self.gamepads.len()
}
fn handle_hotplug(&mut self) -> Option<Event> {
while let Ok(event) = self.hotplug_rx.try_recv() {
match event {
HotplugEvent::New(gamepad) => {
return if let Some(id) = self
.gamepads
.iter()
.position(|gp| gp.uuid() == gamepad.uuid && !gp.is_connected)
{
self.gamepads[id] = *gamepad;
Some(Event::new(id, EventType::Connected))
} else {
self.gamepads.push(*gamepad);
Some(Event::new(self.gamepads.len() - 1, EventType::Connected))
}
}
HotplugEvent::Removed(devpath) => {
if let Some(id) = self
.gamepads
.iter()
.position(|gp| devpath == gp.devpath && gp.is_connected)
{
self.gamepads[id].disconnect();
return Some(Event::new(id, EventType::Disconnected));
} else {
debug!("Could not find disconnected gamepad {devpath:?}");
}
}
}
}
None
}
}
enum HotplugEvent {
New(Box<Gamepad>),
Removed(String),
}
fn handle_hotplug(sender: Sender<HotplugEvent>, monitor: Monitor) {
loop {
if !monitor.wait_hotplug_available() {
continue;
}
let dev = monitor.device();
unsafe {
if let Some(val) = dev.property_value(cstr_new(b"ID_INPUT_JOYSTICK\0")) {
if val != cstr_new(b"1\0") {
continue;
}
} else {
continue;
}
let action = match dev.action() {
Some(a) => a,
None => continue,
};
if action == cstr_new(b"add\0") {
if let Some(gamepad) = Gamepad::open(&dev) {
if sender.send(HotplugEvent::New(Box::new(gamepad))).is_err() {
debug!("All receivers dropped, ending hot plug loop.");
break;
}
}
} else if action == cstr_new(b"remove\0") {
if let Some(devnode) = dev.devnode() {
if let Ok(str) = devnode.to_str() {
if sender.send(HotplugEvent::Removed(str.to_owned())).is_err() {
debug!("All receivers dropped, ending hot plug loop.");
break;
}
} else {
warn!("Received event with devnode that is not valid utf8: {devnode:?}")
}
}
}
}
}
}
#[derive(Debug, Clone)]
struct AxesInfo {
info: VecMap<AxisInfo>,
}
impl AxesInfo {
fn new(fd: i32) -> Self {
let mut map = VecMap::new();
unsafe {
let mut abs_bits = [0u8; (ABS_MAX / 8) as usize + 1];
ioctl::eviocgbit(
fd,
u32::from(EV_ABS),
abs_bits.len() as i32,
abs_bits.as_mut_ptr(),
);
for axis in Gamepad::find_axes(&abs_bits) {
let mut info = input_absinfo::default();
ioctl::eviocgabs(fd, u32::from(axis.code), &mut info);
map.insert(
axis.code as usize,
AxisInfo {
min: info.minimum,
max: info.maximum,
deadzone: Some(info.flat as u32),
},
);
}
}
AxesInfo { info: map }
}
}
impl Index<u16> for AxesInfo {
type Output = AxisInfo;
fn index(&self, i: u16) -> &Self::Output {
&self.info[i as usize]
}
}
#[derive(Debug)]
pub struct Gamepad {
fd: i32,
axes_info: AxesInfo,
ff_supported: bool,
devpath: String,
name: String,
uuid: Uuid,
bt_capacity_fd: i32,
bt_status_fd: i32,
axes_values: VecMap<i32>,
buttons_values: VecMap<bool>,
events: Vec<input_event>,
axes: Vec<EvCode>,
buttons: Vec<EvCode>,
is_connected: bool,
}
impl Gamepad {
fn open(dev: &Device) -> Option<Gamepad> {
let path = match dev.devnode() {
Some(path) => path,
None => return None,
};
if unsafe { !c::strstr(path.as_ptr(), b"js\0".as_ptr() as *const c_char).is_null() } {
trace!("Device {:?} is js interface, ignoring.", path);
return None;
}
let fd = unsafe { c::open(path.as_ptr(), c::O_RDWR | c::O_NONBLOCK) };
if fd < 0 {
error!("Failed to open {:?}", path);
return None;
}
let uuid = match Self::create_uuid(fd) {
Some(uuid) => uuid,
None => {
error!("Failed to get id of device {:?}", path);
unsafe {
c::close(fd);
}
return None;
}
};
let name = Self::get_name(fd).unwrap_or_else(|| {
error!("Failed to get name od device {:?}", path);
"Unknown".into()
});
let axesi = AxesInfo::new(fd);
let ff_supported = Self::test_ff(fd);
let (cap, status) = Self::battery_fd(dev);
let mut gamepad = Gamepad {
fd,
axes_info: axesi,
ff_supported,
devpath: path.to_string_lossy().into_owned(),
name,
uuid,
bt_capacity_fd: cap,
bt_status_fd: status,
axes_values: VecMap::new(),
buttons_values: VecMap::new(),
events: Vec::new(),
axes: Vec::new(),
buttons: Vec::new(),
is_connected: true,
};
gamepad.collect_axes_and_buttons();
if !gamepad.is_gamepad() {
warn!(
"{:?} doesn't have at least 1 button and 2 axes, ignoring.",
path
);
return None;
}
info!("Gamepad {} ({}) connected.", gamepad.devpath, gamepad.name);
debug!(
"Gamepad {}: uuid: {}, ff_supported: {}, axes: {:?}, buttons: {:?}, axes_info: {:?}",
gamepad.devpath,
gamepad.uuid,
gamepad.ff_supported,
gamepad.axes,
gamepad.buttons,
gamepad.axes_info
);
Some(gamepad)
}
fn collect_axes_and_buttons(&mut self) {
let mut key_bits = [0u8; (KEY_MAX / 8) as usize + 1];
let mut abs_bits = [0u8; (ABS_MAX / 8) as usize + 1];
unsafe {
ioctl::eviocgbit(
self.fd,
u32::from(EV_KEY),
key_bits.len() as i32,
key_bits.as_mut_ptr(),
);
ioctl::eviocgbit(
self.fd,
u32::from(EV_ABS),
abs_bits.len() as i32,
abs_bits.as_mut_ptr(),
);
}
self.buttons = Self::find_buttons(&key_bits, false);
self.axes = Self::find_axes(&abs_bits);
}
fn get_name(fd: i32) -> Option<String> {
unsafe {
let mut namebuff: [MaybeUninit<u8>; 128] = MaybeUninit::uninit().assume_init();
if ioctl::eviocgname(fd, &mut namebuff).is_err() {
None
} else {
Some(
CStr::from_ptr(namebuff.as_ptr() as *const c_char)
.to_string_lossy()
.into_owned(),
)
}
}
}
fn test_ff(fd: i32) -> bool {
unsafe {
let mut ff_bits = [0u8; (FF_MAX / 8) as usize + 1];
if ioctl::eviocgbit(
fd,
u32::from(EV_FF),
ff_bits.len() as i32,
ff_bits.as_mut_ptr(),
) >= 0
{
utils::test_bit(FF_SQUARE, &ff_bits)
&& utils::test_bit(FF_TRIANGLE, &ff_bits)
&& utils::test_bit(FF_SINE, &ff_bits)
&& utils::test_bit(FF_GAIN, &ff_bits)
} else {
false
}
}
}
fn is_gamepad(&self) -> bool {
!self.buttons.is_empty() && self.axes.len() >= 2
}
fn create_uuid(fd: i32) -> Option<Uuid> {
let iid = unsafe {
let mut iid = MaybeUninit::<ioctl::input_id>::uninit();
if ioctl::eviocgid(fd, iid.as_mut_ptr()).is_err() {
return None;
}
iid.assume_init()
};
Some(create_uuid(iid))
}
fn find_buttons(key_bits: &[u8], only_gamepad_btns: bool) -> Vec<EvCode> {
let mut buttons = Vec::with_capacity(16);
for bit in BTN_MISC..BTN_MOUSE {
if utils::test_bit(bit, key_bits) {
buttons.push(EvCode::new(EV_KEY, bit));
}
}
for bit in BTN_JOYSTICK..(key_bits.len() as u16 * 8) {
if utils::test_bit(bit, key_bits) {
buttons.push(EvCode::new(EV_KEY, bit));
}
}
if !only_gamepad_btns {
for bit in 0..BTN_MISC {
if utils::test_bit(bit, key_bits) {
buttons.push(EvCode::new(EV_KEY, bit));
}
}
for bit in BTN_MOUSE..BTN_JOYSTICK {
if utils::test_bit(bit, key_bits) {
buttons.push(EvCode::new(EV_KEY, bit));
}
}
}
buttons
}
fn find_axes(abs_bits: &[u8]) -> Vec<EvCode> {
let mut axes = Vec::with_capacity(8);
for bit in 0..(abs_bits.len() * 8) {
if utils::test_bit(bit as u16, abs_bits) {
axes.push(EvCode::new(EV_ABS, bit as u16));
}
}
axes
}
fn battery_fd(dev: &Device) -> (i32, i32) {
use std::ffi::OsStr;
use std::fs::{self, File};
use std::os::unix::ffi::OsStrExt;
use std::os::unix::io::IntoRawFd;
use std::path::Path;
let syspath = Path::new(OsStr::from_bytes(dev.syspath().to_bytes()));
let syspath = syspath.join("device/device/power_supply");
if let Ok(mut read_dir) = fs::read_dir(syspath) {
if let Some(Ok(bat_entry)) = read_dir.next() {
if let Ok(cap) = File::open(bat_entry.path().join("capacity")) {
if let Ok(status) = File::open(bat_entry.path().join("status")) {
return (cap.into_raw_fd(), status.into_raw_fd());
}
}
}
}
(-1, -1)
}
fn event(&mut self) -> Option<(EventType, SystemTime)> {
let mut skip = false;
loop {
let event = match self.next_event() {
Some(e) => e,
None => return None,
};
if skip {
if event.type_ == EV_SYN && event.code == SYN_REPORT {
skip = false;
self.compare_state();
}
continue;
}
let ev = match event.type_ {
EV_SYN if event.code == SYN_DROPPED => {
skip = true;
None
}
EV_KEY => {
self.buttons_values
.insert(event.code as usize, event.value == 1);
match event.value {
0 => Some(EventType::ButtonReleased(event.into())),
1 => Some(EventType::ButtonPressed(event.into())),
_ => None,
}
}
EV_ABS => {
self.axes_values.insert(event.code as usize, event.value);
Some(EventType::AxisValueChanged(event.value, event.into()))
}
_ => {
trace!("Skipping event {:?}", event);
None
}
};
if let Some(ev) = ev {
let dur = Duration::new(event.time.tv_sec as u64, event.time.tv_usec as u32 * 1000);
return Some((ev, UNIX_EPOCH + dur));
}
}
}
fn next_event(&mut self) -> Option<input_event> {
if !self.events.is_empty() {
self.events.pop()
} else {
unsafe {
let mut event_buf: [MaybeUninit<ioctl::input_event>; 12] =
MaybeUninit::uninit().assume_init();
let size = mem::size_of::<ioctl::input_event>();
let n = c::read(
self.fd,
event_buf.as_mut_ptr() as *mut c::c_void,
size * event_buf.len(),
);
if n == -1 || n == 0 {
None
} else if n % size as isize != 0 {
error!("Unexpected read of size {}", n);
None
} else {
let n = n as usize / size;
trace!("Got {} new events", n);
for ev in event_buf[1..n].iter().rev() {
self.events.push(ev.assume_init());
}
Some(event_buf[0].assume_init())
}
}
}
}
fn compare_state(&mut self) {
let mut absinfo = input_absinfo::default();
for axis in self.axes.iter().cloned() {
let value = unsafe {
ioctl::eviocgabs(self.fd, u32::from(axis.code), &mut absinfo);
absinfo.value
};
if self
.axes_values
.get(axis.code as usize)
.cloned()
.unwrap_or(0)
!= value
{
self.events.push(input_event {
type_: EV_ABS,
code: axis.code,
value,
..Default::default()
});
}
}
let mut buf = [0u8; KEY_MAX as usize / 8 + 1];
unsafe {
let _ = ioctl::eviocgkey(self.fd, &mut buf);
}
for btn in self.buttons.iter().cloned() {
let val = utils::test_bit(btn.code, &buf);
if self
.buttons_values
.get(btn.code as usize)
.cloned()
.unwrap_or(false)
!= val
{
self.events.push(input_event {
type_: EV_KEY,
code: btn.code,
value: val as i32,
..Default::default()
});
}
}
}
fn disconnect(&mut self) {
unsafe {
if self.fd >= 0 {
c::close(self.fd);
}
}
self.fd = -2;
self.devpath.clear();
self.is_connected = false;
}
pub fn is_connected(&self) -> bool {
self.is_connected
}
pub fn power_info(&self) -> PowerInfo {
if self.bt_capacity_fd > -1 && self.bt_status_fd > -1 {
unsafe {
let mut buff = [0u8; 15];
c::lseek(self.bt_capacity_fd, 0, c::SEEK_SET);
c::lseek(self.bt_status_fd, 0, c::SEEK_SET);
let len = c::read(
self.bt_capacity_fd,
buff.as_mut_ptr() as *mut c::c_void,
buff.len(),
) as usize;
if len > 0 {
let cap = match str::from_utf8_unchecked(&buff[..(len - 1)]).parse() {
Ok(cap) => cap,
Err(_) => {
error!(
"Failed to parse battery capacity: {}",
str::from_utf8_unchecked(&buff[..(len - 1)])
);
return PowerInfo::Unknown;
}
};
let len = c::read(
self.bt_status_fd,
buff.as_mut_ptr() as *mut c::c_void,
buff.len(),
) as usize;
if len > 0 {
return match str::from_utf8_unchecked(&buff[..(len - 1)]) {
"Charging" => PowerInfo::Charging(cap),
"Discharging" => PowerInfo::Discharging(cap),
"Full" | "Not charging" => PowerInfo::Charged,
s => {
error!("Unknown battery status value: {}", s);
PowerInfo::Unknown
}
};
}
}
}
PowerInfo::Unknown
} else if self.fd > -1 {
PowerInfo::Wired
} else {
PowerInfo::Unknown
}
}
pub fn is_ff_supported(&self) -> bool {
self.ff_supported
}
pub fn name(&self) -> &str {
&self.name
}
pub fn uuid(&self) -> Uuid {
self.uuid
}
pub fn ff_device(&self) -> Option<FfDevice> {
if self.is_ff_supported() {
FfDevice::new(&self.devpath).ok()
} else {
None
}
}
pub fn buttons(&self) -> &[EvCode] {
&self.buttons
}
pub fn axes(&self) -> &[EvCode] {
&self.axes
}
pub(crate) fn axis_info(&self, nec: EvCode) -> Option<&AxisInfo> {
if nec.kind != EV_ABS {
None
} else {
self.axes_info.info.get(nec.code as usize)
}
}
}
impl Drop for Gamepad {
fn drop(&mut self) {
unsafe {
if self.fd >= 0 {
c::close(self.fd);
}
if self.bt_capacity_fd >= 0 {
c::close(self.bt_capacity_fd);
}
if self.bt_status_fd >= 0 {
c::close(self.bt_status_fd);
}
}
}
}
impl PartialEq for Gamepad {
fn eq(&self, other: &Self) -> bool {
self.uuid == other.uuid
}
}
fn create_uuid(iid: ioctl::input_id) -> Uuid {
let bus = (u32::from(iid.bustype)).to_be();
let vendor = iid.vendor.to_be();
let product = iid.product.to_be();
let version = iid.version.to_be();
Uuid::from_fields(
bus,
vendor,
0,
&[
(product >> 8) as u8,
product as u8,
0,
0,
(version >> 8) as u8,
version as u8,
0,
0,
],
)
}
unsafe fn cstr_new(bytes: &[u8]) -> &CStr {
CStr::from_bytes_with_nul_unchecked(bytes)
}
#[cfg(feature = "serde-serialize")]
use serde::{Deserialize, Serialize};
#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
pub struct EvCode {
kind: u16,
code: u16,
}
impl EvCode {
fn new(kind: u16, code: u16) -> Self {
EvCode { kind, code }
}
pub fn into_u32(self) -> u32 {
u32::from(self.kind) << 16 | u32::from(self.code)
}
}
impl From<input_event> for crate::EvCode {
fn from(f: input_event) -> Self {
crate::EvCode(EvCode {
kind: f.type_,
code: f.code,
})
}
}
impl Display for EvCode {
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
match self.kind {
EV_SYN => f.write_str("SYN")?,
EV_KEY => f.write_str("KEY")?,
0x02 => f.write_str("REL")?,
EV_ABS => f.write_str("ABS")?,
0x04 => f.write_str("MSC")?,
0x05 => f.write_str("SW")?,
kind => f.write_fmt(format_args!("EV_TYPE_{}", kind))?,
}
f.write_fmt(format_args!("({})", self.code))
}
}
#[derive(Debug, Copy, Clone)]
#[allow(clippy::enum_variant_names)]
enum Error {
UdevCtx,
UdevEnumerate,
}
impl Display for Error {
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
match *self {
Error::UdevCtx => f.write_str("Failed to create udev context"),
Error::UdevEnumerate => f.write_str("Failed to create udev enumerate object"),
}
}
}
impl error::Error for Error {}
const KEY_MAX: u16 = 0x2ff;
#[allow(dead_code)]
const EV_MAX: u16 = 0x1f;
const EV_SYN: u16 = 0x00;
const EV_KEY: u16 = 0x01;
const EV_ABS: u16 = 0x03;
const ABS_MAX: u16 = 0x3f;
const EV_FF: u16 = 0x15;
const SYN_REPORT: u16 = 0x00;
const SYN_DROPPED: u16 = 0x03;
const BTN_MISC: u16 = 0x100;
const BTN_MOUSE: u16 = 0x110;
const BTN_JOYSTICK: u16 = 0x120;
const BTN_SOUTH: u16 = 0x130;
const BTN_EAST: u16 = 0x131;
#[allow(dead_code)]
const BTN_C: u16 = 0x132;
const BTN_NORTH: u16 = 0x133;
const BTN_WEST: u16 = 0x134;
#[allow(dead_code)]
const BTN_Z: u16 = 0x135;
const BTN_TL: u16 = 0x136;
const BTN_TR: u16 = 0x137;
const BTN_TL2: u16 = 0x138;
const BTN_TR2: u16 = 0x139;
const BTN_SELECT: u16 = 0x13a;
const BTN_START: u16 = 0x13b;
const BTN_MODE: u16 = 0x13c;
const BTN_THUMBL: u16 = 0x13d;
const BTN_THUMBR: u16 = 0x13e;
const BTN_DPAD_UP: u16 = 0x220;
const BTN_DPAD_DOWN: u16 = 0x221;
const BTN_DPAD_LEFT: u16 = 0x222;
const BTN_DPAD_RIGHT: u16 = 0x223;
const ABS_X: u16 = 0x00;
const ABS_Y: u16 = 0x01;
const ABS_Z: u16 = 0x02;
const ABS_RX: u16 = 0x03;
const ABS_RY: u16 = 0x04;
const ABS_RZ: u16 = 0x05;
const ABS_HAT0X: u16 = 0x10;
const ABS_HAT0Y: u16 = 0x11;
const ABS_HAT1X: u16 = 0x12;
const ABS_HAT1Y: u16 = 0x13;
const ABS_HAT2X: u16 = 0x14;
const ABS_HAT2Y: u16 = 0x15;
const FF_MAX: u16 = FF_GAIN;
const FF_SQUARE: u16 = 0x58;
const FF_TRIANGLE: u16 = 0x59;
const FF_SINE: u16 = 0x5a;
const FF_GAIN: u16 = 0x60;
pub mod native_ev_codes {
use super::*;
pub const BTN_SOUTH: EvCode = EvCode {
kind: EV_KEY,
code: super::BTN_SOUTH,
};
pub const BTN_EAST: EvCode = EvCode {
kind: EV_KEY,
code: super::BTN_EAST,
};
pub const BTN_C: EvCode = EvCode {
kind: EV_KEY,
code: super::BTN_C,
};
pub const BTN_NORTH: EvCode = EvCode {
kind: EV_KEY,
code: super::BTN_NORTH,
};
pub const BTN_WEST: EvCode = EvCode {
kind: EV_KEY,
code: super::BTN_WEST,
};
pub const BTN_Z: EvCode = EvCode {
kind: EV_KEY,
code: super::BTN_Z,
};
pub const BTN_LT: EvCode = EvCode {
kind: EV_KEY,
code: super::BTN_TL,
};
pub const BTN_RT: EvCode = EvCode {
kind: EV_KEY,
code: super::BTN_TR,
};
pub const BTN_LT2: EvCode = EvCode {
kind: EV_KEY,
code: super::BTN_TL2,
};
pub const BTN_RT2: EvCode = EvCode {
kind: EV_KEY,
code: super::BTN_TR2,
};
pub const BTN_SELECT: EvCode = EvCode {
kind: EV_KEY,
code: super::BTN_SELECT,
};
pub const BTN_START: EvCode = EvCode {
kind: EV_KEY,
code: super::BTN_START,
};
pub const BTN_MODE: EvCode = EvCode {
kind: EV_KEY,
code: super::BTN_MODE,
};
pub const BTN_LTHUMB: EvCode = EvCode {
kind: EV_KEY,
code: super::BTN_THUMBL,
};
pub const BTN_RTHUMB: EvCode = EvCode {
kind: EV_KEY,
code: super::BTN_THUMBR,
};
pub const BTN_DPAD_UP: EvCode = EvCode {
kind: EV_KEY,
code: super::BTN_DPAD_UP,
};
pub const BTN_DPAD_DOWN: EvCode = EvCode {
kind: EV_KEY,
code: super::BTN_DPAD_DOWN,
};
pub const BTN_DPAD_LEFT: EvCode = EvCode {
kind: EV_KEY,
code: super::BTN_DPAD_LEFT,
};
pub const BTN_DPAD_RIGHT: EvCode = EvCode {
kind: EV_KEY,
code: super::BTN_DPAD_RIGHT,
};
pub const AXIS_LSTICKX: EvCode = EvCode {
kind: EV_ABS,
code: super::ABS_X,
};
pub const AXIS_LSTICKY: EvCode = EvCode {
kind: EV_ABS,
code: super::ABS_Y,
};
pub const AXIS_LEFTZ: EvCode = EvCode {
kind: EV_ABS,
code: super::ABS_Z,
};
pub const AXIS_RSTICKX: EvCode = EvCode {
kind: EV_ABS,
code: super::ABS_RX,
};
pub const AXIS_RSTICKY: EvCode = EvCode {
kind: EV_ABS,
code: super::ABS_RY,
};
pub const AXIS_RIGHTZ: EvCode = EvCode {
kind: EV_ABS,
code: super::ABS_RZ,
};
pub const AXIS_DPADX: EvCode = EvCode {
kind: EV_ABS,
code: super::ABS_HAT0X,
};
pub const AXIS_DPADY: EvCode = EvCode {
kind: EV_ABS,
code: super::ABS_HAT0Y,
};
pub const AXIS_RT: EvCode = EvCode {
kind: EV_ABS,
code: super::ABS_HAT1X,
};
pub const AXIS_LT: EvCode = EvCode {
kind: EV_ABS,
code: super::ABS_HAT1Y,
};
pub const AXIS_RT2: EvCode = EvCode {
kind: EV_ABS,
code: super::ABS_HAT2X,
};
pub const AXIS_LT2: EvCode = EvCode {
kind: EV_ABS,
code: super::ABS_HAT2Y,
};
}
#[cfg(test)]
mod tests {
use super::super::ioctl;
use super::create_uuid;
use uuid::Uuid;
#[test]
fn sdl_uuid() {
let x = Uuid::parse_str("030000005e0400008e02000020200000").unwrap();
let y = create_uuid(ioctl::input_id {
bustype: 0x3,
vendor: 0x045e,
product: 0x028e,
version: 0x2020,
});
assert_eq!(x, y);
}
}