runner.rs

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