mirror of
				https://github.com/StuffAnThings/qbit_manage.git
				synced 2025-10-31 00:17:27 +08:00 
			
		
		
		
	refactor(desktop): extract constants and improve code organization
- Add constants for timeouts, ports, and Windows flags - Extract helper functions for binary resolution, menu building, and URL construction - Simplify conditional logic with functional programming patterns - Consolidate duplicate menu building code into reusable function - Improve error handling and reduce code duplication - Fix Windows registry API usage with proper byte array handling
This commit is contained in:
		
							parent
							
								
									86dc484584
								
							
						
					
					
						commit
						026e05e859
					
				
					 2 changed files with 154 additions and 162 deletions
				
			
		
							
								
								
									
										2
									
								
								VERSION
									
										
									
									
									
								
							
							
						
						
									
										2
									
								
								VERSION
									
										
									
									
									
								
							|  | @ -1 +1 @@ | |||
| 4.6.3-develop8 | ||||
| 4.6.3-develop9 | ||||
|  |  | |||
|  | @ -18,7 +18,19 @@ use tauri::{ | |||
| use tauri_plugin_single_instance::init as single_instance; | ||||
| use tokio::time::sleep; | ||||
| #[cfg(target_os = "windows")] | ||||
| use windows::Win32::System::Registry::*; | ||||
| use windows::{core::w, Win32::System::Registry::*}; | ||||
| 
 | ||||
| // Constants
 | ||||
| const DEFAULT_PORT: u16 = 8080; | ||||
| const SERVER_READY_TIMEOUT_SECS: u64 = 20; | ||||
| const SERVER_RESTART_TIMEOUT_SECS: u64 = 15; | ||||
| const PROCESS_WAIT_TIMEOUT_MS: u64 = 200; | ||||
| const GRACEFUL_SHUTDOWN_WAIT_MS: u64 = 50; | ||||
| const POLL_INTERVAL_MS: u64 = 10; | ||||
| const HTTP_POLL_INTERVAL_MS: u64 = 250; | ||||
| 
 | ||||
| #[cfg(target_os = "windows")] | ||||
| const CREATE_NO_WINDOW: u32 = 0x08000000; | ||||
| 
 | ||||
| static SERVER_STATE: Lazy<Arc<Mutex<Option<ServerProcess>>>> = Lazy::new(|| Arc::new(Mutex::new(None))); | ||||
| static SHOULD_EXIT: Lazy<Arc<Mutex<bool>>> = Lazy::new(|| Arc::new(Mutex::new(false))); | ||||
|  | @ -38,9 +50,13 @@ struct AppConfig { | |||
|   base_url: Option<String>, | ||||
| } | ||||
| 
 | ||||
| fn get_fallback_binary_name() -> &'static str { | ||||
|   if cfg!(target_os = "windows") { "qbit-manage.exe" } else { "qbit-manage" } | ||||
| } | ||||
| 
 | ||||
