platform.rs

   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// }