1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
//! The document macOS was asked to open, which does not arrive as an argument.
//!
//! Linux and Windows both hand a double-clicked document to an application as
//! `argv[1]`, which is what [`run`](crate::run) reads. macOS does not: it
//! launches the application with no arguments at all and then sends the document
//! as an Apple Event. With nothing listening for one, `AppKit` refuses the event
//! and Finder blames the application, reporting that it cannot open files in a
//! format whose association is in fact correct.
//!
//! **This is the one place in the whole workspace that writes `unsafe`.** It is
//! here rather than in the three application crates because all three windows
//! come through [`run`](crate::run), so a copy in each would be three
//! byte-identical copies of two hundred lines of Objective-C glue. `lib.rs` is
//! `deny` with an `allow` on this module alone, where it was `forbid`, and the
//! three applications became `forbid` in the same change: a net gain of two.
//! `odox-core` is untouched and stays `forbid`.
//!
//! It handles the event rather than replacing the application delegate.
//! `NSApplication` has exactly one delegate, winit sets its own inside
//! `EventLoop::new` and depends on it for startup, and there is no point in the
//! sequence where this code holds control in between. `NSAppleEventManager` is
//! additive: registering here displaces nothing of winit's, and the delegate
//! problem that made this look impossible does not arise.
//
// Author: David M. Anderson
// Built with AI assistance (Claude, Anthropic)
// Ported from slipcase-desktop, which measured everything this file asserts.
use PathBuf;
use ;
use egui;
use RcBlock;
use Retained;
use ;
use NonNull;
use ;
use NSApplicationWillFinishLaunchingNotification;
use ;
// Four-character codes, which are what the Apple Event world names things
// with. Spelled from their bytes rather than as hexadecimal so that the name
// and the number cannot drift apart: `kCoreEventClass` really is the four
// characters `aevt`. They are `FourCharCode`, which is `u32`, big-endian.
const CORE_EVENT_CLASS: u32 = u32from_be_bytes;
const OPEN_DOCUMENTS: u32 = u32from_be_bytes;
const DIRECT_OBJECT: u32 = u32from_be_bytes;
const FILE_URL: u32 = u32from_be_bytes;
/// The document macOS last asked for, waiting to be picked up by the window.
///
/// One rather than a list. An Apple Event can carry several documents, because
/// a person can select three documents and press Open, but this application
/// shows one document at a time and has nowhere to put the others. The first
/// of the event is taken and the rest are ignored, which is at least the one a
/// person clicked when they clicked one.
static ARRIVED: = new;
/// How to wake the window when a document arrives.
///
/// egui draws when something happens, and an Apple Event is not something that
/// happens to egui: without this the document would sit in `ARRIVED` until a
/// person moved the mouse over a window they had not asked to be looking at.
/// Asking for a repaint is what turns a delivery into a drawn frame.
static WAKE: = new;
define_class!;
/// The first document in an open-documents event, as a path.
///
/// The direct object is a list of one descriptor per document, indexed from
/// one rather than zero. Each is coerced to `typeFileURL` and read as the bytes
/// of a URL, which is the shape Apple documents; a path is not taken from the
/// descriptor directly, because what it holds is an alias or a bookmark as
/// often as anything a path could be read out of. `NSURL` then does the
/// percent-decoding, so a document with a space in its name arrives with the
/// space rather than with `%20`.
/// Start listening for documents macOS asks this application to open.
///
/// Called once, before `eframe` runs, by `run`. What this actually registers is a
/// notification observer; the Apple Event handler goes on at
/// `applicationWillFinishLaunching:`, which is the only moment that works and
/// was found by measuring the two that do not.
///
/// Registering **before** `NSApplication` exists is overwritten: `AppKit`
/// installs its own handler for this event while starting up, and its handler
/// is the one that refuses the document. Measured — with the registration
/// there, neither a cold launch nor a document double-clicked into a running
/// window arrived. Registering **after** `eframe`'s creation closure is too
/// late for the launch itself: measured, a document double-clicked into a
/// running window arrived and the one that started the process did not,
/// because `AppKit` had already dispatched and refused it. Between them is
/// `applicationWillFinishLaunching:`, which is where Apple's own documentation
/// says to install Apple Event handlers, and it is right.
///
/// The observer is used rather than a delegate method because
/// `NSApplication` has exactly one delegate and winit owns it. A notification
/// has any number of observers, so this displaces nothing.
/// Put the handler on, at the moment `watch` arranged for.
/// How to wake the window, once there is a window to wake.
///
/// Separate from `watch` because the handler has to be installed before
/// `eframe` runs and the context does not exist until after. A document that
/// arrives in between is not lost: it waits in `ARRIVED`, and the first frame
/// draws anyway.
/// The document macOS asked for since this was last called, if any.
///
/// Taken rather than read, so that one event opens one document and the window
/// does not reopen it on every frame after.