mirror of
https://codeberg.org/icewind/evdev-shortcut.git
synced 2026-06-03 10:04:11 +02:00
docs
This commit is contained in:
parent
7f555ba931
commit
025ee6a620
6 changed files with 123 additions and 21 deletions
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "evdev-shortcut"
|
||||
version = "0.1.3"
|
||||
version = "0.1.4"
|
||||
authors = ["Robin Appelman <robin@icewind.nl>"]
|
||||
edition = "2021"
|
||||
description = "Global shortcuts using evdev"
|
||||
|
|
|
|||
33
README.md
33
README.md
|
|
@ -5,22 +5,29 @@ Global shortcuts using evdev
|
|||
## Usage
|
||||
|
||||
```rust
|
||||
use std::path::PathBuf;
|
||||
use glob::GlobError;
|
||||
use evdev_shortcut::{ShortcutListener, Shortcut, Modifier, Key};
|
||||
use tokio::pin;
|
||||
use futures::stream::StreamExt;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
let listener = ShortcutListener::new();
|
||||
listener.add(Shortcut::new(&[Modifier::Meta], Key::KeyN));
|
||||
|
||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let listener = ShortcutListener::new();
|
||||
listener.add(Shortcut::new(&[Modifier::Meta], Key::KeyN));
|
||||
|
||||
let devices =
|
||||
glob::glob("/dev/input/by-id/*-kbd").unwrap().collect::<Result<Vec<PathBuf>, GlobError>>().unwrap();
|
||||
|
||||
let stream = listener.listen(&devices).unwrap();
|
||||
|
||||
pin_mut!(stream);
|
||||
|
||||
while let Some(shortcut) = stream.next().await {
|
||||
dbg!(shortcut);
|
||||
glob::glob("/dev/input/by-id/*-kbd")?.collect::<Result<Vec<PathBuf>, GlobError>>()?;
|
||||
|
||||
let stream = listener.listen(&devices)?;
|
||||
pin!(stream);
|
||||
|
||||
while let Some(event) = stream.next().await {
|
||||
println!("{} {}", event.shortcut, event.state);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
|
||||
Note that raw access to evdev devices is a privileged operation and usually requires root.
|
||||
Note that raw access to evdev devices is a privileged operation and usually requires running with elevated privileges.
|
||||
See [shortcutd](https://github.com/icewind1991/shortcutd) for a solution to running the elevated input handling in a separate process.
|
||||
|
|
@ -15,7 +15,7 @@ async fn main() {
|
|||
|
||||
pin_mut!(stream);
|
||||
|
||||
while let Some(shortcut) = stream.next().await {
|
||||
dbg!(shortcut);
|
||||
while let Some(event) = stream.next().await {
|
||||
println!("{} {}", event.shortcut, event.state);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
use num_enum::TryFromPrimitive;
|
||||
use parse_display::{Display, FromStr};
|
||||
|
||||
/// evdev keycode
|
||||
#[derive(Copy, Clone, Debug, TryFromPrimitive, PartialEq, Eq, Hash, Display, FromStr)]
|
||||
#[repr(u16)]
|
||||
pub enum Key {
|
||||
|
|
|
|||
76
src/lib.rs
76
src/lib.rs
|
|
@ -1,7 +1,42 @@
|
|||
//! Global keyboard shortcuts using evdev.
|
||||
//!
|
||||
//! By connecting to the input devices directly with evdev the shortcuts can work regardless of the environment,
|
||||
//! they will work under X11, wayland and in the terminal.
|
||||
//!
|
||||
//! This does come at the cost of having to run the program with elevated permissions.
|
||||
//! See [shortcutd](https://docs.rs/shortcutd/latest/shortcutd/) for a solution to running the elevated input handling in a separate process.
|
||||
//!
|
||||
//! Example:
|
||||
//!
|
||||
//! ```rust,no_run
|
||||
//! # use std::path::PathBuf;
|
||||
//! # use glob::GlobError;
|
||||
//! # use evdev_shortcut::{ShortcutListener, Shortcut, Modifier, Key};
|
||||
//! # use tokio::pin;
|
||||
//! # use futures::stream::StreamExt;
|
||||
//! # #[tokio::main]
|
||||
//! # async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
//! let listener = ShortcutListener::new();
|
||||
//! listener.add(Shortcut::new(&[Modifier::Meta], Key::KeyN));
|
||||
//!
|
||||
//! let devices =
|
||||
//! glob::glob("/dev/input/by-id/*-kbd")?.collect::<Result<Vec<PathBuf>, GlobError>>()?;
|
||||
//!
|
||||
//! let stream = listener.listen(&devices)?;
|
||||
//! pin!(stream);
|
||||
//!
|
||||
//! while let Some(event) = stream.next().await {
|
||||
//! println!("{} {}", event.shortcut, event.state);
|
||||
//! }
|
||||
//! # Ok(())
|
||||
//! # }
|
||||
//! ```
|
||||
|
||||
pub use keycodes::Key;
|
||||
use parse_display::{Display, FromStr, ParseError};
|
||||
use std::collections::HashSet;
|
||||
use std::fmt::{self, Debug, Display, Formatter};
|
||||
use std::path::PathBuf;
|
||||
use std::str::FromStr;
|
||||
use thiserror::Error;
|
||||
|
||||
|
|
@ -13,10 +48,14 @@ mod listener;
|
|||
#[cfg(feature = "listener")]
|
||||
pub use listener::ShortcutListener;
|
||||
|
||||
/// Error emitted when an input device can't be opened
|
||||
#[derive(Debug, Clone, Error)]
|
||||
#[error("Failed to open device")]
|
||||
pub struct DeviceOpenError;
|
||||
#[error("Failed to open device {device:?}")]
|
||||
pub struct DeviceOpenError {
|
||||
pub device: PathBuf,
|
||||
}
|
||||
|
||||
/// Modifier key for shortcuts
|
||||
#[derive(Clone, Copy, Debug, Hash, Eq, PartialEq, Display, FromStr)]
|
||||
#[repr(u8)]
|
||||
pub enum Modifier {
|
||||
|
|
@ -89,6 +128,7 @@ impl Modifier {
|
|||
}
|
||||
}
|
||||
|
||||
/// Set of modifier keys for shortcuts
|
||||
#[derive(Clone, Debug, Hash, PartialEq, Eq, Copy, Default)]
|
||||
pub struct ModifierList(u8);
|
||||
|
||||
|
|
@ -152,6 +192,29 @@ impl FromStr for ModifierList {
|
|||
}
|
||||
}
|
||||
|
||||
/// A keyboard shortcut consisting of zero or more modifier keys and a non-modifier key
|
||||
///
|
||||
/// Examples:
|
||||
///
|
||||
/// Create from keys:
|
||||
///
|
||||
/// ```rust
|
||||
/// # use evdev_shortcut::{Shortcut, Modifier, Key};
|
||||
/// # fn main() {
|
||||
/// let shortcut = Shortcut::new(&[Modifier::Meta], Key::KeyN);
|
||||
/// # }
|
||||
/// ```
|
||||
///
|
||||
/// Parse from string:
|
||||
///
|
||||
/// ```rust
|
||||
/// # use evdev_shortcut::{Shortcut, Modifier, Key};
|
||||
/// # use std::str::FromStr;
|
||||
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
/// let shortcut: Shortcut = "<Meta>-KeyN".parse()?;
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
|
||||
pub struct Shortcut {
|
||||
pub modifiers: ModifierList,
|
||||
|
|
@ -168,7 +231,6 @@ impl FromStr for Shortcut {
|
|||
key: key.parse()?,
|
||||
})
|
||||
} else {
|
||||
|
||||
Ok(Shortcut {
|
||||
modifiers: ModifierList::default(),
|
||||
key: s.parse()?,
|
||||
|
|
@ -264,6 +326,7 @@ mod triggered_tests {
|
|||
}
|
||||
}
|
||||
|
||||
/// Whether the shortcut was pressed or released
|
||||
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
|
||||
pub enum ShortcutState {
|
||||
Pressed,
|
||||
|
|
@ -279,6 +342,13 @@ impl ShortcutState {
|
|||
}
|
||||
}
|
||||
|
||||
impl Display for ShortcutState {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{}", self.as_str())
|
||||
}
|
||||
}
|
||||
|
||||
/// Event emitted when a shortcut is pressed or released.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ShortcutEvent {
|
||||
pub shortcut: Shortcut,
|
||||
|
|
|
|||
|
|
@ -10,6 +10,25 @@ use futures::{Stream, StreamExt};
|
|||
use futures::stream::{iter};
|
||||
use tracing::{debug, trace, info};
|
||||
|
||||
/// A listener for shortcut events
|
||||
///
|
||||
/// Example:
|
||||
///
|
||||
/// ```rust,no_run
|
||||
/// # use std::path::PathBuf;
|
||||
/// # use glob::GlobError;
|
||||
/// # use evdev_shortcut::{ShortcutListener, Shortcut, Modifier, Key};
|
||||
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
/// let listener = ShortcutListener::new();
|
||||
/// listener.add(Shortcut::new(&[Modifier::Meta], Key::KeyN));
|
||||
///
|
||||
/// let devices =
|
||||
/// glob::glob("/dev/input/by-id/*-kbd")?.collect::<Result<Vec<PathBuf>, GlobError>>()?;
|
||||
///
|
||||
/// let stream = listener.listen(&devices)?;
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
#[derive(Default)]
|
||||
pub struct ShortcutListener {
|
||||
shortcuts: Arc<Mutex<HashSet<Shortcut>>>,
|
||||
|
|
@ -20,14 +39,18 @@ impl ShortcutListener {
|
|||
ShortcutListener::default()
|
||||
}
|
||||
|
||||
/// Listen for shortcuts on the provided set of input devices.
|
||||
///
|
||||
/// Note that you need to register shortcuts using [add](ShortcutListener::add) to get any events.
|
||||
pub fn listen<P: AsRef<Path>>(&self, devices: &[P]) -> Result<impl Stream<Item=ShortcutEvent>, DeviceOpenError> {
|
||||
let shortcuts = self.shortcuts.clone();
|
||||
|
||||
let devices = devices
|
||||
.iter()
|
||||
.map(|path| {
|
||||
let res = Device::open(path).map_err(|_| DeviceOpenError);
|
||||
debug!(device = ?path.as_ref(), success = res.is_ok(), "opening input device");
|
||||
let path = path.as_ref();
|
||||
let res = Device::open(path).map_err(|_| DeviceOpenError { device: path.into() });
|
||||
debug!(device = ?path, success = res.is_ok(), "opening input device");
|
||||
res
|
||||
})
|
||||
.collect::<Result<Vec<Device>, DeviceOpenError>>()?;
|
||||
|
|
@ -85,6 +108,7 @@ impl ShortcutListener {
|
|||
self.shortcuts.lock().unwrap().remove(shortcut)
|
||||
}
|
||||
|
||||
/// Check if a shortcut is currently being listened for
|
||||
pub fn has(&self, shortcut: &Shortcut) -> bool {
|
||||
self.shortcuts.lock().unwrap().contains(shortcut)
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue