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
This commit is contained in:
Noah 2021-02-23 14:31:36 -06:00 committed by GitHub
parent 73fd0ae421
commit 2e2d6f1468
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 120 additions and 110 deletions

View file

@ -1,16 +1,13 @@
// Similar to the evtest tool. // Similar to the evtest tool.
extern crate evdev;
use std::io::prelude::*; use std::io::prelude::*;
fn main() { fn main() {
let mut args = std::env::args_os(); let mut args = std::env::args_os();
let mut d; let mut d = if args.len() > 1 {
if args.len() > 1 { evdev::Device::open(&args.nth(1).unwrap()).unwrap()
d = evdev::Device::open(&args.nth(1).unwrap()).unwrap();
} else { } else {
let mut devices = evdev::enumerate(); let mut devices = evdev::enumerate().collect::<Vec<_>>();
for (i, d) in devices.iter().enumerate() { for (i, d) in devices.iter().enumerate() {
println!("{}: {:?}", i, d.name()); println!("{}: {:?}", i, d.name());
} }
@ -18,13 +15,14 @@ fn main() {
let _ = std::io::stdout().flush(); let _ = std::io::stdout().flush();
let mut chosen = String::new(); let mut chosen = String::new();
std::io::stdin().read_line(&mut chosen).unwrap(); std::io::stdin().read_line(&mut chosen).unwrap();
d = devices.swap_remove(chosen.trim().parse::<usize>().unwrap()); devices.swap_remove(chosen.trim().parse::<usize>().unwrap())
} };
println!("{}", d); println!("{}", d);
println!("Events:"); println!("Events:");
loop { loop {
for ev in d.events_no_sync().unwrap() { for ev in d.events_no_sync().unwrap() {
println!("{:?}", ev); println!("{:?}", ev);
} }
d.wait_ready().unwrap();
} }
} }

View file

@ -41,7 +41,7 @@ mod scancodes;
use fixedbitset::FixedBitSet; use fixedbitset::FixedBitSet;
use std::fs::File; use std::fs::File;
use std::fs::OpenOptions; use std::fs::OpenOptions;
use std::mem::size_of; use std::mem;
use std::os::unix::{ use std::os::unix::{
fs::OpenOptionsExt, fs::OpenOptionsExt,
io::{AsRawFd, RawFd}, io::{AsRawFd, RawFd},
@ -187,6 +187,12 @@ pub struct Device {
state: DeviceState, 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 { const fn bus_name(x: u16) -> &'static str {
match x { match x {
0x1 => "PCI", 0x1 => "PCI",
@ -320,10 +326,6 @@ impl std::fmt::Display for Device {
} }
impl Device { impl Device {
pub fn fd(&self) -> RawFd {
self.file.as_raw_fd()
}
pub fn events_supported(&self) -> Types { pub fn events_supported(&self) -> Types {
self.ty self.ty
} }
@ -388,7 +390,12 @@ impl Device {
&self.state &self.state
} }
pub fn open(path: &dyn AsRef<Path>) -> Result<Device, Error> { #[inline(always)]
pub fn open(path: impl AsRef<Path>) -> Result<Device, Error> {
Self::_open(path.as_ref())
}
fn _open(path: &Path) -> Result<Device, Error> {
let mut options = OpenOptions::new(); let mut options = OpenOptions::new();
// Try to load read/write, then fall back to read-only. // Try to load read/write, then fall back to read-only.
@ -399,14 +406,9 @@ impl Device {
.open(path) .open(path)
.or_else(|_| options.write(false).open(path))?; .or_else(|_| options.write(false).open(path))?;
let mut bits: u32 = 0; let mut ty = 0;
let mut bits64: u64 = 0; unsafe { eviocgbit_type(file.as_raw_fd(), &mut ty)? };
let ty = Types::from_bits(ty).expect("evdev: unexpected type bits! report a bug");
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 name = ioctl_get_cstring(eviocgname, file.as_raw_fd()); let name = ioctl_get_cstring(eviocgname, file.as_raw_fd());
let phys = ioctl_get_cstring(eviocgphys, 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, (driver_version & 0xff) as u8,
); );
let mut props = 0;
unsafe { unsafe {
let (_, bits_as_u8_slice, _) = std::slice::from_mut(&mut bits).align_to_mut(); eviocgprop(file.as_raw_fd(), &mut props)?;
eviocgprop(file.as_raw_fd(), bits_as_u8_slice)?;
} // FIXME: handle old kernel } // 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(); let mut state = DeviceState::default();
@ -442,11 +444,7 @@ impl Device {
unsafe { unsafe {
let (_, supported_keys_as_u8_slice, _) = key_slice.align_to_mut(); let (_, supported_keys_as_u8_slice, _) = key_slice.align_to_mut();
debug_assert!(supported_keys_as_u8_slice.len() == Key::MAX / 8); debug_assert!(supported_keys_as_u8_slice.len() == Key::MAX / 8);
eviocgbit( eviocgbit_key(file.as_raw_fd(), supported_keys_as_u8_slice)?;
file.as_raw_fd(),
Types::KEY.number(),
supported_keys_as_u8_slice,
)?;
} }
let key_vals = FixedBitSet::with_capacity(Key::MAX); let key_vals = FixedBitSet::with_capacity(Key::MAX);
debug_assert!(key_vals.len() % 8 == 0); debug_assert!(key_vals.len() % 8 == 0);
@ -458,62 +456,48 @@ impl Device {
}; };
let supported_relative = if ty.contains(Types::RELATIVE) { let supported_relative = if ty.contains(Types::RELATIVE) {
unsafe { let mut rel = 0;
let (_, bits_as_u8_slice, _) = std::slice::from_mut(&mut bits).align_to_mut(); unsafe { eviocgbit_relative(file.as_raw_fd(), &mut rel)? };
eviocgbit(file.as_raw_fd(), Types::RELATIVE.number(), bits_as_u8_slice)?; Some(RelativeAxis::from_bits(rel).expect("evdev: unexpected rel bits! report a bug"))
}
Some(RelativeAxis::from_bits(bits).expect("evdev: unexpected rel bits! report a bug"))
} else { } else {
None None
}; };
let supported_absolute = if ty.contains(Types::ABSOLUTE) { let supported_absolute = if ty.contains(Types::ABSOLUTE) {
unsafe { let mut abs = 0;
let (_, bits64_as_u8_slice, _) = std::slice::from_mut(&mut bits64).align_to_mut(); unsafe { eviocgbit_absolute(file.as_raw_fd(), &mut abs)? };
eviocgbit(
file.as_raw_fd(),
Types::ABSOLUTE.number(),
bits64_as_u8_slice,
)?;
}
state.abs_vals = Some(vec![input_absinfo_default(); 0x3f]); 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 { } else {
None None
}; };
let supported_switch = if ty.contains(Types::SWITCH) { let supported_switch = if ty.contains(Types::SWITCH) {
unsafe { let mut switch = 0;
let (_, bits_as_u8_slice, _) = std::slice::from_mut(&mut bits).align_to_mut(); unsafe { eviocgbit_switch(file.as_raw_fd(), &mut switch)? };
eviocgbit(file.as_raw_fd(), Types::SWITCH.number(), bits_as_u8_slice)?;
}
state.switch_vals = Some(FixedBitSet::with_capacity(0x10)); 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 { } else {
None None
}; };
let supported_led = if ty.contains(Types::LED) { let supported_led = if ty.contains(Types::LED) {
unsafe { let mut led = 0;
let (_, bits_as_u8_slice, _) = std::slice::from_mut(&mut bits).align_to_mut(); unsafe { eviocgbit_led(file.as_raw_fd(), &mut led)? };
eviocgbit(file.as_raw_fd(), Types::LED.number(), bits_as_u8_slice)?;
}
let led_vals = FixedBitSet::with_capacity(0x10); let led_vals = FixedBitSet::with_capacity(0x10);
debug_assert!(led_vals.len() % 8 == 0); debug_assert!(led_vals.len() % 8 == 0);
state.led_vals = Some(led_vals); 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 { } else {
None None
}; };
let supported_misc = if ty.contains(Types::MISC) { let supported_misc = if ty.contains(Types::MISC) {
unsafe { let mut misc = 0;
let (_, bits_as_u8_slice, _) = std::slice::from_mut(&mut bits).align_to_mut(); unsafe { eviocgbit_misc(file.as_raw_fd(), &mut misc)? };
eviocgbit(file.as_raw_fd(), Types::MISC.number(), bits_as_u8_slice)?; Some(Misc::from_bits(misc).expect("evdev: unexpected misc bits! report a bug"))
}
Some(Misc::from_bits(bits).expect("evdev: unexpected misc bits! report a bug"))
} else { } else {
None None
}; };
@ -521,11 +505,9 @@ impl Device {
//unsafe { eviocgbit(file.as_raw_fd(), ffs(FORCEFEEDBACK.bits()), 0x7f, bits_as_u8_slice)?; } //unsafe { eviocgbit(file.as_raw_fd(), ffs(FORCEFEEDBACK.bits()), 0x7f, bits_as_u8_slice)?; }
let supported_snd = if ty.contains(Types::SOUND) { let supported_snd = if ty.contains(Types::SOUND) {
unsafe { let mut snd = 0;
let (_, bits_as_u8_slice, _) = std::slice::from_mut(&mut bits).align_to_mut(); unsafe { eviocgbit_sound(file.as_raw_fd(), &mut snd)? };
eviocgbit(file.as_raw_fd(), Types::SOUND.number(), bits_as_u8_slice)?; Some(Sound::from_bits(snd).expect("evdev: unexpected sound bits! report a bug"))
}
Some(Sound::from_bits(bits).expect("evdev: unexpected sound bits! report a bug"))
} else { } else {
None None
}; };
@ -560,11 +542,12 @@ impl Device {
/// ///
/// If there is an error at any point, the state will not be synchronized completely. /// If there is an error at any point, the state will not be synchronized completely.
pub fn sync_state(&mut self) -> Result<(), Error> { pub fn sync_state(&mut self) -> Result<(), Error> {
let fd = self.as_raw_fd();
if let Some(key_vals) = &mut self.state.key_vals { if let Some(key_vals) = &mut self.state.key_vals {
unsafe { unsafe {
let key_slice = key_vals.as_mut_slice(); let key_slice = key_vals.as_mut_slice();
let (_, key_vals_as_u8_slice, _) = key_slice.align_to_mut(); 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) // the abs data seems to be fine (tested ABS_MT_POSITION_X/Y)
if supported_abs.contains(abs) { if supported_abs.contains(abs) {
unsafe { 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 { unsafe {
let switch_slice = switch_vals.as_mut_slice(); let switch_slice = switch_vals.as_mut_slice();
let (_, switch_vals_as_u8_slice, _) = switch_slice.align_to_mut(); 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 { unsafe {
let led_slice = led_vals.as_mut_slice(); let led_slice = led_vals.as_mut_slice();
let (_, led_vals_as_u8_slice, _) = led_slice.align_to_mut(); 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> { fn fill_events(&mut self) -> Result<(), Error> {
let fd = self.as_raw_fd();
let buf = &mut self.pending_events; let buf = &mut self.pending_events;
loop { loop {
buf.reserve(20); buf.reserve(20);
// TODO: use spare_capacity_mut or split_at_spare_mut when they stabilize // TODO: use Vec::spare_capacity_mut or Vec::split_at_spare_mut when they stabilize
let pre_len = buf.len(); let spare_capacity = vec_spare_capacity_mut(buf);
let capacity = buf.capacity(); let (_, uninit_buf, _) =
let (_, unsafe_buf_slice, _) = unsafe { spare_capacity.align_to_mut::<mem::MaybeUninit<u8>>() };
unsafe { buf.get_unchecked_mut(pre_len..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 { Ok(bytes_read) => unsafe {
buf.set_len(pre_len + (bytes_read / size_of::<raw::input_event>())); let pre_len = buf.len();
buf.set_len(
pre_len + (bytes_read as usize / mem::size_of::<raw::input_event>()),
);
}, },
Err(e) => { Err(e) => {
if e == nix::Error::Sys(::nix::errno::Errno::EAGAIN) { if e == nix::Error::Sys(::nix::errno::Errno::EAGAIN) {
@ -755,7 +743,14 @@ impl Device {
self.fill_events()?; self.fill_events()?;
self.compensate_dropped()?; 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. /// Crawls `/dev/input` for evdev devices.
/// ///
/// Will not bubble up any errors in opening devices or traversing the directory. Instead returns /// 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. /// an empty iterator or omits the devices that could not be opened.
pub fn enumerate() -> Vec<Device> { pub fn enumerate() -> EnumerateDevices {
let mut res = Vec::new(); EnumerateDevices {
if let Ok(dir) = std::fs::read_dir("/dev/input") { readdir: std::fs::read_dir("/dev/input").ok(),
for entry in dir { }
if let Ok(entry) = entry { }
if let Ok(dev) = Device::open(&entry.path()) {
res.push(dev) pub struct EnumerateDevices {
readdir: Option<std::fs::ReadDir>,
}
impl Iterator for EnumerateDevices {
type Item = Device;
fn next(&mut self) -> Option<Device> {
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 /// A safe Rust version of clock_gettime against CLOCK_REALTIME
@ -814,6 +818,18 @@ fn into_timeval(time: &SystemTime) -> Result<libc::timeval, std::time::SystemTim
}) })
} }
/// A copy of the unstable Vec::spare_capacity_mut
#[inline]
fn vec_spare_capacity_mut<T>(v: &mut Vec<T>) -> &mut [mem::MaybeUninit<T>] {
let (len, cap) = (v.len(), v.capacity());
unsafe {
std::slice::from_raw_parts_mut(
v.as_mut_ptr().add(len) as *mut mem::MaybeUninit<T>,
cap - len,
)
}
}
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use std::mem::MaybeUninit; use std::mem::MaybeUninit;

View file

@ -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!(eviocgname, b'E', 0x06, u8);
ioctl_read_buf!(eviocgphys, b'E', 0x07, u8); ioctl_read_buf!(eviocgphys, b'E', 0x07, u8);
ioctl_read_buf!(eviocguniq, b'E', 0x08, 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!(eviocgmtslots, b'E', 0x0a, u8);
ioctl_read_buf!(eviocgkey, b'E', 0x18, u8); ioctl_read_buf!(eviocgkey, b'E', 0x18, u8);
ioctl_read_buf!(eviocgled, b'E', 0x19, 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!(eviocrevoke, b'E', 0x91);
ioctl_write_int!(eviocsclockid, b'E', 0xa0); ioctl_write_int!(eviocsclockid, b'E', 0xa0);
/// ioctl: "get event bits" macro_rules! eviocgbit_ioctl {
/// ($mac:ident!($name:ident, $ev:ident, $ty:ty)) => {
/// `ev` should be one of the "Event types" as defined in the Linux kernel headers. eviocgbit_ioctl!($mac!($name, $crate::Types::$ev.number::<u32>(), $ty));
/// 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` ($mac:ident!($name:ident, $ev:expr, $ty:ty)) => {
/// $mac!($name, b'E', 0x20 + $ev, $ty);
/// # 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<c_int> {
assert!(ev <= 0x1f);
convert_ioctl_res!(::nix::libc::ioctl(
fd,
request_code_read!(b'E', 0x20 + ev, buf.len()),
buf.as_mut_ptr()
))
} }
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" /// ioctl: "get abs value/limits"
/// ///
/// `abs` should be one of the "Absolute axes" values defined in the Linux kernel headers. /// `abs` should be one of the "Absolute axes" values defined in the Linux kernel headers.