First draft of evdev inject backend
This commit is contained in:
parent
ff6bfa20cb
commit
a9d24d400d
16
Cargo.lock
generated
16
Cargo.lock
generated
|
@ -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"
|
||||
|
|
|
@ -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(),
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
47
espanso-inject/src/evdev/context.rs
Normal file
47
espanso-inject/src/evdev/context.rs
Normal 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(),
|
||||
}
|
84
espanso-inject/src/evdev/ffi.rs
Normal file
84
espanso-inject/src/evdev/ffi.rs
Normal 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);
|
||||
}
|
86
espanso-inject/src/evdev/keymap.rs
Normal file
86
espanso-inject/src/evdev/keymap.rs
Normal 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(),
|
||||
}
|
332
espanso-inject/src/evdev/mod.rs
Normal file
332
espanso-inject/src/evdev/mod.rs
Normal 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(¤t_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),
|
||||
}
|
73
espanso-inject/src/evdev/native.c
Normal file
73
espanso-inject/src/evdev/native.c
Normal 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);
|
||||
}
|
31
espanso-inject/src/evdev/native.h
Normal file
31
espanso-inject/src/evdev/native.h
Normal 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
|
89
espanso-inject/src/evdev/state.rs
Normal file
89
espanso-inject/src/evdev/state.rs
Normal 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(),
|
||||
}
|
108
espanso-inject/src/evdev/uinput.rs
Normal file
108
espanso-inject/src/evdev/uinput.rs
Normal 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(),
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
20
espanso-inject/src/linux/mod.rs
Normal file
20
espanso-inject/src/linux/mod.rs
Normal 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;
|
|
@ -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),
|
||||
}
|
|
@ -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> {
|
||||
|
|
|
@ -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> {
|
||||
|
|
|
@ -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),
|
||||
}
|
Loading…
Reference in New Issue
Block a user