Finish implementation of hotkey detection on Wayland
This commit is contained in:
parent
b18cf1c153
commit
307599b761
|
@ -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,
|
||||
};
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -17,52 +17,128 @@
|
|||
* along with espanso. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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<KeySym, KeyCode>;
|
||||
pub type HotkeyMemoryMap = Vec<(KeyCode, Instant)>;
|
||||
|
||||
pub type HotkeyMemoryMap = VecDeque<(KeyCode, Instant)>;
|
||||
|
||||
pub fn generate_sym_map(state: &State) -> HashMap<KeySym, KeyCode> {
|
||||
let mut map = HashMap::new();
|
||||
for code in 0..256 {
|
||||
if let Some(sym) = state.get_sym(code) {
|
||||
map.insert(sym, code);
|
||||
}
|
||||
}
|
||||
map
|
||||
pub struct HotKeyFilter {
|
||||
map: HashMap<KeySym, KeyCode>,
|
||||
memory: HotkeyMemoryMap,
|
||||
hotkey_raw_map: HashMap<i32, Vec<KeyCode>>,
|
||||
}
|
||||
|
||||
pub fn convert_hotkey_to_codes(hk: &HotKey, map: &SymMap) -> Option<Vec<KeyCode>> {
|
||||
impl HotKeyFilter {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
map: HashMap::new(),
|
||||
memory: HotkeyMemoryMap::new(),
|
||||
hotkey_raw_map: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
// 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();
|
||||
}
|
||||
|
||||
pub fn process_event(
|
||||
&mut self,
|
||||
event: &KeyboardEvent,
|
||||
) -> Option<i32> {
|
||||
let mut hotkey = None;
|
||||
let mut key_code = None;
|
||||
|
||||
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<Vec<KeyCode>> {
|
||||
let mut codes = Vec::new();
|
||||
let key_code = map.get(&hk.key.to_code()?)?;
|
||||
let key_code = self.map.get(&hk.key.to_code()?)?;
|
||||
codes.push(*key_code);
|
||||
|
||||
for modifier in hk.modifiers.iter() {
|
||||
let code = map.get(&modifier.to_code()?)?;
|
||||
let code = self.map.get(&modifier.to_code()?)?;
|
||||
codes.push(*code);
|
||||
}
|
||||
|
||||
Some(codes)
|
||||
}
|
||||
|
||||
pub fn detect_hotkey(
|
||||
event: &KeyboardEvent,
|
||||
memory: &mut HotkeyMemoryMap,
|
||||
hotkeys: &HashMap<i32, Vec<KeyCode>>,
|
||||
) -> Option<i32> {
|
||||
// TODO: implement the actual matching mechanism
|
||||
// We need to "clean" the old entries
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<KeyboardConfig>,
|
||||
_context: LazyCell<Context>,
|
||||
_keymap: LazyCell<Keymap>,
|
||||
_hotkey_codes: HashMap<i32, Vec<KeyCode>>,
|
||||
_hotkey_filter: RefCell<HotKeyFilter>,
|
||||
}
|
||||
|
||||
#[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<InputEvent> = 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<RawInputEvent> for Option<InputEvent> {
|
|||
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<InputEvent> = raw.into();
|
||||
|
@ -356,6 +356,7 @@ mod tests {
|
|||
status: Released,
|
||||
value: Some("k".to_string()),
|
||||
variant: None,
|
||||
code: 0,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -63,6 +63,7 @@ pub struct KeyboardEvent {
|
|||
pub value: Option<String>,
|
||||
pub status: Status,
|
||||
pub variant: Option<Variant>,
|
||||
pub code: u32,
|
||||
}
|
||||
|
||||
// A subset of the Web's key values: https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key/Key_Values
|
||||
|
|
|
@ -265,6 +265,7 @@ impl From<RawInputEvent> for Option<InputEvent> {
|
|||
value,
|
||||
status,
|
||||
variant,
|
||||
code: raw.key_code.try_into().expect("unable to convert keycode to u32"),
|
||||
}));
|
||||
}
|
||||
// Mouse events
|
||||
|
|
|
@ -274,6 +274,7 @@ impl From<RawInputEvent> for Option<InputEvent> {
|
|||
value,
|
||||
status,
|
||||
variant,
|
||||
code: raw.key_code.try_into().expect("unable to convert keycode to u32"),
|
||||
}));
|
||||
}
|
||||
// Mouse events
|
||||
|
|
|
@ -17,10 +17,7 @@
|
|||
* along with espanso. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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<InputEvent> = 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,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user