diff --git a/espanso-engine/src/process/default.rs b/espanso-engine/src/process/default.rs index 826f388..b0f85b8 100644 --- a/espanso-engine/src/process/default.rs +++ b/espanso-engine/src/process/default.rs @@ -33,8 +33,8 @@ use super::{ render::RenderMiddleware, }, DisableOptions, EnabledStatusProvider, MatchFilter, MatchInfoProvider, MatchProvider, - MatchSelector, Matcher, MatcherMiddlewareConfigProvider, Middleware, Multiplexer, PathProvider, - Processor, Renderer, UndoEnabledProvider, + MatchSelector, Matcher, MatcherMiddlewareConfigProvider, Middleware, ModifierStateProvider, + Multiplexer, PathProvider, Processor, Renderer, UndoEnabledProvider, }; use crate::{ event::{Event, EventType}, @@ -69,6 +69,7 @@ impl<'a> DefaultProcessor<'a> { match_provider: &'a dyn MatchProvider, undo_enabled_provider: &'a dyn UndoEnabledProvider, enabled_status_provider: &'a dyn EnabledStatusProvider, + modifier_state_provider: &'a dyn ModifierStateProvider, ) -> DefaultProcessor<'a> { Self { event_queue: VecDeque::new(), @@ -76,7 +77,11 @@ impl<'a> DefaultProcessor<'a> { Box::new(EventsDiscardMiddleware::new()), Box::new(DisableMiddleware::new(disable_options)), Box::new(IconStatusMiddleware::new()), - Box::new(MatcherMiddleware::new(matchers, matcher_options_provider)), + Box::new(MatcherMiddleware::new( + matchers, + matcher_options_provider, + modifier_state_provider, + )), Box::new(SuppressMiddleware::new(enabled_status_provider)), Box::new(ContextMenuMiddleware::new()), Box::new(HotKeyMiddleware::new()), diff --git a/espanso-engine/src/process/middleware/matcher.rs b/espanso-engine/src/process/middleware/matcher.rs index 7b3e666..87d97ea 100644 --- a/espanso-engine/src/process/middleware/matcher.rs +++ b/espanso-engine/src/process/middleware/matcher.rs @@ -57,18 +57,32 @@ pub trait MatcherMiddlewareConfigProvider { fn max_history_size(&self) -> usize; } +pub trait ModifierStateProvider { + fn get_modifier_state(&self) -> ModifierState; +} + +#[derive(Debug, Clone)] +pub struct ModifierState { + pub is_ctrl_down: bool, + pub is_alt_down: bool, + pub is_meta_down: bool, +} + pub struct MatcherMiddleware<'a, State> { matchers: &'a [&'a dyn Matcher<'a, State>], matcher_states: RefCell>>, max_history_size: usize, + + modifier_status_provider: &'a dyn ModifierStateProvider, } impl<'a, State> MatcherMiddleware<'a, State> { pub fn new( matchers: &'a [&'a dyn Matcher<'a, State>], options_provider: &'a dyn MatcherMiddlewareConfigProvider, + modifier_status_provider: &'a dyn ModifierStateProvider, ) -> Self { let max_history_size = options_provider.max_history_size(); @@ -76,6 +90,7 @@ impl<'a, State> MatcherMiddleware<'a, State> { matchers, matcher_states: RefCell::new(VecDeque::new()), max_history_size, + modifier_status_provider, } } } @@ -101,6 +116,17 @@ impl<'a, State> Middleware for MatcherMiddleware<'a, State> { matcher_states.pop_back(); return event; } + + // We need to filter out some keyboard events if they are generated + // while some modifier keys are pressed, otherwise we could have + // wrong matches being detected. + // See: https://github.com/federico-terzi/espanso/issues/725 + if should_skip_key_event_due_to_modifier_press( + &self.modifier_status_provider.get_modifier_state(), + ) { + trace!("skipping keyboard event because incompatible modifiers are pressed"); + return event; + } } // Some keys (such as the arrow keys) and mouse clicks prevent espanso from building @@ -161,6 +187,17 @@ fn is_event_of_interest(event_type: &EventType) -> bool { // Skip non-press events false } else { + // Skip linux Keyboard (XKB) Extension function and modifier keys + // In hex, they have the byte 3 = 0xfe + // See list in "keysymdef.h" file + if cfg!(target_os = "linux") { + if let Key::Other(raw_code) = &keyboard_event.key { + if (65025..=65276).contains(raw_code) { + return false; + } + } + } + // Skip modifier keys !matches!( keyboard_event.key, @@ -205,4 +242,16 @@ fn is_invalidating_event(event_type: &EventType) -> bool { } } +fn should_skip_key_event_due_to_modifier_press(modifier_state: &ModifierState) -> bool { + if cfg!(target_os = "macos") { + modifier_state.is_meta_down + } else if cfg!(target_os = "windows") { + modifier_state.is_alt_down + } else if cfg!(target_os = "linux") { + modifier_state.is_alt_down || modifier_state.is_meta_down + } else { + unreachable!() + } +} + // TODO: test diff --git a/espanso-engine/src/process/mod.rs b/espanso-engine/src/process/mod.rs index 91bce0d..1e295f9 100644 --- a/espanso-engine/src/process/mod.rs +++ b/espanso-engine/src/process/mod.rs @@ -39,7 +39,8 @@ pub use middleware::disable::DisableOptions; pub use middleware::image_resolve::PathProvider; pub use middleware::match_select::{MatchFilter, MatchSelector}; pub use middleware::matcher::{ - MatchResult, Matcher, MatcherEvent, MatcherMiddlewareConfigProvider, + MatchResult, Matcher, MatcherEvent, MatcherMiddlewareConfigProvider, ModifierState, + ModifierStateProvider, }; pub use middleware::multiplex::Multiplexer; pub use middleware::render::{Renderer, RendererError}; @@ -63,6 +64,7 @@ pub fn default<'a, MatcherState>( match_provider: &'a dyn MatchProvider, undo_enabled_provider: &'a dyn UndoEnabledProvider, enabled_status_provider: &'a dyn EnabledStatusProvider, + modifier_state_provider: &'a dyn ModifierStateProvider, ) -> impl Processor + 'a { default::DefaultProcessor::new( matchers, @@ -79,5 +81,6 @@ pub fn default<'a, MatcherState>( match_provider, undo_enabled_provider, enabled_status_provider, + modifier_state_provider, ) }