22mod app;
33mod util;
44
5- use tauri:: Manager ;
5+ use std:: sync:: {
6+ atomic:: { AtomicBool , Ordering } ,
7+ Arc ,
8+ } ;
9+ use tauri:: { webview:: PageLoadEvent , Manager , Url , WebviewWindow } ;
610use tauri_plugin_window_state:: Builder as WindowStatePlugin ;
711use tauri_plugin_window_state:: StateFlags ;
812
913#[ cfg( target_os = "macos" ) ]
1014use std:: time:: Duration ;
1115
12- const WINDOW_SHOW_DELAY : u64 = 50 ;
16+ // Fallback when PageLoadEvent::Finished never arrives (offline / stalled).
17+ // Deliberately longer than a paint tick so the normal path can win first.
18+ const STARTUP_WINDOW_FALLBACK_DELAY : u64 = 3_000 ;
1319#[ cfg( target_os = "linux" ) ]
1420const PAKE_LINUX_WEBKIT_SAFE_MODE : & str = "PAKE_LINUX_WEBKIT_SAFE_MODE" ;
1521#[ cfg( target_os = "linux" ) ]
@@ -29,6 +35,43 @@ use app::{
2935} ;
3036use util:: get_pake_config;
3137
38+ /// Placeholder documents used before the real target URL navigates (e.g. macOS
39+ /// cert-bypass starts on about:blank). Revealing on these would reintroduce the
40+ /// blank-window flash the page-load gate is meant to prevent.
41+ fn is_placeholder_startup_url ( url : & Url ) -> bool {
42+ url. scheme ( ) . eq_ignore_ascii_case ( "about" )
43+ }
44+
45+ fn reveal_startup_window ( window : WebviewWindow , init_fullscreen : bool , revealed : & Arc < AtomicBool > ) {
46+ if revealed. swap ( true , Ordering :: AcqRel ) {
47+ return ;
48+ }
49+
50+ tauri:: async_runtime:: spawn ( async move {
51+ let _ = window. show ( ) ;
52+ reapply_window_icon ( & window) ;
53+
54+ // Fixed: Linux fullscreen issue with virtual keyboard
55+ #[ cfg( target_os = "linux" ) ]
56+ {
57+ if init_fullscreen {
58+ let _ = window. set_fullscreen ( true ) ;
59+ // Ensure webview maintains focus for input after fullscreen
60+ let _ = window. set_focus ( ) ;
61+ } else {
62+ // Fix: Ubuntu 24.04/GNOME window buttons non-functional until resize (#1122)
63+ // The window manager needs time to process the MapWindow event before
64+ // accepting focus requests. Without this, decorations remain non-interactive.
65+ tokio:: time:: sleep ( tokio:: time:: Duration :: from_millis ( 30 ) ) . await ;
66+ let _ = window. set_focus ( ) ;
67+ }
68+ }
69+
70+ #[ cfg( not( target_os = "linux" ) ) ]
71+ let _ = init_fullscreen;
72+ } ) ;
73+ }
74+
3275#[ cfg( any( target_os = "linux" , test) ) ]
3376fn is_disabled_env_value ( value : & str ) -> bool {
3477 matches ! (
@@ -149,6 +192,7 @@ pub fn run_app() {
149192 let multi_instance = pake_config. multi_instance ;
150193 let multi_window = pake_config. multi_window ;
151194 let _enable_find = pake_config. windows [ 0 ] . enable_find ;
195+ let startup_window_revealed = Arc :: new ( AtomicBool :: new ( false ) ) ;
152196
153197 let window_state_plugin = WindowStatePlugin :: default ( )
154198 . with_state_flags ( if init_fullscreen {
@@ -186,6 +230,30 @@ pub fn run_app() {
186230 ) ) ;
187231 }
188232
233+ // Reveal the main window after the first real document finishes loading so
234+ // slow WKWebView cold starts do not expose an empty but interactive shell.
235+ // start_to_tray keeps the window hidden for the whole session until the user
236+ // opens it from the tray / shortcut.
237+ if !start_to_tray {
238+ let page_load_revealed = startup_window_revealed. clone ( ) ;
239+ app_builder = app_builder. on_page_load ( move |webview, payload| {
240+ if webview. label ( ) != "pake" {
241+ return ;
242+ }
243+ if !matches ! ( payload. event( ) , PageLoadEvent :: Finished ) {
244+ return ;
245+ }
246+ // Skip about:blank (and other about: placeholders) used by the macOS
247+ // cert-bypass path before the real target URL navigates.
248+ if is_placeholder_startup_url ( payload. url ( ) ) {
249+ return ;
250+ }
251+ if let Some ( window) = webview. app_handle ( ) . get_webview_window ( "pake" ) {
252+ reveal_startup_window ( window, init_fullscreen, & page_load_revealed) ;
253+ }
254+ } ) ;
255+ }
256+
189257 app_builder
190258 . invoke_handler ( tauri:: generate_handler![
191259 download_file,
@@ -226,29 +294,17 @@ pub fn run_app() {
226294 set_global_shortcut ( app. app_handle ( ) , activation_shortcut, init_fullscreen) ?;
227295
228296 // Show window after state restoration to prevent position flashing
229- // Unless start_to_tray is enabled, then keep it hidden
297+ // once its first page finishes. A fallback keeps offline or stalled
298+ // pages reachable without exposing a blank webview during normal startup.
230299 if !start_to_tray {
231300 let window_clone = window. clone ( ) ;
301+ let fallback_revealed = startup_window_revealed. clone ( ) ;
232302 tauri:: async_runtime:: spawn ( async move {
233- tokio:: time:: sleep ( tokio:: time:: Duration :: from_millis ( WINDOW_SHOW_DELAY ) ) . await ;
234- let _ = window_clone. show ( ) ;
235- reapply_window_icon ( & window_clone) ;
236-
237- // Fixed: Linux fullscreen issue with virtual keyboard
238- #[ cfg( target_os = "linux" ) ]
239- {
240- if init_fullscreen {
241- let _ = window_clone. set_fullscreen ( true ) ;
242- // Ensure webview maintains focus for input after fullscreen
243- let _ = window_clone. set_focus ( ) ;
244- } else {
245- // Fix: Ubuntu 24.04/GNOME window buttons non-functional until resize (#1122)
246- // The window manager needs time to process the MapWindow event before
247- // accepting focus requests. Without this, decorations remain non-interactive.
248- tokio:: time:: sleep ( tokio:: time:: Duration :: from_millis ( 30 ) ) . await ;
249- let _ = window_clone. set_focus ( ) ;
250- }
251- }
303+ tokio:: time:: sleep ( tokio:: time:: Duration :: from_millis (
304+ STARTUP_WINDOW_FALLBACK_DELAY ,
305+ ) )
306+ . await ;
307+ reveal_startup_window ( window_clone, init_fullscreen, & fallback_revealed) ;
252308 } ) ;
253309 }
254310
@@ -318,6 +374,19 @@ pub fn run() {
318374mod tests {
319375 use super :: * ;
320376
377+ #[ test]
378+ fn placeholder_startup_urls_cover_about_blank ( ) {
379+ let blank: Url = "about:blank" . parse ( ) . unwrap ( ) ;
380+ let srcdoc: Url = "about:srcdoc" . parse ( ) . unwrap ( ) ;
381+ let https: Url = "https://github.com/" . parse ( ) . unwrap ( ) ;
382+ let tauri: Url = "tauri://localhost/" . parse ( ) . unwrap ( ) ;
383+
384+ assert ! ( is_placeholder_startup_url( & blank) ) ;
385+ assert ! ( is_placeholder_startup_url( & srcdoc) ) ;
386+ assert ! ( !is_placeholder_startup_url( & https) ) ;
387+ assert ! ( !is_placeholder_startup_url( & tauri) ) ;
388+ }
389+
321390 #[ test]
322391 fn linux_webkit_safe_mode_stays_on_by_default ( ) {
323392 assert ! ( should_enable_linux_webkit_safe_mode_from_values(
0 commit comments