evdev_shortcut/
listener.rs

1use crate::{DeviceOpenError, Key, Shortcut, ShortcutEvent, ShortcutState};
2use async_stream::stream;
3use evdev::Device;
4use futures::pin_mut;
5use futures::stream::iter;
6use futures::{Stream, StreamExt};
7use std::collections::HashSet;
8use std::convert::TryFrom;
9use std::path::Path;
10use std::sync::{Arc, Mutex};
11use tracing::{debug, info, trace};
12
13/// A listener for shortcut events
14///
15/// Example:
16///
17/// ```rust,no_run
18/// # use std::path::PathBuf;
19/// # use glob::GlobError;
20/// # use evdev_shortcut::{ShortcutListener, Shortcut, Modifier, Key};
21/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
22/// let listener = ShortcutListener::new();
23/// listener.add(Shortcut::new(&[Modifier::Meta], Key::KeyN));
24///
25/// let devices =
26///     glob::glob("/dev/input/by-id/*-kbd")?.collect::<Result<Vec<PathBuf>, GlobError>>()?;
27///
28/// let stream = listener.listen(&devices)?;
29/// # Ok(())
30/// # }
31/// ```
32#[derive(Default)]
33pub struct ShortcutListener {
34    shortcuts: Arc<Mutex<HashSet<Shortcut>>>,
35}
36
37impl ShortcutListener {
38    pub fn new() -> Self {
39        ShortcutListener::default()
40    }
41
42    /// Listen for shortcuts on the provided set of input devices.
43    ///
44    /// Note that you need to register shortcuts using [add](ShortcutListener::add) to get any events.
45    pub fn listen<P: AsRef<Path>>(
46        &self,
47        devices: &[P],
48    ) -> Result<impl Stream<Item = ShortcutEvent>, DeviceOpenError> {
49        let shortcuts = self.shortcuts.clone();
50
51        let devices = devices
52            .iter()
53            .map(|path| {
54                let path = path.as_ref();
55                let res = Device::open(path).map_err(|_| DeviceOpenError {
56                    device: path.into(),
57                });
58                debug!(device = ?path, success = res.is_ok(), "opening input device");
59                res
60            })
61            .collect::<Result<Vec<Device>, DeviceOpenError>>()?;
62        let events = iter(
63            devices
64                .into_iter()
65                .flat_map(|device| device.into_event_stream()),
66        )
67        .flatten();
68
69        Ok(stream! {
70            let mut active_keys = HashSet::new();
71            let mut pressed_shortcuts = HashSet::new();
72
73            pin_mut!(events);
74
75            while let Some(Ok(event)) = events.next().await {
76                trace!(?event, "evdev event");
77                if let Ok(key) = Key::try_from(event.code()) {
78                    match event.value() {
79                        1 => active_keys.insert(key),
80                        0 => active_keys.remove(&key),
81                        _ => false,
82                    };
83                }
84
85                let shortcuts: Vec<_> = shortcuts.lock().unwrap().iter().cloned().collect();
86
87                for shortcut in shortcuts {
88                    let is_triggered = shortcut.is_triggered(&active_keys);
89                    let was_triggered = pressed_shortcuts.contains(&shortcut);
90                    if is_triggered && !was_triggered {
91                        pressed_shortcuts.insert(shortcut.clone());
92                        info!(?shortcut, "pressed");
93                        yield ShortcutEvent {
94                            shortcut,
95                            state: ShortcutState::Pressed,
96                        };
97                    } else if !is_triggered && was_triggered {
98                        pressed_shortcuts.remove(&shortcut);
99                        info!(?shortcut, "released");
100                        yield ShortcutEvent {
101                            shortcut,
102                            state: ShortcutState::Released,
103                        };
104                    }
105                }
106            }
107        })
108    }
109
110    /// Returns `true` if the shortcut was not previously listened to
111    pub fn add(&self, shortcut: Shortcut) -> bool {
112        self.shortcuts.lock().unwrap().insert(shortcut)
113    }
114
115    /// Returns `true` if the shortcut was previously listened to
116    pub fn remove(&self, shortcut: &Shortcut) -> bool {
117        self.shortcuts.lock().unwrap().remove(shortcut)
118    }
119
120    /// Check if a shortcut is currently being listened for
121    pub fn has(&self, shortcut: &Shortcut) -> bool {
122        self.shortcuts.lock().unwrap().contains(shortcut)
123    }
124}