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:
parent
4fcf0447b3
commit
ee571b6810
2 changed files with 39 additions and 0 deletions
|
|
@ -86,6 +86,8 @@ windows = { version = "0.61.3", features = [
|
||||||
"Win32_Media_Audio_Endpoints",
|
"Win32_Media_Audio_Endpoints",
|
||||||
"Win32_System_Com_StructuredStorage",
|
"Win32_System_Com_StructuredStorage",
|
||||||
"Win32_System_Variant",
|
"Win32_System_Variant",
|
||||||
|
"Win32_Foundation",
|
||||||
|
"Win32_UI_WindowsAndMessaging",
|
||||||
] }
|
] }
|
||||||
|
|
||||||
[target.'cfg(target_os = "macos")'.dependencies]
|
[target.'cfg(target_os = "macos")'.dependencies]
|
||||||
|
|
|
||||||
|
|
@ -39,6 +39,33 @@ const OVERLAY_BOTTOM_OFFSET: f64 = 15.0;
|
||||||
#[cfg(any(target_os = "windows", target_os = "linux"))]
|
#[cfg(any(target_os = "windows", target_os = "linux"))]
|
||||||
const OVERLAY_BOTTOM_OFFSET: f64 = 40.0;
|
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> {
|
fn get_monitor_with_cursor(app_handle: &AppHandle) -> Option<tauri::Monitor> {
|
||||||
let enigo = Enigo::new(&Default::default());
|
let enigo = Enigo::new(&Default::default());
|
||||||
if let Ok(enigo) = enigo {
|
if let Ok(enigo) = enigo {
|
||||||
|
|
@ -198,6 +225,11 @@ pub fn show_recording_overlay(app_handle: &AppHandle) {
|
||||||
}
|
}
|
||||||
|
|
||||||
let _ = overlay_window.show();
|
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
|
// Emit event to trigger fade-in animation with recording state
|
||||||
let _ = overlay_window.emit("show-overlay", "recording");
|
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") {
|
if let Some(overlay_window) = app_handle.get_webview_window("recording_overlay") {
|
||||||
let _ = overlay_window.show();
|
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
|
// Emit event to switch to transcribing state
|
||||||
let _ = overlay_window.emit("show-overlay", "transcribing");
|
let _ = overlay_window.emit("show-overlay", "transcribing");
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue