winit_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::{MainThreadMarker, available};
8use objc2_app_kit::{
9    NSApplication, NSApplicationActivationPolicy, NSApplicationDidFinishLaunchingNotification,
10    NSApplicationWillTerminateNotification, NSWindow,
11};
12use objc2_foundation::{NSNotificationCenter, NSObjectProtocol};
13use rwh_06::HasDisplayHandle;
14use winit_core::application::ApplicationHandler;
15use winit_core::cursor::{CustomCursor as CoreCustomCursor, CustomCursorSource};
16use winit_core::error::{EventLoopError, RequestError};
17use winit_core::event_loop::pump_events::PumpStatus;
18use winit_core::event_loop::{
19    ActiveEventLoop as RootActiveEventLoop, ControlFlow, DeviceEvents,
20    EventLoopProxy as CoreEventLoopProxy, OwnedDisplayHandle as CoreOwnedDisplayHandle,
21};
22use winit_core::monitor::MonitorHandle as CoreMonitorHandle;
23use winit_core::window::Theme;
24
25use super::app::override_send_event;
26use super::app_state::AppState;
27use super::cursor::CustomCursor;
28use super::event::dummy_event;
29use super::monitor;
30use super::notification_center::create_observer;
31use super::observer::setup_control_flow_observers;
32use crate::ActivationPolicy;
33use crate::window::Window;
34
35#[derive(Debug)]
36pub struct ActiveEventLoop {
37    pub(super) app_state: Rc<AppState>,
38    pub(super) mtm: MainThreadMarker,
39}
40
41impl ActiveEventLoop {
42    pub(crate) fn hide_application(&self) {
43        NSApplication::sharedApplication(self.mtm).hide(None)
44    }
45
46    pub(crate) fn hide_other_applications(&self) {
47        NSApplication::sharedApplication(self.mtm).hideOtherApplications(None)
48    }
49
50    pub(crate) fn set_allows_automatic_window_tabbing(&self, enabled: bool) {
51        NSWindow::setAllowsAutomaticWindowTabbing(enabled, self.mtm)
52    }
53
54    pub(crate) fn allows_automatic_window_tabbing(&self) -> bool {
55        NSWindow::allowsAutomaticWindowTabbing(self.mtm)
56    }
57}
58
59impl RootActiveEventLoop for ActiveEventLoop {
60    fn create_proxy(&self) -> CoreEventLoopProxy {
61        CoreEventLoopProxy::new(self.app_state.event_loop_proxy().clone())
62    }
63
64    fn create_window(
65        &self,
66        window_attributes: winit_core::window::WindowAttributes,
67    ) -> Result<Box<dyn winit_core::window::Window>, RequestError> {
68        Ok(Box::new(Window::new(self, window_attributes)?))
69    }
70
71    fn create_custom_cursor(
72        &self,
73        source: CustomCursorSource,
74    ) -> Result<CoreCustomCursor, RequestError> {
75        Ok(CoreCustomCursor(Arc::new(CustomCursor::new(source)?)))
76    }
77
78    fn available_monitors(&self) -> Box<dyn Iterator<Item = CoreMonitorHandle>> {
79        Box::new(
80            monitor::available_monitors()
81                .into_iter()
82                .map(|monitor| CoreMonitorHandle(Arc::new(monitor))),
83        )
84    }
85
86    fn primary_monitor(&self) -> Option<winit_core::monitor::MonitorHandle> {
87        let monitor = monitor::primary_monitor();
88        Some(CoreMonitorHandle(Arc::new(monitor)))
89    }
90
91    fn listen_device_events(&self, _allowed: DeviceEvents) {}
92
93    fn system_theme(&self) -> Option<Theme> {
94        let app = NSApplication::sharedApplication(self.mtm);
95
96        // Dark appearance was introduced in macOS 10.14
97        if available!(macos = 10.14) {
98            Some(super::window_delegate::appearance_to_theme(&app.effectiveAppearance()))
99        } else {
100            Some(Theme::Light)
101        }
102    }
103
104    fn set_control_flow(&self, control_flow: ControlFlow) {
105        self.app_state.set_control_flow(control_flow)
106    }
107
108    fn control_flow(&self) -> ControlFlow {
109        self.app_state.control_flow()
110    }
111
112    fn exit(&self) {
113        self.app_state.exit()
114    }
115
116    fn exiting(&self) -> bool {
117        self.app_state.exiting()
118    }
119
120    fn owned_display_handle(&self) -> CoreOwnedDisplayHandle {
121        CoreOwnedDisplayHandle::new(Arc::new(OwnedDisplayHandle))
122    }
123
124    fn rwh_06_handle(&self) -> &dyn rwh_06::HasDisplayHandle {
125        self
126    }
127}
128
129impl rwh_06::HasDisplayHandle for ActiveEventLoop {
130    fn display_handle(&self) -> Result<rwh_06::DisplayHandle<'_>, rwh_06::HandleError> {
131        let raw = rwh_06::RawDisplayHandle::AppKit(rwh_06::AppKitDisplayHandle::new());
132        unsafe { Ok(rwh_06::DisplayHandle::borrow_raw(raw)) }
133    }
134}
135
136#[derive(Debug)]
137pub struct EventLoop {
138    /// Store a reference to the application for convenience.
139    ///
140    /// We intentionally don't store `WinitApplication` since we want to have
141    /// the possibility of swapping that out at some point.
142    app: Retained<NSApplication>,
143    app_state: Rc<AppState>,
144
145    window_target: ActiveEventLoop,
146
147    // Since macOS 10.11, we no longer need to remove the observers before they are deallocated;
148    // the system instead cleans it up next time it would have posted a notification to it.
149    //
150    // Though we do still need to keep the observers around to prevent them from being deallocated.
151    _did_finish_launching_observer: Retained<ProtocolObject<dyn NSObjectProtocol>>,
152    _will_terminate_observer: Retained<ProtocolObject<dyn NSObjectProtocol>>,
153}
154
155#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
156pub struct PlatformSpecificEventLoopAttributes {
157    pub activation_policy: Option<ActivationPolicy>,
158    pub default_menu: bool,
159    pub activate_ignoring_other_apps: bool,
160}
161
162impl Default for PlatformSpecificEventLoopAttributes {
163    fn default() -> Self {
164        Self { activation_policy: None, default_menu: true, activate_ignoring_other_apps: true }
165    }
166}
167
168impl EventLoop {
169    pub fn new(attributes: &PlatformSpecificEventLoopAttributes) -> Result<Self, EventLoopError> {
170        let mtm = MainThreadMarker::new()
171            .expect("on macOS, `EventLoop` must be created on the main thread!");
172
173        let activation_policy = match attributes.activation_policy {
174            None => None,
175            Some(ActivationPolicy::Regular) => Some(NSApplicationActivationPolicy::Regular),
176            Some(ActivationPolicy::Accessory) => Some(NSApplicationActivationPolicy::Accessory),
177            Some(ActivationPolicy::Prohibited) => Some(NSApplicationActivationPolicy::Prohibited),
178        };
179
180        let app_state = AppState::setup_global(
181            mtm,
182            activation_policy,
183            attributes.default_menu,
184            attributes.activate_ignoring_other_apps,
185        )
186        .ok_or_else(|| EventLoopError::RecreationAttempt)?;
187
188        // Initialize the application (if it has not already been).
189        let app = NSApplication::sharedApplication(mtm);
190
191        // Override `sendEvent:` on the application to forward to our application state.
192        override_send_event(&app);
193
194        let center = NSNotificationCenter::defaultCenter();
195
196        let weak_app_state = Rc::downgrade(&app_state);
197        let _did_finish_launching_observer = create_observer(
198            &center,
199            // `applicationDidFinishLaunching:`
200            unsafe { NSApplicationDidFinishLaunchingNotification },
201            move |notification| {
202                if let Some(app_state) = weak_app_state.upgrade() {
203                    app_state.did_finish_launching(notification);
204                }
205            },
206        );
207
208        let weak_app_state = Rc::downgrade(&app_state);
209        let _will_terminate_observer = create_observer(
210            &center,
211            // `applicationWillTerminate:`
212            unsafe { NSApplicationWillTerminateNotification },
213            move |notification| {
214                if let Some(app_state) = weak_app_state.upgrade() {
215                    app_state.will_terminate(notification);
216                }
217            },
218        );
219
220        setup_control_flow_observers(mtm);
221
222        Ok(EventLoop {
223            app,
224            app_state: app_state.clone(),
225            window_target: ActiveEventLoop { app_state, mtm },
226            _did_finish_launching_observer,
227            _will_terminate_observer,
228        })
229    }
230
231    pub fn window_target(&self) -> &dyn RootActiveEventLoop {
232        &self.window_target
233    }
234
235    // NB: we don't base this on `pump_events` because for `MacOs` we can't support
236    // `pump_events` elegantly (we just ask to run the loop for a "short" amount of
237    // time and so a layered implementation would end up using a lot of CPU due to
238    // redundant wake ups.
239    pub fn run_app_on_demand<A: ApplicationHandler>(
240        &mut self,
241        app: A,
242    ) -> Result<(), EventLoopError> {
243        self.app_state.clear_exit();
244        self.app_state.set_event_handler(app, || {
245            autoreleasepool(|_| {
246                // clear / normalize pump_events state
247                self.app_state.set_wait_timeout(None);
248                self.app_state.set_stop_before_wait(false);
249                self.app_state.set_stop_after_wait(false);
250                self.app_state.set_stop_on_redraw(false);
251
252                if self.app_state.is_launched() {
253                    debug_assert!(!self.app_state.is_running());
254                    self.app_state.set_is_running(true);
255                    self.app_state.dispatch_init_events();
256                }
257
258                // NOTE: Make sure to not run the application re-entrantly, as that'd be confusing.
259                self.app.run();
260
261                self.app_state.internal_exit()
262            })
263        });
264
265        Ok(())
266    }
267
268    pub fn pump_app_events<A: ApplicationHandler>(
269        &mut self,
270        timeout: Option<Duration>,
271        app: A,
272    ) -> PumpStatus {
273        self.app_state.set_event_handler(app, || {
274            autoreleasepool(|_| {
275                // As a special case, if the application hasn't been launched yet then we at least
276                // run the loop until it has fully launched.
277                if !self.app_state.is_launched() {
278                    debug_assert!(!self.app_state.is_running());
279
280                    self.app_state.set_stop_on_launch();
281                    self.app.run();
282
283                    // Note: we dispatch `NewEvents(Init)` + `Resumed` events after the application
284                    // has launched
285                } else if !self.app_state.is_running() {
286                    // Even though the application may have been launched, it's possible we aren't
287                    // running if the `EventLoop` was run before and has since
288                    // exited. This indicates that we just starting to re-run
289                    // the same `EventLoop` again.
290                    self.app_state.set_is_running(true);
291                    self.app_state.dispatch_init_events();
292                } else {
293                    // Only run for as long as the given `Duration` allows so we don't block the
294                    // external loop.
295                    match timeout {
296                        Some(Duration::ZERO) => {
297                            self.app_state.set_wait_timeout(None);
298                            self.app_state.set_stop_before_wait(true);
299                        },
300                        Some(duration) => {
301                            self.app_state.set_stop_before_wait(false);
302                            let timeout = Instant::now() + duration;
303                            self.app_state.set_wait_timeout(Some(timeout));
304                            self.app_state.set_stop_after_wait(true);
305                        },
306                        None => {
307                            self.app_state.set_wait_timeout(None);
308                            self.app_state.set_stop_before_wait(false);
309                            self.app_state.set_stop_after_wait(true);
310                        },
311                    }
312                    self.app_state.set_stop_on_redraw(true);
313                    self.app.run();
314                }
315
316                if self.app_state.exiting() {
317                    self.app_state.internal_exit();
318                    PumpStatus::Exit(0)
319                } else {
320                    PumpStatus::Continue
321                }
322            })
323        })
324    }
325}
326
327pub(crate) struct OwnedDisplayHandle;
328
329impl HasDisplayHandle for OwnedDisplayHandle {
330    fn display_handle(&self) -> Result<rwh_06::DisplayHandle<'_>, rwh_06::HandleError> {
331        let raw = rwh_06::RawDisplayHandle::AppKit(rwh_06::AppKitDisplayHandle::new());
332        unsafe { Ok(rwh_06::DisplayHandle::borrow_raw(raw)) }
333    }
334}
335
336pub(super) fn stop_app_immediately(app: &NSApplication) {
337    autoreleasepool(|_| {
338        app.stop(None);
339        // To stop event loop immediately, we need to post some event here.
340        // See: https://stackoverflow.com/questions/48041279/stopping-the-nsapplication-main-event-loop/48064752#48064752
341        app.postEvent_atStart(&dummy_event().unwrap(), true);
342    });
343}
344
345/// Tell all windows to close.
346///
347/// This will synchronously trigger `WindowEvent::Destroyed` within
348/// `windowWillClose:`, giving the application one last chance to handle
349/// those events. It doesn't matter if the user also ends up closing the
350/// windows in `Window`'s `Drop` impl, once a window has been closed once, it
351/// stays closed.
352///
353/// This ensures that no windows linger on after the event loop has exited,
354/// see <https://github.com/rust-windowing/winit/issues/4135>.
355pub(super) fn notify_windows_of_exit(app: &NSApplication) {
356    for window in app.windows() {
357        window.close();
358    }
359}