feat(core): add SecureInput context menu entries

This commit is contained in:
Federico Terzi 2021-08-01 19:06:18 +02:00
parent 79be8d2988
commit 04bc9b904d
9 changed files with 199 additions and 34 deletions

View File

@ -22,6 +22,7 @@ pub mod context_menu;
pub mod event_injector; pub mod event_injector;
pub mod icon; pub mod icon;
pub mod key_injector; pub mod key_injector;
pub mod secure_input;
pub trait InjectParamsProvider { pub trait InjectParamsProvider {
fn get(&self) -> InjectParams; fn get(&self) -> InjectParams;

View 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 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");
}
}
}

View File

@ -28,13 +28,7 @@ use espanso_path::Paths;
use espanso_ui::{event::UIEvent, UIRemote}; use espanso_ui::{event::UIEvent, UIRemote};
use log::{debug, error, info, warn}; use log::{debug, error, info, warn};
use crate::{cli::worker::{context::Context, engine::{ 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::{
dispatch::executor::{
clipboard_injector::ClipboardInjectorAdapter, context_menu::ContextMenuHandlerAdapter,
event_injector::EventInjectorAdapter, icon::IconHandlerAdapter,
key_injector::KeyInjectorAdapter,
},
process::middleware::{
image_resolve::PathProviderAdapter, image_resolve::PathProviderAdapter,
match_select::MatchSelectorAdapter, match_select::MatchSelectorAdapter,
matcher::{ matcher::{
@ -47,8 +41,7 @@ use crate::{cli::worker::{context::Context, engine::{
extension::{clipboard::ClipboardAdapter, form::FormProviderAdapter}, extension::{clipboard::ClipboardAdapter, form::FormProviderAdapter},
RendererAdapter, RendererAdapter,
}, },
}, }}, match_cache::{CombinedMatchCache, MatchCache}}, engine::event::ExitMode, preferences::Preferences};
}, match_cache::{CombinedMatchCache, MatchCache}}, engine::event::ExitMode, preferences::Preferences};
use super::secure_input::SecureInputEvent; use super::secure_input::SecureInputEvent;
@ -196,6 +189,7 @@ pub fn initialize_and_spawn(
let key_injector = KeyInjectorAdapter::new(&*injector, &config_manager); let key_injector = KeyInjectorAdapter::new(&*injector, &config_manager);
let context_menu_adapter = ContextMenuHandlerAdapter::new(&*ui_remote); let context_menu_adapter = ContextMenuHandlerAdapter::new(&*ui_remote);
let icon_adapter = IconHandlerAdapter::new(&*ui_remote); let icon_adapter = IconHandlerAdapter::new(&*ui_remote);
let secure_input_adapter = SecureInputManagerAdapter::new();
let dispatcher = crate::engine::dispatch::default( let dispatcher = crate::engine::dispatch::default(
&event_injector, &event_injector,
&clipboard_injector, &clipboard_injector,
@ -205,6 +199,7 @@ pub fn initialize_and_spawn(
&clipboard_injector, &clipboard_injector,
&context_menu_adapter, &context_menu_adapter,
&icon_adapter, &icon_adapter,
&secure_input_adapter,
); );
// Disable previously granted linux capabilities if not needed anymore // Disable previously granted linux capabilities if not needed anymore

View File

@ -17,7 +17,7 @@
* along with espanso. If not, see <https://www.gnu.org/licenses/>. * along with espanso. If not, see <https://www.gnu.org/licenses/>.
*/ */
use super::{ContextMenuHandler, Event, IconHandler, ImageInjector}; use super::{ContextMenuHandler, Event, IconHandler, ImageInjector, SecureInputManager};
use super::{ModeProvider, Dispatcher, Executor, KeyInjector, TextInjector, HtmlInjector}; use super::{ModeProvider, Dispatcher, Executor, KeyInjector, TextInjector, HtmlInjector};
pub struct DefaultDispatcher<'a> { pub struct DefaultDispatcher<'a> {
@ -34,6 +34,7 @@ impl<'a> DefaultDispatcher<'a> {
image_injector: &'a dyn ImageInjector, image_injector: &'a dyn ImageInjector,
context_menu_handler: &'a dyn ContextMenuHandler, context_menu_handler: &'a dyn ContextMenuHandler,
icon_handler: &'a dyn IconHandler, icon_handler: &'a dyn IconHandler,
secure_input_manager: &'a dyn SecureInputManager,
) -> Self { ) -> Self {
Self { Self {
executors: vec![ executors: vec![
@ -56,7 +57,10 @@ impl<'a> DefaultDispatcher<'a> {
)), )),
Box::new(super::executor::icon_update::IconUpdateExecutor::new( Box::new(super::executor::icon_update::IconUpdateExecutor::new(
icon_handler, icon_handler,
)) )),
Box::new(super::executor::secure_input::SecureInputExecutor::new(
secure_input_manager,
)),
], ],
} }
} }

View File

