From 5e50e13b428e5c37619db1e75ff546f6dc71dcb7 Mon Sep 17 00:00:00 2001 From: mg <102437792+mg-dev25@users.noreply.github.com> Date: Sat, 29 Nov 2025 17:33:27 +0100 Subject: [PATCH] fix(ui): improve paste reliability with overlay timing callback Add hide_recording_overlay_with_callback() that waits for the overlay fade-out animation to complete and the window manager to restore focus before executing the paste operation. This fixes paste failures on Wayland/Linux where focus changes are asynchronous and the previous application may not have regained focus by the time the paste is attempted. Changes: - Add hide_recording_overlay_with_callback() in overlay.rs with: - 300ms delay for fade-out animation - 150ms delay for window manager focus restoration - Immediate callback execution if no overlay exists - Update actions.rs to use callback-based approach for pasting - Remove run_on_main_thread wrapper (callback runs in spawned thread) Relates to #390 --- src-tauri/src/actions.rs | 19 ++++++++----------- src-tauri/src/overlay.rs | 29 +++++++++++++++++++++++++++++ 2 files changed, 37 insertions(+), 11 deletions(-) diff --git a/src-tauri/src/actions.rs b/src-tauri/src/actions.rs index f8d490f89..d573c605f 100644 --- a/src-tauri/src/actions.rs +++ b/src-tauri/src/actions.rs @@ -5,6 +5,7 @@ use crate::managers::transcription::TranscriptionManager; use crate::settings::{get_settings, AppSettings}; use crate::shortcut; use crate::tray::{change_tray_icon, TrayIconState}; +use crate::overlay::hide_recording_overlay_with_callback; use crate::utils::{self, show_recording_overlay, show_transcribing_overlay}; use async_openai::types::{ ChatCompletionRequestMessage, ChatCompletionRequestUserMessageArgs, @@ -371,10 +372,14 @@ impl ShortcutAction for TranscribeAction { } }); - // Paste the final text (either processed or original) + // Hide overlay first, then paste when focus is restored. + // This ensures reliable paste on Wayland/Linux where focus + // changes are asynchronous. let ah_clone = ah.clone(); - let paste_time = Instant::now(); - ah.run_on_main_thread(move || { + hide_recording_overlay_with_callback(&ah, move || { + let paste_time = Instant::now(); + change_tray_icon(&ah_clone, TrayIconState::Idle); + match utils::paste(final_text, ah_clone.clone()) { Ok(()) => debug!( "Text pasted successfully in {:?}", @@ -382,14 +387,6 @@ impl ShortcutAction for TranscribeAction { ), Err(e) => error!("Failed to paste transcription: {}", e), } - // Hide the overlay after transcription is complete - utils::hide_recording_overlay(&ah_clone); - change_tray_icon(&ah_clone, TrayIconState::Idle); - }) - .unwrap_or_else(|e| { - error!("Failed to run paste on main thread: {:?}", e); - utils::hide_recording_overlay(&ah); - change_tray_icon(&ah, TrayIconState::Idle); }); } else { utils::hide_recording_overlay(&ah); diff --git a/src-tauri/src/overlay.rs b/src-tauri/src/overlay.rs index e5bc6a01b..b7c5c03e5 100644 --- a/src-tauri/src/overlay.rs +++ b/src-tauri/src/overlay.rs @@ -281,6 +281,35 @@ pub fn hide_recording_overlay(app_handle: &AppHandle) { } } +/// Hides the recording overlay window with fade-out animation and executes callback when done. +/// This ensures the overlay is fully hidden and focus is restored before the callback runs, +/// which is critical for reliable paste operations on Wayland/Linux. +pub fn hide_recording_overlay_with_callback(app_handle: &AppHandle, callback: F) +where + F: FnOnce() + Send + 'static, +{ + // Always hide the overlay regardless of settings - if setting was changed while recording, + // we still want to hide it properly + if let Some(overlay_window) = app_handle.get_webview_window("recording_overlay") { + // Emit event to trigger fade-out animation + let _ = overlay_window.emit("hide-overlay", ()); + // Hide the window after animation completes, then wait for window manager to restore focus + let window_clone = overlay_window.clone(); + std::thread::spawn(move || { + // Wait for fade-out animation to complete + std::thread::sleep(std::time::Duration::from_millis(300)); + let _ = window_clone.hide(); + // Additional delay for window manager to restore focus to the previous application. + // This is especially important on Wayland where focus changes are asynchronous. + std::thread::sleep(std::time::Duration::from_millis(150)); + callback(); + }); + } else { + // If no overlay window exists, run callback immediately + callback(); + } +} + pub fn emit_levels(app_handle: &AppHandle, levels: &Vec) { // emit levels to main app let _ = app_handle.emit("mic-level", levels);