| fn app_config(app: &AppHandle) -> AppConfig { | ||||
|   // simple env-based configuration; could be read from a file later
 | ||||
|   let port = std::env::var("QBT_PORT").ok().and_then(|v| v.parse().ok()).unwrap_or(8080); | ||||
|   let port = std::env::var("QBT_PORT").ok().and_then(|v| v.parse().ok()).unwrap_or(DEFAULT_PORT); | ||||
|   let base_url = std::env::var("QBT_BASE_URL").ok().and_then(|v| { | ||||
|     let s = v.trim().to_string(); | ||||
|     if s.is_empty() { None } else { Some(s) } | ||||
|  | @ -53,16 +69,13 @@ fn app_config(app: &AppHandle) -> AppConfig { | |||
| } | ||||
| 
 | ||||
| fn load_minimize_setting(app: &AppHandle) -> bool { | ||||
|   if let Ok(data_dir) = app.path().app_data_dir() { | ||||
|   app.path().app_data_dir() | ||||
|     .ok() | ||||
|     .map(|data_dir| { | ||||
|       let file = data_dir.join("minimize_to_tray.txt"); | ||||
|     if file.exists() { | ||||
|       std::fs::read_to_string(&file).map(|s| s.trim() == "true").unwrap_or(false) | ||||
|     } else { | ||||
|       false | ||||
|     } | ||||
|   } else { | ||||
|     false | ||||
|   } | ||||
|       file.exists() && std::fs::read_to_string(&file).map_or(false, |s| s.trim() == "true") | ||||
|     }) | ||||
|     .unwrap_or(false) | ||||
| } | ||||
| 
 | ||||
| fn save_minimize_setting(app: &AppHandle, value: bool) { | ||||
|  | @ -75,7 +88,6 @@ fn save_minimize_setting(app: &AppHandle, value: bool) { | |||
| #[cfg(target_os = "windows")] | ||||
| fn is_startup_enabled() -> bool { | ||||
|   unsafe { | ||||
|     use windows::Win32::System::Registry::*; | ||||
|     let mut hkey = HKEY::default(); | ||||
|     if RegOpenKeyExW(HKEY_CURRENT_USER, w!("Software\\Microsoft\\Windows\\CurrentVersion\\Run"), 0, KEY_READ, &mut hkey).is_ok() { | ||||
|       let mut buffer = [0u16; 260]; | ||||
|  | @ -92,14 +104,14 @@ fn is_startup_enabled() -> bool { | |||
| #[cfg(target_os = "windows")] | ||||
| fn set_startup_enabled(enabled: bool) { | ||||
|   unsafe { | ||||
|     use windows::Win32::System::Registry::*; | ||||
|     let mut hkey = HKEY::default(); | ||||
|     if RegOpenKeyExW(HKEY_CURRENT_USER, w!("Software\\Microsoft\\Windows\\CurrentVersion\\Run"), 0, KEY_SET_VALUE, &mut hkey).is_ok() { | ||||
|       if enabled { | ||||
|         if let Ok(exe_path) = std::env::current_exe() { | ||||
|           if let Some(path_str) = exe_path.to_str() { | ||||
|             let wide_path: Vec<u16> = path_str.encode_utf16().chain(std::iter::once(0)).collect(); | ||||
|             let _ = RegSetValueExW(hkey, w!("qbit-manage-desktop"), 0, REG_SZ, Some(&wide_path)); | ||||
|             let data_bytes = std::slice::from_raw_parts(wide_path.as_ptr() as *const u8, wide_path.len() * 2); | ||||
|             let _ = RegSetValueExW(hkey, w!("qbit-manage-desktop"), 0, REG_SZ, Some(data_bytes)); | ||||
|           } | ||||
|         } | ||||
|       } else { | ||||
|  | @ -110,20 +122,25 @@ fn set_startup_enabled(enabled: bool) { | |||
|   } | ||||
| } | ||||
| 
 | ||||
| #[cfg(any(target_os = "macos", target_os = "linux"))] | ||||
| fn get_startup_file_path() -> Option<std::path::PathBuf> { | ||||
|   std::env::var("HOME").ok().map(|home| { | ||||
|     #[cfg(target_os = "macos")] | ||||
|     return std::path::PathBuf::from(format!("{}/Library/LaunchAgents/com.qbit-manage.desktop.plist", home)); | ||||
| 
 | ||||
|     #[cfg(target_os = "linux")] | ||||
|     return std::path::PathBuf::from(format!("{}/.config/autostart/qbit-manage.desktop", home)); | ||||
|   }) | ||||
| } | ||||
| 
 | ||||
| #[cfg(target_os = "macos")] | ||||
| fn is_startup_enabled() -> bool { | ||||
|   if let Ok(home) = std::env::var("HOME") { | ||||
|     let plist_path = format!("{}/Library/LaunchAgents/com.qbit-manage.desktop.plist", home); | ||||
|     std::path::Path::new(&plist_path).exists() | ||||
|   } else { | ||||
|     false | ||||
|   } | ||||
|   get_startup_file_path().map_or(false, |path| path.exists()) | ||||
| } | ||||
| 
 | ||||
| #[cfg(target_os = "macos")] | ||||
| fn set_startup_enabled(enabled: bool) { | ||||
|   if let Ok(home) = std::env::var("HOME") { | ||||
|     let plist_path = format!("{}/Library/LaunchAgents/com.qbit-manage.desktop.plist", home); | ||||
|   if let Some(plist_path) = get_startup_file_path() { | ||||
|     if enabled { | ||||
|       if let Ok(exe_path) = std::env::current_exe() { | ||||
|         if let Some(path_str) = exe_path.to_str() { | ||||
|  | @ -152,18 +169,12 @@ fn set_startup_enabled(enabled: bool) { | |||
| 
 | ||||
| #[cfg(target_os = "linux")] | ||||
| fn is_startup_enabled() -> bool { | ||||
|   if let Ok(home) = std::env::var("HOME") { | ||||
|     let desktop_path = format!("{}/.config/autostart/qbit-manage.desktop", home); | ||||
|     std::path::Path::new(&desktop_path).exists() | ||||
|   } else { | ||||
|     false | ||||
|   } | ||||
|   get_startup_file_path().map_or(false, |path| path.exists()) | ||||
| } | ||||
| 
 | ||||
| #[cfg(target_os = "linux")] | ||||
| fn set_startup_enabled(enabled: bool) { | ||||
|   if let Ok(home) = std::env::var("HOME") { | ||||
|     let desktop_path = format!("{}/.config/autostart/qbit-manage.desktop", home); | ||||
|   if let Some(desktop_path) = get_startup_file_path() { | ||||
|     if enabled { | ||||
|       if let Ok(exe_path) = std::env::current_exe() { | ||||
|         if let Some(path_str) = exe_path.to_str() { | ||||
|  | @ -175,8 +186,9 @@ NoDisplay=false | |||
| X-GNOME-Autostart-enabled=true | ||||
| Name=qbit-manage | ||||
| Comment=Start qbit-manage on login"#, path_str);
 | ||||
|           let dir = format!("{}/.config/autostart", home); | ||||
|           let _ = std::fs::create_dir_all(&dir); | ||||
|           if let Some(dir) = desktop_path.parent() { | ||||
|             let _ = std::fs::create_dir_all(dir); | ||||
|           } | ||||
|           let _ = std::fs::write(&desktop_path, desktop_content); | ||||
|         } | ||||
|       } | ||||
|  | @ -186,20 +198,45 @@ Comment=Start qbit-manage on login"#, path_str); | |||
|   } | ||||
| } | ||||
| 
 | ||||
| fn resolve_server_binary(app: &AppHandle) -> Option<std::path::PathBuf> { | ||||
|   // Priority:
 | ||||
|   // 1) QBM_SERVER_PATH env override
 | ||||
|   if let Ok(p) = std::env::var("QBM_SERVER_PATH") { | ||||
|     let candidate = std::path::PathBuf::from(p); | ||||
|     if candidate.exists() { | ||||
|       return Some(candidate); | ||||
| fn wait_for_process_exit(child: &mut Child, timeout: Duration) { | ||||
|   let start = std::time::Instant::now(); | ||||
|   while start.elapsed() < timeout { | ||||
|     match child.try_wait() { | ||||
|       Ok(Some(_)) => break, // Process has exited
 | ||||
|       Ok(None) => { | ||||
|         // Process still running, wait a bit more
 | ||||
|         std::thread::sleep(Duration::from_millis(POLL_INTERVAL_MS)); | ||||
|       } | ||||
|       Err(_) => break, // Error occurred, assume process is gone
 | ||||
|     } | ||||
|   } | ||||
| } | ||||
| 
 | ||||
|   // 2) resources/bin/{platform}/qbit-manage*
 | ||||
|   // 3) resources/ (same dir) qbit-manage*
 | ||||
|   // 4) current executable dir siblings
 | ||||
|   let bin_names = if cfg!(target_os = "windows") { | ||||
| fn build_server_url(port: u16, base_url: &Option<String>) -> String { | ||||
|   match base_url { | ||||
|     Some(b) if !b.trim().is_empty() => format!("http://127.0.0.1:{}/{}", port, b.trim().trim_start_matches('/')), | ||||
|     _ => format!("http://127.0.0.1:{}", port), | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| fn build_tray_menu(app: &AppHandle, minimize_to_tray: bool, startup_enabled: bool) -> Result<tauri::menu::Menu<tauri::Wry>, tauri::Error> { | ||||
|   let open_item = MenuItemBuilder::with_id("open", "Open").build(app)?; | ||||
|   let restart_item = MenuItemBuilder::with_id("restart", "Restart Server").build(app)?; | ||||
|   let minimize_item = CheckMenuItemBuilder::with_id("minimize_startup", "Minimize to Tray on Startup") | ||||
|     .checked(minimize_to_tray) | ||||
|     .build(app)?; | ||||
|   let startup_item = CheckMenuItemBuilder::with_id("startup", "Start on System Startup") | ||||
|     .checked(startup_enabled) | ||||
|     .build(app)?; | ||||
|   let quit_item = MenuItemBuilder::with_id("quit", "Quit").build(app)?; | ||||
| 
 | ||||
|   MenuBuilder::new(app) | ||||
|     .items(&[&open_item, &restart_item, &minimize_item, &startup_item, &quit_item]) | ||||
|     .build() | ||||
| } | ||||
| 
 | ||||
| fn get_binary_names() -> Vec<&'static str> { | ||||
|   if cfg!(target_os = "windows") { | ||||
|     vec!["qbit-manage.exe", "qbit-manage-windows-amd64.exe"] | ||||
|   } else { | ||||
|     vec![ | ||||
|  | @ -208,47 +245,50 @@ fn resolve_server_binary(app: &AppHandle) -> Option<std::path::PathBuf> { | |||
|       "qbit-manage-macos-x86_64", | ||||
|       "qbit-manage-macos-arm64" | ||||
|     ] | ||||
|   }; | ||||
| 
 | ||||
|   // resource dir (Tauri 2 path resolver)
 | ||||
|   if let Ok(resource_dir) = app.path().resource_dir() { | ||||
|     for name in &bin_names { | ||||
|       let p = resource_dir.join("bin").join(name); | ||||
|       if p.exists() { | ||||
|         return Some(p); | ||||
|       } | ||||
|     } | ||||
|     for name in &bin_names { | ||||
|       let p = resource_dir.join(name); | ||||
|       if p.exists() { | ||||
|         return Some(p); | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   // executable dir
 | ||||
|   if let Ok(exe) = std::env::current_exe() { | ||||
|     if let Some(mut exe_dir) = exe.parent().map(|p| p.to_path_buf()) { | ||||
|       for name in &bin_names { | ||||
|         let p = exe_dir.join(name); | ||||
|         if p.exists() { | ||||
|           return Some(p); | ||||
|         } | ||||
|       } | ||||
|       // try ../Resources
 | ||||
|       exe_dir = exe_dir.join(".."); | ||||
|       for name in &bin_names { | ||||
|         let p = exe_dir.join(name); | ||||
|         if p.exists() { | ||||
|           return Some(p); | ||||
|         } | ||||
|       } | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| fn find_binary_in_paths(paths: &[std::path::PathBuf], bin_names: &[&str]) -> Option<std::path::PathBuf> { | ||||
|   for path in paths { | ||||
|     for name in bin_names { | ||||
|       let candidate = path.join(name); | ||||
|       if candidate.exists() { | ||||
|         return Some(candidate); | ||||
|       } | ||||
|     } | ||||
|   } | ||||
|   None | ||||
| } | ||||
| 
 | ||||
| fn resolve_server_binary(app: &AppHandle) -> Option<std::path::PathBuf> { | ||||
|   // Priority: 1) QBM_SERVER_PATH env override
 | ||||
|   if let Ok(p) = std::env::var("QBM_SERVER_PATH") { | ||||
|     let candidate = std::path::PathBuf::from(p); | ||||
|     if candidate.exists() { | ||||
|       return Some(candidate); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   let bin_names = get_binary_names(); | ||||
|   let mut search_paths = Vec::new(); | ||||
| 
 | ||||
|   // Priority: 2) resource dir paths
 | ||||
|   if let Ok(resource_dir) = app.path().resource_dir() { | ||||
|     search_paths.push(resource_dir.join("bin")); | ||||
|     search_paths.push(resource_dir); | ||||
|   } | ||||
| 
 | ||||
|   // Priority: 3) executable dir and parent paths
 | ||||
|   if let Ok(exe) = std::env::current_exe() { | ||||
|     if let Some(exe_dir) = exe.parent() { | ||||
|       search_paths.push(exe_dir.to_path_buf()); | ||||
|       search_paths.push(exe_dir.join("..")); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   find_binary_in_paths(&search_paths, &bin_names) | ||||
| } | ||||
| 
 | ||||
| fn stop_server() { | ||||
|   if let Some(server_process) = SERVER_STATE.lock().unwrap().take() { | ||||
|     let mut child = server_process.child; | ||||
|  | @ -277,24 +317,14 @@ fn stop_server() { | |||
|     { | ||||
|       unsafe { libc::kill(pid as i32, libc::SIGTERM); } | ||||
|       // Very brief wait for graceful shutdown
 | ||||
|       std::thread::sleep(Duration::from_millis(50)); | ||||
|       std::thread::sleep(Duration::from_millis(GRACEFUL_SHUTDOWN_WAIT_MS)); | ||||
|       if child.try_wait().ok().flatten().is_none() { | ||||
|         let _ = child.kill(); | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     // Brief wait to ensure process termination, but don't wait too long
 | ||||
|     let start = std::time::Instant::now(); | ||||
|     while start.elapsed() < Duration::from_millis(200) { | ||||
|       match child.try_wait() { | ||||
|         Ok(Some(_)) => break, // Process has exited
 | ||||
|         Ok(None) => { | ||||
|           // Process still running, wait a bit more
 | ||||
|           std::thread::sleep(Duration::from_millis(10)); | ||||
|         } | ||||
|         Err(_) => break, // Error occurred, assume process is gone
 | ||||
|       } | ||||
|     } | ||||
|     wait_for_process_exit(&mut child, Duration::from_millis(PROCESS_WAIT_TIMEOUT_MS)); | ||||
| 
 | ||||
|     // Force kill if still running
 | ||||
|     let _ = child.kill(); | ||||
|  | @ -306,22 +336,26 @@ fn stop_server() { | |||
| fn terminate_process_tree_windows(pid: u32) { | ||||
|   use std::os::windows::process::CommandExt; | ||||
| 
 | ||||
|   let null_stdio = || (std::process::Stdio::null(), std::process::Stdio::null(), std::process::Stdio::null()); | ||||
| 
 | ||||
|   // Kill the process tree on Windows using taskkill with hidden window
 | ||||
|   let (stdin, stdout, stderr) = null_stdio(); | ||||
|   let _ = std::process::Command::new("taskkill") | ||||
|     .args(&["/F", "/T", "/PID", &pid.to_string()]) | ||||
|     .creation_flags(0x08000000) // CREATE_NO_WINDOW
 | ||||
|     .stdin(std::process::Stdio::null()) | ||||
|     .stdout(std::process::Stdio::null()) | ||||
|     .stderr(std::process::Stdio::null()) | ||||
|     .creation_flags(CREATE_NO_WINDOW) | ||||
|     .stdin(stdin) | ||||
|     .stdout(stdout) | ||||
|     .stderr(stderr) | ||||
|     .output(); | ||||
| 
 | ||||
|   // Also try direct process termination as backup with hidden window
 | ||||
|   let (stdin, stdout, stderr) = null_stdio(); | ||||
|   let _ = std::process::Command::new("taskkill") | ||||
|     .args(&["/F", "/IM", "qbit-manage-windows-amd64.exe"]) | ||||
|     .creation_flags(0x08000000) // CREATE_NO_WINDOW
 | ||||
|     .stdin(std::process::Stdio::null()) | ||||
|     .stdout(std::process::Stdio::null()) | ||||
|     .stderr(std::process::Stdio::null()) | ||||
|     .creation_flags(CREATE_NO_WINDOW) | ||||
|     .stdin(stdin) | ||||
|     .stdout(stdout) | ||||
|     .stderr(stderr) | ||||
|     .output(); | ||||
| } | ||||
| 
 | ||||
|  | @ -346,17 +380,13 @@ fn cleanup_and_exit_with_app(app: &AppHandle) { | |||
| 
 | ||||
| 
 | ||||
| async fn wait_until_ready(port: u16, base_url: &Option<String>, timeout: Duration) -> bool { | ||||
|   let client = reqwest::Client::builder().danger_accept_invalid_certs(true).build().ok(); | ||||
|   if client.is_none() { | ||||
|     return false; | ||||
|   } | ||||
|   let client = client.unwrap(); | ||||
| 
 | ||||
|   let url = match base_url { | ||||
|     Some(b) if !b.trim().is_empty() => format!("http://127.0.0.1:{}/{}", port, b.trim().trim_start_matches('/')), | ||||
|     _ => format!("http://127.0.0.1:{}", port), | ||||
|   let client = match reqwest::Client::builder().danger_accept_invalid_certs(true).build() { | ||||
|     Ok(client) => client, | ||||
|     Err(_) => return false, | ||||
|   }; | ||||
| 
 | ||||
|   let url = build_server_url(port, base_url); | ||||
| 
 | ||||
|   let start = std::time::Instant::now(); | ||||
|   while start.elapsed() < timeout { | ||||
|     if let Ok(resp) = client.get(&url).send().await { | ||||
|  | @ -364,7 +394,7 @@ async fn wait_until_ready(port: u16, base_url: &Option<String>, timeout: Duratio | |||
|         return true; | ||||
|       } | ||||
|     } | ||||
|     sleep(Duration::from_millis(250)).await; | ||||
|     sleep(Duration::from_millis(HTTP_POLL_INTERVAL_MS)).await; | ||||
|   } | ||||
|   false | ||||
| } | ||||
|  | @ -379,10 +409,7 @@ fn open_app_window(app: &AppHandle) { | |||
| 
 | ||||
| 
 | ||||
| fn redirect_to_server(app: &AppHandle, cfg: &AppConfig) { | ||||
|   let url = match &cfg.base_url { | ||||
|     Some(b) if !b.trim().is_empty() => format!("http://127.0.0.1:{}/{}", cfg.port, b.trim().trim_start_matches('/')), | ||||
|     _ => format!("http://127.0.0.1:{}", cfg.port), | ||||
|   }; | ||||
|   let url = build_server_url(cfg.port, &cfg.base_url); | ||||
|   if let Some(win) = app.get_webview_window("main") { | ||||
|     let _ = win.eval(&format!("window.location.replace('{}')", url)); | ||||
|   } | ||||
|  | @ -411,11 +438,7 @@ fn start_server(app: &AppHandle, cfg: &AppConfig) -> tauri::Result<()> { | |||
| 
 | ||||
|   let server_path = resolve_server_binary(app).unwrap_or_else(|| { | ||||
|     // fall back to expecting binary on PATH
 | ||||
|     if cfg!(target_os = "windows") { | ||||
|       std::path::PathBuf::from("qbit-manage.exe") | ||||
|     } else { | ||||
|       std::path::PathBuf::from("qbit-manage") | ||||
|     } | ||||
|     std::path::PathBuf::from(get_fallback_binary_name()) | ||||
|   }); | ||||
| 
 | ||||
|   // Create Windows Job Object if feature is enabled
 | ||||
|  | @ -462,7 +485,7 @@ fn start_server(app: &AppHandle, cfg: &AppConfig) -> tauri::Result<()> { | |||
|   #[cfg(target_os = "windows")] | ||||
|   { | ||||
|     use std::os::windows::process::CommandExt; | ||||
|     cmd.creation_flags(0x08000000); | ||||
|     cmd.creation_flags(CREATE_NO_WINDOW); | ||||
|   } | ||||
| 
 | ||||
|   let child = cmd.spawn()?; | ||||
|  | @ -521,18 +544,7 @@ pub fn run() { | |||
|       *STARTUP_ENABLED.lock().unwrap() = startup_enabled; | ||||
| 
 | ||||
|       // Build tray menu (v2 API)
 | ||||
|       let open_item = MenuItemBuilder::with_id("open", "Open").build(app)?; | ||||
|       let restart_item = MenuItemBuilder::with_id("restart", "Restart Server").build(app)?; | ||||
|       let minimize_item = CheckMenuItemBuilder::with_id("minimize_startup", "Minimize to Tray on Startup") | ||||
|         .checked(minimize_to_tray) | ||||
|         .build(app)?; | ||||
|       let startup_item = CheckMenuItemBuilder::with_id("startup", "Start on System Startup") | ||||
|         .checked(startup_enabled) | ||||
|         .build(app)?; | ||||
|       let quit_item = MenuItemBuilder::with_id("quit", "Quit").build(app)?; | ||||
|       let tray_menu = MenuBuilder::new(app) | ||||
|         .items(&[&open_item, &restart_item, &minimize_item, &startup_item, &quit_item]) | ||||
|         .build()?; | ||||
|       let tray_menu = build_tray_menu(app, minimize_to_tray, startup_enabled)?; | ||||
| 
 | ||||
|       // Create tray icon with explicit icon
 | ||||
|       let _tray_icon = TrayIconBuilder::new() | ||||
|  | @ -566,10 +578,10 @@ pub fn run() { | |||
|               // Start server in a separate thread to avoid blocking the UI
 | ||||
|               std::thread::spawn(move || { | ||||
|                 // Brief delay to ensure process cleanup
 | ||||
|                 std::thread::sleep(Duration::from_millis(200)); | ||||
|                 std::thread::sleep(Duration::from_millis(PROCESS_WAIT_TIMEOUT_MS)); | ||||
|                 if start_server(&app_handle_restart, &cfg).is_ok() { | ||||
|                   tauri::async_runtime::spawn(async move { | ||||
|                     if wait_until_ready(cfg.port, &cfg.base_url, Duration::from_secs(15)).await { | ||||
|                     if wait_until_ready(cfg.port, &cfg.base_url, Duration::from_secs(SERVER_RESTART_TIMEOUT_SECS)).await { | ||||
|                       redirect_to_server(&app_handle_restart, &cfg); | ||||
|                     } | ||||
|                   }); | ||||
|  | @ -584,21 +596,11 @@ pub fn run() { | |||
|               // Rebuild menu with updated checked state
 | ||||
|               let minimize_to_tray = *current; | ||||
|               let startup_enabled = *STARTUP_ENABLED.lock().unwrap(); | ||||
|               let open_item = MenuItemBuilder::with_id("open", "Open").build(app).unwrap(); | ||||
|               let restart_item = MenuItemBuilder::with_id("restart", "Restart Server").build(app).unwrap(); | ||||
|               let minimize_item = CheckMenuItemBuilder::with_id("minimize_startup", "Minimize to Tray on Startup") | ||||
|                 .checked(minimize_to_tray) | ||||
|                 .build(app).unwrap(); | ||||
|               let startup_item = CheckMenuItemBuilder::with_id("startup", "Start on System Startup") | ||||
|                 .checked(startup_enabled) | ||||
|                 .build(app).unwrap(); | ||||
|               let quit_item = MenuItemBuilder::with_id("quit", "Quit").build(app).unwrap(); | ||||
|               let tray_menu = MenuBuilder::new(app) | ||||
|                 .items(&[&open_item, &restart_item, &minimize_item, &startup_item, &quit_item]) | ||||
|                 .build().unwrap(); | ||||
| 
 | ||||
|               if let Ok(tray_menu) = build_tray_menu(app, minimize_to_tray, startup_enabled) { | ||||
|                 if let Some(tray) = TRAY_HANDLE.lock().unwrap().as_ref() { | ||||
|                 tray.set_menu(Some(tray_menu)).unwrap(); | ||||
|                   let _ = tray.set_menu(Some(tray_menu)); | ||||
|                 } | ||||
|               } | ||||
|             } | ||||
|             "startup" => { | ||||
|  | @ -609,21 +611,11 @@ pub fn run() { | |||
|               // Rebuild menu with updated checked state
 | ||||
|               let minimize_to_tray = *MINIMIZE_TO_TRAY.lock().unwrap(); | ||||
|               let startup_enabled = *current; | ||||
|               let open_item = MenuItemBuilder::with_id("open", "Open").build(app).unwrap(); | ||||
|               let restart_item = MenuItemBuilder::with_id("restart", "Restart Server").build(app).unwrap(); | ||||
|               let minimize_item = CheckMenuItemBuilder::with_id("minimize_startup", "Minimize to Tray on Startup") | ||||
|                 .checked(minimize_to_tray) | ||||
|                 .build(app).unwrap(); | ||||
|               let startup_item = CheckMenuItemBuilder::with_id("startup", "Start on System Startup") | ||||
|                 .checked(startup_enabled) | ||||
|                 .build(app).unwrap(); | ||||
|               let quit_item = MenuItemBuilder::with_id("quit", "Quit").build(app).unwrap(); | ||||
|               let tray_menu = MenuBuilder::new(app) | ||||
|                 .items(&[&open_item, &restart_item, &minimize_item, &startup_item, &quit_item]) | ||||
|                 .build().unwrap(); | ||||
| 
 | ||||
|               if let Ok(tray_menu) = build_tray_menu(app, minimize_to_tray, startup_enabled) { | ||||
|                 if let Some(tray) = TRAY_HANDLE.lock().unwrap().as_ref() { | ||||
|                 tray.set_menu(Some(tray_menu)).unwrap(); | ||||
|                   let _ = tray.set_menu(Some(tray_menu)); | ||||
|                 } | ||||
|               } | ||||
|             } | ||||
|             "quit" => { | ||||
|  | @ -659,7 +651,7 @@ pub fn run() { | |||
|       let app_handle3 = app_handle.clone(); | ||||
|       tauri::async_runtime::spawn(async move { | ||||
|         let _ = start_server(&app_handle3, &cfg); | ||||
|         if wait_until_ready(cfg.port, &cfg.base_url, Duration::from_secs(20)).await { | ||||
|         if wait_until_ready(cfg.port, &cfg.base_url, Duration::from_secs(SERVER_READY_TIMEOUT_SECS)).await { | ||||
|           redirect_to_server(&app_handle3, &cfg); | ||||
|         } | ||||
|       }); | ||||
|  |  | |||
		Loading…
	
	Add table
		
		Reference in a new issue