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)]
|
#[derive(Debug)]
|
||||||
pub struct RawKeyboardEvent {
|
pub struct RawKeyboardEvent {
|
||||||
pub sym: u32,
|
pub sym: u32,
|
||||||
|
pub code: u32,
|
||||||
pub value: String,
|
pub value: String,
|
||||||
pub state: i32,
|
pub state: i32,
|
||||||
}
|
}
|
||||||
|
@ -171,6 +172,7 @@ impl Device {
|
||||||
|
|
||||||
let event = RawKeyboardEvent {
|
let event = RawKeyboardEvent {
|
||||||
state: value,
|
state: value,
|
||||||
|
code: keycode,
|
||||||
sym,
|
sym,
|
||||||
value: content,
|
value: content,
|
||||||
};
|
};
|
||||||
|
|
|
@ -43,8 +43,6 @@ pub type xkb_state_component = u32;
|
||||||
|
|
||||||
pub const EV_KEY: u16 = 0x01;
|
pub const EV_KEY: u16 = 0x01;
|
||||||
|
|
||||||
pub const XKB_KEYCODE_INVALID: u32 = 0xffffffff;
|
|
||||||
|
|
||||||
#[link(name = "xkbcommon")]
|
#[link(name = "xkbcommon")]
|
||||||
extern "C" {
|
extern "C" {
|
||||||
pub fn xkb_state_unref(state: *mut xkb_state);
|
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_context_unref(context: *mut xkb_context);
|
||||||
pub fn xkb_state_get_keymap(state: *mut xkb_state) -> *mut xkb_keymap;
|
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_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(
|
pub fn xkb_state_update_key(
|
||||||
state: *mut xkb_state,
|
state: *mut xkb_state,
|
||||||
key: xkb_keycode_t,
|
key: xkb_keycode_t,
|
||||||
|
|
|
@ -17,52 +17,128 @@
|
||||||
* along with espanso. If not, see <https://www.gnu.org/licenses/>.
|
* along with espanso. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
use crate::{event::KeyboardEvent, hotkey::HotKey};
|
use log::error;
|
||||||
use std::{
|
|
||||||
collections::{HashMap, VecDeque},
|
use crate::{event::{KeyboardEvent, Status}, hotkey::HotKey};
|
||||||
time::Instant,
|
use std::{collections::HashMap, time::Instant};
|
||||||
|
|
||||||
|
use super::{
|
||||||
|
state::State,
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::state::State;
|
// Number of milliseconds that define how long the hotkey memory
|
||||||
|
// should retain pressed keys
|
||||||
// Number of milliseconds between the first and last codes of an hotkey
|
const HOTKEY_WINDOW_TIMEOUT: u128 = 5000;
|
||||||
// to be considered valid
|
|
||||||
const HOTKEY_WINDOW: i32 = 5000;
|
|
||||||
|
|
||||||
pub type KeySym = u32;
|
pub type KeySym = u32;
|
||||||
pub type KeyCode = u32;
|
pub type KeyCode = u32;
|
||||||
pub type SymMap = HashMap<KeySym, KeyCode>;
|
pub type HotkeyMemoryMap = Vec<(KeyCode, Instant)>;
|
||||||
|
|
||||||
pub type HotkeyMemoryMap = VecDeque<(KeyCode, Instant)>;
|
pub struct HotKeyFilter {
|
||||||
|
map: HashMap<KeySym, KeyCode>,
|
||||||
|
memory: HotkeyMemoryMap,
|
||||||
|
hotkey_raw_map: HashMap<i32, Vec<KeyCode>>,
|
||||||
|
}
|
||||||
|
|
||||||
pub fn generate_sym_map(state: &State) -> HashMap<KeySym, KeyCode> {
|
impl HotKeyFilter {
|
||||||
let mut map = HashMap::new();
|
pub fn new() -> Self {
|
||||||
for code in 0..256 {
|
Self {
|
||||||
if let Some(sym) = state.get_sym(code) {
|
map: HashMap::new(),
|
||||||
map.insert(sym, code);
|
memory: HotkeyMemoryMap::new(),
|
||||||
|
hotkey_raw_map: HashMap::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
map
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn convert_hotkey_to_codes(hk: &HotKey, map: &SymMap) -> Option<Vec<KeyCode>> {
|
pub fn initialize(&mut self, state: &State, hotkeys: &[HotKey]) {
|
||||||
let mut codes = Vec::new();
|
// First load the map
|
||||||
let key_code = map.get(&hk.key.to_code()?)?;
|
self.map = HashMap::new();
|
||||||
codes.push(*key_code);
|
for code in 0..256 {
|
||||||
|
if let Some(sym) = state.get_sym(code) {
|
||||||
|
self.map.insert(sym, code);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for modifier in hk.modifiers.iter() {
|
// Then the actual hotkeys
|
||||||
let code = map.get(&modifier.to_code()?)?;
|
self.hotkey_raw_map = hotkeys
|
||||||
codes.push(*code);
|
.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<i32> {
|
||||||
|
let mut hotkey = None;
|
||||||
|
let mut key_code = None;
|
||||||
|
|
||||||
pub fn detect_hotkey(
|
let mut to_be_removed = Vec::new();
|
||||||
event: &KeyboardEvent,
|
|
||||||
memory: &mut HotkeyMemoryMap,
|
if event.status == Status::Released {
|
||||||
hotkeys: &HashMap<i32, Vec<KeyCode>>,
|
// Remove from the memory all the key occurrences
|
||||||
) -> Option<i32> {
|
to_be_removed.extend(self.memory.iter().enumerate().filter_map(|(i, (code, _))| {
|
||||||
// TODO: implement the actual matching mechanism
|
if *code == event.code {
|
||||||
// We need to "clean" the old entries
|
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 = 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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,16 +23,15 @@
|
||||||
mod context;
|
mod context;
|
||||||
mod device;
|
mod device;
|
||||||
mod ffi;
|
mod ffi;
|
||||||
mod keymap;
|
|
||||||
mod hotkey;
|
mod hotkey;
|
||||||
|
mod keymap;
|
||||||
mod state;
|
mod state;
|
||||||
|
|
||||||
use std::collections::HashMap;
|
use std::{cell::RefCell};
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use context::Context;
|
use context::Context;
|
||||||
use device::{get_devices, Device};
|
use device::{get_devices, Device};
|
||||||
use hotkey::HotkeyMemoryMap;
|
|
||||||
use keymap::Keymap;
|
use keymap::Keymap;
|
||||||
use lazycell::LazyCell;
|
use lazycell::LazyCell;
|
||||||
use libc::{
|
use libc::{
|
||||||
|
@ -41,12 +40,12 @@ use libc::{
|
||||||
use log::{error, trace};
|
use log::{error, trace};
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
use crate::{event::Variant::*, hotkey::HotKey};
|
|
||||||
use crate::event::{InputEvent, Key, KeyboardEvent, Variant};
|
use crate::event::{InputEvent, Key, KeyboardEvent, Variant};
|
||||||
use crate::event::{Key::*, MouseButton, MouseEvent};
|
use crate::event::{Key::*, MouseButton, MouseEvent};
|
||||||
use crate::{event::Status::*, KeyboardConfig, Source, SourceCallback, SourceCreationOptions};
|
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_LEFT: u16 = 0x110;
|
||||||
const BTN_RIGHT: u16 = 0x111;
|
const BTN_RIGHT: u16 = 0x111;
|
||||||
|
@ -61,7 +60,7 @@ pub struct EVDEVSource {
|
||||||
_keyboard_rmlvo: Option<KeyboardConfig>,
|
_keyboard_rmlvo: Option<KeyboardConfig>,
|
||||||
_context: LazyCell<Context>,
|
_context: LazyCell<Context>,
|
||||||
_keymap: LazyCell<Keymap>,
|
_keymap: LazyCell<Keymap>,
|
||||||
_hotkey_codes: HashMap<i32, Vec<KeyCode>>,
|
_hotkey_filter: RefCell<HotKeyFilter>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(clippy::new_without_default)]
|
#[allow(clippy::new_without_default)]
|
||||||
|
@ -73,7 +72,7 @@ impl EVDEVSource {
|
||||||
_context: LazyCell::new(),
|
_context: LazyCell::new(),
|
||||||
_keymap: LazyCell::new(),
|
_keymap: LazyCell::new(),
|
||||||
_keyboard_rmlvo: options.evdev_keyboard_rmlvo,
|
_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
|
// Initialize the hotkeys
|
||||||
let state = State::new(&keymap)?;
|
let state = State::new(&keymap)?;
|
||||||
let sym_map = generate_sym_map(&state);
|
self._hotkey_filter.borrow_mut().initialize(&state, &self.hotkeys);
|
||||||
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() {
|
if self._context.fill(context).is_err() {
|
||||||
return Err(EVDEVSourceError::InitFailure().into());
|
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
|
// Read events indefinitely
|
||||||
let mut evs: [epoll_event; 16] = unsafe { std::mem::zeroed() };
|
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| {
|
events.into_iter().for_each(|raw_event| {
|
||||||
let event: Option<InputEvent> = raw_event.into();
|
let event: Option<InputEvent> = raw_event.into();
|
||||||
if let Some(event) = event {
|
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);
|
event_callback(event);
|
||||||
} else {
|
} else {
|
||||||
|
@ -233,6 +231,7 @@ impl From<RawInputEvent> for Option<InputEvent> {
|
||||||
value,
|
value,
|
||||||
status,
|
status,
|
||||||
variant,
|
variant,
|
||||||
|
code: keyboard_event.code,
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
RawInputEvent::Mouse(mouse_event) => {
|
RawInputEvent::Mouse(mouse_event) => {
|
||||||
|
@ -345,7 +344,8 @@ mod tests {
|
||||||
let raw = RawInputEvent::Keyboard(RawKeyboardEvent {
|
let raw = RawInputEvent::Keyboard(RawKeyboardEvent {
|
||||||
sym: 0x4B,
|
sym: 0x4B,
|
||||||
value: "k".to_owned(),
|
value: "k".to_owned(),
|
||||||
is_down: false,
|
state: KEY_STATE_RELEASE,
|
||||||
|
code: 0,
|
||||||
});
|
});
|
||||||
|
|
||||||
let result: Option<InputEvent> = raw.into();
|
let result: Option<InputEvent> = raw.into();
|
||||||
|
@ -356,6 +356,7 @@ mod tests {
|
||||||
status: Released,
|
status: Released,
|
||||||
value: Some("k".to_string()),
|
value: Some("k".to_string()),
|
||||||
variant: None,
|
variant: None,
|
||||||
|
code: 0,
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,6 @@
|
||||||
// This code is a port of the libxkbcommon "interactive-evdev.c" example
|
// This code is a port of the libxkbcommon "interactive-evdev.c" example
|
||||||
// https://github.com/xkbcommon/libxkbcommon/blob/master/tools/interactive-evdev.c
|
// https://github.com/xkbcommon/libxkbcommon/blob/master/tools/interactive-evdev.c
|
||||||
|
|
||||||
use std::ffi::CStr;
|
|
||||||
|
|
||||||
use scopeguard::ScopeGuard;
|
use scopeguard::ScopeGuard;
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
|
|
|
@ -63,6 +63,7 @@ pub struct KeyboardEvent {
|
||||||
pub value: Option<String>,
|
pub value: Option<String>,
|
||||||
pub status: Status,
|
pub status: Status,
|
||||||
pub variant: Option<Variant>,
|
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
|
// 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,
|
value,
|
||||||
status,
|
status,
|
||||||
variant,
|
variant,
|
||||||
|
code: raw.key_code.try_into().expect("unable to convert keycode to u32"),
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
// Mouse events
|
// Mouse events
|
||||||
|
|
|
@ -274,6 +274,7 @@ impl From<RawInputEvent> for Option<InputEvent> {
|
||||||
value,
|
value,
|
||||||
status,
|
status,
|
||||||
variant,
|
variant,
|
||||||
|
code: raw.key_code.try_into().expect("unable to convert keycode to u32"),
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
// Mouse events
|
// Mouse events
|
||||||
|
|
|
@ -17,10 +17,7 @@
|
||||||
* along with espanso. If not, see <https://www.gnu.org/licenses/>.
|
* along with espanso. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
use std::{
|
use std::{collections::HashMap, convert::TryInto, ffi::{c_void, CStr}};
|
||||||
collections::HashMap,
|
|
||||||
ffi::{c_void, CStr},
|
|
||||||
};
|
|
||||||
|
|
||||||
use lazycell::LazyCell;
|
use lazycell::LazyCell;
|
||||||
use log::{debug, error, trace, warn};
|
use log::{debug, error, trace, warn};
|
||||||
|
@ -309,6 +306,7 @@ fn convert_raw_input_event_to_input_event(
|
||||||
value,
|
value,
|
||||||
status,
|
status,
|
||||||
variant,
|
variant,
|
||||||
|
code: raw.key_code.try_into().expect("invalid keycode conversion to u32"),
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
// Mouse events
|
// Mouse events
|
||||||
|
@ -436,6 +434,7 @@ mod tests {
|
||||||
raw.buffer_len = 1;
|
raw.buffer_len = 1;
|
||||||
raw.status = INPUT_STATUS_RELEASED;
|
raw.status = INPUT_STATUS_RELEASED;
|
||||||
raw.key_sym = 0x4B;
|
raw.key_sym = 0x4B;
|
||||||
|
raw.key_code = 1;
|
||||||
|
|
||||||
let result: Option<InputEvent> = convert_raw_input_event_to_input_event(raw, &HashMap::new(), 0);
|
let result: Option<InputEvent> = convert_raw_input_event_to_input_event(raw, &HashMap::new(), 0);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
@ -445,6 +444,7 @@ mod tests {
|
||||||
status: Released,
|
status: Released,
|
||||||
value: Some("k".to_string()),
|
value: Some("k".to_string()),
|
||||||
variant: None,
|
variant: None,
|
||||||
|
code: 1,
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user