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