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}