feat(core): add SecureInput context menu entries
This commit is contained in:
parent
79be8d2988
commit
04bc9b904d
|
@ -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;
|
||||||
|
|
|
@ -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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
||||||
|
|
|
@ -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,
|
||||||
|
)),
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
58
espanso/src/engine/dispatch/executor/secure_input.rs
Normal file
58
espanso/src/engine/dispatch/executor/secure_input.rs
Normal 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
|
||||||
|
}
|
||||||
|
}
|
|
@ -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,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)]
|
||||||
|
|
|
@ -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,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user