Skip to main content

wingy_appkit/
event_loop.rs

1use std::rc::Rc;
2use std::sync::Arc;
3use std::time::{Duration, Instant};
4
5use objc2::rc::{Retained, autoreleasepool};
6use objc2::runtime::ProtocolObject;
7use objc2::{AnyThread, MainThreadMarker, available};
8use objc2_app_kit::{
9    NSApplication, NSApplicationActivationPolicy, NSApplicationDidFinishLaunchingNotification,
10    NSApplicationWillTerminateNotification, NSDraggingItem, NSPasteboard, NSPasteboardItem,
11    NSPasteboardTypeFileURL, NSPasteboardWriting, NSWindow,
12};
13use objc2_core_foundation::{
14    CFIndex, CFRunLoopActivity, CGPoint, CGRect, CGSize, kCFRunLoopCommonModes,
15};
16use objc2_foundation::{NSArray, NSNotificationCenter, NSObjectProtocol, NSString};
17use rwh_06::HasDisplayHandle;
18use tracing::debug_span;
19use wingy_common::core_foundation::{MainRunLoop, MainRunLoopObserver, tracing_observers};
20use wingy_common::foundation::create_observer;
21use wingy_core::application::ApplicationHandler;
22use wingy_core::cursor::{CustomCursor as CoreCustomCursor, CustomCursorSource};
23use wingy_core::data_transfer::{
24    DataTransfer, DataTransferId, DataTransferSend, SendData, TransferType, TypeHint, TypedData,
25};
26use wingy_core::error::{CreateWindowError, CustomCursorError, EventLoopError, TransferError};
27use wingy_core::event::WindowEvent;
28use wingy_core::event_loop::pump_events::PumpStatus;
29use wingy_core::event_loop::{
30    ActiveEventLoop as RootActiveEventLoop, AsyncRequestSerial, ControlFlow, DeviceEvents,
31    DndAction, DragIcon, EventLoopProvider, EventLoopProxy as CoreEventLoopProxy,
32    OwnedDisplayHandle as CoreOwnedDisplayHandle,
33};
34use wingy_core::monitor::MonitorHandle as CoreMonitorHandle;
35use wingy_core::window::{Theme, WindowId};
36
37use super::app::override_send_event;
38use super::app_state::AppState;
39use super::cursor::CustomCursor;
40use super::event::dummy_event;
41use super::monitor;
42use crate::ActivationPolicy;
43use crate::cursor::image_from_icon;
44use crate::data_transfer::{
45    Pasteboard, PasteboardType, PasteboardTypeSpec, PasteboardWriter,
46    dnd_actions_to_ns_drag_operation,
47};
48use crate::window::Window;
49
50#[derive(Debug)]
51pub struct ActiveEventLoop {
52    pub(super) app_state: Rc<AppState>,
53    pub(super) mtm: MainThreadMarker,
54}
55
56impl ActiveEventLoop {
57    pub(crate) fn hide_application(&self) {
58        NSApplication::sharedApplication(self.mtm).hide(None)
59    }
60
61    pub(crate) fn hide_other_applications(&self) {
62        NSApplication::sharedApplication(self.mtm).hideOtherApplications(None)
63    }
64
65    pub(crate) fn set_allows_automatic_window_tabbing(&self, enabled: bool) {
66        NSWindow::setAllowsAutomaticWindowTabbing(enabled, self.mtm)
67    }
68
69    pub(crate) fn allows_automatic_window_tabbing(&self) -> bool {
70        NSWindow::allowsAutomaticWindowTabbing(self.mtm)
71    }
72
73    fn clipboard_by_id(&self, id: DataTransferId) -> Option<Pasteboard> {
74        let pb = Pasteboard::new(id, NSPasteboard::generalPasteboard(), self.mtm);
75        (self.app_state.data_transfer().clipboard_transfer_id(pb.change_count()) == id)
76            .then_some(pb)
77    }
78}
79
80impl RootActiveEventLoop for ActiveEventLoop {
81    fn create_proxy(&self) -> CoreEventLoopProxy {
82        CoreEventLoopProxy::new(self.app_state.event_loop_proxy().clone())
83    }
84
85    fn create_window(
86        &self,
87        window_attributes: wingy_core::window::WindowAttributes,
88    ) -> Result<Box<dyn wingy_core::window::Window>, CreateWindowError> {
89        Ok(Box::new(Window::new(self, window_attributes)?))
90    }
91
92    fn create_custom_cursor(
93        &self,
94        source: CustomCursorSource,
95    ) -> Result<CoreCustomCursor, CustomCursorError> {
96        Ok(CoreCustomCursor(Arc::new(CustomCursor::new(source)?)))
97    }
98
99    fn available_monitors(&self) -> Box<dyn Iterator<Item = CoreMonitorHandle>> {
100        Box::new(
101            monitor::available_monitors()
102                .into_iter()
103                .map(|monitor| CoreMonitorHandle(Arc::new(monitor))),
104        )
105    }
106
107    fn primary_monitor(&self) -> Option<wingy_core::monitor::MonitorHandle> {
108        let monitor = monitor::primary_monitor();
109        Some(CoreMonitorHandle(Arc::new(monitor)))
110    }
111
112    fn listen_device_events(&self, _allowed: DeviceEvents) {}
113
114    fn system_theme(&self) -> Option<Theme> {
115        let app = NSApplication::sharedApplication(self.mtm);
116
117        // Dark appearance was introduced in macOS 10.14
118        if available!(macos = 10.14) {
119            Some(super::window_delegate::appearance_to_theme(&app.effectiveAppearance()))
120        } else {
121            Some(Theme::Light)
122        }
123    }
124
125    fn set_control_flow(&self, control_flow: ControlFlow) {
126        self.app_state.set_control_flow(control_flow)
127    }
128
129    fn control_flow(&self) -> ControlFlow {
130        self.app_state.control_flow()
131    }
132
133    fn exit(&self) {
134        self.app_state.exit()
135    }
136
137    fn exiting(&self) -> bool {
138        self.app_state.exiting()
139    }
140
141    fn owned_display_handle(&self) -> CoreOwnedDisplayHandle {
142        CoreOwnedDisplayHandle::new(Arc::new(OwnedDisplayHandle))
143    }
144
145    fn rwh_06_handle(&self) -> &dyn rwh_06::HasDisplayHandle {
146        self
147    }
148
149    fn fetch_data_transfer(
150        &self,
151        id: DataTransferId,
152        type_: &dyn TransferType,
153    ) -> Result<AsyncRequestSerial, TransferError> {
154        let (pb, window_id) = match self.app_state.data_transfer().pasteboards().get(id) {
155            Some(pb) => {
156                let Some(window_id) = self.app_state.data_transfer().pasteboards().window_id(id)
157                else {
158                    return Err(TransferError::Failed);
159                };
160                (pb, window_id)
161            },
162            None => {
163                let pb = self.clipboard_by_id(id).ok_or(TransferError::UnknownTransfer(id))?;
164                let window_id =
165                    self.app_state.clipboard_window(self.mtm).ok_or(TransferError::Failed)?;
166                (pb, window_id)
167            },
168        };
169
170        let type_ = match type_.cast_ref::<PasteboardType>() {
171            Some(pb_type) => pb_type.clone(),
172            None => {
173                let hint = type_.hint().ok_or(TransferError::Failed)?;
174                pb.types()
175                    .iter()
176                    .find(|advertised| {
177                        advertised.hint().is_some_and(|haystack| haystack.matches(&hint))
178                    })
179                    .cloned()
180                    .ok_or(TransferError::Failed)?
181            },
182        };
183
184        let serial = AsyncRequestSerial::get();
185
186        let data: Arc<dyn TypedData> =
187            Arc::new(pb.with_type(PasteboardTypeSpec::PasteboardType(type_)));
188
189        self.app_state.maybe_queue_with_handler(move |app, event_loop| {
190            app.window_event(
191                event_loop,
192                window_id,
193                WindowEvent::DataTransferReceived { id, serial, value: data },
194            );
195        });
196
197        Ok(serial)
198    }
199
200    fn data_transfer(&self, id: DataTransferId) -> Result<Box<dyn DataTransfer>, TransferError> {
201        if let Some(pb) = self.app_state.data_transfer().pasteboards().get(id) {
202            return Ok(Box::new(pb));
203        }
204
205        let pb = self.clipboard_by_id(id).ok_or(TransferError::UnknownTransfer(id))?;
206
207        Ok(Box::new(pb))
208    }
209
210    fn clipboard(&self) -> Result<Option<DataTransferId>, TransferError> {
211        let pb = NSPasteboard::generalPasteboard();
212        if pb.types().is_none_or(|types| types.is_empty()) {
213            return Ok(None);
214        }
215
216        Ok(Some(self.app_state.data_transfer().clipboard_transfer_id(pb.changeCount())))
217    }
218
219    fn set_clipboard(&self, send_data: Box<dyn DataTransferSend>) -> Result<(), TransferError> {
220        let mut uris = match send_data.data_for_type(&TypeHint::UriList) {
221            Some(SendData::Uris(uris)) => uris,
222            Some(SendData::String(string)) => vec![string],
223            _ => vec![],
224        }
225        .into_iter()
226        .map(|uri| NSString::from_str(&uri));
227
228        let first_uri = uris.next();
229
230        let mut objects: Vec<Retained<ProtocolObject<dyn NSPasteboardWriting>>> =
231            vec![ProtocolObject::from_retained(PasteboardWriter::new(send_data, first_uri))];
232
233        for uri in uris {
234            let item = NSPasteboardItem::new();
235            if !item.setString_forType(&uri, unsafe { NSPasteboardTypeFileURL }) {
236                return Err(os_error!("failed to write a URI to a pasteboard item").into());
237            }
238            objects.push(ProtocolObject::from_retained(item));
239        }
240
241        let pb = NSPasteboard::generalPasteboard();
242        pb.clearContents();
243        if pb.writeObjects(&NSArray::from_retained_slice(&objects)) {
244            Ok(())
245        } else {
246            Err(os_error!("failed to write to the general pasteboard").into())
247        }
248    }
249
250    fn set_valid_dnd_actions(
251        &self,
252        id: DataTransferId,
253        actions: &[DndAction],
254    ) -> Result<(), TransferError> {
255        if self.app_state.data_transfer().set_valid_dnd_actions(id, actions) {
256            Ok(())
257        } else {
258            Err(TransferError::UnknownTransfer(id))
259        }
260    }
261
262    fn start_drag(
263        &self,
264        source: WindowId,
265        send_data: Box<dyn DataTransferSend>,
266        actions: &[DndAction],
267        icon: Option<DragIcon>,
268    ) -> Result<DataTransferId, TransferError> {
269        let drag_operation = dnd_actions_to_ns_drag_operation(actions);
270
271        self.app_state
272            .with_window_delegate_on_main(source, move |delegate| {
273                let (dragging_rect_offset_x, dragging_rect_offset_y) =
274                    icon.as_ref().map(|icon| (icon.offset_x, icon.offset_y)).unwrap_or_default();
275                let drag_image = icon.and_then(|icon| image_from_icon(&icon.icon).ok());
276
277                let Some(event) = delegate.window().currentEvent() else {
278                    return Err(TransferError::Failed);
279                };
280
281                let dragging_rect_size = drag_image
282                    .as_ref()
283                    .map(|img| img.size())
284                    // Seemingly we need some kind of dragging rectangle even if no icon is
285                    // supplied.
286                    .unwrap_or(CGSize::new(16., 16.));
287
288                let event_location = event.locationInWindow();
289                let dragging_rect_location = CGPoint::new(
290                    event_location.x + dragging_rect_offset_x as f64,
291                    // Convert generic coordinates (y=0 is top of image) to AppKit coordinates (y=0
292                    // is bottom of image)
293                    event_location.y - dragging_rect_size.height - dragging_rect_offset_y as f64,
294                );
295                let dragging_rect = CGRect::new(dragging_rect_location, dragging_rect_size);
296
297                let mut uris = send_data
298                    .data_for_type(&TypeHint::UriList)
299                    .and_then(|file_uris| {
300                        // TODO: Might not be ideal to do this
301                        let ns_url_from_str = |str: String| NSString::from_str(&str);
302                        // Slightly complicated use of iterators in order to ensure that branches
303                        // have the same opaque type
304                        match file_uris {
305                            SendData::Uris(os_strings) => Some(
306                                None.into_iter().chain(os_strings.into_iter().map(ns_url_from_str)),
307                            ),
308                            SendData::String(string) => Some(
309                                Some(NSString::from_str(&string))
310                                    .into_iter()
311                                    .chain(Vec::new().into_iter().map(ns_url_from_str)),
312                            ),
313                            SendData::Bytes(_) => None,
314                            _ => None,
315                        }
316                    })
317                    .into_iter()
318                    .flatten();
319
320                let first_uri = uris.next();
321
322                let mut pasteboard_items = uris
323                    .map(|ns_url| {
324                        let dragging_item = NSDraggingItem::initWithPasteboardWriter(
325                            NSDraggingItem::alloc(),
326                            ProtocolObject::from_ref(&*ns_url),
327                        );
328
329                        // No dragging frame/contents, icon only applies to the first item.
330
331                        dragging_item
332                    })
333                    .collect::<Vec<_>>();
334
335                let first_dragging_item = NSDraggingItem::initWithPasteboardWriter(
336                    NSDraggingItem::alloc(),
337                    ProtocolObject::from_ref(&*PasteboardWriter::new(send_data, first_uri)),
338                );
339
340                unsafe {
341                    first_dragging_item.setDraggingFrame_contents(
342                        dragging_rect,
343                        drag_image.as_ref().map(AsRef::as_ref),
344                    )
345                };
346
347                pasteboard_items.insert(0, first_dragging_item);
348
349                let pasteboard_items = NSArray::from_retained_slice(&pasteboard_items);
350
351                let session = delegate.window().beginDraggingSessionWithItems_event_source(
352                    &pasteboard_items,
353                    &event,
354                    ProtocolObject::from_ref(&*delegate),
355                );
356
357                let id = DataTransferId::from_raw(session.draggingSequenceNumber() as i64);
358
359                delegate.view().set_dragging_session(session, drag_operation);
360
361                Ok(id)
362            })
363            .ok_or(TransferError::Failed)?
364    }
365}
366
367impl rwh_06::HasDisplayHandle for ActiveEventLoop {
368    fn display_handle(&self) -> Result<rwh_06::DisplayHandle<'_>, rwh_06::HandleError> {
369        let raw = rwh_06::RawDisplayHandle::AppKit(rwh_06::AppKitDisplayHandle::new());
370        unsafe { Ok(rwh_06::DisplayHandle::borrow_raw(raw)) }
371    }
372}
373
374#[derive(Debug)]
375pub struct EventLoop {
376    /// Store a reference to the application for convenience.
377    ///
378    /// We intentionally don't store `WingyApplication` since we want to have
379    /// the possibility of swapping that out at some point.
380    app: Retained<NSApplication>,
381    app_state: Rc<AppState>,
382
383    window_target: ActiveEventLoop,
384
385    // Since macOS 10.11, we no longer need to remove the observers before they are deallocated;
386    // the system instead cleans it up next time it would have posted a notification to it.
387    //
388    // Though we do still need to keep the observers around to prevent them from being deallocated.
389    _did_finish_launching_observer: Retained<ProtocolObject<dyn NSObjectProtocol>>,
390    _will_terminate_observer: Retained<ProtocolObject<dyn NSObjectProtocol>>,
391
392    _tracing_observers: Option<(MainRunLoopObserver, MainRunLoopObserver)>,
393    _before_waiting_observer: MainRunLoopObserver,
394    _after_waiting_observer: MainRunLoopObserver,
395}
396
397#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
398pub struct PlatformSpecificEventLoopAttributes {
399    pub activation_policy: Option<ActivationPolicy>,
400    pub default_menu: bool,
401    pub activate_ignoring_other_apps: bool,
402}
403
404impl Default for PlatformSpecificEventLoopAttributes {
405    fn default() -> Self {
406        Self { activation_policy: None, default_menu: true, activate_ignoring_other_apps: true }
407    }
408}
409
410impl EventLoop {
411    pub fn new(attributes: &PlatformSpecificEventLoopAttributes) -> Result<Self, EventLoopError> {
412        let mtm = MainThreadMarker::new()
413            .expect("on macOS, `EventLoop` must be created on the main thread!");
414
415        let activation_policy = match attributes.activation_policy {
416            None => None,
417            Some(ActivationPolicy::Regular) => Some(NSApplicationActivationPolicy::Regular),
418            Some(ActivationPolicy::Accessory) => Some(NSApplicationActivationPolicy::Accessory),
419            Some(ActivationPolicy::Prohibited) => Some(NSApplicationActivationPolicy::Prohibited),
420        };
421
422        let app_state = AppState::setup_global(
423            mtm,
424            activation_policy,
425            attributes.default_menu,
426            attributes.activate_ignoring_other_apps,
427        )
428        .ok_or_else(|| EventLoopError::RecreationAttempt)?;
429
430        // Initialize the application (if it has not already been).
431        let app = NSApplication::sharedApplication(mtm);
432
433        // Override `sendEvent:` on the application to forward to our application state.
434        override_send_event(&app);
435
436        let center = NSNotificationCenter::defaultCenter();
437
438        let weak_app_state = Rc::downgrade(&app_state);
439        let _did_finish_launching_observer = create_observer(
440            &center,
441            // `applicationDidFinishLaunching:`
442            unsafe { NSApplicationDidFinishLaunchingNotification },
443            move |notification| {
444                let _entered = debug_span!("NSApplicationDidFinishLaunchingNotification").entered();
445                if let Some(app_state) = weak_app_state.upgrade() {
446                    app_state.did_finish_launching(notification);
447                }
448            },
449        );
450
451        let weak_app_state = Rc::downgrade(&app_state);
452        let _will_terminate_observer = create_observer(
453            &center,
454            // `applicationWillTerminate:`
455            unsafe { NSApplicationWillTerminateNotification },
456            move |notification| {
457                let _entered = debug_span!("NSApplicationWillTerminateNotification").entered();
458                if let Some(app_state) = weak_app_state.upgrade() {
459                    app_state.will_terminate(notification);
460                }
461            },
462        );
463
464        let main_loop = MainRunLoop::get(mtm);
465        let mode = unsafe { kCFRunLoopCommonModes }.unwrap();
466
467        // Tracing observers have the lowest and highest orderings.
468        let _tracing_observers = tracing_observers(mtm).inspect(|(start, end)| {
469            main_loop.add_observer(start, mode);
470            main_loop.add_observer(end, mode);
471        });
472
473        let app_state_clone = Rc::clone(&app_state);
474        let _before_waiting_observer = MainRunLoopObserver::new(
475            mtm,
476            CFRunLoopActivity::BeforeWaiting,
477            true,
478            // Queued with the second-lowest priority (tracing observers use the lowest) to ensure
479            // it is processed after other observers.
480            CFIndex::MAX - 1,
481            move |_| app_state_clone.cleared(),
482        );
483        main_loop.add_observer(&_before_waiting_observer, mode);
484
485        let app_state_clone = Rc::clone(&app_state);
486        let _after_waiting_observer = MainRunLoopObserver::new(
487            mtm,
488            CFRunLoopActivity::AfterWaiting,
489            true,
490            // Queued with the second-highest priority (tracing observers use the highest) to
491            // ensure it is processed before other observers.
492            CFIndex::MIN + 1,
493            move |_| app_state_clone.wakeup(),
494        );
495        main_loop.add_observer(&_after_waiting_observer, mode);
496
497        Ok(EventLoop {
498            app,
499            app_state: app_state.clone(),
500            window_target: ActiveEventLoop { app_state, mtm },
501            _did_finish_launching_observer,
502            _will_terminate_observer,
503            _tracing_observers,
504            _before_waiting_observer,
505            _after_waiting_observer,
506        })
507    }
508
509    pub fn window_target(&self) -> &dyn RootActiveEventLoop {
510        &self.window_target
511    }
512
513    // NB: we don't base this on `pump_events` because for `MacOs` we can't support
514    // `pump_events` elegantly (we just ask to run the loop for a "short" amount of
515    // time and so a layered implementation would end up using a lot of CPU due to
516    // redundant wake ups.
517    pub fn run_app_on_demand<A: ApplicationHandler>(
518        &mut self,
519        app: A,
520    ) -> Result<(), EventLoopError> {
521        self.app_state.clear_exit();
522        self.app_state.set_event_handler(app, || {
523            autoreleasepool(|_| {
524                // clear / normalize pump_events state
525                self.app_state.set_wait_timeout(None);
526                self.app_state.set_stop_before_wait(false);
527                self.app_state.set_stop_after_wait(false);
528                self.app_state.set_stop_on_redraw(false);
529
530                if self.app_state.is_launched() {
531                    debug_assert!(!self.app_state.is_running());
532                    self.app_state.set_is_running(true);
533                    self.app_state.dispatch_init_events();
534                }
535
536                // NOTE: Make sure to not run the application re-entrantly, as that'd be confusing.
537                self.app.run();
538
539                self.app_state.internal_exit()
540            })
541        });
542
543        Ok(())
544    }
545
546    pub fn pump_app_events<A: ApplicationHandler>(
547        &mut self,
548        timeout: Option<Duration>,
549        app: A,
550    ) -> PumpStatus {
551        self.app_state.set_event_handler(app, || {
552            autoreleasepool(|_| {
553                // As a special case, if the application hasn't been launched yet then we at least
554                // run the loop until it has fully launched.
555                if !self.app_state.is_launched() {
556                    debug_assert!(!self.app_state.is_running());
557
558                    self.app_state.set_stop_on_launch();
559                    self.app.run();
560
561                    // Note: we dispatch `NewEvents(Init)` + `Resumed` events after the application
562                    // has launched
563                } else if !self.app_state.is_running() {
564                    // Even though the application may have been launched, it's possible we aren't
565                    // running if the `EventLoop` was run before and has since
566                    // exited. This indicates that we just starting to re-run
567                    // the same `EventLoop` again.
568                    self.app_state.set_is_running(true);
569                    self.app_state.dispatch_init_events();
570                } else {
571                    // Only run for as long as the given `Duration` allows so we don't block the
572                    // external loop.
573                    match timeout {
574                        Some(Duration::ZERO) => {
575                            self.app_state.set_wait_timeout(None);
576                            self.app_state.set_stop_before_wait(true);
577                        },
578                        Some(duration) => {
579                            self.app_state.set_stop_before_wait(false);
580                            let timeout = Instant::now() + duration;
581                            self.app_state.set_wait_timeout(Some(timeout));
582                            self.app_state.set_stop_after_wait(true);
583                        },
584                        None => {
585                            self.app_state.set_wait_timeout(None);
586                            self.app_state.set_stop_before_wait(false);
587                            self.app_state.set_stop_after_wait(true);
588                        },
589                    }
590                    self.app_state.set_stop_on_redraw(true);
591                    self.app.run();
592                }
593
594                if self.app_state.exiting() {
595                    self.app_state.internal_exit();
596                    PumpStatus::Exit(0)
597                } else {
598                    PumpStatus::Continue
599                }
600            })
601        })
602    }
603}
604
605impl EventLoopProvider for EventLoop {
606    fn run_app<A: ApplicationHandler + 'static>(
607        mut self,
608        mut app: A,
609    ) -> Result<(), EventLoopError> {
610        let result = self.run_app_on_demand(&mut app);
611        // SAFETY: unsure that the state is dropped before the exit from the event loop.
612        drop(app);
613        result
614    }
615
616    fn create_proxy(&self) -> CoreEventLoopProxy {
617        self.window_target().create_proxy()
618    }
619
620    fn owned_display_handle(&self) -> CoreOwnedDisplayHandle {
621        self.window_target().owned_display_handle()
622    }
623
624    fn listen_device_events(&self, allowed: DeviceEvents) {
625        self.window_target().listen_device_events(allowed);
626    }
627
628    fn set_control_flow(&self, control_flow: ControlFlow) {
629        self.window_target().set_control_flow(control_flow);
630    }
631
632    fn create_custom_cursor(
633        &self,
634        custom_cursor: CustomCursorSource,
635    ) -> Result<CoreCustomCursor, CustomCursorError> {
636        self.window_target().create_custom_cursor(custom_cursor)
637    }
638}
639
640pub(crate) struct OwnedDisplayHandle;
641
642impl HasDisplayHandle for OwnedDisplayHandle {
643    fn display_handle(&self) -> Result<rwh_06::DisplayHandle<'_>, rwh_06::HandleError> {
644        let raw = rwh_06::RawDisplayHandle::AppKit(rwh_06::AppKitDisplayHandle::new());
645        unsafe { Ok(rwh_06::DisplayHandle::borrow_raw(raw)) }
646    }
647}
648
649pub(super) fn stop_app_immediately(app: &NSApplication) {
650    autoreleasepool(|_| {
651        app.stop(None);
652        // To stop event loop immediately, we need to post some event here.
653        // See: https://stackoverflow.com/questions/48041279/stopping-the-nsapplication-main-event-loop/48064752#48064752
654        app.postEvent_atStart(&dummy_event().unwrap(), true);
655    });
656}
657
658/// Tell all windows to close.
659///
660/// This will synchronously trigger `WindowEvent::Destroyed` within
661/// `windowWillClose:`, giving the application one last chance to handle
662/// those events. It doesn't matter if the user also ends up closing the
663/// windows in `Window`'s `Drop` impl, once a window has been closed once, it
664/// stays closed.
665///
666/// This ensures that no windows linger on after the event loop has exited,
667/// see <https://github.com/rust-windowing/winit/issues/4135>.
668pub(super) fn notify_windows_of_exit(app: &NSApplication) {
669    for window in app.windows() {
670        window.close();
671    }
672}