use std::ffi::c_void; use std::ptr; use std::sync::Arc; use objc2::rc::Retained; use objc2::runtime::ProtocolObject; use objc2::{msg_send, ClassType, MainThreadMarker}; use objc2_core_foundation::{ kCFRunLoopDefaultMode, CFIndex, CFRunLoop, CFRunLoopActivity, CFRunLoopObserver, }; use objc2_foundation::{NSNotificationCenter, NSObjectProtocol}; use objc2_ui_kit::{ UIApplication, UIApplicationDidBecomeActiveNotification, UIApplicationDidEnterBackgroundNotification, UIApplicationDidFinishLaunchingNotification, UIApplicationDidReceiveMemoryWarningNotification, UIApplicationWillEnterForegroundNotification, UIApplicationWillResignActiveNotification, UIApplicationWillTerminateNotification, UIScreen, }; use rwh_06::HasDisplayHandle; use winit_core::application::ApplicationHandler; use winit_core::cursor::{CustomCursor, CustomCursorSource}; use winit_core::error::{EventLoopError, NotSupportedError, RequestError}; use winit_core::event_loop::{ ActiveEventLoop as RootActiveEventLoop, ControlFlow, DeviceEvents, EventLoopProxy as CoreEventLoopProxy, OwnedDisplayHandle as CoreOwnedDisplayHandle, }; use winit_core::monitor::MonitorHandle as CoreMonitorHandle; use winit_core::window::{Theme, Window as CoreWindow}; use super::app_state::{send_occluded_event_for_all_windows, AppState}; use super::notification_center::create_observer; use crate::monitor::MonitorHandle; use crate::window::Window; use crate::{app_state, monitor}; #[derive(Debug)] pub struct ActiveEventLoop { pub(super) mtm: MainThreadMarker, } impl RootActiveEventLoop for ActiveEventLoop { fn create_proxy(&self) -> CoreEventLoopProxy { CoreEventLoopProxy::new(AppState::get(self.mtm).event_loop_proxy().clone()) } fn create_window( &self, window_attributes: winit_core::window::WindowAttributes, ) -> Result, RequestError> { Ok(Box::new(Window::new(self, window_attributes)?)) } fn create_custom_cursor( &self, _source: CustomCursorSource, ) -> Result { Err(NotSupportedError::new("create_custom_cursor is not supported").into()) } fn available_monitors(&self) -> Box> { Box::new( monitor::uiscreens(self.mtm) .into_iter() .map(|monitor| CoreMonitorHandle(Arc::new(monitor))), ) } fn primary_monitor(&self) -> Option { #[allow(deprecated)] let monitor = MonitorHandle::new(UIScreen::mainScreen(self.mtm)); Some(CoreMonitorHandle(Arc::new(monitor))) } fn listen_device_events(&self, _allowed: DeviceEvents) {} fn set_control_flow(&self, control_flow: ControlFlow) { AppState::get(self.mtm).set_control_flow(control_flow) } fn system_theme(&self) -> Option { None } fn control_flow(&self) -> ControlFlow { AppState::get(self.mtm).control_flow() } fn exit(&self) { // https://developer.apple.com/library/archive/qa/qa1561/_index.html // it is not possible to quit an iOS app gracefully and programmatically tracing::warn!("`ControlFlow::Exit` ignored on iOS"); } fn exiting(&self) -> bool { false } fn owned_display_handle(&self) -> CoreOwnedDisplayHandle { CoreOwnedDisplayHandle::new(Arc::new(OwnedDisplayHandle)) } fn rwh_06_handle(&self) -> &dyn rwh_06::HasDisplayHandle { self } } impl rwh_06::HasDisplayHandle for ActiveEventLoop { fn display_handle(&self) -> Result, rwh_06::HandleError> { let raw = rwh_06::RawDisplayHandle::UiKit(rwh_06::UiKitDisplayHandle::new()); unsafe { Ok(rwh_06::DisplayHandle::borrow_raw(raw)) } } } #[derive(Clone, PartialEq, Eq)] pub(crate) struct OwnedDisplayHandle; impl HasDisplayHandle for OwnedDisplayHandle { fn display_handle(&self) -> Result, rwh_06::HandleError> { let raw = rwh_06::RawDisplayHandle::UiKit(rwh_06::UiKitDisplayHandle::new()); unsafe { Ok(rwh_06::DisplayHandle::borrow_raw(raw)) } } } #[derive(Debug)] pub struct EventLoop { mtm: MainThreadMarker, window_target: ActiveEventLoop, // Since iOS 9.0, we no longer need to remove the observers before they are deallocated; the // system instead cleans it up next time it would have posted a notification to it. // // Though we do still need to keep the observers around to prevent them from being deallocated. _did_finish_launching_observer: Retained>, _did_become_active_observer: Retained>, _will_resign_active_observer: Retained>, _will_enter_foreground_observer: Retained>, _did_enter_background_observer: Retained>, _will_terminate_observer: Retained>, _did_receive_memory_warning_observer: Retained>, } #[derive(Default, Debug, Copy, Clone, PartialEq, Eq, Hash)] pub struct PlatformSpecificEventLoopAttributes {} impl EventLoop { pub fn new(_: &PlatformSpecificEventLoopAttributes) -> Result { let mtm = MainThreadMarker::new() .expect("On iOS, `EventLoop` must be created on the main thread"); if !AppState::setup_global(mtm) { // Required, AppState is global state, and event loop can only be run once. return Err(EventLoopError::RecreationAttempt); } // this line sets up the main run loop before `UIApplicationMain` setup_control_flow_observers(); let center = unsafe { NSNotificationCenter::defaultCenter() }; let _did_finish_launching_observer = create_observer( ¢er, // `application:didFinishLaunchingWithOptions:` unsafe { UIApplicationDidFinishLaunchingNotification }, move |_| { app_state::did_finish_launching(mtm); }, ); let _did_become_active_observer = create_observer( ¢er, // `applicationDidBecomeActive:` unsafe { UIApplicationDidBecomeActiveNotification }, move |_| app_state::handle_resumed(mtm), ); let _will_resign_active_observer = create_observer( ¢er, // `applicationWillResignActive:` unsafe { UIApplicationWillResignActiveNotification }, move |_| app_state::handle_suspended(mtm), ); let _will_enter_foreground_observer = create_observer( ¢er, // `applicationWillEnterForeground:` unsafe { UIApplicationWillEnterForegroundNotification }, move |notification| { let app = unsafe { notification.object() }.expect( "UIApplicationWillEnterForegroundNotification to have application object", ); // The `object` in `UIApplicationWillEnterForegroundNotification` is documented to // be `UIApplication`. let app = app.downcast::().unwrap(); send_occluded_event_for_all_windows(&app, false); }, ); let _did_enter_background_observer = create_observer( ¢er, // `applicationDidEnterBackground:` unsafe { UIApplicationDidEnterBackgroundNotification }, move |notification| { let app = unsafe { notification.object() }.expect( "UIApplicationDidEnterBackgroundNotification to have application object", ); // The `object` in `UIApplicationDidEnterBackgroundNotification` is documented to be // `UIApplication`. let app = app.downcast::().unwrap(); send_occluded_event_for_all_windows(&app, true); }, ); let _will_terminate_observer = create_observer( ¢er, // `applicationWillTerminate:` unsafe { UIApplicationWillTerminateNotification }, move |notification| { let app = unsafe { notification.object() } .expect("UIApplicationWillTerminateNotification to have application object"); // The `object` in `UIApplicationWillTerminateNotification` is (somewhat) documented // to be `UIApplication`. let app = app.downcast::().unwrap(); app_state::terminated(&app); }, ); let _did_receive_memory_warning_observer = create_observer( ¢er, // `applicationDidReceiveMemoryWarning:` unsafe { UIApplicationDidReceiveMemoryWarningNotification }, move |_| app_state::handle_memory_warning(mtm), ); Ok(EventLoop { mtm, window_target: ActiveEventLoop { mtm }, _did_finish_launching_observer, _did_become_active_observer, _will_resign_active_observer, _will_enter_foreground_observer, _did_enter_background_observer, _will_terminate_observer, _did_receive_memory_warning_observer, }) } pub fn run_app(self, app: A) -> ! { let application: Option> = unsafe { msg_send![UIApplication::class(), sharedApplication] }; assert!( application.is_none(), "\ `EventLoop` cannot be `run` after a call to `UIApplicationMain` on iOS\nNote: \ `EventLoop::run_app` calls `UIApplicationMain` on iOS", ); // We intentionally override neither the application nor the delegate, // to allow the user to do so themselves! app_state::launch(self.mtm, app, || UIApplication::main(None, None, self.mtm)) } pub fn window_target(&self) -> &dyn RootActiveEventLoop { &self.window_target } } fn setup_control_flow_observers() { unsafe { // begin is queued with the highest priority to ensure it is processed before other // observers extern "C-unwind" fn control_flow_begin_handler( _: *mut CFRunLoopObserver, activity: CFRunLoopActivity, _: *mut c_void, ) { let mtm = MainThreadMarker::new().unwrap(); #[allow(non_upper_case_globals)] match activity { CFRunLoopActivity::AfterWaiting => app_state::handle_wakeup_transition(mtm), _ => unreachable!(), } } // Core Animation registers its `CFRunLoopObserver` that performs drawing operations in // `CA::Transaction::ensure_implicit` with a priority of `0x1e8480`. We set the main_end // priority to be 0, in order to send AboutToWait before RedrawRequested. This value was // chosen conservatively to guard against apple using different priorities for their redraw // observers in different OS's or on different devices. If it so happens that it's too // conservative, the main symptom would be non-redraw events coming in after `AboutToWait`. // // The value of `0x1e8480` was determined by inspecting stack traces and the associated // registers for every `CFRunLoopAddObserver` call on an iPad Air 2 running iOS 11.4. // // Also tested to be `0x1e8480` on iPhone 8, iOS 13 beta 4. extern "C-unwind" fn control_flow_main_end_handler( _: *mut CFRunLoopObserver, activity: CFRunLoopActivity, _: *mut c_void, ) { let mtm = MainThreadMarker::new().unwrap(); #[allow(non_upper_case_globals)] match activity { CFRunLoopActivity::BeforeWaiting => app_state::handle_main_events_cleared(mtm), CFRunLoopActivity::Exit => {}, // may happen when running on macOS _ => unreachable!(), } } // end is queued with the lowest priority to ensure it is processed after other observers extern "C-unwind" fn control_flow_end_handler( _: *mut CFRunLoopObserver, activity: CFRunLoopActivity, _: *mut c_void, ) { let mtm = MainThreadMarker::new().unwrap(); #[allow(non_upper_case_globals)] match activity { CFRunLoopActivity::BeforeWaiting => app_state::handle_events_cleared(mtm), CFRunLoopActivity::Exit => {}, // may happen when running on macOS _ => unreachable!(), } } let main_loop = CFRunLoop::main().unwrap(); let begin_observer = CFRunLoopObserver::new( None, CFRunLoopActivity::AfterWaiting.0, true, CFIndex::MIN, Some(control_flow_begin_handler), ptr::null_mut(), ) .unwrap(); main_loop.add_observer(Some(&begin_observer), kCFRunLoopDefaultMode); let main_end_observer = CFRunLoopObserver::new( None, (CFRunLoopActivity::Exit | CFRunLoopActivity::BeforeWaiting).0, true, 0, // see comment on `control_flow_main_end_handler` Some(control_flow_main_end_handler), ptr::null_mut(), ) .unwrap(); main_loop.add_observer(Some(&main_end_observer), kCFRunLoopDefaultMode); let end_observer = CFRunLoopObserver::new( None, (CFRunLoopActivity::Exit | CFRunLoopActivity::BeforeWaiting).0, true, CFIndex::MAX, Some(control_flow_end_handler), ptr::null_mut(), ) .unwrap(); main_loop.add_observer(Some(&end_observer), kCFRunLoopDefaultMode); } }