diff --git a/espanso/src/cli/worker/builtin/debug.rs b/espanso/src/cli/worker/builtin/debug.rs index 2074528..4d43e65 100644 --- a/espanso/src/cli/worker/builtin/debug.rs +++ b/espanso/src/cli/worker/builtin/debug.rs @@ -39,6 +39,7 @@ pub fn create_match_paste_active_config_info() -> BuiltInMatch { force_mode: None, }) }, + ..Default::default() } } @@ -62,5 +63,6 @@ pub fn create_match_paste_active_app_info() -> BuiltInMatch { force_mode: None, }) }, + ..Default::default() } } diff --git a/espanso/src/cli/worker/builtin/mod.rs b/espanso/src/cli/worker/builtin/mod.rs index d2f7bf9..a27b457 100644 --- a/espanso/src/cli/worker/builtin/mod.rs +++ b/espanso/src/cli/worker/builtin/mod.rs @@ -34,17 +34,33 @@ pub struct BuiltInMatch { pub id: i32, pub label: &'static str, pub triggers: Vec, + pub hotkey: Option, pub action: fn(context: &dyn Context) -> EventType, } +impl Default for BuiltInMatch { + fn default() -> Self { + Self { + id: 0, + label: "", + triggers: Vec::new(), + hotkey: None, + action: |_| EventType::NOOP, + } + } +} + pub fn get_builtin_matches(config: &dyn Config) -> Vec { let mut matches = vec![ debug::create_match_paste_active_config_info(), debug::create_match_paste_active_app_info(), ]; - if let Some(search_trigger) = config.search_trigger() { - matches.push(search::create_match_trigger_search_bar(&search_trigger)); + if config.search_trigger().is_some() || config.search_shortcut().is_some() { + matches.push(search::create_match_trigger_search_bar( + config.search_trigger(), + config.search_shortcut(), + )); } matches diff --git a/espanso/src/cli/worker/builtin/search.rs b/espanso/src/cli/worker/builtin/search.rs index 296540d..8443bce 100644 --- a/espanso/src/cli/worker/builtin/search.rs +++ b/espanso/src/cli/worker/builtin/search.rs @@ -21,13 +21,15 @@ use crate::{cli::worker::builtin::generate_next_builtin_id, engine::event::{Even use super::BuiltInMatch; -pub fn create_match_trigger_search_bar(shortcut: &str) -> BuiltInMatch { +pub fn create_match_trigger_search_bar(trigger: Option, hotkey: Option) -> BuiltInMatch { BuiltInMatch { id: generate_next_builtin_id(), label: "Open search bar", - triggers: vec![shortcut.to_string()], + triggers: trigger.map(|trigger| vec![trigger]).unwrap_or_default(), + hotkey, action: |_| { EventType::ShowSearchBar }, + ..Default::default() } } diff --git a/espanso/src/cli/worker/engine/funnel/detect.rs b/espanso/src/cli/worker/engine/funnel/detect.rs index b4a77ed..035612e 100644 --- a/espanso/src/cli/worker/engine/funnel/detect.rs +++ b/espanso/src/cli/worker/engine/funnel/detect.rs @@ -18,9 +18,9 @@ */ use crossbeam::channel::{Receiver, Select, SelectedOperation}; -use espanso_detect::event::InputEvent; +use espanso_detect::event::{InputEvent}; -use crate::engine::{event::{Event, EventType, SourceId, input::{Key, KeyboardEvent, MouseButton, MouseEvent, Status, Variant}}, funnel}; +use crate::engine::{event::{Event, EventType, SourceId, input::{HotKeyEvent, Key, KeyboardEvent, MouseButton, MouseEvent, Status, Variant}}, funnel}; pub struct DetectSource { pub receiver: Receiver<(InputEvent, SourceId)>, @@ -52,7 +52,12 @@ impl<'a> funnel::Source<'a> for DetectSource { button: mouse_event.button.into(), }), }, - InputEvent::HotKey(_) => todo!(), // TODO + InputEvent::HotKey(hotkey_event) => Event { + source_id, + etype: EventType::HotKey(HotKeyEvent { + hotkey_id: hotkey_event.hotkey_id, + }) + } } } } diff --git a/espanso/src/cli/worker/engine/mod.rs b/espanso/src/cli/worker/engine/mod.rs index bdb6653..5a1bf19 100644 --- a/espanso/src/cli/worker/engine/mod.rs +++ b/espanso/src/cli/worker/engine/mod.rs @@ -91,7 +91,7 @@ pub fn initialize_and_spawn( super::engine::funnel::init_and_spawn(SourceCreationOptions { use_evdev: use_evdev_backend, evdev_keyboard_rmlvo: keyboard_layout_util::generate_detect_rmlvo(&*config_manager.default()), - ..Default::default() + hotkeys: match_converter.get_hotkeys(), }) .expect("failed to initialize detector module"); let exit_source = super::engine::funnel::exit::ExitSource::new(exit_signal, &sequencer); diff --git a/espanso/src/cli/worker/engine/process/middleware/matcher/convert.rs b/espanso/src/cli/worker/engine/process/middleware/matcher/convert.rs index 24bad2b..3652e13 100644 --- a/espanso/src/cli/worker/engine/process/middleware/matcher/convert.rs +++ b/espanso/src/cli/worker/engine/process/middleware/matcher/convert.rs @@ -24,10 +24,12 @@ use espanso_config::{ MatchCause, }, }; +use espanso_detect::hotkey::HotKey; use espanso_match::{ regex::RegexMatch, rolling::{RollingMatch, StringMatchOptions}, }; +use log::error; use std::iter::FromIterator; use crate::cli::worker::builtin::BuiltInMatch; @@ -79,7 +81,7 @@ impl<'a> MatchConverter<'a> { matches.push(RollingMatch::from_string( m.id, &trigger, - &StringMatchOptions::default() + &StringMatchOptions::default(), )) } } @@ -101,6 +103,26 @@ impl<'a> MatchConverter<'a> { matches } + pub fn get_hotkeys(&self) -> Vec { + let mut hotkeys = Vec::new(); + + // TODO: read user-defined matches + + // Then convert built-in ones + for m in self.builtin_matches { + if let Some(hotkey) = &m.hotkey { + match HotKey::new(m.id, hotkey) { + Ok(hotkey) => hotkeys.push(hotkey), + Err(err) => { + error!("unable to register hotkey: {}, with error: {}", hotkey, err); + } + } + } + } + + hotkeys + } + fn global_match_set(&self) -> MatchSet { let paths = self.config_store.get_all_match_paths(); self.match_store.query(&Vec::from_iter(paths.into_iter())) diff --git a/espanso/src/engine/event/input.rs b/espanso/src/engine/event/input.rs index b667837..b0f9f21 100644 --- a/espanso/src/engine/event/input.rs +++ b/espanso/src/engine/event/input.rs @@ -115,4 +115,9 @@ pub enum Key { #[derive(Debug, Clone, PartialEq)] pub struct ContextMenuClickedEvent { pub context_item_id: u32, +} + +#[derive(Debug, Clone, PartialEq)] +pub struct HotKeyEvent { + pub hotkey_id: i32, } \ No newline at end of file diff --git a/espanso/src/engine/event/internal.rs b/espanso/src/engine/event/internal.rs index 642a0b1..a35acad 100644 --- a/espanso/src/engine/event/internal.rs +++ b/espanso/src/engine/event/internal.rs @@ -24,7 +24,7 @@ pub struct MatchesDetectedEvent { pub matches: Vec, } -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Default)] pub struct DetectedMatch { pub id: i32, pub trigger: Option, diff --git a/espanso/src/engine/event/mod.rs b/espanso/src/engine/event/mod.rs index c0b1c9b..7e642bc 100644 --- a/espanso/src/engine/event/mod.rs +++ b/espanso/src/engine/event/mod.rs @@ -55,7 +55,7 @@ pub enum EventType { // Inputs Keyboard(input::KeyboardEvent), Mouse(input::MouseEvent), - // TODO: hotkeys + HotKey(input::HotKeyEvent), TrayIconClicked, ContextMenuClicked(input::ContextMenuClickedEvent), diff --git a/espanso/src/engine/process/default.rs b/espanso/src/engine/process/default.rs index 967ec6a..6af7f0f 100644 --- a/espanso/src/engine/process/default.rs +++ b/espanso/src/engine/process/default.rs @@ -25,7 +25,7 @@ use super::{DisableOptions, MatchFilter, MatchInfoProvider, MatchProvider, Match delay_modifiers::{DelayForModifierReleaseMiddleware, ModifierStatusProvider}, markdown::MarkdownMiddleware, past_discard::PastEventsDiscardMiddleware, }}; -use crate::engine::{event::{Event, EventType}, process::middleware::{context_menu::ContextMenuMiddleware, disable::DisableMiddleware, exit::ExitMiddleware, icon_status::IconStatusMiddleware, image_resolve::ImageResolverMiddleware, search::SearchMiddleware}}; +use crate::engine::{event::{Event, EventType}, process::middleware::{context_menu::ContextMenuMiddleware, disable::DisableMiddleware, exit::ExitMiddleware, hotkey::HotKeyMiddleware, icon_status::IconStatusMiddleware, image_resolve::ImageResolverMiddleware, search::SearchMiddleware}}; use std::collections::VecDeque; pub struct DefaultProcessor<'a> { @@ -56,6 +56,7 @@ impl<'a> DefaultProcessor<'a> { Box::new(IconStatusMiddleware::new()), Box::new(MatcherMiddleware::new(matchers, matcher_options_provider)), Box::new(ContextMenuMiddleware::new()), + Box::new(HotKeyMiddleware::new()), Box::new(MatchSelectMiddleware::new(match_filter, match_selector)), Box::new(CauseCompensateMiddleware::new()), Box::new(MultiplexMiddleware::new(multiplexer)), diff --git a/espanso/src/engine/process/middleware/cause.rs b/espanso/src/engine/process/middleware/cause.rs index 521ef22..cb1f357 100644 --- a/espanso/src/engine/process/middleware/cause.rs +++ b/espanso/src/engine/process/middleware/cause.rs @@ -30,7 +30,7 @@ impl CauseCompensateMiddleware { impl Middleware for CauseCompensateMiddleware { fn name(&self) -> &'static str { - "discard" + "cause_compensate" } fn next(&self, event: Event, dispatch: &mut dyn FnMut(Event)) -> Event { diff --git a/espanso/src/engine/process/middleware/hotkey.rs b/espanso/src/engine/process/middleware/hotkey.rs new file mode 100644 index 0000000..1a42d80 --- /dev/null +++ b/espanso/src/engine/process/middleware/hotkey.rs @@ -0,0 +1,52 @@ +/* + * This file is part of espanso id: (), trigger: (), trigger: (), left_separator: (), right_separator: (), args: () left_separator: (), right_separator: (), args: () id: (), trigger: (), left_separator: (), right_separator: (), args: (). + * + * Copyright (C) 2019-2021 Federico Terzi + * + * espanso is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * espanso is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with espanso. If not, see . + */ + +use super::super::Middleware; +use crate::engine::{event::{Event, EventType, internal::{DetectedMatch, MatchesDetectedEvent}}}; + +pub struct HotKeyMiddleware {} + +impl HotKeyMiddleware { + pub fn new() -> Self { + Self {} + } +} + +impl Middleware for HotKeyMiddleware { + fn name(&self) -> &'static str { + "hotkey" + } + + fn next(&self, event: Event, _: &mut dyn FnMut(Event)) -> Event { + if let EventType::HotKey(m_event) = &event.etype { + return Event::caused_by(event.source_id, EventType::MatchesDetected(MatchesDetectedEvent { + matches: vec![ + DetectedMatch { + id: m_event.hotkey_id, + ..Default::default() + } + ], + })); + } + + event + } +} + +// TODO: test diff --git a/espanso/src/engine/process/middleware/mod.rs b/espanso/src/engine/process/middleware/mod.rs index 1c7a9b4..d585c07 100644 --- a/espanso/src/engine/process/middleware/mod.rs +++ b/espanso/src/engine/process/middleware/mod.rs @@ -24,6 +24,7 @@ pub mod cursor_hint; pub mod delay_modifiers; pub mod disable; pub mod exit; +pub mod hotkey; pub mod icon_status; pub mod image_resolve; pub mod match_select; diff --git a/espanso/src/patch/patches/macros.rs b/espanso/src/patch/patches/macros.rs index 5a41fd3..05d0943 100644 --- a/espanso/src/patch/patches/macros.rs +++ b/espanso/src/patch/patches/macros.rs @@ -78,6 +78,10 @@ macro_rules! generate_patchable_config { fn search_trigger(&self) -> Option { self.base.search_trigger() } + + fn search_shortcut(&self) -> Option { + self.base.search_shortcut() + } } }; } \ No newline at end of file