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

518 lines
14 KiB
Rust
Raw Normal View History

2021-01-31 17:09:03 +00:00
/*
* 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/>.
*/
2021-03-15 18:08:08 +00:00
use std::{
collections::HashMap,
convert::TryInto,
ffi::{c_void, CStr},
};
2021-01-31 17:09:03 +00:00
use lazycell::LazyCell;
2021-03-14 20:53:17 +00:00
use log::{debug, error, trace, warn};
2021-01-31 17:09:03 +00:00
2021-02-08 16:16:35 +00:00
use anyhow::Result;
use thiserror::Error;
2021-03-14 20:53:17 +00:00
use crate::event::{HotKeyEvent, Key::*, MouseButton, MouseEvent};
2021-01-31 17:09:03 +00:00
use crate::event::{InputEvent, Key, KeyboardEvent, Variant};
2021-03-09 15:06:50 +00:00
use crate::{event::Status::*, Source, SourceCallback};
2021-03-14 20:53:17 +00:00
use crate::{event::Variant::*, hotkey::HotKey};
2021-01-31 17:09:03 +00:00
const INPUT_EVENT_TYPE_KEYBOARD: i32 = 1;
const INPUT_EVENT_TYPE_MOUSE: i32 = 2;
2021-03-14 20:53:17 +00:00
const INPUT_EVENT_TYPE_HOTKEY: i32 = 3;
2021-01-31 17:09:03 +00:00
const INPUT_STATUS_PRESSED: i32 = 1;
const INPUT_STATUS_RELEASED: i32 = 2;
const INPUT_MOUSE_LEFT_BUTTON: i32 = 1;
const INPUT_MOUSE_RIGHT_BUTTON: i32 = 3;
const INPUT_MOUSE_MIDDLE_BUTTON: i32 = 2;
const INPUT_MOUSE_BUTTON_1: i32 = 9;
const INPUT_MOUSE_BUTTON_2: i32 = 8;
// Take a look at the native.h header file for an explanation of the fields
#[repr(C)]
pub struct RawInputEvent {
pub event_type: i32,
pub buffer: [u8; 24],
pub buffer_len: i32,
pub key_sym: i32,
pub key_code: i32,
pub status: i32,
2021-03-14 20:53:17 +00:00
pub state: u32,
}
#[repr(C)]
#[derive(Debug, Clone, Copy)]
pub struct RawModifierIndexes {
pub ctrl: i32,
pub alt: i32,
pub shift: i32,
pub meta: i32,
}
#[repr(C)]
pub struct RawHotKeyRequest {
pub key_sym: u32,
pub ctrl: i32,
pub alt: i32,
pub shift: i32,
pub meta: i32,
}
#[repr(C)]
#[derive(Debug)]
pub struct RawHotKeyResult {
pub success: i32,
pub key_code: i32,
pub state: u32,
2021-01-31 17:09:03 +00:00
}
#[allow(improper_ctypes)]
#[link(name = "espansodetect", kind = "static")]
extern "C" {
pub fn detect_check_x11() -> i32;
pub fn detect_initialize(_self: *const X11Source, error_code: *mut i32) -> *mut c_void;
2021-03-14 20:53:17 +00:00
pub fn detect_get_modifier_indexes(context: *const c_void) -> RawModifierIndexes;
pub fn detect_register_hotkey(
context: *const c_void,
request: RawHotKeyRequest,
mod_indexes: RawModifierIndexes,
) -> RawHotKeyResult;
2021-01-31 17:09:03 +00:00
pub fn detect_eventloop(
2021-03-14 20:53:17 +00:00
context: *const c_void,
2021-01-31 17:09:03 +00:00
event_callback: extern "C" fn(_self: *mut X11Source, event: RawInputEvent),
) -> i32;
2021-03-14 20:53:17 +00:00
pub fn detect_destroy(context: *const c_void) -> i32;
2021-01-31 17:09:03 +00:00
}
pub struct X11Source {
handle: *mut c_void,
2021-02-14 21:01:42 +00:00
callback: LazyCell<SourceCallback>,
2021-03-14 20:53:17 +00:00
hotkeys: Vec<HotKey>,
raw_hotkey_mapping: HashMap<(i32, u32), i32>, // (key_code, state) -> hotkey ID
valid_modifiers_mask: u32,
2021-01-31 17:09:03 +00:00
}
#[allow(clippy::new_without_default)]
impl X11Source {
2021-03-14 20:53:17 +00:00
pub fn new(hotkeys: &[HotKey]) -> X11Source {
2021-01-31 17:09:03 +00:00
Self {
handle: std::ptr::null_mut(),
callback: LazyCell::new(),
2021-03-14 20:53:17 +00:00
hotkeys: hotkeys.to_vec(),
raw_hotkey_mapping: HashMap::new(),
valid_modifiers_mask: 0,
2021-01-31 17:09:03 +00:00
}
}
pub fn is_compatible() -> bool {
unsafe { detect_check_x11() != 0 }
}
2021-02-14 21:01:42 +00:00
}
impl Source for X11Source {
fn initialize(&mut self) -> Result<()> {
2021-01-31 17:09:03 +00:00
let mut error_code = 0;
let handle = unsafe { detect_initialize(self as *const X11Source, &mut error_code) };
if handle.is_null() {
2021-02-08 16:16:35 +00:00
let error = match error_code {
-1 => X11SourceError::DisplayFailure(),
-2 => X11SourceError::XRecordMissing(),
-3 => X11SourceError::XKeyboardMissing(),
-4 => X11SourceError::FailedRegistration("cannot initialize record range".to_owned()),
-5 => X11SourceError::FailedRegistration("cannot initialize XRecord context".to_owned()),
-6 => X11SourceError::FailedRegistration("cannot enable XRecord context".to_owned()),
_ => X11SourceError::Unknown(),
};
2021-02-08 20:23:28 +00:00
return Err(error.into());
2021-01-31 17:09:03 +00:00
}
2021-03-14 20:53:17 +00:00
let mod_indexes = unsafe { detect_get_modifier_indexes(handle) };
self.valid_modifiers_mask |= 1 << mod_indexes.ctrl;
self.valid_modifiers_mask |= 1 << mod_indexes.alt;
self.valid_modifiers_mask |= 1 << mod_indexes.meta;
self.valid_modifiers_mask |= 1 << mod_indexes.shift;
// Register the hotkeys
let raw_hotkey_mapping = &mut self.raw_hotkey_mapping;
self.hotkeys.iter().for_each(|hk| {
2021-10-06 17:17:59 +00:00
let raw = convert_hotkey_to_raw(hk);
2021-03-14 20:53:17 +00:00
if let Some(raw_hk) = raw {
let result = unsafe { detect_register_hotkey(handle, raw_hk, mod_indexes) };
if result.success == 0 {
error!("unable to register hotkey: {}", hk);
} else {
raw_hotkey_mapping.insert((result.key_code, result.state), hk.id);
debug!("registered hotkey: {}", hk);
}
} else {
error!("unable to generate raw hotkey mapping: {}", hk);
}
});
2021-01-31 17:09:03 +00:00
self.handle = handle;
2021-02-08 16:16:35 +00:00
Ok(())
2021-01-31 17:09:03 +00:00
}
2021-02-18 18:51:45 +00:00
fn eventloop(&self, event_callback: SourceCallback) -> Result<()> {
2021-01-31 17:09:03 +00:00
if self.handle.is_null() {
2021-02-18 18:51:45 +00:00
error!("Attempt to start X11Source eventloop without initialization");
return Err(X11SourceError::Internal().into());
2021-01-31 17:09:03 +00:00
}
if self.callback.fill(event_callback).is_err() {
2021-02-18 18:51:45 +00:00
error!("Unable to set X11Source event callback");
return Err(X11SourceError::Internal().into());
2021-01-31 17:09:03 +00:00
}
extern "C" fn callback(_self: *mut X11Source, event: RawInputEvent) {
2021-03-14 20:53:17 +00:00
let source_self = unsafe { &*_self };
let event: Option<InputEvent> = convert_raw_input_event_to_input_event(
event,
&source_self.raw_hotkey_mapping,
source_self.valid_modifiers_mask,
);
if let Some(callback) = source_self.callback.borrow() {
2021-01-31 17:09:03 +00:00
if let Some(event) = event {
callback(event)
} else {
trace!("Unable to convert raw event to input event");
}
}
}
let error_code = unsafe { detect_eventloop(self.handle, callback) };
if error_code <= 0 {
2021-02-18 18:51:45 +00:00
error!("X11Source eventloop returned a negative error code");
return Err(X11SourceError::Internal().into());
2021-01-31 17:09:03 +00:00
}
2021-02-18 18:51:45 +00:00
Ok(())
2021-01-31 17:09:03 +00:00
}
}
impl Drop for X11Source {
fn drop(&mut self) {
if self.handle.is_null() {
error!("X11Source destruction cannot be performed, handle is null");
return;
}
let result = unsafe { detect_destroy(self.handle) };
if result != 0 {
error!("X11Source destruction returned non-zero code");
}
}
}
2021-03-14 20:53:17 +00:00
fn convert_hotkey_to_raw(hk: &HotKey) -> Option<RawHotKeyRequest> {
let key_sym = hk.key.to_code()?;
Some(RawHotKeyRequest {
key_sym,
ctrl: if hk.has_ctrl() { 1 } else { 0 },
alt: if hk.has_alt() { 1 } else { 0 },
shift: if hk.has_shift() { 1 } else { 0 },
meta: if hk.has_meta() { 1 } else { 0 },
})
}
2021-02-08 16:16:35 +00:00
#[derive(Error, Debug)]
pub enum X11SourceError {
#[error("cannot open displays")]
DisplayFailure(),
#[error("X Record Extension is not installed")]
XRecordMissing(),
#[error("X Keyboard Extension is not installed")]
XKeyboardMissing(),
#[error("failed registration: ${0}")]
FailedRegistration(String),
#[error("unknown error")]
Unknown(),
2021-02-18 18:51:45 +00:00
#[error("internal error")]
Internal(),
2021-02-08 16:16:35 +00:00
}
2021-03-14 20:53:17 +00:00
fn convert_raw_input_event_to_input_event(
raw: RawInputEvent,
raw_hotkey_mapping: &HashMap<(i32, u32), i32>,
valid_modifiers_mask: u32,
) -> Option<InputEvent> {
let status = match raw.status {
INPUT_STATUS_RELEASED => Released,
INPUT_STATUS_PRESSED => Pressed,
_ => Pressed,
};
match raw.event_type {
// Keyboard events
INPUT_EVENT_TYPE_KEYBOARD => {
let (key, variant) = key_sym_to_key(raw.key_sym);
let value = if raw.buffer_len > 0 {
let raw_string_result =
CStr::from_bytes_with_nul(&raw.buffer[..((raw.buffer_len + 1) as usize)]);
match raw_string_result {
Ok(c_string) => {
let string_result = c_string.to_str();
match string_result {
Ok(value) => Some(value.to_string()),
Err(err) => {
warn!("char conversion error: {}", err);
None
2021-01-31 17:09:03 +00:00
}
}
}
2021-03-14 20:53:17 +00:00
Err(err) => {
warn!("Received malformed char: {}", err);
None
}
2021-01-31 17:09:03 +00:00
}
2021-03-14 20:53:17 +00:00
} else {
None
};
return Some(InputEvent::Keyboard(KeyboardEvent {
key,
value,
status,
variant,
2021-03-15 18:08:08 +00:00
code: raw
.key_code
.try_into()
.expect("invalid keycode conversion to u32"),
2021-03-14 20:53:17 +00:00
}));
2021-01-31 17:09:03 +00:00
}
2021-03-14 20:53:17 +00:00
// Mouse events
INPUT_EVENT_TYPE_MOUSE => {
let button = raw_to_mouse_button(raw.key_code);
2021-01-31 17:09:03 +00:00
2021-03-14 20:53:17 +00:00
if let Some(button) = button {
return Some(InputEvent::Mouse(MouseEvent { button, status }));
}
}
// Hotkey events
INPUT_EVENT_TYPE_HOTKEY => {
let state = raw.state & valid_modifiers_mask;
if let Some(id) = raw_hotkey_mapping.get(&(raw.key_code, state)) {
return Some(InputEvent::HotKey(HotKeyEvent { hotkey_id: *id }));
}
}
_ => {}
2021-01-31 17:09:03 +00:00
}
2021-03-14 20:53:17 +00:00
None
2021-01-31 17:09:03 +00:00
}
// Mappings from: https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key/Key_Values
// TODO: might need to add also the variants
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),
2021-02-09 16:12:16 +00:00
// UI keys
0xFF1B => (Escape, None),
2021-01-31 17:09:03 +00:00
// 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),
}
}
fn raw_to_mouse_button(raw: i32) -> Option<MouseButton> {
match raw {
INPUT_MOUSE_LEFT_BUTTON => Some(MouseButton::Left),
INPUT_MOUSE_RIGHT_BUTTON => Some(MouseButton::Right),
INPUT_MOUSE_MIDDLE_BUTTON => Some(MouseButton::Middle),
INPUT_MOUSE_BUTTON_1 => Some(MouseButton::Button1),
INPUT_MOUSE_BUTTON_2 => Some(MouseButton::Button2),
_ => None,
}
}
#[cfg(test)]
mod tests {
use std::ffi::CString;
2021-02-08 20:23:28 +00:00
use super::*;
2021-01-31 17:09:03 +00:00
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,
2021-03-14 20:53:17 +00:00
state: 0,
2021-01-31 17:09:03 +00:00
}
}
#[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;
raw.key_code = 1;
2021-01-31 17:09:03 +00:00
2021-03-15 18:08:08 +00:00
let result: Option<InputEvent> =
convert_raw_input_event_to_input_event(raw, &HashMap::new(), 0);
2021-01-31 17:09:03 +00:00
assert_eq!(
result.unwrap(),
InputEvent::Keyboard(KeyboardEvent {
key: Other(0x4B),
status: Released,
value: Some("k".to_string()),
variant: None,
code: 1,
2021-01-31 17:09:03 +00:00
})
);
}
#[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;
2021-03-15 18:08:08 +00:00
let result: Option<InputEvent> =
convert_raw_input_event_to_input_event(raw, &HashMap::new(), 0);
2021-01-31 17:09:03 +00:00
assert_eq!(
result.unwrap(),
InputEvent::Mouse(MouseEvent {
status: Released,
button: MouseButton::Right,
})
);
}
2021-03-14 20:53:17 +00:00
#[test]
fn raw_to_input_event_hotkey_works_correctly() {
let mut raw = default_raw_input_event();
raw.event_type = INPUT_EVENT_TYPE_HOTKEY;
raw.state = 0b00000011;
raw.key_code = 10;
let mut raw_hotkey_mapping = HashMap::new();
raw_hotkey_mapping.insert((10, 1), 20);
2021-03-15 18:08:08 +00:00
let result: Option<InputEvent> =
convert_raw_input_event_to_input_event(raw, &raw_hotkey_mapping, 1);
2021-03-14 20:53:17 +00:00
assert_eq!(
result.unwrap(),
2021-03-15 18:08:08 +00:00
InputEvent::HotKey(HotKeyEvent { hotkey_id: 20 })
2021-03-14 20:53:17 +00:00
);
}
2021-01-31 17:09:03 +00:00
#[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;
2021-03-15 18:08:08 +00:00
let result: Option<InputEvent> =
convert_raw_input_event_to_input_event(raw, &HashMap::new(), 0);
2021-01-31 17:09:03 +00:00
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;
2021-03-15 18:08:08 +00:00
let result: Option<InputEvent> =
convert_raw_input_event_to_input_event(raw, &HashMap::new(), 0);
2021-01-31 17:09:03 +00:00
assert!(result.is_none());
}
2021-02-08 20:23:28 +00:00
}