1629 lines · 60.9 KB · Rust
RawDownload
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.