mirror of
https://github.com/rust-windowing/winit.git
synced 2026-06-26 14:49:07 -04:00
Events like `resumed`, `new_events`, `about_to_wait`, and so on will never happen as a result of programmer action, so we'll never need to queue those. This allows us to remove the need for the old `Event` struct in the iOS backend. Furthermore, we can now remove `InUserCallback`, since that state is already stored inside `EventHandler`. I've tried to otherwise keep the semantics as close to the original by calling `handle_nonuser_events(mtm, [])`, which flushes pending events.
429 lines
16 KiB
Rust
429 lines
16 KiB
Rust
use std::ffi::{c_char, c_int, c_void};
|
|
use std::ptr::{self, NonNull};
|
|
use std::sync::atomic::{AtomicBool, Ordering as AtomicOrdering};
|
|
use std::sync::Arc;
|
|
|
|
use core_foundation::base::{CFIndex, CFRelease};
|
|
use core_foundation::runloop::{
|
|
kCFRunLoopAfterWaiting, kCFRunLoopBeforeWaiting, kCFRunLoopCommonModes, kCFRunLoopDefaultMode,
|
|
kCFRunLoopExit, CFRunLoopActivity, CFRunLoopAddObserver, CFRunLoopAddSource, CFRunLoopGetMain,
|
|
CFRunLoopObserverCreate, CFRunLoopObserverRef, CFRunLoopSourceContext, CFRunLoopSourceCreate,
|
|
CFRunLoopSourceInvalidate, CFRunLoopSourceRef, CFRunLoopSourceSignal, CFRunLoopWakeUp,
|
|
};
|
|
use objc2::rc::Retained;
|
|
use objc2::{msg_send_id, ClassType};
|
|
use objc2_foundation::{MainThreadMarker, NSNotificationCenter, NSObject};
|
|
use objc2_ui_kit::{
|
|
UIApplication, UIApplicationDidBecomeActiveNotification,
|
|
UIApplicationDidEnterBackgroundNotification, UIApplicationDidFinishLaunchingNotification,
|
|
UIApplicationDidReceiveMemoryWarningNotification, UIApplicationMain,
|
|
UIApplicationWillEnterForegroundNotification, UIApplicationWillResignActiveNotification,
|
|
UIApplicationWillTerminateNotification, UIScreen,
|
|
};
|
|
use rwh_06::HasDisplayHandle;
|
|
|
|
use super::super::notification_center::create_observer;
|
|
use super::app_state::{send_occluded_event_for_all_windows, AppState};
|
|
use super::{app_state, monitor, MonitorHandle};
|
|
use crate::application::ApplicationHandler;
|
|
use crate::error::{EventLoopError, NotSupportedError, RequestError};
|
|
use crate::event_loop::{
|
|
ActiveEventLoop as RootActiveEventLoop, ControlFlow, DeviceEvents,
|
|
EventLoopProxy as CoreEventLoopProxy, EventLoopProxyProvider,
|
|
OwnedDisplayHandle as CoreOwnedDisplayHandle,
|
|
};
|
|
use crate::monitor::MonitorHandle as RootMonitorHandle;
|
|
use crate::platform_impl::Window;
|
|
use crate::window::{CustomCursor, CustomCursorSource, Theme, Window as CoreWindow};
|
|
|
|
#[derive(Debug)]
|
|
pub(crate) struct ActiveEventLoop {
|
|
pub(super) mtm: MainThreadMarker,
|
|
}
|
|
|
|
impl RootActiveEventLoop for ActiveEventLoop {
|
|
fn create_proxy(&self) -> CoreEventLoopProxy {
|
|
CoreEventLoopProxy::new(AppState::get_mut(self.mtm).event_loop_proxy().clone())
|
|
}
|
|
|
|
fn create_window(
|
|
&self,
|
|
window_attributes: crate::window::WindowAttributes,
|
|
) -> Result<Box<dyn CoreWindow>, RequestError> {
|
|
Ok(Box::new(Window::new(self, window_attributes)?))
|
|
}
|
|
|
|
fn create_custom_cursor(
|
|
&self,
|
|
_source: CustomCursorSource,
|
|
) -> Result<CustomCursor, RequestError> {
|
|
Err(NotSupportedError::new("create_custom_cursor is not supported").into())
|
|
}
|
|
|
|
fn available_monitors(&self) -> Box<dyn Iterator<Item = RootMonitorHandle>> {
|
|
Box::new(monitor::uiscreens(self.mtm).into_iter().map(|inner| RootMonitorHandle { inner }))
|
|
}
|
|
|
|
fn primary_monitor(&self) -> Option<crate::monitor::MonitorHandle> {
|
|
#[allow(deprecated)]
|
|
let monitor = MonitorHandle::new(UIScreen::mainScreen(self.mtm));
|
|
Some(RootMonitorHandle { inner: monitor })
|
|
}
|
|
|
|
fn listen_device_events(&self, _allowed: DeviceEvents) {}
|
|
|
|
fn set_control_flow(&self, control_flow: ControlFlow) {
|
|
AppState::get_mut(self.mtm).set_control_flow(control_flow)
|
|
}
|
|
|
|
fn system_theme(&self) -> Option<Theme> {
|
|
None
|
|
}
|
|
|
|
fn control_flow(&self) -> ControlFlow {
|
|
AppState::get_mut(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::DisplayHandle<'_>, 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::DisplayHandle<'_>, rwh_06::HandleError> {
|
|
let raw = rwh_06::RawDisplayHandle::UiKit(rwh_06::UiKitDisplayHandle::new());
|
|
unsafe { Ok(rwh_06::DisplayHandle::borrow_raw(raw)) }
|
|
}
|
|
}
|
|
|
|
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<NSObject>,
|
|
_did_become_active_observer: Retained<NSObject>,
|
|
_will_resign_active_observer: Retained<NSObject>,
|
|
_will_enter_foreground_observer: Retained<NSObject>,
|
|
_did_enter_background_observer: Retained<NSObject>,
|
|
_will_terminate_observer: Retained<NSObject>,
|
|
_did_receive_memory_warning_observer: Retained<NSObject>,
|
|
}
|
|
|
|
#[derive(Default, Debug, Copy, Clone, PartialEq, Eq, Hash)]
|
|
pub(crate) struct PlatformSpecificEventLoopAttributes {}
|
|
|
|
impl EventLoop {
|
|
pub(crate) fn new(
|
|
_: &PlatformSpecificEventLoopAttributes,
|
|
) -> Result<EventLoop, EventLoopError> {
|
|
let mtm = MainThreadMarker::new()
|
|
.expect("On iOS, `EventLoop` must be created on the main thread");
|
|
|
|
static mut SINGLETON_INIT: bool = false;
|
|
unsafe {
|
|
assert!(
|
|
!SINGLETON_INIT,
|
|
"Only one `EventLoop` is supported on iOS. `EventLoopProxy` might be helpful"
|
|
);
|
|
SINGLETON_INIT = true;
|
|
}
|
|
|
|
// 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",
|
|
);
|
|
// SAFETY: The `object` in `UIApplicationWillEnterForegroundNotification` is
|
|
// documented to be `UIApplication`.
|
|
let app: Retained<UIApplication> = unsafe { Retained::cast(app) };
|
|
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",
|
|
);
|
|
// SAFETY: The `object` in `UIApplicationDidEnterBackgroundNotification` is
|
|
// documented to be `UIApplication`.
|
|
let app: Retained<UIApplication> = unsafe { Retained::cast(app) };
|
|
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");
|
|
// SAFETY: The `object` in `UIApplicationWillTerminateNotification` is
|
|
// (somewhat) documented to be `UIApplication`.
|
|
let app: Retained<UIApplication> = unsafe { Retained::cast(app) };
|
|
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<A: ApplicationHandler>(self, mut app: A) -> ! {
|
|
let application: Option<Retained<UIApplication>> =
|
|
unsafe { msg_send_id![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",
|
|
);
|
|
|
|
extern "C" {
|
|
// These functions are in crt_externs.h.
|
|
fn _NSGetArgc() -> *mut c_int;
|
|
fn _NSGetArgv() -> *mut *mut *mut c_char;
|
|
}
|
|
|
|
app_state::launch(self.mtm, &mut app, || unsafe {
|
|
UIApplicationMain(
|
|
*_NSGetArgc(),
|
|
NonNull::new(*_NSGetArgv()).unwrap(),
|
|
// We intentionally override neither the application nor the delegate, to allow
|
|
// the user to do so themselves!
|
|
None,
|
|
None,
|
|
);
|
|
});
|
|
|
|
unreachable!()
|
|
}
|
|
|
|
pub fn window_target(&self) -> &dyn RootActiveEventLoop {
|
|
&self.window_target
|
|
}
|
|
}
|
|
|
|
pub struct EventLoopProxy {
|
|
pub(crate) wake_up: AtomicBool,
|
|
source: CFRunLoopSourceRef,
|
|
}
|
|
|
|
unsafe impl Send for EventLoopProxy {}
|
|
unsafe impl Sync for EventLoopProxy {}
|
|
|
|
impl Drop for EventLoopProxy {
|
|
fn drop(&mut self) {
|
|
unsafe {
|
|
CFRunLoopSourceInvalidate(self.source);
|
|
CFRelease(self.source as _);
|
|
}
|
|
}
|
|
}
|
|
|
|
impl EventLoopProxy {
|
|
pub(crate) fn new() -> EventLoopProxy {
|
|
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 { wake_up: AtomicBool::new(false), source }
|
|
}
|
|
}
|
|
}
|
|
|
|
impl EventLoopProxyProvider for EventLoopProxy {
|
|
fn wake_up(&self) {
|
|
self.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);
|
|
}
|
|
}
|
|
}
|
|
|
|
fn setup_control_flow_observers() {
|
|
unsafe {
|
|
// begin is queued with the highest priority to ensure it is processed before other
|
|
// observers
|
|
extern "C" fn control_flow_begin_handler(
|
|
_: CFRunLoopObserverRef,
|
|
activity: CFRunLoopActivity,
|
|
_: *mut c_void,
|
|
) {
|
|
let mtm = MainThreadMarker::new().unwrap();
|
|
#[allow(non_upper_case_globals)]
|
|
match activity {
|
|
kCFRunLoopAfterWaiting => 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" fn control_flow_main_end_handler(
|
|
_: CFRunLoopObserverRef,
|
|
activity: CFRunLoopActivity,
|
|
_: *mut c_void,
|
|
) {
|
|
let mtm = MainThreadMarker::new().unwrap();
|
|
#[allow(non_upper_case_globals)]
|
|
match activity {
|
|
kCFRunLoopBeforeWaiting => app_state::handle_main_events_cleared(mtm),
|
|
kCFRunLoopExit => {}, // may happen when running on macOS
|
|
_ => unreachable!(),
|
|
}
|
|
}
|
|
|
|
// end is queued with the lowest priority to ensure it is processed after other observers
|
|
extern "C" fn control_flow_end_handler(
|
|
_: CFRunLoopObserverRef,
|
|
activity: CFRunLoopActivity,
|
|
_: *mut c_void,
|
|
) {
|
|
let mtm = MainThreadMarker::new().unwrap();
|
|
#[allow(non_upper_case_globals)]
|
|
match activity {
|
|
kCFRunLoopBeforeWaiting => app_state::handle_events_cleared(mtm),
|
|
kCFRunLoopExit => {}, // may happen when running on macOS
|
|
_ => unreachable!(),
|
|
}
|
|
}
|
|
|
|
let main_loop = CFRunLoopGetMain();
|
|
|
|
let begin_observer = CFRunLoopObserverCreate(
|
|
ptr::null_mut(),
|
|
kCFRunLoopAfterWaiting,
|
|
1, // repeat = true
|
|
CFIndex::MIN,
|
|
control_flow_begin_handler,
|
|
ptr::null_mut(),
|
|
);
|
|
CFRunLoopAddObserver(main_loop, begin_observer, kCFRunLoopDefaultMode);
|
|
|
|
let main_end_observer = CFRunLoopObserverCreate(
|
|
ptr::null_mut(),
|
|
kCFRunLoopExit | kCFRunLoopBeforeWaiting,
|
|
1, // repeat = true
|
|
0, // see comment on `control_flow_main_end_handler`
|
|
control_flow_main_end_handler,
|
|
ptr::null_mut(),
|
|
);
|
|
CFRunLoopAddObserver(main_loop, main_end_observer, kCFRunLoopDefaultMode);
|
|
|
|
let end_observer = CFRunLoopObserverCreate(
|
|
ptr::null_mut(),
|
|
kCFRunLoopExit | kCFRunLoopBeforeWaiting,
|
|
1, // repeat = true
|
|
CFIndex::MAX,
|
|
control_flow_end_handler,
|
|
ptr::null_mut(),
|
|
);
|
|
CFRunLoopAddObserver(main_loop, end_observer, kCFRunLoopDefaultMode);
|
|
}
|
|
}
|