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