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:
parent
73fd0ae421
commit
2e2d6f1468
3 changed files with 120 additions and 110 deletions
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
170
src/lib.rs
170
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<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;
|
||||
|
|
46
src/raw.rs
46
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<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.
|
||||
|
|
Loading…
Reference in a new issue