Fixed issue with deep links
This commit is contained in:
@@ -211,6 +211,8 @@ pub fn copy_icon_file(path: String, icon_info: IconInfo) -> CmdResult<String> {
|
|||||||
pub fn notify_ui_ready() -> CmdResult<()> {
|
pub fn notify_ui_ready() -> CmdResult<()> {
|
||||||
log::info!(target: "app", "Frontend UI is ready");
|
log::info!(target: "app", "Frontend UI is ready");
|
||||||
crate::utils::resolve::mark_ui_ready();
|
crate::utils::resolve::mark_ui_ready();
|
||||||
|
// Flush any pending messages queued while UI was not ready (e.g. minimized to tray)
|
||||||
|
crate::core::handle::Handle::global().flush_ui_pending_messages();
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -258,6 +258,8 @@ pub struct Handle {
|
|||||||
startup_errors: Arc<RwLock<Vec<ErrorMessage>>>,
|
startup_errors: Arc<RwLock<Vec<ErrorMessage>>>,
|
||||||
startup_completed: Arc<RwLock<bool>>,
|
startup_completed: Arc<RwLock<bool>>,
|
||||||
notification_system: Arc<RwLock<Option<NotificationSystem>>>,
|
notification_system: Arc<RwLock<Option<NotificationSystem>>>,
|
||||||
|
/// Messages that should be emitted only after UI is really ready
|
||||||
|
ui_pending_messages: Arc<RwLock<Vec<ErrorMessage>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Handle {
|
impl Default for Handle {
|
||||||
@@ -268,6 +270,7 @@ impl Default for Handle {
|
|||||||
startup_errors: Arc::new(RwLock::new(Vec::new())),
|
startup_errors: Arc::new(RwLock::new(Vec::new())),
|
||||||
startup_completed: Arc::new(RwLock::new(false)),
|
startup_completed: Arc::new(RwLock::new(false)),
|
||||||
notification_system: Arc::new(RwLock::new(Some(NotificationSystem::new()))),
|
notification_system: Arc::new(RwLock::new(Some(NotificationSystem::new()))),
|
||||||
|
ui_pending_messages: Arc::new(RwLock::new(Vec::new())),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -295,6 +298,10 @@ impl Handle {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_window(&self) -> Option<WebviewWindow> {
|
pub fn get_window(&self) -> Option<WebviewWindow> {
|
||||||
|
// If we are in lightweight mode, treat as no window (webview may be destroyed)
|
||||||
|
if crate::module::lightweight::is_in_lightweight_mode() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
let app_handle = self.app_handle()?;
|
let app_handle = self.app_handle()?;
|
||||||
let window: Option<WebviewWindow> = app_handle.get_webview_window("main");
|
let window: Option<WebviewWindow> = app_handle.get_webview_window("main");
|
||||||
if window.is_none() {
|
if window.is_none() {
|
||||||
@@ -411,6 +418,7 @@ impl Handle {
|
|||||||
let status_str = status.into();
|
let status_str = status.into();
|
||||||
let msg_str = msg.into();
|
let msg_str = msg.into();
|
||||||
|
|
||||||
|
// If startup not completed, buffer messages (existing behavior)
|
||||||
if !*handle.startup_completed.read() {
|
if !*handle.startup_completed.read() {
|
||||||
logging!(
|
logging!(
|
||||||
info,
|
info,
|
||||||
@@ -429,6 +437,23 @@ impl Handle {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If UI is not yet ready (e.g., window re-created from tray or lightweight mode),
|
||||||
|
// buffer messages to emit after UI signals readiness.
|
||||||
|
if !crate::utils::resolve::is_ui_ready() {
|
||||||
|
log::debug!(
|
||||||
|
target: "app",
|
||||||
|
"UI not ready, queue notice message: {} - {}",
|
||||||
|
status_str,
|
||||||
|
msg_str
|
||||||
|
);
|
||||||
|
let mut pendings = handle.ui_pending_messages.write();
|
||||||
|
pendings.push(ErrorMessage {
|
||||||
|
status: status_str,
|
||||||
|
message: msg_str,
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if handle.is_exiting() {
|
if handle.is_exiting() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -442,6 +467,34 @@ impl Handle {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Flush messages buffered while UI was not ready
|
||||||
|
pub fn flush_ui_pending_messages(&self) {
|
||||||
|
let pending = {
|
||||||
|
let mut msgs = self.ui_pending_messages.write();
|
||||||
|
std::mem::take(&mut *msgs)
|
||||||
|
};
|
||||||
|
|
||||||
|
if pending.is_empty() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.is_exiting() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let system_opt = self.notification_system.read();
|
||||||
|
if let Some(system) = system_opt.as_ref() {
|
||||||
|
for msg in pending {
|
||||||
|
system.send_event(FrontendEvent::NoticeMessage {
|
||||||
|
status: msg.status,
|
||||||
|
message: msg.message,
|
||||||
|
});
|
||||||
|
// small pacing to avoid flooding immediately on resume
|
||||||
|
std::thread::sleep(std::time::Duration::from_millis(10));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn mark_startup_completed(&self) {
|
pub fn mark_startup_completed(&self) {
|
||||||
{
|
{
|
||||||
let mut completed = self.startup_completed.write();
|
let mut completed = self.startup_completed.write();
|
||||||
|
|||||||
@@ -7,11 +7,7 @@ mod module;
|
|||||||
mod process;
|
mod process;
|
||||||
mod state;
|
mod state;
|
||||||
mod utils;
|
mod utils;
|
||||||
use crate::{
|
use crate::{core::hotkey, process::AsyncHandler, utils::resolve};
|
||||||
core::hotkey,
|
|
||||||
process::AsyncHandler,
|
|
||||||
utils::{resolve, resolve::resolve_scheme},
|
|
||||||
};
|
|
||||||
use config::Config;
|
use config::Config;
|
||||||
use std::sync::{Mutex, Once};
|
use std::sync::{Mutex, Once};
|
||||||
use tauri::AppHandle;
|
use tauri::AppHandle;
|
||||||
@@ -86,7 +82,10 @@ impl AppHandleManager {
|
|||||||
|
|
||||||
#[allow(clippy::panic)]
|
#[allow(clippy::panic)]
|
||||||
pub fn run() {
|
pub fn run() {
|
||||||
utils::network::NetworkManager::global().init();
|
// Capture early deep link before any async setup (cold start on macOS)
|
||||||
|
utils::resolve::capture_early_deep_link_from_args();
|
||||||
|
|
||||||
|
utils::network::NetworkManager::global().init();
|
||||||
|
|
||||||
let _ = utils::dirs::init_portable_flag();
|
let _ = utils::dirs::init_portable_flag();
|
||||||
|
|
||||||
@@ -96,52 +95,54 @@ pub fn run() {
|
|||||||
#[cfg(debug_assertions)]
|
#[cfg(debug_assertions)]
|
||||||
let devtools = tauri_plugin_devtools::init();
|
let devtools = tauri_plugin_devtools::init();
|
||||||
|
|
||||||
#[allow(unused_mut)]
|
#[allow(unused_mut)]
|
||||||
let mut builder = tauri::Builder::default()
|
let mut builder = tauri::Builder::default()
|
||||||
.plugin(tauri_plugin_single_instance::init(|app, _argv, _cwd| {
|
.plugin(tauri_plugin_single_instance::init(|_app, argv, _cwd| {
|
||||||
if let Some(window) = app.get_webview_window("main") {
|
// Handle deep link when a second instance is invoked: forward URL to the running instance
|
||||||
let _ = window.show();
|
if let Some(url) = argv
|
||||||
let _ = window.unminimize();
|
.iter()
|
||||||
let _ = window.set_focus();
|
.find(|a| a.starts_with("clash://") || a.starts_with("koala-clash://"))
|
||||||
}
|
.cloned()
|
||||||
}))
|
{
|
||||||
.plugin(tauri_plugin_notification::init())
|
// Robust scheduling avoids races with lightweight/window
|
||||||
.plugin(tauri_plugin_updater::Builder::new().build())
|
resolve::schedule_handle_deep_link(url);
|
||||||
.plugin(tauri_plugin_clipboard_manager::init())
|
}
|
||||||
.plugin(tauri_plugin_process::init())
|
}))
|
||||||
.plugin(tauri_plugin_global_shortcut::Builder::new().build())
|
.plugin(tauri_plugin_notification::init())
|
||||||
.plugin(tauri_plugin_fs::init())
|
.plugin(tauri_plugin_updater::Builder::new().build())
|
||||||
.plugin(tauri_plugin_dialog::init())
|
.plugin(tauri_plugin_clipboard_manager::init())
|
||||||
.plugin(tauri_plugin_shell::init())
|
.plugin(tauri_plugin_process::init())
|
||||||
.plugin(tauri_plugin_deep_link::init())
|
.plugin(tauri_plugin_global_shortcut::Builder::new().build())
|
||||||
.setup(|app| {
|
.plugin(tauri_plugin_fs::init())
|
||||||
logging!(info, Type::Setup, true, "Starting app initialization...");
|
.plugin(tauri_plugin_dialog::init())
|
||||||
let mut auto_start_plugin_builder = tauri_plugin_autostart::Builder::new();
|
.plugin(tauri_plugin_shell::init())
|
||||||
#[cfg(target_os = "macos")]
|
.plugin(tauri_plugin_deep_link::init())
|
||||||
{
|
.setup(|app| {
|
||||||
auto_start_plugin_builder = auto_start_plugin_builder
|
logging!(info, Type::Setup, true, "Starting app initialization...");
|
||||||
.macos_launcher(MacosLauncher::LaunchAgent)
|
|
||||||
.app_name(app.config().identifier.clone());
|
|
||||||
}
|
|
||||||
let _ = app.handle().plugin(auto_start_plugin_builder.build());
|
|
||||||
|
|
||||||
#[cfg(any(target_os = "linux", all(debug_assertions, windows)))]
|
// Register deep link handler as early as possible to not miss cold-start events (macOS)
|
||||||
{
|
app.deep_link().on_open_url(|event| {
|
||||||
use tauri_plugin_deep_link::DeepLinkExt;
|
let urls: Vec<String> = event.urls().iter().map(|u| u.to_string()).collect();
|
||||||
logging!(info, Type::Setup, true, "Registering deep links...");
|
logging!(info, Type::Setup, true, "on_open_url received: {:?}", urls);
|
||||||
logging_error!(Type::System, true, app.deep_link().register_all());
|
if let Some(url) = urls.first().cloned() {
|
||||||
}
|
resolve::schedule_handle_deep_link(url);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
app.deep_link().on_open_url(|event| {
|
let mut auto_start_plugin_builder = tauri_plugin_autostart::Builder::new();
|
||||||
AsyncHandler::spawn(move || {
|
#[cfg(target_os = "macos")]
|
||||||
let url = event.urls().first().map(|u| u.to_string());
|
{
|
||||||
async move {
|
auto_start_plugin_builder = auto_start_plugin_builder
|
||||||
if let Some(url) = url {
|
.macos_launcher(MacosLauncher::LaunchAgent)
|
||||||
logging_error!(Type::Setup, true, resolve_scheme(url).await);
|
.app_name(app.config().identifier.clone());
|
||||||
}
|
}
|
||||||
}
|
let _ = app.handle().plugin(auto_start_plugin_builder.build());
|
||||||
});
|
|
||||||
});
|
// Ensure URL schemes are registered with the OS (all platforms)
|
||||||
|
logging!(info, Type::Setup, true, "Registering deep links with OS...");
|
||||||
|
logging_error!(Type::System, true, app.deep_link().register_all());
|
||||||
|
|
||||||
|
// Deep link handler will be registered AFTER core handle init to ensure window creation works
|
||||||
|
|
||||||
// 窗口管理
|
// 窗口管理
|
||||||
logging!(
|
logging!(
|
||||||
@@ -223,18 +224,23 @@ pub fn run() {
|
|||||||
app.manage(Mutex::new(state::proxy::CmdProxyState::default()));
|
app.manage(Mutex::new(state::proxy::CmdProxyState::default()));
|
||||||
app.manage(Mutex::new(state::lightweight::LightWeightState::default()));
|
app.manage(Mutex::new(state::lightweight::LightWeightState::default()));
|
||||||
|
|
||||||
tauri::async_runtime::spawn(async {
|
// If an early deep link was captured from argv, schedule it now (after core and window can be created)
|
||||||
tokio::time::sleep(Duration::from_secs(5)).await;
|
utils::resolve::replay_early_deep_link();
|
||||||
logging!(
|
|
||||||
info,
|
// (deep link handler already registered above)
|
||||||
Type::Cmd,
|
|
||||||
true,
|
tauri::async_runtime::spawn(async {
|
||||||
"Running profile updates at startup..."
|
tokio::time::sleep(Duration::from_secs(5)).await;
|
||||||
);
|
logging!(
|
||||||
if let Err(e) = crate::cmd::update_profiles_on_startup().await {
|
info,
|
||||||
log::error!("Failed to update profiles on startup: {e}");
|
Type::Cmd,
|
||||||
}
|
true,
|
||||||
});
|
"Running profile updates at startup..."
|
||||||
|
);
|
||||||
|
if let Err(e) = crate::cmd::update_profiles_on_startup().await {
|
||||||
|
log::error!("Failed to update profiles on startup: {e}");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
logging!(
|
logging!(
|
||||||
info,
|
info,
|
||||||
|
|||||||
@@ -115,7 +115,7 @@ pub fn disable_auto_light_weight_mode() {
|
|||||||
|
|
||||||
pub fn entry_lightweight_mode() {
|
pub fn entry_lightweight_mode() {
|
||||||
use crate::utils::window_manager::WindowManager;
|
use crate::utils::window_manager::WindowManager;
|
||||||
|
crate::utils::resolve::reset_ui_ready();
|
||||||
let result = WindowManager::hide_main_window();
|
let result = WindowManager::hide_main_window();
|
||||||
logging!(
|
logging!(
|
||||||
info,
|
info,
|
||||||
|
|||||||
@@ -398,6 +398,33 @@ pub fn init_scheme() -> Result<()> {
|
|||||||
}
|
}
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
pub fn init_scheme() -> Result<()> {
|
pub fn init_scheme() -> Result<()> {
|
||||||
|
use std::process::Command;
|
||||||
|
use tauri::utils::platform::current_exe;
|
||||||
|
|
||||||
|
// Try to re-register the app bundle with LaunchServices to ensure URL schemes are active
|
||||||
|
if let Ok(exe) = current_exe() {
|
||||||
|
if let (Some(_parent1), Some(_parent2), Some(app_bundle)) =
|
||||||
|
(exe.parent(), exe.parent().and_then(|p| p.parent()), exe.parent().and_then(|p| p.parent()).and_then(|p| p.parent()))
|
||||||
|
{
|
||||||
|
let app_bundle_path = app_bundle.to_string_lossy().into_owned();
|
||||||
|
let lsregister = "/System/Library/Frameworks/CoreServices.framework/Frameworks/LaunchServices.framework/Support/lsregister";
|
||||||
|
let output = Command::new(lsregister)
|
||||||
|
.args(["-f", "-R", &app_bundle_path])
|
||||||
|
.output();
|
||||||
|
match output {
|
||||||
|
Ok(out) => {
|
||||||
|
if !out.status.success() {
|
||||||
|
log::warn!(target: "app", "lsregister returned non-zero: {:?}", out.status);
|
||||||
|
} else {
|
||||||
|
log::info!(target: "app", "Re-registered URL schemes with LaunchServices");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
log::warn!(target: "app", "Failed to run lsregister: {e}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,10 +3,11 @@ use crate::AppHandleManager;
|
|||||||
use crate::{
|
use crate::{
|
||||||
config::{Config, IVerge, PrfItem},
|
config::{Config, IVerge, PrfItem},
|
||||||
core::*,
|
core::*,
|
||||||
|
core::handle::Handle,
|
||||||
logging, logging_error,
|
logging, logging_error,
|
||||||
module::lightweight::{self, auto_lightweight_mode_init},
|
module::lightweight::{self, auto_lightweight_mode_init},
|
||||||
process::AsyncHandler,
|
process::AsyncHandler,
|
||||||
utils::{init, logging::Type, server},
|
utils::{init, logging::Type, server, window_manager::WindowManager},
|
||||||
wrap_err,
|
wrap_err,
|
||||||
};
|
};
|
||||||
use anyhow::{bail, Result};
|
use anyhow::{bail, Result};
|
||||||
@@ -65,6 +66,35 @@ impl Default for UiReadyState {
|
|||||||
// 获取UI就绪状态细节
|
// 获取UI就绪状态细节
|
||||||
static UI_READY_STATE: OnceCell<Arc<UiReadyState>> = OnceCell::new();
|
static UI_READY_STATE: OnceCell<Arc<UiReadyState>> = OnceCell::new();
|
||||||
|
|
||||||
|
// Early deep link capture on cold start
|
||||||
|
static EARLY_DEEP_LINK: OnceCell<Mutex<Option<String>>> = OnceCell::new();
|
||||||
|
// Deduplication for deep links to avoid processing same URL twice in short time
|
||||||
|
static LAST_DEEP_LINK: OnceCell<Mutex<Option<(String, Instant)>>> = OnceCell::new();
|
||||||
|
|
||||||
|
fn get_early_deep_link() -> &'static Mutex<Option<String>> {
|
||||||
|
EARLY_DEEP_LINK.get_or_init(|| Mutex::new(None))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Capture deep link from process arguments as early as possible (cold start on macOS)
|
||||||
|
pub fn capture_early_deep_link_from_args() {
|
||||||
|
let args: Vec<String> = std::env::args().collect();
|
||||||
|
if let Some(url) = args.iter().find(|a| a.starts_with("clash://") || a.starts_with("koala-clash://")).cloned() {
|
||||||
|
println!("[DeepLink][argv] {}", url);
|
||||||
|
logging!(info, Type::Setup, true, "argv captured deep link: {}", url);
|
||||||
|
*get_early_deep_link().lock() = Some(url);
|
||||||
|
} else {
|
||||||
|
println!("[DeepLink][argv] none: {:?}", args);
|
||||||
|
logging!(info, Type::Setup, true, "no deep link found in argv at startup: {:?}", args);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// If an early deep link was captured before setup, schedule it now
|
||||||
|
pub fn replay_early_deep_link() {
|
||||||
|
if let Some(url) = get_early_deep_link().lock().take() {
|
||||||
|
schedule_handle_deep_link(url);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn get_window_creating_lock() -> &'static Mutex<(bool, Instant)> {
|
fn get_window_creating_lock() -> &'static Mutex<(bool, Instant)> {
|
||||||
WINDOW_CREATING.get_or_init(|| Mutex::new((false, Instant::now())))
|
WINDOW_CREATING.get_or_init(|| Mutex::new((false, Instant::now())))
|
||||||
}
|
}
|
||||||
@@ -73,6 +103,11 @@ fn get_ui_ready() -> &'static Arc<RwLock<bool>> {
|
|||||||
UI_READY.get_or_init(|| Arc::new(RwLock::new(false)))
|
UI_READY.get_or_init(|| Arc::new(RwLock::new(false)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Check whether the UI has finished initialization on the frontend side
|
||||||
|
pub fn is_ui_ready() -> bool {
|
||||||
|
*get_ui_ready().read()
|
||||||
|
}
|
||||||
|
|
||||||
fn get_ui_ready_state() -> &'static Arc<UiReadyState> {
|
fn get_ui_ready_state() -> &'static Arc<UiReadyState> {
|
||||||
UI_READY_STATE.get_or_init(|| Arc::new(UiReadyState::default()))
|
UI_READY_STATE.get_or_init(|| Arc::new(UiReadyState::default()))
|
||||||
}
|
}
|
||||||
@@ -94,6 +129,9 @@ pub fn mark_ui_ready() {
|
|||||||
let mut ready = get_ui_ready().write();
|
let mut ready = get_ui_ready().write();
|
||||||
*ready = true;
|
*ready = true;
|
||||||
logging!(info, Type::Window, true, "UI已标记为完全就绪");
|
logging!(info, Type::Window, true, "UI已标记为完全就绪");
|
||||||
|
|
||||||
|
// If any deep links were queued while UI was not ready, handle them now
|
||||||
|
// No queued deep links list anymore; early and runtime deep links are deduped
|
||||||
}
|
}
|
||||||
|
|
||||||
// 重置UI就绪状态
|
// 重置UI就绪状态
|
||||||
@@ -110,6 +148,82 @@ pub fn reset_ui_ready() {
|
|||||||
logging!(info, Type::Window, true, "UI就绪状态已重置");
|
logging!(info, Type::Window, true, "UI就绪状态已重置");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Schedule robust deep-link handling to avoid races with lightweight mode and window creation
|
||||||
|
pub fn schedule_handle_deep_link(url: String) {
|
||||||
|
AsyncHandler::spawn(move || async move {
|
||||||
|
// Normalize dedup key to the actual subscription URL inside the deep link
|
||||||
|
let dedup_key = (|| {
|
||||||
|
if let Ok(parsed) = Url::parse(&url) {
|
||||||
|
for (k, v) in parsed.query_pairs() {
|
||||||
|
if k == "url" {
|
||||||
|
return percent_decode_str(&v).decode_utf8_lossy().to_string();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
url.clone()
|
||||||
|
})();
|
||||||
|
|
||||||
|
// Deduplicate: if the same deep/subscription link was handled very recently, skip
|
||||||
|
{
|
||||||
|
let now = Instant::now();
|
||||||
|
let mut last = LAST_DEEP_LINK.get_or_init(|| Mutex::new(None)).lock();
|
||||||
|
if let Some((prev_url, prev_time)) = last.as_ref() {
|
||||||
|
if *prev_url == dedup_key && now.duration_since(*prev_time) < Duration::from_secs(5) {
|
||||||
|
log::warn!(target: "app", "Skip duplicate deep link within 5s: {}", dedup_key);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*last = Some((dedup_key.clone(), now));
|
||||||
|
}
|
||||||
|
// Wait until app handle exists
|
||||||
|
for i in 0..100u8 {
|
||||||
|
if Handle::global().app_handle().is_some() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if i % 10 == 0 { logging!(info, Type::Setup, true, "waiting for app handle... ({}ms)", i as u64 * 20); }
|
||||||
|
tokio::time::sleep(Duration::from_millis(20)).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure we are not in lightweight mode (webview destroyed)
|
||||||
|
lightweight::exit_lightweight_mode();
|
||||||
|
for _ in 0..150u16 {
|
||||||
|
if !lightweight::is_in_lightweight_mode() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
tokio::time::sleep(Duration::from_millis(20)).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure a window exists ASAP so UI can mount
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
{
|
||||||
|
AppHandleManager::global().set_activation_policy_regular();
|
||||||
|
}
|
||||||
|
// If lightweight mode was active, give it a bit of time to unwind before recreating window
|
||||||
|
if lightweight::is_in_lightweight_mode() {
|
||||||
|
tokio::time::sleep(Duration::from_millis(200)).await;
|
||||||
|
}
|
||||||
|
let _ = WindowManager::show_main_window();
|
||||||
|
|
||||||
|
// Ensure profiles directory exists on cold start
|
||||||
|
if let Ok(dir) = crate::utils::dirs::app_profiles_dir() {
|
||||||
|
if !dir.exists() {
|
||||||
|
let _ = std::fs::create_dir_all(&dir);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process deep link (add profile regardless of UI state)
|
||||||
|
logging!(info, Type::Setup, true, "processing deep link: {}", dedup_key);
|
||||||
|
if let Err(e) = resolve_scheme(url.clone()).await {
|
||||||
|
log::error!(target: "app", "Deep link handling failed: {e}");
|
||||||
|
}
|
||||||
|
|
||||||
|
// If UI is ready, small delay to let listeners settle before finishing
|
||||||
|
if is_ui_ready() {
|
||||||
|
tokio::time::sleep(Duration::from_millis(120)).await;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn find_unused_port() -> Result<u16> {
|
pub async fn find_unused_port() -> Result<u16> {
|
||||||
match TcpListener::bind("127.0.0.1:0").await {
|
match TcpListener::bind("127.0.0.1:0").await {
|
||||||
Ok(listener) => {
|
Ok(listener) => {
|
||||||
@@ -286,21 +400,34 @@ pub fn create_window(is_show: bool) -> bool {
|
|||||||
|
|
||||||
if let Some(app_handle) = handle::Handle::global().app_handle() {
|
if let Some(app_handle) = handle::Handle::global().app_handle() {
|
||||||
if let Some(window) = app_handle.get_webview_window("main") {
|
if let Some(window) = app_handle.get_webview_window("main") {
|
||||||
logging!(info, Type::Window, true, "主窗口已存在,将显示现有窗口");
|
logging!(info, Type::Window, true, "主窗口已存在,将尝试显示现有窗口");
|
||||||
if is_show {
|
if is_show {
|
||||||
if window.is_minimized().unwrap_or(false) {
|
if window.is_minimized().unwrap_or(false) {
|
||||||
logging!(info, Type::Window, true, "窗口已最小化,正在取消最小化");
|
logging!(info, Type::Window, true, "窗口已最小化,正在取消最小化");
|
||||||
let _ = window.unminimize();
|
let _ = window.unminimize();
|
||||||
}
|
}
|
||||||
let _ = window.show();
|
let show_result = window.show();
|
||||||
let _ = window.set_focus();
|
let focus_result = window.set_focus();
|
||||||
|
|
||||||
#[cfg(target_os = "macos")]
|
// If showing or focusing fails (possibly destroyed webview after lightweight), fallback to recreate
|
||||||
{
|
if show_result.is_err() || focus_result.is_err() {
|
||||||
AppHandleManager::global().set_activation_policy_regular();
|
logging!(
|
||||||
|
warn,
|
||||||
|
Type::Window,
|
||||||
|
true,
|
||||||
|
"现有窗口显示失败,尝试销毁并重新创建"
|
||||||
|
);
|
||||||
|
let _ = window.destroy();
|
||||||
|
} else {
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
{
|
||||||
|
AppHandleManager::global().set_activation_policy_regular();
|
||||||
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -566,11 +693,12 @@ pub async fn resolve_scheme(param: String) -> Result<()> {
|
|||||||
Some(url) => {
|
Some(url) => {
|
||||||
log::info!(target:"app", "decoded subscription url: {url}");
|
log::info!(target:"app", "decoded subscription url: {url}");
|
||||||
|
|
||||||
create_window(true);
|
// Deep link inside resolver is now executed via schedule_handle_deep_link
|
||||||
match PrfItem::from_url(url.as_ref(), name, None, None).await {
|
match PrfItem::from_url(url.as_ref(), name, None, None).await {
|
||||||
Ok(item) => {
|
Ok(item) => {
|
||||||
let uid = item.uid.clone().unwrap();
|
let uid = item.uid.clone().unwrap();
|
||||||
let _ = wrap_err!(Config::profiles().data().append_item(item));
|
let _ = wrap_err!(Config::profiles().data().append_item(item));
|
||||||
|
// If UI not ready yet, message will be queued and flushed on ready
|
||||||
handle::Handle::notice_message("import_sub_url::ok", uid);
|
handle::Handle::notice_message("import_sub_url::ok", uid);
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
|
|||||||
@@ -19,7 +19,8 @@ export const useListen = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const setupCloseListener = async function () {
|
const setupCloseListener = async function () {
|
||||||
await event.once("tauri://close-requested", async () => {
|
// Do not clear listeners on close-requested (we hide to tray). Clean up only when window is destroyed.
|
||||||
|
await event.once("tauri://destroyed", async () => {
|
||||||
removeAllListeners();
|
removeAllListeners();
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user