Skip to content

Commit aab53fa

Browse files
aboseclaude
andcommitted
fix: restore custom window position/size persistence across restarts
Replace non-functional tauri-plugin-window-state with a custom implementation that saves window x, y, width, height, and maximized state to boot_config.json on close and restores on launch. - Add window state fields to BootConfig struct - Add restore_window_state() with multi-monitor awareness: validates saved position against connected monitors, maximizes if saved size exceeds current screen, falls back to 1366x900 on first launch - Save outer_size (not inner_size) to avoid progressive shrinking from title bar/border mismatch - Remove tauri-plugin-window-state dependency and plugin registration Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent ac4195d commit aab53fa

5 files changed

Lines changed: 169 additions & 37 deletions

File tree

src-tauri/Cargo.lock

Lines changed: 0 additions & 24 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src-tauri/Cargo.toml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,6 @@ tauri = { version = "1.6.2", features = [ "updater", "cli", "api-all", "devtools
3131
winapi = { version = "0.3.9", features = ["fileapi", "wingdi", "winuser", "windef"] }
3232
png = "0.17"
3333
tauri-plugin-fs-extra = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v1" }
34-
tauri-plugin-window-state = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v1" }
3534
tauri-plugin-single-instance = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v1" }
3635
fix-path-env = { git = "https://github.com/tauri-apps/fix-path-env-rs" }
3736
keyring = "2.3.3"

src-tauri/src/boot_config.rs

Lines changed: 40 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,12 @@ pub static APP_CONSTANTS: OnceCell<AppConstants> = OnceCell::new();
1515

1616
#[derive(Serialize)]
1717
pub struct BootConfig {
18-
pub version: u32
18+
pub version: u32,
19+
pub last_window_x: i32,
20+
pub last_window_y: i32,
21+
pub last_window_width: u32,
22+
pub last_window_height: u32,
23+
pub last_window_maximized: bool,
1924
}
2025
static BOOT_CONFIG_FILE_NAME: &'static str = "boot_config.json";
2126

@@ -30,11 +35,36 @@ fn _set_boot_config(boot_config: &mut BootConfig, value: &Value) {
3035
Some(value) => value as u32,
3136
None => 0
3237
};
38+
boot_config.last_window_x = match value["last_window_x"].as_i64() {
39+
Some(v) => v as i32,
40+
None => 0
41+
};
42+
boot_config.last_window_y = match value["last_window_y"].as_i64() {
43+
Some(v) => v as i32,
44+
None => 0
45+
};
46+
boot_config.last_window_width = match value["last_window_width"].as_u64() {
47+
Some(v) => v as u32,
48+
None => 0
49+
};
50+
boot_config.last_window_height = match value["last_window_height"].as_u64() {
51+
Some(v) => v as u32,
52+
None => 0
53+
};
54+
boot_config.last_window_maximized = match value["last_window_maximized"].as_bool() {
55+
Some(v) => v,
56+
None => false
57+
};
3358
}
3459

3560
pub fn read_boot_config() -> BootConfig {
3661
let mut boot_config = BootConfig {
37-
version: 1
62+
version: 1,
63+
last_window_x: 0,
64+
last_window_y: 0,
65+
last_window_width: 0,
66+
last_window_height: 0,
67+
last_window_maximized: false,
3868
};
3969
if let Some(app_constants) = APP_CONSTANTS.get() {
4070
let boot_config_file_path = get_boot_config_file_path(&app_constants.app_local_data_dir);
@@ -62,8 +92,13 @@ fn _write_boot_config(boot_config: &BootConfig) {
6292
}
6393

6494
// WARNING: If there are multiple windows, this will be called on each window close.
65-
pub fn write_boot_config(version: u32) {
95+
pub fn write_boot_config(version: u32, x: i32, y: i32, width: u32, height: u32, maximized: bool) {
6696
_write_boot_config(&BootConfig {
67-
version
68-
})
97+
version,
98+
last_window_x: x,
99+
last_window_y: y,
100+
last_window_width: width,
101+
last_window_height: height,
102+
last_window_maximized: maximized,
103+
})
69104
}

src-tauri/src/init.rs

Lines changed: 115 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,118 @@
11
use crate::utilities::ensure_dir_exists;
2-
use crate::boot_config::read_boot_config;
2+
use crate::boot_config::{read_boot_config, BootConfig};
33
use crate::boot_config::APP_CONSTANTS;
44
use crate::boot_config::AppConstants;
5+
use tauri::Manager;
6+
7+
/// Returns true if the point (x, y) falls within any of the available monitors.
8+
fn is_position_on_any_monitor(x: i32, y: i32, monitors: &[tauri::Monitor]) -> bool {
9+
for monitor in monitors {
10+
let mon_pos = monitor.position();
11+
let mon_size = monitor.size();
12+
if x >= mon_pos.x
13+
&& x < mon_pos.x + mon_size.width as i32
14+
&& y >= mon_pos.y
15+
&& y < mon_pos.y + mon_size.height as i32
16+
{
17+
return true;
18+
}
19+
}
20+
false
21+
}
22+
23+
/// Restores the main window's position, size, and maximized state from boot_config.
24+
/// This is intentionally fault-tolerant: any failure is logged and silently ignored
25+
/// so the app always starts, even with a corrupted or missing config.
26+
fn restore_window_state(app: &mut tauri::App, boot_config: &BootConfig) {
27+
let win = match app.get_window("main") {
28+
Some(w) => w,
29+
None => {
30+
eprintln!("restore_window_state: could not find main window");
31+
return;
32+
}
33+
};
34+
35+
if boot_config.last_window_maximized {
36+
if let Err(e) = win.maximize() {
37+
eprintln!("restore_window_state: failed to maximize window: {}", e);
38+
}
39+
return;
40+
}
41+
42+
let saved_w = boot_config.last_window_width;
43+
let saved_h = boot_config.last_window_height;
44+
45+
if saved_w > 0 && saved_h > 0 {
46+
// If the saved size exceeds the current screen, maximize instead of clamping.
47+
// Clamping to raw pixels would still ignore taskbar/dock; maximizing lets the
48+
// OS place the window correctly within the usable work area.
49+
let fits_screen = match win.current_monitor() {
50+
Ok(Some(monitor)) => {
51+
let screen = monitor.size();
52+
saved_w <= screen.width && saved_h <= screen.height
53+
}
54+
_ => true, // can't determine screen size, assume it fits
55+
};
56+
57+
if !fits_screen {
58+
if let Err(e) = win.maximize() {
59+
eprintln!("restore_window_state: failed to maximize for oversized saved state: {}", e);
60+
}
61+
} else {
62+
if let Err(e) = win.set_size(tauri::Size::Physical(tauri::PhysicalSize {
63+
width: saved_w,
64+
height: saved_h,
65+
})) {
66+
eprintln!("restore_window_state: failed to set window size: {}", e);
67+
}
68+
69+
// Restore position only if the saved position is on a currently connected monitor
70+
let saved_x = boot_config.last_window_x;
71+
let saved_y = boot_config.last_window_y;
72+
match win.available_monitors() {
73+
Ok(monitors) => {
74+
if is_position_on_any_monitor(saved_x, saved_y, &monitors) {
75+
if let Err(e) = win.set_position(tauri::Position::Physical(
76+
tauri::PhysicalPosition { x: saved_x, y: saved_y },
77+
)) {
78+
eprintln!("restore_window_state: failed to set window position: {}", e);
79+
}
80+
}
81+
// else: monitor disconnected, let the OS place the window
82+
}
83+
Err(e) => {
84+
eprintln!("restore_window_state: failed to list monitors: {}", e);
85+
// Cannot validate position; skip position restore, size is already set
86+
}
87+
}
88+
}
89+
} else {
90+
// First launch (no saved state) - use preferred size or maximize if screen is too small
91+
match win.current_monitor() {
92+
Ok(Some(monitor)) => {
93+
let screen = monitor.size();
94+
if screen.width > 1366 && screen.height > 900 {
95+
if let Err(e) = win.set_size(tauri::Size::Physical(tauri::PhysicalSize {
96+
width: 1366,
97+
height: 900,
98+
})) {
99+
eprintln!("restore_window_state: failed to set default size: {}", e);
100+
}
101+
} else {
102+
if let Err(e) = win.maximize() {
103+
eprintln!("restore_window_state: failed to maximize on small screen: {}", e);
104+
}
105+
}
106+
}
107+
Ok(None) => {
108+
eprintln!("restore_window_state: no current monitor detected");
109+
}
110+
Err(e) => {
111+
eprintln!("restore_window_state: failed to get current monitor: {}", e);
112+
}
113+
}
114+
}
115+
}
5116

6117
pub fn init_app(app: &mut tauri::App) {
7118
let config = app.config().clone();
@@ -19,9 +130,10 @@ pub fn init_app(app: &mut tauri::App) {
19130
println!("Bundle ID is {}", app_constants.tauri_config.tauri.bundle.identifier);
20131
}
21132
ensure_dir_exists(&app_constants.app_local_data_dir);
22-
read_boot_config();
133+
let boot_config = read_boot_config();
23134
#[cfg(debug_assertions)]{
24-
println!("Bootconfig version is {}", read_boot_config().version);
135+
println!("Bootconfig version is {}", boot_config.version);
25136
}
137+
restore_window_state(app, &boot_config);
26138
}
27139
}

