From 79b6c2b403f0d68c506edfa19b4d561770bb5714 Mon Sep 17 00:00:00 2001 From: Jeff Hiner <37913568+jeff-hiner@users.noreply.github.com> Date: Thu, 18 Mar 2021 14:34:13 -0600 Subject: [PATCH] Ergonomics (#38) * Hide sync_* implementations, expose get_* for state * Add get_abs_state and rustdoc * Refactor * Update rustdoc Co-authored-by: Jeff Hiner --- Cargo.toml | 2 +- src/device_state.rs | 42 +++++++++++-- src/lib.rs | 39 ++++++++---- src/raw_stream.rs | 146 ++++++++++++++++++++++---------------------- src/sync_stream.rs | 60 ++++++++++++++++-- 5 files changed, 193 insertions(+), 96 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index c6d6c7e..e5bf442 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "evdev" -version = "0.11.0-alpha.5" +version = "0.11.0-alpha.6" authors = ["Corey Richardson "] description = "evdev interface for Linux" license = "Apache-2.0 OR MIT" diff --git a/src/device_state.rs b/src/device_state.rs index 937b0f9..8bfac97 100644 --- a/src/device_state.rs +++ b/src/device_state.rs @@ -1,12 +1,12 @@ -use crate::constants::*; +use crate::{constants::*, raw_stream::RawDevice}; use crate::{AttributeSet, AttributeSetRef, InputEvent, InputEventKind, Key}; use std::time::SystemTime; -/// A cached representation of device state at a certain time. +/// A **cached** representation of device state at a certain time. #[derive(Debug)] pub struct DeviceState { /// The state corresponds to kernel state at this timestamp. - pub(crate) timestamp: libc::timeval, + pub(crate) timestamp: SystemTime, /// Set = key pressed pub(crate) key_vals: Option>, pub(crate) abs_vals: Option>, @@ -37,9 +37,43 @@ impl Clone for DeviceState { } impl DeviceState { + /// Create an empty `DeviceState`. The `{abs,key,etc}_vals` for the returned state will return + /// `Some` if `supported_events()` contains that `EventType`. + pub(crate) fn new(device: &RawDevice) -> Self { + let supports = device.supported_events(); + + let key_vals = if supports.contains(EventType::KEY) { + Some(AttributeSet::new()) + } else { + None + }; + let abs_vals = if supports.contains(EventType::ABSOLUTE) { + Some(Box::new(crate::raw_stream::ABS_VALS_INIT)) + } else { + None + }; + let switch_vals = if supports.contains(EventType::SWITCH) { + Some(AttributeSet::new()) + } else { + None + }; + let led_vals = if supports.contains(EventType::LED) { + Some(AttributeSet::new()) + } else { + None + }; + + DeviceState { + timestamp: std::time::UNIX_EPOCH, + key_vals, + abs_vals, + switch_vals, + led_vals, + } + } /// Returns the time when this snapshot was taken. pub fn timestamp(&self) -> SystemTime { - crate::timeval_to_systime(&self.timestamp) + self.timestamp } /// Returns the set of keys pressed when the snapshot was taken. diff --git a/src/lib.rs b/src/lib.rs index 52e37d0..a30a5bb 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -28,20 +28,37 @@ //! # } //! ``` //! -//! This state can be queried. For example, the [`DeviceState::led_vals`] method will tell you which -//! LEDs are currently lit on the device. As the application reads events, this state will be -//! updated, and it will be fully synchronized with the kernel if the stream drops any events. +//! The evdev crate exposes functions to query the current state of a device from the kernel, as +//! well as a function that can be called continuously to provide an iterator over update events +//! as they arrive. //! -//! As the state changes, the kernel will write events into a ring buffer. The application can read -//! from this ring buffer, thus retrieving events. However, if the ring buffer becomes full, the -//! kernel will *drop* every event in the ring buffer and leave an event telling userspace that it +//! # Synchronizing versus Raw modes +//! +//! This library can be used in either Raw or Synchronizing modes, which correspond roughly to +//! evdev's `LIBEVDEV_READ_FLAG_NORMAL` and `LIBEVDEV_READ_FLAG_SYNC` modes, respectively. +//! In both modes, calling `fetch_events` and driving the resulting iterator to completion +//! will provide a stream of real-time events from the underlying kernel device state. +//! As the state changes, the kernel will write events into a ring buffer. If the buffer becomes full, the +//! kernel will *drop* events from the ring buffer and leave an event telling userspace that it //! did so. At this point, if the application were using the events it received to update its //! internal idea of what state the hardware device is in, it will be wrong: it is missing some -//! events. This library tries to ease that pain, but it is best-effort. Events can never be -//! recovered once lost. For example, if a switch is toggled twice, there will be two switch events -//! in the buffer. However if the kernel needs to drop events, when the device goes to synchronize -//! state with the kernel, only one (or zero, if the switch is in the same state as it was before -//! the sync) switch events will be emulated. +//! events. +//! +//! In synchronous mode, this library tries to ease that pain by removing the corrupted events +//! and injecting fake events as if the device had updated normally. Note that this is best-effort; +//! events can never be recovered once lost. This synchronization comes at a performance cost: each +//! set of input events read from the kernel in turn updates an internal state buffer, and events +//! must be internally held back until the end of each frame. If this latency is unacceptable or +//! for any reason you want to see every event directly, a raw stream reader is also provided. +//! +//! As an example of how synchronization behaves, if a switch is toggled twice there will be two switch events +//! in the buffer. However, if the kernel needs to drop events, when the device goes to synchronize +//! state with the kernel only one (or zero, if the switch is in the same state as it was before +//! the sync) switch events will be visible in the stream. +//! +//! This cache can also be queried. For example, the [`DeviceState::led_vals`] method will tell you which +//! LEDs are currently lit on the device. As calling code consumes each iterator, this state will be +//! updated, and it will be fully re-synchronized with the kernel if the stream drops any events. //! //! It is recommended that you dedicate a thread to processing input events, or use epoll or an //! async runtime with the fd returned by `::as_raw_fd` to process events when diff --git a/src/raw_stream.rs b/src/raw_stream.rs index 81fc665..7e39690 100644 --- a/src/raw_stream.rs +++ b/src/raw_stream.rs @@ -5,7 +5,7 @@ use std::path::Path; use std::{io, mem}; use crate::constants::*; -use crate::{nix_err, sys, AttributeSet, AttributeSetRef, DeviceState, InputEvent, InputId, Key}; +use crate::{nix_err, sys, AttributeSet, AttributeSetRef, InputEvent, InputId, Key}; fn ioctl_get_cstring( f: unsafe fn(RawFd, &mut [u8]) -> nix::Result, @@ -31,6 +31,13 @@ fn bytes_into_string_lossy(v: Vec) -> String { String::from_utf8(v).unwrap_or_else(|v| String::from_utf8_lossy(v.as_bytes()).into_owned()) } +#[rustfmt::skip] +const ABSINFO_ZERO: libc::input_absinfo = libc::input_absinfo { + value: 0, minimum: 0, maximum: 0, fuzz: 0, flat: 0, resolution: 0, +}; +pub(crate) const ABS_VALS_INIT: [libc::input_absinfo; AbsoluteAxisType::COUNT] = + [ABSINFO_ZERO; AbsoluteAxisType::COUNT]; + /// A physical or virtual device supported by evdev. /// /// Each device corresponds to a path typically found in `/dev/input`, and supports access via @@ -398,71 +405,57 @@ impl RawDevice { Ok(self.event_buf.drain(..).map(InputEvent)) } - /// Create an empty `DeviceState`. The `{abs,key,etc}_vals` for the returned state will return - /// `Some` if `self.supported_events()` contains that `EventType`. - pub fn empty_state(&self) -> DeviceState { - let supports = self.supported_events(); - - let key_vals = if supports.contains(EventType::KEY) { - Some(AttributeSet::new()) - } else { - None - }; - let abs_vals = if supports.contains(EventType::ABSOLUTE) { - #[rustfmt::skip] - const ABSINFO_ZERO: libc::input_absinfo = libc::input_absinfo { - value: 0, minimum: 0, maximum: 0, fuzz: 0, flat: 0, resolution: 0, - }; - const ABS_VALS_INIT: [libc::input_absinfo; AbsoluteAxisType::COUNT] = - [ABSINFO_ZERO; AbsoluteAxisType::COUNT]; - Some(Box::new(ABS_VALS_INIT)) - } else { - None - }; - let switch_vals = if supports.contains(EventType::SWITCH) { - Some(AttributeSet::new()) - } else { - None - }; - let led_vals = if supports.contains(EventType::LED) { - Some(AttributeSet::new()) - } else { - None - }; - - DeviceState { - timestamp: libc::timeval { - tv_sec: 0, - tv_usec: 0, - }, - key_vals, - abs_vals, - switch_vals, - led_vals, - } + /// Retrieve the current keypress state directly via kernel syscall. + #[inline] + pub fn get_key_state(&self) -> io::Result> { + let mut key_vals = AttributeSet::new(); + self.update_key_state(&mut key_vals)?; + Ok(key_vals) } - pub fn sync_state(&self, state: &mut DeviceState) -> io::Result<()> { - self.sync_key_state(state)?; - self.sync_abs_state(state)?; - self.sync_switch_state(state)?; - self.sync_led_state(state)?; - Ok(()) + /// Retrieve the current absolute axis state directly via kernel syscall. + #[inline] + pub fn get_abs_state(&self) -> io::Result<[libc::input_absinfo; AbsoluteAxisType::COUNT]> { + let mut abs_vals: [libc::input_absinfo; AbsoluteAxisType::COUNT] = ABS_VALS_INIT; + self.update_abs_state(&mut abs_vals)?; + Ok(abs_vals) } - pub fn sync_key_state(&self, state: &mut DeviceState) -> io::Result<()> { - if let Some(key_vals) = &mut state.key_vals { - unsafe { - sys::eviocgkey(self.as_raw_fd(), key_vals.as_mut_raw_slice()).map_err(nix_err)? - }; - } - Ok(()) + /// Retrieve the current switch state directly via kernel syscall. + #[inline] + pub fn get_switch_state(&self) -> io::Result> { + let mut switch_vals = AttributeSet::new(); + self.update_switch_state(&mut switch_vals)?; + Ok(switch_vals) } - pub fn sync_abs_state(&self, state: &mut DeviceState) -> io::Result<()> { - if let (Some(supported_abs), Some(abs_vals)) = - (self.supported_absolute_axes(), &mut state.abs_vals) - { + /// Retrieve the current LED state directly via kernel syscall. + #[inline] + pub fn get_led_state(&self) -> io::Result> { + let mut led_vals = AttributeSet::new(); + self.update_led_state(&mut led_vals)?; + Ok(led_vals) + } + + /// Fetch the current kernel key state directly into the provided buffer. + /// If you don't already have a buffer, you probably want + /// [`get_key_state`](Self::get_key_state) instead. + #[inline] + pub fn update_key_state(&self, key_vals: &mut AttributeSet) -> io::Result<()> { + unsafe { sys::eviocgkey(self.as_raw_fd(), key_vals.as_mut_raw_slice()) } + .map(|_| ()) + .map_err(nix_err) + } + + /// Fetch the current kernel absolute axis state directly into the provided buffer. + /// If you don't already have a buffer, you probably want + /// [`get_abs_state`](Self::get_abs_state) instead. + #[inline] + pub fn update_abs_state( + &self, + abs_vals: &mut [libc::input_absinfo; AbsoluteAxisType::COUNT], + ) -> io::Result<()> { + if let Some(supported_abs) = self.supported_absolute_axes() { for AbsoluteAxisType(idx) in supported_abs.iter() { // ignore multitouch, we'll handle that later. // @@ -477,22 +470,27 @@ impl RawDevice { Ok(()) } - pub fn sync_switch_state(&self, state: &mut DeviceState) -> io::Result<()> { - if let Some(switch_vals) = &mut state.switch_vals { - unsafe { - sys::eviocgsw(self.as_raw_fd(), switch_vals.as_mut_raw_slice()).map_err(nix_err)? - }; - } - Ok(()) + /// Fetch the current kernel switch state directly into the provided buffer. + /// If you don't already have a buffer, you probably want + /// [`get_switch_state`](Self::get_switch_state) instead. + #[inline] + pub fn update_switch_state( + &self, + switch_vals: &mut AttributeSet, + ) -> io::Result<()> { + unsafe { sys::eviocgsw(self.as_raw_fd(), switch_vals.as_mut_raw_slice()) } + .map(|_| ()) + .map_err(nix_err) } - pub fn sync_led_state(&self, state: &mut DeviceState) -> io::Result<()> { - if let Some(led_vals) = &mut state.led_vals { - unsafe { - sys::eviocgled(self.as_raw_fd(), led_vals.as_mut_raw_slice()).map_err(nix_err)? - }; - } - Ok(()) + /// Fetch the current kernel LED state directly into the provided buffer. + /// If you don't already have a buffer, you probably want + /// [`get_led_state`](Self::get_led_state) instead. + #[inline] + pub fn update_led_state(&self, led_vals: &mut AttributeSet) -> io::Result<()> { + unsafe { sys::eviocgled(self.as_raw_fd(), led_vals.as_mut_raw_slice()) } + .map(|_| ()) + .map_err(nix_err) } } diff --git a/src/sync_stream.rs b/src/sync_stream.rs index 1acd68a..80e95d4 100644 --- a/src/sync_stream.rs +++ b/src/sync_stream.rs @@ -1,8 +1,10 @@ use crate::constants::*; +use crate::device_state::DeviceState; use crate::raw_stream::RawDevice; -use crate::{AttributeSetRef, DeviceState, InputEvent, InputEventKind, InputId, Key}; +use crate::{AttributeSet, AttributeSetRef, InputEvent, InputEventKind, InputId, Key}; use std::os::unix::io::{AsRawFd, RawFd}; use std::path::Path; +use std::time::SystemTime; use std::{fmt, io}; /// A physical or virtual device supported by evdev. @@ -16,7 +18,7 @@ use std::{fmt, io}; /// /// If `fetch_events()` isn't called often enough and the kernel drops events from its internal /// buffer, synthetic events will be injected into the iterator returned by `fetch_events()` and -/// [`Device::state()`] will be kept up to date when `fetch_events()` is called. +/// [`Device::cached_state()`] will be kept up to date when `fetch_events()` is called. pub struct Device { raw: RawDevice, prev_state: DeviceState, @@ -36,7 +38,7 @@ impl Device { fn _open(path: &Path) -> io::Result { let raw = RawDevice::open(path)?; - let state = raw.empty_state(); + let state = DeviceState::new(&raw); let prev_state = state.clone(); Ok(Device { @@ -47,7 +49,15 @@ impl Device { }) } - pub fn state(&self) -> &DeviceState { + /// Returns the synchronization engine's current understanding (cache) of the device state. + /// + /// Note that this represents the internal cache of the synchronization engine as of the last + /// entry that was pulled out. The advantage to calling this instead of invoking + /// [`get_key_state`](RawDevice::get_key_state) + /// and the like directly is speed: because reading this cache doesn't require any syscalls it's + /// easy to do inside a tight loop. The downside is that if the stream is not being driven quickly, + /// this can very quickly get desynchronized from the kernel and provide inaccurate data. + pub fn cached_state(&self) -> &DeviceState { &self.state } @@ -205,6 +215,43 @@ impl Device { self.raw.supported_sounds() } + /// Retrieve the current keypress state directly via kernel syscall. + pub fn get_key_state(&self) -> io::Result> { + self.raw.get_key_state() + } + + /// Retrieve the current absolute axis state directly via kernel syscall. + pub fn get_abs_state(&self) -> io::Result<[libc::input_absinfo; AbsoluteAxisType::COUNT]> { + self.raw.get_abs_state() + } + + /// Retrieve the current switch state directly via kernel syscall. + pub fn get_switch_state(&self) -> io::Result> { + self.raw.get_switch_state() + } + + /// Retrieve the current LED state directly via kernel syscall. + pub fn get_led_state(&self) -> io::Result> { + self.raw.get_led_state() + } + + fn sync_state(&mut self, now: SystemTime) -> io::Result<()> { + if let Some(ref mut key_vals) = self.state.key_vals { + self.raw.update_key_state(key_vals)?; + } + if let Some(ref mut abs_vals) = self.state.abs_vals { + self.raw.update_abs_state(abs_vals)?; + } + if let Some(ref mut switch_vals) = self.state.switch_vals { + self.raw.update_switch_state(switch_vals)?; + } + if let Some(ref mut led_vals) = self.state.led_vals { + self.raw.update_led_state(led_vals)?; + } + self.state.timestamp = now; + Ok(()) + } + /// Fetches and returns events from the kernel ring buffer, doing synchronization on SYN_DROPPED. /// /// By default this will block until events are available. Typically, users will want to call @@ -214,9 +261,10 @@ impl Device { let block_dropped = std::mem::take(&mut self.block_dropped); let sync = if block_dropped { self.prev_state.clone_from(&self.state); - self.raw.sync_state(&mut self.state)?; + let now = SystemTime::now(); + self.sync_state(now)?; Some(SyncState::Keys { - time: crate::systime_to_timeval(&std::time::SystemTime::now()), + time: crate::systime_to_timeval(&now), start: Key::new(0), }) } else {