From 2e2d6f146871d0e22c85437f0291e0fd8e116037 Mon Sep 17 00:00:00 2001 From: Noah <33094578+coolreader18@users.noreply.github.com> Date: Tue, 23 Feb 2021 14:31:36 -0600 Subject: [PATCH] Use typed ioctl calls + other misc changes (#29) * Use typed ioctl calls * Make enumerate() return an iterator instead of a vec * Fix(?) events() behavior * read() into a buf of mem::MaybeUninit * Add Device.wait_ready() * impl AsRawFd for Device * Add remaining ioctls --- examples/evtest.rs | 14 ++-- src/lib.rs | 170 +++++++++++++++++++++++++-------------------- src/raw.rs | 46 ++++++------ 3 files changed, 120 insertions(+), 110 deletions(-) diff --git a/examples/evtest.rs b/examples/evtest.rs index e458367..8632bca 100644 --- a/examples/evtest.rs +++ b/examples/evtest.rs @@ -1,16 +1,13 @@ // Similar to the evtest tool. -extern crate evdev; - use std::io::prelude::*; fn main() { let mut args = std::env::args_os(); - let mut d; - if args.len() > 1 { - d = evdev::Device::open(&args.nth(1).unwrap()).unwrap(); + let mut d = if args.len() > 1 { + evdev::Device::open(&args.nth(1).unwrap()).unwrap() } else { - let mut devices = evdev::enumerate(); + let mut devices = evdev::enumerate().collect::>(); for (i, d) in devices.iter().enumerate() { println!("{}: {:?}", i, d.name()); } @@ -18,13 +15,14 @@ fn main() { let _ = std::io::stdout().flush(); let mut chosen = String::new(); std::io::stdin().read_line(&mut chosen).unwrap(); - d = devices.swap_remove(chosen.trim().parse::().unwrap()); - } + devices.swap_remove(chosen.trim().parse::().unwrap()) + }; println!("{}", d); println!("Events:"); loop { for ev in d.events_no_sync().unwrap() { println!("{:?}", ev); } + d.wait_ready().unwrap(); } } diff --git a/src/lib.rs b/src/lib.rs index 0e530b1..e86110d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -41,7 +41,7 @@ mod scancodes; use fixedbitset::FixedBitSet; use std::fs::File; use std::fs::OpenOptions; -use std::mem::size_of; +use std::mem; use std::os::unix::{ fs::OpenOptionsExt, io::{AsRawFd, RawFd}, @@ -187,6 +187,12 @@ pub struct Device { state: DeviceState, } +impl AsRawFd for Device { + fn as_raw_fd(&self) -> RawFd { + self.file.as_raw_fd() + } +} + const fn bus_name(x: u16) -> &'static str { match x { 0x1 => "PCI", @@ -320,10 +326,6 @@ impl std::fmt::Display for Device { } impl Device { - pub fn fd(&self) -> RawFd { - self.file.as_raw_fd() - } - pub fn events_supported(&self) -> Types { self.ty } @@ -388,7 +390,12 @@ impl Device { &self.state } - pub fn open(path: &dyn AsRef) -> Result { + #[inline(always)] + pub fn open(path: impl AsRef) -> Result { + Self::_open(path.as_ref()) + } + + fn _open(path: &Path) -> Result { let mut options = OpenOptions::new(); // Try to load read/write, then fall back to read-only. @@ -399,14 +406,9 @@ impl Device { .open(path) .or_else(|_| options.write(false).open(path))?; - let mut bits: u32 = 0; - let mut bits64: u64 = 0; - - unsafe { - let (_, bits_as_u8_slice, _) = std::slice::from_mut(&mut bits).align_to_mut(); - eviocgbit(file.as_raw_fd(), 0, bits_as_u8_slice)?; - } - let ty = Types::from_bits(bits).expect("evdev: unexpected type bits! report a bug"); + let mut ty = 0; + unsafe { eviocgbit_type(file.as_raw_fd(), &mut ty)? }; + let ty = Types::from_bits(ty).expect("evdev: unexpected type bits! report a bug"); let name = ioctl_get_cstring(eviocgname, file.as_raw_fd()); let phys = ioctl_get_cstring(eviocgphys, file.as_raw_fd()); @@ -427,11 +429,11 @@ impl Device { (driver_version & 0xff) as u8, ); + let mut props = 0; unsafe { - let (_, bits_as_u8_slice, _) = std::slice::from_mut(&mut bits).align_to_mut(); - eviocgprop(file.as_raw_fd(), bits_as_u8_slice)?; + eviocgprop(file.as_raw_fd(), &mut props)?; } // FIXME: handle old kernel - let props = Props::from_bits(bits).expect("evdev: unexpected prop bits! report a bug"); + let props = Props::from_bits(props).expect("evdev: unexpected prop bits! report a bug"); let mut state = DeviceState::default(); @@ -442,11 +444,7 @@ impl Device { unsafe { let (_, supported_keys_as_u8_slice, _) = key_slice.align_to_mut(); debug_assert!(supported_keys_as_u8_slice.len() == Key::MAX / 8); - eviocgbit( - file.as_raw_fd(), - Types::KEY.number(), - supported_keys_as_u8_slice, - )?; + eviocgbit_key(file.as_raw_fd(), supported_keys_as_u8_slice)?; } let key_vals = FixedBitSet::with_capacity(Key::MAX); debug_assert!(key_vals.len() % 8 == 0); @@ -458,62 +456,48 @@ impl Device { }; let supported_relative = if ty.contains(Types::RELATIVE) { - unsafe { - let (_, bits_as_u8_slice, _) = std::slice::from_mut(&mut bits).align_to_mut(); - eviocgbit(file.as_raw_fd(), Types::RELATIVE.number(), bits_as_u8_slice)?; - } - Some(RelativeAxis::from_bits(bits).expect("evdev: unexpected rel bits! report a bug")) + let mut rel = 0; + unsafe { eviocgbit_relative(file.as_raw_fd(), &mut rel)? }; + Some(RelativeAxis::from_bits(rel).expect("evdev: unexpected rel bits! report a bug")) } else { None }; let supported_absolute = if ty.contains(Types::ABSOLUTE) { - unsafe { - let (_, bits64_as_u8_slice, _) = std::slice::from_mut(&mut bits64).align_to_mut(); - eviocgbit( - file.as_raw_fd(), - Types::ABSOLUTE.number(), - bits64_as_u8_slice, - )?; - } + let mut abs = 0; + unsafe { eviocgbit_absolute(file.as_raw_fd(), &mut abs)? }; state.abs_vals = Some(vec![input_absinfo_default(); 0x3f]); - Some(AbsoluteAxis::from_bits(bits64).expect("evdev: unexpected abs bits! report a bug")) + Some(AbsoluteAxis::from_bits(abs).expect("evdev: unexpected abs bits! report a bug")) } else { None }; let supported_switch = if ty.contains(Types::SWITCH) { - unsafe { - let (_, bits_as_u8_slice, _) = std::slice::from_mut(&mut bits).align_to_mut(); - eviocgbit(file.as_raw_fd(), Types::SWITCH.number(), bits_as_u8_slice)?; - } + let mut switch = 0; + unsafe { eviocgbit_switch(file.as_raw_fd(), &mut switch)? }; state.switch_vals = Some(FixedBitSet::with_capacity(0x10)); - Some(Switch::from_bits(bits).expect("evdev: unexpected switch bits! report a bug")) + Some(Switch::from_bits(switch).expect("evdev: unexpected switch bits! report a bug")) } else { None }; let supported_led = if ty.contains(Types::LED) { - unsafe { - let (_, bits_as_u8_slice, _) = std::slice::from_mut(&mut bits).align_to_mut(); - eviocgbit(file.as_raw_fd(), Types::LED.number(), bits_as_u8_slice)?; - } + let mut led = 0; + unsafe { eviocgbit_led(file.as_raw_fd(), &mut led)? }; let led_vals = FixedBitSet::with_capacity(0x10); debug_assert!(led_vals.len() % 8 == 0); state.led_vals = Some(led_vals); - Some(Led::from_bits(bits).expect("evdev: unexpected led bits! report a bug")) + Some(Led::from_bits(led).expect("evdev: unexpected led bits! report a bug")) } else { None }; let supported_misc = if ty.contains(Types::MISC) { - unsafe { - let (_, bits_as_u8_slice, _) = std::slice::from_mut(&mut bits).align_to_mut(); - eviocgbit(file.as_raw_fd(), Types::MISC.number(), bits_as_u8_slice)?; - } - Some(Misc::from_bits(bits).expect("evdev: unexpected misc bits! report a bug")) + let mut misc = 0; + unsafe { eviocgbit_misc(file.as_raw_fd(), &mut misc)? }; + Some(Misc::from_bits(misc).expect("evdev: unexpected misc bits! report a bug")) } else { None }; @@ -521,11 +505,9 @@ impl Device { //unsafe { eviocgbit(file.as_raw_fd(), ffs(FORCEFEEDBACK.bits()), 0x7f, bits_as_u8_slice)?; } let supported_snd = if ty.contains(Types::SOUND) { - unsafe { - let (_, bits_as_u8_slice, _) = std::slice::from_mut(&mut bits).align_to_mut(); - eviocgbit(file.as_raw_fd(), Types::SOUND.number(), bits_as_u8_slice)?; - } - Some(Sound::from_bits(bits).expect("evdev: unexpected sound bits! report a bug")) + let mut snd = 0; + unsafe { eviocgbit_sound(file.as_raw_fd(), &mut snd)? }; + Some(Sound::from_bits(snd).expect("evdev: unexpected sound bits! report a bug")) } else { None }; @@ -560,11 +542,12 @@ impl Device { /// /// If there is an error at any point, the state will not be synchronized completely. pub fn sync_state(&mut self) -> Result<(), Error> { + let fd = self.as_raw_fd(); if let Some(key_vals) = &mut self.state.key_vals { unsafe { let key_slice = key_vals.as_mut_slice(); let (_, key_vals_as_u8_slice, _) = key_slice.align_to_mut(); - eviocgkey(self.file.as_raw_fd(), key_vals_as_u8_slice)?; + eviocgkey(fd, key_vals_as_u8_slice)?; } } @@ -579,7 +562,7 @@ impl Device { // the abs data seems to be fine (tested ABS_MT_POSITION_X/Y) if supported_abs.contains(abs) { unsafe { - eviocgabs(self.file.as_raw_fd(), idx as u32, &mut abs_vals[idx])?; + eviocgabs(fd, idx as u32, &mut abs_vals[idx])?; } } } @@ -589,7 +572,7 @@ impl Device { unsafe { let switch_slice = switch_vals.as_mut_slice(); let (_, switch_vals_as_u8_slice, _) = switch_slice.align_to_mut(); - eviocgsw(self.file.as_raw_fd(), switch_vals_as_u8_slice)?; + eviocgsw(fd, switch_vals_as_u8_slice)?; } } @@ -597,7 +580,7 @@ impl Device { unsafe { let led_slice = led_vals.as_mut_slice(); let (_, led_vals_as_u8_slice, _) = led_slice.align_to_mut(); - eviocgled(self.file.as_raw_fd(), led_vals_as_u8_slice)?; + eviocgled(fd, led_vals_as_u8_slice)?; } } @@ -717,18 +700,23 @@ impl Device { } fn fill_events(&mut self) -> Result<(), Error> { + let fd = self.as_raw_fd(); let buf = &mut self.pending_events; loop { buf.reserve(20); - // TODO: use spare_capacity_mut or split_at_spare_mut when they stabilize - let pre_len = buf.len(); - let capacity = buf.capacity(); - let (_, unsafe_buf_slice, _) = - unsafe { buf.get_unchecked_mut(pre_len..capacity).align_to_mut() }; + // TODO: use Vec::spare_capacity_mut or Vec::split_at_spare_mut when they stabilize + let spare_capacity = vec_spare_capacity_mut(buf); + let (_, uninit_buf, _) = + unsafe { spare_capacity.align_to_mut::>() }; - match nix::unistd::read(self.file.as_raw_fd(), unsafe_buf_slice) { + // use libc::read instead of nix::unistd::read b/c we need to pass an uninitialized buf + let res = unsafe { libc::read(fd, uninit_buf.as_mut_ptr() as _, uninit_buf.len()) }; + match nix::errno::Errno::result(res) { Ok(bytes_read) => unsafe { - buf.set_len(pre_len + (bytes_read / size_of::())); + let pre_len = buf.len(); + buf.set_len( + pre_len + (bytes_read as usize / mem::size_of::()), + ); }, Err(e) => { if e == nix::Error::Sys(::nix::errno::Errno::EAGAIN) { @@ -755,7 +743,14 @@ impl Device { self.fill_events()?; self.compensate_dropped()?; - Ok(RawEvents(self)) + Ok(RawEvents::new(self)) + } + + pub fn wait_ready(&self) -> nix::Result<()> { + use nix::poll; + let mut pfd = poll::PollFd::new(self.as_raw_fd(), poll::PollFlags::POLLIN); + poll::poll(std::slice::from_mut(&mut pfd), -1)?; + Ok(()) } } @@ -789,19 +784,28 @@ impl<'a> Iterator for RawEvents<'a> { /// Crawls `/dev/input` for evdev devices. /// /// Will not bubble up any errors in opening devices or traversing the directory. Instead returns -/// an empty vector or omits the devices that could not be opened. -pub fn enumerate() -> Vec { - let mut res = Vec::new(); - if let Ok(dir) = std::fs::read_dir("/dev/input") { - for entry in dir { - if let Ok(entry) = entry { - if let Ok(dev) = Device::open(&entry.path()) { - res.push(dev) +/// an empty iterator or omits the devices that could not be opened. +pub fn enumerate() -> EnumerateDevices { + EnumerateDevices { + readdir: std::fs::read_dir("/dev/input").ok(), + } +} + +pub struct EnumerateDevices { + readdir: Option, +} +impl Iterator for EnumerateDevices { + type Item = Device; + fn next(&mut self) -> Option { + let readdir = self.readdir.as_mut()?; + loop { + if let Ok(entry) = readdir.next()? { + if let Ok(dev) = Device::open(entry.path()) { + return Some(dev); } } } } - res } /// A safe Rust version of clock_gettime against CLOCK_REALTIME @@ -814,6 +818,18 @@ fn into_timeval(time: &SystemTime) -> Result(v: &mut Vec) -> &mut [mem::MaybeUninit] { + let (len, cap) = (v.len(), v.capacity()); + unsafe { + std::slice::from_raw_parts_mut( + v.as_mut_ptr().add(len) as *mut mem::MaybeUninit, + cap - len, + ) + } +} + #[cfg(test)] mod test { use std::mem::MaybeUninit; diff --git a/src/raw.rs b/src/raw.rs index 15ae193..bcb4a26 100644 --- a/src/raw.rs +++ b/src/raw.rs @@ -35,7 +35,7 @@ ioctl_write_ptr!(eviocsrep, b'E', 0x03, [::libc::c_uint; 2]); ioctl_read_buf!(eviocgname, b'E', 0x06, u8); ioctl_read_buf!(eviocgphys, b'E', 0x07, u8); ioctl_read_buf!(eviocguniq, b'E', 0x08, u8); -ioctl_read_buf!(eviocgprop, b'E', 0x09, u8); +ioctl_read!(eviocgprop, b'E', 0x09, u32); ioctl_read_buf!(eviocgmtslots, b'E', 0x0a, u8); ioctl_read_buf!(eviocgkey, b'E', 0x18, u8); ioctl_read_buf!(eviocgled, b'E', 0x19, u8); @@ -47,32 +47,28 @@ ioctl_write_int!(eviocgrab, b'E', 0x90); ioctl_write_int!(eviocrevoke, b'E', 0x91); ioctl_write_int!(eviocsclockid, b'E', 0xa0); -/// ioctl: "get event bits" -/// -/// `ev` should be one of the "Event types" as defined in the Linux kernel headers. -/// In modern (5.11) kernels these are in `include/uapi/linux/input-event-codes.h`, and in older -/// kernels these defines can be found in `include/uapi/linux/input.h` -/// -/// # Panics -/// -/// Calling this with a value greater than the kernel-defined `EV_MAX` (typically 0x1f) will panic. -/// -/// # Safety -/// -/// `ev` must be a valid event number otherwise the behavior is undefined. -pub unsafe fn eviocgbit( - fd: ::libc::c_int, - ev: u32, - buf: &mut [u8], -) -> ::nix::Result { - assert!(ev <= 0x1f); - convert_ioctl_res!(::nix::libc::ioctl( - fd, - request_code_read!(b'E', 0x20 + ev, buf.len()), - buf.as_mut_ptr() - )) +macro_rules! eviocgbit_ioctl { + ($mac:ident!($name:ident, $ev:ident, $ty:ty)) => { + eviocgbit_ioctl!($mac!($name, $crate::Types::$ev.number::(), $ty)); + }; + ($mac:ident!($name:ident, $ev:expr, $ty:ty)) => { + $mac!($name, b'E', 0x20 + $ev, $ty); + }; } +eviocgbit_ioctl!(ioctl_read!(eviocgbit_type, 0, u32)); +eviocgbit_ioctl!(ioctl_read_buf!(eviocgbit_key, KEY, u8)); +eviocgbit_ioctl!(ioctl_read!(eviocgbit_relative, RELATIVE, u32)); +eviocgbit_ioctl!(ioctl_read!(eviocgbit_absolute, ABSOLUTE, u64)); +eviocgbit_ioctl!(ioctl_read!(eviocgbit_misc, MISC, u32)); +eviocgbit_ioctl!(ioctl_read!(eviocgbit_switch, SWITCH, u32)); +eviocgbit_ioctl!(ioctl_read!(eviocgbit_led, LED, u32)); +eviocgbit_ioctl!(ioctl_read!(eviocgbit_sound, SOUND, u32)); +eviocgbit_ioctl!(ioctl_read_buf!(eviocgbit_repeat, REPEAT, u8)); +eviocgbit_ioctl!(ioctl_read_buf!(eviocgbit_ff, FORCEFEEDBACK, u8)); +eviocgbit_ioctl!(ioctl_read_buf!(eviocgbit_power, POWER, u8)); +eviocgbit_ioctl!(ioctl_read_buf!(eviocgbit_ffstatus, FORCEFEEDBACKSTATUS, u8)); + /// ioctl: "get abs value/limits" /// /// `abs` should be one of the "Absolute axes" values defined in the Linux kernel headers.