Refactor anti self-injection mechanism
This commit is contained in:
parent
b523eadf6e
commit
6235377b86
|
@ -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
|
||||||
|
|
|
@ -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));
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
14
src/main.rs
14
src/main.rs
|
@ -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);
|
||||||
|
|
Loading…
Reference in New Issue
Block a user