mirror of
https://github.com/bolucat/Archive.git
synced 2025-10-30 11:26:53 +08:00
Update On Fri May 9 20:36:54 CEST 2025
This commit is contained in:
@@ -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<()> {
|
||||
|
||||
@@ -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(¤t_profile_uid);
|
||||
current_profile_name = match ¤t_profile.unwrap().name {
|
||||
Some(profile_name) => profile_name.to_string(),
|
||||
None => current_profile_name,
|
||||
};
|
||||
if let Ok(profile) = profiles.get_item(¤t_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(),
|
||||
|
||||
@@ -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")]
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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> {
|
||||
|
||||
@@ -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);
|
||||
}));
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
pub mod autostart;
|
||||
pub mod dirs;
|
||||
pub mod error;
|
||||
pub mod help;
|
||||
pub mod i18n;
|
||||
pub mod init;
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user