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_enable_passive() -> bool { false }
fn default_enable_active() -> bool { true }
fn default_action_noop_interval() -> u128 { 500 }
fn default_backspace_limit() -> i32 { 3 }
fn default_restore_clipboard_delay() -> i32 { 300 }
fn default_exclude_default_entries() -> bool {false}
@ -130,9 +129,6 @@ pub struct Configs {
#[serde(default = "default_enable_active")]
pub enable_active: bool,
#[serde(default = "default_action_noop_interval")]
pub action_noop_interval: u128,
#[serde(default)]
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_escape, default_passive_arg_escape());
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());
result

View File

@ -26,14 +26,18 @@ use std::process::exit;
use log::{debug, error, info};
use std::ffi::CStr;
use std::{thread, time};
use std::sync::atomic::AtomicBool;
use std::sync::Arc;
use std::sync::atomic::Ordering::Acquire;
#[repr(C)]
pub struct LinuxContext {
pub send_channel: Sender<Event>
pub send_channel: Sender<Event>,
is_injecting: Arc<AtomicBool>,
}
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
let x11_available = unsafe {
check_x11()
@ -46,6 +50,7 @@ impl LinuxContext {
let context = Box::new(LinuxContext {
send_channel,
is_injecting
});
unsafe {
@ -85,6 +90,14 @@ extern fn keypress_callback(_self: *mut c_void, raw_buffer: *const u8, len: i32,
unsafe {
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
// Convert the received buffer to a string
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 std::path::PathBuf;
use std::fs::create_dir_all;
use std::sync::Once;
use std::sync::{Once, Arc};
use std::sync::atomic::AtomicBool;
pub trait Context {
fn eventloop(&self);
@ -44,8 +45,8 @@ pub fn new(send_channel: Sender<Event>) -> Box<dyn Context> {
// LINUX IMPLEMENTATION
#[cfg(target_os = "linux")]
pub fn new(send_channel: Sender<Event>) -> Box<dyn Context> {
linux::LinuxContext::new(send_channel)
pub fn new(send_channel: Sender<Event>, is_injecting: Arc<AtomicBool>,) -> Box<dyn Context> {
linux::LinuxContext::new(send_channel, is_injecting)
}
// WINDOWS IMPLEMENTATION

View File

@ -33,6 +33,9 @@ use std::collections::HashMap;
use std::path::PathBuf;
use regex::{Regex, Captures};
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>,
U: UIManager, R: Renderer> {
@ -41,29 +44,28 @@ pub struct Engine<'a, S: KeyboardManager, C: ClipboardManager, M: ConfigManager<
config_manager: &'a M,
ui_manager: &'a U,
renderer: &'a R,
is_injecting: Arc<AtomicBool>,
enabled: RefCell<bool>,
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>
Engine<'a, S, C, M, U, R> {
pub fn new(keyboard_manager: &'a S, clipboard_manager: &'a C,
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 last_action_time = RefCell::new(SystemTime::now());
let action_noop_interval = config_manager.default_config().action_noop_interval;
Engine{keyboard_manager,
clipboard_manager,
config_manager,
ui_manager,
renderer,
is_injecting,
enabled,
last_action_time,
action_noop_interval,
}
}
@ -139,11 +141,8 @@ impl <'a, S: KeyboardManager, C: ClipboardManager, M: ConfigManager<'a>, U: UIMa
return;
}
// avoid espanso reinterpreting its own actions
if self.check_last_action_and_set(self.action_noop_interval) {
debug!("Last action was too near, nooping the action.");
return;
}
// Block espanso from reinterpreting its own actions
self.is_injecting.store(true, Release);
let char_count = if trailing_separator.is_none() {
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);
}
// Re-allow espanso to interpret actions
self.is_injecting.store(false, Release);
}
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 {
"espanso enabled"
}else{
@ -269,11 +266,6 @@ impl <'a, S: KeyboardManager, C: ClipboardManager, M: ConfigManager<'a>, U: UIMa
}
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();
if !config.enable_passive {

View File

@ -23,7 +23,7 @@ extern crate lazy_static;
use std::thread;
use std::fs::{File, OpenOptions};
use std::process::exit;
use std::sync::mpsc;
use std::sync::{mpsc, Arc};
use std::sync::mpsc::Receiver;
use std::time::Duration;
@ -44,6 +44,7 @@ use crate::protocol::*;
use std::io::{BufReader, BufRead};
use crate::package::default::DefaultPackageManager;
use crate::package::{PackageManager, InstallResult, UpdateResult, RemoveResult};
use std::sync::atomic::AtomicBool;
mod ui;
mod edit;
@ -319,11 +320,15 @@ fn daemon_main(config_set: ConfigSet) {
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();
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");
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
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 config_manager = RuntimeConfigManager::new(config_set, system_manager);
@ -354,6 +359,7 @@ fn daemon_background(receive_channel: Receiver<Event>, config_set: ConfigSet) {
&config_manager,
&ui_manager,
&renderer,
is_injecting,
);
let matcher = ScrollingMatcher::new(&config_manager, &engine);