refactor: reduce webview lock contention (#6271)

* refactor: replace handle::Handle::get_window() with WindowManager::get_main_window() in multiple files

* refactor: enhance WindowManager to return window state alongside the main window instance

* refactor: update useProfiles and ProfilePage to support profile overrides and improve patchProfilesConfig return type

* refactor: enhance handle_success to check main window existence before notifying profile changes

* refactor: simplify get_main_window_with_state by using pattern matching and improve window state handling

* refactor: fix window activation by removing unnecessary reference in activate_existing_main_window

* refactor: remove redundant macOS conditional for window_manager import
This commit is contained in:
Tunglies 2026-02-07 13:45:15 +08:00 committed by GitHub
parent 6c6e0812b8
commit c30eaa3678
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 83 additions and 72 deletions

View File

@ -1,5 +1,6 @@
use super::CmdResult;
use super::StringifyErr as _;
use crate::utils::window_manager::WindowManager;
use crate::{
config::{
Config, IProfiles, PrfItem, PrfOption,
@ -310,7 +311,9 @@ async fn handle_success(current_value: Option<&String>) -> CmdResult<bool> {
logging!(warn, Type::Cmd, "Warning: 异步保存配置文件失败: {e}");
}
if let Some(current) = current_value {
if let Some(current) = current_value
&& WindowManager::get_main_window().is_some()
{
logging!(info, Type::Cmd, "向前端发送配置变更事件: {}", current);
handle::Handle::notify_profile_changed(current.to_owned());
}

View File

@ -5,7 +5,7 @@ use std::sync::{
Arc,
atomic::{AtomicBool, Ordering},
};
use tauri::{AppHandle, Manager as _, WebviewWindow};
use tauri::AppHandle;
use tauri_plugin_mihomo::{Mihomo, MihomoExt as _};
use tokio::sync::RwLockReadGuard;
@ -55,10 +55,6 @@ impl Handle {
Self::app_handle().mihomo().read().await
}
pub fn get_window() -> Option<WebviewWindow> {
Self::app_handle().get_webview_window("main")
}
pub fn refresh_clash() {
let handle = Self::global();
if handle.is_exiting() {

View File

@ -1,6 +1,7 @@
use crate::process::AsyncHandler;
use crate::singleton;
use crate::utils::notification::{NotificationEvent, notify_event};
use crate::utils::window_manager::WindowManager;
use crate::{config::Config, core::handle, feat, module::lightweight::entry_lightweight_mode};
use anyhow::{Result, bail};
use arc_swap::ArcSwap;
@ -243,7 +244,7 @@ impl Hotkey {
logging!(debug, Type::Hotkey, "Hotkey pressed: {:?}", hotkey_event);
let hotkey = hotkey_event.key;
if hotkey == Code::KeyQ && is_quit {
if let Some(window) = handle::Handle::get_window()
if let Some(window) = WindowManager::get_main_window()
&& window.is_focused().unwrap_or(false)
{
logging!(debug, Type::Hotkey, "Executing quit function");
@ -260,8 +261,9 @@ impl Hotkey {
Self::execute_function(function);
} else {
use crate::utils::window_manager::WindowManager;
let is_visible = WindowManager::is_main_window_visible();
let is_focused = WindowManager::is_main_window_focused();
let window = WindowManager::get_main_window();
let is_visible = WindowManager::is_main_window_visible(window.as_ref());
let is_focused = WindowManager::is_main_window_focused(window.as_ref());
if is_focused && is_visible {
Self::execute_function(function);

View File

@ -1,5 +1,5 @@
use super::handle::Handle;
use crate::constants::timing;
use crate::{constants::timing, utils::window_manager::WindowManager};
use clash_verge_logging::{Type, logging};
use smartstring::alias::String;
use std::{sync::mpsc, thread};
@ -84,7 +84,7 @@ impl NotificationSystem {
None => return,
};
if let Some(window) = super::handle::Handle::get_window() {
if let Some(window) = WindowManager::get_main_window() {
system.emit_to_window(&window, event);
drop(binding);
thread::sleep(timing::EVENT_EMIT_DELAY);

View File

@ -178,7 +178,7 @@ pub async fn hide() {
add_light_weight_timer().await;
}
if let Some(window) = handle::Handle::get_window()
if let Some(window) = WindowManager::get_main_window()
&& window.is_visible().unwrap_or(false)
{
let _ = window.hide();

View File

@ -264,7 +264,6 @@ pub fn run() {
mod event_handlers {
#[cfg(target_os = "macos")]
use crate::module::lightweight;
#[cfg(target_os = "macos")]
use crate::utils::window_manager::WindowManager;
use crate::{
config::Config,
@ -316,7 +315,7 @@ pub fn run() {
if let tauri::WindowEvent::CloseRequested { api, .. } = api {
api.prevent_close();
if let Some(window) = core::handle::Handle::get_window() {
if let Some(window) = WindowManager::get_main_window() {
let _ = window.hide();
}
}

View File

@ -1,6 +1,6 @@
use crate::{
config::Config,
core::{handle, timer::Timer, tray::Tray},
core::{timer::Timer, tray::Tray},
process::AsyncHandler,
};
@ -143,7 +143,7 @@ pub async fn add_light_weight_timer() {
}
fn setup_window_close_listener() {
if let Some(window) = handle::Handle::get_window() {
if let Some(window) = WindowManager::get_main_window() {
let handler_id = window.listen("tauri://close-requested", move |_event| {
std::mem::drop(AsyncHandler::spawn(|| async {
if let Err(e) = setup_light_weight_timer().await {
@ -161,7 +161,7 @@ fn setup_window_close_listener() {
}
fn cancel_window_close_listener() {
if let Some(window) = handle::Handle::get_window() {
if let Some(window) = WindowManager::get_main_window() {
let id = WINDOW_CLOSE_HANDLER_ID.swap(0, Ordering::AcqRel);
if id != 0 {
window.unlisten(id);
@ -171,7 +171,7 @@ fn cancel_window_close_listener() {
}
fn setup_webview_focus_listener() {
if let Some(window) = handle::Handle::get_window() {
if let Some(window) = WindowManager::get_main_window() {
let handler_id = window.listen("tauri://focus", move |_event| {
logging_error!(Type::Lightweight, cancel_light_weight_timer());
logging!(debug, Type::Lightweight, "监听到窗口获得焦点,取消轻量模式计时");
@ -181,7 +181,7 @@ fn setup_webview_focus_listener() {
}
fn cancel_webview_focus_listener() {
if let Some(window) = handle::Handle::get_window() {
if let Some(window) = WindowManager::get_main_window() {
let id = WEBVIEW_FOCUS_HANDLER_ID.swap(0, Ordering::AcqRel);
if id != 0 {
window.unlisten(id);

View File

@ -59,6 +59,28 @@ fn should_handle_window_operation() -> bool {
pub struct WindowManager;
impl WindowManager {
pub fn get_main_window_with_state() -> (Option<WebviewWindow<Wry>>, WindowState) {
let Some(window) = Self::get_main_window() else {
return (None, WindowState::NotExist);
};
let is_minimized = window.is_minimized().unwrap_or(false);
let is_visible = window.is_visible().unwrap_or(false);
let is_focused = window.is_focused().unwrap_or(false);
let state = if is_minimized {
WindowState::Minimized
} else if !is_visible {
WindowState::Hidden
} else if is_focused {
WindowState::VisibleFocused
} else {
WindowState::VisibleUnfocused
};
(Some(window), state)
}
pub fn get_main_window_state() -> WindowState {
match Self::get_main_window() {
Some(window) => {
@ -119,12 +141,12 @@ impl WindowManager {
WindowOperationResult::NoAction
}
WindowState::VisibleUnfocused | WindowState::Minimized | WindowState::Hidden => {
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, "窗口在检查期间已变为可见和有焦点状态");
return WindowOperationResult::NoAction;
}
let (window, state_after_check) = Self::get_main_window_with_state();
if state_after_check == WindowState::VisibleFocused {
logging!(info, Type::Window, "窗口在检查期间已变为可见和有焦点状态");
return WindowOperationResult::NoAction;
}
if let Some(window) = window {
Self::activate_window(&window)
} else {
WindowOperationResult::Failed
@ -135,25 +157,18 @@ impl WindowManager {
/// 切换主窗口显示状态(显示/隐藏)
pub async fn toggle_main_window() -> WindowOperationResult {
// 防抖检查
if !should_handle_window_operation() {
return WindowOperationResult::NoAction;
};
logging!(info, Type::Window, "开始切换主窗口显示状态");
}
let current_state = Self::get_main_window_state();
logging!(
info,
Type::Window,
"当前窗口状态: {:?} | 详细状态: {}",
current_state,
Self::get_window_status_info()
);
let (window, state) = Self::get_main_window_with_state();
match current_state {
logging!(debug, Type::Window, "当前状态: {:?}", state);
match state {
WindowState::NotExist => Self::handle_not_exist_toggle().await,
WindowState::VisibleFocused | WindowState::VisibleUnfocused => Self::hide_main_window(),
WindowState::Minimized | WindowState::Hidden => Self::activate_existing_main_window(),
WindowState::VisibleFocused | WindowState::VisibleUnfocused => Self::hide_main_window(window.as_ref()),
WindowState::Minimized | WindowState::Hidden => Self::activate_existing_main_window(window.as_ref()),
}
}
@ -169,9 +184,9 @@ impl WindowManager {
}
// 隐藏主窗口
fn hide_main_window() -> WindowOperationResult {
fn hide_main_window(window: Option<&WebviewWindow<Wry>>) -> WindowOperationResult {
logging!(info, Type::Window, "窗口可见,将隐藏窗口");
if let Some(window) = Self::get_main_window() {
if let Some(window) = window {
match window.hide() {
Ok(_) => {
logging!(info, Type::Window, "窗口已成功隐藏");
@ -189,10 +204,10 @@ impl WindowManager {
}
// 激活已存在的主窗口
fn activate_existing_main_window() -> WindowOperationResult {
fn activate_existing_main_window(window: Option<&WebviewWindow<Wry>>) -> WindowOperationResult {
logging!(info, Type::Window, "窗口存在但被隐藏或最小化,将激活窗口");
if let Some(window) = Self::get_main_window() {
Self::activate_window(&window)
if let Some(window) = window {
Self::activate_window(window)
} else {
logging!(warn, Type::Window, "无法获取窗口实例");
WindowOperationResult::Failed
@ -255,24 +270,18 @@ impl WindowManager {
}
/// 检查窗口是否可见
pub fn is_main_window_visible() -> bool {
Self::get_main_window()
.map(|window| window.is_visible().unwrap_or(false))
.unwrap_or(false)
pub fn is_main_window_visible(window: Option<&WebviewWindow<Wry>>) -> bool {
window.map(|w| w.is_visible().unwrap_or(false)).unwrap_or(false)
}
/// 检查窗口是否有焦点
pub fn is_main_window_focused() -> bool {
Self::get_main_window()
.map(|window| window.is_focused().unwrap_or(false))
.unwrap_or(false)
pub fn is_main_window_focused(window: Option<&WebviewWindow<Wry>>) -> bool {
window.map(|w| w.is_focused().unwrap_or(false)).unwrap_or(false)
}
/// 检查窗口是否最小化
pub fn is_main_window_minimized() -> bool {
Self::get_main_window()
.map(|window| window.is_minimized().unwrap_or(false))
.unwrap_or(false)
pub fn is_main_window_minimized(window: Option<&WebviewWindow<Wry>>) -> bool {
window.map(|w| w.is_minimized().unwrap_or(false)).unwrap_or(false)
}
/// 创建新窗口,防抖避免重复调用
@ -320,11 +329,11 @@ impl WindowManager {
}
/// 获取详细的窗口状态信息
pub fn get_window_status_info() -> String {
let state = Self::get_main_window_state();
let is_visible = Self::is_main_window_visible();
let is_focused = Self::is_main_window_focused();
let is_minimized = Self::is_main_window_minimized();
fn get_window_status_info() -> String {
let (window, state) = Self::get_main_window_with_state();
let is_visible = Self::is_main_window_visible(window.as_ref());
let is_focused = Self::is_main_window_focused(window.as_ref());
let is_minimized = Self::is_main_window_minimized(window.as_ref());
format!("窗口状态: {state:?} | 可见: {is_visible} | 有焦点: {is_focused} | 最小化: {is_minimized}")
}

View File

@ -2,11 +2,11 @@ import useSWR, { mutate } from "swr";
import { selectNodeForGroup } from "tauri-plugin-mihomo-api";
import {
calcuProxies,
getProfiles,
patchProfile,
patchProfilesConfig,
} from "@/services/cmds";
import { calcuProxies } from "@/services/cmds";
import { debugLog } from "@/utils/debug";
export const useProfiles = () => {
@ -36,6 +36,7 @@ export const useProfiles = () => {
const patchProfiles = async (
value: Partial<IProfilesConfig>,
signal?: AbortSignal,
options?: { deferRefreshOnSuccess?: boolean },
) => {
try {
if (signal?.aborted) {
@ -47,7 +48,9 @@ export const useProfiles = () => {
throw new DOMException("Operation was aborted", "AbortError");
}
await mutateProfiles();
if (!options?.deferRefreshOnSuccess || !success) {
await mutateProfiles();
}
return success;
} catch (error) {
@ -70,16 +73,14 @@ export const useProfiles = () => {
};
// 根据selected的节点选择
const activateSelected = async () => {
const activateSelected = async (profileOverride?: IProfilesConfig) => {
try {
debugLog("[ActivateSelected] 开始处理代理选择");
const [proxiesData, profileData] = await Promise.all([
calcuProxies(),
getProfiles(),
]);
const proxiesData = await calcuProxies();
const profileData = profileOverride ?? profiles;
if (!profileData || !proxiesData) {
if (!profileData || !proxiesData || !profileData.items) {
debugLog("[ActivateSelected] 代理或配置数据不可用,跳过处理");
return;
}

View File

@ -389,7 +389,7 @@ const ProfilePage = () => {
switchingProfileRef.current === profile &&
!abortController.signal.aborted
) {
await activateSelected();
await activateSelected(profiles);
debugLog(`[Profile] 后台处理完成,序列号: ${sequence}`);
} else {
debugProfileSwitch(
@ -402,7 +402,7 @@ const ProfilePage = () => {
console.warn("Failed to activate selected proxies:", err);
}
},
[activateSelected],
[activateSelected, profiles],
);
const activateProfile = useCallback(
@ -456,6 +456,7 @@ const ProfilePage = () => {
const requestPromise = patchProfiles(
{ current: profile },
currentAbortController.signal,
{ deferRefreshOnSuccess: true },
);
pendingRequestRef.current = requestPromise;

View File

@ -18,7 +18,7 @@ export async function enhanceProfiles() {
}
export async function patchProfilesConfig(profiles: IProfilesConfig) {
return invoke<void>("patch_profiles_config", { profiles });
return invoke<boolean>("patch_profiles_config", { profiles });
}
export async function createProfile(