First half of hotkeys detection on Wayland
This commit is contained in:
parent
fbeca8b6e9
commit
b18cf1c153
|
@ -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,
|
||||
};
|
||||
|
|
|
@ -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,
|
||||
|
|
68
espanso-detect/src/evdev/hotkey.rs
Normal file
68
espanso-detect/src/evdev/hotkey.rs
Normal 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
|
||||
}
|
|
@ -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<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 raw_keymap = unsafe {
|
||||
|
|
|
@ -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<Device>,
|
||||
hotkeys: Vec<HotKey>,
|
||||
|
||||
_keyboard_rmlvo: Option<KeyboardConfig>,
|
||||
_context: LazyCell<Context>,
|
||||
_keymap: LazyCell<Keymap>,
|
||||
_hotkey_codes: HashMap<i32, Vec<KeyCode>>,
|
||||
}
|
||||
|
||||
#[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<InputEvent> = 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<RawInputEvent> for Option<InputEvent> {
|
|||
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 {
|
||||
|
|
60
espanso-detect/src/evdev/state.rs
Normal file
60
espanso-detect/src/evdev/state.rs
Normal 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(),
|
||||
}
|
|
@ -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(),
|
||||
|
|
Loading…
Reference in New Issue
Block a user