use smelling_salts::{Device as AsyncDevice, Watcher};
use std::collections::HashSet;
use std::convert::TryInto;
use std::fs;
use std::fs::File;
use std::fs::OpenOptions;
use std::io::ErrorKind;
use std::mem::MaybeUninit;
use std::os::raw::{c_char, c_int, c_long, c_uint, c_ulong, c_ushort, c_void};
use std::os::unix::io::{IntoRawFd, RawFd};
use std::task::{Context, Poll};
use crate::Event;
#[repr(C)]
struct InotifyEv {
wd: c_int,
mask: u32,
cookie: u32,
len: u32,
name: [c_char; 256],
}
#[repr(C)]
struct TimeVal {
tv_sec: c_long,
tv_usec: c_long,
}
#[repr(C)]
struct TimeSpec {
tv_sec: c_long,
tv_nsec: c_long,
}
#[repr(C)]
struct ItimerSpec {
it_interval: TimeSpec,
it_value: TimeSpec,
}
#[repr(C)]
struct InputId {
bustype: u16,
vendor: u16,
product: u16,
version: u16,
}
#[repr(C)]
struct EvdevEv {
ev_time: TimeVal,
ev_type: c_ushort,
ev_code: c_ushort,
ev_value: c_uint,
}
#[repr(C)]
struct AbsInfo {
value: i32,
minimum: u32,
maximum: u32,
fuzz: i32,
flat: i32,
resolution: i32,
}
extern "C" {
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 c_char, mask: u32) -> c_int;
fn timerfd_create(clockid: c_int, flags: c_int) -> RawFd;
fn timerfd_settime(
fd: RawFd,
flags: c_int,
new_value: *const ItimerSpec,
old_value: *mut ItimerSpec,
) -> c_int;
fn __errno_location() -> *mut c_int;
}
struct PortTimer {
device: AsyncDevice,
}
impl PortTimer {
fn new(cx: &mut Context<'_>) -> Self {
let timerfd = unsafe {
timerfd_create(
1,
0o4000,
)
};
assert_ne!(timerfd, -1);
unsafe {
timerfd_settime(
timerfd,
0,
&ItimerSpec {
it_interval: TimeSpec {
tv_sec: 0,
tv_nsec: 10_000_000,
},
it_value: TimeSpec {
tv_sec: 0,
tv_nsec: 10_000_000,
},
},
std::ptr::null_mut(),
);
}
let device = AsyncDevice::new(timerfd, Watcher::new().input());
device.register_waker(cx.waker());
PortTimer { device }
}
}
impl Drop for PortTimer {
fn drop(&mut self) {
let fd = self.device.fd();
self.device.old();
assert_ne!(unsafe { close(fd) }, -1);
}
}
pub(crate) struct Port {
device: AsyncDevice,
connected: HashSet<String>,
timer: Option<PortTimer>,
}
impl Port {
pub(super) fn new() -> Self {
let inotify = unsafe {
inotify_init1(0o0004000 )
};
if inotify == -1 {
panic!("Couldn't create inotify (1)!");
}
if unsafe {
inotify_add_watch(
inotify,
b"/dev/input/by-id/\0".as_ptr() as *const _,
0x0000_0200 | 0x0000_0100,
)
} == -1
{
panic!("Couldn't create inotify (2)!");
}
let watcher = Watcher::new().input();
let device = AsyncDevice::new(inotify, watcher);
let connected = HashSet::new();
let timer = None;
Port {
device,
connected,
timer,
}
}
pub(super) fn poll(
&mut self,
cx: &mut Context<'_>,
) -> Poll<(usize, Event)> {
if let Some(ref timer) = self.timer {
let mut num = MaybeUninit::<u64>::uninit();
if unsafe {
read(
timer.device.fd(),
num.as_mut_ptr().cast(),
std::mem::size_of::<u64>(),
)
} == std::mem::size_of::<u64>() as isize
{
if unsafe { num.assume_init() } >= 100 {
self.timer = None;
}
}
}
let mut ev = MaybeUninit::<InotifyEv>::uninit();
let ev = unsafe {
if read(
self.device.fd(),
ev.as_mut_ptr().cast(),
std::mem::size_of::<InotifyEv>(),
) <= 0
{
let mut all_open = true;
'fds: for file in fs::read_dir("/dev/input/by-id/").unwrap() {
let file = file.unwrap().file_name().into_string().unwrap();
if file.ends_with("-event-joystick") {
if self.connected.contains(&file) {
continue 'fds;
}
let mut filename = "/dev/input/by-id/".to_string();
filename.push_str(&file);
let fd = match OpenOptions::new()
.read(true)
.append(true)
.open(filename)
{
Ok(f) => f,
Err(e) => {
if e.kind() == ErrorKind::PermissionDenied {
all_open = false;
}
continue 'fds;
}
};
self.connected.insert(file);
return Poll::Ready((
usize::MAX,
Event::Connect(Box::new(crate::Gamepad(
Gamepad::new(fd),
))),
));
}
}
if all_open && self.timer.is_some() {
self.timer = None;
}
self.device.register_waker(cx.waker());
return Poll::Pending;
}
ev.assume_init()
};
if (ev.mask & 0x0000_0200) != 0 {
let mut file = "".to_string();
let name = unsafe { std::ffi::CStr::from_ptr(ev.name.as_ptr()) };
file.push_str(&name.to_string_lossy());
if file.ends_with("-event-joystick") {
assert!(self.connected.remove(&file));
}
}
if (ev.mask & 0x0000_0100) != 0 && self.timer.is_none() {
self.timer = Some(PortTimer::new(cx));
}
self.poll(cx)
}
}
impl Drop for Port {
fn drop(&mut self) {
let fd = self.device.fd();
self.device.old();
assert_ne!(unsafe { close(fd) }, -1);
}
}
pub(crate) struct Gamepad {
device: AsyncDevice,
hardware_id: u32,
abs_min: c_int,
abs_range: c_int,
queued: Option<Event>,
emulated: u8,
rumble: i16,
}
impl Gamepad {
fn new(file: File) -> Self {
let fd = file.into_raw_fd();
assert_ne!(unsafe { fcntl(fd, 0x4, 0x800) }, -1);
let mut a = MaybeUninit::<InputId>::uninit();
assert_ne!(
unsafe { ioctl(fd, 0x_8008_4502, a.as_mut_ptr().cast()) },
-1
);
let a = unsafe { a.assume_init() };
let hardware_id =
((u32::from(a.vendor)) << 16) | (u32::from(a.product));
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 abs_min = a.minimum as c_int;
let abs_range = a.maximum as c_int - a.minimum as c_int;
let rumble = joystick_haptic(fd, -1, 0.0);
Gamepad {
hardware_id,
abs_min,
abs_range,
queued: None,
device: AsyncDevice::new(fd, Watcher::new().input()),
emulated: 0,
rumble,
}
}
fn to_float(&self, value: u32) -> f32 {
(value as i32) as f32 * 0.005
}
fn apply_mods(&self, mut event: Event) -> Event {
let dm = if self.hardware_id == 0x_07B5_0316 {
2.0
} else {
1.0
};
let s = |x: f32| {
let v = ((200.0 * x) - self.abs_min as f32) / self.abs_range as f32;
let v = (200.0 * v).trunc() / 199.0;
if (v - 0.5).abs() < dm * 0.0625 {
0.5
} else {
v
}
};
match event {
Event::Accept(p) => {
if self.hardware_id == 0x_0E6F_0501
{
event = Event::Cancel(p);
}
}
Event::Cancel(p) => {
if self.hardware_id == 0x_0E6F_0501
{
event = Event::Accept(p);
}
}
Event::Common(p) => {
if self.hardware_id == 0x_054C_0268
{
event = Event::Action(p);
}
}
Event::Action(p) => {
if self.hardware_id == 0x_054C_0268
{
event = Event::Common(p);
}
}
Event::MotionH(v) => {
if self.hardware_id == 0x_0079_1844
{
event =
Event::MotionH((s(v) * 4.0 - 2.0).min(1.0).max(-1.0));
} else {
event =
Event::MotionH((s(v) * 2.0 - 1.0).min(1.0).max(-1.0));
}
}
Event::MotionV(v) => {
if self.hardware_id == 0x_0079_1844
{
event =
Event::MotionV((s(v) * 4.0 - 2.0).min(1.0).max(-1.0));
} else {
event =
Event::MotionV((s(v) * 2.0 - 1.0).min(1.0).max(-1.0));
}
}
Event::CameraH(v) => {
if self.hardware_id == 0x_0079_1844
{
event = Event::Lz((v * 2.0 - 0.5).min(1.0).max(0.0));
} else {
event =
Event::CameraH((s(v) * 2.0 - 1.0).min(1.0).max(-1.0));
}
}
Event::CameraV(v) => {
if self.hardware_id == 0x_0079_1844
{
event = Event::Rz((v * 2.0 - 0.5).min(1.0).max(0.0));
} else {
event =
Event::CameraV((s(v) * 2.0 - 1.0).min(1.0).max(-1.0))
}
}
Event::Lz(v) => {
if self.hardware_id == 0x_0079_1844 {
event =
Event::CameraV((s(v) * 4.0 - 2.0).min(1.0).max(-1.0));
} else if self.hardware_id == 0x_07B5_0316 {
event = Event::Lz((v + 0.5).min(1.0).max(0.0));
} else {
event = Event::Lz(v.min(1.0).max(0.0));
}
}
Event::Rz(v) => {
if self.hardware_id == 0x_0079_1844
{
event =
Event::CameraH((s(v) * 4.0 - 2.0).min(1.0).max(-1.0));
} else {
event = Event::Rz(v.min(1.0).max(0.0))
}
}
_ => {}
}
event
}
pub(super) fn id(&self) -> u32 {
self.hardware_id
}
fn dpad_h(&mut self, value: c_int) -> Option<Event> {
let emulated = self.emulated;
let left = 0b0000_0001;
let right = 0b0000_0010;
Some(if value < 0 {
if emulated & left != 0 {
return None;
}
self.emulated |= left;
if emulated & right != 0 {
self.emulated &= !right;
self.queued = Some(Event::Left(true));
Event::Right(false)
} else {
Event::Left(true)
}
} else if value > 0 {
if emulated & right != 0 {
return None;
}
self.emulated |= right;
if emulated & left != 0 {
self.emulated &= !left;
self.queued = Some(Event::Right(true));
Event::Left(false)
} else {
Event::Right(true)
}
} else {
self.emulated &= !(left | right);
if emulated & left != 0 {
Event::Left(false)
} else if emulated & right != 0 {
Event::Right(false)
} else {
return None;
}
})
}
fn dpad_v(&mut self, value: c_int) -> Option<Event> {
let emulated = self.emulated;
let up = 0b0000_0100;
let down = 0b0000_1000;
Some(if value < 0 {
if emulated & up != 0 {
return None;
}
self.emulated |= up;
if emulated & down != 0 {
self.emulated &= !down;
self.queued = Some(Event::Up(true));
Event::Down(false)
} else {
Event::Up(true)
}
} else if value > 0 {
if emulated & down != 0 {
return None;
}
self.emulated |= down;
if emulated & up != 0 {
self.emulated &= !up;
self.queued = Some(Event::Down(true));
Event::Up(false)
} else {
Event::Down(true)
}
} else {
self.emulated &= !(up | down);
if emulated & up != 0 {
Event::Up(false)
} else if emulated & down != 0 {
Event::Down(false)
} else {
return None;
}
})
}
pub(super) fn poll(&mut self, cx: &mut Context<'_>) -> Poll<Event> {
if let Some(event) = self.queued.take() {
return Poll::Ready(self.apply_mods(event));
}
let mut ev = MaybeUninit::<EvdevEv>::uninit();
let ev = {
let bytes = unsafe {
read(
self.device.fd(),
ev.as_mut_ptr().cast(),
std::mem::size_of::<EvdevEv>(),
)
};
if bytes <= 0 {
let errno = unsafe { *__errno_location() };
if errno == 19 {
return Poll::Ready(Event::Disconnect);
}
assert_eq!(errno, 11);
self.device.register_waker(cx.waker());
return Poll::Pending;
}
assert_eq!(std::mem::size_of::<EvdevEv>() as isize, bytes);
unsafe { ev.assume_init() }
};
let event = match ev.ev_type {
0x01 => {
let is = ev.ev_value == 1;
match ev.ev_code - 0x120 {
0 | 19 => Event::Common(is),
1 | 17 => Event::Accept(is),
2 | 16 => Event::Cancel(is),
3 | 20 => Event::Action(is),
4 | 24 => return self.poll(cx),
5 | 25 => return self.poll(cx),
6 | 22 => Event::L(is),
7 | 23 => Event::R(is),
8 | 26 => Event::Back(is),
9 | 27 => Event::Forward(is),
10 => {
eprintln!(
"Button 10 is Unknown, report at \
https://github.com/libcala/stick/issues"
);
return self.poll(cx);
}
12 | 256 => {
if let Some(ev) = self.dpad_v(if is { -1 } else { 0 }) {
ev
} else {
return self.poll(cx);
}
}
13 | 259 => {
if let Some(ev) = self.dpad_h(if is { 1 } else { 0 }) {
ev
} else {
return self.poll(cx);
}
}
14 | 257 => {
if let Some(ev) = self.dpad_v(if is { 1 } else { 0 }) {
ev
} else {
return self.poll(cx);
}
}
15 | 258 => {
if let Some(ev) = self.dpad_h(if is { -1 } else { 0 }) {
ev
} else {
return self.poll(cx);
}
}
18 => {
eprintln!(
"Button 18 is Unknown, report at \
https://github.com/libcala/stick/issues"
);
return self.poll(cx);
}
21 => {
eprintln!(
"Button 21 is Unknown, report at \
https://github.com/libcala/stick/issues"
);
return self.poll(cx);
}
28 => {
if is {
Event::Quit
} else {
return self.poll(cx);
}
}
29 => Event::MotionButton(is),
30 => Event::CameraButton(is),
a => {
eprintln!(
"Button {} is Unknown, report at \
https://github.com/libcala/stick/issues",
a
);
return self.poll(cx);
}
}
}
0x03 => {
match ev.ev_code {
0 => Event::MotionH(self.to_float(ev.ev_value)),
1 => Event::MotionV(self.to_float(ev.ev_value)),
2 => Event::Lz(self.to_float(ev.ev_value)),
3 => Event::CameraH(self.to_float(ev.ev_value)),
4 => Event::CameraV(self.to_float(ev.ev_value)),
5 => Event::Rz(self.to_float(ev.ev_value)),
16 => {
if let Some(event) = self.dpad_h(ev.ev_value as c_int) {
event
} else {
return self.poll(cx);
}
}
17 => {
if let Some(event) = self.dpad_v(ev.ev_value as c_int) {
event
} else {
return self.poll(cx);
}
}
40 => return self.poll(cx),
a => {
eprintln!(
"Unknown Axis: {}, report at \
https://github.com/libcala/stick/issues",
a
);
return self.poll(cx);
}
}
}
_ => return self.poll(cx),
};
Poll::Ready(self.apply_mods(event))
}
pub(super) fn name(&self) -> String {
let fd = self.device.fd();
let mut a = MaybeUninit::<[c_char; 256]>::uninit();
assert_ne!(
unsafe { ioctl(fd, 0x80FF_4506, a.as_mut_ptr().cast()) },
-1
);
let a = unsafe { a.assume_init() };
let name = unsafe { std::ffi::CStr::from_ptr(a.as_ptr()) };
name.to_string_lossy().to_string()
}
pub(super) fn rumble(&mut self, v: f32) {
if self.rumble >= 0 {
joystick_ff(self.device.fd(), self.rumble, v);
}
}
}
impl Drop for Gamepad {
fn drop(&mut self) {
let fd = self.device.fd();
self.device.old();
assert_ne!(unsafe { close(fd) }, -1);
}
}
#[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, value: f32) {
let is_powered = value != 0.0;
if is_powered {
joystick_haptic(fd, code, value);
}
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: if is_powered { 1 } else { 0 },
};
let play: *const _ = play;
unsafe {
if write(fd, play.cast(), std::mem::size_of::<EvdevEv>())
!= std::mem::size_of::<EvdevEv>() as isize
{
panic!("Write exited with {}", *__errno_location());
}
}
}
fn joystick_haptic(fd: RawFd, id: i16, power: f32) -> i16 {
let a = &mut FfEffect {
stype: 0x51,
id,
direction: 0,
trigger: FfTrigger {
button: 0,
interval: 0,
},
replay: FfReplay {
length: 0,
delay: 0,
},
u: FfUnion {
periodic: FfPeriodicEffect {
waveform: 0x5a,
period: 0,
magnitude: (32767.0 * power) as i16,
offset: 0,
phase: 0,
envelope: FfEnvelope {
attack_length: 0,
attack_level: 0,
fade_length: 0,
fade_level: 0,
},
custom_len: 0,
custom_data: std::ptr::null_mut(),
},
},
};
let b: *mut _ = a;
let rumble = if unsafe { ioctl(fd, 0x40304580, b.cast()) } == -1 {
-1
} else {
a.id
};
rumble
}