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