src-tauri/src/main.rs

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,6 @@ mod boot_config;
4747
use trash;
4848

4949
mod platform;
50-
use tauri_plugin_window_state::StateFlags;
5150

5251
use keyring::Entry;
5352
use whoami;
@@ -352,8 +351,20 @@ fn process_window_event(event: &GlobalWindowEvent, trust_state: &State<WindowAes
352351
println!("AES trust removed for closing window: {}", window_label);
353352
}
354353

355-
// this does nothing and is here if in future you need to persist something on window close.
356-
boot_config::write_boot_config(1);
354+
// Save main window position/size so it can be restored on next launch
355+
if window_label == "main" {
356+
let window = event.window();
357+
let maximized = window.is_maximized().unwrap_or(false);
358+
let (x, y) = match window.outer_position() {
359+
Ok(pos) => (pos.x, pos.y),
360+
Err(_) => (0, 0),
361+
};
362+
let (width, height) = match window.outer_size() {
363+
Ok(size) => (size.width, size.height),
364+
Err(_) => (0, 0),
365+
};
366+
boot_config::write_boot_config(1, x, y, width, height, maximized);
367+
}
357368
}
358369
}
359370

@@ -813,7 +824,6 @@ fn main() {
813824
Ok(response)
814825
})
815826
.plugin(tauri_plugin_fs_extra::init())
816-
.plugin(tauri_plugin_window_state::Builder::default().with_state_flags(StateFlags::all() & !StateFlags::VISIBLE).build())
817827
.plugin(tauri_plugin_single_instance::init(|app, argv, cwd| {
818828
println!("{}, {argv:?}, {cwd}", app.package_info().name);
819829

0 commit comments

Comments
 (0)