#![deny(unused_results)] use std::cell::{RefCell, RefMut}; use std::collections::HashSet; use std::os::raw::c_void; use std::sync::atomic::AtomicBool; use std::sync::{Arc, Mutex, OnceLock}; use std::time::Instant; use std::{fmt, mem, ptr}; use core_foundation::base::CFRelease; use core_foundation::date::CFAbsoluteTimeGetCurrent; use core_foundation::runloop::{ kCFRunLoopCommonModes, CFRunLoopAddTimer, CFRunLoopGetMain, CFRunLoopRef, CFRunLoopTimerCreate, CFRunLoopTimerInvalidate, CFRunLoopTimerRef, CFRunLoopTimerSetNextFireDate, }; use objc2::rc::Retained; use objc2::runtime::AnyObject; use objc2::{msg_send, sel}; use objc2_foundation::{ CGRect, CGSize, MainThreadMarker, NSInteger, NSObjectProtocol, NSOperatingSystemVersion, NSProcessInfo, }; use objc2_ui_kit::{UIApplication, UICoordinateSpace, UIView, UIWindow}; use super::window::WinitUIWindow; use crate::dpi::PhysicalSize; use crate::event::{Event, InnerSizeWriter, StartCause, WindowEvent}; use crate::event_loop::{ActiveEventLoop as RootActiveEventLoop, ControlFlow}; use crate::window::WindowId as RootWindowId; macro_rules! bug { ($($msg:tt)*) => { panic!("winit iOS bug, file an issue: {}", format!($($msg)*)) }; } macro_rules! bug_assert { ($test:expr, $($msg:tt)*) => { assert!($test, "winit iOS bug, file an issue: {}", format!($($msg)*)) }; } pub(crate) struct EventLoopHandler { #[allow(clippy::type_complexity)] pub(crate) handler: Box, pub(crate) event_loop: RootActiveEventLoop, } impl fmt::Debug for EventLoopHandler { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("EventLoopHandler") .field("handler", &"...") .field("event_loop", &self.event_loop) .finish() } } impl EventLoopHandler { fn handle_event(&mut self, event: Event) { (self.handler)(event, &self.event_loop) } } #[derive(Debug)] pub(crate) enum EventWrapper { StaticEvent(Event), ScaleFactorChanged(ScaleFactorChanged), } #[derive(Debug)] pub struct ScaleFactorChanged { pub(super) window: Retained, pub(super) suggested_size: PhysicalSize, pub(super) scale_factor: f64, } enum UserCallbackTransitionResult<'a> { Success { handler: EventLoopHandler, active_control_flow: ControlFlow, processing_redraws: bool, }, ReentrancyPrevented { queued_events: &'a mut Vec, }, } impl Event { fn is_redraw(&self) -> bool { matches!(self, Event::WindowEvent { event: WindowEvent::RedrawRequested, .. }) } } // this is the state machine for the app lifecycle #[derive(Debug)] #[must_use = "dropping `AppStateImpl` without inspecting it is probably a bug"] enum AppStateImpl { NotLaunched { queued_windows: Vec>, queued_events: Vec, queued_gpu_redraws: HashSet>, }, Launching { queued_windows: Vec>, queued_events: Vec, queued_handler: EventLoopHandler, queued_gpu_redraws: HashSet>, }, ProcessingEvents { handler: EventLoopHandler, queued_gpu_redraws: HashSet>, active_control_flow: ControlFlow, }, // special state to deal with reentrancy and prevent mutable aliasing. InUserCallback { queued_events: Vec, queued_gpu_redraws: HashSet>, }, ProcessingRedraws { handler: EventLoopHandler, active_control_flow: ControlFlow, }, Waiting { waiting_handler: EventLoopHandler, start: Instant, }, PollFinished { waiting_handler: EventLoopHandler, }, Terminated, } pub(crate) struct AppState { // This should never be `None`, except for briefly during a state transition. app_state: Option, control_flow: ControlFlow, waker: EventLoopWaker, proxy_wake_up: Arc, } impl AppState { pub(crate) fn get_mut(_mtm: MainThreadMarker) -> RefMut<'static, AppState> { // basically everything in UIKit requires the main thread, so it's pointless to use the // std::sync APIs. // must be mut because plain `static` requires `Sync` static mut APP_STATE: RefCell> = RefCell::new(None); let mut guard = unsafe { APP_STATE.borrow_mut() }; if guard.is_none() { #[inline(never)] #[cold] fn init_guard(guard: &mut RefMut<'static, Option>) { let waker = EventLoopWaker::new(unsafe { CFRunLoopGetMain() }); **guard = Some(AppState { app_state: Some(AppStateImpl::NotLaunched { queued_windows: Vec::new(), queued_events: Vec::new(), queued_gpu_redraws: HashSet::new(), }), control_flow: ControlFlow::default(), waker, proxy_wake_up: Arc::new(AtomicBool::new(false)), }); } init_guard(&mut guard); } RefMut::map(guard, |state| state.as_mut().unwrap()) } fn state(&self) -> &AppStateImpl { match &self.app_state { Some(ref state) => state, None => bug!("`AppState` previously failed a state transition"), } } fn state_mut(&mut self) -> &mut AppStateImpl { match &mut self.app_state { Some(ref mut state) => state, None => bug!("`AppState` previously failed a state transition"), } } fn take_state(&mut self) -> AppStateImpl { match self.app_state.take() { Some(state) => state, None => bug!("`AppState` previously failed a state transition"), } } fn set_state(&mut self, new_state: AppStateImpl) { bug_assert!( self.app_state.is_none(), "attempted to set an `AppState` without calling `take_state` first {:?}", self.app_state ); self.app_state = Some(new_state) } fn replace_state(&mut self, new_state: AppStateImpl) -> AppStateImpl { match &mut self.app_state { Some(ref mut state) => mem::replace(state, new_state), None => bug!("`AppState` previously failed a state transition"), } } fn has_launched(&self) -> bool { !matches!(self.state(), AppStateImpl::NotLaunched { .. } | AppStateImpl::Launching { .. }) } fn has_terminated(&self) -> bool { matches!(self.state(), AppStateImpl::Terminated) } fn will_launch_transition(&mut self, queued_handler: EventLoopHandler) { let (queued_windows, queued_events, queued_gpu_redraws) = match self.take_state() { AppStateImpl::NotLaunched { queued_windows, queued_events, queued_gpu_redraws } => { (queued_windows, queued_events, queued_gpu_redraws) }, s => bug!("unexpected state {:?}", s), }; self.set_state(AppStateImpl::Launching { queued_windows, queued_events, queued_handler, queued_gpu_redraws, }); } fn did_finish_launching_transition( &mut self, ) -> (Vec>, Vec) { let (windows, events, handler, queued_gpu_redraws) = match self.take_state() { AppStateImpl::Launching { queued_windows, queued_events, queued_handler, queued_gpu_redraws, } => (queued_windows, queued_events, queued_handler, queued_gpu_redraws), s => bug!("unexpected state {:?}", s), }; self.set_state(AppStateImpl::ProcessingEvents { handler, active_control_flow: self.control_flow, queued_gpu_redraws, }); (windows, events) } fn wakeup_transition(&mut self) -> Option { // before `AppState::did_finish_launching` is called, pretend there is no running // event loop. if !self.has_launched() || self.has_terminated() { return None; } let (handler, event) = match (self.control_flow, self.take_state()) { (ControlFlow::Poll, AppStateImpl::PollFinished { waiting_handler }) => { (waiting_handler, EventWrapper::StaticEvent(Event::NewEvents(StartCause::Poll))) }, (ControlFlow::Wait, AppStateImpl::Waiting { waiting_handler, start }) => ( waiting_handler, EventWrapper::StaticEvent(Event::NewEvents(StartCause::WaitCancelled { start, requested_resume: None, })), ), ( ControlFlow::WaitUntil(requested_resume), AppStateImpl::Waiting { waiting_handler, start }, ) => { let event = if Instant::now() >= requested_resume { EventWrapper::StaticEvent(Event::NewEvents(StartCause::ResumeTimeReached { start, requested_resume, })) } else { EventWrapper::StaticEvent(Event::NewEvents(StartCause::WaitCancelled { start, requested_resume: Some(requested_resume), })) }; (waiting_handler, event) }, s => bug!("`EventHandler` unexpectedly woke up {:?}", s), }; self.set_state(AppStateImpl::ProcessingEvents { handler, queued_gpu_redraws: Default::default(), active_control_flow: self.control_flow, }); Some(event) } fn try_user_callback_transition(&mut self) -> UserCallbackTransitionResult<'_> { // If we're not able to process an event due to recursion or `Init` not having been sent out // yet, then queue the events up. match self.state_mut() { &mut AppStateImpl::Launching { ref mut queued_events, .. } | &mut AppStateImpl::NotLaunched { ref mut queued_events, .. } | &mut AppStateImpl::InUserCallback { ref mut queued_events, .. } => { // A lifetime cast: early returns are not currently handled well with NLL, but // polonius handles them well. This transmute is a safe workaround. return unsafe { mem::transmute::< UserCallbackTransitionResult<'_>, UserCallbackTransitionResult<'_>, >(UserCallbackTransitionResult::ReentrancyPrevented { queued_events, }) }; }, &mut AppStateImpl::ProcessingEvents { .. } | &mut AppStateImpl::ProcessingRedraws { .. } => {}, s @ &mut AppStateImpl::PollFinished { .. } | s @ &mut AppStateImpl::Waiting { .. } | s @ &mut AppStateImpl::Terminated => { bug!("unexpected attempted to process an event {:?}", s) }, } let (handler, queued_gpu_redraws, active_control_flow, processing_redraws) = match self .take_state() { AppStateImpl::Launching { .. } | AppStateImpl::NotLaunched { .. } | AppStateImpl::InUserCallback { .. } => unreachable!(), AppStateImpl::ProcessingEvents { handler, queued_gpu_redraws, active_control_flow } => { (handler, queued_gpu_redraws, active_control_flow, false) }, AppStateImpl::ProcessingRedraws { handler, active_control_flow } => { (handler, Default::default(), active_control_flow, true) }, AppStateImpl::PollFinished { .. } | AppStateImpl::Waiting { .. } | AppStateImpl::Terminated => unreachable!(), }; self.set_state(AppStateImpl::InUserCallback { queued_events: Vec::new(), queued_gpu_redraws, }); UserCallbackTransitionResult::Success { handler, active_control_flow, processing_redraws } } fn main_events_cleared_transition(&mut self) -> HashSet> { let (handler, queued_gpu_redraws, active_control_flow) = match self.take_state() { AppStateImpl::ProcessingEvents { handler, queued_gpu_redraws, active_control_flow } => { (handler, queued_gpu_redraws, active_control_flow) }, s => bug!("unexpected state {:?}", s), }; self.set_state(AppStateImpl::ProcessingRedraws { handler, active_control_flow }); queued_gpu_redraws } fn events_cleared_transition(&mut self) { if !self.has_launched() || self.has_terminated() { return; } let (waiting_handler, old) = match self.take_state() { AppStateImpl::ProcessingRedraws { handler, active_control_flow } => { (handler, active_control_flow) }, s => bug!("unexpected state {:?}", s), }; let new = self.control_flow; match (old, new) { (ControlFlow::Wait, ControlFlow::Wait) => { let start = Instant::now(); self.set_state(AppStateImpl::Waiting { waiting_handler, start }); }, (ControlFlow::WaitUntil(old_instant), ControlFlow::WaitUntil(new_instant)) if old_instant == new_instant => { let start = Instant::now(); self.set_state(AppStateImpl::Waiting { waiting_handler, start }); }, (_, ControlFlow::Wait) => { let start = Instant::now(); self.set_state(AppStateImpl::Waiting { waiting_handler, start }); self.waker.stop() }, (_, ControlFlow::WaitUntil(new_instant)) => { let start = Instant::now(); self.set_state(AppStateImpl::Waiting { waiting_handler, start }); self.waker.start_at(new_instant) }, // Unlike on macOS, handle Poll to Poll transition here to call the waker (_, ControlFlow::Poll) => { self.set_state(AppStateImpl::PollFinished { waiting_handler }); self.waker.start() }, } } fn terminated_transition(&mut self) -> EventLoopHandler { match self.replace_state(AppStateImpl::Terminated) { AppStateImpl::ProcessingEvents { handler, .. } => handler, s => bug!("`LoopExiting` happened while not processing events {:?}", s), } } pub(crate) fn proxy_wake_up(&self) -> Arc { self.proxy_wake_up.clone() } pub(crate) fn set_control_flow(&mut self, control_flow: ControlFlow) { self.control_flow = control_flow; } pub(crate) fn control_flow(&self) -> ControlFlow { self.control_flow } } pub(crate) fn set_key_window(mtm: MainThreadMarker, window: &Retained) { let mut this = AppState::get_mut(mtm); match this.state_mut() { &mut AppStateImpl::NotLaunched { ref mut queued_windows, .. } => { return queued_windows.push(window.clone()) }, &mut AppStateImpl::ProcessingEvents { .. } | &mut AppStateImpl::InUserCallback { .. } | &mut AppStateImpl::ProcessingRedraws { .. } => {}, s @ &mut AppStateImpl::Launching { .. } | s @ &mut AppStateImpl::Waiting { .. } | s @ &mut AppStateImpl::PollFinished { .. } => bug!("unexpected state {:?}", s), &mut AppStateImpl::Terminated => { panic!("Attempt to create a `Window` after the app has terminated") }, } drop(this); window.makeKeyAndVisible(); } pub(crate) fn queue_gl_or_metal_redraw(mtm: MainThreadMarker, window: Retained) { let mut this = AppState::get_mut(mtm); match this.state_mut() { &mut AppStateImpl::NotLaunched { ref mut queued_gpu_redraws, .. } | &mut AppStateImpl::Launching { ref mut queued_gpu_redraws, .. } | &mut AppStateImpl::ProcessingEvents { ref mut queued_gpu_redraws, .. } | &mut AppStateImpl::InUserCallback { ref mut queued_gpu_redraws, .. } => { let _ = queued_gpu_redraws.insert(window); }, s @ &mut AppStateImpl::ProcessingRedraws { .. } | s @ &mut AppStateImpl::Waiting { .. } | s @ &mut AppStateImpl::PollFinished { .. } => bug!("unexpected state {:?}", s), &mut AppStateImpl::Terminated => { panic!("Attempt to create a `Window` after the app has terminated") }, } } pub(crate) fn will_launch(mtm: MainThreadMarker, queued_handler: EventLoopHandler) { AppState::get_mut(mtm).will_launch_transition(queued_handler) } pub fn did_finish_launching(mtm: MainThreadMarker) { let mut this = AppState::get_mut(mtm); let windows = match this.state_mut() { AppStateImpl::Launching { queued_windows, .. } => mem::take(queued_windows), s => bug!("unexpected state {:?}", s), }; this.waker.start(); // have to drop RefMut because the window setup code below can trigger new events drop(this); for window in windows { // Do a little screen dance here to account for windows being created before // `UIApplicationMain` is called. This fixes visual issues such as being // offcenter and sized incorrectly. Additionally, to fix orientation issues, we // gotta reset the `rootViewController`. // // relevant iOS log: // ``` // [ApplicationLifecycle] Windows were created before application initialization // completed. This may result in incorrect visual appearance. // ``` let screen = window.screen(); let _: () = unsafe { msg_send![&window, setScreen: ptr::null::()] }; window.setScreen(&screen); let controller = window.rootViewController(); window.setRootViewController(None); window.setRootViewController(controller.as_deref()); window.makeKeyAndVisible(); } let (windows, events) = AppState::get_mut(mtm).did_finish_launching_transition(); let events = std::iter::once(EventWrapper::StaticEvent(Event::NewEvents(StartCause::Init))) .chain(events); handle_nonuser_events(mtm, events); // the above window dance hack, could possibly trigger new windows to be created. // we can just set those windows up normally, as they were created after didFinishLaunching for window in windows { window.makeKeyAndVisible(); } } // AppState::did_finish_launching handles the special transition `Init` pub fn handle_wakeup_transition(mtm: MainThreadMarker) { let mut this = AppState::get_mut(mtm); let wakeup_event = match this.wakeup_transition() { None => return, Some(wakeup_event) => wakeup_event, }; drop(this); handle_nonuser_event(mtm, wakeup_event) } pub(crate) fn handle_nonuser_event(mtm: MainThreadMarker, event: EventWrapper) { handle_nonuser_events(mtm, std::iter::once(event)) } pub(crate) fn handle_nonuser_events>( mtm: MainThreadMarker, events: I, ) { let mut this = AppState::get_mut(mtm); if this.has_terminated() { return; } let (mut handler, active_control_flow, processing_redraws) = match this.try_user_callback_transition() { UserCallbackTransitionResult::ReentrancyPrevented { queued_events } => { queued_events.extend(events); return; }, UserCallbackTransitionResult::Success { handler, active_control_flow, processing_redraws, } => (handler, active_control_flow, processing_redraws), }; drop(this); for wrapper in events { match wrapper { EventWrapper::StaticEvent(event) => { if !processing_redraws && event.is_redraw() { tracing::info!("processing `RedrawRequested` during the main event loop"); } else if processing_redraws && !event.is_redraw() { tracing::warn!( "processing non `RedrawRequested` event after the main event loop: {:#?}", event ); } handler.handle_event(event) }, EventWrapper::ScaleFactorChanged(event) => handle_hidpi_proxy(&mut handler, event), } } loop { let mut this = AppState::get_mut(mtm); let queued_events = match this.state_mut() { &mut AppStateImpl::InUserCallback { ref mut queued_events, queued_gpu_redraws: _ } => { mem::take(queued_events) }, s => bug!("unexpected state {:?}", s), }; if queued_events.is_empty() { let queued_gpu_redraws = match this.take_state() { AppStateImpl::InUserCallback { queued_events: _, queued_gpu_redraws } => { queued_gpu_redraws }, _ => unreachable!(), }; this.app_state = Some(if processing_redraws { bug_assert!( queued_gpu_redraws.is_empty(), "redraw queued while processing redraws" ); AppStateImpl::ProcessingRedraws { handler, active_control_flow } } else { AppStateImpl::ProcessingEvents { handler, queued_gpu_redraws, active_control_flow } }); break; } drop(this); for wrapper in queued_events { match wrapper { EventWrapper::StaticEvent(event) => { if !processing_redraws && event.is_redraw() { tracing::info!("processing `RedrawRequested` during the main event loop"); } else if processing_redraws && !event.is_redraw() { tracing::warn!( "processing non-`RedrawRequested` event after the main event loop: \ {:#?}", event ); } handler.handle_event(event) }, EventWrapper::ScaleFactorChanged(event) => handle_hidpi_proxy(&mut handler, event), } } } } fn handle_user_events(mtm: MainThreadMarker) { let mut this = AppState::get_mut(mtm); let (mut handler, active_control_flow, processing_redraws) = match this.try_user_callback_transition() { UserCallbackTransitionResult::ReentrancyPrevented { .. } => { bug!("unexpected attempted to process an event") }, UserCallbackTransitionResult::Success { handler, active_control_flow, processing_redraws, } => (handler, active_control_flow, processing_redraws), }; if processing_redraws { bug!("user events attempted to be sent out while `ProcessingRedraws`"); } drop(this); handler.handle_event(Event::UserWakeUp); loop { let mut this = AppState::get_mut(mtm); let queued_events = match this.state_mut() { &mut AppStateImpl::InUserCallback { ref mut queued_events, queued_gpu_redraws: _ } => { mem::take(queued_events) }, s => bug!("unexpected state {:?}", s), }; if queued_events.is_empty() { let queued_gpu_redraws = match this.take_state() { AppStateImpl::InUserCallback { queued_events: _, queued_gpu_redraws } => { queued_gpu_redraws }, _ => unreachable!(), }; this.app_state = Some(AppStateImpl::ProcessingEvents { handler, queued_gpu_redraws, active_control_flow, }); break; } drop(this); for wrapper in queued_events { match wrapper { EventWrapper::StaticEvent(event) => handler.handle_event(event), EventWrapper::ScaleFactorChanged(event) => handle_hidpi_proxy(&mut handler, event), } } handler.handle_event(Event::UserWakeUp); } } pub(crate) fn send_occluded_event_for_all_windows(application: &UIApplication, occluded: bool) { let mtm = MainThreadMarker::from(application); let mut events = Vec::new(); #[allow(deprecated)] for window in application.windows().iter() { if window.is_kind_of::() { // SAFETY: We just checked that the window is a `winit` window let window = unsafe { let ptr: *const UIWindow = window; let ptr: *const WinitUIWindow = ptr.cast(); &*ptr }; events.push(EventWrapper::StaticEvent(Event::WindowEvent { window_id: RootWindowId(window.id()), event: WindowEvent::Occluded(occluded), })); } } handle_nonuser_events(mtm, events); } pub fn handle_main_events_cleared(mtm: MainThreadMarker) { let mut this = AppState::get_mut(mtm); if !this.has_launched() || this.has_terminated() { return; } match this.state_mut() { AppStateImpl::ProcessingEvents { .. } => {}, _ => bug!("`ProcessingRedraws` happened unexpectedly"), }; drop(this); handle_user_events(mtm); let mut this = AppState::get_mut(mtm); let redraw_events: Vec = this .main_events_cleared_transition() .into_iter() .map(|window| { EventWrapper::StaticEvent(Event::WindowEvent { window_id: RootWindowId(window.id()), event: WindowEvent::RedrawRequested, }) }) .collect(); drop(this); handle_nonuser_events(mtm, redraw_events); handle_nonuser_event(mtm, EventWrapper::StaticEvent(Event::AboutToWait)); } pub fn handle_events_cleared(mtm: MainThreadMarker) { AppState::get_mut(mtm).events_cleared_transition(); } pub(crate) fn terminated(application: &UIApplication) { let mtm = MainThreadMarker::from(application); let mut events = Vec::new(); #[allow(deprecated)] for window in application.windows().iter() { if window.is_kind_of::() { // SAFETY: We just checked that the window is a `winit` window let window = unsafe { let ptr: *const UIWindow = window; let ptr: *const WinitUIWindow = ptr.cast(); &*ptr }; events.push(EventWrapper::StaticEvent(Event::WindowEvent { window_id: RootWindowId(window.id()), event: WindowEvent::Destroyed, })); } } handle_nonuser_events(mtm, events); let mut this = AppState::get_mut(mtm); let mut handler = this.terminated_transition(); drop(this); handler.handle_event(Event::LoopExiting) } fn handle_hidpi_proxy(handler: &mut EventLoopHandler, event: ScaleFactorChanged) { let ScaleFactorChanged { suggested_size, scale_factor, window } = event; let new_inner_size = Arc::new(Mutex::new(suggested_size)); let event = Event::WindowEvent { window_id: RootWindowId(window.id()), event: WindowEvent::ScaleFactorChanged { scale_factor, inner_size_writer: InnerSizeWriter::new(Arc::downgrade(&new_inner_size)), }, }; handler.handle_event(event); let (view, screen_frame) = get_view_and_screen_frame(&window); let physical_size = *new_inner_size.lock().unwrap(); drop(new_inner_size); let logical_size = physical_size.to_logical(scale_factor); let size = CGSize::new(logical_size.width, logical_size.height); let new_frame: CGRect = CGRect::new(screen_frame.origin, size); view.setFrame(new_frame); } fn get_view_and_screen_frame(window: &WinitUIWindow) -> (Retained, CGRect) { let view_controller = window.rootViewController().unwrap(); let view = view_controller.view().unwrap(); let bounds = window.bounds(); let screen = window.screen(); let screen_space = screen.coordinateSpace(); let screen_frame = window.convertRect_toCoordinateSpace(bounds, &screen_space); (view, screen_frame) } struct EventLoopWaker { timer: CFRunLoopTimerRef, } impl Drop for EventLoopWaker { fn drop(&mut self) { unsafe { CFRunLoopTimerInvalidate(self.timer); CFRelease(self.timer as _); } } } impl EventLoopWaker { fn new(rl: CFRunLoopRef) -> EventLoopWaker { extern "C" fn wakeup_main_loop(_timer: CFRunLoopTimerRef, _info: *mut c_void) {} unsafe { // Create a timer with a 0.1µs interval (1ns does not work) to mimic polling. // It is initially setup with a first fire time really far into the // future, but that gets changed to fire immediately in did_finish_launching let timer = CFRunLoopTimerCreate( ptr::null_mut(), f64::MAX, 0.000_000_1, 0, 0, wakeup_main_loop, ptr::null_mut(), ); CFRunLoopAddTimer(rl, timer, kCFRunLoopCommonModes); EventLoopWaker { timer } } } fn stop(&mut self) { unsafe { CFRunLoopTimerSetNextFireDate(self.timer, f64::MAX) } } fn start(&mut self) { unsafe { CFRunLoopTimerSetNextFireDate(self.timer, f64::MIN) } } fn start_at(&mut self, instant: Instant) { let now = Instant::now(); if now >= instant { self.start(); } else { unsafe { let current = CFAbsoluteTimeGetCurrent(); let duration = instant - now; let fsecs = duration.subsec_nanos() as f64 / 1_000_000_000.0 + duration.as_secs() as f64; CFRunLoopTimerSetNextFireDate(self.timer, current + fsecs) } } } } macro_rules! os_capabilities { ( $( $(#[$attr:meta])* $error_name:ident: $objc_call:literal, $name:ident: $major:literal-$minor:literal ),* $(,)* ) => { #[derive(Clone, Debug)] pub struct OSCapabilities { $( pub $name: bool, )* os_version: NSOperatingSystemVersion, } impl OSCapabilities { fn from_os_version(os_version: NSOperatingSystemVersion) -> Self { $(let $name = meets_requirements(os_version, $major, $minor);)* Self { $($name,)* os_version, } } } impl OSCapabilities {$( $(#[$attr])* pub fn $error_name(&self, extra_msg: &str) { tracing::warn!( concat!("`", $objc_call, "` requires iOS {}.{}+. This device is running iOS {}.{}.{}. {}"), $major, $minor, self.os_version.majorVersion, self.os_version.minorVersion, self.os_version.patchVersion, extra_msg ) } )*} }; } os_capabilities! { /// #[allow(unused)] // error message unused safe_area_err_msg: "-[UIView safeAreaInsets]", safe_area: 11-0, /// home_indicator_hidden_err_msg: "-[UIViewController setNeedsUpdateOfHomeIndicatorAutoHidden]", home_indicator_hidden: 11-0, /// defer_system_gestures_err_msg: "-[UIViewController setNeedsUpdateOfScreenEdgesDeferringSystem]", defer_system_gestures: 11-0, /// maximum_frames_per_second_err_msg: "-[UIScreen maximumFramesPerSecond]", maximum_frames_per_second: 10-3, /// #[allow(unused)] // error message unused force_touch_err_msg: "-[UITouch force]", force_touch: 9-0, } fn meets_requirements( version: NSOperatingSystemVersion, required_major: NSInteger, required_minor: NSInteger, ) -> bool { (version.majorVersion, version.minorVersion) >= (required_major, required_minor) } fn get_version() -> NSOperatingSystemVersion { let process_info = NSProcessInfo::processInfo(); let atleast_ios_8 = process_info.respondsToSelector(sel!(operatingSystemVersion)); // Winit requires atleast iOS 8 because no one has put the time into supporting earlier os // versions. Older iOS versions are increasingly difficult to test. For example, Xcode 11 does // not support debugging on devices with an iOS version of less than 8. Another example, in // order to use an iOS simulator older than iOS 8, you must download an older version of Xcode // (<9), and at least Xcode 7 has been tested to not even run on macOS 10.15 - Xcode 8 might? // // The minimum required iOS version is likely to grow in the future. assert!(atleast_ios_8, "`winit` requires iOS version 8 or greater"); process_info.operatingSystemVersion() } pub fn os_capabilities() -> OSCapabilities { // Cache the version lookup for efficiency static OS_CAPABILITIES: OnceLock = OnceLock::new(); OS_CAPABILITIES.get_or_init(|| OSCapabilities::from_os_version(get_version())).clone() }