window.rs

   1use super::{BoolExt, MacDisplay, NSRange, NSStringExt, ns_string, renderer};
   2use crate::{
   3    AnyWindowHandle, BackgroundExecutor, Bounds, Capslock, DisplayLink, ExternalPaths,
   4    FileDropEvent, ForegroundExecutor, KeyDownEvent, Keystroke, Modifiers, ModifiersChangedEvent,
   5    MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, Pixels, PlatformAtlas,
   6    PlatformDisplay, PlatformInput, PlatformWindow, Point, PromptButton, PromptLevel,
   7    RequestFrameOptions, SharedString, Size, SystemWindowTab, WindowAppearance,
   8    WindowBackgroundAppearance, WindowBounds, WindowControlArea, WindowKind, WindowParams,
   9    dispatch_get_main_queue, dispatch_sys::dispatch_async_f, platform::PlatformInputHandler, point,
  10    px, size,
  11};
  12#[cfg(any(test, feature = "test-support"))]
  13use anyhow::Result;
  14use block::ConcreteBlock;
  15use cocoa::{
  16    appkit::{
  17        NSAppKitVersionNumber, NSAppKitVersionNumber12_0, NSApplication, NSBackingStoreBuffered,
  18        NSColor, NSEvent, NSEventModifierFlags, NSFilenamesPboardType, NSPasteboard, NSScreen,
  19        NSView, NSViewHeightSizable, NSViewWidthSizable, NSVisualEffectMaterial,
  20        NSVisualEffectState, NSVisualEffectView, NSWindow, NSWindowButton,
  21        NSWindowCollectionBehavior, NSWindowOcclusionState, NSWindowOrderingMode,
  22        NSWindowStyleMask, NSWindowTitleVisibility,
  23    },
  24    base::{id, nil},
  25    foundation::{
  26        NSArray, NSAutoreleasePool, NSDictionary, NSFastEnumeration, NSInteger, NSNotFound,
  27        NSOperatingSystemVersion, NSPoint, NSProcessInfo, NSRect, NSSize, NSString, NSUInteger,
  28        NSUserDefaults,
  29    },
  30};
  31#[cfg(any(test, feature = "test-support"))]
  32use image::RgbaImage;
  33
  34use core_graphics::display::{CGDirectDisplayID, CGPoint, CGRect};
  35use ctor::ctor;
  36use futures::channel::oneshot;
  37use objc::{
  38    class,
  39    declare::ClassDecl,
  40    msg_send,
  41    runtime::{BOOL, Class, NO, Object, Protocol, Sel, YES},
  42    sel, sel_impl,
  43};
  44use parking_lot::Mutex;
  45use raw_window_handle as rwh;
  46use smallvec::SmallVec;
  47use std::{
  48    cell::Cell,
  49    ffi::{CStr, c_void},
  50    mem,
  51    ops::Range,
  52    path::PathBuf,
  53    ptr::{self, NonNull},
  54    rc::Rc,
  55    sync::{Arc, Weak},
  56    time::Duration,
  57};
  58use util::ResultExt;
  59
  60const WINDOW_STATE_IVAR: &str = "windowState";
  61
  62static mut WINDOW_CLASS: *const Class = ptr::null();
  63static mut PANEL_CLASS: *const Class = ptr::null();
  64static mut VIEW_CLASS: *const Class = ptr::null();
  65static mut BLURRED_VIEW_CLASS: *const Class = ptr::null();
  66
  67#[allow(non_upper_case_globals)]
  68const NSWindowStyleMaskNonactivatingPanel: NSWindowStyleMask =
  69    NSWindowStyleMask::from_bits_retain(1 << 7);
  70// WindowLevel const value ref: https://docs.rs/core-graphics2/0.4.1/src/core_graphics2/window_level.rs.html
  71#[allow(non_upper_case_globals)]
  72const NSNormalWindowLevel: NSInteger = 0;
  73#[allow(non_upper_case_globals)]
  74const NSFloatingWindowLevel: NSInteger = 3;
  75#[allow(non_upper_case_globals)]
  76const NSPopUpWindowLevel: NSInteger = 101;
  77#[allow(non_upper_case_globals)]
  78const NSTrackingMouseEnteredAndExited: NSUInteger = 0x01;
  79#[allow(non_upper_case_globals)]
  80const NSTrackingMouseMoved: NSUInteger = 0x02;
  81#[allow(non_upper_case_globals)]
  82const NSTrackingActiveAlways: NSUInteger = 0x80;
  83#[allow(non_upper_case_globals)]
  84const NSTrackingInVisibleRect: NSUInteger = 0x200;
  85#[allow(non_upper_case_globals)]
  86const NSWindowAnimationBehaviorUtilityWindow: NSInteger = 4;
  87#[allow(non_upper_case_globals)]
  88const NSViewLayerContentsRedrawDuringViewResize: NSInteger = 2;
  89// https://developer.apple.com/documentation/appkit/nsdragoperation
  90type NSDragOperation = NSUInteger;
  91#[allow(non_upper_case_globals)]
  92const NSDragOperationNone: NSDragOperation = 0;
  93#[allow(non_upper_case_globals)]
  94const NSDragOperationCopy: NSDragOperation = 1;
  95#[derive(PartialEq)]
  96pub enum UserTabbingPreference {
  97    Never,
  98    Always,
  99    InFullScreen,
 100}
 101
 102#[link(name = "CoreGraphics", kind = "framework")]
 103unsafe extern "C" {
 104    // Widely used private APIs; Apple uses them for their Terminal.app.
 105    fn CGSMainConnectionID() -> id;
 106    fn CGSSetWindowBackgroundBlurRadius(
 107        connection_id: id,
 108        window_id: NSInteger,
 109        radius: i64,
 110    ) -> i32;
 111}
 112
 113#[ctor]
 114unsafe fn build_classes() {
 115    unsafe {
 116        WINDOW_CLASS = build_window_class("GPUIWindow", class!(NSWindow));
 117        PANEL_CLASS = build_window_class("GPUIPanel", class!(NSPanel));
 118        VIEW_CLASS = {
 119            let mut decl = ClassDecl::new("GPUIView", class!(NSView)).unwrap();
 120            decl.add_ivar::<*mut c_void>(WINDOW_STATE_IVAR);
 121            unsafe {
 122                decl.add_method(sel!(dealloc), dealloc_view as extern "C" fn(&Object, Sel));
 123
 124                decl.add_method(
 125                    sel!(performKeyEquivalent:),
 126                    handle_key_equivalent as extern "C" fn(&Object, Sel, id) -> BOOL,
 127                );
 128                decl.add_method(
 129                    sel!(keyDown:),
 130                    handle_key_down as extern "C" fn(&Object, Sel, id),
 131                );
 132                decl.add_method(
 133                    sel!(keyUp:),
 134                    handle_key_up as extern "C" fn(&Object, Sel, id),
 135                );
 136                decl.add_method(
 137                    sel!(mouseDown:),
 138                    handle_view_event as extern "C" fn(&Object, Sel, id),
 139                );
 140                decl.add_method(
 141                    sel!(mouseUp:),
 142                    handle_view_event as extern "C" fn(&Object, Sel, id),
 143                );
 144                decl.add_method(
 145                    sel!(rightMouseDown:),
 146                    handle_view_event as extern "C" fn(&Object, Sel, id),
 147                );
 148                decl.add_method(
 149                    sel!(rightMouseUp:),
 150                    handle_view_event as extern "C" fn(&Object, Sel, id),
 151                );
 152                decl.add_method(
 153                    sel!(otherMouseDown:),
 154                    handle_view_event as extern "C" fn(&Object, Sel, id),
 155                );
 156                decl.add_method(
 157                    sel!(otherMouseUp:),
 158                    handle_view_event as extern "C" fn(&Object, Sel, id),
 159                );
 160                decl.add_method(
 161                    sel!(mouseMoved:),
 162                    handle_view_event as extern "C" fn(&Object, Sel, id),
 163                );
 164                decl.add_method(
 165                    sel!(pressureChangeWithEvent:),
 166                    handle_view_event as extern "C" fn(&Object, Sel, id),
 167                );
 168                decl.add_method(
 169                    sel!(mouseExited:),
 170                    handle_view_event as extern "C" fn(&Object, Sel, id),
 171                );
 172                decl.add_method(
 173                    sel!(mouseDragged:),
 174                    handle_view_event as extern "C" fn(&Object, Sel, id),
 175                );
 176                decl.add_method(
 177                    sel!(scrollWheel:),
 178                    handle_view_event as extern "C" fn(&Object, Sel, id),
 179                );
 180                decl.add_method(
 181                    sel!(swipeWithEvent:),
 182                    handle_view_event as extern "C" fn(&Object, Sel, id),
 183                );
 184                decl.add_method(
 185                    sel!(flagsChanged:),
 186                    handle_view_event as extern "C" fn(&Object, Sel, id),
 187                );
 188
 189                decl.add_method(
 190                    sel!(makeBackingLayer),
 191                    make_backing_layer as extern "C" fn(&Object, Sel) -> id,
 192                );
 193
 194                decl.add_protocol(Protocol::get("CALayerDelegate").unwrap());
 195                decl.add_method(
 196                    sel!(viewDidChangeBackingProperties),
 197                    view_did_change_backing_properties as extern "C" fn(&Object, Sel),
 198                );
 199                decl.add_method(
 200                    sel!(setFrameSize:),
 201                    set_frame_size as extern "C" fn(&Object, Sel, NSSize),
 202                );
 203                decl.add_method(
 204                    sel!(displayLayer:),
 205                    display_layer as extern "C" fn(&Object, Sel, id),
 206                );
 207
 208                decl.add_protocol(Protocol::get("NSTextInputClient").unwrap());
 209                decl.add_method(
 210                    sel!(validAttributesForMarkedText),
 211                    valid_attributes_for_marked_text as extern "C" fn(&Object, Sel) -> id,
 212                );
 213                decl.add_method(
 214                    sel!(hasMarkedText),
 215                    has_marked_text as extern "C" fn(&Object, Sel) -> BOOL,
 216                );
 217                decl.add_method(
 218                    sel!(markedRange),
 219                    marked_range as extern "C" fn(&Object, Sel) -> NSRange,
 220                );
 221                decl.add_method(
 222                    sel!(selectedRange),
 223                    selected_range as extern "C" fn(&Object, Sel) -> NSRange,
 224                );
 225                decl.add_method(
 226                    sel!(firstRectForCharacterRange:actualRange:),
 227                    first_rect_for_character_range
 228                        as extern "C" fn(&Object, Sel, NSRange, id) -> NSRect,
 229                );
 230                decl.add_method(
 231                    sel!(insertText:replacementRange:),
 232                    insert_text as extern "C" fn(&Object, Sel, id, NSRange),
 233                );
 234                decl.add_method(
 235                    sel!(setMarkedText:selectedRange:replacementRange:),
 236                    set_marked_text as extern "C" fn(&Object, Sel, id, NSRange, NSRange),
 237                );
 238                decl.add_method(sel!(unmarkText), unmark_text as extern "C" fn(&Object, Sel));
 239                decl.add_method(
 240                    sel!(attributedSubstringForProposedRange:actualRange:),
 241                    attributed_substring_for_proposed_range
 242                        as extern "C" fn(&Object, Sel, NSRange, *mut c_void) -> id,
 243                );
 244                decl.add_method(
 245                    sel!(viewDidChangeEffectiveAppearance),
 246                    view_did_change_effective_appearance as extern "C" fn(&Object, Sel),
 247                );
 248
 249                // Suppress beep on keystrokes with modifier keys.
 250                decl.add_method(
 251                    sel!(doCommandBySelector:),
 252                    do_command_by_selector as extern "C" fn(&Object, Sel, Sel),
 253                );
 254
 255                decl.add_method(
 256                    sel!(acceptsFirstMouse:),
 257                    accepts_first_mouse as extern "C" fn(&Object, Sel, id) -> BOOL,
 258                );
 259
 260                decl.add_method(
 261                    sel!(characterIndexForPoint:),
 262                    character_index_for_point as extern "C" fn(&Object, Sel, NSPoint) -> u64,
 263                );
 264            }
 265            decl.register()
 266        };
 267        BLURRED_VIEW_CLASS = {
 268            let mut decl = ClassDecl::new("BlurredView", class!(NSVisualEffectView)).unwrap();
 269            unsafe {
 270                decl.add_method(
 271                    sel!(initWithFrame:),
 272                    blurred_view_init_with_frame as extern "C" fn(&Object, Sel, NSRect) -> id,
 273                );
 274                decl.add_method(
 275                    sel!(updateLayer),
 276                    blurred_view_update_layer as extern "C" fn(&Object, Sel),
 277                );
 278                decl.register()
 279            }
 280        };
 281    }
 282}
 283
 284pub(crate) fn convert_mouse_position(position: NSPoint, window_height: Pixels) -> Point<Pixels> {
 285    point(
 286        px(position.x as f32),
 287        // macOS screen coordinates are relative to bottom left
 288        window_height - px(position.y as f32),
 289    )
 290}
 291
 292unsafe fn build_window_class(name: &'static str, superclass: &Class) -> *const Class {
 293    unsafe {
 294        let mut decl = ClassDecl::new(name, superclass).unwrap();
 295        decl.add_ivar::<*mut c_void>(WINDOW_STATE_IVAR);
 296        decl.add_method(sel!(dealloc), dealloc_window as extern "C" fn(&Object, Sel));
 297
 298        decl.add_method(
 299            sel!(canBecomeMainWindow),
 300            yes as extern "C" fn(&Object, Sel) -> BOOL,
 301        );
 302        decl.add_method(
 303            sel!(canBecomeKeyWindow),
 304            yes as extern "C" fn(&Object, Sel) -> BOOL,
 305        );
 306        decl.add_method(
 307            sel!(windowDidResize:),
 308            window_did_resize as extern "C" fn(&Object, Sel, id),
 309        );
 310        decl.add_method(
 311            sel!(windowDidChangeOcclusionState:),
 312            window_did_change_occlusion_state as extern "C" fn(&Object, Sel, id),
 313        );
 314        decl.add_method(
 315            sel!(windowWillEnterFullScreen:),
 316            window_will_enter_fullscreen as extern "C" fn(&Object, Sel, id),
 317        );
 318        decl.add_method(
 319            sel!(windowWillExitFullScreen:),
 320            window_will_exit_fullscreen as extern "C" fn(&Object, Sel, id),
 321        );
 322        decl.add_method(
 323            sel!(windowDidMove:),
 324            window_did_move as extern "C" fn(&Object, Sel, id),
 325        );
 326        decl.add_method(
 327            sel!(windowDidChangeScreen:),
 328            window_did_change_screen as extern "C" fn(&Object, Sel, id),
 329        );
 330        decl.add_method(
 331            sel!(windowDidBecomeKey:),
 332            window_did_change_key_status as extern "C" fn(&Object, Sel, id),
 333        );
 334        decl.add_method(
 335            sel!(windowDidResignKey:),
 336            window_did_change_key_status as extern "C" fn(&Object, Sel, id),
 337        );
 338        decl.add_method(
 339            sel!(windowShouldClose:),
 340            window_should_close as extern "C" fn(&Object, Sel, id) -> BOOL,
 341        );
 342
 343        decl.add_method(sel!(close), close_window as extern "C" fn(&Object, Sel));
 344
 345        decl.add_method(
 346            sel!(draggingEntered:),
 347            dragging_entered as extern "C" fn(&Object, Sel, id) -> NSDragOperation,
 348        );
 349        decl.add_method(
 350            sel!(draggingUpdated:),
 351            dragging_updated as extern "C" fn(&Object, Sel, id) -> NSDragOperation,
 352        );
 353        decl.add_method(
 354            sel!(draggingExited:),
 355            dragging_exited as extern "C" fn(&Object, Sel, id),
 356        );
 357        decl.add_method(
 358            sel!(performDragOperation:),
 359            perform_drag_operation as extern "C" fn(&Object, Sel, id) -> BOOL,
 360        );
 361        decl.add_method(
 362            sel!(concludeDragOperation:),
 363            conclude_drag_operation as extern "C" fn(&Object, Sel, id),
 364        );
 365
 366        decl.add_method(
 367            sel!(addTitlebarAccessoryViewController:),
 368            add_titlebar_accessory_view_controller as extern "C" fn(&Object, Sel, id),
 369        );
 370
 371        decl.add_method(
 372            sel!(moveTabToNewWindow:),
 373            move_tab_to_new_window as extern "C" fn(&Object, Sel, id),
 374        );
 375
 376        decl.add_method(
 377            sel!(mergeAllWindows:),
 378            merge_all_windows as extern "C" fn(&Object, Sel, id),
 379        );
 380
 381        decl.add_method(
 382            sel!(selectNextTab:),
 383            select_next_tab as extern "C" fn(&Object, Sel, id),
 384        );
 385
 386        decl.add_method(
 387            sel!(selectPreviousTab:),
 388            select_previous_tab as extern "C" fn(&Object, Sel, id),
 389        );
 390
 391        decl.add_method(
 392            sel!(toggleTabBar:),
 393            toggle_tab_bar as extern "C" fn(&Object, Sel, id),
 394        );
 395
 396        decl.register()
 397    }
 398}
 399
 400struct MacWindowState {
 401    handle: AnyWindowHandle,
 402    foreground_executor: ForegroundExecutor,
 403    background_executor: BackgroundExecutor,
 404    native_window: id,
 405    native_view: NonNull<Object>,
 406    blurred_view: Option<id>,
 407    background_appearance: WindowBackgroundAppearance,
 408    display_link: Option<DisplayLink>,
 409    renderer: renderer::Renderer,
 410    request_frame_callback: Option<Box<dyn FnMut(RequestFrameOptions)>>,
 411    event_callback: Option<Box<dyn FnMut(PlatformInput) -> crate::DispatchEventResult>>,
 412    activate_callback: Option<Box<dyn FnMut(bool)>>,
 413    resize_callback: Option<Box<dyn FnMut(Size<Pixels>, f32)>>,
 414    moved_callback: Option<Box<dyn FnMut()>>,
 415    should_close_callback: Option<Box<dyn FnMut() -> bool>>,
 416    close_callback: Option<Box<dyn FnOnce()>>,
 417    appearance_changed_callback: Option<Box<dyn FnMut()>>,
 418    input_handler: Option<PlatformInputHandler>,
 419    last_key_equivalent: Option<KeyDownEvent>,
 420    synthetic_drag_counter: usize,
 421    traffic_light_position: Option<Point<Pixels>>,
 422    transparent_titlebar: bool,
 423    previous_modifiers_changed_event: Option<PlatformInput>,
 424    keystroke_for_do_command: Option<Keystroke>,
 425    do_command_handled: Option<bool>,
 426    external_files_dragged: bool,
 427    // Whether the next left-mouse click is also the focusing click.
 428    first_mouse: bool,
 429    fullscreen_restore_bounds: Bounds<Pixels>,
 430    move_tab_to_new_window_callback: Option<Box<dyn FnMut()>>,
 431    merge_all_windows_callback: Option<Box<dyn FnMut()>>,
 432    select_next_tab_callback: Option<Box<dyn FnMut()>>,
 433    select_previous_tab_callback: Option<Box<dyn FnMut()>>,
 434    toggle_tab_bar_callback: Option<Box<dyn FnMut()>>,
 435    activated_least_once: bool,
 436    // The parent window if this window is a sheet (Dialog kind)
 437    sheet_parent: Option<id>,
 438}
 439
 440impl MacWindowState {
 441    fn move_traffic_light(&self) {
 442        if let Some(traffic_light_position) = self.traffic_light_position {
 443            if self.is_fullscreen() {
 444                // Moving traffic lights while fullscreen doesn't work,
 445                // see https://github.com/zed-industries/zed/issues/4712
 446                return;
 447            }
 448
 449            let titlebar_height = self.titlebar_height();
 450
 451            unsafe {
 452                let close_button: id = msg_send![
 453                    self.native_window,
 454                    standardWindowButton: NSWindowButton::NSWindowCloseButton
 455                ];
 456                let min_button: id = msg_send![
 457                    self.native_window,
 458                    standardWindowButton: NSWindowButton::NSWindowMiniaturizeButton
 459                ];
 460                let zoom_button: id = msg_send![
 461                    self.native_window,
 462                    standardWindowButton: NSWindowButton::NSWindowZoomButton
 463                ];
 464
 465                let mut close_button_frame: CGRect = msg_send![close_button, frame];
 466                let mut min_button_frame: CGRect = msg_send![min_button, frame];
 467                let mut zoom_button_frame: CGRect = msg_send![zoom_button, frame];
 468                let mut origin = point(
 469                    traffic_light_position.x,
 470                    titlebar_height
 471                        - traffic_light_position.y
 472                        - px(close_button_frame.size.height as f32),
 473                );
 474                let button_spacing =
 475                    px((min_button_frame.origin.x - close_button_frame.origin.x) as f32);
 476
 477                close_button_frame.origin = CGPoint::new(origin.x.into(), origin.y.into());
 478                let _: () = msg_send![close_button, setFrame: close_button_frame];
 479                origin.x += button_spacing;
 480
 481                min_button_frame.origin = CGPoint::new(origin.x.into(), origin.y.into());
 482                let _: () = msg_send![min_button, setFrame: min_button_frame];
 483                origin.x += button_spacing;
 484
 485                zoom_button_frame.origin = CGPoint::new(origin.x.into(), origin.y.into());
 486                let _: () = msg_send![zoom_button, setFrame: zoom_button_frame];
 487                origin.x += button_spacing;
 488            }
 489        }
 490    }
 491
 492    fn start_display_link(&mut self) {
 493        self.stop_display_link();
 494        unsafe {
 495            if !self
 496                .native_window
 497                .occlusionState()
 498                .contains(NSWindowOcclusionState::NSWindowOcclusionStateVisible)
 499            {
 500                return;
 501            }
 502        }
 503        let display_id = unsafe { display_id_for_screen(self.native_window.screen()) };
 504        if let Some(mut display_link) =
 505            DisplayLink::new(display_id, self.native_view.as_ptr() as *mut c_void, step).log_err()
 506        {
 507            display_link.start().log_err();
 508            self.display_link = Some(display_link);
 509        }
 510    }
 511
 512    fn stop_display_link(&mut self) {
 513        self.display_link = None;
 514    }
 515
 516    fn is_maximized(&self) -> bool {
 517        unsafe {
 518            let bounds = self.bounds();
 519            let screen_size = self.native_window.screen().visibleFrame().into();
 520            bounds.size == screen_size
 521        }
 522    }
 523
 524    fn is_fullscreen(&self) -> bool {
 525        unsafe {
 526            let style_mask = self.native_window.styleMask();
 527            style_mask.contains(NSWindowStyleMask::NSFullScreenWindowMask)
 528        }
 529    }
 530
 531    fn bounds(&self) -> Bounds<Pixels> {
 532        let mut window_frame = unsafe { NSWindow::frame(self.native_window) };
 533        let screen = unsafe { NSWindow::screen(self.native_window) };
 534        if screen == nil {
 535            return Bounds::new(point(px(0.), px(0.)), crate::DEFAULT_WINDOW_SIZE);
 536        }
 537        let screen_frame = unsafe { NSScreen::frame(screen) };
 538
 539        // Flip the y coordinate to be top-left origin
 540        window_frame.origin.y =
 541            screen_frame.size.height - window_frame.origin.y - window_frame.size.height;
 542
 543        Bounds::new(
 544            point(
 545                px((window_frame.origin.x - screen_frame.origin.x) as f32),
 546                px((window_frame.origin.y + screen_frame.origin.y) as f32),
 547            ),
 548            size(
 549                px(window_frame.size.width as f32),
 550                px(window_frame.size.height as f32),
 551            ),
 552        )
 553    }
 554
 555    fn content_size(&self) -> Size<Pixels> {
 556        let NSSize { width, height, .. } =
 557            unsafe { NSView::frame(self.native_window.contentView()) }.size;
 558        size(px(width as f32), px(height as f32))
 559    }
 560
 561    fn scale_factor(&self) -> f32 {
 562        get_scale_factor(self.native_window)
 563    }
 564
 565    fn titlebar_height(&self) -> Pixels {
 566        unsafe {
 567            let frame = NSWindow::frame(self.native_window);
 568            let content_layout_rect: CGRect = msg_send![self.native_window, contentLayoutRect];
 569            px((frame.size.height - content_layout_rect.size.height) as f32)
 570        }
 571    }
 572
 573    fn window_bounds(&self) -> WindowBounds {
 574        if self.is_fullscreen() {
 575            WindowBounds::Fullscreen(self.fullscreen_restore_bounds)
 576        } else {
 577            WindowBounds::Windowed(self.bounds())
 578        }
 579    }
 580}
 581
 582unsafe impl Send for MacWindowState {}
 583
 584pub(crate) struct MacWindow(Arc<Mutex<MacWindowState>>);
 585
 586impl MacWindow {
 587    pub fn open(
 588        handle: AnyWindowHandle,
 589        WindowParams {
 590            bounds,
 591            titlebar,
 592            kind,
 593            is_movable,
 594            is_resizable,
 595            is_minimizable,
 596            focus,
 597            show,
 598            display_id,
 599            window_min_size,
 600            tabbing_identifier,
 601        }: WindowParams,
 602        foreground_executor: ForegroundExecutor,
 603        background_executor: BackgroundExecutor,
 604        renderer_context: renderer::Context,
 605    ) -> Self {
 606        unsafe {
 607            let pool = NSAutoreleasePool::new(nil);
 608
 609            let allows_automatic_window_tabbing = tabbing_identifier.is_some();
 610            if allows_automatic_window_tabbing {
 611                let () = msg_send![class!(NSWindow), setAllowsAutomaticWindowTabbing: YES];
 612            } else {
 613                let () = msg_send![class!(NSWindow), setAllowsAutomaticWindowTabbing: NO];
 614            }
 615
 616            let mut style_mask;
 617            if let Some(titlebar) = titlebar.as_ref() {
 618                style_mask =
 619                    NSWindowStyleMask::NSClosableWindowMask | NSWindowStyleMask::NSTitledWindowMask;
 620
 621                if is_resizable {
 622                    style_mask |= NSWindowStyleMask::NSResizableWindowMask;
 623                }
 624
 625                if is_minimizable {
 626                    style_mask |= NSWindowStyleMask::NSMiniaturizableWindowMask;
 627                }
 628
 629                if titlebar.appears_transparent {
 630                    style_mask |= NSWindowStyleMask::NSFullSizeContentViewWindowMask;
 631                }
 632            } else {
 633                style_mask = NSWindowStyleMask::NSTitledWindowMask
 634                    | NSWindowStyleMask::NSFullSizeContentViewWindowMask;
 635            }
 636
 637            let native_window: id = match kind {
 638                WindowKind::Normal => {
 639                    msg_send![WINDOW_CLASS, alloc]
 640                }
 641                WindowKind::PopUp => {
 642                    style_mask |= NSWindowStyleMaskNonactivatingPanel;
 643                    msg_send![PANEL_CLASS, alloc]
 644                }
 645                WindowKind::Floating | WindowKind::Dialog => {
 646                    msg_send![PANEL_CLASS, alloc]
 647                }
 648            };
 649
 650            let display = display_id
 651                .and_then(MacDisplay::find_by_id)
 652                .unwrap_or_else(MacDisplay::primary);
 653
 654            let mut target_screen = nil;
 655            let mut screen_frame = None;
 656
 657            let screens = NSScreen::screens(nil);
 658            let count: u64 = cocoa::foundation::NSArray::count(screens);
 659            for i in 0..count {
 660                let screen = cocoa::foundation::NSArray::objectAtIndex(screens, i);
 661                let frame = NSScreen::frame(screen);
 662                let display_id = display_id_for_screen(screen);
 663                if display_id == display.0 {
 664                    screen_frame = Some(frame);
 665                    target_screen = screen;
 666                }
 667            }
 668
 669            let screen_frame = screen_frame.unwrap_or_else(|| {
 670                let screen = NSScreen::mainScreen(nil);
 671                target_screen = screen;
 672                NSScreen::frame(screen)
 673            });
 674
 675            let window_rect = NSRect::new(
 676                NSPoint::new(
 677                    screen_frame.origin.x + bounds.origin.x.0 as f64,
 678                    screen_frame.origin.y
 679                        + (display.bounds().size.height - bounds.origin.y).0 as f64,
 680                ),
 681                NSSize::new(bounds.size.width.0 as f64, bounds.size.height.0 as f64),
 682            );
 683
 684            let native_window = native_window.initWithContentRect_styleMask_backing_defer_screen_(
 685                window_rect,
 686                style_mask,
 687                NSBackingStoreBuffered,
 688                NO,
 689                target_screen,
 690            );
 691            assert!(!native_window.is_null());
 692            let () = msg_send![
 693                native_window,
 694                registerForDraggedTypes:
 695                    NSArray::arrayWithObject(nil, NSFilenamesPboardType)
 696            ];
 697            let () = msg_send![
 698                native_window,
 699                setReleasedWhenClosed: NO
 700            ];
 701
 702            let content_view = native_window.contentView();
 703            let native_view: id = msg_send![VIEW_CLASS, alloc];
 704            let native_view = NSView::initWithFrame_(native_view, NSView::bounds(content_view));
 705            assert!(!native_view.is_null());
 706
 707            let mut window = Self(Arc::new(Mutex::new(MacWindowState {
 708                handle,
 709                foreground_executor,
 710                background_executor,
 711                native_window,
 712                native_view: NonNull::new_unchecked(native_view),
 713                blurred_view: None,
 714                background_appearance: WindowBackgroundAppearance::Opaque,
 715                display_link: None,
 716                renderer: renderer::new_renderer(
 717                    renderer_context,
 718                    native_window as *mut _,
 719                    native_view as *mut _,
 720                    bounds.size.map(|pixels| pixels.0),
 721                    false,
 722                ),
 723                request_frame_callback: None,
 724                event_callback: None,
 725                activate_callback: None,
 726                resize_callback: None,
 727                moved_callback: None,
 728                should_close_callback: None,
 729                close_callback: None,
 730                appearance_changed_callback: None,
 731                input_handler: None,
 732                last_key_equivalent: None,
 733                synthetic_drag_counter: 0,
 734                traffic_light_position: titlebar
 735                    .as_ref()
 736                    .and_then(|titlebar| titlebar.traffic_light_position),
 737                transparent_titlebar: titlebar
 738                    .as_ref()
 739                    .is_none_or(|titlebar| titlebar.appears_transparent),
 740                previous_modifiers_changed_event: None,
 741                keystroke_for_do_command: None,
 742                do_command_handled: None,
 743                external_files_dragged: false,
 744                first_mouse: false,
 745                fullscreen_restore_bounds: Bounds::default(),
 746                move_tab_to_new_window_callback: None,
 747                merge_all_windows_callback: None,
 748                select_next_tab_callback: None,
 749                select_previous_tab_callback: None,
 750                toggle_tab_bar_callback: None,
 751                activated_least_once: false,
 752                sheet_parent: None,
 753            })));
 754
 755            (*native_window).set_ivar(
 756                WINDOW_STATE_IVAR,
 757                Arc::into_raw(window.0.clone()) as *const c_void,
 758            );
 759            native_window.setDelegate_(native_window);
 760            (*native_view).set_ivar(
 761                WINDOW_STATE_IVAR,
 762                Arc::into_raw(window.0.clone()) as *const c_void,
 763            );
 764
 765            if let Some(title) = titlebar
 766                .as_ref()
 767                .and_then(|t| t.title.as_ref().map(AsRef::as_ref))
 768            {
 769                window.set_title(title);
 770            }
 771
 772            native_window.setMovable_(is_movable as BOOL);
 773
 774            if let Some(window_min_size) = window_min_size {
 775                native_window.setContentMinSize_(NSSize {
 776                    width: window_min_size.width.to_f64(),
 777                    height: window_min_size.height.to_f64(),
 778                });
 779            }
 780
 781            if titlebar.is_none_or(|titlebar| titlebar.appears_transparent) {
 782                native_window.setTitlebarAppearsTransparent_(YES);
 783                native_window.setTitleVisibility_(NSWindowTitleVisibility::NSWindowTitleHidden);
 784            }
 785
 786            native_view.setAutoresizingMask_(NSViewWidthSizable | NSViewHeightSizable);
 787            native_view.setWantsBestResolutionOpenGLSurface_(YES);
 788
 789            // From winit crate: On Mojave, views automatically become layer-backed shortly after
 790            // being added to a native_window. Changing the layer-backedness of a view breaks the
 791            // association between the view and its associated OpenGL context. To work around this,
 792            // on we explicitly make the view layer-backed up front so that AppKit doesn't do it
 793            // itself and break the association with its context.
 794            native_view.setWantsLayer(YES);
 795            let _: () = msg_send![
 796            native_view,
 797            setLayerContentsRedrawPolicy: NSViewLayerContentsRedrawDuringViewResize
 798            ];
 799
 800            content_view.addSubview_(native_view.autorelease());
 801            native_window.makeFirstResponder_(native_view);
 802
 803            let app: id = NSApplication::sharedApplication(nil);
 804            let main_window: id = msg_send![app, mainWindow];
 805            let mut sheet_parent = None;
 806
 807            match kind {
 808                WindowKind::Normal | WindowKind::Floating => {
 809                    if kind == WindowKind::Floating {
 810                        // Let the window float keep above normal windows.
 811                        native_window.setLevel_(NSFloatingWindowLevel);
 812                    } else {
 813                        native_window.setLevel_(NSNormalWindowLevel);
 814                    }
 815                    native_window.setAcceptsMouseMovedEvents_(YES);
 816
 817                    if let Some(tabbing_identifier) = tabbing_identifier {
 818                        let tabbing_id = ns_string(tabbing_identifier.as_str());
 819                        let _: () = msg_send![native_window, setTabbingIdentifier: tabbing_id];
 820                    } else {
 821                        let _: () = msg_send![native_window, setTabbingIdentifier:nil];
 822                    }
 823                }
 824                WindowKind::PopUp => {
 825                    // Use a tracking area to allow receiving MouseMoved events even when
 826                    // the window or application aren't active, which is often the case
 827                    // e.g. for notification windows.
 828                    let tracking_area: id = msg_send![class!(NSTrackingArea), alloc];
 829                    let _: () = msg_send![
 830                        tracking_area,
 831                        initWithRect: NSRect::new(NSPoint::new(0., 0.), NSSize::new(0., 0.))
 832                        options: NSTrackingMouseEnteredAndExited | NSTrackingMouseMoved | NSTrackingActiveAlways | NSTrackingInVisibleRect
 833                        owner: native_view
 834                        userInfo: nil
 835                    ];
 836                    let _: () =
 837                        msg_send![native_view, addTrackingArea: tracking_area.autorelease()];
 838
 839                    native_window.setLevel_(NSPopUpWindowLevel);
 840                    let _: () = msg_send![
 841                        native_window,
 842                        setAnimationBehavior: NSWindowAnimationBehaviorUtilityWindow
 843                    ];
 844                    native_window.setCollectionBehavior_(
 845                        NSWindowCollectionBehavior::NSWindowCollectionBehaviorCanJoinAllSpaces |
 846                        NSWindowCollectionBehavior::NSWindowCollectionBehaviorFullScreenAuxiliary
 847                    );
 848                }
 849                WindowKind::Dialog => {
 850                    if !main_window.is_null() {
 851                        let parent = {
 852                            let active_sheet: id = msg_send![main_window, attachedSheet];
 853                            if active_sheet.is_null() {
 854                                main_window
 855                            } else {
 856                                active_sheet
 857                            }
 858                        };
 859                        let _: () =
 860                            msg_send![parent, beginSheet: native_window completionHandler: nil];
 861                        sheet_parent = Some(parent);
 862                    }
 863                }
 864            }
 865
 866            if allows_automatic_window_tabbing
 867                && !main_window.is_null()
 868                && main_window != native_window
 869            {
 870                let main_window_is_fullscreen = main_window
 871                    .styleMask()
 872                    .contains(NSWindowStyleMask::NSFullScreenWindowMask);
 873                let user_tabbing_preference = Self::get_user_tabbing_preference()
 874                    .unwrap_or(UserTabbingPreference::InFullScreen);
 875                let should_add_as_tab = user_tabbing_preference == UserTabbingPreference::Always
 876                    || user_tabbing_preference == UserTabbingPreference::InFullScreen
 877                        && main_window_is_fullscreen;
 878
 879                if should_add_as_tab {
 880                    let main_window_can_tab: BOOL =
 881                        msg_send![main_window, respondsToSelector: sel!(addTabbedWindow:ordered:)];
 882                    let main_window_visible: BOOL = msg_send![main_window, isVisible];
 883
 884                    if main_window_can_tab == YES && main_window_visible == YES {
 885                        let _: () = msg_send![main_window, addTabbedWindow: native_window ordered: NSWindowOrderingMode::NSWindowAbove];
 886
 887                        // Ensure the window is visible immediately after adding the tab, since the tab bar is updated with a new entry at this point.
 888                        // Note: Calling orderFront here can break fullscreen mode (makes fullscreen windows exit fullscreen), so only do this if the main window is not fullscreen.
 889                        if !main_window_is_fullscreen {
 890                            let _: () = msg_send![native_window, orderFront: nil];
 891                        }
 892                    }
 893                }
 894            }
 895
 896            if focus && show {
 897                native_window.makeKeyAndOrderFront_(nil);
 898            } else if show {
 899                native_window.orderFront_(nil);
 900            }
 901
 902            // Set the initial position of the window to the specified origin.
 903            // Although we already specified the position using `initWithContentRect_styleMask_backing_defer_screen_`,
 904            // the window position might be incorrect if the main screen (the screen that contains the window that has focus)
 905            //  is different from the primary screen.
 906            NSWindow::setFrameTopLeftPoint_(native_window, window_rect.origin);
 907            {
 908                let mut window_state = window.0.lock();
 909                window_state.move_traffic_light();
 910                window_state.sheet_parent = sheet_parent;
 911            }
 912
 913            pool.drain();
 914
 915            window
 916        }
 917    }
 918
 919    pub fn active_window() -> Option<AnyWindowHandle> {
 920        unsafe {
 921            let app = NSApplication::sharedApplication(nil);
 922            let main_window: id = msg_send![app, mainWindow];
 923            if main_window.is_null() {
 924                return None;
 925            }
 926
 927            if msg_send![main_window, isKindOfClass: WINDOW_CLASS] {
 928                let handle = get_window_state(&*main_window).lock().handle;
 929                Some(handle)
 930            } else {
 931                None
 932            }
 933        }
 934    }
 935
 936    pub fn ordered_windows() -> Vec<AnyWindowHandle> {
 937        unsafe {
 938            let app = NSApplication::sharedApplication(nil);
 939            let windows: id = msg_send![app, orderedWindows];
 940            let count: NSUInteger = msg_send![windows, count];
 941
 942            let mut window_handles = Vec::new();
 943            for i in 0..count {
 944                let window: id = msg_send![windows, objectAtIndex:i];
 945                if msg_send![window, isKindOfClass: WINDOW_CLASS] {
 946                    let handle = get_window_state(&*window).lock().handle;
 947                    window_handles.push(handle);
 948                }
 949            }
 950
 951            window_handles
 952        }
 953    }
 954
 955    pub fn get_user_tabbing_preference() -> Option<UserTabbingPreference> {
 956        unsafe {
 957            let defaults: id = NSUserDefaults::standardUserDefaults();
 958            let domain = ns_string("NSGlobalDomain");
 959            let key = ns_string("AppleWindowTabbingMode");
 960
 961            let dict: id = msg_send![defaults, persistentDomainForName: domain];
 962            let value: id = if !dict.is_null() {
 963                msg_send![dict, objectForKey: key]
 964            } else {
 965                nil
 966            };
 967
 968            let value_str = if !value.is_null() {
 969                CStr::from_ptr(NSString::UTF8String(value)).to_string_lossy()
 970            } else {
 971                "".into()
 972            };
 973
 974            match value_str.as_ref() {
 975                "manual" => Some(UserTabbingPreference::Never),
 976                "always" => Some(UserTabbingPreference::Always),
 977                _ => Some(UserTabbingPreference::InFullScreen),
 978            }
 979        }
 980    }
 981}
 982
 983impl Drop for MacWindow {
 984    fn drop(&mut self) {
 985        let mut this = self.0.lock();
 986        this.renderer.destroy();
 987        let window = this.native_window;
 988        let sheet_parent = this.sheet_parent.take();
 989        this.display_link.take();
 990        unsafe {
 991            this.native_window.setDelegate_(nil);
 992        }
 993        this.input_handler.take();
 994        this.foreground_executor
 995            .spawn(async move {
 996                unsafe {
 997                    if let Some(parent) = sheet_parent {
 998                        let _: () = msg_send![parent, endSheet: window];
 999                    }
1000                    window.close();
1001                    window.autorelease();
1002                }
1003            })
1004            .detach();
1005    }
1006}
1007
1008impl PlatformWindow for MacWindow {
1009    fn bounds(&self) -> Bounds<Pixels> {
1010        self.0.as_ref().lock().bounds()
1011    }
1012
1013    fn window_bounds(&self) -> WindowBounds {
1014        self.0.as_ref().lock().window_bounds()
1015    }
1016
1017    fn is_maximized(&self) -> bool {
1018        self.0.as_ref().lock().is_maximized()
1019    }
1020
1021    fn content_size(&self) -> Size<Pixels> {
1022        self.0.as_ref().lock().content_size()
1023    }
1024
1025    fn resize(&mut self, size: Size<Pixels>) {
1026        let this = self.0.lock();
1027        let window = this.native_window;
1028        this.foreground_executor
1029            .spawn(async move {
1030                unsafe {
1031                    window.setContentSize_(NSSize {
1032                        width: size.width.0 as f64,
1033                        height: size.height.0 as f64,
1034                    });
1035                }
1036            })
1037            .detach();
1038    }
1039
1040    fn merge_all_windows(&self) {
1041        let native_window = self.0.lock().native_window;
1042        unsafe extern "C" fn merge_windows_async(context: *mut std::ffi::c_void) {
1043            let native_window = context as id;
1044            let _: () = msg_send![native_window, mergeAllWindows:nil];
1045        }
1046
1047        unsafe {
1048            dispatch_async_f(
1049                dispatch_get_main_queue(),
1050                native_window as *mut std::ffi::c_void,
1051                Some(merge_windows_async),
1052            );
1053        }
1054    }
1055
1056    fn move_tab_to_new_window(&self) {
1057        let native_window = self.0.lock().native_window;
1058        unsafe extern "C" fn move_tab_async(context: *mut std::ffi::c_void) {
1059            let native_window = context as id;
1060            let _: () = msg_send![native_window, moveTabToNewWindow:nil];
1061            let _: () = msg_send![native_window, makeKeyAndOrderFront: nil];
1062        }
1063
1064        unsafe {
1065            dispatch_async_f(
1066                dispatch_get_main_queue(),
1067                native_window as *mut std::ffi::c_void,
1068                Some(move_tab_async),
1069            );
1070        }
1071    }
1072
1073    fn toggle_window_tab_overview(&self) {
1074        let native_window = self.0.lock().native_window;
1075        unsafe {
1076            let _: () = msg_send![native_window, toggleTabOverview:nil];
1077        }
1078    }
1079
1080    fn set_tabbing_identifier(&self, tabbing_identifier: Option<String>) {
1081        let native_window = self.0.lock().native_window;
1082        unsafe {
1083            let allows_automatic_window_tabbing = tabbing_identifier.is_some();
1084            if allows_automatic_window_tabbing {
1085                let () = msg_send![class!(NSWindow), setAllowsAutomaticWindowTabbing: YES];
1086            } else {
1087                let () = msg_send![class!(NSWindow), setAllowsAutomaticWindowTabbing: NO];
1088            }
1089
1090            if let Some(tabbing_identifier) = tabbing_identifier {
1091                let tabbing_id = ns_string(tabbing_identifier.as_str());
1092                let _: () = msg_send![native_window, setTabbingIdentifier: tabbing_id];
1093            } else {
1094                let _: () = msg_send![native_window, setTabbingIdentifier:nil];
1095            }
1096        }
1097    }
1098
1099    fn scale_factor(&self) -> f32 {
1100        self.0.as_ref().lock().scale_factor()
1101    }
1102
1103    fn appearance(&self) -> WindowAppearance {
1104        unsafe {
1105            let appearance: id = msg_send![self.0.lock().native_window, effectiveAppearance];
1106            WindowAppearance::from_native(appearance)
1107        }
1108    }
1109
1110    fn display(&self) -> Option<Rc<dyn PlatformDisplay>> {
1111        unsafe {
1112            let screen = self.0.lock().native_window.screen();
1113            if screen.is_null() {
1114                return None;
1115            }
1116            let device_description: id = msg_send![screen, deviceDescription];
1117            let screen_number: id =
1118                NSDictionary::valueForKey_(device_description, ns_string("NSScreenNumber"));
1119
1120            let screen_number: u32 = msg_send![screen_number, unsignedIntValue];
1121
1122            Some(Rc::new(MacDisplay(screen_number)))
1123        }
1124    }
1125
1126    fn mouse_position(&self) -> Point<Pixels> {
1127        let position = unsafe {
1128            self.0
1129                .lock()
1130                .native_window
1131                .mouseLocationOutsideOfEventStream()
1132        };
1133        convert_mouse_position(position, self.content_size().height)
1134    }
1135
1136    fn modifiers(&self) -> Modifiers {
1137        unsafe {
1138            let modifiers: NSEventModifierFlags = msg_send![class!(NSEvent), modifierFlags];
1139
1140            let control = modifiers.contains(NSEventModifierFlags::NSControlKeyMask);
1141            let alt = modifiers.contains(NSEventModifierFlags::NSAlternateKeyMask);
1142            let shift = modifiers.contains(NSEventModifierFlags::NSShiftKeyMask);
1143            let command = modifiers.contains(NSEventModifierFlags::NSCommandKeyMask);
1144            let function = modifiers.contains(NSEventModifierFlags::NSFunctionKeyMask);
1145
1146            Modifiers {
1147                control,
1148                alt,
1149                shift,
1150                platform: command,
1151                function,
1152            }
1153        }
1154    }
1155
1156    fn capslock(&self) -> Capslock {
1157        unsafe {
1158            let modifiers: NSEventModifierFlags = msg_send![class!(NSEvent), modifierFlags];
1159
1160            Capslock {
1161                on: modifiers.contains(NSEventModifierFlags::NSAlphaShiftKeyMask),
1162            }
1163        }
1164    }
1165
1166    fn set_input_handler(&mut self, input_handler: PlatformInputHandler) {
1167        self.0.as_ref().lock().input_handler = Some(input_handler);
1168    }
1169
1170    fn take_input_handler(&mut self) -> Option<PlatformInputHandler> {
1171        self.0.as_ref().lock().input_handler.take()
1172    }
1173
1174    fn prompt(
1175        &self,
1176        level: PromptLevel,
1177        msg: &str,
1178        detail: Option<&str>,
1179        answers: &[PromptButton],
1180    ) -> Option<oneshot::Receiver<usize>> {
1181        // macOs applies overrides to modal window buttons after they are added.
1182        // Two most important for this logic are:
1183        // * Buttons with "Cancel" title will be displayed as the last buttons in the modal
1184        // * Last button added to the modal via `addButtonWithTitle` stays focused
1185        // * Focused buttons react on "space"/" " keypresses
1186        // * Usage of `keyEquivalent`, `makeFirstResponder` or `setInitialFirstResponder` does not change the focus
1187        //
1188        // See also https://developer.apple.com/documentation/appkit/nsalert/1524532-addbuttonwithtitle#discussion
1189        // ```
1190        // By default, the first button has a key equivalent of Return,
1191        // any button with a title of “Cancel” has a key equivalent of Escape,
1192        // and any button with the title “Don’t Save” has a key equivalent of Command-D (but only if it’s not the first button).
1193        // ```
1194        //
1195        // To avoid situations when the last element added is "Cancel" and it gets the focus
1196        // (hence stealing both ESC and Space shortcuts), we find and add one non-Cancel button
1197        // last, so it gets focus and a Space shortcut.
1198        // This way, "Save this file? Yes/No/Cancel"-ish modals will get all three buttons mapped with a key.
1199        let latest_non_cancel_label = answers
1200            .iter()
1201            .enumerate()
1202            .rev()
1203            .find(|(_, label)| !label.is_cancel())
1204            .filter(|&(label_index, _)| label_index > 0);
1205
1206        unsafe {
1207            let alert: id = msg_send![class!(NSAlert), alloc];
1208            let alert: id = msg_send![alert, init];
1209            let alert_style = match level {
1210                PromptLevel::Info => 1,
1211                PromptLevel::Warning => 0,
1212                PromptLevel::Critical => 2,
1213            };
1214            let _: () = msg_send![alert, setAlertStyle: alert_style];
1215            let _: () = msg_send![alert, setMessageText: ns_string(msg)];
1216            if let Some(detail) = detail {
1217                let _: () = msg_send![alert, setInformativeText: ns_string(detail)];
1218            }
1219
1220            for (ix, answer) in answers
1221                .iter()
1222                .enumerate()
1223                .filter(|&(ix, _)| Some(ix) != latest_non_cancel_label.map(|(ix, _)| ix))
1224            {
1225                let button: id = msg_send![alert, addButtonWithTitle: ns_string(answer.label())];
1226                let _: () = msg_send![button, setTag: ix as NSInteger];
1227
1228                if answer.is_cancel() {
1229                    // Bind Escape Key to Cancel Button
1230                    if let Some(key) = std::char::from_u32(super::events::ESCAPE_KEY as u32) {
1231                        let _: () =
1232                            msg_send![button, setKeyEquivalent: ns_string(&key.to_string())];
1233                    }
1234                }
1235            }
1236            if let Some((ix, answer)) = latest_non_cancel_label {
1237                let button: id = msg_send![alert, addButtonWithTitle: ns_string(answer.label())];
1238                let _: () = msg_send![button, setTag: ix as NSInteger];
1239            }
1240
1241            let (done_tx, done_rx) = oneshot::channel();
1242            let done_tx = Cell::new(Some(done_tx));
1243            let block = ConcreteBlock::new(move |answer: NSInteger| {
1244                let _: () = msg_send![alert, release];
1245                if let Some(done_tx) = done_tx.take() {
1246                    let _ = done_tx.send(answer.try_into().unwrap());
1247                }
1248            });
1249            let block = block.copy();
1250            let native_window = self.0.lock().native_window;
1251            let executor = self.0.lock().foreground_executor.clone();
1252            executor
1253                .spawn(async move {
1254                    let _: () = msg_send![
1255                        alert,
1256                        beginSheetModalForWindow: native_window
1257                        completionHandler: block
1258                    ];
1259                })
1260                .detach();
1261
1262            Some(done_rx)
1263        }
1264    }
1265
1266    fn activate(&self) {
1267        let window = self.0.lock().native_window;
1268        let executor = self.0.lock().foreground_executor.clone();
1269        executor
1270            .spawn(async move {
1271                unsafe {
1272                    let _: () = msg_send![window, makeKeyAndOrderFront: nil];
1273                }
1274            })
1275            .detach();
1276    }
1277
1278    fn is_active(&self) -> bool {
1279        unsafe { self.0.lock().native_window.isKeyWindow() == YES }
1280    }
1281
1282    // is_hovered is unused on macOS. See Window::is_window_hovered.
1283    fn is_hovered(&self) -> bool {
1284        false
1285    }
1286
1287    fn set_title(&mut self, title: &str) {
1288        unsafe {
1289            let app = NSApplication::sharedApplication(nil);
1290            let window = self.0.lock().native_window;
1291            let title = ns_string(title);
1292            let _: () = msg_send![app, changeWindowsItem:window title:title filename:false];
1293            let _: () = msg_send![window, setTitle: title];
1294            self.0.lock().move_traffic_light();
1295        }
1296    }
1297
1298    fn get_title(&self) -> String {
1299        unsafe {
1300            let title: id = msg_send![self.0.lock().native_window, title];
1301            if title.is_null() {
1302                "".to_string()
1303            } else {
1304                title.to_str().to_string()
1305            }
1306        }
1307    }
1308
1309    fn set_app_id(&mut self, _app_id: &str) {}
1310
1311    fn set_background_appearance(&self, background_appearance: WindowBackgroundAppearance) {
1312        let mut this = self.0.as_ref().lock();
1313        this.background_appearance = background_appearance;
1314
1315        let opaque = background_appearance == WindowBackgroundAppearance::Opaque;
1316        this.renderer.update_transparency(!opaque);
1317
1318        unsafe {
1319            this.native_window.setOpaque_(opaque as BOOL);
1320            let background_color = if opaque {
1321                NSColor::colorWithSRGBRed_green_blue_alpha_(nil, 0f64, 0f64, 0f64, 1f64)
1322            } else {
1323                // Not using `+[NSColor clearColor]` to avoid broken shadow.
1324                NSColor::colorWithSRGBRed_green_blue_alpha_(nil, 0f64, 0f64, 0f64, 0.0001)
1325            };
1326            this.native_window.setBackgroundColor_(background_color);
1327
1328            if NSAppKitVersionNumber < NSAppKitVersionNumber12_0 {
1329                // Whether `-[NSVisualEffectView respondsToSelector:@selector(_updateProxyLayer)]`.
1330                // On macOS Catalina/Big Sur `NSVisualEffectView` doesn’t own concrete sublayers
1331                // but uses a `CAProxyLayer`. Use the legacy WindowServer API.
1332                let blur_radius = if background_appearance == WindowBackgroundAppearance::Blurred {
1333                    80
1334                } else {
1335                    0
1336                };
1337
1338                let window_number = this.native_window.windowNumber();
1339                CGSSetWindowBackgroundBlurRadius(CGSMainConnectionID(), window_number, blur_radius);
1340            } else {
1341                // On newer macOS `NSVisualEffectView` manages the effect layer directly. Using it
1342                // could have a better performance (it downsamples the backdrop) and more control
1343                // over the effect layer.
1344                if background_appearance != WindowBackgroundAppearance::Blurred {
1345                    if let Some(blur_view) = this.blurred_view {
1346                        NSView::removeFromSuperview(blur_view);
1347                        this.blurred_view = None;
1348                    }
1349                } else if this.blurred_view.is_none() {
1350                    let content_view = this.native_window.contentView();
1351                    let frame = NSView::bounds(content_view);
1352                    let mut blur_view: id = msg_send![BLURRED_VIEW_CLASS, alloc];
1353                    blur_view = NSView::initWithFrame_(blur_view, frame);
1354                    blur_view.setAutoresizingMask_(NSViewWidthSizable | NSViewHeightSizable);
1355
1356                    let _: () = msg_send![
1357                        content_view,
1358                        addSubview: blur_view
1359                        positioned: NSWindowOrderingMode::NSWindowBelow
1360                        relativeTo: nil
1361                    ];
1362                    this.blurred_view = Some(blur_view.autorelease());
1363                }
1364            }
1365        }
1366    }
1367
1368    fn background_appearance(&self) -> WindowBackgroundAppearance {
1369        self.0.as_ref().lock().background_appearance
1370    }
1371
1372    fn is_subpixel_rendering_supported(&self) -> bool {
1373        false
1374    }
1375
1376    fn set_edited(&mut self, edited: bool) {
1377        unsafe {
1378            let window = self.0.lock().native_window;
1379            msg_send![window, setDocumentEdited: edited as BOOL]
1380        }
1381
1382        // Changing the document edited state resets the traffic light position,
1383        // so we have to move it again.
1384        self.0.lock().move_traffic_light();
1385    }
1386
1387    fn show_character_palette(&self) {
1388        let this = self.0.lock();
1389        let window = this.native_window;
1390        this.foreground_executor
1391            .spawn(async move {
1392                unsafe {
1393                    let app = NSApplication::sharedApplication(nil);
1394                    let _: () = msg_send![app, orderFrontCharacterPalette: window];
1395                }
1396            })
1397            .detach();
1398    }
1399
1400    fn minimize(&self) {
1401        let window = self.0.lock().native_window;
1402        unsafe {
1403            window.miniaturize_(nil);
1404        }
1405    }
1406
1407    fn zoom(&self) {
1408        let this = self.0.lock();
1409        let window = this.native_window;
1410        this.foreground_executor
1411            .spawn(async move {
1412                unsafe {
1413                    window.zoom_(nil);
1414                }
1415            })
1416            .detach();
1417    }
1418
1419    fn toggle_fullscreen(&self) {
1420        let this = self.0.lock();
1421        let window = this.native_window;
1422        this.foreground_executor
1423            .spawn(async move {
1424                unsafe {
1425                    window.toggleFullScreen_(nil);
1426                }
1427            })
1428            .detach();
1429    }
1430
1431    fn is_fullscreen(&self) -> bool {
1432        let this = self.0.lock();
1433        let window = this.native_window;
1434
1435        unsafe {
1436            window
1437                .styleMask()
1438                .contains(NSWindowStyleMask::NSFullScreenWindowMask)
1439        }
1440    }
1441
1442    fn on_request_frame(&self, callback: Box<dyn FnMut(RequestFrameOptions)>) {
1443        self.0.as_ref().lock().request_frame_callback = Some(callback);
1444    }
1445
1446    fn on_input(&self, callback: Box<dyn FnMut(PlatformInput) -> crate::DispatchEventResult>) {
1447        self.0.as_ref().lock().event_callback = Some(callback);
1448    }
1449
1450    fn on_active_status_change(&self, callback: Box<dyn FnMut(bool)>) {
1451        self.0.as_ref().lock().activate_callback = Some(callback);
1452    }
1453
1454    fn on_hover_status_change(&self, _: Box<dyn FnMut(bool)>) {}
1455
1456    fn on_resize(&self, callback: Box<dyn FnMut(Size<Pixels>, f32)>) {
1457        self.0.as_ref().lock().resize_callback = Some(callback);
1458    }
1459
1460    fn on_moved(&self, callback: Box<dyn FnMut()>) {
1461        self.0.as_ref().lock().moved_callback = Some(callback);
1462    }
1463
1464    fn on_should_close(&self, callback: Box<dyn FnMut() -> bool>) {
1465        self.0.as_ref().lock().should_close_callback = Some(callback);
1466    }
1467
1468    fn on_close(&self, callback: Box<dyn FnOnce()>) {
1469        self.0.as_ref().lock().close_callback = Some(callback);
1470    }
1471
1472    fn on_hit_test_window_control(&self, _callback: Box<dyn FnMut() -> Option<WindowControlArea>>) {
1473    }
1474
1475    fn on_appearance_changed(&self, callback: Box<dyn FnMut()>) {
1476        self.0.lock().appearance_changed_callback = Some(callback);
1477    }
1478
1479    fn tabbed_windows(&self) -> Option<Vec<SystemWindowTab>> {
1480        unsafe {
1481            let windows: id = msg_send![self.0.lock().native_window, tabbedWindows];
1482            if windows.is_null() {
1483                return None;
1484            }
1485
1486            let count: NSUInteger = msg_send![windows, count];
1487            let mut result = Vec::new();
1488            for i in 0..count {
1489                let window: id = msg_send![windows, objectAtIndex:i];
1490                if msg_send![window, isKindOfClass: WINDOW_CLASS] {
1491                    let handle = get_window_state(&*window).lock().handle;
1492                    let title: id = msg_send![window, title];
1493                    let title = SharedString::from(title.to_str().to_string());
1494
1495                    result.push(SystemWindowTab::new(title, handle));
1496                }
1497            }
1498
1499            Some(result)
1500        }
1501    }
1502
1503    fn tab_bar_visible(&self) -> bool {
1504        unsafe {
1505            let tab_group: id = msg_send![self.0.lock().native_window, tabGroup];
1506            if tab_group.is_null() {
1507                false
1508            } else {
1509                let tab_bar_visible: BOOL = msg_send![tab_group, isTabBarVisible];
1510                tab_bar_visible == YES
1511            }
1512        }
1513    }
1514
1515    fn on_move_tab_to_new_window(&self, callback: Box<dyn FnMut()>) {
1516        self.0.as_ref().lock().move_tab_to_new_window_callback = Some(callback);
1517    }
1518
1519    fn on_merge_all_windows(&self, callback: Box<dyn FnMut()>) {
1520        self.0.as_ref().lock().merge_all_windows_callback = Some(callback);
1521    }
1522
1523    fn on_select_next_tab(&self, callback: Box<dyn FnMut()>) {
1524        self.0.as_ref().lock().select_next_tab_callback = Some(callback);
1525    }
1526
1527    fn on_select_previous_tab(&self, callback: Box<dyn FnMut()>) {
1528        self.0.as_ref().lock().select_previous_tab_callback = Some(callback);
1529    }
1530
1531    fn on_toggle_tab_bar(&self, callback: Box<dyn FnMut()>) {
1532        self.0.as_ref().lock().toggle_tab_bar_callback = Some(callback);
1533    }
1534
1535    fn draw(&self, scene: &crate::Scene) {
1536        let mut this = self.0.lock();
1537        this.renderer.draw(scene);
1538    }
1539
1540    fn sprite_atlas(&self) -> Arc<dyn PlatformAtlas> {
1541        self.0.lock().renderer.sprite_atlas().clone()
1542    }
1543
1544    fn gpu_specs(&self) -> Option<crate::GpuSpecs> {
1545        None
1546    }
1547
1548    fn update_ime_position(&self, _bounds: Bounds<Pixels>) {
1549        let executor = self.0.lock().foreground_executor.clone();
1550        executor
1551            .spawn(async move {
1552                unsafe {
1553                    let input_context: id =
1554                        msg_send![class!(NSTextInputContext), currentInputContext];
1555                    if input_context.is_null() {
1556                        return;
1557                    }
1558                    let _: () = msg_send![input_context, invalidateCharacterCoordinates];
1559                }
1560            })
1561            .detach()
1562    }
1563
1564    fn titlebar_double_click(&self) {
1565        let this = self.0.lock();
1566        let window = this.native_window;
1567        this.foreground_executor
1568            .spawn(async move {
1569                unsafe {
1570                    let defaults: id = NSUserDefaults::standardUserDefaults();
1571                    let domain = ns_string("NSGlobalDomain");
1572                    let key = ns_string("AppleActionOnDoubleClick");
1573
1574                    let dict: id = msg_send![defaults, persistentDomainForName: domain];
1575                    let action: id = if !dict.is_null() {
1576                        msg_send![dict, objectForKey: key]
1577                    } else {
1578                        nil
1579                    };
1580
1581                    let action_str = if !action.is_null() {
1582                        CStr::from_ptr(NSString::UTF8String(action)).to_string_lossy()
1583                    } else {
1584                        "".into()
1585                    };
1586
1587                    match action_str.as_ref() {
1588                        "None" => {
1589                            // "Do Nothing" selected, so do no action
1590                        }
1591                        "Minimize" => {
1592                            window.miniaturize_(nil);
1593                        }
1594                        "Maximize" => {
1595                            window.zoom_(nil);
1596                        }
1597                        "Fill" => {
1598                            // There is no documented API for "Fill" action, so we'll just zoom the window
1599                            window.zoom_(nil);
1600                        }
1601                        _ => {
1602                            window.zoom_(nil);
1603                        }
1604                    }
1605                }
1606            })
1607            .detach();
1608    }
1609
1610    fn start_window_move(&self) {
1611        let this = self.0.lock();
1612        let window = this.native_window;
1613
1614        unsafe {
1615            let app = NSApplication::sharedApplication(nil);
1616            let mut event: id = msg_send![app, currentEvent];
1617            let _: () = msg_send![window, performWindowDragWithEvent: event];
1618        }
1619    }
1620
1621    #[cfg(any(test, feature = "test-support"))]
1622    fn render_to_image(&self, scene: &crate::Scene) -> Result<RgbaImage> {
1623        let mut this = self.0.lock();
1624        this.renderer.render_to_image(scene)
1625    }
1626}
1627
1628impl rwh::HasWindowHandle for MacWindow {
1629    fn window_handle(&self) -> Result<rwh::WindowHandle<'_>, rwh::HandleError> {
1630        // SAFETY: The AppKitWindowHandle is a wrapper around a pointer to an NSView
1631        unsafe {
1632            Ok(rwh::WindowHandle::borrow_raw(rwh::RawWindowHandle::AppKit(
1633                rwh::AppKitWindowHandle::new(self.0.lock().native_view.cast()),
1634            )))
1635        }
1636    }
1637}
1638
1639impl rwh::HasDisplayHandle for MacWindow {
1640    fn display_handle(&self) -> Result<rwh::DisplayHandle<'_>, rwh::HandleError> {
1641        // SAFETY: This is a no-op on macOS
1642        unsafe {
1643            Ok(rwh::DisplayHandle::borrow_raw(
1644                rwh::AppKitDisplayHandle::new().into(),
1645            ))
1646        }
1647    }
1648}
1649
1650fn get_scale_factor(native_window: id) -> f32 {
1651    let factor = unsafe {
1652        let screen: id = msg_send![native_window, screen];
1653        if screen.is_null() {
1654            return 2.0;
1655        }
1656        NSScreen::backingScaleFactor(screen) as f32
1657    };
1658
1659    // We are not certain what triggers this, but it seems that sometimes
1660    // this method would return 0 (https://github.com/zed-industries/zed/issues/6412)
1661    // It seems most likely that this would happen if the window has no screen
1662    // (if it is off-screen), though we'd expect to see viewDidChangeBackingProperties before
1663    // it was rendered for real.
1664    // Regardless, attempt to avoid the issue here.
1665    if factor == 0.0 { 2. } else { factor }
1666}
1667
1668unsafe fn get_window_state(object: &Object) -> Arc<Mutex<MacWindowState>> {
1669    unsafe {
1670        let raw: *mut c_void = *object.get_ivar(WINDOW_STATE_IVAR);
1671        let rc1 = Arc::from_raw(raw as *mut Mutex<MacWindowState>);
1672        let rc2 = rc1.clone();
1673        mem::forget(rc1);
1674        rc2
1675    }
1676}
1677
1678unsafe fn drop_window_state(object: &Object) {
1679    unsafe {
1680        let raw: *mut c_void = *object.get_ivar(WINDOW_STATE_IVAR);
1681        Arc::from_raw(raw as *mut Mutex<MacWindowState>);
1682    }
1683}
1684
1685extern "C" fn yes(_: &Object, _: Sel) -> BOOL {
1686    YES
1687}
1688
1689extern "C" fn dealloc_window(this: &Object, _: Sel) {
1690    unsafe {
1691        drop_window_state(this);
1692        let _: () = msg_send![super(this, class!(NSWindow)), dealloc];
1693    }
1694}
1695
1696extern "C" fn dealloc_view(this: &Object, _: Sel) {
1697    unsafe {
1698        drop_window_state(this);
1699        let _: () = msg_send![super(this, class!(NSView)), dealloc];
1700    }
1701}
1702
1703extern "C" fn handle_key_equivalent(this: &Object, _: Sel, native_event: id) -> BOOL {
1704    handle_key_event(this, native_event, true)
1705}
1706
1707extern "C" fn handle_key_down(this: &Object, _: Sel, native_event: id) {
1708    handle_key_event(this, native_event, false);
1709}
1710
1711extern "C" fn handle_key_up(this: &Object, _: Sel, native_event: id) {
1712    handle_key_event(this, native_event, false);
1713}
1714
1715// Things to test if you're modifying this method:
1716//  U.S. layout:
1717//   - The IME consumes characters like 'j' and 'k', which makes paging through `less` in
1718//     the terminal behave incorrectly by default. This behavior should be patched by our
1719//     IME integration
1720//   - `alt-t` should open the tasks menu
1721//   - In vim mode, this keybinding should work:
1722//     ```
1723//        {
1724//          "context": "Editor && vim_mode == insert",
1725//          "bindings": {"j j": "vim::NormalBefore"}
1726//        }
1727//     ```
1728//     and typing 'j k' in insert mode with this keybinding should insert the two characters
1729//  Brazilian layout:
1730//   - `" space` should create an unmarked quote
1731//   - `" backspace` should delete the marked quote
1732//   - `" "`should create an unmarked quote and a second marked quote
1733//   - `" up` should insert a quote, unmark it, and move up one line
1734//   - `" cmd-down` should insert a quote, unmark it, and move to the end of the file
1735//   - `cmd-ctrl-space` and clicking on an emoji should type it
1736//  Czech (QWERTY) layout:
1737//   - in vim mode `option-4`  should go to end of line (same as $)
1738//  Japanese (Romaji) layout:
1739//   - type `a i left down up enter enter` should create an unmarked text "愛"
1740extern "C" fn handle_key_event(this: &Object, native_event: id, key_equivalent: bool) -> BOOL {
1741    let window_state = unsafe { get_window_state(this) };
1742    let mut lock = window_state.as_ref().lock();
1743
1744    let window_height = lock.content_size().height;
1745    let event = unsafe { PlatformInput::from_native(native_event, Some(window_height)) };
1746
1747    let Some(event) = event else {
1748        return NO;
1749    };
1750
1751    let run_callback = |event: PlatformInput| -> BOOL {
1752        let mut callback = window_state.as_ref().lock().event_callback.take();
1753        let handled: BOOL = if let Some(callback) = callback.as_mut() {
1754            !callback(event).propagate as BOOL
1755        } else {
1756            NO
1757        };
1758        window_state.as_ref().lock().event_callback = callback;
1759        handled
1760    };
1761
1762    match event {
1763        PlatformInput::KeyDown(mut key_down_event) => {
1764            // For certain keystrokes, macOS will first dispatch a "key equivalent" event.
1765            // If that event isn't handled, it will then dispatch a "key down" event. GPUI
1766            // makes no distinction between these two types of events, so we need to ignore
1767            // the "key down" event if we've already just processed its "key equivalent" version.
1768            if key_equivalent {
1769                lock.last_key_equivalent = Some(key_down_event.clone());
1770            } else if lock.last_key_equivalent.take().as_ref() == Some(&key_down_event) {
1771                return NO;
1772            }
1773
1774            drop(lock);
1775
1776            let is_composing =
1777                with_input_handler(this, |input_handler| input_handler.marked_text_range())
1778                    .flatten()
1779                    .is_some();
1780
1781            // If we're composing, send the key to the input handler first;
1782            // otherwise we only send to the input handler if we don't have a matching binding.
1783            // The input handler may call `do_command_by_selector` if it doesn't know how to handle
1784            // a key. If it does so, it will return YES so we won't send the key twice.
1785            // We also do this for non-printing keys (like arrow keys and escape) as the IME menu
1786            // may need them even if there is no marked text;
1787            // however we skip keys with control or the input handler adds control-characters to the buffer.
1788            // and keys with function, as the input handler swallows them.
1789            if is_composing
1790                || (key_down_event.keystroke.key_char.is_none()
1791                    && !key_down_event.keystroke.modifiers.control
1792                    && !key_down_event.keystroke.modifiers.function)
1793            {
1794                {
1795                    let mut lock = window_state.as_ref().lock();
1796                    lock.keystroke_for_do_command = Some(key_down_event.keystroke.clone());
1797                    lock.do_command_handled.take();
1798                    drop(lock);
1799                }
1800
1801                let handled: BOOL = unsafe {
1802                    let input_context: id = msg_send![this, inputContext];
1803                    msg_send![input_context, handleEvent: native_event]
1804                };
1805                window_state.as_ref().lock().keystroke_for_do_command.take();
1806                if let Some(handled) = window_state.as_ref().lock().do_command_handled.take() {
1807                    return handled as BOOL;
1808                } else if handled == YES {
1809                    return YES;
1810                }
1811
1812                let handled = run_callback(PlatformInput::KeyDown(key_down_event));
1813                return handled;
1814            }
1815
1816            let handled = run_callback(PlatformInput::KeyDown(key_down_event.clone()));
1817            if handled == YES {
1818                return YES;
1819            }
1820
1821            if key_down_event.is_held
1822                && let Some(key_char) = key_down_event.keystroke.key_char.as_ref()
1823            {
1824                let handled = with_input_handler(this, |input_handler| {
1825                    if !input_handler.apple_press_and_hold_enabled() {
1826                        input_handler.replace_text_in_range(None, key_char);
1827                        return YES;
1828                    }
1829                    NO
1830                });
1831                if handled == Some(YES) {
1832                    return YES;
1833                }
1834            }
1835
1836            // Don't send key equivalents to the input handler if there are key modifiers other
1837            // than Function key, or macOS shortcuts like cmd-` will stop working.
1838            if key_equivalent && key_down_event.keystroke.modifiers != Modifiers::function() {
1839                return NO;
1840            }
1841
1842            unsafe {
1843                let input_context: id = msg_send![this, inputContext];
1844                msg_send![input_context, handleEvent: native_event]
1845            }
1846        }
1847
1848        PlatformInput::KeyUp(_) => {
1849            drop(lock);
1850            run_callback(event)
1851        }
1852
1853        _ => NO,
1854    }
1855}
1856
1857extern "C" fn handle_view_event(this: &Object, _: Sel, native_event: id) {
1858    let window_state = unsafe { get_window_state(this) };
1859    let weak_window_state = Arc::downgrade(&window_state);
1860    let mut lock = window_state.as_ref().lock();
1861    let window_height = lock.content_size().height;
1862    let event = unsafe { PlatformInput::from_native(native_event, Some(window_height)) };
1863
1864    if let Some(mut event) = event {
1865        match &mut event {
1866            PlatformInput::MouseDown(
1867                event @ MouseDownEvent {
1868                    button: MouseButton::Left,
1869                    modifiers: Modifiers { control: true, .. },
1870                    ..
1871                },
1872            ) => {
1873                // On mac, a ctrl-left click should be handled as a right click.
1874                *event = MouseDownEvent {
1875                    button: MouseButton::Right,
1876                    modifiers: Modifiers {
1877                        control: false,
1878                        ..event.modifiers
1879                    },
1880                    click_count: 1,
1881                    ..*event
1882                };
1883            }
1884
1885            // Handles focusing click.
1886            PlatformInput::MouseDown(
1887                event @ MouseDownEvent {
1888                    button: MouseButton::Left,
1889                    ..
1890                },
1891            ) if (lock.first_mouse) => {
1892                *event = MouseDownEvent {
1893                    first_mouse: true,
1894                    ..*event
1895                };
1896                lock.first_mouse = false;
1897            }
1898
1899            // Because we map a ctrl-left_down to a right_down -> right_up let's ignore
1900            // the ctrl-left_up to avoid having a mismatch in button down/up events if the
1901            // user is still holding ctrl when releasing the left mouse button
1902            PlatformInput::MouseUp(
1903                event @ MouseUpEvent {
1904                    button: MouseButton::Left,
1905                    modifiers: Modifiers { control: true, .. },
1906                    ..
1907                },
1908            ) => {
1909                *event = MouseUpEvent {
1910                    button: MouseButton::Right,
1911                    modifiers: Modifiers {
1912                        control: false,
1913                        ..event.modifiers
1914                    },
1915                    click_count: 1,
1916                    ..*event
1917                };
1918            }
1919
1920            _ => {}
1921        };
1922
1923        match &event {
1924            PlatformInput::MouseDown(_) => {
1925                drop(lock);
1926                unsafe {
1927                    let input_context: id = msg_send![this, inputContext];
1928                    msg_send![input_context, handleEvent: native_event]
1929                }
1930                lock = window_state.as_ref().lock();
1931            }
1932            PlatformInput::MouseMove(
1933                event @ MouseMoveEvent {
1934                    pressed_button: Some(_),
1935                    ..
1936                },
1937            ) => {
1938                // Synthetic drag is used for selecting long buffer contents while buffer is being scrolled.
1939                // External file drag and drop is able to emit its own synthetic mouse events which will conflict
1940                // with these ones.
1941                if !lock.external_files_dragged {
1942                    lock.synthetic_drag_counter += 1;
1943                    let executor = lock.foreground_executor.clone();
1944                    executor
1945                        .spawn(synthetic_drag(
1946                            weak_window_state,
1947                            lock.synthetic_drag_counter,
1948                            event.clone(),
1949                            lock.background_executor.clone(),
1950                        ))
1951                        .detach();
1952                }
1953            }
1954
1955            PlatformInput::MouseUp(MouseUpEvent { .. }) => {
1956                lock.synthetic_drag_counter += 1;
1957            }
1958
1959            PlatformInput::ModifiersChanged(ModifiersChangedEvent {
1960                modifiers,
1961                capslock,
1962            }) => {
1963                // Only raise modifiers changed event when they have actually changed
1964                if let Some(PlatformInput::ModifiersChanged(ModifiersChangedEvent {
1965                    modifiers: prev_modifiers,
1966                    capslock: prev_capslock,
1967                })) = &lock.previous_modifiers_changed_event
1968                    && prev_modifiers == modifiers
1969                    && prev_capslock == capslock
1970                {
1971                    return;
1972                }
1973
1974                lock.previous_modifiers_changed_event = Some(event.clone());
1975            }
1976
1977            _ => {}
1978        }
1979
1980        if let Some(mut callback) = lock.event_callback.take() {
1981            drop(lock);
1982            callback(event);
1983            window_state.lock().event_callback = Some(callback);
1984        }
1985    }
1986}
1987
1988extern "C" fn window_did_change_occlusion_state(this: &Object, _: Sel, _: id) {
1989    let window_state = unsafe { get_window_state(this) };
1990    let lock = &mut *window_state.lock();
1991    unsafe {
1992        if lock
1993            .native_window
1994            .occlusionState()
1995            .contains(NSWindowOcclusionState::NSWindowOcclusionStateVisible)
1996        {
1997            lock.move_traffic_light();
1998            lock.start_display_link();
1999        } else {
2000            lock.stop_display_link();
2001        }
2002    }
2003}
2004
2005extern "C" fn window_did_resize(this: &Object, _: Sel, _: id) {
2006    let window_state = unsafe { get_window_state(this) };
2007    window_state.as_ref().lock().move_traffic_light();
2008}
2009
2010extern "C" fn window_will_enter_fullscreen(this: &Object, _: Sel, _: id) {
2011    let window_state = unsafe { get_window_state(this) };
2012    let mut lock = window_state.as_ref().lock();
2013    lock.fullscreen_restore_bounds = lock.bounds();
2014
2015    let min_version = NSOperatingSystemVersion::new(15, 3, 0);
2016
2017    if is_macos_version_at_least(min_version) {
2018        unsafe {
2019            lock.native_window.setTitlebarAppearsTransparent_(NO);
2020        }
2021    }
2022}
2023
2024extern "C" fn window_will_exit_fullscreen(this: &Object, _: Sel, _: id) {
2025    let window_state = unsafe { get_window_state(this) };
2026    let mut lock = window_state.as_ref().lock();
2027
2028    let min_version = NSOperatingSystemVersion::new(15, 3, 0);
2029
2030    if is_macos_version_at_least(min_version) && lock.transparent_titlebar {
2031        unsafe {
2032            lock.native_window.setTitlebarAppearsTransparent_(YES);
2033        }
2034    }
2035}
2036
2037pub(crate) fn is_macos_version_at_least(version: NSOperatingSystemVersion) -> bool {
2038    unsafe { NSProcessInfo::processInfo(nil).isOperatingSystemAtLeastVersion(version) }
2039}
2040
2041extern "C" fn window_did_move(this: &Object, _: Sel, _: id) {
2042    let window_state = unsafe { get_window_state(this) };
2043    let mut lock = window_state.as_ref().lock();
2044    if let Some(mut callback) = lock.moved_callback.take() {
2045        drop(lock);
2046        callback();
2047        window_state.lock().moved_callback = Some(callback);
2048    }
2049}
2050
2051// Update the window scale factor and drawable size, and call the resize callback if any.
2052fn update_window_scale_factor(window_state: &Arc<Mutex<MacWindowState>>) {
2053    let mut lock = window_state.as_ref().lock();
2054    let scale_factor = lock.scale_factor();
2055    let size = lock.content_size();
2056    let drawable_size = size.to_device_pixels(scale_factor);
2057    unsafe {
2058        let _: () = msg_send![
2059            lock.renderer.layer(),
2060            setContentsScale: scale_factor as f64
2061        ];
2062    }
2063
2064    lock.renderer.update_drawable_size(drawable_size);
2065
2066    if let Some(mut callback) = lock.resize_callback.take() {
2067        let content_size = lock.content_size();
2068        let scale_factor = lock.scale_factor();
2069        drop(lock);
2070        callback(content_size, scale_factor);
2071        window_state.as_ref().lock().resize_callback = Some(callback);
2072    };
2073}
2074
2075extern "C" fn window_did_change_screen(this: &Object, _: Sel, _: id) {
2076    let window_state = unsafe { get_window_state(this) };
2077    let mut lock = window_state.as_ref().lock();
2078    lock.start_display_link();
2079    drop(lock);
2080    update_window_scale_factor(&window_state);
2081}
2082
2083extern "C" fn window_did_change_key_status(this: &Object, selector: Sel, _: id) {
2084    let window_state = unsafe { get_window_state(this) };
2085    let mut lock = window_state.lock();
2086    let is_active = unsafe { lock.native_window.isKeyWindow() == YES };
2087
2088    // When opening a pop-up while the application isn't active, Cocoa sends a spurious
2089    // `windowDidBecomeKey` message to the previous key window even though that window
2090    // isn't actually key. This causes a bug if the application is later activated while
2091    // the pop-up is still open, making it impossible to activate the previous key window
2092    // even if the pop-up gets closed. The only way to activate it again is to de-activate
2093    // the app and re-activate it, which is a pretty bad UX.
2094    // The following code detects the spurious event and invokes `resignKeyWindow`:
2095    // in theory, we're not supposed to invoke this method manually but it balances out
2096    // the spurious `becomeKeyWindow` event and helps us work around that bug.
2097    if selector == sel!(windowDidBecomeKey:) && !is_active {
2098        unsafe {
2099            let _: () = msg_send![lock.native_window, resignKeyWindow];
2100            return;
2101        }
2102    }
2103
2104    let executor = lock.foreground_executor.clone();
2105    drop(lock);
2106
2107    // When a window becomes active, trigger an immediate synchronous frame request to prevent
2108    // tab flicker when switching between windows in native tabs mode.
2109    //
2110    // This is only done on subsequent activations (not the first) to ensure the initial focus
2111    // path is properly established. Without this guard, the focus state would remain unset until
2112    // the first mouse click, causing keybindings to be non-functional.
2113    if selector == sel!(windowDidBecomeKey:) && is_active {
2114        let window_state = unsafe { get_window_state(this) };
2115        let mut lock = window_state.lock();
2116
2117        if lock.activated_least_once {
2118            if let Some(mut callback) = lock.request_frame_callback.take() {
2119                #[cfg(not(feature = "macos-blade"))]
2120                lock.renderer.set_presents_with_transaction(true);
2121                lock.stop_display_link();
2122                drop(lock);
2123                callback(Default::default());
2124
2125                let mut lock = window_state.lock();
2126                lock.request_frame_callback = Some(callback);
2127                #[cfg(not(feature = "macos-blade"))]
2128                lock.renderer.set_presents_with_transaction(false);
2129                lock.start_display_link();
2130            }
2131        } else {
2132            lock.activated_least_once = true;
2133        }
2134    }
2135
2136    executor
2137        .spawn(async move {
2138            let mut lock = window_state.as_ref().lock();
2139            if is_active {
2140                lock.move_traffic_light();
2141            }
2142
2143            if let Some(mut callback) = lock.activate_callback.take() {
2144                drop(lock);
2145                callback(is_active);
2146                window_state.lock().activate_callback = Some(callback);
2147            };
2148        })
2149        .detach();
2150}
2151
2152extern "C" fn window_should_close(this: &Object, _: Sel, _: id) -> BOOL {
2153    let window_state = unsafe { get_window_state(this) };
2154    let mut lock = window_state.as_ref().lock();
2155    if let Some(mut callback) = lock.should_close_callback.take() {
2156        drop(lock);
2157        let should_close = callback();
2158        window_state.lock().should_close_callback = Some(callback);
2159        should_close as BOOL
2160    } else {
2161        YES
2162    }
2163}
2164
2165extern "C" fn close_window(this: &Object, _: Sel) {
2166    unsafe {
2167        let close_callback = {
2168            let window_state = get_window_state(this);
2169            let mut lock = window_state.as_ref().lock();
2170            lock.close_callback.take()
2171        };
2172
2173        if let Some(callback) = close_callback {
2174            callback();
2175        }
2176
2177        let _: () = msg_send![super(this, class!(NSWindow)), close];
2178    }
2179}
2180
2181extern "C" fn make_backing_layer(this: &Object, _: Sel) -> id {
2182    let window_state = unsafe { get_window_state(this) };
2183    let window_state = window_state.as_ref().lock();
2184    window_state.renderer.layer_ptr() as id
2185}
2186
2187extern "C" fn view_did_change_backing_properties(this: &Object, _: Sel) {
2188    let window_state = unsafe { get_window_state(this) };
2189    update_window_scale_factor(&window_state);
2190}
2191
2192extern "C" fn set_frame_size(this: &Object, _: Sel, size: NSSize) {
2193    let window_state = unsafe { get_window_state(this) };
2194    let mut lock = window_state.as_ref().lock();
2195
2196    let new_size = Size::<Pixels>::from(size);
2197    let old_size = unsafe {
2198        let old_frame: NSRect = msg_send![this, frame];
2199        Size::<Pixels>::from(old_frame.size)
2200    };
2201
2202    if old_size == new_size {
2203        return;
2204    }
2205
2206    unsafe {
2207        let _: () = msg_send![super(this, class!(NSView)), setFrameSize: size];
2208    }
2209
2210    let scale_factor = lock.scale_factor();
2211    let drawable_size = new_size.to_device_pixels(scale_factor);
2212    lock.renderer.update_drawable_size(drawable_size);
2213
2214    if let Some(mut callback) = lock.resize_callback.take() {
2215        let content_size = lock.content_size();
2216        let scale_factor = lock.scale_factor();
2217        drop(lock);
2218        callback(content_size, scale_factor);
2219        window_state.lock().resize_callback = Some(callback);
2220    };
2221}
2222
2223extern "C" fn display_layer(this: &Object, _: Sel, _: id) {
2224    let window_state = unsafe { get_window_state(this) };
2225    let mut lock = window_state.lock();
2226    if let Some(mut callback) = lock.request_frame_callback.take() {
2227        #[cfg(not(feature = "macos-blade"))]
2228        lock.renderer.set_presents_with_transaction(true);
2229        lock.stop_display_link();
2230        drop(lock);
2231        callback(Default::default());
2232
2233        let mut lock = window_state.lock();
2234        lock.request_frame_callback = Some(callback);
2235        #[cfg(not(feature = "macos-blade"))]
2236        lock.renderer.set_presents_with_transaction(false);
2237        lock.start_display_link();
2238    }
2239}
2240
2241unsafe extern "C" fn step(view: *mut c_void) {
2242    let view = view as id;
2243    let window_state = unsafe { get_window_state(&*view) };
2244    let mut lock = window_state.lock();
2245
2246    if let Some(mut callback) = lock.request_frame_callback.take() {
2247        drop(lock);
2248        callback(Default::default());
2249        window_state.lock().request_frame_callback = Some(callback);
2250    }
2251}
2252
2253extern "C" fn valid_attributes_for_marked_text(_: &Object, _: Sel) -> id {
2254    unsafe { msg_send![class!(NSArray), array] }
2255}
2256
2257extern "C" fn has_marked_text(this: &Object, _: Sel) -> BOOL {
2258    let has_marked_text_result =
2259        with_input_handler(this, |input_handler| input_handler.marked_text_range()).flatten();
2260
2261    has_marked_text_result.is_some() as BOOL
2262}
2263
2264extern "C" fn marked_range(this: &Object, _: Sel) -> NSRange {
2265    let marked_range_result =
2266        with_input_handler(this, |input_handler| input_handler.marked_text_range()).flatten();
2267
2268    marked_range_result.map_or(NSRange::invalid(), |range| range.into())
2269}
2270
2271extern "C" fn selected_range(this: &Object, _: Sel) -> NSRange {
2272    let selected_range_result = with_input_handler(this, |input_handler| {
2273        input_handler.selected_text_range(false)
2274    })
2275    .flatten();
2276
2277    selected_range_result.map_or(NSRange::invalid(), |selection| selection.range.into())
2278}
2279
2280extern "C" fn first_rect_for_character_range(
2281    this: &Object,
2282    _: Sel,
2283    range: NSRange,
2284    _: id,
2285) -> NSRect {
2286    let frame = get_frame(this);
2287    with_input_handler(this, |input_handler| {
2288        input_handler.bounds_for_range(range.to_range()?)
2289    })
2290    .flatten()
2291    .map_or(
2292        NSRect::new(NSPoint::new(0., 0.), NSSize::new(0., 0.)),
2293        |bounds| {
2294            NSRect::new(
2295                NSPoint::new(
2296                    frame.origin.x + bounds.origin.x.0 as f64,
2297                    frame.origin.y + frame.size.height
2298                        - bounds.origin.y.0 as f64
2299                        - bounds.size.height.0 as f64,
2300                ),
2301                NSSize::new(bounds.size.width.0 as f64, bounds.size.height.0 as f64),
2302            )
2303        },
2304    )
2305}
2306
2307fn get_frame(this: &Object) -> NSRect {
2308    unsafe {
2309        let state = get_window_state(this);
2310        let lock = state.lock();
2311        let mut frame = NSWindow::frame(lock.native_window);
2312        let content_layout_rect: CGRect = msg_send![lock.native_window, contentLayoutRect];
2313        let style_mask: NSWindowStyleMask = msg_send![lock.native_window, styleMask];
2314        if !style_mask.contains(NSWindowStyleMask::NSFullSizeContentViewWindowMask) {
2315            frame.origin.y -= frame.size.height - content_layout_rect.size.height;
2316        }
2317        frame
2318    }
2319}
2320
2321extern "C" fn insert_text(this: &Object, _: Sel, text: id, replacement_range: NSRange) {
2322    unsafe {
2323        let is_attributed_string: BOOL =
2324            msg_send![text, isKindOfClass: [class!(NSAttributedString)]];
2325        let text: id = if is_attributed_string == YES {
2326            msg_send![text, string]
2327        } else {
2328            text
2329        };
2330
2331        let text = text.to_str();
2332        let replacement_range = replacement_range.to_range();
2333        with_input_handler(this, |input_handler| {
2334            input_handler.replace_text_in_range(replacement_range, text)
2335        });
2336    }
2337}
2338
2339extern "C" fn set_marked_text(
2340    this: &Object,
2341    _: Sel,
2342    text: id,
2343    selected_range: NSRange,
2344    replacement_range: NSRange,
2345) {
2346    unsafe {
2347        let is_attributed_string: BOOL =
2348            msg_send![text, isKindOfClass: [class!(NSAttributedString)]];
2349        let text: id = if is_attributed_string == YES {
2350            msg_send![text, string]
2351        } else {
2352            text
2353        };
2354        let selected_range = selected_range.to_range();
2355        let replacement_range = replacement_range.to_range();
2356        let text = text.to_str();
2357        with_input_handler(this, |input_handler| {
2358            input_handler.replace_and_mark_text_in_range(replacement_range, text, selected_range)
2359        });
2360    }
2361}
2362extern "C" fn unmark_text(this: &Object, _: Sel) {
2363    with_input_handler(this, |input_handler| input_handler.unmark_text());
2364}
2365
2366extern "C" fn attributed_substring_for_proposed_range(
2367    this: &Object,
2368    _: Sel,
2369    range: NSRange,
2370    actual_range: *mut c_void,
2371) -> id {
2372    with_input_handler(this, |input_handler| {
2373        let range = range.to_range()?;
2374        if range.is_empty() {
2375            return None;
2376        }
2377        let mut adjusted: Option<Range<usize>> = None;
2378
2379        let selected_text = input_handler.text_for_range(range.clone(), &mut adjusted)?;
2380        if let Some(adjusted) = adjusted
2381            && adjusted != range
2382        {
2383            unsafe { (actual_range as *mut NSRange).write(NSRange::from(adjusted)) };
2384        }
2385        unsafe {
2386            let string: id = msg_send![class!(NSAttributedString), alloc];
2387            let string: id = msg_send![string, initWithString: ns_string(&selected_text)];
2388            Some(string)
2389        }
2390    })
2391    .flatten()
2392    .unwrap_or(nil)
2393}
2394
2395// We ignore which selector it asks us to do because the user may have
2396// bound the shortcut to something else.
2397extern "C" fn do_command_by_selector(this: &Object, _: Sel, _: Sel) {
2398    let state = unsafe { get_window_state(this) };
2399    let mut lock = state.as_ref().lock();
2400    let keystroke = lock.keystroke_for_do_command.take();
2401    let mut event_callback = lock.event_callback.take();
2402    drop(lock);
2403
2404    if let Some((keystroke, mut callback)) = keystroke.zip(event_callback.as_mut()) {
2405        let handled = (callback)(PlatformInput::KeyDown(KeyDownEvent {
2406            keystroke,
2407            is_held: false,
2408            prefer_character_input: false,
2409        }));
2410        state.as_ref().lock().do_command_handled = Some(!handled.propagate);
2411    }
2412
2413    state.as_ref().lock().event_callback = event_callback;
2414}
2415
2416extern "C" fn view_did_change_effective_appearance(this: &Object, _: Sel) {
2417    unsafe {
2418        let state = get_window_state(this);
2419        let mut lock = state.as_ref().lock();
2420        if let Some(mut callback) = lock.appearance_changed_callback.take() {
2421            drop(lock);
2422            callback();
2423            state.lock().appearance_changed_callback = Some(callback);
2424        }
2425    }
2426}
2427
2428extern "C" fn accepts_first_mouse(this: &Object, _: Sel, _: id) -> BOOL {
2429    let window_state = unsafe { get_window_state(this) };
2430    let mut lock = window_state.as_ref().lock();
2431    lock.first_mouse = true;
2432    YES
2433}
2434
2435extern "C" fn character_index_for_point(this: &Object, _: Sel, position: NSPoint) -> u64 {
2436    let position = screen_point_to_gpui_point(this, position);
2437    with_input_handler(this, |input_handler| {
2438        input_handler.character_index_for_point(position)
2439    })
2440    .flatten()
2441    .map(|index| index as u64)
2442    .unwrap_or(NSNotFound as u64)
2443}
2444
2445fn screen_point_to_gpui_point(this: &Object, position: NSPoint) -> Point<Pixels> {
2446    let frame = get_frame(this);
2447    let window_x = position.x - frame.origin.x;
2448    let window_y = frame.size.height - (position.y - frame.origin.y);
2449
2450    point(px(window_x as f32), px(window_y as f32))
2451}
2452
2453extern "C" fn dragging_entered(this: &Object, _: Sel, dragging_info: id) -> NSDragOperation {
2454    let window_state = unsafe { get_window_state(this) };
2455    let position = drag_event_position(&window_state, dragging_info);
2456    let paths = external_paths_from_event(dragging_info);
2457    if let Some(event) =
2458        paths.map(|paths| PlatformInput::FileDrop(FileDropEvent::Entered { position, paths }))
2459        && send_new_event(&window_state, event)
2460    {
2461        window_state.lock().external_files_dragged = true;
2462        return NSDragOperationCopy;
2463    }
2464    NSDragOperationNone
2465}
2466
2467extern "C" fn dragging_updated(this: &Object, _: Sel, dragging_info: id) -> NSDragOperation {
2468    let window_state = unsafe { get_window_state(this) };
2469    let position = drag_event_position(&window_state, dragging_info);
2470    if send_new_event(
2471        &window_state,
2472        PlatformInput::FileDrop(FileDropEvent::Pending { position }),
2473    ) {
2474        NSDragOperationCopy
2475    } else {
2476        NSDragOperationNone
2477    }
2478}
2479
2480extern "C" fn dragging_exited(this: &Object, _: Sel, _: id) {
2481    let window_state = unsafe { get_window_state(this) };
2482    send_new_event(
2483        &window_state,
2484        PlatformInput::FileDrop(FileDropEvent::Exited),
2485    );
2486    window_state.lock().external_files_dragged = false;
2487}
2488
2489extern "C" fn perform_drag_operation(this: &Object, _: Sel, dragging_info: id) -> BOOL {
2490    let window_state = unsafe { get_window_state(this) };
2491    let position = drag_event_position(&window_state, dragging_info);
2492    send_new_event(
2493        &window_state,
2494        PlatformInput::FileDrop(FileDropEvent::Submit { position }),
2495    )
2496    .to_objc()
2497}
2498
2499fn external_paths_from_event(dragging_info: *mut Object) -> Option<ExternalPaths> {
2500    let mut paths = SmallVec::new();
2501    let pasteboard: id = unsafe { msg_send![dragging_info, draggingPasteboard] };
2502    let filenames = unsafe { NSPasteboard::propertyListForType(pasteboard, NSFilenamesPboardType) };
2503    if filenames == nil {
2504        return None;
2505    }
2506    for file in unsafe { filenames.iter() } {
2507        let path = unsafe {
2508            let f = NSString::UTF8String(file);
2509            CStr::from_ptr(f).to_string_lossy().into_owned()
2510        };
2511        paths.push(PathBuf::from(path))
2512    }
2513    Some(ExternalPaths(paths))
2514}
2515
2516extern "C" fn conclude_drag_operation(this: &Object, _: Sel, _: id) {
2517    let window_state = unsafe { get_window_state(this) };
2518    send_new_event(
2519        &window_state,
2520        PlatformInput::FileDrop(FileDropEvent::Exited),
2521    );
2522}
2523
2524async fn synthetic_drag(
2525    window_state: Weak<Mutex<MacWindowState>>,
2526    drag_id: usize,
2527    event: MouseMoveEvent,
2528    executor: BackgroundExecutor,
2529) {
2530    loop {
2531        executor.timer(Duration::from_millis(16)).await;
2532        if let Some(window_state) = window_state.upgrade() {
2533            let mut lock = window_state.lock();
2534            if lock.synthetic_drag_counter == drag_id {
2535                if let Some(mut callback) = lock.event_callback.take() {
2536                    drop(lock);
2537                    callback(PlatformInput::MouseMove(event.clone()));
2538                    window_state.lock().event_callback = Some(callback);
2539                }
2540            } else {
2541                break;
2542            }
2543        }
2544    }
2545}
2546
2547fn send_new_event(window_state_lock: &Mutex<MacWindowState>, e: PlatformInput) -> bool {
2548    let window_state = window_state_lock.lock().event_callback.take();
2549    if let Some(mut callback) = window_state {
2550        callback(e);
2551        window_state_lock.lock().event_callback = Some(callback);
2552        true
2553    } else {
2554        false
2555    }
2556}
2557
2558fn drag_event_position(window_state: &Mutex<MacWindowState>, dragging_info: id) -> Point<Pixels> {
2559    let drag_location: NSPoint = unsafe { msg_send![dragging_info, draggingLocation] };
2560    convert_mouse_position(drag_location, window_state.lock().content_size().height)
2561}
2562
2563fn with_input_handler<F, R>(window: &Object, f: F) -> Option<R>
2564where
2565    F: FnOnce(&mut PlatformInputHandler) -> R,
2566{
2567    let window_state = unsafe { get_window_state(window) };
2568    let mut lock = window_state.as_ref().lock();
2569    if let Some(mut input_handler) = lock.input_handler.take() {
2570        drop(lock);
2571        let result = f(&mut input_handler);
2572        window_state.lock().input_handler = Some(input_handler);
2573        Some(result)
2574    } else {
2575        None
2576    }
2577}
2578
2579unsafe fn display_id_for_screen(screen: id) -> CGDirectDisplayID {
2580    unsafe {
2581        let device_description = NSScreen::deviceDescription(screen);
2582        let screen_number_key: id = ns_string("NSScreenNumber");
2583        let screen_number = device_description.objectForKey_(screen_number_key);
2584        let screen_number: NSUInteger = msg_send![screen_number, unsignedIntegerValue];
2585        screen_number as CGDirectDisplayID
2586    }
2587}
2588
2589extern "C" fn blurred_view_init_with_frame(this: &Object, _: Sel, frame: NSRect) -> id {
2590    unsafe {
2591        let view = msg_send![super(this, class!(NSVisualEffectView)), initWithFrame: frame];
2592        // Use a colorless semantic material. The default value `AppearanceBased`, though not
2593        // manually set, is deprecated.
2594        NSVisualEffectView::setMaterial_(view, NSVisualEffectMaterial::Selection);
2595        NSVisualEffectView::setState_(view, NSVisualEffectState::Active);
2596        view
2597    }
2598}
2599
2600extern "C" fn blurred_view_update_layer(this: &Object, _: Sel) {
2601    unsafe {
2602        let _: () = msg_send![super(this, class!(NSVisualEffectView)), updateLayer];
2603        let layer: id = msg_send![this, layer];
2604        if !layer.is_null() {
2605            remove_layer_background(layer);
2606        }
2607    }
2608}
2609
2610unsafe fn remove_layer_background(layer: id) {
2611    unsafe {
2612        let _: () = msg_send![layer, setBackgroundColor:nil];
2613
2614        let class_name: id = msg_send![layer, className];
2615        if class_name.isEqualToString("CAChameleonLayer") {
2616            // Remove the desktop tinting effect.
2617            let _: () = msg_send![layer, setHidden: YES];
2618            return;
2619        }
2620
2621        let filters: id = msg_send![layer, filters];
2622        if !filters.is_null() {
2623            // Remove the increased saturation.
2624            // The effect of a `CAFilter` or `CIFilter` is determined by its name, and the
2625            // `description` reflects its name and some parameters. Currently `NSVisualEffectView`
2626            // uses a `CAFilter` named "colorSaturate". If one day they switch to `CIFilter`, the
2627            // `description` will still contain "Saturat" ("... inputSaturation = ...").
2628            let test_string: id = ns_string("Saturat");
2629            let count = NSArray::count(filters);
2630            for i in 0..count {
2631                let description: id = msg_send![filters.objectAtIndex(i), description];
2632                let hit: BOOL = msg_send![description, containsString: test_string];
2633                if hit == NO {
2634                    continue;
2635                }
2636
2637                let all_indices = NSRange {
2638                    location: 0,
2639                    length: count,
2640                };
2641                let indices: id = msg_send![class!(NSMutableIndexSet), indexSet];
2642                let _: () = msg_send![indices, addIndexesInRange: all_indices];
2643                let _: () = msg_send![indices, removeIndex:i];
2644                let filtered: id = msg_send![filters, objectsAtIndexes: indices];
2645                let _: () = msg_send![layer, setFilters: filtered];
2646                break;
2647            }
2648        }
2649
2650        let sublayers: id = msg_send![layer, sublayers];
2651        if !sublayers.is_null() {
2652            let count = NSArray::count(sublayers);
2653            for i in 0..count {
2654                let sublayer = sublayers.objectAtIndex(i);
2655                remove_layer_background(sublayer);
2656            }
2657        }
2658    }
2659}
2660
2661extern "C" fn add_titlebar_accessory_view_controller(this: &Object, _: Sel, view_controller: id) {
2662    unsafe {
2663        let _: () = msg_send![super(this, class!(NSWindow)), addTitlebarAccessoryViewController: view_controller];
2664
2665        // Hide the native tab bar and set its height to 0, since we render our own.
2666        let accessory_view: id = msg_send![view_controller, view];
2667        let _: () = msg_send![accessory_view, setHidden: YES];
2668        let mut frame: NSRect = msg_send![accessory_view, frame];
2669        frame.size.height = 0.0;
2670        let _: () = msg_send![accessory_view, setFrame: frame];
2671    }
2672}
2673
2674extern "C" fn move_tab_to_new_window(this: &Object, _: Sel, _: id) {
2675    unsafe {
2676        let _: () = msg_send![super(this, class!(NSWindow)), moveTabToNewWindow:nil];
2677
2678        let window_state = get_window_state(this);
2679        let mut lock = window_state.as_ref().lock();
2680        if let Some(mut callback) = lock.move_tab_to_new_window_callback.take() {
2681            drop(lock);
2682            callback();
2683            window_state.lock().move_tab_to_new_window_callback = Some(callback);
2684        }
2685    }
2686}
2687
2688extern "C" fn merge_all_windows(this: &Object, _: Sel, _: id) {
2689    unsafe {
2690        let _: () = msg_send![super(this, class!(NSWindow)), mergeAllWindows:nil];
2691
2692        let window_state = get_window_state(this);
2693        let mut lock = window_state.as_ref().lock();
2694        if let Some(mut callback) = lock.merge_all_windows_callback.take() {
2695            drop(lock);
2696            callback();
2697            window_state.lock().merge_all_windows_callback = Some(callback);
2698        }
2699    }
2700}
2701
2702extern "C" fn select_next_tab(this: &Object, _sel: Sel, _id: id) {
2703    let window_state = unsafe { get_window_state(this) };
2704    let mut lock = window_state.as_ref().lock();
2705    if let Some(mut callback) = lock.select_next_tab_callback.take() {
2706        drop(lock);
2707        callback();
2708        window_state.lock().select_next_tab_callback = Some(callback);
2709    }
2710}
2711
2712extern "C" fn select_previous_tab(this: &Object, _sel: Sel, _id: id) {
2713    let window_state = unsafe { get_window_state(this) };
2714    let mut lock = window_state.as_ref().lock();
2715    if let Some(mut callback) = lock.select_previous_tab_callback.take() {
2716        drop(lock);
2717        callback();
2718        window_state.lock().select_previous_tab_callback = Some(callback);
2719    }
2720}
2721
2722extern "C" fn toggle_tab_bar(this: &Object, _sel: Sel, _id: id) {
2723    unsafe {
2724        let _: () = msg_send![super(this, class!(NSWindow)), toggleTabBar:nil];
2725
2726        let window_state = get_window_state(this);
2727        let mut lock = window_state.as_ref().lock();
2728        lock.move_traffic_light();
2729
2730        if let Some(mut callback) = lock.toggle_tab_bar_callback.take() {
2731            drop(lock);
2732            callback();
2733            window_state.lock().toggle_tab_bar_callback = Some(callback);
2734        }
2735    }
2736}