Repository navigation
[Feature]: Ask to save unsaved changes when RustGIS is quit on macOS (Cmd+Q, Dock) #966
Description
Before you start
- I searched the existing issues and the roadmap and did not find a duplicate request.
What problem does this solve?
#945 (for #906) asks Save changes to…? before the desktop app discards unsaved changes. On macOS, Quit RustGIS (⌘Q) and the Dock's Quit skip that question. AppKit terminates the app without a window close request, and eframe goes straight to its exiting handler, which notes: "On Mac, Cmd-Q we get here and then run_app_on_demand doesn't return". saveprompt::close_requested never sees the quit.
#945 limits the damage. An exit with unsaved changes the user never chose to discard keeps a last recovery copy, and the next start offers it back ("RustGIS was quit last time without saving your changes."). That still isn't the prompt Windows and Linux get. With autosave off, the work is lost.
Proposed solution
Route macOS application termination into the same Save / Don't Save / Cancel flow:
- Answer
applicationShouldTerminate:withNSTerminateCancel(orNSTerminateLater) while the project has unsaved changes. Raise the save prompt assaveprompt::close_requesteddoes today. - Once the user chooses Save (and it succeeds) or Don't Save, terminate. With
NSTerminateLater, that meansreplyToApplicationShouldTerminate:. Cancel keeps the app running. - Keep it inside
apps/rustgis/src/macos.rs, beside the existing title-bar integration. Use the objc2 crates already inapps/rustgis/Cargo.toml, so no new dependency.
The blocker is unsafe. objc2 can only do this with unsafe code:
- Delegate: defining an application delegate (or subclassing the one winit installs) uses
define_class!, which needsunsafe impl. - Menu re-routing: pointing the Quit menu item at
performClose:instead would useNSMenuItem::setAction, which objc2-app-kit 0.3 marksunsafe. It also wouldn't cover the Dock's Quit, which sendsterminate:directly.
The workspace sets unsafe_code = "forbid". This needs a maintainer decision:
- Allow a narrow exception: a single
#[allow(unsafe_code)]module inapps/rustgis(an app, not a library crate), reviewed and tested on macOS (ci:macos). - Keep the recovery-copy behaviour from feat(ui): ask to save unsaved changes before quitting or replacing the project #945 and close this issue.
- Wait for winit to forward termination as a close request. Newer winit releases may expose application termination to the app, without any
unsafehere.
A pull request would add a macOS-only test where possible. At minimum it would need a manual check on a Mac: ⌘Q, Dock Quit, and logout with unsaved changes, under each of the three answers.
Estimated scope
Small: a contained change, but it needs a macOS machine to verify and a decision on unsafe.
Alternatives considered
- Hot exit (the recovery copy from feat(ui): ask to save unsaved changes before quitting or replacing the project #945): what macOS does now. It keeps the work, but it doesn't ask, and it does nothing with autosave off.
- Replace winit's default app menu with one built without the
terminate:Quit item, handling ⌘Q as an ordinary shortcut that runsui.quit. That avoidsunsafefor the menu, but removes the standard app menu, and the Dock's Quit still bypasses the prompt.
Raised in review of #945 (CodeRabbit).
Activity
Mavrick-F commentedon Oct 26, 2026
Looked into this upstream. The fix likely doesn't need
unsafein RustGIS after all.Cause. winit 0.30.13's macOS application delegate implements
applicationWillTerminate:but notapplicationShouldTerminate:. AppKit therefore terminates at once onterminate:. That covers the default menu's Quit (⌘Q), the Dock's Quit, logout and shutdown. No close request reaches eframe. eframe's ownconfirm_exitexample has the same gap: emilk/egui#7115.Upstream routes:
v0.30.x, the branch RustGIS is on. It adds an opt-inmacos-quit-as-closefeature.applicationShouldTerminate:cancels and emitsCloseRequestedinstead, so ⌘Q, the Dock and logout all arrive as an ordinary close request. The save prompt from feat(ui): ask to save unsaved changes before quitting or replacing the project #945 would then handle them with no further code. The only RustGIS change would be enabling the feature fromapps/rustgis/Cargo.toml. The PR has been open since September without review, and its CI is blocked by an unrelated lockfile issue on that branch.confirm_exitexample can be exited without a prompt on macOS by using cmd+Q (or macOS menu item "Quit confirm_exit") emilk/egui#7115 proposes that eframe answerapplicationShouldTerminate:itself: cancel, send the root viewport a close request, and quit if the app doesn't sendCancelClose. That would fix every eframe app, and RustGIS would only need to upgrade.The local route this issue first described works on 0.30 only by adding the method to winit's delegate class at runtime (
class_addMethod). That'sunsafeand patches another crate's class, so I'd hold it back unless both routes above stall.Proposed plan: wait for whichever lands first. Then either enable the winit feature or take the eframe upgrade, and verify ⌘Q, Dock Quit and logout on a Mac. Until then, #945's recovery copy keeps the work when autosave is on.