First draft of evdev inject backend

This commit is contained in:
Federico Terzi 2021-02-14 21:02:50 +01:00
parent ff6bfa20cb
commit a9d24d400d
20 changed files with 1046 additions and 30 deletions

16
Cargo.lock generated
View File

@ -125,6 +125,12 @@ dependencies = [
"winapi",
]
[[package]]
name = "either"
version = "1.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457"
[[package]]
name = "enum-as-inner"
version = "0.3.3"
@ -171,6 +177,7 @@ dependencies = [
"anyhow",
"cc",
"enum-as-inner",
"itertools",
"lazy_static",
"lazycell",
"libc",
@ -216,6 +223,15 @@ dependencies = [
"unicode-segmentation",
]
[[package]]
name = "itertools"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37d572918e350e82412fe766d24b15e6682fb2ed2bbe018280caa810397cb319"
dependencies = [
"either",
]
[[package]]
name = "itoa"
version = "0.4.7"

View File

@ -29,6 +29,7 @@ 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,
};
@ -51,6 +52,9 @@ const BTN_EXTRA: u16 = 0x114;
pub type EVDEVSourceCallback = Box<dyn Fn(InputEvent)>;
pub struct EVDEVSource {
devices: Vec<Device>,
_context: LazyCell<Context>,
_keymap: LazyCell<Keymap>,
}
#[allow(clippy::new_without_default)]
@ -58,12 +62,15 @@ impl EVDEVSource {
pub fn new() -> EVDEVSource {
Self {
devices: Vec::new(),
_context: LazyCell::new(),
_keymap: LazyCell::new(),
}
}
pub fn initialize(&mut self) -> Result<()> {
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) => {
@ -80,6 +87,13 @@ impl EVDEVSource {
}
}
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(())
}
@ -150,6 +164,9 @@ impl EVDEVSource {
#[derive(Error, Debug)]
pub enum EVDEVSourceError {
#[error("initialization failed")]
InitFailure(),
#[error("permission denied")]
PermissionDenied(),
}

View File

@ -19,6 +19,7 @@ widestring = "0.4.3"
[target.'cfg(target_os="linux")'.dependencies]
libc = "0.2.85"
scopeguard = "1.1.0"
itertools = "0.10.0"
[build-dependencies]
cc = "1.0.66"

View File

@ -39,19 +39,20 @@ fn cc_config() {
// println!("cargo:rerun-if-changed=src/x11/native.h");
// println!("cargo:rerun-if-changed=src/evdev/native.cpp");
// println!("cargo:rerun-if-changed=src/evdev/native.h");
println!("cargo:rerun-if-changed=src/evdev/native.h");
println!("cargo:rerun-if-changed=src/evdev/native.c");
// cc::Build::new()
// .cpp(true)
// .include("src/x11/native.h")
// .file("src/x11/native.cpp")
// .compile("espansoinject");
// cc::Build::new()
// .cpp(true)
// .include("src/evdev/native.h")
// .file("src/evdev/native.cpp")
// .compile("espansoinjectevdev");
cc::Build::new()
.include("src/evdev/native.h")
.file("src/evdev/native.c")
.compile("espansoinjectev");
println!("cargo:rustc-link-search=native=/usr/lib/x86_64-linux-gnu/");
// println!("cargo:rustc-link-lib=static=espansoinject");
// println!("cargo:rustc-link-lib=static=espansoinjectevdev");
println!("cargo:rustc-link-lib=static=espansoinjectev");
println!("cargo:rustc-link-lib=dylib=X11");
println!("cargo:rustc-link-lib=dylib=Xtst");
println!("cargo:rustc-link-lib=dylib=xkbcommon");

View File

@ -38,6 +38,8 @@ The available modifiers can be found in the https://github.com/torvalds/linux/bl
#define KEY_RIGHTMETA 126
#define KEY_RIGHTCTRL 97
#define KEY_RIGHTALT 100
#define KEY_CAPSLOCK 58
#define KEY_NUMLOCK 69
All these codes have to be added the EVDEV_OFFSET = 8

View File

@ -0,0 +1,47 @@
// This code is a port of the libxkbcommon "interactive-evdev.c" example
// https://github.com/xkbcommon/libxkbcommon/blob/master/tools/interactive-evdev.c
use scopeguard::ScopeGuard;
use super::ffi::{xkb_context, xkb_context_new, xkb_context_unref, XKB_CONTEXT_NO_FLAGS};
use anyhow::Result;
use thiserror::Error;
pub struct Context {
context: *mut xkb_context,
}
impl Context {
pub fn new() -> Result<Context> {
let raw_context = unsafe { xkb_context_new(XKB_CONTEXT_NO_FLAGS) };
let context = scopeguard::guard(raw_context, |raw_context| unsafe {
xkb_context_unref(raw_context);
});
if raw_context.is_null() {
return Err(ContextError::FailedCreation().into());
}
Ok(Self {
context: ScopeGuard::into_inner(context),
})
}
pub fn get_handle(&self) -> *mut xkb_context {
self.context
}
}
impl Drop for Context {
fn drop(&mut self) {
unsafe {
xkb_context_unref(self.context);
}
}
}
#[derive(Error, Debug)]
pub enum ContextError {
#[error("could not create xkb context")]
FailedCreation(),
}

View File

@ -0,0 +1,84 @@
// Bindings taken from: https://github.com/rtbo/xkbcommon-rs/blob/master/src/xkb/ffi.rs
use std::os::raw::c_int;
use libc::{c_char, c_uint, c_ulong};
#[allow(non_camel_case_types)]
pub enum xkb_context {}
#[allow(non_camel_case_types)]
pub enum xkb_state {}
#[allow(non_camel_case_types)]
pub enum xkb_keymap {}
#[allow(non_camel_case_types)]
pub type xkb_keycode_t = u32;
#[allow(non_camel_case_types)]
pub type xkb_keysym_t = u32;
#[repr(C)]
pub struct xkb_rule_names {
pub rules: *const c_char,
pub model: *const c_char,
pub layout: *const c_char,
pub variant: *const c_char,
pub options: *const c_char,
}
#[repr(C)]
pub enum xkb_key_direction {
UP,
DOWN,
}
#[allow(non_camel_case_types)]
pub type xkb_keymap_compile_flags = u32;
pub const XKB_KEYMAP_COMPILE_NO_FLAGS: u32 = 0;
#[allow(non_camel_case_types)]
pub type xkb_context_flags = u32;
pub const XKB_CONTEXT_NO_FLAGS: u32 = 0;
#[allow(non_camel_case_types)]
pub type xkb_state_component = u32;
pub const EV_KEY: u16 = 0x01;
#[link(name = "xkbcommon")]
extern "C" {
pub fn xkb_state_unref(state: *mut xkb_state);
pub fn xkb_state_new(keymap: *mut xkb_keymap) -> *mut xkb_state;
pub fn xkb_keymap_new_from_names(
context: *mut xkb_context,
names: *const xkb_rule_names,
flags: xkb_keymap_compile_flags,
) -> *mut xkb_keymap;
pub fn xkb_keymap_unref(keymap: *mut xkb_keymap);
pub fn xkb_context_new(flags: xkb_context_flags) -> *mut xkb_context;
pub fn xkb_context_unref(context: *mut xkb_context);
pub fn xkb_state_update_key(
state: *mut xkb_state,
key: xkb_keycode_t,
direction: xkb_key_direction,
) -> xkb_state_component;
pub fn xkb_state_key_get_utf8(
state: *mut xkb_state,
key: xkb_keycode_t,
buffer: *mut c_char,
size: usize,
) -> c_int;
pub fn xkb_state_key_get_one_sym(state: *mut xkb_state, key: xkb_keycode_t) -> xkb_keysym_t;
}
// These are used to retrieve constants from the C side.
// This is needed as those constants are defined with C macros,
// and converting them manually is error-prone.
#[link(name = "espansoinjectev", kind = "static")]
extern "C" {
pub fn ui_dev_create() -> c_ulong;
pub fn ui_dev_destroy() -> c_ulong;
pub fn ui_set_evbit() -> c_ulong;
pub fn ui_set_keybit() -> c_ulong;
pub fn setup_uinput_device(fd: c_int) -> c_int;
pub fn uinput_emit(fd: c_int, code: c_uint, pressed: c_int);
}

View File

@ -0,0 +1,86 @@
// 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::CString;
use scopeguard::ScopeGuard;
use anyhow::Result;
use thiserror::Error;
use crate::KeyboardConfig;
use super::{
context::Context,
ffi::{
xkb_keymap, xkb_keymap_new_from_names, xkb_keymap_unref, xkb_rule_names,
XKB_KEYMAP_COMPILE_NO_FLAGS,
},
};
pub struct Keymap {
keymap: *mut xkb_keymap,
}
impl Keymap {
pub fn new(context: &Context, rmlvo: Option<KeyboardConfig>) -> Result<Keymap> {
let names = rmlvo.map(|rmlvo| {
Self::generate_names(rmlvo)
});
let names_ptr = names.map_or(std::ptr::null(), |names| &names);
let raw_keymap = unsafe {
xkb_keymap_new_from_names(
context.get_handle(),
names_ptr,
XKB_KEYMAP_COMPILE_NO_FLAGS,
)
};
let keymap = scopeguard::guard(raw_keymap, |raw_keymap| unsafe {
xkb_keymap_unref(raw_keymap);
});
if raw_keymap.is_null() {
return Err(KeymapError::FailedCreation().into());
}
Ok(Self {
keymap: ScopeGuard::into_inner(keymap),
})
}
pub fn get_handle(&self) -> *mut xkb_keymap {
self.keymap
}
fn generate_names(rmlvo: KeyboardConfig) -> xkb_rule_names {
let rules = rmlvo.rules.map(|s| { CString::new(s).expect("unable to create CString for keymap") });
let model = rmlvo.model.map(|s| { CString::new(s).expect("unable to create CString for keymap") });
let layout = rmlvo.layout.map(|s| { CString::new(s).expect("unable to create CString for keymap") });
let variant = rmlvo.variant.map(|s| { CString::new(s).expect("unable to create CString for keymap") });
let options = rmlvo.options.map(|s| { CString::new(s).expect("unable to create CString for keymap") });
xkb_rule_names {
rules: rules.map_or(std::ptr::null(), |s| s.as_ptr()),
model: model.map_or(std::ptr::null(), |s| s.as_ptr()),
layout: layout.map_or(std::ptr::null(), |s| s.as_ptr()),
variant: variant.map_or(std::ptr::null(), |s| s.as_ptr()),
options: options.map_or(std::ptr::null(), |s| s.as_ptr()),
}
}
}
impl Drop for Keymap {
fn drop(&mut self) {
unsafe {
xkb_keymap_unref(self.keymap);
}
}
}
#[derive(Error, Debug)]
pub enum KeymapError {
#[error("could not create xkb keymap")]
FailedCreation(),
}

View File

@ -0,0 +1,332 @@
/*
* 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 ffi;
mod keymap;
mod state;
mod uinput;
use std::{
collections::{HashMap, HashSet},
ffi::{CString},
};
use context::Context;
use keymap::Keymap;
use log::error;
use std::iter::FromIterator;
use uinput::UInputDevice;
use crate::{
linux::raw_keys::{convert_to_sym_array},
InjectorCreationOptions,
};
use anyhow::Result;
use itertools::Itertools;
use thiserror::Error;
use crate::{keys, InjectionOptions, Injector};
use self::state::State;
// 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
// These can be overridden by changing the "evdev_modifier" option during initialization
const KEY_LEFTCTRL: u32 = 29;
const KEY_LEFTSHIFT: u32 = 42;
const KEY_RIGHTSHIFT: u32 = 54;
const KEY_LEFTALT: u32 = 56;
const KEY_LEFTMETA: u32 = 125;
const KEY_RIGHTMETA: u32 = 126;
const KEY_RIGHTCTRL: u32 = 97;
const KEY_RIGHTALT: u32 = 100;
const KEY_CAPSLOCK: u32 = 58;
const KEY_NUMLOCK: u32 = 69;
const DEFAULT_MODIFIERS: [u32; 10] = [
KEY_LEFTCTRL,
KEY_LEFTSHIFT,
KEY_RIGHTSHIFT,
KEY_LEFTALT,
KEY_LEFTMETA,
KEY_RIGHTMETA,
KEY_RIGHTCTRL,
KEY_RIGHTALT,
KEY_CAPSLOCK,
KEY_NUMLOCK,
];
const DEFAULT_MAX_MODIFIER_COMBINATION_LEN: i32 = 3;
pub type KeySym = u32;
#[derive(Clone, Debug)]
struct KeyRecord {
// Keycode
code: u32,
// List of modifiers that must be pressed
modifiers: Vec<u32>,
}
type CharMap = HashMap<String, KeyRecord>;
type SymMap = HashMap<KeySym, KeyRecord>;
pub struct EVDEVInjector {
device: UInputDevice,
// Lookup maps
char_map: CharMap,
sym_map: SymMap,
// Ownership
_context: Context,
_keymap: Keymap,
}
#[allow(clippy::new_without_default)]
impl EVDEVInjector {
pub fn new(options: InjectorCreationOptions) -> Result<Self> {
let modifiers = options
.evdev_modifiers
.unwrap_or_else(|| DEFAULT_MODIFIERS.to_vec());
let max_modifier_combination_len = options
.evdev_max_modifier_combination_len
.unwrap_or(DEFAULT_MAX_MODIFIER_COMBINATION_LEN);
// Necessary to properly handle non-ascii chars
let empty_string = CString::new("")?;
unsafe {
libc::setlocale(libc::LC_ALL, empty_string.as_ptr());
}
let context = Context::new().expect("unable to obtain xkb context");
let keymap = Keymap::new(&context, options.evdev_keyboard_rmlvo).expect("unable to create xkb keymap");
let (char_map, sym_map) =
Self::generate_maps(&modifiers, max_modifier_combination_len, &keymap)?;
// Create the uinput virtual device
let device = UInputDevice::new()?;
Ok(Self {
device,
char_map,
sym_map,
_context: context,
_keymap: keymap,
})
}
fn generate_maps(
modifiers: &[u32],
max_modifier_sequence_len: i32,
keymap: &Keymap,
) -> Result<(CharMap, SymMap)> {
let mut char_map = HashMap::new();
let mut sym_map = HashMap::new();
let modifier_combinations = Self::generate_combinations(modifiers, max_modifier_sequence_len);
// Cycle through all code/modifiers combinations to populate the reverse lookup tables
for key_code in 8..256u32 {
for modifier_combination in modifier_combinations.iter() {
let state = State::new(keymap)?;
// Apply the modifiers
for modifier in modifier_combination.iter() {
// We need to add the EVDEV offset for xkbcommon to recognize it correctly
state.update_key(*modifier + EVDEV_OFFSET, true);
}
let key_record = KeyRecord {
code: key_code - EVDEV_OFFSET,
modifiers: modifier_combination.clone(),
};
// Keysym was found
if let Some(sym) = state.get_sym(key_code) {
sym_map.entry(sym).or_insert_with(|| key_record.clone());
}
// Char was found
if let Some(string) = state.get_string(key_code) {
char_map.entry(string).or_insert(key_record);
}
}
}
Ok((char_map, sym_map))
}
fn generate_combinations(modifiers: &[u32], max_modifier_sequence_len: i32) -> Vec<Vec<u32>> {
let mut combinations = vec![vec![]]; // Initial empty combination
for sequence_len in 1..=max_modifier_sequence_len {
let current_combinations = modifiers
.iter()
.cloned()
.combinations(sequence_len as usize);
combinations.extend(current_combinations);
}
combinations
}
fn convert_to_record_array(&self, syms: &[u64]) -> Result<Vec<KeyRecord>> {
syms
.iter()
.map(|sym| {
self
.sym_map
.get(&(*sym as u32))
.cloned()
.ok_or_else(|| EVDEVInjectorError::SymMappingFailure(*sym as u32).into())
})
.collect()
}
fn send_key(&self, code: u32, pressed: bool, delay_us: u32) {
self.device.emit(code, pressed);
if delay_us != 0 {
unsafe {
libc::usleep(delay_us);
}
}
}
}
impl Injector for EVDEVInjector {
fn send_string(&self, string: &str, options: InjectionOptions) -> Result<()> {
// Compute all the key record sequence first to make sure a mapping is available
let records: Result<Vec<KeyRecord>> = string
.chars()
.map(|c| c.to_string())
.map(|char| {
self
.char_map
.get(&char)
.cloned()
.ok_or_else(|| EVDEVInjectorError::CharMappingFailure(char).into())
})
.collect();
let delay_us = options.delay as u32 * 1000; // Convert to micro seconds
// We need to keep track of the modifiers currently pressed to
// press or release them accordingly
let mut current_modifiers: HashSet<u32> = HashSet::new();
for record in records? {
let record_modifiers = HashSet::from_iter(record.modifiers.iter().cloned());
// Release all the modifiers that are not needed anymore
for expired_modifier in current_modifiers.difference(&record_modifiers) {
self.send_key(*expired_modifier, false, delay_us);
}
// Press all the new modifiers that are now needed
for new_modifier in record_modifiers.difference(&current_modifiers) {
self.send_key(*new_modifier, true, delay_us);
}
// Send the char
self.send_key(record.code, true, delay_us);
self.send_key(record.code, false, delay_us);
current_modifiers = record_modifiers;
}
// Release all the remaining modifiers
for expired_modifier in current_modifiers {
self.send_key(expired_modifier, false, delay_us);
}
Ok(())
}
fn send_keys(&self, keys: &[keys::Key], options: InjectionOptions) -> Result<()> {
// Compute all the key record sequence first to make sure a mapping is available
let syms = convert_to_sym_array(keys)?;
let records = self.convert_to_record_array(&syms)?;
let delay_us = options.delay as u32 * 1000; // Convert to micro seconds
for record in records {
// Press the modifiers
for modifier in record.modifiers.iter() {
self.send_key(*modifier, true, delay_us);
}
// Send the key
self.send_key(record.code, true, delay_us);
self.send_key(record.code, false, delay_us);
// Release the modifiers
for modifier in record.modifiers.iter() {
self.send_key(*modifier, false, delay_us);
}
}
Ok(())
}
fn send_key_combination(&self, keys: &[keys::Key], options: InjectionOptions) -> Result<()> {
// Compute all the key record sequence first to make sure a mapping is available
let syms = convert_to_sym_array(keys)?;
let records = self.convert_to_record_array(&syms)?;
let delay_us = options.delay as u32 * 1000; // Convert to micro seconds
// First press the keys
for record in records.iter() {
// Press the modifiers
for modifier in record.modifiers.iter() {
self.send_key(*modifier, true, delay_us);
}
// Send the key
self.send_key(record.code, true, delay_us);
}
// Then release them
for record in records.iter().rev() {
self.send_key(record.code, false, delay_us);
// Release the modifiers
for modifier in record.modifiers.iter() {
self.send_key(*modifier, false, delay_us);
}
}
Ok(())
}
}
#[derive(Error, Debug)]
pub enum EVDEVInjectorError {
#[error("missing vkey mapping for char `{0}`")]
CharMappingFailure(String),
#[error("missing record mapping for sym `{0}`")]
SymMappingFailure(u32),
}

View File

@ -0,0 +1,73 @@
/*
* 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/>.
*/
#include "native.h"
#include <linux/uinput.h>
#include <memory.h>
unsigned long ui_dev_destroy()
{
return UI_DEV_DESTROY;
}
unsigned long ui_dev_create()
{
return UI_DEV_CREATE;
}
unsigned long ui_set_evbit()
{
return UI_SET_EVBIT;
}
unsigned long ui_set_keybit()
{
return UI_SET_KEYBIT;
}
int setup_uinput_device(int fd)
{
struct uinput_setup usetup;
memset(&usetup, 0, sizeof(usetup));
usetup.id.bustype = BUS_USB;
usetup.id.vendor = 0x1234; // sample vendor
usetup.id.product = 0x5678; // sample product
strcpy(usetup.name, "Espanso virtual device");
return ioctl(fd, UI_DEV_SETUP, &usetup);
}
void emit(int fd, int type, int code, int val)
{
struct input_event ie;
ie.type = type;
ie.code = code;
ie.value = val;
// timestamp values below are ignored
ie.time.tv_sec = 0;
ie.time.tv_usec = 0;
write(fd, &ie, sizeof(ie));
}
void uinput_emit(int fd, unsigned int code, int pressed) {
emit(fd, EV_KEY, code, pressed);
emit(fd, EV_SYN, SYN_REPORT, 0);
}

View File

@ -0,0 +1,31 @@
/*
* 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/>.
*/
#ifndef ESPANSO_INJECT_EV_CONSTANTS_H
#define ESPANSO_INJECT_EV_CONSTANTS_H
unsigned long ui_dev_destroy();
unsigned long ui_dev_create();
unsigned long ui_set_evbit();
unsigned long ui_set_keybit();
int setup_uinput_device(int fd);
void uinput_emit(int fd, unsigned int code, int pressed);
#endif //ESPANSO_INJECT_EV_CONSTANTS_H

View File

@ -0,0 +1,89 @@
// 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_key_get_utf8, xkb_state_new, xkb_state_unref, xkb_state_update_key}, 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 update_key(&self, code: u32, pressed: bool) {
let direction = if pressed {
super::ffi::xkb_key_direction::DOWN
} else {
super::ffi::xkb_key_direction::UP
};
unsafe {
xkb_state_update_key(self.state, code, direction);
}
}
pub fn get_string(&self, code: u32) -> Option<String> {
let mut buffer: [u8; 16] = [0; 16];
let len = unsafe {
xkb_state_key_get_utf8(
self.state,
code,
buffer.as_mut_ptr() as *mut i8,
std::mem::size_of_val(&buffer),
)
};
if len > 0 {
let content_raw = unsafe { CStr::from_ptr(buffer.as_ptr() as *mut i8) };
let string = content_raw.to_string_lossy().to_string();
if string.is_empty() {
None
} else {
Some(string)
}
} else {
None
}
}
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(),
}

