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