| @@ -1,1102 +1,1130 @@ |
| 1 | | −// use anyhow::{anyhow, Result}; |
| 2 | | −// use block::ConcreteBlock; |
| 3 | | −// use cocoa::{ |
| 4 | | −// appkit::{ |
| 5 | | −// NSApplication, NSApplicationActivationPolicy::NSApplicationActivationPolicyRegular, |
| 6 | | −// NSEventModifierFlags, NSMenu, NSMenuItem, NSModalResponse, NSOpenPanel, NSPasteboard, |
| 7 | | −// NSSavePanel, NSWindow, |
| 8 | | −// }, |
| 9 | | −// base::{id, nil, selector, BOOL, YES}, |
| 10 | | −// foundation::{NSArray, NSAutoreleasePool, NSData, NSInteger, NSString, NSUInteger, NSURL}, |
| 11 | | −// }; |
| 12 | | −// use core_foundation::{ |
| 13 | | −// base::{CFTypeRef, OSStatus}, |
| 14 | | −// dictionary::CFDictionaryRef, |
| 15 | | −// string::CFStringRef, |
| 16 | | −// }; |
| 17 | | −// use ctor::ctor; |
| 18 | | −// use objc::{ |
| 19 | | −// class, |
| 20 | | −// declare::ClassDecl, |
| 21 | | −// msg_send, |
| 22 | | −// runtime::{Class, Object, Sel}, |
| 23 | | −// sel, sel_impl, |
| 24 | | −// }; |
| 25 | | − |
| 26 | | −// use postage::oneshot; |
| 27 | | −// use ptr::null_mut; |
| 28 | | −// use std::{ |
| 29 | | −// cell::{Cell, RefCell}, |
| 30 | | −// convert::TryInto, |
| 31 | | −// ffi::{c_void, CStr, OsStr}, |
| 32 | | −// os::{raw::c_char, unix::ffi::OsStrExt}, |
| 33 | | −// path::{Path, PathBuf}, |
| 34 | | −// process::Command, |
| 35 | | −// ptr, |
| 36 | | −// rc::Rc, |
| 37 | | −// slice, str, |
| 38 | | −// sync::Arc, |
| 39 | | −// }; |
| 40 | | − |
| 41 | | −// use crate::Event; |
| 42 | | − |
| 43 | | −// #[allow(non_upper_case_globals)] |
| 44 | | −// const NSUTF8StringEncoding: NSUInteger = 4; |
| 45 | | − |
| 46 | | −// const MAC_PLATFORM_IVAR: &str = "platform"; |
| 47 | | −// static mut APP_CLASS: *const Class = ptr::null(); |
| 48 | | −// static mut APP_DELEGATE_CLASS: *const Class = ptr::null(); |
| 49 | | − |
| 50 | | −// #[ctor] |
| 51 | | −// unsafe fn build_classes() { |
| 52 | | −// APP_CLASS = { |
| 53 | | −// let mut decl = ClassDecl::new("GPUIApplication", class!(NSApplication)).unwrap(); |
| 54 | | −// decl.add_ivar::<*mut c_void>(MAC_PLATFORM_IVAR); |
| 55 | | −// decl.add_method( |
| 56 | | −// sel!(sendEvent:), |
| 57 | | −// send_event as extern "C" fn(&mut Object, Sel, id), |
| 58 | | −// ); |
| 59 | | −// decl.register() |
| 60 | | −// }; |
| 61 | | − |
| 62 | | −// APP_DELEGATE_CLASS = { |
| 63 | | −// let mut decl = ClassDecl::new("GPUIApplicationDelegate", class!(NSResponder)).unwrap(); |
| 64 | | −// decl.add_ivar::<*mut c_void>(MAC_PLATFORM_IVAR); |
| 65 | | −// decl.add_method( |
| 66 | | −// sel!(applicationDidFinishLaunching:), |
| 67 | | −// did_finish_launching as extern "C" fn(&mut Object, Sel, id), |
| 68 | | −// ); |
| 69 | | −// decl.add_method( |
| 70 | | −// sel!(applicationShouldHandleReopen:hasVisibleWindows:), |
| 71 | | −// should_handle_reopen as extern "C" fn(&mut Object, Sel, id, bool), |
| 72 | | −// ); |
| 73 | | −// decl.add_method( |
| 74 | | −// sel!(applicationDidBecomeActive:), |
| 75 | | −// did_become_active as extern "C" fn(&mut Object, Sel, id), |
| 76 | | −// ); |
| 77 | | −// decl.add_method( |
| 78 | | −// sel!(applicationDidResignActive:), |
| 79 | | −// did_resign_active as extern "C" fn(&mut Object, Sel, id), |
| 80 | | −// ); |
| 81 | | −// decl.add_method( |
| 82 | | −// sel!(applicationWillTerminate:), |
| 83 | | −// will_terminate as extern "C" fn(&mut Object, Sel, id), |
| 84 | | −// ); |
| 85 | | −// decl.add_method( |
| 86 | | −// sel!(handleGPUIMenuItem:), |
| 87 | | −// handle_menu_item as extern "C" fn(&mut Object, Sel, id), |
| 88 | | −// ); |
| 89 | | −// // Add menu item handlers so that OS save panels have the correct key commands |
| 90 | | −// decl.add_method( |
| 91 | | −// sel!(cut:), |
| 92 | | −// handle_menu_item as extern "C" fn(&mut Object, Sel, id), |
| 93 | | −// ); |
| 94 | | −// decl.add_method( |
| 95 | | −// sel!(copy:), |
| 96 | | −// handle_menu_item as extern "C" fn(&mut Object, Sel, id), |
| 97 | | −// ); |
| 98 | | −// decl.add_method( |
| 99 | | −// sel!(paste:), |
| 100 | | −// handle_menu_item as extern "C" fn(&mut Object, Sel, id), |
| 101 | | −// ); |
| 102 | | −// decl.add_method( |
| 103 | | −// sel!(selectAll:), |
| 104 | | −// handle_menu_item as extern "C" fn(&mut Object, Sel, id), |
| 105 | | −// ); |
| 106 | | −// decl.add_method( |
| 107 | | −// sel!(undo:), |
| 108 | | −// handle_menu_item as extern "C" fn(&mut Object, Sel, id), |
| 109 | | −// ); |
| 110 | | −// decl.add_method( |
| 111 | | −// sel!(redo:), |
| 112 | | −// handle_menu_item as extern "C" fn(&mut Object, Sel, id), |
| 113 | | −// ); |
| 114 | | −// decl.add_method( |
| 115 | | −// sel!(validateMenuItem:), |
| 116 | | −// validate_menu_item as extern "C" fn(&mut Object, Sel, id) -> bool, |
| 117 | | −// ); |
| 118 | | −// decl.add_method( |
| 119 | | −// sel!(menuWillOpen:), |
| 120 | | −// menu_will_open as extern "C" fn(&mut Object, Sel, id), |
| 121 | | −// ); |
| 122 | | −// decl.add_method( |
| 123 | | −// sel!(application:openURLs:), |
| 124 | | −// open_urls as extern "C" fn(&mut Object, Sel, id, id), |
| 125 | | −// ); |
| 126 | | −// decl.register() |
| 127 | | −// } |
| 128 | | −// } |
| 129 | | − |
| 130 | | −// pub struct MacForegroundPlatformState { |
| 131 | | −// dispatcher: Arc<MacDispatcher>, |
| 132 | | −// fonts: Arc<MacFontSystem>, |
| 133 | | −// pasteboard: id, |
| 134 | | −// text_hash_pasteboard_type: id, |
| 135 | | −// metadata_pasteboard_type: id, |
| 136 | | −// become_active: Option<Box<dyn FnMut()>>, |
| 137 | | −// resign_active: Option<Box<dyn FnMut()>>, |
| 138 | | −// reopen: Option<Box<dyn FnMut()>>, |
| 139 | | −// quit: Option<Box<dyn FnMut()>>, |
| 140 | | −// event: Option<Box<dyn FnMut(Event) -> bool>>, |
| 141 | | −// // menu_command: Option<Box<dyn FnMut(&dyn Action)>>, |
| 142 | | −// validate_menu_command: Option<Box<dyn FnMut(&dyn Action) -> bool>>, |
| 143 | | −// will_open_menu: Option<Box<dyn FnMut()>>, |
| 144 | | −// open_urls: Option<Box<dyn FnMut(Vec<String>)>>, |
| 145 | | −// finish_launching: Option<Box<dyn FnOnce()>>, |
| 146 | | −// // menu_actions: Vec<Box<dyn Action>>, |
| 147 | | −// // foreground: Rc<executor::Foreground>, |
| 148 | | −// } |
| 149 | | − |
| 150 | | −// impl MacForegroundPlatform { |
| 151 | | −// pub fn new(foreground: Rc<executor::Foreground>) -> Self { |
| 152 | | −// Self(RefCell::new(MacForegroundPlatformState { |
| 153 | | −// become_active: None, |
| 154 | | −// resign_active: None, |
| 155 | | −// reopen: None, |
| 156 | | −// quit: None, |
| 157 | | −// event: None, |
| 158 | | −// // menu_command: None, |
| 159 | | −// validate_menu_command: None, |
| 160 | | −// will_open_menu: None, |
| 161 | | −// open_urls: None, |
| 162 | | −// finish_launching: None, |
| 163 | | −// menu_actions: Default::default(), |
| 164 | | −// foreground, |
| 165 | | −// })) |
| 166 | | −// } |
| 167 | | − |
| 168 | | −// unsafe fn create_menu_bar( |
| 169 | | −// &self, |
| 170 | | −// menus: Vec<Menu>, |
| 171 | | −// delegate: id, |
| 172 | | −// actions: &mut Vec<Box<dyn Action>>, |
| 173 | | −// keystroke_matcher: &KeymapMatcher, |
| 174 | | −// ) -> id { |
| 175 | | −// let application_menu = NSMenu::new(nil).autorelease(); |
| 176 | | −// application_menu.setDelegate_(delegate); |
| 177 | | − |
| 178 | | −// for menu_config in menus { |
| 179 | | −// let menu = NSMenu::new(nil).autorelease(); |
| 180 | | −// menu.setTitle_(ns_string(menu_config.name)); |
| 181 | | −// menu.setDelegate_(delegate); |
| 182 | | − |
| 183 | | −// for item_config in menu_config.items { |
| 184 | | −// menu.addItem_(self.create_menu_item( |
| 185 | | −// item_config, |
| 186 | | −// delegate, |
| 187 | | −// actions, |
| 188 | | −// keystroke_matcher, |
| 189 | | −// )); |
| 190 | | −// } |
| 191 | | − |
| 192 | | −// let menu_item = NSMenuItem::new(nil).autorelease(); |
| 193 | | −// menu_item.setSubmenu_(menu); |
| 194 | | −// application_menu.addItem_(menu_item); |
| 195 | | − |
| 196 | | −// if menu_config.name == "Window" { |
| 197 | | −// let app: id = msg_send![APP_CLASS, sharedApplication]; |
| 198 | | −// app.setWindowsMenu_(menu); |
| 199 | | −// } |
| 200 | | −// } |
| 201 | | − |
| 202 | | −// application_menu |
| 203 | | −// } |
| 204 | | − |
| 205 | | −// // unsafe fn create_menu_item( |
| 206 | | −// // &self, |
| 207 | | −// // item: MenuItem, |
| 208 | | −// // delegate: id, |
| 209 | | −// // actions: &mut Vec<Box<dyn Action>>, |
| 210 | | −// // keystroke_matcher: &KeymapMatcher, |
| 211 | | −// // ) -> id { |
| 212 | | −// // match item { |
| 213 | | −// // MenuItem::Separator => NSMenuItem::separatorItem(nil), |
| 214 | | −// // MenuItem::Action { |
| 215 | | −// // name, |
| 216 | | −// // action, |
| 217 | | −// // os_action, |
| 218 | | −// // } => { |
| 219 | | −// // // TODO |
| 220 | | −// // let keystrokes = keystroke_matcher |
| 221 | | −// // .bindings_for_action(action.id()) |
| 222 | | −// // .find(|binding| binding.action().eq(action.as_ref())) |
| 223 | | −// // .map(|binding| binding.keystrokes()); |
| 224 | | −// // let selector = match os_action { |
| 225 | | −// // Some(crate::OsAction::Cut) => selector("cut:"), |
| 226 | | −// // Some(crate::OsAction::Copy) => selector("copy:"), |
| 227 | | −// // Some(crate::OsAction::Paste) => selector("paste:"), |
| 228 | | −// // Some(crate::OsAction::SelectAll) => selector("selectAll:"), |
| 229 | | −// // Some(crate::OsAction::Undo) => selector("undo:"), |
| 230 | | −// // Some(crate::OsAction::Redo) => selector("redo:"), |
| 231 | | −// // None => selector("handleGPUIMenuItem:"), |
| 232 | | −// // }; |
| 233 | | − |
| 234 | | −// // let item; |
| 235 | | −// // if let Some(keystrokes) = keystrokes { |
| 236 | | −// // if keystrokes.len() == 1 { |
| 237 | | −// // let keystroke = &keystrokes[0]; |
| 238 | | −// // let mut mask = NSEventModifierFlags::empty(); |
| 239 | | −// // for (modifier, flag) in &[ |
| 240 | | −// // (keystroke.cmd, NSEventModifierFlags::NSCommandKeyMask), |
| 241 | | −// // (keystroke.ctrl, NSEventModifierFlags::NSControlKeyMask), |
| 242 | | −// // (keystroke.alt, NSEventModifierFlags::NSAlternateKeyMask), |
| 243 | | −// // (keystroke.shift, NSEventModifierFlags::NSShiftKeyMask), |
| 244 | | −// // ] { |
| 245 | | −// // if *modifier { |
| 246 | | −// // mask |= *flag; |
| 247 | | −// // } |
| 248 | | −// // } |
| 249 | | − |
| 250 | | −// // item = NSMenuItem::alloc(nil) |
| 251 | | −// // .initWithTitle_action_keyEquivalent_( |
| 252 | | −// // ns_string(name), |
| 253 | | −// // selector, |
| 254 | | −// // ns_string(key_to_native(&keystroke.key).as_ref()), |
| 255 | | −// // ) |
| 256 | | −// // .autorelease(); |
| 257 | | −// // item.setKeyEquivalentModifierMask_(mask); |
| 258 | | −// // } |
| 259 | | −// // // For multi-keystroke bindings, render the keystroke as part of the title. |
| 260 | | −// // else { |
| 261 | | −// // use std::fmt::Write; |
| 262 | | − |
| 263 | | −// // let mut name = format!("{name} ["); |
| 264 | | −// // for (i, keystroke) in keystrokes.iter().enumerate() { |
| 265 | | −// // if i > 0 { |
| 266 | | −// // name.push(' '); |
| 267 | | −// // } |
| 268 | | −// // write!(&mut name, "{}", keystroke).unwrap(); |
| 269 | | −// // } |
| 270 | | −// // name.push(']'); |
| 271 | | − |
| 272 | | −// // item = NSMenuItem::alloc(nil) |
| 273 | | −// // .initWithTitle_action_keyEquivalent_( |
| 274 | | −// // ns_string(&name), |
| 275 | | −// // selector, |
| 276 | | −// // ns_string(""), |
| 277 | | −// // ) |
| 278 | | −// // .autorelease(); |
| 279 | | −// // } |
| 280 | | −// // } else { |
| 281 | | −// // item = NSMenuItem::alloc(nil) |
| 282 | | −// // .initWithTitle_action_keyEquivalent_( |
| 283 | | −// // ns_string(name), |
| 284 | | −// // selector, |
| 285 | | −// // ns_string(""), |
| 286 | | −// // ) |
| 287 | | −// // .autorelease(); |
| 288 | | −// // } |
| 289 | | − |
| 290 | | −// // let tag = actions.len() as NSInteger; |
| 291 | | −// // let _: () = msg_send![item, setTag: tag]; |
| 292 | | −// // actions.push(action); |
| 293 | | −// // item |
| 294 | | −// // } |
| 295 | | −// // MenuItem::Submenu(Menu { name, items }) => { |
| 296 | | −// // let item = NSMenuItem::new(nil).autorelease(); |
| 297 | | −// // let submenu = NSMenu::new(nil).autorelease(); |
| 298 | | −// // submenu.setDelegate_(delegate); |
| 299 | | −// // for item in items { |
| 300 | | −// // submenu.addItem_(self.create_menu_item( |
| 301 | | −// // item, |
| 302 | | −// // delegate, |
| 303 | | −// // actions, |
| 304 | | −// // keystroke_matcher, |
| 305 | | −// // )); |
| 306 | | −// // } |
| 307 | | −// // item.setSubmenu_(submenu); |
| 308 | | −// // item.setTitle_(ns_string(name)); |
| 309 | | −// // item |
| 310 | | −// // } |
| 311 | | −// // } |
| 312 | | −// // } |
| 313 | | − |
| 314 | | −// unsafe fn read_from_pasteboard(&self, kind: id) -> Option<&[u8]> { |
| 315 | | −// let data = self.pasteboard.dataForType(kind); |
| 316 | | −// if data == nil { |
| 317 | | −// None |
| 318 | | −// } else { |
| 319 | | −// Some(slice::from_raw_parts( |
| 320 | | −// data.bytes() as *mut u8, |
| 321 | | −// data.length() as usize, |
| 322 | | −// )) |
| 323 | | −// } |
| 324 | | −// } |
| 325 | | −// } |
| 326 | | − |
| 327 | | −// // impl platform::ForegroundPlatform for MacForegroundPlatform { |
| 328 | | −// // fn on_become_active(&self, callback: Box<dyn FnMut()>) { |
| 329 | | −// // self.0.borrow_mut().become_active = Some(callback); |
| 330 | | −// // } |
| 331 | | − |
| 332 | | −// // fn on_resign_active(&self, callback: Box<dyn FnMut()>) { |
| 333 | | −// // self.0.borrow_mut().resign_active = Some(callback); |
| 334 | | −// // } |
| 335 | | − |
| 336 | | −// // fn on_quit(&self, callback: Box<dyn FnMut()>) { |
| 337 | | −// // self.0.borrow_mut().quit = Some(callback); |
| 338 | | −// // } |
| 339 | | − |
| 340 | | −// // fn on_reopen(&self, callback: Box<dyn FnMut()>) { |
| 341 | | −// // self.0.borrow_mut().reopen = Some(callback); |
| 342 | | −// // } |
| 343 | | − |
| 344 | | −// // fn on_event(&self, callback: Box<dyn FnMut(platform::Event) -> bool>) { |
| 345 | | −// // self.0.borrow_mut().event = Some(callback); |
| 346 | | −// // } |
| 347 | | − |
| 348 | | −// // fn on_open_urls(&self, callback: Box<dyn FnMut(Vec<String>)>) { |
| 349 | | −// // self.0.borrow_mut().open_urls = Some(callback); |
| 350 | | −// // } |
| 351 | | − |
| 352 | | −// // fn run(&self, on_finish_launching: Box<dyn FnOnce()>) { |
| 353 | | −// // self.0.borrow_mut().finish_launching = Some(on_finish_launching); |
| 354 | | − |
| 355 | | −// // unsafe { |
| 356 | | −// // let app: id = msg_send![APP_CLASS, sharedApplication]; |
| 357 | | −// // let app_delegate: id = msg_send![APP_DELEGATE_CLASS, new]; |
| 358 | | −// // app.setDelegate_(app_delegate); |
| 359 | | − |
| 360 | | −// // let self_ptr = self as *const Self as *const c_void; |
| 361 | | −// // (*app).set_ivar(MAC_PLATFORM_IVAR, self_ptr); |
| 362 | | −// // (*app_delegate).set_ivar(MAC_PLATFORM_IVAR, self_ptr); |
| 363 | | − |
| 364 | | −// // let pool = NSAutoreleasePool::new(nil); |
| 365 | | −// // app.run(); |
| 366 | | −// // pool.drain(); |
| 367 | | − |
| 368 | | −// // (*app).set_ivar(MAC_PLATFORM_IVAR, null_mut::<c_void>()); |
| 369 | | −// // (*app.delegate()).set_ivar(MAC_PLATFORM_IVAR, null_mut::<c_void>()); |
| 370 | | −// // } |
| 371 | | −// // } |
| 372 | | − |
| 373 | | −// // fn on_menu_command(&self, callback: Box<dyn FnMut(&dyn Action)>) { |
| 374 | | −// // self.0.borrow_mut().menu_command = Some(callback); |
| 375 | | −// // } |
| 376 | | − |
| 377 | | −// // fn on_will_open_menu(&self, callback: Box<dyn FnMut()>) { |
| 378 | | −// // self.0.borrow_mut().will_open_menu = Some(callback); |
| 379 | | −// // } |
| 380 | | − |
| 381 | | −// // fn on_validate_menu_command(&self, callback: Box<dyn FnMut(&dyn Action) -> bool>) { |
| 382 | | −// // self.0.borrow_mut().validate_menu_command = Some(callback); |
| 383 | | −// // } |
| 384 | | − |
| 385 | | −// // fn set_menus(&self, menus: Vec<Menu>, keystroke_matcher: &KeymapMatcher) { |
| 386 | | −// // unsafe { |
| 387 | | −// // let app: id = msg_send![APP_CLASS, sharedApplication]; |
| 388 | | −// // let mut state = self.0.borrow_mut(); |
| 389 | | −// // let actions = &mut state.menu_actions; |
| 390 | | −// // app.setMainMenu_(self.create_menu_bar( |
| 391 | | −// // menus, |
| 392 | | −// // app.delegate(), |
| 393 | | −// // actions, |
| 394 | | −// // keystroke_matcher, |
| 395 | | −// // )); |
| 396 | | −// // } |
| 397 | | −// // } |
| 398 | | − |
| 399 | | −// // fn prompt_for_paths( |
| 400 | | −// // &self, |
| 401 | | −// // options: platform::PathPromptOptions, |
| 402 | | −// // ) -> oneshot::Receiver<Option<Vec<PathBuf>>> { |
| 403 | | −// // unsafe { |
| 404 | | −// // let panel = NSOpenPanel::openPanel(nil); |
| 405 | | −// // panel.setCanChooseDirectories_(options.directories.to_objc()); |
| 406 | | −// // panel.setCanChooseFiles_(options.files.to_objc()); |
| 407 | | −// // panel.setAllowsMultipleSelection_(options.multiple.to_objc()); |
| 408 | | −// // panel.setResolvesAliases_(false.to_objc()); |
| 409 | | −// // let (done_tx, done_rx) = oneshot::channel(); |
| 410 | | −// // let done_tx = Cell::new(Some(done_tx)); |
| 411 | | −// // let block = ConcreteBlock::new(move |response: NSModalResponse| { |
| 412 | | −// // let result = if response == NSModalResponse::NSModalResponseOk { |
| 413 | | −// // let mut result = Vec::new(); |
| 414 | | −// // let urls = panel.URLs(); |
| 415 | | −// // for i in 0..urls.count() { |
| 416 | | −// // let url = urls.objectAtIndex(i); |
| 417 | | −// // if url.isFileURL() == YES { |
| 418 | | −// // if let Ok(path) = ns_url_to_path(url) { |
| 419 | | −// // result.push(path) |
| 420 | | −// // } |
| 421 | | −// // } |
| 422 | | −// // } |
| 423 | | −// // Some(result) |
| 424 | | −// // } else { |
| 425 | | −// // None |
| 426 | | −// // }; |
| 427 | | − |
| 428 | | −// // if let Some(mut done_tx) = done_tx.take() { |
| 429 | | −// // let _ = postage::sink::Sink::try_send(&mut done_tx, result); |
| 430 | | −// // } |
| 431 | | −// // }); |
| 432 | | −// // let block = block.copy(); |
| 433 | | −// // let _: () = msg_send![panel, beginWithCompletionHandler: block]; |
| 434 | | −// // done_rx |
| 435 | | −// // } |
| 436 | | −// // } |
| 437 | | − |
| 438 | | −// // fn prompt_for_new_path(&self, directory: &Path) -> oneshot::Receiver<Option<PathBuf>> { |
| 439 | | −// // unsafe { |
| 440 | | −// // let panel = NSSavePanel::savePanel(nil); |
| 441 | | −// // let path = ns_string(directory.to_string_lossy().as_ref()); |
| 442 | | −// // let url = NSURL::fileURLWithPath_isDirectory_(nil, path, true.to_objc()); |
| 443 | | −// // panel.setDirectoryURL(url); |
| 444 | | − |
| 445 | | −// // let (done_tx, done_rx) = oneshot::channel(); |
| 446 | | −// // let done_tx = Cell::new(Some(done_tx)); |
| 447 | | −// // let block = ConcreteBlock::new(move |response: NSModalResponse| { |
| 448 | | −// // let mut result = None; |
| 449 | | −// // if response == NSModalResponse::NSModalResponseOk { |
| 450 | | −// // let url = panel.URL(); |
| 451 | | −// // if url.isFileURL() == YES { |
| 452 | | −// // result = ns_url_to_path(panel.URL()).ok() |
| 453 | | −// // } |
| 454 | | −// // } |
| 455 | | − |
| 456 | | −// // if let Some(mut done_tx) = done_tx.take() { |
| 457 | | −// // let _ = postage::sink::Sink::try_send(&mut done_tx, result); |
| 458 | | −// // } |
| 459 | | −// // }); |
| 460 | | −// // let block = block.copy(); |
| 461 | | −// // let _: () = msg_send![panel, beginWithCompletionHandler: block]; |
| 462 | | −// // done_rx |
| 463 | | −// // } |
| 464 | | −// // } |
| 465 | | − |
| 466 | | −// // fn reveal_path(&self, path: &Path) { |
| 467 | | −// // unsafe { |
| 468 | | −// // let path = path.to_path_buf(); |
| 469 | | −// // self.0 |
| 470 | | −// // .borrow() |
| 471 | | −// // .foreground |
| 472 | | −// // .spawn(async move { |
| 473 | | −// // let full_path = ns_string(path.to_str().unwrap_or("")); |
| 474 | | −// // let root_full_path = ns_string(""); |
| 475 | | −// // let workspace: id = msg_send![class!(NSWorkspace), sharedWorkspace]; |
| 476 | | −// // let _: BOOL = msg_send![ |
| 477 | | −// // workspace, |
| 478 | | −// // selectFile: full_path |
| 479 | | −// // inFileViewerRootedAtPath: root_full_path |
| 480 | | −// // ]; |
| 481 | | −// // }) |
| 482 | | −// // .detach(); |
| 483 | | −// // } |
| 484 | | −// // } |
| 485 | | −// // } |
| 486 | | − |
| 487 | | −// // impl Platform for MacPlatform { |
| 488 | | −// // fn dispatcher(&self) -> Arc<dyn platform::Dispatcher> { |
| 489 | | −// // self.dispatcher.clone() |
| 490 | | −// // } |
| 491 | | − |
| 492 | | −// // fn fonts(&self) -> Arc<dyn platform::FontSystem> { |
| 493 | | −// // self.fonts.clone() |
| 494 | | −// // } |
| 495 | | − |
| 496 | | −// // fn activate(&self, ignoring_other_apps: bool) { |
| 497 | | −// // unsafe { |
| 498 | | −// // let app = NSApplication::sharedApplication(nil); |
| 499 | | −// // app.activateIgnoringOtherApps_(ignoring_other_apps.to_objc()); |
| 500 | | −// // } |
| 501 | | −// // } |
| 502 | | − |
| 503 | | −// // fn hide(&self) { |
| 504 | | −// // unsafe { |
| 505 | | −// // let app = NSApplication::sharedApplication(nil); |
| 506 | | −// // let _: () = msg_send![app, hide: nil]; |
| 507 | | −// // } |
| 508 | | −// // } |
| 509 | | − |
| 510 | | −// // fn hide_other_apps(&self) { |
| 511 | | −// // unsafe { |
| 512 | | −// // let app = NSApplication::sharedApplication(nil); |
| 513 | | −// // let _: () = msg_send![app, hideOtherApplications: nil]; |
| 514 | | −// // } |
| 515 | | −// // } |
| 516 | | − |
| 517 | | −// // fn unhide_other_apps(&self) { |
| 518 | | −// // unsafe { |
| 519 | | −// // let app = NSApplication::sharedApplication(nil); |
| 520 | | −// // let _: () = msg_send![app, unhideAllApplications: nil]; |
| 521 | | −// // } |
| 522 | | −// // } |
| 523 | | − |
| 524 | | −// // fn quit(&self) { |
| 525 | | −// // // Quitting the app causes us to close windows, which invokes `Window::on_close` callbacks |
| 526 | | −// // // synchronously before this method terminates. If we call `Platform::quit` while holding a |
| 527 | | −// // // borrow of the app state (which most of the time we will do), we will end up |
| 528 | | −// // // double-borrowing the app state in the `on_close` callbacks for our open windows. To solve |
| 529 | | −// // // this, we make quitting the application asynchronous so that we aren't holding borrows to |
| 530 | | −// // // the app state on the stack when we actually terminate the app. |
| 531 | | − |
| 532 | | −// // use super::dispatcher::{dispatch_async_f, dispatch_get_main_queue}; |
| 533 | | − |
| 534 | | −// // unsafe { |
| 535 | | −// // dispatch_async_f(dispatch_get_main_queue(), ptr::null_mut(), Some(quit)); |
| 536 | | −// // } |
| 537 | | − |
| 538 | | −// // unsafe extern "C" fn quit(_: *mut c_void) { |
| 539 | | −// // let app = NSApplication::sharedApplication(nil); |
| 540 | | −// // let _: () = msg_send![app, terminate: nil]; |
| 541 | | −// // } |
| 542 | | −// // } |
| 543 | | − |
| 544 | | −// // fn screen_by_id(&self, id: uuid::Uuid) -> Option<Rc<dyn platform::Screen>> { |
| 545 | | −// // Screen::find_by_id(id).map(|screen| Rc::new(screen) as Rc<_>) |
| 546 | | −// // } |
| 547 | | − |
| 548 | | −// // fn screens(&self) -> Vec<Rc<dyn platform::Screen>> { |
| 549 | | −// // Screen::all() |
| 550 | | −// // .into_iter() |
| 551 | | −// // .map(|screen| Rc::new(screen) as Rc<_>) |
| 552 | | −// // .collect() |
| 553 | | −// // } |
| 554 | | − |
| 555 | | −// // fn open_window( |
| 556 | | −// // &self, |
| 557 | | −// // handle: AnyWindowHandle, |
| 558 | | −// // options: platform::WindowOptions, |
| 559 | | −// // executor: Rc<executor::Foreground>, |
| 560 | | −// // ) -> Box<dyn platform::Window> { |
| 561 | | −// // Box::new(MacWindow::open(handle, options, executor, self.fonts())) |
| 562 | | −// // } |
| 563 | | − |
| 564 | | −// // fn main_window(&self) -> Option<AnyWindowHandle> { |
| 565 | | −// // MacWindow::main_window() |
| 566 | | −// // } |
| 567 | | − |
| 568 | | −// // fn add_status_item(&self, _handle: AnyWindowHandle) -> Box<dyn platform::Window> { |
| 569 | | −// // Box::new(StatusItem::add(self.fonts())) |
| 570 | | −// // } |
| 571 | | − |
| 572 | | −// // fn write_to_clipboard(&self, item: ClipboardItem) { |
| 573 | | −// // unsafe { |
| 574 | | −// // self.pasteboard.clearContents(); |
| 575 | | − |
| 576 | | −// // let text_bytes = NSData::dataWithBytes_length_( |
| 577 | | −// // nil, |
| 578 | | −// // item.text.as_ptr() as *const c_void, |
| 579 | | −// // item.text.len() as u64, |
| 580 | | −// // ); |
| 581 | | −// // self.pasteboard |
| 582 | | −// // .setData_forType(text_bytes, NSPasteboardTypeString); |
| 583 | | − |
| 584 | | −// // if let Some(metadata) = item.metadata.as_ref() { |
| 585 | | −// // let hash_bytes = ClipboardItem::text_hash(&item.text).to_be_bytes(); |
| 586 | | −// // let hash_bytes = NSData::dataWithBytes_length_( |
| 587 | | −// // nil, |
| 588 | | −// // hash_bytes.as_ptr() as *const c_void, |
| 589 | | −// // hash_bytes.len() as u64, |
| 590 | | −// // ); |
| 591 | | −// // self.pasteboard |
| 592 | | −// // .setData_forType(hash_bytes, self.text_hash_pasteboard_type); |
| 593 | | − |
| 594 | | −// // let metadata_bytes = NSData::dataWithBytes_length_( |
| 595 | | −// // nil, |
| 596 | | −// // metadata.as_ptr() as *const c_void, |
| 597 | | −// // metadata.len() as u64, |
| 598 | | −// // ); |
| 599 | | −// // self.pasteboard |
| 600 | | −// // .setData_forType(metadata_bytes, self.metadata_pasteboard_type); |
| 601 | | −// // } |
| 602 | | −// // } |
| 603 | | −// // } |
| 604 | | − |
| 605 | | −// // fn read_from_clipboard(&self) -> Option<ClipboardItem> { |
| 606 | | −// // unsafe { |
| 607 | | −// // if let Some(text_bytes) = self.read_from_pasteboard(NSPasteboardTypeString) { |
| 608 | | −// // let text = String::from_utf8_lossy(text_bytes).to_string(); |
| 609 | | −// // let hash_bytes = self |
| 610 | | −// // .read_from_pasteboard(self.text_hash_pasteboard_type) |
| 611 | | −// // .and_then(|bytes| bytes.try_into().ok()) |
| 612 | | −// // .map(u64::from_be_bytes); |
| 613 | | −// // let metadata_bytes = self |
| 614 | | −// // .read_from_pasteboard(self.metadata_pasteboard_type) |
| 615 | | −// // .and_then(|bytes| String::from_utf8(bytes.to_vec()).ok()); |
| 616 | | − |
| 617 | | −// // if let Some((hash, metadata)) = hash_bytes.zip(metadata_bytes) { |
| 618 | | −// // if hash == ClipboardItem::text_hash(&text) { |
| 619 | | −// // Some(ClipboardItem { |
| 620 | | −// // text, |
| 621 | | −// // metadata: Some(metadata), |
| 622 | | −// // }) |
| 623 | | −// // } else { |
| 624 | | −// // Some(ClipboardItem { |
| 625 | | −// // text, |
| 626 | | −// // metadata: None, |
| 627 | | −// // }) |
| 628 | | −// // } |
| 629 | | −// // } else { |
| 630 | | −// // Some(ClipboardItem { |
| 631 | | −// // text, |
| 632 | | −// // metadata: None, |
| 633 | | −// // }) |
| 634 | | −// // } |
| 635 | | −// // } else { |
| 636 | | −// // None |
| 637 | | −// // } |
| 638 | | −// // } |
| 639 | | −// // } |
| 640 | | − |
| 641 | | −// // fn open_url(&self, url: &str) { |
| 642 | | −// // unsafe { |
| 643 | | −// // let url = NSURL::alloc(nil) |
| 644 | | −// // .initWithString_(ns_string(url)) |
| 645 | | −// // .autorelease(); |
| 646 | | −// // let workspace: id = msg_send![class!(NSWorkspace), sharedWorkspace]; |
| 647 | | −// // msg_send![workspace, openURL: url] |
| 648 | | −// // } |
| 649 | | −// // } |
| 650 | | − |
| 651 | | −// // fn write_credentials(&self, url: &str, username: &str, password: &[u8]) -> Result<()> { |
| 652 | | −// // let url = CFString::from(url); |
| 653 | | −// // let username = CFString::from(username); |
| 654 | | −// // let password = CFData::from_buffer(password); |
| 655 | | − |
| 656 | | −// // unsafe { |
| 657 | | −// // use security::*; |
| 658 | | − |
| 659 | | −// // // First, check if there are already credentials for the given server. If so, then |
| 660 | | −// // // update the username and password. |
| 661 | | −// // let mut verb = "updating"; |
| 662 | | −// // let mut query_attrs = CFMutableDictionary::with_capacity(2); |
| 663 | | −// // query_attrs.set(kSecClass as *const _, kSecClassInternetPassword as *const _); |
| 664 | | −// // query_attrs.set(kSecAttrServer as *const _, url.as_CFTypeRef()); |
| 665 | | − |
| 666 | | −// // let mut attrs = CFMutableDictionary::with_capacity(4); |
| 667 | | −// // attrs.set(kSecClass as *const _, kSecClassInternetPassword as *const _); |
| 668 | | −// // attrs.set(kSecAttrServer as *const _, url.as_CFTypeRef()); |
| 669 | | −// // attrs.set(kSecAttrAccount as *const _, username.as_CFTypeRef()); |
| 670 | | −// // attrs.set(kSecValueData as *const _, password.as_CFTypeRef()); |
| 671 | | − |
| 672 | | −// // let mut status = SecItemUpdate( |
| 673 | | −// // query_attrs.as_concrete_TypeRef(), |
| 674 | | −// // attrs.as_concrete_TypeRef(), |
| 675 | | −// // ); |
| 676 | | − |
| 677 | | −// // // If there were no existing credentials for the given server, then create them. |
| 678 | | −// // if status == errSecItemNotFound { |
| 679 | | −// // verb = "creating"; |
| 680 | | −// // status = SecItemAdd(attrs.as_concrete_TypeRef(), ptr::null_mut()); |
| 681 | | −// // } |
| 682 | | − |
| 683 | | −// // if status != errSecSuccess { |
| 684 | | −// // return Err(anyhow!("{} password failed: {}", verb, status)); |
| 685 | | −// // } |
| 686 | | −// // } |
| 687 | | −// // Ok(()) |
| 688 | | −// // } |
| 689 | | − |
| 690 | | −// // fn read_credentials(&self, url: &str) -> Result<Option<(String, Vec<u8>)>> { |
| 691 | | −// // let url = CFString::from(url); |
| 692 | | −// // let cf_true = CFBoolean::true_value().as_CFTypeRef(); |
| 693 | | − |
| 694 | | −// // unsafe { |
| 695 | | −// // use security::*; |
| 696 | | − |
| 697 | | −// // // Find any credentials for the given server URL. |
| 698 | | −// // let mut attrs = CFMutableDictionary::with_capacity(5); |
| 699 | | −// // attrs.set(kSecClass as *const _, kSecClassInternetPassword as *const _); |
| 700 | | −// // attrs.set(kSecAttrServer as *const _, url.as_CFTypeRef()); |
| 701 | | −// // attrs.set(kSecReturnAttributes as *const _, cf_true); |
| 702 | | −// // attrs.set(kSecReturnData as *const _, cf_true); |
| 703 | | − |
| 704 | | −// // let mut result = CFTypeRef::from(ptr::null()); |
| 705 | | −// // let status = SecItemCopyMatching(attrs.as_concrete_TypeRef(), &mut result); |
| 706 | | −// // match status { |
| 707 | | −// // security::errSecSuccess => {} |
| 708 | | −// // security::errSecItemNotFound | security::errSecUserCanceled => return Ok(None), |
| 709 | | −// // _ => return Err(anyhow!("reading password failed: {}", status)), |
| 710 | | −// // } |
| 711 | | − |
| 712 | | −// // let result = CFType::wrap_under_create_rule(result) |
| 713 | | −// // .downcast::<CFDictionary>() |
| 714 | | −// // .ok_or_else(|| anyhow!("keychain item was not a dictionary"))?; |
| 715 | | −// // let username = result |
| 716 | | −// // .find(kSecAttrAccount as *const _) |
| 717 | | −// // .ok_or_else(|| anyhow!("account was missing from keychain item"))?; |
| 718 | | −// // let username = CFType::wrap_under_get_rule(*username) |
| 719 | | −// // .downcast::<CFString>() |
| 720 | | −// // .ok_or_else(|| anyhow!("account was not a string"))?; |
| 721 | | −// // let password = result |
| 722 | | −// // .find(kSecValueData as *const _) |
| 723 | | −// // .ok_or_else(|| anyhow!("password was missing from keychain item"))?; |
| 724 | | −// // let password = CFType::wrap_under_get_rule(*password) |
| 725 | | −// // .downcast::<CFData>() |
| 726 | | −// // .ok_or_else(|| anyhow!("password was not a string"))?; |
| 727 | | − |
| 728 | | −// // Ok(Some((username.to_string(), password.bytes().to_vec()))) |
| 729 | | −// // } |
| 730 | | −// // } |
| 731 | | − |
| 732 | | −// // fn delete_credentials(&self, url: &str) -> Result<()> { |
| 733 | | −// // let url = CFString::from(url); |
| 734 | | − |
| 735 | | −// // unsafe { |
| 736 | | −// // use security::*; |
| 737 | | − |
| 738 | | −// // let mut query_attrs = CFMutableDictionary::with_capacity(2); |
| 739 | | −// // query_attrs.set(kSecClass as *const _, kSecClassInternetPassword as *const _); |
| 740 | | −// // query_attrs.set(kSecAttrServer as *const _, url.as_CFTypeRef()); |
| 741 | | − |
| 742 | | −// // let status = SecItemDelete(query_attrs.as_concrete_TypeRef()); |
| 743 | | − |
| 744 | | −// // if status != errSecSuccess { |
| 745 | | −// // return Err(anyhow!("delete password failed: {}", status)); |
| 746 | | −// // } |
| 747 | | −// // } |
| 748 | | −// // Ok(()) |
| 749 | | −// // } |
| 750 | | − |
| 751 | | −// // fn set_cursor_style(&self, style: CursorStyle) { |
| 752 | | −// // unsafe { |
| 753 | | −// // let new_cursor: id = match style { |
| 754 | | −// // CursorStyle::Arrow => msg_send![class!(NSCursor), arrowCursor], |
| 755 | | −// // CursorStyle::ResizeLeftRight => { |
| 756 | | −// // msg_send![class!(NSCursor), resizeLeftRightCursor] |
| 757 | | −// // } |
| 758 | | −// // CursorStyle::ResizeUpDown => msg_send![class!(NSCursor), resizeUpDownCursor], |
| 759 | | −// // CursorStyle::PointingHand => msg_send![class!(NSCursor), pointingHandCursor], |
| 760 | | −// // CursorStyle::IBeam => msg_send![class!(NSCursor), IBeamCursor], |
| 761 | | −// // }; |
| 762 | | − |
| 763 | | −// // let old_cursor: id = msg_send![class!(NSCursor), currentCursor]; |
| 764 | | −// // if new_cursor != old_cursor { |
| 765 | | −// // let _: () = msg_send![new_cursor, set]; |
| 766 | | −// // } |
| 767 | | −// // } |
| 768 | | −// // } |
| 769 | | − |
| 770 | | −// // fn should_auto_hide_scrollbars(&self) -> bool { |
| 771 | | −// // #[allow(non_upper_case_globals)] |
| 772 | | −// // const NSScrollerStyleOverlay: NSInteger = 1; |
| 773 | | − |
| 774 | | −// // unsafe { |
| 775 | | −// // let style: NSInteger = msg_send![class!(NSScroller), preferredScrollerStyle]; |
| 776 | | −// // style == NSScrollerStyleOverlay |
| 777 | | −// // } |
| 778 | | −// // } |
| 779 | | − |
| 780 | | −// // fn local_timezone(&self) -> UtcOffset { |
| 781 | | −// // unsafe { |
| 782 | | −// // let local_timezone: id = msg_send![class!(NSTimeZone), localTimeZone]; |
| 783 | | −// // let seconds_from_gmt: NSInteger = msg_send![local_timezone, secondsFromGMT]; |
| 784 | | −// // UtcOffset::from_whole_seconds(seconds_from_gmt.try_into().unwrap()).unwrap() |
| 785 | | −// // } |
| 786 | | −// // } |
| 787 | | − |
| 788 | | −// // fn path_for_auxiliary_executable(&self, name: &str) -> Result<PathBuf> { |
| 789 | | −// // unsafe { |
| 790 | | −// // let bundle: id = NSBundle::mainBundle(); |
| 791 | | −// // if bundle.is_null() { |
| 792 | | −// // Err(anyhow!("app is not running inside a bundle")) |
| 793 | | −// // } else { |
| 794 | | −// // let name = ns_string(name); |
| 795 | | −// // let url: id = msg_send![bundle, URLForAuxiliaryExecutable: name]; |
| 796 | | −// // if url.is_null() { |
| 797 | | −// // Err(anyhow!("resource not found")) |
| 798 | | −// // } else { |
| 799 | | −// // ns_url_to_path(url) |
| 800 | | −// // } |
| 801 | | −// // } |
| 802 | | −// // } |
| 803 | | −// // } |
| 804 | | − |
| 805 | | −// // fn app_path(&self) -> Result<PathBuf> { |
| 806 | | −// // unsafe { |
| 807 | | −// // let bundle: id = NSBundle::mainBundle(); |
| 808 | | −// // if bundle.is_null() { |
| 809 | | −// // Err(anyhow!("app is not running inside a bundle")) |
| 810 | | −// // } else { |
| 811 | | −// // Ok(path_from_objc(msg_send![bundle, bundlePath])) |
| 812 | | −// // } |
| 813 | | −// // } |
| 814 | | −// // } |
| 815 | | − |
| 816 | | −// // fn app_version(&self) -> Result<platform::AppVersion> { |
| 817 | | −// // unsafe { |
| 818 | | −// // let bundle: id = NSBundle::mainBundle(); |
| 819 | | −// // if bundle.is_null() { |
| 820 | | −// // Err(anyhow!("app is not running inside a bundle")) |
| 821 | | −// // } else { |
| 822 | | −// // let version: id = msg_send![bundle, objectForInfoDictionaryKey: ns_string("CFBundleShortVersionString")]; |
| 823 | | −// // let len = msg_send![version, lengthOfBytesUsingEncoding: NSUTF8StringEncoding]; |
| 824 | | −// // let bytes = version.UTF8String() as *const u8; |
| 825 | | −// // let version = str::from_utf8(slice::from_raw_parts(bytes, len)).unwrap(); |
| 826 | | −// // version.parse() |
| 827 | | −// // } |
| 828 | | −// // } |
| 829 | | −// // } |
| 830 | | − |
| 831 | | −// // fn os_name(&self) -> &'static str { |
| 832 | | −// // "macOS" |
| 833 | | −// // } |
| 834 | | − |
| 835 | | −// // fn os_version(&self) -> Result<crate::platform::AppVersion> { |
| 836 | | −// // unsafe { |
| 837 | | −// // let process_info = NSProcessInfo::processInfo(nil); |
| 838 | | −// // let version = process_info.operatingSystemVersion(); |
| 839 | | −// // Ok(AppVersion { |
| 840 | | −// // major: version.majorVersion as usize, |
| 841 | | −// // minor: version.minorVersion as usize, |
| 842 | | −// // patch: version.patchVersion as usize, |
| 843 | | −// // }) |
| 844 | | −// // } |
| 845 | | −// // } |
| 846 | | − |
| 847 | | −// // fn restart(&self) { |
| 848 | | −// // use std::os::unix::process::CommandExt as _; |
| 849 | | − |
| 850 | | −// // let app_pid = std::process::id().to_string(); |
| 851 | | −// // let app_path = self |
| 852 | | −// // .app_path() |
| 853 | | −// // .ok() |
| 854 | | −// // // When the app is not bundled, `app_path` returns the |
| 855 | | −// // // directory containing the executable. Disregard this |
| 856 | | −// // // and get the path to the executable itself. |
| 857 | | −// // .and_then(|path| (path.extension()?.to_str()? == "app").then_some(path)) |
| 858 | | −// // .unwrap_or_else(|| std::env::current_exe().unwrap()); |
| 859 | | − |
| 860 | | −// // // Wait until this process has exited and then re-open this path. |
| 861 | | −// // let script = r#" |
| 862 | | −// // while kill -0 $0 2> /dev/null; do |
| 863 | | −// // sleep 0.1 |
| 864 | | −// // done |
| 865 | | −// // open "$1" |
| 866 | | −// // "#; |
| 867 | | − |
| 868 | | −// // let restart_process = Command::new("/bin/bash") |
| 869 | | −// // .arg("-c") |
| 870 | | −// // .arg(script) |
| 871 | | −// // .arg(app_pid) |
| 872 | | −// // .arg(app_path) |
| 873 | | −// // .process_group(0) |
| 874 | | −// // .spawn(); |
| 875 | | − |
| 876 | | −// // match restart_process { |
| 877 | | −// // Ok(_) => self.quit(), |
| 878 | | −// // Err(e) => log::error!("failed to spawn restart script: {:?}", e), |
| 879 | | −// // } |
| 880 | | −// // } |
| 881 | | −// // } |
| 882 | | − |
| 883 | | −// unsafe fn path_from_objc(path: id) -> PathBuf { |
| 884 | | −// let len = msg_send![path, lengthOfBytesUsingEncoding: NSUTF8StringEncoding]; |
| 885 | | −// let bytes = path.UTF8String() as *const u8; |
| 886 | | −// let path = str::from_utf8(slice::from_raw_parts(bytes, len)).unwrap(); |
| 887 | | −// PathBuf::from(path) |
| 888 | | −// } |
| 889 | | − |
| 890 | | −// unsafe fn get_foreground_platform(object: &mut Object) -> &MacForegroundPlatform { |
| 891 | | −// let platform_ptr: *mut c_void = *object.get_ivar(MAC_PLATFORM_IVAR); |
| 892 | | −// assert!(!platform_ptr.is_null()); |
| 893 | | −// &*(platform_ptr as *const MacForegroundPlatform) |
| 894 | | −// } |
| 895 | | − |
| 896 | | −// extern "C" fn send_event(this: &mut Object, _sel: Sel, native_event: id) { |
| 897 | | −// unsafe { |
| 898 | | −// if let Some(event) = Event::from_native(native_event, None) { |
| 899 | | −// let platform = get_foreground_platform(this); |
| 900 | | −// if let Some(callback) = platform.0.borrow_mut().event.as_mut() { |
| 901 | | −// if callback(event) { |
| 902 | | −// return; |
| 903 | | −// } |
| 904 | | −// } |
| 905 | | −// } |
| 906 | | −// msg_send![super(this, class!(NSApplication)), sendEvent: native_event] |
| 907 | | −// } |
| 908 | | −// } |
| 909 | | − |
| 910 | | −// extern "C" fn did_finish_launching(this: &mut Object, _: Sel, _: id) { |
| 911 | | −// unsafe { |
| 912 | | −// let app: id = msg_send![APP_CLASS, sharedApplication]; |
| 913 | | −// app.setActivationPolicy_(NSApplicationActivationPolicyRegular); |
| 914 | | − |
| 915 | | −// let platform = get_foreground_platform(this); |
| 916 | | −// let callback = platform.0.borrow_mut().finish_launching.take(); |
| 917 | | −// if let Some(callback) = callback { |
| 918 | | −// callback(); |
| 919 | | −// } |
| 920 | | −// } |
| 921 | | −// } |
| 922 | | − |
| 923 | | −// extern "C" fn should_handle_reopen(this: &mut Object, _: Sel, _: id, has_open_windows: bool) { |
| 924 | | −// if !has_open_windows { |
| 925 | | −// let platform = unsafe { get_foreground_platform(this) }; |
| 926 | | −// if let Some(callback) = platform.0.borrow_mut().reopen.as_mut() { |
| 927 | | −// callback(); |
| 928 | | −// } |
| 929 | | −// } |
| 930 | | −// } |
| 931 | | − |
| 932 | | −// extern "C" fn did_become_active(this: &mut Object, _: Sel, _: id) { |
| 933 | | −// let platform = unsafe { get_foreground_platform(this) }; |
| 934 | | −// if let Some(callback) = platform.0.borrow_mut().become_active.as_mut() { |
| 935 | | −// callback(); |
| 936 | | −// } |
| 937 | | −// } |
| 938 | | − |
| 939 | | −// extern "C" fn did_resign_active(this: &mut Object, _: Sel, _: id) { |
| 940 | | −// let platform = unsafe { get_foreground_platform(this) }; |
| 941 | | −// if let Some(callback) = platform.0.borrow_mut().resign_active.as_mut() { |
| 942 | | −// callback(); |
| 943 | | −// } |
| 944 | | −// } |
| 945 | | − |
| 946 | | −// extern "C" fn will_terminate(this: &mut Object, _: Sel, _: id) { |
| 947 | | −// let platform = unsafe { get_foreground_platform(this) }; |
| 948 | | −// if let Some(callback) = platform.0.borrow_mut().quit.as_mut() { |
| 949 | | −// callback(); |
| 950 | | −// } |
| 951 | | −// } |
| 952 | | − |
| 953 | | −// extern "C" fn open_urls(this: &mut Object, _: Sel, _: id, urls: id) { |
| 954 | | −// let urls = unsafe { |
| 955 | | −// (0..urls.count()) |
| 956 | | −// .into_iter() |
| 957 | | −// .filter_map(|i| { |
| 958 | | −// let url = urls.objectAtIndex(i); |
| 959 | | −// match CStr::from_ptr(url.absoluteString().UTF8String() as *mut c_char).to_str() { |
| 960 | | −// Ok(string) => Some(string.to_string()), |
| 961 | | −// Err(err) => { |
| 962 | | −// log::error!("error converting path to string: {}", err); |
| 963 | | −// None |
| 964 | | −// } |
| 965 | | −// } |
| 966 | | −// }) |
| 967 | | −// .collect::<Vec<_>>() |
| 968 | | −// }; |
| 969 | | −// let platform = unsafe { get_foreground_platform(this) }; |
| 970 | | −// if let Some(callback) = platform.0.borrow_mut().open_urls.as_mut() { |
| 971 | | −// callback(urls); |
| 972 | | −// } |
| 973 | | −// } |
| 974 | | − |
| 975 | | −// extern "C" fn handle_menu_item(this: &mut Object, _: Sel, item: id) { |
| 976 | | −// unsafe { |
| 977 | | −// let platform = get_foreground_platform(this); |
| 978 | | −// let mut platform = platform.0.borrow_mut(); |
| 979 | | −// if let Some(mut callback) = platform.menu_command.take() { |
| 980 | | −// let tag: NSInteger = msg_send![item, tag]; |
| 981 | | −// let index = tag as usize; |
| 982 | | −// if let Some(action) = platform.menu_actions.get(index) { |
| 983 | | −// callback(action.as_ref()); |
| 984 | | −// } |
| 985 | | −// platform.menu_command = Some(callback); |
| 986 | | −// } |
| 987 | | −// } |
| 988 | | −// } |
| 989 | | − |
| 990 | | −// extern "C" fn validate_menu_item(this: &mut Object, _: Sel, item: id) -> bool { |
| 991 | | −// unsafe { |
| 992 | | −// let mut result = false; |
| 993 | | −// let platform = get_foreground_platform(this); |
| 994 | | −// let mut platform = platform.0.borrow_mut(); |
| 995 | | −// if let Some(mut callback) = platform.validate_menu_command.take() { |
| 996 | | −// let tag: NSInteger = msg_send![item, tag]; |
| 997 | | −// let index = tag as usize; |
| 998 | | −// if let Some(action) = platform.menu_actions.get(index) { |
| 999 | | −// result = callback(action.as_ref()); |
| 1000 | | −// } |
| 1001 | | −// platform.validate_menu_command = Some(callback); |
| 1002 | | −// } |
| 1003 | | −// result |
| 1004 | | −// } |
| 1005 | | −// } |
| 1006 | | − |
| 1007 | | −// extern "C" fn menu_will_open(this: &mut Object, _: Sel, _: id) { |
| 1008 | | −// unsafe { |
| 1009 | | −// let platform = get_foreground_platform(this); |
| 1010 | | −// let mut platform = platform.0.borrow_mut(); |
| 1011 | | −// if let Some(mut callback) = platform.will_open_menu.take() { |
| 1012 | | −// callback(); |
| 1013 | | −// platform.will_open_menu = Some(callback); |
| 1014 | | −// } |
| 1015 | | −// } |
| 1016 | | −// } |
| 1017 | | − |
| 1018 | | −// unsafe fn ns_string(string: &str) -> id { |
| 1019 | | −// NSString::alloc(nil).init_str(string).autorelease() |
| 1020 | | −// } |
| 1021 | | − |
| 1022 | | −// unsafe fn ns_url_to_path(url: id) -> Result<PathBuf> { |
| 1023 | | −// let path: *mut c_char = msg_send![url, fileSystemRepresentation]; |
| 1024 | | −// if path.is_null() { |
| 1025 | | −// Err(anyhow!( |
| 1026 | | −// "url is not a file path: {}", |
| 1027 | | −// CStr::from_ptr(url.absoluteString().UTF8String()).to_string_lossy() |
| 1028 | | −// )) |
| 1029 | | −// } else { |
| 1030 | | −// Ok(PathBuf::from(OsStr::from_bytes( |
| 1031 | | −// CStr::from_ptr(path).to_bytes(), |
| 1032 | | −// ))) |
| 1033 | | −// } |
| 1034 | | −// } |
| 1035 | | − |
| 1036 | | −// mod security { |
| 1037 | | −// #![allow(non_upper_case_globals)] |
| 1038 | | −// use super::*; |
| 1039 | | − |
| 1040 | | −// #[link(name = "Security", kind = "framework")] |
| 1041 | | −// extern "C" { |
| 1042 | | −// pub static kSecClass: CFStringRef; |
| 1043 | | −// pub static kSecClassInternetPassword: CFStringRef; |
| 1044 | | −// pub static kSecAttrServer: CFStringRef; |
| 1045 | | −// pub static kSecAttrAccount: CFStringRef; |
| 1046 | | −// pub static kSecValueData: CFStringRef; |
| 1047 | | −// pub static kSecReturnAttributes: CFStringRef; |
| 1048 | | −// pub static kSecReturnData: CFStringRef; |
| 1049 | | − |
| 1050 | | −// pub fn SecItemAdd(attributes: CFDictionaryRef, result: *mut CFTypeRef) -> OSStatus; |
| 1051 | | −// pub fn SecItemUpdate(query: CFDictionaryRef, attributes: CFDictionaryRef) -> OSStatus; |
| 1052 | | −// pub fn SecItemDelete(query: CFDictionaryRef) -> OSStatus; |
| 1053 | | −// pub fn SecItemCopyMatching(query: CFDictionaryRef, result: *mut CFTypeRef) -> OSStatus; |
| 1054 | | −// } |
| 1055 | | − |
| 1056 | | −// pub const errSecSuccess: OSStatus = 0; |
| 1057 | | −// pub const errSecUserCanceled: OSStatus = -128; |
| 1058 | | −// pub const errSecItemNotFound: OSStatus = -25300; |
| 1059 | | −// } |
| 1060 | | − |
| 1061 | | −// #[cfg(test)] |
| 1062 | | −// mod tests { |
| 1063 | | −// use crate::platform::Platform; |
| 1064 | | − |
| 1065 | | −// use super::*; |
| 1066 | | − |
| 1067 | | −// #[test] |
| 1068 | | −// fn test_clipboard() { |
| 1069 | | −// let platform = build_platform(); |
| 1070 | | −// assert_eq!(platform.read_from_clipboard(), None); |
| 1071 | | − |
| 1072 | | −// let item = ClipboardItem::new("1".to_string()); |
| 1073 | | −// platform.write_to_clipboard(item.clone()); |
| 1074 | | −// assert_eq!(platform.read_from_clipboard(), Some(item)); |
| 1075 | | − |
| 1076 | | −// let item = ClipboardItem::new("2".to_string()).with_metadata(vec![3, 4]); |
| 1077 | | −// platform.write_to_clipboard(item.clone()); |
| 1078 | | −// assert_eq!(platform.read_from_clipboard(), Some(item)); |
| 1079 | | − |
| 1080 | | −// let text_from_other_app = "text from other app"; |
| 1081 | | −// unsafe { |
| 1082 | | −// let bytes = NSData::dataWithBytes_length_( |
| 1083 | | −// nil, |
| 1084 | | −// text_from_other_app.as_ptr() as *const c_void, |
| 1085 | | −// text_from_other_app.len() as u64, |
| 1086 | | −// ); |
| 1087 | | −// platform |
| 1088 | | −// .pasteboard |
| 1089 | | −// .setData_forType(bytes, NSPasteboardTypeString); |
| 1090 | | −// } |
| 1091 | | −// assert_eq!( |
| 1092 | | −// platform.read_from_clipboard(), |
| 1093 | | −// Some(ClipboardItem::new(text_from_other_app.to_string())) |
| 1094 | | −// ); |
| 1095 | | −// } |
| 1096 | | − |
| 1097 | | −// fn build_platform() -> MacPlatform { |
| 1098 | | −// let mut platform = MacPlatform::new(); |
| 1099 | | −// platform.pasteboard = unsafe { NSPasteboard::pasteboardWithUniqueName(nil) }; |
| 1100 | | −// platform |
| 1101 | | −// } |
| 1102 | | −// } |
| 1 | +use super::BoolExt; |
| 2 | +use crate::{ |
| 3 | + AnyWindowHandle, ClipboardItem, CursorStyle, Event, ForegroundExecutor, MacDispatcher, |
| 4 | + MacScreen, MacTextSystem, MacWindow, PathPromptOptions, Platform, PlatformScreen, |
| 5 | + PlatformTextSystem, PlatformWindow, Result, SemanticVersion, WindowOptions, |
| 6 | +}; |
| 7 | +use anyhow::anyhow; |
| 8 | +use block::ConcreteBlock; |
| 9 | +use cocoa::{ |
| 10 | + appkit::{ |
| 11 | + NSApplication, NSApplicationActivationPolicy::NSApplicationActivationPolicyRegular, |
| 12 | + NSModalResponse, NSOpenPanel, NSPasteboard, NSPasteboardTypeString, NSSavePanel, NSWindow, |
| 13 | + }, |
| 14 | + base::{id, nil, BOOL, YES}, |
| 15 | + foundation::{ |
| 16 | + NSArray, NSAutoreleasePool, NSBundle, NSData, NSInteger, NSProcessInfo, NSString, |
| 17 | + NSUInteger, NSURL, |
| 18 | + }, |
| 19 | +}; |
| 20 | +use core_foundation::{ |
| 21 | + base::{CFType, CFTypeRef, OSStatus, TCFType as _}, |
| 22 | + boolean::CFBoolean, |
| 23 | + data::CFData, |
| 24 | + dictionary::{CFDictionary, CFDictionaryRef, CFMutableDictionary}, |
| 25 | + string::{CFString, CFStringRef}, |
| 26 | +}; |
| 27 | +use ctor::ctor; |
| 28 | +use futures::channel::oneshot; |
| 29 | +use objc::{ |
| 30 | + class, |
| 31 | + declare::ClassDecl, |
| 32 | + msg_send, |
| 33 | + runtime::{Class, Object, Sel}, |
| 34 | + sel, sel_impl, |
| 35 | +}; |
| 36 | +use ptr::null_mut; |
| 37 | +use std::{ |
| 38 | + cell::{Cell, RefCell}, |
| 39 | + convert::TryInto, |
| 40 | + ffi::{c_void, CStr, OsStr}, |
| 41 | + os::{raw::c_char, unix::ffi::OsStrExt}, |
| 42 | + path::{Path, PathBuf}, |
| 43 | + process::Command, |
| 44 | + ptr, |
| 45 | + rc::Rc, |
| 46 | + slice, str, |
| 47 | + sync::Arc, |
| 48 | +}; |
| 49 | +use time::UtcOffset; |
| 50 | + |
| 51 | +#[allow(non_upper_case_globals)] |
| 52 | +const NSUTF8StringEncoding: NSUInteger = 4; |
| 53 | + |
| 54 | +#[allow(non_upper_case_globals)] |
| 55 | +pub const NSViewLayerContentsRedrawDuringViewResize: NSInteger = 2; |
| 56 | + |
| 57 | +const MAC_PLATFORM_IVAR: &str = "platform"; |
| 58 | +static mut APP_CLASS: *const Class = ptr::null(); |
| 59 | +static mut APP_DELEGATE_CLASS: *const Class = ptr::null(); |
| 60 | + |
| 61 | +#[ctor] |
| 62 | +unsafe fn build_classes() { |
| 63 | + APP_CLASS = { |
| 64 | + let mut decl = ClassDecl::new("GPUIApplication", class!(NSApplication)).unwrap(); |
| 65 | + decl.add_ivar::<*mut c_void>(MAC_PLATFORM_IVAR); |
| 66 | + decl.add_method( |
| 67 | + sel!(sendEvent:), |
| 68 | + send_event as extern "C" fn(&mut Object, Sel, id), |
| 69 | + ); |
| 70 | + decl.register() |
| 71 | + }; |
| 72 | + |
| 73 | + APP_DELEGATE_CLASS = { |
| 74 | + let mut decl = ClassDecl::new("GPUIApplicationDelegate", class!(NSResponder)).unwrap(); |
| 75 | + decl.add_ivar::<*mut c_void>(MAC_PLATFORM_IVAR); |
| 76 | + decl.add_method( |
| 77 | + sel!(applicationDidFinishLaunching:), |
| 78 | + did_finish_launching as extern "C" fn(&mut Object, Sel, id), |
| 79 | + ); |
| 80 | + decl.add_method( |
| 81 | + sel!(applicationShouldHandleReopen:hasVisibleWindows:), |
| 82 | + should_handle_reopen as extern "C" fn(&mut Object, Sel, id, bool), |
| 83 | + ); |
| 84 | + decl.add_method( |
| 85 | + sel!(applicationDidBecomeActive:), |
| 86 | + did_become_active as extern "C" fn(&mut Object, Sel, id), |
| 87 | + ); |
| 88 | + decl.add_method( |
| 89 | + sel!(applicationDidResignActive:), |
| 90 | + did_resign_active as extern "C" fn(&mut Object, Sel, id), |
| 91 | + ); |
| 92 | + decl.add_method( |
| 93 | + sel!(applicationWillTerminate:), |
| 94 | + will_terminate as extern "C" fn(&mut Object, Sel, id), |
| 95 | + ); |
| 96 | + decl.add_method( |
| 97 | + sel!(handleGPUIMenuItem:), |
| 98 | + handle_menu_item as extern "C" fn(&mut Object, Sel, id), |
| 99 | + ); |
| 100 | + // Add menu item handlers so that OS save panels have the correct key commands |
| 101 | + decl.add_method( |
| 102 | + sel!(cut:), |
| 103 | + handle_menu_item as extern "C" fn(&mut Object, Sel, id), |
| 104 | + ); |
| 105 | + decl.add_method( |
| 106 | + sel!(copy:), |
| 107 | + handle_menu_item as extern "C" fn(&mut Object, Sel, id), |
| 108 | + ); |
| 109 | + decl.add_method( |
| 110 | + sel!(paste:), |
| 111 | + handle_menu_item as extern "C" fn(&mut Object, Sel, id), |
| 112 | + ); |
| 113 | + decl.add_method( |
| 114 | + sel!(selectAll:), |
| 115 | + handle_menu_item as extern "C" fn(&mut Object, Sel, id), |
| 116 | + ); |
| 117 | + decl.add_method( |
| 118 | + sel!(undo:), |
| 119 | + handle_menu_item as extern "C" fn(&mut Object, Sel, id), |
| 120 | + ); |
| 121 | + decl.add_method( |
| 122 | + sel!(redo:), |
| 123 | + handle_menu_item as extern "C" fn(&mut Object, Sel, id), |
| 124 | + ); |
| 125 | + decl.add_method( |
| 126 | + sel!(validateMenuItem:), |
| 127 | + validate_menu_item as extern "C" fn(&mut Object, Sel, id) -> bool, |
| 128 | + ); |
| 129 | + decl.add_method( |
| 130 | + sel!(menuWillOpen:), |
| 131 | + menu_will_open as extern "C" fn(&mut Object, Sel, id), |
| 132 | + ); |
| 133 | + decl.add_method( |
| 134 | + sel!(application:openURLs:), |
| 135 | + open_urls as extern "C" fn(&mut Object, Sel, id, id), |
| 136 | + ); |
| 137 | + decl.register() |
| 138 | + } |
| 139 | +} |
| 140 | + |
| 141 | +pub struct MacPlatform(RefCell<MacPlatformState>); |
| 142 | + |
| 143 | +pub struct MacPlatformState { |
| 144 | + executor: Rc<ForegroundExecutor>, |
| 145 | + text_system: Arc<MacTextSystem>, |
| 146 | + pasteboard: id, |
| 147 | + text_hash_pasteboard_type: id, |
| 148 | + metadata_pasteboard_type: id, |
| 149 | + become_active: Option<Box<dyn FnMut()>>, |
| 150 | + resign_active: Option<Box<dyn FnMut()>>, |
| 151 | + reopen: Option<Box<dyn FnMut()>>, |
| 152 | + quit: Option<Box<dyn FnMut()>>, |
| 153 | + event: Option<Box<dyn FnMut(Event) -> bool>>, |
| 154 | + // menu_command: Option<Box<dyn FnMut(&dyn Action)>>, |
| 155 | + // validate_menu_command: Option<Box<dyn FnMut(&dyn Action) -> bool>>, |
| 156 | + will_open_menu: Option<Box<dyn FnMut()>>, |
| 157 | + open_urls: Option<Box<dyn FnMut(Vec<String>)>>, |
| 158 | + finish_launching: Option<Box<dyn FnOnce()>>, |
| 159 | + // menu_actions: Vec<Box<dyn Action>>, |
| 160 | +} |
| 161 | + |
| 162 | +impl MacPlatform { |
| 163 | + pub fn new() -> Self { |
| 164 | + Self(RefCell::new(MacPlatformState { |
| 165 | + executor: Rc::new(ForegroundExecutor::new(Arc::new(MacDispatcher)).unwrap()), |
| 166 | + text_system: Arc::new(MacTextSystem::new()), |
| 167 | + pasteboard: unsafe { NSPasteboard::generalPasteboard(nil) }, |
| 168 | + text_hash_pasteboard_type: unsafe { ns_string("zed-text-hash") }, |
| 169 | + metadata_pasteboard_type: unsafe { ns_string("zed-metadata") }, |
| 170 | + become_active: None, |
| 171 | + resign_active: None, |
| 172 | + reopen: None, |
| 173 | + quit: None, |
| 174 | + event: None, |
| 175 | + will_open_menu: None, |
| 176 | + open_urls: None, |
| 177 | + finish_launching: None, |
| 178 | + // menu_command: None, |
| 179 | + // validate_menu_command: None, |
| 180 | + // menu_actions: Default::default(), |
| 181 | + })) |
| 182 | + } |
| 183 | + |
| 184 | + unsafe fn read_from_pasteboard(&self, kind: id) -> Option<&[u8]> { |
| 185 | + let pasteboard = self.0.borrow().pasteboard; |
| 186 | + let data = pasteboard.dataForType(kind); |
| 187 | + if data == nil { |
| 188 | + None |
| 189 | + } else { |
| 190 | + Some(slice::from_raw_parts( |
| 191 | + data.bytes() as *mut u8, |
| 192 | + data.length() as usize, |
| 193 | + )) |
| 194 | + } |
| 195 | + } |
| 196 | + |
| 197 | + // unsafe fn create_menu_bar( |
| 198 | + // &self, |
| 199 | + // menus: Vec<Menu>, |
| 200 | + // delegate: id, |
| 201 | + // actions: &mut Vec<Box<dyn Action>>, |
| 202 | + // keystroke_matcher: &KeymapMatcher, |
| 203 | + // ) -> id { |
| 204 | + // let application_menu = NSMenu::new(nil).autorelease(); |
| 205 | + // application_menu.setDelegate_(delegate); |
| 206 | + |
| 207 | + // for menu_config in menus { |
| 208 | + // let menu = NSMenu::new(nil).autorelease(); |
| 209 | + // menu.setTitle_(ns_string(menu_config.name)); |
| 210 | + // menu.setDelegate_(delegate); |
| 211 | + |
| 212 | + // for item_config in menu_config.items { |
| 213 | + // menu.addItem_(self.create_menu_item( |
| 214 | + // item_config, |
| 215 | + // delegate, |
| 216 | + // actions, |
| 217 | + // keystroke_matcher, |
| 218 | + // )); |
| 219 | + // } |
| 220 | + |
| 221 | + // let menu_item = NSMenuItem::new(nil).autorelease(); |
| 222 | + // menu_item.setSubmenu_(menu); |
| 223 | + // application_menu.addItem_(menu_item); |
| 224 | + |
| 225 | + // if menu_config.name == "Window" { |
| 226 | + // let app: id = msg_send![APP_CLASS, sharedApplication]; |
| 227 | + // app.setWindowsMenu_(menu); |
| 228 | + // } |
| 229 | + // } |
| 230 | + |
| 231 | + // application_menu |
| 232 | + // } |
| 233 | + |
| 234 | + // unsafe fn create_menu_item( |
| 235 | + // &self, |
| 236 | + // item: MenuItem, |
| 237 | + // delegate: id, |
| 238 | + // actions: &mut Vec<Box<dyn Action>>, |
| 239 | + // keystroke_matcher: &KeymapMatcher, |
| 240 | + // ) -> id { |
| 241 | + // match item { |
| 242 | + // MenuItem::Separator => NSMenuItem::separatorItem(nil), |
| 243 | + // MenuItem::Action { |
| 244 | + // name, |
| 245 | + // action, |
| 246 | + // os_action, |
| 247 | + // } => { |
| 248 | + // // TODO |
| 249 | + // let keystrokes = keystroke_matcher |
| 250 | + // .bindings_for_action(action.id()) |
| 251 | + // .find(|binding| binding.action().eq(action.as_ref())) |
| 252 | + // .map(|binding| binding.keystrokes()); |
| 253 | + // let selector = match os_action { |
| 254 | + // Some(crate::OsAction::Cut) => selector("cut:"), |
| 255 | + // Some(crate::OsAction::Copy) => selector("copy:"), |
| 256 | + // Some(crate::OsAction::Paste) => selector("paste:"), |
| 257 | + // Some(crate::OsAction::SelectAll) => selector("selectAll:"), |
| 258 | + // Some(crate::OsAction::Undo) => selector("undo:"), |
| 259 | + // Some(crate::OsAction::Redo) => selector("redo:"), |
| 260 | + // None => selector("handleGPUIMenuItem:"), |
| 261 | + // }; |
| 262 | + |
| 263 | + // let item; |
| 264 | + // if let Some(keystrokes) = keystrokes { |
| 265 | + // if keystrokes.len() == 1 { |
| 266 | + // let keystroke = &keystrokes[0]; |
| 267 | + // let mut mask = NSEventModifierFlags::empty(); |
| 268 | + // for (modifier, flag) in &[ |
| 269 | + // (keystroke.cmd, NSEventModifierFlags::NSCommandKeyMask), |
| 270 | + // (keystroke.ctrl, NSEventModifierFlags::NSControlKeyMask), |
| 271 | + // (keystroke.alt, NSEventModifierFlags::NSAlternateKeyMask), |
| 272 | + // (keystroke.shift, NSEventModifierFlags::NSShiftKeyMask), |
| 273 | + // ] { |
| 274 | + // if *modifier { |
| 275 | + // mask |= *flag; |
| 276 | + // } |
| 277 | + // } |
| 278 | + |
| 279 | + // item = NSMenuItem::alloc(nil) |
| 280 | + // .initWithTitle_action_keyEquivalent_( |
| 281 | + // ns_string(name), |
| 282 | + // selector, |
| 283 | + // ns_string(key_to_native(&keystroke.key).as_ref()), |
| 284 | + // ) |
| 285 | + // .autorelease(); |
| 286 | + // item.setKeyEquivalentModifierMask_(mask); |
| 287 | + // } |
| 288 | + // // For multi-keystroke bindings, render the keystroke as part of the title. |
| 289 | + // else { |
| 290 | + // use std::fmt::Write; |
| 291 | + |
| 292 | + // let mut name = format!("{name} ["); |
| 293 | + // for (i, keystroke) in keystrokes.iter().enumerate() { |
| 294 | + // if i > 0 { |
| 295 | + // name.push(' '); |
| 296 | + // } |
| 297 | + // write!(&mut name, "{}", keystroke).unwrap(); |
| 298 | + // } |
| 299 | + // name.push(']'); |
| 300 | + |
| 301 | + // item = NSMenuItem::alloc(nil) |
| 302 | + // .initWithTitle_action_keyEquivalent_( |
| 303 | + // ns_string(&name), |
| 304 | + // selector, |
| 305 | + // ns_string(""), |
| 306 | + // ) |
| 307 | + // .autorelease(); |
| 308 | + // } |
| 309 | + // } else { |
| 310 | + // item = NSMenuItem::alloc(nil) |
| 311 | + // .initWithTitle_action_keyEquivalent_( |
| 312 | + // ns_string(name), |
| 313 | + // selector, |
| 314 | + // ns_string(""), |
| 315 | + // ) |
| 316 | + // .autorelease(); |
| 317 | + // } |
| 318 | + |
| 319 | + // let tag = actions.len() as NSInteger; |
| 320 | + // let _: () = msg_send![item, setTag: tag]; |
| 321 | + // actions.push(action); |
| 322 | + // item |
| 323 | + // } |
| 324 | + // MenuItem::Submenu(Menu { name, items }) => { |
| 325 | + // let item = NSMenuItem::new(nil).autorelease(); |
| 326 | + // let submenu = NSMenu::new(nil).autorelease(); |
| 327 | + // submenu.setDelegate_(delegate); |
| 328 | + // for item in items { |
| 329 | + // submenu.addItem_(self.create_menu_item( |
| 330 | + // item, |
| 331 | + // delegate, |
| 332 | + // actions, |
| 333 | + // keystroke_matcher, |
| 334 | + // )); |
| 335 | + // } |
| 336 | + // item.setSubmenu_(submenu); |
| 337 | + // item.setTitle_(ns_string(name)); |
| 338 | + // item |
| 339 | + // } |
| 340 | + // } |
| 341 | + // } |
| 342 | +} |
| 343 | + |
| 344 | +impl Platform for MacPlatform { |
| 345 | + fn executor(&self) -> Rc<ForegroundExecutor> { |
| 346 | + self.0.borrow().executor.clone() |
| 347 | + } |
| 348 | + |
| 349 | + fn text_system(&self) -> Arc<dyn PlatformTextSystem> { |
| 350 | + self.0.borrow().text_system.clone() |
| 351 | + } |
| 352 | + |
| 353 | + fn run(&self, on_finish_launching: Box<dyn FnOnce()>) { |
| 354 | + self.0.borrow_mut().finish_launching = Some(on_finish_launching); |
| 355 | + |
| 356 | + unsafe { |
| 357 | + let app: id = msg_send![APP_CLASS, sharedApplication]; |
| 358 | + let app_delegate: id = msg_send![APP_DELEGATE_CLASS, new]; |
| 359 | + app.setDelegate_(app_delegate); |
| 360 | + |
| 361 | + let self_ptr = self as *const Self as *const c_void; |
| 362 | + (*app).set_ivar(MAC_PLATFORM_IVAR, self_ptr); |
| 363 | + (*app_delegate).set_ivar(MAC_PLATFORM_IVAR, self_ptr); |
| 364 | + |
| 365 | + let pool = NSAutoreleasePool::new(nil); |
| 366 | + app.run(); |
| 367 | + pool.drain(); |
| 368 | + |
| 369 | + (*app).set_ivar(MAC_PLATFORM_IVAR, null_mut::<c_void>()); |
| 370 | + (*app.delegate()).set_ivar(MAC_PLATFORM_IVAR, null_mut::<c_void>()); |
| 371 | + } |
| 372 | + } |
| 373 | + |
| 374 | + fn quit(&self) { |
| 375 | + // Quitting the app causes us to close windows, which invokes `Window::on_close` callbacks |
| 376 | + // synchronously before this method terminates. If we call `Platform::quit` while holding a |
| 377 | + // borrow of the app state (which most of the time we will do), we will end up |
| 378 | + // double-borrowing the app state in the `on_close` callbacks for our open windows. To solve |
| 379 | + // this, we make quitting the application asynchronous so that we aren't holding borrows to |
| 380 | + // the app state on the stack when we actually terminate the app. |
| 381 | + |
| 382 | + use super::dispatcher::{dispatch_async_f, dispatch_get_main_queue}; |
| 383 | + |
| 384 | + unsafe { |
| 385 | + dispatch_async_f(dispatch_get_main_queue(), ptr::null_mut(), Some(quit)); |
| 386 | + } |
| 387 | + |
| 388 | + unsafe extern "C" fn quit(_: *mut c_void) { |
| 389 | + let app = NSApplication::sharedApplication(nil); |
| 390 | + let _: () = msg_send![app, terminate: nil]; |
| 391 | + } |
| 392 | + } |
| 393 | + |
| 394 | + fn restart(&self) { |
| 395 | + use std::os::unix::process::CommandExt as _; |
| 396 | + |
| 397 | + let app_pid = std::process::id().to_string(); |
| 398 | + let app_path = self |
| 399 | + .app_path() |
| 400 | + .ok() |
| 401 | + // When the app is not bundled, `app_path` returns the |
| 402 | + // directory containing the executable. Disregard this |
| 403 | + // and get the path to the executable itself. |
| 404 | + .and_then(|path| (path.extension()?.to_str()? == "app").then_some(path)) |
| 405 | + .unwrap_or_else(|| std::env::current_exe().unwrap()); |
| 406 | + |
| 407 | + // Wait until this process has exited and then re-open this path. |
| 408 | + let script = r#" |
| 409 | + while kill -0 $0 2> /dev/null; do |
| 410 | + sleep 0.1 |
| 411 | + done |
| 412 | + open "$1" |
| 413 | + "#; |
| 414 | + |
| 415 | + let restart_process = Command::new("/bin/bash") |
| 416 | + .arg("-c") |
| 417 | + .arg(script) |
| 418 | + .arg(app_pid) |
| 419 | + .arg(app_path) |
| 420 | + .process_group(0) |
| 421 | + .spawn(); |
| 422 | + |
| 423 | + match restart_process { |
| 424 | + Ok(_) => self.quit(), |
| 425 | + Err(e) => log::error!("failed to spawn restart script: {:?}", e), |
| 426 | + } |
| 427 | + } |
| 428 | + |
| 429 | + fn activate(&self, ignoring_other_apps: bool) { |
| 430 | + unsafe { |
| 431 | + let app = NSApplication::sharedApplication(nil); |
| 432 | + app.activateIgnoringOtherApps_(ignoring_other_apps.to_objc()); |
| 433 | + } |
| 434 | + } |
| 435 | + |
| 436 | + fn hide(&self) { |
| 437 | + unsafe { |
| 438 | + let app = NSApplication::sharedApplication(nil); |
| 439 | + let _: () = msg_send![app, hide: nil]; |
| 440 | + } |
| 441 | + } |
| 442 | + |
| 443 | + fn hide_other_apps(&self) { |
| 444 | + unsafe { |
| 445 | + let app = NSApplication::sharedApplication(nil); |
| 446 | + let _: () = msg_send![app, hideOtherApplications: nil]; |
| 447 | + } |
| 448 | + } |
| 449 | + |
| 450 | + fn unhide_other_apps(&self) { |
| 451 | + unsafe { |
| 452 | + let app = NSApplication::sharedApplication(nil); |
| 453 | + let _: () = msg_send![app, unhideAllApplications: nil]; |
| 454 | + } |
| 455 | + } |
| 456 | + |
| 457 | + fn screens(&self) -> Vec<Rc<dyn PlatformScreen>> { |
| 458 | + MacScreen::all() |
| 459 | + .into_iter() |
| 460 | + .map(|screen| Rc::new(screen) as Rc<_>) |
| 461 | + .collect() |
| 462 | + } |
| 463 | + |
| 464 | + fn screen_by_id(&self, id: uuid::Uuid) -> Option<Rc<dyn PlatformScreen>> { |
| 465 | + MacScreen::find_by_id(id).map(|screen| Rc::new(screen) as Rc<_>) |
| 466 | + } |
| 467 | + |
| 468 | + fn main_window(&self) -> Option<AnyWindowHandle> { |
| 469 | + MacWindow::main_window() |
| 470 | + } |
| 471 | + |
| 472 | + // fn add_status_item(&self, _handle: AnyWindowHandle) -> Box<dyn platform::Window> { |
| 473 | + // Box::new(StatusItem::add(self.fonts())) |
| 474 | + // } |
| 475 | + |
| 476 | + fn open_window( |
| 477 | + &self, |
| 478 | + handle: AnyWindowHandle, |
| 479 | + options: WindowOptions, |
| 480 | + ) -> Box<dyn PlatformWindow> { |
| 481 | + Box::new(MacWindow::open( |
| 482 | + handle, |
| 483 | + options, |
| 484 | + self.executor(), |
| 485 | + self.text_system(), |
| 486 | + )) |
| 487 | + } |
| 488 | + |
| 489 | + fn open_url(&self, url: &str) { |
| 490 | + unsafe { |
| 491 | + let url = NSURL::alloc(nil) |
| 492 | + .initWithString_(ns_string(url)) |
| 493 | + .autorelease(); |
| 494 | + let workspace: id = msg_send![class!(NSWorkspace), sharedWorkspace]; |
| 495 | + msg_send![workspace, openURL: url] |
| 496 | + } |
| 497 | + } |
| 498 | + |
| 499 | + fn on_open_urls(&self, callback: Box<dyn FnMut(Vec<String>)>) { |
| 500 | + self.0.borrow_mut().open_urls = Some(callback); |
| 501 | + } |
| 502 | + |
| 503 | + fn prompt_for_paths( |
| 504 | + &self, |
| 505 | + options: PathPromptOptions, |
| 506 | + ) -> oneshot::Receiver<Option<Vec<PathBuf>>> { |
| 507 | + unsafe { |
| 508 | + let panel = NSOpenPanel::openPanel(nil); |
| 509 | + panel.setCanChooseDirectories_(options.directories.to_objc()); |
| 510 | + panel.setCanChooseFiles_(options.files.to_objc()); |
| 511 | + panel.setAllowsMultipleSelection_(options.multiple.to_objc()); |
| 512 | + panel.setResolvesAliases_(false.to_objc()); |
| 513 | + let (done_tx, done_rx) = oneshot::channel(); |
| 514 | + let done_tx = Cell::new(Some(done_tx)); |
| 515 | + let block = ConcreteBlock::new(move |response: NSModalResponse| { |
| 516 | + let result = if response == NSModalResponse::NSModalResponseOk { |
| 517 | + let mut result = Vec::new(); |
| 518 | + let urls = panel.URLs(); |
| 519 | + for i in 0..urls.count() { |
| 520 | + let url = urls.objectAtIndex(i); |
| 521 | + if url.isFileURL() == YES { |
| 522 | + if let Ok(path) = ns_url_to_path(url) { |
| 523 | + result.push(path) |
| 524 | + } |
| 525 | + } |
| 526 | + } |
| 527 | + Some(result) |
| 528 | + } else { |
| 529 | + None |
| 530 | + }; |
| 531 | + |
| 532 | + if let Some(mut done_tx) = done_tx.take() { |
| 533 | + let _ = done_tx.send(result); |
| 534 | + } |
| 535 | + }); |
| 536 | + let block = block.copy(); |
| 537 | + let _: () = msg_send![panel, beginWithCompletionHandler: block]; |
| 538 | + done_rx |
| 539 | + } |
| 540 | + } |
| 541 | + |
| 542 | + fn prompt_for_new_path(&self, directory: &Path) -> oneshot::Receiver<Option<PathBuf>> { |
| 543 | + unsafe { |
| 544 | + let panel = NSSavePanel::savePanel(nil); |
| 545 | + let path = ns_string(directory.to_string_lossy().as_ref()); |
| 546 | + let url = NSURL::fileURLWithPath_isDirectory_(nil, path, true.to_objc()); |
| 547 | + panel.setDirectoryURL(url); |
| 548 | + |
| 549 | + let (done_tx, done_rx) = oneshot::channel(); |
| 550 | + let done_tx = Cell::new(Some(done_tx)); |
| 551 | + let block = ConcreteBlock::new(move |response: NSModalResponse| { |
| 552 | + let mut result = None; |
| 553 | + if response == NSModalResponse::NSModalResponseOk { |
| 554 | + let url = panel.URL(); |
| 555 | + if url.isFileURL() == YES { |
| 556 | + result = ns_url_to_path(panel.URL()).ok() |
| 557 | + } |
| 558 | + } |
| 559 | + |
| 560 | + if let Some(mut done_tx) = done_tx.take() { |
| 561 | + let _ = done_tx.send(result); |
| 562 | + } |
| 563 | + }); |
| 564 | + let block = block.copy(); |
| 565 | + let _: () = msg_send![panel, beginWithCompletionHandler: block]; |
| 566 | + done_rx |
| 567 | + } |
| 568 | + } |
| 569 | + |
| 570 | + fn reveal_path(&self, path: &Path) { |
| 571 | + unsafe { |
| 572 | + let path = path.to_path_buf(); |
| 573 | + self.0 |
| 574 | + .borrow() |
| 575 | + .executor |
| 576 | + .spawn(async move { |
| 577 | + let full_path = ns_string(path.to_str().unwrap_or("")); |
| 578 | + let root_full_path = ns_string(""); |
| 579 | + let workspace: id = msg_send![class!(NSWorkspace), sharedWorkspace]; |
| 580 | + let _: BOOL = msg_send![ |
| 581 | + workspace, |
| 582 | + selectFile: full_path |
| 583 | + inFileViewerRootedAtPath: root_full_path |
| 584 | + ]; |
| 585 | + }) |
| 586 | + .detach(); |
| 587 | + } |
| 588 | + } |
| 589 | + |
| 590 | + fn on_become_active(&self, callback: Box<dyn FnMut()>) { |
| 591 | + self.0.borrow_mut().become_active = Some(callback); |
| 592 | + } |
| 593 | + |
| 594 | + fn on_resign_active(&self, callback: Box<dyn FnMut()>) { |
| 595 | + self.0.borrow_mut().resign_active = Some(callback); |
| 596 | + } |
| 597 | + |
| 598 | + fn on_quit(&self, callback: Box<dyn FnMut()>) { |
| 599 | + self.0.borrow_mut().quit = Some(callback); |
| 600 | + } |
| 601 | + |
| 602 | + fn on_reopen(&self, callback: Box<dyn FnMut()>) { |
| 603 | + self.0.borrow_mut().reopen = Some(callback); |
| 604 | + } |
| 605 | + |
| 606 | + fn on_event(&self, callback: Box<dyn FnMut(Event) -> bool>) { |
| 607 | + self.0.borrow_mut().event = Some(callback); |
| 608 | + } |
| 609 | + |
| 610 | + fn os_name(&self) -> &'static str { |
| 611 | + "macOS" |
| 612 | + } |
| 613 | + |
| 614 | + fn os_version(&self) -> Result<SemanticVersion> { |
| 615 | + unsafe { |
| 616 | + let process_info = NSProcessInfo::processInfo(nil); |
| 617 | + let version = process_info.operatingSystemVersion(); |
| 618 | + Ok(SemanticVersion { |
| 619 | + major: version.majorVersion as usize, |
| 620 | + minor: version.minorVersion as usize, |
| 621 | + patch: version.patchVersion as usize, |
| 622 | + }) |
| 623 | + } |
| 624 | + } |
| 625 | + |
| 626 | + fn app_version(&self) -> Result<SemanticVersion> { |
| 627 | + unsafe { |
| 628 | + let bundle: id = NSBundle::mainBundle(); |
| 629 | + if bundle.is_null() { |
| 630 | + Err(anyhow!("app is not running inside a bundle")) |
| 631 | + } else { |
| 632 | + let version: id = msg_send![bundle, objectForInfoDictionaryKey: ns_string("CFBundleShortVersionString")]; |
| 633 | + let len = msg_send![version, lengthOfBytesUsingEncoding: NSUTF8StringEncoding]; |
| 634 | + let bytes = version.UTF8String() as *const u8; |
| 635 | + let version = str::from_utf8(slice::from_raw_parts(bytes, len)).unwrap(); |
| 636 | + version.parse() |
| 637 | + } |
| 638 | + } |
| 639 | + } |
| 640 | + |
| 641 | + fn app_path(&self) -> Result<PathBuf> { |
| 642 | + unsafe { |
| 643 | + let bundle: id = NSBundle::mainBundle(); |
| 644 | + if bundle.is_null() { |
| 645 | + Err(anyhow!("app is not running inside a bundle")) |
| 646 | + } else { |
| 647 | + Ok(path_from_objc(msg_send![bundle, bundlePath])) |
| 648 | + } |
| 649 | + } |
| 650 | + } |
| 651 | + |
| 652 | + fn local_timezone(&self) -> UtcOffset { |
| 653 | + unsafe { |
| 654 | + let local_timezone: id = msg_send![class!(NSTimeZone), localTimeZone]; |
| 655 | + let seconds_from_gmt: NSInteger = msg_send![local_timezone, secondsFromGMT]; |
| 656 | + UtcOffset::from_whole_seconds(seconds_from_gmt.try_into().unwrap()).unwrap() |
| 657 | + } |
| 658 | + } |
| 659 | + |
| 660 | + fn path_for_auxiliary_executable(&self, name: &str) -> Result<PathBuf> { |
| 661 | + unsafe { |
| 662 | + let bundle: id = NSBundle::mainBundle(); |
| 663 | + if bundle.is_null() { |
| 664 | + Err(anyhow!("app is not running inside a bundle")) |
| 665 | + } else { |
| 666 | + let name = ns_string(name); |
| 667 | + let url: id = msg_send![bundle, URLForAuxiliaryExecutable: name]; |
| 668 | + if url.is_null() { |
| 669 | + Err(anyhow!("resource not found")) |
| 670 | + } else { |
| 671 | + ns_url_to_path(url) |
| 672 | + } |
| 673 | + } |
| 674 | + } |
| 675 | + } |
| 676 | + |
| 677 | + fn set_cursor_style(&self, style: CursorStyle) { |
| 678 | + unsafe { |
| 679 | + let new_cursor: id = match style { |
| 680 | + CursorStyle::Arrow => msg_send![class!(NSCursor), arrowCursor], |
| 681 | + CursorStyle::ResizeLeftRight => { |
| 682 | + msg_send![class!(NSCursor), resizeLeftRightCursor] |
| 683 | + } |
| 684 | + CursorStyle::ResizeUpDown => msg_send![class!(NSCursor), resizeUpDownCursor], |
| 685 | + CursorStyle::PointingHand => msg_send![class!(NSCursor), pointingHandCursor], |
| 686 | + CursorStyle::IBeam => msg_send![class!(NSCursor), IBeamCursor], |
| 687 | + }; |
| 688 | + |
| 689 | + let old_cursor: id = msg_send![class!(NSCursor), currentCursor]; |
| 690 | + if new_cursor != old_cursor { |
| 691 | + let _: () = msg_send![new_cursor, set]; |
| 692 | + } |
| 693 | + } |
| 694 | + } |
| 695 | + |
| 696 | + fn should_auto_hide_scrollbars(&self) -> bool { |
| 697 | + #[allow(non_upper_case_globals)] |
| 698 | + const NSScrollerStyleOverlay: NSInteger = 1; |
| 699 | + |
| 700 | + unsafe { |
| 701 | + let style: NSInteger = msg_send![class!(NSScroller), preferredScrollerStyle]; |
| 702 | + style == NSScrollerStyleOverlay |
| 703 | + } |
| 704 | + } |
| 705 | + |
| 706 | + fn write_to_clipboard(&self, item: ClipboardItem) { |
| 707 | + let state = self.0.borrow(); |
| 708 | + unsafe { |
| 709 | + state.pasteboard.clearContents(); |
| 710 | + |
| 711 | + let text_bytes = NSData::dataWithBytes_length_( |
| 712 | + nil, |
| 713 | + item.text.as_ptr() as *const c_void, |
| 714 | + item.text.len() as u64, |
| 715 | + ); |
| 716 | + state |
| 717 | + .pasteboard |
| 718 | + .setData_forType(text_bytes, NSPasteboardTypeString); |
| 719 | + |
| 720 | + if let Some(metadata) = item.metadata.as_ref() { |
| 721 | + let hash_bytes = ClipboardItem::text_hash(&item.text).to_be_bytes(); |
| 722 | + let hash_bytes = NSData::dataWithBytes_length_( |
| 723 | + nil, |
| 724 | + hash_bytes.as_ptr() as *const c_void, |
| 725 | + hash_bytes.len() as u64, |
| 726 | + ); |
| 727 | + state |
| 728 | + .pasteboard |
| 729 | + .setData_forType(hash_bytes, state.text_hash_pasteboard_type); |
| 730 | + |
| 731 | + let metadata_bytes = NSData::dataWithBytes_length_( |
| 732 | + nil, |
| 733 | + metadata.as_ptr() as *const c_void, |
| 734 | + metadata.len() as u64, |
| 735 | + ); |
| 736 | + state |
| 737 | + .pasteboard |
| 738 | + .setData_forType(metadata_bytes, state.metadata_pasteboard_type); |
| 739 | + } |
| 740 | + } |
| 741 | + } |
| 742 | + |
| 743 | + fn read_from_clipboard(&self) -> Option<ClipboardItem> { |
| 744 | + let state = self.0.borrow(); |
| 745 | + unsafe { |
| 746 | + if let Some(text_bytes) = self.read_from_pasteboard(NSPasteboardTypeString) { |
| 747 | + let text = String::from_utf8_lossy(text_bytes).to_string(); |
| 748 | + let hash_bytes = self |
| 749 | + .read_from_pasteboard(state.text_hash_pasteboard_type) |
| 750 | + .and_then(|bytes| bytes.try_into().ok()) |
| 751 | + .map(u64::from_be_bytes); |
| 752 | + let metadata_bytes = self |
| 753 | + .read_from_pasteboard(state.metadata_pasteboard_type) |
| 754 | + .and_then(|bytes| String::from_utf8(bytes.to_vec()).ok()); |
| 755 | + |
| 756 | + if let Some((hash, metadata)) = hash_bytes.zip(metadata_bytes) { |
| 757 | + if hash == ClipboardItem::text_hash(&text) { |
| 758 | + Some(ClipboardItem { |
| 759 | + text, |
| 760 | + metadata: Some(metadata), |
| 761 | + }) |
| 762 | + } else { |
| 763 | + Some(ClipboardItem { |
| 764 | + text, |
| 765 | + metadata: None, |
| 766 | + }) |
| 767 | + } |
| 768 | + } else { |
| 769 | + Some(ClipboardItem { |
| 770 | + text, |
| 771 | + metadata: None, |
| 772 | + }) |
| 773 | + } |
| 774 | + } else { |
| 775 | + None |
| 776 | + } |
| 777 | + } |
| 778 | + } |
| 779 | + |
| 780 | + fn write_credentials(&self, url: &str, username: &str, password: &[u8]) -> Result<()> { |
| 781 | + let url = CFString::from(url); |
| 782 | + let username = CFString::from(username); |
| 783 | + let password = CFData::from_buffer(password); |
| 784 | + |
| 785 | + unsafe { |
| 786 | + use security::*; |
| 787 | + |
| 788 | + // First, check if there are already credentials for the given server. If so, then |
| 789 | + // update the username and password. |
| 790 | + let mut verb = "updating"; |
| 791 | + let mut query_attrs = CFMutableDictionary::with_capacity(2); |
| 792 | + query_attrs.set(kSecClass as *const _, kSecClassInternetPassword as *const _); |
| 793 | + query_attrs.set(kSecAttrServer as *const _, url.as_CFTypeRef()); |
| 794 | + |
| 795 | + let mut attrs = CFMutableDictionary::with_capacity(4); |
| 796 | + attrs.set(kSecClass as *const _, kSecClassInternetPassword as *const _); |
| 797 | + attrs.set(kSecAttrServer as *const _, url.as_CFTypeRef()); |
| 798 | + attrs.set(kSecAttrAccount as *const _, username.as_CFTypeRef()); |
| 799 | + attrs.set(kSecValueData as *const _, password.as_CFTypeRef()); |
| 800 | + |
| 801 | + let mut status = SecItemUpdate( |
| 802 | + query_attrs.as_concrete_TypeRef(), |
| 803 | + attrs.as_concrete_TypeRef(), |
| 804 | + ); |
| 805 | + |
| 806 | + // If there were no existing credentials for the given server, then create them. |
| 807 | + if status == errSecItemNotFound { |
| 808 | + verb = "creating"; |
| 809 | + status = SecItemAdd(attrs.as_concrete_TypeRef(), ptr::null_mut()); |
| 810 | + } |
| 811 | + |
| 812 | + if status != errSecSuccess { |
| 813 | + return Err(anyhow!("{} password failed: {}", verb, status)); |
| 814 | + } |
| 815 | + } |
| 816 | + Ok(()) |
| 817 | + } |
| 818 | + |
| 819 | + // fn on_menu_command(&self, callback: Box<dyn FnMut(&dyn Action)>) { |
| 820 | + // self.0.borrow_mut().menu_command = Some(callback); |
| 821 | + // } |
| 822 | + |
| 823 | + // fn on_will_open_menu(&self, callback: Box<dyn FnMut()>) { |
| 824 | + // self.0.borrow_mut().will_open_menu = Some(callback); |
| 825 | + // } |
| 826 | + |
| 827 | + // fn on_validate_menu_command(&self, callback: Box<dyn FnMut(&dyn Action) -> bool>) { |
| 828 | + // self.0.borrow_mut().validate_menu_command = Some(callback); |
| 829 | + // } |
| 830 | + |
| 831 | + // fn set_menus(&self, menus: Vec<Menu>, keystroke_matcher: &KeymapMatcher) { |
| 832 | + // unsafe { |
| 833 | + // let app: id = msg_send![APP_CLASS, sharedApplication]; |
| 834 | + // let mut state = self.0.borrow_mut(); |
| 835 | + // let actions = &mut state.menu_actions; |
| 836 | + // app.setMainMenu_(self.create_menu_bar( |
| 837 | + // menus, |
| 838 | + // app.delegate(), |
| 839 | + // actions, |
| 840 | + // keystroke_matcher, |
| 841 | + // )); |
| 842 | + // } |
| 843 | + // } |
| 844 | + |
| 845 | + fn read_credentials(&self, url: &str) -> Result<Option<(String, Vec<u8>)>> { |
| 846 | + let url = CFString::from(url); |
| 847 | + let cf_true = CFBoolean::true_value().as_CFTypeRef(); |
| 848 | + |
| 849 | + unsafe { |
| 850 | + use security::*; |
| 851 | + |
| 852 | + // Find any credentials for the given server URL. |
| 853 | + let mut attrs = CFMutableDictionary::with_capacity(5); |
| 854 | + attrs.set(kSecClass as *const _, kSecClassInternetPassword as *const _); |
| 855 | + attrs.set(kSecAttrServer as *const _, url.as_CFTypeRef()); |
| 856 | + attrs.set(kSecReturnAttributes as *const _, cf_true); |
| 857 | + attrs.set(kSecReturnData as *const _, cf_true); |
| 858 | + |
| 859 | + let mut result = CFTypeRef::from(ptr::null()); |
| 860 | + let status = SecItemCopyMatching(attrs.as_concrete_TypeRef(), &mut result); |
| 861 | + match status { |
| 862 | + security::errSecSuccess => {} |
| 863 | + security::errSecItemNotFound | security::errSecUserCanceled => return Ok(None), |
| 864 | + _ => return Err(anyhow!("reading password failed: {}", status)), |
| 865 | + } |
| 866 | + |
| 867 | + let result = CFType::wrap_under_create_rule(result) |
| 868 | + .downcast::<CFDictionary>() |
| 869 | + .ok_or_else(|| anyhow!("keychain item was not a dictionary"))?; |
| 870 | + let username = result |
| 871 | + .find(kSecAttrAccount as *const _) |
| 872 | + .ok_or_else(|| anyhow!("account was missing from keychain item"))?; |
| 873 | + let username = CFType::wrap_under_get_rule(*username) |
| 874 | + .downcast::<CFString>() |
| 875 | + .ok_or_else(|| anyhow!("account was not a string"))?; |
| 876 | + let password = result |
| 877 | + .find(kSecValueData as *const _) |
| 878 | + .ok_or_else(|| anyhow!("password was missing from keychain item"))?; |
| 879 | + let password = CFType::wrap_under_get_rule(*password) |
| 880 | + .downcast::<CFData>() |
| 881 | + .ok_or_else(|| anyhow!("password was not a string"))?; |
| 882 | + |
| 883 | + Ok(Some((username.to_string(), password.bytes().to_vec()))) |
| 884 | + } |
| 885 | + } |
| 886 | + |
| 887 | + fn delete_credentials(&self, url: &str) -> Result<()> { |
| 888 | + let url = CFString::from(url); |
| 889 | + |
| 890 | + unsafe { |
| 891 | + use security::*; |
| 892 | + |
| 893 | + let mut query_attrs = CFMutableDictionary::with_capacity(2); |
| 894 | + query_attrs.set(kSecClass as *const _, kSecClassInternetPassword as *const _); |
| 895 | + query_attrs.set(kSecAttrServer as *const _, url.as_CFTypeRef()); |
| 896 | + |
| 897 | + let status = SecItemDelete(query_attrs.as_concrete_TypeRef()); |
| 898 | + |
| 899 | + if status != errSecSuccess { |
| 900 | + return Err(anyhow!("delete password failed: {}", status)); |
| 901 | + } |
| 902 | + } |
| 903 | + Ok(()) |
| 904 | + } |
| 905 | +} |
| 906 | + |
| 907 | +unsafe fn path_from_objc(path: id) -> PathBuf { |
| 908 | + let len = msg_send![path, lengthOfBytesUsingEncoding: NSUTF8StringEncoding]; |
| 909 | + let bytes = path.UTF8String() as *const u8; |
| 910 | + let path = str::from_utf8(slice::from_raw_parts(bytes, len)).unwrap(); |
| 911 | + PathBuf::from(path) |
| 912 | +} |
| 913 | + |
| 914 | +unsafe fn get_foreground_platform(object: &mut Object) -> &MacPlatform { |
| 915 | + let platform_ptr: *mut c_void = *object.get_ivar(MAC_PLATFORM_IVAR); |
| 916 | + assert!(!platform_ptr.is_null()); |
| 917 | + &*(platform_ptr as *const MacPlatform) |
| 918 | +} |
| 919 | + |
| 920 | +extern "C" fn send_event(this: &mut Object, _sel: Sel, native_event: id) { |
| 921 | + unsafe { |
| 922 | + if let Some(event) = Event::from_native(native_event, None) { |
| 923 | + let platform = get_foreground_platform(this); |
| 924 | + if let Some(callback) = platform.0.borrow_mut().event.as_mut() { |
| 925 | + if callback(event) { |
| 926 | + return; |
| 927 | + } |
| 928 | + } |
| 929 | + } |
| 930 | + msg_send![super(this, class!(NSApplication)), sendEvent: native_event] |
| 931 | + } |
| 932 | +} |
| 933 | + |
| 934 | +extern "C" fn did_finish_launching(this: &mut Object, _: Sel, _: id) { |
| 935 | + unsafe { |
| 936 | + let app: id = msg_send![APP_CLASS, sharedApplication]; |
| 937 | + app.setActivationPolicy_(NSApplicationActivationPolicyRegular); |
| 938 | + |
| 939 | + let platform = get_foreground_platform(this); |
| 940 | + let callback = platform.0.borrow_mut().finish_launching.take(); |
| 941 | + if let Some(callback) = callback { |
| 942 | + callback(); |
| 943 | + } |
| 944 | + } |
| 945 | +} |
| 946 | + |
| 947 | +extern "C" fn should_handle_reopen(this: &mut Object, _: Sel, _: id, has_open_windows: bool) { |
| 948 | + if !has_open_windows { |
| 949 | + let platform = unsafe { get_foreground_platform(this) }; |
| 950 | + if let Some(callback) = platform.0.borrow_mut().reopen.as_mut() { |
| 951 | + callback(); |
| 952 | + } |
| 953 | + } |
| 954 | +} |
| 955 | + |
| 956 | +extern "C" fn did_become_active(this: &mut Object, _: Sel, _: id) { |
| 957 | + let platform = unsafe { get_foreground_platform(this) }; |
| 958 | + if let Some(callback) = platform.0.borrow_mut().become_active.as_mut() { |
| 959 | + callback(); |
| 960 | + } |
| 961 | +} |
| 962 | + |
| 963 | +extern "C" fn did_resign_active(this: &mut Object, _: Sel, _: id) { |
| 964 | + let platform = unsafe { get_foreground_platform(this) }; |
| 965 | + if let Some(callback) = platform.0.borrow_mut().resign_active.as_mut() { |
| 966 | + callback(); |
| 967 | + } |
| 968 | +} |
| 969 | + |
| 970 | +extern "C" fn will_terminate(this: &mut Object, _: Sel, _: id) { |
| 971 | + let platform = unsafe { get_foreground_platform(this) }; |
| 972 | + if let Some(callback) = platform.0.borrow_mut().quit.as_mut() { |
| 973 | + callback(); |
| 974 | + } |
| 975 | +} |
| 976 | + |
| 977 | +extern "C" fn open_urls(this: &mut Object, _: Sel, _: id, urls: id) { |
| 978 | + let urls = unsafe { |
| 979 | + (0..urls.count()) |
| 980 | + .into_iter() |
| 981 | + .filter_map(|i| { |
| 982 | + let url = urls.objectAtIndex(i); |
| 983 | + match CStr::from_ptr(url.absoluteString().UTF8String() as *mut c_char).to_str() { |
| 984 | + Ok(string) => Some(string.to_string()), |
| 985 | + Err(err) => { |
| 986 | + log::error!("error converting path to string: {}", err); |
| 987 | + None |
| 988 | + } |
| 989 | + } |
| 990 | + }) |
| 991 | + .collect::<Vec<_>>() |
| 992 | + }; |
| 993 | + let platform = unsafe { get_foreground_platform(this) }; |
| 994 | + if let Some(callback) = platform.0.borrow_mut().open_urls.as_mut() { |
| 995 | + callback(urls); |
| 996 | + } |
| 997 | +} |
| 998 | + |
| 999 | +extern "C" fn handle_menu_item(this: &mut Object, _: Sel, item: id) { |
| 1000 | + todo!() |
| 1001 | + // unsafe { |
| 1002 | + // let platform = get_foreground_platform(this); |
| 1003 | + // let mut platform = platform.0.borrow_mut(); |
| 1004 | + // if let Some(mut callback) = platform.menu_command.take() { |
| 1005 | + // let tag: NSInteger = msg_send![item, tag]; |
| 1006 | + // let index = tag as usize; |
| 1007 | + // if let Some(action) = platform.menu_actions.get(index) { |
| 1008 | + // callback(action.as_ref()); |
| 1009 | + // } |
| 1010 | + // platform.menu_command = Some(callback); |
| 1011 | + // } |
| 1012 | + // } |
| 1013 | +} |
| 1014 | + |
| 1015 | +extern "C" fn validate_menu_item(this: &mut Object, _: Sel, item: id) -> bool { |
| 1016 | + todo!() |
| 1017 | + // unsafe { |
| 1018 | + // let mut result = false; |
| 1019 | + // let platform = get_foreground_platform(this); |
| 1020 | + // let mut platform = platform.0.borrow_mut(); |
| 1021 | + // if let Some(mut callback) = platform.validate_menu_command.take() { |
| 1022 | + // let tag: NSInteger = msg_send![item, tag]; |
| 1023 | + // let index = tag as usize; |
| 1024 | + // if let Some(action) = platform.menu_actions.get(index) { |
| 1025 | + // result = callback(action.as_ref()); |
| 1026 | + // } |
| 1027 | + // platform.validate_menu_command = Some(callback); |
| 1028 | + // } |
| 1029 | + // result |
| 1030 | + // } |
| 1031 | +} |
| 1032 | + |
| 1033 | +extern "C" fn menu_will_open(this: &mut Object, _: Sel, _: id) { |
| 1034 | + unsafe { |
| 1035 | + let platform = get_foreground_platform(this); |
| 1036 | + let mut platform = platform.0.borrow_mut(); |
| 1037 | + if let Some(mut callback) = platform.will_open_menu.take() { |
| 1038 | + callback(); |
| 1039 | + platform.will_open_menu = Some(callback); |
| 1040 | + } |
| 1041 | + } |
| 1042 | +} |
| 1043 | + |
| 1044 | +unsafe fn ns_string(string: &str) -> id { |
| 1045 | + NSString::alloc(nil).init_str(string).autorelease() |
| 1046 | +} |
| 1047 | + |
| 1048 | +unsafe fn ns_url_to_path(url: id) -> Result<PathBuf> { |
| 1049 | + let path: *mut c_char = msg_send![url, fileSystemRepresentation]; |
| 1050 | + if path.is_null() { |
| 1051 | + Err(anyhow!( |
| 1052 | + "url is not a file path: {}", |
| 1053 | + CStr::from_ptr(url.absoluteString().UTF8String()).to_string_lossy() |
| 1054 | + )) |
| 1055 | + } else { |
| 1056 | + Ok(PathBuf::from(OsStr::from_bytes( |
| 1057 | + CStr::from_ptr(path).to_bytes(), |
| 1058 | + ))) |
| 1059 | + } |
| 1060 | +} |
| 1061 | + |
| 1062 | +mod security { |
| 1063 | + #![allow(non_upper_case_globals)] |
| 1064 | + use super::*; |
| 1065 | + |
| 1066 | + #[link(name = "Security", kind = "framework")] |
| 1067 | + extern "C" { |
| 1068 | + pub static kSecClass: CFStringRef; |
| 1069 | + pub static kSecClassInternetPassword: CFStringRef; |
| 1070 | + pub static kSecAttrServer: CFStringRef; |
| 1071 | + pub static kSecAttrAccount: CFStringRef; |
| 1072 | + pub static kSecValueData: CFStringRef; |
| 1073 | + pub static kSecReturnAttributes: CFStringRef; |
| 1074 | + pub static kSecReturnData: CFStringRef; |
| 1075 | + |
| 1076 | + pub fn SecItemAdd(attributes: CFDictionaryRef, result: *mut CFTypeRef) -> OSStatus; |
| 1077 | + pub fn SecItemUpdate(query: CFDictionaryRef, attributes: CFDictionaryRef) -> OSStatus; |
| 1078 | + pub fn SecItemDelete(query: CFDictionaryRef) -> OSStatus; |
| 1079 | + pub fn SecItemCopyMatching(query: CFDictionaryRef, result: *mut CFTypeRef) -> OSStatus; |
| 1080 | + } |
| 1081 | + |
| 1082 | + pub const errSecSuccess: OSStatus = 0; |
| 1083 | + pub const errSecUserCanceled: OSStatus = -128; |
| 1084 | + pub const errSecItemNotFound: OSStatus = -25300; |
| 1085 | +} |
| 1086 | + |
| 1087 | +#[cfg(test)] |
| 1088 | +mod tests { |
| 1089 | + use crate::ClipboardItem; |
| 1090 | + |
| 1091 | + use super::*; |
| 1092 | + |
| 1093 | + #[test] |
| 1094 | + fn test_clipboard() { |
| 1095 | + let platform = build_platform(); |
| 1096 | + assert_eq!(platform.read_from_clipboard(), None); |
| 1097 | + |
| 1098 | + let item = ClipboardItem::new("1".to_string()); |
| 1099 | + platform.write_to_clipboard(item.clone()); |
| 1100 | + assert_eq!(platform.read_from_clipboard(), Some(item)); |
| 1101 | + |
| 1102 | + let item = ClipboardItem::new("2".to_string()).with_metadata(vec![3, 4]); |
| 1103 | + platform.write_to_clipboard(item.clone()); |
| 1104 | + assert_eq!(platform.read_from_clipboard(), Some(item)); |
| 1105 | + |
| 1106 | + let text_from_other_app = "text from other app"; |
| 1107 | + unsafe { |
| 1108 | + let bytes = NSData::dataWithBytes_length_( |
| 1109 | + nil, |
| 1110 | + text_from_other_app.as_ptr() as *const c_void, |
| 1111 | + text_from_other_app.len() as u64, |
| 1112 | + ); |
| 1113 | + platform |
| 1114 | + .0 |
| 1115 | + .borrow_mut() |
| 1116 | + .pasteboard |
| 1117 | + .setData_forType(bytes, NSPasteboardTypeString); |
| 1118 | + } |
| 1119 | + assert_eq!( |
| 1120 | + platform.read_from_clipboard(), |
| 1121 | + Some(ClipboardItem::new(text_from_other_app.to_string())) |
| 1122 | + ); |
| 1123 | + } |
| 1124 | + |
| 1125 | + fn build_platform() -> MacPlatform { |
| 1126 | + let mut platform = MacPlatform::new(); |
| 1127 | + platform.0.borrow_mut().pasteboard = unsafe { NSPasteboard::pasteboardWithUniqueName(nil) }; |
| 1128 | + platform |
| 1129 | + } |
| 1130 | +} |