diff --git a/src-tauri/src/cmd/profile.rs b/src-tauri/src/cmd/profile.rs index 60bdb09c1..9388e3b7b 100644 --- a/src-tauri/src/cmd/profile.rs +++ b/src-tauri/src/cmd/profile.rs @@ -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 { 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()); } diff --git a/src-tauri/src/core/handle.rs b/src-tauri/src/core/handle.rs index 1a027f68a..3f3a6ff7a 100644 --- a/src-tauri/src/core/handle.rs +++ b/src-tauri/src/core/handle.rs @@ -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 { - Self::app_handle().get_webview_window("main") - } - pub fn refresh_clash() { let handle = Self::global(); if handle.is_exiting() { diff --git a/src-tauri/src/core/hotkey.rs b/src-tauri/src/core/hotkey.rs index be7b927b5..a34fda63c 100755 --- a/src-tauri/src/core/hotkey.rs +++ b/src-tauri/src/core/hotkey.rs @@ -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); diff --git a/src-tauri/src/core/notification.rs b/src-tauri/src/core/notification.rs index e6c0e0a75..fe4c9d5e1 100644 --- a/src-tauri/src/core/notification.rs +++ b/src-tauri/src/core/notification.rs @@ -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); diff --git a/src-tauri/src/feat/window.rs b/src-tauri/src/feat/window.rs index d2d4ba1f4..276eaba06 100644 --- a/src-tauri/src/feat/window.rs +++ b/src-tauri/src/feat/window.rs @@ -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(); diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 1752992c4..bd3fa20c7 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -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(); } } diff --git a/src-tauri/src/module/lightweight.rs b/src-tauri/src/module/lightweight.rs index 04f87230a..489280878 100644 --- a/src-tauri/src/module/lightweight.rs +++ b/src-tauri/src/module/lightweight.rs @@ -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); diff --git a/src-tauri/src/utils/window_manager.rs b/src-tauri/src/utils/window_manager.rs index 30ee4abcb..83b970e42 100644 --- a/src-tauri/src/utils/window_manager.rs +++ b/src-tauri/src/utils/window_manager.rs @@ -59,6 +59,28 @@ fn should_handle_window_operation() -> bool { pub struct WindowManager; impl WindowManager { + pub fn get_main_window_with_state() -> (Option>, 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>) -> 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>) -> 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>) -> 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>) -> 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>) -> 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}") } diff --git a/src/hooks/use-profiles.ts b/src/hooks/use-profiles.ts index 76db32faa..6d84ecc8a 100644 --- a/src/hooks/use-profiles.ts +++ b/src/hooks/use-profiles.ts @@ -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, 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; } diff --git a/src/pages/profiles.tsx b/src/pages/profiles.tsx index 15774b578..2162b60b8 100644 --- a/src/pages/profiles.tsx +++ b/src/pages/profiles.tsx @@ -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; diff --git a/src/services/cmds.ts b/src/services/cmds.ts index 0f2311473..13ec3be36 100644 --- a/src/services/cmds.ts +++ b/src/services/cmds.ts @@ -18,7 +18,7 @@ export async function enhanceProfiles() { } export async function patchProfilesConfig(profiles: IProfilesConfig) { - return invoke("patch_profiles_config", { profiles }); + return invoke("patch_profiles_config", { profiles }); } export async function createProfile(