From a4958ff352d4ee10531b04f17e206b81a8cc32ba Mon Sep 17 00:00:00 2001 From: Federico Terzi Date: Thu, 1 Apr 2021 22:13:56 +0200 Subject: [PATCH] feat(core): early structure of the engine --- Cargo.lock | 1 + espanso/Cargo.toml | 3 +- espanso/src/cli/mod.rs | 1 + espanso/src/cli/worker/mod.rs | 44 ++++++ espanso/src/cli/worker/source/detect.rs | 41 ++++++ espanso/src/cli/worker/source/mod.rs | 20 +++ espanso/src/engine/dispatch/default.rs | 45 ++++++ espanso/src/engine/dispatch/executor/mod.rs | 20 +++ .../engine/dispatch/executor/text_inject.rs | 47 +++++++ espanso/src/engine/dispatch/mod.rs | 42 ++++++ espanso/src/engine/event/inject.rs | 30 ++++ espanso/src/engine/event/keyboard.rs | 95 +++++++++++++ espanso/src/engine/event/matches_detected.rs | 32 +++++ espanso/src/engine/event/mod.rs | 36 +++++ espanso/src/engine/funnel/default.rs | 56 ++++++++ espanso/src/engine/funnel/mod.rs | 44 ++++++ espanso/src/engine/mod.rs | 68 +++++++++ espanso/src/engine/process/default.rs | 78 ++++++++++ .../src/engine/process/middleware/matcher.rs | 133 ++++++++++++++++++ espanso/src/engine/process/middleware/mod.rs | 20 +++ espanso/src/engine/process/mod.rs | 56 ++++++++ espanso/src/main.rs | 32 ++--- 22 files changed, 927 insertions(+), 17 deletions(-) create mode 100644 espanso/src/cli/worker/mod.rs create mode 100644 espanso/src/cli/worker/source/detect.rs create mode 100644 espanso/src/cli/worker/source/mod.rs create mode 100644 espanso/src/engine/dispatch/default.rs create mode 100644 espanso/src/engine/dispatch/executor/mod.rs create mode 100644 espanso/src/engine/dispatch/executor/text_inject.rs create mode 100644 espanso/src/engine/dispatch/mod.rs create mode 100644 espanso/src/engine/event/inject.rs create mode 100644 espanso/src/engine/event/keyboard.rs create mode 100644 espanso/src/engine/event/matches_detected.rs create mode 100644 espanso/src/engine/event/mod.rs create mode 100644 espanso/src/engine/funnel/default.rs create mode 100644 espanso/src/engine/funnel/mod.rs create mode 100644 espanso/src/engine/mod.rs create mode 100644 espanso/src/engine/process/default.rs create mode 100644 espanso/src/engine/process/middleware/matcher.rs create mode 100644 espanso/src/engine/process/middleware/mod.rs create mode 100644 espanso/src/engine/process/mod.rs 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