mirror of
https://github.com/clash-verge-rev/clash-verge-rev.git
synced 2026-01-29 00:35:38 +08:00
fix: resolve race condition freeze on rapid tray icon clicks in lightweight mode
This commit is contained in:
@@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
- 修复系统代理端口不同步问题
|
- 修复系统代理端口不同步问题
|
||||||
- 修复自定义 `css` 背景图无法生效问题
|
- 修复自定义 `css` 背景图无法生效问题
|
||||||
|
- 修复在轻量模式下快速点击托盘图标带来的竞争态卡死问题
|
||||||
|
|
||||||
### ✨ 新增功能
|
### ✨ 新增功能
|
||||||
|
|
||||||
|
|||||||
1
src-tauri/Cargo.lock
generated
1
src-tauri/Cargo.lock
generated
@@ -1078,6 +1078,7 @@ dependencies = [
|
|||||||
"reqwest",
|
"reqwest",
|
||||||
"reqwest_dav",
|
"reqwest_dav",
|
||||||
"runas",
|
"runas",
|
||||||
|
"scopeguard",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"serde_yaml",
|
"serde_yaml",
|
||||||
|
|||||||
@@ -80,6 +80,7 @@ gethostname = "1.0.2"
|
|||||||
hmac = "0.12.1"
|
hmac = "0.12.1"
|
||||||
sha2 = "0.10.9"
|
sha2 = "0.10.9"
|
||||||
hex = "0.4.3"
|
hex = "0.4.3"
|
||||||
|
scopeguard = "1.2.0"
|
||||||
|
|
||||||
[target.'cfg(windows)'.dependencies]
|
[target.'cfg(windows)'.dependencies]
|
||||||
runas = "=1.2.0"
|
runas = "=1.2.0"
|
||||||
|
|||||||
@@ -1,10 +1,6 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
config::Config,
|
config::Config, core::handle, feat, logging, logging_error,
|
||||||
core::handle,
|
module::lightweight::entry_lightweight_mode, utils::logging::Type,
|
||||||
feat, logging, logging_error,
|
|
||||||
module::lightweight::entry_lightweight_mode,
|
|
||||||
process::AsyncHandler,
|
|
||||||
utils::{logging::Type, resolve},
|
|
||||||
};
|
};
|
||||||
use anyhow::{bail, Result};
|
use anyhow::{bail, Result};
|
||||||
use once_cell::sync::OnceCell;
|
use once_cell::sync::OnceCell;
|
||||||
@@ -14,7 +10,7 @@ use tauri::Manager;
|
|||||||
use tauri_plugin_global_shortcut::{Code, GlobalShortcutExt, ShortcutState};
|
use tauri_plugin_global_shortcut::{Code, GlobalShortcutExt, ShortcutState};
|
||||||
|
|
||||||
pub struct Hotkey {
|
pub struct Hotkey {
|
||||||
current: Arc<Mutex<Vec<String>>>, // 保存当前的热键设置
|
current: Arc<Mutex<Vec<String>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Hotkey {
|
impl Hotkey {
|
||||||
@@ -38,7 +34,6 @@ impl Hotkey {
|
|||||||
enable_global_hotkey
|
enable_global_hotkey
|
||||||
);
|
);
|
||||||
|
|
||||||
// 如果全局热键被禁用,则不注册热键
|
|
||||||
if !enable_global_hotkey {
|
if !enable_global_hotkey {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
@@ -153,76 +148,14 @@ impl Hotkey {
|
|||||||
"=== Hotkey Dashboard Window Operation Start ==="
|
"=== Hotkey Dashboard Window Operation Start ==="
|
||||||
);
|
);
|
||||||
|
|
||||||
// 检查是否在轻量模式下,如果是,需要同步处理
|
logging!(
|
||||||
if crate::module::lightweight::is_in_lightweight_mode() {
|
info,
|
||||||
logging!(
|
Type::Hotkey,
|
||||||
info,
|
true,
|
||||||
Type::Hotkey,
|
"Using unified WindowManager for hotkey operation (bypass debounce)"
|
||||||
true,
|
);
|
||||||
"In lightweight mode, calling open_or_close_dashboard directly"
|
|
||||||
);
|
|
||||||
crate::feat::open_or_close_dashboard();
|
|
||||||
} else {
|
|
||||||
AsyncHandler::spawn(move || async move {
|
|
||||||
logging!(
|
|
||||||
debug,
|
|
||||||
Type::Hotkey,
|
|
||||||
true,
|
|
||||||
"Toggle dashboard window visibility (async)"
|
|
||||||
);
|
|
||||||
|
|
||||||
// 检查窗口是否存在
|
crate::feat::open_or_close_dashboard_hotkey();
|
||||||
if let Some(window) = handle::Handle::global().get_window() {
|
|
||||||
// 如果窗口可见,则隐藏
|
|
||||||
match window.is_visible() {
|
|
||||||
Ok(visible) => {
|
|
||||||
if visible {
|
|
||||||
logging!(
|
|
||||||
info,
|
|
||||||
Type::Window,
|
|
||||||
true,
|
|
||||||
"Window is visible, hiding it"
|
|
||||||
);
|
|
||||||
let _ = window.hide();
|
|
||||||
} else {
|
|
||||||
// 如果窗口不可见,则显示
|
|
||||||
logging!(
|
|
||||||
info,
|
|
||||||
Type::Window,
|
|
||||||
true,
|
|
||||||
"Window is hidden, showing it"
|
|
||||||
);
|
|
||||||
if window.is_minimized().unwrap_or(false) {
|
|
||||||
let _ = window.unminimize();
|
|
||||||
}
|
|
||||||
let _ = window.show();
|
|
||||||
let _ = window.set_focus();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
logging!(
|
|
||||||
warn,
|
|
||||||
Type::Window,
|
|
||||||
true,
|
|
||||||
"Failed to check window visibility: {}",
|
|
||||||
e
|
|
||||||
);
|
|
||||||
let _ = window.show();
|
|
||||||
let _ = window.set_focus();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// 如果窗口不存在,创建一个新窗口
|
|
||||||
logging!(
|
|
||||||
info,
|
|
||||||
Type::Window,
|
|
||||||
true,
|
|
||||||
"Window does not exist, creating a new one"
|
|
||||||
);
|
|
||||||
resolve::create_window(true);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
logging!(
|
logging!(
|
||||||
debug,
|
debug,
|
||||||
@@ -261,10 +194,8 @@ impl Hotkey {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// 直接执行函数,不做任何状态检查
|
|
||||||
logging!(debug, Type::Hotkey, "Executing function directly");
|
logging!(debug, Type::Hotkey, "Executing function directly");
|
||||||
|
|
||||||
// 获取全局热键状态
|
|
||||||
let is_enable_global_hotkey = Config::verge()
|
let is_enable_global_hotkey = Config::verge()
|
||||||
.latest()
|
.latest()
|
||||||
.enable_global_hotkey
|
.enable_global_hotkey
|
||||||
@@ -274,7 +205,6 @@ impl Hotkey {
|
|||||||
f();
|
f();
|
||||||
} else {
|
} else {
|
||||||
use crate::utils::window_manager::WindowManager;
|
use crate::utils::window_manager::WindowManager;
|
||||||
// 非轻量模式且未启用全局热键时,只在窗口可见且有焦点的情况下响应热键
|
|
||||||
let is_visible = WindowManager::is_main_window_visible();
|
let is_visible = WindowManager::is_main_window_visible();
|
||||||
let is_focused = WindowManager::is_main_window_focused();
|
let is_focused = WindowManager::is_main_window_focused();
|
||||||
|
|
||||||
|
|||||||
@@ -39,6 +39,29 @@ use super::handle;
|
|||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
struct TrayState {}
|
struct TrayState {}
|
||||||
|
|
||||||
|
// 托盘点击防抖机制
|
||||||
|
static TRAY_CLICK_DEBOUNCE: OnceCell<Mutex<Instant>> = OnceCell::new();
|
||||||
|
const TRAY_CLICK_DEBOUNCE_MS: u64 = 300;
|
||||||
|
|
||||||
|
fn get_tray_click_debounce() -> &'static Mutex<Instant> {
|
||||||
|
TRAY_CLICK_DEBOUNCE.get_or_init(|| Mutex::new(Instant::now() - Duration::from_secs(1)))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn should_handle_tray_click() -> bool {
|
||||||
|
let debounce_lock = get_tray_click_debounce();
|
||||||
|
let mut last_click = debounce_lock.lock();
|
||||||
|
let now = Instant::now();
|
||||||
|
|
||||||
|
if now.duration_since(*last_click) >= Duration::from_millis(TRAY_CLICK_DEBOUNCE_MS) {
|
||||||
|
*last_click = now;
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
log::debug!(target: "app", "托盘点击被防抖机制忽略,距离上次点击 {:?}ms",
|
||||||
|
now.duration_since(*last_click).as_millis());
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
pub struct Tray {
|
pub struct Tray {
|
||||||
pub speed_rate: Arc<Mutex<Option<SpeedRate>>>,
|
pub speed_rate: Arc<Mutex<Option<SpeedRate>>>,
|
||||||
@@ -664,6 +687,11 @@ impl Tray {
|
|||||||
..
|
..
|
||||||
} = event
|
} = event
|
||||||
{
|
{
|
||||||
|
// 添加防抖检查,防止快速连击
|
||||||
|
if !should_handle_tray_click() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
match tray_event.as_str() {
|
match tray_event.as_str() {
|
||||||
"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),
|
||||||
@@ -949,12 +977,15 @@ fn on_menu_event(_: &AppHandle, event: MenuEvent) {
|
|||||||
"open_window" => {
|
"open_window" => {
|
||||||
use crate::utils::window_manager::WindowManager;
|
use crate::utils::window_manager::WindowManager;
|
||||||
log::info!(target: "app", "托盘菜单点击: 打开窗口");
|
log::info!(target: "app", "托盘菜单点击: 打开窗口");
|
||||||
// 如果在轻量模式中,先退出轻量模式
|
|
||||||
|
if !should_handle_tray_click() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if crate::module::lightweight::is_in_lightweight_mode() {
|
if crate::module::lightweight::is_in_lightweight_mode() {
|
||||||
log::info!(target: "app", "当前在轻量模式,正在退出");
|
log::info!(target: "app", "当前在轻量模式,正在退出");
|
||||||
crate::module::lightweight::exit_lightweight_mode();
|
crate::module::lightweight::exit_lightweight_mode();
|
||||||
}
|
}
|
||||||
// 使用统一的窗口管理器显示窗口
|
|
||||||
let result = WindowManager::show_main_window();
|
let result = WindowManager::show_main_window();
|
||||||
log::info!(target: "app", "窗口显示结果: {:?}", result);
|
log::info!(target: "app", "窗口显示结果: {:?}", result);
|
||||||
}
|
}
|
||||||
@@ -977,7 +1008,10 @@ fn on_menu_event(_: &AppHandle, event: MenuEvent) {
|
|||||||
"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" => {
|
||||||
// 处理轻量模式的切换
|
if !should_handle_tray_click() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
let was_lightweight = crate::module::lightweight::is_in_lightweight_mode();
|
let was_lightweight = crate::module::lightweight::is_in_lightweight_mode();
|
||||||
if was_lightweight {
|
if was_lightweight {
|
||||||
crate::module::lightweight::exit_lightweight_mode();
|
crate::module::lightweight::exit_lightweight_mode();
|
||||||
@@ -985,7 +1019,6 @@ fn on_menu_event(_: &AppHandle, event: MenuEvent) {
|
|||||||
crate::module::lightweight::entry_lightweight_mode();
|
crate::module::lightweight::entry_lightweight_mode();
|
||||||
}
|
}
|
||||||
|
|
||||||
// 退出轻量模式后显示主窗口
|
|
||||||
if was_lightweight {
|
if was_lightweight {
|
||||||
use crate::utils::window_manager::WindowManager;
|
use crate::utils::window_manager::WindowManager;
|
||||||
let result = WindowManager::show_main_window();
|
let result = WindowManager::show_main_window();
|
||||||
@@ -1002,7 +1035,6 @@ fn on_menu_event(_: &AppHandle, event: MenuEvent) {
|
|||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 统一调用状态更新
|
|
||||||
if let Err(e) = Tray::global().update_all_states() {
|
if let Err(e) = Tray::global().update_all_states() {
|
||||||
log::warn!(target: "app", "更新托盘状态失败: {}", e);
|
log::warn!(target: "app", "更新托盘状态失败: {}", e);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,11 +11,43 @@ use crate::{
|
|||||||
/// Open or close the dashboard window
|
/// Open or close the dashboard window
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub fn open_or_close_dashboard() {
|
pub fn open_or_close_dashboard() {
|
||||||
|
open_or_close_dashboard_internal(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Open or close the dashboard window (hotkey call, dispatched to main thread)
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub fn open_or_close_dashboard_hotkey() {
|
||||||
|
open_or_close_dashboard_internal(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Internal implementation for opening/closing dashboard
|
||||||
|
fn open_or_close_dashboard_internal(bypass_debounce: bool) {
|
||||||
|
use crate::process::AsyncHandler;
|
||||||
use crate::utils::window_manager::WindowManager;
|
use crate::utils::window_manager::WindowManager;
|
||||||
|
|
||||||
log::info!(target: "app", "Attempting to open/close dashboard");
|
log::info!(target: "app", "Attempting to open/close dashboard (绕过防抖: {})", bypass_debounce);
|
||||||
|
|
||||||
// 检查是否在轻量模式下
|
// 热键调用调度到主线程执行,避免 WebView 创建死锁
|
||||||
|
if bypass_debounce {
|
||||||
|
log::info!(target: "app", "热键调用,调度到主线程执行窗口操作");
|
||||||
|
|
||||||
|
AsyncHandler::spawn(move || async move {
|
||||||
|
log::info!(target: "app", "主线程中执行热键窗口操作");
|
||||||
|
|
||||||
|
if crate::module::lightweight::is_in_lightweight_mode() {
|
||||||
|
log::info!(target: "app", "Currently in lightweight mode, exiting lightweight mode");
|
||||||
|
crate::module::lightweight::exit_lightweight_mode();
|
||||||
|
log::info!(target: "app", "Creating new window after exiting lightweight mode");
|
||||||
|
let result = WindowManager::show_main_window();
|
||||||
|
log::info!(target: "app", "Window operation result: {:?}", result);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let result = WindowManager::toggle_main_window();
|
||||||
|
log::info!(target: "app", "Window toggle result: {:?}", result);
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
if crate::module::lightweight::is_in_lightweight_mode() {
|
if crate::module::lightweight::is_in_lightweight_mode() {
|
||||||
log::info!(target: "app", "Currently in lightweight mode, exiting lightweight mode");
|
log::info!(target: "app", "Currently in lightweight mode, exiting lightweight mode");
|
||||||
crate::module::lightweight::exit_lightweight_mode();
|
crate::module::lightweight::exit_lightweight_mode();
|
||||||
@@ -25,7 +57,6 @@ pub fn open_or_close_dashboard() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 使用统一的窗口管理器切换窗口状态
|
|
||||||
let result = WindowManager::toggle_main_window();
|
let result = WindowManager::toggle_main_window();
|
||||||
log::info!(target: "app", "Window toggle result: {:?}", result);
|
log::info!(target: "app", "Window toggle result: {:?}", result);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,11 +13,17 @@ use crate::AppHandleManager;
|
|||||||
|
|
||||||
use anyhow::{Context, Result};
|
use anyhow::{Context, Result};
|
||||||
use delay_timer::prelude::TaskBuilder;
|
use delay_timer::prelude::TaskBuilder;
|
||||||
use std::sync::Mutex;
|
use std::sync::{
|
||||||
|
atomic::{AtomicBool, Ordering},
|
||||||
|
Mutex,
|
||||||
|
};
|
||||||
use tauri::{Listener, Manager};
|
use tauri::{Listener, Manager};
|
||||||
|
|
||||||
const LIGHT_WEIGHT_TASK_UID: &str = "light_weight_task";
|
const LIGHT_WEIGHT_TASK_UID: &str = "light_weight_task";
|
||||||
|
|
||||||
|
// 添加退出轻量模式的锁,防止并发调用
|
||||||
|
static EXITING_LIGHTWEIGHT: AtomicBool = AtomicBool::new(false);
|
||||||
|
|
||||||
fn with_lightweight_status<F, R>(f: F) -> R
|
fn with_lightweight_status<F, R>(f: F) -> R
|
||||||
where
|
where
|
||||||
F: FnOnce(&mut LightWeightState) -> R,
|
F: FnOnce(&mut LightWeightState) -> R,
|
||||||
@@ -131,6 +137,25 @@ pub fn entry_lightweight_mode() {
|
|||||||
|
|
||||||
// 添加从轻量模式恢复的函数
|
// 添加从轻量模式恢复的函数
|
||||||
pub fn exit_lightweight_mode() {
|
pub fn exit_lightweight_mode() {
|
||||||
|
// 使用原子操作检查是否已经在退出过程中,防止并发调用
|
||||||
|
if EXITING_LIGHTWEIGHT
|
||||||
|
.compare_exchange(false, true, Ordering::SeqCst, Ordering::SeqCst)
|
||||||
|
.is_err()
|
||||||
|
{
|
||||||
|
logging!(
|
||||||
|
info,
|
||||||
|
Type::Lightweight,
|
||||||
|
true,
|
||||||
|
"轻量模式退出操作已在进行中,跳过重复调用"
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 使用defer确保无论如何都会重置标志
|
||||||
|
let _guard = scopeguard::guard((), |_| {
|
||||||
|
EXITING_LIGHTWEIGHT.store(false, Ordering::SeqCst);
|
||||||
|
});
|
||||||
|
|
||||||
// 确保当前确实处于轻量模式才执行退出操作
|
// 确保当前确实处于轻量模式才执行退出操作
|
||||||
if !is_in_lightweight_mode() {
|
if !is_in_lightweight_mode() {
|
||||||
logging!(info, Type::Lightweight, true, "当前不在轻量模式,无需退出");
|
logging!(info, Type::Lightweight, true, "当前不在轻量模式,无需退出");
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ use anyhow::{bail, Result};
|
|||||||
use once_cell::sync::OnceCell;
|
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 scopeguard;
|
||||||
use serde_yaml::Mapping;
|
use serde_yaml::Mapping;
|
||||||
use std::{
|
use std::{
|
||||||
sync::Arc,
|
sync::Arc,
|
||||||
@@ -337,6 +338,12 @@ pub fn create_window(is_show: bool) -> bool {
|
|||||||
|
|
||||||
*creating = (true, Instant::now());
|
*creating = (true, Instant::now());
|
||||||
|
|
||||||
|
// ScopeGuard 确保创建状态重置,防止 webview 卡死
|
||||||
|
let _guard = scopeguard::guard(creating, |mut creating_guard| {
|
||||||
|
*creating_guard = (false, Instant::now());
|
||||||
|
logging!(debug, Type::Window, true, "[ScopeGuard] 窗口创建状态已重置");
|
||||||
|
});
|
||||||
|
|
||||||
match tauri::WebviewWindowBuilder::new(
|
match tauri::WebviewWindowBuilder::new(
|
||||||
&handle::Handle::global().app_handle().unwrap(),
|
&handle::Handle::global().app_handle().unwrap(),
|
||||||
"main", /* the unique window label */
|
"main", /* the unique window label */
|
||||||
@@ -419,8 +426,6 @@ pub fn create_window(is_show: bool) -> bool {
|
|||||||
Ok(newly_created_window) => {
|
Ok(newly_created_window) => {
|
||||||
logging!(debug, Type::Window, true, "主窗口实例创建成功");
|
logging!(debug, Type::Window, true, "主窗口实例创建成功");
|
||||||
|
|
||||||
*creating = (false, Instant::now());
|
|
||||||
|
|
||||||
update_ui_ready_stage(UiReadyStage::NotStarted);
|
update_ui_ready_stage(UiReadyStage::NotStarted);
|
||||||
|
|
||||||
AsyncHandler::spawn(move || async move {
|
AsyncHandler::spawn(move || async move {
|
||||||
@@ -534,7 +539,6 @@ pub fn create_window(is_show: bool) -> bool {
|
|||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
logging!(error, Type::Window, true, "主窗口构建失败: {}", e);
|
logging!(error, Type::Window, true, "主窗口构建失败: {}", e);
|
||||||
*creating = (false, Instant::now()); // Reset the creating state if window creation failed
|
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,14 @@ use tauri::{Manager, WebviewWindow, Wry};
|
|||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
use crate::AppHandleManager;
|
use crate::AppHandleManager;
|
||||||
|
|
||||||
|
use once_cell::sync::OnceCell;
|
||||||
|
use parking_lot::Mutex;
|
||||||
|
use scopeguard;
|
||||||
|
use std::{
|
||||||
|
sync::atomic::{AtomicBool, Ordering},
|
||||||
|
time::{Duration, Instant},
|
||||||
|
};
|
||||||
|
|
||||||
/// 窗口操作结果
|
/// 窗口操作结果
|
||||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||||
pub enum WindowOperationResult {
|
pub enum WindowOperationResult {
|
||||||
@@ -34,6 +42,45 @@ pub enum WindowState {
|
|||||||
NotExist,
|
NotExist,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 窗口操作防抖机制
|
||||||
|
static WINDOW_OPERATION_DEBOUNCE: OnceCell<Mutex<Instant>> = OnceCell::new();
|
||||||
|
static WINDOW_OPERATION_IN_PROGRESS: AtomicBool = AtomicBool::new(false);
|
||||||
|
const WINDOW_OPERATION_DEBOUNCE_MS: u64 = 500;
|
||||||
|
|
||||||
|
fn get_window_operation_debounce() -> &'static Mutex<Instant> {
|
||||||
|
WINDOW_OPERATION_DEBOUNCE.get_or_init(|| Mutex::new(Instant::now() - Duration::from_secs(1)))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn should_handle_window_operation() -> bool {
|
||||||
|
if WINDOW_OPERATION_IN_PROGRESS.load(Ordering::Acquire) {
|
||||||
|
log::warn!(target: "app", "[防抖] 窗口操作已在进行中,跳过重复调用");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
let debounce_lock = get_window_operation_debounce();
|
||||||
|
let mut last_operation = debounce_lock.lock();
|
||||||
|
let now = Instant::now();
|
||||||
|
let elapsed = now.duration_since(*last_operation);
|
||||||
|
|
||||||
|
log::debug!(target: "app", "[防抖] 检查窗口操作间隔: {}ms (需要>={}ms)",
|
||||||
|
elapsed.as_millis(), WINDOW_OPERATION_DEBOUNCE_MS);
|
||||||
|
|
||||||
|
if elapsed >= Duration::from_millis(WINDOW_OPERATION_DEBOUNCE_MS) {
|
||||||
|
*last_operation = now;
|
||||||
|
WINDOW_OPERATION_IN_PROGRESS.store(true, Ordering::Release);
|
||||||
|
log::info!(target: "app", "[防抖] 窗口操作被允许执行");
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
log::warn!(target: "app", "[防抖] 窗口操作被防抖机制忽略,距离上次操作 {}ms < {}ms",
|
||||||
|
elapsed.as_millis(), WINDOW_OPERATION_DEBOUNCE_MS);
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn finish_window_operation() {
|
||||||
|
WINDOW_OPERATION_IN_PROGRESS.store(false, Ordering::Release);
|
||||||
|
}
|
||||||
|
|
||||||
/// 统一的窗口管理器
|
/// 统一的窗口管理器
|
||||||
pub struct WindowManager;
|
pub struct WindowManager;
|
||||||
|
|
||||||
@@ -65,6 +112,14 @@ impl WindowManager {
|
|||||||
|
|
||||||
/// 智能显示主窗口
|
/// 智能显示主窗口
|
||||||
pub fn show_main_window() -> WindowOperationResult {
|
pub fn show_main_window() -> WindowOperationResult {
|
||||||
|
// 防抖检查
|
||||||
|
if !should_handle_window_operation() {
|
||||||
|
return WindowOperationResult::NoAction;
|
||||||
|
}
|
||||||
|
let _guard = scopeguard::guard((), |_| {
|
||||||
|
finish_window_operation();
|
||||||
|
});
|
||||||
|
|
||||||
logging!(info, Type::Window, true, "开始智能显示主窗口");
|
logging!(info, Type::Window, true, "开始智能显示主窗口");
|
||||||
logging!(
|
logging!(
|
||||||
debug,
|
debug,
|
||||||
@@ -80,8 +135,11 @@ impl WindowManager {
|
|||||||
WindowState::NotExist => {
|
WindowState::NotExist => {
|
||||||
logging!(info, Type::Window, true, "窗口不存在,创建新窗口");
|
logging!(info, Type::Window, true, "窗口不存在,创建新窗口");
|
||||||
if Self::create_new_window() {
|
if Self::create_new_window() {
|
||||||
|
logging!(info, Type::Window, true, "窗口创建成功");
|
||||||
|
std::thread::sleep(std::time::Duration::from_millis(100));
|
||||||
WindowOperationResult::Created
|
WindowOperationResult::Created
|
||||||
} else {
|
} else {
|
||||||
|
logging!(warn, Type::Window, true, "窗口创建失败");
|
||||||
WindowOperationResult::Failed
|
WindowOperationResult::Failed
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -91,6 +149,16 @@ impl WindowManager {
|
|||||||
}
|
}
|
||||||
WindowState::VisibleUnfocused | WindowState::Minimized | WindowState::Hidden => {
|
WindowState::VisibleUnfocused | WindowState::Minimized | WindowState::Hidden => {
|
||||||
if let Some(window) = Self::get_main_window() {
|
if let Some(window) = Self::get_main_window() {
|
||||||
|
let state_after_check = Self::get_main_window_state();
|
||||||
|
if state_after_check == WindowState::VisibleFocused {
|
||||||
|
logging!(
|
||||||
|
info,
|
||||||
|
Type::Window,
|
||||||
|
true,
|
||||||
|
"窗口在检查期间已变为可见和有焦点状态"
|
||||||
|
);
|
||||||
|
return WindowOperationResult::NoAction;
|
||||||
|
}
|
||||||
Self::activate_window(&window)
|
Self::activate_window(&window)
|
||||||
} else {
|
} else {
|
||||||
WindowOperationResult::Failed
|
WindowOperationResult::Failed
|
||||||
@@ -101,6 +169,14 @@ impl WindowManager {
|
|||||||
|
|
||||||
/// 切换主窗口显示状态(显示/隐藏)
|
/// 切换主窗口显示状态(显示/隐藏)
|
||||||
pub fn toggle_main_window() -> WindowOperationResult {
|
pub fn toggle_main_window() -> WindowOperationResult {
|
||||||
|
// 防抖检查
|
||||||
|
if !should_handle_window_operation() {
|
||||||
|
return WindowOperationResult::NoAction;
|
||||||
|
}
|
||||||
|
let _guard = scopeguard::guard((), |_| {
|
||||||
|
finish_window_operation();
|
||||||
|
});
|
||||||
|
|
||||||
logging!(info, Type::Window, true, "开始切换主窗口显示状态");
|
logging!(info, Type::Window, true, "开始切换主窗口显示状态");
|
||||||
|
|
||||||
let current_state = Self::get_main_window_state();
|
let current_state = Self::get_main_window_state();
|
||||||
@@ -108,37 +184,61 @@ impl WindowManager {
|
|||||||
info,
|
info,
|
||||||
Type::Window,
|
Type::Window,
|
||||||
true,
|
true,
|
||||||
"当前窗口状态: {:?}",
|
"当前窗口状态: {:?} | 详细状态: {}",
|
||||||
current_state
|
current_state,
|
||||||
|
Self::get_window_status_info()
|
||||||
);
|
);
|
||||||
|
|
||||||
match current_state {
|
match current_state {
|
||||||
WindowState::NotExist => {
|
WindowState::NotExist => {
|
||||||
// 窗口不存在,创建新窗口
|
// 窗口不存在,创建新窗口
|
||||||
|
logging!(info, Type::Window, true, "窗口不存在,将创建新窗口");
|
||||||
|
// 由于已经有防抖保护,直接调用内部方法
|
||||||
if Self::create_new_window() {
|
if Self::create_new_window() {
|
||||||
WindowOperationResult::Created
|
WindowOperationResult::Created
|
||||||
} else {
|
} else {
|
||||||
WindowOperationResult::Failed
|
WindowOperationResult::Failed
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
WindowState::VisibleFocused => {
|
WindowState::VisibleFocused | WindowState::VisibleUnfocused => {
|
||||||
// 窗口可见且有焦点,隐藏它
|
logging!(
|
||||||
if let Some(window) = Self::get_main_window() {
|
info,
|
||||||
if window.hide().is_ok() {
|
Type::Window,
|
||||||
logging!(info, Type::Window, true, "窗口已隐藏");
|
true,
|
||||||
WindowOperationResult::Hidden
|
"窗口可见(焦点状态: {}),将隐藏窗口",
|
||||||
|
if current_state == WindowState::VisibleFocused {
|
||||||
|
"有焦点"
|
||||||
} else {
|
} else {
|
||||||
WindowOperationResult::Failed
|
"无焦点"
|
||||||
|
}
|
||||||
|
);
|
||||||
|
if let Some(window) = Self::get_main_window() {
|
||||||
|
match window.hide() {
|
||||||
|
Ok(_) => {
|
||||||
|
logging!(info, Type::Window, true, "窗口已成功隐藏");
|
||||||
|
WindowOperationResult::Hidden
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
logging!(warn, Type::Window, true, "隐藏窗口失败: {}", e);
|
||||||
|
WindowOperationResult::Failed
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
logging!(warn, Type::Window, true, "无法获取窗口实例");
|
||||||
WindowOperationResult::Failed
|
WindowOperationResult::Failed
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
WindowState::VisibleUnfocused | WindowState::Minimized | WindowState::Hidden => {
|
WindowState::Minimized | WindowState::Hidden => {
|
||||||
// 窗口存在但不可见或无焦点,激活它
|
logging!(
|
||||||
|
info,
|
||||||
|
Type::Window,
|
||||||
|
true,
|
||||||
|
"窗口存在但被隐藏或最小化,将激活窗口"
|
||||||
|
);
|
||||||
if let Some(window) = Self::get_main_window() {
|
if let Some(window) = Self::get_main_window() {
|
||||||
Self::activate_window(&window)
|
Self::activate_window(&window)
|
||||||
} else {
|
} else {
|
||||||
|
logging!(warn, Type::Window, true, "无法获取窗口实例");
|
||||||
WindowOperationResult::Failed
|
WindowOperationResult::Failed
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -251,7 +351,7 @@ impl WindowManager {
|
|||||||
.unwrap_or(false)
|
.unwrap_or(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 创建新窗口现有的实现
|
/// 创建新窗口,防抖避免重复调用
|
||||||
fn create_new_window() -> bool {
|
fn create_new_window() -> bool {
|
||||||
use crate::utils::resolve;
|
use crate::utils::resolve;
|
||||||
resolve::create_window(true)
|
resolve::create_window(true)
|
||||||
|
|||||||
Reference in New Issue
Block a user