//! A wrapper for `NSApplication` on macOS. If you opt in to the `cocoa` feature on //! Alchemy, this will loop system-level application events back to your `AppDelegate`. use std::sync::{Once, ONCE_INIT}; use cocoa::base::{id, nil}; use cocoa::appkit::{NSApplication, NSRunningApplication}; use objc_id::Id; use objc::declare::ClassDecl; use objc::runtime::{Class, Object, Sel}; use objc::{msg_send, class, sel, sel_impl}; use alchemy_lifecycle::traits::AppDelegate; static ALCHEMY_APP_PTR: &str = "alchemyParentAppPtr"; /// A wrapper for `NSApplication`. It holds (retains) pointers for the Objective-C runtime, /// which is where our application instance lives. It also injects an `NSObject` subclass, /// which acts as the Delegate, looping back into our Alchemy shared application. pub struct App { pub inner: Id, pub delegate: Id } impl App { /// Creates an NSAutoReleasePool, configures various NSApplication properties (e.g, activation /// policies), injects an `NSObject` delegate wrapper, and retains everything on the /// Objective-C side of things. pub fn new(parent_app_ptr: *const T) -> Self { let inner = unsafe { let _pool = cocoa::foundation::NSAutoreleasePool::new(nil); let app = cocoa::appkit::NSApp(); app.setActivationPolicy_(cocoa::appkit::NSApplicationActivationPolicyRegular); Id::from_ptr(app) }; let delegate = unsafe { let delegate_class = register_app_delegate_class::(); let delegate: id = msg_send![delegate_class, new]; (&mut *delegate).set_ivar(ALCHEMY_APP_PTR, parent_app_ptr as usize); msg_send![&*inner, setDelegate:delegate]; Id::from_ptr(delegate) }; App { delegate: delegate, inner: inner } } /// Kicks off the NSRunLoop for the NSApplication instance. This blocks when called. pub fn run(&self) { unsafe { let current_app = cocoa::appkit::NSRunningApplication::currentApplication(nil); current_app.activateWithOptions_(cocoa::appkit::NSApplicationActivateIgnoringOtherApps); let shared_app: id = msg_send![class!(NSApplication), sharedApplication]; msg_send![shared_app, run]; } } } /// Fires when the Application Delegate receives a `applicationWillFinishLaunching` notification. extern fn will_finish_launching(this: &Object, _: Sel, _: id) { unsafe { let app_ptr: usize = *this.get_ivar(ALCHEMY_APP_PTR); let app = app_ptr as *mut T; (*app).will_finish_launching(); }; } /// Fires when the Application Delegate receives a `applicationDidFinishLaunching` notification. extern fn did_finish_launching(this: &Object, _: Sel, _: id) { unsafe { let app_ptr: usize = *this.get_ivar(ALCHEMY_APP_PTR); let app = app_ptr as *mut T; (*app).did_finish_launching(); }; } /// Fires when the Application Delegate receives a `applicationWillBecomeActive` notification. extern fn will_become_active(this: &Object, _: Sel, _: id) { unsafe { let app_ptr: usize = *this.get_ivar(ALCHEMY_APP_PTR); let app = app_ptr as *mut T; (*app).will_become_active(); }; } /// Fires when the Application Delegate receives a `applicationDidBecomeActive` notification. extern fn did_become_active(this: &Object, _: Sel, _: id) { unsafe { let app_ptr: usize = *this.get_ivar(ALCHEMY_APP_PTR); let app = app_ptr as *mut T; (*app).did_become_active(); }; } /// Fires when the Application Delegate receives a `applicationWillResignActive` notification. extern fn will_resign_active(this: &Object, _: Sel, _: id) { unsafe { let app_ptr: usize = *this.get_ivar(ALCHEMY_APP_PTR); let app = app_ptr as *mut T; (*app).will_resign_active(); }; } /// Fires when the Application Delegate receives a `applicationDidResignActive` notification. extern fn did_resign_active(this: &Object, _: Sel, _: id) { unsafe { let app_ptr: usize = *this.get_ivar(ALCHEMY_APP_PTR); let app = app_ptr as *mut T; (*app).did_resign_active(); }; } /// Fires when the Application Delegate receives a `applicationWillTerminate` notification. extern fn will_terminate(this: &Object, _: Sel, _: id) { unsafe { let app_ptr: usize = *this.get_ivar(ALCHEMY_APP_PTR); let app = app_ptr as *mut T; (*app).will_terminate(); }; } /// Registers an `NSObject` application delegate, and configures it for the various callbacks and /// pointers we need to have. fn register_app_delegate_class() -> *const Class { static mut DELEGATE_CLASS: *const Class = 0 as *const Class; static INIT: Once = ONCE_INIT; INIT.call_once(|| unsafe { let superclass = Class::get("NSObject").unwrap(); let mut decl = ClassDecl::new("AlchemyAppDelegate", superclass).unwrap(); decl.add_ivar::(ALCHEMY_APP_PTR); // Add callback methods decl.add_method(sel!(applicationWillFinishLaunching:), will_finish_launching:: as extern fn(&Object, _, _)); decl.add_method(sel!(applicationDidFinishLaunching:), did_finish_launching:: as extern fn(&Object, _, _)); decl.add_method(sel!(applicationWillBecomeActive:), will_become_active:: as extern fn(&Object, _, _)); decl.add_method(sel!(applicationDidBecomeActive:), did_become_active:: as extern fn(&Object, _, _)); decl.add_method(sel!(applicationWillResignActive:), will_resign_active:: as extern fn(&Object, _, _)); decl.add_method(sel!(applicationDidResignActive:), did_resign_active:: as extern fn(&Object, _, _)); decl.add_method(sel!(applicationWillTerminate:), will_terminate:: as extern fn(&Object, _, _)); DELEGATE_CLASS = decl.register(); }); unsafe { DELEGATE_CLASS } }