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