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 .map_or(true, |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.map_or(true, |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 let handled = with_input_handler(this, |input_handler| {
1484 if !input_handler.apple_press_and_hold_enabled() {
1485 input_handler.replace_text_in_range(None, key_char);
1486 return YES;
1487 }
1488 NO
1489 });
1490 if handled == Some(YES) {
1491 return YES;
1492 }
1493 }
1494
1495 // Don't send key equivalents to the input handler,
1496 // or macOS shortcuts like cmd-` will stop working.
1497 if key_equivalent {
1498 return NO;
1499 }
1500
1501 unsafe {
1502 let input_context: id = msg_send![this, inputContext];
1503 msg_send![input_context, handleEvent: native_event]
1504 }
1505 }
1506
1507 PlatformInput::KeyUp(_) => {
1508 drop(lock);
1509 run_callback(event)
1510 }
1511
1512 _ => NO,
1513 }
1514}
1515
1516extern "C" fn handle_view_event(this: &Object, _: Sel, native_event: id) {
1517 let window_state = unsafe { get_window_state(this) };
1518 let weak_window_state = Arc::downgrade(&window_state);
1519 let mut lock = window_state.as_ref().lock();
1520 let window_height = lock.content_size().height;
1521 let event = unsafe { PlatformInput::from_native(native_event, Some(window_height)) };
1522
1523 if let Some(mut event) = event {
1524 match &mut event {
1525 PlatformInput::MouseDown(
1526 event @ MouseDownEvent {
1527 button: MouseButton::Left,
1528 modifiers: Modifiers { control: true, .. },
1529 ..
1530 },
1531 ) => {
1532 // On mac, a ctrl-left click should be handled as a right click.
1533 *event = MouseDownEvent {
1534 button: MouseButton::Right,
1535 modifiers: Modifiers {
1536 control: false,
1537 ..event.modifiers
1538 },
1539 click_count: 1,
1540 ..*event
1541 };
1542 }
1543
1544 // Handles focusing click.
1545 PlatformInput::MouseDown(
1546 event @ MouseDownEvent {
1547 button: MouseButton::Left,
1548 ..
1549 },
1550 ) if (lock.first_mouse) => {
1551 *event = MouseDownEvent {
1552 first_mouse: true,
1553 ..*event
1554 };
1555 lock.first_mouse = false;
1556 }
1557
1558 // Because we map a ctrl-left_down to a right_down -> right_up let's ignore
1559 // the ctrl-left_up to avoid having a mismatch in button down/up events if the
1560 // user is still holding ctrl when releasing the left mouse button
1561 PlatformInput::MouseUp(
1562 event @ MouseUpEvent {
1563 button: MouseButton::Left,
1564 modifiers: Modifiers { control: true, .. },
1565 ..
1566 },
1567 ) => {
1568 *event = MouseUpEvent {
1569 button: MouseButton::Right,
1570 modifiers: Modifiers {
1571 control: false,
1572 ..event.modifiers
1573 },
1574 click_count: 1,
1575 ..*event
1576 };
1577 }
1578
1579 _ => {}
1580 };
1581
1582 match &event {
1583 PlatformInput::MouseDown(_) => {
1584 drop(lock);
1585 unsafe {
1586 let input_context: id = msg_send![this, inputContext];
1587 msg_send![input_context, handleEvent: native_event]
1588 }
1589 lock = window_state.as_ref().lock();
1590 }
1591 PlatformInput::MouseMove(
1592 event @ MouseMoveEvent {
1593 pressed_button: Some(_),
1594 ..
1595 },
1596 ) => {
1597 // Synthetic drag is used for selecting long buffer contents while buffer is being scrolled.
1598 // External file drag and drop is able to emit its own synthetic mouse events which will conflict
1599 // with these ones.
1600 if !lock.external_files_dragged {
1601 lock.synthetic_drag_counter += 1;
1602 let executor = lock.executor.clone();
1603 executor
1604 .spawn(synthetic_drag(
1605 weak_window_state,
1606 lock.synthetic_drag_counter,
1607 event.clone(),
1608 ))
1609 .detach();
1610 }
1611 }
1612
1613 PlatformInput::MouseUp(MouseUpEvent { .. }) => {
1614 lock.synthetic_drag_counter += 1;
1615 }
1616
1617 PlatformInput::ModifiersChanged(ModifiersChangedEvent {
1618 modifiers,
1619 capslock,
1620 }) => {
1621 // Only raise modifiers changed event when they have actually changed
1622 if let Some(PlatformInput::ModifiersChanged(ModifiersChangedEvent {
1623 modifiers: prev_modifiers,
1624 capslock: prev_capslock,
1625 })) = &lock.previous_modifiers_changed_event
1626 && prev_modifiers == modifiers && prev_capslock == capslock {
1627 return;
1628 }
1629
1630 lock.previous_modifiers_changed_event = Some(event.clone());
1631 }
1632
1633 _ => {}
1634 }
1635
1636 if let Some(mut callback) = lock.event_callback.take() {
1637 drop(lock);
1638 callback(event);
1639 window_state.lock().event_callback = Some(callback);
1640 }
1641 }
1642}
1643
1644extern "C" fn window_did_change_occlusion_state(this: &Object, _: Sel, _: id) {
1645 let window_state = unsafe { get_window_state(this) };
1646 let lock = &mut *window_state.lock();
1647 unsafe {
1648 if lock
1649 .native_window
1650 .occlusionState()
1651 .contains(NSWindowOcclusionState::NSWindowOcclusionStateVisible)
1652 {
1653 lock.start_display_link();
1654 } else {
1655 lock.stop_display_link();
1656 }
1657 }
1658}
1659
1660extern "C" fn window_did_resize(this: &Object, _: Sel, _: id) {
1661 let window_state = unsafe { get_window_state(this) };
1662 window_state.as_ref().lock().move_traffic_light();
1663}
1664
1665extern "C" fn window_will_enter_fullscreen(this: &Object, _: Sel, _: id) {
1666 let window_state = unsafe { get_window_state(this) };
1667 let mut lock = window_state.as_ref().lock();
1668 lock.fullscreen_restore_bounds = lock.bounds();
1669
1670 let min_version = NSOperatingSystemVersion::new(15, 3, 0);
1671
1672 if is_macos_version_at_least(min_version) {
1673 unsafe {
1674 lock.native_window.setTitlebarAppearsTransparent_(NO);
1675 }
1676 }
1677}
1678
1679extern "C" fn window_will_exit_fullscreen(this: &Object, _: Sel, _: id) {
1680 let window_state = unsafe { get_window_state(this) };
1681 let mut lock = window_state.as_ref().lock();
1682
1683 let min_version = NSOperatingSystemVersion::new(15, 3, 0);
1684
1685 if is_macos_version_at_least(min_version) && lock.transparent_titlebar {
1686 unsafe {
1687 lock.native_window.setTitlebarAppearsTransparent_(YES);
1688 }
1689 }
1690}
1691
1692pub(crate) fn is_macos_version_at_least(version: NSOperatingSystemVersion) -> bool {
1693 unsafe { NSProcessInfo::processInfo(nil).isOperatingSystemAtLeastVersion(version) }
1694}
1695
1696extern "C" fn window_did_move(this: &Object, _: Sel, _: id) {
1697 let window_state = unsafe { get_window_state(this) };
1698 let mut lock = window_state.as_ref().lock();
1699 if let Some(mut callback) = lock.moved_callback.take() {
1700 drop(lock);
1701 callback();
1702 window_state.lock().moved_callback = Some(callback);
1703 }
1704}
1705
1706extern "C" fn window_did_change_screen(this: &Object, _: Sel, _: id) {
1707 let window_state = unsafe { get_window_state(this) };
1708 let mut lock = window_state.as_ref().lock();
1709 lock.start_display_link();
1710}
1711
1712extern "C" fn window_did_change_key_status(this: &Object, selector: Sel, _: id) {
1713 let window_state = unsafe { get_window_state(this) };
1714 let lock = window_state.lock();
1715 let is_active = unsafe { lock.native_window.isKeyWindow() == YES };
1716
1717 // When opening a pop-up while the application isn't active, Cocoa sends a spurious
1718 // `windowDidBecomeKey` message to the previous key window even though that window
1719 // isn't actually key. This causes a bug if the application is later activated while
1720 // the pop-up is still open, making it impossible to activate the previous key window
1721 // even if the pop-up gets closed. The only way to activate it again is to de-activate
1722 // the app and re-activate it, which is a pretty bad UX.
1723 // The following code detects the spurious event and invokes `resignKeyWindow`:
1724 // in theory, we're not supposed to invoke this method manually but it balances out
1725 // the spurious `becomeKeyWindow` event and helps us work around that bug.
1726 if selector == sel!(windowDidBecomeKey:) && !is_active {
1727 unsafe {
1728 let _: () = msg_send![lock.native_window, resignKeyWindow];
1729 return;
1730 }
1731 }
1732
1733 let executor = lock.executor.clone();
1734 drop(lock);
1735 executor
1736 .spawn(async move {
1737 let mut lock = window_state.as_ref().lock();
1738 if let Some(mut callback) = lock.activate_callback.take() {
1739 drop(lock);
1740 callback(is_active);
1741 window_state.lock().activate_callback = Some(callback);
1742 };
1743 })
1744 .detach();
1745}
1746
1747extern "C" fn window_should_close(this: &Object, _: Sel, _: id) -> BOOL {
1748 let window_state = unsafe { get_window_state(this) };
1749 let mut lock = window_state.as_ref().lock();
1750 if let Some(mut callback) = lock.should_close_callback.take() {
1751 drop(lock);
1752 let should_close = callback();
1753 window_state.lock().should_close_callback = Some(callback);
1754 should_close as BOOL
1755 } else {
1756 YES
1757 }
1758}
1759
1760extern "C" fn close_window(this: &Object, _: Sel) {
1761 unsafe {
1762 let close_callback = {
1763 let window_state = get_window_state(this);
1764 let mut lock = window_state.as_ref().lock();
1765 lock.close_callback.take()
1766 };
1767
1768 if let Some(callback) = close_callback {
1769 callback();
1770 }
1771
1772 let _: () = msg_send![super(this, class!(NSWindow)), close];
1773 }
1774}
1775
1776extern "C" fn make_backing_layer(this: &Object, _: Sel) -> id {
1777 let window_state = unsafe { get_window_state(this) };
1778 let window_state = window_state.as_ref().lock();
1779 window_state.renderer.layer_ptr() as id
1780}
1781
1782extern "C" fn view_did_change_backing_properties(this: &Object, _: Sel) {
1783 let window_state = unsafe { get_window_state(this) };
1784 let mut lock = window_state.as_ref().lock();
1785
1786 let scale_factor = lock.scale_factor();
1787 let size = lock.content_size();
1788 let drawable_size = size.to_device_pixels(scale_factor);
1789 unsafe {
1790 let _: () = msg_send![
1791 lock.renderer.layer(),
1792 setContentsScale: scale_factor as f64
1793 ];
1794 }
1795
1796 lock.renderer.update_drawable_size(drawable_size);
1797
1798 if let Some(mut callback) = lock.resize_callback.take() {
1799 let content_size = lock.content_size();
1800 let scale_factor = lock.scale_factor();
1801 drop(lock);
1802 callback(content_size, scale_factor);
1803 window_state.as_ref().lock().resize_callback = Some(callback);
1804 };
1805}
1806
1807extern "C" fn set_frame_size(this: &Object, _: Sel, size: NSSize) {
1808 let window_state = unsafe { get_window_state(this) };
1809 let mut lock = window_state.as_ref().lock();
1810
1811 let new_size = Size::<Pixels>::from(size);
1812 let old_size = unsafe {
1813 let old_frame: NSRect = msg_send![this, frame];
1814 Size::<Pixels>::from(old_frame.size)
1815 };
1816
1817 if old_size == new_size {
1818 return;
1819 }
1820
1821 unsafe {
1822 let _: () = msg_send![super(this, class!(NSView)), setFrameSize: size];
1823 }
1824
1825 let scale_factor = lock.scale_factor();
1826 let drawable_size = new_size.to_device_pixels(scale_factor);
1827 lock.renderer.update_drawable_size(drawable_size);
1828
1829 if let Some(mut callback) = lock.resize_callback.take() {
1830 let content_size = lock.content_size();
1831 let scale_factor = lock.scale_factor();
1832 drop(lock);
1833 callback(content_size, scale_factor);
1834 window_state.lock().resize_callback = Some(callback);
1835 };
1836}
1837
1838extern "C" fn display_layer(this: &Object, _: Sel, _: id) {
1839 let window_state = unsafe { get_window_state(this) };
1840 let mut lock = window_state.lock();
1841 if let Some(mut callback) = lock.request_frame_callback.take() {
1842 #[cfg(not(feature = "macos-blade"))]
1843 lock.renderer.set_presents_with_transaction(true);
1844 lock.stop_display_link();
1845 drop(lock);
1846 callback(Default::default());
1847
1848 let mut lock = window_state.lock();
1849 lock.request_frame_callback = Some(callback);
1850 #[cfg(not(feature = "macos-blade"))]
1851 lock.renderer.set_presents_with_transaction(false);
1852 lock.start_display_link();
1853 }
1854}
1855
1856unsafe extern "C" fn step(view: *mut c_void) {
1857 let view = view as id;
1858 let window_state = unsafe { get_window_state(&*view) };
1859 let mut lock = window_state.lock();
1860
1861 if let Some(mut callback) = lock.request_frame_callback.take() {
1862 drop(lock);
1863 callback(Default::default());
1864 window_state.lock().request_frame_callback = Some(callback);
1865 }
1866}
1867
1868extern "C" fn valid_attributes_for_marked_text(_: &Object, _: Sel) -> id {
1869 unsafe { msg_send![class!(NSArray), array] }
1870}
1871
1872extern "C" fn has_marked_text(this: &Object, _: Sel) -> BOOL {
1873 let has_marked_text_result =
1874 with_input_handler(this, |input_handler| input_handler.marked_text_range()).flatten();
1875
1876 has_marked_text_result.is_some() as BOOL
1877}
1878
1879extern "C" fn marked_range(this: &Object, _: Sel) -> NSRange {
1880 let marked_range_result =
1881 with_input_handler(this, |input_handler| input_handler.marked_text_range()).flatten();
1882
1883 marked_range_result.map_or(NSRange::invalid(), |range| range.into())
1884}
1885
1886extern "C" fn selected_range(this: &Object, _: Sel) -> NSRange {
1887 let selected_range_result = with_input_handler(this, |input_handler| {
1888 input_handler.selected_text_range(false)
1889 })
1890 .flatten();
1891
1892 selected_range_result.map_or(NSRange::invalid(), |selection| selection.range.into())
1893}
1894
1895extern "C" fn first_rect_for_character_range(
1896 this: &Object,
1897 _: Sel,
1898 range: NSRange,
1899 _: id,
1900) -> NSRect {
1901 let frame = get_frame(this);
1902 with_input_handler(this, |input_handler| {
1903 input_handler.bounds_for_range(range.to_range()?)
1904 })
1905 .flatten()
1906 .map_or(
1907 NSRect::new(NSPoint::new(0., 0.), NSSize::new(0., 0.)),
1908 |bounds| {
1909 NSRect::new(
1910 NSPoint::new(
1911 frame.origin.x + bounds.origin.x.0 as f64,
1912 frame.origin.y + frame.size.height
1913 - bounds.origin.y.0 as f64
1914 - bounds.size.height.0 as f64,
1915 ),
1916 NSSize::new(bounds.size.width.0 as f64, bounds.size.height.0 as f64),
1917 )
1918 },
1919 )
1920}
1921
1922fn get_frame(this: &Object) -> NSRect {
1923 unsafe {
1924 let state = get_window_state(this);
1925 let lock = state.lock();
1926 let mut frame = NSWindow::frame(lock.native_window);
1927 let content_layout_rect: CGRect = msg_send![lock.native_window, contentLayoutRect];
1928 let style_mask: NSWindowStyleMask = msg_send![lock.native_window, styleMask];
1929 if !style_mask.contains(NSWindowStyleMask::NSFullSizeContentViewWindowMask) {
1930 frame.origin.y -= frame.size.height - content_layout_rect.size.height;
1931 }
1932 frame
1933 }
1934}
1935
1936extern "C" fn insert_text(this: &Object, _: Sel, text: id, replacement_range: NSRange) {
1937 unsafe {
1938 let is_attributed_string: BOOL =
1939 msg_send![text, isKindOfClass: [class!(NSAttributedString)]];
1940 let text: id = if is_attributed_string == YES {
1941 msg_send![text, string]
1942 } else {
1943 text
1944 };
1945
1946 let text = text.to_str();
1947 let replacement_range = replacement_range.to_range();
1948 with_input_handler(this, |input_handler| {
1949 input_handler.replace_text_in_range(replacement_range, text)
1950 });
1951 }
1952}
1953
1954extern "C" fn set_marked_text(
1955 this: &Object,
1956 _: Sel,
1957 text: id,
1958 selected_range: NSRange,
1959 replacement_range: NSRange,
1960) {
1961 unsafe {
1962 let is_attributed_string: BOOL =
1963 msg_send![text, isKindOfClass: [class!(NSAttributedString)]];
1964 let text: id = if is_attributed_string == YES {
1965 msg_send![text, string]
1966 } else {
1967 text
1968 };
1969 let selected_range = selected_range.to_range();
1970 let replacement_range = replacement_range.to_range();
1971 let text = text.to_str();
1972 with_input_handler(this, |input_handler| {
1973 input_handler.replace_and_mark_text_in_range(replacement_range, text, selected_range)
1974 });
1975 }
1976}
1977extern "C" fn unmark_text(this: &Object, _: Sel) {
1978 with_input_handler(this, |input_handler| input_handler.unmark_text());
1979}
1980
1981extern "C" fn attributed_substring_for_proposed_range(
1982 this: &Object,
1983 _: Sel,
1984 range: NSRange,
1985 actual_range: *mut c_void,
1986) -> id {
1987 with_input_handler(this, |input_handler| {
1988 let range = range.to_range()?;
1989 if range.is_empty() {
1990 return None;
1991 }
1992 let mut adjusted: Option<Range<usize>> = None;
1993
1994 let selected_text = input_handler.text_for_range(range.clone(), &mut adjusted)?;
1995 if let Some(adjusted) = adjusted
1996 && adjusted != range {
1997 unsafe { (actual_range as *mut NSRange).write(NSRange::from(adjusted)) };
1998 }
1999 unsafe {
2000 let string: id = msg_send![class!(NSAttributedString), alloc];
2001 let string: id = msg_send![string, initWithString: ns_string(&selected_text)];
2002 Some(string)
2003 }
2004 })
2005 .flatten()
2006 .unwrap_or(nil)
2007}
2008
2009// We ignore which selector it asks us to do because the user may have
2010// bound the shortcut to something else.
2011extern "C" fn do_command_by_selector(this: &Object, _: Sel, _: Sel) {
2012 let state = unsafe { get_window_state(this) };
2013 let mut lock = state.as_ref().lock();
2014 let keystroke = lock.keystroke_for_do_command.take();
2015 let mut event_callback = lock.event_callback.take();
2016 drop(lock);
2017
2018 if let Some((keystroke, mut callback)) = keystroke.zip(event_callback.as_mut()) {
2019 let handled = (callback)(PlatformInput::KeyDown(KeyDownEvent {
2020 keystroke,
2021 is_held: false,
2022 }));
2023 state.as_ref().lock().do_command_handled = Some(!handled.propagate);
2024 }
2025
2026 state.as_ref().lock().event_callback = event_callback;
2027}
2028
2029extern "C" fn view_did_change_effective_appearance(this: &Object, _: Sel) {
2030 unsafe {
2031 let state = get_window_state(this);
2032 let mut lock = state.as_ref().lock();
2033 if let Some(mut callback) = lock.appearance_changed_callback.take() {
2034 drop(lock);
2035 callback();
2036 state.lock().appearance_changed_callback = Some(callback);
2037 }
2038 }
2039}
2040
2041extern "C" fn accepts_first_mouse(this: &Object, _: Sel, _: id) -> BOOL {
2042 let window_state = unsafe { get_window_state(this) };
2043 let mut lock = window_state.as_ref().lock();
2044 lock.first_mouse = true;
2045 YES
2046}
2047
2048extern "C" fn character_index_for_point(this: &Object, _: Sel, position: NSPoint) -> u64 {
2049 let position = screen_point_to_gpui_point(this, position);
2050 with_input_handler(this, |input_handler| {
2051 input_handler.character_index_for_point(position)
2052 })
2053 .flatten()
2054 .map(|index| index as u64)
2055 .unwrap_or(NSNotFound as u64)
2056}
2057
2058fn screen_point_to_gpui_point(this: &Object, position: NSPoint) -> Point<Pixels> {
2059 let frame = get_frame(this);
2060 let window_x = position.x - frame.origin.x;
2061 let window_y = frame.size.height - (position.y - frame.origin.y);
2062 let position = point(px(window_x as f32), px(window_y as f32));
2063 position
2064}
2065
2066extern "C" fn dragging_entered(this: &Object, _: Sel, dragging_info: id) -> NSDragOperation {
2067 let window_state = unsafe { get_window_state(this) };
2068 let position = drag_event_position(&window_state, dragging_info);
2069 let paths = external_paths_from_event(dragging_info);
2070 if let Some(event) =
2071 paths.map(|paths| PlatformInput::FileDrop(FileDropEvent::Entered { position, paths }))
2072 && send_new_event(&window_state, event) {
2073 window_state.lock().external_files_dragged = true;
2074 return NSDragOperationCopy;
2075 }
2076 NSDragOperationNone
2077}
2078
2079extern "C" fn dragging_updated(this: &Object, _: Sel, dragging_info: id) -> NSDragOperation {
2080 let window_state = unsafe { get_window_state(this) };
2081 let position = drag_event_position(&window_state, dragging_info);
2082 if send_new_event(
2083 &window_state,
2084 PlatformInput::FileDrop(FileDropEvent::Pending { position }),
2085 ) {
2086 NSDragOperationCopy
2087 } else {
2088 NSDragOperationNone
2089 }
2090}
2091
2092extern "C" fn dragging_exited(this: &Object, _: Sel, _: id) {
2093 let window_state = unsafe { get_window_state(this) };
2094 send_new_event(
2095 &window_state,
2096 PlatformInput::FileDrop(FileDropEvent::Exited),
2097 );
2098 window_state.lock().external_files_dragged = false;
2099}
2100
2101extern "C" fn perform_drag_operation(this: &Object, _: Sel, dragging_info: id) -> BOOL {
2102 let window_state = unsafe { get_window_state(this) };
2103 let position = drag_event_position(&window_state, dragging_info);
2104 send_new_event(
2105 &window_state,
2106 PlatformInput::FileDrop(FileDropEvent::Submit { position }),
2107 )
2108 .to_objc()
2109}
2110
2111fn external_paths_from_event(dragging_info: *mut Object) -> Option<ExternalPaths> {
2112 let mut paths = SmallVec::new();
2113 let pasteboard: id = unsafe { msg_send![dragging_info, draggingPasteboard] };
2114 let filenames = unsafe { NSPasteboard::propertyListForType(pasteboard, NSFilenamesPboardType) };
2115 if filenames == nil {
2116 return None;
2117 }
2118 for file in unsafe { filenames.iter() } {
2119 let path = unsafe {
2120 let f = NSString::UTF8String(file);
2121 CStr::from_ptr(f).to_string_lossy().into_owned()
2122 };
2123 paths.push(PathBuf::from(path))
2124 }
2125 Some(ExternalPaths(paths))
2126}
2127
2128extern "C" fn conclude_drag_operation(this: &Object, _: Sel, _: id) {
2129 let window_state = unsafe { get_window_state(this) };
2130 send_new_event(
2131 &window_state,
2132 PlatformInput::FileDrop(FileDropEvent::Exited),
2133 );
2134}
2135
2136async fn synthetic_drag(
2137 window_state: Weak<Mutex<MacWindowState>>,
2138 drag_id: usize,
2139 event: MouseMoveEvent,
2140) {
2141 loop {
2142 Timer::after(Duration::from_millis(16)).await;
2143 if let Some(window_state) = window_state.upgrade() {
2144 let mut lock = window_state.lock();
2145 if lock.synthetic_drag_counter == drag_id {
2146 if let Some(mut callback) = lock.event_callback.take() {
2147 drop(lock);
2148 callback(PlatformInput::MouseMove(event.clone()));
2149 window_state.lock().event_callback = Some(callback);
2150 }
2151 } else {
2152 break;
2153 }
2154 }
2155 }
2156}
2157
2158fn send_new_event(window_state_lock: &Mutex<MacWindowState>, e: PlatformInput) -> bool {
2159 let window_state = window_state_lock.lock().event_callback.take();
2160 if let Some(mut callback) = window_state {
2161 callback(e);
2162 window_state_lock.lock().event_callback = Some(callback);
2163 true
2164 } else {
2165 false
2166 }
2167}
2168
2169fn drag_event_position(window_state: &Mutex<MacWindowState>, dragging_info: id) -> Point<Pixels> {
2170 let drag_location: NSPoint = unsafe { msg_send![dragging_info, draggingLocation] };
2171 convert_mouse_position(drag_location, window_state.lock().content_size().height)
2172}
2173
2174fn with_input_handler<F, R>(window: &Object, f: F) -> Option<R>
2175where
2176 F: FnOnce(&mut PlatformInputHandler) -> R,
2177{
2178 let window_state = unsafe { get_window_state(window) };
2179 let mut lock = window_state.as_ref().lock();
2180 if let Some(mut input_handler) = lock.input_handler.take() {
2181 drop(lock);
2182 let result = f(&mut input_handler);
2183 window_state.lock().input_handler = Some(input_handler);
2184 Some(result)
2185 } else {
2186 None
2187 }
2188}
2189
2190unsafe fn display_id_for_screen(screen: id) -> CGDirectDisplayID {
2191 unsafe {
2192 let device_description = NSScreen::deviceDescription(screen);
2193 let screen_number_key: id = NSString::alloc(nil).init_str("NSScreenNumber");
2194 let screen_number = device_description.objectForKey_(screen_number_key);
2195 let screen_number: NSUInteger = msg_send![screen_number, unsignedIntegerValue];
2196 screen_number as CGDirectDisplayID
2197 }
2198}
2199
2200extern "C" fn blurred_view_init_with_frame(this: &Object, _: Sel, frame: NSRect) -> id {
2201 unsafe {
2202 let view = msg_send![super(this, class!(NSVisualEffectView)), initWithFrame: frame];
2203 // Use a colorless semantic material. The default value `AppearanceBased`, though not
2204 // manually set, is deprecated.
2205 NSVisualEffectView::setMaterial_(view, NSVisualEffectMaterial::Selection);
2206 NSVisualEffectView::setState_(view, NSVisualEffectState::Active);
2207 view
2208 }
2209}
2210
2211extern "C" fn blurred_view_update_layer(this: &Object, _: Sel) {
2212 unsafe {
2213 let _: () = msg_send![super(this, class!(NSVisualEffectView)), updateLayer];
2214 let layer: id = msg_send![this, layer];
2215 if !layer.is_null() {
2216 remove_layer_background(layer);
2217 }
2218 }
2219}
2220
2221unsafe fn remove_layer_background(layer: id) {
2222 unsafe {
2223 let _: () = msg_send![layer, setBackgroundColor:nil];
2224
2225 let class_name: id = msg_send![layer, className];
2226 if class_name.isEqualToString("CAChameleonLayer") {
2227 // Remove the desktop tinting effect.
2228 let _: () = msg_send![layer, setHidden: YES];
2229 return;
2230 }
2231
2232 let filters: id = msg_send![layer, filters];
2233 if !filters.is_null() {
2234 // Remove the increased saturation.
2235 // The effect of a `CAFilter` or `CIFilter` is determined by its name, and the
2236 // `description` reflects its name and some parameters. Currently `NSVisualEffectView`
2237 // uses a `CAFilter` named "colorSaturate". If one day they switch to `CIFilter`, the
2238 // `description` will still contain "Saturat" ("... inputSaturation = ...").
2239 let test_string: id = NSString::alloc(nil).init_str("Saturat").autorelease();
2240 let count = NSArray::count(filters);
2241 for i in 0..count {
2242 let description: id = msg_send![filters.objectAtIndex(i), description];
2243 let hit: BOOL = msg_send![description, containsString: test_string];
2244 if hit == NO {
2245 continue;
2246 }
2247
2248 let all_indices = NSRange {
2249 location: 0,
2250 length: count,
2251 };
2252 let indices: id = msg_send![class!(NSMutableIndexSet), indexSet];
2253 let _: () = msg_send![indices, addIndexesInRange: all_indices];
2254 let _: () = msg_send![indices, removeIndex:i];
2255 let filtered: id = msg_send![filters, objectsAtIndexes: indices];
2256 let _: () = msg_send![layer, setFilters: filtered];
2257 break;
2258 }
2259 }
2260
2261 let sublayers: id = msg_send![layer, sublayers];
2262 if !sublayers.is_null() {
2263 let count = NSArray::count(sublayers);
2264 for i in 0..count {
2265 let sublayer = sublayers.objectAtIndex(i);
2266 remove_layer_background(sublayer);
2267 }
2268 }
2269 }
2270}