diff --git a/espanso-detect/src/evdev/device.rs b/espanso-detect/src/evdev/device.rs
index e07e730..61763f3 100644
--- a/espanso-detect/src/evdev/device.rs
+++ b/espanso-detect/src/evdev/device.rs
@@ -23,9 +23,9 @@ use super::{
};
const EVDEV_OFFSET: i32 = 8;
-const KEY_STATE_RELEASE: i32 = 0;
-const KEY_STATE_PRESS: i32 = 1;
-const KEY_STATE_REPEAT: i32 = 2;
+pub const KEY_STATE_RELEASE: i32 = 0;
+pub const KEY_STATE_PRESS: i32 = 1;
+pub const KEY_STATE_REPEAT: i32 = 2;
#[derive(Debug)]
pub enum RawInputEvent {
@@ -37,7 +37,7 @@ pub enum RawInputEvent {
pub struct RawKeyboardEvent {
pub sym: u32,
pub value: String,
- pub is_down: bool,
+ pub state: i32,
}
#[derive(Debug)]
@@ -170,7 +170,7 @@ impl Device {
let content = content_raw.to_string_lossy().to_string();
let event = RawKeyboardEvent {
- is_down,
+ state: value,
sym,
value: content,
};
diff --git a/espanso-detect/src/evdev/ffi.rs b/espanso-detect/src/evdev/ffi.rs
index e242ca5..31bf665 100644
--- a/espanso-detect/src/evdev/ffi.rs
+++ b/espanso-detect/src/evdev/ffi.rs
@@ -43,6 +43,8 @@ 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);
@@ -57,6 +59,7 @@ 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
new file mode 100644
index 0000000..2549efe
--- /dev/null
+++ b/espanso-detect/src/evdev/hotkey.rs
@@ -0,0 +1,68 @@
+/*
+ * This file is part of espanso.
+ *
+ * Copyright (C) 2019-2021 Federico Terzi
+ *
+ * espanso is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * espanso is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with espanso. If not, see .
+ */
+
+use crate::{event::KeyboardEvent, hotkey::HotKey};
+use std::{
+ collections::{HashMap, VecDeque},
+ time::Instant,
+};
+
+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;
+
+pub type KeySym = u32;
+pub type KeyCode = u32;
+pub type SymMap = HashMap;
+
+pub type HotkeyMemoryMap = VecDeque<(KeyCode, Instant)>;
+
+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);
+ }
+ }
+ 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);
+
+ for modifier in hk.modifiers.iter() {
+ let code = map.get(&modifier.to_code()?)?;
+ codes.push(*code);
+ }
+
+ Some(codes)
+}
+
+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
+}
diff --git a/espanso-detect/src/evdev/keymap.rs b/espanso-detect/src/evdev/keymap.rs
index bb2f266..89d1c72 100644
--- a/espanso-detect/src/evdev/keymap.rs
+++ b/espanso-detect/src/evdev/keymap.rs
@@ -10,13 +10,7 @@ use thiserror::Error;
use crate::KeyboardConfig;
-use super::{
- context::Context,
- ffi::{
- xkb_keymap, xkb_keymap_new_from_names, xkb_keymap_unref, xkb_rule_names,
- XKB_KEYMAP_COMPILE_NO_FLAGS,
- },
-};
+use super::{context::Context, ffi::{XKB_KEYMAP_COMPILE_NO_FLAGS, xkb_keymap, xkb_keymap_new_from_names, xkb_keymap_unref, xkb_rule_names}};
pub struct Keymap {
keymap: *mut xkb_keymap,
@@ -24,7 +18,7 @@ pub struct Keymap {
impl Keymap {
pub fn new(context: &Context, rmlvo: Option) -> Result {
- let names = rmlvo.map(|rmlvo| Self::generate_names(rmlvo));
+ let names = rmlvo.map(Self::generate_names);
let names_ptr = names.map_or(std::ptr::null(), |names| &names);
let raw_keymap = unsafe {
diff --git a/espanso-detect/src/evdev/mod.rs b/espanso-detect/src/evdev/mod.rs
index 54814f8..01550c5 100644
--- a/espanso-detect/src/evdev/mod.rs
+++ b/espanso-detect/src/evdev/mod.rs
@@ -1,4 +1,4 @@
-// This code is a port of the libxkbcommon "interactive-evdev.c" example
+// This code is heavily inspired by the libxkbcommon "interactive-evdev.c" example
// https://github.com/xkbcommon/libxkbcommon/blob/master/tools/interactive-evdev.c
/*
@@ -24,10 +24,15 @@ mod context;
mod device;
mod ffi;
mod keymap;
+mod hotkey;
+mod state;
+
+use std::collections::HashMap;
use anyhow::Result;
use context::Context;
use device::{get_devices, Device};
+use hotkey::HotkeyMemoryMap;
use keymap::Keymap;
use lazycell::LazyCell;
use libc::{
@@ -36,12 +41,12 @@ use libc::{
use log::{error, trace};
use thiserror::Error;
-use crate::event::Variant::*;
+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 self::device::{DeviceError, RawInputEvent};
+use self::{device::{DeviceError, KEY_STATE_PRESS, KEY_STATE_RELEASE, RawInputEvent}, hotkey::{KeyCode, convert_hotkey_to_codes, generate_sym_map}, state::State};
const BTN_LEFT: u16 = 0x110;
const BTN_RIGHT: u16 = 0x111;
@@ -51,10 +56,12 @@ const BTN_EXTRA: u16 = 0x114;
pub struct EVDEVSource {
devices: Vec,
+ hotkeys: Vec,
_keyboard_rmlvo: Option,
_context: LazyCell,
_keymap: LazyCell,
+ _hotkey_codes: HashMap>,
}
#[allow(clippy::new_without_default)]
@@ -62,9 +69,11 @@ impl EVDEVSource {
pub fn new(options: SourceCreationOptions) -> EVDEVSource {
Self {
devices: Vec::new(),
+ hotkeys: options.hotkeys,
_context: LazyCell::new(),
_keymap: LazyCell::new(),
_keyboard_rmlvo: options.evdev_keyboard_rmlvo,
+ _hotkey_codes: HashMap::new(),
}
}
}
@@ -91,6 +100,17 @@ 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();
+
if self._context.fill(context).is_err() {
return Err(EVDEVSourceError::InitFailure().into());
}
@@ -133,6 +153,8 @@ impl Source for EVDEVSource {
}
}
+ let mut hotkey_memory = HotkeyMemoryMap::new();
+
// Read events indefinitely
let mut evs: [epoll_event; 16] = unsafe { std::mem::zeroed() };
loop {
@@ -154,6 +176,9 @@ 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
+
+
event_callback(event);
} else {
trace!("unable to convert raw event to input event");
@@ -194,10 +219,13 @@ impl From for Option {
Some(keyboard_event.value)
};
- let status = if keyboard_event.is_down {
+ let status = if keyboard_event.state == KEY_STATE_PRESS {
Pressed
- } else {
+ } else if keyboard_event.state == KEY_STATE_RELEASE {
Released
+ } else {
+ // Filter out the "repeated" events
+ return None;
};
return Some(InputEvent::Keyboard(KeyboardEvent {
diff --git a/espanso-detect/src/evdev/state.rs b/espanso-detect/src/evdev/state.rs
new file mode 100644
index 0000000..5de799b
--- /dev/null
+++ b/espanso-detect/src/evdev/state.rs
@@ -0,0 +1,60 @@
+// 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;
+use thiserror::Error;
+
+use super::{
+ ffi::{
+ xkb_state, xkb_state_key_get_one_sym, xkb_state_new, xkb_state_unref,
+ },
+ keymap::Keymap,
+};
+
+pub struct State {
+ state: *mut xkb_state,
+}
+
+impl State {
+ pub fn new(keymap: &Keymap) -> Result {
+ let raw_state = unsafe { xkb_state_new(keymap.get_handle()) };
+ let state = scopeguard::guard(raw_state, |raw_state| unsafe {
+ xkb_state_unref(raw_state);
+ });
+
+ if raw_state.is_null() {
+ return Err(StateError::FailedCreation().into());
+ }
+
+ Ok(Self {
+ state: ScopeGuard::into_inner(state),
+ })
+ }
+
+ pub fn get_sym(&self, code: u32) -> Option {
+ let sym = unsafe { xkb_state_key_get_one_sym(self.state, code) };
+ if sym == 0 {
+ None
+ } else {
+ Some(sym)
+ }
+ }
+}
+
+impl Drop for State {
+ fn drop(&mut self) {
+ unsafe {
+ xkb_state_unref(self.state);
+ }
+ }
+}
+
+#[derive(Error, Debug)]
+pub enum StateError {
+ #[error("could not create xkb state")]
+ FailedCreation(),
+}
diff --git a/espanso/src/main.rs b/espanso/src/main.rs
index 89a233e..3c2bbd2 100644
--- a/espanso/src/main.rs
+++ b/espanso/src/main.rs
@@ -61,6 +61,7 @@ fn main() {
let handle = std::thread::spawn(move || {
let injector = get_injector(Default::default()).unwrap();
let mut source = get_source(SourceCreationOptions {
+ use_evdev: true,
hotkeys: vec![
HotKey::new(1, "OPTION+SPACE").unwrap(),
HotKey::new(2, "CTRL+OPTION+3").unwrap(),