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