update to streams

This commit is contained in:
Robin Appelman 2023-06-17 15:52:26 +02:00
commit 07d9edac20
10 changed files with 802 additions and 271 deletions

View file

@ -3,20 +3,22 @@ use parse_display::{Display, FromStr, ParseError};
use std::collections::HashSet;
use std::fmt::{self, Display};
use std::str::FromStr;
use err_derive::Error;
use thiserror::Error;
mod keycodes;
#[cfg(feature = "listener")]
mod listener;
#[cfg(feature = "listener")]
pub use listener::ShortcutListener;
#[derive(Debug, Clone, Error)]
#[error(display = "Failed to open device")]
#[error("Failed to open device")]
pub struct DeviceOpenError;
#[derive(Clone, Copy, Debug, Hash, Eq, PartialEq, Display, FromStr)]
#[repr(u8)]
pub enum Modifier {
Alt,
LeftAlt,
@ -32,8 +34,30 @@ pub enum Modifier {
RightMeta,
}
const ALL_MODIFIERS: &[Modifier] = &[
Modifier::Alt,
Modifier::LeftAlt,
Modifier::RightAlt,
Modifier::Ctrl,
Modifier::LeftCtrl,
Modifier::RightCtrl,
Modifier::Shift,
Modifier::LeftShift,
Modifier::RightShift,
Modifier::Meta,
Modifier::LeftMeta,
Modifier::RightMeta,
];
const COMBINED_MODIFIERS: &[Modifier] = &[
Modifier::Alt,
Modifier::Ctrl,
Modifier::Shift,
Modifier::Meta,
];
impl Modifier {
pub fn as_mask(&self) -> u8 {
pub fn mask(&self) -> u8 {
match self {
Modifier::Alt => 0b00000011,
Modifier::LeftAlt => 0b00000001,
@ -65,20 +89,45 @@ impl Modifier {
}
}
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
pub struct ModifierList(Vec<Modifier>);
#[derive(Clone, Debug, Hash, PartialEq, Eq, Copy)]
pub struct ModifierList(u8);
impl ModifierList {
pub fn as_mask(&self) -> u8 {
self.0
pub fn new(modifiers: &[Modifier]) -> Self {
ModifierList(modifiers
.iter()
.fold(0, |mask, modifier| mask | modifier.as_mask())
.fold(0, |mask, modifier| mask | modifier.mask()))
}
pub fn mask(&self) -> u8 {
self.0
}
pub fn modifiers(&self) -> impl Iterator<Item=Modifier> {
let mask = self.mask();
ALL_MODIFIERS.iter().copied().filter(move |modifier| {
for combined in COMBINED_MODIFIERS {
// if <Ctrl> is enabled, don't emit <LeftCtrl> and <RightCtrl>
if combined != modifier && combined.mask() & modifier.mask() == modifier.mask() && combined.mask() & mask == combined.mask() {
return false;
}
}
modifier.mask() & mask == modifier.mask()
})
}
pub fn len(&self) -> u32 {
self.modifiers().count() as u32
}
pub fn is_empty(&self) -> bool {
self.mask() == 0
}
}
impl Display for ModifierList {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
for modifier in self.0.iter() {
for modifier in self.modifiers() {
write!(f, "<{}>", modifier)?;
}
Ok(())
@ -89,18 +138,17 @@ impl FromStr for ModifierList {
type Err = ParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(ModifierList(
s.split('>')
.filter(|part| !part.is_empty())
.map(|part| {
if !part.starts_with('<') {
Err(ParseError::with_message("Invalid modifier"))
} else {
Ok(part[1..].parse::<Modifier>()?)
}
})
.collect::<Result<Vec<Modifier>, ParseError>>()?,
))
let modifiers = s.split('>')
.filter(|part| !part.is_empty())
.map(|part| {
if !part.starts_with('<') {
Err(ParseError::with_message("Invalid modifier"))
} else {
Ok(part[1..].parse::<Modifier>()?)
}
})
.collect::<Result<Vec<Modifier>, ParseError>>()?;
Ok(ModifierList::new(&modifiers))
}
}
@ -113,44 +161,50 @@ pub struct Shortcut {
#[cfg(test)]
mod tests {
use crate::keyboard::{Key, Modifier, Shortcut};
use test_case::test_case;
use crate::{Key, Modifier, ModifierList, Shortcut};
#[test_case("<Ctrl>-KeyP", Shortcut::new(vec ! [Modifier::Ctrl], Key::KeyP))]
#[test_case("<LeftCtrl><LeftAlt>-KeyLeft", Shortcut::new(vec ! [Modifier::LeftCtrl, Modifier::LeftAlt], Key::KeyLeft))]
#[test_case("<Ctrl>-KeyP", Shortcut::new(& [Modifier::Ctrl], Key::KeyP))]
#[test_case("<LeftAlt><LeftCtrl>-KeyLeft", Shortcut::new(& [Modifier::LeftCtrl, Modifier::LeftAlt], Key::KeyLeft))]
fn shortcut_parse_display_test(s: &str, shortcut: Shortcut) {
assert_eq!(s, format!("{}", shortcut));
assert_eq!(shortcut, s.parse().unwrap());
}
#[test_case(& [Modifier::Ctrl])]
#[test_case(& [Modifier::LeftAlt, Modifier::LeftCtrl])]
#[test_case(& [Modifier::Shift, Modifier::Meta])]
fn test_modifier_list(modifiers: &[Modifier]) {
assert_eq!(modifiers.to_vec(), ModifierList::new(modifiers).modifiers().collect::<Vec<_>>())
}
}
impl Shortcut {
pub fn new(modifiers: Vec<Modifier>, key: Key) -> Self {
pub fn new(modifiers: &[Modifier], key: Key) -> Self {
Shortcut {
modifiers: ModifierList(modifiers),
modifiers: ModifierList::new(modifiers),
key,
}
}
pub fn identifier(&self) -> String {
self.to_string()
.replace('<', "")
.replace('>', "")
.replace(['<', '>'], "")
.replace('-', "_")
}
}
impl Shortcut {
pub fn is_triggered(&self, active_keys: &HashSet<Key>) -> bool {
let desired_mask = self.modifiers.as_mask();
let desired_mask = self.modifiers.mask();
let pressed_mask = active_keys
.iter()
.fold(0, |mask, key| mask | Modifier::mask_from_key(*key));
let desired_presses = desired_mask & pressed_mask;
let modifiers_match = (desired_presses == pressed_mask)
&& (desired_presses.count_ones() == self.modifiers.0.len() as u32);
&& (desired_presses.count_ones() == self.modifiers.len());
modifiers_match && active_keys.contains(&self.key)
}
@ -158,7 +212,7 @@ impl Shortcut {
#[cfg(test)]
mod triggered_tests {
use crate::keyboard::{Key, Shortcut};
use crate::{Key, Shortcut};
use test_case::test_case;
#[test_case("<Ctrl>-KeyP", & [] => false)]
@ -179,4 +233,16 @@ mod triggered_tests {
let shortcut: Shortcut = s.parse().unwrap();
shortcut.is_triggered(&keys.into_iter().copied().collect())
}
}
#[derive(Debug, Clone, Copy)]
pub enum ShortcutState {
Pressed,
Released,
}
#[derive(Debug, Clone)]
pub struct ShortcutEvent {
pub shortcut: Shortcut,
pub state: ShortcutState,
}

View file

@ -1,68 +1,71 @@
use evdev::Device;
use std::collections::HashSet;
use std::convert::TryFrom;
use std::sync::mpsc::{channel, Receiver};
use std::sync::{Arc, Mutex};
use std::time::Duration;
use crate::{Shortcut, DeviceOpenError, Key};
use crate::{Shortcut, DeviceOpenError, Key, ShortcutEvent, ShortcutState};
use std::path::Path;
use async_stream::stream;
use futures::pin_mut;
use futures::{Stream, StreamExt};
use futures::stream::{iter};
#[derive(Default)]
pub struct ShortcutListener {
shortcuts: Arc<Mutex<HashSet<Shortcut>>>,
}
impl ShortcutListener {
pub fn new() -> Self {
ShortcutListener {
shortcuts: Arc::default(),
}
ShortcutListener::default()
}
pub fn listen<P: AsRef<Path>>(&self, devices: &[P]) -> Result<Receiver<Shortcut>, DeviceOpenError> {
let mut devices = devices
.iter()
.map(|path| Ok(Device::open(path).map_err(|_| DeviceOpenError)?))
.collect::<Result<Vec<Device>, DeviceOpenError>>()?;
let (tx, rx) = channel();
pub fn listen<P: AsRef<Path>>(&self, devices: &[P]) -> Result<impl Stream<Item=ShortcutEvent>, DeviceOpenError> {
let shortcuts = self.shortcuts.clone();
std::thread::spawn(move || {
let devices = devices
.iter()
.map(|path| Device::open(path).map_err(|_| DeviceOpenError))
.collect::<Result<Vec<Device>, DeviceOpenError>>()?;
let events = iter(devices.into_iter().flat_map(|device| device.into_event_stream()))
.flatten();
Ok(stream! {
let mut active_keys = HashSet::new();
let mut pressed_shortcuts = HashSet::new();
loop {
let mut got_event = false;
pin_mut!(events);
let events = devices
.iter_mut()
.flat_map(|device| device.events().unwrap());
while let Some(Ok(event)) = events.next().await {
// dbg!(&event);
if let Ok(key) = Key::try_from(event.code()) {
match event.value() {
1 => active_keys.insert(key),
0 => active_keys.remove(&key),
_ => false,
};
}
for ev in events {
got_event = true;
let shortcuts: Vec<_> = shortcuts.lock().unwrap().iter().cloned().collect();
if let Ok(key) = Key::try_from(ev.code) {
match ev.value {
1 => active_keys.insert(key),
0 => active_keys.remove(&key),
_ => false,
for shortcut in shortcuts {
let is_triggered = shortcut.is_triggered(&active_keys);
let was_triggered = pressed_shortcuts.contains(&shortcut);
if is_triggered && !was_triggered {
pressed_shortcuts.insert(shortcut.clone());
yield ShortcutEvent {
shortcut,
state: ShortcutState::Pressed,
};
} else if !is_triggered && was_triggered {
pressed_shortcuts.remove(&shortcut);
yield ShortcutEvent {
shortcut,
state: ShortcutState::Released,
};
}
}
if got_event {
for shortcut in shortcuts.lock().unwrap().iter() {
if shortcut.is_triggered(&active_keys) {
tx.send(shortcut.clone()).unwrap()
}
}
} else {
std::thread::sleep(Duration::from_millis(10));
}
}
});
Ok(rx)
})
}
pub fn add(&self, shortcut: Shortcut) {