use super::{ BoolExt, MacKeyboardLayout, attributed_string::{NSAttributedString, NSMutableAttributedString}, events::key_to_native, is_macos_version_at_least, renderer, screen_capture,};use crate::{ Action, AnyWindowHandle, BackgroundExecutor, ClipboardEntry, ClipboardItem, ClipboardString, CursorStyle, ForegroundExecutor, Image, ImageFormat, KeyContext, Keymap, MacDispatcher, MacDisplay, MacWindow, Menu, MenuItem, PathPromptOptions, Platform, PlatformDisplay, PlatformKeyboardLayout, PlatformTextSystem, PlatformWindow, Result, ScreenCaptureSource, SemanticVersion, Task, WindowAppearance, WindowParams, hash,};use anyhow::{Context as _, anyhow};use block::ConcreteBlock;use cocoa::{ appkit::{ NSApplication, NSApplicationActivationPolicy::NSApplicationActivationPolicyRegular, NSEventModifierFlags, NSMenu, NSMenuItem, NSModalResponse, NSOpenPanel, NSPasteboard, NSPasteboardTypePNG, NSPasteboardTypeRTF, NSPasteboardTypeRTFD, NSPasteboardTypeString, NSPasteboardTypeTIFF, NSSavePanel, NSWindow, }, base::{BOOL, NO, YES, id, nil, selector}, foundation::{ NSArray, NSAutoreleasePool, NSBundle, NSData, NSInteger, NSOperatingSystemVersion, NSProcessInfo, NSRange, NSString, NSUInteger, NSURL, },};use core_foundation::{ base::{CFRelease, CFType, CFTypeRef, OSStatus, TCFType}, boolean::CFBoolean, data::CFData, dictionary::{CFDictionary, CFDictionaryRef, CFMutableDictionary}, runloop::CFRunLoopRun, string::{CFString, CFStringRef},};use ctor::ctor;use futures::channel::oneshot;use itertools::Itertools;use objc::{ class, declare::ClassDecl, msg_send, runtime::{Class, Object, Sel}, sel, sel_impl,};use parking_lot::Mutex;use ptr::null_mut;use std::{ cell::{Cell, LazyCell}, convert::TryInto, ffi::{CStr, OsStr, c_void}, os::{raw::c_char, unix::ffi::OsStrExt}, path::{Path, PathBuf}, process::Command, ptr, rc::Rc, slice, str, sync::Arc,};use strum::IntoEnumIterator;use util::ResultExt; #[allow(non_upper_case_globals)]const NSUTF8StringEncoding: NSUInteger = 4; const MAC_PLATFORM_IVAR: &str = "platform";static mut APP_CLASS: *const Class = ptr::null();static mut APP_DELEGATE_CLASS: *const Class = ptr::null(); #[ctor]unsafe fn build_classes() { unsafe { APP_CLASS = { let mut decl = ClassDecl::new("GPUIApplication", class!(NSApplication)).unwrap(); decl.add_ivar::<*mut c_void>(MAC_PLATFORM_IVAR); decl.register() } }; unsafe { APP_DELEGATE_CLASS = unsafe { let mut decl = ClassDecl::new("GPUIApplicationDelegate", class!(NSResponder)).unwrap(); decl.add_ivar::<*mut c_void>(MAC_PLATFORM_IVAR); decl.add_method( sel!(applicationDidFinishLaunching:), did_finish_launching as extern "C" fn(&mut Object, Sel, id), ); decl.add_method( sel!(applicationShouldHandleReopen:hasVisibleWindows:), should_handle_reopen as extern "C" fn(&mut Object, Sel, id, bool), ); decl.add_method( sel!(applicationWillTerminate:), will_terminate as extern "C" fn(&mut Object, Sel, id), ); decl.add_method( sel!(handleGPUIMenuItem:), handle_menu_item as extern "C" fn(&mut Object, Sel, id), ); // Add menu item handlers so that OS save panels have the correct key commands decl.add_method( sel!(cut:), handle_menu_item as extern "C" fn(&mut Object, Sel, id), ); decl.add_method( sel!(copy:), handle_menu_item as extern "C" fn(&mut Object, Sel, id), ); decl.add_method( sel!(paste:), handle_menu_item as extern "C" fn(&mut Object, Sel, id), ); decl.add_method( sel!(selectAll:), handle_menu_item as extern "C" fn(&mut Object, Sel, id), ); decl.add_method( sel!(undo:), handle_menu_item as extern "C" fn(&mut Object, Sel, id), ); decl.add_method( sel!(redo:), handle_menu_item as extern "C" fn(&mut Object, Sel, id), ); decl.add_method( sel!(validateMenuItem:), validate_menu_item as extern "C" fn(&mut Object, Sel, id) -> bool, ); decl.add_method( sel!(menuWillOpen:), menu_will_open as extern "C" fn(&mut Object, Sel, id), ); decl.add_method( sel!(applicationDockMenu:), handle_dock_menu as extern "C" fn(&mut Object, Sel, id) -> id, ); decl.add_method( sel!(application:openURLs:), open_urls as extern "C" fn(&mut Object, Sel, id, id), ); decl.add_method( sel!(onKeyboardLayoutChange:), on_keyboard_layout_change as extern "C" fn(&mut Object, Sel, id), ); decl.register() } }} pub(crate) struct MacPlatform(Mutex<MacPlatformState>); pub(crate) struct MacPlatformState { background_executor: BackgroundExecutor, foreground_executor: ForegroundExecutor, text_system: Arc<dyn PlatformTextSystem>, renderer_context: renderer::Context, headless: bool, pasteboard: id, text_hash_pasteboard_type: id, metadata_pasteboard_type: id, reopen: Option<Box<dyn FnMut()>>, on_keyboard_layout_change: Option<Box<dyn FnMut()>>, quit: Option<Box<dyn FnMut()>>, menu_command: Option<Box<dyn FnMut(&dyn Action)>>, validate_menu_command: Option<Box<dyn FnMut(&dyn Action) -> bool>>, will_open_menu: Option<Box<dyn FnMut()>>, menu_actions: Vec<Box<dyn Action>>, open_urls: Option<Box<dyn FnMut(Vec<String>)>>, finish_launching: Option<Box<dyn FnOnce()>>, dock_menu: Option<id>,} impl Default for MacPlatform { fn default() -> Self { Self::new(false) }} impl MacPlatform { pub(crate) fn new(headless: bool) -> Self { let dispatcher = Arc::new(MacDispatcher::new()); #[cfg(feature = "font-kit")] let text_system = Arc::new(crate::MacTextSystem::new()); #[cfg(not(feature = "font-kit"))] let text_system = Arc::new(crate::NoopTextSystem::new()); Self(Mutex::new(MacPlatformState { headless, text_system, background_executor: BackgroundExecutor::new(dispatcher.clone()), foreground_executor: ForegroundExecutor::new(dispatcher), renderer_context: renderer::Context::default(), pasteboard: unsafe { NSPasteboard::generalPasteboard(nil) }, text_hash_pasteboard_type: unsafe { ns_string("zed-text-hash") }, metadata_pasteboard_type: unsafe { ns_string("zed-metadata") }, reopen: None, quit: None, menu_command: None, validate_menu_command: None, will_open_menu: None, menu_actions: Default::default(), open_urls: None, finish_launching: None, dock_menu: None, on_keyboard_layout_change: None, })) } unsafe fn read_from_pasteboard(&self, pasteboard: *mut Object, kind: id) -> Option<&[u8]> { unsafe { let data = pasteboard.dataForType(kind); if data == nil { None } else { Some(slice::from_raw_parts( data.bytes() as *mut u8, data.length() as usize, )) } } } unsafe fn create_menu_bar( &self, menus: Vec<Menu>, delegate: id, actions: &mut Vec<Box<dyn Action>>, keymap: &Keymap, ) -> id { unsafe { let application_menu = NSMenu::new(nil).autorelease(); application_menu.setDelegate_(delegate); for menu_config in menus { let menu = NSMenu::new(nil).autorelease(); let menu_title = ns_string(&menu_config.name); menu.setTitle_(menu_title); menu.setDelegate_(delegate); for item_config in menu_config.items { menu.addItem_(Self::create_menu_item( item_config, delegate, actions, keymap, )); } let menu_item = NSMenuItem::new(nil).autorelease(); menu_item.setTitle_(menu_title); menu_item.setSubmenu_(menu); application_menu.addItem_(menu_item); if menu_config.name == "Window" { let app: id = msg_send![APP_CLASS, sharedApplication]; app.setWindowsMenu_(menu); } } application_menu } } unsafe fn create_dock_menu( &self, menu_items: Vec<MenuItem>, delegate: id, actions: &mut Vec<Box<dyn Action>>, keymap: &Keymap, ) -> id { unsafe { let dock_menu = NSMenu::new(nil); dock_menu.setDelegate_(delegate); for item_config in menu_items { dock_menu.addItem_(Self::create_menu_item( item_config, delegate, actions, keymap, )); } dock_menu } } unsafe fn create_menu_item( item: MenuItem, delegate: id, actions: &mut Vec<Box<dyn Action>>, keymap: &Keymap, ) -> id { const DEFAULT_CONTEXT: LazyCell<Vec<KeyContext>> = LazyCell::new(|| { let mut workspace_context = KeyContext::new_with_defaults(); workspace_context.add("Workspace"); let mut pane_context = KeyContext::new_with_defaults(); pane_context.add("Pane"); let mut editor_context = KeyContext::new_with_defaults(); editor_context.add("Editor"); pane_context.extend(&editor_context); workspace_context.extend(&pane_context); vec![workspace_context] }); unsafe { match item { MenuItem::Separator => NSMenuItem::separatorItem(nil), MenuItem::Action { name, action, os_action, } => { let keystrokes = keymap .bindings_for_action(action.as_ref()) .find_or_first(|binding| { binding .predicate() .is_none_or(|predicate| predicate.eval(&DEFAULT_CONTEXT)) }) .map(|binding| binding.keystrokes()); let selector = match os_action { Some(crate::OsAction::Cut) => selector("cut:"), Some(crate::OsAction::Copy) => selector("copy:"), Some(crate::OsAction::Paste) => selector("paste:"), Some(crate::OsAction::SelectAll) => selector("selectAll:"), // "undo:" and "redo:" are always disabled in our case, as // we don't have a NSTextView/NSTextField to enable them on. Some(crate::OsAction::Undo) => selector("handleGPUIMenuItem:"), Some(crate::OsAction::Redo) => selector("handleGPUIMenuItem:"), None => selector("handleGPUIMenuItem:"), }; let item; if let Some(keystrokes) = keystrokes { if keystrokes.len() == 1 { let keystroke = &keystrokes[0]; let mut mask = NSEventModifierFlags::empty(); for (modifier, flag) in &[ ( keystroke.modifiers.platform, NSEventModifierFlags::NSCommandKeyMask, ), ( keystroke.modifiers.control, NSEventModifierFlags::NSControlKeyMask, ), ( keystroke.modifiers.alt, NSEventModifierFlags::NSAlternateKeyMask, ), ( keystroke.modifiers.shift, NSEventModifierFlags::NSShiftKeyMask, ), ] { if *modifier { mask |= *flag; } } item = NSMenuItem::alloc(nil) .initWithTitle_action_keyEquivalent_( ns_string(&name), selector, ns_string(key_to_native(&keystroke.key).as_ref()), ) .autorelease(); if Self::os_version() >= SemanticVersion::new(12, 0, 0) { let _: () = msg_send![item, setAllowsAutomaticKeyEquivalentLocalization: NO]; } item.setKeyEquivalentModifierMask_(mask); } else { item = NSMenuItem::alloc(nil) .initWithTitle_action_keyEquivalent_( ns_string(&name), selector, ns_string(""), ) .autorelease(); } } else { item = NSMenuItem::alloc(nil) .initWithTitle_action_keyEquivalent_( ns_string(&name), selector, ns_string(""), ) .autorelease(); } let tag = actions.len() as NSInteger; let _: () = msg_send![item, setTag: tag]; actions.push(action); item } MenuItem::Submenu(Menu { name, items }) => { let item = NSMenuItem::new(nil).autorelease(); let submenu = NSMenu::new(nil).autorelease(); submenu.setDelegate_(delegate); for item in items { submenu.addItem_(Self::create_menu_item(item, delegate, actions, keymap)); } item.setSubmenu_(submenu); item.setTitle_(ns_string(&name)); if name == "Services" { let app: id = msg_send![APP_CLASS, sharedApplication]; app.setServicesMenu_(item); } item } } } } fn os_version() -> SemanticVersion { let version = unsafe { let process_info = NSProcessInfo::processInfo(nil); process_info.operatingSystemVersion() }; SemanticVersion::new( version.majorVersion as usize, version.minorVersion as usize, version.patchVersion as usize, ) }} impl Platform for MacPlatform { fn background_executor(&self) -> BackgroundExecutor { self.0.lock().background_executor.clone() } fn foreground_executor(&self) -> crate::ForegroundExecutor { self.0.lock().foreground_executor.clone() } fn text_system(&self) -> Arc<dyn PlatformTextSystem> { self.0.lock().text_system.clone() } fn run(&self, on_finish_launching: Box<dyn FnOnce()>) { let mut state = self.0.lock(); if state.headless { drop(state); on_finish_launching(); unsafe { CFRunLoopRun() }; } else { state.finish_launching = Some(on_finish_launching); drop(state); } unsafe { let app: id = msg_send![APP_CLASS, sharedApplication]; let app_delegate: id = msg_send![APP_DELEGATE_CLASS, new]; app.setDelegate_(app_delegate); let self_ptr = self as *const Self as *const c_void; (*app).set_ivar(MAC_PLATFORM_IVAR, self_ptr); (*app_delegate).set_ivar(MAC_PLATFORM_IVAR, self_ptr); let pool = NSAutoreleasePool::new(nil); app.run(); pool.drain(); (*app).set_ivar(MAC_PLATFORM_IVAR, null_mut::<c_void>()); (*NSWindow::delegate(app)).set_ivar(MAC_PLATFORM_IVAR, null_mut::<c_void>()); } } fn quit(&self) { // Quitting the app causes us to close windows, which invokes `Window::on_close` callbacks // synchronously before this method terminates. If we call `Platform::quit` while holding a // borrow of the app state (which most of the time we will do), we will end up // double-borrowing the app state in the `on_close` callbacks for our open windows. To solve // this, we make quitting the application asynchronous so that we aren't holding borrows to // the app state on the stack when we actually terminate the app. use super::dispatcher::{dispatch_get_main_queue, dispatch_sys::dispatch_async_f}; unsafe { dispatch_async_f(dispatch_get_main_queue(), ptr::null_mut(), Some(quit)); } unsafe extern "C" fn quit(_: *mut c_void) { unsafe { let app = NSApplication::sharedApplication(nil); let _: () = msg_send![app, terminate: nil]; } } } fn restart(&self, _binary_path: Option<PathBuf>) { use std::os::unix::process::CommandExt as _;Lines 1–500 of 1629. Load more
use super::{ BoolExt, MacKeyboardLayout, attributed_string::{NSAttributedString, NSMutableAttributedString}, events::key_to_native, is_macos_version_at_least, renderer, screen_capture, }; use crate::{ Action, AnyWindowHandle, BackgroundExecutor, ClipboardEntry, ClipboardItem, ClipboardString, CursorStyle, ForegroundExecutor, Image, ImageFormat, KeyContext, Keymap, MacDispatcher, MacDisplay, MacWindow, Menu, MenuItem, PathPromptOptions, Platform, PlatformDisplay, PlatformKeyboardLayout, PlatformTextSystem, PlatformWindow, Result, ScreenCaptureSource, SemanticVersion, Task, WindowAppearance, WindowParams, hash, }; use anyhow::{Context as _, anyhow}; use block::ConcreteBlock; use cocoa::{ appkit::{ NSApplication, NSApplicationActivationPolicy::NSApplicationActivationPolicyRegular, NSEventModifierFlags, NSMenu, NSMenuItem, NSModalResponse, NSOpenPanel, NSPasteboard, NSPasteboardTypePNG, NSPasteboardTypeRTF, NSPasteboardTypeRTFD, NSPasteboardTypeString, NSPasteboardTypeTIFF, NSSavePanel, NSWindow, }, base::{BOOL, NO, YES, id, nil, selector}, foundation::{ NSArray, NSAutoreleasePool, NSBundle, NSData, NSInteger, NSOperatingSystemVersion, NSProcessInfo, NSRange, NSString, NSUInteger, NSURL, }, }; use core_foundation::{ base::{CFRelease, CFType, CFTypeRef, OSStatus, TCFType}, boolean::CFBoolean, data::CFData, dictionary::{CFDictionary, CFDictionaryRef, CFMutableDictionary}, runloop::CFRunLoopRun, string::{CFString, CFStringRef}, }; use ctor::ctor; use futures::channel::oneshot; use itertools::Itertools; use objc::{ class, declare::ClassDecl, msg_send, runtime::{Class, Object, Sel}, sel, sel_impl, }; use parking_lot::Mutex; use ptr::null_mut; use std::{ cell::{Cell, LazyCell}, convert::TryInto, ffi::{CStr, OsStr, c_void}, os::{raw::c_char, unix::ffi::OsStrExt}, path::{Path, PathBuf}, process::Command, ptr, rc::Rc, slice, str, sync::Arc, }; use strum::IntoEnumIterator; use util::ResultExt; #[allow(non_upper_case_globals)] const NSUTF8StringEncoding: NSUInteger = 4; const MAC_PLATFORM_IVAR: &str = "platform"; static mut APP_CLASS: *const Class = ptr::null(); static mut APP_DELEGATE_CLASS: *const Class = ptr::null(); #[ctor] unsafe fn build_classes() { unsafe { APP_CLASS = { let mut decl = ClassDecl::new("GPUIApplication", class!(NSApplication)).unwrap(); decl.add_ivar::<*mut c_void>(MAC_PLATFORM_IVAR); decl.register() } }; unsafe { APP_DELEGATE_CLASS = unsafe { let mut decl = ClassDecl::new("GPUIApplicationDelegate", class!(NSResponder)).unwrap(); decl.add_ivar::<*mut c_void>(MAC_PLATFORM_IVAR); decl.add_method( sel!(applicationDidFinishLaunching:), did_finish_launching as extern "C" fn(&mut Object, Sel, id), ); decl.add_method( sel!(applicationShouldHandleReopen:hasVisibleWindows:), should_handle_reopen as extern "C" fn(&mut Object, Sel, id, bool), ); decl.add_method( sel!(applicationWillTerminate:), will_terminate as extern "C" fn(&mut Object, Sel, id), ); decl.add_method( sel!(handleGPUIMenuItem:), handle_menu_item as extern "C" fn(&mut Object, Sel, id), ); // Add menu item handlers so that OS save panels have the correct key commands decl.add_method( sel!(cut:), handle_menu_item as extern "C" fn(&mut Object, Sel, id), ); decl.add_method( sel!(copy:), handle_menu_item as extern "C" fn(&mut Object, Sel, id), ); decl.add_method( sel!(paste:), handle_menu_item as extern "C" fn(&mut Object, Sel, id), ); decl.add_method( sel!(selectAll:), handle_menu_item as extern "C" fn(&mut Object, Sel, id), ); decl.add_method( sel!(undo:), handle_menu_item as extern "C" fn(&mut Object, Sel, id), ); decl.add_method( sel!(redo:), handle_menu_item as extern "C" fn(&mut Object, Sel, id), ); decl.add_method( sel!(validateMenuItem:), validate_menu_item as extern "C" fn(&mut Object, Sel, id) -> bool, ); decl.add_method( sel!(menuWillOpen:), menu_will_open as extern "C" fn(&mut Object, Sel, id), ); decl.add_method( sel!(applicationDockMenu:), handle_dock_menu as extern "C" fn(&mut Object, Sel, id) -> id, ); decl.add_method( sel!(application:openURLs:), open_urls as extern "C" fn(&mut Object, Sel, id, id), ); decl.add_method( sel!(onKeyboardLayoutChange:), on_keyboard_layout_change as extern "C" fn(&mut Object, Sel, id), ); decl.register() } } } pub(crate) struct MacPlatform(Mutex<MacPlatformState>); pub(crate) struct MacPlatformState { background_executor: BackgroundExecutor, foreground_executor: ForegroundExecutor, text_system: Arc<dyn PlatformTextSystem>, renderer_context: renderer::Context, headless: bool, pasteboard: id, text_hash_pasteboard_type: id, metadata_pasteboard_type: id, reopen: Option<Box<dyn FnMut()>>, on_keyboard_layout_change: Option<Box<dyn FnMut()>>, quit: Option<Box<dyn FnMut()>>, menu_command: Option<Box<dyn FnMut(&dyn Action)>>, validate_menu_command: Option<Box<dyn FnMut(&dyn Action) -> bool>>, will_open_menu: Option<Box<dyn FnMut()>>, menu_actions: Vec<Box<dyn Action>>, open_urls: Option<Box<dyn FnMut(Vec<String>)>>, finish_launching: Option<Box<dyn FnOnce()>>, dock_menu: Option<id>, } impl Default for MacPlatform { fn default() -> Self { Self::new(false) } } impl MacPlatform { pub(crate) fn new(headless: bool) -> Self { let dispatcher = Arc::new(MacDispatcher::new()); #[cfg(feature = "font-kit")] let text_system = Arc::new(crate::MacTextSystem::new()); #[cfg(not(feature = "font-kit"))] let text_system = Arc::new(crate::NoopTextSystem::new()); Self(Mutex::new(MacPlatformState { headless, text_system, background_executor: BackgroundExecutor::new(dispatcher.clone()), foreground_executor: ForegroundExecutor::new(dispatcher), renderer_context: renderer::Context::default(), pasteboard: unsafe { NSPasteboard::generalPasteboard(nil) }, text_hash_pasteboard_type: unsafe { ns_string("zed-text-hash") }, metadata_pasteboard_type: unsafe { ns_string("zed-metadata") }, reopen: None, quit: None, menu_command: None, validate_menu_command: None, will_open_menu: None, menu_actions: Default::default(), open_urls: None, finish_launching: None, dock_menu: None, on_keyboard_layout_change: None, })) } unsafe fn read_from_pasteboard(&self, pasteboard: *mut Object, kind: id) -> Option<&[u8]> { unsafe { let data = pasteboard.dataForType(kind); if data == nil { None } else { Some(slice::from_raw_parts( data.bytes() as *mut u8, data.length() as usize, )) } } } unsafe fn create_menu_bar( &self, menus: Vec<Menu>, delegate: id, actions: &mut Vec<Box<dyn Action>>, keymap: &Keymap, ) -> id { unsafe { let application_menu = NSMenu::new(nil).autorelease(); application_menu.setDelegate_(delegate); for menu_config in menus { let menu = NSMenu::new(nil).autorelease(); let menu_title = ns_string(&menu_config.name); menu.setTitle_(menu_title); menu.setDelegate_(delegate); for item_config in menu_config.items { menu.addItem_(Self::create_menu_item( item_config, delegate, actions, keymap, )); } let menu_item = NSMenuItem::new(nil).autorelease(); menu_item.setTitle_(menu_title); menu_item.setSubmenu_(menu); application_menu.addItem_(menu_item); if menu_config.name == "Window" { let app: id = msg_send![APP_CLASS, sharedApplication]; app.setWindowsMenu_(menu); } } application_menu } } unsafe fn create_dock_menu( &self, menu_items: Vec<MenuItem>, delegate: id, actions: &mut Vec<Box<dyn Action>>, keymap: &Keymap, ) -> id { unsafe { let dock_menu = NSMenu::new(nil); dock_menu.setDelegate_(delegate); for item_config in menu_items { dock_menu.addItem_(Self::create_menu_item( item_config, delegate, actions, keymap, )); } dock_menu } } unsafe fn create_menu_item( item: MenuItem, delegate: id, actions: &mut Vec<Box<dyn Action>>, keymap: &Keymap, ) -> id { const DEFAULT_CONTEXT: LazyCell<Vec<KeyContext>> = LazyCell::new(|| { let mut workspace_context = KeyContext::new_with_defaults(); workspace_context.add("Workspace"); let mut pane_context = KeyContext::new_with_defaults(); pane_context.add("Pane"); let mut editor_context = KeyContext::new_with_defaults(); editor_context.add("Editor"); pane_context.extend(&editor_context); workspace_context.extend(&pane_context); vec![workspace_context] }); unsafe { match item { MenuItem::Separator => NSMenuItem::separatorItem(nil), MenuItem::Action { name, action, os_action, } => { let keystrokes = keymap .bindings_for_action(action.as_ref()) .find_or_first(|binding| { binding .predicate() .is_none_or(|predicate| predicate.eval(&DEFAULT_CONTEXT)) }) .map(|binding| binding.keystrokes()); let selector = match os_action { Some(crate::OsAction::Cut) => selector("cut:"), Some(crate::OsAction::Copy) => selector("copy:"), Some(crate::OsAction::Paste) => selector("paste:"), Some(crate::OsAction::SelectAll) => selector("selectAll:"), // "undo:" and "redo:" are always disabled in our case, as // we don't have a NSTextView/NSTextField to enable them on. Some(crate::OsAction::Undo) => selector("handleGPUIMenuItem:"), Some(crate::OsAction::Redo) => selector("handleGPUIMenuItem:"), None => selector("handleGPUIMenuItem:"), }; let item; if let Some(keystrokes) = keystrokes { if keystrokes.len() == 1 { let keystroke = &keystrokes[0]; let mut mask = NSEventModifierFlags::empty(); for (modifier, flag) in &[ ( keystroke.modifiers.platform, NSEventModifierFlags::NSCommandKeyMask, ), ( keystroke.modifiers.control, NSEventModifierFlags::NSControlKeyMask, ), ( keystroke.modifiers.alt, NSEventModifierFlags::NSAlternateKeyMask, ), ( keystroke.modifiers.shift, NSEventModifierFlags::NSShiftKeyMask, ), ] { if *modifier { mask |= *flag; } } item = NSMenuItem::alloc(nil) .initWithTitle_action_keyEquivalent_( ns_string(&name), selector, ns_string(key_to_native(&keystroke.key).as_ref()), ) .autorelease(); if Self::os_version() >= SemanticVersion::new(12, 0, 0) { let _: () = msg_send![item, setAllowsAutomaticKeyEquivalentLocalization: NO]; } item.setKeyEquivalentModifierMask_(mask); } else { item = NSMenuItem::alloc(nil) .initWithTitle_action_keyEquivalent_( ns_string(&name), selector, ns_string(""), ) .autorelease(); } } else { item = NSMenuItem::alloc(nil) .initWithTitle_action_keyEquivalent_( ns_string(&name), selector, ns_string(""), ) .autorelease(); } let tag = actions.len() as NSInteger; let _: () = msg_send![item, setTag: tag]; actions.push(action); item } MenuItem::Submenu(Menu { name, items }) => { let item = NSMenuItem::new(nil).autorelease(); let submenu = NSMenu::new(nil).autorelease(); submenu.setDelegate_(delegate); for item in items { submenu.addItem_(Self::create_menu_item(item, delegate, actions, keymap)); } item.setSubmenu_(submenu); item.setTitle_(ns_string(&name)); if name == "Services" { let app: id = msg_send![APP_CLASS, sharedApplication]; app.setServicesMenu_(item); } item } } } } fn os_version() -> SemanticVersion { let version = unsafe { let process_info = NSProcessInfo::processInfo(nil); process_info.operatingSystemVersion() }; SemanticVersion::new( version.majorVersion as usize, version.minorVersion as usize, version.patchVersion as usize, ) } } impl Platform for MacPlatform { fn background_executor(&self) -> BackgroundExecutor { self.0.lock().background_executor.clone() } fn foreground_executor(&self) -> crate::ForegroundExecutor { self.0.lock().foreground_executor.clone() } fn text_system(&self) -> Arc<dyn PlatformTextSystem> { self.0.lock().text_system.clone() } fn run(&self, on_finish_launching: Box<dyn FnOnce()>) { let mut state = self.0.lock(); if state.headless { drop(state); on_finish_launching(); unsafe { CFRunLoopRun() }; } else { state.finish_launching = Some(on_finish_launching); drop(state); } unsafe { let app: id = msg_send![APP_CLASS, sharedApplication]; let app_delegate: id = msg_send![APP_DELEGATE_CLASS, new]; app.setDelegate_(app_delegate); let self_ptr = self as *const Self as *const c_void; (*app).set_ivar(MAC_PLATFORM_IVAR, self_ptr); (*app_delegate).set_ivar(MAC_PLATFORM_IVAR, self_ptr); let pool = NSAutoreleasePool::new(nil); app.run(); pool.drain(); (*app).set_ivar(MAC_PLATFORM_IVAR, null_mut::<c_void>()); (*NSWindow::delegate(app)).set_ivar(MAC_PLATFORM_IVAR, null_mut::<c_void>()); } } fn quit(&self) { // Quitting the app causes us to close windows, which invokes `Window::on_close` callbacks // synchronously before this method terminates. If we call `Platform::quit` while holding a // borrow of the app state (which most of the time we will do), we will end up // double-borrowing the app state in the `on_close` callbacks for our open windows. To solve // this, we make quitting the application asynchronous so that we aren't holding borrows to // the app state on the stack when we actually terminate the app. use super::dispatcher::{dispatch_get_main_queue, dispatch_sys::dispatch_async_f}; unsafe { dispatch_async_f(dispatch_get_main_queue(), ptr::null_mut(), Some(quit)); } unsafe extern "C" fn quit(_: *mut c_void) { unsafe { let app = NSApplication::sharedApplication(nil); let _: () = msg_send![app, terminate: nil]; } } } fn restart(&self, _binary_path: Option<PathBuf>) { use std::os::unix::process::CommandExt as _;