Skip to content

Commit 7e537a6

Browse files
AtmanActiveclaude
andcommitted
Add system menu About item and config error dialog
- Add "Tauri WebApp on Demand vX.Y.Z" to the window system menu (title bar icon menu) that opens the project GitHub page on click - Show a friendly native MessageBox when the config JSON is missing, explaining the expected filename and minimum required content - Bump version to 3.0.7 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 11f95e3 commit 7e537a6

3 files changed

Lines changed: 128 additions & 2 deletions

File tree

src-tauri/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,4 +29,5 @@ windows = { version = "0.61", features = [
2929
"Win32_System_Threading",
3030
"Win32_Foundation",
3131
"Win32_UI_WindowsAndMessaging",
32+
"Win32_UI_Shell",
3233
] }

src-tauri/src/config.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ impl AppConfig {
3636
Ok(config)
3737
}
3838

39-
fn config_filename() -> String {
39+
pub(crate) fn config_filename() -> String {
4040
// Derive config filename from the executable name: MyApp.exe -> MyApp.json
4141
std::env::current_exe()
4242
.ok()

src-tauri/src/lib.rs

Lines changed: 126 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,20 @@ mod config;
33
use config::{AppConfig, WindowState};
44
use 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)]
710
pub 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

Comments
 (0)