33Desktop application entry point
44Cross-platform GUI using pywebview
55
6- Version: 1.2.0
7- - Improved cross-platform compatibility
8- - Added proper GUI backend selection
9- - Better error handling and logging
6+ Version: 1.3.0
7+ - Optimized startup using webview.start(func) pattern
8+ - Added window.events for proper lifecycle management
9+ - Background session pre-warming for faster first login
10+ - Platform-specific optimizations (Windows/macOS/Linux)
11+
12+ Based on pywebview best practices:
13+ - https://pywebview.flowrl.com/guide/usage.html
14+ - https://github.com/r0x0r/pywebview/issues/627
1015"""
1116
1217import logging
1318import os
1419import platform
1520import sys
16- from typing import Optional
21+ import threading
22+ from typing import Optional , Callable
1723
1824# Add parent directory to path for PyInstaller compatibility
1925if getattr (sys , 'frozen' , False ):
3642
3743# Application constants
3844APP_TITLE = 'BUAA Evaluation'
39- APP_VERSION = '1.2.1 '
45+ APP_VERSION = '1.3.0 '
4046WINDOW_WIDTH = 520
4147WINDOW_HEIGHT = 720
4248MIN_WIDTH = 400
@@ -66,37 +72,44 @@ def get_gui_backend() -> Optional[str]:
6672 Returns:
6773 GUI backend name or None for auto-detection
6874
69- Note: We let pywebview auto-detect the best backend.
70- Manual detection can slow down startup significantly.
75+ Platform-specific behavior:
76+ - Windows: Auto-detect (EdgeChromium > EdgeHTML > MSHTML)
77+ - macOS: Auto-detect (Cocoa WebKit)
78+ - Linux: Explicitly use GTK with WebKit2
7179 """
7280 system = platform .system ().lower ()
7381
7482 if system == 'linux' :
7583 # Linux: Explicitly use GTK with WebKit2
7684 return 'gtk'
7785 else :
78- # Windows/macOS: Let pywebview auto-detect
79- # This is faster than trying to import clr/pythonnet
86+ # Windows/macOS: Let pywebview auto-detect the best renderer
87+ # Avoid manual detection which can slow down startup
8088 return None
8189
8290
8391def setup_platform_specific () -> None :
84- """Apply platform-specific configurations"""
92+ """
93+ Apply platform-specific configurations before window creation
94+
95+ This runs on the main thread before webview.start()
96+ """
8597 system = platform .system ().lower ()
8698
8799 if system == 'darwin' :
88100 # macOS: Enable high-DPI support
89101 try :
90- from AppKit import NSApplication , NSApp
102+ from AppKit import NSApplication
91103 NSApplication .sharedApplication ()
92104 except ImportError :
93105 pass
94106
95107 elif system == 'windows' :
96- # Windows: Enable DPI awareness for crisp rendering
108+ # Windows: Enable DPI awareness for crisp rendering on 4K displays
97109 try :
98110 import ctypes
99- ctypes .windll .shcore .SetProcessDpiAwareness (2 ) # PROCESS_PER_MONITOR_DPI_AWARE
111+ # PROCESS_PER_MONITOR_DPI_AWARE = 2
112+ ctypes .windll .shcore .SetProcessDpiAwareness (2 )
100113 except (AttributeError , OSError ):
101114 try :
102115 ctypes .windll .user32 .SetProcessDPIAware ()
@@ -105,7 +118,12 @@ def setup_platform_specific() -> None:
105118
106119
107120def create_window (api : EvaluationAPI ) -> webview .Window :
108- """Create and configure the main application window"""
121+ """
122+ Create and configure the main application window
123+
124+ Window is created but not shown until webview.start() is called.
125+ This allows for faster perceived startup.
126+ """
109127 html_path = get_resource_path ('web/index.html' )
110128
111129 if not os .path .exists (html_path ):
@@ -127,26 +145,111 @@ def create_window(api: EvaluationAPI) -> webview.Window:
127145 return window
128146
129147
130- def on_closing (api : EvaluationAPI ) -> bool :
131- """Handle window close event"""
132- # Stop any running evaluation
133- api .stop_evaluation ()
134- return True
148+ def on_window_shown (api : EvaluationAPI ) -> Callable [[], None ]:
149+ """
150+ Factory function that returns window shown event handler
151+
152+ This runs when the window is first displayed to the user.
153+ Use this for non-critical initialization that can happen after UI shows.
154+ """
155+ def handler ():
156+ logger .info ("Window shown - starting background initialization" )
157+
158+ # Pre-warm session in background thread
159+ # This makes the first login faster
160+ def prewarm_session ():
161+ try :
162+ # Access the session property to trigger lazy initialization
163+ _ = api .session
164+ logger .info ("HTTP session pre-warmed successfully" )
165+ except Exception as e :
166+ logger .warning (f"Session pre-warm failed (non-critical): { e } " )
167+
168+ # Run in daemon thread so it doesn't block app exit
169+ threading .Thread (
170+ target = prewarm_session ,
171+ daemon = True ,
172+ name = "SessionPrewarm"
173+ ).start ()
174+
175+ return handler
176+
177+
178+ def on_window_loaded (window : webview .Window ) -> Callable [[], None ]:
179+ """
180+ Factory function that returns DOM loaded event handler
181+
182+ This runs when the frontend HTML/JS has fully loaded.
183+ """
184+ def handler ():
185+ logger .info ("Frontend loaded - DOM ready" )
186+ # Notify frontend that Python backend is ready
187+ try :
188+ window .evaluate_js ("window.dispatchEvent(new Event('pythonReady'))" )
189+ except Exception as e :
190+ logger .debug (f"Could not dispatch pythonReady event: { e } " )
191+
192+ return handler
193+
194+
195+ def on_window_closing (api : EvaluationAPI ) -> Callable [[], bool ]:
196+ """
197+ Factory function that returns window closing event handler
198+
199+ Returns True to allow closing, False to prevent.
200+ """
201+ def handler ():
202+ logger .info ("Window closing - cleaning up" )
203+ api .stop_evaluation ()
204+ return True
205+
206+ return handler
207+
208+
209+ def on_startup (window : webview .Window , api : EvaluationAPI ) -> None :
210+ """
211+ Startup callback executed in a separate thread after webview.start()
212+
213+ This is the recommended pywebview pattern for background initialization.
214+ The GUI loop is running, so the window stays responsive.
215+
216+ See: https://pywebview.flowrl.com/guide/usage.html
217+ """
218+ logger .info ("Startup callback running in background thread" )
219+
220+ # Register event handlers
221+ # Note: Events must be registered after start() is called
222+ window .events .shown += on_window_shown (api )
223+ window .events .loaded += on_window_loaded (window )
224+ window .events .closing += on_window_closing (api )
225+
226+ logger .info ("Event handlers registered" )
135227
136228
137229def main () -> None :
138- """Application entry point"""
230+ """
231+ Application entry point
232+
233+ Startup sequence:
234+ 1. Platform-specific setup (DPI awareness, etc.)
235+ 2. Create API instance (minimal initialization)
236+ 3. Create window (not shown yet)
237+ 4. Start webview with callback
238+ 5. Callback registers events and does background init
239+ 6. Window shows with loading spinner
240+ 7. Frontend loads and becomes interactive
241+ """
139242 logger .info (f"Starting { APP_TITLE } v{ APP_VERSION } " )
140243 logger .info (f"Platform: { platform .system ()} { platform .release ()} " )
141244 logger .info (f"Python: { sys .version } " )
142245
143- # Apply platform -specific setup
246+ # Step 1: Platform -specific setup (must be before window creation)
144247 setup_platform_specific ()
145248
146- # Initialize API
249+ # Step 2: Initialize API (minimal - uses lazy initialization)
147250 api = EvaluationAPI ()
148251
149- # Create window
252+ # Step 3: Create window
150253 try :
151254 window = create_window (api )
152255 except FileNotFoundError as e :
@@ -157,18 +260,19 @@ def main() -> None:
157260 # Store window reference for JavaScript callbacks
158261 api .set_window (window )
159262
160- # Set up closing handler
161- window .events .closing += lambda : on_closing (api )
162-
163- # Get optimal GUI backend
263+ # Step 4: Get optimal GUI backend
164264 gui = get_gui_backend ()
265+ logger .info (f"Using GUI backend: { gui or 'auto' } " )
165266
166- # Start application
167- logger .info (f"Starting webview with GUI: { gui or 'auto' } " )
267+ # Step 5: Start application with startup callback
268+ # The callback runs in a separate thread, keeping GUI responsive
269+ # See: https://pywebview.flowrl.com/guide/usage.html
168270 webview .start (
271+ func = on_startup ,
272+ args = (window , api ),
169273 debug = os .environ .get ('DEBUG' , '' ).lower () in ('1' , 'true' ),
170274 gui = gui ,
171- http_server = True , # Use HTTP server for better compatibility
275+ http_server = True , # Required for proper asset loading
172276 )
173277
174278 logger .info ("Application closed" )
0 commit comments