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