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.
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::<Vec<_>>();
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::<usize>().unwrap());
}
devices.swap_remove(chosen.trim().parse::<usize>().unwrap())
};
println!("{}", d);
println!("Events:");
loop {
for ev in d.events_no_sync().unwrap() {
println!("{:?}", ev);
}
d.wait_ready().unwrap();
}
}

View file

@ -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<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();
// 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::<mem::MaybeUninit<u8>>() };
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::<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) => {
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<Device> {
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<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
@ -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)]
mod test {
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!(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<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()
))
macro_rules! eviocgbit_ioctl {
($mac:ident!($name:ident, $ev:ident, $ty:ty)) => {
eviocgbit_ioctl!($mac!($name, $crate::Types::$ev.number::<u32>(), $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.