334 lines
9.3 KiB
Rust
334 lines
9.3 KiB
Rust
|
// This code is a port of 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 keymap;
|
||
|
|
||
|
use context::Context;
|
||
|
use device::{get_devices, Device};
|
||
|
use keymap::Keymap;
|
||
|
use libc::{
|
||
|
__errno_location, close, epoll_ctl, epoll_event, epoll_wait, EINTR, EPOLLIN, EPOLL_CTL_ADD,
|
||
|
};
|
||
|
use log::{error, trace};
|
||
|
|
||
|
use crate::event::Status::*;
|
||
|
use crate::event::Variant::*;
|
||
|
use crate::event::{InputEvent, Key, KeyboardEvent, Variant};
|
||
|
use crate::event::{Key::*, MouseButton, MouseEvent};
|
||
|
|
||
|
use self::device::{DeviceError, RawInputEvent};
|
||
|
|
||
|
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;
|
||
|
|
||
|
pub type EVDEVSourceCallback = Box<dyn Fn(InputEvent)>;
|
||
|
pub struct EVDEVSource {
|
||
|
devices: Vec<Device>,
|
||
|
}
|
||
|
|
||
|
#[allow(clippy::new_without_default)]
|
||
|
impl EVDEVSource {
|
||
|
pub fn new() -> EVDEVSource {
|
||
|
Self {
|
||
|
devices: Vec::new(),
|
||
|
}
|
||
|
}
|
||
|
|
||
|
pub fn initialize(&mut self) {
|
||
|
let context = Context::new().expect("unable to obtain xkb context");
|
||
|
let keymap = Keymap::new(&context).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"
|
||
|
);
|
||
|
}
|
||
|
}
|
||
|
panic!("error when initilizing EVDEV source {}", error);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
pub fn eventloop(&self, event_callback: EVDEVSourceCallback) {
|
||
|
if self.devices.is_empty() {
|
||
|
panic!("can't start eventloop without evdev devices");
|
||
|
}
|
||
|
|
||
|
let raw_epfd = unsafe { libc::epoll_create1(0) };
|
||
|
let epfd = scopeguard::guard(raw_epfd, |raw_epfd| unsafe {
|
||
|
close(raw_epfd);
|
||
|
});
|
||
|
|
||
|
if *epfd < 0 {
|
||
|
panic!("could not create epoll instance");
|
||
|
}
|
||
|
|
||
|
// 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 {
|
||
|
panic!(format!(
|
||
|
"Could not add {} to epoll, errno {}",
|
||
|
device.get_path(),
|
||
|
unsafe { *errno_ptr }
|
||
|
));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// 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 {
|
||
|
panic!(format!("Could not poll for events, {}", unsafe {
|
||
|
*errno_ptr
|
||
|
}))
|
||
|
}
|
||
|
}
|
||
|
|
||
|
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 {
|
||
|
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),
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
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.is_down { Pressed } else { Released };
|
||
|
|
||
|
return Some(InputEvent::Keyboard(KeyboardEvent {
|
||
|
key,
|
||
|
value,
|
||
|
status,
|
||
|
variant,
|
||
|
}));
|
||
|
}
|
||
|
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),
|
||
|
|
||
|
// 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,
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* TODO convert tests
|
||
|
#[cfg(test)]
|
||
|
mod tests {
|
||
|
use std::ffi::CString;
|
||
|
|
||
|
use super::*;
|
||
|
|
||
|
fn default_raw_input_event() -> RawInputEvent {
|
||
|
RawInputEvent {
|
||
|
event_type: INPUT_EVENT_TYPE_KEYBOARD,
|
||
|
buffer: [0; 24],
|
||
|
buffer_len: 0,
|
||
|
key_code: 0,
|
||
|
key_sym: 0,
|
||
|
status: INPUT_STATUS_PRESSED,
|
||
|
}
|
||
|
}
|
||
|
|
||
|
#[test]
|
||
|
fn raw_to_input_event_keyboard_works_correctly() {
|
||
|
let c_string = CString::new("k".to_string()).unwrap();
|
||
|
let mut buffer: [u8; 24] = [0; 24];
|
||
|
buffer[..1].copy_from_slice(c_string.as_bytes());
|
||
|
|
||
|
let mut raw = default_raw_input_event();
|
||
|
raw.buffer = buffer;
|
||
|
raw.buffer_len = 1;
|
||
|
raw.status = INPUT_STATUS_RELEASED;
|
||
|
raw.key_sym = 0x4B;
|
||
|
|
||
|
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,
|
||
|
})
|
||
|
);
|
||
|
}
|
||
|
|
||
|
#[test]
|
||
|
fn raw_to_input_event_mouse_works_correctly() {
|
||
|
let mut raw = default_raw_input_event();
|
||
|
raw.event_type = INPUT_EVENT_TYPE_MOUSE;
|
||
|
raw.status = INPUT_STATUS_RELEASED;
|
||
|
raw.key_code = INPUT_MOUSE_RIGHT_BUTTON;
|
||
|
|
||
|
let result: Option<InputEvent> = raw.into();
|
||
|
assert_eq!(
|
||
|
result.unwrap(),
|
||
|
InputEvent::Mouse(MouseEvent {
|
||
|
status: Released,
|
||
|
button: MouseButton::Right,
|
||
|
})
|
||
|
);
|
||
|
}
|
||
|
|
||
|
#[test]
|
||
|
fn raw_to_input_invalid_buffer() {
|
||
|
let buffer: [u8; 24] = [123; 24];
|
||
|
|
||
|
let mut raw = default_raw_input_event();
|
||
|
raw.buffer = buffer;
|
||
|
raw.buffer_len = 5;
|
||
|
|
||
|
let result: Option<InputEvent> = raw.into();
|
||
|
assert!(result.unwrap().into_keyboard().unwrap().value.is_none());
|
||
|
}
|
||
|
|
||
|
#[test]
|
||
|
fn raw_to_input_event_returns_none_when_missing_type() {
|
||
|
let mut raw = default_raw_input_event();
|
||
|
raw.event_type = 0;
|
||
|
let result: Option<InputEvent> = raw.into();
|
||
|
assert!(result.is_none());
|
||
|
}
|
||
|
}
|
||
|
|
||
|
*/
|