diff --git a/src-tauri/src/commands/window.rs b/src-tauri/src/commands/window.rs index 84d8d8bd8..b87219987 100644 --- a/src-tauri/src/commands/window.rs +++ b/src-tauri/src/commands/window.rs @@ -9,7 +9,7 @@ use specta::Type; use std::path::PathBuf; use std::sync::atomic::{AtomicBool, Ordering}; use tauri::ipc::Channel; -use tauri::{AppHandle, State, WebviewWindow}; +use tauri::{AppHandle, State, WebviewWindow, Window}; /// Opens `path` in a new window (`new_window`) or tells the caller to open it /// itself (`current`). A project already open in some window is focused instead. @@ -211,8 +211,18 @@ pub struct CloseContext { #[tauri::command] #[specta::specta] -pub fn window_close_context(app: AppHandle, windows: State<'_, WindowsState>) -> CloseContext { - CloseContext { quitting: windows.quitting(), windows: windows::project_window_labels(&app).len() as u32 } +pub fn window_close_context(app: AppHandle, window: Window, windows: State<'_, WindowsState>) -> CloseContext { + let all = windows::project_window_labels(&app); + let pending: Vec = windows::sorted_labels( + windows.pending_closes().iter().map(String::as_str).filter(|l| all.iter().any(|a| a == l)), + ); + // Members of a tab group asking to close together are one window closing: the first of them + // is the last window left (it returns to the welcome screen); the others just close. + let count = match pending.first() { + Some(keeper) if pending.len() > 1 && keeper == window.label() => all.len() - pending.len() + 1, + _ => all.len(), + }; + CloseContext { quitting: windows.quitting(), windows: count as u32 } } /// Quits: closes every window, each of which runs its own unsaved-changes check. diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index e5dd7f88f..12f76873e 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -205,8 +205,13 @@ pub fn run() { Ok(()) }) .on_window_event(|window, event| match event { - #[cfg(target_os = "linux")] - tauri::WindowEvent::CloseRequested { .. } => linux_window_state::save_before_close(window.app_handle()), + tauri::WindowEvent::CloseRequested { .. } => { + #[cfg(target_os = "linux")] + linux_window_state::save_before_close(window.app_handle()); + if let Some(w) = window.try_state::() { + w.close_requested(window.label()); + } + } tauri::WindowEvent::Focused(true) => { application_focus::changed(window.app_handle(), true); if let Some(w) = window.try_state::() { diff --git a/src-tauri/src/window_groups.rs b/src-tauri/src/window_groups.rs index bac36d3a4..4192cc7f1 100644 --- a/src-tauri/src/window_groups.rs +++ b/src-tauri/src/window_groups.rs @@ -214,6 +214,7 @@ pub fn close_tabs(app: &AppHandle, caller: &str, keep: &str, labels: Vec /// The closing tab's project vetoed (its unsaved-changes prompt was cancelled): leave the rest open. pub fn cancel_close(app: &AppHandle, caller: &str) { let state = app.state::(); + state.close_settled(caller); let mut batch = state.tab_closes.lock(); if batch.as_ref().is_some_and(|b| b.pending.front().is_some_and(|l| l == caller)) { *batch = None; diff --git a/src-tauri/src/windows.rs b/src-tauri/src/windows.rs index 97c1ffbd9..82f39ef2b 100644 --- a/src-tauri/src/windows.rs +++ b/src-tauri/src/windows.rs @@ -140,6 +140,9 @@ pub struct WindowsState { last_focused: Mutex>, /// Set by Quit while windows close one by one. quitting: AtomicBool, + /// Windows asked to close whose guards have not finished. AppKit closes a whole tab group at + /// once, so its members ask together. + close_requests: Mutex>, /// Project tabs closing one by one (Close Other Tabs and friends). pub tab_closes: Mutex>, /// Relaunch once the quit completes (after installing an update). @@ -149,6 +152,21 @@ pub struct WindowsState { } impl WindowsState { + pub fn close_requested(&self, label: &str) { + let mut requests = self.close_requests.lock(); + if !requests.iter().any(|l| l == label) { + requests.push(label.to_owned()); + } + } + + pub fn close_settled(&self, label: &str) { + self.close_requests.lock().retain(|l| l != label); + } + + pub fn pending_closes(&self) -> Vec { + self.close_requests.lock().clone() + } + pub fn set_launch(&self, label: &str, launch: Launch) { self.launches.lock().insert(label.to_owned(), launch); } @@ -187,6 +205,7 @@ impl WindowsState { } pub fn forget(&self, label: &str) { + self.close_settled(label); self.launches.lock().remove(label); let mut last = self.last_focused.lock(); if last.as_deref() == Some(label) { diff --git a/src/features/app/closeGuard.ts b/src/features/app/closeGuard.ts index 084fb5244..5155ce9b6 100644 --- a/src/features/app/closeGuard.ts +++ b/src/features/app/closeGuard.ts @@ -189,6 +189,7 @@ async function onCloseRequested(deps: CloseDeps): Promise { // screen (to open another one); closing the welcome screen, or quitting, closes the window. if (!ctx.quitting && ctx.windows <= 1 && (s.project || s.lightEdit)) { if (await confirmLeave(deps)) await closeProject(); + await commands.windowGroupCancelClose(); return false; } const confirmTerminate = workflowSettings().run.confirmTerminate;