macOS: open documents from Finder, the Dock and `open -a` (#69)
Double-clicking a workbook, Open With, dropping on the Dock icon and `open -a GridCraft book.xlsx` send the app a kAEOpenDocuments Apple event instead of command-line paths. winit 0.30 doesn't handle it, so AppKit answered "GridCraft cannot open files in the Excel Workbook format". Register the handler with the audited fmv-macos-events crate that PdfCraft and PhotoCraft already use: it does the Objective-C interop (unsafe, which this workspace forbids) behind a safe main-thread API and leaves winit's delegate alone. Opened paths go through SheetApp::open_path; a quit event closes the window. The registration lives in main() until the event loop returns, so the event that launched the app is caught too. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
13664fe66b
commit
3509bcfd87
|
|
@ -1132,6 +1132,17 @@ dependencies = [
|
|||
"zlib-rs",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fmv-macos-events"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "90642cf66dde7f205dc8b42dfe57b47d0f3a29f571b832742883ef512e23fe55"
|
||||
dependencies = [
|
||||
"objc2 0.6.5",
|
||||
"objc2-app-kit 0.3.2",
|
||||
"objc2-foundation 0.3.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "foldhash"
|
||||
version = "0.2.0"
|
||||
|
|
@ -1396,6 +1407,7 @@ version = "0.4.0"
|
|||
dependencies = [
|
||||
"eframe",
|
||||
"egui",
|
||||
"fmv-macos-events",
|
||||
"gridcraft-engine",
|
||||
"gridcraft-ui-egui",
|
||||
"image",
|
||||
|
|
|
|||
|
|
@ -28,3 +28,8 @@ rfd = { workspace = true }
|
|||
|
||||
[target.'cfg(target_os = "macos")'.dependencies]
|
||||
muda = { version = "0.21", default-features = false }
|
||||
# Finder double-click, Open With, Dock drops and `open -a GridCraft file.xlsx` arrive as a
|
||||
# kAEOpenDocuments Apple event, which winit 0.30 doesn't handle (#69). This audited crate (also used
|
||||
# by PdfCraft and PhotoCraft) registers the handler (Objective-C interop needs unsafe, which our
|
||||
# workspace forbids) behind a safe API. Pinned exactly: review the diff before bumping.
|
||||
fmv-macos-events = "=0.1.0"
|
||||
|
|
|
|||
|
|
@ -0,0 +1,49 @@
|
|||
//! macOS open-documents and quit Apple events (#69).
|
||||
//!
|
||||
//! Finder double-clicks, Open With, drops on the Dock icon and `open -a GridCraft book.xlsx` don't
|
||||
//! pass paths on the command line: LaunchServices sends the running (or just-launched) app a
|
||||
//! `kAEOpenDocuments` ('odoc') Apple event. winit 0.30 doesn't handle it and owns the
|
||||
//! `NSApplicationDelegate`, so AppKit answered "GridCraft cannot open files in the Excel Workbook
|
||||
//! format". Handling it ourselves needs Objective-C class declarations, i.e. `unsafe`, which this
|
||||
//! workspace forbids. The audited `fmv-macos-events` crate (also used by PdfCraft and PhotoCraft)
|
||||
//! wraps exactly that, an `NSAppleEventManager` handler registered before Finder's launch event
|
||||
//! that leaves winit's delegate alone, behind a safe main-thread API.
|
||||
|
||||
use fmv_macos_events::{Event, Inbox, Registration};
|
||||
use gridcraft_ui_egui::SheetApp;
|
||||
|
||||
/// Keeps the Apple-event handlers registered; hold it until the event loop returns.
|
||||
pub struct AppleEvents {
|
||||
_registration: Registration,
|
||||
inbox: Inbox,
|
||||
}
|
||||
|
||||
impl AppleEvents {
|
||||
/// Register the handlers. Call on the main thread before the event loop starts, so the event
|
||||
/// that launched the app (a Finder double-click) is caught too.
|
||||
pub fn install() -> Self {
|
||||
let (registration, inbox) = Registration::install();
|
||||
Self { _registration: registration, inbox }
|
||||
}
|
||||
|
||||
/// The queue the app drains every frame ([`poll`]); events arriving later wake `ctx`.
|
||||
pub fn connect(&self, ctx: &egui::Context) -> Inbox {
|
||||
let ctx = ctx.clone();
|
||||
self.inbox.set_wake(move || ctx.request_repaint());
|
||||
self.inbox.clone()
|
||||
}
|
||||
}
|
||||
|
||||
/// Open the documents and act on the quit requests that arrived since the last frame.
|
||||
pub fn poll(inbox: &Inbox, app: &mut SheetApp, ctx: &egui::Context) {
|
||||
for e in inbox.drain() {
|
||||
match e {
|
||||
Event::Open(paths) => {
|
||||
for p in paths.iter().filter_map(|p| p.to_str()) {
|
||||
app.open_path(p);
|
||||
}
|
||||
}
|
||||
Event::Quit => ctx.send_viewport_cmd(egui::ViewportCommand::Close),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -8,6 +8,8 @@
|
|||
#![cfg_attr(all(target_os = "windows", not(debug_assertions)), windows_subsystem = "windows")]
|
||||
#![deny(clippy::unwrap_used, clippy::expect_used, clippy::panic, clippy::unimplemented, clippy::todo, clippy::unreachable)]
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
mod apple_events;
|
||||
mod control_server;
|
||||
#[cfg(target_os = "macos")]
|
||||
mod native_menu;
|
||||
|
|
@ -16,7 +18,7 @@ use gridcraft_engine::Session;
|
|||
use gridcraft_ui_egui::{Services, SheetApp};
|
||||
use serde_json::json;
|
||||
|
||||
struct App(SheetApp, #[cfg(target_os = "macos")] Option<native_menu::NativeMenu>);
|
||||
struct App(SheetApp, #[cfg(target_os = "macos")] Option<native_menu::NativeMenu>, #[cfg(target_os = "macos")] fmv_macos_events::Inbox);
|
||||
|
||||
impl eframe::App for App {
|
||||
fn logic(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
|
||||
|
|
@ -28,6 +30,7 @@ impl eframe::App for App {
|
|||
if let Some(m) = &self.1 {
|
||||
m.poll(&mut self.0, ctx);
|
||||
}
|
||||
apple_events::poll(&self.2, &mut self.0, ctx);
|
||||
}
|
||||
self.0.logic(ctx);
|
||||
}
|
||||
|
|
@ -187,6 +190,12 @@ fn main() -> eframe::Result<()> {
|
|||
viewport = viewport.with_icon(i);
|
||||
}
|
||||
let options = eframe::NativeOptions { viewport, ..Default::default() };
|
||||
// Registered before the event loop starts, so it catches the Finder event that launched us as
|
||||
// well as later ones. Lives until the event loop returns; the app creator only borrows it.
|
||||
#[cfg(target_os = "macos")]
|
||||
let apple_events = apple_events::AppleEvents::install();
|
||||
#[cfg(target_os = "macos")]
|
||||
let apple_events = &apple_events;
|
||||
eframe::run_native(
|
||||
"GridCraft",
|
||||
options,
|
||||
|
|
@ -202,6 +211,8 @@ fn main() -> eframe::Result<()> {
|
|||
app,
|
||||
#[cfg(target_os = "macos")]
|
||||
None,
|
||||
#[cfg(target_os = "macos")]
|
||||
apple_events.connect(&cc.egui_ctx),
|
||||
)))
|
||||
}),
|
||||
)
|
||||
|
|
|
|||
Loading…
Reference in New Issue