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).
This commit is contained in:
parent
3392e9c1de
commit
92e9bfe0fc
17 changed files with 445 additions and 269 deletions
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
);
|
||||
|
|
@ -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<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)]
|
||||
|
|
@ -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<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_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<A: ApplicationHandler>(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!()
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
#![allow(clippy::let_unit_value)]
|
||||
|
||||
mod app_delegate;
|
||||
mod app_state;
|
||||
mod event_loop;
|
||||
mod monitor;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue