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