diff --git a/espanso-detect/src/evdev/device.rs b/espanso-detect/src/evdev/device.rs
index 61763f3..df4ab9a 100644
--- a/espanso-detect/src/evdev/device.rs
+++ b/espanso-detect/src/evdev/device.rs
@@ -36,6 +36,7 @@ pub enum RawInputEvent {
#[derive(Debug)]
pub struct RawKeyboardEvent {
pub sym: u32,
+ pub code: u32,
pub value: String,
pub state: i32,
}
@@ -171,6 +172,7 @@ impl Device {
let event = RawKeyboardEvent {
state: value,
+ code: keycode,
sym,
value: content,
};
diff --git a/espanso-detect/src/evdev/ffi.rs b/espanso-detect/src/evdev/ffi.rs
index 31bf665..e242ca5 100644
--- a/espanso-detect/src/evdev/ffi.rs
+++ b/espanso-detect/src/evdev/ffi.rs
@@ -43,8 +43,6 @@ pub type xkb_state_component = u32;
pub const EV_KEY: u16 = 0x01;
-pub const XKB_KEYCODE_INVALID: u32 = 0xffffffff;
-
#[link(name = "xkbcommon")]
extern "C" {
pub fn xkb_state_unref(state: *mut xkb_state);
@@ -59,7 +57,6 @@ extern "C" {
pub fn xkb_context_unref(context: *mut xkb_context);
pub fn xkb_state_get_keymap(state: *mut xkb_state) -> *mut xkb_keymap;
pub fn xkb_keymap_key_repeats(keymap: *mut xkb_keymap, key: xkb_keycode_t) -> c_int;
- pub fn xkb_keymap_key_by_name(keymap: *mut xkb_keymap, name: *const c_char) -> u32;
pub fn xkb_state_update_key(
state: *mut xkb_state,
key: xkb_keycode_t,
diff --git a/espanso-detect/src/evdev/hotkey.rs b/espanso-detect/src/evdev/hotkey.rs
index 2549efe..b7b2a1f 100644
--- a/espanso-detect/src/evdev/hotkey.rs
+++ b/espanso-detect/src/evdev/hotkey.rs
@@ -17,52 +17,128 @@
* along with espanso. If not, see .
*/
-use crate::{event::KeyboardEvent, hotkey::HotKey};
-use std::{
- collections::{HashMap, VecDeque},
- time::Instant,
+use log::error;
+
+use crate::{event::{KeyboardEvent, Status}, hotkey::HotKey};
+use std::{collections::HashMap, time::Instant};
+
+use super::{
+ state::State,
};
-use super::state::State;
-
-// Number of milliseconds between the first and last codes of an hotkey
-// to be considered valid
-const HOTKEY_WINDOW: i32 = 5000;
+// Number of milliseconds that define how long the hotkey memory
+// should retain pressed keys
+const HOTKEY_WINDOW_TIMEOUT: u128 = 5000;
pub type KeySym = u32;
pub type KeyCode = u32;
-pub type SymMap = HashMap;
+pub type HotkeyMemoryMap = Vec<(KeyCode, Instant)>;
-pub type HotkeyMemoryMap = VecDeque<(KeyCode, Instant)>;
+pub struct HotKeyFilter {
+ map: HashMap,
+ memory: HotkeyMemoryMap,
+ hotkey_raw_map: HashMap>,
+}
-pub fn generate_sym_map(state: &State) -> HashMap {
- let mut map = HashMap::new();
- for code in 0..256 {
- if let Some(sym) = state.get_sym(code) {
- map.insert(sym, code);
+impl HotKeyFilter {
+ pub fn new() -> Self {
+ Self {
+ map: HashMap::new(),
+ memory: HotkeyMemoryMap::new(),
+ hotkey_raw_map: HashMap::new(),
}
}
- map
-}
-pub fn convert_hotkey_to_codes(hk: &HotKey, map: &SymMap) -> Option> {
- let mut codes = Vec::new();
- let key_code = map.get(&hk.key.to_code()?)?;
- codes.push(*key_code);
+ pub fn initialize(&mut self, state: &State, hotkeys: &[HotKey]) {
+ // First load the map
+ self.map = HashMap::new();
+ for code in 0..256 {
+ if let Some(sym) = state.get_sym(code) {
+ self.map.insert(sym, code);
+ }
+ }
- for modifier in hk.modifiers.iter() {
- let code = map.get(&modifier.to_code()?)?;
- codes.push(*code);
+ // Then the actual hotkeys
+ self.hotkey_raw_map = hotkeys
+ .iter()
+ .filter_map(|hk| {
+ let codes = Self::convert_hotkey_to_codes(self, hk);
+ if codes.is_none() {
+ error!("unable to register hotkey {:?}", hk);
+ }
+ Some((hk.id, codes?))
+ })
+ .collect();
}
- Some(codes)
-}
+ pub fn process_event(
+ &mut self,
+ event: &KeyboardEvent,
+ ) -> Option {
+ let mut hotkey = None;
+ let mut key_code = None;
-pub fn detect_hotkey(
- event: &KeyboardEvent,
- memory: &mut HotkeyMemoryMap,
- hotkeys: &HashMap>,
-) -> Option {
- // TODO: implement the actual matching mechanism
- // We need to "clean" the old entries
+ let mut to_be_removed = Vec::new();
+
+ if event.status == Status::Released {
+ // Remove from the memory all the key occurrences
+ to_be_removed.extend(self.memory.iter().enumerate().filter_map(|(i, (code, _))| {
+ if *code == event.code {
+ Some(i)
+ } else {
+ None
+ }
+ }));
+ } else {
+ key_code = Some(event.code)
+ }
+
+ // Remove the old entries
+ to_be_removed.extend(self.memory.iter().enumerate().filter_map(|(i, (_, instant))| {
+ if instant.elapsed().as_millis() > HOTKEY_WINDOW_TIMEOUT {
+ Some(i)
+ } else {
+ None
+ }
+ }));
+
+ // Remove duplicates and revert
+ if !to_be_removed.is_empty() {
+ to_be_removed.sort();
+ to_be_removed.dedup();
+ to_be_removed.reverse();
+ to_be_removed.into_iter().for_each(|index| {
+ self.memory.remove(index);
+ })
+ }
+
+ if let Some(code) = key_code {
+ self.memory.push((code, Instant::now()));
+
+ for (id, codes) in self.hotkey_raw_map.iter() {
+ if codes
+ .iter()
+ .all(|hk_code| self.memory.iter().any(|(m_code, _)| m_code == hk_code))
+ {
+ hotkey = Some(*id);
+ break;
+ }
+ }
+ }
+
+ hotkey
+ }
+
+ fn convert_hotkey_to_codes(&self, hk: &HotKey) -> Option> {
+ let mut codes = Vec::new();
+ let key_code = self.map.get(&hk.key.to_code()?)?;
+ codes.push(*key_code);
+
+ for modifier in hk.modifiers.iter() {
+ let code = self.map.get(&modifier.to_code()?)?;
+ codes.push(*code);
+ }
+
+ Some(codes)
+ }
}
diff --git a/espanso-detect/src/evdev/mod.rs b/espanso-detect/src/evdev/mod.rs
index 01550c5..392a764 100644
--- a/espanso-detect/src/evdev/mod.rs
+++ b/espanso-detect/src/evdev/mod.rs
@@ -23,16 +23,15 @@
mod context;
mod device;
mod ffi;
-mod keymap;
mod hotkey;
+mod keymap;
mod state;
-use std::collections::HashMap;
+use std::{cell::RefCell};
use anyhow::Result;
use context::Context;
use device::{get_devices, Device};
-use hotkey::HotkeyMemoryMap;
use keymap::Keymap;
use lazycell::LazyCell;
use libc::{
@@ -41,12 +40,12 @@ use libc::{
use log::{error, trace};
use thiserror::Error;
-use crate::{event::Variant::*, hotkey::HotKey};
use crate::event::{InputEvent, Key, KeyboardEvent, Variant};
use crate::event::{Key::*, MouseButton, MouseEvent};
use crate::{event::Status::*, KeyboardConfig, Source, SourceCallback, SourceCreationOptions};
+use crate::{event::Variant::*, event::HotKeyEvent, hotkey::HotKey};
-use self::{device::{DeviceError, KEY_STATE_PRESS, KEY_STATE_RELEASE, RawInputEvent}, hotkey::{KeyCode, convert_hotkey_to_codes, generate_sym_map}, state::State};
+use self::{device::{DeviceError, RawInputEvent, KEY_STATE_PRESS, KEY_STATE_RELEASE}, hotkey::{HotKeyFilter}, state::State};
const BTN_LEFT: u16 = 0x110;
const BTN_RIGHT: u16 = 0x111;
@@ -61,7 +60,7 @@ pub struct EVDEVSource {
_keyboard_rmlvo: Option,
_context: LazyCell,
_keymap: LazyCell,
- _hotkey_codes: HashMap>,
+ _hotkey_filter: RefCell,
}
#[allow(clippy::new_without_default)]
@@ -73,7 +72,7 @@ impl EVDEVSource {
_context: LazyCell::new(),
_keymap: LazyCell::new(),
_keyboard_rmlvo: options.evdev_keyboard_rmlvo,
- _hotkey_codes: HashMap::new(),
+ _hotkey_filter: RefCell::new(HotKeyFilter::new()),
}
}
}
@@ -102,14 +101,7 @@ impl Source for EVDEVSource {
// Initialize the hotkeys
let state = State::new(&keymap)?;
- let sym_map = generate_sym_map(&state);
- self._hotkey_codes = self.hotkeys.iter().filter_map(|hk| {
- let codes = convert_hotkey_to_codes(hk, &sym_map);
- if codes.is_none() {
- error!("unable to register hotkey {:?}", hk);
- }
- Some((hk.id, codes?))
- }).collect();
+ self._hotkey_filter.borrow_mut().initialize(&state, &self.hotkeys);
if self._context.fill(context).is_err() {
return Err(EVDEVSourceError::InitFailure().into());
@@ -153,7 +145,7 @@ impl Source for EVDEVSource {
}
}
- let mut hotkey_memory = HotkeyMemoryMap::new();
+ let mut hotkey_filter = self._hotkey_filter.borrow_mut();
// Read events indefinitely
let mut evs: [epoll_event; 16] = unsafe { std::mem::zeroed() };
@@ -176,8 +168,14 @@ impl Source for EVDEVSource {
events.into_iter().for_each(|raw_event| {
let event: Option = raw_event.into();
if let Some(event) = event {
- // First process the hotkey
-
+ // On Wayland we need to detect the global shortcuts manually
+ if let InputEvent::Keyboard(key_event) = &event {
+ if let Some(hotkey) = (*hotkey_filter).process_event(&key_event) {
+ event_callback(InputEvent::HotKey(HotKeyEvent {
+ hotkey_id: hotkey,
+ }))
+ }
+ }
event_callback(event);
} else {
@@ -233,6 +231,7 @@ impl From for Option {
value,
status,
variant,
+ code: keyboard_event.code,
}));
}
RawInputEvent::Mouse(mouse_event) => {
@@ -345,7 +344,8 @@ mod tests {
let raw = RawInputEvent::Keyboard(RawKeyboardEvent {
sym: 0x4B,
value: "k".to_owned(),
- is_down: false,
+ state: KEY_STATE_RELEASE,
+ code: 0,
});
let result: Option = raw.into();
@@ -356,6 +356,7 @@ mod tests {
status: Released,
value: Some("k".to_string()),
variant: None,
+ code: 0,
})
);
}
diff --git a/espanso-detect/src/evdev/state.rs b/espanso-detect/src/evdev/state.rs
index 5de799b..9dcb843 100644
--- a/espanso-detect/src/evdev/state.rs
+++ b/espanso-detect/src/evdev/state.rs
@@ -1,8 +1,6 @@
// This code is a port of the libxkbcommon "interactive-evdev.c" example
// https://github.com/xkbcommon/libxkbcommon/blob/master/tools/interactive-evdev.c
-use std::ffi::CStr;
-
use scopeguard::ScopeGuard;
use anyhow::Result;
diff --git a/espanso-detect/src/event.rs b/espanso-detect/src/event.rs
index a1135cf..98412c4 100644
--- a/espanso-detect/src/event.rs
+++ b/espanso-detect/src/event.rs
@@ -63,6 +63,7 @@ pub struct KeyboardEvent {
pub value: Option,
pub status: Status,
pub variant: Option,
+ pub code: u32,
}
// A subset of the Web's key values: https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key/Key_Values
diff --git a/espanso-detect/src/mac/mod.rs b/espanso-detect/src/mac/mod.rs
index ab997ee..979ce5a 100644
--- a/espanso-detect/src/mac/mod.rs
+++ b/espanso-detect/src/mac/mod.rs
@@ -265,6 +265,7 @@ impl From for Option {
value,
status,
variant,
+ code: raw.key_code.try_into().expect("unable to convert keycode to u32"),
}));
}
// Mouse events
diff --git a/espanso-detect/src/win32/mod.rs b/espanso-detect/src/win32/mod.rs
index 0680cd6..18b413d 100644
--- a/espanso-detect/src/win32/mod.rs
+++ b/espanso-detect/src/win32/mod.rs
@@ -274,6 +274,7 @@ impl From for Option {
value,
status,
variant,
+ code: raw.key_code.try_into().expect("unable to convert keycode to u32"),
}));
}
// Mouse events
diff --git a/espanso-detect/src/x11/mod.rs b/espanso-detect/src/x11/mod.rs
index a147938..a5887d6 100644
--- a/espanso-detect/src/x11/mod.rs
+++ b/espanso-detect/src/x11/mod.rs
@@ -17,10 +17,7 @@
* along with espanso. If not, see .
*/
-use std::{
- collections::HashMap,
- ffi::{c_void, CStr},
-};
+use std::{collections::HashMap, convert::TryInto, ffi::{c_void, CStr}};
use lazycell::LazyCell;
use log::{debug, error, trace, warn};
@@ -309,6 +306,7 @@ fn convert_raw_input_event_to_input_event(
value,
status,
variant,
+ code: raw.key_code.try_into().expect("invalid keycode conversion to u32"),
}));
}
// Mouse events
@@ -436,6 +434,7 @@ mod tests {
raw.buffer_len = 1;
raw.status = INPUT_STATUS_RELEASED;
raw.key_sym = 0x4B;
+ raw.key_code = 1;
let result: Option = convert_raw_input_event_to_input_event(raw, &HashMap::new(), 0);
assert_eq!(
@@ -445,6 +444,7 @@ mod tests {
status: Released,
value: Some("k".to_string()),
variant: None,
+ code: 1,
})
);
}