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 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,
|
||||||
};
|
};
|
||||||
|
|
|
@ -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,
|
||||||
|
|
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 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 {
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
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 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(),
|
||||||
|
|
Loading…
Reference in New Issue
Block a user