fix(windows): improve startup window recovery and diagnostics
This commit is contained in:
3 files changed
+232
-13
No files matched your search
@@ -8,8 +8,8 @@ use tauri::{AppHandle, Manager, State, Window};
|
|||||||
|
|
||||||
use super::connection::AppState;
|
use super::connection::AppState;
|
||||||
use crate::{
|
use crate::{
|
||||||
apply_debug_log_level, apply_desktop_settings, hide_main_window_for_close, refresh_native_menus, request_app_close,
|
apply_debug_log_level, apply_desktop_settings, clear_startup_probe_after_frontend_ready,
|
||||||
AppLocaleState, CloseBehaviorState,
|
hide_main_window_for_close, refresh_native_menus, request_app_close, AppLocaleState, CloseBehaviorState,
|
||||||
};
|
};
|
||||||
|
|
||||||
const DEVELOPMENT_OPEN_TABS_STATE_KEY: &str = "development_open_tabs";
|
const DEVELOPMENT_OPEN_TABS_STATE_KEY: &str = "development_open_tabs";
|
||||||
@@ -101,6 +101,7 @@ pub fn mark_frontend_ready(app: AppHandle) -> Result<(), String> {
|
|||||||
let state =
|
let state =
|
||||||
app.try_state::<CloseBehaviorState>().ok_or_else(|| "close behavior state is unavailable".to_string())?;
|
app.try_state::<CloseBehaviorState>().ok_or_else(|| "close behavior state is unavailable".to_string())?;
|
||||||
state.set_frontend_ready(true);
|
state.set_frontend_ready(true);
|
||||||
|
clear_startup_probe_after_frontend_ready();
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+214
-10
@@ -10,6 +10,9 @@ use commands::connection::AppState;
|
|||||||
use dbx_core::sql_dialect::dialect_loader::{register_core_dialects, DialectPluginLoader, DialectRegistry};
|
use dbx_core::sql_dialect::dialect_loader::{register_core_dialects, DialectPluginLoader, DialectRegistry};
|
||||||
use dbx_core::sql_dialect::hot_reload::DialectHotReload;
|
use dbx_core::sql_dialect::hot_reload::DialectHotReload;
|
||||||
use dbx_core::storage::{maybe_import_user_data_db, DesktopIconTheme, DesktopSettings, Storage};
|
use dbx_core::storage::{maybe_import_user_data_db, DesktopIconTheme, DesktopSettings, Storage};
|
||||||
|
use std::ffi::OsString;
|
||||||
|
use std::io::Write;
|
||||||
|
use std::path::PathBuf;
|
||||||
use std::sync::atomic::{AtomicBool, Ordering};
|
use std::sync::atomic::{AtomicBool, Ordering};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::time::{Duration, Instant};
|
use std::time::{Duration, Instant};
|
||||||
@@ -31,6 +34,10 @@ use tauri_plugin_deep_link::DeepLinkExt;
|
|||||||
|
|
||||||
const DESKTOP_TRAY_ID: &str = "main-tray";
|
const DESKTOP_TRAY_ID: &str = "main-tray";
|
||||||
const APP_CLOSE_REQUESTED_EVENT: &str = "dbx-app-close-requested";
|
const APP_CLOSE_REQUESTED_EVENT: &str = "dbx-app-close-requested";
|
||||||
|
const STARTUP_PROBE_LOG_FILE: &str = "startup.log";
|
||||||
|
const STARTUP_PROBE_LOG_DIR_ENV: &str = "DBX_STARTUP_LOG_DIR";
|
||||||
|
const STARTUP_PROBE_KEEP_ENV: &str = "DBX_KEEP_STARTUP_LOG";
|
||||||
|
const WINDOWS_APP_DATA_DIR_NAME: &str = "com.dbx.app";
|
||||||
#[cfg(target_os = "windows")]
|
#[cfg(target_os = "windows")]
|
||||||
const WEBVIEW2_NO_SANDBOX_ENV: &str = "DBX_WEBVIEW2_NO_SANDBOX";
|
const WEBVIEW2_NO_SANDBOX_ENV: &str = "DBX_WEBVIEW2_NO_SANDBOX";
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
@@ -146,6 +153,97 @@ fn should_show_main_window_after_setup() -> bool {
|
|||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn should_show_main_window_before_setup_tasks() -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
fn startup_probe_log_dir_from_inputs(
|
||||||
|
target_os: &str,
|
||||||
|
explicit_dir: Option<OsString>,
|
||||||
|
windows_appdata: Option<OsString>,
|
||||||
|
) -> Option<PathBuf> {
|
||||||
|
if let Some(dir) = explicit_dir.filter(|value| !value.is_empty()) {
|
||||||
|
return Some(PathBuf::from(dir));
|
||||||
|
}
|
||||||
|
if target_os == "windows" {
|
||||||
|
return windows_appdata
|
||||||
|
.filter(|value| !value.is_empty())
|
||||||
|
.map(PathBuf::from)
|
||||||
|
.map(|dir| dir.join(WINDOWS_APP_DATA_DIR_NAME));
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
fn startup_probe_log_dir() -> Option<PathBuf> {
|
||||||
|
startup_probe_log_dir_from_inputs(
|
||||||
|
std::env::consts::OS,
|
||||||
|
std::env::var_os(STARTUP_PROBE_LOG_DIR_ENV),
|
||||||
|
std::env::var_os("APPDATA"),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn startup_probe_log_path() -> Option<PathBuf> {
|
||||||
|
startup_probe_log_dir().map(|dir| dir.join(STARTUP_PROBE_LOG_FILE))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn startup_probe_should_keep_after_frontend_ready_from_value(value: Option<&str>) -> bool {
|
||||||
|
matches!(value, Some("1"))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn startup_probe_should_keep_after_frontend_ready() -> bool {
|
||||||
|
startup_probe_should_keep_after_frontend_ready_from_value(std::env::var(STARTUP_PROBE_KEEP_ENV).ok().as_deref())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn ensure_startup_probe_parent_dir(path: &std::path::Path) -> bool {
|
||||||
|
let Some(dir) = path.parent() else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
if std::fs::create_dir_all(dir).is_err() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
fn reset_startup_probe() {
|
||||||
|
let Some(path) = startup_probe_log_path() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
if !ensure_startup_probe_parent_dir(&path) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let _ = std::fs::remove_file(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn append_startup_probe(message: impl AsRef<str>) {
|
||||||
|
let Some(path) = startup_probe_log_path() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
if !ensure_startup_probe_parent_dir(&path) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let Ok(mut file) = std::fs::OpenOptions::new().create(true).append(true).open(path) else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
let _ = writeln!(
|
||||||
|
file,
|
||||||
|
"[{}][pid={}] {}",
|
||||||
|
chrono::Local::now().format("%Y-%m-%d %H:%M:%S%.3f"),
|
||||||
|
std::process::id(),
|
||||||
|
message.as_ref()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn clear_startup_probe_after_frontend_ready() {
|
||||||
|
if startup_probe_should_keep_after_frontend_ready() {
|
||||||
|
append_startup_probe("frontend ready; keeping startup probe by request");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let Some(path) = startup_probe_log_path() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
let _ = std::fs::remove_file(path);
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(target_os = "windows")]
|
#[cfg(target_os = "windows")]
|
||||||
fn configure_webview2_sandbox_compat() {
|
fn configure_webview2_sandbox_compat() {
|
||||||
if !matches!(std::env::var(WEBVIEW2_NO_SANDBOX_ENV).as_deref(), Ok("1")) {
|
if !matches!(std::env::var(WEBVIEW2_NO_SANDBOX_ENV).as_deref(), Ok("1")) {
|
||||||
@@ -460,6 +558,30 @@ fn show_main_window<R: tauri::Runtime>(app: &tauri::AppHandle<R>) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn main_window_probe_state<R: tauri::Runtime>(app: &tauri::AppHandle<R>) -> String {
|
||||||
|
let Some(window) = app.get_webview_window("main") else {
|
||||||
|
return "main_window=missing".to_string();
|
||||||
|
};
|
||||||
|
format!(
|
||||||
|
"main_window visible={:?} minimized={:?} maximized={:?} fullscreen={:?} position={:?} size={:?}",
|
||||||
|
window.is_visible(),
|
||||||
|
window.is_minimized(),
|
||||||
|
window.is_maximized(),
|
||||||
|
window.is_fullscreen(),
|
||||||
|
window.outer_position(),
|
||||||
|
window.outer_size()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn prepare_main_window_for_display<R: tauri::Runtime>(app: &tauri::AppHandle<R>) {
|
||||||
|
if let Some(decorations) = native_window_decorations_override(std::env::consts::OS) {
|
||||||
|
if let Some(window) = app.get_webview_window("main") {
|
||||||
|
let _ = window.set_decorations(decorations);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
window_state_guard::enforce_main_window_bounds(app);
|
||||||
|
}
|
||||||
|
|
||||||
fn clear_main_webview_focus<R: tauri::Runtime>(app: &tauri::AppHandle<R>) {
|
fn clear_main_webview_focus<R: tauri::Runtime>(app: &tauri::AppHandle<R>) {
|
||||||
if let Some(window) = app.get_webview_window("main") {
|
if let Some(window) = app.get_webview_window("main") {
|
||||||
let _ = window.eval(
|
let _ = window.eval(
|
||||||
@@ -805,10 +927,12 @@ mod tests {
|
|||||||
linux_appimage_wayland_backend_override, linux_nvidia_driver_from_state, linux_selected_drm_render_device,
|
linux_appimage_wayland_backend_override, linux_nvidia_driver_from_state, linux_selected_drm_render_device,
|
||||||
linux_webkit_rendering_workarounds, native_window_decorations_override, should_confirm_app_exit_request,
|
linux_webkit_rendering_workarounds, native_window_decorations_override, should_confirm_app_exit_request,
|
||||||
should_enable_single_instance, should_fallback_to_native_quit, should_hide_window_on_close,
|
should_enable_single_instance, should_fallback_to_native_quit, should_hide_window_on_close,
|
||||||
should_setup_desktop_tray, should_show_main_window_after_setup, tray_menu_labels_for_locale,
|
should_setup_desktop_tray, should_show_main_window_after_setup, should_show_main_window_before_setup_tasks,
|
||||||
uses_application_level_icon, LinuxDrmRenderDevice, LinuxNvidiaDriver,
|
startup_probe_log_dir_from_inputs, startup_probe_should_keep_after_frontend_ready_from_value,
|
||||||
|
tray_menu_labels_for_locale, uses_application_level_icon, LinuxDrmRenderDevice, LinuxNvidiaDriver,
|
||||||
|
WINDOWS_APP_DATA_DIR_NAME,
|
||||||
};
|
};
|
||||||
use std::ffi::OsStr;
|
use std::ffi::{OsStr, OsString};
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
const TEST_GTK3_IMMODULES_CACHE: &str = "/usr/lib/test/gtk-3.0/3.0.0/immodules.cache";
|
const TEST_GTK3_IMMODULES_CACHE: &str = "/usr/lib/test/gtk-3.0/3.0.0/immodules.cache";
|
||||||
@@ -918,6 +1042,45 @@ mod tests {
|
|||||||
assert!(should_show_main_window_after_setup());
|
assert!(should_show_main_window_after_setup());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn shows_main_window_while_startup_setup_continues() {
|
||||||
|
assert!(should_show_main_window_before_setup_tasks());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn startup_probe_log_dir_prefers_explicit_override() {
|
||||||
|
assert_eq!(
|
||||||
|
startup_probe_log_dir_from_inputs(
|
||||||
|
"windows",
|
||||||
|
Some(OsString::from(r"D:\DBXDiagnostics")),
|
||||||
|
Some(OsString::from(r"C:\Users\test\AppData\Roaming")),
|
||||||
|
),
|
||||||
|
Some(PathBuf::from(r"D:\DBXDiagnostics"))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn startup_probe_log_dir_uses_windows_appdata() {
|
||||||
|
assert_eq!(
|
||||||
|
startup_probe_log_dir_from_inputs("windows", None, Some(OsString::from(r"C:\Users\test\AppData\Roaming")),),
|
||||||
|
Some(PathBuf::from(r"C:\Users\test\AppData\Roaming").join(WINDOWS_APP_DATA_DIR_NAME))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn startup_probe_log_dir_is_disabled_without_windows_appdata() {
|
||||||
|
assert_eq!(startup_probe_log_dir_from_inputs("windows", None, None), None);
|
||||||
|
assert_eq!(startup_probe_log_dir_from_inputs("macos", None, Some(OsString::from("/Users/test/Library"))), None);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn startup_probe_log_is_kept_only_when_requested() {
|
||||||
|
assert!(startup_probe_should_keep_after_frontend_ready_from_value(Some("1")));
|
||||||
|
assert!(!startup_probe_should_keep_after_frontend_ready_from_value(None));
|
||||||
|
assert!(!startup_probe_should_keep_after_frontend_ready_from_value(Some("true")));
|
||||||
|
assert!(!startup_probe_should_keep_after_frontend_ready_from_value(Some("0")));
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn only_user_requested_app_exit_needs_frontend_confirmation() {
|
fn only_user_requested_app_exit_needs_frontend_confirmation() {
|
||||||
assert!(should_confirm_app_exit_request("windows", None, false));
|
assert!(should_confirm_app_exit_request("windows", None, false));
|
||||||
@@ -1116,8 +1279,17 @@ mod tests {
|
|||||||
|
|
||||||
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
||||||
pub fn run() {
|
pub fn run() {
|
||||||
|
reset_startup_probe();
|
||||||
|
append_startup_probe(format!(
|
||||||
|
"process start version={} os={} arch={} exe={:?}",
|
||||||
|
env!("CARGO_PKG_VERSION"),
|
||||||
|
std::env::consts::OS,
|
||||||
|
std::env::consts::ARCH,
|
||||||
|
std::env::current_exe()
|
||||||
|
));
|
||||||
rustls::crypto::aws_lc_rs::default_provider().install_default().expect("Failed to install rustls crypto provider");
|
rustls::crypto::aws_lc_rs::default_provider().install_default().expect("Failed to install rustls crypto provider");
|
||||||
configure_webview2_sandbox_compat();
|
configure_webview2_sandbox_compat();
|
||||||
|
append_startup_probe("runtime prerequisites configured");
|
||||||
#[cfg(target_os = "linux")]
|
#[cfg(target_os = "linux")]
|
||||||
apply_linux_webkit_rendering_workarounds();
|
apply_linux_webkit_rendering_workarounds();
|
||||||
|
|
||||||
@@ -1159,7 +1331,11 @@ pub fn run() {
|
|||||||
.plugin(tauri_plugin_shell::init())
|
.plugin(tauri_plugin_shell::init())
|
||||||
.plugin(tauri_plugin_updater::Builder::new().build())
|
.plugin(tauri_plugin_updater::Builder::new().build())
|
||||||
.plugin(tauri_plugin_process::init())
|
.plugin(tauri_plugin_process::init())
|
||||||
.plugin(tauri_plugin_window_state::Builder::default().build());
|
.plugin(
|
||||||
|
tauri_plugin_window_state::Builder::default()
|
||||||
|
.with_state_flags(window_state_guard::persisted_main_window_state_flags())
|
||||||
|
.build(),
|
||||||
|
);
|
||||||
|
|
||||||
// macOS app menu (Cmd+Q / Dock Quit). Skip on Linux/Windows so an empty menu bar
|
// macOS app menu (Cmd+Q / Dock Quit). Skip on Linux/Windows so an empty menu bar
|
||||||
// is not installed where there was none before.
|
// is not installed where there was none before.
|
||||||
@@ -1187,12 +1363,24 @@ pub fn run() {
|
|||||||
.setup(move |app| {
|
.setup(move |app| {
|
||||||
let setup_start = Instant::now();
|
let setup_start = Instant::now();
|
||||||
eprintln!("[STARTUP] plugins registered in {:?}", startup_begin.elapsed());
|
eprintln!("[STARTUP] plugins registered in {:?}", startup_begin.elapsed());
|
||||||
|
append_startup_probe(format!("setup entered after {:?}", startup_begin.elapsed()));
|
||||||
|
|
||||||
|
if should_show_main_window_before_setup_tasks() {
|
||||||
|
prepare_main_window_for_display(app.handle());
|
||||||
|
show_main_window(app.handle());
|
||||||
|
append_startup_probe(format!(
|
||||||
|
"early main window show requested; {}",
|
||||||
|
main_window_probe_state(app.handle())
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
append_startup_probe("resolving app data dir");
|
||||||
let default_data_dir =
|
let default_data_dir =
|
||||||
app.path().app_data_dir().map_err(|e| e.to_string()).expect("Failed to resolve app data dir");
|
app.path().app_data_dir().map_err(|e| e.to_string()).expect("Failed to resolve app data dir");
|
||||||
let data_dir_resolution = data_dir::resolve_data_dir_with_mode(default_data_dir);
|
let data_dir_resolution = data_dir::resolve_data_dir_with_mode(default_data_dir);
|
||||||
let data_dir = data_dir_resolution.data_dir.clone();
|
let data_dir = data_dir_resolution.data_dir.clone();
|
||||||
std::fs::create_dir_all(&data_dir).expect("Failed to create data dir");
|
std::fs::create_dir_all(&data_dir).expect("Failed to create data dir");
|
||||||
|
append_startup_probe(format!("data dir ready: {}", data_dir.display()));
|
||||||
let alternative_data_dir = data_dir::alternative_data_dir(&data_dir_resolution);
|
let alternative_data_dir = data_dir::alternative_data_dir(&data_dir_resolution);
|
||||||
match maybe_import_user_data_db(&data_dir, alternative_data_dir.as_deref()) {
|
match maybe_import_user_data_db(&data_dir, alternative_data_dir.as_deref()) {
|
||||||
Ok(result) => eprintln!("[STARTUP] data db fallback import: {result:?}"),
|
Ok(result) => eprintln!("[STARTUP] data db fallback import: {result:?}"),
|
||||||
@@ -1201,12 +1389,15 @@ pub fn run() {
|
|||||||
let db_path = data_dir.join("dbx.db");
|
let db_path = data_dir.join("dbx.db");
|
||||||
|
|
||||||
let t = Instant::now();
|
let t = Instant::now();
|
||||||
|
append_startup_probe(format!("opening storage: {}", db_path.display()));
|
||||||
let storage = tauri::async_runtime::block_on(async {
|
let storage = tauri::async_runtime::block_on(async {
|
||||||
let s = Storage::open(&db_path).await.expect("Failed to open storage");
|
let s = Storage::open(&db_path).await.expect("Failed to open storage");
|
||||||
eprintln!("[STARTUP] Storage::open in {:?}", t.elapsed());
|
eprintln!("[STARTUP] Storage::open in {:?}", t.elapsed());
|
||||||
|
append_startup_probe(format!("storage opened in {:?}", t.elapsed()));
|
||||||
let t2 = Instant::now();
|
let t2 = Instant::now();
|
||||||
s.migrate_from_json(&data_dir).await.expect("Failed to migrate JSON data");
|
s.migrate_from_json(&data_dir).await.expect("Failed to migrate JSON data");
|
||||||
eprintln!("[STARTUP] migrate_from_json in {:?}", t2.elapsed());
|
eprintln!("[STARTUP] migrate_from_json in {:?}", t2.elapsed());
|
||||||
|
append_startup_probe(format!("json migration completed in {:?}", t2.elapsed()));
|
||||||
s
|
s
|
||||||
});
|
});
|
||||||
let desktop_settings = tauri::async_runtime::block_on(storage.load_desktop_settings()).unwrap_or_default();
|
let desktop_settings = tauri::async_runtime::block_on(storage.load_desktop_settings()).unwrap_or_default();
|
||||||
@@ -1227,6 +1418,7 @@ pub fn run() {
|
|||||||
)?;
|
)?;
|
||||||
apply_debug_log_level(desktop_settings.debug_logging_enabled);
|
apply_debug_log_level(desktop_settings.debug_logging_enabled);
|
||||||
eprintln!("[STARTUP] storage ready in {:?}", t.elapsed());
|
eprintln!("[STARTUP] storage ready in {:?}", t.elapsed());
|
||||||
|
append_startup_probe(format!("storage ready in {:?}", t.elapsed()));
|
||||||
|
|
||||||
// Initialize core dialect registry and load external plugin dialects
|
// Initialize core dialect registry and load external plugin dialects
|
||||||
let dialect_init_start = Instant::now();
|
let dialect_init_start = Instant::now();
|
||||||
@@ -1241,6 +1433,13 @@ pub fn run() {
|
|||||||
load_result.skipped.len(),
|
load_result.skipped.len(),
|
||||||
dialect_init_start.elapsed()
|
dialect_init_start.elapsed()
|
||||||
);
|
);
|
||||||
|
append_startup_probe(format!(
|
||||||
|
"dialect plugins loaded: {} success, {} errors, {} skipped in {:?}",
|
||||||
|
load_result.loaded.len(),
|
||||||
|
load_result.errors.len(),
|
||||||
|
load_result.skipped.len(),
|
||||||
|
dialect_init_start.elapsed()
|
||||||
|
));
|
||||||
|
|
||||||
// Start dialect YAML hot-reload watcher
|
// Start dialect YAML hot-reload watcher
|
||||||
let watch_dirs = plugin_dirs.clone();
|
let watch_dirs = plugin_dirs.clone();
|
||||||
@@ -1289,12 +1488,13 @@ pub fn run() {
|
|||||||
let app_handle = app.handle().clone();
|
let app_handle = app.handle().clone();
|
||||||
commands::mcp_bridge::start(app_handle, state, data_dir);
|
commands::mcp_bridge::start(app_handle, state, data_dir);
|
||||||
eprintln!("[STARTUP] setup complete in {:?} (total {:?})", setup_start.elapsed(), startup_begin.elapsed());
|
eprintln!("[STARTUP] setup complete in {:?} (total {:?})", setup_start.elapsed(), startup_begin.elapsed());
|
||||||
|
append_startup_probe(format!(
|
||||||
|
"setup tasks complete in {:?} total {:?}",
|
||||||
|
setup_start.elapsed(),
|
||||||
|
startup_begin.elapsed()
|
||||||
|
));
|
||||||
|
|
||||||
if let Some(decorations) = native_window_decorations_override(std::env::consts::OS) {
|
prepare_main_window_for_display(app.handle());
|
||||||
if let Some(window) = app.get_webview_window("main") {
|
|
||||||
let _ = window.set_decorations(decorations);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if should_setup_desktop_tray(
|
if should_setup_desktop_tray(
|
||||||
std::env::consts::OS,
|
std::env::consts::OS,
|
||||||
desktop_settings.show_tray_icon,
|
desktop_settings.show_tray_icon,
|
||||||
@@ -1305,13 +1505,17 @@ pub fn run() {
|
|||||||
apply_desktop_icon_theme(app.handle(), desktop_settings.icon_theme)?;
|
apply_desktop_icon_theme(app.handle(), desktop_settings.icon_theme)?;
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
apply_macos_development_dock_badge(app.handle())?;
|
apply_macos_development_dock_badge(app.handle())?;
|
||||||
window_state_guard::enforce_main_window_bounds(app.handle());
|
|
||||||
if should_show_main_window_after_setup() {
|
if should_show_main_window_after_setup() {
|
||||||
show_main_window(app.handle());
|
show_main_window(app.handle());
|
||||||
|
append_startup_probe(format!(
|
||||||
|
"final main window show requested; {}",
|
||||||
|
main_window_probe_state(app.handle())
|
||||||
|
));
|
||||||
}
|
}
|
||||||
#[cfg(any(windows, target_os = "linux"))]
|
#[cfg(any(windows, target_os = "linux"))]
|
||||||
let _ = app.deep_link().register_all();
|
let _ = app.deep_link().register_all();
|
||||||
|
|
||||||
|
append_startup_probe("setup finished");
|
||||||
Ok(())
|
Ok(())
|
||||||
})
|
})
|
||||||
.on_window_event(|window, event| {
|
.on_window_event(|window, event| {
|
||||||
|
|||||||
@@ -33,11 +33,15 @@ pub(crate) fn corrected_window_rect(
|
|||||||
pub(crate) fn enforce_main_window_bounds<R: Runtime>(app: &AppHandle<R>) {
|
pub(crate) fn enforce_main_window_bounds<R: Runtime>(app: &AppHandle<R>) {
|
||||||
if let Some(window) = app.get_webview_window("main") {
|
if let Some(window) = app.get_webview_window("main") {
|
||||||
if enforce_window_bounds(&window) {
|
if enforce_window_bounds(&window) {
|
||||||
let _ = app.save_window_state(StateFlags::all());
|
let _ = app.save_window_state(persisted_main_window_state_flags());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn persisted_main_window_state_flags() -> StateFlags {
|
||||||
|
StateFlags::SIZE | StateFlags::POSITION | StateFlags::MAXIMIZED | StateFlags::DECORATIONS | StateFlags::FULLSCREEN
|
||||||
|
}
|
||||||
|
|
||||||
fn enforce_window_bounds<R: Runtime>(window: &WebviewWindow<R>) -> bool {
|
fn enforce_window_bounds<R: Runtime>(window: &WebviewWindow<R>) -> bool {
|
||||||
if window.is_maximized().unwrap_or(false) || window.is_fullscreen().unwrap_or(false) {
|
if window.is_maximized().unwrap_or(false) || window.is_fullscreen().unwrap_or(false) {
|
||||||
return false;
|
return false;
|
||||||
@@ -94,6 +98,16 @@ fn centered_axis_position(window_start: i32, window_length: u32, monitor_start:
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::{corrected_window_rect, PhysicalMonitorRect, PhysicalWindowRect};
|
use super::{corrected_window_rect, PhysicalMonitorRect, PhysicalWindowRect};
|
||||||
|
use tauri_plugin_window_state::StateFlags;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn persisted_state_excludes_window_visibility() {
|
||||||
|
let flags = super::persisted_main_window_state_flags();
|
||||||
|
|
||||||
|
assert!(flags.contains(StateFlags::SIZE));
|
||||||
|
assert!(flags.contains(StateFlags::POSITION));
|
||||||
|
assert!(!flags.contains(StateFlags::VISIBLE));
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn moves_window_back_onto_monitor_when_restored_above_screen() {
|
fn moves_window_back_onto_monitor_when_restored_above_screen() {
|
||||||
|
|||||||
Reference in new issue
Block a user