View File

@ -0,0 +1,108 @@
/*
* 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 std::ffi::CString;
use libc::{c_uint, close, ioctl, open, O_NONBLOCK, O_WRONLY};
use scopeguard::ScopeGuard;
use anyhow::Result;
use thiserror::Error;
use super::ffi::{
setup_uinput_device, ui_dev_create, ui_dev_destroy, ui_set_evbit, ui_set_keybit, uinput_emit,
EV_KEY,
};
pub struct UInputDevice {
fd: i32,
}
impl UInputDevice {
pub fn new() -> Result<UInputDevice> {
let uinput_path = CString::new("/dev/uinput").expect("unable to generate /dev/uinput path");
let raw_fd = unsafe { open(uinput_path.as_ptr(), O_WRONLY | O_NONBLOCK) };
if raw_fd < 0 {
return Err(UInputDeviceError::OpenFailed().into());
}
let fd = scopeguard::guard(raw_fd, |raw_fd| unsafe {
close(raw_fd);
});
// Enable keyboard events
if unsafe { ioctl(*fd, ui_set_evbit(), EV_KEY as c_uint) } != 0 {
return Err(UInputDeviceError::KeyEVBitFailed().into());
}
// Register all keycodes
for key_code in 0..256 {
if unsafe { ioctl(*fd, ui_set_keybit(), key_code) } != 0 {
return Err(UInputDeviceError::KeyBitFailed().into());
}
}
// Register the virtual device
if unsafe { setup_uinput_device(*fd) } != 0 {
return Err(UInputDeviceError::DeviceSetupFailed().into());
}
// Create the device
if unsafe { ioctl(*fd, ui_dev_create()) } != 0 {
return Err(UInputDeviceError::DeviceCreateFailed().into());
}
Ok(Self {
fd: ScopeGuard::into_inner(fd),
})
}
pub fn emit(&self, key_code: u32, pressed: bool) {
let pressed = if pressed { 1 } else { 0 };
unsafe {
uinput_emit(self.fd, key_code, pressed);
}
}
}
impl Drop for UInputDevice {
fn drop(&mut self) {
unsafe {
ioctl(self.fd, ui_dev_destroy());
close(self.fd);
}
}
}
#[derive(Error, Debug)]
pub enum UInputDeviceError {
#[error("could not open uinput device")]
OpenFailed(),
#[error("could not set keyboard evbit")]
KeyEVBitFailed(),
#[error("could not set keyboard keybit")]
KeyBitFailed(),
#[error("could not register virtual device")]
DeviceSetupFailed(),
#[error("could not create uinput device")]
DeviceCreateFailed(),
}

View File

@ -1,3 +1,22 @@
/*
* 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 std::fmt::Display;
use regex::Regex;

View File

@ -27,8 +27,11 @@ mod win32;
#[cfg(target_os = "linux")]
mod x11;
//#[cfg(target_os = "linux")]
//mod evdev;
#[cfg(target_os = "linux")]
mod evdev;
#[cfg(target_os = "linux")]
mod linux;
#[cfg(target_os = "macos")]
mod mac;
@ -72,17 +75,42 @@ impl Default for InjectionOptions {
}
}
#[allow(dead_code)]
pub struct InjectorCreationOptions {
// Only relevant in Linux systems
use_evdev: bool,
// Overwrite the list of modifiers to be scanned when
// populating the evdev injector lookup maps
evdev_modifiers: Option<Vec<u32>>,
// Overwrite the maximum number of modifiers used tested in
// a single combination to populate the lookup maps
evdev_max_modifier_combination_len: Option<i32>,
// Can be used to overwrite the keymap configuration
// used by espanso to inject key presses.
evdev_keyboard_rmlvo: Option<KeyboardConfig>,
}
// This struct identifies the keyboard layout that
// should be used by EVDEV when loading the keymap.
// For more information: https://xkbcommon.org/doc/current/structxkb__rule__names.html
pub struct KeyboardConfig {
pub rules: Option<String>,
pub model: Option<String>,
pub layout: Option<String>,
pub variant: Option<String>,
pub options: Option<String>,
}
impl Default for InjectorCreationOptions {
fn default() -> Self {
Self {
use_evdev: false,
evdev_modifiers: None,
evdev_max_modifier_combination_len: None,
evdev_keyboard_rmlvo: None,
}
}
}
@ -100,6 +128,7 @@ pub fn get_injector(_options: InjectorOptions) -> impl Injector {
#[cfg(target_os = "linux")]
pub fn get_injector(options: InjectorCreationOptions) -> Result<impl Injector> {
// TODO: differenciate based on the options
x11::X11Injector::new()
//x11::X11Injector::new()
evdev::EVDEVInjector::new(options)
}

View File

@ -0,0 +1,20 @@
/*
* 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/>.
*/
pub mod raw_keys;

