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