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