feat(core): first half of context menu handling
This commit is contained in:
parent
716c50cee1
commit
cba94607fa
70
espanso/src/cli/worker/engine/executor/context_menu.rs
Normal file
70
espanso/src/cli/worker/engine/executor/context_menu.rs
Normal file
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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<espanso_ui::menu::MenuItem> =
|
||||
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,
|
||||
}
|
||||
}
|
|
@ -18,5 +18,6 @@
|
|||
*/
|
||||
|
||||
pub mod clipboard_injector;
|
||||
pub mod context_menu;
|
||||
pub mod event_injector;
|
||||
pub mod key_injector;
|
|
@ -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<dyn UIRemote>,
|
||||
exit_signal: Receiver<()>,
|
||||
ui_event_receiver: Receiver<UIEvent>,
|
||||
) -> Result<JoinHandle<()>> {
|
||||
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);
|
||||
|
|
|
@ -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)> {
|
||||
|
|
64
espanso/src/cli/worker/engine/source/ui.rs
Normal file
64
espanso/src/cli/worker/engine/source/ui.rs
Normal file
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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<UIEvent>,
|
||||
pub sequencer: &'a Sequencer,
|
||||
}
|
||||
|
||||
impl<'a> UISource<'a> {
|
||||
pub fn new(ui_receiver: Receiver<UIEvent>, 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 })
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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...");
|
||||
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
* along with espanso. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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,
|
||||
))
|
||||
],
|
||||
}
|
||||
|
|
53
espanso/src/engine/dispatch/executor/context_menu.rs
Normal file
53
espanso/src/engine/dispatch/executor/context_menu.rs
Normal file
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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
|
|
@ -17,7 +17,8 @@
|
|||
* along with espanso. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
pub mod text_inject;
|
||||
pub mod key_inject;
|
||||
pub mod context_menu;
|
||||
pub mod image_inject;
|
||||
pub mod html_inject;
|
||||
pub mod image_inject;
|
||||
pub mod key_inject;
|
||||
pub mod text_inject;
|
|
@ -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,
|
||||
)
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
}
|
|
@ -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),
|
||||
}
|
42
espanso/src/engine/event/ui.rs
Normal file
42
espanso/src/engine/event/ui.rs
Normal file
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct ShowContextMenuEvent {
|
||||
pub items: Vec<MenuItem>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum MenuItem {
|
||||
Simple(SimpleMenuItem),
|
||||
Sub(SubMenuItem),
|
||||
Separator,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct SimpleMenuItem {
|
||||
pub id: u32,
|
||||
pub label: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct SubMenuItem {
|
||||
pub label: String,
|
||||
pub items: Vec<MenuItem>,
|
||||
}
|
|
@ -25,7 +25,7 @@ use super::{MatchFilter, MatchInfoProvider, MatchSelector, Matcher, Middleware,
|
|||
delay_modifiers::{DelayForModifierReleaseMiddleware, ModifierStatusProvider}, markdown::MarkdownMiddleware,
|
||||
past_discard::PastEventsDiscardMiddleware,
|
||||
}};
|
||||
use crate::engine::{event::{Event, EventType}, process::middleware::{exit::ExitMiddleware, image_resolve::ImageResolverMiddleware}};
|
||||
use crate::engine::{event::{Event, EventType}, process::middleware::{context_menu::ContextMenuMiddleware, exit::ExitMiddleware, image_resolve::ImageResolverMiddleware}};
|
||||
use std::collections::VecDeque;
|
||||
|
||||
pub struct DefaultProcessor<'a> {
|
||||
|
@ -50,6 +50,7 @@ impl<'a> DefaultProcessor<'a> {
|
|||
middleware: vec![
|
||||
Box::new(PastEventsDiscardMiddleware::new()),
|
||||
Box::new(MatcherMiddleware::new(matchers)),
|
||||
Box::new(ContextMenuMiddleware::new()),
|
||||
Box::new(MatchSelectMiddleware::new(match_filter, match_selector)),
|
||||
Box::new(CauseCompensateMiddleware::new()),
|
||||
Box::new(MultiplexMiddleware::new(multiplexer)),
|
||||
|
|
65
espanso/src/engine/process/middleware/context_menu.rs
Normal file
65
espanso/src/engine/process/middleware/context_menu.rs
Normal file
|
@ -0,0 +1,65 @@
|
|||
/*
|
||||
* This file is part of espanso.
|
||||
*
|
||||
* Copyright id: (), label: () id: (), label: () id: (), label: ()(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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
use super::super::Middleware;
|
||||
use crate::engine::{event::{Event, EventType, ui::{MenuItem, ShowContextMenuEvent, SimpleMenuItem}}};
|
||||
|
||||
pub struct ContextMenuMiddleware {
|
||||
}
|
||||
|
||||
impl ContextMenuMiddleware {
|
||||
pub fn new() -> Self {
|
||||
Self {}
|
||||
}
|
||||
}
|
||||
|
||||
impl Middleware for ContextMenuMiddleware {
|
||||
fn name(&self) -> &'static str {
|
||||
"context_menu"
|
||||
}
|
||||
|
||||
fn next(&self, event: Event, _: &mut dyn FnMut(Event)) -> Event {
|
||||
if let EventType::TrayIconClicked = event.etype {
|
||||
// TODO: fetch top matches for the active config to be added
|
||||
|
||||
// TODO: my idea is to use a set of reserved u32 ids for built-in
|
||||
// actions such as Exit, Open Editor etc
|
||||
// then we need some u32 for the matches, so we need to create
|
||||
// a mapping structure match_id <-> context-menu-id
|
||||
return Event::caused_by(
|
||||
event.source_id,
|
||||
EventType::ShowContextMenu(ShowContextMenuEvent {
|
||||
// TODO: add actual entries
|
||||
items: vec![
|
||||
MenuItem::Simple(SimpleMenuItem {
|
||||
id: 0,
|
||||
label: "Exit espanso".to_string(),
|
||||
})
|
||||
]
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
// TODO: handle context menu clicks
|
||||
|
||||
event
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: test
|
|
@ -19,6 +19,7 @@
|
|||
|
||||
pub mod action;
|
||||
pub mod cause;
|
||||
pub mod context_menu;
|
||||
pub mod cursor_hint;
|
||||
pub mod delay_modifiers;
|
||||
pub mod exit;
|
||||
|
|
Loading…
Reference in New Issue
Block a user