diff --git a/Cargo.lock b/Cargo.lock
index 5fed2e9..7133b15 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -287,6 +287,7 @@ version = "1.0.0"
dependencies = [
"anyhow",
"clap",
+ "crossbeam",
"espanso-clipboard",
"espanso-config",
"espanso-detect",
diff --git a/espanso/Cargo.toml b/espanso/Cargo.toml
index f6a2412..a177579 100644
--- a/espanso/Cargo.toml
+++ b/espanso/Cargo.toml
@@ -28,4 +28,5 @@ log = "0.4.14"
anyhow = "1.0.38"
thiserror = "1.0.23"
clap = "2.33.3"
-lazy_static = "1.4.0"
\ No newline at end of file
+lazy_static = "1.4.0"
+crossbeam = "0.8.0"
\ No newline at end of file
diff --git a/espanso/src/cli/mod.rs b/espanso/src/cli/mod.rs
index df2cb10..fa68778 100644
--- a/espanso/src/cli/mod.rs
+++ b/espanso/src/cli/mod.rs
@@ -23,6 +23,7 @@ use espanso_path::Paths;
pub mod log;
pub mod path;
+pub mod worker;
pub struct CliModule {
pub enable_logs: bool,
diff --git a/espanso/src/cli/worker/mod.rs b/espanso/src/cli/worker/mod.rs
new file mode 100644
index 0000000..74eb4df
--- /dev/null
+++ b/espanso/src/cli/worker/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 crate::engine::{Engine, funnel};
+use super::{CliModule, CliModuleArgs};
+
+mod source;
+
+pub fn new() -> CliModule {
+ #[allow(clippy::needless_update)]
+ CliModule {
+ requires_paths: true,
+ requires_config: true,
+ enable_logs: true,
+ subcommand: "worker".to_string(),
+ entry: worker_main,
+ ..Default::default()
+ }
+}
+
+fn worker_main(args: CliModuleArgs) {
+ let funnel = funnel::default(vec![
+ Box::new(),
+ ]);
+
+ let engine = Engine::new(funnel);
+}
+
diff --git a/espanso/src/cli/worker/source/detect.rs b/espanso/src/cli/worker/source/detect.rs
new file mode 100644
index 0000000..f9cb687
--- /dev/null
+++ b/espanso/src/cli/worker/source/detect.rs
@@ -0,0 +1,41 @@
+/*
+ * 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::{Receiver, Select, SelectedOperation};
+use espanso_detect::event::InputEvent;
+
+use crate::engine::{event::Event, funnel::Source};
+
+pub struct DetectorSource {
+ receiver: Receiver,
+}
+
+impl Source for DetectorSource {
+ fn register(&self, select: &Select) -> i32 {
+ todo!()
+ }
+
+ fn receive(&self, select: &SelectedOperation) -> Event {
+ todo!()
+ }
+}
+
+pub fn new() {
+ todo!()
+}
\ No newline at end of file
diff --git a/espanso/src/cli/worker/source/mod.rs b/espanso/src/cli/worker/source/mod.rs
new file mode 100644
index 0000000..e5f4092
--- /dev/null
+++ b/espanso/src/cli/worker/source/mod.rs
@@ -0,0 +1,20 @@
+/*
+ * 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 detect;
\ No newline at end of file
diff --git a/espanso/src/engine/dispatch/default.rs b/espanso/src/engine/dispatch/default.rs
new file mode 100644
index 0000000..9d1b2a8
--- /dev/null
+++ b/espanso/src/engine/dispatch/default.rs
@@ -0,0 +1,45 @@
+/*
+ * 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::{Dispatcher, Executor, TextInjector};
+use super::Event;
+
+pub struct DefaultDispatcher {
+ executors: Vec>,
+}
+
+impl DefaultDispatcher {
+ pub fn new(text_injector: impl TextInjector + 'static) -> Self {
+ Self {
+ executors: vec![
+ Box::new(super::executor::text_inject::TextInjectExecutor::new(text_injector)),
+ ]
+ }
+ }
+}
+
+impl Dispatcher for DefaultDispatcher {
+ fn dispatch(&self, event: Event) {
+ for executor in self.executors.iter() {
+ if executor.execute(&event) {
+ break
+ }
+ }
+ }
+}
diff --git a/espanso/src/engine/dispatch/executor/mod.rs b/espanso/src/engine/dispatch/executor/mod.rs
new file mode 100644
index 0000000..05c1a2f
--- /dev/null
+++ b/espanso/src/engine/dispatch/executor/mod.rs
@@ -0,0 +1,20 @@
+/*
+ * 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 text_inject;
\ No newline at end of file
diff --git a/espanso/src/engine/dispatch/executor/text_inject.rs b/espanso/src/engine/dispatch/executor/text_inject.rs
new file mode 100644
index 0000000..29f00db
--- /dev/null
+++ b/espanso/src/engine/dispatch/executor/text_inject.rs
@@ -0,0 +1,47 @@
+/*
+ * 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::{Event, Executor, TextInjector};
+use crate::engine::event::inject::TextInjectMode;
+use log::error;
+
+pub struct TextInjectExecutor {
+ injector: T,
+}
+
+impl TextInjectExecutor {
+ pub fn new(injector: T) -> Self {
+ Self { injector }
+ }
+}
+
+impl Executor for TextInjectExecutor {
+ fn execute(&self, event: &Event) -> bool {
+ if let Event::TextInject(inject_event) = event {
+ if inject_event.mode == TextInjectMode::Keys {
+ if let Err(error) = self.injector.inject(&inject_event.text) {
+ error!("text injector reported an error: {:?}", error);
+ }
+ return true;
+ }
+ }
+
+ false
+ }
+}
diff --git a/espanso/src/engine/dispatch/mod.rs b/espanso/src/engine/dispatch/mod.rs
new file mode 100644
index 0000000..80709d0
--- /dev/null
+++ b/espanso/src/engine/dispatch/mod.rs
@@ -0,0 +1,42 @@
+/*
+ * 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 super::Event;
+
+mod executor;
+mod default;
+
+pub trait Executor {
+ fn execute(&self, event: &Event) -> bool;
+}
+
+pub trait Dispatcher {
+ fn dispatch(&self, event: Event);
+}
+
+pub trait TextInjector {
+ fn inject(&self, text: &str) -> Result<()>;
+}
+
+pub fn default(text_injector: impl TextInjector + 'static) -> impl Dispatcher {
+ default::DefaultDispatcher::new(
+ text_injector,
+ )
+}
\ No newline at end of file
diff --git a/espanso/src/engine/event/inject.rs b/espanso/src/engine/event/inject.rs
new file mode 100644
index 0000000..de2b254
--- /dev/null
+++ b/espanso/src/engine/event/inject.rs
@@ -0,0 +1,30 @@
+/*
+ * 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)]
+pub struct TextInjectEvent {
+ pub text: String,
+ pub mode: TextInjectMode,
+}
+
+#[derive(Debug, PartialEq)]
+pub enum TextInjectMode {
+ Keys,
+ Clipboard,
+}
\ No newline at end of file
diff --git a/espanso/src/engine/event/keyboard.rs b/espanso/src/engine/event/keyboard.rs
new file mode 100644
index 0000000..05fb0a8
--- /dev/null
+++ b/espanso/src/engine/event/keyboard.rs
@@ -0,0 +1,95 @@
+/*
+ * 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)]
+pub enum Status {
+ Pressed,
+ Released,
+}
+
+#[derive(Debug, PartialEq)]
+pub enum Variant {
+ Left,
+ Right,
+}
+
+#[derive(Debug, PartialEq)]
+pub struct KeyboardEvent {
+ pub key: Key,
+ pub value: Option,
+ pub status: Status,
+ pub variant: Option,
+}
+
+#[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),
+}
\ No newline at end of file
diff --git a/espanso/src/engine/event/matches_detected.rs b/espanso/src/engine/event/matches_detected.rs
new file mode 100644
index 0000000..cb9e331
--- /dev/null
+++ b/espanso/src/engine/event/matches_detected.rs
@@ -0,0 +1,32 @@
+/*
+ * 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 results: Vec,
+}
+
+#[derive(Debug, Clone, PartialEq)]
+pub struct MatchResult {
+ pub id: i32,
+ pub trigger: String,
+ pub vars: HashMap,
+}
\ No newline at end of file
diff --git a/espanso/src/engine/event/mod.rs b/espanso/src/engine/event/mod.rs
new file mode 100644
index 0000000..ecb1f86
--- /dev/null
+++ b/espanso/src/engine/event/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 keyboard;
+pub mod inject;
+pub mod matches_detected;
+
+#[derive(Debug)]
+pub enum Event {
+ NOOP,
+
+ // Inputs
+ Keyboard(keyboard::KeyboardEvent),
+
+ // Internal
+ MatchesDetected(matches_detected::MatchesDetectedEvent),
+
+ // Effects
+ TextInject(inject::TextInjectEvent),
+}
\ No newline at end of file
diff --git a/espanso/src/engine/funnel/default.rs b/espanso/src/engine/funnel/default.rs
new file mode 100644
index 0000000..63a83c5
--- /dev/null
+++ b/espanso/src/engine/funnel/default.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 crossbeam::channel::Select;
+
+use super::{Funnel, FunnelResult, Source};
+
+pub struct DefaultFunnel {
+ sources: Vec>,
+}
+
+impl DefaultFunnel {
+ pub fn new(sources: Vec>) -> Self {
+ Self {
+ sources
+ }
+ }
+}
+
+impl Funnel for DefaultFunnel {
+ 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(&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/src/engine/funnel/mod.rs b/espanso/src/engine/funnel/mod.rs
new file mode 100644
index 0000000..f258753
--- /dev/null
+++ b/espanso/src/engine/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 super::Event;
+
+mod default;
+
+pub trait Source {
+ fn register(&self, select: &Select) -> i32;
+ fn receive(&self, select: &SelectedOperation) -> Event;
+}
+
+pub trait Funnel {
+ fn receive(&self) -> FunnelResult;
+}
+
+pub enum FunnelResult {
+ Event(Event),
+ EndOfStream,
+}
+
+pub fn default(sources: Vec>) -> impl Funnel {
+ DefaultFunnel::new(sources)
+}
\ No newline at end of file
diff --git a/espanso/src/engine/mod.rs b/espanso/src/engine/mod.rs
new file mode 100644
index 0000000..deef302
--- /dev/null
+++ b/espanso/src/engine/mod.rs
@@ -0,0 +1,68 @@
+/*
+ * 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, trace};
+use std::collections::VecDeque;
+
+use self::{
+ dispatch::Dispatcher,
+ event::Event,
+ process::Processor,
+ funnel::{Funnel, FunnelResult},
+};
+
+pub mod dispatch;
+pub mod event;
+pub mod process;
+pub mod funnel;
+
+pub struct Engine {
+ funnel: TFunnel,
+ processor: TProcessor,
+ dispatcher: TDispatcher,
+}
+
+impl Engine {
+ pub fn new(funnel: TFunnel, processor: TProcessor, dispatcher: TDispatcher) -> Self {
+ Self {
+ funnel,
+ processor,
+ dispatcher,
+ }
+ }
+
+ pub fn run(&mut self) {
+ loop {
+ match self.funnel.receive() {
+ FunnelResult::Event(event) => {
+ trace!("received event from stream: {:?}", event);
+
+ let processed_events = self.processor.process(event);
+ for event in processed_events {
+ self.dispatcher.dispatch(event);
+ }
+ }
+ FunnelResult::EndOfStream => {
+ debug!("end of stream received");
+ break;
+ }
+ }
+ }
+ }
+}
diff --git a/espanso/src/engine/process/default.rs b/espanso/src/engine/process/default.rs
new file mode 100644
index 0000000..53204e2
--- /dev/null
+++ b/espanso/src/engine/process/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::{Event, Matcher, Middleware, Processor, middleware::matcher::MatchMiddleware};
+use std::collections::VecDeque;
+
+pub struct DefaultProcessor {
+ event_queue: VecDeque,
+ middleware: Vec>,
+}
+
+impl DefaultProcessor {
+ pub fn new(matchers: Vec>>) -> Self {
+ Self {
+ event_queue: VecDeque::new(),
+ middleware: vec![
+ Box::new(MatchMiddleware::new(matchers)),
+ ]
+ }
+ }
+
+ 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 dispatch = |event: Event| {
+ // TODO: add tracing information
+ current_queue.push_front(event);
+ };
+
+ for middleware in self.middleware.iter() {
+ // TODO: add tracing information
+ current_event = middleware.next(current_event, &dispatch);
+ }
+
+ while let Some(event) = current_queue.pop_back() {
+ self.event_queue.push_front(event);
+ }
+
+ Some(current_event)
+ } else {
+ None
+ }
+ }
+}
+
+impl Processor for DefaultProcessor {
+ 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
+ }
+}
\ No newline at end of file
diff --git a/espanso/src/engine/process/middleware/matcher.rs b/espanso/src/engine/process/middleware/matcher.rs
new file mode 100644
index 0000000..7890e93
--- /dev/null
+++ b/espanso/src/engine/process/middleware/matcher.rs
@@ -0,0 +1,133 @@
+/*
+ * 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::VecDeque};
+
+use super::super::Middleware;
+use crate::engine::{event::{Event, keyboard::{Key, Status}, matches_detected::{MatchResult, MatchesDetectedEvent}}, process::{Matcher, MatcherEvent}};
+
+const MAX_HISTORY: usize = 3; // TODO: get as parameter
+
+pub struct MatchMiddleware {
+ matchers: Vec>>,
+
+ matcher_states: RefCell>>,
+}
+
+impl MatchMiddleware {
+ pub fn new(matchers: Vec>>) -> Self {
+ Self {
+ matchers,
+ matcher_states: RefCell::new(VecDeque::new()),
+ }
+ }
+}
+
+impl Middleware for MatchMiddleware {
+ fn next(&self, event: Event, _: &dyn FnMut(Event)) -> Event {
+ 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 Event::Keyboard(keyboard_event) = &event {
+ // 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) prevent espanso from building
+ // an accurate key buffer, so we need to invalidate it.
+ if is_invalidating_key(&keyboard_event.key) {
+ matcher_states.clear();
+ return event;
+ }
+ }
+
+ // TODO: test if the matcher detects a word match when the states are cleared (probably not :( )
+
+ let mut all_results = Vec::new();
+
+ if let Some(matcher_event) = convert_to_matcher_event(&event) {
+ 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() > MAX_HISTORY {
+ matcher_states.pop_front();
+ }
+
+ if !all_results.is_empty() {
+ return Event::MatchesDetected(MatchesDetectedEvent {
+ results: all_results.into_iter().map(|result | {
+ MatchResult {
+ id: result.id,
+ trigger: result.trigger,
+ vars: result.vars,
+ }
+ }).collect()
+ })
+ }
+ }
+
+ event
+ }
+}
+
+fn convert_to_matcher_event(event: &Event) -> Option {
+ if let Event::Keyboard(keyboard_event) = event {
+ if keyboard_event.status == Status::Pressed {
+ return Some(MatcherEvent::Key {
+ key: keyboard_event.key.clone(),
+ chars: keyboard_event.value.clone(),
+ });
+ }
+ }
+
+ // TODO: mouse event should act as separator
+
+ None
+}
+
+fn is_invalidating_key(key: &Key) -> bool {
+ match key {
+ Key::ArrowDown => true,
+ Key::ArrowLeft => true,
+ Key::ArrowRight => true,
+ Key::ArrowUp => true,
+ Key::End => true,
+ Key::Home => true,
+ Key::PageDown => true,
+ Key::PageUp => true,
+ Key::Escape => true,
+ _ => false,
+ }
+}
diff --git a/espanso/src/engine/process/middleware/mod.rs b/espanso/src/engine/process/middleware/mod.rs
new file mode 100644
index 0000000..4b34c33
--- /dev/null
+++ b/espanso/src/engine/process/middleware/mod.rs
@@ -0,0 +1,20 @@
+/*
+ * 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 matcher;
\ No newline at end of file
diff --git a/espanso/src/engine/process/mod.rs b/espanso/src/engine/process/mod.rs
new file mode 100644
index 0000000..cee4133
--- /dev/null
+++ b/espanso/src/engine/process/mod.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 std::collections::HashMap;
+
+use super::{Event, event::keyboard::Key};
+
+mod middleware;
+mod default;
+
+pub trait Middleware {
+ fn next(&self, event: Event, dispatch: &dyn FnMut(Event)) -> Event;
+}
+
+pub trait Processor {
+ fn process(&mut self, event: Event) -> Vec;
+}
+
+// Dependency inversion entities
+
+pub trait Matcher {
+ fn process(&self, prev_state: Option<&State>, event: &MatcherEvent) -> (State, Vec);
+}
+
+pub enum MatcherEvent {
+ Key { key: Key, chars: Option },
+ VirtualSeparator,
+}
+
+#[derive(Debug, Clone, PartialEq)]
+pub struct MatchResult {
+ id: i32,
+ trigger: String,
+ vars: HashMap,
+}
+
+pub fn default(matchers: Vec>>) -> impl Processor {
+ default::DefaultProcessor::new(matchers)
+}
\ No newline at end of file
diff --git a/espanso/src/main.rs b/espanso/src/main.rs
index 97b490b..d46f122 100644
--- a/espanso/src/main.rs
+++ b/espanso/src/main.rs
@@ -28,7 +28,6 @@ use simplelog::{CombinedLogger, ConfigBuilder, LevelFilter, TermLogger, Terminal
mod cli;
mod engine;
mod logging;
-mod util;
const VERSION: &str = env!("CARGO_PKG_VERSION");
const LOG_FILE_NAME: &str = "espanso.log";
@@ -37,6 +36,7 @@ lazy_static! {
static ref CLI_HANDLERS: Vec = vec![
cli::path::new(),
cli::log::new(),
+ cli::worker::new(),
];
}
@@ -170,19 +170,19 @@ fn main() {
// )
// )
// Package manager
- .subcommand(SubCommand::with_name("package")
- .about("Espanso package manager commands")
- .subcommand(install_subcommand.clone())
- .subcommand(uninstall_subcommand.clone())
- .subcommand(SubCommand::with_name("list")
- .about("List all installed packages")
- .arg(Arg::with_name("full")
- .help("Print all package info")
- .long("full")))
+ // .subcommand(SubCommand::with_name("package")
+ // .about("Espanso package manager commands")
+ // .subcommand(install_subcommand.clone())
+ // .subcommand(uninstall_subcommand.clone())
+ // .subcommand(SubCommand::with_name("list")
+ // .about("List all installed packages")
+ // .arg(Arg::with_name("full")
+ // .help("Print all package info")
+ // .long("full")))
- .subcommand(SubCommand::with_name("refresh")
- .about("Update espanso package index"))
- )
+ // .subcommand(SubCommand::with_name("refresh")
+ // .about("Update espanso package index"))
+ // )
.subcommand(SubCommand::with_name("worker")
.setting(AppSettings::Hidden)
.arg(Arg::with_name("reload")
@@ -190,9 +190,9 @@ fn main() {
.long("reload")
.required(false)
.takes_value(false))
- )
- .subcommand(install_subcommand)
- .subcommand(uninstall_subcommand);
+ );
+ // .subcommand(install_subcommand)
+ // .subcommand(uninstall_subcommand);
// TODO: explain that the start and restart commands are only meaningful
// when using the system daemon manager on macOS and Linux