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]
name = "evdev"
version = "0.11.0-alpha.5"
version = "0.11.0-alpha.6"
authors = ["Corey Richardson <corey@octayn.net>"]
description = "evdev interface for Linux"
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 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<AttributeSet<Key>>,
pub(crate) abs_vals: Option<Box<[libc::input_absinfo; AbsoluteAxisType::COUNT]>>,
@ -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.

View file

@ -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 `<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 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<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())
}
#[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<AttributeSet<Key>> {
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<AttributeSet<SwitchType>> {
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<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() {
// 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<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<()> {
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<LedType>) -> io::Result<()> {
unsafe { sys::eviocgled(self.as_raw_fd(), led_vals.as_mut_raw_slice()) }
.map(|_| ())
.map_err(nix_err)
}
}

View file

@ -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<Device> {
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<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.
///
/// 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 {