diff --git a/src-tauri/packages/macos/entitlements.plist b/src-tauri/packages/macos/entitlements.plist index bfd172dd..a9b8a6b3 100644 --- a/src-tauri/packages/macos/entitlements.plist +++ b/src-tauri/packages/macos/entitlements.plist @@ -2,6 +2,12 @@ + CFBundleIconFile + icon.icns + CFBundleName + Clash Verge + CFBundleDisplayName + Clash Verge com.apple.security.app-sandbox com.apple.security.application-groups @@ -10,5 +16,11 @@ com.apple.security.inherit + LSUIElement + + NSHighResolutionCapable + + NSRequiresAquaSystemAppearance + \ No newline at end of file diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 725d0bcc..464a3675 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -9,6 +9,70 @@ use crate::utils::{resolve, resolve::resolve_scheme, server}; use config::Config; use tauri_plugin_autostart::MacosLauncher; use tauri_plugin_deep_link::DeepLinkExt; +use std::{sync::{Mutex, Once}}; +use tauri::AppHandle; + +/// A global singleton handle to the application. +pub struct AppHandleManager { + inner: Mutex>, + init: Once, +} + +impl AppHandleManager { + /// Get the global instance of the app handle manager. + pub fn global() -> &'static Self { + static INSTANCE: AppHandleManager = AppHandleManager { + inner: Mutex::new(None), + init: Once::new(), + }; + &INSTANCE + } + + /// Initialize the app handle manager with an app handle. + pub fn init(&self, handle: AppHandle) { + self.init.call_once(|| { + let mut app_handle = self.inner.lock().unwrap(); + *app_handle = Some(handle); + }); + } + + /// Get the app handle if it has been initialized. + pub fn get(&self) -> Option { + self.inner.lock().unwrap().clone() + } + + /// Get the app handle, panics if it hasn't been initialized. + pub fn get_handle(&self) -> AppHandle { + self.get().expect("AppHandle not initialized") + } + + pub fn set_activation_policy_regular(&self) { + #[cfg(target_os = "macos")] + { + let app_handle = self.inner.lock().unwrap(); + let app_handle = app_handle.as_ref().unwrap(); + let _ = app_handle.set_activation_policy(tauri::ActivationPolicy::Regular); + } + } + + pub fn set_activation_policy_accessory(&self) { + #[cfg(target_os = "macos")] + { + let app_handle = self.inner.lock().unwrap(); + let app_handle = app_handle.as_ref().unwrap(); + let _ = app_handle.set_activation_policy(tauri::ActivationPolicy::Accessory); + } + } + + pub fn set_activation_policy_prohibited(&self) { + #[cfg(target_os = "macos")] + { + let app_handle = self.inner.lock().unwrap(); + let app_handle = app_handle.as_ref().unwrap(); + let _ = app_handle.set_activation_policy(tauri::ActivationPolicy::Prohibited); + } + } +} pub fn run() { // 单例检测 @@ -136,7 +200,16 @@ pub fn run() { .build(tauri::generate_context!()) .expect("error while running tauri application"); - app.run(|_, e| match e { + app.run(|app_handle, e| match e { + tauri::RunEvent::Ready | tauri::RunEvent::Resumed => { + AppHandleManager::global().init(app_handle.clone()); + } + tauri::RunEvent::Reopen { has_visible_windows, .. } => { + if !has_visible_windows { + AppHandleManager::global().set_activation_policy_regular(); + } + AppHandleManager::global().init(app_handle.clone()); + } tauri::RunEvent::ExitRequested { api, code, .. } => { if code.is_none() { api.prevent_exit(); @@ -146,6 +219,7 @@ pub fn run() { if label == "main" { match event { tauri::WindowEvent::CloseRequested { api, .. } => { + AppHandleManager::global().set_activation_policy_accessory(); if core::handle::Handle::global().is_exiting() { return; } diff --git a/src-tauri/src/utils/resolve.rs b/src-tauri/src/utils/resolve.rs index 8bf402ba..4b160005 100644 --- a/src-tauri/src/utils/resolve.rs +++ b/src-tauri/src/utils/resolve.rs @@ -1,7 +1,7 @@ use crate::config::IVerge; use crate::utils::error; use crate::{config::Config, config::PrfItem, core::*, utils::init, utils::server}; -use crate::{log_err, wrap_err}; +use crate::{log_err, wrap_err, AppHandleManager}; use anyhow::{bail, Result}; use once_cell::sync::OnceCell; use percent_encoding::percent_decode_str; @@ -37,7 +37,10 @@ pub fn find_unused_port() -> Result { pub async fn resolve_setup(app: &mut App) { error::redirect_panic_to_log(); #[cfg(target_os = "macos")] - app.set_activation_policy(tauri::ActivationPolicy::Accessory); + { + AppHandleManager::global().init(app.app_handle().clone()); + AppHandleManager::global().set_activation_policy_accessory(); + } let version = app.package_info().version.to_string(); handle::Handle::global().init(app.app_handle()); @@ -130,6 +133,7 @@ pub fn create_window() { log::info!(target: "app", "Starting to create window"); let app_handle = handle::Handle::global().app_handle().unwrap(); + AppHandleManager::global().set_activation_policy_regular(); if let Some(window) = handle::Handle::global().get_window() { println!("Found existing window, trying to show it"); diff --git a/src-tauri/tauri.macos.conf.json b/src-tauri/tauri.macos.conf.json index 809e86c0..51090fe3 100644 --- a/src-tauri/tauri.macos.conf.json +++ b/src-tauri/tauri.macos.conf.json @@ -1,6 +1,7 @@ { "$schema": "../node_modules/@tauri-apps/cli/config.schema.json", "identifier": "io.github.clash-verge-rev.clash-verge-rev", + "productName": "Clash Verge", "bundle": { "targets": ["app", "dmg"], "macOS": { @@ -31,6 +32,11 @@ } }, "app": { + "windows": [ + { + "title": "Clash Verge" + } + ], "trayIcon": { "iconPath": "icons/tray-icon-mono.ico", "iconAsTemplate": true