From 92e9bfe0fc20d5e8f740897767306f7b42b24856 Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Sun, 11 Aug 2024 23:14:18 +0200 Subject: [PATCH] Allow the user to register the application delegate on macOS and iOS (#3758) This allows the user to override the application delegate themselves, which opens several doors for customization that were previously closed. To do this, we use notifications instead of top-level application delegate methods. One effect of not providing an application delegate on iOS is that we no longer act as-if the application successfully open all URLs there. This is a breaking change, although unlikely to matter in practice, since the return value of `application:didFinishLaunchingWithOptions:` is seldom used by the system (and is likely the preferred behaviour anyhow). --- Cargo.toml | 5 +- src/changelog/unreleased.md | 4 + src/platform/ios.rs | 15 +- src/platform/macos.rs | 70 +++++- src/platform_impl/apple/appkit/app.rs | 16 +- src/platform_impl/apple/appkit/app_state.rs | 208 ++++++++---------- .../apple/appkit/event_handler.rs | 2 +- src/platform_impl/apple/appkit/event_loop.rs | 136 +++++++----- src/platform_impl/apple/appkit/observer.rs | 6 +- src/platform_impl/apple/appkit/view.rs | 15 +- src/platform_impl/apple/appkit/window.rs | 5 +- .../apple/appkit/window_delegate.rs | 19 +- src/platform_impl/apple/mod.rs | 1 + .../apple/notification_center.rs | 27 +++ src/platform_impl/apple/uikit/app_delegate.rs | 60 ----- src/platform_impl/apple/uikit/event_loop.rs | 126 ++++++++++- src/platform_impl/apple/uikit/mod.rs | 1 - 17 files changed, 446 insertions(+), 270 deletions(-) create mode 100644 src/platform_impl/apple/notification_center.rs delete mode 100644 src/platform_impl/apple/uikit/app_delegate.rs diff --git a/Cargo.toml b/Cargo.toml index fa40ab4f64..98e17f3485 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -105,12 +105,12 @@ ndk = { version = "0.9.0", default-features = false } # AppKit or UIKit [target.'cfg(target_vendor = "apple")'.dependencies] +block2 = "0.5.1" core-foundation = "0.9.3" objc2 = "0.5.2" # AppKit [target.'cfg(target_os = "macos")'.dependencies] -block2 = "0.5.1" core-graphics = "0.23.1" objc2-app-kit = { version = "0.2.2", features = [ "NSAppearance", @@ -152,6 +152,7 @@ objc2-foundation = { version = "0.2.2", features = [ "NSKeyValueObserving", "NSNotification", "NSObjCRuntime", + "NSOperation", "NSPathUtilities", "NSProcessInfo", "NSRunLoop", @@ -163,11 +164,13 @@ objc2-foundation = { version = "0.2.2", features = [ # UIKit [target.'cfg(all(target_vendor = "apple", not(target_os = "macos")))'.dependencies] objc2-foundation = { version = "0.2.2", features = [ + "block2", "dispatch", "NSArray", "NSEnumerator", "NSGeometry", "NSObjCRuntime", + "NSOperation", "NSString", "NSProcessInfo", "NSThread", diff --git a/src/changelog/unreleased.md b/src/changelog/unreleased.md index 27a5d8c748..9e710c5a1f 100644 --- a/src/changelog/unreleased.md +++ b/src/changelog/unreleased.md @@ -94,6 +94,10 @@ changelog entry. - Changed how `ModifiersState` is serialized by Serde. - `VideoModeHandle::refresh_rate_millihertz()` and `bit_depth()` now return a `Option`. - `MonitorHandle::position()` now returns an `Option`. +- On iOS and macOS, remove custom application delegates. You are now allowed to override the + application delegate yourself. +- On iOS, no longer act as-if the application successfully open all URLs. Override + `application:didFinishLaunchingWithOptions:` and provide the desired behaviour yourself. ### Removed diff --git a/src/platform/ios.rs b/src/platform/ios.rs index 3b11830823..9716adc880 100644 --- a/src/platform/ios.rs +++ b/src/platform/ios.rs @@ -3,11 +3,14 @@ //! Winit has an OS requirement of iOS 8 or higher, and is regularly tested on //! iOS 9.3. //! +//! ## Window initialization +//! //! iOS's main `UIApplicationMain` does some init work that's required by all //! UI-related code (see issue [#1705]). It is best to create your windows -//! inside `Event::Resumed`. +//! inside [`ApplicationHandler::resumed`]. //! //! [#1705]: https://github.com/rust-windowing/winit/issues/1705 +//! [`ApplicationHandler::resumed`]: crate::application::ApplicationHandler::resumed //! //! ## Building app //! @@ -63,6 +66,16 @@ //! opengl will result in segfault. //! //! Also note that app may not receive the LoopExiting event if suspended; it might be SIGKILL'ed. +//! +//! ## Custom `UIApplicationDelegate` +//! +//! Winit usually handles everything related to the lifecycle events of the application. Sometimes, +//! though, you might want to access some of the more niche stuff that [the application +//! delegate][app-delegate] provides. This functionality is not exposed directly in Winit, since it +//! would increase the API surface by quite a lot. Instead, Winit guarantees that it will not +//! register an application delegate, so you can set up a custom one in a nib file instead. +//! +//! [app-delegate]: https://developer.apple.com/documentation/uikit/uiapplicationdelegate?language=objc use std::os::raw::c_void; diff --git a/src/platform/macos.rs b/src/platform/macos.rs index a09d7e18d5..129294757d 100644 --- a/src/platform/macos.rs +++ b/src/platform/macos.rs @@ -3,16 +3,84 @@ //! Winit has an OS requirement of macOS 10.11 or higher (same as Rust //! itself), and is regularly tested on macOS 10.14. //! +//! ## Window initialization +//! //! A lot of functionality expects the application to be ready before you //! start doing anything; this includes creating windows, fetching monitors, //! drawing, and so on, see issues [#2238], [#2051] and [#2087]. //! //! If you encounter problems, you should try doing your initialization inside -//! `Event::Resumed`. +//! [`ApplicationHandler::resumed`]. //! //! [#2238]: https://github.com/rust-windowing/winit/issues/2238 //! [#2051]: https://github.com/rust-windowing/winit/issues/2051 //! [#2087]: https://github.com/rust-windowing/winit/issues/2087 +//! [`ApplicationHandler::resumed`]: crate::application::ApplicationHandler::resumed +//! +//! ## Custom `NSApplicationDelegate` +//! +//! Winit usually handles everything related to the lifecycle events of the application. Sometimes, +//! though, you might want to do more niche stuff, such as [handle when the user re-activates the +//! application][reopen]. Such functionality is not exposed directly in Winit, since it would +//! increase the API surface by quite a lot. +//! +//! [reopen]: https://developer.apple.com/documentation/appkit/nsapplicationdelegate/1428638-applicationshouldhandlereopen?language=objc +//! +//! Instead, Winit guarantees that it will not register an application delegate, so the solution is +//! to register your own application delegate, as outlined in the following example (see +//! `objc2-app-kit` for more detailed information). +#![cfg_attr(target_os = "macos", doc = "```")] +#![cfg_attr(not(target_os = "macos"), doc = "```ignore")] +//! use objc2::rc::Retained; +//! use objc2::runtime::ProtocolObject; +//! use objc2::{declare_class, msg_send_id, mutability, ClassType, DeclaredClass}; +//! use objc2_app_kit::{NSApplication, NSApplicationDelegate}; +//! use objc2_foundation::{NSArray, NSURL, MainThreadMarker, NSObject, NSObjectProtocol}; +//! use winit::event_loop::EventLoop; +//! +//! declare_class!( +//! struct AppDelegate; +//! +//! unsafe impl ClassType for AppDelegate { +//! type Super = NSObject; +//! type Mutability = mutability::MainThreadOnly; +//! const NAME: &'static str = "MyAppDelegate"; +//! } +//! +//! impl DeclaredClass for AppDelegate {} +//! +//! unsafe impl NSObjectProtocol for AppDelegate {} +//! +//! unsafe impl NSApplicationDelegate for AppDelegate { +//! #[method(application:openURLs:)] +//! fn application_openURLs(&self, application: &NSApplication, urls: &NSArray) { +//! // Note: To specifically get `application:openURLs:` to work, you _might_ +//! // have to bundle your application. This is not done in this example. +//! println!("open urls: {application:?}, {urls:?}"); +//! } +//! } +//! ); +//! +//! impl AppDelegate { +//! fn new(mtm: MainThreadMarker) -> Retained { +//! unsafe { msg_send_id![super(mtm.alloc().set_ivars(())), init] } +//! } +//! } +//! +//! fn main() -> Result<(), Box> { +//! let event_loop = EventLoop::new()?; +//! +//! let mtm = MainThreadMarker::new().unwrap(); +//! let delegate = AppDelegate::new(mtm); +//! // Important: Call `sharedApplication` after `EventLoop::new`, +//! // doing it before is not yet supported. +//! let app = NSApplication::sharedApplication(mtm); +//! app.setDelegate(Some(ProtocolObject::from_ref(&*delegate))); +//! +//! // event_loop.run_app(&mut my_app); +//! Ok(()) +//! } +//! ``` use std::os::raw::c_void; diff --git a/src/platform_impl/apple/appkit/app.rs b/src/platform_impl/apple/appkit/app.rs index bbbd14e4b7..5313640131 100644 --- a/src/platform_impl/apple/appkit/app.rs +++ b/src/platform_impl/apple/appkit/app.rs @@ -1,10 +1,12 @@ #![allow(clippy::unnecessary_cast)] +use std::rc::Rc; + use objc2::{declare_class, msg_send, mutability, ClassType, DeclaredClass}; use objc2_app_kit::{NSApplication, NSEvent, NSEventModifierFlags, NSEventType, NSResponder}; use objc2_foundation::{MainThreadMarker, NSObject}; -use super::app_state::ApplicationDelegate; +use super::app_state::AppState; use super::DEVICE_ID; use crate::event::{DeviceEvent, ElementState}; @@ -38,15 +40,15 @@ declare_class!( key_window.sendEvent(event); } } else { - let delegate = ApplicationDelegate::get(MainThreadMarker::from(self)); - maybe_dispatch_device_event(&delegate, event); + let app_state = AppState::get(MainThreadMarker::from(self)); + maybe_dispatch_device_event(&app_state, event); unsafe { msg_send![super(self), sendEvent: event] } } } } ); -fn maybe_dispatch_device_event(delegate: &ApplicationDelegate, event: &NSEvent) { +fn maybe_dispatch_device_event(app_state: &Rc, event: &NSEvent) { let event_type = unsafe { event.r#type() }; #[allow(non_upper_case_globals)] match event_type { @@ -58,7 +60,7 @@ fn maybe_dispatch_device_event(delegate: &ApplicationDelegate, event: &NSEvent) let delta_y = unsafe { event.deltaY() } as f64; if delta_x != 0.0 || delta_y != 0.0 { - delegate.maybe_queue_with_handler(move |app, event_loop| { + app_state.maybe_queue_with_handler(move |app, event_loop| { app.device_event(event_loop, DEVICE_ID, DeviceEvent::MouseMotion { delta: (delta_x, delta_y), }); @@ -67,7 +69,7 @@ fn maybe_dispatch_device_event(delegate: &ApplicationDelegate, event: &NSEvent) }, NSEventType::LeftMouseDown | NSEventType::RightMouseDown | NSEventType::OtherMouseDown => { let button = unsafe { event.buttonNumber() } as u32; - delegate.maybe_queue_with_handler(move |app, event_loop| { + app_state.maybe_queue_with_handler(move |app, event_loop| { app.device_event(event_loop, DEVICE_ID, DeviceEvent::Button { button, state: ElementState::Pressed, @@ -76,7 +78,7 @@ fn maybe_dispatch_device_event(delegate: &ApplicationDelegate, event: &NSEvent) }, NSEventType::LeftMouseUp | NSEventType::RightMouseUp | NSEventType::OtherMouseUp => { let button = unsafe { event.buttonNumber() } as u32; - delegate.maybe_queue_with_handler(move |app, event_loop| { + app_state.maybe_queue_with_handler(move |app, event_loop| { app.device_event(event_loop, DEVICE_ID, DeviceEvent::Button { button, state: ElementState::Released, diff --git a/src/platform_impl/apple/appkit/app_state.rs b/src/platform_impl/apple/appkit/app_state.rs index 0a82daf09a..0bf7e0d57b 100644 --- a/src/platform_impl/apple/appkit/app_state.rs +++ b/src/platform_impl/apple/appkit/app_state.rs @@ -1,14 +1,12 @@ -use std::cell::{Cell, RefCell}; +use std::cell::{Cell, OnceCell, RefCell}; use std::mem; -use std::rc::Weak; +use std::rc::{Rc, Weak}; use std::sync::atomic::{AtomicBool, Ordering as AtomicOrdering}; use std::sync::Arc; use std::time::Instant; -use objc2::rc::Retained; -use objc2::{declare_class, msg_send_id, mutability, ClassType, DeclaredClass}; -use objc2_app_kit::{NSApplication, NSApplicationActivationPolicy, NSApplicationDelegate}; -use objc2_foundation::{MainThreadMarker, NSNotification, NSObject, NSObjectProtocol}; +use objc2_app_kit::{NSApplication, NSApplicationActivationPolicy}; +use objc2_foundation::{MainThreadMarker, NSNotification}; use super::event_handler::EventHandler; use super::event_loop::{stop_app_immediately, ActiveEventLoop, PanicInfo}; @@ -21,6 +19,7 @@ use crate::window::WindowId as RootWindowId; #[derive(Debug)] pub(super) struct AppState { + mtm: MainThreadMarker, activation_policy: NSApplicationActivationPolicy, default_menu: bool, activate_ignoring_other_apps: bool, @@ -46,43 +45,32 @@ pub(super) struct AppState { // as such should be careful to not add fields that, in turn, strongly reference those. } -declare_class!( - #[derive(Debug)] - pub(super) struct ApplicationDelegate; +// TODO(madsmtm): Use `MainThreadBound` once that is possible in `static`s. +struct StaticMainThreadBound(T); - unsafe impl ClassType for ApplicationDelegate { - type Super = NSObject; - type Mutability = mutability::MainThreadOnly; - const NAME: &'static str = "WinitApplicationDelegate"; +impl StaticMainThreadBound { + const fn get(&self, _mtm: MainThreadMarker) -> &T { + &self.0 } +} - impl DeclaredClass for ApplicationDelegate { - type Ivars = AppState; - } - - unsafe impl NSObjectProtocol for ApplicationDelegate {} - - unsafe impl NSApplicationDelegate for ApplicationDelegate { - #[method(applicationDidFinishLaunching:)] - fn app_did_finish_launching(&self, notification: &NSNotification) { - self.did_finish_launching(notification) - } +unsafe impl Send for StaticMainThreadBound {} +unsafe impl Sync for StaticMainThreadBound {} - #[method(applicationWillTerminate:)] - fn app_will_terminate(&self, notification: &NSNotification) { - self.will_terminate(notification) - } - } -); +// SAFETY: Creating `StaticMainThreadBound` in a `const` context, where there is no concept of the +// main thread. +static GLOBAL: StaticMainThreadBound>> = + StaticMainThreadBound(OnceCell::new()); -impl ApplicationDelegate { - pub(super) fn new( +impl AppState { + pub(super) fn setup_global( mtm: MainThreadMarker, activation_policy: NSApplicationActivationPolicy, default_menu: bool, activate_ignoring_other_apps: bool, - ) -> Retained { - let this = mtm.alloc().set_ivars(AppState { + ) -> Rc { + let this = Rc::new(AppState { + mtm, activation_policy, proxy_wake_up: Arc::new(AtomicBool::new(false)), default_menu, @@ -102,33 +90,42 @@ impl ApplicationDelegate { wait_timeout: Cell::new(None), pending_redraw: RefCell::new(vec![]), }); - unsafe { msg_send_id![super(this), init] } + + GLOBAL.get(mtm).set(this.clone()).expect("application state can only be set once"); + this } - // NOTE: This will, globally, only be run once, no matter how many - // `EventLoop`s the user creates. - fn did_finish_launching(&self, _notification: &NSNotification) { - trace_scope!("applicationDidFinishLaunching:"); - self.ivars().is_launched.set(true); + pub fn get(mtm: MainThreadMarker) -> Rc { + GLOBAL + .get(mtm) + .get() + .expect("tried to get application state before it was registered") + .clone() + } + + // NOTE: This notification will, globally, only be emitted once, + // no matter how many `EventLoop`s the user creates. + pub fn did_finish_launching(self: &Rc, _notification: &NSNotification) { + trace_scope!("NSApplicationDidFinishLaunchingNotification"); + self.is_launched.set(true); - let mtm = MainThreadMarker::from(self); - let app = NSApplication::sharedApplication(mtm); + let app = NSApplication::sharedApplication(self.mtm); // We need to delay setting the activation policy and activating the app // until `applicationDidFinishLaunching` has been called. Otherwise the // menu bar is initially unresponsive on macOS 10.15. - app.setActivationPolicy(self.ivars().activation_policy); + app.setActivationPolicy(self.activation_policy); window_activation_hack(&app); #[allow(deprecated)] - app.activateIgnoringOtherApps(self.ivars().activate_ignoring_other_apps); + app.activateIgnoringOtherApps(self.activate_ignoring_other_apps); - if self.ivars().default_menu { + if self.default_menu { // The menubar initialization should be before the `NewEvents` event, to allow // overriding of the default menu even if it's created menu::initialize(&app); } - self.ivars().waker.borrow_mut().start(); + self.waker.borrow_mut().start(); self.set_is_running(true); self.dispatch_init_events(); @@ -138,77 +135,65 @@ impl ApplicationDelegate { // // In this case we still want to consider Winit's `EventLoop` to be "running", // so we call `start_running()` above. - if self.ivars().stop_on_launch.get() { + if self.stop_on_launch.get() { // NOTE: the original idea had been to only stop the underlying `RunLoop` // for the app but that didn't work as expected (`-[NSApplication run]` // effectively ignored the attempt to stop the RunLoop and re-started it). // // So we return from `pump_events` by stopping the application. - let app = NSApplication::sharedApplication(mtm); + let app = NSApplication::sharedApplication(self.mtm); stop_app_immediately(&app); } } - fn will_terminate(&self, _notification: &NSNotification) { - trace_scope!("applicationWillTerminate:"); + pub fn will_terminate(self: &Rc, _notification: &NSNotification) { + trace_scope!("NSApplicationWillTerminateNotification"); // TODO: Notify every window that it will be destroyed, like done in iOS? self.internal_exit(); } - pub fn get(mtm: MainThreadMarker) -> Retained { - let app = NSApplication::sharedApplication(mtm); - let delegate = - unsafe { app.delegate() }.expect("a delegate was not configured on the application"); - if delegate.is_kind_of::() { - // SAFETY: Just checked that the delegate is an instance of `ApplicationDelegate` - unsafe { Retained::cast(delegate) } - } else { - panic!("tried to get a delegate that was not the one Winit has registered") - } - } - - /// Place the event handler in the application delegate for the duration + /// Place the event handler in the application state for the duration /// of the given closure. pub fn set_event_handler( &self, handler: &mut dyn ApplicationHandler, closure: impl FnOnce() -> R, ) -> R { - self.ivars().event_handler.set(handler, closure) + self.event_handler.set(handler, closure) } pub fn proxy_wake_up(&self) -> Arc { - self.ivars().proxy_wake_up.clone() + self.proxy_wake_up.clone() } /// If `pump_events` is called to progress the event loop then we /// bootstrap the event loop via `-[NSApplication run]` but will use /// `CFRunLoopRunInMode` for subsequent calls to `pump_events`. pub fn set_stop_on_launch(&self) { - self.ivars().stop_on_launch.set(true); + self.stop_on_launch.set(true); } pub fn set_stop_before_wait(&self, value: bool) { - self.ivars().stop_before_wait.set(value) + self.stop_before_wait.set(value) } pub fn set_stop_after_wait(&self, value: bool) { - self.ivars().stop_after_wait.set(value) + self.stop_after_wait.set(value) } pub fn set_stop_on_redraw(&self, value: bool) { - self.ivars().stop_on_redraw.set(value) + self.stop_on_redraw.set(value) } pub fn set_wait_timeout(&self, value: Option) { - self.ivars().wait_timeout.set(value) + self.wait_timeout.set(value) } /// Clears the `running` state and resets the `control_flow` state when an `EventLoop` exits. /// /// NOTE: that if the `NSApplication` has been launched then that state is preserved, /// and we won't need to re-launch the app if subsequent EventLoops are run. - pub fn internal_exit(&self) { + pub fn internal_exit(self: &Rc) { self.with_handler(|app, event_loop| { app.exiting(event_loop); }); @@ -221,42 +206,41 @@ impl ApplicationDelegate { } pub fn is_launched(&self) -> bool { - self.ivars().is_launched.get() + self.is_launched.get() } pub fn set_is_running(&self, value: bool) { - self.ivars().is_running.set(value) + self.is_running.set(value) } pub fn is_running(&self) -> bool { - self.ivars().is_running.get() + self.is_running.get() } pub fn exit(&self) { - self.ivars().exit.set(true) + self.exit.set(true) } pub fn clear_exit(&self) { - self.ivars().exit.set(false) + self.exit.set(false) } pub fn exiting(&self) -> bool { - self.ivars().exit.get() + self.exit.get() } pub fn set_control_flow(&self, value: ControlFlow) { - self.ivars().control_flow.set(value) + self.control_flow.set(value) } pub fn control_flow(&self) -> ControlFlow { - self.ivars().control_flow.get() + self.control_flow.get() } - pub fn handle_redraw(&self, window_id: WindowId) { - let mtm = MainThreadMarker::from(self); + pub fn handle_redraw(self: &Rc, window_id: WindowId) { // Redraw request might come out of order from the OS. // -> Don't go back into the event handler when our callstack originates from there - if !self.ivars().event_handler.in_use() { + if !self.event_handler.in_use() { self.with_handler(|app, event_loop| { app.window_event(event_loop, RootWindowId(window_id), WindowEvent::RedrawRequested); }); @@ -264,24 +248,24 @@ impl ApplicationDelegate { // `pump_events` will request to stop immediately _after_ dispatching RedrawRequested // events as a way to ensure that `pump_events` can't block an external loop // indefinitely - if self.ivars().stop_on_redraw.get() { - let app = NSApplication::sharedApplication(mtm); + if self.stop_on_redraw.get() { + let app = NSApplication::sharedApplication(self.mtm); stop_app_immediately(&app); } } } pub fn queue_redraw(&self, window_id: WindowId) { - let mut pending_redraw = self.ivars().pending_redraw.borrow_mut(); + let mut pending_redraw = self.pending_redraw.borrow_mut(); if !pending_redraw.contains(&window_id) { pending_redraw.push(window_id); } - self.ivars().run_loop.wakeup(); + self.run_loop.wakeup(); } #[track_caller] pub fn maybe_queue_with_handler( - &self, + self: &Rc, callback: impl FnOnce(&mut dyn ApplicationHandler, &ActiveEventLoop) + 'static, ) { // Most programmer actions in AppKit (e.g. change window fullscreen, set focused, etc.) @@ -290,26 +274,28 @@ impl ApplicationDelegate { // However, it is not documented which actions do this, and which ones are done immediately, // so to make sure that we don't encounter re-entrancy issues, we first check if we're // currently handling another event, and if we are, we queue the event instead. - if !self.ivars().event_handler.in_use() { + if !self.event_handler.in_use() { self.with_handler(callback); } else { tracing::debug!("had to queue event since another is currently being handled"); - let this = self.retain(); - self.ivars().run_loop.queue_closure(move || { + let this = Rc::clone(self); + self.run_loop.queue_closure(move || { this.with_handler(callback); }); } } #[track_caller] - fn with_handler(&self, callback: impl FnOnce(&mut dyn ApplicationHandler, &ActiveEventLoop)) { - let event_loop = - ActiveEventLoop { delegate: self.retain(), mtm: MainThreadMarker::from(self) }; - self.ivars().event_handler.handle(callback, &event_loop); + fn with_handler( + self: &Rc, + callback: impl FnOnce(&mut dyn ApplicationHandler, &ActiveEventLoop), + ) { + let event_loop = ActiveEventLoop { app_state: Rc::clone(self), mtm: self.mtm }; + self.event_handler.handle(callback, &event_loop); } /// dispatch `NewEvents(Init)` + `Resumed` - pub fn dispatch_init_events(&self) { + pub fn dispatch_init_events(self: &Rc) { self.with_handler(|app, event_loop| app.new_events(event_loop, StartCause::Init)); // NB: For consistency all platforms must call `can_create_surfaces` even though macOS // applications don't themselves have a formal surface destroy/create lifecycle. @@ -317,23 +303,22 @@ impl ApplicationDelegate { } // Called by RunLoopObserver after finishing waiting for new events - pub fn wakeup(&self, panic_info: Weak) { - let mtm = MainThreadMarker::from(self); + pub fn wakeup(self: &Rc, panic_info: Weak) { let panic_info = panic_info .upgrade() .expect("The panic info must exist here. This failure indicates a developer error."); // Return when in event handler due to https://github.com/rust-windowing/winit/issues/1779 - if panic_info.is_panicking() || !self.ivars().event_handler.ready() || !self.is_running() { + if panic_info.is_panicking() || !self.event_handler.ready() || !self.is_running() { return; } - if self.ivars().stop_after_wait.get() { - let app = NSApplication::sharedApplication(mtm); + if self.stop_after_wait.get() { + let app = NSApplication::sharedApplication(self.mtm); stop_app_immediately(&app); } - let start = self.ivars().start_time.get().unwrap(); + let start = self.start_time.get().unwrap(); let cause = match self.control_flow() { ControlFlow::Poll => StartCause::Poll, ControlFlow::Wait => StartCause::WaitCancelled { start, requested_resume: None }, @@ -350,8 +335,7 @@ impl ApplicationDelegate { } // Called by RunLoopObserver before waiting for new events - pub fn cleared(&self, panic_info: Weak) { - let mtm = MainThreadMarker::from(self); + pub fn cleared(self: &Rc, panic_info: Weak) { let panic_info = panic_info .upgrade() .expect("The panic info must exist here. This failure indicates a developer error."); @@ -359,15 +343,15 @@ impl ApplicationDelegate { // Return when in event handler due to https://github.com/rust-windowing/winit/issues/1779 // XXX: how does it make sense that `event_handler.ready()` can ever return `false` here if // we're about to return to the `CFRunLoop` to poll for new events? - if panic_info.is_panicking() || !self.ivars().event_handler.ready() || !self.is_running() { + if panic_info.is_panicking() || !self.event_handler.ready() || !self.is_running() { return; } - if self.ivars().proxy_wake_up.swap(false, AtomicOrdering::Relaxed) { + if self.proxy_wake_up.swap(false, AtomicOrdering::Relaxed) { self.with_handler(|app, event_loop| app.proxy_wake_up(event_loop)); } - let redraw = mem::take(&mut *self.ivars().pending_redraw.borrow_mut()); + let redraw = mem::take(&mut *self.pending_redraw.borrow_mut()); for window_id in redraw { self.with_handler(|app, event_loop| { app.window_event(event_loop, RootWindowId(window_id), WindowEvent::RedrawRequested); @@ -378,22 +362,22 @@ impl ApplicationDelegate { }); if self.exiting() { - let app = NSApplication::sharedApplication(mtm); + let app = NSApplication::sharedApplication(self.mtm); stop_app_immediately(&app); } - if self.ivars().stop_before_wait.get() { - let app = NSApplication::sharedApplication(mtm); + if self.stop_before_wait.get() { + let app = NSApplication::sharedApplication(self.mtm); stop_app_immediately(&app); } - self.ivars().start_time.set(Some(Instant::now())); - let wait_timeout = self.ivars().wait_timeout.get(); // configured by pump_events + self.start_time.set(Some(Instant::now())); + let wait_timeout = self.wait_timeout.get(); // configured by pump_events let app_timeout = match self.control_flow() { ControlFlow::Wait => None, ControlFlow::Poll => Some(Instant::now()), ControlFlow::WaitUntil(instant) => Some(instant), }; - self.ivars().waker.borrow_mut().start_at(min_timeout(wait_timeout, app_timeout)); + self.waker.borrow_mut().start_at(min_timeout(wait_timeout, app_timeout)); } } diff --git a/src/platform_impl/apple/appkit/event_handler.rs b/src/platform_impl/apple/appkit/event_handler.rs index 8cc755549e..9eebdba702 100644 --- a/src/platform_impl/apple/appkit/event_handler.rs +++ b/src/platform_impl/apple/appkit/event_handler.rs @@ -124,7 +124,7 @@ impl EventHandler { callback(*user_app, event_loop); }, Ok(None) => { - // `NSApplication`, our app delegate and this handler are all + // `NSApplication`, our app state and this handler are all // global state and so it's not impossible that we could get // an event after the application has exited the `EventLoop`. tracing::error!("tried to run event handler, but no handler was set"); diff --git a/src/platform_impl/apple/appkit/event_loop.rs b/src/platform_impl/apple/appkit/event_loop.rs index 6af45aa0e7..e31c2cdf28 100644 --- a/src/platform_impl/apple/appkit/event_loop.rs +++ b/src/platform_impl/apple/appkit/event_loop.rs @@ -14,13 +14,16 @@ use core_foundation::runloop::{ CFRunLoopSourceCreate, CFRunLoopSourceRef, CFRunLoopSourceSignal, CFRunLoopWakeUp, }; use objc2::rc::{autoreleasepool, Retained}; -use objc2::runtime::ProtocolObject; use objc2::{msg_send_id, sel, ClassType}; -use objc2_app_kit::{NSApplication, NSApplicationActivationPolicy, NSWindow}; -use objc2_foundation::{MainThreadMarker, NSObjectProtocol}; +use objc2_app_kit::{ + NSApplication, NSApplicationActivationPolicy, NSApplicationDidFinishLaunchingNotification, + NSApplicationWillTerminateNotification, NSWindow, +}; +use objc2_foundation::{MainThreadMarker, NSNotificationCenter, NSObject, NSObjectProtocol}; +use super::super::notification_center::create_observer; use super::app::WinitApplication; -use super::app_state::ApplicationDelegate; +use super::app_state::AppState; use super::cursor::CustomCursor; use super::event::dummy_event; use super::monitor; @@ -71,15 +74,11 @@ impl PanicInfo { #[derive(Debug)] pub struct ActiveEventLoop { - pub(super) delegate: Retained, + pub(super) app_state: Rc, pub(super) mtm: MainThreadMarker, } impl ActiveEventLoop { - pub(super) fn app_delegate(&self) -> &ApplicationDelegate { - &self.delegate - } - pub(crate) fn hide_application(&self) { NSApplication::sharedApplication(self.mtm).hide(None) } @@ -99,7 +98,7 @@ impl ActiveEventLoop { impl RootActiveEventLoop for ActiveEventLoop { fn create_proxy(&self) -> RootEventLoopProxy { - let event_loop_proxy = EventLoopProxy::new(self.delegate.proxy_wake_up()); + let event_loop_proxy = EventLoopProxy::new(self.app_state.proxy_wake_up()); RootEventLoopProxy { event_loop_proxy } } @@ -140,19 +139,19 @@ impl RootActiveEventLoop for ActiveEventLoop { } fn set_control_flow(&self, control_flow: ControlFlow) { - self.delegate.set_control_flow(control_flow) + self.app_state.set_control_flow(control_flow) } fn control_flow(&self) -> ControlFlow { - self.delegate.control_flow() + self.app_state.control_flow() } fn exit(&self) { - self.delegate.exit() + self.app_state.exit() } fn exiting(&self) -> bool { - self.delegate.exiting() + self.app_state.exiting() } fn owned_display_handle(&self) -> RootOwnedDisplayHandle { @@ -179,14 +178,17 @@ pub struct EventLoop { /// 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, + app_state: Rc, window_target: ActiveEventLoop, panic_info: Rc, + + // Since macOS 10.11, 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, + _will_terminate_observer: Retained, } #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] @@ -229,25 +231,49 @@ impl EventLoop { ActivationPolicy::Prohibited => NSApplicationActivationPolicy::Prohibited, }; - let delegate = ApplicationDelegate::new( + let app_state = AppState::setup_global( mtm, activation_policy, attributes.default_menu, attributes.activate_ignoring_other_apps, ); - autoreleasepool(|_| { - app.setDelegate(Some(ProtocolObject::from_ref(&*delegate))); - }); + let center = unsafe { NSNotificationCenter::defaultCenter() }; + + let weak_app_state = Rc::downgrade(&app_state); + let _did_finish_launching_observer = create_observer( + ¢er, + // `applicationDidFinishLaunching:` + unsafe { NSApplicationDidFinishLaunchingNotification }, + move |notification| { + if let Some(app_state) = weak_app_state.upgrade() { + app_state.did_finish_launching(notification); + } + }, + ); + + let weak_app_state = Rc::downgrade(&app_state); + let _will_terminate_observer = create_observer( + ¢er, + // `applicationWillTerminate:` + unsafe { NSApplicationWillTerminateNotification }, + move |notification| { + if let Some(app_state) = weak_app_state.upgrade() { + app_state.will_terminate(notification); + } + }, + ); let panic_info: Rc = Default::default(); setup_control_flow_observers(mtm, Rc::downgrade(&panic_info)); Ok(EventLoop { app, - delegate: delegate.clone(), - window_target: ActiveEventLoop { delegate, mtm }, + app_state: app_state.clone(), + window_target: ActiveEventLoop { app_state, mtm }, panic_info, + _did_finish_launching_observer, + _will_terminate_observer, }) } @@ -267,19 +293,19 @@ impl EventLoop { &mut self, mut app: A, ) -> Result<(), EventLoopError> { - self.delegate.clear_exit(); - self.delegate.set_event_handler(&mut app, || { + self.app_state.clear_exit(); + self.app_state.set_event_handler(&mut 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(); + self.app_state.set_wait_timeout(None); + self.app_state.set_stop_before_wait(false); + self.app_state.set_stop_after_wait(false); + self.app_state.set_stop_on_redraw(false); + + if self.app_state.is_launched() { + debug_assert!(!self.app_state.is_running()); + self.app_state.set_is_running(true); + self.app_state.dispatch_init_events(); } // SAFETY: We do not run the application re-entrantly @@ -294,7 +320,7 @@ impl EventLoop { resume_unwind(panic); } - self.delegate.internal_exit() + self.app_state.internal_exit() }) }); @@ -306,47 +332,47 @@ impl EventLoop { timeout: Option, mut app: A, ) -> PumpStatus { - self.delegate.set_event_handler(&mut app, || { + self.app_state.set_event_handler(&mut 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()); + if !self.app_state.is_launched() { + debug_assert!(!self.app_state.is_running()); - self.delegate.set_stop_on_launch(); + self.app_state.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() { + } else if !self.app_state.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(); + self.app_state.set_is_running(true); + self.app_state.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); + self.app_state.set_wait_timeout(None); + self.app_state.set_stop_before_wait(true); }, Some(duration) => { - self.delegate.set_stop_before_wait(false); + self.app_state.set_stop_before_wait(false); let timeout = Instant::now() + duration; - self.delegate.set_wait_timeout(Some(timeout)); - self.delegate.set_stop_after_wait(true); + self.app_state.set_wait_timeout(Some(timeout)); + self.app_state.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.app_state.set_wait_timeout(None); + self.app_state.set_stop_before_wait(false); + self.app_state.set_stop_after_wait(true); }, } - self.delegate.set_stop_on_redraw(true); + self.app_state.set_stop_on_redraw(true); // SAFETY: We do not run the application re-entrantly unsafe { self.app.run() }; } @@ -360,8 +386,8 @@ impl EventLoop { resume_unwind(panic); } - if self.delegate.exiting() { - self.delegate.internal_exit(); + if self.app_state.exiting() { + self.app_state.internal_exit(); PumpStatus::Exit(0) } else { PumpStatus::Continue diff --git a/src/platform_impl/apple/appkit/observer.rs b/src/platform_impl/apple/appkit/observer.rs index 8339803086..426b90bb96 100644 --- a/src/platform_impl/apple/appkit/observer.rs +++ b/src/platform_impl/apple/appkit/observer.rs @@ -22,7 +22,7 @@ use core_foundation::runloop::{ use objc2_foundation::MainThreadMarker; use tracing::error; -use super::app_state::ApplicationDelegate; +use super::app_state::AppState; use super::event_loop::{stop_app_on_panic, PanicInfo}; use super::ffi; @@ -59,7 +59,7 @@ extern "C" fn control_flow_begin_handler( match activity { kCFRunLoopAfterWaiting => { // trace!("Triggered `CFRunLoopAfterWaiting`"); - ApplicationDelegate::get(MainThreadMarker::new().unwrap()).wakeup(panic_info); + AppState::get(MainThreadMarker::new().unwrap()).wakeup(panic_info); // trace!("Completed `CFRunLoopAfterWaiting`"); }, _ => unreachable!(), @@ -81,7 +81,7 @@ extern "C" fn control_flow_end_handler( match activity { kCFRunLoopBeforeWaiting => { // trace!("Triggered `CFRunLoopBeforeWaiting`"); - ApplicationDelegate::get(MainThreadMarker::new().unwrap()).cleared(panic_info); + AppState::get(MainThreadMarker::new().unwrap()).cleared(panic_info); // trace!("Completed `CFRunLoopBeforeWaiting`"); }, kCFRunLoopExit => (), // unimplemented!(), // not expected to ever happen diff --git a/src/platform_impl/apple/appkit/view.rs b/src/platform_impl/apple/appkit/view.rs index a5b048ea67..9392a283fc 100644 --- a/src/platform_impl/apple/appkit/view.rs +++ b/src/platform_impl/apple/appkit/view.rs @@ -2,6 +2,7 @@ use std::cell::{Cell, RefCell}; use std::collections::{HashMap, VecDeque}; use std::ptr; +use std::rc::Rc; use objc2::rc::{Retained, WeakId}; use objc2::runtime::{AnyObject, Sel}; @@ -16,7 +17,7 @@ use objc2_foundation::{ NSPoint, NSRange, NSRect, NSSize, NSString, NSUInteger, }; -use super::app_state::ApplicationDelegate; +use super::app_state::AppState; use super::cursor::{default_cursor, invisible_cursor}; use super::event::{ code_to_key, code_to_location, create_key_event, event_mods, lalt_pressed, ralt_pressed, @@ -112,7 +113,7 @@ fn get_left_modifier_code(key: &Key) -> KeyCode { #[derive(Debug)] pub struct ViewState { /// Strong reference to the global application state. - app_delegate: Retained, + app_state: Rc, cursor_state: RefCell, ime_position: Cell, @@ -206,7 +207,7 @@ declare_class!( // It's a workaround for https://github.com/rust-windowing/winit/issues/2640, don't replace with `self.window_id()`. if let Some(window) = self.ivars()._ns_window.load() { - self.ivars().app_delegate.handle_redraw(window.id()); + self.ivars().app_state.handle_redraw(window.id()); } // This is a direct subclass of NSView, no need to call superclass' drawRect: @@ -687,7 +688,7 @@ declare_class!( self.update_modifiers(event, false); - self.ivars().app_delegate.maybe_queue_with_handler(move |app, event_loop| + self.ivars().app_state.maybe_queue_with_handler(move |app, event_loop| app.device_event(event_loop, DEVICE_ID, DeviceEvent::MouseWheel { delta }) ); self.queue_event(WindowEvent::MouseWheel { @@ -782,14 +783,14 @@ declare_class!( impl WinitView { pub(super) fn new( - app_delegate: &ApplicationDelegate, + app_state: &Rc, window: &WinitWindow, accepts_first_mouse: bool, option_as_alt: OptionAsAlt, ) -> Retained { let mtm = MainThreadMarker::from(window); let this = mtm.alloc().set_ivars(ViewState { - app_delegate: app_delegate.retain(), + app_state: Rc::clone(app_state), cursor_state: Default::default(), ime_position: Default::default(), ime_size: Default::default(), @@ -834,7 +835,7 @@ impl WinitView { fn queue_event(&self, event: WindowEvent) { let window_id = RootWindowId(self.window().id()); - self.ivars().app_delegate.maybe_queue_with_handler(move |app, event_loop| { + self.ivars().app_state.maybe_queue_with_handler(move |app, event_loop| { app.window_event(event_loop, window_id, event); }); } diff --git a/src/platform_impl/apple/appkit/window.rs b/src/platform_impl/apple/appkit/window.rs index 04da8b29ee..06013051e0 100644 --- a/src/platform_impl/apple/appkit/window.rs +++ b/src/platform_impl/apple/appkit/window.rs @@ -28,9 +28,8 @@ impl Window { attributes: WindowAttributes, ) -> Result { let mtm = window_target.mtm; - let delegate = autoreleasepool(|_| { - WindowDelegate::new(window_target.app_delegate(), attributes, mtm) - })?; + let delegate = + autoreleasepool(|_| WindowDelegate::new(&window_target.app_state, attributes, mtm))?; Ok(Window { window: MainThreadBound::new(delegate.window().retain(), mtm), delegate: MainThreadBound::new(delegate, mtm), diff --git a/src/platform_impl/apple/appkit/window_delegate.rs b/src/platform_impl/apple/appkit/window_delegate.rs index 0450448848..a38cd0af51 100644 --- a/src/platform_impl/apple/appkit/window_delegate.rs +++ b/src/platform_impl/apple/appkit/window_delegate.rs @@ -3,6 +3,7 @@ use std::cell::{Cell, RefCell}; use std::collections::VecDeque; use std::ffi::c_void; use std::ptr; +use std::rc::Rc; use std::sync::{Arc, Mutex}; use core_graphics::display::{CGDisplay, CGPoint}; @@ -26,7 +27,7 @@ use objc2_foundation::{ }; use tracing::{trace, warn}; -use super::app_state::ApplicationDelegate; +use super::app_state::AppState; use super::cursor::cursor_from_icon; use super::monitor::{self, flip_window_screen_coordinates, get_display_id}; use super::observer::RunLoop; @@ -79,7 +80,7 @@ impl Default for PlatformSpecificWindowAttributes { #[derive(Debug)] pub(crate) struct State { /// Strong reference to the global application state. - app_delegate: Retained, + app_state: Rc, window: Retained, @@ -482,7 +483,7 @@ impl Drop for WindowDelegate { } fn new_window( - app_delegate: &ApplicationDelegate, + app_state: &Rc, attrs: &WindowAttributes, mtm: MainThreadMarker, ) -> Option> { @@ -622,7 +623,7 @@ fn new_window( } let view = WinitView::new( - app_delegate, + app_state, &window, attrs.platform_specific.accepts_first_mouse, attrs.platform_specific.option_as_alt, @@ -665,11 +666,11 @@ fn new_window( impl WindowDelegate { pub(super) fn new( - app_delegate: &ApplicationDelegate, + app_state: &Rc, attrs: WindowAttributes, mtm: MainThreadMarker, ) -> Result, RootOsError> { - let window = new_window(app_delegate, &attrs, mtm) + let window = new_window(app_state, &attrs, mtm) .ok_or_else(|| os_error!(OsError::CreationError("couldn't create `NSWindow`")))?; #[cfg(feature = "rwh_06")] @@ -709,7 +710,7 @@ impl WindowDelegate { } let delegate = mtm.alloc().set_ivars(State { - app_delegate: app_delegate.retain(), + app_state: Rc::clone(app_state), window: window.retain(), previous_position: Cell::new(None), previous_scale_factor: Cell::new(scale_factor), @@ -808,7 +809,7 @@ impl WindowDelegate { pub(crate) fn queue_event(&self, event: WindowEvent) { let window_id = RootWindowId(self.window().id()); - self.ivars().app_delegate.maybe_queue_with_handler(move |app, event_loop| { + self.ivars().app_state.maybe_queue_with_handler(move |app, event_loop| { app.window_event(event_loop, window_id, event); }); } @@ -907,7 +908,7 @@ impl WindowDelegate { } pub fn request_redraw(&self) { - self.ivars().app_delegate.queue_redraw(self.window().id()); + self.ivars().app_state.queue_redraw(self.window().id()); } #[inline] diff --git a/src/platform_impl/apple/mod.rs b/src/platform_impl/apple/mod.rs index 807aa61d9a..705ad1fc22 100644 --- a/src/platform_impl/apple/mod.rs +++ b/src/platform_impl/apple/mod.rs @@ -2,6 +2,7 @@ #[cfg(target_os = "macos")] mod appkit; +mod notification_center; #[cfg(not(target_os = "macos"))] mod uikit; diff --git a/src/platform_impl/apple/notification_center.rs b/src/platform_impl/apple/notification_center.rs new file mode 100644 index 0000000000..652bf1d079 --- /dev/null +++ b/src/platform_impl/apple/notification_center.rs @@ -0,0 +1,27 @@ +use std::ptr::NonNull; + +use block2::RcBlock; +use objc2::rc::Retained; +use objc2_foundation::{NSNotification, NSNotificationCenter, NSNotificationName, NSObject}; + +/// Observe the given notification. +/// +/// This is used in Winit as an alternative to declaring an application delegate, as we want to +/// give the user full control over those. +pub fn create_observer( + center: &NSNotificationCenter, + name: &NSNotificationName, + handler: impl Fn(&NSNotification) + 'static, +) -> Retained { + let block = RcBlock::new(move |notification: NonNull| { + handler(unsafe { notification.as_ref() }); + }); + unsafe { + center.addObserverForName_object_queue_usingBlock( + Some(name), + None, // No sender filter + None, // No queue, run on posting thread (i.e. main thread) + &block, + ) + } +} diff --git a/src/platform_impl/apple/uikit/app_delegate.rs b/src/platform_impl/apple/uikit/app_delegate.rs deleted file mode 100644 index 2328c58c50..0000000000 --- a/src/platform_impl/apple/uikit/app_delegate.rs +++ /dev/null @@ -1,60 +0,0 @@ -use objc2::{declare_class, mutability, ClassType, DeclaredClass}; -use objc2_foundation::{MainThreadMarker, NSObject}; -use objc2_ui_kit::UIApplication; - -use super::app_state::{self, send_occluded_event_for_all_windows, EventWrapper}; -use crate::event::Event; - -declare_class!( - pub struct AppDelegate; - - unsafe impl ClassType for AppDelegate { - type Super = NSObject; - type Mutability = mutability::InteriorMutable; - const NAME: &'static str = "WinitApplicationDelegate"; - } - - impl DeclaredClass for AppDelegate {} - - // UIApplicationDelegate protocol - unsafe impl AppDelegate { - #[method(application:didFinishLaunchingWithOptions:)] - fn did_finish_launching(&self, _application: &UIApplication, _: *mut NSObject) -> bool { - app_state::did_finish_launching(MainThreadMarker::new().unwrap()); - true - } - - #[method(applicationDidBecomeActive:)] - fn did_become_active(&self, _application: &UIApplication) { - let mtm = MainThreadMarker::new().unwrap(); - app_state::handle_nonuser_event(mtm, EventWrapper::StaticEvent(Event::Resumed)) - } - - #[method(applicationWillResignActive:)] - fn will_resign_active(&self, _application: &UIApplication) { - let mtm = MainThreadMarker::new().unwrap(); - app_state::handle_nonuser_event(mtm, EventWrapper::StaticEvent(Event::Suspended)) - } - - #[method(applicationWillEnterForeground:)] - fn will_enter_foreground(&self, application: &UIApplication) { - send_occluded_event_for_all_windows(application, false); - } - - #[method(applicationDidEnterBackground:)] - fn did_enter_background(&self, application: &UIApplication) { - send_occluded_event_for_all_windows(application, true); - } - - #[method(applicationWillTerminate:)] - fn will_terminate(&self, application: &UIApplication) { - app_state::terminated(application); - } - - #[method(applicationDidReceiveMemoryWarning:)] - fn did_receive_memory_warning(&self, _application: &UIApplication) { - let mtm = MainThreadMarker::new().unwrap(); - app_state::handle_nonuser_event(mtm, EventWrapper::StaticEvent(Event::MemoryWarning)) - } - } -); diff --git a/src/platform_impl/apple/uikit/event_loop.rs b/src/platform_impl/apple/uikit/event_loop.rs index 54abfe4459..bd52fc4029 100644 --- a/src/platform_impl/apple/uikit/event_loop.rs +++ b/src/platform_impl/apple/uikit/event_loop.rs @@ -12,11 +12,19 @@ use core_foundation::runloop::{ }; use objc2::rc::Retained; use objc2::{msg_send_id, ClassType}; -use objc2_foundation::{MainThreadMarker, NSString}; -use objc2_ui_kit::{UIApplication, UIApplicationMain, UIScreen}; +use objc2_foundation::{MainThreadMarker, NSNotificationCenter, NSObject}; +use objc2_ui_kit::{ + UIApplication, UIApplicationDidBecomeActiveNotification, + UIApplicationDidEnterBackgroundNotification, UIApplicationDidFinishLaunchingNotification, + UIApplicationDidReceiveMemoryWarningNotification, UIApplicationMain, + UIApplicationWillEnterForegroundNotification, UIApplicationWillResignActiveNotification, + UIApplicationWillTerminateNotification, UIScreen, +}; -use super::app_delegate::AppDelegate; -use super::app_state::{AppState, EventLoopHandler}; +use super::super::notification_center::create_observer; +use super::app_state::{ + send_occluded_event_for_all_windows, AppState, EventLoopHandler, EventWrapper, +}; use super::{app_state, monitor, MonitorHandle}; use crate::application::ApplicationHandler; use crate::error::{EventLoopError, ExternalError, NotSupportedError, OsError}; @@ -149,6 +157,18 @@ fn map_user_event<'a, A: ApplicationHandler + 'a>( 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)] @@ -173,7 +193,96 @@ impl EventLoop { // this line sets up the main run loop before `UIApplicationMain` setup_control_flow_observers(); - Ok(EventLoop { mtm, window_target: ActiveEventLoop { mtm } }) + 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_nonuser_event(mtm, EventWrapper::StaticEvent(Event::Resumed)); + }, + ); + let _will_resign_active_observer = create_observer( + ¢er, + // `applicationWillResignActive:` + unsafe { UIApplicationWillResignActiveNotification }, + move |_| { + app_state::handle_nonuser_event(mtm, EventWrapper::StaticEvent(Event::Suspended)); + }, + ); + 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 = 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 = 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 = unsafe { Retained::cast(app) }; + app_state::terminated(&app); + }, + ); + let _did_receive_memory_warning_observer = create_observer( + ¢er, + // `applicationDidReceiveMemoryWarning:` + unsafe { UIApplicationDidReceiveMemoryWarningNotification }, + move |_| { + app_state::handle_nonuser_event( + mtm, + EventWrapper::StaticEvent(Event::MemoryWarning), + ); + }, + ); + + 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) -> ! { @@ -199,9 +308,6 @@ impl EventLoop { app_state::will_launch(self.mtm, handler); - // Ensure application delegate is initialized - let _ = AppDelegate::class(); - extern "C" { // These functions are in crt_externs.h. fn _NSGetArgc() -> *mut c_int; @@ -212,8 +318,10 @@ impl EventLoop { 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, - Some(&NSString::from_str(AppDelegate::NAME)), ) }; unreachable!() diff --git a/src/platform_impl/apple/uikit/mod.rs b/src/platform_impl/apple/uikit/mod.rs index 417587e7db..d8347a75ee 100644 --- a/src/platform_impl/apple/uikit/mod.rs +++ b/src/platform_impl/apple/uikit/mod.rs @@ -1,6 +1,5 @@ #![allow(clippy::let_unit_value)] -mod app_delegate; mod app_state; mod event_loop; mod monitor;