use std::any::Any; use std::cell::Cell; use std::collections::VecDeque; use std::marker::PhantomData; use std::os::raw::c_void; use std::panic::{catch_unwind, resume_unwind, RefUnwindSafe, UnwindSafe}; use std::ptr; use std::rc::{Rc, Weak}; use std::sync::atomic::{AtomicBool, Ordering as AtomicOrdering}; use std::sync::Arc; use std::time::{Duration, Instant}; use core_foundation::base::{CFIndex, CFRelease}; use core_foundation::runloop::{ kCFRunLoopCommonModes, CFRunLoopAddSource, CFRunLoopGetMain, CFRunLoopSourceContext, CFRunLoopSourceCreate, CFRunLoopSourceRef, CFRunLoopSourceSignal, CFRunLoopWakeUp, }; use objc2::rc::{autoreleasepool, Retained}; use objc2::runtime::ProtocolObject; use objc2::{msg_send_id, ClassType}; use objc2_app_kit::{NSApplication, NSApplicationActivationPolicy, NSWindow}; use objc2_foundation::{MainThreadMarker, NSObjectProtocol}; use super::app::WinitApplication; use super::app_state::ApplicationDelegate; use super::event::dummy_event; use super::monitor::{self, MonitorHandle}; use super::observer::setup_control_flow_observers; use crate::application::ApplicationHandler; use crate::error::EventLoopError; use crate::event_loop::{ActiveEventLoop as RootWindowTarget, ControlFlow, DeviceEvents}; use crate::platform::macos::ActivationPolicy; use crate::platform::pump_events::PumpStatus; use crate::platform_impl::platform::cursor::CustomCursor; use crate::window::{CustomCursor as RootCustomCursor, CustomCursorSource}; #[derive(Default)] pub struct PanicInfo { inner: Cell>>, } // WARNING: // As long as this struct is used through its `impl`, it is UnwindSafe. // (If `get_mut` is called on `inner`, unwind safety may get broken.) impl UnwindSafe for PanicInfo {} impl RefUnwindSafe for PanicInfo {} impl PanicInfo { pub fn is_panicking(&self) -> bool { let inner = self.inner.take(); let result = inner.is_some(); self.inner.set(inner); result } /// Overwrites the current state if the current state is not panicking pub fn set_panic(&self, p: Box) { if !self.is_panicking() { self.inner.set(Some(p)); } } pub fn take(&self) -> Option> { self.inner.take() } } #[derive(Debug)] pub struct ActiveEventLoop { delegate: Retained, pub(super) mtm: MainThreadMarker, } impl ActiveEventLoop { pub(super) fn new_root(delegate: Retained) -> RootWindowTarget { let mtm = MainThreadMarker::from(&*delegate); let p = Self { delegate, mtm }; RootWindowTarget { p, _marker: PhantomData } } pub(super) fn app_delegate(&self) -> &ApplicationDelegate { &self.delegate } pub fn create_custom_cursor(&self, source: CustomCursorSource) -> RootCustomCursor { RootCustomCursor { inner: CustomCursor::new(source.inner) } } #[inline] pub fn available_monitors(&self) -> VecDeque { monitor::available_monitors() } #[inline] pub fn primary_monitor(&self) -> Option { let monitor = monitor::primary_monitor(); Some(monitor) } #[inline] pub fn listen_device_events(&self, _allowed: DeviceEvents) {} #[cfg(feature = "rwh_05")] #[inline] pub fn raw_display_handle_rwh_05(&self) -> rwh_05::RawDisplayHandle { rwh_05::RawDisplayHandle::AppKit(rwh_05::AppKitDisplayHandle::empty()) } #[cfg(feature = "rwh_06")] #[inline] pub fn raw_display_handle_rwh_06( &self, ) -> Result { Ok(rwh_06::RawDisplayHandle::AppKit(rwh_06::AppKitDisplayHandle::new())) } pub(crate) fn set_control_flow(&self, control_flow: ControlFlow) { self.delegate.set_control_flow(control_flow) } pub(crate) fn control_flow(&self) -> ControlFlow { self.delegate.control_flow() } pub(crate) fn exit(&self) { self.delegate.exit() } pub(crate) fn clear_exit(&self) { self.delegate.clear_exit() } pub(crate) fn exiting(&self) -> bool { self.delegate.exiting() } pub(crate) fn owned_display_handle(&self) -> OwnedDisplayHandle { OwnedDisplayHandle } pub(crate) fn hide_application(&self) { NSApplication::sharedApplication(self.mtm).hide(None) } pub(crate) fn hide_other_applications(&self) { NSApplication::sharedApplication(self.mtm).hideOtherApplications(None) } pub(crate) fn set_allows_automatic_window_tabbing(&self, enabled: bool) { NSWindow::setAllowsAutomaticWindowTabbing(enabled, self.mtm) } pub(crate) fn allows_automatic_window_tabbing(&self) -> bool { NSWindow::allowsAutomaticWindowTabbing(self.mtm) } } pub struct EventLoop { /// Store a reference to the application for convenience. /// /// We intentionally don't store `WinitApplication` since we want to have /// the possibility of swapping that out at some point. app: Retained, /// The application delegate that we've registered. /// /// The delegate is only weakly referenced by NSApplication, so we must /// keep it around here as well. delegate: Retained, proxy_wake_up: Arc, window_target: RootWindowTarget, panic_info: Rc, } #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] pub(crate) struct PlatformSpecificEventLoopAttributes { pub(crate) activation_policy: ActivationPolicy, pub(crate) default_menu: bool, pub(crate) activate_ignoring_other_apps: bool, } impl Default for PlatformSpecificEventLoopAttributes { fn default() -> Self { Self { activation_policy: Default::default(), // Regular default_menu: true, activate_ignoring_other_apps: true, } } } impl EventLoop { pub(crate) fn new( attributes: &PlatformSpecificEventLoopAttributes, ) -> Result { let mtm = MainThreadMarker::new() .expect("on macOS, `EventLoop` must be created on the main thread!"); let app: Retained = unsafe { msg_send_id![WinitApplication::class(), sharedApplication] }; if !app.is_kind_of::() { panic!( "`winit` requires control over the principal class. You must create the event \ loop before other parts of your application initialize NSApplication" ); } let activation_policy = match attributes.activation_policy { ActivationPolicy::Regular => NSApplicationActivationPolicy::Regular, ActivationPolicy::Accessory => NSApplicationActivationPolicy::Accessory, ActivationPolicy::Prohibited => NSApplicationActivationPolicy::Prohibited, }; let proxy_wake_up = Arc::new(AtomicBool::new(false)); let delegate = ApplicationDelegate::new( mtm, activation_policy, proxy_wake_up.clone(), attributes.default_menu, attributes.activate_ignoring_other_apps, ); autoreleasepool(|_| { app.setDelegate(Some(ProtocolObject::from_ref(&*delegate))); }); let panic_info: Rc = Default::default(); setup_control_flow_observers(mtm, Rc::downgrade(&panic_info)); Ok(EventLoop { app, delegate: delegate.clone(), window_target: RootWindowTarget { p: ActiveEventLoop { delegate, mtm }, _marker: PhantomData, }, proxy_wake_up, panic_info, }) } pub fn window_target(&self) -> &RootWindowTarget { &self.window_target } pub fn run_app(mut self, app: &mut A) -> Result<(), EventLoopError> { self.run_app_on_demand(app) } // NB: we don't base this on `pump_events` because for `MacOs` we can't support // `pump_events` elegantly (we just ask to run the loop for a "short" amount of // time and so a layered implementation would end up using a lot of CPU due to // redundant wake ups. pub fn run_app_on_demand( &mut self, app: &mut A, ) -> Result<(), EventLoopError> { self.delegate.set_event_handler(app, || { autoreleasepool(|_| { // clear / normalize pump_events state self.delegate.set_wait_timeout(None); self.delegate.set_stop_before_wait(false); self.delegate.set_stop_after_wait(false); self.delegate.set_stop_on_redraw(false); if self.delegate.is_launched() { debug_assert!(!self.delegate.is_running()); self.delegate.set_is_running(true); self.delegate.dispatch_init_events(); } // SAFETY: We do not run the application re-entrantly unsafe { self.app.run() }; // While the app is running it's possible that we catch a panic // to avoid unwinding across an objective-c ffi boundary, which // will lead to us stopping the `NSApplication` and saving the // `PanicInfo` so that we can resume the unwind at a controlled, // safe point in time. if let Some(panic) = self.panic_info.take() { resume_unwind(panic); } self.delegate.internal_exit() }) }); Ok(()) } pub fn pump_app_events( &mut self, timeout: Option, app: &mut A, ) -> PumpStatus { self.delegate.set_event_handler(app, || { autoreleasepool(|_| { // As a special case, if the application hasn't been launched yet then we at least // run the loop until it has fully launched. if !self.delegate.is_launched() { debug_assert!(!self.delegate.is_running()); self.delegate.set_stop_on_launch(); // SAFETY: We do not run the application re-entrantly unsafe { self.app.run() }; // Note: we dispatch `NewEvents(Init)` + `Resumed` events after the application // has launched } else if !self.delegate.is_running() { // Even though the application may have been launched, it's possible we aren't // running if the `EventLoop` was run before and has since // exited. This indicates that we just starting to re-run // the same `EventLoop` again. self.delegate.set_is_running(true); self.delegate.dispatch_init_events(); } else { // Only run for as long as the given `Duration` allows so we don't block the // external loop. match timeout { Some(Duration::ZERO) => { self.delegate.set_wait_timeout(None); self.delegate.set_stop_before_wait(true); }, Some(duration) => { self.delegate.set_stop_before_wait(false); let timeout = Instant::now() + duration; self.delegate.set_wait_timeout(Some(timeout)); self.delegate.set_stop_after_wait(true); }, None => { self.delegate.set_wait_timeout(None); self.delegate.set_stop_before_wait(false); self.delegate.set_stop_after_wait(true); }, } self.delegate.set_stop_on_redraw(true); // SAFETY: We do not run the application re-entrantly unsafe { self.app.run() }; } // While the app is running it's possible that we catch a panic // to avoid unwinding across an objective-c ffi boundary, which // will lead to us stopping the application and saving the // `PanicInfo` so that we can resume the unwind at a controlled, // safe point in time. if let Some(panic) = self.panic_info.take() { resume_unwind(panic); } if self.delegate.exiting() { self.delegate.internal_exit(); PumpStatus::Exit(0) } else { PumpStatus::Continue } }) }) } pub fn create_proxy(&self) -> EventLoopProxy { EventLoopProxy::new(self.proxy_wake_up.clone()) } } #[derive(Clone)] pub(crate) struct OwnedDisplayHandle; impl OwnedDisplayHandle { #[cfg(feature = "rwh_05")] #[inline] pub fn raw_display_handle_rwh_05(&self) -> rwh_05::RawDisplayHandle { rwh_05::AppKitDisplayHandle::empty().into() } #[cfg(feature = "rwh_06")] #[inline] pub fn raw_display_handle_rwh_06( &self, ) -> Result { Ok(rwh_06::AppKitDisplayHandle::new().into()) } } pub(super) fn stop_app_immediately(app: &NSApplication) { autoreleasepool(|_| { app.stop(None); // To stop event loop immediately, we need to post some event here. // See: https://stackoverflow.com/questions/48041279/stopping-the-nsapplication-main-event-loop/48064752#48064752 app.postEvent_atStart(&dummy_event().unwrap(), true); }); } /// Catches panics that happen inside `f` and when a panic /// happens, stops the `sharedApplication` #[inline] pub fn stop_app_on_panic R + UnwindSafe, R>( mtm: MainThreadMarker, panic_info: Weak, f: F, ) -> Option { match catch_unwind(f) { Ok(r) => Some(r), Err(e) => { // It's important that we set the panic before requesting a `stop` // because some callback are still called during the `stop` message // and we need to know in those callbacks if the application is currently // panicking { let panic_info = panic_info.upgrade().unwrap(); panic_info.set_panic(e); } let app = NSApplication::sharedApplication(mtm); stop_app_immediately(&app); None }, } } pub struct EventLoopProxy { proxy_wake_up: Arc, source: CFRunLoopSourceRef, } unsafe impl Send for EventLoopProxy {} unsafe impl Sync for EventLoopProxy {} impl Drop for EventLoopProxy { fn drop(&mut self) { unsafe { CFRelease(self.source as _); } } } impl Clone for EventLoopProxy { fn clone(&self) -> Self { EventLoopProxy::new(self.proxy_wake_up.clone()) } } impl EventLoopProxy { fn new(proxy_wake_up: Arc) -> Self { unsafe { // just wake up the eventloop extern "C" fn event_loop_proxy_handler(_: *const c_void) {} // adding a Source to the main CFRunLoop lets us wake it up and // process user events through the normal OS EventLoop mechanisms. let rl = CFRunLoopGetMain(); let mut context = CFRunLoopSourceContext { version: 0, info: ptr::null_mut(), retain: None, release: None, copyDescription: None, equal: None, hash: None, schedule: None, cancel: None, perform: event_loop_proxy_handler, }; let source = CFRunLoopSourceCreate(ptr::null_mut(), CFIndex::MAX - 1, &mut context); CFRunLoopAddSource(rl, source, kCFRunLoopCommonModes); CFRunLoopWakeUp(rl); EventLoopProxy { proxy_wake_up, source } } } pub fn wake_up(&self) { self.proxy_wake_up.store(true, AtomicOrdering::Relaxed); unsafe { // let the main thread know there's a new event CFRunLoopSourceSignal(self.source); let rl = CFRunLoopGetMain(); CFRunLoopWakeUp(rl); } } }