1use super::{global_bounds_from_ns_rect, ns_string, renderer, MacDisplay, NSRange};
2use crate::{
3 global_bounds_to_ns_rect, platform::PlatformInputHandler, point, px, size, AnyWindowHandle,
4 Bounds, DisplayLink, ExternalPaths, FileDropEvent, ForegroundExecutor, GlobalPixels,
5 KeyDownEvent, Keystroke, Modifiers, ModifiersChangedEvent, MouseButton, MouseDownEvent,
6 MouseMoveEvent, MouseUpEvent, Pixels, PlatformAtlas, PlatformDisplay, PlatformInput,
7 PlatformWindow, Point, PromptLevel, Size, Timer, WindowAppearance, WindowKind, WindowParams,
8};
9use block::ConcreteBlock;
10use cocoa::{
11 appkit::{
12 CGPoint, NSApplication, NSBackingStoreBuffered, NSEventModifierFlags,
13 NSFilenamesPboardType, NSPasteboard, NSScreen, NSView, NSViewHeightSizable,
14 NSViewWidthSizable, NSWindow, NSWindowButton, NSWindowCollectionBehavior,
15 NSWindowOcclusionState, NSWindowStyleMask, NSWindowTitleVisibility,
16 },
17 base::{id, nil},
18 foundation::{
19 NSArray, NSAutoreleasePool, NSDictionary, NSFastEnumeration, NSInteger, NSPoint, NSRect,
20 NSSize, NSString, NSUInteger,
21 },
22};
23use core_graphics::display::{CGDirectDisplayID, CGRect};
24use ctor::ctor;
25use futures::channel::oneshot;
26use objc::{
27 class,
28 declare::ClassDecl,
29 msg_send,
30 runtime::{Class, Object, Protocol, Sel, BOOL, NO, YES},
31 sel, sel_impl,
32};
33use parking_lot::Mutex;
34use raw_window_handle::{
35 AppKitDisplayHandle, AppKitWindowHandle, DisplayHandle, HasDisplayHandle, HasWindowHandle,
36 RawWindowHandle, WindowHandle,
37};
38use smallvec::SmallVec;
39use std::{
40 any::Any,
41 cell::Cell,
42 ffi::{c_void, CStr},
43 mem,
44 ops::Range,
45 os::raw::c_char,
46 path::PathBuf,
47 ptr::{self, NonNull},
48 rc::Rc,
49 sync::{Arc, Weak},
50 time::Duration,
51};
52use util::ResultExt;
53
54const WINDOW_STATE_IVAR: &str = "windowState";
55
56static mut WINDOW_CLASS: *const Class = ptr::null();
57static mut PANEL_CLASS: *const Class = ptr::null();
58static mut VIEW_CLASS: *const Class = ptr::null();
59
60#[allow(non_upper_case_globals)]
61const NSWindowStyleMaskNonactivatingPanel: NSWindowStyleMask =
62 unsafe { NSWindowStyleMask::from_bits_unchecked(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#[ctor]
87unsafe fn build_classes() {
88 WINDOW_CLASS = build_window_class("GPUIWindow", class!(NSWindow));
89 PANEL_CLASS = build_window_class("GPUIPanel", class!(NSPanel));
90 VIEW_CLASS = {
91 let mut decl = ClassDecl::new("GPUIView", class!(NSView)).unwrap();
92 decl.add_ivar::<*mut c_void>(WINDOW_STATE_IVAR);
93
94 decl.add_method(sel!(dealloc), dealloc_view as extern "C" fn(&Object, Sel));
95
96 decl.add_method(
97 sel!(performKeyEquivalent:),
98 handle_key_equivalent as extern "C" fn(&Object, Sel, id) -> BOOL,
99 );
100 decl.add_method(
101 sel!(keyDown:),
102 handle_key_down as extern "C" fn(&Object, Sel, id),
103 );
104 decl.add_method(
105 sel!(mouseDown:),
106 handle_view_event as extern "C" fn(&Object, Sel, id),
107 );
108 decl.add_method(
109 sel!(mouseUp:),
110 handle_view_event as extern "C" fn(&Object, Sel, id),
111 );
112 decl.add_method(
113 sel!(rightMouseDown:),
114 handle_view_event as extern "C" fn(&Object, Sel, id),
115 );
116 decl.add_method(
117 sel!(rightMouseUp:),
118 handle_view_event as extern "C" fn(&Object, Sel, id),
119 );
120 decl.add_method(
121 sel!(otherMouseDown:),
122 handle_view_event as extern "C" fn(&Object, Sel, id),
123 );
124 decl.add_method(
125 sel!(otherMouseUp:),
126 handle_view_event as extern "C" fn(&Object, Sel, id),
127 );
128 decl.add_method(
129 sel!(mouseMoved:),
130 handle_view_event as extern "C" fn(&Object, Sel, id),
131 );
132 decl.add_method(
133 sel!(mouseExited:),
134 handle_view_event as extern "C" fn(&Object, Sel, id),
135 );
136 decl.add_method(
137 sel!(mouseDragged:),
138 handle_view_event as extern "C" fn(&Object, Sel, id),
139 );
140 decl.add_method(
141 sel!(scrollWheel:),
142 handle_view_event as extern "C" fn(&Object, Sel, id),
143 );
144 decl.add_method(
145 sel!(flagsChanged:),
146 handle_view_event as extern "C" fn(&Object, Sel, id),
147 );
148 decl.add_method(
149 sel!(cancelOperation:),
150 cancel_operation as extern "C" fn(&Object, Sel, id),
151 );
152
153 decl.add_method(
154 sel!(makeBackingLayer),
155 make_backing_layer as extern "C" fn(&Object, Sel) -> id,
156 );
157
158 decl.add_protocol(Protocol::get("CALayerDelegate").unwrap());
159 decl.add_method(
160 sel!(viewDidChangeBackingProperties),
161 view_did_change_backing_properties as extern "C" fn(&Object, Sel),
162 );
163 decl.add_method(
164 sel!(setFrameSize:),
165 set_frame_size as extern "C" fn(&Object, Sel, NSSize),
166 );
167 decl.add_method(
168 sel!(displayLayer:),
169 display_layer as extern "C" fn(&Object, Sel, id),
170 );
171
172 decl.add_protocol(Protocol::get("NSTextInputClient").unwrap());
173 decl.add_method(
174 sel!(validAttributesForMarkedText),
175 valid_attributes_for_marked_text as extern "C" fn(&Object, Sel) -> id,
176 );
177 decl.add_method(
178 sel!(hasMarkedText),
179 has_marked_text as extern "C" fn(&Object, Sel) -> BOOL,
180 );
181 decl.add_method(
182 sel!(markedRange),
183 marked_range as extern "C" fn(&Object, Sel) -> NSRange,
184 );
185 decl.add_method(
186 sel!(selectedRange),
187 selected_range as extern "C" fn(&Object, Sel) -> NSRange,
188 );
189 decl.add_method(
190 sel!(firstRectForCharacterRange:actualRange:),
191 first_rect_for_character_range as extern "C" fn(&Object, Sel, NSRange, id) -> NSRect,
192 );
193 decl.add_method(
194 sel!(insertText:replacementRange:),
195 insert_text as extern "C" fn(&Object, Sel, id, NSRange),
196 );
197 decl.add_method(
198 sel!(setMarkedText:selectedRange:replacementRange:),
199 set_marked_text as extern "C" fn(&Object, Sel, id, NSRange, NSRange),
200 );
201 decl.add_method(sel!(unmarkText), unmark_text as extern "C" fn(&Object, Sel));
202 decl.add_method(
203 sel!(attributedSubstringForProposedRange:actualRange:),
204 attributed_substring_for_proposed_range
205 as extern "C" fn(&Object, Sel, NSRange, *mut c_void) -> id,
206 );
207 decl.add_method(
208 sel!(viewDidChangeEffectiveAppearance),
209 view_did_change_effective_appearance as extern "C" fn(&Object, Sel),
210 );
211
212 // Suppress beep on keystrokes with modifier keys.
213 decl.add_method(
214 sel!(doCommandBySelector:),
215 do_command_by_selector as extern "C" fn(&Object, Sel, Sel),
216 );
217
218 decl.add_method(
219 sel!(acceptsFirstMouse:),
220 accepts_first_mouse as extern "C" fn(&Object, Sel, id) -> BOOL,
221 );
222
223 decl.register()
224 };
225}
226
227pub(crate) fn convert_mouse_position(position: NSPoint, window_height: Pixels) -> Point<Pixels> {
228 point(
229 px(position.x as f32),
230 // MacOS screen coordinates are relative to bottom left
231 window_height - px(position.y as f32),
232 )
233}
234
235unsafe fn build_window_class(name: &'static str, superclass: &Class) -> *const Class {
236 let mut decl = ClassDecl::new(name, superclass).unwrap();
237 decl.add_ivar::<*mut c_void>(WINDOW_STATE_IVAR);
238 decl.add_method(sel!(dealloc), dealloc_window as extern "C" fn(&Object, Sel));
239 decl.add_method(
240 sel!(canBecomeMainWindow),
241 yes as extern "C" fn(&Object, Sel) -> BOOL,
242 );
243 decl.add_method(
244 sel!(canBecomeKeyWindow),
245 yes as extern "C" fn(&Object, Sel) -> BOOL,
246 );
247 decl.add_method(
248 sel!(windowDidResize:),
249 window_did_resize as extern "C" fn(&Object, Sel, id),
250 );
251 decl.add_method(
252 sel!(windowDidChangeOcclusionState:),
253 window_did_change_occlusion_state as extern "C" fn(&Object, Sel, id),
254 );
255 decl.add_method(
256 sel!(windowWillEnterFullScreen:),
257 window_will_enter_fullscreen as extern "C" fn(&Object, Sel, id),
258 );
259 decl.add_method(
260 sel!(windowWillExitFullScreen:),
261 window_will_exit_fullscreen as extern "C" fn(&Object, Sel, id),
262 );
263 decl.add_method(
264 sel!(windowDidMove:),
265 window_did_move as extern "C" fn(&Object, Sel, id),
266 );
267 decl.add_method(
268 sel!(windowDidChangeScreen:),
269 window_did_change_screen as extern "C" fn(&Object, Sel, id),
270 );
271 decl.add_method(
272 sel!(windowDidBecomeKey:),
273 window_did_change_key_status as extern "C" fn(&Object, Sel, id),
274 );
275 decl.add_method(
276 sel!(windowDidResignKey:),
277 window_did_change_key_status as extern "C" fn(&Object, Sel, id),
278 );
279 decl.add_method(
280 sel!(windowShouldClose:),
281 window_should_close as extern "C" fn(&Object, Sel, id) -> BOOL,
282 );
283
284 decl.add_method(sel!(close), close_window as extern "C" fn(&Object, Sel));
285
286 decl.add_method(
287 sel!(draggingEntered:),
288 dragging_entered as extern "C" fn(&Object, Sel, id) -> NSDragOperation,
289 );
290 decl.add_method(
291 sel!(draggingUpdated:),
292 dragging_updated as extern "C" fn(&Object, Sel, id) -> NSDragOperation,
293 );
294 decl.add_method(
295 sel!(draggingExited:),
296 dragging_exited as extern "C" fn(&Object, Sel, id),
297 );
298 decl.add_method(
299 sel!(performDragOperation:),
300 perform_drag_operation as extern "C" fn(&Object, Sel, id) -> BOOL,
301 );
302 decl.add_method(
303 sel!(concludeDragOperation:),
304 conclude_drag_operation as extern "C" fn(&Object, Sel, id),
305 );
306
307 decl.register()
308}
309
310#[allow(clippy::enum_variant_names)]
311enum ImeInput {
312 InsertText(String, Option<Range<usize>>),
313 SetMarkedText(String, Option<Range<usize>>, Option<Range<usize>>),
314 UnmarkText,
315}
316
317struct MacWindowState {
318 handle: AnyWindowHandle,
319 executor: ForegroundExecutor,
320 native_window: id,
321 native_window_was_closed: bool,
322 native_view: NonNull<Object>,
323 display_link: Option<DisplayLink>,
324 renderer: renderer::Renderer,
325 kind: WindowKind,
326 request_frame_callback: Option<Box<dyn FnMut()>>,
327 event_callback: Option<Box<dyn FnMut(PlatformInput) -> crate::DispatchEventResult>>,
328 activate_callback: Option<Box<dyn FnMut(bool)>>,
329 resize_callback: Option<Box<dyn FnMut(Size<Pixels>, f32)>>,
330 fullscreen_callback: Option<Box<dyn FnMut(bool)>>,
331 moved_callback: Option<Box<dyn FnMut()>>,
332 should_close_callback: Option<Box<dyn FnMut() -> bool>>,
333 close_callback: Option<Box<dyn FnOnce()>>,
334 appearance_changed_callback: Option<Box<dyn FnMut()>>,
335 input_handler: Option<PlatformInputHandler>,
336 last_key_equivalent: Option<KeyDownEvent>,
337 synthetic_drag_counter: usize,
338 last_fresh_keydown: Option<Keystroke>,
339 traffic_light_position: Option<Point<Pixels>>,
340 previous_modifiers_changed_event: Option<PlatformInput>,
341 // State tracking what the IME did after the last request
342 input_during_keydown: Option<SmallVec<[ImeInput; 1]>>,
343 previous_keydown_inserted_text: Option<String>,
344 external_files_dragged: bool,
345}
346
347impl MacWindowState {
348 fn move_traffic_light(&self) {
349 if let Some(traffic_light_position) = self.traffic_light_position {
350 if self.is_fullscreen() {
351 // Moving traffic lights while fullscreen doesn't work,
352 // see https://github.com/zed-industries/zed/issues/4712
353 return;
354 }
355
356 let titlebar_height = self.titlebar_height();
357
358 unsafe {
359 let close_button: id = msg_send![
360 self.native_window,
361 standardWindowButton: NSWindowButton::NSWindowCloseButton
362 ];
363 let min_button: id = msg_send![
364 self.native_window,
365 standardWindowButton: NSWindowButton::NSWindowMiniaturizeButton
366 ];
367 let zoom_button: id = msg_send![
368 self.native_window,
369 standardWindowButton: NSWindowButton::NSWindowZoomButton
370 ];
371
372 let mut close_button_frame: CGRect = msg_send![close_button, frame];
373 let mut min_button_frame: CGRect = msg_send![min_button, frame];
374 let mut zoom_button_frame: CGRect = msg_send![zoom_button, frame];
375 let mut origin = point(
376 traffic_light_position.x,
377 titlebar_height
378 - traffic_light_position.y
379 - px(close_button_frame.size.height as f32),
380 );
381 let button_spacing =
382 px((min_button_frame.origin.x - close_button_frame.origin.x) as f32);
383
384 close_button_frame.origin = CGPoint::new(origin.x.into(), origin.y.into());
385 let _: () = msg_send![close_button, setFrame: close_button_frame];
386 origin.x += button_spacing;
387
388 min_button_frame.origin = CGPoint::new(origin.x.into(), origin.y.into());
389 let _: () = msg_send![min_button, setFrame: min_button_frame];
390 origin.x += button_spacing;
391
392 zoom_button_frame.origin = CGPoint::new(origin.x.into(), origin.y.into());
393 let _: () = msg_send![zoom_button, setFrame: zoom_button_frame];
394 origin.x += button_spacing;
395 }
396 }
397 }
398
399 fn start_display_link(&mut self) {
400 self.stop_display_link();
401 let display_id = unsafe { display_id_for_screen(self.native_window.screen()) };
402 if let Some(mut display_link) =
403 DisplayLink::new(display_id, self.native_view.as_ptr() as *mut c_void, step).log_err()
404 {
405 display_link.start().log_err();
406 self.display_link = Some(display_link);
407 }
408 }
409
410 fn stop_display_link(&mut self) {
411 self.display_link = None;
412 }
413
414 fn is_maximized(&self) -> bool {
415 unsafe {
416 let bounds = self.bounds();
417 let screen_size = self.native_window.screen().visibleFrame().into();
418 bounds.size == screen_size
419 }
420 }
421
422 // todo(macos)
423 fn is_minimized(&self) -> bool {
424 false
425 }
426
427 fn is_fullscreen(&self) -> bool {
428 unsafe {
429 let style_mask = self.native_window.styleMask();
430 style_mask.contains(NSWindowStyleMask::NSFullScreenWindowMask)
431 }
432 }
433
434 fn bounds(&self) -> Bounds<GlobalPixels> {
435 let frame = unsafe { NSWindow::frame(self.native_window) };
436 global_bounds_from_ns_rect(frame)
437 }
438
439 fn content_size(&self) -> Size<Pixels> {
440 let NSSize { width, height, .. } =
441 unsafe { NSView::frame(self.native_window.contentView()) }.size;
442 size(px(width as f32), px(height as f32))
443 }
444
445 fn scale_factor(&self) -> f32 {
446 get_scale_factor(self.native_window)
447 }
448
449 fn update_drawable_size(&mut self, drawable_size: NSSize) {
450 self.renderer.update_drawable_size(Size {
451 width: drawable_size.width,
452 height: drawable_size.height,
453 })
454 }
455
456 fn titlebar_height(&self) -> Pixels {
457 unsafe {
458 let frame = NSWindow::frame(self.native_window);
459 let content_layout_rect: CGRect = msg_send![self.native_window, contentLayoutRect];
460 px((frame.size.height - content_layout_rect.size.height) as f32)
461 }
462 }
463
464 fn to_screen_ns_point(&self, point: Point<Pixels>) -> NSPoint {
465 unsafe {
466 let point = NSPoint::new(
467 point.x.into(),
468 (self.content_size().height - point.y).into(),
469 );
470 msg_send![self.native_window, convertPointToScreen: point]
471 }
472 }
473}
474
475unsafe impl Send for MacWindowState {}
476
477pub(crate) struct MacWindow(Arc<Mutex<MacWindowState>>);
478
479impl MacWindow {
480 pub fn open(
481 handle: AnyWindowHandle,
482 WindowParams {
483 bounds,
484 titlebar,
485 kind,
486 is_movable,
487 display_id,
488 focus,
489 show,
490 }: WindowParams,
491 executor: ForegroundExecutor,
492 renderer_context: renderer::Context,
493 ) -> Self {
494 unsafe {
495 let pool = NSAutoreleasePool::new(nil);
496
497 let mut style_mask;
498 if let Some(titlebar) = titlebar.as_ref() {
499 style_mask = NSWindowStyleMask::NSClosableWindowMask
500 | NSWindowStyleMask::NSMiniaturizableWindowMask
501 | NSWindowStyleMask::NSResizableWindowMask
502 | NSWindowStyleMask::NSTitledWindowMask;
503
504 if titlebar.appears_transparent {
505 style_mask |= NSWindowStyleMask::NSFullSizeContentViewWindowMask;
506 }
507 } else {
508 style_mask = NSWindowStyleMask::NSTitledWindowMask
509 | NSWindowStyleMask::NSFullSizeContentViewWindowMask;
510 }
511
512 let native_window: id = match kind {
513 WindowKind::Normal => msg_send![WINDOW_CLASS, alloc],
514 WindowKind::PopUp => {
515 style_mask |= NSWindowStyleMaskNonactivatingPanel;
516 msg_send![PANEL_CLASS, alloc]
517 }
518 };
519
520 let display = display_id
521 .and_then(MacDisplay::find_by_id)
522 .unwrap_or_else(MacDisplay::primary);
523
524 let mut target_screen = nil;
525 let screens = NSScreen::screens(nil);
526 let count: u64 = cocoa::foundation::NSArray::count(screens);
527 for i in 0..count {
528 let screen = cocoa::foundation::NSArray::objectAtIndex(screens, i);
529 let display_id = display_id_for_screen(screen);
530 if display_id == display.id().0 {
531 target_screen = screen;
532 break;
533 }
534 }
535
536 let window_rect = {
537 let display_bounds = display.bounds();
538 if bounds.intersects(&display_bounds) {
539 global_bounds_to_ns_rect(bounds)
540 } else {
541 global_bounds_to_ns_rect(display_bounds)
542 }
543 };
544
545 let native_window = native_window.initWithContentRect_styleMask_backing_defer_screen_(
546 window_rect,
547 style_mask,
548 NSBackingStoreBuffered,
549 NO,
550 target_screen,
551 );
552 assert!(!native_window.is_null());
553 let () = msg_send![
554 native_window,
555 registerForDraggedTypes:
556 NSArray::arrayWithObject(nil, NSFilenamesPboardType)
557 ];
558
559 let native_view: id = msg_send![VIEW_CLASS, alloc];
560 let native_view = NSView::init(native_view);
561 assert!(!native_view.is_null());
562
563 let window_size = {
564 let scale = get_scale_factor(native_window);
565 size(bounds.size.width.0 * scale, bounds.size.height.0 * scale)
566 };
567
568 let window = Self(Arc::new(Mutex::new(MacWindowState {
569 handle,
570 executor,
571 native_window,
572 native_window_was_closed: false,
573 native_view: NonNull::new_unchecked(native_view),
574 display_link: None,
575 renderer: renderer::new_renderer(
576 renderer_context,
577 native_window as *mut _,
578 native_view as *mut _,
579 window_size,
580 ),
581 kind,
582 request_frame_callback: None,
583 event_callback: None,
584 activate_callback: None,
585 resize_callback: None,
586 fullscreen_callback: None,
587 moved_callback: None,
588 should_close_callback: None,
589 close_callback: None,
590 appearance_changed_callback: None,
591 input_handler: None,
592 last_key_equivalent: None,
593 synthetic_drag_counter: 0,
594 last_fresh_keydown: None,
595 traffic_light_position: titlebar
596 .as_ref()
597 .and_then(|titlebar| titlebar.traffic_light_position),
598 previous_modifiers_changed_event: None,
599 input_during_keydown: None,
600 previous_keydown_inserted_text: None,
601 external_files_dragged: false,
602 })));
603
604 (*native_window).set_ivar(
605 WINDOW_STATE_IVAR,
606 Arc::into_raw(window.0.clone()) as *const c_void,
607 );
608 native_window.setDelegate_(native_window);
609 (*native_view).set_ivar(
610 WINDOW_STATE_IVAR,
611 Arc::into_raw(window.0.clone()) as *const c_void,
612 );
613
614 if let Some(title) = titlebar
615 .as_ref()
616 .and_then(|t| t.title.as_ref().map(AsRef::as_ref))
617 {
618 native_window.setTitle_(NSString::alloc(nil).init_str(title));
619 }
620
621 native_window.setMovable_(is_movable as BOOL);
622
623 if titlebar.map_or(true, |titlebar| titlebar.appears_transparent) {
624 native_window.setTitlebarAppearsTransparent_(YES);
625 native_window.setTitleVisibility_(NSWindowTitleVisibility::NSWindowTitleHidden);
626 }
627
628 native_view.setAutoresizingMask_(NSViewWidthSizable | NSViewHeightSizable);
629 native_view.setWantsBestResolutionOpenGLSurface_(YES);
630
631 // From winit crate: On Mojave, views automatically become layer-backed shortly after
632 // being added to a native_window. Changing the layer-backedness of a view breaks the
633 // association between the view and its associated OpenGL context. To work around this,
634 // on we explicitly make the view layer-backed up front so that AppKit doesn't do it
635 // itself and break the association with its context.
636 native_view.setWantsLayer(YES);
637 let _: () = msg_send![
638 native_view,
639 setLayerContentsRedrawPolicy: NSViewLayerContentsRedrawDuringViewResize
640 ];
641
642 native_window.setContentView_(native_view.autorelease());
643 native_window.makeFirstResponder_(native_view);
644
645 match kind {
646 WindowKind::Normal => {
647 native_window.setLevel_(NSNormalWindowLevel);
648 native_window.setAcceptsMouseMovedEvents_(YES);
649 }
650 WindowKind::PopUp => {
651 // Use a tracking area to allow receiving MouseMoved events even when
652 // the window or application aren't active, which is often the case
653 // e.g. for notification windows.
654 let tracking_area: id = msg_send![class!(NSTrackingArea), alloc];
655 let _: () = msg_send![
656 tracking_area,
657 initWithRect: NSRect::new(NSPoint::new(0., 0.), NSSize::new(0., 0.))
658 options: NSTrackingMouseEnteredAndExited | NSTrackingMouseMoved | NSTrackingActiveAlways | NSTrackingInVisibleRect
659 owner: native_view
660 userInfo: nil
661 ];
662 let _: () =
663 msg_send![native_view, addTrackingArea: tracking_area.autorelease()];
664
665 native_window.setLevel_(NSPopUpWindowLevel);
666 let _: () = msg_send![
667 native_window,
668 setAnimationBehavior: NSWindowAnimationBehaviorUtilityWindow
669 ];
670 native_window.setCollectionBehavior_(
671 NSWindowCollectionBehavior::NSWindowCollectionBehaviorCanJoinAllSpaces |
672 NSWindowCollectionBehavior::NSWindowCollectionBehaviorFullScreenAuxiliary
673 );
674 }
675 }
676
677 if focus {
678 native_window.makeKeyAndOrderFront_(nil);
679 } else if show {
680 native_window.orderFront_(nil);
681 }
682
683 window.0.lock().move_traffic_light();
684
685 pool.drain();
686
687 window
688 }
689 }
690
691 pub fn active_window() -> Option<AnyWindowHandle> {
692 unsafe {
693 let app = NSApplication::sharedApplication(nil);
694 let main_window: id = msg_send![app, mainWindow];
695 if msg_send![main_window, isKindOfClass: WINDOW_CLASS] {
696 let handle = get_window_state(&*main_window).lock().handle;
697 Some(handle)
698 } else {
699 None
700 }
701 }
702 }
703}
704
705impl Drop for MacWindow {
706 fn drop(&mut self) {
707 let mut this = self.0.lock();
708 this.renderer.destroy();
709 let window = this.native_window;
710 this.display_link.take();
711 if !this.native_window_was_closed {
712 unsafe {
713 this.native_window.setDelegate_(nil);
714 }
715
716 this.executor
717 .spawn(async move {
718 unsafe {
719 window.close();
720 }
721 })
722 .detach();
723 }
724 }
725}
726
727impl PlatformWindow for MacWindow {
728 fn bounds(&self) -> Bounds<GlobalPixels> {
729 self.0.as_ref().lock().bounds()
730 }
731
732 fn is_maximized(&self) -> bool {
733 self.0.as_ref().lock().is_maximized()
734 }
735
736 fn is_minimized(&self) -> bool {
737 self.0.as_ref().lock().is_minimized()
738 }
739
740 fn content_size(&self) -> Size<Pixels> {
741 self.0.as_ref().lock().content_size()
742 }
743
744 fn scale_factor(&self) -> f32 {
745 self.0.as_ref().lock().scale_factor()
746 }
747
748 fn appearance(&self) -> WindowAppearance {
749 unsafe {
750 let appearance: id = msg_send![self.0.lock().native_window, effectiveAppearance];
751 WindowAppearance::from_native(appearance)
752 }
753 }
754
755 fn display(&self) -> Rc<dyn PlatformDisplay> {
756 unsafe {
757 let screen = self.0.lock().native_window.screen();
758 let device_description: id = msg_send![screen, deviceDescription];
759 let screen_number: id = NSDictionary::valueForKey_(
760 device_description,
761 NSString::alloc(nil).init_str("NSScreenNumber"),
762 );
763
764 let screen_number: u32 = msg_send![screen_number, unsignedIntValue];
765
766 Rc::new(MacDisplay(screen_number))
767 }
768 }
769
770 fn mouse_position(&self) -> Point<Pixels> {
771 let position = unsafe {
772 self.0
773 .lock()
774 .native_window
775 .mouseLocationOutsideOfEventStream()
776 };
777 convert_mouse_position(position, self.content_size().height)
778 }
779
780 fn modifiers(&self) -> Modifiers {
781 unsafe {
782 let modifiers: NSEventModifierFlags = msg_send![class!(NSEvent), modifierFlags];
783
784 let control = modifiers.contains(NSEventModifierFlags::NSControlKeyMask);
785 let alt = modifiers.contains(NSEventModifierFlags::NSAlternateKeyMask);
786 let shift = modifiers.contains(NSEventModifierFlags::NSShiftKeyMask);
787 let command = modifiers.contains(NSEventModifierFlags::NSCommandKeyMask);
788 let function = modifiers.contains(NSEventModifierFlags::NSFunctionKeyMask);
789
790 Modifiers {
791 control,
792 alt,
793 shift,
794 command,
795 function,
796 }
797 }
798 }
799
800 fn as_any_mut(&mut self) -> &mut dyn Any {
801 self
802 }
803
804 fn set_input_handler(&mut self, input_handler: PlatformInputHandler) {
805 self.0.as_ref().lock().input_handler = Some(input_handler);
806 }
807
808 fn take_input_handler(&mut self) -> Option<PlatformInputHandler> {
809 self.0.as_ref().lock().input_handler.take()
810 }
811
812 fn prompt(
813 &self,
814 level: PromptLevel,
815 msg: &str,
816 detail: Option<&str>,
817 answers: &[&str],
818 ) -> Option<oneshot::Receiver<usize>> {
819 // macOs applies overrides to modal window buttons after they are added.
820 // Two most important for this logic are:
821 // * Buttons with "Cancel" title will be displayed as the last buttons in the modal
822 // * Last button added to the modal via `addButtonWithTitle` stays focused
823 // * Focused buttons react on "space"/" " keypresses
824 // * Usage of `keyEquivalent`, `makeFirstResponder` or `setInitialFirstResponder` does not change the focus
825 //
826 // See also https://developer.apple.com/documentation/appkit/nsalert/1524532-addbuttonwithtitle#discussion
827 // ```
828 // By default, the first button has a key equivalent of Return,
829 // any button with a title of “Cancel” has a key equivalent of Escape,
830 // 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).
831 // ```
832 //
833 // To avoid situations when the last element added is "Cancel" and it gets the focus
834 // (hence stealing both ESC and Space shortcuts), we find and add one non-Cancel button
835 // last, so it gets focus and a Space shortcut.
836 // This way, "Save this file? Yes/No/Cancel"-ish modals will get all three buttons mapped with a key.
837 let latest_non_cancel_label = answers
838 .iter()
839 .enumerate()
840 .rev()
841 .find(|(_, &label)| label != "Cancel")
842 .filter(|&(label_index, _)| label_index > 0);
843
844 unsafe {
845 let alert: id = msg_send![class!(NSAlert), alloc];
846 let alert: id = msg_send![alert, init];
847 let alert_style = match level {
848 PromptLevel::Info => 1,
849 PromptLevel::Warning => 0,
850 PromptLevel::Critical => 2,
851 };
852 let _: () = msg_send![alert, setAlertStyle: alert_style];
853 let _: () = msg_send![alert, setMessageText: ns_string(msg)];
854 if let Some(detail) = detail {
855 let _: () = msg_send![alert, setInformativeText: ns_string(detail)];
856 }
857
858 for (ix, answer) in answers
859 .iter()
860 .enumerate()
861 .filter(|&(ix, _)| Some(ix) != latest_non_cancel_label.map(|(ix, _)| ix))
862 {
863 let button: id = msg_send![alert, addButtonWithTitle: ns_string(answer)];
864 let _: () = msg_send![button, setTag: ix as NSInteger];
865 }
866 if let Some((ix, answer)) = latest_non_cancel_label {
867 let button: id = msg_send![alert, addButtonWithTitle: ns_string(answer)];
868 let _: () = msg_send![button, setTag: ix as NSInteger];
869 }
870
871 let (done_tx, done_rx) = oneshot::channel();
872 let done_tx = Cell::new(Some(done_tx));
873 let block = ConcreteBlock::new(move |answer: NSInteger| {
874 if let Some(done_tx) = done_tx.take() {
875 let _ = done_tx.send(answer.try_into().unwrap());
876 }
877 });
878 let block = block.copy();
879 let native_window = self.0.lock().native_window;
880 let executor = self.0.lock().executor.clone();
881 executor
882 .spawn(async move {
883 let _: () = msg_send![
884 alert,
885 beginSheetModalForWindow: native_window
886 completionHandler: block
887 ];
888 })
889 .detach();
890
891 Some(done_rx)
892 }
893 }
894
895 fn activate(&self) {
896 let window = self.0.lock().native_window;
897 let executor = self.0.lock().executor.clone();
898 executor
899 .spawn(async move {
900 unsafe {
901 let _: () = msg_send![window, makeKeyAndOrderFront: nil];
902 }
903 })
904 .detach();
905 }
906
907 fn set_title(&mut self, title: &str) {
908 unsafe {
909 let app = NSApplication::sharedApplication(nil);
910 let window = self.0.lock().native_window;
911 let title = ns_string(title);
912 let _: () = msg_send![app, changeWindowsItem:window title:title filename:false];
913 let _: () = msg_send![window, setTitle: title];
914 self.0.lock().move_traffic_light();
915 }
916 }
917
918 fn set_edited(&mut self, edited: bool) {
919 unsafe {
920 let window = self.0.lock().native_window;
921 msg_send![window, setDocumentEdited: edited as BOOL]
922 }
923
924 // Changing the document edited state resets the traffic light position,
925 // so we have to move it again.
926 self.0.lock().move_traffic_light();
927 }
928
929 fn show_character_palette(&self) {
930 let this = self.0.lock();
931 let window = this.native_window;
932 this.executor
933 .spawn(async move {
934 unsafe {
935 let app = NSApplication::sharedApplication(nil);
936 let _: () = msg_send![app, orderFrontCharacterPalette: window];
937 }
938 })
939 .detach();
940 }
941
942 fn minimize(&self) {
943 let window = self.0.lock().native_window;
944 unsafe {
945 window.miniaturize_(nil);
946 }
947 }
948
949 fn zoom(&self) {
950 let this = self.0.lock();
951 let window = this.native_window;
952 this.executor
953 .spawn(async move {
954 unsafe {
955 window.zoom_(nil);
956 }
957 })
958 .detach();
959 }
960
961 fn toggle_fullscreen(&self) {
962 let this = self.0.lock();
963 let window = this.native_window;
964 this.executor
965 .spawn(async move {
966 unsafe {
967 window.toggleFullScreen_(nil);
968 }
969 })
970 .detach();
971 }
972
973 fn is_fullscreen(&self) -> bool {
974 let this = self.0.lock();
975 let window = this.native_window;
976
977 unsafe {
978 window
979 .styleMask()
980 .contains(NSWindowStyleMask::NSFullScreenWindowMask)
981 }
982 }
983
984 fn on_request_frame(&self, callback: Box<dyn FnMut()>) {
985 self.0.as_ref().lock().request_frame_callback = Some(callback);
986 }
987
988 fn on_input(&self, callback: Box<dyn FnMut(PlatformInput) -> crate::DispatchEventResult>) {
989 self.0.as_ref().lock().event_callback = Some(callback);
990 }
991
992 fn on_active_status_change(&self, callback: Box<dyn FnMut(bool)>) {
993 self.0.as_ref().lock().activate_callback = Some(callback);
994 }
995
996 fn on_resize(&self, callback: Box<dyn FnMut(Size<Pixels>, f32)>) {
997 self.0.as_ref().lock().resize_callback = Some(callback);
998 }
999
1000 fn on_fullscreen(&self, callback: Box<dyn FnMut(bool)>) {
1001 self.0.as_ref().lock().fullscreen_callback = Some(callback);
1002 }
1003
1004 fn on_moved(&self, callback: Box<dyn FnMut()>) {
1005 self.0.as_ref().lock().moved_callback = Some(callback);
1006 }
1007
1008 fn on_should_close(&self, callback: Box<dyn FnMut() -> bool>) {
1009 self.0.as_ref().lock().should_close_callback = Some(callback);
1010 }
1011
1012 fn on_close(&self, callback: Box<dyn FnOnce()>) {
1013 self.0.as_ref().lock().close_callback = Some(callback);
1014 }
1015
1016 fn on_appearance_changed(&self, callback: Box<dyn FnMut()>) {
1017 self.0.lock().appearance_changed_callback = Some(callback);
1018 }
1019
1020 fn is_topmost_for_position(&self, position: Point<Pixels>) -> bool {
1021 let self_borrow = self.0.lock();
1022 let self_handle = self_borrow.handle;
1023
1024 unsafe {
1025 let app = NSApplication::sharedApplication(nil);
1026
1027 // Convert back to screen coordinates
1028 let screen_point = self_borrow.to_screen_ns_point(position);
1029
1030 let window_number: NSInteger = msg_send![class!(NSWindow), windowNumberAtPoint:screen_point belowWindowWithWindowNumber:0];
1031 let top_most_window: id = msg_send![app, windowWithWindowNumber: window_number];
1032
1033 let is_panel: BOOL = msg_send![top_most_window, isKindOfClass: PANEL_CLASS];
1034 let is_window: BOOL = msg_send![top_most_window, isKindOfClass: WINDOW_CLASS];
1035 if is_panel == YES || is_window == YES {
1036 let topmost_window = get_window_state(&*top_most_window).lock().handle;
1037 topmost_window == self_handle
1038 } else {
1039 // Someone else's window is on top
1040 false
1041 }
1042 }
1043 }
1044
1045 fn draw(&self, scene: &crate::Scene) {
1046 let mut this = self.0.lock();
1047 this.renderer.draw(scene);
1048 }
1049
1050 fn sprite_atlas(&self) -> Arc<dyn PlatformAtlas> {
1051 self.0.lock().renderer.sprite_atlas().clone()
1052 }
1053}
1054
1055impl HasWindowHandle for MacWindow {
1056 fn window_handle(
1057 &self,
1058 ) -> Result<raw_window_handle::WindowHandle<'_>, raw_window_handle::HandleError> {
1059 // SAFETY: The AppKitWindowHandle is a wrapper around a pointer to an NSView
1060 unsafe {
1061 Ok(WindowHandle::borrow_raw(RawWindowHandle::AppKit(
1062 AppKitWindowHandle::new(self.0.lock().native_view.cast()),
1063 )))
1064 }
1065 }
1066}
1067
1068impl HasDisplayHandle for MacWindow {
1069 fn display_handle(
1070 &self,
1071 ) -> Result<raw_window_handle::DisplayHandle<'_>, raw_window_handle::HandleError> {
1072 // SAFETY: This is a no-op on macOS
1073 unsafe { Ok(DisplayHandle::borrow_raw(AppKitDisplayHandle::new().into())) }
1074 }
1075}
1076
1077fn get_scale_factor(native_window: id) -> f32 {
1078 let factor = unsafe {
1079 let screen: id = msg_send![native_window, screen];
1080 NSScreen::backingScaleFactor(screen) as f32
1081 };
1082
1083 // We are not certain what triggers this, but it seems that sometimes
1084 // this method would return 0 (https://github.com/zed-industries/zed/issues/6412)
1085 // It seems most likely that this would happen if the window has no screen
1086 // (if it is off-screen), though we'd expect to see viewDidChangeBackingProperties before
1087 // it was rendered for real.
1088 // Regardless, attempt to avoid the issue here.
1089 if factor == 0.0 {
1090 2.
1091 } else {
1092 factor
1093 }
1094}
1095
1096unsafe fn get_window_state(object: &Object) -> Arc<Mutex<MacWindowState>> {
1097 let raw: *mut c_void = *object.get_ivar(WINDOW_STATE_IVAR);
1098 let rc1 = Arc::from_raw(raw as *mut Mutex<MacWindowState>);
1099 let rc2 = rc1.clone();
1100 mem::forget(rc1);
1101 rc2
1102}
1103
1104unsafe fn drop_window_state(object: &Object) {
1105 let raw: *mut c_void = *object.get_ivar(WINDOW_STATE_IVAR);
1106 Arc::from_raw(raw as *mut Mutex<MacWindowState>);
1107}
1108
1109extern "C" fn yes(_: &Object, _: Sel) -> BOOL {
1110 YES
1111}
1112
1113extern "C" fn dealloc_window(this: &Object, _: Sel) {
1114 unsafe {
1115 drop_window_state(this);
1116 let _: () = msg_send![super(this, class!(NSWindow)), dealloc];
1117 }
1118}
1119
1120extern "C" fn dealloc_view(this: &Object, _: Sel) {
1121 unsafe {
1122 drop_window_state(this);
1123 let _: () = msg_send![super(this, class!(NSView)), dealloc];
1124 }
1125}
1126
1127extern "C" fn handle_key_equivalent(this: &Object, _: Sel, native_event: id) -> BOOL {
1128 handle_key_event(this, native_event, true)
1129}
1130
1131extern "C" fn handle_key_down(this: &Object, _: Sel, native_event: id) {
1132 handle_key_event(this, native_event, false);
1133}
1134
1135// Things to test if you're modifying this method:
1136// Brazilian layout:
1137// - `" space` should type a quote
1138// - `" backspace` should delete the marked quote
1139// - `" up` should type the quote, unmark it, and move up one line
1140// - `" cmd-down` should not leave a marked quote behind (it maybe should dispatch the key though?)
1141// - `cmd-ctrl-space` and clicking on an emoji should type it
1142// Czech (QWERTY) layout:
1143// - in vim mode `option-4` should go to end of line (same as $)
1144extern "C" fn handle_key_event(this: &Object, native_event: id, key_equivalent: bool) -> BOOL {
1145 let window_state = unsafe { get_window_state(this) };
1146 let mut lock = window_state.as_ref().lock();
1147
1148 let window_height = lock.content_size().height;
1149 let event = unsafe { PlatformInput::from_native(native_event, Some(window_height)) };
1150
1151 if let Some(PlatformInput::KeyDown(mut event)) = event {
1152 // For certain keystrokes, macOS will first dispatch a "key equivalent" event.
1153 // If that event isn't handled, it will then dispatch a "key down" event. GPUI
1154 // makes no distinction between these two types of events, so we need to ignore
1155 // the "key down" event if we've already just processed its "key equivalent" version.
1156 if key_equivalent {
1157 lock.last_key_equivalent = Some(event.clone());
1158 } else if lock.last_key_equivalent.take().as_ref() == Some(&event) {
1159 return NO;
1160 }
1161
1162 let keydown = event.keystroke.clone();
1163 let fn_modifier = keydown.modifiers.function;
1164 // Ignore events from held-down keys after some of the initially-pressed keys
1165 // were released.
1166 if event.is_held {
1167 if lock.last_fresh_keydown.as_ref() != Some(&keydown) {
1168 return YES;
1169 }
1170 } else {
1171 lock.last_fresh_keydown = Some(keydown.clone());
1172 }
1173 lock.input_during_keydown = Some(SmallVec::new());
1174 drop(lock);
1175
1176 // Send the event to the input context for IME handling, unless the `fn` modifier is
1177 // being pressed.
1178 // this will call back into `insert_text`, etc.
1179 if !fn_modifier {
1180 unsafe {
1181 let input_context: id = msg_send![this, inputContext];
1182 let _: BOOL = msg_send![input_context, handleEvent: native_event];
1183 }
1184 }
1185
1186 let mut handled = false;
1187 let mut lock = window_state.lock();
1188 let previous_keydown_inserted_text = lock.previous_keydown_inserted_text.take();
1189 let mut input_during_keydown = lock.input_during_keydown.take().unwrap();
1190 let mut callback = lock.event_callback.take();
1191 drop(lock);
1192
1193 let last_ime = input_during_keydown.pop();
1194 // on a brazilian keyboard typing `"` and then hitting `up` will cause two IME
1195 // events, one to unmark the quote, and one to send the up arrow.
1196 for ime in input_during_keydown {
1197 send_to_input_handler(this, ime);
1198 }
1199
1200 let is_composing =
1201 with_input_handler(this, |input_handler| input_handler.marked_text_range())
1202 .flatten()
1203 .is_some();
1204
1205 if let Some(ime) = last_ime {
1206 if let ImeInput::InsertText(text, _) = &ime {
1207 if !is_composing {
1208 window_state.lock().previous_keydown_inserted_text = Some(text.clone());
1209 if let Some(callback) = callback.as_mut() {
1210 event.keystroke.ime_key = Some(text.clone());
1211 handled = !callback(PlatformInput::KeyDown(event)).propagate;
1212 }
1213 }
1214 }
1215
1216 if !handled {
1217 handled = true;
1218 send_to_input_handler(this, ime);
1219 }
1220 } else if !is_composing {
1221 let is_held = event.is_held;
1222
1223 if let Some(callback) = callback.as_mut() {
1224 handled = !callback(PlatformInput::KeyDown(event)).propagate;
1225 }
1226
1227 if !handled && is_held {
1228 if let Some(text) = previous_keydown_inserted_text {
1229 // MacOS IME is a bit funky, and even when you've told it there's nothing to
1230 // enter it will still swallow certain keys (e.g. 'f', 'j') and not others
1231 // (e.g. 'n'). This is a problem for certain kinds of views, like the terminal.
1232 with_input_handler(this, |input_handler| {
1233 if input_handler.selected_text_range().is_none() {
1234 handled = true;
1235 input_handler.replace_text_in_range(None, &text)
1236 }
1237 });
1238 window_state.lock().previous_keydown_inserted_text = Some(text);
1239 }
1240 }
1241 }
1242
1243 window_state.lock().event_callback = callback;
1244
1245 handled as BOOL
1246 } else {
1247 NO
1248 }
1249}
1250
1251extern "C" fn handle_view_event(this: &Object, _: Sel, native_event: id) {
1252 let window_state = unsafe { get_window_state(this) };
1253 let weak_window_state = Arc::downgrade(&window_state);
1254 let mut lock = window_state.as_ref().lock();
1255 let is_active = unsafe { lock.native_window.isKeyWindow() == YES };
1256
1257 let window_height = lock.content_size().height;
1258 let event = unsafe { PlatformInput::from_native(native_event, Some(window_height)) };
1259
1260 if let Some(mut event) = event {
1261 match &mut event {
1262 PlatformInput::MouseDown(
1263 event @ MouseDownEvent {
1264 button: MouseButton::Left,
1265 modifiers: Modifiers { control: true, .. },
1266 ..
1267 },
1268 ) => {
1269 // On mac, a ctrl-left click should be handled as a right click.
1270 *event = MouseDownEvent {
1271 button: MouseButton::Right,
1272 modifiers: Modifiers {
1273 control: false,
1274 ..event.modifiers
1275 },
1276 click_count: 1,
1277 ..*event
1278 };
1279 }
1280
1281 // Because we map a ctrl-left_down to a right_down -> right_up let's ignore
1282 // the ctrl-left_up to avoid having a mismatch in button down/up events if the
1283 // user is still holding ctrl when releasing the left mouse button
1284 PlatformInput::MouseUp(
1285 event @ MouseUpEvent {
1286 button: MouseButton::Left,
1287 modifiers: Modifiers { control: true, .. },
1288 ..
1289 },
1290 ) => {
1291 *event = MouseUpEvent {
1292 button: MouseButton::Right,
1293 modifiers: Modifiers {
1294 control: false,
1295 ..event.modifiers
1296 },
1297 click_count: 1,
1298 ..*event
1299 };
1300 }
1301
1302 _ => {}
1303 };
1304
1305 match &event {
1306 PlatformInput::MouseMove(
1307 event @ MouseMoveEvent {
1308 pressed_button: Some(_),
1309 ..
1310 },
1311 ) => {
1312 // Synthetic drag is used for selecting long buffer contents while buffer is being scrolled.
1313 // External file drag and drop is able to emit its own synthetic mouse events which will conflict
1314 // with these ones.
1315 if !lock.external_files_dragged {
1316 lock.synthetic_drag_counter += 1;
1317 let executor = lock.executor.clone();
1318 executor
1319 .spawn(synthetic_drag(
1320 weak_window_state,
1321 lock.synthetic_drag_counter,
1322 event.clone(),
1323 ))
1324 .detach();
1325 }
1326 }
1327
1328 PlatformInput::MouseMove(_) if !(is_active || lock.kind == WindowKind::PopUp) => return,
1329
1330 PlatformInput::MouseUp(MouseUpEvent { .. }) => {
1331 lock.synthetic_drag_counter += 1;
1332 }
1333
1334 PlatformInput::ModifiersChanged(ModifiersChangedEvent { modifiers }) => {
1335 // Only raise modifiers changed event when they have actually changed
1336 if let Some(PlatformInput::ModifiersChanged(ModifiersChangedEvent {
1337 modifiers: prev_modifiers,
1338 })) = &lock.previous_modifiers_changed_event
1339 {
1340 if prev_modifiers == modifiers {
1341 return;
1342 }
1343 }
1344
1345 lock.previous_modifiers_changed_event = Some(event.clone());
1346 }
1347
1348 _ => {}
1349 }
1350
1351 if let Some(mut callback) = lock.event_callback.take() {
1352 drop(lock);
1353 callback(event);
1354 window_state.lock().event_callback = Some(callback);
1355 }
1356 }
1357}
1358
1359// Allows us to receive `cmd-.` (the shortcut for closing a dialog)
1360// https://bugs.eclipse.org/bugs/show_bug.cgi?id=300620#c6
1361extern "C" fn cancel_operation(this: &Object, _sel: Sel, _sender: id) {
1362 let window_state = unsafe { get_window_state(this) };
1363 let mut lock = window_state.as_ref().lock();
1364
1365 let keystroke = Keystroke {
1366 modifiers: Default::default(),
1367 key: ".".into(),
1368 ime_key: None,
1369 };
1370 let event = PlatformInput::KeyDown(KeyDownEvent {
1371 keystroke: keystroke.clone(),
1372 is_held: false,
1373 });
1374
1375 lock.last_fresh_keydown = Some(keystroke);
1376 if let Some(mut callback) = lock.event_callback.take() {
1377 drop(lock);
1378 callback(event);
1379 window_state.lock().event_callback = Some(callback);
1380 }
1381}
1382
1383extern "C" fn window_did_change_occlusion_state(this: &Object, _: Sel, _: id) {
1384 let window_state = unsafe { get_window_state(this) };
1385 let lock = &mut *window_state.lock();
1386 unsafe {
1387 if lock
1388 .native_window
1389 .occlusionState()
1390 .contains(NSWindowOcclusionState::NSWindowOcclusionStateVisible)
1391 {
1392 lock.start_display_link();
1393 } else {
1394 lock.stop_display_link();
1395 }
1396 }
1397}
1398
1399extern "C" fn window_did_resize(this: &Object, _: Sel, _: id) {
1400 let window_state = unsafe { get_window_state(this) };
1401 window_state.as_ref().lock().move_traffic_light();
1402}
1403
1404extern "C" fn window_will_enter_fullscreen(this: &Object, _: Sel, _: id) {
1405 window_fullscreen_changed(this, true);
1406}
1407
1408extern "C" fn window_will_exit_fullscreen(this: &Object, _: Sel, _: id) {
1409 window_fullscreen_changed(this, false);
1410}
1411
1412fn window_fullscreen_changed(this: &Object, is_fullscreen: bool) {
1413 let window_state = unsafe { get_window_state(this) };
1414 let mut lock = window_state.as_ref().lock();
1415 if let Some(mut callback) = lock.fullscreen_callback.take() {
1416 drop(lock);
1417 callback(is_fullscreen);
1418 window_state.lock().fullscreen_callback = Some(callback);
1419 }
1420}
1421
1422extern "C" fn window_did_move(this: &Object, _: Sel, _: id) {
1423 let window_state = unsafe { get_window_state(this) };
1424 let mut lock = window_state.as_ref().lock();
1425 if let Some(mut callback) = lock.moved_callback.take() {
1426 drop(lock);
1427 callback();
1428 window_state.lock().moved_callback = Some(callback);
1429 }
1430}
1431
1432extern "C" fn window_did_change_screen(this: &Object, _: Sel, _: id) {
1433 let window_state = unsafe { get_window_state(this) };
1434 let mut lock = window_state.as_ref().lock();
1435 lock.start_display_link();
1436}
1437
1438extern "C" fn window_did_change_key_status(this: &Object, selector: Sel, _: id) {
1439 let window_state = unsafe { get_window_state(this) };
1440 let lock = window_state.lock();
1441 let is_active = unsafe { lock.native_window.isKeyWindow() == YES };
1442
1443 // When opening a pop-up while the application isn't active, Cocoa sends a spurious
1444 // `windowDidBecomeKey` message to the previous key window even though that window
1445 // isn't actually key. This causes a bug if the application is later activated while
1446 // the pop-up is still open, making it impossible to activate the previous key window
1447 // even if the pop-up gets closed. The only way to activate it again is to de-activate
1448 // the app and re-activate it, which is a pretty bad UX.
1449 // The following code detects the spurious event and invokes `resignKeyWindow`:
1450 // in theory, we're not supposed to invoke this method manually but it balances out
1451 // the spurious `becomeKeyWindow` event and helps us work around that bug.
1452 if selector == sel!(windowDidBecomeKey:) && !is_active {
1453 unsafe {
1454 let _: () = msg_send![lock.native_window, resignKeyWindow];
1455 return;
1456 }
1457 }
1458
1459 let executor = lock.executor.clone();
1460 drop(lock);
1461 executor
1462 .spawn(async move {
1463 let mut lock = window_state.as_ref().lock();
1464 if let Some(mut callback) = lock.activate_callback.take() {
1465 drop(lock);
1466 callback(is_active);
1467 window_state.lock().activate_callback = Some(callback);
1468 };
1469 })
1470 .detach();
1471}
1472
1473extern "C" fn window_should_close(this: &Object, _: Sel, _: id) -> BOOL {
1474 let window_state = unsafe { get_window_state(this) };
1475 let mut lock = window_state.as_ref().lock();
1476 if let Some(mut callback) = lock.should_close_callback.take() {
1477 drop(lock);
1478 let should_close = callback();
1479 window_state.lock().should_close_callback = Some(callback);
1480 should_close as BOOL
1481 } else {
1482 YES
1483 }
1484}
1485
1486extern "C" fn close_window(this: &Object, _: Sel) {
1487 unsafe {
1488 let close_callback = {
1489 let window_state = get_window_state(this);
1490 let mut lock = window_state.as_ref().lock();
1491 lock.native_window_was_closed = true;
1492 lock.close_callback.take()
1493 };
1494
1495 if let Some(callback) = close_callback {
1496 callback();
1497 }
1498
1499 let _: () = msg_send![super(this, class!(NSWindow)), close];
1500 }
1501}
1502
1503extern "C" fn make_backing_layer(this: &Object, _: Sel) -> id {
1504 let window_state = unsafe { get_window_state(this) };
1505 let window_state = window_state.as_ref().lock();
1506 window_state.renderer.layer_ptr() as id
1507}
1508
1509extern "C" fn view_did_change_backing_properties(this: &Object, _: Sel) {
1510 let window_state = unsafe { get_window_state(this) };
1511 let mut lock = window_state.as_ref().lock();
1512
1513 let scale_factor = lock.scale_factor() as f64;
1514 let size = lock.content_size();
1515 let drawable_size: NSSize = NSSize {
1516 width: f64::from(size.width) * scale_factor,
1517 height: f64::from(size.height) * scale_factor,
1518 };
1519 unsafe {
1520 let _: () = msg_send![
1521 lock.renderer.layer(),
1522 setContentsScale: scale_factor
1523 ];
1524 }
1525
1526 lock.update_drawable_size(drawable_size);
1527
1528 if let Some(mut callback) = lock.resize_callback.take() {
1529 let content_size = lock.content_size();
1530 let scale_factor = lock.scale_factor();
1531 drop(lock);
1532 callback(content_size, scale_factor);
1533 window_state.as_ref().lock().resize_callback = Some(callback);
1534 };
1535}
1536
1537extern "C" fn set_frame_size(this: &Object, _: Sel, size: NSSize) {
1538 let window_state = unsafe { get_window_state(this) };
1539 let mut lock = window_state.as_ref().lock();
1540
1541 if lock.content_size() == size.into() {
1542 return;
1543 }
1544
1545 unsafe {
1546 let _: () = msg_send![super(this, class!(NSView)), setFrameSize: size];
1547 }
1548
1549 let scale_factor = lock.scale_factor() as f64;
1550 let drawable_size: NSSize = NSSize {
1551 width: size.width * scale_factor,
1552 height: size.height * scale_factor,
1553 };
1554
1555 lock.update_drawable_size(drawable_size);
1556
1557 drop(lock);
1558 let mut lock = window_state.lock();
1559 if let Some(mut callback) = lock.resize_callback.take() {
1560 let content_size = lock.content_size();
1561 let scale_factor = lock.scale_factor();
1562 drop(lock);
1563 callback(content_size, scale_factor);
1564 window_state.lock().resize_callback = Some(callback);
1565 };
1566}
1567
1568extern "C" fn display_layer(this: &Object, _: Sel, _: id) {
1569 let window_state = unsafe { get_window_state(this) };
1570 let mut lock = window_state.lock();
1571 if let Some(mut callback) = lock.request_frame_callback.take() {
1572 #[cfg(not(feature = "macos-blade"))]
1573 lock.renderer.set_presents_with_transaction(true);
1574 lock.stop_display_link();
1575 drop(lock);
1576 callback();
1577
1578 let mut lock = window_state.lock();
1579 lock.request_frame_callback = Some(callback);
1580 #[cfg(not(feature = "macos-blade"))]
1581 lock.renderer.set_presents_with_transaction(false);
1582 lock.start_display_link();
1583 }
1584}
1585
1586unsafe extern "C" fn step(view: *mut c_void) {
1587 let view = view as id;
1588 let window_state = unsafe { get_window_state(&*view) };
1589 let mut lock = window_state.lock();
1590
1591 if let Some(mut callback) = lock.request_frame_callback.take() {
1592 drop(lock);
1593 callback();
1594 window_state.lock().request_frame_callback = Some(callback);
1595 }
1596}
1597
1598extern "C" fn valid_attributes_for_marked_text(_: &Object, _: Sel) -> id {
1599 unsafe { msg_send![class!(NSArray), array] }
1600}
1601
1602extern "C" fn has_marked_text(this: &Object, _: Sel) -> BOOL {
1603 with_input_handler(this, |input_handler| input_handler.marked_text_range())
1604 .flatten()
1605 .is_some() as BOOL
1606}
1607
1608extern "C" fn marked_range(this: &Object, _: Sel) -> NSRange {
1609 with_input_handler(this, |input_handler| input_handler.marked_text_range())
1610 .flatten()
1611 .map_or(NSRange::invalid(), |range| range.into())
1612}
1613
1614extern "C" fn selected_range(this: &Object, _: Sel) -> NSRange {
1615 with_input_handler(this, |input_handler| input_handler.selected_text_range())
1616 .flatten()
1617 .map_or(NSRange::invalid(), |range| range.into())
1618}
1619
1620extern "C" fn first_rect_for_character_range(
1621 this: &Object,
1622 _: Sel,
1623 range: NSRange,
1624 _: id,
1625) -> NSRect {
1626 let frame = unsafe {
1627 let window = get_window_state(this).lock().native_window;
1628 NSView::frame(window)
1629 };
1630 with_input_handler(this, |input_handler| {
1631 input_handler.bounds_for_range(range.to_range()?)
1632 })
1633 .flatten()
1634 .map_or(
1635 NSRect::new(NSPoint::new(0., 0.), NSSize::new(0., 0.)),
1636 |bounds| {
1637 NSRect::new(
1638 NSPoint::new(
1639 frame.origin.x + bounds.origin.x.0 as f64,
1640 frame.origin.y + frame.size.height
1641 - bounds.origin.y.0 as f64
1642 - bounds.size.height.0 as f64,
1643 ),
1644 NSSize::new(bounds.size.width.0 as f64, bounds.size.height.0 as f64),
1645 )
1646 },
1647 )
1648}
1649
1650extern "C" fn insert_text(this: &Object, _: Sel, text: id, replacement_range: NSRange) {
1651 unsafe {
1652 let is_attributed_string: BOOL =
1653 msg_send![text, isKindOfClass: [class!(NSAttributedString)]];
1654 let text: id = if is_attributed_string == YES {
1655 msg_send![text, string]
1656 } else {
1657 text
1658 };
1659 let text = CStr::from_ptr(text.UTF8String() as *mut c_char)
1660 .to_str()
1661 .unwrap();
1662 let replacement_range = replacement_range.to_range();
1663 send_to_input_handler(
1664 this,
1665 ImeInput::InsertText(text.to_string(), replacement_range),
1666 );
1667 }
1668}
1669
1670extern "C" fn set_marked_text(
1671 this: &Object,
1672 _: Sel,
1673 text: id,
1674 selected_range: NSRange,
1675 replacement_range: NSRange,
1676) {
1677 unsafe {
1678 let is_attributed_string: BOOL =
1679 msg_send![text, isKindOfClass: [class!(NSAttributedString)]];
1680 let text: id = if is_attributed_string == YES {
1681 msg_send![text, string]
1682 } else {
1683 text
1684 };
1685 let selected_range = selected_range.to_range();
1686 let replacement_range = replacement_range.to_range();
1687 let text = CStr::from_ptr(text.UTF8String() as *mut c_char)
1688 .to_str()
1689 .unwrap();
1690
1691 send_to_input_handler(
1692 this,
1693 ImeInput::SetMarkedText(text.to_string(), replacement_range, selected_range),
1694 );
1695 }
1696}
1697extern "C" fn unmark_text(this: &Object, _: Sel) {
1698 send_to_input_handler(this, ImeInput::UnmarkText);
1699}
1700
1701extern "C" fn attributed_substring_for_proposed_range(
1702 this: &Object,
1703 _: Sel,
1704 range: NSRange,
1705 _actual_range: *mut c_void,
1706) -> id {
1707 with_input_handler(this, |input_handler| {
1708 let range = range.to_range()?;
1709 if range.is_empty() {
1710 return None;
1711 }
1712
1713 let selected_text = input_handler.text_for_range(range)?;
1714 unsafe {
1715 let string: id = msg_send![class!(NSAttributedString), alloc];
1716 let string: id = msg_send![string, initWithString: ns_string(&selected_text)];
1717 Some(string)
1718 }
1719 })
1720 .flatten()
1721 .unwrap_or(nil)
1722}
1723
1724extern "C" fn do_command_by_selector(_: &Object, _: Sel, _: Sel) {}
1725
1726extern "C" fn view_did_change_effective_appearance(this: &Object, _: Sel) {
1727 unsafe {
1728 let state = get_window_state(this);
1729 let mut lock = state.as_ref().lock();
1730 if let Some(mut callback) = lock.appearance_changed_callback.take() {
1731 drop(lock);
1732 callback();
1733 state.lock().appearance_changed_callback = Some(callback);
1734 }
1735 }
1736}
1737
1738extern "C" fn accepts_first_mouse(this: &Object, _: Sel, _: id) -> BOOL {
1739 unsafe {
1740 let state = get_window_state(this);
1741 let lock = state.as_ref().lock();
1742 if lock.kind == WindowKind::PopUp {
1743 YES
1744 } else {
1745 NO
1746 }
1747 }
1748}
1749
1750extern "C" fn dragging_entered(this: &Object, _: Sel, dragging_info: id) -> NSDragOperation {
1751 let window_state = unsafe { get_window_state(this) };
1752 if send_new_event(&window_state, {
1753 let position = drag_event_position(&window_state, dragging_info);
1754 let paths = external_paths_from_event(dragging_info);
1755 PlatformInput::FileDrop(FileDropEvent::Entered { position, paths })
1756 }) {
1757 window_state.lock().external_files_dragged = true;
1758 NSDragOperationCopy
1759 } else {
1760 NSDragOperationNone
1761 }
1762}
1763
1764extern "C" fn dragging_updated(this: &Object, _: Sel, dragging_info: id) -> NSDragOperation {
1765 let window_state = unsafe { get_window_state(this) };
1766 let position = drag_event_position(&window_state, dragging_info);
1767 if send_new_event(
1768 &window_state,
1769 PlatformInput::FileDrop(FileDropEvent::Pending { position }),
1770 ) {
1771 NSDragOperationCopy
1772 } else {
1773 NSDragOperationNone
1774 }
1775}
1776
1777extern "C" fn dragging_exited(this: &Object, _: Sel, _: id) {
1778 let window_state = unsafe { get_window_state(this) };
1779 send_new_event(
1780 &window_state,
1781 PlatformInput::FileDrop(FileDropEvent::Exited),
1782 );
1783 window_state.lock().external_files_dragged = false;
1784}
1785
1786extern "C" fn perform_drag_operation(this: &Object, _: Sel, dragging_info: id) -> BOOL {
1787 let window_state = unsafe { get_window_state(this) };
1788 let position = drag_event_position(&window_state, dragging_info);
1789 if send_new_event(
1790 &window_state,
1791 PlatformInput::FileDrop(FileDropEvent::Submit { position }),
1792 ) {
1793 YES
1794 } else {
1795 NO
1796 }
1797}
1798
1799fn external_paths_from_event(dragging_info: *mut Object) -> ExternalPaths {
1800 let mut paths = SmallVec::new();
1801 let pasteboard: id = unsafe { msg_send![dragging_info, draggingPasteboard] };
1802 let filenames = unsafe { NSPasteboard::propertyListForType(pasteboard, NSFilenamesPboardType) };
1803 for file in unsafe { filenames.iter() } {
1804 let path = unsafe {
1805 let f = NSString::UTF8String(file);
1806 CStr::from_ptr(f).to_string_lossy().into_owned()
1807 };
1808 paths.push(PathBuf::from(path))
1809 }
1810 ExternalPaths(paths)
1811}
1812
1813extern "C" fn conclude_drag_operation(this: &Object, _: Sel, _: id) {
1814 let window_state = unsafe { get_window_state(this) };
1815 send_new_event(
1816 &window_state,
1817 PlatformInput::FileDrop(FileDropEvent::Exited),
1818 );
1819}
1820
1821async fn synthetic_drag(
1822 window_state: Weak<Mutex<MacWindowState>>,
1823 drag_id: usize,
1824 event: MouseMoveEvent,
1825) {
1826 loop {
1827 Timer::after(Duration::from_millis(16)).await;
1828 if let Some(window_state) = window_state.upgrade() {
1829 let mut lock = window_state.lock();
1830 if lock.synthetic_drag_counter == drag_id {
1831 if let Some(mut callback) = lock.event_callback.take() {
1832 drop(lock);
1833 callback(PlatformInput::MouseMove(event.clone()));
1834 window_state.lock().event_callback = Some(callback);
1835 }
1836 } else {
1837 break;
1838 }
1839 }
1840 }
1841}
1842
1843fn send_new_event(window_state_lock: &Mutex<MacWindowState>, e: PlatformInput) -> bool {
1844 let window_state = window_state_lock.lock().event_callback.take();
1845 if let Some(mut callback) = window_state {
1846 callback(e);
1847 window_state_lock.lock().event_callback = Some(callback);
1848 true
1849 } else {
1850 false
1851 }
1852}
1853
1854fn drag_event_position(window_state: &Mutex<MacWindowState>, dragging_info: id) -> Point<Pixels> {
1855 let drag_location: NSPoint = unsafe { msg_send![dragging_info, draggingLocation] };
1856 convert_mouse_position(drag_location, window_state.lock().content_size().height)
1857}
1858
1859fn with_input_handler<F, R>(window: &Object, f: F) -> Option<R>
1860where
1861 F: FnOnce(&mut PlatformInputHandler) -> R,
1862{
1863 let window_state = unsafe { get_window_state(window) };
1864 let mut lock = window_state.as_ref().lock();
1865 if let Some(mut input_handler) = lock.input_handler.take() {
1866 drop(lock);
1867 let result = f(&mut input_handler);
1868 window_state.lock().input_handler = Some(input_handler);
1869 Some(result)
1870 } else {
1871 None
1872 }
1873}
1874
1875fn send_to_input_handler(window: &Object, ime: ImeInput) {
1876 unsafe {
1877 let window_state = get_window_state(window);
1878 let mut lock = window_state.lock();
1879 if let Some(ime_input) = lock.input_during_keydown.as_mut() {
1880 ime_input.push(ime);
1881 return;
1882 }
1883 if let Some(mut input_handler) = lock.input_handler.take() {
1884 drop(lock);
1885 match ime {
1886 ImeInput::InsertText(text, range) => {
1887 input_handler.replace_text_in_range(range, &text)
1888 }
1889 ImeInput::SetMarkedText(text, range, marked_range) => {
1890 input_handler.replace_and_mark_text_in_range(range, &text, marked_range)
1891 }
1892 ImeInput::UnmarkText => input_handler.unmark_text(),
1893 }
1894 window_state.lock().input_handler = Some(input_handler);
1895 }
1896 }
1897}
1898
1899unsafe fn display_id_for_screen(screen: id) -> CGDirectDisplayID {
1900 let device_description = NSScreen::deviceDescription(screen);
1901 let screen_number_key: id = NSString::alloc(nil).init_str("NSScreenNumber");
1902 let screen_number = device_description.objectForKey_(screen_number_key);
1903 let screen_number: NSUInteger = msg_send![screen_number, unsignedIntegerValue];
1904 screen_number as CGDirectDisplayID
1905}