Ergonomics (#38)

* Hide sync_* implementations, expose get_* for state

* Add get_abs_state and rustdoc

* Refactor

* Update rustdoc

Co-authored-by: Jeff Hiner <jeff-hiner@users.noreply.github.com>
This commit is contained in:
Jeff Hiner 2021-03-18 14:34:13 -06:00 committed by GitHub
parent 3581aa25e0
commit 79b6c2b403
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 193 additions and 96 deletions

View file

@ -1,6 +1,6 @@
[package] [package]
name = "evdev" name = "evdev"
version = "0.11.0-alpha.5" version = "0.11.0-alpha.6"
authors = ["Corey Richardson <corey@octayn.net>"] authors = ["Corey Richardson <corey@octayn.net>"]
description = "evdev interface for Linux" description = "evdev interface for Linux"
license = "Apache-2.0 OR MIT" license = "Apache-2.0 OR MIT"

View file

@ -1,12 +1,12 @@
use crate::constants::*; use crate::{constants::*, raw_stream::RawDevice};
use crate::{AttributeSet, AttributeSetRef, InputEvent, InputEventKind, Key}; use crate::{AttributeSet, AttributeSetRef, InputEvent, InputEventKind, Key};
use std::time::SystemTime; 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)] #[derive(Debug)]
pub struct DeviceState { pub struct DeviceState {
/// The state corresponds to kernel state at this timestamp. /// The state corresponds to kernel state at this timestamp.
pub(crate) timestamp: libc::timeval, pub(crate) timestamp: SystemTime,
/// Set = key pressed /// Set = key pressed
pub(crate) key_vals: Option<AttributeSet<Key>>, pub(crate) key_vals: Option<AttributeSet<Key>>,
pub(crate) abs_vals: Option<Box<[libc::input_absinfo; AbsoluteAxisType::COUNT]>>, pub(crate) abs_vals: Option<Box<[libc::input_absinfo; AbsoluteAxisType::COUNT]>>,
@ -37,9 +37,43 @@ impl Clone for DeviceState {
} }
impl 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. /// Returns the time when this snapshot was taken.
pub fn timestamp(&self) -> SystemTime { pub fn timestamp(&self) -> SystemTime {
crate::timeval_to_systime(&self.timestamp) self.timestamp
} }
/// Returns the set of keys pressed when the snapshot was taken. /// Returns the set of keys pressed when the snapshot was taken.

View file

@ -28,20 +28,37 @@
//! # } //! # }
//! ``` //! ```
//! //!
//! This state can be queried. For example, the [`DeviceState::led_vals`] method will tell you which //! The evdev crate exposes functions to query the current state of a device from the kernel, as
//! LEDs are currently lit on the device. As the application reads events, this state will be //! well as a function that can be called continuously to provide an iterator over update events
//! updated, and it will be fully synchronized with the kernel if the stream drops any events. //! as they arrive.
//! //!
//! As the state changes, the kernel will write events into a ring buffer. The application can read //! # Synchronizing versus Raw modes
//! 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 //! 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 //! 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 //! 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 //! events.
//! 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 //! In synchronous mode, this library tries to ease that pain by removing the corrupted events
//! state with the kernel, only one (or zero, if the switch is in the same state as it was before //! and injecting fake events as if the device had updated normally. Note that this is best-effort;
//! the sync) switch events will be emulated. //! 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 //! It is recommended that you dedicate a thread to processing input events, or use epoll or an
//! async runtime with the fd returned by `<Device as AsRawFd>::as_raw_fd` to process events when //! async runtime with the fd returned by `<Device as AsRawFd>::as_raw_fd` to process events when

View file

@ -5,7 +5,7 @@ use std::path::Path;
use std::{io, mem}; use std::{io, mem};
use crate::constants::*; 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( fn ioctl_get_cstring(
f: unsafe fn(RawFd, &mut [u8]) -> nix::Result<libc::c_int>, f: unsafe fn(RawFd, &mut [u8]) -> nix::Result<libc::c_int>,
@ -31,6 +31,13 @@ fn bytes_into_string_lossy(v: Vec<u8>) -> String {
String::from_utf8(v).unwrap_or_else(|v| String::from_utf8_lossy(v.as_bytes()).into_owned()) 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. /// A physical or virtual device supported by evdev.
/// ///
/// Each device corresponds to a path typically found in `/dev/input`, and supports access via /// 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)) Ok(self.event_buf.drain(..).map(InputEvent))
} }
/// Create an empty `DeviceState`. The `{abs,key,etc}_vals` for the returned state will return /// Retrieve the current keypress state directly via kernel syscall.
/// `Some` if `self.supported_events()` contains that `EventType`. #[inline]
pub fn empty_state(&self) -> DeviceState { pub fn get_key_state(&self) -> io::Result<AttributeSet<Key>> {
let supports = self.supported_events(); let mut key_vals = AttributeSet::new();
self.update_key_state(&mut key_vals)?;
let key_vals = if supports.contains(EventType::KEY) { Ok(key_vals)
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,
}
} }
pub fn sync_state(&self, state: &mut DeviceState) -> io::Result<()> { /// Retrieve the current absolute axis state directly via kernel syscall.
self.sync_key_state(state)?; #[inline]
self.sync_abs_state(state)?; pub fn get_abs_state(&self) -> io::Result<[libc::input_absinfo; AbsoluteAxisType::COUNT]> {
self.sync_switch_state(state)?; let mut abs_vals: [libc::input_absinfo; AbsoluteAxisType::COUNT] = ABS_VALS_INIT;
self.sync_led_state(state)?; self.update_abs_state(&mut abs_vals)?;
Ok(()) Ok(abs_vals)
} }
pub fn sync_key_state(&self, state: &mut DeviceState) -> io::Result<()> { /// Retrieve the current switch state directly via kernel syscall.
if let Some(key_vals) = &mut state.key_vals { #[inline]
unsafe { pub fn get_switch_state(&self) -> io::Result<AttributeSet<SwitchType>> {
sys::eviocgkey(self.as_raw_fd(), key_vals.as_mut_raw_slice()).map_err(nix_err)? let mut switch_vals = AttributeSet::new();
}; self.update_switch_state(&mut switch_vals)?;
} Ok(switch_vals)
Ok(())
} }
pub fn sync_abs_state(&self, state: &mut DeviceState) -> io::Result<()> { /// Retrieve the current LED state directly via kernel syscall.
if let (Some(supported_abs), Some(abs_vals)) = #[inline]
(self.supported_absolute_axes(), &mut state.abs_vals) pub fn get_led_state(&self) -> io::Result<AttributeSet<LedType>> {
{ 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<Key>) -> 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() { for AbsoluteAxisType(idx) in supported_abs.iter() {
// ignore multitouch, we'll handle that later. // ignore multitouch, we'll handle that later.
// //
@ -477,22 +470,27 @@ impl RawDevice {
Ok(()) Ok(())
} }
pub fn sync_switch_state(&self, state: &mut DeviceState) -> io::Result<()> { /// Fetch the current kernel switch state directly into the provided buffer.
if let Some(switch_vals) = &mut state.switch_vals { /// If you don't already have a buffer, you probably want
unsafe { /// [`get_switch_state`](Self::get_switch_state) instead.
sys::eviocgsw(self.as_raw_fd(), switch_vals.as_mut_raw_slice()).map_err(nix_err)? #[inline]
}; pub fn update_switch_state(
} &self,
Ok(()) switch_vals: &mut AttributeSet<SwitchType>,
) -> 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<()> { /// Fetch the current kernel LED state directly into the provided buffer.
if let Some(led_vals) = &mut state.led_vals { /// If you don't already have a buffer, you probably want
unsafe { /// [`get_led_state`](Self::get_led_state) instead.
sys::eviocgled(self.as_raw_fd(), led_vals.as_mut_raw_slice()).map_err(nix_err)? #[inline]
}; pub fn update_led_state(&self, led_vals: &mut AttributeSet<LedType>) -> io::Result<()> {
} unsafe { sys::eviocgled(self.as_raw_fd(), led_vals.as_mut_raw_slice()) }
Ok(()) .map(|_| ())
.map_err(nix_err)
} }
} }

View file

@ -1,8 +1,10 @@
use crate::constants::*; use crate::constants::*;
use crate::device_state::DeviceState;
use crate::raw_stream::RawDevice; 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::os::unix::io::{AsRawFd, RawFd};
use std::path::Path; use std::path::Path;
use std::time::SystemTime;
use std::{fmt, io}; use std::{fmt, io};
/// A physical or virtual device supported by evdev. /// 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 /// 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 /// 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 { pub struct Device {
raw: RawDevice, raw: RawDevice,
prev_state: DeviceState, prev_state: DeviceState,
@ -36,7 +38,7 @@ impl Device {
fn _open(path: &Path) -> io::Result<Device> { fn _open(path: &Path) -> io::Result<Device> {
let raw = RawDevice::open(path)?; let raw = RawDevice::open(path)?;
let state = raw.empty_state(); let state = DeviceState::new(&raw);
let prev_state = state.clone(); let prev_state = state.clone();
Ok(Device { 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 &self.state
} }
@ -205,6 +215,43 @@ impl Device {
self.raw.supported_sounds() self.raw.supported_sounds()
} }
/// Retrieve the current keypress state directly via kernel syscall.
pub fn get_key_state(&self) -> io::Result<AttributeSet<Key>> {
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<AttributeSet<SwitchType>> {
self.raw.get_switch_state()
}
/// Retrieve the current LED state directly via kernel syscall.
pub fn get_led_state(&self) -> io::Result<AttributeSet<LedType>> {
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. /// 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 /// 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 block_dropped = std::mem::take(&mut self.block_dropped);
let sync = if block_dropped { let sync = if block_dropped {
self.prev_state.clone_from(&self.state); 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 { Some(SyncState::Keys {
time: crate::systime_to_timeval(&std::time::SystemTime::now()), time: crate::systime_to_timeval(&now),
start: Key::new(0), start: Key::new(0),
}) })
} else { } else {