From ee571b681066ae04bf94dd8ed30e9d569e9cbd24 Mon Sep 17 00:00:00 2001 From: Danielalnajjar Date: Thu, 27 Nov 2025 18:55:13 -0800 Subject: [PATCH] fix: re-assert always-on-top for overlay on Windows (#383) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: re-assert always-on-top for overlay on Windows On Windows, the recording/transcribing overlay can lose its topmost Z-order and fall behind other windows during certain window manager events (e.g., dragging windows, alt-tabbing, or other apps asserting focus). This fix uses the Win32 SetWindowPos API directly with HWND_TOPMOST to aggressively re-assert the overlay's Z-order immediately after showing it. Key implementation details: - Uses Tauri 2's WebviewWindow::hwnd() to get the native handle - Calls SetWindowPos on the main UI thread via run_on_main_thread - Applies flags: SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE | SWP_SHOWWINDOW - SWP_NOACTIVATE prevents stealing focus from the active window - SWP_SHOWWINDOW reinforces the topmost state during the show The fix is called after .show() in both show_recording_overlay and show_transcribing_overlay, which is more reliable than setting it before the window is visible. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude * keep the fix to windows only --------- Co-authored-by: Claude Co-authored-by: CJ Pais --- src-tauri/Cargo.toml | 2 ++ src-tauri/src/overlay.rs | 37 +++++++++++++++++++++++++++++++++++++ 2 files changed, 39 insertions(+) diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 130615e..f253579 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -86,6 +86,8 @@ windows = { version = "0.61.3", features = [ "Win32_Media_Audio_Endpoints", "Win32_System_Com_StructuredStorage", "Win32_System_Variant", + "Win32_Foundation", + "Win32_UI_WindowsAndMessaging", ] } [target.'cfg(target_os = "macos")'.dependencies] diff --git a/src-tauri/src/overlay.rs b/src-tauri/src/overlay.rs index a5f7472..2fc226a 100644 --- a/src-tauri/src/overlay.rs +++ b/src-tauri/src/overlay.rs @@ -39,6 +39,33 @@ const OVERLAY_BOTTOM_OFFSET: f64 = 15.0; #[cfg(any(target_os = "windows", target_os = "linux"))] const OVERLAY_BOTTOM_OFFSET: f64 = 40.0; +/// Forces a window to be topmost using Win32 API (Windows only) +/// This is more reliable than Tauri's set_always_on_top which can be overridden +#[cfg(target_os = "windows")] +fn force_overlay_topmost(overlay_window: &tauri::webview::WebviewWindow) { + use windows::Win32::UI::WindowsAndMessaging::{ + SetWindowPos, HWND_TOPMOST, SWP_NOMOVE, SWP_NOSIZE, SWP_NOACTIVATE, SWP_SHOWWINDOW, + }; + + // Clone because run_on_main_thread takes 'static + let overlay_clone = overlay_window.clone(); + + // Make sure the Win32 call happens on the UI thread + let _ = overlay_clone.clone().run_on_main_thread(move || { + if let Ok(hwnd) = overlay_clone.hwnd() { + unsafe { + // Force Z-order: make this window topmost without changing size/pos or stealing focus + let _ = SetWindowPos( + hwnd, + Some(HWND_TOPMOST), + 0, 0, 0, 0, + SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE | SWP_SHOWWINDOW, + ); + } + } + }); +} + fn get_monitor_with_cursor(app_handle: &AppHandle) -> Option { let enigo = Enigo::new(&Default::default()); if let Ok(enigo) = enigo { @@ -198,6 +225,11 @@ pub fn show_recording_overlay(app_handle: &AppHandle) { } let _ = overlay_window.show(); + + // On Windows, aggressively re-assert "topmost" in the native Z-order after showing + #[cfg(target_os = "windows")] + force_overlay_topmost(&overlay_window); + // Emit event to trigger fade-in animation with recording state let _ = overlay_window.emit("show-overlay", "recording"); } @@ -215,6 +247,11 @@ pub fn show_transcribing_overlay(app_handle: &AppHandle) { if let Some(overlay_window) = app_handle.get_webview_window("recording_overlay") { let _ = overlay_window.show(); + + // On Windows, aggressively re-assert "topmost" in the native Z-order after showing + #[cfg(target_os = "windows")] + force_overlay_topmost(&overlay_window); + // Emit event to switch to transcribing state let _ = overlay_window.emit("show-overlay", "transcribing"); }