Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 8 additions & 11 deletions src-tauri/src/actions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -371,25 +372,21 @@ 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 {:?}",
paste_time.elapsed()
),
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);
Expand Down
29 changes: 29 additions & 0 deletions src-tauri/src/overlay.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<F>(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<f32>) {
// emit levels to main app
let _ = app_handle.emit("mic-level", levels);
Expand Down