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
");
+ }
+
+ 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,
+ )
+}