feat(core): implement secure-input watcher
This commit is contained in:
parent
e7df57ef87
commit
09095a1e8d
14
Cargo.lock
generated
14
Cargo.lock
generated
|
@ -427,6 +427,7 @@ dependencies = [
|
|||
"espanso-info",
|
||||
"espanso-inject",
|
||||
"espanso-ipc",
|
||||
"espanso-mac-utils",
|
||||
"espanso-match",
|
||||
"espanso-migrate",
|
||||
"espanso-modulo",
|
||||
|
@ -548,6 +549,19 @@ dependencies = [
|
|||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "espanso-mac-utils"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"cc",
|
||||
"lazy_static",
|
||||
"lazycell",
|
||||
"log",
|
||||
"regex",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "espanso-match"
|
||||
version = "0.1.0"
|
||||
|
|
|
@ -14,4 +14,5 @@ members = [
|
|||
"espanso-path",
|
||||
"espanso-modulo",
|
||||
"espanso-migrate",
|
||||
"espanso-mac-utils",
|
||||
]
|
|
@ -57,4 +57,7 @@ notify = "4.0.17"
|
|||
|
||||
[target.'cfg(windows)'.dependencies]
|
||||
named_pipe = "0.4.1"
|
||||
winapi = { version = "0.3.9", features = ["wincon"] }
|
||||
winapi = { version = "0.3.9", features = ["wincon"] }
|
||||
|
||||
[target.'cfg(target_os="macos")'.dependencies]
|
||||
espanso-mac-utils = { path = "../espanso-mac-utils" }
|
|
@ -29,6 +29,7 @@ use self::{modifier::{Modifier, ModifierStateStore}, sequencer::Sequencer};
|
|||
pub mod detect;
|
||||
pub mod exit;
|
||||
pub mod modifier;
|
||||
pub mod secure_input;
|
||||
pub mod sequencer;
|
||||
pub mod ui;
|
||||
|
||||
|
|
66
espanso/src/cli/worker/engine/funnel/secure_input.rs
Normal file
66
espanso/src/cli/worker/engine/funnel/secure_input.rs
Normal file
|
@ -0,0 +1,66 @@
|
|||
/*
|
||||
* 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 crate::{
|
||||
cli::worker::secure_input::SecureInputEvent,
|
||||
engine::{
|
||||
event::{internal::SecureInputEnabledEvent, Event, EventType},
|
||||
funnel,
|
||||
},
|
||||
};
|
||||
|
||||
use super::sequencer::Sequencer;
|
||||
|
||||
pub struct SecureInputSource<'a> {
|
||||
pub receiver: Receiver<SecureInputEvent>,
|
||||
pub sequencer: &'a Sequencer,
|
||||
}
|
||||
|
||||
impl<'a> SecureInputSource<'a> {
|
||||
pub fn new(receiver: Receiver<SecureInputEvent>, sequencer: &'a Sequencer) -> Self {
|
||||
SecureInputSource {
|
||||
receiver,
|
||||
sequencer,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> funnel::Source<'a> for SecureInputSource<'a> {
|
||||
fn register(&'a self, select: &mut Select<'a>) -> usize {
|
||||
select.recv(&self.receiver)
|
||||
}
|
||||
|
||||
fn receive(&self, op: SelectedOperation) -> Event {
|
||||
let si_event = op
|
||||
.recv(&self.receiver)
|
||||
.expect("unable to select data from SecureInputSource receiver");
|
||||
|
||||
Event {
|
||||
source_id: self.sequencer.next_id(),
|
||||
etype: match si_event {
|
||||
SecureInputEvent::Disabled => EventType::SecureInputDisabled,
|
||||
SecureInputEvent::Enabled { app_name, app_path } => {
|
||||
EventType::SecureInputEnabled(SecureInputEnabledEvent { app_name, app_path })
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
|
@ -54,6 +54,8 @@ use crate::{
|
|||
engine::event::ExitMode,
|
||||
};
|
||||
|
||||
use super::secure_input::SecureInputEvent;
|
||||
|
||||
pub mod dispatch;
|
||||
pub mod funnel;
|
||||
pub mod process;
|
||||
|
@ -65,6 +67,7 @@ pub fn initialize_and_spawn(
|
|||
ui_remote: Box<dyn UIRemote>,
|
||||
exit_signal: Receiver<()>,
|
||||
ui_event_receiver: Receiver<UIEvent>,
|
||||
secure_input_receiver: Receiver<SecureInputEvent>,
|
||||
) -> Result<JoinHandle<ExitMode>> {
|
||||
let handle = std::thread::Builder::new()
|
||||
.name("engine thread".to_string())
|
||||
|
@ -86,8 +89,9 @@ pub fn initialize_and_spawn(
|
|||
super::engine::funnel::init_and_spawn().expect("failed to initialize detector module");
|
||||
let exit_source = super::engine::funnel::exit::ExitSource::new(exit_signal, &sequencer);
|
||||
let ui_source = super::engine::funnel::ui::UISource::new(ui_event_receiver, &sequencer);
|
||||
let secure_input_source = super::engine::funnel::secure_input::SecureInputSource::new(secure_input_receiver, &sequencer);
|
||||
let sources: Vec<&dyn crate::engine::funnel::Source> =
|
||||
vec![&detect_source, &exit_source, &ui_source];
|
||||
vec![&detect_source, &exit_source, &ui_source, &secure_input_source];
|
||||
let funnel = crate::engine::funnel::default(&sources);
|
||||
|
||||
let rolling_matcher = RollingMatcherAdapter::new(&match_converter.get_rolling_matches());
|
||||
|
|
|
@ -38,6 +38,7 @@ mod daemon_monitor;
|
|||
mod engine;
|
||||
mod ipc;
|
||||
mod match_cache;
|
||||
mod secure_input;
|
||||
mod ui;
|
||||
|
||||
pub fn new() -> CliModule {
|
||||
|
@ -100,6 +101,7 @@ fn worker_main(args: CliModuleArgs) -> i32 {
|
|||
|
||||
let (engine_exit_notify, engine_exit_receiver) = unbounded();
|
||||
let (engine_ui_event_sender, engine_ui_event_receiver) = unbounded();
|
||||
let (engine_secure_input_sender, engine_secure_input_receiver) = unbounded();
|
||||
|
||||
// Initialize the engine on another thread and start it
|
||||
let engine_handle = engine::initialize_and_spawn(
|
||||
|
@ -109,6 +111,7 @@ fn worker_main(args: CliModuleArgs) -> i32 {
|
|||
remote,
|
||||
engine_exit_receiver,
|
||||
engine_ui_event_receiver,
|
||||
engine_secure_input_receiver,
|
||||
)
|
||||
.expect("unable to initialize engine");
|
||||
|
||||
|
@ -125,6 +128,9 @@ fn worker_main(args: CliModuleArgs) -> i32 {
|
|||
.expect("unable to initialize daemon monitor thread");
|
||||
}
|
||||
|
||||
secure_input::initialize_and_spawn(engine_secure_input_sender)
|
||||
.expect("unable to initialize secure input watcher");
|
||||
|
||||
eventloop
|
||||
.run(Box::new(move |event| {
|
||||
if let Err(error) = engine_ui_event_sender.send(event) {
|
||||
|
|
107
espanso/src/cli/worker/secure_input.rs
Normal file
107
espanso/src/cli/worker/secure_input.rs
Normal file
|
@ -0,0 +1,107 @@
|
|||
/*
|
||||
* 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::time::Duration;
|
||||
|
||||
use anyhow::Result;
|
||||
use crossbeam::channel::Sender;
|
||||
use log::{error, info};
|
||||
|
||||
pub enum SecureInputEvent {
|
||||
Disabled,
|
||||
Enabled { app_name: String, app_path: String },
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
pub fn initialize_and_spawn(_secure_input_send: Sender<SecureInputEvent>) -> Result<()> {
|
||||
// NOOP on Windows and Linux
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
pub fn initialize_and_spawn(secure_input_sender: Sender<SecureInputEvent>) -> Result<()> {
|
||||
std::thread::Builder::new()
|
||||
.name("secure-input-monitor".to_string())
|
||||
.spawn(move || {
|
||||
// TODO: pass interval from config parameter
|
||||
secure_input_main(secure_input_sender, std::time::Duration::from_secs(5));
|
||||
})?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
fn secure_input_main(secure_input_sender: Sender<SecureInputEvent>, watch_interval: Duration) {
|
||||
info!("monitoring the status of secure input");
|
||||
|
||||
let mut last_secure_input_pid: Option<i64> = None;
|
||||
loop {
|
||||
let pid = espanso_mac_utils::get_secure_input_pid();
|
||||
|
||||
if let Some(pid) = pid {
|
||||
// Some application is currently on SecureInput
|
||||
let should_notify = if let Some(old_pid) = last_secure_input_pid {
|
||||
// We already detected a SecureInput app
|
||||
if old_pid != pid {
|
||||
// The old app is different from the current one, we should take action
|
||||
true
|
||||
} else {
|
||||
// We already notified this application before
|
||||
false
|
||||
}
|
||||
} else {
|
||||
// First time we see this SecureInput app, we should take action
|
||||
true
|
||||
};
|
||||
|
||||
if should_notify {
|
||||
let secure_input_app = espanso_mac_utils::get_secure_input_application();
|
||||
|
||||
if let Some((app_name, app_path)) = secure_input_app {
|
||||
info!("secure input has been acquired by {}, preventing espanso from working correctly. Full path: {}", app_name, app_path);
|
||||
|
||||
if let Err(error) =
|
||||
secure_input_sender.send(SecureInputEvent::Enabled { app_name, app_path })
|
||||
{
|
||||
error!("unable to send secure input disabled event: {}", error);
|
||||
}
|
||||
} else {
|
||||
error!("detected secure input, but could not figure out which application triggered it");
|
||||
}
|
||||
}
|
||||
|
||||
last_secure_input_pid = Some(pid);
|
||||
} else {
|
||||
// No app is currently keeping SecureInput
|
||||
|
||||
// If there was an app with SecureInput, notify that is now free
|
||||
if let Some(old_pid) = last_secure_input_pid {
|
||||
info!("secure input has been disabled");
|
||||
|
||||
if let Err(error) = secure_input_sender.send(SecureInputEvent::Disabled) {
|
||||
error!("unable to send secure input disabled event: {}", error);
|
||||
}
|
||||
}
|
||||
|
||||
last_secure_input_pid = None
|
||||
}
|
||||
|
||||
std::thread::sleep(watch_interval);
|
||||
}
|
||||
}
|
|
@ -82,4 +82,10 @@ pub struct RenderedEvent {
|
|||
pub struct DiscardPreviousEvent {
|
||||
// All Events with a source_id smaller than this one will be discarded
|
||||
pub minimum_source_id: u32,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct SecureInputEnabledEvent {
|
||||
pub app_name: String,
|
||||
pub app_path: String,
|
||||
}
|
|
@ -74,7 +74,7 @@ pub enum EventType {
|
|||
Enabled,
|
||||
DisableRequest,
|
||||
EnableRequest,
|
||||
SecureInputEnabled,
|
||||
SecureInputEnabled(internal::SecureInputEnabledEvent),
|
||||
SecureInputDisabled,
|
||||
|
||||
// Effects
|
||||
|
|
|
@ -51,7 +51,7 @@ impl Middleware for IconStatusMiddleware {
|
|||
match &event.etype {
|
||||
EventType::Enabled => *enabled = true,
|
||||
EventType::Disabled => *enabled = false,
|
||||
EventType::SecureInputEnabled => *secure_input_enabled = true,
|
||||
EventType::SecureInputEnabled(_) => *secure_input_enabled = true,
|
||||
EventType::SecureInputDisabled => *secure_input_enabled = false,
|
||||
_ => did_update = false,
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user