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