1561 lines · 59.0 KB · Rust
RawDownload
use crate::{    BoolExt, MacDispatcher, MacDisplay, MacKeyboardLayout, MacKeyboardMapper, MacWindow,    events::key_to_native, ns_string, pasteboard::Pasteboard, renderer,    set_active_window_cursor_style,};use anyhow::{Context as _, anyhow};use block::ConcreteBlock;use cocoa::{    appkit::{        NSAppearanceNameVibrantDark, NSAppearanceNameVibrantLight, NSApplication,        NSApplicationActivationPolicy::NSApplicationActivationPolicyRegular, NSControl as _,        NSEventModifierFlags, NSMenu, NSMenuItem, NSModalResponse, NSOpenPanel, NSSavePanel,        NSVisualEffectState, NSVisualEffectView, NSWindow,    },    base::{BOOL, NO, YES, id, nil, selector},    foundation::{        NSArray, NSAutoreleasePool, NSBundle, NSInteger, NSProcessInfo, 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 dispatch2::DispatchQueue;use futures::channel::oneshot;use gpui::{    Action, AnyWindowHandle, BackgroundExecutor, ClipboardItem, CursorStyle, ForegroundExecutor,    KeyContext, Keymap, Menu, MenuItem, OsMenu, OwnedMenu, PathPromptOptions, Platform,    PlatformDisplay, PlatformKeyboardLayout, PlatformKeyboardMapper, PlatformTextSystem,    PlatformWindow, Result, SystemMenuType, Task, ThermalState, WindowAppearance, WindowKind,    WindowParams, popup::PopupNotSupportedError,};use gpui_util::{ResultExt, new_std_command};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 semver::Version;use std::{    cell::Cell,    ffi::{CStr, OsStr, c_void},    os::{raw::c_char, unix::ffi::OsStrExt},    path::{Path, PathBuf},    ptr,    rc::Rc,    slice, str,    sync::{        Arc, OnceLock,        atomic::{AtomicBool, Ordering},    },}; #[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)]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 = {            let mut decl = ClassDecl::new("GPUIApplicationDelegate", class!(NSResponder)).unwrap();            decl.add_ivar::<*mut c_void>(MAC_PLATFORM_IVAR);            decl.add_method(                sel!(applicationWillFinishLaunching:),                will_finish_launching as extern "C" fn(&mut Object, Sel, id),            );            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.add_method(                sel!(onThermalStateChange:),                on_thermal_state_change as extern "C" fn(&mut Object, Sel, id),            );             decl.add_method(                sel!(onSystemWake:),                on_system_wake as extern "C" fn(&mut Object, Sel, id),            );             decl.register()        }    }} pub 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,    general_pasteboard: Pasteboard,    find_pasteboard: Pasteboard,    reopen: Option<Box<dyn FnMut()>>,    on_keyboard_layout_change: Option<Box<dyn FnMut()>>,    on_thermal_state_change: Option<Box<dyn FnMut()>>,    on_system_wake: Option<Box<dyn FnMut()>>,    system_wake_observer_registered: bool,    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>,    menus: Option<Vec<OwnedMenu>>,    keyboard_mapper: Rc<MacKeyboardMapper>,    /// Mirrors `[NSCursor setHiddenUntilMouseMoves:]` state, which AppKit doesn't expose.    cursor_visible: Arc<AtomicBool>,    system_notifications: crate::system_notifications::SystemNotificationState,} impl MacPlatform {    pub 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 = {            if !headless {                log::warn!(                    "gpui_macos was compiled without the `font-kit` feature, so no text will be rendered."                );            }            Arc::new(gpui::NoopTextSystem::new())        };         let keyboard_layout = MacKeyboardLayout::new();        let keyboard_mapper = Rc::new(MacKeyboardMapper::new(keyboard_layout.id()));         Self(Mutex::new(MacPlatformState {            headless,            text_system,            background_executor: BackgroundExecutor::new(dispatcher.clone()),            foreground_executor: ForegroundExecutor::new(dispatcher),            renderer_context: renderer::Context::default(),            general_pasteboard: Pasteboard::general(),            find_pasteboard: Pasteboard::find(),            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,            on_thermal_state_change: None,            on_system_wake: None,            system_wake_observer_registered: false,            menus: None,            keyboard_mapper,            cursor_visible: Arc::new(AtomicBool::new(true)),            system_notifications: crate::system_notifications::SystemNotificationState::new(),        }))    }     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 {        static DEFAULT_CONTEXT: OnceLock<Vec<KeyContext>> = OnceLock::new();         unsafe {            match item {                MenuItem::Separator => NSMenuItem::separatorItem(nil),                MenuItem::Action {                    name,                    action,                    os_action,                    checked,                    disabled,                } => {                    // Note that this is intentionally using earlier bindings, whereas typically                    // later ones take display precedence. See the discussion on                    // https://github.com/zed-industries/zed/issues/23621                    let keystrokes = keymap                        .bindings_for_action(action.as_ref())                        .find_or_first(|binding| {                            binding.predicate().is_none_or(|predicate| {                                predicate.eval(DEFAULT_CONTEXT.get_or_init(|| {                                    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]                                }))                            })                        })                        .map(|binding| binding.keystrokes());                     let selector = match os_action {                        Some(gpui::OsAction::Cut) => selector("cut:"),                        Some(gpui::OsAction::Copy) => selector("copy:"),                        Some(gpui::OsAction::Paste) => selector("paste:"),                        Some(gpui::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(gpui::OsAction::Undo) => selector("handleGPUIMenuItem:"),                        Some(gpui::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() >= Version::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();                    }                     if *checked {                        item.setState_(NSVisualEffectState::Active);                    }                    item.setEnabled_(if *disabled { NO } else { YES });                     let tag = actions.len() as NSInteger;                    let _: () = msg_send![item, setTag: tag];                    actions.push(action.boxed_clone());                    item                }                MenuItem::Submenu(Menu {                    name,                    items,                    disabled,                }) => {                    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.setEnabled_(if *disabled { NO } else { YES });                    item.setTitle_(ns_string(name));                    item                }                MenuItem::SystemMenu(OsMenu { name, menu_type }) => {                    let item = NSMenuItem::new(nil).autorelease();                    let submenu = NSMenu::new(nil).autorelease();                    submenu.setDelegate_(delegate);                    item.setSubmenu_(submenu);                    item.setTitle_(ns_string(name));                     match menu_type {                        SystemMenuType::Services => {                            let app: id = msg_send![APP_CLASS, sharedApplication];                            app.setServicesMenu_(item);                        }                    }                     item                }            }        }    }     fn os_version() -> Version {        let version = unsafe {            let process_info = NSProcessInfo::processInfo(nil);            process_info.operatingSystemVersion()        };        Version::new(            version.majorVersion,            version.minorVersion,            version.patchVersion,        )    }} impl Platform for MacPlatform {    fn background_executor(&self) -> BackgroundExecutor {        self.0.lock().background_executor.clone()    }     fn foreground_executor(&self) -> gpui::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);        }

Lines 1–500 of 1561.