From 974e405b235f62a06cf6e2a1c170b94068d6b0cc Mon Sep 17 00:00:00 2001 From: Federico Terzi Date: Sat, 14 Aug 2021 11:07:43 +0200 Subject: [PATCH] feat(engine): move engine into separate module --- espanso-engine/Cargo.toml | 17 ++ espanso-engine/src/dispatch/default.rs | 78 +++++++ .../src/dispatch/executor/context_menu.rs | 53 +++++ .../src/dispatch/executor/html_inject.rs | 64 ++++++ .../src/dispatch/executor/icon_update.rs | 56 +++++ .../src/dispatch/executor/image_inject.rs | 56 +++++ .../src/dispatch/executor/key_inject.rs | 52 +++++ espanso-engine/src/dispatch/executor/mod.rs | 26 +++ .../src/dispatch/executor/secure_input.rs | 59 +++++ .../src/dispatch/executor/text_inject.rs | 117 ++++++++++ espanso-engine/src/dispatch/mod.rs | 65 ++++++ espanso-engine/src/event/effect.rs | 63 ++++++ espanso-engine/src/event/input.rs | 123 +++++++++++ espanso-engine/src/event/internal.rs | 91 ++++++++ espanso-engine/src/event/mod.rs | 107 +++++++++ espanso-engine/src/event/ui.rs | 54 +++++ espanso-engine/src/funnel/default.rs | 54 +++++ espanso-engine/src/funnel/mod.rs | 44 ++++ espanso-engine/src/lib.rs | 74 +++++++ espanso-engine/src/process/default.rs | 155 +++++++++++++ .../src/process/middleware/action.rs | 136 ++++++++++++ .../src/process/middleware/cause.rs | 58 +++++ .../src/process/middleware/context_menu.rs | 185 ++++++++++++++++ .../src/process/middleware/cursor_hint.rs | 82 +++++++ .../src/process/middleware/delay_modifiers.rs | 84 +++++++ .../src/process/middleware/disable.rs | 138 ++++++++++++ espanso-engine/src/process/middleware/exit.rs | 51 +++++ .../src/process/middleware/hotkey.rs | 56 +++++ .../src/process/middleware/icon_status.rs | 81 +++++++ .../src/process/middleware/image_resolve.rs | 81 +++++++ .../src/process/middleware/markdown.rs | 63 ++++++ .../src/process/middleware/match_select.rs | 102 +++++++++ .../src/process/middleware/matcher.rs | 207 ++++++++++++++++++ espanso-engine/src/process/middleware/mod.rs | 36 +++ .../src/process/middleware/multiplex.rs | 59 +++++ .../src/process/middleware/past_discard.rs | 69 ++++++ .../src/process/middleware/render.rs | 104 +++++++++ .../src/process/middleware/search.rs | 75 +++++++ espanso-engine/src/process/mod.rs | 77 +++++++ 39 files changed, 3152 insertions(+) create mode 100644 espanso-engine/Cargo.toml create mode 100644 espanso-engine/src/dispatch/default.rs create mode 100644 espanso-engine/src/dispatch/executor/context_menu.rs create mode 100644 espanso-engine/src/dispatch/executor/html_inject.rs create mode 100644 espanso-engine/src/dispatch/executor/icon_update.rs create mode 100644 espanso-engine/src/dispatch/executor/image_inject.rs create mode 100644 espanso-engine/src/dispatch/executor/key_inject.rs create mode 100644 espanso-engine/src/dispatch/executor/mod.rs create mode 100644 espanso-engine/src/dispatch/executor/secure_input.rs create mode 100644 espanso-engine/src/dispatch/executor/text_inject.rs create mode 100644 espanso-engine/src/dispatch/mod.rs create mode 100644 espanso-engine/src/event/effect.rs create mode 100644 espanso-engine/src/event/input.rs create mode 100644 espanso-engine/src/event/internal.rs create mode 100644 espanso-engine/src/event/mod.rs create mode 100644 espanso-engine/src/event/ui.rs create mode 100644 espanso-engine/src/funnel/default.rs create mode 100644 espanso-engine/src/funnel/mod.rs create mode 100644 espanso-engine/src/lib.rs create mode 100644 espanso-engine/src/process/default.rs create mode 100644 espanso-engine/src/process/middleware/action.rs create mode 100644 espanso-engine/src/process/middleware/cause.rs create mode 100644 espanso-engine/src/process/middleware/context_menu.rs create mode 100644 espanso-engine/src/process/middleware/cursor_hint.rs create mode 100644 espanso-engine/src/process/middleware/delay_modifiers.rs create mode 100644 espanso-engine/src/process/middleware/disable.rs create mode 100644 espanso-engine/src/process/middleware/exit.rs create mode 100644 espanso-engine/src/process/middleware/hotkey.rs create mode 100644 espanso-engine/src/process/middleware/icon_status.rs create mode 100644 espanso-engine/src/process/middleware/image_resolve.rs create mode 100644 espanso-engine/src/process/middleware/markdown.rs create mode 100644 espanso-engine/src/process/middleware/match_select.rs create mode 100644 espanso-engine/src/process/middleware/matcher.rs create mode 100644 espanso-engine/src/process/middleware/mod.rs create mode 100644 espanso-engine/src/process/middleware/multiplex.rs create mode 100644 espanso-engine/src/process/middleware/past_discard.rs create mode 100644 espanso-engine/src/process/middleware/render.rs create mode 100644 espanso-engine/src/process/middleware/search.rs create mode 100644 espanso-engine/src/process/mod.rs diff --git a/espanso-engine/Cargo.toml b/espanso-engine/Cargo.toml new file mode 100644 index 0000000..0ad0db8 --- /dev/null +++ b/espanso-engine/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "espanso-engine" +version = "0.1.0" +authors = ["Federico Terzi "] +edition = "2018" + +[dependencies] +log = "0.4.14" +anyhow = "1.0.38" +thiserror = "1.0.23" +crossbeam = "0.8.0" +markdown = "0.3.0" +html2text = "0.2.1" + + +[dev-dependencies] +tempdir = "0.3.7" \ No newline at end of file diff --git a/espanso-engine/src/dispatch/default.rs b/espanso-engine/src/dispatch/default.rs new file mode 100644 index 0000000..bb14a77 --- /dev/null +++ b/espanso-engine/src/dispatch/default.rs @@ -0,0 +1,78 @@ +/* + * This file is part of espanso. + * + * 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::{ContextMenuHandler, Event, IconHandler, ImageInjector, SecureInputManager}; +use super::{Dispatcher, Executor, HtmlInjector, KeyInjector, ModeProvider, TextInjector}; + +pub struct DefaultDispatcher<'a> { + executors: Vec>, +} + +#[allow(clippy::too_many_arguments)] +impl<'a> DefaultDispatcher<'a> { + pub fn new( + event_injector: &'a dyn TextInjector, + clipboard_injector: &'a dyn TextInjector, + mode_provider: &'a dyn ModeProvider, + key_injector: &'a dyn KeyInjector, + html_injector: &'a dyn HtmlInjector, + image_injector: &'a dyn ImageInjector, + context_menu_handler: &'a dyn ContextMenuHandler, + icon_handler: &'a dyn IconHandler, + secure_input_manager: &'a dyn SecureInputManager, + ) -> Self { + Self { + executors: vec![ + Box::new(super::executor::text_inject::TextInjectExecutor::new( + event_injector, + clipboard_injector, + mode_provider, + )), + Box::new(super::executor::key_inject::KeyInjectExecutor::new( + key_injector, + )), + Box::new(super::executor::html_inject::HtmlInjectExecutor::new( + html_injector, + )), + Box::new(super::executor::image_inject::ImageInjectExecutor::new( + image_injector, + )), + Box::new(super::executor::context_menu::ContextMenuExecutor::new( + context_menu_handler, + )), + Box::new(super::executor::icon_update::IconUpdateExecutor::new( + icon_handler, + )), + Box::new(super::executor::secure_input::SecureInputExecutor::new( + secure_input_manager, + )), + ], + } + } +} + +impl<'a> Dispatcher for DefaultDispatcher<'a> { + fn dispatch(&self, event: Event) { + for executor in self.executors.iter() { + if executor.execute(&event) { + break; + } + } + } +} diff --git a/espanso-engine/src/dispatch/executor/context_menu.rs b/espanso-engine/src/dispatch/executor/context_menu.rs new file mode 100644 index 0000000..5c94ec1 --- /dev/null +++ b/espanso-engine/src/dispatch/executor/context_menu.rs @@ -0,0 +1,53 @@ +/* + * This file is part of espanso. + * + * 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 crate::event::{ui::MenuItem, EventType}; +use crate::{dispatch::Executor, event::Event}; +use anyhow::Result; +use log::error; + +pub trait ContextMenuHandler { + fn show_context_menu(&self, items: &[MenuItem]) -> Result<()>; +} + +pub struct ContextMenuExecutor<'a> { + handler: &'a dyn ContextMenuHandler, +} + +impl<'a> ContextMenuExecutor<'a> { + pub fn new(handler: &'a dyn ContextMenuHandler) -> Self { + Self { handler } + } +} + +impl<'a> Executor for ContextMenuExecutor<'a> { + fn execute(&self, event: &Event) -> bool { + if let EventType::ShowContextMenu(context_menu_event) = &event.etype { + if let Err(error) = self.handler.show_context_menu(&context_menu_event.items) { + error!("context menu handler reported an error: {:?}", error); + } + + return true; + } + + false + } +} + +// TODO: test diff --git a/espanso-engine/src/dispatch/executor/html_inject.rs b/espanso-engine/src/dispatch/executor/html_inject.rs new file mode 100644 index 0000000..d7207c0 --- /dev/null +++ b/espanso-engine/src/dispatch/executor/html_inject.rs @@ -0,0 +1,64 @@ +/* + * This file is part of espanso. + * + * 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 anyhow::Result; +use log::error; + +use crate::{ + dispatch::Executor, + event::{Event, EventType}, +}; + +pub trait HtmlInjector { + fn inject_html(&self, html: &str, fallback: &str) -> Result<()>; +} + +pub struct HtmlInjectExecutor<'a> { + injector: &'a dyn HtmlInjector, +} + +impl<'a> HtmlInjectExecutor<'a> { + pub fn new(injector: &'a dyn HtmlInjector) -> Self { + Self { injector } + } +} + +impl<'a> Executor for HtmlInjectExecutor<'a> { + fn execute(&self, event: &Event) -> bool { + if let EventType::HtmlInject(inject_event) = &event.etype { + // Render the text fallback for those applications that don't support HTML clipboard + let decorator = html2text::render::text_renderer::TrivialDecorator::new(); + let fallback_text = + html2text::from_read_with_decorator(inject_event.html.as_bytes(), 1000000, decorator); + + if let Err(error) = self + .injector + .inject_html(&inject_event.html, &fallback_text) + { + error!("html injector reported an error: {:?}", error); + } + + return true; + } + + false + } +} + +// TODO: test diff --git a/espanso-engine/src/dispatch/executor/icon_update.rs b/espanso-engine/src/dispatch/executor/icon_update.rs new file mode 100644 index 0000000..7a8d6f7 --- /dev/null +++ b/espanso-engine/src/dispatch/executor/icon_update.rs @@ -0,0 +1,56 @@ +/* + * This file is part of espanso. + * + * 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 anyhow::Result; +use log::error; + +use crate::{ + dispatch::Executor, + event::{ui::IconStatus, Event, EventType}, +}; + +pub trait IconHandler { + fn update_icon(&self, status: &IconStatus) -> Result<()>; +} + +pub struct IconUpdateExecutor<'a> { + handler: &'a dyn IconHandler, +} + +impl<'a> IconUpdateExecutor<'a> { + pub fn new(handler: &'a dyn IconHandler) -> Self { + Self { handler } + } +} + +impl<'a> Executor for IconUpdateExecutor<'a> { + fn execute(&self, event: &Event) -> bool { + if let EventType::IconStatusChange(m_event) = &event.etype { + if let Err(error) = self.handler.update_icon(&m_event.status) { + error!("icon handler reported an error: {:?}", error); + } + + return true; + } + + false + } +} + +// TODO: test diff --git a/espanso-engine/src/dispatch/executor/image_inject.rs b/espanso-engine/src/dispatch/executor/image_inject.rs new file mode 100644 index 0000000..df3daa5 --- /dev/null +++ b/espanso-engine/src/dispatch/executor/image_inject.rs @@ -0,0 +1,56 @@ +/* + * This file is part of espanso. + * + * 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 anyhow::Result; +use log::error; + +use crate::{ + dispatch::Executor, + event::{Event, EventType}, +}; + +pub trait ImageInjector { + fn inject_image(&self, path: &str) -> Result<()>; +} + +pub struct ImageInjectExecutor<'a> { + injector: &'a dyn ImageInjector, +} + +impl<'a> ImageInjectExecutor<'a> { + pub fn new(injector: &'a dyn ImageInjector) -> Self { + Self { injector } + } +} + +impl<'a> Executor for ImageInjectExecutor<'a> { + fn execute(&self, event: &Event) -> bool { + if let EventType::ImageInject(inject_event) = &event.etype { + if let Err(error) = self.injector.inject_image(&inject_event.image_path) { + error!("image injector reported an error: {:?}", error); + } + + return true; + } + + false + } +} + +// TODO: test diff --git a/espanso-engine/src/dispatch/executor/key_inject.rs b/espanso-engine/src/dispatch/executor/key_inject.rs new file mode 100644 index 0000000..e2c8d74 --- /dev/null +++ b/espanso-engine/src/dispatch/executor/key_inject.rs @@ -0,0 +1,52 @@ +/* + * This file is part of espanso. + * + * 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 crate::{ + dispatch::Executor, + event::{input::Key, Event, EventType}, +}; +use anyhow::Result; +use log::error; + +pub trait KeyInjector { + fn inject_sequence(&self, keys: &[Key]) -> Result<()>; +} + +pub struct KeyInjectExecutor<'a> { + injector: &'a dyn KeyInjector, +} + +impl<'a> KeyInjectExecutor<'a> { + pub fn new(injector: &'a dyn KeyInjector) -> Self { + Self { injector } + } +} + +impl<'a> Executor for KeyInjectExecutor<'a> { + fn execute(&self, event: &Event) -> bool { + if let EventType::KeySequenceInject(inject_event) = &event.etype { + if let Err(error) = self.injector.inject_sequence(&inject_event.keys) { + error!("key injector reported an error: {}", error); + } + return true; + } + + false + } +} diff --git a/espanso-engine/src/dispatch/executor/mod.rs b/espanso-engine/src/dispatch/executor/mod.rs new file mode 100644 index 0000000..31bb6d0 --- /dev/null +++ b/espanso-engine/src/dispatch/executor/mod.rs @@ -0,0 +1,26 @@ +/* + * This file is part of espanso. + * + * 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 . + */ + +pub mod context_menu; +pub mod html_inject; +pub mod icon_update; +pub mod image_inject; +pub mod key_inject; +pub mod secure_input; +pub mod text_inject; diff --git a/espanso-engine/src/dispatch/executor/secure_input.rs b/espanso-engine/src/dispatch/executor/secure_input.rs new file mode 100644 index 0000000..17481ab --- /dev/null +++ b/espanso-engine/src/dispatch/executor/secure_input.rs @@ -0,0 +1,59 @@ +/* + * This file is part of espanso. + * + * 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 anyhow::Result; +use log::error; + +use crate::{ + dispatch::Executor, + event::{Event, EventType}, +}; + +pub trait SecureInputManager { + fn display_secure_input_troubleshoot(&self) -> Result<()>; + fn launch_secure_input_autofix(&self) -> Result<()>; +} + +pub struct SecureInputExecutor<'a> { + manager: &'a dyn SecureInputManager, +} + +impl<'a> SecureInputExecutor<'a> { + pub fn new(manager: &'a dyn SecureInputManager) -> Self { + Self { manager } + } +} + +impl<'a> Executor for SecureInputExecutor<'a> { + fn execute(&self, event: &Event) -> bool { + if let EventType::DisplaySecureInputTroubleshoot = &event.etype { + if let Err(error) = self.manager.display_secure_input_troubleshoot() { + error!("unable to display secure input troubleshoot: {}", error); + } + return true; + } else if let EventType::LaunchSecureInputAutoFix = &event.etype { + if let Err(error) = self.manager.launch_secure_input_autofix() { + error!("unable to launch secure input autofix: {}", error); + } + return true; + } + + false + } +} diff --git a/espanso-engine/src/dispatch/executor/text_inject.rs b/espanso-engine/src/dispatch/executor/text_inject.rs new file mode 100644 index 0000000..f2eb9fb --- /dev/null +++ b/espanso-engine/src/dispatch/executor/text_inject.rs @@ -0,0 +1,117 @@ +/* + * This file is part of espanso. + * + * 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 crate::{ + dispatch::Executor, + event::{effect::TextInjectMode, Event, EventType}, +}; +use anyhow::Result; +use log::{error, trace}; + +pub trait TextInjector { + fn name(&self) -> &'static str; + fn inject_text(&self, text: &str) -> Result<()>; +} + +pub trait ModeProvider { + fn active_mode(&self) -> Mode; +} + +pub enum Mode { + Event, + Clipboard, + Auto { + // Maximum size after which the clipboard backend + // is used over the event one to speed up the injection. + clipboard_threshold: usize, + }, +} + +pub struct TextInjectExecutor<'a> { + event_injector: &'a dyn TextInjector, + clipboard_injector: &'a dyn TextInjector, + mode_provider: &'a dyn ModeProvider, +} + +impl<'a> TextInjectExecutor<'a> { + pub fn new( + event_injector: &'a dyn TextInjector, + clipboard_injector: &'a dyn TextInjector, + mode_provider: &'a dyn ModeProvider, + ) -> Self { + Self { + event_injector, + clipboard_injector, + mode_provider, + } + } +} + +impl<'a> Executor for TextInjectExecutor<'a> { + fn execute(&self, event: &Event) -> bool { + if let EventType::TextInject(inject_event) = &event.etype { + let active_mode = self.mode_provider.active_mode(); + + let injector = if let Some(force_mode) = &inject_event.force_mode { + if let TextInjectMode::Keys = force_mode { + self.event_injector + } else { + self.clipboard_injector + } + } else if let Mode::Clipboard = active_mode { + self.clipboard_injector + } else if let Mode::Event = active_mode { + self.event_injector + } else if let Mode::Auto { + clipboard_threshold, + } = active_mode + { + if inject_event.text.chars().count() > clipboard_threshold { + self.clipboard_injector + } else if cfg!(target_os = "linux") { + if inject_event.text.chars().all(|c| c.is_ascii()) { + self.event_injector + } else { + self.clipboard_injector + } + } else { + self.event_injector + } + } else { + self.event_injector + }; + + trace!("using injector: {}", injector.name()); + + if let Err(error) = injector.inject_text(&inject_event.text) { + error!( + "text injector ({}) reported an error: {:?}", + injector.name(), + error + ); + } + + return true; + } + + false + } +} + +// TODO: test diff --git a/espanso-engine/src/dispatch/mod.rs b/espanso-engine/src/dispatch/mod.rs new file mode 100644 index 0000000..d2959ff --- /dev/null +++ b/espanso-engine/src/dispatch/mod.rs @@ -0,0 +1,65 @@ +/* + * This file is part of espanso. + * + * 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 crate::event::Event; + +mod default; +mod executor; + +pub trait Executor { + fn execute(&self, event: &Event) -> bool; +} + +pub trait Dispatcher { + fn dispatch(&self, event: Event); +} + +// Re-export dependency injection entities +pub use executor::context_menu::ContextMenuHandler; +pub use executor::html_inject::HtmlInjector; +pub use executor::icon_update::IconHandler; +pub use executor::image_inject::ImageInjector; +pub use executor::key_inject::KeyInjector; +pub use executor::secure_input::SecureInputManager; +pub use executor::text_inject::{Mode, ModeProvider, TextInjector}; + +#[allow(clippy::too_many_arguments)] +pub fn default<'a>( + event_injector: &'a dyn TextInjector, + clipboard_injector: &'a dyn TextInjector, + mode_provider: &'a dyn ModeProvider, + key_injector: &'a dyn KeyInjector, + html_injector: &'a dyn HtmlInjector, + image_injector: &'a dyn ImageInjector, + context_menu_handler: &'a dyn ContextMenuHandler, + icon_handler: &'a dyn IconHandler, + secure_input_manager: &'a dyn SecureInputManager, +) -> impl Dispatcher + 'a { + default::DefaultDispatcher::new( + event_injector, + clipboard_injector, + mode_provider, + key_injector, + html_injector, + image_injector, + context_menu_handler, + icon_handler, + secure_input_manager, + ) +} diff --git a/espanso-engine/src/event/effect.rs b/espanso-engine/src/event/effect.rs new file mode 100644 index 0000000..505c043 --- /dev/null +++ b/espanso-engine/src/event/effect.rs @@ -0,0 +1,63 @@ +/* + * This file is part of espanso. + * + * 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::input::Key; + +#[derive(Debug, Clone, PartialEq)] +pub struct TriggerCompensationEvent { + pub trigger: String, + pub left_separator: Option, +} + +#[derive(Debug, Clone, PartialEq)] +pub struct CursorHintCompensationEvent { + pub cursor_hint_back_count: usize, +} + +#[derive(Debug, Clone)] +pub struct TextInjectRequest { + pub text: String, + pub force_mode: Option, +} + +#[derive(Debug, Clone)] +pub struct MarkdownInjectRequest { + pub markdown: String, +} + +#[derive(Debug, Clone)] +pub struct HtmlInjectRequest { + pub html: String, +} + +#[derive(Debug, PartialEq, Clone)] +pub enum TextInjectMode { + Keys, + Clipboard, +} + +#[derive(Debug, Clone)] +pub struct KeySequenceInjectRequest { + pub keys: Vec, +} + +#[derive(Debug, Clone)] +pub struct ImageInjectRequest { + pub image_path: String, +} diff --git a/espanso-engine/src/event/input.rs b/espanso-engine/src/event/input.rs new file mode 100644 index 0000000..ebe830e --- /dev/null +++ b/espanso-engine/src/event/input.rs @@ -0,0 +1,123 @@ +/* + * This file is part of espanso. + * + * 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 . + */ + +#[derive(Debug, PartialEq, Clone)] +pub enum Status { + Pressed, + Released, +} + +#[derive(Debug, PartialEq, Clone)] +pub enum Variant { + Left, + Right, +} + +#[derive(Debug, PartialEq, Clone)] +pub struct KeyboardEvent { + pub key: Key, + pub value: Option, + pub status: Status, + pub variant: Option, +} + +#[derive(Debug, Clone, PartialEq)] +pub enum MouseButton { + Left, + Right, + Middle, + Button1, + Button2, + Button3, + Button4, + Button5, +} + +#[derive(Debug, Clone, PartialEq)] +pub struct MouseEvent { + pub button: MouseButton, + pub status: Status, +} + +#[derive(Debug, Clone, PartialEq)] +pub enum Key { + // Modifiers + Alt, + CapsLock, + Control, + Meta, + NumLock, + Shift, + + // Whitespace + Enter, + Tab, + Space, + + // Navigation + ArrowDown, + ArrowLeft, + ArrowRight, + ArrowUp, + End, + Home, + PageDown, + PageUp, + + // UI + Escape, + + // Editing keys + Backspace, + + // Function keys + F1, + F2, + F3, + F4, + F5, + F6, + F7, + F8, + F9, + F10, + F11, + F12, + F13, + F14, + F15, + F16, + F17, + F18, + F19, + F20, + + // Other keys, includes the raw code provided by the operating system + Other(i32), +} + +#[derive(Debug, Clone, PartialEq)] +pub struct ContextMenuClickedEvent { + pub context_item_id: u32, +} + +#[derive(Debug, Clone, PartialEq)] +pub struct HotKeyEvent { + pub hotkey_id: i32, +} diff --git a/espanso-engine/src/event/internal.rs b/espanso-engine/src/event/internal.rs new file mode 100644 index 0000000..872a7ce --- /dev/null +++ b/espanso-engine/src/event/internal.rs @@ -0,0 +1,91 @@ +/* + * This file is part of espanso. + * + * 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 std::collections::HashMap; + +#[derive(Debug, Clone, PartialEq)] +pub struct MatchesDetectedEvent { + pub matches: Vec, +} + +#[derive(Debug, Clone, PartialEq, Default)] +pub struct DetectedMatch { + pub id: i32, + pub trigger: Option, + pub left_separator: Option, + pub right_separator: Option, + pub args: HashMap, +} + +#[derive(Debug, Clone, PartialEq)] +pub struct MatchSelectedEvent { + pub chosen: DetectedMatch, +} + +#[derive(Debug, Clone, PartialEq)] +pub struct CauseCompensatedMatchEvent { + pub m: DetectedMatch, +} + +#[derive(Debug, Clone, PartialEq)] +pub struct RenderingRequestedEvent { + pub match_id: i32, + pub trigger: Option, + pub left_separator: Option, + pub right_separator: Option, + pub trigger_args: HashMap, + pub format: TextFormat, +} + +#[derive(Debug, Clone, PartialEq)] +pub enum TextFormat { + Plain, + Markdown, + Html, +} + +#[derive(Debug, Clone, PartialEq)] +pub struct ImageRequestedEvent { + pub match_id: i32, + pub image_path: String, +} + +#[derive(Debug, Clone, PartialEq)] +pub struct ImageResolvedEvent { + pub image_path: String, +} + +#[derive(Debug, Clone, PartialEq)] +pub struct RenderedEvent { + pub match_id: i32, + pub body: String, + pub format: TextFormat, +} + +#[derive(Debug, Clone, PartialEq)] +pub struct DiscardPreviousEvent { + // All Events with a source_id smaller than this one will be discarded + pub minimum_source_id: u32, +} + +#[derive(Debug, Clone, PartialEq)] +pub struct SecureInputEnabledEvent { + pub app_name: String, + pub app_path: String, +} diff --git a/espanso-engine/src/event/mod.rs b/espanso-engine/src/event/mod.rs new file mode 100644 index 0000000..5d9c53f --- /dev/null +++ b/espanso-engine/src/event/mod.rs @@ -0,0 +1,107 @@ +/* + * This file is part of espanso. + * + * 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 . + */ + +pub mod effect; +pub mod input; +pub mod internal; +pub mod ui; + +pub type SourceId = u32; + +#[derive(Debug, Clone)] +pub struct Event { + // The source id is a unique, monothonically increasing number + // that is given to each event by the source and is propagated + // to all consequential events. + // For example, if a keyboard event with source_id = 5 generates + // a detected match event, this event will have source_id = 5 + pub source_id: SourceId, + pub etype: EventType, +} + +impl Event { + pub fn caused_by(cause_id: SourceId, event_type: EventType) -> Event { + Event { + source_id: cause_id, + etype: event_type, + } + } +} + +#[derive(Debug, Clone)] +#[allow(clippy::upper_case_acronyms)] +pub enum EventType { + NOOP, + ProcessingError(String), + ExitRequested(ExitMode), + Exit(ExitMode), + Heartbeat, + + // Inputs + Keyboard(input::KeyboardEvent), + Mouse(input::MouseEvent), + HotKey(input::HotKeyEvent), + TrayIconClicked, + ContextMenuClicked(input::ContextMenuClickedEvent), + + // Internal + MatchesDetected(internal::MatchesDetectedEvent), + MatchSelected(internal::MatchSelectedEvent), + CauseCompensatedMatch(internal::CauseCompensatedMatchEvent), + + RenderingRequested(internal::RenderingRequestedEvent), + ImageRequested(internal::ImageRequestedEvent), + Rendered(internal::RenderedEvent), + ImageResolved(internal::ImageResolvedEvent), + MatchInjected, + DiscardPrevious(internal::DiscardPreviousEvent), + + Disabled, + Enabled, + DisableRequest, + EnableRequest, + SecureInputEnabled(internal::SecureInputEnabledEvent), + SecureInputDisabled, + + // Effects + TriggerCompensation(effect::TriggerCompensationEvent), + CursorHintCompensation(effect::CursorHintCompensationEvent), + + KeySequenceInject(effect::KeySequenceInjectRequest), + TextInject(effect::TextInjectRequest), + MarkdownInject(effect::MarkdownInjectRequest), + HtmlInject(effect::HtmlInjectRequest), + ImageInject(effect::ImageInjectRequest), + + // UI + ShowContextMenu(ui::ShowContextMenuEvent), + IconStatusChange(ui::IconStatusChangeEvent), + DisplaySecureInputTroubleshoot, + ShowSearchBar, + + // Other + LaunchSecureInputAutoFix, +} + +#[derive(Debug, Clone)] +pub enum ExitMode { + Exit, + ExitAllProcesses, + RestartWorker, +} diff --git a/espanso-engine/src/event/ui.rs b/espanso-engine/src/event/ui.rs new file mode 100644 index 0000000..b172c45 --- /dev/null +++ b/espanso-engine/src/event/ui.rs @@ -0,0 +1,54 @@ +/* + * This file is part of espanso. + * + * 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 . + */ + +#[derive(Debug, Clone, PartialEq)] +pub struct ShowContextMenuEvent { + pub items: Vec, +} + +#[derive(Debug, Clone, PartialEq)] +pub enum MenuItem { + Simple(SimpleMenuItem), + Sub(SubMenuItem), + Separator, +} + +#[derive(Debug, Clone, PartialEq)] +pub struct SimpleMenuItem { + pub id: u32, + pub label: String, +} + +#[derive(Debug, Clone, PartialEq)] +pub struct SubMenuItem { + pub label: String, + pub items: Vec, +} + +#[derive(Debug, Clone, PartialEq)] +pub struct IconStatusChangeEvent { + pub status: IconStatus, +} + +#[derive(Debug, Clone, PartialEq)] +pub enum IconStatus { + Enabled, + Disabled, + SecureInputDisabled, +} diff --git a/espanso-engine/src/funnel/default.rs b/espanso-engine/src/funnel/default.rs new file mode 100644 index 0000000..d409bc5 --- /dev/null +++ b/espanso-engine/src/funnel/default.rs @@ -0,0 +1,54 @@ +/* + * This file is part of espanso. + * + * 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 crossbeam::channel::Select; + +use super::{Funnel, FunnelResult, Source}; + +pub struct DefaultFunnel<'a> { + sources: &'a [&'a dyn Source<'a>], +} + +impl<'a> DefaultFunnel<'a> { + pub fn new(sources: &'a [&'a dyn Source<'a>]) -> Self { + Self { sources } + } +} + +impl<'a> Funnel for DefaultFunnel<'a> { + fn receive(&self) -> FunnelResult { + let mut select = Select::new(); + + // First register all the sources to the select operation + for source in self.sources.iter() { + source.register(&mut select); + } + + // Wait for the first source (blocking operation) + let op = select.select(); + let source = self + .sources + .get(op.index()) + .expect("invalid source index returned by select operation"); + + // Receive (and convert) the event + let event = source.receive(op); + FunnelResult::Event(event) + } +} diff --git a/espanso-engine/src/funnel/mod.rs b/espanso-engine/src/funnel/mod.rs new file mode 100644 index 0000000..d920310 --- /dev/null +++ b/espanso-engine/src/funnel/mod.rs @@ -0,0 +1,44 @@ +/* + * This file is part of espanso. + * + * 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 crossbeam::{channel::Select, channel::SelectedOperation}; + +use self::default::DefaultFunnel; + +use crate::event::Event; + +mod default; + +pub trait Source<'a> { + fn register(&'a self, select: &mut Select<'a>) -> usize; + fn receive(&'a self, op: SelectedOperation) -> Event; +} + +pub trait Funnel { + fn receive(&self) -> FunnelResult; +} + +pub enum FunnelResult { + Event(Event), + EndOfStream, +} + +pub fn default<'a>(sources: &'a [&'a dyn Source<'a>]) -> impl Funnel + 'a { + DefaultFunnel::new(sources) +} diff --git a/espanso-engine/src/lib.rs b/espanso-engine/src/lib.rs new file mode 100644 index 0000000..eb4fbd4 --- /dev/null +++ b/espanso-engine/src/lib.rs @@ -0,0 +1,74 @@ +/* + * This file is part of espanso. + * + * 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 log::debug; + +use self::{ + dispatch::Dispatcher, + event::{Event, EventType, ExitMode}, + funnel::{Funnel, FunnelResult}, + process::Processor, +}; + +pub mod dispatch; +pub mod event; +pub mod funnel; +pub mod process; + +pub struct Engine<'a> { + funnel: &'a dyn Funnel, + processor: &'a mut dyn Processor, + dispatcher: &'a dyn Dispatcher, +} + +impl<'a> Engine<'a> { + pub fn new( + funnel: &'a dyn Funnel, + processor: &'a mut dyn Processor, + dispatcher: &'a dyn Dispatcher, + ) -> Self { + Self { + funnel, + processor, + dispatcher, + } + } + + pub fn run(&mut self) -> ExitMode { + loop { + match self.funnel.receive() { + FunnelResult::Event(event) => { + let processed_events = self.processor.process(event); + for event in processed_events { + if let EventType::Exit(mode) = &event.etype { + debug!("exit event received with mode {:?}, exiting engine", mode); + return mode.clone(); + } + + self.dispatcher.dispatch(event); + } + } + FunnelResult::EndOfStream => { + debug!("end of stream received"); + return ExitMode::Exit; + } + } + } + } +} diff --git a/espanso-engine/src/process/default.rs b/espanso-engine/src/process/default.rs new file mode 100644 index 0000000..95e42f5 --- /dev/null +++ b/espanso-engine/src/process/default.rs @@ -0,0 +1,155 @@ +/* + * This file is part of espanso. + * + * 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 log::trace; + +use super::{ + middleware::{ + action::{ActionMiddleware, EventSequenceProvider}, + cause::CauseCompensateMiddleware, + cursor_hint::CursorHintMiddleware, + delay_modifiers::{DelayForModifierReleaseMiddleware, ModifierStatusProvider}, + markdown::MarkdownMiddleware, + match_select::MatchSelectMiddleware, + matcher::MatcherMiddleware, + multiplex::MultiplexMiddleware, + past_discard::PastEventsDiscardMiddleware, + render::RenderMiddleware, + }, + DisableOptions, MatchFilter, MatchInfoProvider, MatchProvider, MatchSelector, Matcher, + MatcherMiddlewareConfigProvider, Middleware, Multiplexer, PathProvider, Processor, Renderer, +}; +use crate::{ + 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> { + event_queue: VecDeque, + middleware: Vec>, +} + +#[allow(clippy::too_many_arguments)] +impl<'a> DefaultProcessor<'a> { + pub fn new( + matchers: &'a [&'a dyn Matcher<'a, MatcherState>], + match_filter: &'a dyn MatchFilter, + match_selector: &'a dyn MatchSelector, + multiplexer: &'a dyn Multiplexer, + renderer: &'a dyn Renderer<'a>, + match_info_provider: &'a dyn MatchInfoProvider, + modifier_status_provider: &'a dyn ModifierStatusProvider, + event_sequence_provider: &'a dyn EventSequenceProvider, + path_provider: &'a dyn PathProvider, + disable_options: DisableOptions, + matcher_options_provider: &'a dyn MatcherMiddlewareConfigProvider, + match_provider: &'a dyn MatchProvider, + ) -> DefaultProcessor<'a> { + Self { + event_queue: VecDeque::new(), + middleware: vec![ + Box::new(PastEventsDiscardMiddleware::new()), + Box::new(DisableMiddleware::new(disable_options)), + 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)), + Box::new(RenderMiddleware::new(renderer)), + Box::new(ImageResolverMiddleware::new(path_provider)), + Box::new(CursorHintMiddleware::new()), + Box::new(ExitMiddleware::new()), + Box::new(ActionMiddleware::new( + match_info_provider, + event_sequence_provider, + )), + Box::new(SearchMiddleware::new(match_provider)), + Box::new(MarkdownMiddleware::new()), + Box::new(DelayForModifierReleaseMiddleware::new( + modifier_status_provider, + )), + ], + } + } + + fn process_one(&mut self) -> Option { + if let Some(event) = self.event_queue.pop_back() { + let mut current_event = event; + + let mut current_queue = VecDeque::new(); + let mut dispatch = |event: Event| { + trace!("dispatched event: {:?}", event); + current_queue.push_front(event); + }; + + trace!("--------------- new event -----------------"); + for middleware in self.middleware.iter() { + trace!( + "middleware '{}' received event: {:?}", + middleware.name(), + current_event + ); + + current_event = middleware.next(current_event, &mut dispatch); + + trace!( + "middleware '{}' produced event: {:?}", + middleware.name(), + current_event + ); + + if let EventType::NOOP = current_event.etype { + trace!("interrupting chain as the event is NOOP"); + break; + } + } + + while let Some(event) = current_queue.pop_back() { + self.event_queue.push_front(event); + } + + Some(current_event) + } else { + None + } + } +} + +impl<'a> Processor for DefaultProcessor<'a> { + fn process(&mut self, event: Event) -> Vec { + self.event_queue.push_front(event); + + let mut processed_events = Vec::new(); + + while !self.event_queue.is_empty() { + if let Some(event) = self.process_one() { + processed_events.push(event); + } + } + + processed_events + } +} diff --git a/espanso-engine/src/process/middleware/action.rs b/espanso-engine/src/process/middleware/action.rs new file mode 100644 index 0000000..5d65651 --- /dev/null +++ b/espanso-engine/src/process/middleware/action.rs @@ -0,0 +1,136 @@ +/* + * This file is part of espanso. + * + * 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::event::{ + effect::{ + HtmlInjectRequest, ImageInjectRequest, KeySequenceInjectRequest, MarkdownInjectRequest, + TextInjectMode, TextInjectRequest, + }, + input::Key, + internal::{DiscardPreviousEvent, TextFormat}, + Event, EventType, +}; + +pub trait MatchInfoProvider { + fn get_force_mode(&self, match_id: i32) -> Option; +} + +pub trait EventSequenceProvider { + fn get_next_id(&self) -> u32; +} + +pub struct ActionMiddleware<'a> { + match_info_provider: &'a dyn MatchInfoProvider, + event_sequence_provider: &'a dyn EventSequenceProvider, +} + +impl<'a> ActionMiddleware<'a> { + pub fn new( + match_info_provider: &'a dyn MatchInfoProvider, + event_sequence_provider: &'a dyn EventSequenceProvider, + ) -> Self { + Self { + match_info_provider, + event_sequence_provider, + } + } +} + +impl<'a> Middleware for ActionMiddleware<'a> { + fn name(&self) -> &'static str { + "action" + } + + fn next(&self, event: Event, dispatch: &mut dyn FnMut(Event)) -> Event { + match &event.etype { + EventType::Rendered(_) | EventType::ImageResolved(_) => { + dispatch(Event::caused_by(event.source_id, EventType::MatchInjected)); + dispatch(Event::caused_by( + event.source_id, + EventType::DiscardPrevious(DiscardPreviousEvent { + minimum_source_id: self.event_sequence_provider.get_next_id(), + }), + )); + + match &event.etype { + EventType::Rendered(m_event) => Event::caused_by( + event.source_id, + match m_event.format { + TextFormat::Plain => EventType::TextInject(TextInjectRequest { + text: m_event.body.clone(), + force_mode: self.match_info_provider.get_force_mode(m_event.match_id), + }), + TextFormat::Html => EventType::HtmlInject(HtmlInjectRequest { + html: m_event.body.clone(), + }), + TextFormat::Markdown => EventType::MarkdownInject(MarkdownInjectRequest { + markdown: m_event.body.clone(), + }), + }, + ), + EventType::ImageResolved(m_event) => Event::caused_by( + event.source_id, + EventType::ImageInject(ImageInjectRequest { + image_path: m_event.image_path.clone(), + }), + ), + _ => unreachable!(), + } + } + EventType::CursorHintCompensation(m_event) => { + dispatch(Event::caused_by( + event.source_id, + EventType::DiscardPrevious(DiscardPreviousEvent { + minimum_source_id: self.event_sequence_provider.get_next_id(), + }), + )); + + Event::caused_by( + event.source_id, + EventType::KeySequenceInject(KeySequenceInjectRequest { + keys: (0..m_event.cursor_hint_back_count) + .map(|_| Key::ArrowLeft) + .collect(), + }), + ) + } + EventType::TriggerCompensation(m_event) => { + let mut backspace_count = m_event.trigger.chars().count(); + + // We want to preserve the left separator if present + if let Some(left_separator) = &m_event.left_separator { + backspace_count -= left_separator.chars().count(); + } + + Event::caused_by( + event.source_id, + EventType::KeySequenceInject(KeySequenceInjectRequest { + keys: (0..backspace_count).map(|_| Key::Backspace).collect(), + }), + ) + } + _ => event, + } + + // TODO: handle images + } +} + +// TODO: test diff --git a/espanso-engine/src/process/middleware/cause.rs b/espanso-engine/src/process/middleware/cause.rs new file mode 100644 index 0000000..22c98ad --- /dev/null +++ b/espanso-engine/src/process/middleware/cause.rs @@ -0,0 +1,58 @@ +/* + * This file is part of espanso. + * + * 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::{event::{Event, EventType, effect::TriggerCompensationEvent, internal::CauseCompensatedMatchEvent}}; + +pub struct CauseCompensateMiddleware {} + +impl CauseCompensateMiddleware { + pub fn new() -> Self { + Self {} + } +} + +impl Middleware for CauseCompensateMiddleware { + fn name(&self) -> &'static str { + "cause_compensate" + } + + fn next(&self, event: Event, dispatch: &mut dyn FnMut(Event)) -> Event { + if let EventType::MatchSelected(m_event) = &event.etype { + let compensated_event = + Event::caused_by(event.source_id, EventType::CauseCompensatedMatch(CauseCompensatedMatchEvent { m: m_event.chosen.clone() })); + + if let Some(trigger) = &m_event.chosen.trigger { + dispatch(compensated_event); + + // Before the event, place a trigger compensation + return Event::caused_by(event.source_id, EventType::TriggerCompensation(TriggerCompensationEvent { + trigger: trigger.clone(), + left_separator: m_event.chosen.left_separator.clone(), + })); + } else { + return compensated_event; + } + } + + event + } +} + +// TODO: test diff --git a/espanso-engine/src/process/middleware/context_menu.rs b/espanso-engine/src/process/middleware/context_menu.rs new file mode 100644 index 0000000..79de2e3 --- /dev/null +++ b/espanso-engine/src/process/middleware/context_menu.rs @@ -0,0 +1,185 @@ +/* + * This file is part of espanso. + * + * Copyright id: (), label: () id: (), label: () id: (), label: ()(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 std::cell::RefCell; + +use super::super::Middleware; +use crate::event::{ + ui::{MenuItem, ShowContextMenuEvent, SimpleMenuItem}, + Event, EventType, ExitMode, +}; + +const CONTEXT_ITEM_EXIT: u32 = 0; +const CONTEXT_ITEM_RELOAD: u32 = 1; +const CONTEXT_ITEM_ENABLE: u32 = 2; +const CONTEXT_ITEM_DISABLE: u32 = 3; +const CONTEXT_ITEM_SECURE_INPUT_EXPLAIN: u32 = 4; +const CONTEXT_ITEM_SECURE_INPUT_TRIGGER_WORKAROUND: u32 = 5; +const CONTEXT_ITEM_OPEN_SEARCH: u32 = 6; + +pub struct ContextMenuMiddleware { + is_enabled: RefCell, + is_secure_input_enabled: RefCell, +} + +impl ContextMenuMiddleware { + pub fn new() -> Self { + Self { + is_enabled: RefCell::new(true), + is_secure_input_enabled: RefCell::new(false), + } + } +} + +#[allow(clippy::needless_return)] +impl Middleware for ContextMenuMiddleware { + fn name(&self) -> &'static str { + "context_menu" + } + + fn next(&self, event: Event, dispatch: &mut dyn FnMut(Event)) -> Event { + let mut is_enabled = self.is_enabled.borrow_mut(); + let mut is_secure_input_enabled = self.is_secure_input_enabled.borrow_mut(); + + match &event.etype { + EventType::TrayIconClicked => { + // TODO: fetch top matches for the active config to be added + + let mut items = vec![ + MenuItem::Simple(if *is_enabled { + SimpleMenuItem { + id: CONTEXT_ITEM_DISABLE, + label: "Disable".to_string(), + } + } else { + SimpleMenuItem { + id: CONTEXT_ITEM_ENABLE, + label: "Enable".to_string(), + } + }), + MenuItem::Simple(SimpleMenuItem { + id: CONTEXT_ITEM_OPEN_SEARCH, + label: "Open search bar".to_string(), + }), + MenuItem::Separator, + MenuItem::Simple(SimpleMenuItem { + id: CONTEXT_ITEM_RELOAD, + label: "Reload config".to_string(), + }), + MenuItem::Separator, + MenuItem::Simple(SimpleMenuItem { + id: CONTEXT_ITEM_EXIT, + label: "Exit espanso".to_string(), + }), + ]; + + if *is_secure_input_enabled { + items.insert( + 0, + MenuItem::Simple(SimpleMenuItem { + id: CONTEXT_ITEM_SECURE_INPUT_EXPLAIN, + label: "Why is espanso not working?".to_string(), + }), + ); + items.insert( + 1, + MenuItem::Simple(SimpleMenuItem { + id: CONTEXT_ITEM_SECURE_INPUT_TRIGGER_WORKAROUND, + label: "Launch SecureInput auto-fix".to_string(), + }), + ); + items.insert(2, MenuItem::Separator); + } + + // TODO: my idea is to use a set of reserved u32 ids for built-in + // actions such as Exit, Open Editor etc + // then we need some u32 for the matches, so we need to create + // a mapping structure match_id <-> context-menu-id + return Event::caused_by( + event.source_id, + EventType::ShowContextMenu(ShowContextMenuEvent { + // TODO: add actual entries + items, + }), + ); + } + EventType::ContextMenuClicked(context_click_event) => { + match context_click_event.context_item_id { + CONTEXT_ITEM_EXIT => Event::caused_by( + event.source_id, + EventType::ExitRequested(ExitMode::ExitAllProcesses), + ), + CONTEXT_ITEM_RELOAD => Event::caused_by( + event.source_id, + EventType::ExitRequested(ExitMode::RestartWorker), + ), + CONTEXT_ITEM_ENABLE => { + dispatch(Event::caused_by(event.source_id, EventType::EnableRequest)); + Event::caused_by(event.source_id, EventType::NOOP) + } + CONTEXT_ITEM_DISABLE => { + dispatch(Event::caused_by(event.source_id, EventType::DisableRequest)); + Event::caused_by(event.source_id, EventType::NOOP) + } + CONTEXT_ITEM_SECURE_INPUT_EXPLAIN => { + dispatch(Event::caused_by( + event.source_id, + EventType::DisplaySecureInputTroubleshoot, + )); + Event::caused_by(event.source_id, EventType::NOOP) + } + CONTEXT_ITEM_SECURE_INPUT_TRIGGER_WORKAROUND => { + dispatch(Event::caused_by( + event.source_id, + EventType::LaunchSecureInputAutoFix, + )); + Event::caused_by(event.source_id, EventType::NOOP) + } + CONTEXT_ITEM_OPEN_SEARCH => { + dispatch(Event::caused_by(event.source_id, EventType::ShowSearchBar)); + Event::caused_by(event.source_id, EventType::NOOP) + } + _ => { + // TODO: handle dynamic items + todo!() + } + } + } + EventType::Disabled => { + *is_enabled = false; + event + } + EventType::Enabled => { + *is_enabled = true; + event + } + EventType::SecureInputEnabled(_) => { + *is_secure_input_enabled = true; + event + } + EventType::SecureInputDisabled => { + *is_secure_input_enabled = false; + event + } + _ => event, + } + } +} + +// TODO: test diff --git a/espanso-engine/src/process/middleware/cursor_hint.rs b/espanso-engine/src/process/middleware/cursor_hint.rs new file mode 100644 index 0000000..cdb9bd0 --- /dev/null +++ b/espanso-engine/src/process/middleware/cursor_hint.rs @@ -0,0 +1,82 @@ +/* + * This file is part of espanso. + * + * 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::event::{ + effect::CursorHintCompensationEvent, internal::RenderedEvent, Event, EventType, +}; + +pub struct CursorHintMiddleware {} + +impl CursorHintMiddleware { + pub fn new() -> Self { + Self {} + } +} + +impl Middleware for CursorHintMiddleware { + fn name(&self) -> &'static str { + "cursor_hint" + } + + fn next(&self, event: Event, dispatch: &mut dyn FnMut(Event)) -> Event { + if let EventType::Rendered(m_event) = event.etype { + let (body, cursor_hint_back_count) = process_cursor_hint(m_event.body); + + if let Some(cursor_hint_back_count) = cursor_hint_back_count { + dispatch(Event::caused_by( + event.source_id, + EventType::CursorHintCompensation(CursorHintCompensationEvent { + cursor_hint_back_count, + }), + )); + } + + // Alter the rendered event to remove the cursor hint from the body + return Event::caused_by( + event.source_id, + EventType::Rendered(RenderedEvent { body, ..m_event }), + ); + } + + event + } +} + +// TODO: test +fn process_cursor_hint(body: String) -> (String, Option) { + if let Some(index) = body.find("$|$") { + // Convert the byte index to a char index + let char_str = &body[0..index]; + let char_index = char_str.chars().count(); + let total_size = body.chars().count(); + + // Remove the $|$ placeholder + let body = body.replace("$|$", ""); + + // Calculate the amount of rewind moves needed (LEFT ARROW). + // Subtract also 3, equal to the number of chars of the placeholder "$|$" + let moves = total_size - char_index - 3; + (body, Some(moves)) + } else { + (body, None) + } +} + +// TODO: test diff --git a/espanso-engine/src/process/middleware/delay_modifiers.rs b/espanso-engine/src/process/middleware/delay_modifiers.rs new file mode 100644 index 0000000..e4fc746 --- /dev/null +++ b/espanso-engine/src/process/middleware/delay_modifiers.rs @@ -0,0 +1,84 @@ +/* + * This file is part of espanso. + * + * 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 std::time::{Duration, Instant}; + +use log::{trace, warn}; + +use super::super::Middleware; +use crate::event::{Event, EventType}; + +/// Maximum time to wait for modifiers being released before +/// giving up. +const MODIFIER_DELAY_TIMEOUT: Duration = Duration::from_secs(3); + +pub trait ModifierStatusProvider { + fn is_any_conflicting_modifier_pressed(&self) -> bool; +} + +/// This middleware is used to delay the injection of text until +/// all modifiers have been released. This is needed as otherwise, +/// injections might misbehave as pressed modifiers might alter +/// the keys being injected. +pub struct DelayForModifierReleaseMiddleware<'a> { + provider: &'a dyn ModifierStatusProvider, +} + +impl<'a> DelayForModifierReleaseMiddleware<'a> { + pub fn new(provider: &'a dyn ModifierStatusProvider) -> Self { + Self { provider } + } +} + +impl<'a> Middleware for DelayForModifierReleaseMiddleware<'a> { + fn name(&self) -> &'static str { + "delay_modifiers" + } + + fn next(&self, event: Event, _: &mut dyn FnMut(Event)) -> Event { + if is_injection_event(&event.etype) { + let start = Instant::now(); + while self.provider.is_any_conflicting_modifier_pressed() { + if Instant::now().duration_since(start) > MODIFIER_DELAY_TIMEOUT { + warn!("injection delay has timed out, please release the modifier keys (SHIFT, CTRL, ALT, CMD) to trigger an expansion"); + break; + } + + // TODO: here we might show a popup window to tell the users to release those keys + + trace!("delaying injection event as some modifiers are pressed"); + std::thread::sleep(Duration::from_millis(100)); + } + } + + event + } +} + +fn is_injection_event(event_type: &EventType) -> bool { + matches!( + event_type, + EventType::TriggerCompensation(_) + | EventType::CursorHintCompensation(_) + | EventType::KeySequenceInject(_) + | EventType::TextInject(_) + ) +} + +// TODO: test diff --git a/espanso-engine/src/process/middleware/disable.rs b/espanso-engine/src/process/middleware/disable.rs new file mode 100644 index 0000000..77bd0e8 --- /dev/null +++ b/espanso-engine/src/process/middleware/disable.rs @@ -0,0 +1,138 @@ +/* + * This file is part of espanso. + * + * 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 std::{ + cell::RefCell, + time::{Duration, Instant}, +}; + +use log::info; + +use super::super::Middleware; +use crate::event::{ + input::{Key, KeyboardEvent, Status, Variant}, + Event, EventType, +}; + +pub struct DisableOptions { + pub toggle_key: Option, + pub toggle_key_variant: Option, + pub toggle_key_maximum_window: Duration, + // TODO: toggle shortcut? +} + +pub struct DisableMiddleware { + enabled: RefCell, + last_toggle_press: RefCell>, + options: DisableOptions, +} + +impl DisableMiddleware { + pub fn new(options: DisableOptions) -> Self { + Self { + enabled: RefCell::new(true), + last_toggle_press: RefCell::new(None), + options, + } + } +} + +impl Middleware for DisableMiddleware { + fn name(&self) -> &'static str { + "disable" + } + + fn next(&self, event: Event, dispatch: &mut dyn FnMut(Event)) -> Event { + let mut has_status_changed = false; + let mut enabled = self.enabled.borrow_mut(); + + match &event.etype { + EventType::Keyboard(m_event) => { + if is_toggle_key(m_event, &self.options) { + let mut last_toggle_press = self.last_toggle_press.borrow_mut(); + if let Some(previous_press) = *last_toggle_press { + if previous_press.elapsed() < self.options.toggle_key_maximum_window { + *enabled = !*enabled; + *last_toggle_press = None; + has_status_changed = true; + } else { + *last_toggle_press = Some(Instant::now()); + } + } else { + *last_toggle_press = Some(Instant::now()); + } + } + } + EventType::EnableRequest => { + *enabled = true; + has_status_changed = true; + } + EventType::DisableRequest => { + *enabled = false; + has_status_changed = true; + } + _ => {} + } + + if has_status_changed { + info!("toggled enabled state, is_enabled = {}", *enabled); + dispatch(Event::caused_by( + event.source_id, + if *enabled { + EventType::Enabled + } else { + EventType::Disabled + }, + )) + } + + // Block keyboard events when disabled + if let EventType::Keyboard(_) = &event.etype { + if !*enabled { + return Event::caused_by(event.source_id, EventType::NOOP); + } + } + // TODO: also ignore hotkey and mouse events + + event + } +} + +fn is_toggle_key(event: &KeyboardEvent, options: &DisableOptions) -> bool { + if event.status != Status::Released { + return false; + } + + if options + .toggle_key + .as_ref() + .map(|key| key == &event.key) + .unwrap_or(false) + { + if let (Some(variant), Some(e_variant)) = (&options.toggle_key_variant, &event.variant) { + variant == e_variant + } else { + true + } + } else { + false + } +} + +// TODO: test diff --git a/espanso-engine/src/process/middleware/exit.rs b/espanso-engine/src/process/middleware/exit.rs new file mode 100644 index 0000000..d4fa58f --- /dev/null +++ b/espanso-engine/src/process/middleware/exit.rs @@ -0,0 +1,51 @@ +/* + * This file is part of espanso. + * + * 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 log::debug; + +use super::super::Middleware; +use crate::event::{Event, EventType}; + +pub struct ExitMiddleware {} + +impl ExitMiddleware { + pub fn new() -> Self { + Self {} + } +} + +impl Middleware for ExitMiddleware { + fn name(&self) -> &'static str { + "exit" + } + + fn next(&self, event: Event, _: &mut dyn FnMut(Event)) -> Event { + if let EventType::ExitRequested(mode) = &event.etype { + debug!( + "received ExitRequested event with mode: {:?}, dispatching exit", + mode + ); + return Event::caused_by(event.source_id, EventType::Exit(mode.clone())); + } + + event + } +} + +// TODO: test diff --git a/espanso-engine/src/process/middleware/hotkey.rs b/espanso-engine/src/process/middleware/hotkey.rs new file mode 100644 index 0000000..e606ff6 --- /dev/null +++ b/espanso-engine/src/process/middleware/hotkey.rs @@ -0,0 +1,56 @@ +/* + * 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::event::{ + internal::{DetectedMatch, MatchesDetectedEvent}, + Event, EventType, +}; + +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-engine/src/process/middleware/icon_status.rs b/espanso-engine/src/process/middleware/icon_status.rs new file mode 100644 index 0000000..b682a55 --- /dev/null +++ b/espanso-engine/src/process/middleware/icon_status.rs @@ -0,0 +1,81 @@ +/* + * This file is part of espanso. + * + * 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 std::cell::RefCell; + +use super::super::Middleware; +use crate::event::{ + ui::{IconStatus, IconStatusChangeEvent}, + Event, EventType, +}; + +pub struct IconStatusMiddleware { + enabled: RefCell, + secure_input_enabled: RefCell, +} + +impl IconStatusMiddleware { + pub fn new() -> Self { + Self { + enabled: RefCell::new(true), + secure_input_enabled: RefCell::new(false), + } + } +} + +impl Middleware for IconStatusMiddleware { + fn name(&self) -> &'static str { + "icon_status" + } + + fn next(&self, event: Event, dispatch: &mut dyn FnMut(Event)) -> Event { + let mut enabled = self.enabled.borrow_mut(); + let mut secure_input_enabled = self.secure_input_enabled.borrow_mut(); + + let mut did_update = true; + match &event.etype { + EventType::Enabled => *enabled = true, + EventType::Disabled => *enabled = false, + EventType::SecureInputEnabled(_) => *secure_input_enabled = true, + EventType::SecureInputDisabled => *secure_input_enabled = false, + _ => did_update = false, + } + + if did_update { + let status = if *enabled { + if *secure_input_enabled { + IconStatus::SecureInputDisabled + } else { + IconStatus::Enabled + } + } else { + IconStatus::Disabled + }; + + dispatch(Event::caused_by( + event.source_id, + EventType::IconStatusChange(IconStatusChangeEvent { status }), + )); + } + + event + } +} + +// TODO: test diff --git a/espanso-engine/src/process/middleware/image_resolve.rs b/espanso-engine/src/process/middleware/image_resolve.rs new file mode 100644 index 0000000..8a9569f --- /dev/null +++ b/espanso-engine/src/process/middleware/image_resolve.rs @@ -0,0 +1,81 @@ +/* + * This file is part of espanso. + * + * 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 std::path::Path; + +use log::error; + +use super::super::Middleware; +use crate::event::{internal::ImageResolvedEvent, Event, EventType}; + +pub trait PathProvider { + fn get_config_path(&self) -> &Path; +} + +pub struct ImageResolverMiddleware<'a> { + provider: &'a dyn PathProvider, +} + +impl<'a> ImageResolverMiddleware<'a> { + pub fn new(provider: &'a dyn PathProvider) -> Self { + Self { provider } + } +} + +impl<'a> Middleware for ImageResolverMiddleware<'a> { + fn name(&self) -> &'static str { + "image_resolve" + } + + fn next(&self, event: Event, _: &mut dyn FnMut(Event)) -> Event { + if let EventType::ImageRequested(m_event) = &event.etype { + // On Windows, we have to replace the forward / with the backslash \ in the path + let path = if cfg!(target_os = "windows") { + m_event.image_path.replace("/", "\\") + } else { + m_event.image_path.to_owned() + }; + + let path = if path.contains("$CONFIG") { + let config_path = match self.provider.get_config_path().canonicalize() { + Ok(path) => path, + Err(err) => { + error!( + "unable to canonicalize the config path into the image resolver: {}", + err + ); + self.provider.get_config_path().to_owned() + } + }; + path.replace("$CONFIG", &config_path.to_string_lossy()) + } else { + path + }; + + return Event::caused_by( + event.source_id, + EventType::ImageResolved(ImageResolvedEvent { image_path: path }), + ); + } + + event + } +} + +// TODO: test diff --git a/espanso-engine/src/process/middleware/markdown.rs b/espanso-engine/src/process/middleware/markdown.rs new file mode 100644 index 0000000..abebc54 --- /dev/null +++ b/espanso-engine/src/process/middleware/markdown.rs @@ -0,0 +1,63 @@ +/* + * This file is part of espanso. + * + * 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::event::{effect::HtmlInjectRequest, Event, EventType}; + +// Convert markdown injection requests to HTML on the fly +pub struct MarkdownMiddleware {} + +impl MarkdownMiddleware { + pub fn new() -> Self { + Self {} + } +} + +impl Middleware for MarkdownMiddleware { + fn name(&self) -> &'static str { + "markdown" + } + + fn next(&self, event: Event, _: &mut dyn FnMut(Event)) -> Event { + if let EventType::MarkdownInject(m_event) = &event.etype { + // Render the markdown into HTML + let html = markdown::to_html(&m_event.markdown); + let mut html = html.trim(); + + // Remove the surrounding paragraph + if html.starts_with("

