From 165018bcbc7c00fbc7acd66947a45a226a8b21ce Mon Sep 17 00:00:00 2001 From: Tunglies <77394545+Tunglies@users.noreply.github.com> Date: Fri, 29 Aug 2025 05:48:37 +0800 Subject: [PATCH] feat: Implement DNS management for macOS - Added `set_public_dns` and `restore_public_dns` functions in `dns.rs` to manage system DNS settings. - Introduced `resolve` module to encapsulate DNS and scheme resolution functionalities. - Implemented `resolve_scheme` function in `scheme.rs` to handle deep links and profile imports. - Created UI readiness management in `ui.rs` to track and update UI loading states. - Developed window management logic in `window.rs` to handle window creation and visibility. - Added initial loading overlay script in `window_script.rs` for better user experience during startup. - Updated server handling in `server.rs` to integrate new resolve functionalities. - Refactored window creation calls in `window_manager.rs` to use the new window management logic. --- src-tauri/src/cmd/app.rs | 8 +- src-tauri/src/core/tray/mod.rs | 24 +- src-tauri/src/enhance/tun.rs | 6 +- src-tauri/src/feat/window.rs | 2 +- src-tauri/src/lib.rs | 23 +- src-tauri/src/module/lightweight.rs | 9 +- src-tauri/src/utils/network.rs | 9 +- src-tauri/src/utils/resolve.rs | 737 ------------------- src-tauri/src/utils/resolve/dns.rs | 94 +++ src-tauri/src/utils/resolve/mod.rs | 186 +++++ src-tauri/src/utils/resolve/scheme.rs | 82 +++ src-tauri/src/utils/resolve/ui.rs | 74 ++ src-tauri/src/utils/resolve/window.rs | 291 ++++++++ src-tauri/src/utils/resolve/window_script.rs | 71 ++ src-tauri/src/utils/server.rs | 7 +- src-tauri/src/utils/window_manager.rs | 2 +- 16 files changed, 837 insertions(+), 788 deletions(-) delete mode 100644 src-tauri/src/utils/resolve.rs create mode 100644 src-tauri/src/utils/resolve/dns.rs create mode 100644 src-tauri/src/utils/resolve/mod.rs create mode 100644 src-tauri/src/utils/resolve/scheme.rs create mode 100644 src-tauri/src/utils/resolve/ui.rs create mode 100644 src-tauri/src/utils/resolve/window.rs create mode 100644 src-tauri/src/utils/resolve/window_script.rs diff --git a/src-tauri/src/cmd/app.rs b/src-tauri/src/cmd/app.rs index 37e880dd..44b2cecf 100644 --- a/src-tauri/src/cmd/app.rs +++ b/src-tauri/src/cmd/app.rs @@ -209,7 +209,7 @@ pub fn copy_icon_file(path: String, icon_info: IconInfo) -> CmdResult { #[tauri::command] pub fn notify_ui_ready() -> CmdResult<()> { log::info!(target: "app", "前端UI已准备就绪"); - crate::utils::resolve::mark_ui_ready(); + crate::utils::resolve::ui::mark_ui_ready(); Ok(()) } @@ -218,7 +218,7 @@ pub fn notify_ui_ready() -> CmdResult<()> { pub fn update_ui_stage(stage: String) -> CmdResult<()> { log::info!(target: "app", "UI加载阶段更新: {stage}"); - use crate::utils::resolve::UiReadyStage; + use crate::utils::resolve::ui::UiReadyStage; let stage_enum = match stage.as_str() { "NotStarted" => UiReadyStage::NotStarted, @@ -232,7 +232,7 @@ pub fn update_ui_stage(stage: String) -> CmdResult<()> { } }; - crate::utils::resolve::update_ui_ready_stage(stage_enum); + crate::utils::resolve::ui::update_ui_ready_stage(stage_enum); Ok(()) } @@ -240,6 +240,6 @@ pub fn update_ui_stage(stage: String) -> CmdResult<()> { #[tauri::command] pub fn reset_ui_ready_state() -> CmdResult<()> { log::info!(target: "app", "重置UI就绪状态"); - crate::utils::resolve::reset_ui_ready(); + crate::utils::resolve::ui::reset_ui_ready(); Ok(()) } diff --git a/src-tauri/src/core/tray/mod.rs b/src-tauri/src/core/tray/mod.rs index 6ed15184..a7f308ea 100644 --- a/src-tauri/src/core/tray/mod.rs +++ b/src-tauri/src/core/tray/mod.rs @@ -10,7 +10,7 @@ use crate::{ feat, logging, module::lightweight::is_in_lightweight_mode, singleton_lazy, - utils::{dirs::find_target_icons, i18n::t, resolve::VERSION}, + utils::{dirs::find_target_icons, i18n::t}, Type, }; @@ -186,7 +186,11 @@ impl Default for Tray { singleton_lazy!(Tray, TRAY, Tray::default); impl Tray { - pub fn init(&self) -> Result<()> { + pub async fn init(&self) -> Result<()> { + let app_handle = handle::Handle::global() + .app_handle() + .ok_or_else(|| anyhow::anyhow!("Failed to get app handle for tray initialization"))?; + self.create_tray_from_handle(&app_handle).await?; Ok(()) } @@ -396,14 +400,6 @@ impl Tray { } }; - let version = match VERSION.get() { - Some(v) => v, - None => { - log::warn!(target: "app", "更新托盘提示失败: 版本信息不存在"); - return Ok(()); - } - }; - let verge = Config::verge().await.latest_ref().clone(); let system_proxy = verge.enable_system_proxy.as_ref().unwrap_or(&false); let tun_mode = verge.enable_tun_mode.as_ref().unwrap_or(&false); @@ -434,6 +430,7 @@ impl Tray { let tun_text = t("TUN").await; let profile_text = t("Profile").await; + let version = env!("CARGO_PKG_VERSION"); if let Some(tray) = app_handle.tray_by_id("main") { let _ = tray.set_tooltip(Some(&format!( "Clash Verge {version}\n{}: {}\n{}: {}\n{}: {}", @@ -460,10 +457,6 @@ impl Tray { Ok(()) } - /// 取消订阅 traffic 数据 - #[cfg(target_os = "macos")] - pub fn unsubscribe_traffic(&self) {} - pub async fn create_tray_from_handle(&self, app_handle: &AppHandle) -> Result<()> { log::info!(target: "app", "正在从AppHandle创建系统托盘"); @@ -567,8 +560,7 @@ async fn create_tray_menu( ) -> Result> { let mode = mode.unwrap_or(""); - let unknown_version = String::from("unknown"); - let version = VERSION.get().unwrap_or(&unknown_version); + let version = env!("CARGO_PKG_VERSION"); let hotkeys = Config::verge() .await diff --git a/src-tauri/src/enhance/tun.rs b/src-tauri/src/enhance/tun.rs index c8fec783..92f7a5eb 100644 --- a/src-tauri/src/enhance/tun.rs +++ b/src-tauri/src/enhance/tun.rs @@ -63,8 +63,8 @@ pub fn use_tun(mut config: Mapping, enable: bool) -> Mapping { #[cfg(target_os = "macos")] { AsyncHandler::spawn(move || async move { - crate::utils::resolve::restore_public_dns().await; - crate::utils::resolve::set_public_dns("223.6.6.6".to_string()).await; + crate::utils::resolve::dns::restore_public_dns().await; + crate::utils::resolve::dns::set_public_dns("223.6.6.6".to_string()).await; }); } } @@ -75,7 +75,7 @@ pub fn use_tun(mut config: Mapping, enable: bool) -> Mapping { // TUN未启用时,仅恢复系统DNS,不修改配置文件中的DNS设置 #[cfg(target_os = "macos")] AsyncHandler::spawn(move || async move { - crate::utils::resolve::restore_public_dns().await; + crate::utils::resolve::dns::restore_public_dns().await; }); } diff --git a/src-tauri/src/feat/window.rs b/src-tauri/src/feat/window.rs index 370bc2a0..b80b1b8a 100644 --- a/src-tauri/src/feat/window.rs +++ b/src-tauri/src/feat/window.rs @@ -166,7 +166,7 @@ async fn clean_async() -> bool { let dns_task = async { match timeout( Duration::from_millis(1000), - crate::utils::resolve::restore_public_dns(), + crate::utils::resolve::dns::restore_public_dns(), ) .await { diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index a14f56b5..8eb844ea 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -10,11 +10,7 @@ mod state; mod utils; #[cfg(target_os = "macos")] use crate::utils::window_manager::WindowManager; -use crate::{ - core::hotkey, - process::AsyncHandler, - utils::{resolve, resolve::resolve_scheme, server}, -}; +use crate::{core::hotkey, process::AsyncHandler, utils::server}; use config::Config; use parking_lot::Mutex; use tauri::AppHandle; @@ -28,7 +24,10 @@ use utils::logging::Type; /// Application initialization helper functions mod app_init { - use crate::core::handle; + use crate::{ + core::handle, + utils::resolve::{self, scheme::resolve_scheme}, + }; use super::*; @@ -134,16 +133,10 @@ mod app_init { } /// Initialize core components asynchronously - pub fn init_core_async(app_handle: &AppHandle) { - let app_handle = app_handle.clone(); + pub fn init_core_async() { AsyncHandler::spawn(move || async move { logging!(info, Type::Setup, true, "异步执行应用设置..."); - match timeout( - Duration::from_secs(30), - resolve::resolve_setup_async(&app_handle), - ) - .await - { + match timeout(Duration::from_secs(30), resolve::resolve_setup_async()).await { Ok(_) => { logging!(info, Type::Setup, true, "应用设置成功完成"); } @@ -372,7 +365,7 @@ pub fn run() { let app_handle = app.handle().clone(); // Initialize core components asynchronously - app_init::init_core_async(&app_handle); + app_init::init_core_async(); logging!(info, Type::Setup, true, "执行主要设置操作..."); diff --git a/src-tauri/src/module/lightweight.rs b/src-tauri/src/module/lightweight.rs index 48821e8a..d764b734 100644 --- a/src-tauri/src/module/lightweight.rs +++ b/src-tauri/src/module/lightweight.rs @@ -78,7 +78,7 @@ pub async fn run_once_auto_lightweight() { } } -pub async fn auto_lightweight_mode_init() { +pub async fn auto_lightweight_mode_init() -> Result<()> { if let Some(app_handle) = handle::Handle::global().app_handle() { // Check if state is available before accessing it if app_handle.try_state::>().is_none() { @@ -88,7 +88,7 @@ pub async fn auto_lightweight_mode_init() { true, "LightWeightState 尚未初始化,跳过自动轻量模式初始化" ); - return; + return Err(anyhow::anyhow!("LightWeightState not be initialized")); } let is_silent_start = @@ -114,9 +114,12 @@ pub async fn auto_lightweight_mode_init() { // 确保托盘状态更新 if let Err(e) = Tray::global().update_part().await { log::warn!("Failed to update tray: {e}"); + return Err(e); } } } + + Ok(()) } // 检查是否处于轻量模式 @@ -214,7 +217,7 @@ pub async fn exit_lightweight_mode() { handle::Handle::global().set_activation_policy_regular(); // 重置UI就绪状态 - crate::utils::resolve::reset_ui_ready(); + crate::utils::resolve::ui::reset_ui_ready(); // 更新托盘显示 let _tray = crate::core::tray::Tray::global(); diff --git a/src-tauri/src/utils/network.rs b/src-tauri/src/utils/network.rs index f4efa1cb..d7e8fb56 100644 --- a/src-tauri/src/utils/network.rs +++ b/src-tauri/src/utils/network.rs @@ -2,6 +2,7 @@ use anyhow::Result; use parking_lot::Mutex; use reqwest::{Client, ClientBuilder, Proxy, RequestBuilder, Response}; use std::{ + env, sync::{ atomic::{AtomicUsize, Ordering}, Arc, Once, @@ -244,12 +245,8 @@ impl NetworkManager { if let Some(ua) = user_agent { builder = builder.user_agent(ua); } else { - use crate::utils::resolve::VERSION; - - let version = match VERSION.get() { - Some(v) => format!("clash-verge/v{v}"), - None => "clash-verge/unknown".to_string(), - }; + let verge_version = env!("CARGO_PKG_VERSION"); + let version = format!("clash-verge/v{verge_version}"); builder = builder.user_agent(version); } diff --git a/src-tauri/src/utils/resolve.rs b/src-tauri/src/utils/resolve.rs deleted file mode 100644 index e2c85a3e..00000000 --- a/src-tauri/src/utils/resolve.rs +++ /dev/null @@ -1,737 +0,0 @@ -use crate::{ - config::{Config, PrfItem}, - core::*, - logging, logging_error, - module::lightweight::{self, auto_lightweight_mode_init}, - process::AsyncHandler, - utils::{init, logging::Type, server}, - wrap_err, -}; -use anyhow::{bail, Result}; -use once_cell::sync::OnceCell; -use parking_lot::{Mutex, RwLock}; -use percent_encoding::percent_decode_str; -use scopeguard; -use std::time::{Duration, Instant}; -use tauri::{AppHandle, Manager}; - -use tauri::Url; -//#[cfg(not(target_os = "linux"))] -// use window_shadows::set_shadow; - -pub static VERSION: OnceCell = OnceCell::new(); - -// 定义默认窗口尺寸常量 -const DEFAULT_WIDTH: u32 = 940; -const DEFAULT_HEIGHT: u32 = 700; - -// 添加全局UI准备就绪标志 -static UI_READY: OnceCell> = OnceCell::new(); - -// 窗口创建锁,防止并发创建窗口 -static WINDOW_CREATING: OnceCell> = OnceCell::new(); - -// UI就绪阶段状态枚举 -#[derive(Debug, Clone, Copy, PartialEq)] -pub enum UiReadyStage { - NotStarted, - Loading, - DomReady, - ResourcesLoaded, - Ready, -} - -// UI就绪详细状态 -#[derive(Debug)] -struct UiReadyState { - stage: RwLock, -} - -impl Default for UiReadyState { - fn default() -> Self { - Self { - stage: RwLock::new(UiReadyStage::NotStarted), - } - } -} - -// 获取UI就绪状态细节 -static UI_READY_STATE: OnceCell = OnceCell::new(); - -fn get_window_creating_lock() -> &'static Mutex<(bool, Instant)> { - WINDOW_CREATING.get_or_init(|| Mutex::new((false, Instant::now()))) -} - -fn get_ui_ready() -> &'static RwLock { - UI_READY.get_or_init(|| RwLock::new(false)) -} - -fn get_ui_ready_state() -> &'static UiReadyState { - UI_READY_STATE.get_or_init(UiReadyState::default) -} - -// 更新UI准备阶段 -pub fn update_ui_ready_stage(stage: UiReadyStage) { - let state = get_ui_ready_state(); - let mut stage_lock = state.stage.write(); - - *stage_lock = stage; - // 如果是最终阶段,标记UI完全就绪 - if stage == UiReadyStage::Ready { - mark_ui_ready(); - } -} - -// 标记UI已准备就绪 -pub fn mark_ui_ready() { - let mut ready = get_ui_ready().write(); - *ready = true; - logging!(info, Type::Window, true, "UI已标记为完全就绪"); -} - -// 重置UI就绪状态 -pub fn reset_ui_ready() { - { - let mut ready = get_ui_ready().write(); - *ready = false; - } - { - let state = get_ui_ready_state(); - let mut stage = state.stage.write(); - *stage = UiReadyStage::NotStarted; - } - logging!(info, Type::Window, true, "UI就绪状态已重置"); -} - -/// 异步方式处理启动后的额外任务 -pub async fn resolve_setup_async(app_handle: &AppHandle) { - let start_time = std::time::Instant::now(); - logging!( - info, - Type::Setup, - true, - "开始执行异步设置任务... 线程ID: {:?}", - std::thread::current().id() - ); - - if VERSION.get().is_none() { - let version = app_handle.package_info().version.to_string(); - VERSION.get_or_init(|| { - logging!(info, Type::Setup, true, "初始化版本信息: {}", version); - version.clone() - }); - } - - logging_error!(Type::Setup, true, init::init_scheme()); - - logging_error!(Type::Setup, true, init::startup_script().await); - - logging!( - info, - Type::Config, - true, - "开始初始化配置... 线程ID: {:?}", - std::thread::current().id() - ); - logging_error!(Type::Config, true, Config::init_config().await); - logging!(info, Type::Config, true, "配置初始化完成"); - - logging!(trace, Type::Core, true, "启动核心管理器..."); - logging_error!(Type::Core, true, CoreManager::global().init().await); - - log::trace!(target: "app", "启动内嵌服务器..."); - server::embed_server(); - - logging!(trace, Type::Core, true, "启动 IPC 监控服务..."); - - logging_error!(Type::Tray, true, tray::Tray::global().init()); - - if let Some(app_handle) = handle::Handle::global().app_handle() { - logging!(info, Type::Tray, true, "创建系统托盘..."); - let result = tray::Tray::global() - .create_tray_from_handle(&app_handle) - .await; - if result.is_ok() { - logging!(info, Type::Tray, true, "系统托盘创建成功"); - } else if let Err(e) = result { - logging!(error, Type::Tray, true, "系统托盘创建失败: {}", e); - } - } else { - logging!( - error, - Type::Tray, - true, - "无法创建系统托盘: app_handle不存在" - ); - } - - // 更新系统代理 - logging_error!( - Type::System, - true, - sysopt::Sysopt::global().update_sysproxy().await - ); - logging_error!( - Type::System, - true, - sysopt::Sysopt::global().init_guard_sysproxy() - ); - - // 创建窗口 - let is_silent_start = - { Config::verge().await.latest_ref().enable_silent_start }.unwrap_or(false); - #[cfg(target_os = "macos")] - { - if is_silent_start { - handle::Handle::global().set_activation_policy_accessory(); - } - } - create_window(!is_silent_start).await; - - // 初始化定时器 - logging_error!(Type::System, true, timer::Timer::global().init().await); - - // 自动进入轻量模式 - auto_lightweight_mode_init().await; - - logging_error!(Type::Tray, true, tray::Tray::global().update_part().await); - - logging!(trace, Type::System, true, "初始化热键..."); - logging_error!(Type::System, true, hotkey::Hotkey::global().init().await); - - let elapsed = start_time.elapsed(); - logging!( - info, - Type::Setup, - true, - "异步设置任务完成,耗时: {:?}", - elapsed - ); - - // 如果初始化时间过长,记录警告 - if elapsed.as_secs() > 10 { - logging!( - warn, - Type::Setup, - true, - "异步设置任务耗时较长({:?})", - elapsed - ); - } -} - -/// reset system proxy (异步) -pub async fn resolve_reset_async() { - #[cfg(target_os = "macos")] - logging!(info, Type::Tray, true, "Unsubscribing from traffic updates"); - #[cfg(target_os = "macos")] - tray::Tray::global().unsubscribe_traffic(); - - logging_error!( - Type::System, - true, - sysopt::Sysopt::global().reset_sysproxy().await - ); - logging_error!(Type::Core, true, CoreManager::global().stop_core().await); - #[cfg(target_os = "macos")] - { - logging!(info, Type::System, true, "Restoring system DNS settings"); - restore_public_dns().await; - } -} - -/// Create the main window -pub async fn create_window(is_show: bool) -> bool { - logging!( - info, - Type::Window, - true, - "开始创建/显示主窗口, is_show={}", - is_show - ); - - if !is_show { - logging!(info, Type::Window, true, "静默模式启动时不创建窗口"); - lightweight::set_lightweight_mode(true).await; - handle::Handle::notify_startup_completed(); - return false; - } - - if let Some(app_handle) = handle::Handle::global().app_handle() { - if let Some(window) = app_handle.get_webview_window("main") { - logging!(info, Type::Window, true, "主窗口已存在,将显示现有窗口"); - if is_show { - if window.is_minimized().unwrap_or(false) { - logging!(info, Type::Window, true, "窗口已最小化,正在取消最小化"); - let _ = window.unminimize(); - } - let _ = window.show(); - let _ = window.set_focus(); - - #[cfg(target_os = "macos")] - handle::Handle::global().set_activation_policy_regular(); - } - return true; - } - } - - let creating_lock = get_window_creating_lock(); - let mut creating = creating_lock.lock(); - - let (is_creating, last_time) = *creating; - let elapsed = last_time.elapsed(); - - if is_creating && elapsed < Duration::from_secs(2) { - logging!( - info, - Type::Window, - true, - "窗口创建请求被忽略,因为最近创建过 ({:?}ms)", - elapsed.as_millis() - ); - return false; - } - - *creating = (true, Instant::now()); - - // ScopeGuard 确保创建状态重置,防止 webview 卡死 - let _guard = scopeguard::guard(creating, |mut creating_guard| { - *creating_guard = (false, Instant::now()); - logging!(debug, Type::Window, true, "[ScopeGuard] 窗口创建状态已重置"); - }); - - let app_handle = match handle::Handle::global().app_handle() { - Some(handle) => handle, - None => { - logging!( - error, - Type::Window, - true, - "无法获取app_handle,窗口创建失败" - ); - return false; - } - }; - - match tauri::WebviewWindowBuilder::new( - &app_handle, - "main", /* the unique window label */ - tauri::WebviewUrl::App("index.html".into()), - ) - .title("Clash Verge") - .center() - .decorations(true) - .fullscreen(false) - .inner_size(DEFAULT_WIDTH as f64, DEFAULT_HEIGHT as f64) - .min_inner_size(520.0, 520.0) - .visible(true) // 立即显示窗口,避免用户等待 - .initialization_script( - r#" - console.log('[Tauri] 窗口初始化脚本开始执行'); - - function createLoadingOverlay() { - - if (document.getElementById('initial-loading-overlay')) { - console.log('[Tauri] 加载指示器已存在'); - return; - } - - console.log('[Tauri] 创建加载指示器'); - const loadingDiv = document.createElement('div'); - loadingDiv.id = 'initial-loading-overlay'; - loadingDiv.innerHTML = ` -
-
-
-
-
Loading Clash Verge...
-
- - `; - - if (document.body) { - document.body.appendChild(loadingDiv); - } else { - document.addEventListener('DOMContentLoaded', () => { - if (document.body && !document.getElementById('initial-loading-overlay')) { - document.body.appendChild(loadingDiv); - } - }); - } - } - - createLoadingOverlay(); - - if (document.readyState === 'loading') { - document.addEventListener('DOMContentLoaded', createLoadingOverlay); - } else { - createLoadingOverlay(); - } - - console.log('[Tauri] 窗口初始化脚本执行完成'); - "#, - ) - .build() - { - Ok(newly_created_window) => { - logging!(debug, Type::Window, true, "主窗口实例创建成功"); - - update_ui_ready_stage(UiReadyStage::NotStarted); - - AsyncHandler::spawn(move || async move { - handle::Handle::global().mark_startup_completed(); - logging!( - debug, - Type::Window, - true, - "异步窗口任务开始 (启动已标记完成)" - ); - - // 先运行轻量模式检测 - lightweight::run_once_auto_lightweight().await; - - // 发送启动完成事件,触发前端开始加载 - logging!( - debug, - Type::Window, - true, - "发送 verge://startup-completed 事件" - ); - handle::Handle::notify_startup_completed(); - - if is_show { - let window_clone = newly_created_window.clone(); - - // 立即显示窗口 - let _ = window_clone.show(); - let _ = window_clone.set_focus(); - logging!(info, Type::Window, true, "窗口已立即显示"); - #[cfg(target_os = "macos")] - handle::Handle::global().set_activation_policy_regular(); - - let timeout_seconds = if crate::module::lightweight::is_in_lightweight_mode() { - 3 - } else { - 8 - }; - - logging!( - info, - Type::Window, - true, - "开始监控UI加载状态 (最多{}秒)...", - timeout_seconds - ); - - // 异步监控UI状态,使用try_read避免死锁 - AsyncHandler::spawn(move || async move { - logging!( - debug, - Type::Window, - true, - "启动UI状态监控线程,超时{}秒", - timeout_seconds - ); - - let ui_ready_checker = || async { - let (mut check_count, mut consecutive_failures) = (0, 0); - - loop { - let is_ready = get_ui_ready() - .try_read() - .map(|guard| *guard) - .unwrap_or_else(|| { - consecutive_failures += 1; - if consecutive_failures > 50 { - logging!( - warn, - Type::Window, - true, - "UI状态监控连续{}次无法获取读锁,可能存在死锁", - consecutive_failures - ); - consecutive_failures = 0; - } - false - }); - - if is_ready { - logging!( - debug, - Type::Window, - true, - "UI状态监控检测到就绪信号,退出监控" - ); - return; - } - - consecutive_failures = 0; - tokio::time::sleep(Duration::from_millis(100)).await; - check_count += 1; - - if check_count % 20 == 0 { - logging!( - debug, - Type::Window, - true, - "UI加载状态检查... ({}秒)", - check_count / 10 - ); - } - } - }; - - let wait_result = tokio::time::timeout( - Duration::from_secs(timeout_seconds), - ui_ready_checker(), - ) - .await; - - match wait_result { - Ok(_) => { - logging!(info, Type::Window, true, "UI已完全加载就绪"); - handle::Handle::global() - .get_window() - .map(|window| window.eval(r" - const overlay = document.getElementById('initial-loading-overlay'); - if (overlay) { - overlay.style.opacity = '0'; - setTimeout(() => overlay.remove(), 300); - } - ")); - } - Err(_) => { - logging!( - warn, - Type::Window, - true, - "UI加载监控超时({}秒),但窗口已正常显示", - timeout_seconds - ); - - get_ui_ready() - .try_write() - .map(|mut guard| { - *guard = true; - logging!( - info, - Type::Window, - true, - "超时后成功设置UI就绪状态" - ); - }) - .unwrap_or_else(|| { - logging!( - error, - Type::Window, - true, - "超时后无法获取UI状态写锁,可能存在严重死锁" - ); - }); - } - } - }); - - logging!(info, Type::Window, true, "窗口显示流程完成"); - } else { - logging!( - debug, - Type::Window, - true, - "is_show为false,窗口保持隐藏状态" - ); - } - }); - true - } - Err(e) => { - logging!(error, Type::Window, true, "主窗口构建失败: {}", e); - false - } - } -} - -pub async fn resolve_scheme(param: String) -> Result<()> { - log::info!(target:"app", "received deep link: {param}"); - - let param_str = if param.starts_with("[") && param.len() > 4 { - param - .get(2..param.len() - 2) - .ok_or_else(|| anyhow::anyhow!("Invalid string slice boundaries"))? - } else { - param.as_str() - }; - - // 解析 URL - let link_parsed = match Url::parse(param_str) { - Ok(url) => url, - Err(e) => { - bail!("failed to parse deep link: {:?}, param: {:?}", e, param); - } - }; - - if link_parsed.scheme() == "clash" || link_parsed.scheme() == "clash-verge" { - let name = link_parsed - .query_pairs() - .find(|(key, _)| key == "name") - .map(|(_, value)| value.into_owned()); - - let url_param = if let Some(query) = link_parsed.query() { - let prefix = "url="; - if let Some(pos) = query.find(prefix) { - let raw_url = &query[pos + prefix.len()..]; - Some(percent_decode_str(raw_url).decode_utf8_lossy().to_string()) - } else { - None - } - } else { - None - }; - - match url_param { - Some(url) => { - log::info!(target:"app", "decoded subscription url: {url}"); - - create_window(false).await; - match PrfItem::from_url(url.as_ref(), name, None, None).await { - Ok(item) => { - let uid = match item.uid.clone() { - Some(uid) => uid, - None => { - logging!(error, Type::Config, true, "Profile item missing UID"); - handle::Handle::notice_message( - "import_sub_url::error", - "Profile item missing UID".to_string(), - ); - return Ok(()); - } - }; - let result = crate::config::profiles::profiles_append_item_safe(item).await; - let _ = wrap_err!(result); - handle::Handle::notice_message("import_sub_url::ok", uid); - } - Err(e) => { - handle::Handle::notice_message("import_sub_url::error", e.to_string()); - } - } - } - None => bail!("failed to get profile url"), - } - } - - Ok(()) -} - -#[cfg(target_os = "macos")] -pub async fn set_public_dns(dns_server: String) { - use crate::{core::handle, utils::dirs}; - use tauri_plugin_shell::ShellExt; - let app_handle = match handle::Handle::global().app_handle() { - Some(handle) => handle, - None => { - log::error!(target: "app", "app_handle not available for DNS configuration"); - return; - } - }; - - log::info!(target: "app", "try to set system dns"); - let resource_dir = match dirs::app_resources_dir() { - Ok(dir) => dir, - Err(e) => { - log::error!(target: "app", "Failed to get resource directory: {}", e); - return; - } - }; - let script = resource_dir.join("set_dns.sh"); - if !script.exists() { - log::error!(target: "app", "set_dns.sh not found"); - return; - } - let script = script.to_string_lossy().into_owned(); - match app_handle - .shell() - .command("bash") - .args([script, dns_server]) - .current_dir(resource_dir) - .status() - .await - { - Ok(status) => { - if status.success() { - log::info!(target: "app", "set system dns successfully"); - } else { - let code = status.code().unwrap_or(-1); - log::error!(target: "app", "set system dns failed: {code}"); - } - } - Err(err) => { - log::error!(target: "app", "set system dns failed: {err}"); - } - } -} - -#[cfg(target_os = "macos")] -pub async fn restore_public_dns() { - use crate::{core::handle, utils::dirs}; - use tauri_plugin_shell::ShellExt; - let app_handle = match handle::Handle::global().app_handle() { - Some(handle) => handle, - None => { - log::error!(target: "app", "app_handle not available for DNS restoration"); - return; - } - }; - log::info!(target: "app", "try to unset system dns"); - let resource_dir = match dirs::app_resources_dir() { - Ok(dir) => dir, - Err(e) => { - log::error!(target: "app", "Failed to get resource directory: {}", e); - return; - } - }; - let script = resource_dir.join("unset_dns.sh"); - if !script.exists() { - log::error!(target: "app", "unset_dns.sh not found"); - return; - } - let script = script.to_string_lossy().into_owned(); - match app_handle - .shell() - .command("bash") - .args([script]) - .current_dir(resource_dir) - .status() - .await - { - Ok(status) => { - if status.success() { - log::info!(target: "app", "unset system dns successfully"); - } else { - let code = status.code().unwrap_or(-1); - log::error!(target: "app", "unset system dns failed: {code}"); - } - } - Err(err) => { - log::error!(target: "app", "unset system dns failed: {err}"); - } - } -} diff --git a/src-tauri/src/utils/resolve/dns.rs b/src-tauri/src/utils/resolve/dns.rs new file mode 100644 index 00000000..e3618178 --- /dev/null +++ b/src-tauri/src/utils/resolve/dns.rs @@ -0,0 +1,94 @@ +#[cfg(target_os = "macos")] +pub async fn set_public_dns(dns_server: String) { + use crate::{core::handle, utils::dirs}; + use tauri_plugin_shell::ShellExt; + let app_handle = match handle::Handle::global().app_handle() { + Some(handle) => handle, + None => { + log::error!(target: "app", "app_handle not available for DNS configuration"); + return; + } + }; + + log::info!(target: "app", "try to set system dns"); + let resource_dir = match dirs::app_resources_dir() { + Ok(dir) => dir, + Err(e) => { + log::error!(target: "app", "Failed to get resource directory: {}", e); + return; + } + }; + let script = resource_dir.join("set_dns.sh"); + if !script.exists() { + log::error!(target: "app", "set_dns.sh not found"); + return; + } + let script = script.to_string_lossy().into_owned(); + match app_handle + .shell() + .command("bash") + .args([script, dns_server]) + .current_dir(resource_dir) + .status() + .await + { + Ok(status) => { + if status.success() { + log::info!(target: "app", "set system dns successfully"); + } else { + let code = status.code().unwrap_or(-1); + log::error!(target: "app", "set system dns failed: {code}"); + } + } + Err(err) => { + log::error!(target: "app", "set system dns failed: {err}"); + } + } +} + +#[cfg(target_os = "macos")] +pub async fn restore_public_dns() { + use crate::{core::handle, utils::dirs}; + use tauri_plugin_shell::ShellExt; + let app_handle = match handle::Handle::global().app_handle() { + Some(handle) => handle, + None => { + log::error!(target: "app", "app_handle not available for DNS restoration"); + return; + } + }; + log::info!(target: "app", "try to unset system dns"); + let resource_dir = match dirs::app_resources_dir() { + Ok(dir) => dir, + Err(e) => { + log::error!(target: "app", "Failed to get resource directory: {}", e); + return; + } + }; + let script = resource_dir.join("unset_dns.sh"); + if !script.exists() { + log::error!(target: "app", "unset_dns.sh not found"); + return; + } + let script = script.to_string_lossy().into_owned(); + match app_handle + .shell() + .command("bash") + .args([script]) + .current_dir(resource_dir) + .status() + .await + { + Ok(status) => { + if status.success() { + log::info!(target: "app", "unset system dns successfully"); + } else { + let code = status.code().unwrap_or(-1); + log::error!(target: "app", "unset system dns failed: {code}"); + } + } + Err(err) => { + log::error!(target: "app", "unset system dns failed: {err}"); + } + } +} diff --git a/src-tauri/src/utils/resolve/mod.rs b/src-tauri/src/utils/resolve/mod.rs new file mode 100644 index 00000000..0ebd4fea --- /dev/null +++ b/src-tauri/src/utils/resolve/mod.rs @@ -0,0 +1,186 @@ +use crate::{ + config::Config, + core::{hotkey::Hotkey, sysopt, tray::Tray, CoreManager, Timer}, + logging, logging_error, + module::lightweight::auto_lightweight_mode_init, + process::AsyncHandler, + utils::{init, logging::Type, resolve::window::create_window, server}, +}; + +pub mod dns; +pub mod scheme; +pub mod ui; +pub mod window; +pub mod window_script; + +pub async fn resolve_setup_async() { + let start_time = std::time::Instant::now(); + logging!( + info, + Type::Setup, + true, + "开始执行异步设置任务... 线程ID: {:?}", + std::thread::current().id() + ); + + AsyncHandler::spawn_blocking(|| async { + init_scheme(); + init_startup_script().await; + init_embed_server(); + }); + + AsyncHandler::spawn_blocking(|| async { + init_config().await; + init_core_manager().await; + init_tray().await; + }); + + AsyncHandler::spawn_blocking(|| async { + init_system_proxy().await; + init_system_proxy_guard().await; + }); + + AsyncHandler::spawn_blocking(|| async { + init_timer().await; + init_auto_lightweight_mode().await; + }); + + AsyncHandler::spawn_blocking(|| async { + init_hotkey().await; + init_window().await; + refresh_tray_menu().await; + }); + + let elapsed = start_time.elapsed(); + logging!( + info, + Type::Setup, + true, + "异步设置任务完成,耗时: {:?}", + elapsed + ); + + // 如果初始化时间过长,记录警告 + if elapsed.as_secs() > 10 { + logging!( + warn, + Type::Setup, + true, + "异步设置任务耗时较长({:?})", + elapsed + ); + } +} + +pub async fn resolve_reset_async() { + logging!(info, Type::Tray, true, "Resetting system proxy"); + logging_error!( + Type::System, + true, + sysopt::Sysopt::global().reset_sysproxy().await + ); + + logging!(info, Type::Core, true, "Stopping core service"); + logging_error!(Type::Core, true, CoreManager::global().stop_core().await); + + #[cfg(target_os = "macos")] + { + use dns::restore_public_dns; + + logging!(info, Type::System, true, "Restoring system DNS settings"); + restore_public_dns().await; + } + todo!() +} + +pub(super) fn init_scheme() { + logging!(info, Type::Setup, true, "Initializing custom URL scheme"); + logging_error!(Type::Setup, true, init::init_scheme()); +} + +pub(super) fn init_embed_server() { + logging!(info, Type::Setup, true, "Initializing embedded server..."); + server::embed_server(); +} + +pub(super) async fn init_startup_script() { + logging!(info, Type::Setup, true, "Initializing startup script"); + logging_error!(Type::Setup, true, init::startup_script().await); +} + +pub(super) async fn init_timer() { + logging!(info, Type::Setup, true, "Initializing timer..."); + logging_error!(Type::Setup, true, Timer::global().init().await); +} + +pub(super) async fn init_hotkey() { + logging!(info, Type::Setup, true, "Initializing hotkey..."); + logging_error!(Type::Setup, true, Hotkey::global().init().await); +} + +pub(super) async fn init_auto_lightweight_mode() { + logging!( + info, + Type::Setup, + true, + "Initializing auto lightweight mode..." + ); + logging_error!(Type::Setup, true, auto_lightweight_mode_init().await); +} + +pub(super) async fn init_tray() { + logging!(info, Type::Setup, true, "Initializing system tray..."); + logging_error!(Type::Setup, true, Tray::global().init().await); +} + +pub(super) async fn init_config() { + logging!(info, Type::Setup, true, "Initializing configuration..."); + logging_error!(Type::Setup, true, Config::init_config().await); +} + +pub(super) async fn init_core_manager() { + logging!(info, Type::Setup, true, "Initializing core manager..."); + logging_error!(Type::Setup, true, CoreManager::global().init().await); +} + +pub(super) async fn init_system_proxy() { + logging!(info, Type::Setup, true, "Initializing system proxy..."); + logging_error!( + Type::Setup, + true, + sysopt::Sysopt::global().update_sysproxy().await + ); +} + +pub(super) async fn init_system_proxy_guard() { + logging!( + info, + Type::Setup, + true, + "Initializing system proxy guard..." + ); + logging_error!( + Type::Setup, + true, + sysopt::Sysopt::global().init_guard_sysproxy() + ); +} + +pub(super) async fn refresh_tray_menu() { + logging!(info, Type::Setup, true, "Refreshing tray menu..."); + logging_error!(Type::Setup, true, Tray::global().update_part().await); +} + +pub(super) async fn init_window() { + let is_silent_start = + { Config::verge().await.latest_ref().enable_silent_start }.unwrap_or(false); + #[cfg(target_os = "macos")] + { + if is_silent_start { + use crate::core::handle::Handle; + + Handle::global().set_activation_policy_accessory(); + } + } + create_window(!is_silent_start).await; +} diff --git a/src-tauri/src/utils/resolve/scheme.rs b/src-tauri/src/utils/resolve/scheme.rs new file mode 100644 index 00000000..ce0bc54f --- /dev/null +++ b/src-tauri/src/utils/resolve/scheme.rs @@ -0,0 +1,82 @@ +use anyhow::{bail, Result}; +use percent_encoding::percent_decode_str; +use tauri::Url; + +use crate::{ + config::PrfItem, + core::handle, + logging, + utils::{logging::Type, resolve::window::create_window}, + wrap_err, +}; + +pub async fn resolve_scheme(param: String) -> Result<()> { + log::info!(target:"app", "received deep link: {param}"); + + let param_str = if param.starts_with("[") && param.len() > 4 { + param + .get(2..param.len() - 2) + .ok_or_else(|| anyhow::anyhow!("Invalid string slice boundaries"))? + } else { + param.as_str() + }; + + // 解析 URL + let link_parsed = match Url::parse(param_str) { + Ok(url) => url, + Err(e) => { + bail!("failed to parse deep link: {:?}, param: {:?}", e, param); + } + }; + + if link_parsed.scheme() == "clash" || link_parsed.scheme() == "clash-verge" { + let name = link_parsed + .query_pairs() + .find(|(key, _)| key == "name") + .map(|(_, value)| value.into_owned()); + + let url_param = if let Some(query) = link_parsed.query() { + let prefix = "url="; + if let Some(pos) = query.find(prefix) { + let raw_url = &query[pos + prefix.len()..]; + Some(percent_decode_str(raw_url).decode_utf8_lossy().to_string()) + } else { + None + } + } else { + None + }; + + match url_param { + Some(url) => { + log::info!(target:"app", "decoded subscription url: {url}"); + + create_window(false).await; + match PrfItem::from_url(url.as_ref(), name, None, None).await { + Ok(item) => { + let uid = match item.uid.clone() { + Some(uid) => uid, + None => { + logging!(error, Type::Config, true, "Profile item missing UID"); + handle::Handle::notice_message( + "import_sub_url::error", + "Profile item missing UID".to_string(), + ); + return Ok(()); + } + }; + let result = crate::config::profiles::profiles_append_item_safe(item).await; + let _ = wrap_err!(result); + handle::Handle::notice_message("import_sub_url::ok", uid); + } + Err(e) => { + handle::Handle::notice_message("import_sub_url::error", e.to_string()); + } + } + } + None => bail!("failed to get profile url"), + } + } + + Ok(()) +} diff --git a/src-tauri/src/utils/resolve/ui.rs b/src-tauri/src/utils/resolve/ui.rs new file mode 100644 index 00000000..939fca07 --- /dev/null +++ b/src-tauri/src/utils/resolve/ui.rs @@ -0,0 +1,74 @@ +use once_cell::sync::OnceCell; +use parking_lot::RwLock; + +use crate::{logging, utils::logging::Type}; + +// 添加全局UI准备就绪标志 +static UI_READY: OnceCell> = OnceCell::new(); +// 获取UI就绪状态细节 +static UI_READY_STATE: OnceCell = OnceCell::new(); + +// UI就绪阶段状态枚举 +#[derive(Debug, Clone, Copy, PartialEq)] +pub enum UiReadyStage { + NotStarted, + Loading, + DomReady, + ResourcesLoaded, + Ready, +} + +// UI就绪详细状态 +#[derive(Debug)] +struct UiReadyState { + stage: RwLock, +} + +impl Default for UiReadyState { + fn default() -> Self { + Self { + stage: RwLock::new(UiReadyStage::NotStarted), + } + } +} + +pub(super) fn get_ui_ready() -> &'static RwLock { + UI_READY.get_or_init(|| RwLock::new(false)) +} + +fn get_ui_ready_state() -> &'static UiReadyState { + UI_READY_STATE.get_or_init(UiReadyState::default) +} + +// 更新UI准备阶段 +pub fn update_ui_ready_stage(stage: UiReadyStage) { + let state = get_ui_ready_state(); + let mut stage_lock = state.stage.write(); + + *stage_lock = stage; + // 如果是最终阶段,标记UI完全就绪 + if stage == UiReadyStage::Ready { + mark_ui_ready(); + } +} + +// 标记UI已准备就绪 +pub fn mark_ui_ready() { + let mut ready = get_ui_ready().write(); + *ready = true; + logging!(info, Type::Window, true, "UI已标记为完全就绪"); +} + +// 重置UI就绪状态 +pub fn reset_ui_ready() { + { + let mut ready = get_ui_ready().write(); + *ready = false; + } + { + let state = get_ui_ready_state(); + let mut stage = state.stage.write(); + *stage = UiReadyStage::NotStarted; + } + logging!(info, Type::Window, true, "UI就绪状态已重置"); +} diff --git a/src-tauri/src/utils/resolve/window.rs b/src-tauri/src/utils/resolve/window.rs new file mode 100644 index 00000000..6e223748 --- /dev/null +++ b/src-tauri/src/utils/resolve/window.rs @@ -0,0 +1,291 @@ +use std::time::{Duration, Instant}; + +use once_cell::sync::OnceCell; +use parking_lot::Mutex; +use tauri::Manager; + +use crate::{ + core::handle, + logging, + module::lightweight, + process::AsyncHandler, + utils::{ + logging::Type, + resolve::{ + ui::{get_ui_ready, update_ui_ready_stage, UiReadyStage}, + window_script::{INITIAL_LOADING_OVERLAY, WINDOW_INITIAL_SCRIPT}, + }, + }, +}; + +// 定义默认窗口尺寸常量 +const DEFAULT_WIDTH: f64 = 940.0; +const DEFAULT_HEIGHT: f64 = 700.0; + +const MINIMAL_WIDTH: f64 = 520.0; +const MINIMAL_HEIGHT: f64 = 520.0; + +// 窗口创建锁,防止并发创建窗口 +static WINDOW_CREATING: OnceCell> = OnceCell::new(); + +fn get_window_creating_lock() -> &'static Mutex<(bool, Instant)> { + WINDOW_CREATING.get_or_init(|| Mutex::new((false, Instant::now()))) +} + +pub async fn create_window(is_show: bool) -> bool { + logging!( + info, + Type::Window, + true, + "开始创建/显示主窗口, is_show={}", + is_show + ); + + if !is_show { + lightweight::set_lightweight_mode(true).await; + handle::Handle::notify_startup_completed(); + return false; + } + + if let Some(app_handle) = handle::Handle::global().app_handle() { + if let Some(window) = app_handle.get_webview_window("main") { + logging!(info, Type::Window, true, "主窗口已存在,将显示现有窗口"); + if is_show { + if window.is_minimized().unwrap_or(false) { + logging!(info, Type::Window, true, "窗口已最小化,正在取消最小化"); + let _ = window.unminimize(); + } + let _ = window.show(); + let _ = window.set_focus(); + + #[cfg(target_os = "macos")] + handle::Handle::global().set_activation_policy_regular(); + } + return true; + } + } + + let creating_lock = get_window_creating_lock(); + let mut creating = creating_lock.lock(); + + let (is_creating, last_time) = *creating; + let elapsed = last_time.elapsed(); + + if is_creating && elapsed < Duration::from_secs(2) { + logging!( + info, + Type::Window, + true, + "窗口创建请求被忽略,因为最近创建过 ({:?}ms)", + elapsed.as_millis() + ); + return false; + } + + *creating = (true, Instant::now()); + + // ScopeGuard 确保创建状态重置,防止 webview 卡死 + let _guard = scopeguard::guard(creating, |mut creating_guard| { + *creating_guard = (false, Instant::now()); + logging!(debug, Type::Window, true, "[ScopeGuard] 窗口创建状态已重置"); + }); + + let app_handle = match handle::Handle::global().app_handle() { + Some(handle) => handle, + None => { + logging!( + error, + Type::Window, + true, + "无法获取app_handle,窗口创建失败" + ); + return false; + } + }; + + match tauri::WebviewWindowBuilder::new( + &app_handle, + "main", /* the unique window label */ + tauri::WebviewUrl::App("index.html".into()), + ) + .title("Clash Verge") + .center() + .decorations(true) + .fullscreen(false) + .inner_size(DEFAULT_WIDTH, DEFAULT_HEIGHT) + .min_inner_size(MINIMAL_WIDTH, MINIMAL_HEIGHT) + .visible(true) // 立即显示窗口,避免用户等待 + .initialization_script(WINDOW_INITIAL_SCRIPT) + .build() + { + Ok(newly_created_window) => { + logging!(debug, Type::Window, true, "主窗口实例创建成功"); + + update_ui_ready_stage(UiReadyStage::NotStarted); + + AsyncHandler::spawn(move || async move { + handle::Handle::global().mark_startup_completed(); + logging!( + debug, + Type::Window, + true, + "异步窗口任务开始 (启动已标记完成)" + ); + + // 先运行轻量模式检测 + lightweight::run_once_auto_lightweight().await; + + // 发送启动完成事件,触发前端开始加载 + logging!( + debug, + Type::Window, + true, + "发送 verge://startup-completed 事件" + ); + handle::Handle::notify_startup_completed(); + + if is_show { + let window_clone = newly_created_window.clone(); + + // 立即显示窗口 + let _ = window_clone.show(); + let _ = window_clone.set_focus(); + logging!(info, Type::Window, true, "窗口已立即显示"); + #[cfg(target_os = "macos")] + handle::Handle::global().set_activation_policy_regular(); + + let timeout_seconds = if crate::module::lightweight::is_in_lightweight_mode() { + 3 + } else { + 8 + }; + + logging!( + info, + Type::Window, + true, + "开始监控UI加载状态 (最多{}秒)...", + timeout_seconds + ); + + // 异步监控UI状态,使用try_read避免死锁 + AsyncHandler::spawn(move || async move { + logging!( + debug, + Type::Window, + true, + "启动UI状态监控线程,超时{}秒", + timeout_seconds + ); + + let ui_ready_checker = || async { + let (mut check_count, mut consecutive_failures) = (0, 0); + + loop { + let is_ready = get_ui_ready() + .try_read() + .map(|guard| *guard) + .unwrap_or_else(|| { + consecutive_failures += 1; + if consecutive_failures > 50 { + logging!( + warn, + Type::Window, + true, + "UI状态监控连续{}次无法获取读锁,可能存在死锁", + consecutive_failures + ); + consecutive_failures = 0; + } + false + }); + + if is_ready { + logging!( + debug, + Type::Window, + true, + "UI状态监控检测到就绪信号,退出监控" + ); + return; + } + + consecutive_failures = 0; + tokio::time::sleep(Duration::from_millis(100)).await; + check_count += 1; + + if check_count % 20 == 0 { + logging!( + debug, + Type::Window, + true, + "UI加载状态检查... ({}秒)", + check_count / 10 + ); + } + } + }; + + let wait_result = tokio::time::timeout( + Duration::from_secs(timeout_seconds), + ui_ready_checker(), + ) + .await; + + match wait_result { + Ok(_) => { + logging!(info, Type::Window, true, "UI已完全加载就绪"); + handle::Handle::global() + .get_window() + .map(|window| window.eval(INITIAL_LOADING_OVERLAY)); + } + Err(_) => { + logging!( + warn, + Type::Window, + true, + "UI加载监控超时({}秒),但窗口已正常显示", + timeout_seconds + ); + + get_ui_ready() + .try_write() + .map(|mut guard| { + *guard = true; + logging!( + info, + Type::Window, + true, + "超时后成功设置UI就绪状态" + ); + }) + .unwrap_or_else(|| { + logging!( + error, + Type::Window, + true, + "超时后无法获取UI状态写锁,可能存在严重死锁" + ); + }); + } + } + }); + + logging!(info, Type::Window, true, "窗口显示流程完成"); + } else { + logging!( + debug, + Type::Window, + true, + "is_show为false,窗口保持隐藏状态" + ); + } + }); + true + } + Err(e) => { + logging!(error, Type::Window, true, "主窗口构建失败: {}", e); + false + } + } +} diff --git a/src-tauri/src/utils/resolve/window_script.rs b/src-tauri/src/utils/resolve/window_script.rs new file mode 100644 index 00000000..632b0bf8 --- /dev/null +++ b/src-tauri/src/utils/resolve/window_script.rs @@ -0,0 +1,71 @@ +pub const WINDOW_INITIAL_SCRIPT: &str = r#" + console.log('[Tauri] 窗口初始化脚本开始执行'); + + function createLoadingOverlay() { + + if (document.getElementById('initial-loading-overlay')) { + console.log('[Tauri] 加载指示器已存在'); + return; + } + + console.log('[Tauri] 创建加载指示器'); + const loadingDiv = document.createElement('div'); + loadingDiv.id = 'initial-loading-overlay'; + loadingDiv.innerHTML = ` +
+
+
+
+
Loading Clash Verge...
+
+ + `; + + if (document.body) { + document.body.appendChild(loadingDiv); + } else { + document.addEventListener('DOMContentLoaded', () => { + if (document.body && !document.getElementById('initial-loading-overlay')) { + document.body.appendChild(loadingDiv); + } + }); + } + } + + createLoadingOverlay(); + + if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', createLoadingOverlay); + } else { + createLoadingOverlay(); + } + + console.log('[Tauri] 窗口初始化脚本执行完成'); +"#; + +pub const INITIAL_LOADING_OVERLAY: &str = r" + const overlay = document.getElementById('initial-loading-overlay'); + if (overlay) { + overlay.style.opacity = '0'; + setTimeout(() => overlay.remove(), 300); + } +"; diff --git a/src-tauri/src/utils/server.rs b/src-tauri/src/utils/server.rs index 2ec197f6..df044c76 100644 --- a/src-tauri/src/utils/server.rs +++ b/src-tauri/src/utils/server.rs @@ -46,7 +46,6 @@ pub fn embed_server() { AsyncHandler::spawn(move || async move { let visible = warp::path!("commands" / "visible").and_then(|| async { - resolve::create_window(false).await; Ok::<_, warp::Rejection>(warp::reply::with_status( "ok".to_string(), warp::http::StatusCode::OK, @@ -85,7 +84,11 @@ pub fn embed_server() { // Spawn async work in a fire-and-forget manner let param = query.param.clone(); tokio::task::spawn_local(async move { - logging_error!(Type::Setup, true, resolve::resolve_scheme(param).await); + logging_error!( + Type::Setup, + true, + resolve::scheme::resolve_scheme(param).await + ); }); warp::reply::with_status("ok".to_string(), warp::http::StatusCode::OK) }); diff --git a/src-tauri/src/utils/window_manager.rs b/src-tauri/src/utils/window_manager.rs index c1b287f9..97b5984a 100644 --- a/src-tauri/src/utils/window_manager.rs +++ b/src-tauri/src/utils/window_manager.rs @@ -365,7 +365,7 @@ impl WindowManager { use crate::utils::resolve; // 使用 tokio runtime 阻塞调用 async 函数 - AsyncHandler::block_on(resolve::create_window(true)) + AsyncHandler::block_on(resolve::window::create_window(true)) } /// 获取详细的窗口状态信息