feat: persist Tauri window state (#2377)

Add the window-state plugin and explicitly save size and position across app exit, restart, and lightweight-mode transitions.
This commit is contained in:
BillSaul
2026-05-01 17:51:25 +08:00
committed by GitHub
parent 295dd9a944
commit 7b667f7a44
5 changed files with 48 additions and 1 deletions
+16
View File
@@ -785,6 +785,7 @@ dependencies = [
"tauri-plugin-single-instance",
"tauri-plugin-store",
"tauri-plugin-updater",
"tauri-plugin-window-state",
"tempfile",
"thiserror 2.0.18",
"tokio",
@@ -5687,6 +5688,21 @@ dependencies = [
"zip 4.6.1",
]
[[package]]
name = "tauri-plugin-window-state"
version = "2.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73736611e14142408d15353e21e3cca2f12a3cfb523ad0ce85999b6d2ef1a704"
dependencies = [
"bitflags 2.11.0",
"log",
"serde",
"serde_json",
"tauri",
"tauri-plugin",
"thiserror 2.0.18",
]
[[package]]
name = "tauri-runtime"
version = "2.10.1"
+1
View File
@@ -35,6 +35,7 @@ tauri-plugin-updater = "2"
tauri-plugin-dialog = "2"
tauri-plugin-store = "2"
tauri-plugin-deep-link = "2"
tauri-plugin-window-state = "2"
dirs = "5.0"
toml = "0.8"
toml_edit = "0.22"
+2
View File
@@ -42,6 +42,8 @@ pub async fn save_settings(settings: crate::settings::AppSettings) -> Result<boo
/// 重启应用程序(当 app_config_dir 变更后使用)
#[tauri::command]
pub async fn restart_app(app: AppHandle) -> Result<bool, String> {
crate::save_window_state_before_exit(&app);
// 在后台延迟重启,让函数有时间返回响应
tauri::async_runtime::spawn(async move {
tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
+26
View File
@@ -64,6 +64,7 @@ use tauri::image::Image;
use tauri::tray::{TrayIconBuilder, TrayIconEvent};
use tauri::RunEvent;
use tauri::{Emitter, Manager};
use tauri_plugin_window_state::{AppHandleExt, StateFlags};
fn redact_url_for_log(url_str: &str) -> String {
match url::Url::parse(url_str) {
@@ -264,6 +265,7 @@ pub fn run() {
tray::apply_tray_policy(window.app_handle(), false);
}
} else {
api.prevent_close();
window.app_handle().exit(0);
}
}
@@ -272,6 +274,11 @@ pub fn run() {
.plugin(tauri_plugin_dialog::init())
.plugin(tauri_plugin_opener::init())
.plugin(tauri_plugin_store::Builder::new().build())
.plugin(
tauri_plugin_window_state::Builder::default()
.with_state_flags(window_state_flags())
.build(),
)
.setup(|app| {
let _ = rustls::crypto::ring::default_provider().install_default();
@@ -1351,6 +1358,7 @@ pub fn run() {
let app_handle = app_handle.clone();
tauri::async_runtime::spawn(async move {
save_window_state_before_exit(&app_handle);
cleanup_before_exit(&app_handle).await;
log::info!("清理完成,退出应用");
@@ -1757,3 +1765,21 @@ fn show_database_init_error_dialog(
))
.blocking_show()
}
// ============================================================
// 在应用主动退出前显式持久化窗口状态
// ============================================================
fn window_state_flags() -> StateFlags {
StateFlags::POSITION | StateFlags::SIZE | StateFlags::MAXIMIZED
}
/// 当前应用的退出路径会拦截 `ExitRequested` 并最终直接 `std::process::exit(0)`
/// 这里需要在真正结束进程前手动落盘,避免 window-state 插件的默认退出钩子被绕过。
pub fn save_window_state_before_exit(app_handle: &tauri::AppHandle) {
if let Err(err) = app_handle.save_window_state(window_state_flags()) {
log::error!("退出前保存窗口状态失败: {err}");
} else {
log::info!("已在退出前保存窗口状态");
}
}
+3 -1
View File
@@ -17,6 +17,7 @@ pub fn enter_lightweight_mode(app: &tauri::AppHandle) -> Result<(), String> {
}
if let Some(window) = app.get_webview_window("main") {
crate::save_window_state_before_exit(app);
window
.destroy()
.map_err(|e| format!("销毁主窗口失败: {e}"))?;
@@ -64,11 +65,12 @@ pub fn exit_lightweight_mode(app: &tauri::AppHandle) -> Result<(), String> {
WebviewWindowBuilder::from_config(app, window_config)
.map_err(|e| format!("加载主窗口配置失败: {e}"))?
.visible(true)
.build()
.map_err(|e| format!("创建主窗口失败: {e}"))?;
if let Some(window) = app.get_webview_window("main") {
let _ = window.unminimize();
let _ = window.show();
let _ = window.set_focus();
#[cfg(target_os = "linux")]
{