diff --git a/src-tauri/src/shortcut.rs b/src-tauri/src/shortcut.rs index 04d8e0bc0..a44d0b437 100644 --- a/src-tauri/src/shortcut.rs +++ b/src-tauri/src/shortcut.rs @@ -842,25 +842,32 @@ pub fn register_shortcut(app: &AppHandle, binding: ShortcutBinding) -> Result<() action.stop(ah, &binding_id_for_closure, &shortcut_string); } } else { + // Toggle mode: toggle on press only if event.state == ShortcutState::Pressed { - let toggle_state_manager = ah.state::(); - - let mut states = toggle_state_manager.lock().expect("Failed to lock toggle state manager"); - - let is_currently_active = states.active_toggles - .entry(binding_id_for_closure.clone()) - .or_insert(false); - - if *is_currently_active { - action.stop( - ah, - &binding_id_for_closure, - &shortcut_string, - ); - *is_currently_active = false; // Update state to inactive - } else { + // Determine action and update state while holding the lock, + // but RELEASE the lock before calling the action to avoid deadlocks. + // (Actions may need to acquire the lock themselves, e.g., cancel_current_operation) + let should_start: bool; + { + let toggle_state_manager = ah.state::(); + let mut states = toggle_state_manager + .lock() + .expect("Failed to lock toggle state manager"); + + let is_currently_active = states + .active_toggles + .entry(binding_id_for_closure.clone()) + .or_insert(false); + + should_start = !*is_currently_active; + *is_currently_active = should_start; + } // Lock released here + + // Now call the action without holding the lock + if should_start { action.start(ah, &binding_id_for_closure, &shortcut_string); - *is_currently_active = true; // Update state to active + } else { + action.stop(ah, &binding_id_for_closure, &shortcut_string); } } } diff --git a/src-tauri/src/signal_handle.rs b/src-tauri/src/signal_handle.rs index c4445c99e..ff4ad2df0 100644 --- a/src-tauri/src/signal_handle.rs +++ b/src-tauri/src/signal_handle.rs @@ -25,32 +25,40 @@ pub fn setup_signal_handler(app_handle: AppHandle, mut signals: Signals) { 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 - debug!("SIGUSR2: Transcription stopped"); - } else { - debug!("SIGUSR2: Starting transcription (currently inactive)"); + // Determine action and update state while holding the lock, + // but RELEASE the lock before calling the action to avoid deadlocks. + // (Actions may need to acquire the lock themselves, e.g., cancel_current_operation) + let should_start: bool; + { + 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); + + should_start = !*is_currently_active; + *is_currently_active = should_start; + } // Lock released here + + // Now call the action without holding the lock + if should_start { + debug!("SIGUSR2: Starting transcription (was inactive)"); action.start(&app_handle_for_signal, binding_id, shortcut_string); - *is_currently_active = true; // Update state to active info!("SIGUSR2: Transcription started"); + } else { + debug!("SIGUSR2: Stopping transcription (was active)"); + action.stop(&app_handle_for_signal, binding_id, shortcut_string); + debug!("SIGUSR2: Transcription stopped"); } } else { warn!("No action defined in ACTION_MAP for binding ID '{binding_id}'");