mirror of
https://github.com/clash-verge-rev/clash-verge-rev.git
synced 2026-10-27 16:22:11 +08:00
* chore: update tauri-plugin-mihomo dep * refactor(tauri): remove unnecessary await from clear_all_ws_connections
187 lines
6.4 KiB
Rust
187 lines
6.4 KiB
Rust
use dark_light::{Mode as SystemTheme, detect as detect_system_theme};
|
|
use tauri::utils::config::Color;
|
|
use tauri::webview::PageLoadEvent;
|
|
use tauri::{Theme, WebviewWindow};
|
|
|
|
use crate::{config::Config, core::handle, utils::resolve::window_script::build_window_initial_script};
|
|
#[cfg(target_os = "macos")]
|
|
use clash_verge_logging::logging;
|
|
use clash_verge_logging::{Type, logging_error};
|
|
|
|
const DARK_BACKGROUND_COLOR: Color = Color(46, 48, 61, 255); // #2E303D
|
|
const LIGHT_BACKGROUND_COLOR: Color = Color(245, 245, 245, 255); // #F5F5F5
|
|
const DARK_BACKGROUND_HEX: &str = "#2E303D";
|
|
const LIGHT_BACKGROUND_HEX: &str = "#F5F5F5";
|
|
|
|
const DEFAULT_WIDTH: f64 = 940.0;
|
|
const DEFAULT_HEIGHT: f64 = 700.0;
|
|
|
|
const MINIMAL_WIDTH: f64 = 520.0;
|
|
const MINIMAL_HEIGHT: f64 = 520.0;
|
|
|
|
#[cfg(target_os = "linux")]
|
|
const DEFAULT_DECORATIONS: bool = false;
|
|
#[cfg(not(target_os = "linux"))]
|
|
const DEFAULT_DECORATIONS: bool = true;
|
|
|
|
const fn restored_window_size_is_too_small(width: u32, height: u32) -> bool {
|
|
width < MINIMAL_WIDTH as u32 || height < MINIMAL_HEIGHT as u32
|
|
}
|
|
|
|
fn restore_default_size_if_needed(window: &WebviewWindow) {
|
|
let Ok(size) = window.outer_size() else {
|
|
return;
|
|
};
|
|
|
|
if !restored_window_size_is_too_small(size.width, size.height) {
|
|
return;
|
|
}
|
|
|
|
logging_error!(
|
|
Type::Window,
|
|
window.set_size(tauri::LogicalSize::new(DEFAULT_WIDTH, DEFAULT_HEIGHT))
|
|
);
|
|
logging_error!(Type::Window, window.center());
|
|
}
|
|
|
|
pub async fn build_new_window() -> Result<WebviewWindow, String> {
|
|
let app_handle = handle::Handle::app_handle();
|
|
|
|
let config = Config::verge().await;
|
|
let latest = config.latest_arc();
|
|
let start_page = latest.start_page.as_deref().unwrap_or("/");
|
|
let initial_theme_mode = match latest.theme_mode.as_deref() {
|
|
Some("dark") => "dark",
|
|
Some("light") => "light",
|
|
_ => "system",
|
|
};
|
|
|
|
let resolved_theme = match initial_theme_mode {
|
|
"dark" => Some(Theme::Dark),
|
|
"light" => Some(Theme::Light),
|
|
_ => None,
|
|
};
|
|
|
|
let prefers_dark_background = match resolved_theme {
|
|
Some(Theme::Dark) => true,
|
|
Some(Theme::Light) => false,
|
|
_ => !matches!(detect_system_theme().ok(), Some(SystemTheme::Light)),
|
|
};
|
|
|
|
let background_color = if prefers_dark_background {
|
|
DARK_BACKGROUND_COLOR
|
|
} else {
|
|
LIGHT_BACKGROUND_COLOR
|
|
};
|
|
|
|
let initial_script = build_window_initial_script(initial_theme_mode, DARK_BACKGROUND_HEX, LIGHT_BACKGROUND_HEX);
|
|
|
|
let mut builder = tauri::WebviewWindowBuilder::new(
|
|
app_handle,
|
|
"main", /* the unique window label */
|
|
tauri::WebviewUrl::App(start_page.into()),
|
|
)
|
|
.title("Clash Verge")
|
|
.center()
|
|
.decorations(DEFAULT_DECORATIONS)
|
|
.fullscreen(false)
|
|
.inner_size(DEFAULT_WIDTH, DEFAULT_HEIGHT)
|
|
.min_inner_size(MINIMAL_WIDTH, MINIMAL_HEIGHT)
|
|
.visible(false) // 等待主题色准备好后再展示,避免启动色差
|
|
.initialization_script(&initial_script)
|
|
.general_autofill_enabled(false) // 禁用自动填充
|
|
.on_page_load(move |window, payload| {
|
|
if payload.event() != PageLoadEvent::Finished {
|
|
return;
|
|
}
|
|
|
|
logging_error!(Type::Window, window.show());
|
|
logging_error!(Type::Window, window.set_focus());
|
|
});
|
|
|
|
if let Some(theme) = resolved_theme {
|
|
builder = builder.theme(Some(theme));
|
|
}
|
|
|
|
builder = builder.background_color(background_color);
|
|
|
|
match builder.build() {
|
|
Ok(window) => {
|
|
logging_error!(Type::Window, window.set_background_color(Some(background_color)));
|
|
restore_default_size_if_needed(&window);
|
|
// A new page supersedes any reload marker left by the old window.
|
|
#[cfg(target_os = "macos")]
|
|
take_webview_needs_reload();
|
|
Ok(window)
|
|
}
|
|
Err(e) => Err(e.to_string()),
|
|
}
|
|
}
|
|
|
|
/// Defers recovery of a terminated hidden main webview until its next activation.
|
|
#[cfg(target_os = "macos")]
|
|
static WEBVIEW_NEEDS_RELOAD: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(false);
|
|
|
|
#[cfg(target_os = "macos")]
|
|
pub fn take_webview_needs_reload() -> bool {
|
|
WEBVIEW_NEEDS_RELOAD.swap(false, std::sync::atomic::Ordering::SeqCst)
|
|
}
|
|
|
|
///
|
|
/// macOS may kill hidden WebContent under memory pressure. Clean orphaned Mihomo subscriptions,
|
|
/// reload visible webviews immediately, and defer a hidden main-window reload until activation.
|
|
/// Registering this callback replaces Tauri's default automatic reload.
|
|
#[cfg(target_os = "macos")]
|
|
pub fn on_web_content_process_terminated(webview: &tauri::Webview) {
|
|
if handle::Handle::global().is_exiting() {
|
|
return;
|
|
}
|
|
|
|
logging!(
|
|
warn,
|
|
Type::Window,
|
|
"WebView 渲染进程已被系统终止(label={}),开始恢复",
|
|
webview.label()
|
|
);
|
|
|
|
let window = webview.window();
|
|
let is_user_visible = window.is_visible().unwrap_or(false) && !window.is_minimized().unwrap_or(false);
|
|
|
|
// Only the main window has a path that consumes the deferred marker.
|
|
let is_main_window = webview.label() == "main";
|
|
let reload_now = is_user_visible || !is_main_window;
|
|
|
|
if !reload_now {
|
|
WEBVIEW_NEEDS_RELOAD.store(true, std::sync::atomic::Ordering::SeqCst);
|
|
logging!(info, Type::Window, "窗口不可见,页面将在下次打开窗口时重载");
|
|
}
|
|
|
|
// Clean before reload so cleanup cannot remove subscriptions created by the new page.
|
|
let webview = webview.clone();
|
|
crate::process::AsyncHandler::spawn(move || async move {
|
|
if let Err(err) = handle::Handle::mihomo().clear_all_ws_connections() {
|
|
logging!(warn, Type::Window, "清理 Mihomo WebSocket 连接失败: {err}");
|
|
} else {
|
|
logging!(info, Type::Window, "已清理全部 Mihomo WebSocket 连接");
|
|
}
|
|
if reload_now {
|
|
logging_error!(Type::Window, webview.reload());
|
|
}
|
|
});
|
|
}
|
|
|
|
/// Consumes the shared marker for native unminimize paths that bypass `activate_window`.
|
|
#[cfg(target_os = "macos")]
|
|
pub fn reload_main_window_if_needed() {
|
|
if !take_webview_needs_reload() {
|
|
return;
|
|
}
|
|
let Some(window) = crate::utils::window_manager::WindowManager::get_main_window() else {
|
|
return;
|
|
};
|
|
logging!(info, Type::Window, "渲染进程曾被系统终止,窗口聚焦后重载页面");
|
|
if let Err(e) = window.reload() {
|
|
logging!(warn, Type::Window, "重载页面失败: {e}");
|
|
}
|
|
}
|