From 203ba1dbc267d3b5e1109ecd0dc3e10b7973e5bb Mon Sep 17 00:00:00 2001 From: Ari <37885347+arimxyer@users.noreply.github.com> Date: Mon, 16 Feb 2026 09:41:33 -0500 Subject: [PATCH] feat(linux): add SIGUSR1 signal for transcribe_with_post_process (#759) * feat(linux): add SIGUSR1 signal for transcribe_with_post_process Register SIGUSR1 alongside SIGUSR2 in the signal handler so Linux users can trigger transcription with post-processing via system signals (e.g. `pkill -SIGUSR1 -x handy`). This mirrors the existing SIGUSR2 toggle pattern and maps to the `transcribe_with_post_process` action already present in ACTION_MAP. Closes #758 Co-Authored-By: Claude Opus 4.6 * readme * format --------- Co-authored-by: Claude Opus 4.6 Co-authored-by: CJ Pais --- README.md | 10 +++++++++- src-tauri/src/lib.rs | 6 +++--- src-tauri/src/signal_handle.rs | 21 ++++++++++++--------- 3 files changed, 24 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index e746fad..b0405cf 100644 --- a/README.md +++ b/README.md @@ -118,10 +118,18 @@ Without these tools, Handy falls back to enigo which may have limited compatibil - The recording overlay is disabled by default on Linux (`Overlay Position: None`) because certain compositors treat it as the active window. When the overlay is visible it can steal focus, which prevents Handy from pasting back into the application that triggered transcription. If you enable the overlay anyway, be aware that clipboard-based pasting might fail or end up in the wrong window. - If you are having trouble with the app, running with the environment variable `WEBKIT_DISABLE_DMABUF_RENDERER=1` may help -- You can manage global shortcuts outside of Handy and still control the app via signals. Sending `SIGUSR2` to the Handy process toggles recording on/off, which lets Wayland window managers or other hotkey daemons keep ownership of keybindings. Example (Sway): +- You can manage global shortcuts outside of Handy and still control the app via signals, which lets Wayland window managers or other hotkey daemons keep ownership of keybindings: + + | Signal | Action | Example | + | --------- | ----------------------------------------- | ---------------------- | + | `SIGUSR2` | Toggle transcription | `pkill -USR2 -n handy` | + | `SIGUSR1` | Toggle transcription with post-processing | `pkill -USR1 -n handy` | + + Example Sway config: ```ini bindsym $mod+o exec pkill -USR2 -n handy + bindsym $mod+p exec pkill -USR1 -n handy ``` `pkill` here simply delivers the signal—it does not terminate the process. diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 36f28e5..7b280c1 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -26,7 +26,7 @@ use managers::history::HistoryManager; use managers::model::ModelManager; use managers::transcription::TranscriptionManager; #[cfg(unix)] -use signal_hook::consts::SIGUSR2; +use signal_hook::consts::{SIGUSR1, SIGUSR2}; #[cfg(unix)] use signal_hook::iterator::Signals; use std::sync::atomic::{AtomicU8, Ordering}; @@ -132,8 +132,8 @@ fn initialize_core_logic(app_handle: &AppHandle) { // This matches the pattern used for Enigo initialization. #[cfg(unix)] - let signals = Signals::new(&[SIGUSR2]).unwrap(); - // Set up SIGUSR2 signal handler for toggling transcription + let signals = Signals::new(&[SIGUSR1, SIGUSR2]).unwrap(); + // Set up signal handlers for toggling transcription #[cfg(unix)] signal_handle::setup_signal_handler(app_handle.clone(), signals); diff --git a/src-tauri/src/signal_handle.rs b/src-tauri/src/signal_handle.rs index 35ff261..1113eb5 100644 --- a/src-tauri/src/signal_handle.rs +++ b/src-tauri/src/signal_handle.rs @@ -4,22 +4,25 @@ use std::thread; use tauri::{AppHandle, Manager}; #[cfg(unix)] -use signal_hook::consts::SIGUSR2; +use signal_hook::consts::{SIGUSR1, SIGUSR2}; #[cfg(unix)] use signal_hook::iterator::Signals; #[cfg(unix)] pub fn setup_signal_handler(app_handle: AppHandle, mut signals: Signals) { - debug!("SIGUSR2 signal handler registered"); + debug!("Signal handlers registered (SIGUSR1, SIGUSR2)"); thread::spawn(move || { for sig in signals.forever() { - if sig == SIGUSR2 { - debug!("Received SIGUSR2"); - if let Some(c) = app_handle.try_state::() { - c.send_input("transcribe", "SIGUSR2", true, false); - } else { - warn!("TranscriptionCoordinator not initialized"); - } + let (binding_id, signal_name) = match sig { + SIGUSR1 => ("transcribe_with_post_process", "SIGUSR1"), + SIGUSR2 => ("transcribe", "SIGUSR2"), + _ => continue, + }; + debug!("Received {signal_name}"); + if let Some(c) = app_handle.try_state::() { + c.send_input(binding_id, signal_name, true, false); + } else { + warn!("TranscriptionCoordinator not initialized"); } } });