11//! Theme System
22
33use std:: sync:: { OnceLock , RwLock } ;
4- use std:: time:: Instant ;
4+ use std:: time:: { Duration , Instant } ;
55
66use bitfun_core:: infrastructure:: try_get_path_manager_arc;
77use bitfun_core:: service:: config:: types:: GlobalConfig ;
@@ -18,11 +18,106 @@ const AGENT_COMPANION_WINDOW_MAX_WIDTH: f64 = 360.0;
1818const AGENT_COMPANION_WINDOW_MAX_HEIGHT : f64 = 240.0 ;
1919const AGENT_COMPANION_WINDOW_MARGIN : i32 = 64 ;
2020const AGENT_COMPANION_WINDOW_EDGE_MARGIN : f64 = 8.0 ;
21+ #[ cfg( target_os = "windows" ) ]
22+ const WINDOWS_STARTUP_MAXIMIZE_SHOW_WAIT_MAX : Duration = Duration :: from_millis ( 150 ) ;
23+ #[ cfg( target_os = "windows" ) ]
24+ const WINDOWS_STARTUP_MAXIMIZE_SHOW_WAIT_MIN : Duration = Duration :: from_millis ( 16 ) ;
25+ #[ cfg( target_os = "windows" ) ]
26+ const WINDOWS_STARTUP_MAXIMIZE_SHOW_WAIT_POLL : Duration = Duration :: from_millis ( 8 ) ;
2127
2228static AGENT_COMPANION_WINDOW_OPS : OnceLock < tokio:: sync:: Mutex < ( ) > > = OnceLock :: new ( ) ;
2329static AGENT_COMPANION_WINDOW_LAST_POSITION : OnceLock < RwLock < Option < tauri:: LogicalPosition < f64 > > > > =
2430 OnceLock :: new ( ) ;
2531
32+ #[ cfg( target_os = "windows" ) ]
33+ #[ derive( Debug , Clone , Copy , PartialEq , Eq ) ]
34+ enum WindowsMaximizeShowWaitAction {
35+ Ready ,
36+ Sleep ( Duration ) ,
37+ TimedOut ,
38+ }
39+
40+ #[ cfg( target_os = "windows" ) ]
41+ fn windows_maximize_show_wait_action (
42+ is_maximized : Option < bool > ,
43+ elapsed : Duration ,
44+ ) -> WindowsMaximizeShowWaitAction {
45+ if is_maximized == Some ( true ) && elapsed >= WINDOWS_STARTUP_MAXIMIZE_SHOW_WAIT_MIN {
46+ return WindowsMaximizeShowWaitAction :: Ready ;
47+ }
48+
49+ if elapsed >= WINDOWS_STARTUP_MAXIMIZE_SHOW_WAIT_MAX {
50+ return WindowsMaximizeShowWaitAction :: TimedOut ;
51+ }
52+
53+ let target_wait = if is_maximized == Some ( true ) {
54+ WINDOWS_STARTUP_MAXIMIZE_SHOW_WAIT_MIN
55+ } else {
56+ WINDOWS_STARTUP_MAXIMIZE_SHOW_WAIT_MAX
57+ } ;
58+ WindowsMaximizeShowWaitAction :: Sleep (
59+ target_wait
60+ . saturating_sub ( elapsed)
61+ . min ( WINDOWS_STARTUP_MAXIMIZE_SHOW_WAIT_POLL ) ,
62+ )
63+ }
64+
65+ #[ cfg( target_os = "windows" ) ]
66+ fn read_windows_maximize_state (
67+ window : & tauri:: WebviewWindow ,
68+ logged_error : & mut bool ,
69+ ) -> Option < bool > {
70+ match window. is_maximized ( ) {
71+ Ok ( is_maximized) => Some ( is_maximized) ,
72+ Err ( error) => {
73+ if !* logged_error {
74+ warn ! (
75+ "Failed to read main window maximize state during startup show wait: {}" ,
76+ error
77+ ) ;
78+ * logged_error = true ;
79+ }
80+ None
81+ }
82+ }
83+ }
84+
85+ #[ cfg( target_os = "windows" ) ]
86+ fn wait_for_windows_maximize_before_show ( window : & tauri:: WebviewWindow ) -> & ' static str {
87+ let started_at = Instant :: now ( ) ;
88+ let mut logged_error = false ;
89+
90+ loop {
91+ match windows_maximize_show_wait_action (
92+ read_windows_maximize_state ( window, & mut logged_error) ,
93+ started_at. elapsed ( ) ,
94+ ) {
95+ WindowsMaximizeShowWaitAction :: Ready => return "ready" ,
96+ WindowsMaximizeShowWaitAction :: TimedOut => return "timeout" ,
97+ WindowsMaximizeShowWaitAction :: Sleep ( duration) => std:: thread:: sleep ( duration) ,
98+ }
99+ }
100+ }
101+
102+ #[ cfg( target_os = "windows" ) ]
103+ async fn wait_for_windows_maximize_before_show_async (
104+ window : & tauri:: WebviewWindow ,
105+ ) -> & ' static str {
106+ let started_at = Instant :: now ( ) ;
107+ let mut logged_error = false ;
108+
109+ loop {
110+ match windows_maximize_show_wait_action (
111+ read_windows_maximize_state ( window, & mut logged_error) ,
112+ started_at. elapsed ( ) ,
113+ ) {
114+ WindowsMaximizeShowWaitAction :: Ready => return "ready" ,
115+ WindowsMaximizeShowWaitAction :: TimedOut => return "timeout" ,
116+ WindowsMaximizeShowWaitAction :: Sleep ( duration) => tokio:: time:: sleep ( duration) . await ,
117+ }
118+ }
119+ }
120+
26121fn agent_companion_window_ops ( ) -> & ' static tokio:: sync:: Mutex < ( ) > {
27122 AGENT_COMPANION_WINDOW_OPS . get_or_init ( || tokio:: sync:: Mutex :: new ( ( ) ) )
28123}
@@ -568,12 +663,18 @@ fn show_main_window_for_startup(
568663 ) ;
569664 }
570665 let show_delay_started_at = Instant :: now ( ) ;
571- std :: thread :: sleep ( std :: time :: Duration :: from_millis ( 150 ) ) ;
666+ let show_wait_outcome = wait_for_windows_maximize_before_show ( window ) ;
572667 startup_trace. record_elapsed_step (
573668 "native_window" ,
574669 "windows_show_after_maximize_wait" ,
575670 show_delay_started_at,
576671 ) ;
672+ debug ! (
673+ "Main window startup show step completed: step=wait_for_maximize_state outcome={} duration_ms={} since_create_start_ms={}" ,
674+ show_wait_outcome,
675+ show_delay_started_at. elapsed( ) . as_millis( ) ,
676+ total_started_at. elapsed( ) . as_millis( )
677+ ) ;
577678 }
578679
579680 let show_started_at = Instant :: now ( ) ;
@@ -915,7 +1016,13 @@ pub async fn show_main_window(app: tauri::AppHandle) -> Result<(), String> {
9151016 step_started_at. elapsed( ) . as_millis( )
9161017 ) ;
9171018
918- tokio:: time:: sleep ( std:: time:: Duration :: from_millis ( 150 ) ) . await ;
1019+ let wait_started_at = Instant :: now ( ) ;
1020+ let show_wait_outcome = wait_for_windows_maximize_before_show_async ( & main_window) . await ;
1021+ debug ! (
1022+ "Main window show step completed: step=wait_for_maximize_state outcome={} duration_ms={}" ,
1023+ show_wait_outcome,
1024+ wait_started_at. elapsed( ) . as_millis( )
1025+ ) ;
9191026 }
9201027
9211028 let step_started_at = Instant :: now ( ) ;
@@ -954,3 +1061,43 @@ pub async fn show_main_window(app: tauri::AppHandle) -> Result<(), String> {
9541061 ) ;
9551062 Ok ( ( ) )
9561063}
1064+
1065+ #[ cfg( test) ]
1066+ mod tests {
1067+ use super :: * ;
1068+
1069+ #[ cfg( target_os = "windows" ) ]
1070+ #[ test]
1071+ fn windows_maximize_show_wait_releases_when_maximized ( ) {
1072+ assert_eq ! (
1073+ windows_maximize_show_wait_action( Some ( true ) , Duration :: ZERO ) ,
1074+ WindowsMaximizeShowWaitAction :: Sleep ( WINDOWS_STARTUP_MAXIMIZE_SHOW_WAIT_POLL )
1075+ ) ;
1076+ assert_eq ! (
1077+ windows_maximize_show_wait_action( Some ( true ) , WINDOWS_STARTUP_MAXIMIZE_SHOW_WAIT_MIN ) ,
1078+ WindowsMaximizeShowWaitAction :: Ready
1079+ ) ;
1080+ }
1081+
1082+ #[ cfg( target_os = "windows" ) ]
1083+ #[ test]
1084+ fn windows_maximize_show_wait_polls_until_max_wait ( ) {
1085+ assert_eq ! (
1086+ windows_maximize_show_wait_action( Some ( false ) , Duration :: from_millis( 20 ) ) ,
1087+ WindowsMaximizeShowWaitAction :: Sleep ( WINDOWS_STARTUP_MAXIMIZE_SHOW_WAIT_POLL )
1088+ ) ;
1089+ assert_eq ! (
1090+ windows_maximize_show_wait_action( None , Duration :: from_millis( 148 ) ) ,
1091+ WindowsMaximizeShowWaitAction :: Sleep ( Duration :: from_millis( 2 ) )
1092+ ) ;
1093+ }
1094+
1095+ #[ cfg( target_os = "windows" ) ]
1096+ #[ test]
1097+ fn windows_maximize_show_wait_times_out_at_original_bound ( ) {
1098+ assert_eq ! (
1099+ windows_maximize_show_wait_action( Some ( false ) , WINDOWS_STARTUP_MAXIMIZE_SHOW_WAIT_MAX ) ,
1100+ WindowsMaximizeShowWaitAction :: TimedOut
1101+ ) ;
1102+ }
1103+ }
0 commit comments