use crate::{Event, Remap};
use smelling_salts::{Device, Watcher};
use std::cmp::Ordering;
use std::convert::TryInto;
use std::fs::read_dir;
use std::mem::{size_of, MaybeUninit};
use std::os::raw::{c_char, c_int, c_long, c_uint, c_ulong, c_ushort, c_void};
use std::os::unix::io::RawFd;
use std::task::{Context, Poll};
fn linux_btn_to_stick_event(
pending: &mut Vec<Event>,
btn: c_ushort,
pushed: bool,
) {
pending.push(match btn {
0x08B => Event::Context(pushed),
0x09E => Event::PaddleLeft(pushed),
0x09F => Event::PaddleRight(pushed),
0x120 => Event::Trigger(pushed),
0x121 => Event::ActionM(pushed),
0x122 => Event::Bumper(pushed),
0x123 => Event::ActionR(pushed),
0x124 => Event::ActionL(pushed),
0x125 => Event::Pinky(pushed),
0x126 => Event::Number(1, pushed),
0x127 => Event::Number(2, pushed),
0x128 => Event::Number(3, pushed),
0x129 => Event::Number(4, pushed),
0x12A => Event::Number(5, pushed),
0x12B => Event::Number(6, pushed),
0x12C => Event::Number(7, pushed),
0x12D => Event::Number(8, pushed),
0x12E => Event::Number(9, pushed),
0x12F => Event::Number(10, pushed),
0x130 => Event::ActionA(pushed),
0x131 => Event::ActionB(pushed),
0x132 => Event::ActionC(pushed),
0x133 => Event::ActionV(pushed),
0x134 => Event::ActionH(pushed),
0x135 => Event::ActionD(pushed),
0x136 => Event::BumperL(pushed),
0x137 => Event::BumperR(pushed),
0x138 => Event::TriggerL(f64::from(u8::from(pushed)) * 255.0),
0x139 => Event::TriggerR(f64::from(u8::from(pushed)) * 255.0),
0x13A => Event::MenuL(pushed),
0x13B => Event::MenuR(pushed),
0x13C => Event::Exit(pushed),
0x13D => Event::Joy(pushed),
0x13E => Event::Cam(pushed),
0x13F => Event::PinkyRight(pushed),
0x140 => Event::PinkyLeft(pushed),
0x220 => Event::Up(pushed),
0x221 => Event::Down(pushed),
0x222 => Event::Left(pushed),
0x223 => Event::Right(pushed),
0x2C0 => Event::Number(11, pushed),
0x2C1 => Event::Number(12, pushed),
0x2C2 => Event::Number(13, pushed),
0x2C3 => Event::Number(14, pushed),
0x2C4 => Event::Number(15, pushed),
0x2C5 => Event::Number(16, pushed),
0x2C6 => Event::Number(17, pushed),
0x2C7 => Event::Number(18, pushed),
0x2C8 => Event::Number(19, pushed),
0x2C9 => Event::Number(20, pushed),
0x2CA => Event::Number(21, pushed),
0x2CB => Event::Number(22, pushed),
0x2CC => Event::Number(23, pushed),
0x2CD => Event::Number(24, pushed),
0x2CE => Event::Number(25, pushed),
0x2CF => Event::Number(26, pushed),
0x2D0 => Event::Number(27, pushed),
0x2D1 => Event::Number(28, pushed),
0x2D2 => Event::Number(29, pushed),
0x2D3 => Event::Number(30, pushed),
0x2D4 => Event::Number(31, pushed),
0x2D5 => Event::Number(32, pushed),
0x2D6 => Event::Number(33, pushed),
0x2D7 => Event::Number(34, pushed),
0x2D8 => Event::Number(35, pushed),
0x2D9 => Event::Number(36, pushed),
0x2DA => Event::Number(37, pushed),
0x2DB => Event::Number(38, pushed),
0x2DC => Event::Number(39, pushed),
0x2DD => Event::Number(40, pushed),
0x2DE => Event::Number(41, pushed),
0x2DF => Event::Number(42, pushed),
0x2E0 => Event::Number(43, pushed),
0x2E1 => Event::Number(44, pushed),
0x2E2 => Event::Number(45, pushed),
0x2E3 => Event::Number(46, pushed),
0x2E4 => Event::Number(47, pushed),
0x2E5 => Event::Number(48, pushed),
0x2E6 => Event::Number(49, pushed),
0x2E7 => Event::Number(50, pushed),
_unknown => {
eprintln!("Unknown Linux Button {}", _unknown);
eprintln!("Report at https://github.com/ardaku/stick/issues");
return;
}
})
}
fn linux_rel_to_stick_event(
pending: &mut Vec<Event>,
axis: c_ushort,
value: c_int,
) {
match axis {
0x00 => pending.push(Event::MouseX(value as f64)),
0x01 => pending.push(Event::MouseY(value as f64)),
0x02 => {
eprintln!("FIXME: REL_Z");
eprintln!("Report at https://github.com/ardaku/stick/issues");
}
0x03 => {
eprintln!("FIXME: REL_RX");
eprintln!("Report at https://github.com/ardaku/stick/issues");
}
0x04 => {
eprintln!("FIXME: REL_RY");
eprintln!("Report at https://github.com/ardaku/stick/issues");
}
0x05 => {
eprintln!("FIXME: REL_RZ");
eprintln!("Report at https://github.com/ardaku/stick/issues");
}
0x06 => {
eprintln!("FIXME: REL_HWHEEL");
eprintln!("Report at https://github.com/ardaku/stick/issues");
}
0x07 => {
eprintln!("FIXME: REL_DIAL");
eprintln!("Report at https://github.com/ardaku/stick/issues");
}
0x08 => {
eprintln!("FIXME: REL_WHEEL");
eprintln!("Report at https://github.com/ardaku/stick/issues");
}
0x09 => {
eprintln!("FIXME: REL_MISC");
eprintln!("Report at https://github.com/ardaku/stick/issues");
}
_unknown => {
eprintln!("Unknown Linux Axis {}", _unknown);
eprintln!("Report at https://github.com/ardaku/stick/issues");
}
}
}
fn linux_abs_to_stick_event(
pending: &mut Vec<Event>,
axis: c_ushort,
value: c_int,
) {
match axis {
0x00 => pending.push(Event::JoyX(value as f64)),
0x01 => pending.push(Event::JoyY(value as f64)),
0x02 => pending.push(Event::JoyZ(value as f64)),
0x03 => pending.push(Event::CamX(value as f64)),
0x04 => pending.push(Event::CamY(value as f64)),
0x05 => pending.push(Event::CamZ(value as f64)),
0x06 => pending.push(Event::Throttle(value as f64)),
0x07 => pending.push(Event::Rudder(value as f64)),
0x08 => pending.push(Event::Wheel(value as f64)),
0x09 => pending.push(Event::Gas(value as f64)),
0x0A => pending.push(Event::Brake(value as f64)),
0x0B => pending.push(Event::Slew(value as f64)),
0x0C => pending.push(Event::ThrottleL(value as f64)),
0x0D => pending.push(Event::ThrottleR(value as f64)),
0x0E => pending.push(Event::ScrollX(value as f64)),
0x0F => pending.push(Event::ScrollY(value as f64)),
0x10 => match value.cmp(&0) {
Ordering::Greater => pending.push(Event::PovRight(true)),
Ordering::Less => pending.push(Event::PovLeft(true)),
Ordering::Equal => {
pending.push(Event::PovRight(false));
pending.push(Event::PovLeft(false));
}
},
0x11 => match value.cmp(&0) {
Ordering::Greater => pending.push(Event::PovDown(true)),
Ordering::Less => pending.push(Event::PovUp(true)),
Ordering::Equal => {
pending.push(Event::PovUp(false));
pending.push(Event::PovDown(false));
}
},
0x12 => match value.cmp(&0) {
Ordering::Greater => pending.push(Event::HatRight(true)),
Ordering::Less => pending.push(Event::HatLeft(true)),
Ordering::Equal => {
pending.push(Event::HatRight(false));
pending.push(Event::HatLeft(false));
}
},
0x13 => match value.cmp(&0) {
Ordering::Greater => pending.push(Event::HatDown(true)),
Ordering::Less => pending.push(Event::HatUp(true)),
Ordering::Equal => {
pending.push(Event::HatUp(false));
pending.push(Event::HatDown(false));
}
},
0x14 => match value.cmp(&0) {
Ordering::Greater => pending.push(Event::TrimRight(true)),
Ordering::Less => pending.push(Event::TrimLeft(true)),
Ordering::Equal => {
pending.push(Event::TrimRight(false));
pending.push(Event::TrimLeft(false));
}
},
0x15 => match value.cmp(&0) {
Ordering::Greater => pending.push(Event::TrimDown(true)),
Ordering::Less => pending.push(Event::TrimUp(true)),
Ordering::Equal => {
pending.push(Event::TrimUp(false));
pending.push(Event::TrimDown(false));
}
},
0x16 => match value.cmp(&0) {
Ordering::Greater => pending.push(Event::MicRight(true)),
Ordering::Less => pending.push(Event::MicLeft(true)),
Ordering::Equal => {
pending.push(Event::MicRight(false));
pending.push(Event::MicLeft(false));
}
},
0x17 => match value.cmp(&0) {
Ordering::Greater => pending.push(Event::MicDown(true)),
Ordering::Less => pending.push(Event::MicUp(true)),
Ordering::Equal => {
pending.push(Event::MicUp(false));
pending.push(Event::MicDown(false));
}
},
0x18 => {
eprintln!("Unknown Event: ABS_PRESSURE");
eprintln!("Report at https://github.com/ardaku/stick/issues");
}
0x19 => {
eprintln!("Unknown Event: ABS_DISTANCE");
eprintln!("Report at https://github.com/ardaku/stick/issues");
}
0x1a => {
eprintln!("Unknown Event: ABS_TILT_X");
eprintln!("Report at https://github.com/ardaku/stick/issues");
}
0x1b => {
eprintln!("Unknown Event: ABS_TILT_Y");
eprintln!("Report at https://github.com/ardaku/stick/issues");
}
0x1c => {
eprintln!("Unknown Event: ABS_TOOL_WIDTH");
eprintln!("Report at https://github.com/ardaku/stick/issues");
}
0x20 => {
eprintln!("Unknown Event: ABS_VOLUME");
eprintln!("Report at https://github.com/ardaku/stick/issues");
}
0x28 => {
eprintln!("Unknown Event: ABS_MISC");
eprintln!("Report at https://github.com/ardaku/stick/issues");
}
_unknown => {
eprintln!("Unknown Linux Axis {}", _unknown);
eprintln!("Report at https://github.com/ardaku/stick/issues");
}
}
}
fn linux_evdev_to_stick_event(pending: &mut Vec<Event>, e: EvdevEv) {
match e.ev_type {
0x00 => {}, 0x01 => linux_btn_to_stick_event(pending, e.ev_code, e.ev_value != 0),
0x02 => linux_rel_to_stick_event(pending, e.ev_code, e.ev_value),
0x03 => linux_abs_to_stick_event(pending, e.ev_code, e.ev_value),
0x04 => {
if e.ev_code != 4 { let (code, val) = (e.ev_code, e.ev_value);
eprintln!("Unknown Linux Misc Code: {}, Value: {}", code, val);
eprintln!("Report at https://github.com/ardaku/stick/issues");
}
}
0x15 => {}, _unknown => {
eprintln!("Unknown Linux Event Type: {}", _unknown);
eprintln!("Report at https://github.com/ardaku/stick/issues");
}
}
}
#[repr(C)]
struct InotifyEv {
wd: c_int,
mask: u32,
cookie: u32,
len: u32,
name: [u8; 256],
}
#[repr(C)]
struct TimeVal {
tv_sec: c_long,
tv_usec: c_long,
}
#[repr(C)]
struct EvdevEv {
ev_time: TimeVal,
ev_type: c_ushort,
ev_code: c_ushort,
ev_value: c_int,
}
#[repr(C)]
struct AbsInfo {
value: i32,
minimum: i32,
maximum: i32,
fuzz: i32,
flat: i32,
resolution: i32,
}
extern "C" {
fn strlen(s: *const u8) -> usize;
fn open(pathname: *const u8, flags: c_int) -> c_int;
fn read(fd: RawFd, buf: *mut c_void, count: usize) -> isize;
fn write(fd: RawFd, buf: *const c_void, count: usize) -> isize;
fn close(fd: RawFd) -> c_int;
fn fcntl(fd: RawFd, cmd: c_int, v: c_int) -> c_int;
fn ioctl(fd: RawFd, request: c_ulong, v: *mut c_void) -> c_int;
fn inotify_init1(flags: c_int) -> c_int;
fn inotify_add_watch(fd: RawFd, path: *const u8, mask: u32) -> c_int;
fn __errno_location() -> *mut c_int;
}
#[repr(C)]
struct FfTrigger {
button: u16,
interval: u16,
}
#[repr(C)]
struct FfReplay {
length: u16,
delay: u16,
}
#[repr(C)]
#[derive(Copy, Clone)]
struct FfEnvelope {
attack_length: u16,
attack_level: u16,
fade_length: u16,
fade_level: u16,
}
#[repr(C)]
#[derive(Copy, Clone)]
struct FfConstantEffect {
level: i16,
envelope: FfEnvelope,
}
#[repr(C)]
#[derive(Copy, Clone)]
struct FfRampEffect {
start_level: i16,
end_level: i16,
envelope: FfEnvelope,
}
#[repr(C)]
#[derive(Copy, Clone)]
struct FfPeriodicEffect {
waveform: u16,
period: u16,
magnitude: i16,
offset: i16,
phase: u16,
envelope: FfEnvelope,
custom_len: u32,
custom_data: *mut i16,
}
#[repr(C)]
#[derive(Copy, Clone)]
struct FfConditionEffect {
right_saturation: u16,
left_saturation: u16,
right_coeff: i16,
left_coeff: i16,
deadband: u16,
center: i16,
}
#[repr(C)]
#[derive(Copy, Clone)]
struct FfRumbleEffect {
strong_magnitude: u16,
weak_magnitude: u16,
}
#[repr(C)]
union FfUnion {
constant: FfConstantEffect, ramp: FfRampEffect,
periodic: FfPeriodicEffect,
condition: [FfConditionEffect; 2],
rumble: FfRumbleEffect, }
#[repr(C)]
struct FfEffect {
stype: u16,
id: i16,
direction: u16,
trigger: FfTrigger,
replay: FfReplay,
u: FfUnion,
}
fn joystick_ff(fd: RawFd, code: i16, strong: f32, weak: f32) {
if strong != 0.0 || weak != 0.0 {
joystick_haptic(fd, code, strong, weak);
}
let ev_code = code.try_into().unwrap();
let play = &EvdevEv {
ev_time: TimeVal {
tv_sec: 0,
tv_usec: 0,
},
ev_type: 0x15,
ev_code,
ev_value: (strong > 0.0 || weak > 0.0) as _,
};
let play: *const _ = play;
unsafe {
if write(fd, play.cast(), size_of::<EvdevEv>())
!= size_of::<EvdevEv>() as isize
{
let errno = *__errno_location();
if errno != 19 && errno != 9 {
panic!("Write exited with {}", *__errno_location());
}
}
}
}
fn joystick_haptic(fd: RawFd, id: i16, strong: f32, weak: f32) -> i16 {
let a = &mut FfEffect {
stype: 0x50,
id,
direction: 0,
trigger: FfTrigger {
button: 0,
interval: 0,
},
replay: FfReplay {
length: 0,
delay: 0,
},
u: FfUnion {
rumble: FfRumbleEffect {
strong_magnitude: (u16::MAX as f32 * strong) as u16,
weak_magnitude: (u16::MAX as f32 * weak) as u16,
},
},
};
let b: *mut _ = a;
if unsafe { ioctl(fd, 0x40304580, b.cast()) } == -1 {
-1
} else {
a.id
}
}
struct Controller {
device: Device,
id: u64,
rumble: i16,
norm: f64,
zero: f64,
flat: f64,
pending_events: Vec<Event>,
name: String,
}
impl Controller {
fn new(fd: c_int) -> Self {
assert_ne!(unsafe { fcntl(fd, 0x4, 0x800) }, -1);
let mut id = MaybeUninit::<u64>::uninit();
assert_ne!(
unsafe { ioctl(fd, 0x_8008_4502, id.as_mut_ptr().cast()) },
-1
);
let id = unsafe { id.assume_init() }.to_be();
let mut a = MaybeUninit::<AbsInfo>::uninit();
assert_ne!(
unsafe { ioctl(fd, 0x_8018_4540, a.as_mut_ptr().cast()) },
-1
);
let a = unsafe { a.assume_init() };
let norm = (a.maximum as f64 - a.minimum as f64) * 0.5;
let zero = a.minimum as f64 + norm;
let norm = norm.recip();
let flat = a.flat as f64 * norm;
let rumble = joystick_haptic(fd, -1, 0.0, 0.0);
let device = Device::new(fd, Watcher::new().input());
let pending_events = Vec::new();
let fd = device.raw();
let mut a = MaybeUninit::<[c_char; 256]>::uninit();
assert_ne!(
unsafe { ioctl(fd, 0x80FF_4506, a.as_mut_ptr().cast()) },
-1
);
let a = unsafe { a.assume_init() };
let name = unsafe { std::ffi::CStr::from_ptr(a.as_ptr()) };
let name = name.to_string_lossy().to_string();
Self {
device,
id,
rumble,
norm,
zero,
flat,
pending_events,
name,
}
}
}
impl super::Controller for Controller {
fn id(&self) -> u64 {
self.id
}
fn poll(&mut self, cx: &mut Context<'_>) -> Poll<Event> {
if let Some(e) = self.pending_events.pop() {
return Poll::Ready(e);
}
if self.device.pending() {
return self.device.sleep(cx);
}
let mut ev = MaybeUninit::<EvdevEv>::uninit();
let ev = {
let bytes = unsafe {
read(
self.device.raw(),
ev.as_mut_ptr().cast(),
size_of::<EvdevEv>(),
)
};
if bytes <= 0 {
let errno = unsafe { *__errno_location() };
if errno == 19 {
return Poll::Ready(Event::Disconnect);
}
assert_eq!(errno, 11);
return self.device.sleep(cx);
}
assert_eq!(size_of::<EvdevEv>() as isize, bytes);
unsafe { ev.assume_init() }
};
linux_evdev_to_stick_event(&mut self.pending_events, ev);
if !ENABLED.load(std::sync::atomic::Ordering::Relaxed) {
self.pending_events.clear();
}
self.poll(cx)
}
fn name(&self) -> &str {
&self.name
}
fn rumble(&mut self, left: f32, right: f32) {
if self.rumble >= 0 {
joystick_ff(self.device.raw(), self.rumble, left, right);
}
}
fn pressure(&self, input: f64) -> f64 {
input * (1.0 / 255.0)
}
fn axis(&self, input: f64) -> f64 {
let input = (input - self.zero) * self.norm;
if input.abs() <= self.flat {
0.0
} else {
input
}
}
}
impl Drop for Controller {
fn drop(&mut self) {
assert_ne!(unsafe { close(self.device.stop()) }, -1);
}
}
struct Listener {
device: Device,
read_dir: Option<Box<std::fs::ReadDir>>,
remap: Remap,
}
impl Listener {
fn new(remap: Remap) -> Self {
const CLOEXEC: c_int = 0o2000000;
const NONBLOCK: c_int = 0o0004000;
const ATTRIB: c_uint = 0x00000004;
const DIR: &[u8] = b"/dev/input/\0";
let listen = unsafe { inotify_init1(NONBLOCK | CLOEXEC) };
if listen == -1 {
panic!("Couldn't create inotify!");
}
if unsafe { inotify_add_watch(listen, DIR.as_ptr(), ATTRIB) } == -1 {
panic!("Couldn't add inotify watch!");
}
Self {
device: Device::new(listen, Watcher::new().input()),
read_dir: Some(Box::new(read_dir("/dev/input/").unwrap())),
remap,
}
}
fn controller(
remap: &Remap,
mut filename: String,
) -> Poll<crate::Controller> {
if filename.contains("event") {
filename.push('\0');
let mut fd = unsafe { open(filename.as_ptr(), 2) };
if fd == -1 {
fd = unsafe { open(filename.as_ptr(), 0) };
}
if fd == -1 {
fd = unsafe { open(filename.as_ptr(), 1) };
}
if fd != -1 {
return Poll::Ready(crate::Controller::new(
Box::new(Controller::new(fd)),
remap,
));
}
}
Poll::Pending
}
}
impl super::Listener for Listener {
fn poll(&mut self, cx: &mut Context<'_>) -> Poll<crate::Controller> {
if let Some(ref mut read_dir) = &mut self.read_dir {
for dir_entry in read_dir.flatten() {
let file = dir_entry.path();
let path = file.as_path().to_string_lossy().to_string();
if let Poll::Ready(controller) =
Self::controller(&self.remap, path)
{
return Poll::Ready(controller);
}
}
self.read_dir = None;
}
let mut ev = MaybeUninit::<InotifyEv>::zeroed();
let read = unsafe {
read(
self.device.raw(),
ev.as_mut_ptr().cast(),
size_of::<InotifyEv>(),
)
};
if read > 0 {
let ev = unsafe { ev.assume_init() };
let len = unsafe { strlen(&ev.name[0]) };
let filename = String::from_utf8_lossy(&ev.name[..len]);
let path = format!("/dev/input/{}", filename);
if let Poll::Ready(controller) = Self::controller(&self.remap, path)
{
return Poll::Ready(controller);
}
}
self.device.sleep(cx)
}
}
impl Drop for Listener {
fn drop(&mut self) {
assert_eq!(unsafe { close(self.device.stop()) }, 0);
}
}
static ENABLED: std::sync::atomic::AtomicBool =
std::sync::atomic::AtomicBool::new(true);
struct Global;
impl super::Global for Global {
fn enable(&self) {
ENABLED.store(true, std::sync::atomic::Ordering::Relaxed);
}
fn disable(&self) {
ENABLED.store(false, std::sync::atomic::Ordering::Relaxed);
}
fn listener(&self, remap: Remap) -> Box<dyn super::Listener> {
Box::new(Listener::new(remap))
}
}
pub(super) fn global() -> Box<dyn super::Global> {
Box::new(Global)
}