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