Refactor anti self-injection mechanism

This commit is contained in:
Federico Terzi 2020-03-08 00:23:26 +01:00
parent b523eadf6e
commit 6235377b86
5 changed files with 40 additions and 33 deletions

View File

@ -61,7 +61,6 @@ fn default_passive_arg_escape() -> char { '\\' }
fn default_passive_key() -> KeyModifier { KeyModifier::OFF } fn default_passive_key() -> KeyModifier { KeyModifier::OFF }
fn default_enable_passive() -> bool { false } fn default_enable_passive() -> bool { false }
fn default_enable_active() -> bool { true } fn default_enable_active() -> bool { true }
fn default_action_noop_interval() -> u128 { 500 }
fn default_backspace_limit() -> i32 { 3 } fn default_backspace_limit() -> i32 { 3 }
fn default_restore_clipboard_delay() -> i32 { 300 } fn default_restore_clipboard_delay() -> i32 { 300 }
fn default_exclude_default_entries() -> bool {false} fn default_exclude_default_entries() -> bool {false}
@ -130,9 +129,6 @@ pub struct Configs {
#[serde(default = "default_enable_active")] #[serde(default = "default_enable_active")]
pub enable_active: bool, pub enable_active: bool,
#[serde(default = "default_action_noop_interval")]
pub action_noop_interval: u128,
#[serde(default)] #[serde(default)]
pub paste_shortcut: PasteShortcut, pub paste_shortcut: PasteShortcut,
@ -193,7 +189,6 @@ impl Configs {
validate_field!(result, self.passive_arg_delimiter, default_passive_arg_delimiter()); validate_field!(result, self.passive_arg_delimiter, default_passive_arg_delimiter());
validate_field!(result, self.passive_arg_escape, default_passive_arg_escape()); validate_field!(result, self.passive_arg_escape, default_passive_arg_escape());
validate_field!(result, self.passive_key, default_passive_key()); validate_field!(result, self.passive_key, default_passive_key());
validate_field!(result, self.action_noop_interval, default_action_noop_interval());
validate_field!(result, self.restore_clipboard_delay, default_restore_clipboard_delay()); validate_field!(result, self.restore_clipboard_delay, default_restore_clipboard_delay());
result result

View File

@ -26,14 +26,18 @@ use std::process::exit;
use log::{debug, error, info}; use log::{debug, error, info};
use std::ffi::CStr; use std::ffi::CStr;
use std::{thread, time}; use std::{thread, time};
use std::sync::atomic::AtomicBool;
use std::sync::Arc;
use std::sync::atomic::Ordering::Acquire;
#[repr(C)] #[repr(C)]
pub struct LinuxContext { pub struct LinuxContext {
pub send_channel: Sender<Event> pub send_channel: Sender<Event>,
is_injecting: Arc<AtomicBool>,
} }
impl LinuxContext { impl LinuxContext {
pub fn new(send_channel: Sender<Event>) -> Box<LinuxContext> { pub fn new(send_channel: Sender<Event>, is_injecting: Arc<AtomicBool>) -> Box<LinuxContext> {
// Check if the X11 context is available // Check if the X11 context is available
let x11_available = unsafe { let x11_available = unsafe {
check_x11() check_x11()
@ -46,6 +50,7 @@ impl LinuxContext {
let context = Box::new(LinuxContext { let context = Box::new(LinuxContext {
send_channel, send_channel,
is_injecting
}); });
unsafe { unsafe {
@ -85,6 +90,14 @@ extern fn keypress_callback(_self: *mut c_void, raw_buffer: *const u8, len: i32,
unsafe { unsafe {
let _self = _self as *mut LinuxContext; let _self = _self as *mut LinuxContext;
// If espanso is currently injecting text, we should avoid processing
// external events, as it could happen that espanso reinterpret its
// own input.
if (*_self).is_injecting.load(Acquire) {
debug!("Input ignored while espanso is injecting text...");
return;
}
if is_modifier == 0 { // Char event if is_modifier == 0 { // Char event
// Convert the received buffer to a string // Convert the received buffer to a string
let c_str = CStr::from_ptr(raw_buffer as (*const c_char)); let c_str = CStr::from_ptr(raw_buffer as (*const c_char));

View File

@ -30,7 +30,8 @@ use std::sync::mpsc::Sender;
use crate::event::Event; use crate::event::Event;
use std::path::PathBuf; use std::path::PathBuf;
use std::fs::create_dir_all; use std::fs::create_dir_all;
use std::sync::Once; use std::sync::{Once, Arc};
use std::sync::atomic::AtomicBool;
pub trait Context { pub trait Context {
fn eventloop(&self); fn eventloop(&self);
@ -44,8 +45,8 @@ pub fn new(send_channel: Sender<Event>) -> Box<dyn Context> {
// LINUX IMPLEMENTATION // LINUX IMPLEMENTATION
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
pub fn new(send_channel: Sender<Event>) -> Box<dyn Context> { pub fn new(send_channel: Sender<Event>, is_injecting: Arc<AtomicBool>,) -> Box<dyn Context> {
linux::LinuxContext::new(send_channel) linux::LinuxContext::new(send_channel, is_injecting)
} }
// WINDOWS IMPLEMENTATION // WINDOWS IMPLEMENTATION

View File

@ -33,6 +33,9 @@ use std::collections::HashMap;
use std::path::PathBuf; use std::path::PathBuf;
use regex::{Regex, Captures}; use regex::{Regex, Captures};
use std::time::SystemTime; use std::time::SystemTime;
use std::sync::Arc;
use std::sync::atomic::AtomicBool;
use std::sync::atomic::Ordering::{Relaxed, Release, Acquire, AcqRel, SeqCst};
pub struct Engine<'a, S: KeyboardManager, C: ClipboardManager, M: ConfigManager<'a>, pub struct Engine<'a, S: KeyboardManager, C: ClipboardManager, M: ConfigManager<'a>,
U: UIManager, R: Renderer> { U: UIManager, R: Renderer> {
@ -41,29 +44,28 @@ pub struct Engine<'a, S: KeyboardManager, C: ClipboardManager, M: ConfigManager<
config_manager: &'a M, config_manager: &'a M,
ui_manager: &'a U, ui_manager: &'a U,
renderer: &'a R, renderer: &'a R,
is_injecting: Arc<AtomicBool>,
enabled: RefCell<bool>, enabled: RefCell<bool>,
last_action_time: RefCell<SystemTime>, // Used to block espanso from re-interpreting it's own inputs last_action_time: RefCell<SystemTime>, // Used to block espanso from re-interpreting it's own inputs
action_noop_interval: u128,
} }
impl <'a, S: KeyboardManager, C: ClipboardManager, M: ConfigManager<'a>, U: UIManager, R: Renderer> impl <'a, S: KeyboardManager, C: ClipboardManager, M: ConfigManager<'a>, U: UIManager, R: Renderer>
Engine<'a, S, C, M, U, R> { Engine<'a, S, C, M, U, R> {
pub fn new(keyboard_manager: &'a S, clipboard_manager: &'a C, pub fn new(keyboard_manager: &'a S, clipboard_manager: &'a C,
config_manager: &'a M, ui_manager: &'a U, config_manager: &'a M, ui_manager: &'a U,
renderer: &'a R) -> Engine<'a, S, C, M, U, R> { renderer: &'a R, is_injecting: Arc<AtomicBool>) -> Engine<'a, S, C, M, U, R> {
let enabled = RefCell::new(true); let enabled = RefCell::new(true);
let last_action_time = RefCell::new(SystemTime::now()); let last_action_time = RefCell::new(SystemTime::now());
let action_noop_interval = config_manager.default_config().action_noop_interval;
Engine{keyboard_manager, Engine{keyboard_manager,
clipboard_manager, clipboard_manager,
config_manager, config_manager,
ui_manager, ui_manager,
renderer, renderer,
is_injecting,
enabled, enabled,
last_action_time, last_action_time,
action_noop_interval,
} }
} }
@ -139,11 +141,8 @@ impl <'a, S: KeyboardManager, C: ClipboardManager, M: ConfigManager<'a>, U: UIMa
return; return;
} }
// avoid espanso reinterpreting its own actions // Block espanso from reinterpreting its own actions
if self.check_last_action_and_set(self.action_noop_interval) { self.is_injecting.store(true, Release);
debug!("Last action was too near, nooping the action.");
return;
}
let char_count = if trailing_separator.is_none() { let char_count = if trailing_separator.is_none() {
m.triggers[trigger_offset].chars().count() as i32 m.triggers[trigger_offset].chars().count() as i32
@ -246,14 +245,12 @@ impl <'a, S: KeyboardManager, C: ClipboardManager, M: ConfigManager<'a>, U: UIMa
self.clipboard_manager.set_clipboard(&previous_clipboard_content); self.clipboard_manager.set_clipboard(&previous_clipboard_content);
} }
// Re-allow espanso to interpret actions
self.is_injecting.store(false, Release);
} }
fn on_enable_update(&self, status: bool) { fn on_enable_update(&self, status: bool) {
// avoid espanso reinterpreting its own actions
if self.check_last_action_and_set(self.action_noop_interval) {
return;
}
let message = if status { let message = if status {
"espanso enabled" "espanso enabled"
}else{ }else{
@ -269,11 +266,6 @@ impl <'a, S: KeyboardManager, C: ClipboardManager, M: ConfigManager<'a>, U: UIMa
} }
fn on_passive(&self) { fn on_passive(&self) {
// avoid espanso reinterpreting its own actions
if self.check_last_action_and_set(self.action_noop_interval) {
return;
}
let config = self.config_manager.active_config(); let config = self.config_manager.active_config();
if !config.enable_passive { if !config.enable_passive {

View File

@ -23,7 +23,7 @@ extern crate lazy_static;
use std::thread; use std::thread;
use std::fs::{File, OpenOptions}; use std::fs::{File, OpenOptions};
use std::process::exit; use std::process::exit;
use std::sync::mpsc; use std::sync::{mpsc, Arc};
use std::sync::mpsc::Receiver; use std::sync::mpsc::Receiver;
use std::time::Duration; use std::time::Duration;
@ -44,6 +44,7 @@ use crate::protocol::*;
use std::io::{BufReader, BufRead}; use std::io::{BufReader, BufRead};
use crate::package::default::DefaultPackageManager; use crate::package::default::DefaultPackageManager;
use crate::package::{PackageManager, InstallResult, UpdateResult, RemoveResult}; use crate::package::{PackageManager, InstallResult, UpdateResult, RemoveResult};
use std::sync::atomic::AtomicBool;
mod ui; mod ui;
mod edit; mod edit;
@ -319,11 +320,15 @@ fn daemon_main(config_set: ConfigSet) {
let (send_channel, receive_channel) = mpsc::channel(); let (send_channel, receive_channel) = mpsc::channel();
let context = context::new(send_channel.clone()); // This atomic bool is used to "disable" espanso when espanding its own matches, otherwise
// we could reinterpret the characters we are injecting
let is_injecting = Arc::new(std::sync::atomic::AtomicBool::new(false));
let context = context::new(send_channel.clone(), is_injecting.clone());
let config_set_copy = config_set.clone(); let config_set_copy = config_set.clone();
thread::Builder::new().name("daemon_background".to_string()).spawn(move || { thread::Builder::new().name("daemon_background".to_string()).spawn(move || {
daemon_background(receive_channel, config_set_copy); daemon_background(receive_channel, config_set_copy, is_injecting);
}).expect("Unable to spawn daemon background thread"); }).expect("Unable to spawn daemon background thread");
let ipc_server = protocol::get_ipc_server(config_set, send_channel.clone()); let ipc_server = protocol::get_ipc_server(config_set, send_channel.clone());
@ -333,7 +338,7 @@ fn daemon_main(config_set: ConfigSet) {
} }
/// Background thread worker for the daemon /// Background thread worker for the daemon
fn daemon_background(receive_channel: Receiver<Event>, config_set: ConfigSet) { fn daemon_background(receive_channel: Receiver<Event>, config_set: ConfigSet, is_injecting: Arc<AtomicBool>) {
let system_manager = system::get_manager(); let system_manager = system::get_manager();
let config_manager = RuntimeConfigManager::new(config_set, system_manager); let config_manager = RuntimeConfigManager::new(config_set, system_manager);
@ -354,6 +359,7 @@ fn daemon_background(receive_channel: Receiver<Event>, config_set: ConfigSet) {
&config_manager, &config_manager,
&ui_manager, &ui_manager,
&renderer, &renderer,
is_injecting,
); );
let matcher = ScrollingMatcher::new(&config_manager, &engine); let matcher = ScrollingMatcher::new(&config_manager, &engine);