// 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 . */ mod context; mod device; mod ffi; mod hotkey; mod keymap; mod state; mod sync; use std::cell::RefCell; use std::collections::HashMap; use anyhow::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, hotkeys: Vec, _keyboard_rmlvo: Option, _context: LazyCell, _keymap: LazyCell, _hotkey_filter: RefCell, _modifiers_map: HashMap, } #[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::() { 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()? { 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 = 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 for Option { fn from(raw: RawInputEvent) -> Option { 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) { 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 { 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 = 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 = raw.into(); assert_eq!( result.unwrap(), InputEvent::Mouse(MouseEvent { status: Released, button: MouseButton::Right, }) ); } }