Update On Fri May 9 20:36:54 CEST 2025

This commit is contained in:
github-action[bot]
2025-05-09 20:36:54 +02:00
parent 40a1860078
commit 5ad5eb65a0
268 changed files with 32572 additions and 1774 deletions

View File

@@ -87,23 +87,18 @@ pub async fn download_icon_cache(url: String, name: String) -> CmdResult<String>
let icon_cache_dir = wrap_err!(dirs::app_home_dir())?.join("icons").join("cache");
let icon_path = icon_cache_dir.join(&name);
// 如果文件已存在,直接返回路径
if icon_path.exists() {
return Ok(icon_path.to_string_lossy().to_string());
}
// 确保缓存目录存在
if !icon_cache_dir.exists() {
let _ = std::fs::create_dir_all(&icon_cache_dir);
}
// 使用临时文件名来下载
let temp_path = icon_cache_dir.join(format!("{}.downloading", &name));
// 下载文件到临时位置
let response = wrap_err!(reqwest::get(&url).await)?;
// 检查内容类型是否为图片
let content_type = response
.headers()
.get(reqwest::header::CONTENT_TYPE)
@@ -112,16 +107,13 @@ pub async fn download_icon_cache(url: String, name: String) -> CmdResult<String>
let is_image = content_type.starts_with("image/");
// 获取响应内容
let content = wrap_err!(response.bytes().await)?;
// 检查内容是否为HTML (针对CDN错误页面)
let is_html = content.len() > 15
&& (content.starts_with(b"<!DOCTYPE html")
|| content.starts_with(b"<html")
|| content.starts_with(b"<?xml"));
// 只有当内容确实是图片时才保存
if is_image && !is_html {
{
let mut file = match std::fs::File::create(&temp_path) {
@@ -138,7 +130,6 @@ pub async fn download_icon_cache(url: String, name: String) -> CmdResult<String>
wrap_err!(std::io::copy(&mut content.as_ref(), &mut file))?;
}
// 再次检查目标文件是否已存在,避免重命名覆盖其他线程已完成的文件
if !icon_path.exists() {
match std::fs::rename(&temp_path, &icon_path) {
Ok(_) => {}
@@ -223,6 +214,29 @@ pub fn notify_ui_ready() -> CmdResult<()> {
Ok(())
}
/// UI加载阶段
#[tauri::command]
pub fn update_ui_stage(stage: String) -> CmdResult<()> {
log::info!(target: "app", "UI加载阶段更新: {}", stage);
use crate::utils::resolve::UiReadyStage;
let stage_enum = match stage.as_str() {
"NotStarted" => UiReadyStage::NotStarted,
"Loading" => UiReadyStage::Loading,
"DomReady" => UiReadyStage::DomReady,
"ResourcesLoaded" => UiReadyStage::ResourcesLoaded,
"Ready" => UiReadyStage::Ready,
_ => {
log::warn!(target: "app", "未知的UI加载阶段: {}", stage);
return Err(format!("未知的UI加载阶段: {}", stage));
}
};
crate::utils::resolve::update_ui_ready_stage(stage_enum);
Ok(())
}
/// 重置UI就绪状态
#[tauri::command]
pub fn reset_ui_ready_state() -> CmdResult<()> {

View File

@@ -11,7 +11,7 @@ use crate::{
mihomo::Rate,
},
resolve,
utils::{dirs::find_target_icons, i18n::t, logging::Type, resolve::VERSION},
utils::{dirs::find_target_icons, i18n::t, resolve::VERSION},
};
use anyhow::Result;
@@ -29,7 +29,7 @@ use std::sync::Arc;
use tauri::{
menu::{CheckMenuItem, IsMenuItem, MenuEvent, MenuItem, PredefinedMenuItem, Submenu},
tray::{MouseButton, MouseButtonState, TrayIconEvent},
App, AppHandle, Wry,
AppHandle, Wry,
};
#[cfg(target_os = "macos")]
use tokio::sync::broadcast;
@@ -178,52 +178,6 @@ impl Tray {
Ok(())
}
pub fn create_systray(&self, app: &App) -> Result<()> {
let mut builder = TrayIconBuilder::with_id("main")
.icon(app.default_window_icon().unwrap().clone())
.icon_as_template(false);
#[cfg(any(target_os = "macos", target_os = "windows"))]
{
let tray_event = { Config::verge().latest().tray_event.clone() };
let tray_event: String = tray_event.unwrap_or("main_window".into());
if tray_event.as_str() != "tray_menu" {
builder = builder.show_menu_on_left_click(false);
}
}
let tray = builder.build(app)?;
tray.on_tray_icon_event(|_, event| {
let tray_event = { Config::verge().latest().tray_event.clone() };
let tray_event: String = tray_event.unwrap_or("main_window".into());
log::debug!(target: "app","tray event: {:?}", tray_event);
if let TrayIconEvent::Click {
button: MouseButton::Left,
button_state: MouseButtonState::Down,
..
} = event
{
match tray_event.as_str() {
"system_proxy" => feat::toggle_system_proxy(),
"tun_mode" => feat::toggle_tun_mode(None),
"main_window" => {
// 如果在轻量模式中,先退出轻量模式
if crate::module::lightweight::is_in_lightweight_mode() {
crate::module::lightweight::exit_lightweight_mode();
}
// 然后创建窗口
resolve::create_window(true)
}
_ => {}
}
}
});
tray.on_menu_event(on_menu_event);
Ok(())
}
/// 更新托盘点击行为
pub fn update_click_behavior(&self) -> Result<()> {
let app_handle = handle::Handle::global().app_handle().unwrap();
@@ -239,7 +193,14 @@ impl Tray {
/// 更新托盘菜单
pub fn update_menu(&self) -> Result<()> {
let app_handle = handle::Handle::global().app_handle().unwrap();
let app_handle = match handle::Handle::global().app_handle() {
Some(handle) => handle,
None => {
log::warn!(target: "app", "更新托盘菜单失败: app_handle不存在");
return Ok(()); // 早期返回避免panic
}
};
let verge = Config::verge().latest().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);
@@ -258,27 +219,47 @@ impl Tray {
.unwrap_or_default();
let is_lightweight_mode = is_in_lightweight_mode();
let tray = app_handle.tray_by_id("main").unwrap();
let _ = tray.set_menu(Some(create_tray_menu(
&app_handle,
Some(mode.as_str()),
*system_proxy,
*tun_mode,
profile_uid_and_name,
is_lightweight_mode,
)?));
Ok(())
match app_handle.tray_by_id("main") {
Some(tray) => {
let _ = tray.set_menu(Some(create_tray_menu(
&app_handle,
Some(mode.as_str()),
*system_proxy,
*tun_mode,
profile_uid_and_name,
is_lightweight_mode,
)?));
Ok(())
}
None => {
log::warn!(target: "app", "更新托盘菜单失败: 托盘不存在");
Ok(())
}
}
}
/// 更新托盘图标
pub fn update_icon(&self, rate: Option<Rate>) -> Result<()> {
let app_handle = match handle::Handle::global().app_handle() {
Some(handle) => handle,
None => {
log::warn!(target: "app", "更新托盘图标失败: app_handle不存在");
return Ok(());
}
};
let tray = match app_handle.tray_by_id("main") {
Some(tray) => tray,
None => {
log::warn!(target: "app", "更新托盘图标失败: 托盘不存在");
return Ok(());
}
};
let verge = Config::verge().latest().clone();
let system_mode = verge.enable_system_proxy.as_ref().unwrap_or(&false);
let tun_mode = verge.enable_tun_mode.as_ref().unwrap_or(&false);
let app_handle = handle::Handle::global().app_handle().unwrap();
let tray = app_handle.tray_by_id("main").unwrap();
let (is_custom_icon, icon_bytes) = match (*system_mode, *tun_mode) {
(true, true) => TrayState::get_tun_tray_icon(),
(true, false) => TrayState::get_sysproxy_tray_icon(),
@@ -302,8 +283,12 @@ impl Tray {
Some(rate)
} else {
let guard = self.speed_rate.lock();
if let Some(rate) = guard.as_ref().unwrap().get_curent_rate() {
Some(rate)
if let Some(guard) = guard.as_ref() {
if let Some(rate) = guard.get_curent_rate() {
Some(rate)
} else {
Some(Rate::default())
}
} else {
Some(Rate::default())
}
@@ -320,9 +305,10 @@ impl Tray {
};
let rate = rate_guard.as_ref();
let rate_bytes = SpeedRate::add_speed_text(is_custom_icon, bytes, rate).unwrap();
let _ = tray.set_icon(Some(tauri::image::Image::from_bytes(&rate_bytes)?));
let _ = tray.set_icon_as_template(!is_custom_icon && !is_colorful);
if let Ok(rate_bytes) = SpeedRate::add_speed_text(is_custom_icon, bytes, rate) {
let _ = tray.set_icon(Some(tauri::image::Image::from_bytes(&rate_bytes)?));
let _ = tray.set_icon_as_template(!is_custom_icon && !is_colorful);
}
}
Ok(())
}
@@ -336,8 +322,21 @@ impl Tray {
/// 更新托盘提示
pub fn update_tooltip(&self) -> Result<()> {
let app_handle = handle::Handle::global().app_handle().unwrap();
let version = VERSION.get().unwrap();
let app_handle = match handle::Handle::global().app_handle() {
Some(handle) => handle,
None => {
log::warn!(target: "app", "更新托盘提示失败: app_handle不存在");
return Ok(());
}
};
let version = match VERSION.get() {
Some(v) => v,
None => {
log::warn!(target: "app", "更新托盘提示失败: 版本信息不存在");
return Ok(());
}
};
let verge = Config::verge().latest().clone();
let system_proxy = verge.enable_system_proxy.as_ref().unwrap_or(&false);
@@ -354,23 +353,28 @@ impl Tray {
let profiles = Config::profiles();
let profiles = profiles.latest();
if let Some(current_profile_uid) = profiles.get_current() {
let current_profile = profiles.get_item(&current_profile_uid);
current_profile_name = match &current_profile.unwrap().name {
Some(profile_name) => profile_name.to_string(),
None => current_profile_name,
};
if let Ok(profile) = profiles.get_item(&current_profile_uid) {
current_profile_name = match &profile.name {
Some(profile_name) => profile_name.to_string(),
None => current_profile_name,
};
}
};
let tray = app_handle.tray_by_id("main").unwrap();
let _ = tray.set_tooltip(Some(&format!(
"Clash Verge {version}\n{}: {}\n{}: {}\n{}: {}",
t("SysProxy"),
switch_map[system_proxy],
t("TUN"),
switch_map[tun_mode],
t("Profile"),
current_profile_name
)));
if let Some(tray) = app_handle.tray_by_id("main") {
let _ = tray.set_tooltip(Some(&format!(
"Clash Verge {version}\n{}: {}\n{}: {}\n{}: {}",
t("SysProxy"),
switch_map[system_proxy],
t("TUN"),
switch_map[tun_mode],
t("Profile"),
current_profile_name
)));
} else {
log::warn!(target: "app", "更新托盘提示失败: 托盘不存在");
}
Ok(())
}
@@ -532,6 +536,57 @@ impl Tray {
drop(tx);
}
}
pub fn create_tray_from_handle(&self, app_handle: &AppHandle) -> Result<()> {
log::info!(target: "app", "正在从AppHandle创建系统托盘");
// 获取图标
let icon_bytes = TrayState::get_common_tray_icon().1;
let icon = tauri::image::Image::from_bytes(&icon_bytes)?;
let mut builder = TrayIconBuilder::with_id("main")
.icon(icon)
.icon_as_template(false);
#[cfg(any(target_os = "macos", target_os = "windows"))]
{
let tray_event = { Config::verge().latest().tray_event.clone() };
let tray_event: String = tray_event.unwrap_or("main_window".into());
if tray_event.as_str() != "tray_menu" {
builder = builder.show_menu_on_left_click(false);
}
}
let tray = builder.build(app_handle)?;
tray.on_tray_icon_event(|_, event| {
let tray_event = { Config::verge().latest().tray_event.clone() };
let tray_event: String = tray_event.unwrap_or("main_window".into());
log::debug!(target: "app","tray event: {:?}", tray_event);
if let TrayIconEvent::Click {
button: MouseButton::Left,
button_state: MouseButtonState::Down,
..
} = event
{
match tray_event.as_str() {
"system_proxy" => feat::toggle_system_proxy(),
"tun_mode" => feat::toggle_tun_mode(None),
"main_window" => {
if crate::module::lightweight::is_in_lightweight_mode() {
crate::module::lightweight::exit_lightweight_mode();
}
let _ = resolve::create_window(true);
}
_ => {}
}
}
});
tray.on_menu_event(on_menu_event);
log::info!(target: "app", "系统托盘创建成功");
Ok(())
}
}
fn create_tray_menu(
@@ -543,7 +598,10 @@ fn create_tray_menu(
is_lightweight_mode: bool,
) -> Result<tauri::menu::Menu<Wry>> {
let mode = mode.unwrap_or("");
let version = VERSION.get().unwrap();
let unknown_version = String::from("unknown");
let version = VERSION.get().unwrap_or(&unknown_version);
let hotkeys = Config::verge()
.latest()
.hotkeys
@@ -779,14 +837,20 @@ fn on_menu_event(_: &AppHandle, event: MenuEvent) {
crate::module::lightweight::exit_lightweight_mode();
}
// 然后创建窗口
resolve::create_window(true)
let _ = resolve::create_window(true);
}
"system_proxy" => feat::toggle_system_proxy(),
"tun_mode" => feat::toggle_tun_mode(None),
"copy_env" => feat::copy_clash_env(),
"open_app_dir" => crate::logging_error!(Type::Cmd, true, cmd::open_app_dir()),
"open_core_dir" => crate::logging_error!(Type::Cmd, true, cmd::open_core_dir()),
"open_logs_dir" => crate::logging_error!(Type::Cmd, true, cmd::open_logs_dir()),
"open_app_dir" => {
let _ = cmd::open_app_dir();
}
"open_core_dir" => {
let _ = cmd::open_core_dir();
}
"open_logs_dir" => {
let _ = cmd::open_logs_dir();
}
"restart_clash" => feat::restart_clash_core(),
"restart_app" => feat::restart_app(),
"entry_lightweight_mode" => entry_lightweight_mode(),

View File

@@ -86,23 +86,30 @@ impl AppHandleManager {
#[allow(clippy::panic)]
pub fn run() {
// 初始化网络管理器
utils::network::NetworkManager::global().init();
// 单例检测 - 使用超时机制防止阻塞
let _ = utils::dirs::init_portable_flag();
// 单例检测
let app_exists: bool = AsyncHandler::block_on(move || async move {
logging!(info, Type::Setup, true, "开始检查单例实例...");
match timeout(Duration::from_secs(3), server::check_singleton()).await {
Ok(result) => {
if result.is_err() {
println!("app exists");
logging!(info, Type::Setup, true, "检测到已有应用实例运行");
true
} else {
logging!(info, Type::Setup, true, "未检测到其他应用实例");
false
}
}
Err(_) => {
// 超时处理
println!("singleton check timeout, assuming app doesn't exist");
logging!(
warn,
Type::Setup,
true,
"单例检查超时,假定没有其他实例运行"
);
false
}
}
@@ -136,9 +143,11 @@ pub fn run() {
.build(),
)
.setup(|app| {
logging!(info, Type::Setup, true, "开始应用初始化...");
#[cfg(any(target_os = "linux", all(debug_assertions, windows)))]
{
use tauri_plugin_deep_link::DeepLinkExt;
logging!(info, Type::Setup, true, "注册深层链接...");
logging_error!(Type::System, true, app.deep_link().register_all());
}
app.deep_link().on_open_url(|event| {
@@ -152,23 +161,49 @@ pub fn run() {
});
});
// 使用 block_on 但增加超时保护
AsyncHandler::block_on(|| async {
match timeout(Duration::from_secs(30), resolve::resolve_setup(app)).await {
// 异步处理
let app_handle = app.handle().clone();
AsyncHandler::spawn(move || async move {
logging!(info, Type::Setup, true, "异步执行应用设置...");
match timeout(
Duration::from_secs(30),
resolve::resolve_setup_async(&app_handle),
)
.await
{
Ok(_) => {
logging!(info, Type::Setup, true, "App setup completed successfully");
logging!(info, Type::Setup, true, "应用设置成功完成");
}
Err(_) => {
logging!(
error,
Type::Setup,
true,
"App setup timed out, proceeding anyway"
"应用设置超时(30秒),继续执行后续流程"
);
}
}
});
logging!(info, Type::Setup, true, "执行主要设置操作...");
logging!(info, Type::Setup, true, "初始化AppHandleManager...");
AppHandleManager::global().init(app.handle().clone());
logging!(info, Type::Setup, true, "初始化核心句柄...");
core::handle::Handle::global().init(app.handle());
logging!(info, Type::Setup, true, "初始化配置...");
if let Err(e) = utils::init::init_config() {
logging!(error, Type::Setup, true, "初始化配置失败: {}", e);
}
logging!(info, Type::Setup, true, "初始化资源...");
if let Err(e) = utils::init::init_resources() {
logging!(error, Type::Setup, true, "初始化资源失败: {}", e);
}
logging!(info, Type::Setup, true, "初始化完成,继续执行");
Ok(())
})
.invoke_handler(tauri::generate_handler![
@@ -184,8 +219,9 @@ pub fn run() {
cmd::get_system_hostname,
cmd::restart_core,
cmd::restart_app,
// 添加新的命令
// 启动命令
cmd::notify_ui_ready,
cmd::update_ui_stage,
cmd::reset_ui_ready_state,
cmd::get_running_mode,
cmd::get_app_uptime,
@@ -279,6 +315,7 @@ pub fn run() {
app.run(|app_handle, e| match e {
tauri::RunEvent::Ready | tauri::RunEvent::Resumed => {
logging!(info, Type::System, true, "应用就绪或恢复");
AppHandleManager::global().init(app_handle.clone());
#[cfg(target_os = "macos")]
{
@@ -286,6 +323,7 @@ pub fn run() {
.get_handle()
.get_webview_window("main")
{
logging!(info, Type::Window, true, "设置macOS窗口标题");
let _ = window.set_title("Clash Verge");
}
}
@@ -323,8 +361,11 @@ pub fn run() {
}
println!("closing window...");
api.prevent_close();
let window = core::handle::Handle::global().get_window().unwrap();
let _ = window.hide();
if let Some(window) = core::handle::Handle::global().get_window() {
let _ = window.hide();
} else {
logging!(warn, Type::Window, true, "尝试隐藏窗口但窗口不存在");
}
}
tauri::WindowEvent::Focused(true) => {
#[cfg(target_os = "macos")]

View File

@@ -16,8 +16,6 @@ use std::{
};
use tauri::{Listener, Manager};
pub static AUTO_LIGHT_WEIGHT_MODE_INIT: OnceCell<()> = OnceCell::new();
const LIGHT_WEIGHT_TASK_UID: &str = "light_weight_task";
// 轻量模式状态标志
@@ -217,19 +215,3 @@ fn cancel_light_weight_timer() -> Result<()> {
Ok(())
}
pub fn run_once_auto_lightweight() {
AUTO_LIGHT_WEIGHT_MODE_INIT.get_or_init(|| {
let is_silent_start = { Config::verge().data().enable_silent_start }.unwrap_or(false);
let enable_auto = { Config::verge().data().enable_auto_light_weight_mode }.unwrap_or(false);
if enable_auto && is_silent_start {
logging!(
info,
Type::Lightweight,
true,
"Add timer listener when creating window in silent start mode"
);
enable_auto_light_weight_mode();
}
});
}

View File

@@ -49,12 +49,60 @@ pub fn app_home_dir() -> Result<PathBuf> {
.ok_or(anyhow::anyhow!("failed to get the portable app dir"))?;
return Ok(PathBuf::from(app_dir).join(".config").join(APP_ID));
}
let app_handle = handle::Handle::global().app_handle().unwrap();
// 避免在Handle未初始化时崩溃
let app_handle = match handle::Handle::global().app_handle() {
Some(handle) => handle,
None => {
log::warn!(target: "app", "app_handle not initialized, using default path");
// 使用可执行文件目录作为备用
let exe_path = tauri::utils::platform::current_exe()?;
let exe_dir = exe_path
.parent()
.ok_or(anyhow::anyhow!("failed to get executable directory"))?;
// 使用系统临时目录 + 应用ID
#[cfg(target_os = "windows")]
{
if let Some(local_app_data) = std::env::var_os("LOCALAPPDATA") {
let path = PathBuf::from(local_app_data).join(APP_ID);
return Ok(path);
}
}
#[cfg(target_os = "macos")]
{
if let Some(home) = std::env::var_os("HOME") {
let path = PathBuf::from(home)
.join("Library")
.join("Application Support")
.join(APP_ID);
return Ok(path);
}
}
#[cfg(target_os = "linux")]
{
if let Some(home) = std::env::var_os("HOME") {
let path = PathBuf::from(home)
.join(".local")
.join("share")
.join(APP_ID);
return Ok(path);
}
}
// 如果无法获取系统目录,则回退到可执行文件目录
let fallback_dir = PathBuf::from(exe_dir).join(".config").join(APP_ID);
log::warn!(target: "app", "Using fallback data directory: {:?}", fallback_dir);
return Ok(fallback_dir);
}
};
match app_handle.path().data_dir() {
Ok(dir) => Ok(dir.join(APP_ID)),
Err(e) => {
log::error!(target:"app", "Failed to get the app home directory: {}", e);
log::error!(target: "app", "Failed to get the app home directory: {}", e);
Err(anyhow::anyhow!("Failed to get the app homedirectory"))
}
}
@@ -62,11 +110,24 @@ pub fn app_home_dir() -> Result<PathBuf> {
/// get the resources dir
pub fn app_resources_dir() -> Result<PathBuf> {
let app_handle = handle::Handle::global().app_handle().unwrap();
// 避免在Handle未初始化时崩溃
let app_handle = match handle::Handle::global().app_handle() {
Some(handle) => handle,
None => {
log::warn!(target: "app", "app_handle not initialized in app_resources_dir, using fallback");
// 使用可执行文件目录作为备用
let exe_dir = tauri::utils::platform::current_exe()?
.parent()
.ok_or(anyhow::anyhow!("failed to get executable directory"))?
.to_path_buf();
return Ok(exe_dir.join("resources"));
}
};
match app_handle.path().resource_dir() {
Ok(dir) => Ok(dir.join("resources")),
Err(e) => {
log::error!(target:"app", "Failed to get the resource directory: {}", e);
log::error!(target: "app", "Failed to get the resource directory: {}", e);
Err(anyhow::anyhow!("Failed to get the resource directory"))
}
}
@@ -126,12 +187,14 @@ pub fn profiles_path() -> Result<PathBuf> {
#[cfg(target_os = "macos")]
pub fn service_path() -> Result<PathBuf> {
Ok(app_resources_dir()?.join("clash-verge-service"))
let res_dir = app_resources_dir()?;
Ok(res_dir.join("clash-verge-service"))
}
#[cfg(windows)]
pub fn service_path() -> Result<PathBuf> {
Ok(app_resources_dir()?.join("clash-verge-service.exe"))
let res_dir = app_resources_dir()?;
Ok(res_dir.join("clash-verge-service.exe"))
}
pub fn service_log_file() -> Result<PathBuf> {

View File

@@ -1,40 +0,0 @@
use crate::log_err;
use anyhow;
use std::{
backtrace::{Backtrace, BacktraceStatus},
thread,
};
pub fn redirect_panic_to_log() {
std::panic::set_hook(Box::new(move |panic_info| {
let thread = thread::current();
let thread_name = thread.name().unwrap_or("<unnamed>");
let payload = panic_info.payload();
let payload = if let Some(s) = payload.downcast_ref::<&str>() {
&**s
} else if let Some(s) = payload.downcast_ref::<String>() {
s
} else {
&format!("{:?}", payload)
};
let location = panic_info
.location()
.map(|l| l.to_string())
.unwrap_or("unknown location".to_string());
let backtrace = Backtrace::capture();
let backtrace = if backtrace.status() == BacktraceStatus::Captured {
&format!("stack backtrace:\n{}", backtrace)
} else {
"note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace"
};
let err: Result<(), anyhow::Error> = Err(anyhow::anyhow!(format!(
"thread '{}' panicked at {}:\n{}\n{}",
thread_name, location, payload, backtrace
)));
log_err!(err);
}));
}

View File

@@ -1,6 +1,5 @@
pub mod autostart;
pub mod dirs;
pub mod error;
pub mod help;
pub mod i18n;
pub mod init;

View File

@@ -128,118 +128,118 @@ impl NetworkManager {
*error_count = 0;
}
}
/*
/// 获取或创建自代理客户端
fn get_or_create_self_proxy_client(&self) -> Client {
if self.should_reset_clients() {
self.reset_clients();
}
/// 获取或创建自代理客户端
fn get_or_create_self_proxy_client(&self) -> Client {
if self.should_reset_clients() {
self.reset_clients();
}
let mut client_guard = self.self_proxy_client.lock().unwrap();
let mut client_guard = self.self_proxy_client.lock().unwrap();
if client_guard.is_none() {
let port = Config::verge()
.latest()
.verge_mixed_port
.unwrap_or(Config::clash().data().get_mixed_port());
if client_guard.is_none() {
let port = Config::verge()
.latest()
.verge_mixed_port
.unwrap_or(Config::clash().data().get_mixed_port());
let proxy_scheme = format!("http://127.0.0.1:{port}");
let proxy_scheme = format!("http://127.0.0.1:{port}");
let mut builder = ClientBuilder::new()
.use_rustls_tls()
.pool_max_idle_per_host(POOL_MAX_IDLE_PER_HOST)
.pool_idle_timeout(POOL_IDLE_TIMEOUT)
.connect_timeout(DEFAULT_CONNECT_TIMEOUT)
.timeout(DEFAULT_REQUEST_TIMEOUT)
.http2_initial_stream_window_size(H2_STREAM_WINDOW_SIZE)
.http2_initial_connection_window_size(H2_CONNECTION_WINDOW_SIZE)
.http2_adaptive_window(true)
.http2_keep_alive_interval(Some(H2_KEEP_ALIVE_INTERVAL))
.http2_keep_alive_timeout(H2_KEEP_ALIVE_TIMEOUT)
.http2_max_frame_size(H2_MAX_FRAME_SIZE)
.tcp_keepalive(Some(Duration::from_secs(10)))
.http2_prior_knowledge()
.http2_max_header_list_size(16 * 1024);
let mut builder = ClientBuilder::new()
.use_rustls_tls()
.pool_max_idle_per_host(POOL_MAX_IDLE_PER_HOST)
.pool_idle_timeout(POOL_IDLE_TIMEOUT)
.connect_timeout(DEFAULT_CONNECT_TIMEOUT)
.timeout(DEFAULT_REQUEST_TIMEOUT)
.http2_initial_stream_window_size(H2_STREAM_WINDOW_SIZE)
.http2_initial_connection_window_size(H2_CONNECTION_WINDOW_SIZE)
.http2_adaptive_window(true)
.http2_keep_alive_interval(Some(H2_KEEP_ALIVE_INTERVAL))
.http2_keep_alive_timeout(H2_KEEP_ALIVE_TIMEOUT)
.http2_max_frame_size(H2_MAX_FRAME_SIZE)
.tcp_keepalive(Some(Duration::from_secs(10)))
.http2_prior_knowledge()
.http2_max_header_list_size(16 * 1024);
if let Ok(proxy) = Proxy::http(&proxy_scheme) {
builder = builder.proxy(proxy);
}
if let Ok(proxy) = Proxy::https(&proxy_scheme) {
builder = builder.proxy(proxy);
}
if let Ok(proxy) = Proxy::all(&proxy_scheme) {
builder = builder.proxy(proxy);
}
if let Ok(proxy) = Proxy::http(&proxy_scheme) {
builder = builder.proxy(proxy);
}
if let Ok(proxy) = Proxy::https(&proxy_scheme) {
builder = builder.proxy(proxy);
}
if let Ok(proxy) = Proxy::all(&proxy_scheme) {
builder = builder.proxy(proxy);
}
let client = builder.build().expect("Failed to build self_proxy client");
*client_guard = Some(client);
}
let client = builder.build().expect("Failed to build self_proxy client");
*client_guard = Some(client);
}
client_guard.as_ref().unwrap().clone()
}
client_guard.as_ref().unwrap().clone()
}
/// 获取或创建系统代理客户端
fn get_or_create_system_proxy_client(&self) -> Client {
if self.should_reset_clients() {
self.reset_clients();
}
/// 获取或创建系统代理客户端
fn get_or_create_system_proxy_client(&self) -> Client {
if self.should_reset_clients() {
self.reset_clients();
}
let mut client_guard = self.system_proxy_client.lock().unwrap();
let mut client_guard = self.system_proxy_client.lock().unwrap();
if client_guard.is_none() {
use sysproxy::Sysproxy;
if client_guard.is_none() {
use sysproxy::Sysproxy;
let mut builder = ClientBuilder::new()
.use_rustls_tls()
.pool_max_idle_per_host(POOL_MAX_IDLE_PER_HOST)
.pool_idle_timeout(POOL_IDLE_TIMEOUT)
.connect_timeout(DEFAULT_CONNECT_TIMEOUT)
.timeout(DEFAULT_REQUEST_TIMEOUT)
.http2_initial_stream_window_size(H2_STREAM_WINDOW_SIZE)
.http2_initial_connection_window_size(H2_CONNECTION_WINDOW_SIZE)
.http2_adaptive_window(true)
.http2_keep_alive_interval(Some(H2_KEEP_ALIVE_INTERVAL))
.http2_keep_alive_timeout(H2_KEEP_ALIVE_TIMEOUT)
.http2_max_frame_size(H2_MAX_FRAME_SIZE)
.tcp_keepalive(Some(Duration::from_secs(10)))
.http2_prior_knowledge()
.http2_max_header_list_size(16 * 1024);
let mut builder = ClientBuilder::new()
.use_rustls_tls()
.pool_max_idle_per_host(POOL_MAX_IDLE_PER_HOST)
.pool_idle_timeout(POOL_IDLE_TIMEOUT)
.connect_timeout(DEFAULT_CONNECT_TIMEOUT)
.timeout(DEFAULT_REQUEST_TIMEOUT)
.http2_initial_stream_window_size(H2_STREAM_WINDOW_SIZE)
.http2_initial_connection_window_size(H2_CONNECTION_WINDOW_SIZE)
.http2_adaptive_window(true)
.http2_keep_alive_interval(Some(H2_KEEP_ALIVE_INTERVAL))
.http2_keep_alive_timeout(H2_KEEP_ALIVE_TIMEOUT)
.http2_max_frame_size(H2_MAX_FRAME_SIZE)
.tcp_keepalive(Some(Duration::from_secs(10)))
.http2_prior_knowledge()
.http2_max_header_list_size(16 * 1024);
if let Ok(p @ Sysproxy { enable: true, .. }) = Sysproxy::get_system_proxy() {
let proxy_scheme = format!("http://{}:{}", p.host, p.port);
if let Ok(p @ Sysproxy { enable: true, .. }) = Sysproxy::get_system_proxy() {
let proxy_scheme = format!("http://{}:{}", p.host, p.port);
if let Ok(proxy) = Proxy::http(&proxy_scheme) {
builder = builder.proxy(proxy);
}
if let Ok(proxy) = Proxy::https(&proxy_scheme) {
builder = builder.proxy(proxy);
}
if let Ok(proxy) = Proxy::all(&proxy_scheme) {
builder = builder.proxy(proxy);
}
}
if let Ok(proxy) = Proxy::http(&proxy_scheme) {
builder = builder.proxy(proxy);
}
if let Ok(proxy) = Proxy::https(&proxy_scheme) {
builder = builder.proxy(proxy);
}
if let Ok(proxy) = Proxy::all(&proxy_scheme) {
builder = builder.proxy(proxy);
}
}
let client = builder
.build()
.expect("Failed to build system_proxy client");
*client_guard = Some(client);
}
let client = builder
.build()
.expect("Failed to build system_proxy client");
*client_guard = Some(client);
}
client_guard.as_ref().unwrap().clone()
}
/// 根据代理设置选择合适的客户端
pub fn get_client(&self, proxy_type: ProxyType) -> Client {
match proxy_type {
ProxyType::NoProxy => {
let client_guard = self.no_proxy_client.lock().unwrap();
client_guard.as_ref().unwrap().clone()
}
ProxyType::SelfProxy => self.get_or_create_self_proxy_client(),
ProxyType::SystemProxy => self.get_or_create_system_proxy_client(),
}
}
client_guard.as_ref().unwrap().clone()
}
/// 根据代理设置选择合适的客户端
pub fn get_client(&self, proxy_type: ProxyType) -> Client {
match proxy_type {
ProxyType::NoProxy => {
let client_guard = self.no_proxy_client.lock().unwrap();
client_guard.as_ref().unwrap().clone()
}
ProxyType::SelfProxy => self.get_or_create_self_proxy_client(),
ProxyType::SystemProxy => self.get_or_create_system_proxy_client(),
}
}
*/
/// 创建带有自定义选项的HTTP请求
pub fn create_request(
&self,
@@ -335,7 +335,7 @@ impl NetworkManager {
client.get(url)
}
/// 执行GET请求添加错误跟踪
/* /// 执行GET请求添加错误跟踪
pub async fn get(
&self,
url: &str,
@@ -370,7 +370,7 @@ impl NetworkManager {
))
}
}
}
} */
pub async fn get_with_interrupt(
&self,

View File

@@ -1,12 +1,10 @@
#[cfg(target_os = "macos")]
use crate::AppHandleManager;
use crate::{
config::{Config, IVerge, PrfItem},
core::*,
logging, logging_error,
module::lightweight,
process::AsyncHandler,
utils::{dirs, error, init, logging::Type, server},
utils::{init, logging::Type, server},
wrap_err,
};
use anyhow::{bail, Result};
@@ -14,14 +12,13 @@ use once_cell::sync::OnceCell;
use parking_lot::{Mutex, RwLock};
use percent_encoding::percent_decode_str;
use serde::{Deserialize, Serialize};
use serde_json;
use serde_yaml::Mapping;
use std::{
net::TcpListener,
sync::Arc,
time::{Duration, Instant},
};
use tauri::{App, Emitter, Manager};
use tauri::{AppHandle, Emitter, Manager};
use tauri::Url;
//#[cfg(not(target_os = "linux"))]
@@ -29,10 +26,6 @@ use tauri::Url;
pub static VERSION: OnceCell<String> = OnceCell::new();
// 窗口状态文件中的尺寸
static STATE_WIDTH: OnceCell<u32> = OnceCell::new();
static STATE_HEIGHT: OnceCell<u32> = OnceCell::new();
// 定义默认窗口尺寸常量
const DEFAULT_WIDTH: u32 = 900;
const DEFAULT_HEIGHT: u32 = 700;
@@ -43,6 +36,35 @@ static UI_READY: OnceCell<Arc<RwLock<bool>>> = OnceCell::new();
// 窗口创建锁,防止并发创建窗口
static WINDOW_CREATING: OnceCell<Mutex<(bool, Instant)>> = OnceCell::new();
// UI就绪阶段状态枚举
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum UiReadyStage {
NotStarted,
Loading,
DomReady,
ResourcesLoaded,
Ready,
}
// UI就绪详细状态
#[derive(Debug)]
struct UiReadyState {
stage: RwLock<UiReadyStage>,
last_update: RwLock<Instant>,
}
impl Default for UiReadyState {
fn default() -> Self {
Self {
stage: RwLock::new(UiReadyStage::NotStarted),
last_update: RwLock::new(Instant::now()),
}
}
}
// 获取UI就绪状态细节
static UI_READY_STATE: OnceCell<Arc<UiReadyState>> = OnceCell::new();
// 定义窗口状态结构体
#[derive(Debug, Serialize, Deserialize)]
struct WindowState {
@@ -58,16 +80,54 @@ fn get_ui_ready() -> &'static Arc<RwLock<bool>> {
UI_READY.get_or_init(|| Arc::new(RwLock::new(false)))
}
fn get_ui_ready_state() -> &'static Arc<UiReadyState> {
UI_READY_STATE.get_or_init(|| Arc::new(UiReadyState::default()))
}
// 更新UI准备阶段
pub fn update_ui_ready_stage(stage: UiReadyStage) {
let state = get_ui_ready_state();
let mut stage_lock = state.stage.write();
let mut time_lock = state.last_update.write();
*stage_lock = stage;
*time_lock = Instant::now();
logging!(
info,
Type::Window,
true,
"UI准备阶段更新: {:?}, 耗时: {:?}ms",
stage,
time_lock.elapsed().as_millis()
);
// 如果是最终阶段标记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 mut ready = get_ui_ready().write();
*ready = false;
}
{
let state = get_ui_ready_state();
let mut stage = state.stage.write();
let mut time = state.last_update.write();
*stage = UiReadyStage::NotStarted;
*time = Instant::now();
}
logging!(info, Type::Window, true, "UI就绪状态已重置");
}
@@ -88,54 +148,53 @@ pub fn find_unused_port() -> Result<u16> {
}
}
/// handle something when start app
pub async fn resolve_setup(app: &mut App) {
error::redirect_panic_to_log();
#[cfg(target_os = "macos")]
{
AppHandleManager::global().init(app.app_handle().clone());
AppHandleManager::global().set_activation_policy_accessory();
/// 异步方式处理启动后的额外任务
pub async fn resolve_setup_async(app_handle: &AppHandle) {
logging!(info, Type::Setup, true, "执行异步设置任务...");
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()
});
}
let version = app.package_info().version.to_string();
handle::Handle::global().init(app.app_handle());
VERSION.get_or_init(|| version.clone());
logging_error!(Type::Config, true, init::init_config());
logging_error!(Type::Setup, true, init::init_resources());
logging_error!(Type::Setup, true, init::init_scheme());
logging_error!(Type::Setup, true, init::startup_script().await);
// 诊断服务状态
/* logging!(info, Type::Service, true, "执行服务状态诊断");
{
match crate::core::service::diagnose_service().await {
Ok(_) => {
logging!(info, Type::Service, true, "服务诊断完成");
},
Err(e) => {
logging!(error, Type::Service, true, "服务诊断出错: {}", e);
},
}
} */
// 处理随机端口
logging_error!(Type::System, true, resolve_random_port_config());
// 启动核心
logging!(trace, Type::Config, true, "Initial config");
logging!(trace, Type::Config, true, "初始化配置...");
logging_error!(Type::Config, true, Config::init_config().await);
logging!(trace, Type::Core, "Starting CoreManager");
logging!(trace, Type::Core, true, "启动核心管理器...");
logging_error!(Type::Core, true, CoreManager::global().init().await);
// setup a simple http server for singleton
log::trace!(target: "app", "launch embed server");
log::trace!(target: "app", "启动内嵌服务器...");
server::embed_server();
log::trace!(target: "app", "Initial system tray");
logging_error!(Type::Tray, true, tray::Tray::global().init());
logging_error!(Type::Tray, true, tray::Tray::global().create_systray(app));
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);
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,
@@ -147,11 +206,14 @@ pub async fn resolve_setup(app: &mut App) {
sysopt::Sysopt::global().init_guard_sysproxy()
);
// 创建窗口
let is_silent_start = { Config::verge().data().enable_silent_start }.unwrap_or(false);
create_window(!is_silent_start);
// 初始化定时器
logging_error!(Type::System, true, timer::Timer::global().init());
// 自动进入轻量模式
let enable_auto_light_weight_mode =
{ Config::verge().data().enable_auto_light_weight_mode }.unwrap_or(false);
if enable_auto_light_weight_mode && !is_silent_start {
@@ -160,12 +222,13 @@ pub async fn resolve_setup(app: &mut App) {
logging_error!(Type::Tray, true, tray::Tray::global().update_part());
// 初始化热键
logging!(trace, Type::System, true, "Initial hotkeys");
logging!(trace, Type::System, true, "初始化热键...");
logging_error!(Type::System, true, hotkey::Hotkey::global().init());
logging!(info, Type::Setup, true, "异步设置任务完成");
}
/// reset system proxy (异步)
/// reset system proxy (异步)
pub async fn resolve_reset_async() {
#[cfg(target_os = "macos")]
logging!(info, Type::Tray, true, "Unsubscribing from traffic updates");
@@ -185,321 +248,135 @@ pub async fn resolve_reset_async() {
}
}
/// 窗口创建锁守卫
struct WindowCreateGuard;
/// Create the main window
pub fn create_window(is_show: bool) -> bool {
logging!(
info,
Type::Window,
true,
"开始创建主窗口, is_show={}",
is_show
);
impl Drop for WindowCreateGuard {
fn drop(&mut self) {
let mut lock = get_window_creating_lock().lock();
lock.0 = false;
logging!(info, Type::Window, true, "窗口创建过程已完成,释放锁");
}
}
let creating_lock = get_window_creating_lock();
let mut creating = creating_lock.lock();
/// create main window
pub fn create_window(is_showup: bool) {
// 尝试获取窗口创建锁
let mut creating_lock = get_window_creating_lock().lock();
let (is_creating, last_create_time) = *creating_lock;
let now = Instant::now();
let (is_creating, last_time) = *creating;
let elapsed = last_time.elapsed();
// 检查是否有其他线程正在创建窗口,防止短时间内多次创建窗口导致竞态条件
if is_creating && now.duration_since(last_create_time) < Duration::from_secs(2) {
if is_creating && elapsed < Duration::from_secs(2) {
logging!(
warn,
info,
Type::Window,
true,
"另一个窗口创建过程正在进行中,跳过本次创建请求"
"窗口创建请求被忽略,因为最近创建过 ({:?}ms)",
elapsed.as_millis()
);
return;
return false;
}
*creating_lock = (true, now);
drop(creating_lock);
*creating = (true, Instant::now());
// 创建窗口锁守卫结束时自动释放锁
let _guard = WindowCreateGuard;
// 打印 .window-state.json 文件路径
let window_state_file = dirs::app_home_dir()
.ok()
.map(|dir| dir.join(".window-state.json"));
logging!(
info,
Type::Window,
true,
"窗口状态文件路径: {:?}",
window_state_file
);
// 从文件加载窗口状态
if let Some(window_state_file_path) = window_state_file {
if window_state_file_path.exists() {
match std::fs::read_to_string(&window_state_file_path) {
Ok(content) => {
logging!(
debug,
Type::Window,
true,
"读取窗口状态文件内容成功: {} 字节",
content.len()
);
match serde_json::from_str::<WindowState>(&content) {
Ok(window_state) => {
logging!(
info,
Type::Window,
true,
"成功解析窗口状态: width={:?}, height={:?}",
window_state.width,
window_state.height
);
// 存储窗口状态以供后续使用
if let Some(width) = window_state.width {
STATE_WIDTH.set(width).ok();
}
if let Some(height) = window_state.height {
STATE_HEIGHT.set(height).ok();
}
}
Err(e) => {
logging!(error, Type::Window, true, "解析窗口状态文件失败: {:?}", e);
}
}
}
Err(e) => {
logging!(error, Type::Window, true, "读取窗口状态文件失败: {:?}", e);
}
}
} else {
logging!(
info,
Type::Window,
true,
"窗口状态文件不存在,将使用默认设置"
);
}
}
if !is_showup {
logging!(info, Type::Window, "Not to display create window");
return;
}
logging!(info, Type::Window, true, "Creating window");
let app_handle = handle::Handle::global().app_handle().unwrap();
#[cfg(target_os = "macos")]
AppHandleManager::global().set_activation_policy_regular();
// 检查是否从轻量模式恢复
let from_lightweight = crate::module::lightweight::is_in_lightweight_mode();
if from_lightweight {
logging!(info, Type::Window, true, "从轻量模式恢复窗口");
crate::module::lightweight::exit_lightweight_mode();
}
if let Some(window) = handle::Handle::global().get_window() {
logging!(info, Type::Window, true, "Found existing window");
if window.is_minimized().unwrap_or(false) {
let _ = window.unminimize();
}
if from_lightweight {
// 从轻量模式恢复需要销毁旧窗口以重建
logging!(info, Type::Window, true, "销毁旧窗口以重建新窗口");
let _ = window.close();
// 添加短暂延迟确保窗口正确关闭
std::thread::sleep(std::time::Duration::from_millis(100));
} else {
// 普通情况直接显示窗口
let _ = window.show();
let _ = window.set_focus();
return;
}
}
let width = STATE_WIDTH.get().copied().unwrap_or(DEFAULT_WIDTH);
let height = STATE_HEIGHT.get().copied().unwrap_or(DEFAULT_HEIGHT);
logging!(
info,
Type::Window,
true,
"Initializing new window with size: {}x{}",
width,
height
);
// 根据不同平台创建不同配置的窗口
#[cfg(target_os = "macos")]
let win_builder = {
// 基本配置
let builder = tauri::WebviewWindowBuilder::new(
&app_handle,
"main",
tauri::WebviewUrl::App("index.html".into()),
)
.title("Clash Verge")
.center()
.decorations(true)
.hidden_title(true) // 隐藏标题文本
.fullscreen(false)
.inner_size(width as f64, height as f64)
.min_inner_size(520.0, 520.0)
.visible(false);
// 尝试设置标题栏样式
// 注意根据Tauri版本不同此API可能有变化
// 如果编译出错,请注释掉下面这行
let builder = builder.title_bar_style(tauri::TitleBarStyle::Overlay);
builder
};
#[cfg(not(target_os = "macos"))]
let win_builder = tauri::WebviewWindowBuilder::new(
&app_handle,
"main",
match tauri::WebviewWindowBuilder::new(
&handle::Handle::global().app_handle().unwrap(),
"main", /* the unique window label */
tauri::WebviewUrl::App("index.html".into()),
)
.title("Clash Verge")
.center()
.decorations(true)
.fullscreen(false)
.inner_size(width as f64, height as f64)
.inner_size(DEFAULT_WIDTH as f64, DEFAULT_HEIGHT as f64)
.min_inner_size(520.0, 520.0)
.visible(false)
.decorations(false);
.build()
{
Ok(_) => {
logging!(info, Type::Window, true, "主窗口创建成功");
let window = win_builder.build();
*creating = (false, Instant::now());
match window {
Ok(window) => {
logging!(info, Type::Window, true, "Window created successfully");
// 静默启动模式等窗口初始化再启动自动进入轻量模式的计时监听器,防止初始化的时候找不到窗口对象导致监听器挂载失败
lightweight::run_once_auto_lightweight();
// 标记前端UI已准备就绪向前端发送启动完成事件
let app_handle_clone = app_handle.clone();
// 获取窗口创建后的初始大小
if let Ok(size) = window.inner_size() {
let state_width = STATE_WIDTH.get().copied().unwrap_or(DEFAULT_WIDTH);
let state_height = STATE_HEIGHT.get().copied().unwrap_or(DEFAULT_HEIGHT);
// 输出所有尺寸信息
logging!(
info,
Type::Window,
true,
"API报告的窗口尺寸: {}x{}, 状态文件尺寸: {}x{}, 默认尺寸: {}x{}",
size.width,
size.height,
state_width,
state_height,
DEFAULT_WIDTH,
DEFAULT_HEIGHT
);
// 优化窗口大小设置
if size.width < state_width || size.height < state_height {
logging!(
info,
Type::Window,
true,
"强制设置窗口尺寸: {}x{}",
state_width,
state_height
);
// 尝试不同的方式设置窗口大小
let _ = window.set_size(tauri::PhysicalSize {
width: state_width,
height: state_height,
});
// 关键:等待短暂时间让窗口尺寸生效
std::thread::sleep(std::time::Duration::from_millis(50));
// 再次检查窗口尺寸
if let Ok(new_size) = window.inner_size() {
logging!(
info,
Type::Window,
true,
"设置后API报告的窗口尺寸: {}x{}",
new_size.width,
new_size.height
);
}
}
}
// 标记此窗口是否从轻量模式恢复
let was_from_lightweight = from_lightweight;
update_ui_ready_stage(UiReadyStage::NotStarted);
AsyncHandler::spawn(move || async move {
// 处理启动完成
handle::Handle::global().mark_startup_completed();
logging!(info, Type::Window, true, "标记启动完成");
if let Some(window) = app_handle_clone.get_webview_window("main") {
// 发送启动完成事件
let _ = window.emit("verge://startup-completed", ());
if let Some(app_handle) = handle::Handle::global().app_handle() {
if let Some(window) = app_handle.get_webview_window("main") {
let _ = window.emit("verge://startup-completed", ());
logging!(info, Type::Window, true, "已发送启动完成事件");
if is_showup {
let window_clone = window.clone();
if is_show {
let window_clone = window.clone();
// 从轻量模式恢复时使用较短的超时,避免卡死
let timeout_seconds = if was_from_lightweight {
// 从轻量模式恢复只等待2秒确保不会卡死
2
} else {
5
};
let timeout_seconds =
if crate::module::lightweight::is_in_lightweight_mode() {
2
} else {
5
};
// 使用普通的等待方式替代事件监听,简化实现
let wait_result =
tokio::time::timeout(Duration::from_secs(timeout_seconds), async {
while !*get_ui_ready().read() {
tokio::time::sleep(Duration::from_millis(100)).await;
logging!(
info,
Type::Window,
true,
"等待UI就绪最多{}秒",
timeout_seconds
);
let wait_result =
tokio::time::timeout(Duration::from_secs(timeout_seconds), async {
let mut check_count = 0;
while !*get_ui_ready().read() {
check_count += 1;
if check_count % 10 == 0 {
let state = get_ui_ready_state();
let stage = *state.stage.read();
logging!(
info,
Type::Window,
true,
"等待UI就绪中... 当前阶段: {:?}, 已等待: {}ms",
stage,
check_count * 100
);
}
tokio::time::sleep(Duration::from_millis(100)).await;
}
})
.await;
match wait_result {
Ok(_) => {
logging!(info, Type::Window, true, "UI就绪显示窗口");
}
})
.await;
Err(_) => {
logging!(
warn,
Type::Window,
true,
"等待UI就绪超时({}秒),强制显示窗口",
timeout_seconds
);
// 根据结果处理
match wait_result {
Ok(_) => {
logging!(info, Type::Window, true, "UI就绪显示窗口");
}
Err(_) => {
logging!(
warn,
Type::Window,
true,
"等待UI就绪超时({}秒),强制显示窗口",
timeout_seconds
);
// 强制设置UI就绪状态
*get_ui_ready().write() = true;
*get_ui_ready().write() = true;
}
}
let _ = window_clone.show();
let _ = window_clone.set_focus();
logging!(info, Type::Window, true, "窗口创建和显示流程已完成");
}
// 显示窗口
let _ = window_clone.show();
let _ = window_clone.set_focus();
logging!(info, Type::Window, true, "窗口创建和显示流程已完成");
}
}
});
true
}
Err(e) => {
logging!(error, Type::Window, true, "Failed to create window: {}", e);
false
}
}
}
@@ -529,7 +406,6 @@ pub async fn resolve_scheme(param: String) -> Result<()> {
.find(|(key, _)| key == "name")
.map(|(_, value)| value.into_owned());
// 通过直接获取查询部分并解析特定参数来避免 URL 转义问题
let url_param = if let Some(query) = link_parsed.query() {
let prefix = "url=";
if let Some(pos) = query.find(prefix) {