espanso/espanso-detect/src/evdev/mod.rs

423 lines
12 KiB
Rust

// This code is heavily inspired by the libxkbcommon "interactive-evdev.c" example
// https://github.com/xkbcommon/libxkbcommon/blob/master/tools/interactive-evdev.c
/*
* 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/>.
*/
mod context;
mod device;
mod ffi;
mod hotkey;
mod keymap;
mod state;
mod sync;
use std::cell::RefCell;
use std::collections::HashMap;
use anyhow::{Context as AnyhowContext, Result};
use context::Context;
use device::{get_devices, Device};
use keymap::Keymap;
use lazycell::LazyCell;
use libc::{
__errno_location, close, epoll_ctl, epoll_event, epoll_wait, EINTR, EPOLLIN, EPOLL_CTL_ADD,
};
use log::{debug, error, info, trace};
use thiserror::Error;
use crate::event::{InputEvent, Key, KeyboardEvent, Variant};
use crate::event::{Key::*, MouseButton, MouseEvent};
use crate::{event::HotKeyEvent, event::Variant::*, hotkey::HotKey};
use crate::{event::Status::*, KeyboardConfig, Source, SourceCallback, SourceCreationOptions};
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;
const BTN_MIDDLE: u16 = 0x112;
const BTN_SIDE: u16 = 0x113;
const BTN_EXTRA: u16 = 0x114;
// Offset between evdev keycodes (where KEY_ESCAPE is 1), and the evdev XKB
// keycode set (where ESC is 9).
const EVDEV_OFFSET: u32 = 8;
// List of modifier keycodes, as defined in the "input-event-codes.h" header
// TODO: create an option to override them if needed
const KEY_CTRL: u32 = 29;
const KEY_SHIFT: u32 = 42;
const KEY_ALT: u32 = 56;
const KEY_META: u32 = 125;
const KEY_CAPSLOCK: u32 = 58;
const KEY_NUMLOCK: u32 = 69;
pub struct EVDEVSource {
devices: Vec<Device>,
hotkeys: Vec<HotKey>,
_keyboard_rmlvo: Option<KeyboardConfig>,
_context: LazyCell<Context>,
_keymap: LazyCell<Keymap>,
_hotkey_filter: RefCell<HotKeyFilter>,
_modifiers_map: HashMap<String, u32>,
}
#[allow(clippy::new_without_default)]
impl EVDEVSource {
pub fn new(options: SourceCreationOptions) -> EVDEVSource {
let mut modifiers_map = HashMap::new();
modifiers_map.insert("ctrl".to_string(), KEY_CTRL + EVDEV_OFFSET);
modifiers_map.insert("shift".to_string(), KEY_SHIFT + EVDEV_OFFSET);
modifiers_map.insert("alt".to_string(), KEY_ALT + EVDEV_OFFSET);
modifiers_map.insert("meta".to_string(), KEY_META + EVDEV_OFFSET);
modifiers_map.insert("caps_lock".to_string(), KEY_CAPSLOCK + EVDEV_OFFSET);
modifiers_map.insert("num_lock".to_string(), KEY_NUMLOCK + EVDEV_OFFSET);
Self {
devices: Vec::new(),
hotkeys: options.hotkeys,
_context: LazyCell::new(),
_keymap: LazyCell::new(),
_keyboard_rmlvo: options.evdev_keyboard_rmlvo,
_hotkey_filter: RefCell::new(HotKeyFilter::new()),
_modifiers_map: modifiers_map,
}
}
}
impl Source for EVDEVSource {
fn initialize(&mut self) -> Result<()> {
let context = Context::new().expect("unable to obtain xkb context");
let keymap =
Keymap::new(&context, self._keyboard_rmlvo.clone()).expect("unable to create xkb keymap");
match get_devices(&keymap) {
Ok(devices) => self.devices = devices,
Err(error) => {
if let Some(device_error) = error.downcast_ref::<DeviceError>() {
if matches!(device_error, DeviceError::NoDevicesFound()) {
error!("Unable to open EVDEV devices, this usually has to do with permissions.");
error!(
"You can either add the current user to the 'input' group or run espanso as root"
);
return Err(EVDEVSourceError::PermissionDenied().into());
}
}
return Err(error);
}
}
let state = State::new(&keymap)?;
info!("Querying modifier status...");
if let Some(modifiers_state) =
sync::get_modifiers_state().context("EVDEV modifier context state synchronization")?
{
debug!("Updating device modifier state: {:?}", modifiers_state);
for device in &mut self.devices {
device.update_modifier_state(&modifiers_state, &self._modifiers_map);
}
}
// Initialize the hotkeys
self
._hotkey_filter
.borrow_mut()
.initialize(&state, &self.hotkeys);
if self._context.fill(context).is_err() {
return Err(EVDEVSourceError::InitFailure().into());
}
if self._keymap.fill(keymap).is_err() {
return Err(EVDEVSourceError::InitFailure().into());
}
Ok(())
}
fn eventloop(&self, event_callback: SourceCallback) -> Result<()> {
if self.devices.is_empty() {
error!("can't start eventloop without evdev devices");
return Err(EVDEVSourceError::NoDevices().into());
}
let raw_epfd = unsafe { libc::epoll_create1(0) };
let epfd = scopeguard::guard(raw_epfd, |raw_epfd| unsafe {
close(raw_epfd);
});
if *epfd < 0 {
error!("could not create epoll instance");
return Err(EVDEVSourceError::Internal().into());
}
// Setup epoll for all input devices
let errno_ptr = unsafe { __errno_location() };
for (i, device) in self.devices.iter().enumerate() {
let mut ev: epoll_event = unsafe { std::mem::zeroed() };
ev.events = EPOLLIN as u32;
ev.u64 = i as u64;
if unsafe { epoll_ctl(*epfd, EPOLL_CTL_ADD, device.get_raw_fd(), &mut ev) } != 0 {
error!(
"Could not add {} to epoll, errno {}",
device.get_path(),
unsafe { *errno_ptr }
);
return Err(EVDEVSourceError::Internal().into());
}
}
let mut hotkey_filter = self._hotkey_filter.borrow_mut();
// Read events indefinitely
let mut evs: [epoll_event; 16] = unsafe { std::mem::zeroed() };
loop {
let ret = unsafe { epoll_wait(*epfd, evs.as_mut_ptr(), 16, -1) };
if ret < 0 {
if unsafe { *errno_ptr } == EINTR {
continue;
} else {
error!("Could not poll for events, {}", unsafe { *errno_ptr });
return Err(EVDEVSourceError::Internal().into());
}
}
for ev in evs.iter() {
let device = &self.devices[ev.u64 as usize];
match device.read() {
Ok(events) if !events.is_empty() => {
// Convert raw events to the common format and invoke the callback
events.into_iter().for_each(|raw_event| {
let event: Option<InputEvent> = raw_event.into();
if let Some(event) = event {
// 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 {
trace!("unable to convert raw event to input event");
}
});
}
Ok(_) => { /* SKIP EMPTY */ }
Err(err) => error!("Can't read from device {}: {}", device.get_path(), err),
}
}
}
}
}
#[derive(Error, Debug)]
pub enum EVDEVSourceError {
#[error("initialization failed")]
InitFailure(),
#[error("permission denied")]
PermissionDenied(),
#[error("no devices")]
NoDevices(),
#[error("internal error")]
Internal(),
}
impl From<RawInputEvent> for Option<InputEvent> {
fn from(raw: RawInputEvent) -> Option<InputEvent> {
match raw {
RawInputEvent::Keyboard(keyboard_event) => {
let (key, variant) = key_sym_to_key(keyboard_event.sym as i32);
let value = if keyboard_event.value.is_empty() {
None
} else {
Some(keyboard_event.value)
};
let status = if keyboard_event.state == KEY_STATE_PRESS {
Pressed
} else if keyboard_event.state == KEY_STATE_RELEASE {
Released
} else {
// Filter out the "repeated" events
return None;
};
return Some(InputEvent::Keyboard(KeyboardEvent {
key,
value,
status,
variant,
code: keyboard_event.code,
}));
}
RawInputEvent::Mouse(mouse_event) => {
let button = raw_to_mouse_button(mouse_event.code);
let status = if mouse_event.is_down {
Pressed
} else {
Released
};
if let Some(button) = button {
return Some(InputEvent::Mouse(MouseEvent { button, status }));
}
}
}
None
}
}
// Mappings from: https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key/Key_Values
fn key_sym_to_key(key_sym: i32) -> (Key, Option<Variant>) {
match key_sym {
// Modifiers
0xFFE9 => (Alt, Some(Left)),
0xFFEA => (Alt, Some(Right)),
0xFFE5 => (CapsLock, None),
0xFFE3 => (Control, Some(Left)),
0xFFE4 => (Control, Some(Right)),
0xFFE7 | 0xFFEB => (Meta, Some(Left)),
0xFFE8 | 0xFFEC => (Meta, Some(Right)),
0xFF7F => (NumLock, None),
0xFFE1 => (Shift, Some(Left)),
0xFFE2 => (Shift, Some(Right)),
// Whitespace
0xFF0D => (Enter, None),
0xFF09 => (Tab, None),
0x20 => (Space, None),
// Navigation
0xFF54 => (ArrowDown, None),
0xFF51 => (ArrowLeft, None),
0xFF53 => (ArrowRight, None),
0xFF52 => (ArrowUp, None),
0xFF57 => (End, None),
0xFF50 => (Home, None),
0xFF56 => (PageDown, None),
0xFF55 => (PageUp, None),
// UI
0xFF1B => (Escape, None),
// Editing keys
0xFF08 => (Backspace, None),
// Function keys
0xFFBE => (F1, None),
0xFFBF => (F2, None),
0xFFC0 => (F3, None),
0xFFC1 => (F4, None),
0xFFC2 => (F5, None),
0xFFC3 => (F6, None),
0xFFC4 => (F7, None),
0xFFC5 => (F8, None),
0xFFC6 => (F9, None),
0xFFC7 => (F10, None),
0xFFC8 => (F11, None),
0xFFC9 => (F12, None),
0xFFCA => (F13, None),
0xFFCB => (F14, None),
0xFFCC => (F15, None),
0xFFCD => (F16, None),
0xFFCE => (F17, None),
0xFFCF => (F18, None),
0xFFD0 => (F19, None),
0xFFD1 => (F20, None),
// Other keys, includes the raw code provided by the operating system
_ => (Other(key_sym), None),
}
}
// These codes can be found in the "input-event-codes.h" header file
fn raw_to_mouse_button(raw: u16) -> Option<MouseButton> {
match raw {
BTN_LEFT => Some(MouseButton::Left),
BTN_RIGHT => Some(MouseButton::Right),
BTN_MIDDLE => Some(MouseButton::Middle),
BTN_SIDE => Some(MouseButton::Button1),
BTN_EXTRA => Some(MouseButton::Button2),
_ => None,
}
}
#[cfg(test)]
mod tests {
use device::RawMouseEvent;
use crate::event::{InputEvent, Key::Other, KeyboardEvent};
use super::{
device::{RawInputEvent, RawKeyboardEvent},
*,
};
#[test]
fn raw_to_input_event_keyboard_works_correctly() {
let raw = RawInputEvent::Keyboard(RawKeyboardEvent {
sym: 0x4B,
value: "k".to_owned(),
state: KEY_STATE_RELEASE,
code: 0,
});
let result: Option<InputEvent> = raw.into();
assert_eq!(
result.unwrap(),
InputEvent::Keyboard(KeyboardEvent {
key: Other(0x4B),
status: Released,
value: Some("k".to_string()),
variant: None,
code: 0,
})
);
}
#[test]
fn raw_to_input_event_mouse_works_correctly() {
let raw = RawInputEvent::Mouse(RawMouseEvent {
code: BTN_RIGHT,
is_down: false,
});
let result: Option<InputEvent> = raw.into();
assert_eq!(
result.unwrap(),
InputEvent::Mouse(MouseEvent {
status: Released,
button: MouseButton::Right,
})
);
}
}