diff --git a/Cargo.lock b/Cargo.lock index a388d83e8..49e949bff 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1154,6 +1154,18 @@ dependencies = [ "winsafe", ] +[[package]] +name = "craft-quit" +version = "0.1.0" +source = "git+https://github.com/storytold/craft-libs?rev=f58bc1a3df490992d633393d0898af0588ce882c#f58bc1a3df490992d633393d0898af0588ce882c" +dependencies = [ + "dispatch2", + "egui", + "muda", + "objc2 0.6.4", + "objc2-app-kit 0.3.2", +] + [[package]] name = "craft-ui" version = "0.1.0" @@ -1184,6 +1196,15 @@ dependencies = [ "x509-cert", ] +[[package]] +name = "crossbeam-channel" +version = "0.5.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98b0cc327b5bc766e7fda9c9260cc0fa81b43a8e240440422dff70788e3f9ef1" +dependencies = [ + "crossbeam-utils", +] + [[package]] name = "crossbeam-deque" version = "0.8.8" @@ -3005,6 +3026,16 @@ dependencies = [ "cpufeatures 0.3.1", ] +[[package]] +name = "keyboard-types" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fbe853b403ae61a04233030ae8a79d94975281ed9770a1f9e246732b534b28d" +dependencies = [ + "bitflags 2.13.2", + "serde", +] + [[package]] name = "khronos-egl" version = "6.0.0" @@ -3248,6 +3279,24 @@ dependencies = [ "pxfm", ] +[[package]] +name = "muda" +version = "0.21.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4340e89c178916e62879c5c36b7f699019369be413e496178a6983ce91b8bc03" +dependencies = [ + "crossbeam-channel", + "dpi", + "keyboard-types", + "objc2 0.6.4", + "objc2-app-kit 0.3.2", + "objc2-core-foundation", + "objc2-foundation 0.3.2", + "png 0.18.1", + "thiserror 2.0.21", + "windows-sys 0.60.2", +] + [[package]] name = "multiversion_no_op" version = "1.0.0" @@ -3877,6 +3926,7 @@ dependencies = [ name = "pdfcraft" version = "0.8.0" dependencies = [ + "craft-quit", "eframe", "egui", "fmv-macos-events", @@ -4291,6 +4341,7 @@ dependencies = [ "arboard", "base64 0.22.1", "bytemuck", + "craft-quit", "craft-ui", "eframe", "egui", diff --git a/Cargo.toml b/Cargo.toml index d01821711..597dd316f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,6 +14,8 @@ repository = "https://github.com/storytold/pdfcraft" [workspace.dependencies] # Shared frontend components; engines remain independent of egui. craft-ui = { git = "https://github.com/storytold/craft-libs", version = "0.1.0", rev = "5c874d51eeb3de70e735c8ce87a59a7dedd47675", features = ["serde"] } +# Quit the macOS way (#1136): ⌘Q asks about unsaved changes, and closing quits through `terminate:`. +craft-quit = { git = "https://github.com/storytold/craft-libs", version = "0.1.0", rev = "f58bc1a3df490992d633393d0898af0588ce882c" } # Shared system spooler; document composition remains in pdfcraft-print. craft-print = { git = "https://github.com/storytold/craft-libs", version = "0.1.0", rev = "666c358065261a2816b376fa3bb10538dbf53040" } # internal diff --git a/apps/pdfcraft/Cargo.toml b/apps/pdfcraft/Cargo.toml index 762efbc7e..a03799297 100644 --- a/apps/pdfcraft/Cargo.toml +++ b/apps/pdfcraft/Cargo.toml @@ -29,6 +29,9 @@ log = { workspace = true } # crate registers the handler (Objective-C interop needs unsafe, which our workspace forbids) # behind a safe API. Same version as PhotoCraft. Pinned exactly: review the diff before bumping. fmv-macos-events = "=0.1.0" +# ⌘Q asks about unsaved changes like the close button (#1136): an app menu whose Quit asks the +# window to close instead of winit's, which ends the process without asking. +craft-quit = { workspace = true, features = ["menu"] } [target.'cfg(target_os = "linux")'.dependencies] # Asks the Wayland compositor which GPU it draws with (linux-dmabuf feedback), so the window is diff --git a/apps/pdfcraft/src/main.rs b/apps/pdfcraft/src/main.rs index c91c7b6b1..db855fdac 100644 --- a/apps/pdfcraft/src/main.rs +++ b/apps/pdfcraft/src/main.rs @@ -379,6 +379,11 @@ fn app_creator<'a>( #[cfg(target_os = "macos")] { app.os_events = Some(apple_events.connect(&cc.egui_ctx)); + // winit's Quit (⌘Q) ends the process without asking about unsaved changes (#1136). This + // menu's Quit closes the window instead, so `guard_quit` asks, as for the close button. + if let Err(e) = craft_quit::install_app_menu(&cc.egui_ctx, "PdfCraft") { + log::warn!("couldn't install the app menu, so ⌘Q quits without asking to save: {e}"); + } } if let Some(server) = instance { app.os_events = Some(server.connect(&cc.egui_ctx, native_wayland(cc))); diff --git a/crates/ui-egui/Cargo.toml b/crates/ui-egui/Cargo.toml index e02441cf6..ddab6a223 100644 --- a/crates/ui-egui/Cargo.toml +++ b/crates/ui-egui/Cargo.toml @@ -11,6 +11,8 @@ workspace = true [dependencies] craft-ui = { workspace = true } +# macOS: a window close quits through `terminate:` (the Touch Bar crash, #1136); elsewhere a no-op. +craft-quit = { workspace = true, features = ["egui"] } pdfcraft-fonts = { workspace = true } pdfcraft-engine = { workspace = true } pdfcraft-cos = { workspace = true } diff --git a/crates/ui-egui/src/lib.rs b/crates/ui-egui/src/lib.rs index e5e472979..6f009e54d 100644 --- a/crates/ui-egui/src/lib.rs +++ b/crates/ui-egui/src/lib.rs @@ -3013,6 +3013,8 @@ impl eframe::App for PdfCraftApp { self.control = Some(control); } self.guard_quit(ctx); + // macOS: quit through `terminate:` instead of dropping the window (Touch Bar crash, #1136). + craft_quit::on_close_request(ctx); let now = ctx.input(|i| i.time); self.autosave_tick(now); // Reload documents whose files a build rewrote (#431).