diff --git a/espanso/src/cli/worker/engine/executor/context_menu.rs b/espanso/src/cli/worker/engine/executor/context_menu.rs
new file mode 100644
index 0000000..55822b8
--- /dev/null
+++ b/espanso/src/cli/worker/engine/executor/context_menu.rs
@@ -0,0 +1,70 @@
+/*
+ * 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_ui::UIRemote;
+
+use crate::engine::dispatch::ContextMenuHandler;
+
+pub struct ContextMenuHandlerAdapter<'a> {
+ remote: &'a dyn UIRemote,
+}
+
+impl<'a> ContextMenuHandlerAdapter<'a> {
+ pub fn new(remote: &'a dyn UIRemote) -> Self {
+ Self { remote }
+ }
+}
+
+impl<'a> ContextMenuHandler for ContextMenuHandlerAdapter<'a> {
+ fn show_context_menu(&self, items: &[crate::engine::event::ui::MenuItem]) -> anyhow::Result<()> {
+ let ui_menu_items: Vec =
+ items.iter().map(convert_to_ui_menu_item).collect();
+ let ui_menu = espanso_ui::menu::Menu {
+ items: ui_menu_items,
+ };
+
+ self.remote.show_context_menu(&ui_menu);
+
+ Ok(())
+ }
+}
+
+fn convert_to_ui_menu_item(
+ item: &crate::engine::event::ui::MenuItem,
+) -> espanso_ui::menu::MenuItem {
+ match item {
+ crate::engine::event::ui::MenuItem::Simple(simple) => {
+ espanso_ui::menu::MenuItem::Simple(espanso_ui::menu::SimpleMenuItem {
+ id: simple.id,
+ label: simple.label.clone(),
+ })
+ }
+ crate::engine::event::ui::MenuItem::Sub(sub) => {
+ espanso_ui::menu::MenuItem::Sub(espanso_ui::menu::SubMenuItem {
+ label: sub.label.clone(),
+ items: sub
+ .items
+ .iter()
+ .map(|item| convert_to_ui_menu_item(item))
+ .collect(),
+ })
+ }
+ crate::engine::event::ui::MenuItem::Separator => espanso_ui::menu::MenuItem::Separator,
+ }
+}
diff --git a/espanso/src/cli/worker/engine/executor/mod.rs b/espanso/src/cli/worker/engine/executor/mod.rs
index 162e979..ad18615 100644
--- a/espanso/src/cli/worker/engine/executor/mod.rs
+++ b/espanso/src/cli/worker/engine/executor/mod.rs
@@ -18,5 +18,6 @@
*/
pub mod clipboard_injector;
+pub mod context_menu;
pub mod event_injector;
pub mod key_injector;
\ No newline at end of file
diff --git a/espanso/src/cli/worker/engine/mod.rs b/espanso/src/cli/worker/engine/mod.rs
index d2f39cf..f8e96a9 100644
--- a/espanso/src/cli/worker/engine/mod.rs
+++ b/espanso/src/cli/worker/engine/mod.rs
@@ -23,7 +23,7 @@ use anyhow::Result;
use crossbeam::channel::Receiver;
use espanso_config::{config::ConfigStore, matches::store::MatchStore};
use espanso_path::Paths;
-use espanso_ui::UIRemote;
+use espanso_ui::{UIRemote, event::UIEvent};
use log::info;
use ui::selector::MatchSelectorAdapter;
@@ -47,6 +47,7 @@ pub fn initialize_and_spawn(
icon_paths: IconPaths,
ui_remote: Box,
exit_signal: Receiver<()>,
+ ui_event_receiver: Receiver,
) -> Result> {
let handle = std::thread::Builder::new()
.name("engine thread".to_string())
@@ -68,7 +69,8 @@ pub fn initialize_and_spawn(
let (detect_source, modifier_state_store, sequencer) =
super::engine::source::init_and_spawn().expect("failed to initialize detector module");
let exit_source = super::engine::source::exit::ExitSource::new(exit_signal, &sequencer);
- let sources: Vec<&dyn crate::engine::funnel::Source> = vec![&detect_source, &exit_source];
+ let ui_source = super::engine::source::ui::UISource::new(ui_event_receiver, &sequencer);
+ let sources: Vec<&dyn crate::engine::funnel::Source> = vec![&detect_source, &exit_source, &ui_source];
let funnel = crate::engine::funnel::default(&sources);
let rolling_matcher = super::engine::matcher::rolling::RollingMatcherAdapter::new(
@@ -140,6 +142,7 @@ pub fn initialize_and_spawn(
&config_manager,
);
let key_injector = super::engine::executor::key_injector::KeyInjectorAdapter::new(&*injector);
+ let context_menu_adapter = super::engine::executor::context_menu::ContextMenuHandlerAdapter::new(&*ui_remote);
let dispatcher = crate::engine::dispatch::default(
&event_injector,
&clipboard_injector,
@@ -147,6 +150,7 @@ pub fn initialize_and_spawn(
&key_injector,
&clipboard_injector,
&clipboard_injector,
+ &context_menu_adapter,
);
let mut engine = crate::engine::Engine::new(&funnel, &mut processor, &dispatcher);
diff --git a/espanso/src/cli/worker/engine/source/mod.rs b/espanso/src/cli/worker/engine/source/mod.rs
index 57b6be1..29a5224 100644
--- a/espanso/src/cli/worker/engine/source/mod.rs
+++ b/espanso/src/cli/worker/engine/source/mod.rs
@@ -30,6 +30,7 @@ pub mod detect;
pub mod exit;
pub mod modifier;
pub mod sequencer;
+pub mod ui;
// TODO: pass options
pub fn init_and_spawn() -> Result<(DetectSource, ModifierStateStore, Sequencer)> {
diff --git a/espanso/src/cli/worker/engine/source/ui.rs b/espanso/src/cli/worker/engine/source/ui.rs
new file mode 100644
index 0000000..1ebd3a1
--- /dev/null
+++ b/espanso/src/cli/worker/engine/source/ui.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 crossbeam::channel::{Receiver, Select, SelectedOperation};
+use espanso_ui::event::UIEvent;
+
+use crate::engine::{
+ event::{input::ContextMenuClickedEvent, Event, EventType},
+ funnel,
+};
+
+use super::sequencer::Sequencer;
+
+pub struct UISource<'a> {
+ pub ui_receiver: Receiver,
+ pub sequencer: &'a Sequencer,
+}
+
+impl<'a> UISource<'a> {
+ pub fn new(ui_receiver: Receiver, sequencer: &'a Sequencer) -> Self {
+ UISource {
+ ui_receiver,
+ sequencer,
+ }
+ }
+}
+
+impl<'a> funnel::Source<'a> for UISource<'a> {
+ fn register(&'a self, select: &mut Select<'a>) -> usize {
+ select.recv(&self.ui_receiver)
+ }
+
+ fn receive(&self, op: SelectedOperation) -> Event {
+ let ui_event = op
+ .recv(&self.ui_receiver)
+ .expect("unable to select data from UISource receiver");
+
+ Event {
+ source_id: self.sequencer.next_id(),
+ etype: match ui_event {
+ UIEvent::TrayIconClick => EventType::TrayIconClicked,
+ UIEvent::ContextMenuClick(context_item_id) => {
+ EventType::ContextMenuClicked(ContextMenuClickedEvent { context_item_id })
+ }
+ },
+ }
+ }
+}
diff --git a/espanso/src/cli/worker/mod.rs b/espanso/src/cli/worker/mod.rs
index 7d445d2..ff1f9ee 100644
--- a/espanso/src/cli/worker/mod.rs
+++ b/espanso/src/cli/worker/mod.rs
@@ -80,6 +80,7 @@ fn worker_main(args: CliModuleArgs) -> i32 {
.expect("unable to initialize UI module");
let (engine_exit_notify, engine_exit_receiver) = unbounded();
+ let (engine_ui_event_sender, engine_ui_event_receiver) = unbounded();
// Initialize the engine on another thread and start it
engine::initialize_and_spawn(
@@ -89,6 +90,7 @@ fn worker_main(args: CliModuleArgs) -> i32 {
icon_paths,
remote,
engine_exit_receiver,
+ engine_ui_event_receiver,
)
.expect("unable to initialize engine");
@@ -97,8 +99,10 @@ fn worker_main(args: CliModuleArgs) -> i32 {
.expect("unable to initialize IPC server");
eventloop.run(Box::new(move |event| {
- // TODO: handle event
- }));
+ if let Err(error) = engine_ui_event_sender.send(event) {
+ error!("unable to send UIEvent to engine: {}", error);
+ }
+ })).expect("unable to run main eventloop");
info!("exiting worker process...");
diff --git a/espanso/src/engine/dispatch/default.rs b/espanso/src/engine/dispatch/default.rs
index 53051e7..af91f5a 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, ImageInjector};
+use super::{ContextMenuHandler, Event, ImageInjector};
use super::{ModeProvider, Dispatcher, Executor, KeyInjector, TextInjector, HtmlInjector};
pub struct DefaultDispatcher<'a> {
@@ -32,6 +32,7 @@ impl<'a> DefaultDispatcher<'a> {
key_injector: &'a dyn KeyInjector,
html_injector: &'a dyn HtmlInjector,
image_injector: &'a dyn ImageInjector,
+ context_menu_handler: &'a dyn ContextMenuHandler,
) -> Self {
Self {
executors: vec![
@@ -48,6 +49,9 @@ impl<'a> DefaultDispatcher<'a> {
)),
Box::new(super::executor::image_inject::ImageInjectExecutor::new(
image_injector,
+ )),
+ Box::new(super::executor::context_menu::ContextMenuExecutor::new(
+ context_menu_handler,
))
],
}
diff --git a/espanso/src/engine/dispatch/executor/context_menu.rs b/espanso/src/engine/dispatch/executor/context_menu.rs
new file mode 100644
index 0000000..9345c29
--- /dev/null
+++ b/espanso/src/engine/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 super::super::{Event, Executor};
+use crate::engine::event::{ui::MenuItem, EventType};
+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/src/engine/dispatch/executor/mod.rs b/espanso/src/engine/dispatch/executor/mod.rs
index b6a0d63..158d6d8 100644
--- a/espanso/src/engine/dispatch/executor/mod.rs
+++ b/espanso/src/engine/dispatch/executor/mod.rs
@@ -17,7 +17,8 @@
* along with espanso. If not, see .
*/
-pub mod text_inject;
-pub mod key_inject;
+pub mod context_menu;
+pub mod image_inject;
pub mod html_inject;
-pub mod image_inject;
\ No newline at end of file
+pub mod key_inject;
+pub mod text_inject;
\ No newline at end of file
diff --git a/espanso/src/engine/dispatch/mod.rs b/espanso/src/engine/dispatch/mod.rs
index 46ad9b8..fc710c0 100644
--- a/espanso/src/engine/dispatch/mod.rs
+++ b/espanso/src/engine/dispatch/mod.rs
@@ -36,6 +36,7 @@ pub trait Dispatcher {
pub use executor::html_inject::HtmlInjector;
pub use executor::text_inject::{Mode, ModeProvider, TextInjector};
pub use executor::image_inject::{ImageInjector};
+pub use executor::context_menu::{ContextMenuHandler};
// TODO: move into module
pub trait KeyInjector {
@@ -49,6 +50,7 @@ pub fn default<'a>(
key_injector: &'a dyn KeyInjector,
html_injector: &'a dyn HtmlInjector,
image_injector: &'a dyn ImageInjector,
+ context_menu_handler: &'a dyn ContextMenuHandler,
) -> impl Dispatcher + 'a {
default::DefaultDispatcher::new(
event_injector,
@@ -57,5 +59,6 @@ pub fn default<'a>(
key_injector,
html_injector,
image_injector,
+ context_menu_handler,
)
}
diff --git a/espanso/src/engine/event/input.rs b/espanso/src/engine/event/input.rs
index 190c126..b667837 100644
--- a/espanso/src/engine/event/input.rs
+++ b/espanso/src/engine/event/input.rs
@@ -110,4 +110,9 @@ pub enum Key {
// 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,
}
\ No newline at end of file
diff --git a/espanso/src/engine/event/mod.rs b/espanso/src/engine/event/mod.rs
index b3e3c95..aadbd15 100644
--- a/espanso/src/engine/event/mod.rs
+++ b/espanso/src/engine/event/mod.rs
@@ -20,6 +20,7 @@
pub mod input;
pub mod effect;
pub mod internal;
+pub mod ui;
pub type SourceId = u32;
@@ -54,6 +55,8 @@ pub enum EventType {
Keyboard(input::KeyboardEvent),
Mouse(input::MouseEvent),
// TODO: hotkeys
+ TrayIconClicked,
+ ContextMenuClicked(input::ContextMenuClickedEvent),
// Internal
MatchesDetected(internal::MatchesDetectedEvent),
@@ -76,4 +79,7 @@ pub enum EventType {
MarkdownInject(effect::MarkdownInjectRequest),
HtmlInject(effect::HtmlInjectRequest),
ImageInject(effect::ImageInjectRequest),
+
+ // UI
+ ShowContextMenu(ui::ShowContextMenuEvent),
}
\ No newline at end of file
diff --git a/espanso/src/engine/event/ui.rs b/espanso/src/engine/event/ui.rs
new file mode 100644
index 0000000..1412264
--- /dev/null
+++ b/espanso/src/engine/event/ui.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 .
+ */
+
+#[derive(Debug, Clone, PartialEq)]
+pub struct ShowContextMenuEvent {
+ pub items: Vec