From 8768ebad844c2993721d17edd16e72da099109f7 Mon Sep 17 00:00:00 2001 From: johnpyp Date: Mon, 3 Nov 2025 12:01:27 -0500 Subject: [PATCH] feat: SIGUSR2 handling for toggles (#281) --- src-tauri/Cargo.lock | 28 +++++++++++++++ src-tauri/Cargo.toml | 13 ++++--- src-tauri/src/lib.rs | 19 ++++++++++ src-tauri/src/signal_handler.rs | 64 +++++++++++++++++++++++++++++++++ 4 files changed, 119 insertions(+), 5 deletions(-) create mode 100644 src-tauri/src/signal_handler.rs diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index 231f103..66c2c47 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -1425,6 +1425,10 @@ dependencies = [ "objc2 0.6.3", "objc2-app-kit", "objc2-foundation 0.3.2", + "tempfile", + "wayland-client", + "wayland-protocols-misc", + "wayland-protocols-wlr", "windows 0.61.3", "x11rb", "xkbcommon", @@ -2296,6 +2300,7 @@ dependencies = [ "rustfft", "serde", "serde_json", + "signal-hook", "strsim", "tar", "tauri", @@ -5561,6 +5566,16 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +[[package]] +name = "signal-hook" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d881a16cf4426aa584979d30bd82cb33429027e42122b169753d6ef1085ed6e2" +dependencies = [ + "libc", + "signal-hook-registry", +] + [[package]] name = "signal-hook-registry" version = "1.4.6" @@ -7625,6 +7640,19 @@ dependencies = [ "wayland-scanner", ] +[[package]] +name = "wayland-protocols-misc" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dfe33d551eb8bffd03ff067a8b44bb963919157841a99957151299a6307d19c" +dependencies = [ + "bitflags 2.10.0", + "wayland-backend", + "wayland-client", + "wayland-protocols", + "wayland-scanner", +] + [[package]] name = "wayland-protocols-wlr" version = "0.3.9" diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 74b068e..d5c5ed9 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -29,10 +29,10 @@ tauri-build = { version = "2", features = [] } [dependencies] once_cell = "1" tauri = { version = "2.9.1", features = [ - "protocol-asset", - "macos-private-api", - "tray-icon", - 'image-png', + "protocol-asset", + "macos-private-api", + "tray-icon", + 'image-png', ] } tauri-plugin-opener = "2.5.2" tauri-plugin-store = "2.4.1" @@ -53,7 +53,7 @@ env_logger = "0.11.6" log = "0.4.25" tokio = "1.43.0" vad-rs = { git = "https://github.com/cjpais/vad-rs", default-features = false } -enigo = "0.6.1" +enigo = { version = "0.6.1", features = ["wayland"] } rodio = { git = "https://github.com/cjpais/rodio.git" } reqwest = { version = "0.11.27", features = ["json", "stream"] } futures-util = "0.3" @@ -73,6 +73,9 @@ tauri-plugin-global-shortcut = "2.3.1" tauri-plugin-single-instance = "2.3.2" tauri-plugin-updater = "2.9.0" +[target.'cfg(unix)'.dependencies] +signal-hook = "0.3" + [profile.release] lto = true codegen-units = 1 diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 62dfb4d..865bdfc 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -7,6 +7,7 @@ mod managers; mod overlay; mod settings; mod shortcut; +mod signal_handler; mod tray; mod utils; @@ -23,6 +24,13 @@ use tauri::Emitter; use tauri::{AppHandle, Manager}; use tauri_plugin_autostart::{MacosLauncher, ManagerExt}; +#[cfg(unix)] +use signal_hook::consts::SIGUSR2; +#[cfg(unix)] +use signal_hook::iterator::Signals; + +use log::info; + #[derive(Default)] struct ShortcutToggleStates { // Map: shortcut_binding_id -> is_active @@ -155,6 +163,10 @@ fn trigger_update_check(app: AppHandle) -> Result<(), String> { pub fn run() { env_logger::init(); + info!("Initializing!"); + #[cfg(unix)] + let signals = Signals::new(&[SIGUSR2]).unwrap(); + tauri::Builder::default() .plugin(tauri_plugin_single_instance::init(|app, _args, _cwd| { show_main_window(app); @@ -185,6 +197,13 @@ pub fn run() { let settings = settings::get_settings(&app.handle()); let app_handle = app.handle().clone(); + // Set up SIGUSR2 signal handler for toggling transcription + // + // Note: We register SIGUSR2 here so as to not conflict with + // WebKit's default handling of SIGUSR1 for triggering GC cleanup. + #[cfg(unix)] + signal_handler::setup_signal_handler(app_handle.clone(), signals); + initialize_core_logic(&app_handle); // Show main window only if not starting hidden diff --git a/src-tauri/src/signal_handler.rs b/src-tauri/src/signal_handler.rs new file mode 100644 index 0000000..85c49ff --- /dev/null +++ b/src-tauri/src/signal_handler.rs @@ -0,0 +1,64 @@ +use crate::actions::ACTION_MAP; +use crate::ManagedToggleState; +use log::{debug, info, warn}; +use std::thread; +use tauri::{AppHandle, Manager}; + +#[cfg(unix)] +use signal_hook::consts::SIGUSR2; +#[cfg(unix)] +use signal_hook::iterator::Signals; + +#[cfg(unix)] +pub fn setup_signal_handler(app_handle: AppHandle, mut signals: Signals) { + let app_handle_for_signal = app_handle.clone(); + + info!("SIGUSR2 signal handler registered successfully"); + thread::spawn(move || { + info!("SIGUSR2 signal handler thread started"); + // This blocks until a signal arrives - much more efficient than polling + for sig in signals.forever() { + match sig { + SIGUSR2 => { + debug!("Received SIGUSR2 signal (signal number: {sig})"); + + let binding_id = "transcribe"; + let shortcut_string = "SIGUSR2"; + + if let Some(action) = ACTION_MAP.get(binding_id) { + let toggle_state_manager = + app_handle_for_signal.state::(); + + let mut states = match toggle_state_manager.lock() { + Ok(s) => s, + Err(e) => { + warn!("Failed to lock toggle state manager: {e}"); + continue; + } + }; + + let is_currently_active = states + .active_toggles + .entry(binding_id.to_string()) + .or_insert(false); + + if *is_currently_active { + debug!("SIGUSR2: Stopping transcription (currently active)"); + action.stop(&app_handle_for_signal, binding_id, shortcut_string); + *is_currently_active = false; // Update state to inactive + info!("SIGUSR2: Transcription stopped"); + } else { + debug!("SIGUSR2: Starting transcription (currently inactive)"); + action.start(&app_handle_for_signal, binding_id, shortcut_string); + *is_currently_active = true; // Update state to active + info!("SIGUSR2: Transcription started"); + } + } else { + warn!("No action defined in ACTION_MAP for binding ID '{binding_id}'"); + } + } + _ => unreachable!(), + } + } + }); +}