mirror of
https://github.com/clash-verge-rev/clash-verge-rev.git
synced 2026-01-28 07:14:40 +08:00
feat: add governor crate for rate limiting and improve window/tray operation handling
This commit is contained in:
69
Cargo.lock
generated
69
Cargo.lock
generated
@@ -1215,6 +1215,7 @@ dependencies = [
|
||||
"futures",
|
||||
"gethostname",
|
||||
"getrandom 0.3.4",
|
||||
"governor",
|
||||
"log",
|
||||
"nanoid",
|
||||
"network-interface",
|
||||
@@ -2742,6 +2743,12 @@ version = "0.3.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988"
|
||||
|
||||
[[package]]
|
||||
name = "futures-timer"
|
||||
version = "3.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f288b0a4f20f9a56b5d1da57e2227c661b7b16168e2f72365f57b63326e29b24"
|
||||
|
||||
[[package]]
|
||||
name = "futures-util"
|
||||
version = "0.3.31"
|
||||
@@ -3100,6 +3107,29 @@ dependencies = [
|
||||
"system-deps",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "governor"
|
||||
version = "0.10.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9efcab3c1958580ff1f25a2a41be1668f7603d849bb63af523b208a3cc1223b8"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"dashmap 6.1.0",
|
||||
"futures-sink",
|
||||
"futures-timer",
|
||||
"futures-util",
|
||||
"getrandom 0.3.4",
|
||||
"hashbrown 0.16.1",
|
||||
"nonzero_ext",
|
||||
"parking_lot",
|
||||
"portable-atomic",
|
||||
"quanta",
|
||||
"rand 0.9.2",
|
||||
"smallvec",
|
||||
"spinning_top",
|
||||
"web-time",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gtk"
|
||||
version = "0.18.2"
|
||||
@@ -4649,6 +4679,12 @@ dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nonzero_ext"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "38bf9645c8b145698bb0b18a4637dcacbc421ea49bef2317e4fd8065a387cf21"
|
||||
|
||||
[[package]]
|
||||
name = "normpath"
|
||||
version = "1.5.0"
|
||||
@@ -5904,6 +5940,21 @@ dependencies = [
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quanta"
|
||||
version = "0.12.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f3ab5a9d756f0d97bdc89019bd2e4ea098cf9cde50ee7564dde6b81ccc8f06c7"
|
||||
dependencies = [
|
||||
"crossbeam-utils",
|
||||
"libc",
|
||||
"once_cell",
|
||||
"raw-cpuid",
|
||||
"wasi 0.11.1+wasi-snapshot-preview1",
|
||||
"web-sys",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quick-error"
|
||||
version = "2.0.1"
|
||||
@@ -6117,6 +6168,15 @@ dependencies = [
|
||||
"rand_core 0.5.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "raw-cpuid"
|
||||
version = "11.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "498cd0dc59d73224351ee52a95fee0f1a617a2eae0e7d9d720cc622c73a54186"
|
||||
dependencies = [
|
||||
"bitflags 2.10.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "raw-window-handle"
|
||||
version = "0.6.2"
|
||||
@@ -7235,6 +7295,15 @@ version = "0.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d5fe4ccb98d9c292d56fec89a5e07da7fc4cf0dc11e156b41793132775d3e591"
|
||||
|
||||
[[package]]
|
||||
name = "spinning_top"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d96d2d1d716fb500937168cc09353ffdc7a012be8475ac7308e1bdf0e3923300"
|
||||
dependencies = [
|
||||
"lock_api",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "stable_deref_trait"
|
||||
version = "1.2.1"
|
||||
|
||||
@@ -28,5 +28,6 @@
|
||||
- 性能优化前后端在渲染流量图时的资源
|
||||
- 在 Linux NVIDIA 显卡环境下尝试禁用 WebKit DMABUF 渲染以规避潜在问题
|
||||
- Windows 下自启动改为计划任务实现
|
||||
- 改进托盘和窗口操作频率限制实现
|
||||
|
||||
</details>
|
||||
|
||||
@@ -132,6 +132,13 @@ pub fn set_app_core_mode<R: Runtime>(app: &tauri::AppHandle<R>, mode: impl Into<
|
||||
spec.appinfo.app_core_mode = mode.into();
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_app_uptime<R: Runtime>(app: &tauri::AppHandle<R>) -> Instant {
|
||||
let platform_spec = app.state::<RwLock<Platform>>();
|
||||
let spec = platform_spec.read();
|
||||
spec.appinfo.app_startup_time
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn is_current_app_handle_admin<R: Runtime>(app: &tauri::AppHandle<R>) -> bool {
|
||||
let platform_spec = app.state::<RwLock<Platform>>();
|
||||
|
||||
@@ -100,6 +100,7 @@ clash_verge_service_ipc = { version = "2.0.26", features = [
|
||||
arc-swap = "1.7.1"
|
||||
rust_iso3166 = "0.1.14"
|
||||
dark-light = "2.0.0"
|
||||
governor = "0.10.4"
|
||||
|
||||
[target.'cfg(windows)'.dependencies]
|
||||
deelevate = { workspace = true }
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use once_cell::sync::OnceCell;
|
||||
use governor::{DefaultDirectRateLimiter, Quota, RateLimiter};
|
||||
use tauri::tray::TrayIconBuilder;
|
||||
use tauri_plugin_clash_verge_sysinfo::is_current_app_handle_admin;
|
||||
use tauri_plugin_mihomo::models::Proxies;
|
||||
@@ -18,14 +18,11 @@ use crate::{
|
||||
|
||||
use super::handle;
|
||||
use anyhow::Result;
|
||||
use parking_lot::Mutex;
|
||||
use smartstring::alias::String;
|
||||
use std::collections::HashMap;
|
||||
use std::num::NonZeroU32;
|
||||
use std::sync::Arc;
|
||||
use std::{
|
||||
sync::atomic::{AtomicBool, Ordering},
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
use std::time::Duration;
|
||||
use tauri::{
|
||||
AppHandle, Wry,
|
||||
menu::{CheckMenuItem, IsMenuItem, MenuEvent, MenuItem, PredefinedMenuItem, Submenu},
|
||||
@@ -38,45 +35,13 @@ use menu_def::{MenuIds, MenuTexts};
|
||||
|
||||
type ProxyMenuItem = (Option<Submenu<Wry>>, Vec<Box<dyn IsMenuItem<Wry>>>);
|
||||
|
||||
const TRAY_CLICK_DEBOUNCE_MS: u64 = 1_275;
|
||||
|
||||
#[derive(Clone)]
|
||||
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 now = Instant::now();
|
||||
|
||||
if now.duration_since(*debounce_lock.lock()) >= Duration::from_millis(TRAY_CLICK_DEBOUNCE_MS) {
|
||||
*debounce_lock.lock() = now;
|
||||
true
|
||||
} else {
|
||||
logging!(
|
||||
debug,
|
||||
Type::Tray,
|
||||
"托盘点击被防抖机制忽略,距离上次点击 {}ms",
|
||||
now.duration_since(*debounce_lock.lock()).as_millis()
|
||||
);
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
pub struct Tray {
|
||||
last_menu_update: Mutex<Option<Instant>>,
|
||||
menu_updating: AtomicBool,
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
pub struct Tray {
|
||||
last_menu_update: Mutex<Option<Instant>>,
|
||||
menu_updating: AtomicBool,
|
||||
limiter: DefaultDirectRateLimiter,
|
||||
}
|
||||
|
||||
impl TrayState {
|
||||
@@ -159,10 +124,14 @@ impl TrayState {
|
||||
}
|
||||
|
||||
impl Default for Tray {
|
||||
#[allow(clippy::unwrap_used)]
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
last_menu_update: Mutex::new(None),
|
||||
menu_updating: AtomicBool::new(false),
|
||||
limiter: RateLimiter::direct(
|
||||
Quota::with_period(Duration::from_millis(TRAY_CLICK_DEBOUNCE_MS))
|
||||
.unwrap()
|
||||
.allow_burst(NonZeroU32::new(1).unwrap()),
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -224,45 +193,8 @@ impl Tray {
|
||||
logging!(debug, Type::Tray, "应用正在退出,跳过托盘菜单更新");
|
||||
return Ok(());
|
||||
}
|
||||
// 调整最小更新间隔,确保状态及时刷新
|
||||
const MIN_UPDATE_INTERVAL: Duration = Duration::from_millis(100);
|
||||
|
||||
// 检查是否正在更新
|
||||
if self.menu_updating.load(Ordering::Acquire) {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// 检查更新频率,但允许重要事件跳过频率限制
|
||||
let should_force_update = match std::thread::current().name() {
|
||||
Some("main") => true,
|
||||
_ => {
|
||||
let last_update = self.last_menu_update.lock();
|
||||
if let Some(last_time) = *last_update {
|
||||
last_time.elapsed() >= MIN_UPDATE_INTERVAL
|
||||
} else {
|
||||
true
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if !should_force_update {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let app_handle = handle::Handle::app_handle();
|
||||
|
||||
// 设置更新状态
|
||||
self.menu_updating.store(true, Ordering::Release);
|
||||
|
||||
let result = self.update_menu_internal(app_handle).await;
|
||||
|
||||
{
|
||||
let mut last_update = self.last_menu_update.lock();
|
||||
*last_update = Some(Instant::now());
|
||||
}
|
||||
self.menu_updating.store(false, Ordering::Release);
|
||||
|
||||
result
|
||||
self.update_menu_internal(app_handle).await
|
||||
}
|
||||
|
||||
async fn update_menu_internal(&self, app_handle: &AppHandle) -> Result<()> {
|
||||
@@ -503,8 +435,8 @@ impl Tray {
|
||||
} = event
|
||||
{
|
||||
// 添加防抖检查,防止快速连击
|
||||
if !should_handle_tray_click() {
|
||||
logging!(info, Type::Tray, "click tray icon too fast, ignore");
|
||||
#[allow(clippy::use_self)]
|
||||
if !Tray::global().should_handle_tray_click() {
|
||||
return;
|
||||
}
|
||||
AsyncHandler::spawn(|| async move {
|
||||
@@ -530,6 +462,14 @@ impl Tray {
|
||||
tray.on_menu_event(on_menu_event);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn should_handle_tray_click(&self) -> bool {
|
||||
let res = self.limiter.check().is_ok();
|
||||
if !res {
|
||||
logging!(debug, Type::Tray, "tray click rate limited");
|
||||
}
|
||||
res
|
||||
}
|
||||
}
|
||||
|
||||
fn create_hotkeys(hotkeys: &Option<Vec<String>>) -> HashMap<String, String> {
|
||||
@@ -1001,10 +941,6 @@ fn on_menu_event(_: &AppHandle, event: MenuEvent) {
|
||||
}
|
||||
MenuIds::DASHBOARD => {
|
||||
logging!(info, Type::Tray, "托盘菜单点击: 打开窗口");
|
||||
|
||||
if !should_handle_tray_click() {
|
||||
return;
|
||||
}
|
||||
if !lightweight::exit_lightweight_mode().await {
|
||||
WindowManager::show_main_window().await;
|
||||
};
|
||||
@@ -1040,9 +976,6 @@ fn on_menu_event(_: &AppHandle, event: MenuEvent) {
|
||||
MenuIds::RESTART_CLASH => feat::restart_clash_core().await,
|
||||
MenuIds::RESTART_APP => feat::restart_app().await,
|
||||
MenuIds::LIGHTWEIGHT_MODE => {
|
||||
if !should_handle_tray_click() {
|
||||
return;
|
||||
}
|
||||
if !is_in_lightweight_mode() {
|
||||
lightweight::entry_lightweight_mode().await;
|
||||
} else {
|
||||
|
||||
@@ -1,17 +1,12 @@
|
||||
use crate::{core::handle, utils::resolve::window::build_new_window};
|
||||
use clash_verge_logging::{Type, logging};
|
||||
use std::future::Future;
|
||||
use governor::{DefaultDirectRateLimiter, Quota, RateLimiter};
|
||||
use once_cell::sync::Lazy;
|
||||
use std::num::NonZeroU32;
|
||||
use std::pin::Pin;
|
||||
use std::time::Duration;
|
||||
use tauri::{Manager as _, WebviewWindow, Wry};
|
||||
|
||||
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)]
|
||||
pub enum WindowOperationResult {
|
||||
@@ -45,53 +40,22 @@ pub enum WindowState {
|
||||
}
|
||||
|
||||
// 窗口操作防抖机制
|
||||
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)))
|
||||
}
|
||||
const WINDOW_OPERATION_DEBOUNCE_MS: u64 = 1_275;
|
||||
static WINDOW_OPERATION_LIMITER: Lazy<DefaultDirectRateLimiter> = Lazy::new(|| {
|
||||
#[allow(clippy::unwrap_used)]
|
||||
RateLimiter::direct(
|
||||
Quota::with_period(Duration::from_millis(WINDOW_OPERATION_DEBOUNCE_MS))
|
||||
.unwrap()
|
||||
.allow_burst(NonZeroU32::new(1).unwrap()),
|
||||
)
|
||||
});
|
||||
|
||||
fn should_handle_window_operation() -> bool {
|
||||
if WINDOW_OPERATION_IN_PROGRESS.load(Ordering::Acquire) {
|
||||
logging!(warn, Type::Window, "Warning: [防抖] 窗口操作已在进行中,跳过重复调用");
|
||||
return false;
|
||||
let res = WINDOW_OPERATION_LIMITER.check().is_ok();
|
||||
if !res {
|
||||
logging!(debug, Type::Window, "window operation rate limited");
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
logging!(
|
||||
debug,
|
||||
Type::Window,
|
||||
"[防抖] 检查窗口操作间隔: {}ms (需要>={}ms)",
|
||||
elapsed.as_millis(),
|
||||
WINDOW_OPERATION_DEBOUNCE_MS
|
||||
);
|
||||
|
||||
if elapsed >= Duration::from_millis(WINDOW_OPERATION_DEBOUNCE_MS) {
|
||||
*last_operation = now;
|
||||
drop(last_operation);
|
||||
WINDOW_OPERATION_IN_PROGRESS.store(true, Ordering::Release);
|
||||
logging!(info, Type::Window, "[防抖] 窗口操作被允许执行");
|
||||
true
|
||||
} else {
|
||||
logging!(
|
||||
warn,
|
||||
Type::Window,
|
||||
"Warning: [防抖] 窗口操作被防抖机制忽略,距离上次操作 {}ms < {}ms",
|
||||
elapsed.as_millis(),
|
||||
WINDOW_OPERATION_DEBOUNCE_MS
|
||||
);
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
fn finish_window_operation() {
|
||||
WINDOW_OPERATION_IN_PROGRESS.store(false, Ordering::Release);
|
||||
res
|
||||
}
|
||||
|
||||
/// 统一的窗口管理器
|
||||
@@ -135,9 +99,6 @@ impl WindowManager {
|
||||
if !should_handle_window_operation() {
|
||||
return WindowOperationResult::NoAction;
|
||||
}
|
||||
let _guard = scopeguard::guard((), |_| {
|
||||
finish_window_operation();
|
||||
});
|
||||
|
||||
logging!(info, Type::Window, "开始智能显示主窗口");
|
||||
logging!(debug, Type::Window, "{}", Self::get_window_status_info());
|
||||
@@ -149,7 +110,7 @@ impl WindowManager {
|
||||
logging!(info, Type::Window, "窗口不存在,创建新窗口");
|
||||
if Self::create_window(true).await {
|
||||
logging!(info, Type::Window, "窗口创建成功");
|
||||
std::thread::sleep(std::time::Duration::from_millis(100));
|
||||
std::thread::sleep(std::time::Duration::from_millis(50));
|
||||
WindowOperationResult::Created
|
||||
} else {
|
||||
logging!(warn, Type::Window, "窗口创建失败");
|
||||
@@ -180,11 +141,7 @@ impl WindowManager {
|
||||
// 防抖检查
|
||||
if !should_handle_window_operation() {
|
||||
return WindowOperationResult::NoAction;
|
||||
}
|
||||
let _guard = scopeguard::guard((), |_| {
|
||||
finish_window_operation();
|
||||
});
|
||||
|
||||
};
|
||||
logging!(info, Type::Window, "开始切换主窗口显示状态");
|
||||
|
||||
let current_state = Self::get_main_window_state();
|
||||
|
||||
Reference in New Issue
Block a user