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