feat(inject): add wait mechanism on wayland injector

This commit is contained in:
Federico Terzi 2021-07-28 22:37:21 +02:00
parent 6a558f74c7
commit 31b93ebdb0
2 changed files with 57 additions and 5 deletions

View File

@ -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<Box<dyn KeyboardStateProvider>>,
}
#[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<u32> = HashSet::new();
for record in records? {
let record_modifiers = HashSet::from_iter(record.modifiers.iter().cloned());
let record_modifiers = record.modifiers.iter().cloned().collect::<HashSet<_>>();
// 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(&current_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);

View File

@ -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<KeyboardConfig>,
// 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<Box<dyn KeyboardStateProvider>>,
}
// This struct identifies the keyboard layout that
@ -111,6 +118,10 @@ pub struct KeyboardConfig {
pub options: Option<String>,
}
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,
}
}
}