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