#![allow(non_upper_case_globals)]
use std::cell::RefCell;
use std::ffi::c_void;
use std::rc::Rc;
use cocoa::appkit::{NSApp, NSApplication, NSApplicationActivationPolicyRegular};
use cocoa::base::{id, nil, NO, YES};
use cocoa::foundation::{NSArray, NSAutoreleasePool};
use objc::declare::ClassDecl;
use objc::runtime::{Class, Object, Sel};
use objc::{class, msg_send, sel, sel_impl};
use once_cell::sync::Lazy;
use crate::application::AppHandler;
use super::clipboard::Clipboard;
use super::error::Error;
use super::util;
static APP_HANDLER_IVAR: &str = "druidAppHandler";
#[derive(Clone)]
pub(crate) struct Application {
ns_app: id,
state: Rc<RefCell<State>>,
}
struct State {
quitting: bool,
}
impl Application {
pub fn new() -> Result<Application, Error> {
util::assert_main_thread();
unsafe {
let _pool = NSAutoreleasePool::new(nil);
let ns_app = NSApp();
let state = Rc::new(RefCell::new(State { quitting: false }));
Ok(Application { ns_app, state })
}
}
pub fn run(self, handler: Option<Box<dyn AppHandler>>) {
unsafe {
let delegate: id = msg_send![APP_DELEGATE.0, alloc];
let () = msg_send![delegate, init];
let state = DelegateState { handler };
let state_ptr = Box::into_raw(Box::new(state));
(*delegate).set_ivar(APP_HANDLER_IVAR, state_ptr as *mut c_void);
let () = msg_send![self.ns_app, setDelegate: delegate];
self.ns_app.run();
let () = msg_send![self.ns_app, setDelegate: nil];
drop(Box::from_raw(state_ptr));
}
}
pub fn quit(&self) {
if let Ok(mut state) = self.state.try_borrow_mut() {
if !state.quitting {
state.quitting = true;
unsafe {
let windows: id = msg_send![self.ns_app, windows];
for i in 0..windows.count() {
let window: id = windows.objectAtIndex(i);
let () = msg_send![window, performSelectorOnMainThread: sel!(close) withObject: nil waitUntilDone: NO];
}
let () = msg_send![self.ns_app, stop: nil];
}
}
} else {
tracing::warn!("Application state already borrowed");
}
}
pub fn clipboard(&self) -> Clipboard {
Clipboard
}
pub fn get_locale() -> String {
unsafe {
let nslocale_class = class!(NSLocale);
let locale: id = msg_send![nslocale_class, currentLocale];
let ident: id = msg_send![locale, localeIdentifier];
let mut locale = util::from_nsstring(ident);
if let Some(idx) = locale.chars().position(|c| c == '@') {
locale.truncate(idx);
}
locale
}
}
}
impl crate::platform::mac::ApplicationExt for crate::Application {
fn hide(&self) {
unsafe {
let () = msg_send![self.backend_app.ns_app, hide: nil];
}
}
fn hide_others(&self) {
unsafe {
let workspace = class!(NSWorkspace);
let shared: id = msg_send![workspace, sharedWorkspace];
let () = msg_send![shared, hideOtherApplications];
}
}
fn set_menu(&self, menu: crate::Menu) {
unsafe {
NSApp().setMainMenu_(menu.0.menu);
}
}
}
struct DelegateState {
handler: Option<Box<dyn AppHandler>>,
}
impl DelegateState {
fn command(&mut self, command: u32) {
if let Some(inner) = self.handler.as_mut() {
inner.command(command)
}
}
}
struct AppDelegate(*const Class);
unsafe impl Sync for AppDelegate {}
unsafe impl Send for AppDelegate {}
static APP_DELEGATE: Lazy<AppDelegate> = Lazy::new(|| unsafe {
let mut decl = ClassDecl::new("DruidAppDelegate", class!(NSObject))
.expect("App Delegate definition failed");
decl.add_ivar::<*mut c_void>(APP_HANDLER_IVAR);
decl.add_method(
sel!(applicationDidFinishLaunching:),
application_did_finish_launching as extern "C" fn(&mut Object, Sel, id),
);
decl.add_method(
sel!(handleMenuItem:),
handle_menu_item as extern "C" fn(&mut Object, Sel, id),
);
AppDelegate(decl.register())
});
extern "C" fn application_did_finish_launching(_this: &mut Object, _: Sel, _notification: id) {
unsafe {
let ns_app = NSApp();
ns_app.setActivationPolicy_(NSApplicationActivationPolicyRegular);
let () = msg_send![ns_app, activateIgnoringOtherApps: YES];
}
}
extern "C" fn handle_menu_item(this: &mut Object, _: Sel, item: id) {
unsafe {
let tag: isize = msg_send![item, tag];
let inner: *mut c_void = *this.get_ivar(APP_HANDLER_IVAR);
let inner = &mut *(inner as *mut DelegateState);
(*inner).command(tag as u32);
}
}