") { + html = html.trim_start_matches("

"); + } + if html.ends_with("

") { + html = html.trim_end_matches("

"); + } + + return Event::caused_by( + event.source_id, + EventType::HtmlInject(HtmlInjectRequest { + html: html.to_owned(), + }), + ); + } + + event + } +} + +// TODO: test diff --git a/espanso-engine/src/process/middleware/match_select.rs b/espanso-engine/src/process/middleware/match_select.rs new file mode 100644 index 0000000..46e9003 --- /dev/null +++ b/espanso-engine/src/process/middleware/match_select.rs @@ -0,0 +1,102 @@ +/* + * This file is part of espanso. + * + * 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 log::{debug, error}; + +use super::super::Middleware; +use crate::event::{internal::MatchSelectedEvent, Event, EventType}; + +pub trait MatchFilter { + fn filter_active(&self, matches_ids: &[i32]) -> Vec; +} + +pub trait MatchSelector { + fn select(&self, matches_ids: &[i32]) -> Option; +} + +pub struct MatchSelectMiddleware<'a> { + match_filter: &'a dyn MatchFilter, + match_selector: &'a dyn MatchSelector, +} + +impl<'a> MatchSelectMiddleware<'a> { + pub fn new(match_filter: &'a dyn MatchFilter, match_selector: &'a dyn MatchSelector) -> Self { + Self { + match_filter, + match_selector, + } + } +} + +impl<'a> Middleware for MatchSelectMiddleware<'a> { + fn name(&self) -> &'static str { + "match_select" + } + + fn next(&self, event: Event, _: &mut dyn FnMut(Event)) -> Event { + if let EventType::MatchesDetected(m_event) = event.etype { + let matches_ids: Vec = m_event.matches.iter().map(|m| m.id).collect(); + + // Find the matches that are actually valid in the current context + let valid_ids = self.match_filter.filter_active(&matches_ids); + + return match valid_ids.len() { + 0 => Event::caused_by(event.source_id, EventType::NOOP), // No valid matches, consume the event + 1 => { + // Only one match, no need to show a selection dialog + let m = m_event + .matches + .into_iter() + .find(|m| m.id == *valid_ids.first().unwrap()); + if let Some(m) = m { + Event::caused_by( + event.source_id, + EventType::MatchSelected(MatchSelectedEvent { chosen: m }), + ) + } else { + error!("MatchSelectMiddleware could not find the correspondent match"); + Event::caused_by(event.source_id, EventType::NOOP) + } + } + _ => { + // Multiple matches, we need to ask the user which one to use + if let Some(selected_id) = self.match_selector.select(&valid_ids) { + let m = m_event.matches.into_iter().find(|m| m.id == selected_id); + if let Some(m) = m { + Event::caused_by( + event.source_id, + EventType::MatchSelected(MatchSelectedEvent { chosen: m }), + ) + } else { + error!("MatchSelectMiddleware could not find the correspondent match"); + Event::caused_by(event.source_id, EventType::NOOP) + } + } else { + debug!("MatchSelectMiddleware did not receive any match selection"); + Event::caused_by(event.source_id, EventType::NOOP) + } + } + }; + } + + event + } +} + +// TODO: test diff --git a/espanso-engine/src/process/middleware/matcher.rs b/espanso-engine/src/process/middleware/matcher.rs new file mode 100644 index 0000000..e3e6e14 --- /dev/null +++ b/espanso-engine/src/process/middleware/matcher.rs @@ -0,0 +1,207 @@ +/* + * This file is part of espanso. + * + * 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 log::trace; +use std::{ + cell::RefCell, + collections::{HashMap, VecDeque}, +}; + +use super::super::Middleware; +use crate::event::{ + input::{Key, Status}, + internal::{DetectedMatch, MatchesDetectedEvent}, + Event, EventType, +}; + +pub trait Matcher<'a, State> { + fn process( + &'a self, + prev_state: Option<&State>, + event: &MatcherEvent, + ) -> (State, Vec); +} + +#[derive(Debug)] +pub enum MatcherEvent { + Key { key: Key, chars: Option }, + VirtualSeparator, +} + +#[derive(Debug, Clone, PartialEq)] +pub struct MatchResult { + pub id: i32, + pub trigger: String, + pub left_separator: Option, + pub right_separator: Option, + pub args: HashMap, +} + +pub trait MatcherMiddlewareConfigProvider { + fn max_history_size(&self) -> usize; +} + +pub struct MatcherMiddleware<'a, State> { + matchers: &'a [&'a dyn Matcher<'a, State>], + + matcher_states: RefCell>>, + + max_history_size: usize, +} + +impl<'a, State> MatcherMiddleware<'a, State> { + pub fn new( + matchers: &'a [&'a dyn Matcher<'a, State>], + options_provider: &'a dyn MatcherMiddlewareConfigProvider, + ) -> Self { + let max_history_size = options_provider.max_history_size(); + + Self { + matchers, + matcher_states: RefCell::new(VecDeque::new()), + max_history_size, + } + } +} + +impl<'a, State> Middleware for MatcherMiddleware<'a, State> { + fn name(&self) -> &'static str { + "matcher" + } + + fn next(&self, event: Event, _: &mut dyn FnMut(Event)) -> Event { + if is_event_of_interest(&event.etype) { + let mut matcher_states = self.matcher_states.borrow_mut(); + let prev_states = if !matcher_states.is_empty() { + matcher_states.get(matcher_states.len() - 1) + } else { + None + }; + + if let EventType::Keyboard(keyboard_event) = &event.etype { + // Backspace handling + if keyboard_event.key == Key::Backspace { + trace!("popping the last matcher state"); + matcher_states.pop_back(); + return event; + } + } + + // Some keys (such as the arrow keys) and mouse clicks prevent espanso from building + // an accurate key buffer, so we need to invalidate it. + if is_invalidating_event(&event.etype) { + trace!("invalidating event detected, clearing matching state"); + matcher_states.clear(); + return event; + } + + let mut all_results = Vec::new(); + + if let Some(matcher_event) = convert_to_matcher_event(&event.etype) { + let mut new_states = Vec::new(); + for (i, matcher) in self.matchers.iter().enumerate() { + let prev_state = prev_states.and_then(|states| states.get(i)); + + let (state, results) = matcher.process(prev_state, &matcher_event); + all_results.extend(results); + + new_states.push(state); + } + + matcher_states.push_back(new_states); + if matcher_states.len() > self.max_history_size { + matcher_states.pop_front(); + } + + if !all_results.is_empty() { + return Event::caused_by( + event.source_id, + EventType::MatchesDetected(MatchesDetectedEvent { + matches: all_results + .into_iter() + .map(|result| DetectedMatch { + id: result.id, + trigger: Some(result.trigger), + right_separator: result.right_separator, + left_separator: result.left_separator, + args: result.args, + }) + .collect(), + }), + ); + } + } + } + + event + } +} + +fn is_event_of_interest(event_type: &EventType) -> bool { + match event_type { + EventType::Keyboard(keyboard_event) => { + if keyboard_event.status != Status::Pressed { + // Skip non-press events + false + } else { + // Skip modifier keys + !matches!( + keyboard_event.key, + Key::Alt | Key::Shift | Key::CapsLock | Key::Meta | Key::NumLock | Key::Control + ) + } + } + EventType::Mouse(mouse_event) => mouse_event.status == Status::Pressed, + EventType::MatchInjected => true, + _ => false, + } +} + +fn convert_to_matcher_event(event_type: &EventType) -> Option { + match event_type { + EventType::Keyboard(keyboard_event) => Some(MatcherEvent::Key { + key: keyboard_event.key.clone(), + chars: keyboard_event.value.clone(), + }), + EventType::Mouse(_) => Some(MatcherEvent::VirtualSeparator), + EventType::MatchInjected => Some(MatcherEvent::VirtualSeparator), + _ => None, + } +} + +fn is_invalidating_event(event_type: &EventType) -> bool { + match event_type { + EventType::Keyboard(keyboard_event) => matches!( + keyboard_event.key, + Key::ArrowDown + | Key::ArrowLeft + | Key::ArrowRight + | Key::ArrowUp + | Key::End + | Key::Home + | Key::PageDown + | Key::PageUp + | Key::Escape + ), + EventType::Mouse(_) => true, + _ => false, + } +} + +// TODO: test diff --git a/espanso-engine/src/process/middleware/mod.rs b/espanso-engine/src/process/middleware/mod.rs new file mode 100644 index 0000000..40c72b6 --- /dev/null +++ b/espanso-engine/src/process/middleware/mod.rs @@ -0,0 +1,36 @@ +/* + * This file is part of espanso. + * + * 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 . + */ + +pub mod action; +pub mod cause; +pub mod context_menu; +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 markdown; +pub mod match_select; +pub mod matcher; +pub mod multiplex; +pub mod past_discard; +pub mod render; +pub mod search; diff --git a/espanso-engine/src/process/middleware/multiplex.rs b/espanso-engine/src/process/middleware/multiplex.rs new file mode 100644 index 0000000..dbd38e7 --- /dev/null +++ b/espanso-engine/src/process/middleware/multiplex.rs @@ -0,0 +1,59 @@ +/* + * This file is part of espanso. + * + * 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 log::error; + +use super::super::Middleware; +use crate::event::{internal::DetectedMatch, Event, EventType}; + +pub trait Multiplexer { + fn convert(&self, m: DetectedMatch) -> Option; +} + +pub struct MultiplexMiddleware<'a> { + multiplexer: &'a dyn Multiplexer, +} + +impl<'a> MultiplexMiddleware<'a> { + pub fn new(multiplexer: &'a dyn Multiplexer) -> Self { + Self { multiplexer } + } +} + +impl<'a> Middleware for MultiplexMiddleware<'a> { + fn name(&self) -> &'static str { + "multiplex" + } + + fn next(&self, event: Event, _: &mut dyn FnMut(Event)) -> Event { + if let EventType::CauseCompensatedMatch(m_event) = event.etype { + return match self.multiplexer.convert(m_event.m) { + Some(new_event) => Event::caused_by(event.source_id, new_event), + None => { + error!("match multiplexing failed"); + Event::caused_by(event.source_id, EventType::NOOP) + } + }; + } + + event + } +} + +// TODO: test diff --git a/espanso-engine/src/process/middleware/past_discard.rs b/espanso-engine/src/process/middleware/past_discard.rs new file mode 100644 index 0000000..5b2b9b5 --- /dev/null +++ b/espanso-engine/src/process/middleware/past_discard.rs @@ -0,0 +1,69 @@ +/* + * This file is part of espanso. + * + * 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 std::cell::RefCell; + +use log::trace; + +use super::super::Middleware; +use crate::event::{Event, EventType, SourceId}; + +/// This middleware discards all events that have a source_id smaller than its +/// configured threshold. This useful to discard past events that might have +/// been stuck in the event queue for too long. +pub struct PastEventsDiscardMiddleware { + source_id_threshold: RefCell, +} + +impl PastEventsDiscardMiddleware { + pub fn new() -> Self { + Self { + source_id_threshold: RefCell::new(0), + } + } +} + +impl Middleware for PastEventsDiscardMiddleware { + fn name(&self) -> &'static str { + "past_discard" + } + + fn next(&self, event: Event, _: &mut dyn FnMut(Event)) -> Event { + let mut source_id_threshold = self.source_id_threshold.borrow_mut(); + + // Filter out previous events + if event.source_id < *source_id_threshold { + trace!("discarding previous event: {:?}", event); + return Event::caused_by(event.source_id, EventType::NOOP); + } + + // Update the minimum threshold + if let EventType::DiscardPrevious(m_event) = &event.etype { + trace!( + "updating minimum source id threshold for events to: {}", + m_event.minimum_source_id + ); + *source_id_threshold = m_event.minimum_source_id; + } + + event + } +} + +// TODO: test diff --git a/espanso-engine/src/process/middleware/render.rs b/espanso-engine/src/process/middleware/render.rs new file mode 100644 index 0000000..69aa919 --- /dev/null +++ b/espanso-engine/src/process/middleware/render.rs @@ -0,0 +1,104 @@ +/* + * This file is part of espanso. + * + * 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 std::collections::HashMap; + +use log::error; + +use super::super::Middleware; +use crate::event::{internal::RenderedEvent, Event, EventType}; +use anyhow::Result; +use thiserror::Error; + +pub trait Renderer<'a> { + fn render( + &'a self, + match_id: i32, + trigger: Option<&str>, + trigger_args: HashMap, + ) -> Result; +} + +#[derive(Error, Debug)] +pub enum RendererError { + #[error("rendering error")] + RenderingError(#[from] anyhow::Error), + + #[error("match not found")] + NotFound, + + #[error("aborted")] + Aborted, +} + +pub struct RenderMiddleware<'a> { + renderer: &'a dyn Renderer<'a>, +} + +impl<'a> RenderMiddleware<'a> { + pub fn new(renderer: &'a dyn Renderer<'a>) -> Self { + Self { renderer } + } +} + +impl<'a> Middleware for RenderMiddleware<'a> { + fn name(&self) -> &'static str { + "render" + } + + fn next(&self, event: Event, _: &mut dyn FnMut(Event)) -> Event { + if let EventType::RenderingRequested(m_event) = event.etype { + match self.renderer.render( + m_event.match_id, + m_event.trigger.as_deref(), + m_event.trigger_args, + ) { + Ok(body) => { + let body = if let Some(right_separator) = m_event.right_separator { + format!("{}{}", body, right_separator) + } else { + body + }; + + return Event::caused_by( + event.source_id, + EventType::Rendered(RenderedEvent { + match_id: m_event.match_id, + body, + format: m_event.format, + }), + ); + } + Err(err) => match err.downcast_ref::() { + Some(RendererError::Aborted) => { + return Event::caused_by(event.source_id, EventType::NOOP) + } + _ => { + error!("error during rendering: {}", err); + return Event::caused_by(event.source_id, EventType::ProcessingError("An error has occurred during rendering, please examine the logs or contact support.".to_string())); + } + }, + } + } + + event + } +} + +// TODO: test diff --git a/espanso-engine/src/process/middleware/search.rs b/espanso-engine/src/process/middleware/search.rs new file mode 100644 index 0000000..e7d27ea --- /dev/null +++ b/espanso-engine/src/process/middleware/search.rs @@ -0,0 +1,75 @@ +/* + * This file is part of espanso. + * + * 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 std::collections::HashMap; + +use super::super::Middleware; +use crate::event::{ + internal::{DetectedMatch, MatchesDetectedEvent}, + Event, EventType, +}; + +pub trait MatchProvider { + fn get_all_matches_ids(&self) -> Vec; +} + +pub struct SearchMiddleware<'a> { + match_provider: &'a dyn MatchProvider, +} + +impl<'a> SearchMiddleware<'a> { + pub fn new(match_provider: &'a dyn MatchProvider) -> Self { + Self { match_provider } + } +} + +impl<'a> Middleware for SearchMiddleware<'a> { + fn name(&self) -> &'static str { + "search" + } + + fn next(&self, event: Event, dispatch: &mut dyn FnMut(Event)) -> Event { + if let EventType::ShowSearchBar = event.etype { + let detected_matches = Event::caused_by( + event.source_id, + EventType::MatchesDetected(MatchesDetectedEvent { + matches: self + .match_provider + .get_all_matches_ids() + .into_iter() + .map(|id| DetectedMatch { + id, + trigger: None, + left_separator: None, + right_separator: None, + args: HashMap::new(), + }) + .collect(), + }), + ); + dispatch(detected_matches); + + return Event::caused_by(event.source_id, EventType::NOOP); + } + + event + } +} + +// TODO: test diff --git a/espanso-engine/src/process/mod.rs b/espanso-engine/src/process/mod.rs new file mode 100644 index 0000000..8102ad0 --- /dev/null +++ b/espanso-engine/src/process/mod.rs @@ -0,0 +1,77 @@ +/* + * This file is part of espanso. + * + * 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::Event; + +mod default; +mod middleware; + +pub trait Middleware { + fn name(&self) -> &'static str; + fn next(&self, event: Event, dispatch: &mut dyn FnMut(Event)) -> Event; +} + +pub trait Processor { + fn process(&mut self, event: Event) -> Vec; +} + +// Dependency inversion entities + +pub use middleware::action::{EventSequenceProvider, MatchInfoProvider}; +pub use middleware::delay_modifiers::ModifierStatusProvider; +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, +}; +pub use middleware::multiplex::Multiplexer; +pub use middleware::render::{Renderer, RendererError}; +pub use middleware::search::MatchProvider; + +#[allow(clippy::too_many_arguments)] +pub fn default<'a, MatcherState>( + matchers: &'a [&'a dyn Matcher<'a, MatcherState>], + match_filter: &'a dyn MatchFilter, + match_selector: &'a dyn MatchSelector, + multiplexer: &'a dyn Multiplexer, + renderer: &'a dyn Renderer<'a>, + match_info_provider: &'a dyn MatchInfoProvider, + modifier_status_provider: &'a dyn ModifierStatusProvider, + event_sequence_provider: &'a dyn EventSequenceProvider, + path_provider: &'a dyn PathProvider, + disable_options: DisableOptions, + matcher_options_provider: &'a dyn MatcherMiddlewareConfigProvider, + match_provider: &'a dyn MatchProvider, +) -> impl Processor + 'a { + default::DefaultProcessor::new( + matchers, + match_filter, + match_selector, + multiplexer, + renderer, + match_info_provider, + modifier_status_provider, + event_sequence_provider, + path_provider, + disable_options, + matcher_options_provider, + match_provider, + ) +}