/* * 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 . */ use std::{ collections::{HashMap, HashSet}, ffi::{CStr, CString}, os::raw::c_char, slice, }; use crate::x11::ffi::{ Display, KeyCode, KeyPress, KeyRelease, KeySym, Window, XCloseDisplay, XDefaultRootWindow, XFlush, XFreeModifiermap, XGetInputFocus, XGetModifierMapping, XKeyEvent, XQueryKeymap, XSendEvent, XSync, XTestFakeKeyEvent, }; use libc::c_void; use log::{debug, error}; use crate::{linux::raw_keys::convert_to_sym_array, x11::ffi::Xutf8LookupString}; use anyhow::{bail, Result}; use thiserror::Error; use crate::{keys, InjectionOptions, Injector}; use crate::x11::ffi::{ XCloseIM, XCreateIC, XDestroyIC, XFilterEvent, XFree, XIMPreeditNothing, XIMStatusNothing, XNClientWindow_0, XNInputStyle_0, XOpenIM, XmbResetIC, XIC, }; // Offset between evdev keycodes (where KEY_ESCAPE is 1), and the evdev XKB // keycode set (where ESC is 9). const EVDEV_OFFSET: u32 = 8; #[derive(Clone, Copy, Debug)] struct KeyPair { // Keycode code: u32, // Modifier state which combined with the code produces the char // This is a bit mask: state: u32, } #[derive(Clone, Copy, Debug)] struct KeyRecord { main: KeyPair, // Under some keyboard layouts (de, es), a deadkey // press might be needed to generate the right char preceding_dead_key: Option, } type CharMap = HashMap; type SymMap = HashMap; pub struct X11DefaultInjector { display: *mut Display, char_map: CharMap, sym_map: SymMap, } #[allow(clippy::new_without_default)] impl X11DefaultInjector { pub fn new() -> Result { // Necessary to properly handle non-ascii chars let empty_string = CString::new("")?; unsafe { libc::setlocale(libc::LC_ALL, empty_string.as_ptr()); } let display = unsafe { crate::x11::ffi::XOpenDisplay(std::ptr::null()) }; if display.is_null() { return Err(X11InjectorError::Init().into()); } let (char_map, sym_map) = Self::generate_maps(display)?; Ok(Self { display, char_map, sym_map, }) } fn generate_maps(display: *mut Display) -> Result<(CharMap, SymMap)> { debug!("generating key maps"); let mut char_map = HashMap::new(); let mut sym_map = HashMap::new(); let input_method = unsafe { XOpenIM( display, std::ptr::null_mut(), std::ptr::null_mut(), std::ptr::null_mut(), ) }; if input_method.is_null() { bail!("could not open input method"); } let _im_guard = scopeguard::guard((), |_| { unsafe { XCloseIM(input_method) }; }); let input_context = unsafe { XCreateIC( input_method, XNInputStyle_0.as_ptr(), XIMPreeditNothing | XIMStatusNothing, XNClientWindow_0.as_ptr(), 0, std::ptr::null_mut(), ) }; if input_context.is_null() { bail!("could not open input context"); } let _ic_guard = scopeguard::guard((), |_| { unsafe { XDestroyIC(input_context) }; }); let deadkeys = Self::find_deadkeys(display, &input_context)?; // Cycle through all state/code combinations to populate the reverse lookup tables for key_code in 0..256u32 { for modifier_state in 0..256u32 { for dead_key in deadkeys.iter() { let code_with_offset = key_code + EVDEV_OFFSET; let preceding_dead_key = if let Some(dead_key) = dead_key { let mut dead_key_event = XKeyEvent { display, keycode: dead_key.code, state: dead_key.state, // These might not even need to be filled window: 0, root: 0, same_screen: 1, time: 0, type_: KeyPress, x_root: 1, y_root: 1, x: 1, y: 1, subwindow: 0, serial: 0, send_event: 0, }; unsafe { XFilterEvent(&mut dead_key_event, 0) }; Some(*dead_key) } else { None }; let mut key_event = XKeyEvent { display, keycode: code_with_offset, state: modifier_state, // These might not even need to be filled window: 0, root: 0, same_screen: 1, time: 0, type_: KeyPress, x_root: 1, y_root: 1, x: 1, y: 1, subwindow: 0, serial: 0, send_event: 0, }; unsafe { XFilterEvent(&mut key_event, 0) }; let mut sym: KeySym = 0; let mut buffer: [c_char; 10] = [0; 10]; let result = unsafe { Xutf8LookupString( input_context, &mut key_event, buffer.as_mut_ptr(), (buffer.len() - 1) as i32, &mut sym, std::ptr::null_mut(), ) }; let key_record = KeyRecord { main: KeyPair { code: code_with_offset, state: modifier_state, }, preceding_dead_key, }; // Keysym was found if sym != 0 { sym_map.entry(sym).or_insert(key_record); }; // Char was found if result > 0 { let raw_string = unsafe { CStr::from_ptr(buffer.as_ptr()) }; let string = raw_string.to_string_lossy().to_string(); char_map.entry(string).or_insert(key_record); }; // We need to reset the context state to prevent // deadkeys effect to propagate to the next combination let _reset = unsafe { XmbResetIC(input_context) }; unsafe { XFree(_reset as *mut c_void) }; } } } debug!("Populated char_map with {} symbols", char_map.len()); debug!("Populated sym_map with {} symbols", sym_map.len()); debug!("Detected {} dead key combinations", deadkeys.len()); Ok((char_map, sym_map)) } fn find_deadkeys(display: *mut Display, input_context: &XIC) -> Result>> { let mut deadkeys = vec![None]; let mut seen_keysyms: HashSet = HashSet::new(); // Cycle through all state/code combinations to populate the reverse lookup tables for key_code in 0..256u32 { for modifier_state in 0..256u32 { let code_with_offset = key_code + EVDEV_OFFSET; let mut event = XKeyEvent { display, keycode: code_with_offset, state: modifier_state, // These might not even need to be filled window: 0, root: 0, same_screen: 1, time: 0, type_: KeyPress, x_root: 1, y_root: 1, x: 1, y: 1, subwindow: 0, serial: 0, send_event: 0, }; let filter = unsafe { XFilterEvent(&mut event, 0) }; if filter == 1 { let mut sym: KeySym = 0; let mut buffer: [c_char; 10] = [0; 10]; unsafe { Xutf8LookupString( *input_context, &mut event, buffer.as_mut_ptr(), (buffer.len() - 1) as i32, &mut sym, std::ptr::null_mut(), ) }; if sym != 0 && !seen_keysyms.contains(&sym) { let key_record = KeyPair { code: code_with_offset, state: modifier_state, }; deadkeys.push(Some(key_record)); seen_keysyms.insert(sym); } } let _reset = unsafe { XmbResetIC(*input_context) }; unsafe { XFree(_reset as *mut c_void) }; } } Ok(deadkeys) } fn convert_to_record_array(&self, syms: &[KeySym]) -> Result> { syms .iter() .map(|sym| { self .sym_map .get(sym) .cloned() .ok_or_else(|| X11InjectorError::SymMapping(*sym).into()) }) .collect() } // This method was inspired by the wonderful xdotool by Jordan Sissel // https://github.com/jordansissel/xdotool fn get_modifier_codes(&self) -> Vec> { let modifiers_ptr = unsafe { XGetModifierMapping(self.display) }; let modifiers = unsafe { *modifiers_ptr }; let mut modifiers_codes = Vec::new(); for mod_index in 0..=7 { let mut modifier_codes = Vec::new(); for mod_key in 0..modifiers.max_keypermod { let modifier_map = unsafe { slice::from_raw_parts( modifiers.modifiermap, (8 * modifiers.max_keypermod) as usize, ) }; let keycode = modifier_map[(mod_index * modifiers.max_keypermod + mod_key) as usize]; if keycode != 0 { modifier_codes.push(keycode); } } modifiers_codes.push(modifier_codes); } unsafe { XFreeModifiermap(modifiers_ptr) }; modifiers_codes } fn render_key_combination(&self, original_records: &[KeyRecord]) -> Vec { let modifiers_codes = self.get_modifier_codes(); let mut records = Vec::new(); let mut current_state = 0u32; for record in original_records { let mut current_record = *record; // Render the state by applying the modifiers for (mod_index, modifier) in modifiers_codes.iter().enumerate() { if modifier.contains(&(record.main.code as u8)) { current_state |= 1 << mod_index; } } current_record.main.state = current_state; records.push(current_record); } records } fn get_focused_window(&self) -> Window { let mut focused_window: Window = 0; let mut revert_to = 0; unsafe { XGetInputFocus(self.display, &mut focused_window, &mut revert_to); } focused_window } fn send_key(&self, window: Window, record: &KeyPair, pressed: bool, delay_us: u32) { let root_window = unsafe { XDefaultRootWindow(self.display) }; let mut event = XKeyEvent { display: self.display, keycode: record.code, state: record.state, window, root: root_window, same_screen: 1, time: 0, type_: if pressed { KeyPress } else { KeyRelease }, x_root: 1, y_root: 1, x: 1, y: 1, subwindow: 0, serial: 0, send_event: 0, }; unsafe { XSendEvent(self.display, window, 1, 0, &mut event); XFlush(self.display); } if delay_us != 0 { unsafe { libc::usleep(delay_us); } } } fn xtest_send_modifiers(&self, modmask: u32, pressed: bool) { let modifiers_codes = self.get_modifier_codes(); for (mod_index, modifier_codes) in modifiers_codes.into_iter().enumerate() { if (modmask & (1 << mod_index)) != 0 { for keycode in modifier_codes { let is_press = if pressed { 1 } else { 0 }; unsafe { XTestFakeKeyEvent(self.display, keycode as u32, is_press, 0); XSync(self.display, 0); } } } } } fn xtest_send_key(&self, record: &KeyPair, pressed: bool, delay_us: u32) { // If the key requires any modifier, we need to send those events if record.state != 0 { self.xtest_send_modifiers(record.state, pressed); } let is_press = if pressed { 1 } else { 0 }; unsafe { XTestFakeKeyEvent(self.display, record.code, is_press, 0); XSync(self.display, 0); XFlush(self.display); } if delay_us != 0 { unsafe { libc::usleep(delay_us); } } } fn xtest_release_all_keys(&self) { let mut keys: [u8; 32] = [0; 32]; unsafe { XQueryKeymap(self.display, keys.as_mut_ptr()); } #[allow(clippy::needless_range_loop)] for i in 0..32 { // Only those that are pressed should be changed if keys[i] != 0 { for k in 0..8 { if (keys[i] & (1 << k)) != 0 { let key_code = i * 8 + k; unsafe { XTestFakeKeyEvent(self.display, key_code as u32, 0, 0); } } } } } } } impl Drop for X11DefaultInjector { fn drop(&mut self) { unsafe { XCloseDisplay(self.display); } } } impl Injector for X11DefaultInjector { fn send_string(&self, string: &str, options: InjectionOptions) -> Result<()> { let focused_window = self.get_focused_window(); if options.disable_fast_inject { self.xtest_release_all_keys(); } // Compute all the key record sequence first to make sure a mapping is available let records: Result> = string .chars() .map(|c| c.to_string()) .map(|char| { self .char_map .get(&char) .cloned() .ok_or_else(|| X11InjectorError::CharMapping(char).into()) }) .collect(); let delay_us = options.delay as u32 * 1000; // Convert to micro seconds for record in records? { if options.disable_fast_inject { if let Some(deadkey) = &record.preceding_dead_key { self.xtest_send_key(deadkey, true, delay_us); self.xtest_send_key(deadkey, false, delay_us); } self.xtest_send_key(&record.main, true, delay_us); self.xtest_send_key(&record.main, false, delay_us); } else { if let Some(deadkey) = &record.preceding_dead_key { self.send_key(focused_window, deadkey, true, delay_us); self.send_key(focused_window, deadkey, false, delay_us); } self.send_key(focused_window, &record.main, true, delay_us); self.send_key(focused_window, &record.main, false, delay_us); } } Ok(()) } fn send_keys(&self, keys: &[keys::Key], options: InjectionOptions) -> Result<()> { let focused_window = self.get_focused_window(); // 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)?; if options.disable_fast_inject { self.xtest_release_all_keys(); } let delay_us = options.delay as u32 * 1000; // Convert to micro seconds for record in records { if options.disable_fast_inject { self.xtest_send_key(&record.main, true, delay_us); self.xtest_send_key(&record.main, false, delay_us); } else { self.send_key(focused_window, &record.main, true, delay_us); self.send_key(focused_window, &record.main, false, delay_us); } } Ok(()) } fn send_key_combination(&self, keys: &[keys::Key], options: InjectionOptions) -> Result<()> { let focused_window = self.get_focused_window(); // 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)?; // Render the correct modifier mask for the given sequence let records = self.render_key_combination(&records); if options.disable_fast_inject { self.xtest_release_all_keys(); } let delay_us = options.delay as u32 * 1000; // Convert to micro seconds // First press the keys for record in records.iter() { if options.disable_fast_inject { self.xtest_send_key(&record.main, true, delay_us); } else { self.send_key(focused_window, &record.main, true, delay_us); } } // Then release them for record in records.iter().rev() { if options.disable_fast_inject { self.xtest_send_key(&record.main, false, delay_us); } else { self.send_key(focused_window, &record.main, false, delay_us); } } Ok(()) } } #[derive(Error, Debug)] pub enum X11InjectorError { #[error("failed to initialize x11 display")] Init(), #[error("missing vkey mapping for char `{0}`")] CharMapping(String), #[error("missing record mapping for sym `{0}`")] SymMapping(u64), }