use std::cell::RefCell;
use std::sync::OnceLock;
use objc2::rc::Retained;
use objc2::runtime::{AnyClass, AnyObject, Bool, Imp, Sel};
use objc2::{class, ffi, msg_send, sel};
use objc2_foundation::NSString;
use crate::Waker;
#[derive(Default)]
struct State {
queue: Vec<Vec<String>>,
waker: Option<Waker>,
}
thread_local! {
static STATE: RefCell<State> = RefCell::new(State::default());
}
static INSTALLED: OnceLock<bool> = OnceLock::new();
pub fn set_waker(waker: Waker) {
STATE.with(|s| s.borrow_mut().waker = Some(waker));
}
pub fn install() -> bool {
*INSTALLED.get_or_init(install_once)
}
pub fn take() -> Vec<Vec<String>> {
STATE.with(|s| std::mem::take(&mut s.borrow_mut().queue))
}
pub fn requeue(paths: Vec<String>) {
STATE.with(|s| s.borrow_mut().queue.push(paths));
}
pub fn pending() -> bool {
STATE.with(|s| !s.borrow().queue.is_empty())
}
type OpenUrlsFn = unsafe extern "C-unwind" fn(&AnyObject, Sel, &AnyObject, &AnyObject);
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 install_once() -> bool {
let app: Retained<AnyObject> = unsafe { msg_send![class!(NSApplication), sharedApplication] };
let delegate: Option<Retained<AnyObject>> = unsafe { msg_send![&*app, delegate] };
let Some(delegate) = delegate else {
return false;
};
let cls: &AnyClass = unsafe { msg_send![&*delegate, class] };
let answers = |sel: Sel| -> bool {
let yes: Bool = unsafe { msg_send![cls, instancesRespondToSelector: sel] };
yes.as_bool()
};
if answers(sel!(application:openURLs:))
|| answers(sel!(application:openFiles:))
|| answers(sel!(application:openFile:))
{
return false;
}
let owner = cls as *const AnyClass as *mut AnyClass;
unsafe {
ffi::class_addMethod(
owner,
sel!(application:openURLs:),
imp(open_urls_override as OpenUrlsFn),
c"v@:@@".as_ptr(),
)
.as_bool()
}
}
fn paths_of(urls: &AnyObject) -> Vec<String> {
let count: usize = unsafe { msg_send![urls, count] };
let mut paths = Vec::with_capacity(count);
for i in 0..count {
let url: Retained<AnyObject> = unsafe { msg_send![urls, objectAtIndex: i] };
let file: Bool = unsafe { msg_send![&*url, isFileURL] };
if !file.as_bool() {
continue;
}
let path: Option<Retained<NSString>> = unsafe { msg_send![&*url, path] };
if let Some(path) = path {
paths.push(path.to_string());
}
}
paths
}
unsafe extern "C-unwind" fn open_urls_override(
_this: &AnyObject,
_sel: Sel,
_app: &AnyObject,
urls: &AnyObject,
) {
let paths = paths_of(urls);
if paths.is_empty() {
return;
}
STATE.with(|s| {
let mut s = s.borrow_mut();
s.queue.push(paths);
if let Some(w) = &s.waker {
w.wake();
}
});
}