use std::cell::RefCell;
use std::collections::HashSet;
use std::sync::OnceLock;
use objc2::rc::Retained;
use objc2::runtime::{AnyClass, AnyObject, Bool, Imp, Method, Sel};
use objc2::sel;
use objc2_app_kit::NSView;
use winit::window::Window;
thread_local! {
static REFUSING: RefCell<HashSet<usize>> = RefCell::new(HashSet::new());
}
struct Originals {
key: Imp,
main: Imp,
}
static ORIGINALS: OnceLock<Option<Originals>> = OnceLock::new();
fn ns_window(window: &Window) -> Option<Retained<objc2_app_kit::NSWindow>> {
use winit::raw_window_handle::{HasWindowHandle, RawWindowHandle};
let RawWindowHandle::AppKit(h) = window.window_handle().ok()?.as_raw() else {
return None;
};
let view = unsafe { Retained::retain(h.ns_view.as_ptr().cast::<NSView>()) }?;
view.window()
}
pub fn refuse_key(window: &Window) -> bool {
let Some(ns) = ns_window(window) else {
return false;
};
let installed = ORIGINALS.get_or_init(|| install(ns.class())).is_some();
if installed {
REFUSING.with(|r| r.borrow_mut().insert(Retained::as_ptr(&ns) as usize));
}
installed
}
pub fn release(window: &Window) {
if let Some(ns) = ns_window(window) {
REFUSING.with(|r| r.borrow_mut().remove(&(Retained::as_ptr(&ns) as usize)));
}
}
fn defining_class(cls: &AnyClass, sel: Sel) -> Option<&Method> {
let mut at = Some(cls);
while let Some(c) = at {
if let Some(m) = c
.instance_methods()
.iter()
.find(|m| m.name() == sel)
.copied()
{
return Some(m);
}
at = c.superclass();
}
None
}
type AnswerFn = unsafe extern "C-unwind" fn(&AnyObject, Sel) -> Bool;
fn install(cls: &AnyClass) -> Option<Originals> {
let key = defining_class(cls, sel!(canBecomeKeyWindow))?;
let main = defining_class(cls, sel!(canBecomeMainWindow))?;
let originals = Originals {
key: key.implementation(),
main: main.implementation(),
};
unsafe {
key.set_implementation(imp(key_override as AnswerFn));
main.set_implementation(imp(main_override as AnswerFn));
}
Some(originals)
}
fn imp<F: Copy>(f: F) -> Imp {
const { assert!(std::mem::size_of::<F>() == std::mem::size_of::<Imp>()) };
unsafe { std::mem::transmute_copy(&f) }
}
fn refuses(this: &AnyObject) -> bool {
let ptr = this as *const AnyObject as usize;
REFUSING.with(|r| r.borrow().contains(&ptr))
}
fn original(which: fn(&Originals) -> Imp) -> AnswerFn {
let o = ORIGINALS
.get()
.and_then(Option::as_ref)
.expect("an override runs only once installed");
unsafe { std::mem::transmute::<Imp, AnswerFn>(which(o)) }
}
unsafe extern "C-unwind" fn key_override(this: &AnyObject, sel: Sel) -> Bool {
if refuses(this) {
return Bool::NO;
}
unsafe { original(|o| o.key)(this, sel) }
}
unsafe extern "C-unwind" fn main_override(this: &AnyObject, sel: Sel) -> Bool {
if refuses(this) {
return Bool::NO;
}
unsafe { original(|o| o.main)(this, sel) }
}