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