fix: re-assert always-on-top for overlay on Windows (#383)

* 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 <noreply@anthropic.com>

* keep the fix to windows only

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: CJ Pais <cj@cjpais.com>
This commit is contained in:
Danielalnajjar 2025-11-27 18:55:13 -08:00 committed by GitHub
parent 4fcf0447b3
commit ee571b6810
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 39 additions and 0 deletions

View file

@ -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]

View file

@ -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<tauri::Monitor> {
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");
}