First half of hotkeys detection on Wayland

This commit is contained in:
Federico Terzi 2021-03-15 12:26:22 +01:00
parent fbeca8b6e9
commit b18cf1c153
7 changed files with 172 additions and 18 deletions

View File

@ -23,9 +23,9 @@ use super::{
}; };
const EVDEV_OFFSET: i32 = 8; const EVDEV_OFFSET: i32 = 8;
const KEY_STATE_RELEASE: i32 = 0; pub const KEY_STATE_RELEASE: i32 = 0;
const KEY_STATE_PRESS: i32 = 1; pub const KEY_STATE_PRESS: i32 = 1;
const KEY_STATE_REPEAT: i32 = 2; pub const KEY_STATE_REPEAT: i32 = 2;
#[derive(Debug)] #[derive(Debug)]
pub enum RawInputEvent { pub enum RawInputEvent {
@ -37,7 +37,7 @@ pub enum RawInputEvent {
pub struct RawKeyboardEvent { pub struct RawKeyboardEvent {
pub sym: u32, pub sym: u32,
pub value: String, pub value: String,
pub is_down: bool, pub state: i32,
} }
#[derive(Debug)] #[derive(Debug)]
@ -170,7 +170,7 @@ impl Device {
let content = content_raw.to_string_lossy().to_string(); let content = content_raw.to_string_lossy().to_string();
let event = RawKeyboardEvent { let event = RawKeyboardEvent {
is_down, state: value,
sym, sym,
value: content, value: content,
}; };

View File

@ -43,6 +43,8 @@ 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);
@ -57,6 +59,7 @@ 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,

View File

@ -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 <https://www.gnu.org/licenses/>.
*/
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<KeySym, KeyCode>;
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 fn convert_hotkey_to_codes(hk: &HotKey, map: &SymMap) -> Option<Vec<KeyCode>> {
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<i32, Vec<KeyCode>>,
) -> Option<i32> {
// TODO: implement the actual matching mechanism
// We need to "clean" the old entries
}

View File

@ -10,13 +10,7 @@ use thiserror::Error;
use crate::KeyboardConfig; use crate::KeyboardConfig;
use super::{ use super::{context::Context, ffi::{XKB_KEYMAP_COMPILE_NO_FLAGS, xkb_keymap, xkb_keymap_new_from_names, xkb_keymap_unref, xkb_rule_names}};
context::Context,
ffi::{
xkb_keymap, xkb_keymap_new_from_names, xkb_keymap_unref, xkb_rule_names,
XKB_KEYMAP_COMPILE_NO_FLAGS,
},
};
pub struct Keymap { pub struct Keymap {
keymap: *mut xkb_keymap, keymap: *mut xkb_keymap,
@ -24,7 +18,7 @@ pub struct Keymap {
impl Keymap { impl Keymap {
pub fn new(context: &Context, rmlvo: Option<KeyboardConfig>) -> Result<Keymap> { pub fn new(context: &Context, rmlvo: Option<KeyboardConfig>) -> Result<Keymap> {
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 names_ptr = names.map_or(std::ptr::null(), |names| &names);
let raw_keymap = unsafe { let raw_keymap = unsafe {

View File

@ -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 // https://github.com/xkbcommon/libxkbcommon/blob/master/tools/interactive-evdev.c
/* /*
@ -24,10 +24,15 @@ mod context;
mod device; mod device;
mod ffi; mod ffi;
mod keymap; mod keymap;
mod hotkey;
mod state;
use std::collections::HashMap;
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::{
@ -36,12 +41,12 @@ use libc::{
use log::{error, trace}; use log::{error, trace};
use thiserror::Error; use thiserror::Error;
use crate::event::Variant::*; 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 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_LEFT: u16 = 0x110;
const BTN_RIGHT: u16 = 0x111; const BTN_RIGHT: u16 = 0x111;
@ -51,10 +56,12 @@ const BTN_EXTRA: u16 = 0x114;
pub struct EVDEVSource { pub struct EVDEVSource {
devices: Vec<Device>, devices: Vec<Device>,
hotkeys: Vec<HotKey>,
_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>>,
} }
#[allow(clippy::new_without_default)] #[allow(clippy::new_without_default)]
@ -62,9 +69,11 @@ impl EVDEVSource {
pub fn new(options: SourceCreationOptions) -> EVDEVSource { pub fn new(options: SourceCreationOptions) -> EVDEVSource {
Self { Self {
devices: Vec::new(), devices: Vec::new(),
hotkeys: options.hotkeys,
_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(),
} }
} }
} }
@ -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() { if self._context.fill(context).is_err() {
return Err(EVDEVSourceError::InitFailure().into()); return Err(EVDEVSourceError::InitFailure().into());
} }
@ -133,6 +153,8 @@ impl Source for EVDEVSource {
} }
} }
let mut hotkey_memory = HotkeyMemoryMap::new();
// 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() };
loop { loop {
@ -154,6 +176,9 @@ 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
event_callback(event); event_callback(event);
} else { } else {
trace!("unable to convert raw event to input event"); trace!("unable to convert raw event to input event");
@ -194,10 +219,13 @@ impl From<RawInputEvent> for Option<InputEvent> {
Some(keyboard_event.value) Some(keyboard_event.value)
}; };
let status = if keyboard_event.is_down { let status = if keyboard_event.state == KEY_STATE_PRESS {
Pressed Pressed
} else { } else if keyboard_event.state == KEY_STATE_RELEASE {
Released Released
} else {
// Filter out the "repeated" events
return None;
}; };
return Some(InputEvent::Keyboard(KeyboardEvent { return Some(InputEvent::Keyboard(KeyboardEvent {

View File

@ -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<State> {
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<u32> {
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(),
}

View File

@ -61,6 +61,7 @@ fn main() {
let handle = std::thread::spawn(move || { let handle = std::thread::spawn(move || {
let injector = get_injector(Default::default()).unwrap(); let injector = get_injector(Default::default()).unwrap();
let mut source = get_source(SourceCreationOptions { let mut source = get_source(SourceCreationOptions {
use_evdev: true,
hotkeys: vec![ hotkeys: vec![
HotKey::new(1, "OPTION+SPACE").unwrap(), HotKey::new(1, "OPTION+SPACE").unwrap(),
HotKey::new(2, "CTRL+OPTION+3").unwrap(), HotKey::new(2, "CTRL+OPTION+3").unwrap(),