Merge branch 'linux-backend' into dev

This commit is contained in:
Federico Terzi 2020-03-08 21:49:18 +01:00
commit b7a6d09d23
18 changed files with 236 additions and 82 deletions

View File

@ -137,7 +137,7 @@ int32_t initialize(void * _context_instance) {
return -4;
}
record_range->device_events.first = KeyPress;
record_range->device_events.last = KeyRelease;
record_range->device_events.last = ButtonPress;
// We want to get the keys from all clients
XRecordClientSpec client_spec;
@ -254,13 +254,16 @@ void event_callback(XPointer p, XRecordInterceptData *hook)
switch (event_type) {
case KeyPress:
//printf ("%d %d %s\n", key_code, res, buffer.data());
//printf ("Press %d %d %s\n", key_code, res, buffer.data());
if (res > 0 && key_code != 22) { // Printable character, but not backspace
keypress_callback(context_instance, buffer.data(), buffer.size(), 0, key_code);
}else{ // Modifier key
keypress_callback(context_instance, NULL, 0, 1, key_code);
}
break;
case ButtonPress: // Send also mouse button presses as "other events"
//printf ("Press button %d\n", key_code);
keypress_callback(context_instance, NULL, 0, 2, key_code);
default:
break;
}
@ -268,19 +271,46 @@ void event_callback(XPointer p, XRecordInterceptData *hook)
XRecordFreeData(hook);
}
void release_all_keys() {
char keys[32];
XQueryKeymap(xdo_context->xdpy, keys); // Get the current status of the keyboard
for (int i = 0; i<32; i++) {
// Only those that show a keypress should be changed
if (keys[i] != 0) {
for (int k = 0; k<8; k++) {
if ((keys[i] & (1 << k)) != 0) { // Bit by bit check
int key_code = i*8 + k;
XTestFakeKeyEvent(xdo_context->xdpy, key_code, false, CurrentTime);
}
}
}
}
}
void send_string(const char * string) {
xdo_enter_text_window(xdo_context, CURRENTWINDOW, string, 12000);
// It may happen that when an expansion is triggered, some keys are still pressed.
// This causes a problem if the expanded match contains that character, as the injection
// will not be able to register that keypress (as it is already pressed).
// To solve the problem, before an expansion we get which keys are currently pressed
// and inject a key_release event so that they can be further registered.
release_all_keys();
xdo_enter_text_window(xdo_context, CURRENTWINDOW, string, 1000);
}
void send_enter() {
xdo_send_keysequence_window(xdo_context, CURRENTWINDOW, "Return", 1000);
}
void delete_string(int32_t count) {
for (int i = 0; i<count; i++) {
xdo_send_keysequence_window(xdo_context, CURRENTWINDOW, "BackSpace", 8000);
xdo_send_keysequence_window(xdo_context, CURRENTWINDOW, "BackSpace", 1000);
}
}
void left_arrow(int32_t count) {
for (int i = 0; i<count; i++) {
xdo_send_keysequence_window(xdo_context, CURRENTWINDOW, "Left", 8000);
xdo_send_keysequence_window(xdo_context, CURRENTWINDOW, "Left", 1000);
}
}

View File

@ -48,7 +48,7 @@ extern "C" void cleanup();
* Called when a new keypress is made, the first argument is an char array,
* while the second is the size of the array.
*/
typedef void (*KeypressCallback)(void * self, const char *buffer, int32_t len, int32_t is_modifier, int32_t key_code);
typedef void (*KeypressCallback)(void * self, const char *buffer, int32_t len, int32_t event_type, int32_t key_code);
extern KeypressCallback keypress_callback;
@ -67,6 +67,11 @@ extern "C" void send_string(const char * string);
*/
extern "C" void delete_string(int32_t count);
/*
* Send an Enter key press
*/
extern "C" void send_enter();
/*
* Send the left arrow keypress, *count* times.
*/

View File

