@@ -3,11 +3,20 @@ mod config;
33use config:: { AppConfig , WindowState } ;
44use tauri:: Manager ;
55
6+ const APP_VERSION : & str = "3.0.7" ;
7+ const APP_REPO_URL : & str = "https://github.com/AtmanActive/Tauri2_Any_WebApp_Wrapper" ;
8+
69#[ cfg_attr( mobile, tauri:: mobile_entry_point) ]
710pub fn run ( ) {
811 // Load config early — before Tauri creates the webview — so we can set
912 // environment variables that affect WebView2 initialization.
10- let config = AppConfig :: load ( ) . expect ( "Failed to load config.json" ) ;
13+ let config = match AppConfig :: load ( ) {
14+ Ok ( c) => c,
15+ Err ( e) => {
16+ show_config_error ( & e. to_string ( ) ) ;
17+ std:: process:: exit ( 1 ) ;
18+ }
19+ } ;
1120
1221 // Single-instance enforcement (before any window is created)
1322 if let Some ( mode) = config. instance_mode ( ) {
@@ -57,6 +66,9 @@ pub fn run() {
5766 }
5867 }
5968
69+ // Add "About" item to the system menu (window icon menu)
70+ setup_system_menu ( & window) ;
71+
6072 // Register WebView2 handlers (title sync + color scheme preference)
6173 let title_window = window. clone ( ) ;
6274 let has_static_title = !config. title . is_empty ( ) ;
@@ -456,3 +468,116 @@ fn setup_webview_handlers(
456468) {
457469 // WebView2 APIs are Windows-only; color scheme and title sync are no-ops on other platforms
458470}
471+
472+ /// Show a native error dialog when the config file cannot be loaded.
473+ #[ cfg( target_os = "windows" ) ]
474+ fn show_config_error ( _error : & str ) {
475+ use windows:: Win32 :: UI :: WindowsAndMessaging :: { MessageBoxW , MB_OK , MB_ICONERROR } ;
476+
477+ let config_name = AppConfig :: config_filename ( ) ;
478+ let message = format ! (
479+ "Could not load configuration file.\n \n \
480+ Expected file: {}\n \
481+ Place it next to the executable.\n \n \
482+ Minimum required content:\n \n \
483+ {{\n \" url\" : \" https://example.com\" \n }}" ,
484+ config_name
485+ ) ;
486+
487+ let caption: Vec < u16 > = "Configuration Error\0 " . encode_utf16 ( ) . collect ( ) ;
488+ let text: Vec < u16 > = message. encode_utf16 ( ) . chain ( std:: iter:: once ( 0 ) ) . collect ( ) ;
489+
490+ unsafe {
491+ let _ = MessageBoxW (
492+ None ,
493+ windows:: core:: PCWSTR ( text. as_ptr ( ) ) ,
494+ windows:: core:: PCWSTR ( caption. as_ptr ( ) ) ,
495+ MB_OK | MB_ICONERROR ,
496+ ) ;
497+ }
498+ }
499+
500+ #[ cfg( not( target_os = "windows" ) ) ]
501+ fn show_config_error ( _error : & str ) {
502+ let config_name = AppConfig :: config_filename ( ) ;
503+ eprintln ! (
504+ "Could not load configuration file.\n \n \
505+ Expected file: {}\n \
506+ Place it next to the executable.\n \n \
507+ Minimum required content:\n \n \
508+ {{\n \" url\" : \" https://example.com\" \n }}" ,
509+ config_name
510+ ) ;
511+ }
512+
513+ /// Custom command ID for the "About" item in the system menu (window icon menu).
514+ /// Must be below 0xF000 and above standard SC_* values to avoid conflicts.
515+ #[ cfg( target_os = "windows" ) ]
516+ const SC_ABOUT : usize = 0x0010 ;
517+
518+ /// Add a custom "Tauri WebApp on Demand vX.Y.Z" item to the window's system menu
519+ /// and subclass the window to handle clicks on it.
520+ #[ cfg( target_os = "windows" ) ]
521+ fn setup_system_menu ( window : & tauri:: WebviewWindow ) {
522+ use windows:: Win32 :: Foundation :: HWND ;
523+ use windows:: Win32 :: UI :: WindowsAndMessaging :: {
524+ AppendMenuW , GetSystemMenu , MF_SEPARATOR , MF_STRING ,
525+ } ;
526+ use windows:: Win32 :: UI :: Shell :: SetWindowSubclass ;
527+
528+ let Ok ( hwnd) = window. hwnd ( ) else { return } ;
529+ let hwnd = HWND ( hwnd. 0 as * mut _ ) ;
530+
531+ unsafe {
532+ let hmenu = GetSystemMenu ( hwnd, false ) ;
533+ if hmenu. is_invalid ( ) {
534+ return ;
535+ }
536+
537+ // Add separator + about item
538+ let _ = AppendMenuW ( hmenu, MF_SEPARATOR , 0 , None ) ;
539+ let label: Vec < u16 > = format ! ( "Tauri WebApp on Demand v{}" , APP_VERSION )
540+ . encode_utf16 ( )
541+ . chain ( std:: iter:: once ( 0 ) )
542+ . collect ( ) ;
543+ let _ = AppendMenuW (
544+ hmenu,
545+ MF_STRING ,
546+ SC_ABOUT ,
547+ windows:: core:: PCWSTR ( label. as_ptr ( ) ) ,
548+ ) ;
549+
550+ // Subclass to intercept WM_SYSCOMMAND for our custom menu item
551+ let _ = SetWindowSubclass ( hwnd, Some ( sysmenu_subclass_proc) , 1 , 0 ) ;
552+ }
553+ }
554+
555+ #[ cfg( target_os = "windows" ) ]
556+ unsafe extern "system" fn sysmenu_subclass_proc (
557+ hwnd : windows:: Win32 :: Foundation :: HWND ,
558+ umsg : u32 ,
559+ wparam : windows:: Win32 :: Foundation :: WPARAM ,
560+ lparam : windows:: Win32 :: Foundation :: LPARAM ,
561+ _uidsubclass : usize ,
562+ _dwrefdata : usize ,
563+ ) -> windows:: Win32 :: Foundation :: LRESULT {
564+ use windows:: Win32 :: UI :: Shell :: DefSubclassProc ;
565+ use windows:: Win32 :: UI :: WindowsAndMessaging :: WM_SYSCOMMAND ;
566+
567+ if umsg == WM_SYSCOMMAND && wparam. 0 == SC_ABOUT {
568+ // Open the project URL in the default browser
569+ use std:: os:: windows:: process:: CommandExt ;
570+ let _ = std:: process:: Command :: new ( "cmd" )
571+ . args ( [ "/C" , "start" , "" , APP_REPO_URL ] )
572+ . creation_flags ( 0x08000000 ) // CREATE_NO_WINDOW
573+ . spawn ( ) ;
574+ return windows:: Win32 :: Foundation :: LRESULT ( 0 ) ;
575+ }
576+
577+ DefSubclassProc ( hwnd, umsg, wparam, lparam)
578+ }
579+
580+ #[ cfg( not( target_os = "windows" ) ) ]
581+ fn setup_system_menu ( _window : & tauri:: WebviewWindow ) {
582+ // System menu customization is Windows-only
583+ }
0 commit comments