diff --git a/Cargo.lock b/Cargo.lock
index 28c5866..a6ff414 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -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"
diff --git a/Cargo.toml b/Cargo.toml
index c668f9b..f3a1639 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -14,4 +14,5 @@ members = [
"espanso-path",
"espanso-modulo",
"espanso-migrate",
+ "espanso-mac-utils",
]
\ No newline at end of file
diff --git a/espanso/Cargo.toml b/espanso/Cargo.toml
index 763e545..a12194c 100644
--- a/espanso/Cargo.toml
+++ b/espanso/Cargo.toml
@@ -57,4 +57,7 @@ notify = "4.0.17"
[target.'cfg(windows)'.dependencies]
named_pipe = "0.4.1"
-winapi = { version = "0.3.9", features = ["wincon"] }
\ No newline at end of file
+winapi = { version = "0.3.9", features = ["wincon"] }
+
+[target.'cfg(target_os="macos")'.dependencies]
+espanso-mac-utils = { path = "../espanso-mac-utils" }
\ No newline at end of file
diff --git a/espanso/src/cli/worker/engine/funnel/mod.rs b/espanso/src/cli/worker/engine/funnel/mod.rs
index 29a5224..f912c79 100644
--- a/espanso/src/cli/worker/engine/funnel/mod.rs
+++ b/espanso/src/cli/worker/engine/funnel/mod.rs
@@ -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;
diff --git a/espanso/src/cli/worker/engine/funnel/secure_input.rs b/espanso/src/cli/worker/engine/funnel/secure_input.rs
new file mode 100644
index 0000000..bc68474
--- /dev/null
+++ b/espanso/src/cli/worker/engine/funnel/secure_input.rs
@@ -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 .
+ */
+
+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,
+ pub sequencer: &'a Sequencer,
+}
+
+impl<'a> SecureInputSource<'a> {
+ pub fn new(receiver: Receiver, 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 })
+ }
+ },
+ }
+ }
+}
diff --git a/espanso/src/cli/worker/engine/mod.rs b/espanso/src/cli/worker/engine/mod.rs
index 9b3c62b..a7ddcef 100644
--- a/espanso/src/cli/worker/engine/mod.rs
+++ b/espanso/src/cli/worker/engine/mod.rs
@@ -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,
exit_signal: Receiver<()>,
ui_event_receiver: Receiver,
+ secure_input_receiver: Receiver,
) -> Result> {
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());
diff --git a/espanso/src/cli/worker/mod.rs b/espanso/src/cli/worker/mod.rs
index bcc5914..f56bb24 100644
--- a/espanso/src/cli/worker/mod.rs
+++ b/espanso/src/cli/worker/mod.rs
@@ -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) {
diff --git a/espanso/src/cli/worker/secure_input.rs b/espanso/src/cli/worker/secure_input.rs
new file mode 100644
index 0000000..469afa4
--- /dev/null
+++ b/espanso/src/cli/worker/secure_input.rs
@@ -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 .
+ */
+
+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) -> Result<()> {
+ // NOOP on Windows and Linux
+ Ok(())
+}
+
+#[cfg(target_os = "macos")]
+pub fn initialize_and_spawn(secure_input_sender: Sender) -> 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, watch_interval: Duration) {
+ info!("monitoring the status of secure input");
+
+ let mut last_secure_input_pid: Option = 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);
+ }
+}
diff --git a/espanso/src/engine/event/internal.rs b/espanso/src/engine/event/internal.rs
index 1afed3c..642a0b1 100644
--- a/espanso/src/engine/event/internal.rs
+++ b/espanso/src/engine/event/internal.rs
@@ -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,
}
\ No newline at end of file
diff --git a/espanso/src/engine/event/mod.rs b/espanso/src/engine/event/mod.rs
index 89b843f..029676c 100644
--- a/espanso/src/engine/event/mod.rs
+++ b/espanso/src/engine/event/mod.rs
@@ -74,7 +74,7 @@ pub enum EventType {
Enabled,
DisableRequest,
EnableRequest,
- SecureInputEnabled,
+ SecureInputEnabled(internal::SecureInputEnabledEvent),
SecureInputDisabled,
// Effects
diff --git a/espanso/src/engine/process/middleware/icon_status.rs b/espanso/src/engine/process/middleware/icon_status.rs
index 1bec98b..67ae4ba 100644
--- a/espanso/src/engine/process/middleware/icon_status.rs
+++ b/espanso/src/engine/process/middleware/icon_status.rs
@@ -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,
}