@ -36,8 +36,9 @@
[myStatusItem.button setTarget:self];
// Setup key listener
[NSEvent addGlobalMonitorForEventsMatchingMask:(NSEventMaskKeyDown | NSEventMaskFlagsChanged)
[NSEvent addGlobalMonitorForEventsMatchingMask:(NSEventMaskKeyDown | NSEventMaskFlagsChanged | NSEventMaskLeftMouseDown | NSEventMaskRightMouseDown)
handler:^(NSEvent *event){
if (event.type == NSEventTypeKeyDown
&& event.keyCode != 0x33) { // Send backspace as a modifier
@ -46,6 +47,9 @@
keypress_callback(context_instance, chars, len, 0, event.keyCode);
//NSLog(@"keydown: %@, %d", event.characters, event.keyCode);
}else if (event.type == NSEventTypeLeftMouseDown || event.type == NSEventTypeRightMouseDown) {
// Send the mouse button clicks as "other" events, used to improve word matches reliability
keypress_callback(context_instance, NULL, 0, 2, event.buttonNumber);
}else{
// Because this event is triggered for both the press and release of a modifier, trigger the callback
// only on release

View File

@ -46,7 +46,7 @@ int32_t headless_eventloop();
* Called when a new keypress is made, the first argument is an char array,
* while the second is the size of the array.
*/
typedef void (*KeypressCallback)(void * self, const char *buffer, int32_t len, int32_t is_modifier, int32_t key_code);
typedef void (*KeypressCallback)(void * self, const char *buffer, int32_t len, int32_t event_type, int32_t key_code);
extern KeypressCallback keypress_callback;

View File

@ -263,6 +263,12 @@ LRESULT CALLBACK window_procedure(HWND window, unsigned int msg, WPARAM wp, LPAR
keypress_callback(manager_instance, nullptr, 0, 1, raw->data.keyboard.VKey, is_key_down);
}
}
}else if (raw->header.dwType == RIM_TYPEMOUSE) // Mouse input, registered as "other" events. Needed to improve the reliability of word matches
{
if ((raw->data.mouse.usButtonFlags & (RI_MOUSE_LEFT_BUTTON_DOWN | RI_MOUSE_RIGHT_BUTTON_DOWN | RI_MOUSE_MIDDLE_BUTTON_DOWN)) != 0) {
//std::cout << "mouse down" << std::endl;
keypress_callback(manager_instance, nullptr, 0, 2, raw->data.mouse.usButtonFlags, 0);
}
}
return 0;
@ -343,14 +349,19 @@ int32_t initialize(void * self, wchar_t * ico_path, wchar_t * bmp_path) {
);
// Register raw inputs
RAWINPUTDEVICE Rid[1];
RAWINPUTDEVICE Rid[2];
Rid[0].usUsagePage = 0x01;
Rid[0].usUsage = 0x06;
Rid[0].dwFlags = RIDEV_NOLEGACY | RIDEV_INPUTSINK; // adds HID keyboard and also ignores legacy keyboard messages
Rid[0].hwndTarget = window;
if (RegisterRawInputDevices(Rid, 1, sizeof(Rid[0])) == FALSE) { // Something went wrong, error.
Rid[1].usUsagePage = 0x01;
Rid[1].usUsage = 0x02;
Rid[1].dwFlags = RIDEV_NOLEGACY | RIDEV_INPUTSINK; // adds HID mouse and also ignores legacy mouse messages
Rid[1].hwndTarget = window;
if (RegisterRawInputDevices(Rid, 2, sizeof(Rid[0])) == FALSE) { // Something went wrong, error.
return -1;
}

View File

@ -39,7 +39,7 @@ extern "C" int32_t initialize(void * self, wchar_t * ico_path, wchar_t * bmp_pat
* Called when a new keypress is made, the first argument is an int array,
* while the second is the size of the array.
*/
typedef void (*KeypressCallback)(void * self, uint16_t *buffer, int32_t len, int32_t is_modifier, int32_t key_code, int32_t is_key_down);
typedef void (*KeypressCallback)(void * self, uint16_t *buffer, int32_t len, int32_t event_type, int32_t key_code, int32_t is_key_down);
extern KeypressCallback keypress_callback;
/*

View File

@ -40,6 +40,7 @@ extern {
pub fn send_string(string: *const c_char);
pub fn delete_string(count: i32);
pub fn left_arrow(count: i32);
pub fn send_enter();
pub fn trigger_paste();
pub fn trigger_terminal_paste();
pub fn trigger_shift_ins_paste();

View File

@ -51,7 +51,7 @@ fn default_conflict_check() -> bool{ true }
fn default_ipc_server_port() -> i32 { 34982 }
fn default_use_system_agent() -> bool { true }
fn default_config_caching_interval() -> i32 { 800 }
fn default_word_separators() -> Vec<char> { vec![' ', ',', '.', '\r', '\n', 22u8 as char] }
fn default_word_separators() -> Vec<char> { vec![' ', ',', '.', '?', '!', '\r', '\n', 22u8 as char] }
fn default_toggle_interval() -> u32 { 230 }
fn default_toggle_key() -> KeyModifier { KeyModifier::ALT }
fn default_preserve_clipboard() -> bool {true}
@ -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
@ -203,7 +198,20 @@ impl Configs {
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum BackendType {
Inject,
Clipboard
Clipboard,
// On Linux systems there is a long standing issue with text injection (which
// in general is better than Clipboard copy/pasting) that prevents certain
// apps from correctly handling special characters (such as emojis or accented letters)
// when injected. For this reason, espanso initially defaulted on the Clipboard
// backend on Linux, as it was the most reliable (working in 99% of cases),
// even though it was less efficient and with a few inconveniences (for example, the
// previous clipboard content being overwritten).
// The Auto backend tries to take it a step further, by automatically determining
// when an injection is possible (only ascii characters in the replacement), and falling
// back to the Clipboard backend otherwise.
// Should only be used on Linux systems.
Auto
}
impl Default for BackendType {
// The default backend varies based on the operating system.
@ -318,6 +326,11 @@ impl ConfigSet {
let default_file = config_dir.join(DEFAULT_CONFIG_FILE_NAME);
let default = Configs::load_config(default_file.as_path())?;
// Check that a compatible backend is used, otherwise warn the user
if cfg!(not(target_os = "linux")) && default.backend == BackendType::Auto {
eprintln!("Warning: Using Auto backend is only supported on Linux, falling back to Inject backend.");
}
// Analyze which config files has to be loaded
let mut target_files = Vec::new();

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 {
@ -81,11 +86,19 @@ impl Drop for LinuxContext {
// Native bridge code
extern fn keypress_callback(_self: *mut c_void, raw_buffer: *const u8, len: i32,
is_modifier: i32, key_code: i32) {
event_type: i32, key_code: i32) {
unsafe {
let _self = _self as *mut LinuxContext;
if is_modifier == 0 { // Char event
// 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 event_type == 0 { // Char event
// Convert the received buffer to a string
let c_str = CStr::from_ptr(raw_buffer as (*const c_char));
let char_str = c_str.to_str();
@ -100,7 +113,7 @@ extern fn keypress_callback(_self: *mut c_void, raw_buffer: *const u8, len: i32,
debug!("Unable to receive char: {}",e);
},
}
}else{ // Modifier event
}else if event_type == 1 { // Modifier event
let modifier: Option<KeyModifier> = match key_code {
133 => Some(META),
50 => Some(SHIFT),
@ -113,7 +126,13 @@ extern fn keypress_callback(_self: *mut c_void, raw_buffer: *const u8, len: i32,
if let Some(modifier) = modifier {
let event = Event::Key(KeyEvent::Modifier(modifier));
(*_self).send_channel.send(event).unwrap();
}
}else{ // Not one of the default modifiers, send an "other" event
let event = Event::Key(KeyEvent::Other);
(*_self).send_channel.send(event).unwrap();
}
}else{ // Other type of event
let event = Event::Key(KeyEvent::Other);
(*_self).send_channel.send(event).unwrap();
}
}
}

View File

@ -24,17 +24,21 @@ use crate::event::{Event, KeyEvent, KeyModifier, ActionType};
use crate::event::KeyModifier::*;
use std::ffi::{CString, CStr};
use std::fs;
use log::{info, error};
use log::{info, error, debug};
use std::process::exit;
use std::sync::atomic::AtomicBool;
use std::sync::Arc;
use std::sync::atomic::Ordering::Acquire;
const STATUS_ICON_BINARY : &[u8] = include_bytes!("../res/mac/icon.png");
pub struct MacContext {
pub send_channel: Sender<Event>
pub send_channel: Sender<Event>,
is_injecting: Arc<AtomicBool>,
}
impl MacContext {
pub fn new(send_channel: Sender<Event>) -> Box<MacContext> {
pub fn new(send_channel: Sender<Event>, is_injecting: Arc<AtomicBool>) -> Box<MacContext> {
// Check accessibility
unsafe {
let res = prompt_accessibility();
@ -48,7 +52,8 @@ impl MacContext {
}
let context = Box::new(MacContext {
send_channel
send_channel,
is_injecting
});
// Initialize the status icon path
@ -89,11 +94,19 @@ impl super::Context for MacContext {
// Native bridge code
extern fn keypress_callback(_self: *mut c_void, raw_buffer: *const u8, len: i32,
is_modifier: i32, key_code: i32) {
event_type: i32, key_code: i32) {
unsafe {
let _self = _self as *mut MacContext;
if is_modifier == 0 { // Char event
// 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 event_type == 0 { // Char event
// Convert the received buffer to a string
let c_str = CStr::from_ptr(raw_buffer as (*const c_char));
let char_str = c_str.to_str();
@ -108,7 +121,7 @@ extern fn keypress_callback(_self: *mut c_void, raw_buffer: *const u8, len: i32,
error!("Unable to receive char: {}",e);
},
}
}else{ // Modifier event
}else if event_type == 1 { // Modifier event
let modifier: Option<KeyModifier> = match key_code {
0x37 => Some(META),
0x38 => Some(SHIFT),
@ -121,7 +134,13 @@ extern fn keypress_callback(_self: *mut c_void, raw_buffer: *const u8, len: i32,
if let Some(modifier) = modifier {
let event = Event::Key(KeyEvent::Modifier(modifier));
(*_self).send_channel.send(event).unwrap();
}else{ // Not one of the default modifiers, send an "other" event
let event = Event::Key(KeyEvent::Other);
(*_self).send_channel.send(event).unwrap();
}
}else{ // Other type of event
let event = Event::Key(KeyEvent::Other);
(*_self).send_channel.send(event).unwrap();
}
}
}

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);
@ -38,20 +39,20 @@ pub trait Context {
// MAC IMPLEMENTATION
#[cfg(target_os = "macos")]
pub fn new(send_channel: Sender<Event>) -> Box<dyn Context> {
macos::MacContext::new(send_channel)
pub fn new(send_channel: Sender<Event>, is_injecting: Arc<AtomicBool>) -> Box<dyn Context> {
macos::MacContext::new(send_channel, is_injecting)
}
// 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
#[cfg(target_os = "windows")]
pub fn new(send_channel: Sender<Event>) -> Box<dyn Context> {
windows::WindowsContext::new(send_channel)
pub fn new(send_channel: Sender<Event>, is_injecting: Arc<AtomicBool>) -> Box<dyn Context> {
windows::WindowsContext::new(send_channel, is_injecting)
}
// espanso directories

View File

@ -24,17 +24,21 @@ use crate::event::KeyModifier::*;
use std::ffi::c_void;
use std::{fs};
use widestring::{U16CString, U16CStr};
use log::{info, error};
use log::{info, error, debug};
use std::sync::atomic::AtomicBool;
use std::sync::Arc;
use std::sync::atomic::Ordering::Acquire;
const BMP_BINARY : &[u8] = include_bytes!("../res/win/espanso.bmp");
const ICO_BINARY : &[u8] = include_bytes!("../res/win/espanso.ico");
pub struct WindowsContext {
send_channel: Sender<Event>,
is_injecting: Arc<AtomicBool>,
}
impl WindowsContext {
pub fn new(send_channel: Sender<Event>) -> Box<WindowsContext> {
pub fn new(send_channel: Sender<Event>, is_injecting: Arc<AtomicBool>) -> Box<WindowsContext> {
// Initialize image resources
let espanso_dir = super::get_data_dir();
@ -68,6 +72,7 @@ impl WindowsContext {
let context = Box::new(WindowsContext{
send_channel,
is_injecting,
});
unsafe {
@ -103,11 +108,20 @@ impl super::Context for WindowsContext {
// Native bridge code
extern fn keypress_callback(_self: *mut c_void, raw_buffer: *const u16, len: i32,
is_modifier: i32, key_code: i32, is_key_down: i32) {
event_type: i32, key_code: i32, is_key_down: i32) {
unsafe {
let _self = _self as *mut WindowsContext;
// 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_key_down != 0 { // KEY DOWN EVENT
if is_modifier == 0 { // Char event
if event_type == 0 { // Char event
// Convert the received buffer to a string
let buffer = std::slice::from_raw_parts(raw_buffer, len as usize);
let c_string = U16CStr::from_slice_with_nul(buffer);
@ -130,7 +144,7 @@ extern fn keypress_callback(_self: *mut c_void, raw_buffer: *const u16, len: i32
}
}
}else{ // KEY UP event
if is_modifier != 0 { // Modifier event
if event_type == 1 { // Modifier event
let modifier: Option<KeyModifier> = match key_code {
0x5B | 0x5C => Some(META),
0x10 => Some(SHIFT),
@ -143,7 +157,14 @@ extern fn keypress_callback(_self: *mut c_void, raw_buffer: *const u16, len: i32
if let Some(modifier) = modifier {
let event = Event::Key(KeyEvent::Modifier(modifier));
(*_self).send_channel.send(event).unwrap();
}else{ // Not one of the default modifiers, send an "other" event
let event = Event::Key(KeyEvent::Other);
(*_self).send_channel.send(event).unwrap();
}
}else{
// Other type of event
let event = Event::Key(KeyEvent::Other);
(*_self).send_channel.send(event).unwrap();
}
}
}

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
@ -190,14 +189,25 @@ impl <'a, S: KeyboardManager, C: ClipboardManager, M: ConfigManager<'a>, U: UIMa
None
};
match config.backend {
BackendType::Inject => {
// Send the expected string. On linux, newlines are managed automatically
// while on windows and macos, we need to emulate a Enter key press.
let backend = if config.backend == BackendType::Auto {
if cfg!(target_os = "linux") {
self.keyboard_manager.send_string(&target_string);
let all_ascii = target_string.chars().all(|c| c.is_ascii());
if all_ascii {
debug!("All elements of the replacement are ascii, using Inject backend");
&BackendType::Inject
}else{
debug!("There are non-ascii characters, using Clipboard backend");
&BackendType::Clipboard
}
}else{
&BackendType::Inject
}
}else{
&config.backend
};
match backend {
BackendType::Inject => {
// To handle newlines, substitute each "\n" char with an Enter key press.
let splits = target_string.split('\n');
@ -208,7 +218,6 @@ impl <'a, S: KeyboardManager, C: ClipboardManager, M: ConfigManager<'a>, U: UIMa
self.keyboard_manager.send_string(split);
}
}
},
BackendType::Clipboard => {
// If the preserve_clipboard option is enabled, save the current
@ -218,6 +227,10 @@ impl <'a, S: KeyboardManager, C: ClipboardManager, M: ConfigManager<'a>, U: UIMa
self.clipboard_manager.set_clipboard(&target_string);
self.keyboard_manager.trigger_paste(&config.paste_shortcut);
},
_ => {
error!("Unsupported backend type evaluation.");
return;
}
}
if let Some(moves) = cursor_rewind {
@ -246,14 +259,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 +280,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

@ -53,7 +53,8 @@ impl From<i32> for ActionType {
#[derive(Debug, Clone)]
pub enum KeyEvent {
Char(String),
Modifier(KeyModifier)
Modifier(KeyModifier),
Other
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]

View File

@ -35,7 +35,9 @@ impl super::KeyboardManager for LinuxKeyboardManager {
}
fn send_enter(&self) {
// On linux this is not needed, so NOOP
unsafe {
send_enter();
}
}
fn trigger_paste(&self, shortcut: &PasteShortcut) {

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);

View File

@ -239,6 +239,7 @@ pub trait MatchReceiver {
pub trait Matcher : KeyEventReceiver {
fn handle_char(&self, c: &str);
fn handle_modifier(&self, m: KeyModifier);
fn handle_other(&self);
}
impl <M: Matcher> KeyEventReceiver for M {
@ -250,6 +251,9 @@ impl <M: Matcher> KeyEventReceiver for M {
KeyEvent::Modifier(m) => {
self.handle_modifier(m);
},
KeyEvent::Other => {
self.handle_other();
},
}
}
}

View File

@ -235,6 +235,17 @@ impl <'a, R: MatchReceiver, M: ConfigManager<'a>> super::Matcher for ScrollingMa
let mut current_set_queue = self.current_set_queue.borrow_mut();
current_set_queue.pop_back();
}
// Consider modifiers as separators to improve word matches reliability
let mut was_previous_char_word_separator = self.was_previous_char_word_separator.borrow_mut();
*was_previous_char_word_separator = true;
}
fn handle_other(&self) {
// When receiving "other" type of events, we mark them as valid separators.
// This dramatically improves the reliability of word matches
let mut was_previous_char_word_separator = self.was_previous_char_word_separator.borrow_mut();
*was_previous_char_word_separator = true;
}
}