use std::cell::RefCell;
use std::sync::OnceLock;
use objc2::rc::Retained;
use objc2::runtime::{Imp, NSObject, NSObjectProtocol, Sel};
use objc2::{ClassType, MainThreadMarker, MainThreadOnly, Message, define_class, sel};
use objc2_foundation::{NSBundle, ns_string};
use objc2_ui_kit::{
UIApplication, UIResponder, UIScene, UISceneConnectionOptions, UISceneDelegate, UISceneSession,
UIWindow, UIWindowScene, UIWindowSceneDelegate,
};
define_class!(
#[unsafe(super(UIResponder, NSObject))]
#[thread_kind = MainThreadOnly]
#[name = "CorundumSceneDelegate"]
struct SceneDelegate;
unsafe impl NSObjectProtocol for SceneDelegate {}
unsafe impl UISceneDelegate for SceneDelegate {
#[unsafe(method(scene:willConnectToSession:options:))]
fn scene_will_connect(
&self,
scene: &UIScene,
_session: &UISceneSession,
_options: &UISceneConnectionOptions,
) {
show_waiting(scene);
}
}
unsafe impl UIWindowSceneDelegate for SceneDelegate {}
);
thread_local! {
static WAITING: RefCell<Vec<Retained<UIWindow>>> = const { RefCell::new(Vec::new()) };
}
static MAKE_KEY_AND_VISIBLE: OnceLock<Imp> = OnceLock::new();
pub(in crate::window) fn adopt() {
let manifest = NSBundle::mainBundle()
.objectForInfoDictionaryKey(ns_string!("UIApplicationSceneManifest"))
.is_some();
if !manifest {
tracing::debug!("no UIApplicationSceneManifest in Info.plist: the window has no scene");
return;
}
let _ = SceneDelegate::class();
if MAKE_KEY_AND_VISIBLE.get().is_some() {
return;
}
let Some(method) = UIWindow::class().instance_method(sel!(makeKeyAndVisible)) else {
tracing::warn!("UIWindow has no makeKeyAndVisible: the window has no scene");
return;
};
unsafe {
let imp: Imp =
std::mem::transmute(make_key_and_visible as extern "C-unwind" fn(&UIWindow, Sel));
let original = method.set_implementation(imp);
let _ = MAKE_KEY_AND_VISIBLE.set(original);
}
}
extern "C-unwind" fn make_key_and_visible(window: &UIWindow, cmd: Sel) {
if window.windowScene().is_none() {
let mtm = window.mtm();
match connected_scene(mtm) {
Some(scene) => window.setWindowScene(Some(&scene)),
None => {
WAITING.with_borrow_mut(|waiting| waiting.push(window.retain()));
return;
}
}
}
let Some(&original) = MAKE_KEY_AND_VISIBLE.get() else {
return;
};
unsafe {
let original: extern "C-unwind" fn(&UIWindow, Sel) = std::mem::transmute(original);
original(window, cmd);
}
}
fn connected_scene(mtm: MainThreadMarker) -> Option<Retained<UIWindowScene>> {
UIApplication::sharedApplication(mtm)
.connectedScenes()
.allObjects()
.into_iter()
.find_map(|scene| scene.downcast::<UIWindowScene>().ok())
}
fn show_waiting(scene: &UIScene) {
let Some(scene) = scene.downcast_ref::<UIWindowScene>() else {
return;
};
for window in WAITING.with_borrow_mut(std::mem::take) {
window.setWindowScene(Some(scene));
window.makeKeyAndVisible();
}
}