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-info",
|
||||||
"espanso-inject",
|
"espanso-inject",
|
||||||
"espanso-ipc",
|
"espanso-ipc",
|
||||||
|
"espanso-mac-utils",
|
||||||
"espanso-match",
|
"espanso-match",
|
||||||
"espanso-migrate",
|
"espanso-migrate",
|
||||||
"espanso-modulo",
|
"espanso-modulo",
|
||||||
|
@ -548,6 +549,19 @@ dependencies = [
|
||||||
"thiserror",
|
"thiserror",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "espanso-mac-utils"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
|
"cc",
|
||||||
|
"lazy_static",
|
||||||
|
"lazycell",
|
||||||
|
"log",
|
||||||
|
"regex",
|
||||||
|
"thiserror",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "espanso-match"
|
name = "espanso-match"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
|
|
@ -14,4 +14,5 @@ members = [
|
||||||
"espanso-path",
|
"espanso-path",
|
||||||
"espanso-modulo",
|
"espanso-modulo",
|
||||||
"espanso-migrate",
|
"espanso-migrate",
|
||||||
|
"espanso-mac-utils",
|
||||||
]
|
]
|
|
@ -57,4 +57,7 @@ notify = "4.0.17"
|
||||||
|
|
||||||
[target.'cfg(windows)'.dependencies]
|
[target.'cfg(windows)'.dependencies]
|
||||||
named_pipe = "0.4.1"
|
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 detect;
|
||||||
pub mod exit;
|
pub mod exit;
|
||||||
pub mod modifier;
|
pub mod modifier;
|
||||||
|
pub mod secure_input;
|
||||||
pub mod sequencer;
|
pub mod sequencer;
|
||||||
pub mod ui;
|
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,
|
engine::event::ExitMode,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use super::secure_input::SecureInputEvent;
|
||||||
|
|
||||||
pub mod dispatch;
|
pub mod dispatch;
|
||||||
pub mod funnel;
|
pub mod funnel;
|
||||||
pub mod process;
|
pub mod process;
|
||||||
|
@ -65,6 +67,7 @@ pub fn initialize_and_spawn(
|
||||||
ui_remote: Box<dyn UIRemote>,
|
ui_remote: Box<dyn UIRemote>,
|
||||||
exit_signal: Receiver<()>,
|
exit_signal: Receiver<()>,
|
||||||
ui_event_receiver: Receiver<UIEvent>,
|
ui_event_receiver: Receiver<UIEvent>,
|
||||||
|
secure_input_receiver: Receiver<SecureInputEvent>,
|
||||||
) -> Result<JoinHandle<ExitMode>> {
|
) -> Result<JoinHandle<ExitMode>> {
|
||||||
let handle = std::thread::Builder::new()
|
let handle = std::thread::Builder::new()
|
||||||
.name("engine thread".to_string())
|
.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");
|
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 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 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> =
|
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 funnel = crate::engine::funnel::default(&sources);
|
||||||
|
|
||||||
let rolling_matcher = RollingMatcherAdapter::new(&match_converter.get_rolling_matches());
|
let rolling_matcher = RollingMatcherAdapter::new(&match_converter.get_rolling_matches());
|
||||||
|
|
|
@ -38,6 +38,7 @@ mod daemon_monitor;
|
||||||
mod engine;
|
mod engine;
|
||||||
mod ipc;
|
mod ipc;
|
||||||
mod match_cache;
|
mod match_cache;
|
||||||
|
mod secure_input;
|
||||||
mod ui;
|
mod ui;
|
||||||
|
|
||||||
pub fn new() -> CliModule {
|
pub fn new() -> CliModule {
|
||||||
|
@ -100,6 +101,7 @@ fn worker_main(args: CliModuleArgs) -> i32 {
|
||||||
|
|
||||||
let (engine_exit_notify, engine_exit_receiver) = unbounded();
|
let (engine_exit_notify, engine_exit_receiver) = unbounded();
|
||||||
let (engine_ui_event_sender, engine_ui_event_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
|
// Initialize the engine on another thread and start it
|
||||||
let engine_handle = engine::initialize_and_spawn(
|
let engine_handle = engine::initialize_and_spawn(
|
||||||
|
@ -109,6 +111,7 @@ fn worker_main(args: CliModuleArgs) -> i32 {
|
||||||
remote,
|
remote,
|
||||||
engine_exit_receiver,
|
engine_exit_receiver,
|
||||||
engine_ui_event_receiver,
|
engine_ui_event_receiver,
|
||||||
|
engine_secure_input_receiver,
|
||||||
)
|
)
|
||||||
.expect("unable to initialize engine");
|
.expect("unable to initialize engine");
|
||||||
|
|
||||||
|
@ -125,6 +128,9 @@ fn worker_main(args: CliModuleArgs) -> i32 {
|
||||||
.expect("unable to initialize daemon monitor thread");
|
.expect("unable to initialize daemon monitor thread");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
secure_input::initialize_and_spawn(engine_secure_input_sender)
|
||||||
|
.expect("unable to initialize secure input watcher");
|
||||||
|
|
||||||
eventloop
|
eventloop
|
||||||
.run(Box::new(move |event| {
|
.run(Box::new(move |event| {
|
||||||
if let Err(error) = engine_ui_event_sender.send(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 {
|
pub struct DiscardPreviousEvent {
|
||||||
// All Events with a source_id smaller than this one will be discarded
|
// All Events with a source_id smaller than this one will be discarded
|
||||||
pub minimum_source_id: u32,
|
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,
|
Enabled,
|
||||||
DisableRequest,
|
DisableRequest,
|
||||||
EnableRequest,
|
EnableRequest,
|
||||||
SecureInputEnabled,
|
SecureInputEnabled(internal::SecureInputEnabledEvent),
|
||||||
SecureInputDisabled,
|
SecureInputDisabled,
|
||||||
|
|
||||||
// Effects
|
// Effects
|
||||||
|
|
|
@ -51,7 +51,7 @@ impl Middleware for IconStatusMiddleware {
|
||||||
match &event.etype {
|
match &event.etype {
|
||||||
EventType::Enabled => *enabled = true,
|
EventType::Enabled => *enabled = true,
|
||||||
EventType::Disabled => *enabled = false,
|
EventType::Disabled => *enabled = false,
|
||||||
EventType::SecureInputEnabled => *secure_input_enabled = true,
|
EventType::SecureInputEnabled(_) => *secure_input_enabled = true,
|
||||||
EventType::SecureInputDisabled => *secure_input_enabled = false,
|
EventType::SecureInputDisabled => *secure_input_enabled = false,
|
||||||
_ => did_update = false,
|
_ => did_update = false,
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user