Skip to main content

winit_appkit/
event_loop.rs

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