性能优化

This commit is contained in:
zhongluofeng
2026-08-06 10:33:16 +08:00
parent c7578a2e6b
commit e66c53e66d
105 changed files with 7273 additions and 5002 deletions
+46 -102
View File
@@ -20,14 +20,12 @@ use tauri::{
/// 记录窗口最后显示时间,用于失焦防抖(避免显示瞬间因焦点未稳定而被立即隐藏)
static LAST_SHOW_TIME: Mutex<Option<Instant>> = Mutex::new(None);
/// 保存最近一次右键时计算出的定位参数(逻辑坐标),供 `tray_menu_ready` 使用
/// (x, tray_top_l, wa_top_l, wa_bottom_l, scale)
/// 保存最近一次右键时计算出的定位参数(物理坐标),供 `tray_menu_ready` 使用
/// (x, tray_top, wa_top, wa_bottom, scale)
static LAST_MENU_LAYOUT: Mutex<Option<(f64, f64, f64, f64, f64)>> = Mutex::new(None);
use crate::clipboard::popup::{get_work_area, get_work_area_at_point, get_dpi_for_point};
use crate::clipboard::ClipboardManager;
use crate::download_engine::DownloadEngine;
use crate::mihomo_manager::MihomoManager;
use crate::win32_util::{get_work_area, get_work_area_at_point, get_dpi_for_point};
use crate::mihomo_manager::{MihomoManager, is_pseudo_node};
use crate::monitor_kernel::MonitorKernel;
use crate::process_manager::{ProcessManager, ProcessStatus};
@@ -54,37 +52,6 @@ pub struct TrayMenuState {
pub proxy_current: Option<String>,
}
// ===== 伪节点过滤(与前端 ProxyModule 保持一致) =====
const PSEUDO_KEYWORDS: &[&str] = &[
"DIRECT",
"REJECT",
"PASS",
"COMPATIBLE",
"流量",
"套餐",
"到期",
"续费",
"官网",
"网站",
"刷新",
"更新",
"",
"",
"",
"",
"",
"×",
];
fn is_pseudo_node(name: &str) -> bool {
let upper = name.trim().to_uppercase();
if upper == "DIRECT" || upper == "REJECT" || upper == "PASS" || upper == "GLOBAL" {
return true;
}
PSEUDO_KEYWORDS.iter().any(|kw| name.contains(kw))
}
// ===== 状态查询 =====
fn is_proxy_running(app: &AppHandle) -> bool {
@@ -252,7 +219,7 @@ pub fn precreate_tray_menu_window(app: &AppHandle) {
{
Ok(w) => w,
Err(e) => {
eprintln!("[tray-menu] 预创建菜单窗口失败: {}", e);
crate::logger::log_error("tray", &format!("预创建菜单窗口失败: {}", e));
return;
}
};
@@ -262,7 +229,7 @@ pub fn precreate_tray_menu_window(app: &AppHandle) {
win.on_window_event(move |event| {
if let tauri::WindowEvent::Focused(false) = event {
let should_hide = {
let t = LAST_SHOW_TIME.lock().unwrap();
let t = LAST_SHOW_TIME.lock().unwrap_or_else(|e| e.into_inner());
match *t {
Some(time) => time.elapsed() > Duration::from_millis(300),
None => true,
@@ -274,7 +241,7 @@ pub fn precreate_tray_menu_window(app: &AppHandle) {
}
});
eprintln!("[tray-menu] 菜单窗口已预创建(隐藏渲染)");
crate::logger::log_info("tray", "菜单窗口已预创建(隐藏渲染)");
}
/// 右键托盘时调用:计算定位参数、发送状态给前端,但不立即显示窗口。
@@ -289,29 +256,23 @@ pub fn show_tray_menu(app: &AppHandle, cursor_pos: (f64, f64), tray_rect: (f64,
let (wa_left, wa_top, wa_right, wa_bottom) = get_work_area_at_point(mx as i32, my as i32)
.unwrap_or((0, 0, 1920, 1040));
// 获取光标所在显示器的 DPI,将物理坐标转为逻辑坐标(DIP
// 光标所在显示器的 DPI:菜单宽度按物理像素换算
let dpi = get_dpi_for_point(mx as i32, my as i32).unwrap_or(96);
let scale = dpi as f64 / 96.0;
let menu_w_px = MENU_W * scale;
let mx_l = mx / scale;
let tray_top_l = tray_top / scale;
let wa_left_l = wa_left as f64 / scale;
let wa_right_l = wa_right as f64 / scale;
let wa_top_l = wa_top as f64 / scale;
let wa_bottom_l = wa_bottom as f64 / scale;
// 水平:菜单左边缘对齐鼠标 X(向右延伸),超出右边界则左移(物理坐标)
let x = mx.max(wa_left as f64).min(wa_right as f64 - menu_w_px);
// 水平:菜单左边缘对齐鼠标 X(向右延伸),超出右边界则左移
let x = mx_l.max(wa_left_l).min(wa_right_l - MENU_W);
// 保存布局参数,供 tray_menu_ready 使用
// 保存布局参数(全部物理坐标 + scale,供 tray_menu_ready 换算前端上报的逻辑高度)
{
let mut layout = LAST_MENU_LAYOUT.lock().unwrap();
*layout = Some((x, tray_top_l, wa_top_l, wa_bottom_l, scale));
let mut layout = LAST_MENU_LAYOUT.lock().unwrap_or_else(|e| e.into_inner());
*layout = Some((x, tray_top, wa_top as f64, wa_bottom as f64, scale));
}
// 记录显示时间,用于失焦防抖
{
let mut t = LAST_SHOW_TIME.lock().unwrap();
let mut t = LAST_SHOW_TIME.lock().unwrap_or_else(|e| e.into_inner());
*t = Some(Instant::now());
}
@@ -324,7 +285,7 @@ pub fn show_tray_menu(app: &AppHandle, cursor_pos: (f64, f64), tray_rect: (f64,
let app_clone = app.clone();
tauri::async_runtime::spawn(async move {
let state = get_tray_menu_state(&app_clone).await;
let _ = app_clone.emit("tray-menu-show", state);
let _ = app_clone.emit(crate::constants::events::TRAY_MENU_SHOW, state);
});
}
@@ -338,7 +299,7 @@ pub fn hide_tray_menu(app: &AppHandle) {
/// 刷新菜单状态并发送给前端
async fn refresh_and_emit_state(app: &AppHandle) {
let state = get_tray_menu_state(app).await;
let _ = app.emit("tray-menu-state-updated", state);
let _ = app.emit(crate::constants::events::TRAY_MENU_STATE_UPDATED, state);
}
// ===== Tauri 命令 =====
@@ -353,13 +314,13 @@ pub async fn tray_menu_action(
match action.as_str() {
"proxy_enable" => {
if let Err(e) = enable_proxy(&app).await {
eprintln!("[tray] 开启代理失败: {}", e);
crate::logger::log_error("tray", &format!("开启代理失败: {}", e));
send_notification(&app, "代理启动失败", &e);
}
}
"proxy_disable" => {
if let Err(e) = disable_proxy(&app).await {
eprintln!("[tray] 关闭代理失败: {}", e);
crate::logger::log_error("tray", &format!("关闭代理失败: {}", e));
send_notification(&app, "代理关闭失败", &e);
}
}
@@ -378,28 +339,28 @@ pub async fn tray_menu_action(
}
}
"osd_toggle" => {
let _ = app.emit("tray:toggle-osd", ());
let _ = app.emit(crate::constants::events::TRAY_TOGGLE_OSD, ());
}
"kernel_restart" => {
if let Err(e) = restart_kernel(&app).await {
eprintln!("[tray] 重启 Kernel 失败: {}", e);
crate::logger::log_error("tray", &format!("重启 Kernel 失败: {}", e));
send_notification(&app, "Kernel 重启失败", &e);
}
}
"download_new" => {
if let Some(window) = app.get_webview_window("main") {
if let Some(window) = app.get_webview_window(crate::constants::windows::MAIN) {
window.show().ok();
window.set_focus().ok();
}
let _ = app.emit("tray:new-download", ());
let _ = app.emit(crate::constants::events::TRAY_NEW_DOWNLOAD, ());
hide_tray_menu(&app);
}
"settings" => {
if let Some(window) = app.get_webview_window("main") {
if let Some(window) = app.get_webview_window(crate::constants::windows::MAIN) {
window.show().ok();
window.set_focus().ok();
}
let _ = app.emit("tray:open-settings", ());
let _ = app.emit(crate::constants::events::TRAY_OPEN_SETTINGS, ());
hide_tray_menu(&app);
}
"quit" => {
@@ -431,24 +392,29 @@ pub async fn tray_menu_ready(content_height: f64, app: AppHandle) -> Result<(),
let win = app.get_webview_window(TRAY_MENU_LABEL)
.ok_or("tray-menu window not found")?;
let (x, tray_top_l, wa_top_l, wa_bottom_l, _scale) = {
let layout = LAST_MENU_LAYOUT.lock().unwrap();
let (x, tray_top, wa_top, wa_bottom, scale) = {
let layout = LAST_MENU_LAYOUT.lock().unwrap_or_else(|e| e.into_inner());
layout.unwrap_or((0.0, 1040.0, 0.0, 1040.0, 1.0))
};
// 将内容高度限制在合理范围内
// 将内容高度限制在合理范围内(前端上报为逻辑像素)
let h = content_height.max(100.0).min(520.0);
// 调整窗口尺寸
let _ = win.set_size(tauri::Size::Logical(tauri::LogicalSize {
width: MENU_W,
height: h,
// 调整窗口尺寸(物理像素,与物理坐标定位保持一致,避免混合 DPI 换算偏移)
let win_w_px = MENU_W * scale;
let win_h_px = h * scale;
let _ = win.set_size(tauri::Size::Physical(tauri::PhysicalSize {
width: win_w_px as u32,
height: win_h_px as u32,
}));
// 垂直:菜单下边缘紧贴托盘图标顶部(向上弹出)
let y = (tray_top_l - h).max(wa_top_l).min(wa_bottom_l - h);
// 垂直:菜单下边缘紧贴托盘图标顶部(向上弹出,物理坐标
let y = (tray_top - win_h_px).max(wa_top).min(wa_bottom - win_h_px);
let pos = tauri::Position::Logical(tauri::LogicalPosition { x, y });
let pos = tauri::Position::Physical(tauri::PhysicalPosition {
x: x as i32,
y: y as i32,
});
let _ = win.set_position(pos);
let _ = win.show();
let _ = win.set_focus();
@@ -605,7 +571,7 @@ async fn select_proxy_node(app: &AppHandle, group: &str, name: &str) {
send_notification(app, "节点已切换", &format!("{}\n延迟: {}", name, delay_text));
}
Err(e) => {
eprintln!("[tray] 切换节点失败: {}", e);
crate::logger::log_error("tray", &format!("切换节点失败: {}", e));
send_notification(app, "切换节点失败", &e);
}
}
@@ -657,34 +623,12 @@ fn send_notification(app: &AppHandle, title: &str, message: &str) {
.show();
}
// ===== 退出清理 =====
// ===== 退出入口 =====
/// 托盘菜单"退出"调用。
/// 资源清理统一收敛到 RunEvent::ExitRequested(覆盖所有退出路径),此处仅请求退出,
/// 避免清理逻辑双份执行(monitor 的 cleanup 涉及子进程/网络,重复执行有竞态风险)。
pub fn quit_cleanup(app: &AppHandle) {
if let Some(mihomo) = app.try_state::<MihomoManager>() {
mihomo.cleanup_on_exit();
}
if let Some(engine) = app.try_state::<DownloadEngine>() {
engine.cleanup_on_exit();
}
// monitor.cleanup_on_exit 是 async,在 tokio worker 线程中直接 block_on 会 panic
// 放到独立 OS 线程执行 block_on,避免嵌套 runtime。
if let Some(monitor) = app.try_state::<MonitorKernel>() {
let app_clone = app.clone();
let monitor_clone = monitor.inner().clone();
std::thread::spawn(move || {
tauri::async_runtime::block_on(async move {
monitor_clone.cleanup_on_exit(&app_clone).await;
});
})
.join()
.ok();
}
if let Some(clip) = app.try_state::<ClipboardManager>() {
clip.stop();
}
if let Some(pm) = app.try_state::<ProcessManager>() {
pm.stop_all();
}
app.exit(0);
}
@@ -704,7 +648,7 @@ pub fn create_tray_menu(app: &AppHandle) -> Result<(), tauri::Error> {
..
} => {
// 左键:显示主窗口
if let Some(window) = app.get_webview_window("main") {
if let Some(window) = app.get_webview_window(crate::constants::windows::MAIN) {
window.show().ok();
window.set_focus().ok();
}