diff --git a/espanso/src/cli/worker/engine/dispatch/executor/mod.rs b/espanso/src/cli/worker/engine/dispatch/executor/mod.rs index 1a63b8b..169b41a 100644 --- a/espanso/src/cli/worker/engine/dispatch/executor/mod.rs +++ b/espanso/src/cli/worker/engine/dispatch/executor/mod.rs @@ -22,6 +22,7 @@ pub mod context_menu; pub mod event_injector; pub mod icon; pub mod key_injector; +pub mod secure_input; pub trait InjectParamsProvider { fn get(&self) -> InjectParams; diff --git a/espanso/src/cli/worker/engine/dispatch/executor/secure_input.rs b/espanso/src/cli/worker/engine/dispatch/executor/secure_input.rs new file mode 100644 index 0000000..8d393a9 --- /dev/null +++ b/espanso/src/cli/worker/engine/dispatch/executor/secure_input.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 std::process::Stdio; + +use anyhow::{bail, Context}; +use log::{error, info}; + +use crate::engine::dispatch::SecureInputManager; + +pub struct SecureInputManagerAdapter {} + +impl SecureInputManagerAdapter { + pub fn new() -> Self { + Self {} + } +} + +impl SecureInputManager for SecureInputManagerAdapter { + fn display_secure_input_troubleshoot(&self) -> anyhow::Result<()> { + // TODO: replace with actual URL + // TODO: in the future, this might be a self-contained WebView window + opener::open_browser("https://espanso.org/docs")?; + Ok(()) + } + + fn launch_secure_input_autofix(&self) -> anyhow::Result<()> { + let espanso_path = std::env::current_exe()?; + let child = std::process::Command::new(espanso_path) + .args(&["workaround", "secure-input"]) + .stdout(Stdio::piped()) + .spawn() + .context("unable to spawn workaround process")?; + let output = child.wait_with_output()?; + let output_str = String::from_utf8_lossy(&output.stdout); + let error_str = String::from_utf8_lossy(&output.stderr); + + if output.status.success() { + info!( + "Secure input workaround executed successfully: {}", + output_str + ); + Ok(()) + } else { + error!("Secure input autofix reported error: {}", error_str); + bail!("non-successful autofix status code"); + } + } +} diff --git a/espanso/src/cli/worker/engine/mod.rs b/espanso/src/cli/worker/engine/mod.rs index c919457..f7dc7a7 100644 --- a/espanso/src/cli/worker/engine/mod.rs +++ b/espanso/src/cli/worker/engine/mod.rs @@ -28,13 +28,7 @@ use espanso_path::Paths; use espanso_ui::{event::UIEvent, UIRemote}; use log::{debug, error, info, warn}; -use crate::{cli::worker::{context::Context, engine::{ - dispatch::executor::{ - clipboard_injector::ClipboardInjectorAdapter, context_menu::ContextMenuHandlerAdapter, - event_injector::EventInjectorAdapter, icon::IconHandlerAdapter, - key_injector::KeyInjectorAdapter, - }, - process::middleware::{ +use crate::{cli::worker::{context::Context, engine::{dispatch::executor::{clipboard_injector::ClipboardInjectorAdapter, context_menu::ContextMenuHandlerAdapter, event_injector::EventInjectorAdapter, icon::IconHandlerAdapter, key_injector::KeyInjectorAdapter, secure_input::SecureInputManagerAdapter}, process::middleware::{ image_resolve::PathProviderAdapter, match_select::MatchSelectorAdapter, matcher::{ @@ -47,8 +41,7 @@ use crate::{cli::worker::{context::Context, engine::{ extension::{clipboard::ClipboardAdapter, form::FormProviderAdapter}, RendererAdapter, }, - }, - }, match_cache::{CombinedMatchCache, MatchCache}}, engine::event::ExitMode, preferences::Preferences}; + }}, match_cache::{CombinedMatchCache, MatchCache}}, engine::event::ExitMode, preferences::Preferences}; use super::secure_input::SecureInputEvent; @@ -196,6 +189,7 @@ pub fn initialize_and_spawn( let key_injector = KeyInjectorAdapter::new(&*injector, &config_manager); let context_menu_adapter = ContextMenuHandlerAdapter::new(&*ui_remote); let icon_adapter = IconHandlerAdapter::new(&*ui_remote); + let secure_input_adapter = SecureInputManagerAdapter::new(); let dispatcher = crate::engine::dispatch::default( &event_injector, &clipboard_injector, @@ -205,6 +199,7 @@ pub fn initialize_and_spawn( &clipboard_injector, &context_menu_adapter, &icon_adapter, + &secure_input_adapter, ); // Disable previously granted linux capabilities if not needed anymore diff --git a/espanso/src/engine/dispatch/default.rs b/espanso/src/engine/dispatch/default.rs index 4615643..86cc83c 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::{ContextMenuHandler, Event, IconHandler, ImageInjector}; +use super::{ContextMenuHandler, Event, IconHandler, ImageInjector, SecureInputManager}; use super::{ModeProvider, Dispatcher, Executor, KeyInjector, TextInjector, HtmlInjector}; pub struct DefaultDispatcher<'a> { @@ -34,6 +34,7 @@ impl<'a> DefaultDispatcher<'a> { image_injector: &'a dyn ImageInjector, context_menu_handler: &'a dyn ContextMenuHandler, icon_handler: &'a dyn IconHandler, + secure_input_manager: &'a dyn SecureInputManager, ) -> Self { Self { executors: vec![ @@ -56,7 +57,10 @@ impl<'a> DefaultDispatcher<'a> { )), Box::new(super::executor::icon_update::IconUpdateExecutor::new( icon_handler, - )) + )), + Box::new(super::executor::secure_input::SecureInputExecutor::new( + secure_input_manager, + )), ], } } diff --git a/espanso/src/engine/dispatch/executor/mod.rs b/espanso/src/engine/dispatch/executor/mod.rs index 54d7865..586244c 100644 --- a/espanso/src/engine/dispatch/executor/mod.rs +++ b/espanso/src/engine/dispatch/executor/mod.rs @@ -22,4 +22,5 @@ pub mod icon_update; pub mod image_inject; pub mod html_inject; pub mod key_inject; +pub mod secure_input; pub mod text_inject; \ No newline at end of file diff --git a/espanso/src/engine/dispatch/executor/secure_input.rs b/espanso/src/engine/dispatch/executor/secure_input.rs new file mode 100644 index 0000000..6cf56b0 --- /dev/null +++ b/espanso/src/engine/dispatch/executor/secure_input.rs @@ -0,0 +1,58 @@ +/* + * 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::event::EventType; + +use super::super::{Event, Executor}; +use anyhow::Result; +use log::error; + +pub trait SecureInputManager { + fn display_secure_input_troubleshoot(&self) -> Result<()>; + fn launch_secure_input_autofix(&self) -> Result<()>; +} + +pub struct SecureInputExecutor<'a> { + manager: &'a dyn SecureInputManager, +} + +impl<'a> SecureInputExecutor<'a> { + pub fn new(manager: &'a dyn SecureInputManager) -> Self { + Self { manager } + } +} + +impl<'a> Executor for SecureInputExecutor<'a> { + fn execute(&self, event: &Event) -> bool { + if let EventType::DisplaySecureInputTroubleshoot = &event.etype { + if let Err(error) = self.manager.display_secure_input_troubleshoot() { + error!("unable to display secure input troubleshoot: {}", error); + } + return true; + } else if let EventType::LaunchSecureInputAutoFix = &event.etype { + if let Err(error) = self.manager.launch_secure_input_autofix() { + error!("unable to launch secure input autofix: {}", error); + } + return true; + } + + + false + } +} \ No newline at end of file diff --git a/espanso/src/engine/dispatch/mod.rs b/espanso/src/engine/dispatch/mod.rs index edf606e..0072d75 100644 --- a/espanso/src/engine/dispatch/mod.rs +++ b/espanso/src/engine/dispatch/mod.rs @@ -38,6 +38,7 @@ pub use executor::text_inject::{Mode, ModeProvider, TextInjector}; pub use executor::image_inject::{ImageInjector}; pub use executor::context_menu::{ContextMenuHandler}; pub use executor::icon_update::IconHandler; +pub use executor::secure_input::SecureInputManager; // TODO: move into module pub trait KeyInjector { @@ -53,6 +54,7 @@ pub fn default<'a>( image_injector: &'a dyn ImageInjector, context_menu_handler: &'a dyn ContextMenuHandler, icon_handler: &'a dyn IconHandler, + secure_input_manager: &'a dyn SecureInputManager, ) -> impl Dispatcher + 'a { default::DefaultDispatcher::new( event_injector, @@ -63,5 +65,6 @@ pub fn default<'a>( image_injector, context_menu_handler, icon_handler, + secure_input_manager, ) } diff --git a/espanso/src/engine/event/mod.rs b/espanso/src/engine/event/mod.rs index 029676c..36827cf 100644 --- a/espanso/src/engine/event/mod.rs +++ b/espanso/src/engine/event/mod.rs @@ -90,6 +90,10 @@ pub enum EventType { // UI ShowContextMenu(ui::ShowContextMenuEvent), IconStatusChange(ui::IconStatusChangeEvent), + DisplaySecureInputTroubleshoot, + + // Other + LaunchSecureInputAutoFix, } #[derive(Debug, Clone)] diff --git a/espanso/src/engine/process/middleware/context_menu.rs b/espanso/src/engine/process/middleware/context_menu.rs index 95efb32..654b4e3 100644 --- a/espanso/src/engine/process/middleware/context_menu.rs +++ b/espanso/src/engine/process/middleware/context_menu.rs @@ -29,15 +29,19 @@ const CONTEXT_ITEM_EXIT: u32 = 0; const CONTEXT_ITEM_RELOAD: u32 = 1; const CONTEXT_ITEM_ENABLE: u32 = 2; const CONTEXT_ITEM_DISABLE: u32 = 3; +const CONTEXT_ITEM_SECURE_INPUT_EXPLAIN: u32 = 4; +const CONTEXT_ITEM_SECURE_INPUT_TRIGGER_WORKAROUND: u32 = 5; pub struct ContextMenuMiddleware { is_enabled: RefCell, + is_secure_input_enabled: RefCell, } impl ContextMenuMiddleware { pub fn new() -> Self { Self { is_enabled: RefCell::new(true), + is_secure_input_enabled: RefCell::new(false), } } } @@ -49,11 +53,48 @@ impl Middleware for ContextMenuMiddleware { fn next(&self, event: Event, dispatch: &mut dyn FnMut(Event)) -> Event { let mut is_enabled = self.is_enabled.borrow_mut(); + let mut is_secure_input_enabled = self.is_secure_input_enabled.borrow_mut(); match &event.etype { EventType::TrayIconClicked => { // TODO: fetch top matches for the active config to be added + let mut items = vec![ + MenuItem::Simple(if *is_enabled { + SimpleMenuItem { + id: CONTEXT_ITEM_DISABLE, + label: "Disable".to_string(), + } + } else { + SimpleMenuItem { + id: CONTEXT_ITEM_ENABLE, + label: "Enable".to_string(), + } + }), + MenuItem::Separator, + MenuItem::Simple(SimpleMenuItem { + id: CONTEXT_ITEM_RELOAD, + label: "Reload config".to_string(), + }), + MenuItem::Separator, + MenuItem::Simple(SimpleMenuItem { + id: CONTEXT_ITEM_EXIT, + label: "Exit espanso".to_string(), + }), + ]; + + if *is_secure_input_enabled { + items.insert(0, MenuItem::Simple(SimpleMenuItem { + id: CONTEXT_ITEM_SECURE_INPUT_EXPLAIN, + label: "Why is espanso not working?".to_string(), + })); + items.insert(1, MenuItem::Simple(SimpleMenuItem { + id: CONTEXT_ITEM_SECURE_INPUT_TRIGGER_WORKAROUND, + label: "Launch SecureInput auto-fix".to_string(), + })); + items.insert(2, MenuItem::Separator); + } + // 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 @@ -62,29 +103,7 @@ impl Middleware for ContextMenuMiddleware { event.source_id, EventType::ShowContextMenu(ShowContextMenuEvent { // TODO: add actual entries - items: vec![ - MenuItem::Simple(if *is_enabled { - SimpleMenuItem { - id: CONTEXT_ITEM_DISABLE, - label: "Disable".to_string(), - } - } else { - SimpleMenuItem { - id: CONTEXT_ITEM_ENABLE, - label: "Enable".to_string(), - } - }), - MenuItem::Separator, - MenuItem::Simple(SimpleMenuItem { - id: CONTEXT_ITEM_RELOAD, - label: "Reload config".to_string(), - }), - MenuItem::Separator, - MenuItem::Simple(SimpleMenuItem { - id: CONTEXT_ITEM_EXIT, - label: "Exit espanso".to_string(), - }), - ], + items, }), ); } @@ -106,6 +125,14 @@ impl Middleware for ContextMenuMiddleware { dispatch(Event::caused_by(event.source_id, EventType::DisableRequest)); Event::caused_by(event.source_id, EventType::NOOP) } + CONTEXT_ITEM_SECURE_INPUT_EXPLAIN => { + dispatch(Event::caused_by(event.source_id, EventType::DisplaySecureInputTroubleshoot)); + Event::caused_by(event.source_id, EventType::NOOP) + } + CONTEXT_ITEM_SECURE_INPUT_TRIGGER_WORKAROUND => { + dispatch(Event::caused_by(event.source_id, EventType::LaunchSecureInputAutoFix)); + Event::caused_by(event.source_id, EventType::NOOP) + } custom => { // TODO: handle dynamic items todo!() @@ -120,6 +147,14 @@ impl Middleware for ContextMenuMiddleware { *is_enabled = true; event } + EventType::SecureInputEnabled(_) => { + *is_secure_input_enabled = true; + event + } + EventType::SecureInputDisabled => { + *is_secure_input_enabled = false; + event + } _ => event, } }