From 9c0ba106d665cd17fb474accb8a4fa4da887d898 Mon Sep 17 00:00:00 2001 From: NimBold Date: Thu, 27 Aug 2026 17:00:57 +0330 Subject: [PATCH] fix(startup): defer Windows window activation (#37) - Keep the Windows WebView host hidden and unfocused during native construction.\n- Queue deep-link, torrent, single-instance, and extension restore requests until Tauri is ready.\n- Reveal and focus the main window through one startup-safe path.\n\nFixes #37. --- src-tauri/src/extension_server.rs | 22 ++++++------ src-tauri/src/lib.rs | 56 +++++++++++++++++++++---------- 2 files changed, 49 insertions(+), 29 deletions(-) diff --git a/src-tauri/src/extension_server.rs b/src-tauri/src/extension_server.rs index a5aec7b..3fd8a4c 100644 --- a/src-tauri/src/extension_server.rs +++ b/src-tauri/src/extension_server.rs @@ -308,18 +308,16 @@ async fn download_handler( None => return Err(StatusCode::BAD_REQUEST), }; - if let Some(window) = state.app_handle.get_webview_window("main") { - let is_visible = window.is_visible().unwrap_or(true); - if !is_visible { - let _ = window.show(); - let _ = window.set_focus(); - // Sleep briefly to let the webview wake up from macOS App Nap - // otherwise the IPC event emitted immediately after is dropped. - tokio::time::sleep(std::time::Duration::from_millis(300)).await; - } else { - let _ = window.show(); - let _ = window.set_focus(); - } + let is_hidden = state + .app_handle + .get_webview_window("main") + .and_then(|window| window.is_visible().ok()) + .is_some_and(|is_visible| !is_visible); + crate::restore_main_window(&state.app_handle); + if is_hidden { + // Sleep briefly to let the webview wake up from macOS App Nap + // otherwise the IPC event emitted immediately after is dropped. + tokio::time::sleep(std::time::Duration::from_millis(300)).await; } if !wait_for_frontend(&state.frontend_ready).await { diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index d3b9eb2..8b0b572 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -3659,6 +3659,7 @@ pub struct AppState { #[derive(Default)] struct MainWindowRestoreState { requested: AtomicBool, + startup_complete: AtomicBool, } #[cfg(target_os = "macos")] @@ -3954,7 +3955,26 @@ where .collect() } -fn restore_main_window(app_handle: &tauri::AppHandle) { +pub(crate) fn restore_main_window(app_handle: &tauri::AppHandle) { + let startup_complete = app_handle + .try_state::() + .is_none_or(|state| state.startup_complete.load(Ordering::Acquire)); + if !startup_complete { + if let Some(state) = app_handle.try_state::() { + // The first window can be requested by a single-instance callback + // or an opened .torrent path while the native host is still being + // constructed. Do not touch the HWND until RunEvent::Ready. + state.requested.store(true, Ordering::Release); + // Ready may have won the transition between the first load and + // the request store. Re-check before returning so that request is + // serviced by this call instead of being left pending forever. + if !state.startup_complete.load(Ordering::Acquire) { + return; + } + } else { + return; + } + } let Some(window) = app_handle.get_webview_window("main") else { if let Some(state) = app_handle.try_state::() { state.requested.store(true, Ordering::Release); @@ -3967,6 +3987,12 @@ fn restore_main_window(app_handle: &tauri::AppHandle) { let _ = window.set_focus(); } +fn mark_main_window_startup_complete(app_handle: &tauri::AppHandle) { + if let Some(state) = app_handle.try_state::() { + state.startup_complete.store(true, Ordering::Release); + } +} + fn restore_pending_main_window(app_handle: &tauri::AppHandle) { if app_handle .try_state::() @@ -18514,15 +18540,11 @@ pub fn run() { .prevent_overflow(); #[cfg(target_os = "windows")] { - // WebView2 can return E_INVALIDARG from MoveFocus while the - // newly-created host window is not focusable yet. Wry - // propagates that error from WebviewWindowBuilder::build, - // which destroys the native window and makes a release build - // look like it started headlessly before exiting. Leave the - // window unfocused until Windows has completed native - // activation; a synchronous post-build focus request can - // re-enter the WebView2 focus callback on affected systems. - main_window_builder = main_window_builder.focused(false); + // Wry installs its parent WM_SETFOCUS handler while WebView2 + // is still being initialized. Keep the host unfocused and + // hidden until RunEvent::Ready so Windows cannot re-enter that + // handler during native construction. + main_window_builder = main_window_builder.visible(false).focused(false); } main_window_builder .build() @@ -18535,13 +18557,6 @@ pub fn run() { collect_opened_torrent_paths(std::env::args_os().skip(1)), ); - #[cfg(target_os = "windows")] - if let Some(window) = app.get_webview_window("main") { - window - .set_decorations(false) - .map_err(|error| format!("failed to disable Windows native frame: {error}"))?; - } - let deep_link_app = app.handle().clone(); #[cfg(target_os = "linux")] if let Err(error) = app.deep_link().register_all() { @@ -19866,6 +19881,13 @@ pub fn run() { .build(tauri::generate_context!()) .expect("error while building tauri application") .run(|app_handle, event| match event { + tauri::RunEvent::Ready => { + mark_main_window_startup_complete(app_handle); + if let Some(state) = app_handle.try_state::() { + state.requested.swap(false, Ordering::AcqRel); + } + restore_main_window(app_handle); + } #[cfg(target_os = "macos")] tauri::RunEvent::Opened { urls } => { let paths = collect_opened_torrent_paths(