@ -22,4 +22,5 @@ pub mod icon_update;
pub mod image_inject; pub mod image_inject;
pub mod html_inject; pub mod html_inject;
pub mod key_inject; pub mod key_inject;
pub mod secure_input;
pub mod text_inject; pub mod text_inject;

View File

@ -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 <https://www.gnu.org/licenses/>.
*/
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
}
}

View File

@ -38,6 +38,7 @@ pub use executor::text_inject::{Mode, ModeProvider, TextInjector};
pub use executor::image_inject::{ImageInjector}; pub use executor::image_inject::{ImageInjector};
pub use executor::context_menu::{ContextMenuHandler}; pub use executor::context_menu::{ContextMenuHandler};
pub use executor::icon_update::IconHandler; pub use executor::icon_update::IconHandler;
pub use executor::secure_input::SecureInputManager;
// TODO: move into module // TODO: move into module
pub trait KeyInjector { pub trait KeyInjector {
@ -53,6 +54,7 @@ pub fn default<'a>(
image_injector: &'a dyn ImageInjector, image_injector: &'a dyn ImageInjector,
context_menu_handler: &'a dyn ContextMenuHandler, context_menu_handler: &'a dyn ContextMenuHandler,
icon_handler: &'a dyn IconHandler, icon_handler: &'a dyn IconHandler,
secure_input_manager: &'a dyn SecureInputManager,
) -> impl Dispatcher + 'a { ) -> impl Dispatcher + 'a {
default::DefaultDispatcher::new( default::DefaultDispatcher::new(
event_injector, event_injector,
@ -63,5 +65,6 @@ pub fn default<'a>(
image_injector, image_injector,
context_menu_handler, context_menu_handler,
icon_handler, icon_handler,
secure_input_manager,
) )
} }

View File

@ -90,6 +90,10 @@ pub enum EventType {
// UI // UI
ShowContextMenu(ui::ShowContextMenuEvent), ShowContextMenu(ui::ShowContextMenuEvent),
IconStatusChange(ui::IconStatusChangeEvent), IconStatusChange(ui::IconStatusChangeEvent),
DisplaySecureInputTroubleshoot,
// Other
LaunchSecureInputAutoFix,
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]

View File

@ -29,15 +29,19 @@ const CONTEXT_ITEM_EXIT: u32 = 0;
const CONTEXT_ITEM_RELOAD: u32 = 1; const CONTEXT_ITEM_RELOAD: u32 = 1;
const CONTEXT_ITEM_ENABLE: u32 = 2; const CONTEXT_ITEM_ENABLE: u32 = 2;
const CONTEXT_ITEM_DISABLE: u32 = 3; 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 { pub struct ContextMenuMiddleware {
is_enabled: RefCell<bool>, is_enabled: RefCell<bool>,
is_secure_input_enabled: RefCell<bool>,
} }
impl ContextMenuMiddleware { impl ContextMenuMiddleware {
pub fn new() -> Self { pub fn new() -> Self {
Self { Self {
is_enabled: RefCell::new(true), is_enabled: RefCell::new(true),
is_secure_input_enabled: RefCell::new(false),
} }
} }
} }
@ -49,20 +53,13 @@ impl Middleware for ContextMenuMiddleware {
fn next(&self, event: Event, dispatch: &mut dyn FnMut(Event)) -> Event { fn next(&self, event: Event, dispatch: &mut dyn FnMut(Event)) -> Event {
let mut is_enabled = self.is_enabled.borrow_mut(); 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 { match &event.etype {
EventType::TrayIconClicked => { EventType::TrayIconClicked => {
// TODO: fetch top matches for the active config to be added // 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 let mut items = vec![
// 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(if *is_enabled { MenuItem::Simple(if *is_enabled {
SimpleMenuItem { SimpleMenuItem {
id: CONTEXT_ITEM_DISABLE, id: CONTEXT_ITEM_DISABLE,
@ -84,7 +81,29 @@ impl Middleware for ContextMenuMiddleware {
id: CONTEXT_ITEM_EXIT, id: CONTEXT_ITEM_EXIT,
label: "Exit espanso".to_string(), 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
// a mapping structure match_id <-> context-menu-id
return Event::caused_by(
event.source_id,
EventType::ShowContextMenu(ShowContextMenuEvent {
// TODO: add actual entries
items,
}), }),
); );
} }
@ -106,6 +125,14 @@ impl Middleware for ContextMenuMiddleware {
dispatch(Event::caused_by(event.source_id, EventType::DisableRequest)); dispatch(Event::caused_by(event.source_id, EventType::DisableRequest));
Event::caused_by(event.source_id, EventType::NOOP) 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 => { custom => {
// TODO: handle dynamic items // TODO: handle dynamic items
todo!() todo!()
@ -120,6 +147,14 @@ impl Middleware for ContextMenuMiddleware {
*is_enabled = true; *is_enabled = true;
event event
} }
EventType::SecureInputEnabled(_) => {
*is_secure_input_enabled = true;
event
}
EventType::SecureInputDisabled => {
*is_secure_input_enabled = false;
event
}
_ => event, _ => event,
} }
} }