View File

@ -1,4 +1,25 @@
/*
* 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::keys::Key;
use anyhow::Result;
use thiserror::Error;
pub fn convert_key_to_sym(key: &Key) -> Option<u32> {
match key {
@ -104,3 +125,22 @@ pub fn convert_key_to_sym(key: &Key) -> Option<u32> {
Key::Raw(code) => Some(*code as u32),
}
}
pub fn convert_to_sym_array(keys: &[Key]) -> Result<Vec<u64>> {
let mut virtual_keys: Vec<u64> = Vec::new();
for key in keys.iter() {
let vk = convert_key_to_sym(key);
if let Some(vk) = vk {
virtual_keys.push(vk as u64)
} else {
return Err(LinuxRawKeyError::MappingFailure(key.clone()).into());
}
}
Ok(virtual_keys)
}
#[derive(Error, Debug)]
pub enum LinuxRawKeyError {
#[error("missing mapping for key `{0}`")]
MappingFailure(Key),
}

View File

@ -1,3 +1,22 @@
/*
* 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::keys::Key;
pub fn convert_key_to_vkey(key: &Key) -> Option<i32> {

View File

@ -1,3 +1,22 @@
/*
* 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::keys::Key;
pub fn convert_key_to_vkey(key: &Key) -> Option<i32> {

View File

@ -18,7 +18,6 @@
*/
mod ffi;
mod raw_keys;
use std::{
collections::HashMap,
@ -31,7 +30,7 @@ use ffi::{Display, KeyCode, KeyPress, KeyRelease, KeySym, Window, XCloseDisplay,
use log::error;
use anyhow::Result;
use raw_keys::convert_key_to_sym;
use crate::linux::raw_keys::{convert_key_to_sym, convert_to_sym_array};
use thiserror::Error;
use crate::{keys, InjectionOptions, Injector};
@ -146,19 +145,6 @@ impl X11Injector {
(char_map, sym_map)
}
fn convert_to_sym_array(keys: &[keys::Key]) -> Result<Vec<u64>> {
let mut virtual_keys: Vec<u64> = Vec::new();
for key in keys.iter() {
let vk = convert_key_to_sym(key);
if let Some(vk) = vk {
virtual_keys.push(vk as u64)
} else {
return Err(X11InjectorError::MappingFailure(key.clone()).into());
}
}
Ok(virtual_keys)
}
fn convert_to_record_array(&self, syms: &[KeySym]) -> Result<Vec<KeyRecord>> {
syms
.iter()
@ -371,7 +357,7 @@ impl Injector for X11Injector {
let focused_window = self.get_focused_window();
// Compute all the key record sequence first to make sure a mapping is available
let syms = Self::convert_to_sym_array(keys)?;
let syms = convert_to_sym_array(keys)?;
let records = self.convert_to_record_array(&syms)?;
if options.disable_fast_inject {
@ -397,7 +383,7 @@ impl Injector for X11Injector {
let focused_window = self.get_focused_window();
// Compute all the key record sequence first to make sure a mapping is available
let syms = Self::convert_to_sym_array(keys)?;
let syms = convert_to_sym_array(keys)?;
let records = self.convert_to_record_array(&syms)?;
// Render the correct modifier mask for the given sequence
@ -439,9 +425,6 @@ pub enum X11InjectorError {
#[error("missing vkey mapping for char `{0}`")]
CharMappingFailure(String),
#[error("missing sym mapping for key `{0}`")]
MappingFailure(keys::Key),
#[error("missing record mapping for sym `{0}`")]
SymMappingFailure(u64),
}