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:
parent
3581aa25e0
commit
79b6c2b403
5 changed files with 193 additions and 96 deletions
|
@ -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"
|
||||
|
|
|
@ -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.
|
||||
|
|
39
src/lib.rs
39
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 `<Device as AsRawFd>::as_raw_fd` to process events when
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
Loading…
Reference in a new issue