mirror of
https://github.com/clash-verge-rev/clash-verge-rev
synced 2025-05-06 03:13:44 +08:00
feat: check window render size and wait for completion to prevent white screen
This commit is contained in:
parent
fad73a281a
commit
85d08fadd9
@ -214,3 +214,11 @@ pub fn copy_icon_file(path: String, icon_info: IconInfo) -> CmdResult<String> {
|
|||||||
Err("file not found".to_string())
|
Err("file not found".to_string())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 通知UI已准备就绪
|
||||||
|
#[tauri::command]
|
||||||
|
pub fn notify_ui_ready() -> CmdResult<()> {
|
||||||
|
log::info!(target: "app", "前端UI已准备就绪");
|
||||||
|
crate::utils::resolve::mark_ui_ready();
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
@ -19,8 +19,8 @@ use tauri::AppHandle;
|
|||||||
use tauri::Manager;
|
use tauri::Manager;
|
||||||
use tauri_plugin_autostart::MacosLauncher;
|
use tauri_plugin_autostart::MacosLauncher;
|
||||||
use tauri_plugin_deep_link::DeepLinkExt;
|
use tauri_plugin_deep_link::DeepLinkExt;
|
||||||
use utils::logging::Type;
|
|
||||||
use tauri_plugin_window_state;
|
use tauri_plugin_window_state;
|
||||||
|
use utils::logging::Type;
|
||||||
|
|
||||||
/// A global singleton handle to the application.
|
/// A global singleton handle to the application.
|
||||||
pub struct AppHandleManager {
|
pub struct AppHandleManager {
|
||||||
@ -118,9 +118,11 @@ pub fn run() {
|
|||||||
.plugin(tauri_plugin_dialog::init())
|
.plugin(tauri_plugin_dialog::init())
|
||||||
.plugin(tauri_plugin_shell::init())
|
.plugin(tauri_plugin_shell::init())
|
||||||
.plugin(tauri_plugin_deep_link::init())
|
.plugin(tauri_plugin_deep_link::init())
|
||||||
.plugin(tauri_plugin_window_state::Builder::default()
|
.plugin(
|
||||||
.with_state_flags(tauri_plugin_window_state::StateFlags::all())
|
tauri_plugin_window_state::Builder::default()
|
||||||
.build())
|
.with_state_flags(tauri_plugin_window_state::StateFlags::all())
|
||||||
|
.build(),
|
||||||
|
)
|
||||||
.setup(|app| {
|
.setup(|app| {
|
||||||
#[cfg(any(target_os = "linux", all(debug_assertions, windows)))]
|
#[cfg(any(target_os = "linux", all(debug_assertions, windows)))]
|
||||||
{
|
{
|
||||||
@ -157,6 +159,7 @@ pub fn run() {
|
|||||||
cmd::restart_core,
|
cmd::restart_core,
|
||||||
cmd::restart_app,
|
cmd::restart_app,
|
||||||
// 添加新的命令
|
// 添加新的命令
|
||||||
|
cmd::notify_ui_ready,
|
||||||
cmd::get_running_mode,
|
cmd::get_running_mode,
|
||||||
cmd::get_app_uptime,
|
cmd::get_app_uptime,
|
||||||
cmd::get_auto_launch_status,
|
cmd::get_auto_launch_status,
|
||||||
|
@ -11,9 +11,12 @@ use crate::{
|
|||||||
};
|
};
|
||||||
use anyhow::{bail, Result};
|
use anyhow::{bail, Result};
|
||||||
use once_cell::sync::OnceCell;
|
use once_cell::sync::OnceCell;
|
||||||
|
use parking_lot::RwLock;
|
||||||
use percent_encoding::percent_decode_str;
|
use percent_encoding::percent_decode_str;
|
||||||
|
use serde_json;
|
||||||
use serde_yaml::Mapping;
|
use serde_yaml::Mapping;
|
||||||
use std::net::TcpListener;
|
use std::net::TcpListener;
|
||||||
|
use std::sync::Arc;
|
||||||
use tauri::{App, Manager};
|
use tauri::{App, Manager};
|
||||||
|
|
||||||
use tauri::Url;
|
use tauri::Url;
|
||||||
@ -22,6 +25,23 @@ use tauri::Url;
|
|||||||
|
|
||||||
pub static VERSION: OnceCell<String> = OnceCell::new();
|
pub static VERSION: OnceCell<String> = OnceCell::new();
|
||||||
|
|
||||||
|
// 窗口状态文件中的尺寸
|
||||||
|
static STATE_WIDTH: OnceCell<u32> = OnceCell::new();
|
||||||
|
static STATE_HEIGHT: OnceCell<u32> = OnceCell::new();
|
||||||
|
|
||||||
|
// 添加全局UI准备就绪标志
|
||||||
|
static UI_READY: OnceCell<Arc<RwLock<bool>>> = OnceCell::new();
|
||||||
|
|
||||||
|
fn get_ui_ready() -> &'static Arc<RwLock<bool>> {
|
||||||
|
UI_READY.get_or_init(|| Arc::new(RwLock::new(false)))
|
||||||
|
}
|
||||||
|
|
||||||
|
// 标记UI已准备就绪
|
||||||
|
pub fn mark_ui_ready() {
|
||||||
|
let mut ready = get_ui_ready().write();
|
||||||
|
*ready = true;
|
||||||
|
}
|
||||||
|
|
||||||
pub fn find_unused_port() -> Result<u16> {
|
pub fn find_unused_port() -> Result<u16> {
|
||||||
match TcpListener::bind("127.0.0.1:0") {
|
match TcpListener::bind("127.0.0.1:0") {
|
||||||
Ok(listener) => {
|
Ok(listener) => {
|
||||||
@ -123,6 +143,71 @@ pub async fn resolve_reset_async() {
|
|||||||
|
|
||||||
/// create main window
|
/// create main window
|
||||||
pub fn create_window(is_showup: bool) {
|
pub fn create_window(is_showup: bool) {
|
||||||
|
// 打印 .window-state.json 文件路径
|
||||||
|
if let Ok(app_dir) = crate::utils::dirs::app_home_dir() {
|
||||||
|
let window_state_path = app_dir.join(".window-state.json");
|
||||||
|
logging!(
|
||||||
|
info,
|
||||||
|
Type::Window,
|
||||||
|
true,
|
||||||
|
"窗口状态文件路径: {:?}",
|
||||||
|
window_state_path
|
||||||
|
);
|
||||||
|
|
||||||
|
// 尝试读取窗口状态文件内容
|
||||||
|
if window_state_path.exists() {
|
||||||
|
match std::fs::read_to_string(&window_state_path) {
|
||||||
|
Ok(content) => {
|
||||||
|
logging!(info, Type::Window, true, "窗口状态文件内容: {}", content);
|
||||||
|
|
||||||
|
// 解析窗口状态文件
|
||||||
|
match serde_json::from_str::<serde_json::Value>(&content) {
|
||||||
|
Ok(state_json) => {
|
||||||
|
if let Some(main_window) = state_json.get("main") {
|
||||||
|
let width = main_window
|
||||||
|
.get("width")
|
||||||
|
.and_then(|v| v.as_u64())
|
||||||
|
.unwrap_or(0)
|
||||||
|
as u32;
|
||||||
|
let height = main_window
|
||||||
|
.get("height")
|
||||||
|
.and_then(|v| v.as_u64())
|
||||||
|
.unwrap_or(0)
|
||||||
|
as u32;
|
||||||
|
|
||||||
|
logging!(
|
||||||
|
info,
|
||||||
|
Type::Window,
|
||||||
|
true,
|
||||||
|
"窗口状态文件中的尺寸: {}x{}",
|
||||||
|
width,
|
||||||
|
height
|
||||||
|
);
|
||||||
|
|
||||||
|
// 保存读取到的尺寸,用于后续检查
|
||||||
|
STATE_WIDTH.get_or_init(|| width);
|
||||||
|
STATE_HEIGHT.get_or_init(|| height);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
logging!(error, Type::Window, true, "解析窗口状态文件失败: {:?}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
logging!(error, Type::Window, true, "读取窗口状态文件失败: {:?}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
logging!(
|
||||||
|
info,
|
||||||
|
Type::Window,
|
||||||
|
true,
|
||||||
|
"窗口状态文件不存在,将使用默认设置"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if !is_showup {
|
if !is_showup {
|
||||||
logging!(info, Type::Window, "Not to display create window");
|
logging!(info, Type::Window, "Not to display create window");
|
||||||
return;
|
return;
|
||||||
@ -156,6 +241,12 @@ pub fn create_window(is_showup: bool) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 定义默认窗口大小
|
||||||
|
const DEFAULT_WIDTH: u32 = 900;
|
||||||
|
const DEFAULT_HEIGHT: u32 = 700;
|
||||||
|
const MIN_WIDTH: u32 = 650;
|
||||||
|
const MIN_HEIGHT: u32 = 580;
|
||||||
|
|
||||||
#[cfg(target_os = "windows")]
|
#[cfg(target_os = "windows")]
|
||||||
let window = tauri::WebviewWindowBuilder::new(
|
let window = tauri::WebviewWindowBuilder::new(
|
||||||
&app_handle,
|
&app_handle,
|
||||||
@ -163,14 +254,14 @@ pub fn create_window(is_showup: bool) {
|
|||||||
tauri::WebviewUrl::App("index.html".into()),
|
tauri::WebviewUrl::App("index.html".into()),
|
||||||
)
|
)
|
||||||
.title("Clash Verge")
|
.title("Clash Verge")
|
||||||
.inner_size(900.0, 700.0)
|
.inner_size(DEFAULT_WIDTH as f64, DEFAULT_HEIGHT as f64)
|
||||||
.min_inner_size(650.0, 580.0)
|
.min_inner_size(MIN_WIDTH as f64, MIN_HEIGHT as f64)
|
||||||
.decorations(false)
|
.decorations(false)
|
||||||
.maximizable(true)
|
.maximizable(true)
|
||||||
.additional_browser_args("--enable-features=msWebView2EnableDraggableRegions --disable-features=OverscrollHistoryNavigation,msExperimentalScrolling")
|
.additional_browser_args("--enable-features=msWebView2EnableDraggableRegions --disable-features=OverscrollHistoryNavigation,msExperimentalScrolling")
|
||||||
.transparent(true)
|
.transparent(true)
|
||||||
.shadow(true)
|
.shadow(true)
|
||||||
.visible(true)
|
.visible(false) // 初始不可见,等待UI加载完成后再显示
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
@ -182,9 +273,9 @@ pub fn create_window(is_showup: bool) {
|
|||||||
.decorations(true)
|
.decorations(true)
|
||||||
.hidden_title(true)
|
.hidden_title(true)
|
||||||
.title_bar_style(tauri::TitleBarStyle::Overlay)
|
.title_bar_style(tauri::TitleBarStyle::Overlay)
|
||||||
.inner_size(900.0, 700.0)
|
.inner_size(DEFAULT_WIDTH as f64, DEFAULT_HEIGHT as f64)
|
||||||
.min_inner_size(650.0, 580.0)
|
.min_inner_size(MIN_WIDTH as f64, MIN_HEIGHT as f64)
|
||||||
.visible(true)
|
.visible(false) // 初始不可见,等待UI加载完成后再显示
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
#[cfg(target_os = "linux")]
|
#[cfg(target_os = "linux")]
|
||||||
@ -195,17 +286,63 @@ pub fn create_window(is_showup: bool) {
|
|||||||
)
|
)
|
||||||
.title("Clash Verge")
|
.title("Clash Verge")
|
||||||
.decorations(false)
|
.decorations(false)
|
||||||
.inner_size(900.0, 700.0)
|
.inner_size(DEFAULT_WIDTH as f64, DEFAULT_HEIGHT as f64)
|
||||||
.min_inner_size(650.0, 580.0)
|
.min_inner_size(MIN_WIDTH as f64, MIN_HEIGHT as f64)
|
||||||
.transparent(true)
|
.transparent(true)
|
||||||
.visible(true)
|
.visible(false) // 初始不可见,等待UI加载完成后再显示
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
match window {
|
match window {
|
||||||
Ok(_) => {
|
Ok(window) => {
|
||||||
logging!(info, Type::Window, true, "Window created successfully");
|
logging!(info, Type::Window, true, "Window created successfully");
|
||||||
|
|
||||||
// 标记前端UI已准备就绪,向前端发送启动完成事件
|
// 标记前端UI已准备就绪,向前端发送启动完成事件
|
||||||
let app_handle_clone = app_handle.clone();
|
let app_handle_clone = app_handle.clone();
|
||||||
|
|
||||||
|
// 获取窗口创建后的初始大小
|
||||||
|
if let Ok(size) = window.inner_size() {
|
||||||
|
let state_width = STATE_WIDTH.get().copied().unwrap_or(DEFAULT_WIDTH);
|
||||||
|
let state_height = STATE_HEIGHT.get().copied().unwrap_or(DEFAULT_HEIGHT);
|
||||||
|
|
||||||
|
// 输出所有尺寸信息
|
||||||
|
logging!(
|
||||||
|
info,
|
||||||
|
Type::Window,
|
||||||
|
true,
|
||||||
|
"API报告的窗口尺寸: {}x{}, 状态文件尺寸: {}x{}, 默认尺寸: {}x{}",
|
||||||
|
size.width,
|
||||||
|
size.height,
|
||||||
|
state_width,
|
||||||
|
state_height,
|
||||||
|
DEFAULT_WIDTH,
|
||||||
|
DEFAULT_HEIGHT
|
||||||
|
);
|
||||||
|
|
||||||
|
if state_width < DEFAULT_WIDTH || state_height < DEFAULT_HEIGHT {
|
||||||
|
logging!(
|
||||||
|
info,
|
||||||
|
Type::Window,
|
||||||
|
true,
|
||||||
|
"状态文件窗口尺寸小于默认值,将使用默认尺寸: {}x{}",
|
||||||
|
DEFAULT_WIDTH,
|
||||||
|
DEFAULT_HEIGHT
|
||||||
|
);
|
||||||
|
|
||||||
|
let _ = window.set_size(tauri::LogicalSize::new(
|
||||||
|
DEFAULT_WIDTH as f64,
|
||||||
|
DEFAULT_HEIGHT as f64,
|
||||||
|
));
|
||||||
|
} else if size.width != state_width || size.height != state_height {
|
||||||
|
// 如果API报告的尺寸与状态文件不一致,记录日志
|
||||||
|
logging!(
|
||||||
|
warn,
|
||||||
|
Type::Window,
|
||||||
|
true,
|
||||||
|
"API报告的窗口尺寸与状态文件不一致"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
AsyncHandler::spawn(move || async move {
|
AsyncHandler::spawn(move || async move {
|
||||||
use tauri::Emitter;
|
use tauri::Emitter;
|
||||||
|
|
||||||
@ -213,7 +350,74 @@ pub fn create_window(is_showup: bool) {
|
|||||||
handle::Handle::global().mark_startup_completed();
|
handle::Handle::global().mark_startup_completed();
|
||||||
|
|
||||||
if let Some(window) = app_handle_clone.get_webview_window("main") {
|
if let Some(window) = app_handle_clone.get_webview_window("main") {
|
||||||
|
// 检查窗口大小
|
||||||
|
match window.inner_size() {
|
||||||
|
Ok(size) => {
|
||||||
|
let width = size.width;
|
||||||
|
let height = size.height;
|
||||||
|
|
||||||
|
let state_width = STATE_WIDTH.get().copied().unwrap_or(DEFAULT_WIDTH);
|
||||||
|
let state_height =
|
||||||
|
STATE_HEIGHT.get().copied().unwrap_or(DEFAULT_HEIGHT);
|
||||||
|
|
||||||
|
logging!(
|
||||||
|
info,
|
||||||
|
Type::Window,
|
||||||
|
true,
|
||||||
|
"异步任务中窗口尺寸: {}x{}, 状态文件尺寸: {}x{}",
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
state_width,
|
||||||
|
state_height
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
logging!(
|
||||||
|
error,
|
||||||
|
Type::Window,
|
||||||
|
true,
|
||||||
|
"Failed to get window size: {:?}",
|
||||||
|
e
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 发送启动完成事件
|
||||||
let _ = window.emit("verge://startup-completed", ());
|
let _ = window.emit("verge://startup-completed", ());
|
||||||
|
|
||||||
|
if is_showup {
|
||||||
|
// 启动一个任务等待UI准备就绪再显示窗口
|
||||||
|
let window_clone = window.clone();
|
||||||
|
AsyncHandler::spawn(move || async move {
|
||||||
|
async fn wait_for_ui_ready() {
|
||||||
|
while !*get_ui_ready().read() {
|
||||||
|
tokio::time::sleep(std::time::Duration::from_millis(50)).await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
match tokio::time::timeout(
|
||||||
|
std::time::Duration::from_secs(5),
|
||||||
|
wait_for_ui_ready(),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
Ok(_) => {
|
||||||
|
logging!(info, Type::Window, true, "UI准备就绪,显示窗口");
|
||||||
|
}
|
||||||
|
Err(_) => {
|
||||||
|
logging!(
|
||||||
|
warn,
|
||||||
|
Type::Window,
|
||||||
|
true,
|
||||||
|
"等待UI准备就绪超时,强制显示窗口"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let _ = window_clone.show();
|
||||||
|
let _ = window_clone.set_focus();
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -30,6 +30,7 @@ import { useListen } from "@/hooks/use-listen";
|
|||||||
import { listen } from "@tauri-apps/api/event";
|
import { listen } from "@tauri-apps/api/event";
|
||||||
import { useClashInfo } from "@/hooks/use-clash";
|
import { useClashInfo } from "@/hooks/use-clash";
|
||||||
import { initGlobalLogService } from "@/services/global-log-service";
|
import { initGlobalLogService } from "@/services/global-log-service";
|
||||||
|
import { invoke } from "@tauri-apps/api/core";
|
||||||
|
|
||||||
const appWindow = getCurrentWebviewWindow();
|
const appWindow = getCurrentWebviewWindow();
|
||||||
export let portableFlag = false;
|
export let portableFlag = false;
|
||||||
@ -209,6 +210,34 @@ const Layout = () => {
|
|||||||
};
|
};
|
||||||
}, [handleNotice]);
|
}, [handleNotice]);
|
||||||
|
|
||||||
|
// 监听启动完成事件并通知UI已加载
|
||||||
|
useEffect(() => {
|
||||||
|
const notifyUiReady = async () => {
|
||||||
|
try {
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 200));
|
||||||
|
await invoke("notify_ui_ready");
|
||||||
|
console.log("已通知后端UI准备就绪");
|
||||||
|
} catch (err) {
|
||||||
|
console.error("通知UI准备就绪失败:", err);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 监听后端发送的启动完成事件
|
||||||
|
const listenStartupCompleted = async () => {
|
||||||
|
const unlisten = await listen("verge://startup-completed", () => {
|
||||||
|
console.log("收到启动完成事件");
|
||||||
|
});
|
||||||
|
return unlisten;
|
||||||
|
};
|
||||||
|
|
||||||
|
notifyUiReady();
|
||||||
|
const unlistenPromise = listenStartupCompleted();
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
unlistenPromise.then(unlisten => unlisten());
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
// 语言和起始页设置
|
// 语言和起始页设置
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (language) {
|
if (language) {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user