mirror of
https://github.com/clash-verge-rev/clash-verge-rev.git
synced 2026-01-29 08:45:41 +08:00
refactor: use async approach to restructure UI startup logic and resolve various freeze issues during launch
This commit is contained in:
@@ -18,6 +18,7 @@
|
|||||||
- 切换自定义代理地址导致系统代理状态异常
|
- 切换自定义代理地址导致系统代理状态异常
|
||||||
- Macos TUN 默认无效网卡名称
|
- Macos TUN 默认无效网卡名称
|
||||||
- 托盘更改订阅后 UI 不同步的问题
|
- 托盘更改订阅后 UI 不同步的问题
|
||||||
|
- 修复提权漏洞,改用带认证的 IPC 通信(后续还可以加强完善认证密钥创建和管理机制)
|
||||||
|
|
||||||
#### 新增了:
|
#### 新增了:
|
||||||
- 允许代理主机地址设置为非 127.0.0.1 对 WSL 代理友好
|
- 允许代理主机地址设置为非 127.0.0.1 对 WSL 代理友好
|
||||||
@@ -47,6 +48,7 @@
|
|||||||
- 重构前端通知系统分离通知线程防止前端卡死
|
- 重构前端通知系统分离通知线程防止前端卡死
|
||||||
- 优化网络请求和错误处理
|
- 优化网络请求和错误处理
|
||||||
- 重构通知系统
|
- 重构通知系统
|
||||||
|
- 使用异步方法重构 UI 启动逻辑,解决启动软件过程中的各种卡死问题
|
||||||
|
|
||||||
## v2.2.3
|
## v2.2.3
|
||||||
|
|
||||||
|
|||||||
@@ -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_cache_dir = wrap_err!(dirs::app_home_dir())?.join("icons").join("cache");
|
||||||
let icon_path = icon_cache_dir.join(&name);
|
let icon_path = icon_cache_dir.join(&name);
|
||||||
|
|
||||||
// 如果文件已存在,直接返回路径
|
|
||||||
if icon_path.exists() {
|
if icon_path.exists() {
|
||||||
return Ok(icon_path.to_string_lossy().to_string());
|
return Ok(icon_path.to_string_lossy().to_string());
|
||||||
}
|
}
|
||||||
|
|
||||||
// 确保缓存目录存在
|
|
||||||
if !icon_cache_dir.exists() {
|
if !icon_cache_dir.exists() {
|
||||||
let _ = std::fs::create_dir_all(&icon_cache_dir);
|
let _ = std::fs::create_dir_all(&icon_cache_dir);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 使用临时文件名来下载
|
|
||||||
let temp_path = icon_cache_dir.join(format!("{}.downloading", &name));
|
let temp_path = icon_cache_dir.join(format!("{}.downloading", &name));
|
||||||
|
|
||||||
// 下载文件到临时位置
|
|
||||||
let response = wrap_err!(reqwest::get(&url).await)?;
|
let response = wrap_err!(reqwest::get(&url).await)?;
|
||||||
|
|
||||||
// 检查内容类型是否为图片
|
|
||||||
let content_type = response
|
let content_type = response
|
||||||
.headers()
|
.headers()
|
||||||
.get(reqwest::header::CONTENT_TYPE)
|
.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 is_image = content_type.starts_with("image/");
|
||||||
|
|
||||||
// 获取响应内容
|
|
||||||
let content = wrap_err!(response.bytes().await)?;
|
let content = wrap_err!(response.bytes().await)?;
|
||||||
|
|
||||||
// 检查内容是否为HTML (针对CDN错误页面)
|
|
||||||
let is_html = content.len() > 15
|
let is_html = content.len() > 15
|
||||||
&& (content.starts_with(b"<!DOCTYPE html")
|
&& (content.starts_with(b"<!DOCTYPE html")
|
||||||
|| content.starts_with(b"<html")
|
|| content.starts_with(b"<html")
|
||||||
|| content.starts_with(b"<?xml"));
|
|| content.starts_with(b"<?xml"));
|
||||||
|
|
||||||
// 只有当内容确实是图片时才保存
|
|
||||||
if is_image && !is_html {
|
if is_image && !is_html {
|
||||||
{
|
{
|
||||||
let mut file = match std::fs::File::create(&temp_path) {
|
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))?;
|
wrap_err!(std::io::copy(&mut content.as_ref(), &mut file))?;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 再次检查目标文件是否已存在,避免重命名覆盖其他线程已完成的文件
|
|
||||||
if !icon_path.exists() {
|
if !icon_path.exists() {
|
||||||
match std::fs::rename(&temp_path, &icon_path) {
|
match std::fs::rename(&temp_path, &icon_path) {
|
||||||
Ok(_) => {}
|
Ok(_) => {}
|
||||||
@@ -223,6 +214,29 @@ pub fn notify_ui_ready() -> CmdResult<()> {
|
|||||||
Ok(())
|
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就绪状态
|
/// 重置UI就绪状态
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub fn reset_ui_ready_state() -> CmdResult<()> {
|
pub fn reset_ui_ready_state() -> CmdResult<()> {
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ use crate::{
|
|||||||
mihomo::Rate,
|
mihomo::Rate,
|
||||||
},
|
},
|
||||||
resolve,
|
resolve,
|
||||||
utils::{dirs::find_target_icons, i18n::t, logging::Type, resolve::VERSION},
|
utils::{dirs::find_target_icons, i18n::t, resolve::VERSION},
|
||||||
};
|
};
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
@@ -29,7 +29,7 @@ use std::sync::Arc;
|
|||||||
use tauri::{
|
use tauri::{
|
||||||
menu::{CheckMenuItem, IsMenuItem, MenuEvent, MenuItem, PredefinedMenuItem, Submenu},
|
menu::{CheckMenuItem, IsMenuItem, MenuEvent, MenuItem, PredefinedMenuItem, Submenu},
|
||||||
tray::{MouseButton, MouseButtonState, TrayIconEvent},
|
tray::{MouseButton, MouseButtonState, TrayIconEvent},
|
||||||
App, AppHandle, Wry,
|
AppHandle, Wry,
|
||||||
};
|
};
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
use tokio::sync::broadcast;
|
use tokio::sync::broadcast;
|
||||||
@@ -178,52 +178,6 @@ impl Tray {
|
|||||||
Ok(())
|
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<()> {
|
pub fn update_click_behavior(&self) -> Result<()> {
|
||||||
let app_handle = handle::Handle::global().app_handle().unwrap();
|
let app_handle = handle::Handle::global().app_handle().unwrap();
|
||||||
@@ -239,7 +193,14 @@ impl Tray {
|
|||||||
|
|
||||||
/// 更新托盘菜单
|
/// 更新托盘菜单
|
||||||
pub fn update_menu(&self) -> Result<()> {
|
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 verge = Config::verge().latest().clone();
|
||||||
let system_proxy = verge.enable_system_proxy.as_ref().unwrap_or(&false);
|
let system_proxy = verge.enable_system_proxy.as_ref().unwrap_or(&false);
|
||||||
let tun_mode = verge.enable_tun_mode.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();
|
.unwrap_or_default();
|
||||||
let is_lightweight_mode = is_in_lightweight_mode();
|
let is_lightweight_mode = is_in_lightweight_mode();
|
||||||
|
|
||||||
let tray = app_handle.tray_by_id("main").unwrap();
|
match app_handle.tray_by_id("main") {
|
||||||
let _ = tray.set_menu(Some(create_tray_menu(
|
Some(tray) => {
|
||||||
&app_handle,
|
let _ = tray.set_menu(Some(create_tray_menu(
|
||||||
Some(mode.as_str()),
|
&app_handle,
|
||||||
*system_proxy,
|
Some(mode.as_str()),
|
||||||
*tun_mode,
|
*system_proxy,
|
||||||
profile_uid_and_name,
|
*tun_mode,
|
||||||
is_lightweight_mode,
|
profile_uid_and_name,
|
||||||
)?));
|
is_lightweight_mode,
|
||||||
Ok(())
|
)?));
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
log::warn!(target: "app", "更新托盘菜单失败: 托盘不存在");
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 更新托盘图标
|
/// 更新托盘图标
|
||||||
pub fn update_icon(&self, rate: Option<Rate>) -> Result<()> {
|
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 verge = Config::verge().latest().clone();
|
||||||
let system_mode = verge.enable_system_proxy.as_ref().unwrap_or(&false);
|
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 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) {
|
let (is_custom_icon, icon_bytes) = match (*system_mode, *tun_mode) {
|
||||||
(true, true) => TrayState::get_tun_tray_icon(),
|
(true, true) => TrayState::get_tun_tray_icon(),
|
||||||
(true, false) => TrayState::get_sysproxy_tray_icon(),
|
(true, false) => TrayState::get_sysproxy_tray_icon(),
|
||||||
@@ -302,8 +283,12 @@ impl Tray {
|
|||||||
Some(rate)
|
Some(rate)
|
||||||
} else {
|
} else {
|
||||||
let guard = self.speed_rate.lock();
|
let guard = self.speed_rate.lock();
|
||||||
if let Some(rate) = guard.as_ref().unwrap().get_curent_rate() {
|
if let Some(guard) = guard.as_ref() {
|
||||||
Some(rate)
|
if let Some(rate) = guard.get_curent_rate() {
|
||||||
|
Some(rate)
|
||||||
|
} else {
|
||||||
|
Some(Rate::default())
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
Some(Rate::default())
|
Some(Rate::default())
|
||||||
}
|
}
|
||||||
@@ -320,9 +305,10 @@ impl Tray {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let rate = rate_guard.as_ref();
|
let rate = rate_guard.as_ref();
|
||||||
let rate_bytes = SpeedRate::add_speed_text(is_custom_icon, bytes, rate).unwrap();
|
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(Some(tauri::image::Image::from_bytes(&rate_bytes)?));
|
||||||
let _ = tray.set_icon_as_template(!is_custom_icon && !is_colorful);
|
let _ = tray.set_icon_as_template(!is_custom_icon && !is_colorful);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -336,8 +322,21 @@ impl Tray {
|
|||||||
|
|
||||||
/// 更新托盘提示
|
/// 更新托盘提示
|
||||||
pub fn update_tooltip(&self) -> Result<()> {
|
pub fn update_tooltip(&self) -> Result<()> {
|
||||||
let app_handle = handle::Handle::global().app_handle().unwrap();
|
let app_handle = match handle::Handle::global().app_handle() {
|
||||||
let version = VERSION.get().unwrap();
|
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 verge = Config::verge().latest().clone();
|
||||||
let system_proxy = verge.enable_system_proxy.as_ref().unwrap_or(&false);
|
let system_proxy = verge.enable_system_proxy.as_ref().unwrap_or(&false);
|
||||||
@@ -354,23 +353,28 @@ impl Tray {
|
|||||||
let profiles = Config::profiles();
|
let profiles = Config::profiles();
|
||||||
let profiles = profiles.latest();
|
let profiles = profiles.latest();
|
||||||
if let Some(current_profile_uid) = profiles.get_current() {
|
if let Some(current_profile_uid) = profiles.get_current() {
|
||||||
let current_profile = profiles.get_item(¤t_profile_uid);
|
if let Ok(profile) = profiles.get_item(¤t_profile_uid) {
|
||||||
current_profile_name = match ¤t_profile.unwrap().name {
|
current_profile_name = match &profile.name {
|
||||||
Some(profile_name) => profile_name.to_string(),
|
Some(profile_name) => profile_name.to_string(),
|
||||||
None => current_profile_name,
|
None => current_profile_name,
|
||||||
};
|
};
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let tray = app_handle.tray_by_id("main").unwrap();
|
if let Some(tray) = app_handle.tray_by_id("main") {
|
||||||
let _ = tray.set_tooltip(Some(&format!(
|
let _ = tray.set_tooltip(Some(&format!(
|
||||||
"Clash Verge {version}\n{}: {}\n{}: {}\n{}: {}",
|
"Clash Verge {version}\n{}: {}\n{}: {}\n{}: {}",
|
||||||
t("SysProxy"),
|
t("SysProxy"),
|
||||||
switch_map[system_proxy],
|
switch_map[system_proxy],
|
||||||
t("TUN"),
|
t("TUN"),
|
||||||
switch_map[tun_mode],
|
switch_map[tun_mode],
|
||||||
t("Profile"),
|
t("Profile"),
|
||||||
current_profile_name
|
current_profile_name
|
||||||
)));
|
)));
|
||||||
|
} else {
|
||||||
|
log::warn!(target: "app", "更新托盘提示失败: 托盘不存在");
|
||||||
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -532,6 +536,57 @@ impl Tray {
|
|||||||
drop(tx);
|
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(
|
fn create_tray_menu(
|
||||||
@@ -543,7 +598,10 @@ fn create_tray_menu(
|
|||||||
is_lightweight_mode: bool,
|
is_lightweight_mode: bool,
|
||||||
) -> Result<tauri::menu::Menu<Wry>> {
|
) -> Result<tauri::menu::Menu<Wry>> {
|
||||||
let mode = mode.unwrap_or("");
|
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()
|
let hotkeys = Config::verge()
|
||||||
.latest()
|
.latest()
|
||||||
.hotkeys
|
.hotkeys
|
||||||
@@ -779,14 +837,20 @@ fn on_menu_event(_: &AppHandle, event: MenuEvent) {
|
|||||||
crate::module::lightweight::exit_lightweight_mode();
|
crate::module::lightweight::exit_lightweight_mode();
|
||||||
}
|
}
|
||||||
// 然后创建窗口
|
// 然后创建窗口
|
||||||
resolve::create_window(true)
|
let _ = resolve::create_window(true);
|
||||||
}
|
}
|
||||||
"system_proxy" => feat::toggle_system_proxy(),
|
"system_proxy" => feat::toggle_system_proxy(),
|
||||||
"tun_mode" => feat::toggle_tun_mode(None),
|
"tun_mode" => feat::toggle_tun_mode(None),
|
||||||
"copy_env" => feat::copy_clash_env(),
|
"copy_env" => feat::copy_clash_env(),
|
||||||
"open_app_dir" => crate::logging_error!(Type::Cmd, true, cmd::open_app_dir()),
|
"open_app_dir" => {
|
||||||
"open_core_dir" => crate::logging_error!(Type::Cmd, true, cmd::open_core_dir()),
|
let _ = cmd::open_app_dir();
|
||||||
"open_logs_dir" => crate::logging_error!(Type::Cmd, true, cmd::open_logs_dir()),
|
}
|
||||||
|
"open_core_dir" => {
|
||||||
|
let _ = cmd::open_core_dir();
|
||||||
|
}
|
||||||
|
"open_logs_dir" => {
|
||||||
|
let _ = cmd::open_logs_dir();
|
||||||
|
}
|
||||||
"restart_clash" => feat::restart_clash_core(),
|
"restart_clash" => feat::restart_clash_core(),
|
||||||
"restart_app" => feat::restart_app(),
|
"restart_app" => feat::restart_app(),
|
||||||
"entry_lightweight_mode" => entry_lightweight_mode(),
|
"entry_lightweight_mode" => entry_lightweight_mode(),
|
||||||
|
|||||||
@@ -86,23 +86,30 @@ impl AppHandleManager {
|
|||||||
|
|
||||||
#[allow(clippy::panic)]
|
#[allow(clippy::panic)]
|
||||||
pub fn run() {
|
pub fn run() {
|
||||||
// 初始化网络管理器
|
|
||||||
utils::network::NetworkManager::global().init();
|
utils::network::NetworkManager::global().init();
|
||||||
|
|
||||||
// 单例检测 - 使用超时机制防止阻塞
|
let _ = utils::dirs::init_portable_flag();
|
||||||
|
|
||||||
|
// 单例检测
|
||||||
let app_exists: bool = AsyncHandler::block_on(move || async move {
|
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 {
|
match timeout(Duration::from_secs(3), server::check_singleton()).await {
|
||||||
Ok(result) => {
|
Ok(result) => {
|
||||||
if result.is_err() {
|
if result.is_err() {
|
||||||
println!("app exists");
|
logging!(info, Type::Setup, true, "检测到已有应用实例运行");
|
||||||
true
|
true
|
||||||
} else {
|
} else {
|
||||||
|
logging!(info, Type::Setup, true, "未检测到其他应用实例");
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
// 超时处理
|
logging!(
|
||||||
println!("singleton check timeout, assuming app doesn't exist");
|
warn,
|
||||||
|
Type::Setup,
|
||||||
|
true,
|
||||||
|
"单例检查超时,假定没有其他实例运行"
|
||||||
|
);
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -136,9 +143,11 @@ pub fn run() {
|
|||||||
.build(),
|
.build(),
|
||||||
)
|
)
|
||||||
.setup(|app| {
|
.setup(|app| {
|
||||||
|
logging!(info, Type::Setup, true, "开始应用初始化...");
|
||||||
#[cfg(any(target_os = "linux", all(debug_assertions, windows)))]
|
#[cfg(any(target_os = "linux", all(debug_assertions, windows)))]
|
||||||
{
|
{
|
||||||
use tauri_plugin_deep_link::DeepLinkExt;
|
use tauri_plugin_deep_link::DeepLinkExt;
|
||||||
|
logging!(info, Type::Setup, true, "注册深层链接...");
|
||||||
logging_error!(Type::System, true, app.deep_link().register_all());
|
logging_error!(Type::System, true, app.deep_link().register_all());
|
||||||
}
|
}
|
||||||
app.deep_link().on_open_url(|event| {
|
app.deep_link().on_open_url(|event| {
|
||||||
@@ -152,23 +161,49 @@ pub fn run() {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// 使用 block_on 但增加超时保护
|
// 异步处理
|
||||||
AsyncHandler::block_on(|| async {
|
let app_handle = app.app_handle().clone();
|
||||||
match timeout(Duration::from_secs(30), resolve::resolve_setup(app)).await {
|
AsyncHandler::spawn(move || async move {
|
||||||
|
logging!(info, Type::Setup, true, "异步执行应用设置...");
|
||||||
|
match timeout(
|
||||||
|
Duration::from_secs(30),
|
||||||
|
resolve::resolve_setup_async(&app_handle),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
{
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
logging!(info, Type::Setup, true, "App setup completed successfully");
|
logging!(info, Type::Setup, true, "应用设置成功完成");
|
||||||
}
|
}
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
logging!(
|
logging!(
|
||||||
error,
|
error,
|
||||||
Type::Setup,
|
Type::Setup,
|
||||||
true,
|
true,
|
||||||
"App setup timed out, proceeding anyway"
|
"应用设置超时(30秒),继续执行后续流程"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
logging!(info, Type::Setup, true, "执行主要设置操作...");
|
||||||
|
|
||||||
|
logging!(info, Type::Setup, true, "初始化AppHandleManager...");
|
||||||
|
AppHandleManager::global().init(app.app_handle().clone());
|
||||||
|
|
||||||
|
logging!(info, Type::Setup, true, "初始化核心句柄...");
|
||||||
|
core::handle::Handle::global().init(app.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(())
|
Ok(())
|
||||||
})
|
})
|
||||||
.invoke_handler(tauri::generate_handler![
|
.invoke_handler(tauri::generate_handler![
|
||||||
@@ -184,8 +219,9 @@ pub fn run() {
|
|||||||
cmd::get_system_hostname,
|
cmd::get_system_hostname,
|
||||||
cmd::restart_core,
|
cmd::restart_core,
|
||||||
cmd::restart_app,
|
cmd::restart_app,
|
||||||
// 添加新的命令
|
// 启动命令
|
||||||
cmd::notify_ui_ready,
|
cmd::notify_ui_ready,
|
||||||
|
cmd::update_ui_stage,
|
||||||
cmd::reset_ui_ready_state,
|
cmd::reset_ui_ready_state,
|
||||||
cmd::get_running_mode,
|
cmd::get_running_mode,
|
||||||
cmd::get_app_uptime,
|
cmd::get_app_uptime,
|
||||||
@@ -279,6 +315,7 @@ pub fn run() {
|
|||||||
|
|
||||||
app.run(|app_handle, e| match e {
|
app.run(|app_handle, e| match e {
|
||||||
tauri::RunEvent::Ready | tauri::RunEvent::Resumed => {
|
tauri::RunEvent::Ready | tauri::RunEvent::Resumed => {
|
||||||
|
logging!(info, Type::System, true, "应用就绪或恢复");
|
||||||
AppHandleManager::global().init(app_handle.clone());
|
AppHandleManager::global().init(app_handle.clone());
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
{
|
{
|
||||||
@@ -286,6 +323,7 @@ pub fn run() {
|
|||||||
.get_handle()
|
.get_handle()
|
||||||
.get_webview_window("main")
|
.get_webview_window("main")
|
||||||
{
|
{
|
||||||
|
logging!(info, Type::Window, true, "设置macOS窗口标题");
|
||||||
let _ = window.set_title("Clash Verge");
|
let _ = window.set_title("Clash Verge");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -323,8 +361,11 @@ pub fn run() {
|
|||||||
}
|
}
|
||||||
println!("closing window...");
|
println!("closing window...");
|
||||||
api.prevent_close();
|
api.prevent_close();
|
||||||
let window = core::handle::Handle::global().get_window().unwrap();
|
if let Some(window) = core::handle::Handle::global().get_window() {
|
||||||
let _ = window.hide();
|
let _ = window.hide();
|
||||||
|
} else {
|
||||||
|
logging!(warn, Type::Window, true, "尝试隐藏窗口但窗口不存在");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
tauri::WindowEvent::Focused(true) => {
|
tauri::WindowEvent::Focused(true) => {
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
|
|||||||
@@ -16,8 +16,6 @@ use std::{
|
|||||||
};
|
};
|
||||||
use tauri::{Listener, Manager};
|
use tauri::{Listener, Manager};
|
||||||
|
|
||||||
pub static AUTO_LIGHT_WEIGHT_MODE_INIT: OnceCell<()> = OnceCell::new();
|
|
||||||
|
|
||||||
const LIGHT_WEIGHT_TASK_UID: &str = "light_weight_task";
|
const LIGHT_WEIGHT_TASK_UID: &str = "light_weight_task";
|
||||||
|
|
||||||
// 轻量模式状态标志
|
// 轻量模式状态标志
|
||||||
@@ -217,19 +215,3 @@ fn cancel_light_weight_timer() -> Result<()> {
|
|||||||
|
|
||||||
Ok(())
|
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"))?;
|
.ok_or(anyhow::anyhow!("failed to get the portable app dir"))?;
|
||||||
return Ok(PathBuf::from(app_dir).join(".config").join(APP_ID));
|
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() {
|
match app_handle.path().data_dir() {
|
||||||
Ok(dir) => Ok(dir.join(APP_ID)),
|
Ok(dir) => Ok(dir.join(APP_ID)),
|
||||||
Err(e) => {
|
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"))
|
Err(anyhow::anyhow!("Failed to get the app homedirectory"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -62,11 +110,24 @@ pub fn app_home_dir() -> Result<PathBuf> {
|
|||||||
|
|
||||||
/// get the resources dir
|
/// get the resources dir
|
||||||
pub fn app_resources_dir() -> Result<PathBuf> {
|
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() {
|
match app_handle.path().resource_dir() {
|
||||||
Ok(dir) => Ok(dir.join("resources")),
|
Ok(dir) => Ok(dir.join("resources")),
|
||||||
Err(e) => {
|
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"))
|
Err(anyhow::anyhow!("Failed to get the resource directory"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -126,12 +187,14 @@ pub fn profiles_path() -> Result<PathBuf> {
|
|||||||
|
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
pub fn service_path() -> Result<PathBuf> {
|
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)]
|
#[cfg(windows)]
|
||||||
pub fn service_path() -> Result<PathBuf> {
|
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> {
|
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 autostart;
|
||||||
pub mod dirs;
|
pub mod dirs;
|
||||||
pub mod error;
|
|
||||||
pub mod help;
|
pub mod help;
|
||||||
pub mod i18n;
|
pub mod i18n;
|
||||||
pub mod init;
|
pub mod init;
|
||||||
|
|||||||
@@ -128,118 +128,118 @@ impl NetworkManager {
|
|||||||
*error_count = 0;
|
*error_count = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
/*
|
||||||
|
/// 获取或创建自代理客户端
|
||||||
|
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();
|
||||||
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();
|
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 proxy_scheme = format!("http://127.0.0.1:{port}");
|
||||||
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 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()
|
if let Ok(proxy) = Proxy::http(&proxy_scheme) {
|
||||||
.use_rustls_tls()
|
builder = builder.proxy(proxy);
|
||||||
.pool_max_idle_per_host(POOL_MAX_IDLE_PER_HOST)
|
}
|
||||||
.pool_idle_timeout(POOL_IDLE_TIMEOUT)
|
if let Ok(proxy) = Proxy::https(&proxy_scheme) {
|
||||||
.connect_timeout(DEFAULT_CONNECT_TIMEOUT)
|
builder = builder.proxy(proxy);
|
||||||
.timeout(DEFAULT_REQUEST_TIMEOUT)
|
}
|
||||||
.http2_initial_stream_window_size(H2_STREAM_WINDOW_SIZE)
|
if let Ok(proxy) = Proxy::all(&proxy_scheme) {
|
||||||
.http2_initial_connection_window_size(H2_CONNECTION_WINDOW_SIZE)
|
builder = builder.proxy(proxy);
|
||||||
.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) {
|
let client = builder.build().expect("Failed to build self_proxy client");
|
||||||
builder = builder.proxy(proxy);
|
*client_guard = Some(client);
|
||||||
}
|
}
|
||||||
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.as_ref().unwrap().clone()
|
||||||
*client_guard = Some(client);
|
}
|
||||||
}
|
|
||||||
|
|
||||||
client_guard.as_ref().unwrap().clone()
|
/// 获取或创建系统代理客户端
|
||||||
}
|
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();
|
||||||
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();
|
if client_guard.is_none() {
|
||||||
|
use sysproxy::Sysproxy;
|
||||||
|
|
||||||
if client_guard.is_none() {
|
let mut builder = ClientBuilder::new()
|
||||||
use sysproxy::Sysproxy;
|
.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()
|
if let Ok(p @ Sysproxy { enable: true, .. }) = Sysproxy::get_system_proxy() {
|
||||||
.use_rustls_tls()
|
let proxy_scheme = format!("http://{}:{}", p.host, p.port);
|
||||||
.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() {
|
if let Ok(proxy) = Proxy::http(&proxy_scheme) {
|
||||||
let proxy_scheme = format!("http://{}:{}", p.host, p.port);
|
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) {
|
let client = builder
|
||||||
builder = builder.proxy(proxy);
|
.build()
|
||||||
}
|
.expect("Failed to build system_proxy client");
|
||||||
if let Ok(proxy) = Proxy::https(&proxy_scheme) {
|
*client_guard = Some(client);
|
||||||
builder = builder.proxy(proxy);
|
}
|
||||||
}
|
|
||||||
if let Ok(proxy) = Proxy::all(&proxy_scheme) {
|
|
||||||
builder = builder.proxy(proxy);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let client = builder
|
client_guard.as_ref().unwrap().clone()
|
||||||
.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(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
/// 根据代理设置选择合适的客户端
|
||||||
|
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请求
|
/// 创建带有自定义选项的HTTP请求
|
||||||
pub fn create_request(
|
pub fn create_request(
|
||||||
&self,
|
&self,
|
||||||
@@ -335,7 +335,7 @@ impl NetworkManager {
|
|||||||
client.get(url)
|
client.get(url)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 执行GET请求,添加错误跟踪
|
/* /// 执行GET请求,添加错误跟踪
|
||||||
pub async fn get(
|
pub async fn get(
|
||||||
&self,
|
&self,
|
||||||
url: &str,
|
url: &str,
|
||||||
@@ -370,7 +370,7 @@ impl NetworkManager {
|
|||||||
))
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
} */
|
||||||
|
|
||||||
pub async fn get_with_interrupt(
|
pub async fn get_with_interrupt(
|
||||||
&self,
|
&self,
|
||||||
|
|||||||
@@ -1,12 +1,11 @@
|
|||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
use crate::AppHandleManager;
|
|
||||||
use crate::{
|
use crate::{
|
||||||
config::{Config, IVerge, PrfItem},
|
config::{Config, IVerge, PrfItem},
|
||||||
core::*,
|
core::*,
|
||||||
logging, logging_error,
|
logging, logging_error,
|
||||||
module::lightweight,
|
module::lightweight,
|
||||||
process::AsyncHandler,
|
process::AsyncHandler,
|
||||||
utils::{dirs, error, init, logging::Type, server},
|
utils::{init, logging::Type, server},
|
||||||
wrap_err,
|
wrap_err,
|
||||||
};
|
};
|
||||||
use anyhow::{bail, Result};
|
use anyhow::{bail, Result};
|
||||||
@@ -14,14 +13,13 @@ use once_cell::sync::OnceCell;
|
|||||||
use parking_lot::{Mutex, RwLock};
|
use parking_lot::{Mutex, RwLock};
|
||||||
use percent_encoding::percent_decode_str;
|
use percent_encoding::percent_decode_str;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use serde_json;
|
|
||||||
use serde_yaml::Mapping;
|
use serde_yaml::Mapping;
|
||||||
use std::{
|
use std::{
|
||||||
net::TcpListener,
|
net::TcpListener,
|
||||||
sync::Arc,
|
sync::Arc,
|
||||||
time::{Duration, Instant},
|
time::{Duration, Instant},
|
||||||
};
|
};
|
||||||
use tauri::{App, Emitter, Manager};
|
use tauri::{AppHandle, Emitter, Manager};
|
||||||
|
|
||||||
use tauri::Url;
|
use tauri::Url;
|
||||||
//#[cfg(not(target_os = "linux"))]
|
//#[cfg(not(target_os = "linux"))]
|
||||||
@@ -29,10 +27,6 @@ use tauri::Url;
|
|||||||
|
|
||||||
pub static VERSION: OnceCell<String> = OnceCell::new();
|
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_WIDTH: u32 = 900;
|
||||||
const DEFAULT_HEIGHT: u32 = 700;
|
const DEFAULT_HEIGHT: u32 = 700;
|
||||||
@@ -43,6 +37,35 @@ static UI_READY: OnceCell<Arc<RwLock<bool>>> = OnceCell::new();
|
|||||||
// 窗口创建锁,防止并发创建窗口
|
// 窗口创建锁,防止并发创建窗口
|
||||||
static WINDOW_CREATING: OnceCell<Mutex<(bool, Instant)>> = 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)]
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
struct WindowState {
|
struct WindowState {
|
||||||
@@ -58,16 +81,54 @@ fn get_ui_ready() -> &'static Arc<RwLock<bool>> {
|
|||||||
UI_READY.get_or_init(|| Arc::new(RwLock::new(false)))
|
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已准备就绪
|
// 标记UI已准备就绪
|
||||||
pub fn mark_ui_ready() {
|
pub fn mark_ui_ready() {
|
||||||
let mut ready = get_ui_ready().write();
|
let mut ready = get_ui_ready().write();
|
||||||
*ready = true;
|
*ready = true;
|
||||||
|
logging!(info, Type::Window, true, "UI已标记为完全就绪");
|
||||||
}
|
}
|
||||||
|
|
||||||
// 重置UI就绪状态
|
// 重置UI就绪状态
|
||||||
pub fn reset_ui_ready() {
|
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就绪状态已重置");
|
logging!(info, Type::Window, true, "UI就绪状态已重置");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -88,54 +149,53 @@ pub fn find_unused_port() -> Result<u16> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// handle something when start app
|
/// 异步方式处理启动后的额外任务
|
||||||
pub async fn resolve_setup(app: &mut App) {
|
pub async fn resolve_setup_async(app_handle: &AppHandle) {
|
||||||
error::redirect_panic_to_log();
|
logging!(info, Type::Setup, true, "执行异步设置任务...");
|
||||||
#[cfg(target_os = "macos")]
|
|
||||||
{
|
if VERSION.get().is_none() {
|
||||||
AppHandleManager::global().init(app.app_handle().clone());
|
let version = app_handle.package_info().version.to_string();
|
||||||
AppHandleManager::global().set_activation_policy_accessory();
|
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::init_scheme());
|
||||||
|
|
||||||
logging_error!(Type::Setup, true, init::startup_script().await);
|
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_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_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);
|
logging_error!(Type::Core, true, CoreManager::global().init().await);
|
||||||
|
|
||||||
// setup a simple http server for singleton
|
log::trace!(target: "app", "启动内嵌服务器...");
|
||||||
log::trace!(target: "app", "launch embed server");
|
|
||||||
server::embed_server();
|
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().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!(
|
logging_error!(
|
||||||
Type::System,
|
Type::System,
|
||||||
true,
|
true,
|
||||||
@@ -147,11 +207,14 @@ pub async fn resolve_setup(app: &mut App) {
|
|||||||
sysopt::Sysopt::global().init_guard_sysproxy()
|
sysopt::Sysopt::global().init_guard_sysproxy()
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// 创建窗口
|
||||||
let is_silent_start = { Config::verge().data().enable_silent_start }.unwrap_or(false);
|
let is_silent_start = { Config::verge().data().enable_silent_start }.unwrap_or(false);
|
||||||
create_window(!is_silent_start);
|
create_window(!is_silent_start);
|
||||||
|
|
||||||
|
// 初始化定时器
|
||||||
logging_error!(Type::System, true, timer::Timer::global().init());
|
logging_error!(Type::System, true, timer::Timer::global().init());
|
||||||
|
|
||||||
|
// 自动进入轻量模式
|
||||||
let enable_auto_light_weight_mode =
|
let enable_auto_light_weight_mode =
|
||||||
{ Config::verge().data().enable_auto_light_weight_mode }.unwrap_or(false);
|
{ Config::verge().data().enable_auto_light_weight_mode }.unwrap_or(false);
|
||||||
if enable_auto_light_weight_mode && !is_silent_start {
|
if enable_auto_light_weight_mode && !is_silent_start {
|
||||||
@@ -160,12 +223,13 @@ pub async fn resolve_setup(app: &mut App) {
|
|||||||
|
|
||||||
logging_error!(Type::Tray, true, tray::Tray::global().update_part());
|
logging_error!(Type::Tray, true, tray::Tray::global().update_part());
|
||||||
|
|
||||||
// 初始化热键
|
logging!(trace, Type::System, true, "初始化热键...");
|
||||||
logging!(trace, Type::System, true, "Initial hotkeys");
|
|
||||||
logging_error!(Type::System, true, hotkey::Hotkey::global().init());
|
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() {
|
pub async fn resolve_reset_async() {
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
logging!(info, Type::Tray, true, "Unsubscribing from traffic updates");
|
logging!(info, Type::Tray, true, "Unsubscribing from traffic updates");
|
||||||
@@ -185,321 +249,136 @@ pub async fn resolve_reset_async() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 窗口创建锁守卫
|
/// Create the main window
|
||||||
struct WindowCreateGuard;
|
pub fn create_window(is_show: bool) -> bool {
|
||||||
|
logging!(
|
||||||
|
info,
|
||||||
|
Type::Window,
|
||||||
|
true,
|
||||||
|
"开始创建主窗口, is_show={}",
|
||||||
|
is_show
|
||||||
|
);
|
||||||
|
|
||||||
impl Drop for WindowCreateGuard {
|
let creating_lock = get_window_creating_lock();
|
||||||
fn drop(&mut self) {
|
let mut creating = creating_lock.lock();
|
||||||
let mut lock = get_window_creating_lock().lock();
|
|
||||||
lock.0 = false;
|
|
||||||
logging!(info, Type::Window, true, "窗口创建过程已完成,释放锁");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// create main window
|
let (is_creating, last_time) = *creating;
|
||||||
pub fn create_window(is_showup: bool) {
|
let elapsed = last_time.elapsed();
|
||||||
// 尝试获取窗口创建锁
|
|
||||||
let mut creating_lock = get_window_creating_lock().lock();
|
|
||||||
let (is_creating, last_create_time) = *creating_lock;
|
|
||||||
let now = Instant::now();
|
|
||||||
|
|
||||||
// 检查是否有其他线程正在创建窗口,防止短时间内多次创建窗口导致竞态条件
|
if is_creating && elapsed < Duration::from_secs(2) {
|
||||||
if is_creating && now.duration_since(last_create_time) < Duration::from_secs(2) {
|
|
||||||
logging!(
|
logging!(
|
||||||
warn,
|
info,
|
||||||
Type::Window,
|
Type::Window,
|
||||||
true,
|
true,
|
||||||
"另一个窗口创建过程正在进行中,跳过本次创建请求"
|
"窗口创建请求被忽略,因为最近创建过 ({:?}ms)",
|
||||||
|
elapsed.as_millis()
|
||||||
);
|
);
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
*creating_lock = (true, now);
|
*creating = (true, Instant::now());
|
||||||
drop(creating_lock);
|
|
||||||
|
|
||||||
// 创建窗口锁守卫结束时自动释放锁
|
match tauri::WebviewWindowBuilder::new(
|
||||||
let _guard = WindowCreateGuard;
|
&handle::Handle::global().app_handle().unwrap(),
|
||||||
|
"main", /* the unique window label */
|
||||||
// 打印 .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",
|
|
||||||
tauri::WebviewUrl::App("index.html".into()),
|
tauri::WebviewUrl::App("index.html".into()),
|
||||||
)
|
)
|
||||||
.title("Clash Verge")
|
.title("Clash Verge")
|
||||||
.center()
|
.center()
|
||||||
|
.decorations(true)
|
||||||
|
.hidden_title(true)
|
||||||
.fullscreen(false)
|
.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)
|
.min_inner_size(520.0, 520.0)
|
||||||
.visible(false)
|
.visible(false)
|
||||||
.decorations(false);
|
.build()
|
||||||
|
{
|
||||||
|
Ok(_) => {
|
||||||
|
logging!(info, Type::Window, true, "主窗口创建成功");
|
||||||
|
|
||||||
let window = win_builder.build();
|
*creating = (false, Instant::now());
|
||||||
|
|
||||||
match window {
|
update_ui_ready_stage(UiReadyStage::NotStarted);
|
||||||
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;
|
|
||||||
|
|
||||||
AsyncHandler::spawn(move || async move {
|
AsyncHandler::spawn(move || async move {
|
||||||
// 处理启动完成
|
|
||||||
handle::Handle::global().mark_startup_completed();
|
handle::Handle::global().mark_startup_completed();
|
||||||
|
logging!(info, Type::Window, true, "标记启动完成");
|
||||||
|
|
||||||
if let Some(window) = app_handle_clone.get_webview_window("main") {
|
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", ());
|
let _ = window.emit("verge://startup-completed", ());
|
||||||
|
logging!(info, Type::Window, true, "已发送启动完成事件");
|
||||||
|
|
||||||
if is_showup {
|
if is_show {
|
||||||
let window_clone = window.clone();
|
let window_clone = window.clone();
|
||||||
|
|
||||||
// 从轻量模式恢复时使用较短的超时,避免卡死
|
let timeout_seconds =
|
||||||
let timeout_seconds = if was_from_lightweight {
|
if crate::module::lightweight::is_in_lightweight_mode() {
|
||||||
// 从轻量模式恢复只等待2秒,确保不会卡死
|
2
|
||||||
2
|
} else {
|
||||||
} else {
|
5
|
||||||
5
|
};
|
||||||
};
|
|
||||||
|
|
||||||
// 使用普通的等待方式替代事件监听,简化实现
|
logging!(
|
||||||
let wait_result =
|
info,
|
||||||
tokio::time::timeout(Duration::from_secs(timeout_seconds), async {
|
Type::Window,
|
||||||
while !*get_ui_ready().read() {
|
true,
|
||||||
tokio::time::sleep(Duration::from_millis(100)).await;
|
"等待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就绪,显示窗口");
|
||||||
}
|
}
|
||||||
})
|
Err(_) => {
|
||||||
.await;
|
logging!(
|
||||||
|
warn,
|
||||||
|
Type::Window,
|
||||||
|
true,
|
||||||
|
"等待UI就绪超时({}秒),强制显示窗口",
|
||||||
|
timeout_seconds
|
||||||
|
);
|
||||||
|
|
||||||
// 根据结果处理
|
*get_ui_ready().write() = true;
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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) => {
|
Err(e) => {
|
||||||
logging!(error, Type::Window, true, "Failed to create window: {}", e);
|
logging!(error, Type::Window, true, "Failed to create window: {}", e);
|
||||||
|
false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -529,7 +408,6 @@ pub async fn resolve_scheme(param: String) -> Result<()> {
|
|||||||
.find(|(key, _)| key == "name")
|
.find(|(key, _)| key == "name")
|
||||||
.map(|(_, value)| value.into_owned());
|
.map(|(_, value)| value.into_owned());
|
||||||
|
|
||||||
// 通过直接获取查询部分并解析特定参数来避免 URL 转义问题
|
|
||||||
let url_param = if let Some(query) = link_parsed.query() {
|
let url_param = if let Some(query) = link_parsed.query() {
|
||||||
let prefix = "url=";
|
let prefix = "url=";
|
||||||
if let Some(pos) = query.find(prefix) {
|
if let Some(pos) = query.find(prefix) {
|
||||||
|
|||||||
@@ -165,7 +165,6 @@ const Layout = () => {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (clashInfo) {
|
if (clashInfo) {
|
||||||
const { server = "", secret = "" } = clashInfo;
|
const { server = "", secret = "" } = clashInfo;
|
||||||
// 使用本地存储中的enableLog值初始化全局日志服务
|
|
||||||
initGlobalLogService(server, secret, enableLog, "info");
|
initGlobalLogService(server, secret, enableLog, "info");
|
||||||
}
|
}
|
||||||
}, [clashInfo, enableLog]);
|
}, [clashInfo, enableLog]);
|
||||||
@@ -173,7 +172,6 @@ const Layout = () => {
|
|||||||
// 设置监听器
|
// 设置监听器
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const listeners = [
|
const listeners = [
|
||||||
// 配置更新监听
|
|
||||||
addListener("verge://refresh-clash-config", async () => {
|
addListener("verge://refresh-clash-config", async () => {
|
||||||
await getAxios(true);
|
await getAxios(true);
|
||||||
mutate("getProxies");
|
mutate("getProxies");
|
||||||
@@ -182,21 +180,17 @@ const Layout = () => {
|
|||||||
mutate("getProxyProviders");
|
mutate("getProxyProviders");
|
||||||
}),
|
}),
|
||||||
|
|
||||||
// verge 配置更新监听
|
|
||||||
addListener("verge://refresh-verge-config", () => {
|
addListener("verge://refresh-verge-config", () => {
|
||||||
mutate("getVergeConfig");
|
mutate("getVergeConfig");
|
||||||
// 添加对系统代理状态的刷新
|
|
||||||
mutate("getSystemProxy");
|
mutate("getSystemProxy");
|
||||||
mutate("getAutotemProxy");
|
mutate("getAutotemProxy");
|
||||||
}),
|
}),
|
||||||
|
|
||||||
// 通知消息监听
|
|
||||||
addListener("verge://notice-message", ({ payload }) =>
|
addListener("verge://notice-message", ({ payload }) =>
|
||||||
handleNotice(payload as [string, string]),
|
handleNotice(payload as [string, string]),
|
||||||
),
|
),
|
||||||
];
|
];
|
||||||
|
|
||||||
// 设置窗口显示/隐藏监听
|
|
||||||
const setupWindowListeners = async () => {
|
const setupWindowListeners = async () => {
|
||||||
const [hideUnlisten, showUnlisten] = await Promise.all([
|
const [hideUnlisten, showUnlisten] = await Promise.all([
|
||||||
listen("verge://hide-window", () => appWindow.hide()),
|
listen("verge://hide-window", () => appWindow.hide()),
|
||||||
@@ -209,29 +203,51 @@ const Layout = () => {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
// 初始化
|
|
||||||
setupCloseListener();
|
setupCloseListener();
|
||||||
const cleanupWindow = setupWindowListeners();
|
const cleanupWindow = setupWindowListeners();
|
||||||
|
|
||||||
// 清理函数
|
|
||||||
return () => {
|
return () => {
|
||||||
// 清理主要监听器
|
|
||||||
listeners.forEach((listener) => {
|
listeners.forEach((listener) => {
|
||||||
if (typeof listener.then === "function") {
|
if (typeof listener.then === "function") {
|
||||||
listener.then((unlisten) => unlisten());
|
listener.then((unlisten) => unlisten());
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
// 清理窗口监听器
|
|
||||||
cleanupWindow.then((cleanup) => cleanup());
|
cleanupWindow.then((cleanup) => cleanup());
|
||||||
};
|
};
|
||||||
}, [handleNotice]);
|
}, [handleNotice]);
|
||||||
|
|
||||||
// 监听启动完成事件并通知UI已加载
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
const notifyUiStage = async (stage: string) => {
|
||||||
|
try {
|
||||||
|
console.log(`UI加载阶段: ${stage}`);
|
||||||
|
await invoke("update_ui_stage", { stage });
|
||||||
|
} catch (err) {
|
||||||
|
console.error(`通知UI加载阶段(${stage})失败:`, err);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const notifyUiCoreReady = async () => {
|
||||||
|
try {
|
||||||
|
console.log("核心组件已加载,通知后端");
|
||||||
|
await invoke("update_ui_stage", { stage: "DomReady" });
|
||||||
|
} catch (err) {
|
||||||
|
console.error("通知核心组件加载完成失败:", err);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const notifyUiResourcesLoaded = async () => {
|
||||||
|
try {
|
||||||
|
console.log("所有资源已加载,通知后端");
|
||||||
|
await invoke("update_ui_stage", { stage: "ResourcesLoaded" });
|
||||||
|
} catch (err) {
|
||||||
|
console.error("通知资源加载完成失败:", err);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const notifyUiReady = async () => {
|
const notifyUiReady = async () => {
|
||||||
try {
|
try {
|
||||||
|
console.log("UI完全准备就绪,通知后端");
|
||||||
await invoke("notify_ui_ready");
|
await invoke("notify_ui_ready");
|
||||||
console.log("已通知后端UI准备就绪");
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("通知UI准备就绪失败:", err);
|
console.error("通知UI准备就绪失败:", err);
|
||||||
}
|
}
|
||||||
@@ -240,6 +256,7 @@ const Layout = () => {
|
|||||||
// 监听后端发送的启动完成事件
|
// 监听后端发送的启动完成事件
|
||||||
const listenStartupCompleted = async () => {
|
const listenStartupCompleted = async () => {
|
||||||
try {
|
try {
|
||||||
|
console.log("开始监听启动完成事件");
|
||||||
const unlisten = await listen("verge://startup-completed", () => {
|
const unlisten = await listen("verge://startup-completed", () => {
|
||||||
console.log("收到启动完成事件,开始通知UI就绪");
|
console.log("收到启动完成事件,开始通知UI就绪");
|
||||||
notifyUiReady();
|
notifyUiReady();
|
||||||
@@ -251,9 +268,21 @@ const Layout = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 初始加载时也通知一次
|
// 初始阶段 - 开始加载
|
||||||
console.log("页面初始加载,通知UI就绪");
|
notifyUiStage("Loading");
|
||||||
notifyUiReady();
|
|
||||||
|
setTimeout(() => {
|
||||||
|
notifyUiCoreReady();
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
notifyUiResourcesLoaded();
|
||||||
|
setTimeout(() => {
|
||||||
|
notifyUiReady();
|
||||||
|
}, 100);
|
||||||
|
}, 100);
|
||||||
|
}, 100);
|
||||||
|
|
||||||
|
// 启动监听器
|
||||||
const unlistenPromise = listenStartupCompleted();
|
const unlistenPromise = listenStartupCompleted();
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user