app.rs

  1use super::Event;
  2pub use cocoa::foundation::NSSize;
  3use cocoa::{
  4    base::{id, nil},
  5    foundation::{NSArray, NSAutoreleasePool, NSString},
  6};
  7use objc::{
  8    class,
  9    declare::ClassDecl,
 10    msg_send,
 11    runtime::{Class, Object, Sel},
 12    sel, sel_impl,
 13};
 14use std::{
 15    ffi::CStr,
 16    os::raw::{c_char, c_void},
 17    path::PathBuf,
 18};
 19
 20#[derive(Default)]
 21pub struct App {
 22    finish_launching_callback: Option<Box<dyn FnOnce()>>,
 23    become_active_callback: Option<Box<dyn FnMut()>>,
 24    resign_active_callback: Option<Box<dyn FnMut()>>,
 25    event_callback: Option<Box<dyn FnMut(Event) -> bool>>,
 26    open_files_callback: Option<Box<dyn FnMut(Vec<PathBuf>)>>,
 27}
 28
 29const RUST_WRAPPER_IVAR_NAME: &'static str = "rustWrapper";
 30
 31impl super::App for App {
 32    fn on_finish_launching<F: 'static + FnOnce()>(mut self, callback: F) -> Self {
 33        self.finish_launching_callback = Some(Box::new(callback));
 34        self
 35    }
 36
 37    fn on_become_active<F: 'static + FnMut()>(mut self, callback: F) -> Self {
 38        self.become_active_callback = Some(Box::new(callback));
 39        self
 40    }
 41
 42    fn on_resign_active<F: 'static + FnMut()>(mut self, callback: F) -> Self {
 43        self.resign_active_callback = Some(Box::new(callback));
 44        self
 45    }
 46
 47    fn on_event<F: 'static + FnMut(Event) -> bool>(mut self, callback: F) -> Self {
 48        self.event_callback = Some(Box::new(callback));
 49        self
 50    }
 51
 52    fn on_open_files<F: 'static + FnMut(Vec<PathBuf>)>(mut self, callback: F) -> Self {
 53        self.open_files_callback = Some(Box::new(callback));
 54        self
 55    }
 56
 57    fn run(self) {
 58        unsafe {
 59            let self_ptr = Box::into_raw(Box::new(self));
 60
 61            let pool = NSAutoreleasePool::new(nil);
 62            let app: id = msg_send![build_app_class(), sharedApplication];
 63            (*app).set_ivar(RUST_WRAPPER_IVAR_NAME, self_ptr as *mut c_void);
 64            let app_delegate: id = msg_send![build_app_delegate_class(), new];
 65            (*app_delegate).set_ivar(RUST_WRAPPER_IVAR_NAME, self_ptr as *mut c_void);
 66            let _: () = msg_send![app, setDelegate: app_delegate];
 67            let _: () = msg_send![app, run];
 68            let _: () = msg_send![pool, drain];
 69
 70            // App is done running when we get here, so we can reinstantiate the Box and drop it.
 71            Box::from_raw(self_ptr);
 72        }
 73    }
 74}
 75
 76fn build_app_class() -> *const Class {
 77    unsafe {
 78        let mut decl = ClassDecl::new("GPUIApplication", class!(NSApplication)).unwrap();
 79        decl.add_ivar::<*mut c_void>(RUST_WRAPPER_IVAR_NAME);
 80        decl.add_method(
 81            sel!(sendEvent:),
 82            send_event as extern "C" fn(&Object, Sel, id),
 83        );
 84        decl.register()
 85    }
 86}
 87
 88fn build_app_delegate_class() -> *const Class {
 89    unsafe {
 90        let superclass = class!(NSResponder);
 91        let mut decl = ClassDecl::new("GPUIApplicationDelegate", superclass).unwrap();
 92        decl.add_ivar::<*mut c_void>(RUST_WRAPPER_IVAR_NAME);
 93        decl.add_method(
 94            sel!(applicationDidFinishLaunching:),
 95            did_finish_launching as extern "C" fn(&Object, Sel, id),
 96        );
 97        decl.add_method(
 98            sel!(applicationDidBecomeActive:),
 99            did_become_active as extern "C" fn(&Object, Sel, id),
100        );
101        decl.add_method(
102            sel!(applicationDidResignActive:),
103            did_resign_active as extern "C" fn(&Object, Sel, id),
104        );
105        decl.add_method(
106            sel!(application:openFiles:),
107            open_files as extern "C" fn(&Object, Sel, id, id),
108        );
109        decl.register()
110    }
111}
112
113unsafe fn get_app(object: &Object) -> &mut App {
114    let wrapper_ptr: *mut c_void = *object.get_ivar(RUST_WRAPPER_IVAR_NAME);
115    &mut *(wrapper_ptr as *mut App)
116}
117
118extern "C" fn send_event(this: &Object, _sel: Sel, native_event: id) {
119    let event = unsafe { Event::from_native(native_event, None) };
120
121    if let Some(event) = event {
122        let app = unsafe { get_app(this) };
123        if let Some(callback) = app.event_callback.as_mut() {
124            if callback(event) {
125                return;
126            }
127        }
128    }
129
130    unsafe {
131        let _: () = msg_send![super(this, class!(NSApplication)), sendEvent: native_event];
132    }
133}
134
135extern "C" fn did_finish_launching(this: &Object, _: Sel, _: id) {
136    let app = unsafe { get_app(this) };
137    if let Some(callback) = app.finish_launching_callback.take() {
138        callback();
139    }
140}
141
142extern "C" fn did_become_active(this: &Object, _: Sel, _: id) {
143    let app = unsafe { get_app(this) };
144    if let Some(callback) = app.become_active_callback.as_mut() {
145        callback();
146    }
147}
148
149extern "C" fn did_resign_active(this: &Object, _: Sel, _: id) {
150    let app = unsafe { get_app(this) };
151    if let Some(callback) = app.resign_active_callback.as_mut() {
152        callback();
153    }
154}
155
156extern "C" fn open_files(this: &Object, _: Sel, _: id, paths: id) {
157    let paths = unsafe {
158        (0..paths.count())
159            .into_iter()
160            .filter_map(|i| {
161                let path = paths.objectAtIndex(i);
162                match CStr::from_ptr(path.UTF8String() as *mut c_char).to_str() {
163                    Ok(string) => Some(PathBuf::from(string)),
164                    Err(err) => {
165                        log::error!("error converting path to string: {}", err);
166                        None
167                    }
168                }
169            })
170            .collect::<Vec<_>>()
171    };
172    let app = unsafe { get_app(this) };
173    if let Some(callback) = app.open_files_callback.as_mut() {
174        callback(paths);
175    }
176}