diff --git a/espanso/src/cli/worker/engine/executor/clipboard_injector.rs b/espanso/src/cli/worker/engine/executor/clipboard_injector.rs
index 848ef0b..b549afc 100644
--- a/espanso/src/cli/worker/engine/executor/clipboard_injector.rs
+++ b/espanso/src/cli/worker/engine/executor/clipboard_injector.rs
@@ -17,10 +17,12 @@
* along with espanso. If not, see .
*/
+use std::path::PathBuf;
+
use espanso_inject::{Injector, keys::Key};
use espanso_clipboard::Clipboard;
-use crate::engine::{dispatch::TextInjector, dispatch::HtmlInjector};
+use crate::engine::{dispatch::HtmlInjector, dispatch::{ImageInjector, TextInjector}};
pub struct ClipboardInjectorAdapter<'a> {
injector: &'a dyn Injector,
@@ -71,3 +73,19 @@ impl <'a> HtmlInjector for ClipboardInjectorAdapter<'a> {
Ok(())
}
}
+
+impl <'a> ImageInjector for ClipboardInjectorAdapter<'a> {
+ fn inject_image(&self, image_path: &str) -> anyhow::Result<()> {
+ let path = PathBuf::from(image_path);
+ if !path.is_file() {
+ return Err(std::io::Error::new(std::io::ErrorKind::NotFound, "image can't be found in the given path").into());
+ }
+
+ // TODO: handle clipboard restoration
+ self.clipboard.set_image(&path)?;
+
+ self.send_paste_combination()?;
+
+ Ok(())
+ }
+}
diff --git a/espanso/src/cli/worker/engine/mod.rs b/espanso/src/cli/worker/engine/mod.rs
index 04a31a1..2c59bd7 100644
--- a/espanso/src/cli/worker/engine/mod.rs
+++ b/espanso/src/cli/worker/engine/mod.rs
@@ -22,12 +22,15 @@ use espanso_config::{config::ConfigStore, matches::store::MatchStore};
use espanso_path::Paths;
use ui::selector::MatchSelectorAdapter;
+use crate::cli::worker::engine::path::PathProviderAdapter;
+
use super::ui::icon::IconPaths;
pub mod executor;
pub mod match_cache;
pub mod matcher;
pub mod multiplex;
+pub mod path;
pub mod render;
pub mod source;
pub mod ui;
@@ -99,6 +102,7 @@ pub fn initialize_and_spawn(
]);
let renderer_adapter =
super::engine::render::RendererAdapter::new(&match_cache, &config_manager, &renderer);
+ let path_provider = PathProviderAdapter::new(&paths);
let mut processor = crate::engine::process::default(
&matchers,
@@ -109,6 +113,7 @@ pub fn initialize_and_spawn(
&match_cache,
&modifier_state_store,
&sequencer,
+ &path_provider,
);
let event_injector =
@@ -125,6 +130,7 @@ pub fn initialize_and_spawn(
&config_manager,
&key_injector,
&clipboard_injector,
+ &clipboard_injector,
);
let mut engine = crate::engine::Engine::new(&funnel, &mut processor, &dispatcher);
diff --git a/espanso/src/cli/worker/engine/multiplex.rs b/espanso/src/cli/worker/engine/multiplex.rs
index bbcfb1d..b0db6e2 100644
--- a/espanso/src/cli/worker/engine/multiplex.rs
+++ b/espanso/src/cli/worker/engine/multiplex.rs
@@ -19,14 +19,7 @@
use espanso_config::matches::{Match, MatchEffect};
-use crate::engine::{
- event::{
- internal::DetectedMatch,
- internal::{RenderingRequestedEvent, TextFormat},
- EventType,
- },
- process::Multiplexer,
-};
+use crate::engine::{event::{EventType, internal::DetectedMatch, internal::{ImageRequestedEvent, RenderingRequestedEvent, TextFormat}}, process::Multiplexer};
pub trait MatchProvider<'a> {
fn get(&self, match_id: i32) -> Option<&'a Match>;
@@ -55,7 +48,10 @@ impl<'a> Multiplexer for MultiplexAdapter<'a> {
trigger_args: detected_match.args,
format: convert_format(&effect.format),
})),
- // TODO: think about image
+ MatchEffect::Image(effect) => Some(EventType::ImageRequested(ImageRequestedEvent {
+ match_id: detected_match.id,
+ image_path: effect.path.clone(),
+ })),
MatchEffect::None => None,
}
}
diff --git a/espanso/src/cli/worker/engine/path.rs b/espanso/src/cli/worker/engine/path.rs
new file mode 100644
index 0000000..1737393
--- /dev/null
+++ b/espanso/src/cli/worker/engine/path.rs
@@ -0,0 +1,40 @@
+/*
+ * 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 espanso_path::Paths;
+
+use crate::engine::process::PathProvider;
+
+pub struct PathProviderAdapter<'a> {
+ paths: &'a Paths,
+}
+
+impl <'a> PathProviderAdapter<'a> {
+ pub fn new(paths: &'a Paths) -> Self {
+ Self {
+ paths,
+ }
+ }
+}
+
+impl <'a> PathProvider for PathProviderAdapter<'a> {
+ fn get_config_path(&self) -> &std::path::Path {
+ &self.paths.config
+ }
+}
diff --git a/espanso/src/engine/dispatch/default.rs b/espanso/src/engine/dispatch/default.rs
index 55d28a3..53051e7 100644
--- a/espanso/src/engine/dispatch/default.rs
+++ b/espanso/src/engine/dispatch/default.rs
@@ -17,7 +17,7 @@
* along with espanso. If not, see .
*/
-use super::Event;
+use super::{Event, ImageInjector};
use super::{ModeProvider, Dispatcher, Executor, KeyInjector, TextInjector, HtmlInjector};
pub struct DefaultDispatcher<'a> {
@@ -31,6 +31,7 @@ impl<'a> DefaultDispatcher<'a> {
mode_provider: &'a dyn ModeProvider,
key_injector: &'a dyn KeyInjector,
html_injector: &'a dyn HtmlInjector,
+ image_injector: &'a dyn ImageInjector,
) -> Self {
Self {
executors: vec![
@@ -44,6 +45,9 @@ impl<'a> DefaultDispatcher<'a> {
)),
Box::new(super::executor::html_inject::HtmlInjectExecutor::new(
html_injector,
+ )),
+ Box::new(super::executor::image_inject::ImageInjectExecutor::new(
+ image_injector,
))
],
}
diff --git a/espanso/src/engine/dispatch/executor/image_inject.rs b/espanso/src/engine/dispatch/executor/image_inject.rs
new file mode 100644
index 0000000..1fdcfd2
--- /dev/null
+++ b/espanso/src/engine/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 super::super::{Event, Executor};
+use crate::engine::event::EventType;
+use anyhow::Result;
+use log::error;
+
+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/src/engine/dispatch/executor/mod.rs b/espanso/src/engine/dispatch/executor/mod.rs
index 09c73d0..b6a0d63 100644
--- a/espanso/src/engine/dispatch/executor/mod.rs
+++ b/espanso/src/engine/dispatch/executor/mod.rs
@@ -19,4 +19,5 @@
pub mod text_inject;
pub mod key_inject;
-pub mod html_inject;
\ No newline at end of file
+pub mod html_inject;
+pub mod image_inject;
\ No newline at end of file
diff --git a/espanso/src/engine/dispatch/mod.rs b/espanso/src/engine/dispatch/mod.rs
index ca80f8c..46ad9b8 100644
--- a/espanso/src/engine/dispatch/mod.rs
+++ b/espanso/src/engine/dispatch/mod.rs
@@ -35,6 +35,7 @@ pub trait Dispatcher {
// Re-export dependency injection entities
pub use executor::html_inject::HtmlInjector;
pub use executor::text_inject::{Mode, ModeProvider, TextInjector};
+pub use executor::image_inject::{ImageInjector};
// TODO: move into module
pub trait KeyInjector {
@@ -47,6 +48,7 @@ pub fn default<'a>(
mode_provider: &'a dyn ModeProvider,
key_injector: &'a dyn KeyInjector,
html_injector: &'a dyn HtmlInjector,
+ image_injector: &'a dyn ImageInjector,
) -> impl Dispatcher + 'a {
default::DefaultDispatcher::new(
event_injector,
@@ -54,5 +56,6 @@ pub fn default<'a>(
mode_provider,
key_injector,
html_injector,
+ image_injector,
)
}
diff --git a/espanso/src/engine/event/effect.rs b/espanso/src/engine/event/effect.rs
index d554b1e..e8937b0 100644
--- a/espanso/src/engine/event/effect.rs
+++ b/espanso/src/engine/event/effect.rs
@@ -55,4 +55,9 @@ pub enum TextInjectMode {
#[derive(Debug, Clone)]
pub struct KeySequenceInjectRequest {
pub keys: Vec,
+}
+
+#[derive(Debug, Clone)]
+pub struct ImageInjectRequest {
+ pub image_path: String,
}
\ No newline at end of file
diff --git a/espanso/src/engine/event/internal.rs b/espanso/src/engine/event/internal.rs
index 6d9a510..1afed3c 100644
--- a/espanso/src/engine/event/internal.rs
+++ b/espanso/src/engine/event/internal.rs
@@ -60,6 +60,17 @@ pub enum TextFormat {
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,
diff --git a/espanso/src/engine/event/mod.rs b/espanso/src/engine/event/mod.rs
index 324fead..e199867 100644
--- a/espanso/src/engine/event/mod.rs
+++ b/espanso/src/engine/event/mod.rs
@@ -59,7 +59,9 @@ pub enum EventType {
CauseCompensatedMatch(internal::CauseCompensatedMatchEvent),
RenderingRequested(internal::RenderingRequestedEvent),
+ ImageRequested(internal::ImageRequestedEvent),
Rendered(internal::RenderedEvent),
+ ImageResolved(internal::ImageResolvedEvent),
MatchInjected,
DiscardPrevious(internal::DiscardPreviousEvent),
@@ -71,4 +73,5 @@ pub enum EventType {
TextInject(effect::TextInjectRequest),
MarkdownInject(effect::MarkdownInjectRequest),
HtmlInject(effect::HtmlInjectRequest),
+ ImageInject(effect::ImageInjectRequest),
}
\ No newline at end of file
diff --git a/espanso/src/engine/process/default.rs b/espanso/src/engine/process/default.rs
index 783e8eb..f8432eb 100644
--- a/espanso/src/engine/process/default.rs
+++ b/espanso/src/engine/process/default.rs
@@ -19,13 +19,13 @@
use log::trace;
-use super::{MatchFilter, MatchInfoProvider, MatchSelector, Matcher, Middleware, Multiplexer, Processor, Renderer, middleware::{
+use super::{MatchFilter, MatchInfoProvider, MatchSelector, Matcher, Middleware, Multiplexer, PathProvider, Processor, Renderer, middleware::{
match_select::MatchSelectMiddleware, matcher::MatcherMiddleware, multiplex::MultiplexMiddleware,
render::RenderMiddleware, action::{ActionMiddleware, EventSequenceProvider}, cursor_hint::CursorHintMiddleware, cause::CauseCompensateMiddleware,
delay_modifiers::{DelayForModifierReleaseMiddleware, ModifierStatusProvider}, markdown::MarkdownMiddleware,
past_discard::PastEventsDiscardMiddleware,
}};
-use crate::engine::event::{Event, EventType};
+use crate::engine::{event::{Event, EventType}, process::middleware::image_resolve::ImageResolverMiddleware};
use std::collections::VecDeque;
pub struct DefaultProcessor<'a> {
@@ -43,6 +43,7 @@ impl<'a> DefaultProcessor<'a> {
match_info_provider: &'a dyn MatchInfoProvider,
modifier_status_provider: &'a dyn ModifierStatusProvider,
event_sequence_provider: &'a dyn EventSequenceProvider,
+ path_provider: &'a dyn PathProvider,
) -> DefaultProcessor<'a> {
Self {
event_queue: VecDeque::new(),
@@ -53,6 +54,7 @@ impl<'a> DefaultProcessor<'a> {
Box::new(CauseCompensateMiddleware::new()),
Box::new(MultiplexMiddleware::new(multiplexer)),
Box::new(RenderMiddleware::new(renderer)),
+ Box::new(ImageResolverMiddleware::new(path_provider)),
Box::new(CursorHintMiddleware::new()),
Box::new(ActionMiddleware::new(match_info_provider, event_sequence_provider)),
Box::new(MarkdownMiddleware::new()),
diff --git a/espanso/src/engine/process/middleware/action.rs b/espanso/src/engine/process/middleware/action.rs
index a581ac6..d1c96d8 100644
--- a/espanso/src/engine/process/middleware/action.rs
+++ b/espanso/src/engine/process/middleware/action.rs
@@ -18,7 +18,15 @@
*/
use super::super::Middleware;
-use crate::engine::event::{Event, EventType, effect::{HtmlInjectRequest, KeySequenceInjectRequest, MarkdownInjectRequest, TextInjectMode, TextInjectRequest}, input::Key, internal::{DiscardPreviousEvent, TextFormat}};
+use crate::engine::event::{
+ effect::{
+ HtmlInjectRequest, ImageInjectRequest, KeySequenceInjectRequest, MarkdownInjectRequest,
+ TextInjectMode, TextInjectRequest,
+ },
+ input::Key,
+ internal::{DiscardPreviousEvent, TextFormat},
+ Event, EventType,
+};
pub trait MatchInfoProvider {
fn get_force_mode(&self, match_id: i32) -> Option;
@@ -52,7 +60,7 @@ impl<'a> Middleware for ActionMiddleware<'a> {
fn next(&self, event: Event, dispatch: &mut dyn FnMut(Event)) -> Event {
match &event.etype {
- EventType::Rendered(m_event) => {
+ EventType::Rendered(_) | EventType::ImageResolved(_) => {
dispatch(Event::caused_by(event.source_id, EventType::MatchInjected));
dispatch(Event::caused_by(
event.source_id,
@@ -61,21 +69,30 @@ impl<'a> Middleware for ActionMiddleware<'a> {
}),
));
- Event::caused_by(
- event.source_id,
- match m_event.format {
- TextFormat::Plain => EventType::TextInject(TextInjectRequest {
- text: m_event.body.clone(),
- force_mode: self.match_info_provider.get_force_mode(m_event.match_id),
+ match &event.etype {
+ EventType::Rendered(m_event) => Event::caused_by(
+ event.source_id,
+ match m_event.format {
+ TextFormat::Plain => EventType::TextInject(TextInjectRequest {
+ text: m_event.body.clone(),
+ force_mode: self.match_info_provider.get_force_mode(m_event.match_id),
+ }),
+ TextFormat::Html => EventType::HtmlInject(HtmlInjectRequest {
+ html: m_event.body.clone(),
+ }),
+ TextFormat::Markdown => EventType::MarkdownInject(MarkdownInjectRequest {
+ markdown: m_event.body.clone(),
+ }),
+ },
+ ),
+ EventType::ImageResolved(m_event) => Event::caused_by(
+ event.source_id,
+ EventType::ImageInject(ImageInjectRequest {
+ image_path: m_event.image_path.clone(),
}),
- TextFormat::Html => EventType::HtmlInject(HtmlInjectRequest {
- html: m_event.body.clone(),
- }),
- TextFormat::Markdown => EventType::MarkdownInject(MarkdownInjectRequest {
- markdown: m_event.body.clone(),
- }),
- },
- )
+ ),
+ _ => unreachable!()
+ }
}
EventType::CursorHintCompensation(m_event) => {
dispatch(Event::caused_by(
diff --git a/espanso/src/engine/process/middleware/image_resolve.rs b/espanso/src/engine/process/middleware/image_resolve.rs
new file mode 100644
index 0000000..7710611
--- /dev/null
+++ b/espanso/src/engine/process/middleware/image_resolve.rs
@@ -0,0 +1,81 @@
+/*
+ * This file is part of espanso.
+ *
+ * Copyright (C) 2019-2021 Federico Terzi
+ *
+ * espanso is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * espanso is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with espanso. If not, see .
+ */
+
+use std::{
+ path::Path,
+};
+
+use log::{error};
+
+use super::super::Middleware;
+use crate::engine::event::{Event, EventType, internal::ImageResolvedEvent};
+
+pub trait PathProvider {
+ fn get_config_path(&self) -> &Path;
+}
+
+pub struct ImageResolverMiddleware<'a> {
+ provider: &'a dyn PathProvider,
+}
+
+impl<'a> ImageResolverMiddleware<'a> {
+ pub fn new(provider: &'a dyn PathProvider) -> Self {
+ Self { provider }
+ }
+}
+
+impl<'a> Middleware for ImageResolverMiddleware<'a> {
+ fn name(&self) -> &'static str {
+ "image_resolve"
+ }
+
+ fn next(&self, event: Event, _: &mut dyn FnMut(Event)) -> Event {
+ if let EventType::ImageRequested(m_event) = &event.etype {
+ // On Windows, we have to replace the forward / with the backslash \ in the path
+ let path = if cfg!(target_os = "windows") {
+ m_event.image_path.replace("/", "\\")
+ } else {
+ m_event.image_path.to_owned()
+ };
+
+ let path = if path.contains("$CONFIG") {
+ let config_path = match self.provider.get_config_path().canonicalize() {
+ Ok(path) => path,
+ Err(err) => {
+ error!("unable to canonicalize the config path into the image resolver: {}", err);
+ self.provider.get_config_path().to_owned()
+ },
+ };
+ path.replace("$CONFIG", &config_path.to_string_lossy())
+ } else {
+ path
+ };
+
+ return Event::caused_by(event.source_id, EventType::ImageResolved(ImageResolvedEvent {
+ image_path: path,
+ }))
+ }
+
+
+
+ event
+ }
+}
+
+// TODO: test
diff --git a/espanso/src/engine/process/middleware/mod.rs b/espanso/src/engine/process/middleware/mod.rs
index bb85599..cd930b2 100644
--- a/espanso/src/engine/process/middleware/mod.rs
+++ b/espanso/src/engine/process/middleware/mod.rs
@@ -21,6 +21,7 @@ pub mod action;
pub mod cause;
pub mod cursor_hint;
pub mod delay_modifiers;
+pub mod image_resolve;
pub mod match_select;
pub mod matcher;
pub mod markdown;
diff --git a/espanso/src/engine/process/mod.rs b/espanso/src/engine/process/mod.rs
index 57c772d..45f7060 100644
--- a/espanso/src/engine/process/mod.rs
+++ b/espanso/src/engine/process/mod.rs
@@ -94,6 +94,7 @@ pub enum RendererError {
pub use middleware::action::{MatchInfoProvider, EventSequenceProvider};
pub use middleware::delay_modifiers::ModifierStatusProvider;
+pub use middleware::image_resolve::PathProvider;
pub fn default<'a, MatcherState>(
matchers: &'a [&'a dyn Matcher<'a, MatcherState>],
@@ -104,6 +105,7 @@ pub fn default<'a, MatcherState>(
match_info_provider: &'a dyn MatchInfoProvider,
modifier_status_provider: &'a dyn ModifierStatusProvider,
event_sequence_provider: &'a dyn EventSequenceProvider,
+ path_provider: &'a dyn PathProvider,
) -> impl Processor + 'a {
default::DefaultProcessor::new(
matchers,
@@ -114,5 +116,6 @@ pub fn default<'a, MatcherState>(
match_info_provider,
modifier_status_provider,
event_sequence_provider,
+ path_provider,
)
}