use crate::{APP_HANDLE, singleton}; use smartstring::alias::String; use std::sync::atomic::{AtomicBool, Ordering}; use tauri::AppHandle; use tauri_plugin_mihomo::{Mihomo, MihomoExt as _}; use super::notification::{FrontendEvent, NotificationSystem}; #[derive(Debug)] pub struct Handle { is_exiting: AtomicBool, } impl Default for Handle { fn default() -> Self { Self { is_exiting: AtomicBool::new(false), } } } singleton!(Handle, HANDLE); impl Handle { pub fn new() -> Self { Self::default() } pub fn app_handle() -> &'static AppHandle { #[allow(clippy::expect_used)] APP_HANDLE.get().expect("App handle not initialized") } pub fn mihomo() -> &'static Mihomo { Self::app_handle().mihomo() } pub fn refresh_clash() { Self::send_event(FrontendEvent::RefreshClash); } pub fn refresh_verge() { Self::send_event(FrontendEvent::RefreshVerge); } pub fn refresh_profiles() { Self::send_event(FrontendEvent::RefreshProfiles); } pub fn refresh_proxy_config() { Self::send_event(FrontendEvent::RefreshProxyConfig); } /// Push a Run State snapshot to the frontend. /// /// Sent on every transition, so the frontend does not have to poll to notice that the Core /// stopped or that the Service came back. pub fn notify_run_state(state: &crate::core::runstate::RunStateView) { let Ok(state) = serde_json::to_value(state) else { return; }; Self::send_event(FrontendEvent::RunStateChanged { state }); } pub fn notify_profile_changed(profile_id: &String) { Self::send_event(FrontendEvent::ProfileChanged { current_profile_id: profile_id, }); } pub fn notify_timer_updated(profile_index: &String) { Self::send_event(FrontendEvent::TimerUpdated { profile_index }); } pub fn notify_profile_update_started(uid: &String) { Self::send_event(FrontendEvent::ProfileUpdateStarted { uid }); } pub fn notify_profile_update_completed(uid: &String) { Self::send_event(FrontendEvent::ProfileUpdateCompleted { uid }); } pub fn notice_message, M: Into>(status: S, msg: M) { let status_str = status.as_ref(); let msg_str = msg.into(); Self::send_event(FrontendEvent::NoticeMessage { status: status_str, message: msg_str, }); } pub fn set_is_exiting(&self) { self.is_exiting.store(true, Ordering::Release); } pub fn clear_is_exiting(&self) { self.is_exiting.store(false, Ordering::Release); } pub fn is_exiting(&self) -> bool { self.is_exiting.load(Ordering::Acquire) } fn send_event(event: FrontendEvent) { let handle = Self::global(); if handle.is_exiting() { return; } NotificationSystem::send_event(Self::app_handle().clone(), event); } } #[cfg(target_os = "macos")] impl Handle { pub fn set_activation_policy(&self, policy: tauri::ActivationPolicy) -> Result<(), String> { Self::app_handle() .set_activation_policy(policy) .map_err(|e| e.to_string().into()) } pub fn set_activation_policy_regular(&self) { let _ = self.set_activation_policy(tauri::ActivationPolicy::Regular); } pub fn set_activation_policy_accessory(&self) { let _ = self.set_activation_policy(tauri::ActivationPolicy::Accessory); } }