diff --git a/espanso-inject/src/evdev/mod.rs b/espanso-inject/src/evdev/mod.rs index 0164294..23887d1 100644 --- a/espanso-inject/src/evdev/mod.rs +++ b/espanso-inject/src/evdev/mod.rs @@ -26,16 +26,18 @@ mod uinput; use std::{ collections::{HashMap, HashSet}, ffi::CString, + time::Instant, }; use context::Context; use keymap::Keymap; -use log::error; -use std::iter::FromIterator; +use log::{error, warn}; use uinput::UInputDevice; -use crate::{linux::raw_keys::convert_to_sym_array, InjectorCreationOptions}; -use anyhow::Result; +use crate::{ + linux::raw_keys::convert_to_sym_array, InjectorCreationOptions, KeyboardStateProvider, +}; +use anyhow::{Result, bail}; use itertools::Itertools; use thiserror::Error; @@ -75,6 +77,9 @@ const DEFAULT_MODIFIERS: [u32; 10] = [ const DEFAULT_MAX_MODIFIER_COMBINATION_LEN: i32 = 3; +// TODO: make the timeout a configurable option +const DEFAULT_WAIT_KEY_RELEASE_TIMEOUT_MS: u64 = 4000; + pub type KeySym = u32; #[derive(Clone, Debug)] @@ -98,6 +103,9 @@ pub struct EVDEVInjector { // Ownership _context: Context, _keymap: Keymap, + + // Keyboard state provider + keyboard_state_provider: Option>, } #[allow(clippy::new_without_default)] @@ -126,12 +134,17 @@ impl EVDEVInjector { // Create the uinput virtual device let device = UInputDevice::new()?; + if options.keyboard_state_provider.is_none() { + warn!("EVDEVInjection has been initialized without a KeyboardStateProvider, which might result in partial injections."); + } + Ok(Self { device, char_map, sym_map, _context: context, _keymap: keymap, + keyboard_state_provider: options.keyboard_state_provider, }) } @@ -211,6 +224,31 @@ impl EVDEVInjector { } } } + + fn wait_until_key_is_released(&self, code: u32) -> Result<()> { + if let Some(key_provider) = &self.keyboard_state_provider { + let key_provider_code = code + EVDEV_OFFSET; + + if !key_provider.is_key_pressed(key_provider_code) { + return Ok(()); + } + + // Key is pressed, wait until timeout + let now = Instant::now(); + while now.elapsed() < std::time::Duration::from_millis(DEFAULT_WAIT_KEY_RELEASE_TIMEOUT_MS) { + if !key_provider.is_key_pressed(key_provider_code) { + return Ok(()); + } + + std::thread::sleep(std::time::Duration::from_millis(50)); + } + + bail!("timed-out while waiting for key release: {}", code); + } else { + // Keyboard provider not available, + Ok(()) + } + } } impl Injector for EVDEVInjector { @@ -235,7 +273,7 @@ impl Injector for EVDEVInjector { let mut current_modifiers: HashSet = HashSet::new(); for record in records? { - let record_modifiers = HashSet::from_iter(record.modifiers.iter().cloned()); + let record_modifiers = record.modifiers.iter().cloned().collect::>(); // Release all the modifiers that are not needed anymore for expired_modifier in current_modifiers.difference(&record_modifiers) { @@ -244,10 +282,12 @@ impl Injector for EVDEVInjector { // Press all the new modifiers that are now needed for new_modifier in record_modifiers.difference(¤t_modifiers) { + self.wait_until_key_is_released(record.code)?; self.send_key(*new_modifier, true, delay_us); } // Send the char + self.wait_until_key_is_released(record.code)?; self.send_key(record.code, true, delay_us); self.send_key(record.code, false, delay_us); diff --git a/espanso-inject/src/lib.rs b/espanso-inject/src/lib.rs index df4852a..090970d 100644 --- a/espanso-inject/src/lib.rs +++ b/espanso-inject/src/lib.rs @@ -98,6 +98,13 @@ pub struct InjectorCreationOptions { // Can be used to overwrite the keymap configuration // used by espanso to inject key presses. pub evdev_keyboard_rmlvo: Option, + + // An optional provider that can be used by the injector + // to determine which keys are pressed at the time of injection. + // This is needed on Wayland to "wait" for key releases when + // the injected string contains a key that it's currently pressed. + // Otherwise, a key that is already pressed cannot be injected. + pub keyboard_state_provider: Option>, } // This struct identifies the keyboard layout that @@ -111,6 +118,10 @@ pub struct KeyboardConfig { pub options: Option, } +pub trait KeyboardStateProvider { + fn is_key_pressed(&self, code: u32) -> bool; +} + impl Default for InjectorCreationOptions { fn default() -> Self { Self { @@ -118,6 +129,7 @@ impl Default for InjectorCreationOptions { evdev_modifiers: None, evdev_max_modifier_combination_len: None, evdev_keyboard_rmlvo: None, + keyboard_state_provider: None, } } }