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}