1use super::{BoolExt, MacDisplay, NSRange, NSStringExt, ns_string, renderer};
2use crate::{
3 AnyWindowHandle, Bounds, Capslock, DisplayLink, ExternalPaths, FileDropEvent,
4 ForegroundExecutor, KeyDownEvent, Keystroke, Modifiers, ModifiersChangedEvent, MouseButton,
5 MouseDownEvent, MouseMoveEvent, MouseUpEvent, Pixels, PlatformAtlas, PlatformDisplay,
6 PlatformInput, PlatformWindow, Point, PromptButton, PromptLevel, RequestFrameOptions,
7 ScaledPixels, SharedString, Size, SystemWindowTab, Timer, WindowAppearance,
8 WindowBackgroundAppearance, WindowBounds, WindowControlArea, WindowKind, WindowParams,
9 dispatch_get_main_queue, dispatch_sys::dispatch_async_f, platform::PlatformInputHandler, point,
10 px, size,
11};
12use block::ConcreteBlock;
13use cocoa::{
14 appkit::{
15 NSAppKitVersionNumber, NSAppKitVersionNumber12_0, NSApplication, NSBackingStoreBuffered,
16 NSColor, NSEvent, NSEventModifierFlags, NSFilenamesPboardType, NSPasteboard, NSScreen,
17 NSView, NSViewHeightSizable, NSViewWidthSizable, NSVisualEffectMaterial,
18 NSVisualEffectState, NSVisualEffectView, NSWindow, NSWindowButton,
19 NSWindowCollectionBehavior, NSWindowOcclusionState, NSWindowOrderingMode,
20 NSWindowStyleMask, NSWindowTitleVisibility,
21 },
22 base::{id, nil},
23 foundation::{
24 NSArray, NSAutoreleasePool, NSDictionary, NSFastEnumeration, NSInteger, NSNotFound,
25 NSOperatingSystemVersion, NSPoint, NSProcessInfo, NSRect, NSSize, NSString, NSUInteger,
26 NSUserDefaults,
27 },
28};
29
30use core_graphics::display::{CGDirectDisplayID, CGPoint, CGRect};
31use ctor::ctor;
32use futures::channel::oneshot;
33use objc::{
34 class,
35 declare::ClassDecl,
36 msg_send,
37 runtime::{BOOL, Class, NO, Object, Protocol, Sel, YES},
38 sel, sel_impl,
39};
40use parking_lot::Mutex;
41use raw_window_handle as rwh;
42use smallvec::SmallVec;
43use std::{
44 cell::Cell,
45 ffi::{CStr, c_void},
46 mem,
47 ops::Range,
48 path::PathBuf,
49 ptr::{self, NonNull},
50 rc::Rc,
51 sync::{Arc, Weak},
52 time::Duration,
53};
54use util::ResultExt;
55
56const WINDOW_STATE_IVAR: &str = "windowState";
57
58static mut WINDOW_CLASS: *const Class = ptr::null();
59static mut PANEL_CLASS: *const Class = ptr::null();
60static mut VIEW_CLASS: *const Class = ptr::null();
61static mut BLURRED_VIEW_CLASS: *const Class = ptr::null();
62
63#[allow(non_upper_case_globals)]
64const NSWindowStyleMaskNonactivatingPanel: NSWindowStyleMask =
65 NSWindowStyleMask::from_bits_retain(1 << 7);
66#[allow(non_upper_case_globals)]
67const NSNormalWindowLevel: NSInteger = 0;
68#[allow(non_upper_case_globals)]
69const NSPopUpWindowLevel: NSInteger = 101;
70#[allow(non_upper_case_globals)]
71const NSTrackingMouseEnteredAndExited: NSUInteger = 0x01;
72#[allow(non_upper_case_globals)]
73const NSTrackingMouseMoved: NSUInteger = 0x02;
74#[allow(non_upper_case_globals)]
75const NSTrackingActiveAlways: NSUInteger = 0x80;
76#[allow(non_upper_case_globals)]
77const NSTrackingInVisibleRect: NSUInteger = 0x200;
78#[allow(non_upper_case_globals)]
79const NSWindowAnimationBehaviorUtilityWindow: NSInteger = 4;
80#[allow(non_upper_case_globals)]
81const NSViewLayerContentsRedrawDuringViewResize: NSInteger = 2;
82// https://developer.apple.com/documentation/appkit/nsdragoperation
83type NSDragOperation = NSUInteger;
84#[allow(non_upper_case_globals)]
85const NSDragOperationNone: NSDragOperation = 0;
86#[allow(non_upper_case_globals)]
87const NSDragOperationCopy: NSDragOperation = 1;
88#[derive(PartialEq)]
89pub enum UserTabbingPreference {
90 Never,
91 Always,
92 InFullScreen,
93}
94
95#[link(name = "CoreGraphics", kind = "framework")]
96unsafe extern "C" {
97 // Widely used private APIs; Apple uses them for their Terminal.app.
98 fn CGSMainConnectionID() -> id;
99 fn CGSSetWindowBackgroundBlurRadius(
100 connection_id: id,
101 window_id: NSInteger,
102 radius: i64,
103 ) -> i32;
104}
105
106#[ctor]
107unsafe fn build_classes() {
108 unsafe {
109 WINDOW_CLASS = build_window_class("GPUIWindow", class!(NSWindow));
110 PANEL_CLASS = build_window_class("GPUIPanel", class!(NSPanel));
111 VIEW_CLASS = {
112 let mut decl = ClassDecl::new("GPUIView", class!(NSView)).unwrap();
113 decl.add_ivar::<*mut c_void>(WINDOW_STATE_IVAR);
114 unsafe {
115 decl.add_method(sel!(dealloc), dealloc_view as extern "C" fn(&Object, Sel));
116
117 decl.add_method(
118 sel!(performKeyEquivalent:),
119 handle_key_equivalent as extern "C" fn(&Object, Sel, id) -> BOOL,
120 );
121 decl.add_method(
122 sel!(keyDown:),
123 handle_key_down as extern "C" fn(&Object, Sel, id),
124 );
125 decl.add_method(
126 sel!(keyUp:),
127 handle_key_up as extern "C" fn(&Object, Sel, id),
128 );
129 decl.add_method(
130 sel!(mouseDown:),
131 handle_view_event as extern "C" fn(&Object, Sel, id),
132 );
133 decl.add_method(
134 sel!(mouseUp:),
135 handle_view_event as extern "C" fn(&Object, Sel, id),
136 );
137 decl.add_method(
138 sel!(rightMouseDown:),
139 handle_view_event as extern "C" fn(&Object, Sel, id),
140 );
141 decl.add_method(
142 sel!(rightMouseUp:),
143 handle_view_event as extern "C" fn(&Object, Sel, id),
144 );
145 decl.add_method(
146 sel!(otherMouseDown:),
147 handle_view_event as extern "C" fn(&Object, Sel, id),
148 );
149 decl.add_method(
150 sel!(otherMouseUp:),
151 handle_view_event as extern "C" fn(&Object, Sel, id),
152 );
153 decl.add_method(
154 sel!(mouseMoved:),
155 handle_view_event as extern "C" fn(&Object, Sel, id),
156 );
157 decl.add_method(
158 sel!(mouseExited:),
159 handle_view_event as extern "C" fn(&Object, Sel, id),
160 );
161 decl.add_method(
162 sel!(mouseDragged:),
163 handle_view_event as extern "C" fn(&Object, Sel, id),
164 );
165 decl.add_method(
166 sel!(scrollWheel:),
167 handle_view_event as extern "C" fn(&Object, Sel, id),
168 );
169 decl.add_method(
170 sel!(swipeWithEvent:),
171 handle_view_event as extern "C" fn(&Object, Sel, id),
172 );
173 decl.add_method(
174 sel!(flagsChanged:),
175 handle_view_event as extern "C" fn(&Object, Sel, id),
176 );
177
178 decl.add_method(
179 sel!(makeBackingLayer),
180 make_backing_layer as extern "C" fn(&Object, Sel) -> id,
181 );
182
183 decl.add_protocol(Protocol::get("CALayerDelegate").unwrap());
184 decl.add_method(
185 sel!(viewDidChangeBackingProperties),
186 view_did_change_backing_properties as extern "C" fn(&Object, Sel),
187 );
188 decl.add_method(
189 sel!(setFrameSize:),
190 set_frame_size as extern "C" fn(&Object, Sel, NSSize),
191 );
192 decl.add_method(
193 sel!(displayLayer:),
194 display_layer as extern "C" fn(&Object, Sel, id),
195 );
196
197 decl.add_protocol(Protocol::get("NSTextInputClient").unwrap());
198 decl.add_method(
199 sel!(validAttributesForMarkedText),
200 valid_attributes_for_marked_text as extern "C" fn(&Object, Sel) -> id,
201 );
202 decl.add_method(
203 sel!(hasMarkedText),
204 has_marked_text as extern "C" fn(&Object, Sel) -> BOOL,
205 );
206 decl.add_method(
207 sel!(markedRange),
208 marked_range as extern "C" fn(&Object, Sel) -> NSRange,
209 );
210 decl.add_method(
211 sel!(selectedRange),
212 selected_range as extern "C" fn(&Object, Sel) -> NSRange,
213 );
214 decl.add_method(
215 sel!(firstRectForCharacterRange:actualRange:),
216 first_rect_for_character_range
217 as extern "C" fn(&Object, Sel, NSRange, id) -> NSRect,
218 );
219 decl.add_method(
220 sel!(insertText:replacementRange:),
221 insert_text as extern "C" fn(&Object, Sel, id, NSRange),
222 );
223 decl.add_method(
224 sel!(setMarkedText:selectedRange:replacementRange:),
225 set_marked_text as extern "C" fn(&Object, Sel, id, NSRange, NSRange),
226 );
227 decl.add_method(sel!(unmarkText), unmark_text as extern "C" fn(&Object, Sel));
228 decl.add_method(
229 sel!(attributedSubstringForProposedRange:actualRange:),
230 attributed_substring_for_proposed_range
231 as extern "C" fn(&Object, Sel, NSRange, *mut c_void) -> id,
232 );
233 decl.add_method(
234 sel!(viewDidChangeEffectiveAppearance),
235 view_did_change_effective_appearance as extern "C" fn(&Object, Sel),
236 );
237
238 // Suppress beep on keystrokes with modifier keys.
239 decl.add_method(
240 sel!(doCommandBySelector:),
241 do_command_by_selector as extern "C" fn(&Object, Sel, Sel),
242 );
243
244 decl.add_method(
245 sel!(acceptsFirstMouse:),
246 accepts_first_mouse as extern "C" fn(&Object, Sel, id) -> BOOL,
247 );
248
249 decl.add_method(
250 sel!(characterIndexForPoint:),
251 character_index_for_point as extern "C" fn(&Object, Sel, NSPoint) -> u64,
252 );
253 }
254 decl.register()
255 };
256 BLURRED_VIEW_CLASS = {
257 let mut decl = ClassDecl::new("BlurredView", class!(NSVisualEffectView)).unwrap();
258 unsafe {
259 decl.add_method(
260 sel!(initWithFrame:),
261 blurred_view_init_with_frame as extern "C" fn(&Object, Sel, NSRect) -> id,
262 );
263 decl.add_method(
264 sel!(updateLayer),
265 blurred_view_update_layer as extern "C" fn(&Object, Sel),
266 );
267 decl.register()
268 }
269 };
270 }
271}
272
273pub(crate) fn convert_mouse_position(position: NSPoint, window_height: Pixels) -> Point<Pixels> {
274 point(
275 px(position.x as f32),
276 // macOS screen coordinates are relative to bottom left
277 window_height - px(position.y as f32),
278 )
279}
280
281unsafe fn build_window_class(name: &'static str, superclass: &Class) -> *const Class {
282 unsafe {
283 let mut decl = ClassDecl::new(name, superclass).unwrap();
284 decl.add_ivar::<*mut c_void>(WINDOW_STATE_IVAR);
285 decl.add_method(sel!(dealloc), dealloc_window as extern "C" fn(&Object, Sel));
286
287 decl.add_method(
288 sel!(canBecomeMainWindow),
289 yes as extern "C" fn(&Object, Sel) -> BOOL,
290 );
291 decl.add_method(
292 sel!(canBecomeKeyWindow),
293 yes as extern "C" fn(&Object, Sel) -> BOOL,
294 );
295 decl.add_method(
296 sel!(windowDidResize:),
297 window_did_resize as extern "C" fn(&Object, Sel, id),
298 );
299 decl.add_method(
300 sel!(windowDidChangeOcclusionState:),
301 window_did_change_occlusion_state as extern "C" fn(&Object, Sel, id),
302 );
303 decl.add_method(
304 sel!(windowWillEnterFullScreen:),
305 window_will_enter_fullscreen as extern "C" fn(&Object, Sel, id),
306 );
307 decl.add_method(
308 sel!(windowWillExitFullScreen:),
309 window_will_exit_fullscreen as extern "C" fn(&Object, Sel, id),
310 );
311 decl.add_method(
312 sel!(windowDidMove:),
313 window_did_move as extern "C" fn(&Object, Sel, id),
314 );
315 decl.add_method(
316 sel!(windowDidChangeScreen:),
317 window_did_change_screen as extern "C" fn(&Object, Sel, id),
318 );
319 decl.add_method(
320 sel!(windowDidBecomeKey:),
321 window_did_change_key_status as extern "C" fn(&Object, Sel, id),
322 );
323 decl.add_method(
324 sel!(windowDidResignKey:),
325 window_did_change_key_status as extern "C" fn(&Object, Sel, id),
326 );
327 decl.add_method(
328 sel!(windowShouldClose:),
329 window_should_close as extern "C" fn(&Object, Sel, id) -> BOOL,
330 );
331
332 decl.add_method(sel!(close), close_window as extern "C" fn(&Object, Sel));
333
334 decl.add_method(
335 sel!(draggingEntered:),
336 dragging_entered as extern "C" fn(&Object, Sel, id) -> NSDragOperation,
337 );
338 decl.add_method(
339 sel!(draggingUpdated:),
340 dragging_updated as extern "C" fn(&Object, Sel, id) -> NSDragOperation,
341 );
342 decl.add_method(
343 sel!(draggingExited:),
344 dragging_exited as extern "C" fn(&Object, Sel, id),
345 );
346 decl.add_method(
347 sel!(performDragOperation:),
348 perform_drag_operation as extern "C" fn(&Object, Sel, id) -> BOOL,
349 );
350 decl.add_method(
351 sel!(concludeDragOperation:),
352 conclude_drag_operation as extern "C" fn(&Object, Sel, id),
353 );
354
355 decl.add_method(
356 sel!(addTitlebarAccessoryViewController:),
357 add_titlebar_accessory_view_controller as extern "C" fn(&Object, Sel, id),
358 );
359
360 decl.add_method(
361 sel!(moveTabToNewWindow:),
362 move_tab_to_new_window as extern "C" fn(&Object, Sel, id),
363 );
364
365 decl.add_method(
366 sel!(mergeAllWindows:),
367 merge_all_windows as extern "C" fn(&Object, Sel, id),
368 );
369
370 decl.add_method(
371 sel!(selectNextTab:),
372 select_next_tab as extern "C" fn(&Object, Sel, id),
373 );
374
375 decl.add_method(
376 sel!(selectPreviousTab:),
377 select_previous_tab as extern "C" fn(&Object, Sel, id),
378 );
379
380 decl.add_method(
381 sel!(toggleTabBar:),
382 toggle_tab_bar as extern "C" fn(&Object, Sel, id),
383 );
384
385 decl.register()
386 }
387}
388
389struct MacWindowState {
390 handle: AnyWindowHandle,
391 executor: ForegroundExecutor,
392 native_window: id,
393 native_view: NonNull<Object>,
394 blurred_view: Option<id>,
395 display_link: Option<DisplayLink>,
396 renderer: renderer::Renderer,
397 request_frame_callback: Option<Box<dyn FnMut(RequestFrameOptions)>>,
398 event_callback: Option<Box<dyn FnMut(PlatformInput) -> crate::DispatchEventResult>>,
399 activate_callback: Option<Box<dyn FnMut(bool)>>,
400 resize_callback: Option<Box<dyn FnMut(Size<Pixels>, f32)>>,
401 moved_callback: Option<Box<dyn FnMut()>>,
402 should_close_callback: Option<Box<dyn FnMut() -> bool>>,
403 close_callback: Option<Box<dyn FnOnce()>>,
404 appearance_changed_callback: Option<Box<dyn FnMut()>>,
405 input_handler: Option<PlatformInputHandler>,
406 last_key_equivalent: Option<KeyDownEvent>,
407 synthetic_drag_counter: usize,
408 traffic_light_position: Option<Point<Pixels>>,
409 transparent_titlebar: bool,
410 previous_modifiers_changed_event: Option<PlatformInput>,
411 keystroke_for_do_command: Option<Keystroke>,
412 do_command_handled: Option<bool>,
413 external_files_dragged: bool,
414 // Whether the next left-mouse click is also the focusing click.
415 first_mouse: bool,
416 fullscreen_restore_bounds: Bounds<Pixels>,
417 move_tab_to_new_window_callback: Option<Box<dyn FnMut()>>,
418 merge_all_windows_callback: Option<Box<dyn FnMut()>>,
419 select_next_tab_callback: Option<Box<dyn FnMut()>>,
420 select_previous_tab_callback: Option<Box<dyn FnMut()>>,
421 toggle_tab_bar_callback: Option<Box<dyn FnMut()>>,
422}
423
424impl MacWindowState {
425 fn move_traffic_light(&self) {
426 if let Some(traffic_light_position) = self.traffic_light_position {
427 if self.is_fullscreen() {
428 // Moving traffic lights while fullscreen doesn't work,
429 // see https://github.com/zed-industries/zed/issues/4712
430 return;
431 }
432
433 let titlebar_height = self.titlebar_height();
434
435 unsafe {
436 let close_button: id = msg_send![
437 self.native_window,
438 standardWindowButton: NSWindowButton::NSWindowCloseButton
439 ];
440 let min_button: id = msg_send![
441 self.native_window,
442 standardWindowButton: NSWindowButton::NSWindowMiniaturizeButton
443 ];
444 let zoom_button: id = msg_send![
445 self.native_window,
446 standardWindowButton: NSWindowButton::NSWindowZoomButton
447 ];
448
449 let mut close_button_frame: CGRect = msg_send![close_button, frame];
450 let mut min_button_frame: CGRect = msg_send![min_button, frame];
451 let mut zoom_button_frame: CGRect = msg_send![zoom_button, frame];
452 let mut origin = point(
453 traffic_light_position.x,
454 titlebar_height
455 - traffic_light_position.y
456 - px(close_button_frame.size.height as f32),
457 );
458 let button_spacing =
459 px((min_button_frame.origin.x - close_button_frame.origin.x) as f32);
460
461 close_button_frame.origin = CGPoint::new(origin.x.into(), origin.y.into());
462 let _: () = msg_send![close_button, setFrame: close_button_frame];
463 origin.x += button_spacing;
464
465 min_button_frame.origin = CGPoint::new(origin.x.into(), origin.y.into());
466 let _: () = msg_send![min_button, setFrame: min_button_frame];
467 origin.x += button_spacing;
468
469 zoom_button_frame.origin = CGPoint::new(origin.x.into(), origin.y.into());
470 let _: () = msg_send![zoom_button, setFrame: zoom_button_frame];
471 origin.x += button_spacing;
472 }
473 }
474 }
475
476 fn start_display_link(&mut self) {
477 self.stop_display_link();
478 unsafe {
479 if !self
480 .native_window
481 .occlusionState()
482 .contains(NSWindowOcclusionState::NSWindowOcclusionStateVisible)
483 {
484 return;
485 }
486 }
487 let display_id = unsafe { display_id_for_screen(self.native_window.screen()) };
488 if let Some(mut display_link) =
489 DisplayLink::new(display_id, self.native_view.as_ptr() as *mut c_void, step).log_err()
490 {
491 display_link.start().log_err();
492 self.display_link = Some(display_link);
493 }
494 }
495
496 fn stop_display_link(&mut self) {
497 self.display_link = None;
498 }
499
500 fn is_maximized(&self) -> bool {
501 unsafe {
502 let bounds = self.bounds();
503 let screen_size = self.native_window.screen().visibleFrame().into();
504 bounds.size == screen_size
505 }
506 }
507
508 fn is_fullscreen(&self) -> bool {
509 unsafe {
510 let style_mask = self.native_window.styleMask();
511 style_mask.contains(NSWindowStyleMask::NSFullScreenWindowMask)
512 }
513 }
514
515 fn bounds(&self) -> Bounds<Pixels> {
516 let mut window_frame = unsafe { NSWindow::frame(self.native_window) };
517 let screen_frame = unsafe {
518 let screen = NSWindow::screen(self.native_window);
519 NSScreen::frame(screen)
520 };
521
522 // Flip the y coordinate to be top-left origin
523 window_frame.origin.y =
524 screen_frame.size.height - window_frame.origin.y - window_frame.size.height;
525
526 Bounds::new(
527 point(
528 px((window_frame.origin.x - screen_frame.origin.x) as f32),
529 px((window_frame.origin.y + screen_frame.origin.y) as f32),
530 ),
531 size(
532 px(window_frame.size.width as f32),
533 px(window_frame.size.height as f32),
534 ),
535 )
536 }
537
538 fn content_size(&self) -> Size<Pixels> {
539 let NSSize { width, height, .. } =
540 unsafe { NSView::frame(self.native_window.contentView()) }.size;
541 size(px(width as f32), px(height as f32))
542 }
543
544 fn scale_factor(&self) -> f32 {
545 get_scale_factor(self.native_window)
546 }
547
548 fn titlebar_height(&self) -> Pixels {
549 unsafe {
550 let frame = NSWindow::frame(self.native_window);
551 let content_layout_rect: CGRect = msg_send![self.native_window, contentLayoutRect];
552 px((frame.size.height - content_layout_rect.size.height) as f32)
553 }
554 }
555
556 fn window_bounds(&self) -> WindowBounds {
557 if self.is_fullscreen() {
558 WindowBounds::Fullscreen(self.fullscreen_restore_bounds)
559 } else {
560 WindowBounds::Windowed(self.bounds())
561 }
562 }
563}
564
565unsafe impl Send for MacWindowState {}
566
567pub(crate) struct MacWindow(Arc<Mutex<MacWindowState>>);
568
569impl MacWindow {
570 pub fn open(
571 handle: AnyWindowHandle,
572 WindowParams {
573 bounds,
574 titlebar,
575 kind,
576 is_movable,
577 is_resizable,
578 is_minimizable,
579 focus,
580 show,
581 display_id,
582 window_min_size,
583 tabbing_identifier,
584 }: WindowParams,
585 executor: ForegroundExecutor,
586 renderer_context: renderer::Context,
587 ) -> Self {
588 unsafe {
589 let pool = NSAutoreleasePool::new(nil);
590
591 let allows_automatic_window_tabbing = tabbing_identifier.is_some();
592 if allows_automatic_window_tabbing {
593 let () = msg_send![class!(NSWindow), setAllowsAutomaticWindowTabbing: YES];
594 } else {
595 let () = msg_send![class!(NSWindow), setAllowsAutomaticWindowTabbing: NO];
596 }
597
598 let mut style_mask;
599 if let Some(titlebar) = titlebar.as_ref() {
600 style_mask =
601 NSWindowStyleMask::NSClosableWindowMask | NSWindowStyleMask::NSTitledWindowMask;
602
603 if is_resizable {
604 style_mask |= NSWindowStyleMask::NSResizableWindowMask;
605 }
606
607 if is_minimizable {
608 style_mask |= NSWindowStyleMask::NSMiniaturizableWindowMask;
609 }
610
611 if titlebar.appears_transparent {
612 style_mask |= NSWindowStyleMask::NSFullSizeContentViewWindowMask;
613 }
614 } else {
615 style_mask = NSWindowStyleMask::NSTitledWindowMask
616 | NSWindowStyleMask::NSFullSizeContentViewWindowMask;
617 }
618
619 let native_window: id = match kind {
620 WindowKind::Normal => msg_send![WINDOW_CLASS, alloc],
621 WindowKind::PopUp => {
622 style_mask |= NSWindowStyleMaskNonactivatingPanel;
623 msg_send![PANEL_CLASS, alloc]
624 }
625 };
626
627 let display = display_id
628 .and_then(MacDisplay::find_by_id)
629 .unwrap_or_else(MacDisplay::primary);
630
631 let mut target_screen = nil;
632 let mut screen_frame = None;
633
634 let screens = NSScreen::screens(nil);
635 let count: u64 = cocoa::foundation::NSArray::count(screens);
636 for i in 0..count {
637 let screen = cocoa::foundation::NSArray::objectAtIndex(screens, i);
638 let frame = NSScreen::frame(screen);
639 let display_id = display_id_for_screen(screen);
640 if display_id == display.0 {
641 screen_frame = Some(frame);
642 target_screen = screen;
643 }
644 }
645
646 let screen_frame = screen_frame.unwrap_or_else(|| {
647 let screen = NSScreen::mainScreen(nil);
648 target_screen = screen;
649 NSScreen::frame(screen)
650 });
651
652 let window_rect = NSRect::new(
653 NSPoint::new(
654 screen_frame.origin.x + bounds.origin.x.0 as f64,
655 screen_frame.origin.y
656 + (display.bounds().size.height - bounds.origin.y).0 as f64,
657 ),
658 NSSize::new(bounds.size.width.0 as f64, bounds.size.height.0 as f64),
659 );
660
661 let native_window = native_window.initWithContentRect_styleMask_backing_defer_screen_(
662 window_rect,
663 style_mask,
664 NSBackingStoreBuffered,
665 NO,
666 target_screen,
667 );
668 assert!(!native_window.is_null());
669 let () = msg_send![
670 native_window,
671 registerForDraggedTypes:
672 NSArray::arrayWithObject(nil, NSFilenamesPboardType)
673 ];
674 let () = msg_send![
675 native_window,
676 setReleasedWhenClosed: NO
677 ];
678
679 let content_view = native_window.contentView();
680 let native_view: id = msg_send![VIEW_CLASS, alloc];
681 let native_view = NSView::initWithFrame_(native_view, NSView::bounds(content_view));
682 assert!(!native_view.is_null());
683
684 let mut window = Self(Arc::new(Mutex::new(MacWindowState {
685 handle,
686 executor,
687 native_window,
688 native_view: NonNull::new_unchecked(native_view),
689 blurred_view: None,
690 display_link: None,
691 renderer: renderer::new_renderer(
692 renderer_context,
693 native_window as *mut _,
694 native_view as *mut _,
695 bounds.size.map(|pixels| pixels.0),
696 false,
697 ),
698 request_frame_callback: None,
699 event_callback: None,
700 activate_callback: None,
701 resize_callback: None,
702 moved_callback: None,
703 should_close_callback: None,
704 close_callback: None,
705 appearance_changed_callback: None,
706 input_handler: None,
707 last_key_equivalent: None,
708 synthetic_drag_counter: 0,
709 traffic_light_position: titlebar
710 .as_ref()
711 .and_then(|titlebar| titlebar.traffic_light_position),
712 transparent_titlebar: titlebar
713 .as_ref()
714 .is_none_or(|titlebar| titlebar.appears_transparent),
715 previous_modifiers_changed_event: None,
716 keystroke_for_do_command: None,
717 do_command_handled: None,
718 external_files_dragged: false,
719 first_mouse: false,
720 fullscreen_restore_bounds: Bounds::default(),
721 move_tab_to_new_window_callback: None,
722 merge_all_windows_callback: None,
723 select_next_tab_callback: None,
724 select_previous_tab_callback: None,
725 toggle_tab_bar_callback: None,
726 })));
727
728 (*native_window).set_ivar(
729 WINDOW_STATE_IVAR,
730 Arc::into_raw(window.0.clone()) as *const c_void,
731 );
732 native_window.setDelegate_(native_window);
733 (*native_view).set_ivar(
734 WINDOW_STATE_IVAR,
735 Arc::into_raw(window.0.clone()) as *const c_void,
736 );
737
738 if let Some(title) = titlebar
739 .as_ref()
740 .and_then(|t| t.title.as_ref().map(AsRef::as_ref))
741 {
742 window.set_title(title);
743 }
744
745 native_window.setMovable_(is_movable as BOOL);
746
747 if let Some(window_min_size) = window_min_size {
748 native_window.setContentMinSize_(NSSize {
749 width: window_min_size.width.to_f64(),
750 height: window_min_size.height.to_f64(),
751 });
752 }
753
754 if titlebar.is_none_or(|titlebar| titlebar.appears_transparent) {
755 native_window.setTitlebarAppearsTransparent_(YES);
756 native_window.setTitleVisibility_(NSWindowTitleVisibility::NSWindowTitleHidden);
757 }
758
759 native_view.setAutoresizingMask_(NSViewWidthSizable | NSViewHeightSizable);
760 native_view.setWantsBestResolutionOpenGLSurface_(YES);
761
762 // From winit crate: On Mojave, views automatically become layer-backed shortly after
763 // being added to a native_window. Changing the layer-backedness of a view breaks the
764 // association between the view and its associated OpenGL context. To work around this,
765 // on we explicitly make the view layer-backed up front so that AppKit doesn't do it
766 // itself and break the association with its context.
767 native_view.setWantsLayer(YES);
768 let _: () = msg_send![
769 native_view,
770 setLayerContentsRedrawPolicy: NSViewLayerContentsRedrawDuringViewResize
771 ];
772
773 content_view.addSubview_(native_view.autorelease());
774 native_window.makeFirstResponder_(native_view);
775
776 match kind {
777 WindowKind::Normal => {
778 native_window.setLevel_(NSNormalWindowLevel);
779 native_window.setAcceptsMouseMovedEvents_(YES);
780
781 if let Some(tabbing_identifier) = tabbing_identifier {
782 let tabbing_id = NSString::alloc(nil).init_str(tabbing_identifier.as_str());
783 let _: () = msg_send![native_window, setTabbingIdentifier: tabbing_id];
784 }
785 }
786 WindowKind::PopUp => {
787 // Use a tracking area to allow receiving MouseMoved events even when
788 // the window or application aren't active, which is often the case
789 // e.g. for notification windows.
790 let tracking_area: id = msg_send![class!(NSTrackingArea), alloc];
791 let _: () = msg_send![
792 tracking_area,
793 initWithRect: NSRect::new(NSPoint::new(0., 0.), NSSize::new(0., 0.))
794 options: NSTrackingMouseEnteredAndExited | NSTrackingMouseMoved | NSTrackingActiveAlways | NSTrackingInVisibleRect
795 owner: native_view
796 userInfo: nil
797 ];
798 let _: () =
799 msg_send![native_view, addTrackingArea: tracking_area.autorelease()];
800
801 native_window.setLevel_(NSPopUpWindowLevel);
802 let _: () = msg_send![
803 native_window,
804 setAnimationBehavior: NSWindowAnimationBehaviorUtilityWindow
805 ];
806 native_window.setCollectionBehavior_(
807 NSWindowCollectionBehavior::NSWindowCollectionBehaviorCanJoinAllSpaces |
808 NSWindowCollectionBehavior::NSWindowCollectionBehaviorFullScreenAuxiliary
809 );
810 }
811 }
812
813 let app = NSApplication::sharedApplication(nil);
814 let main_window: id = msg_send![app, mainWindow];
815 if allows_automatic_window_tabbing
816 && !main_window.is_null()
817 && main_window != native_window
818 {
819 let main_window_is_fullscreen = main_window
820 .styleMask()
821 .contains(NSWindowStyleMask::NSFullScreenWindowMask);
822 let user_tabbing_preference = Self::get_user_tabbing_preference()
823 .unwrap_or(UserTabbingPreference::InFullScreen);
824 let should_add_as_tab = user_tabbing_preference == UserTabbingPreference::Always
825 || user_tabbing_preference == UserTabbingPreference::InFullScreen
826 && main_window_is_fullscreen;
827
828 if should_add_as_tab {
829 let main_window_can_tab: BOOL =
830 msg_send![main_window, respondsToSelector: sel!(addTabbedWindow:ordered:)];
831 let main_window_visible: BOOL = msg_send![main_window, isVisible];
832
833 if main_window_can_tab == YES && main_window_visible == YES {
834 let _: () = msg_send![main_window, addTabbedWindow: native_window ordered: NSWindowOrderingMode::NSWindowAbove];
835
836 // Ensure the window is visible immediately after adding the tab, since the tab bar is updated with a new entry at this point.
837 // Note: Calling orderFront here can break fullscreen mode (makes fullscreen windows exit fullscreen), so only do this if the main window is not fullscreen.
838 if !main_window_is_fullscreen {
839 let _: () = msg_send![native_window, orderFront: nil];
840 }
841 }
842 }
843 }
844
845 if focus && show {
846 native_window.makeKeyAndOrderFront_(nil);
847 } else if show {
848 native_window.orderFront_(nil);
849 }
850
851 // Set the initial position of the window to the specified origin.
852 // Although we already specified the position using `initWithContentRect_styleMask_backing_defer_screen_`,
853 // the window position might be incorrect if the main screen (the screen that contains the window that has focus)
854 // is different from the primary screen.
855 NSWindow::setFrameTopLeftPoint_(native_window, window_rect.origin);
856 window.0.lock().move_traffic_light();
857
858 pool.drain();
859
860 window
861 }
862 }
863
864 pub fn active_window() -> Option<AnyWindowHandle> {
865 unsafe {
866 let app = NSApplication::sharedApplication(nil);
867 let main_window: id = msg_send![app, mainWindow];
868 if main_window.is_null() {
869 return None;
870 }
871
872 if msg_send![main_window, isKindOfClass: WINDOW_CLASS] {
873 let handle = get_window_state(&*main_window).lock().handle;
874 Some(handle)
875 } else {
876 None
877 }
878 }
879 }
880
881 pub fn ordered_windows() -> Vec<AnyWindowHandle> {
882 unsafe {
883 let app = NSApplication::sharedApplication(nil);
884 let windows: id = msg_send![app, orderedWindows];
885 let count: NSUInteger = msg_send![windows, count];
886
887 let mut window_handles = Vec::new();
888 for i in 0..count {
889 let window: id = msg_send![windows, objectAtIndex:i];
890 if msg_send![window, isKindOfClass: WINDOW_CLASS] {
891 let handle = get_window_state(&*window).lock().handle;
892 window_handles.push(handle);
893 }
894 }
895
896 window_handles
897 }
898 }
899
900 pub fn get_user_tabbing_preference() -> Option<UserTabbingPreference> {
901 unsafe {
902 let defaults: id = NSUserDefaults::standardUserDefaults();
903 let domain = NSString::alloc(nil).init_str("NSGlobalDomain");
904 let key = NSString::alloc(nil).init_str("AppleWindowTabbingMode");
905
906 let dict: id = msg_send![defaults, persistentDomainForName: domain];
907 let value: id = if !dict.is_null() {
908 msg_send![dict, objectForKey: key]
909 } else {
910 nil
911 };
912
913 let value_str = if !value.is_null() {
914 CStr::from_ptr(NSString::UTF8String(value)).to_string_lossy()
915 } else {
916 "".into()
917 };
918
919 match value_str.as_ref() {
920 "manual" => Some(UserTabbingPreference::Never),
921 "always" => Some(UserTabbingPreference::Always),
922 _ => Some(UserTabbingPreference::InFullScreen),
923 }
924 }
925 }
926}
927
928impl Drop for MacWindow {
929 fn drop(&mut self) {
930 let mut this = self.0.lock();
931 this.renderer.destroy();
932 let window = this.native_window;
933 this.display_link.take();
934 unsafe {
935 this.native_window.setDelegate_(nil);
936 }
937 this.input_handler.take();
938 this.executor
939 .spawn(async move {
940 unsafe {
941 window.close();
942 window.autorelease();
943 }
944 })
945 .detach();
946 }
947}
948
949impl PlatformWindow for MacWindow {
950 fn bounds(&self) -> Bounds<Pixels> {
951 self.0.as_ref().lock().bounds()
952 }
953
954 fn window_bounds(&self) -> WindowBounds {
955 self.0.as_ref().lock().window_bounds()
956 }
957
958 fn is_maximized(&self) -> bool {
959 self.0.as_ref().lock().is_maximized()
960 }
961
962 fn content_size(&self) -> Size<Pixels> {
963 self.0.as_ref().lock().content_size()
964 }
965
966 fn resize(&mut self, size: Size<Pixels>) {
967 let this = self.0.lock();
968 let window = this.native_window;
969 this.executor
970 .spawn(async move {
971 unsafe {
972 window.setContentSize_(NSSize {
973 width: size.width.0 as f64,
974 height: size.height.0 as f64,
975 });
976 }
977 })
978 .detach();
979 }
980
981 fn merge_all_windows(&self) {
982 let native_window = self.0.lock().native_window;
983 unsafe extern "C" fn merge_windows_async(context: *mut std::ffi::c_void) {
984 let native_window = context as id;
985 let _: () = msg_send![native_window, mergeAllWindows:nil];
986 }
987
988 unsafe {
989 dispatch_async_f(
990 dispatch_get_main_queue(),
991 native_window as *mut std::ffi::c_void,
992 Some(merge_windows_async),
993 );
994 }
995 }
996
997 fn move_tab_to_new_window(&self) {
998 let native_window = self.0.lock().native_window;
999 unsafe extern "C" fn move_tab_async(context: *mut std::ffi::c_void) {
1000 let native_window = context as id;
1001 let _: () = msg_send![native_window, moveTabToNewWindow:nil];
1002 let _: () = msg_send![native_window, makeKeyAndOrderFront: nil];
1003 }
1004
1005 unsafe {
1006 dispatch_async_f(
1007 dispatch_get_main_queue(),
1008 native_window as *mut std::ffi::c_void,
1009 Some(move_tab_async),
1010 );
1011 }
1012 }
1013
1014 fn toggle_window_tab_overview(&self) {
1015 let native_window = self.0.lock().native_window;
1016 unsafe {
1017 let _: () = msg_send![native_window, toggleTabOverview:nil];
1018 }
1019 }
1020
1021 fn scale_factor(&self) -> f32 {
1022 self.0.as_ref().lock().scale_factor()
1023 }
1024
1025 fn appearance(&self) -> WindowAppearance {
1026 unsafe {
1027 let appearance: id = msg_send![self.0.lock().native_window, effectiveAppearance];
1028 WindowAppearance::from_native(appearance)
1029 }
1030 }
1031
1032 fn display(&self) -> Option<Rc<dyn PlatformDisplay>> {
1033 unsafe {
1034 let screen = self.0.lock().native_window.screen();
1035 if screen.is_null() {
1036 return None;
1037 }
1038 let device_description: id = msg_send![screen, deviceDescription];
1039 let screen_number: id = NSDictionary::valueForKey_(
1040 device_description,
1041 NSString::alloc(nil).init_str("NSScreenNumber"),
1042 );
1043
1044 let screen_number: u32 = msg_send![screen_number, unsignedIntValue];
1045
1046 Some(Rc::new(MacDisplay(screen_number)))
1047 }
1048 }
1049
1050 fn mouse_position(&self) -> Point<Pixels> {
1051 let position = unsafe {
1052 self.0
1053 .lock()
1054 .native_window
1055 .mouseLocationOutsideOfEventStream()
1056 };
1057 convert_mouse_position(position, self.content_size().height)
1058 }
1059
1060 fn modifiers(&self) -> Modifiers {
1061 unsafe {
1062 let modifiers: NSEventModifierFlags = msg_send![class!(NSEvent), modifierFlags];
1063
1064 let control = modifiers.contains(NSEventModifierFlags::NSControlKeyMask);
1065 let alt = modifiers.contains(NSEventModifierFlags::NSAlternateKeyMask);
1066 let shift = modifiers.contains(NSEventModifierFlags::NSShiftKeyMask);
1067 let command = modifiers.contains(NSEventModifierFlags::NSCommandKeyMask);
1068 let function = modifiers.contains(NSEventModifierFlags::NSFunctionKeyMask);
1069
1070 Modifiers {
1071 control,
1072 alt,
1073 shift,
1074 platform: command,
1075 function,
1076 }
1077 }
1078 }
1079
1080 fn capslock(&self) -> Capslock {
1081 unsafe {
1082 let modifiers: NSEventModifierFlags = msg_send![class!(NSEvent), modifierFlags];
1083
1084 Capslock {
1085 on: modifiers.contains(NSEventModifierFlags::NSAlphaShiftKeyMask),
1086 }
1087 }
1088 }
1089
1090 fn set_input_handler(&mut self, input_handler: PlatformInputHandler) {
1091 self.0.as_ref().lock().input_handler = Some(input_handler);
1092 }
1093
1094 fn take_input_handler(&mut self) -> Option<PlatformInputHandler> {
1095 self.0.as_ref().lock().input_handler.take()
1096 }
1097
1098 fn prompt(
1099 &self,
1100 level: PromptLevel,
1101 msg: &str,
1102 detail: Option<&str>,
1103 answers: &[PromptButton],
1104 ) -> Option<oneshot::Receiver<usize>> {
1105 // macOs applies overrides to modal window buttons after they are added.
1106 // Two most important for this logic are:
1107 // * Buttons with "Cancel" title will be displayed as the last buttons in the modal
1108 // * Last button added to the modal via `addButtonWithTitle` stays focused
1109 // * Focused buttons react on "space"/" " keypresses
1110 // * Usage of `keyEquivalent`, `makeFirstResponder` or `setInitialFirstResponder` does not change the focus
1111 //
1112 // See also https://developer.apple.com/documentation/appkit/nsalert/1524532-addbuttonwithtitle#discussion
1113 // ```
1114 // By default, the first button has a key equivalent of Return,
1115 // any button with a title of “Cancel” has a key equivalent of Escape,
1116 // 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).
1117 // ```
1118 //
1119 // To avoid situations when the last element added is "Cancel" and it gets the focus
1120 // (hence stealing both ESC and Space shortcuts), we find and add one non-Cancel button
1121 // last, so it gets focus and a Space shortcut.
1122 // This way, "Save this file? Yes/No/Cancel"-ish modals will get all three buttons mapped with a key.
1123 let latest_non_cancel_label = answers
1124 .iter()
1125 .enumerate()
1126 .rev()
1127 .find(|(_, label)| !label.is_cancel())
1128 .filter(|&(label_index, _)| label_index > 0);
1129
1130 unsafe {
1131 let alert: id = msg_send![class!(NSAlert), alloc];
1132 let alert: id = msg_send![alert, init];
1133 let alert_style = match level {
1134 PromptLevel::Info => 1,
1135 PromptLevel::Warning => 0,
1136 PromptLevel::Critical => 2,
1137 };
1138 let _: () = msg_send![alert, setAlertStyle: alert_style];
1139 let _: () = msg_send![alert, setMessageText: ns_string(msg)];
1140 if let Some(detail) = detail {
1141 let _: () = msg_send![alert, setInformativeText: ns_string(detail)];
1142 }
1143
1144 for (ix, answer) in answers
1145 .iter()
1146 .enumerate()
1147 .filter(|&(ix, _)| Some(ix) != latest_non_cancel_label.map(|(ix, _)| ix))
1148 {
1149 let button: id = msg_send![alert, addButtonWithTitle: ns_string(answer.label())];
1150 let _: () = msg_send![button, setTag: ix as NSInteger];
1151
1152 if answer.is_cancel() {
1153 // Bind Escape Key to Cancel Button
1154 if let Some(key) = std::char::from_u32(super::events::ESCAPE_KEY as u32) {
1155 let _: () =
1156 msg_send![button, setKeyEquivalent: ns_string(&key.to_string())];
1157 }
1158 }
1159 }
1160 if let Some((ix, answer)) = latest_non_cancel_label {
1161 let button: id = msg_send![alert, addButtonWithTitle: ns_string(answer.label())];
1162 let _: () = msg_send![button, setTag: ix as NSInteger];
1163 }
1164
1165 let (done_tx, done_rx) = oneshot::channel();
1166 let done_tx = Cell::new(Some(done_tx));
1167 let block = ConcreteBlock::new(move |answer: NSInteger| {
1168 if let Some(done_tx) = done_tx.take() {
1169 let _ = done_tx.send(answer.try_into().unwrap());
1170 }
1171 });
1172 let block = block.copy();
1173 let native_window = self.0.lock().native_window;
1174 let executor = self.0.lock().executor.clone();
1175 executor
1176 .spawn(async move {
1177 let _: () = msg_send![
1178 alert,
1179 beginSheetModalForWindow: native_window
1180 completionHandler: block
1181 ];
1182 })
1183 .detach();
1184
1185 Some(done_rx)
1186 }
1187 }
1188
1189 fn activate(&self) {
1190 let window = self.0.lock().native_window;
1191 let executor = self.0.lock().executor.clone();
1192 executor
1193 .spawn(async move {
1194 unsafe {
1195 let _: () = msg_send![window, makeKeyAndOrderFront: nil];
1196 }
1197 })
1198 .detach();
1199 }
1200
1201 fn is_active(&self) -> bool {
1202 unsafe { self.0.lock().native_window.isKeyWindow() == YES }
1203 }
1204
1205 // is_hovered is unused on macOS. See Window::is_window_hovered.
1206 fn is_hovered(&self) -> bool {
1207 false
1208 }
1209
1210 fn set_title(&mut self, title: &str) {
1211 unsafe {
1212 let app = NSApplication::sharedApplication(nil);
1213 let window = self.0.lock().native_window;
1214 let title = ns_string(title);
1215 let _: () = msg_send![app, changeWindowsItem:window title:title filename:false];
1216 let _: () = msg_send![window, setTitle: title];
1217 self.0.lock().move_traffic_light();
1218 }
1219 }
1220
1221 fn get_title(&self) -> String {
1222 unsafe {
1223 let title: id = msg_send![self.0.lock().native_window, title];
1224 if title.is_null() {
1225 "".to_string()
1226 } else {
1227 title.to_str().to_string()
1228 }
1229 }
1230 }
1231
1232 fn set_app_id(&mut self, _app_id: &str) {}
1233
1234 fn set_background_appearance(&self, background_appearance: WindowBackgroundAppearance) {
1235 let mut this = self.0.as_ref().lock();
1236
1237 let opaque = background_appearance == WindowBackgroundAppearance::Opaque;
1238 this.renderer.update_transparency(!opaque);
1239
1240 unsafe {
1241 this.native_window.setOpaque_(opaque as BOOL);
1242 let background_color = if opaque {
1243 NSColor::colorWithSRGBRed_green_blue_alpha_(nil, 0f64, 0f64, 0f64, 1f64)
1244 } else {
1245 // Not using `+[NSColor clearColor]` to avoid broken shadow.
1246 NSColor::colorWithSRGBRed_green_blue_alpha_(nil, 0f64, 0f64, 0f64, 0.0001)
1247 };
1248 this.native_window.setBackgroundColor_(background_color);
1249
1250 if NSAppKitVersionNumber < NSAppKitVersionNumber12_0 {
1251 // Whether `-[NSVisualEffectView respondsToSelector:@selector(_updateProxyLayer)]`.
1252 // On macOS Catalina/Big Sur `NSVisualEffectView` doesn’t own concrete sublayers
1253 // but uses a `CAProxyLayer`. Use the legacy WindowServer API.
1254 let blur_radius = if background_appearance == WindowBackgroundAppearance::Blurred {
1255 80
1256 } else {
1257 0
1258 };
1259
1260 let window_number = this.native_window.windowNumber();
1261 CGSSetWindowBackgroundBlurRadius(CGSMainConnectionID(), window_number, blur_radius);
1262 } else {
1263 // On newer macOS `NSVisualEffectView` manages the effect layer directly. Using it
1264 // could have a better performance (it downsamples the backdrop) and more control
1265 // over the effect layer.
1266 if background_appearance != WindowBackgroundAppearance::Blurred {
1267 if let Some(blur_view) = this.blurred_view {
1268 NSView::removeFromSuperview(blur_view);
1269 this.blurred_view = None;
1270 }
1271 } else if this.blurred_view.is_none() {
1272 let content_view = this.native_window.contentView();
1273 let frame = NSView::bounds(content_view);
1274 let mut blur_view: id = msg_send![BLURRED_VIEW_CLASS, alloc];
1275 blur_view = NSView::initWithFrame_(blur_view, frame);
1276 blur_view.setAutoresizingMask_(NSViewWidthSizable | NSViewHeightSizable);
1277
1278 let _: () = msg_send![
1279 content_view,
1280 addSubview: blur_view
1281 positioned: NSWindowOrderingMode::NSWindowBelow
1282 relativeTo: nil
1283 ];
1284 this.blurred_view = Some(blur_view.autorelease());
1285 }
1286 }
1287 }
1288 }
1289
1290 fn set_edited(&mut self, edited: bool) {
1291 unsafe {
1292 let window = self.0.lock().native_window;
1293 msg_send![window, setDocumentEdited: edited as BOOL]
1294 }
1295
1296 // Changing the document edited state resets the traffic light position,
1297 // so we have to move it again.
1298 self.0.lock().move_traffic_light();
1299 }
1300
1301 fn show_character_palette(&self) {
1302 let this = self.0.lock();
1303 let window = this.native_window;
1304 this.executor
1305 .spawn(async move {
1306 unsafe {
1307 let app = NSApplication::sharedApplication(nil);
1308 let _: () = msg_send![app, orderFrontCharacterPalette: window];
1309 }
1310 })
1311 .detach();
1312 }
1313
1314 fn minimize(&self) {
1315 let window = self.0.lock().native_window;
1316 unsafe {
1317 window.miniaturize_(nil);
1318 }
1319 }
1320
1321 fn zoom(&self) {
1322 let this = self.0.lock();
1323 let window = this.native_window;
1324 this.executor
1325 .spawn(async move {
1326 unsafe {
1327 window.zoom_(nil);
1328 }
1329 })
1330 .detach();
1331 }
1332
1333 fn toggle_fullscreen(&self) {
1334 let this = self.0.lock();
1335 let window = this.native_window;
1336 this.executor
1337 .spawn(async move {
1338 unsafe {
1339 window.toggleFullScreen_(nil);
1340 }
1341 })
1342 .detach();
1343 }
1344
1345 fn is_fullscreen(&self) -> bool {
1346 let this = self.0.lock();
1347 let window = this.native_window;
1348
1349 unsafe {
1350 window
1351 .styleMask()
1352 .contains(NSWindowStyleMask::NSFullScreenWindowMask)
1353 }
1354 }
1355
1356 fn on_request_frame(&self, callback: Box<dyn FnMut(RequestFrameOptions)>) {
1357 self.0.as_ref().lock().request_frame_callback = Some(callback);
1358 }
1359
1360 fn on_input(&self, callback: Box<dyn FnMut(PlatformInput) -> crate::DispatchEventResult>) {
1361 self.0.as_ref().lock().event_callback = Some(callback);
1362 }
1363
1364 fn on_active_status_change(&self, callback: Box<dyn FnMut(bool)>) {
1365 self.0.as_ref().lock().activate_callback = Some(callback);
1366 }
1367
1368 fn on_hover_status_change(&self, _: Box<dyn FnMut(bool)>) {}
1369
1370 fn on_resize(&self, callback: Box<dyn FnMut(Size<Pixels>, f32)>) {
1371 self.0.as_ref().lock().resize_callback = Some(callback);
1372 }
1373
1374 fn on_moved(&self, callback: Box<dyn FnMut()>) {
1375 self.0.as_ref().lock().moved_callback = Some(callback);
1376 }
1377
1378 fn on_should_close(&self, callback: Box<dyn FnMut() -> bool>) {
1379 self.0.as_ref().lock().should_close_callback = Some(callback);
1380 }
1381
1382 fn on_close(&self, callback: Box<dyn FnOnce()>) {
1383 self.0.as_ref().lock().close_callback = Some(callback);
1384 }
1385
1386 fn on_hit_test_window_control(&self, _callback: Box<dyn FnMut() -> Option<WindowControlArea>>) {
1387 }
1388
1389 fn on_appearance_changed(&self, callback: Box<dyn FnMut()>) {
1390 self.0.lock().appearance_changed_callback = Some(callback);
1391 }
1392
1393 fn tabbed_windows(&self) -> Option<Vec<SystemWindowTab>> {
1394 unsafe {
1395 let windows: id = msg_send![self.0.lock().native_window, tabbedWindows];
1396 if windows.is_null() {
1397 return None;
1398 }
1399
1400 let count: NSUInteger = msg_send![windows, count];
1401 let mut result = Vec::new();
1402 for i in 0..count {
1403 let window: id = msg_send![windows, objectAtIndex:i];
1404 if msg_send![window, isKindOfClass: WINDOW_CLASS] {
1405 let handle = get_window_state(&*window).lock().handle;
1406 let title: id = msg_send![window, title];
1407 let title = SharedString::from(title.to_str().to_string());
1408
1409 result.push(SystemWindowTab::new(title, handle));
1410 }
1411 }
1412
1413 Some(result)
1414 }
1415 }
1416
1417 fn tab_bar_visible(&self) -> bool {
1418 unsafe {
1419 let tab_group: id = msg_send![self.0.lock().native_window, tabGroup];
1420 if tab_group.is_null() {
1421 false
1422 } else {
1423 let tab_bar_visible: BOOL = msg_send![tab_group, isTabBarVisible];
1424 tab_bar_visible == YES
1425 }
1426 }
1427 }
1428
1429 fn on_move_tab_to_new_window(&self, callback: Box<dyn FnMut()>) {
1430 self.0.as_ref().lock().move_tab_to_new_window_callback = Some(callback);
1431 }
1432
1433 fn on_merge_all_windows(&self, callback: Box<dyn FnMut()>) {
1434 self.0.as_ref().lock().merge_all_windows_callback = Some(callback);
1435 }
1436
1437 fn on_select_next_tab(&self, callback: Box<dyn FnMut()>) {
1438 self.0.as_ref().lock().select_next_tab_callback = Some(callback);
1439 }
1440
1441 fn on_select_previous_tab(&self, callback: Box<dyn FnMut()>) {
1442 self.0.as_ref().lock().select_previous_tab_callback = Some(callback);
1443 }
1444
1445 fn on_toggle_tab_bar(&self, callback: Box<dyn FnMut()>) {
1446 self.0.as_ref().lock().toggle_tab_bar_callback = Some(callback);
1447 }
1448
1449 fn draw(&self, scene: &crate::Scene) {
1450 let mut this = self.0.lock();
1451 this.renderer.draw(scene);
1452 }
1453
1454 fn sprite_atlas(&self) -> Arc<dyn PlatformAtlas> {
1455 self.0.lock().renderer.sprite_atlas().clone()
1456 }
1457
1458 fn gpu_specs(&self) -> Option<crate::GpuSpecs> {
1459 None
1460 }
1461
1462 fn update_ime_position(&self, _bounds: Bounds<ScaledPixels>) {
1463 let executor = self.0.lock().executor.clone();
1464 executor
1465 .spawn(async move {
1466 unsafe {
1467 let input_context: id =
1468 msg_send![class!(NSTextInputContext), currentInputContext];
1469 if input_context.is_null() {
1470 return;
1471 }
1472 let _: () = msg_send![input_context, invalidateCharacterCoordinates];
1473 }
1474 })
1475 .detach()
1476 }
1477
1478 fn titlebar_double_click(&self) {
1479 let this = self.0.lock();
1480 let window = this.native_window;
1481 this.executor
1482 .spawn(async move {
1483 unsafe {
1484 let defaults: id = NSUserDefaults::standardUserDefaults();
1485 let domain = NSString::alloc(nil).init_str("NSGlobalDomain");
1486 let key = NSString::alloc(nil).init_str("AppleActionOnDoubleClick");
1487
1488 let dict: id = msg_send![defaults, persistentDomainForName: domain];
1489 let action: id = if !dict.is_null() {
1490 msg_send![dict, objectForKey: key]
1491 } else {
1492 nil
1493 };
1494
1495 let action_str = if !action.is_null() {
1496 CStr::from_ptr(NSString::UTF8String(action)).to_string_lossy()
1497 } else {
1498 "".into()
1499 };
1500
1501 match action_str.as_ref() {
1502 "Minimize" => {
1503 window.miniaturize_(nil);
1504 }
1505 "Maximize" => {
1506 window.zoom_(nil);
1507 }
1508 "Fill" => {
1509 // There is no documented API for "Fill" action, so we'll just zoom the window
1510 window.zoom_(nil);
1511 }
1512 _ => {
1513 window.zoom_(nil);
1514 }
1515 }
1516 }
1517 })
1518 .detach();
1519 }
1520}
1521
1522impl rwh::HasWindowHandle for MacWindow {
1523 fn window_handle(&self) -> Result<rwh::WindowHandle<'_>, rwh::HandleError> {
1524 // SAFETY: The AppKitWindowHandle is a wrapper around a pointer to an NSView
1525 unsafe {
1526 Ok(rwh::WindowHandle::borrow_raw(rwh::RawWindowHandle::AppKit(
1527 rwh::AppKitWindowHandle::new(self.0.lock().native_view.cast()),
1528 )))
1529 }
1530 }
1531}
1532
1533impl rwh::HasDisplayHandle for MacWindow {
1534 fn display_handle(&self) -> Result<rwh::DisplayHandle<'_>, rwh::HandleError> {
1535 // SAFETY: This is a no-op on macOS
1536 unsafe {
1537 Ok(rwh::DisplayHandle::borrow_raw(
1538 rwh::AppKitDisplayHandle::new().into(),
1539 ))
1540 }
1541 }
1542}
1543
1544fn get_scale_factor(native_window: id) -> f32 {
1545 let factor = unsafe {
1546 let screen: id = msg_send![native_window, screen];
1547 if screen.is_null() {
1548 return 1.0;
1549 }
1550 NSScreen::backingScaleFactor(screen) as f32
1551 };
1552
1553 // We are not certain what triggers this, but it seems that sometimes
1554 // this method would return 0 (https://github.com/zed-industries/zed/issues/6412)
1555 // It seems most likely that this would happen if the window has no screen
1556 // (if it is off-screen), though we'd expect to see viewDidChangeBackingProperties before
1557 // it was rendered for real.
1558 // Regardless, attempt to avoid the issue here.
1559 if factor == 0.0 { 2. } else { factor }
1560}
1561
1562unsafe fn get_window_state(object: &Object) -> Arc<Mutex<MacWindowState>> {
1563 unsafe {
1564 let raw: *mut c_void = *object.get_ivar(WINDOW_STATE_IVAR);
1565 let rc1 = Arc::from_raw(raw as *mut Mutex<MacWindowState>);
1566 let rc2 = rc1.clone();
1567 mem::forget(rc1);
1568 rc2
1569 }
1570}
1571
1572unsafe fn drop_window_state(object: &Object) {
1573 unsafe {
1574 let raw: *mut c_void = *object.get_ivar(WINDOW_STATE_IVAR);
1575 Arc::from_raw(raw as *mut Mutex<MacWindowState>);
1576 }
1577}
1578
1579extern "C" fn yes(_: &Object, _: Sel) -> BOOL {
1580 YES
1581}
1582
1583extern "C" fn dealloc_window(this: &Object, _: Sel) {
1584 unsafe {
1585 drop_window_state(this);
1586 let _: () = msg_send![super(this, class!(NSWindow)), dealloc];
1587 }
1588}
1589
1590extern "C" fn dealloc_view(this: &Object, _: Sel) {
1591 unsafe {
1592 drop_window_state(this);
1593 let _: () = msg_send![super(this, class!(NSView)), dealloc];
1594 }
1595}
1596
1597extern "C" fn handle_key_equivalent(this: &Object, _: Sel, native_event: id) -> BOOL {
1598 handle_key_event(this, native_event, true)
1599}
1600
1601extern "C" fn handle_key_down(this: &Object, _: Sel, native_event: id) {
1602 handle_key_event(this, native_event, false);
1603}
1604
1605extern "C" fn handle_key_up(this: &Object, _: Sel, native_event: id) {
1606 handle_key_event(this, native_event, false);
1607}
1608
1609// Things to test if you're modifying this method:
1610// U.S. layout:
1611// - The IME consumes characters like 'j' and 'k', which makes paging through `less` in
1612// the terminal behave incorrectly by default. This behavior should be patched by our
1613// IME integration
1614// - `alt-t` should open the tasks menu
1615// - In vim mode, this keybinding should work:
1616// ```
1617// {
1618// "context": "Editor && vim_mode == insert",
1619// "bindings": {"j j": "vim::NormalBefore"}
1620// }
1621// ```
1622// and typing 'j k' in insert mode with this keybinding should insert the two characters
1623// Brazilian layout:
1624// - `" space` should create an unmarked quote
1625// - `" backspace` should delete the marked quote
1626// - `" "`should create an unmarked quote and a second marked quote
1627// - `" up` should insert a quote, unmark it, and move up one line
1628// - `" cmd-down` should insert a quote, unmark it, and move to the end of the file
1629// - `cmd-ctrl-space` and clicking on an emoji should type it
1630// Czech (QWERTY) layout:
1631// - in vim mode `option-4` should go to end of line (same as $)
1632// Japanese (Romaji) layout:
1633// - type `a i left down up enter enter` should create an unmarked text "愛"
1634extern "C" fn handle_key_event(this: &Object, native_event: id, key_equivalent: bool) -> BOOL {
1635 let window_state = unsafe { get_window_state(this) };
1636 let mut lock = window_state.as_ref().lock();
1637
1638 let window_height = lock.content_size().height;
1639 let event = unsafe { PlatformInput::from_native(native_event, Some(window_height)) };
1640
1641 let Some(event) = event else {
1642 return NO;
1643 };
1644
1645 let run_callback = |event: PlatformInput| -> BOOL {
1646 let mut callback = window_state.as_ref().lock().event_callback.take();
1647 let handled: BOOL = if let Some(callback) = callback.as_mut() {
1648 !callback(event).propagate as BOOL
1649 } else {
1650 NO
1651 };
1652 window_state.as_ref().lock().event_callback = callback;
1653 handled
1654 };
1655
1656 match event {
1657 PlatformInput::KeyDown(mut key_down_event) => {
1658 // For certain keystrokes, macOS will first dispatch a "key equivalent" event.
1659 // If that event isn't handled, it will then dispatch a "key down" event. GPUI
1660 // makes no distinction between these two types of events, so we need to ignore
1661 // the "key down" event if we've already just processed its "key equivalent" version.
1662 if key_equivalent {
1663 lock.last_key_equivalent = Some(key_down_event.clone());
1664 } else if lock.last_key_equivalent.take().as_ref() == Some(&key_down_event) {
1665 return NO;
1666 }
1667
1668 drop(lock);
1669
1670 let is_composing =
1671 with_input_handler(this, |input_handler| input_handler.marked_text_range())
1672 .flatten()
1673 .is_some();
1674
1675 // If we're composing, send the key to the input handler first;
1676 // otherwise we only send to the input handler if we don't have a matching binding.
1677 // The input handler may call `do_command_by_selector` if it doesn't know how to handle
1678 // a key. If it does so, it will return YES so we won't send the key twice.
1679 // We also do this for non-printing keys (like arrow keys and escape) as the IME menu
1680 // may need them even if there is no marked text;
1681 // however we skip keys with control or the input handler adds control-characters to the buffer.
1682 // and keys with function, as the input handler swallows them.
1683 if is_composing
1684 || (key_down_event.keystroke.key_char.is_none()
1685 && !key_down_event.keystroke.modifiers.control
1686 && !key_down_event.keystroke.modifiers.function)
1687 {
1688 {
1689 let mut lock = window_state.as_ref().lock();
1690 lock.keystroke_for_do_command = Some(key_down_event.keystroke.clone());
1691 lock.do_command_handled.take();
1692 drop(lock);
1693 }
1694
1695 let handled: BOOL = unsafe {
1696 let input_context: id = msg_send![this, inputContext];
1697 msg_send![input_context, handleEvent: native_event]
1698 };
1699 window_state.as_ref().lock().keystroke_for_do_command.take();
1700 if let Some(handled) = window_state.as_ref().lock().do_command_handled.take() {
1701 return handled as BOOL;
1702 } else if handled == YES {
1703 return YES;
1704 }
1705
1706 let handled = run_callback(PlatformInput::KeyDown(key_down_event));
1707 return handled;
1708 }
1709
1710 let handled = run_callback(PlatformInput::KeyDown(key_down_event.clone()));
1711 if handled == YES {
1712 return YES;
1713 }
1714
1715 if key_down_event.is_held
1716 && let Some(key_char) = key_down_event.keystroke.key_char.as_ref()
1717 {
1718 let handled = with_input_handler(this, |input_handler| {
1719 if !input_handler.apple_press_and_hold_enabled() {
1720 input_handler.replace_text_in_range(None, key_char);
1721 return YES;
1722 }
1723 NO
1724 });
1725 if handled == Some(YES) {
1726 return YES;
1727 }
1728 }
1729
1730 // Don't send key equivalents to the input handler,
1731 // or macOS shortcuts like cmd-` will stop working.
1732 if key_equivalent {
1733 return NO;
1734 }
1735
1736 unsafe {
1737 let input_context: id = msg_send![this, inputContext];
1738 msg_send![input_context, handleEvent: native_event]
1739 }
1740 }
1741
1742 PlatformInput::KeyUp(_) => {
1743 drop(lock);
1744 run_callback(event)
1745 }
1746
1747 _ => NO,
1748 }
1749}
1750
1751extern "C" fn handle_view_event(this: &Object, _: Sel, native_event: id) {
1752 let window_state = unsafe { get_window_state(this) };
1753 let weak_window_state = Arc::downgrade(&window_state);
1754 let mut lock = window_state.as_ref().lock();
1755 let window_height = lock.content_size().height;
1756 let event = unsafe { PlatformInput::from_native(native_event, Some(window_height)) };
1757
1758 if let Some(mut event) = event {
1759 match &mut event {
1760 PlatformInput::MouseDown(
1761 event @ MouseDownEvent {
1762 button: MouseButton::Left,
1763 modifiers: Modifiers { control: true, .. },
1764 ..
1765 },
1766 ) => {
1767 // On mac, a ctrl-left click should be handled as a right click.
1768 *event = MouseDownEvent {
1769 button: MouseButton::Right,
1770 modifiers: Modifiers {
1771 control: false,
1772 ..event.modifiers
1773 },
1774 click_count: 1,
1775 ..*event
1776 };
1777 }
1778
1779 // Handles focusing click.
1780 PlatformInput::MouseDown(
1781 event @ MouseDownEvent {
1782 button: MouseButton::Left,
1783 ..
1784 },
1785 ) if (lock.first_mouse) => {
1786 *event = MouseDownEvent {
1787 first_mouse: true,
1788 ..*event
1789 };
1790 lock.first_mouse = false;
1791 }
1792
1793 // Because we map a ctrl-left_down to a right_down -> right_up let's ignore
1794 // the ctrl-left_up to avoid having a mismatch in button down/up events if the
1795 // user is still holding ctrl when releasing the left mouse button
1796 PlatformInput::MouseUp(
1797 event @ MouseUpEvent {
1798 button: MouseButton::Left,
1799 modifiers: Modifiers { control: true, .. },
1800 ..
1801 },
1802 ) => {
1803 *event = MouseUpEvent {
1804 button: MouseButton::Right,
1805 modifiers: Modifiers {
1806 control: false,
1807 ..event.modifiers
1808 },
1809 click_count: 1,
1810 ..*event
1811 };
1812 }
1813
1814 _ => {}
1815 };
1816
1817 match &event {
1818 PlatformInput::MouseDown(_) => {
1819 drop(lock);
1820 unsafe {
1821 let input_context: id = msg_send![this, inputContext];
1822 msg_send![input_context, handleEvent: native_event]
1823 }
1824 lock = window_state.as_ref().lock();
1825 }
1826 PlatformInput::MouseMove(
1827 event @ MouseMoveEvent {
1828 pressed_button: Some(_),
1829 ..
1830 },
1831 ) => {
1832 // Synthetic drag is used for selecting long buffer contents while buffer is being scrolled.
1833 // External file drag and drop is able to emit its own synthetic mouse events which will conflict
1834 // with these ones.
1835 if !lock.external_files_dragged {
1836 lock.synthetic_drag_counter += 1;
1837 let executor = lock.executor.clone();
1838 executor
1839 .spawn(synthetic_drag(
1840 weak_window_state,
1841 lock.synthetic_drag_counter,
1842 event.clone(),
1843 ))
1844 .detach();
1845 }
1846 }
1847
1848 PlatformInput::MouseUp(MouseUpEvent { .. }) => {
1849 lock.synthetic_drag_counter += 1;
1850 }
1851
1852 PlatformInput::ModifiersChanged(ModifiersChangedEvent {
1853 modifiers,
1854 capslock,
1855 }) => {
1856 // Only raise modifiers changed event when they have actually changed
1857 if let Some(PlatformInput::ModifiersChanged(ModifiersChangedEvent {
1858 modifiers: prev_modifiers,
1859 capslock: prev_capslock,
1860 })) = &lock.previous_modifiers_changed_event
1861 && prev_modifiers == modifiers
1862 && prev_capslock == capslock
1863 {
1864 return;
1865 }
1866
1867 lock.previous_modifiers_changed_event = Some(event.clone());
1868 }
1869
1870 _ => {}
1871 }
1872
1873 if let Some(mut callback) = lock.event_callback.take() {
1874 drop(lock);
1875 callback(event);
1876 window_state.lock().event_callback = Some(callback);
1877 }
1878 }
1879}
1880
1881extern "C" fn window_did_change_occlusion_state(this: &Object, _: Sel, _: id) {
1882 let window_state = unsafe { get_window_state(this) };
1883 let lock = &mut *window_state.lock();
1884 unsafe {
1885 if lock
1886 .native_window
1887 .occlusionState()
1888 .contains(NSWindowOcclusionState::NSWindowOcclusionStateVisible)
1889 {
1890 lock.move_traffic_light();
1891 lock.start_display_link();
1892 } else {
1893 lock.stop_display_link();
1894 }
1895 }
1896}
1897
1898extern "C" fn window_did_resize(this: &Object, _: Sel, _: id) {
1899 let window_state = unsafe { get_window_state(this) };
1900 window_state.as_ref().lock().move_traffic_light();
1901}
1902
1903extern "C" fn window_will_enter_fullscreen(this: &Object, _: Sel, _: id) {
1904 let window_state = unsafe { get_window_state(this) };
1905 let mut lock = window_state.as_ref().lock();
1906 lock.fullscreen_restore_bounds = lock.bounds();
1907
1908 let min_version = NSOperatingSystemVersion::new(15, 3, 0);
1909
1910 if is_macos_version_at_least(min_version) {
1911 unsafe {
1912 lock.native_window.setTitlebarAppearsTransparent_(NO);
1913 }
1914 }
1915}
1916
1917extern "C" fn window_will_exit_fullscreen(this: &Object, _: Sel, _: id) {
1918 let window_state = unsafe { get_window_state(this) };
1919 let mut lock = window_state.as_ref().lock();
1920
1921 let min_version = NSOperatingSystemVersion::new(15, 3, 0);
1922
1923 if is_macos_version_at_least(min_version) && lock.transparent_titlebar {
1924 unsafe {
1925 lock.native_window.setTitlebarAppearsTransparent_(YES);
1926 }
1927 }
1928}
1929
1930pub(crate) fn is_macos_version_at_least(version: NSOperatingSystemVersion) -> bool {
1931 unsafe { NSProcessInfo::processInfo(nil).isOperatingSystemAtLeastVersion(version) }
1932}
1933
1934extern "C" fn window_did_move(this: &Object, _: Sel, _: id) {
1935 let window_state = unsafe { get_window_state(this) };
1936 let mut lock = window_state.as_ref().lock();
1937 if let Some(mut callback) = lock.moved_callback.take() {
1938 drop(lock);
1939 callback();
1940 window_state.lock().moved_callback = Some(callback);
1941 }
1942}
1943
1944extern "C" fn window_did_change_screen(this: &Object, _: Sel, _: id) {
1945 let window_state = unsafe { get_window_state(this) };
1946 let mut lock = window_state.as_ref().lock();
1947 lock.start_display_link();
1948}
1949
1950extern "C" fn window_did_change_key_status(this: &Object, selector: Sel, _: id) {
1951 let window_state = unsafe { get_window_state(this) };
1952 let mut lock = window_state.lock();
1953 let is_active = unsafe { lock.native_window.isKeyWindow() == YES };
1954
1955 // When opening a pop-up while the application isn't active, Cocoa sends a spurious
1956 // `windowDidBecomeKey` message to the previous key window even though that window
1957 // isn't actually key. This causes a bug if the application is later activated while
1958 // the pop-up is still open, making it impossible to activate the previous key window
1959 // even if the pop-up gets closed. The only way to activate it again is to de-activate
1960 // the app and re-activate it, which is a pretty bad UX.
1961 // The following code detects the spurious event and invokes `resignKeyWindow`:
1962 // in theory, we're not supposed to invoke this method manually but it balances out
1963 // the spurious `becomeKeyWindow` event and helps us work around that bug.
1964 if selector == sel!(windowDidBecomeKey:) && !is_active {
1965 unsafe {
1966 let _: () = msg_send![lock.native_window, resignKeyWindow];
1967 return;
1968 }
1969 }
1970
1971 let executor = lock.executor.clone();
1972 drop(lock);
1973
1974 // If window is becoming active, trigger immediate synchronous frame request.
1975 if selector == sel!(windowDidBecomeKey:) && is_active {
1976 let window_state = unsafe { get_window_state(this) };
1977 let mut lock = window_state.lock();
1978
1979 if let Some(mut callback) = lock.request_frame_callback.take() {
1980 #[cfg(not(feature = "macos-blade"))]
1981 lock.renderer.set_presents_with_transaction(true);
1982 lock.stop_display_link();
1983 drop(lock);
1984 callback(Default::default());
1985
1986 let mut lock = window_state.lock();
1987 lock.request_frame_callback = Some(callback);
1988 #[cfg(not(feature = "macos-blade"))]
1989 lock.renderer.set_presents_with_transaction(false);
1990 lock.start_display_link();
1991 }
1992 }
1993
1994 executor
1995 .spawn(async move {
1996 let mut lock = window_state.as_ref().lock();
1997 if is_active {
1998 lock.move_traffic_light();
1999 }
2000
2001 if let Some(mut callback) = lock.activate_callback.take() {
2002 drop(lock);
2003 callback(is_active);
2004 window_state.lock().activate_callback = Some(callback);
2005 };
2006 })
2007 .detach();
2008}
2009
2010extern "C" fn window_should_close(this: &Object, _: Sel, _: id) -> BOOL {
2011 let window_state = unsafe { get_window_state(this) };
2012 let mut lock = window_state.as_ref().lock();
2013 if let Some(mut callback) = lock.should_close_callback.take() {
2014 drop(lock);
2015 let should_close = callback();
2016 window_state.lock().should_close_callback = Some(callback);
2017 should_close as BOOL
2018 } else {
2019 YES
2020 }
2021}
2022
2023extern "C" fn close_window(this: &Object, _: Sel) {
2024 unsafe {
2025 let close_callback = {
2026 let window_state = get_window_state(this);
2027 let mut lock = window_state.as_ref().lock();
2028 lock.close_callback.take()
2029 };
2030
2031 if let Some(callback) = close_callback {
2032 callback();
2033 }
2034
2035 let _: () = msg_send![super(this, class!(NSWindow)), close];
2036 }
2037}
2038
2039extern "C" fn make_backing_layer(this: &Object, _: Sel) -> id {
2040 let window_state = unsafe { get_window_state(this) };
2041 let window_state = window_state.as_ref().lock();
2042 window_state.renderer.layer_ptr() as id
2043}
2044
2045extern "C" fn view_did_change_backing_properties(this: &Object, _: Sel) {
2046 let window_state = unsafe { get_window_state(this) };
2047 let mut lock = window_state.as_ref().lock();
2048
2049 let scale_factor = lock.scale_factor();
2050 let size = lock.content_size();
2051 let drawable_size = size.to_device_pixels(scale_factor);
2052 unsafe {
2053 let _: () = msg_send![
2054 lock.renderer.layer(),
2055 setContentsScale: scale_factor as f64
2056 ];
2057 }
2058
2059 lock.renderer.update_drawable_size(drawable_size);
2060
2061 if let Some(mut callback) = lock.resize_callback.take() {
2062 let content_size = lock.content_size();
2063 let scale_factor = lock.scale_factor();
2064 drop(lock);
2065 callback(content_size, scale_factor);
2066 window_state.as_ref().lock().resize_callback = Some(callback);
2067 };
2068}
2069
2070extern "C" fn set_frame_size(this: &Object, _: Sel, size: NSSize) {
2071 let window_state = unsafe { get_window_state(this) };
2072 let mut lock = window_state.as_ref().lock();
2073
2074 let new_size = Size::<Pixels>::from(size);
2075 let old_size = unsafe {
2076 let old_frame: NSRect = msg_send![this, frame];
2077 Size::<Pixels>::from(old_frame.size)
2078 };
2079
2080 if old_size == new_size {
2081 return;
2082 }
2083
2084 unsafe {
2085 let _: () = msg_send![super(this, class!(NSView)), setFrameSize: size];
2086 }
2087
2088 let scale_factor = lock.scale_factor();
2089 let drawable_size = new_size.to_device_pixels(scale_factor);
2090 lock.renderer.update_drawable_size(drawable_size);
2091
2092 if let Some(mut callback) = lock.resize_callback.take() {
2093 let content_size = lock.content_size();
2094 let scale_factor = lock.scale_factor();
2095 drop(lock);
2096 callback(content_size, scale_factor);
2097 window_state.lock().resize_callback = Some(callback);
2098 };
2099}
2100
2101extern "C" fn display_layer(this: &Object, _: Sel, _: id) {
2102 let window_state = unsafe { get_window_state(this) };
2103 let mut lock = window_state.lock();
2104 if let Some(mut callback) = lock.request_frame_callback.take() {
2105 #[cfg(not(feature = "macos-blade"))]
2106 lock.renderer.set_presents_with_transaction(true);
2107 lock.stop_display_link();
2108 drop(lock);
2109 callback(Default::default());
2110
2111 let mut lock = window_state.lock();
2112 lock.request_frame_callback = Some(callback);
2113 #[cfg(not(feature = "macos-blade"))]
2114 lock.renderer.set_presents_with_transaction(false);
2115 lock.start_display_link();
2116 }
2117}
2118
2119unsafe extern "C" fn step(view: *mut c_void) {
2120 let view = view as id;
2121 let window_state = unsafe { get_window_state(&*view) };
2122 let mut lock = window_state.lock();
2123
2124 if let Some(mut callback) = lock.request_frame_callback.take() {
2125 drop(lock);
2126 callback(Default::default());
2127 window_state.lock().request_frame_callback = Some(callback);
2128 }
2129}
2130
2131extern "C" fn valid_attributes_for_marked_text(_: &Object, _: Sel) -> id {
2132 unsafe { msg_send![class!(NSArray), array] }
2133}
2134
2135extern "C" fn has_marked_text(this: &Object, _: Sel) -> BOOL {
2136 let has_marked_text_result =
2137 with_input_handler(this, |input_handler| input_handler.marked_text_range()).flatten();
2138
2139 has_marked_text_result.is_some() as BOOL
2140}
2141
2142extern "C" fn marked_range(this: &Object, _: Sel) -> NSRange {
2143 let marked_range_result =
2144 with_input_handler(this, |input_handler| input_handler.marked_text_range()).flatten();
2145
2146 marked_range_result.map_or(NSRange::invalid(), |range| range.into())
2147}
2148
2149extern "C" fn selected_range(this: &Object, _: Sel) -> NSRange {
2150 let selected_range_result = with_input_handler(this, |input_handler| {
2151 input_handler.selected_text_range(false)
2152 })
2153 .flatten();
2154
2155 selected_range_result.map_or(NSRange::invalid(), |selection| selection.range.into())
2156}
2157
2158extern "C" fn first_rect_for_character_range(
2159 this: &Object,
2160 _: Sel,
2161 range: NSRange,
2162 _: id,
2163) -> NSRect {
2164 let frame = get_frame(this);
2165 with_input_handler(this, |input_handler| {
2166 input_handler.bounds_for_range(range.to_range()?)
2167 })
2168 .flatten()
2169 .map_or(
2170 NSRect::new(NSPoint::new(0., 0.), NSSize::new(0., 0.)),
2171 |bounds| {
2172 NSRect::new(
2173 NSPoint::new(
2174 frame.origin.x + bounds.origin.x.0 as f64,
2175 frame.origin.y + frame.size.height
2176 - bounds.origin.y.0 as f64
2177 - bounds.size.height.0 as f64,
2178 ),
2179 NSSize::new(bounds.size.width.0 as f64, bounds.size.height.0 as f64),
2180 )
2181 },
2182 )
2183}
2184
2185fn get_frame(this: &Object) -> NSRect {
2186 unsafe {
2187 let state = get_window_state(this);
2188 let lock = state.lock();
2189 let mut frame = NSWindow::frame(lock.native_window);
2190 let content_layout_rect: CGRect = msg_send![lock.native_window, contentLayoutRect];
2191 let style_mask: NSWindowStyleMask = msg_send![lock.native_window, styleMask];
2192 if !style_mask.contains(NSWindowStyleMask::NSFullSizeContentViewWindowMask) {
2193 frame.origin.y -= frame.size.height - content_layout_rect.size.height;
2194 }
2195 frame
2196 }
2197}
2198
2199extern "C" fn insert_text(this: &Object, _: Sel, text: id, replacement_range: NSRange) {
2200 unsafe {
2201 let is_attributed_string: BOOL =
2202 msg_send![text, isKindOfClass: [class!(NSAttributedString)]];
2203 let text: id = if is_attributed_string == YES {
2204 msg_send![text, string]
2205 } else {
2206 text
2207 };
2208
2209 let text = text.to_str();
2210 let replacement_range = replacement_range.to_range();
2211 with_input_handler(this, |input_handler| {
2212 input_handler.replace_text_in_range(replacement_range, text)
2213 });
2214 }
2215}
2216
2217extern "C" fn set_marked_text(
2218 this: &Object,
2219 _: Sel,
2220 text: id,
2221 selected_range: NSRange,
2222 replacement_range: NSRange,
2223) {
2224 unsafe {
2225 let is_attributed_string: BOOL =
2226 msg_send![text, isKindOfClass: [class!(NSAttributedString)]];
2227 let text: id = if is_attributed_string == YES {
2228 msg_send![text, string]
2229 } else {
2230 text
2231 };
2232 let selected_range = selected_range.to_range();
2233 let replacement_range = replacement_range.to_range();
2234 let text = text.to_str();
2235 with_input_handler(this, |input_handler| {
2236 input_handler.replace_and_mark_text_in_range(replacement_range, text, selected_range)
2237 });
2238 }
2239}
2240extern "C" fn unmark_text(this: &Object, _: Sel) {
2241 with_input_handler(this, |input_handler| input_handler.unmark_text());
2242}
2243
2244extern "C" fn attributed_substring_for_proposed_range(
2245 this: &Object,
2246 _: Sel,
2247 range: NSRange,
2248 actual_range: *mut c_void,
2249) -> id {
2250 with_input_handler(this, |input_handler| {
2251 let range = range.to_range()?;
2252 if range.is_empty() {
2253 return None;
2254 }
2255 let mut adjusted: Option<Range<usize>> = None;
2256
2257 let selected_text = input_handler.text_for_range(range.clone(), &mut adjusted)?;
2258 if let Some(adjusted) = adjusted
2259 && adjusted != range
2260 {
2261 unsafe { (actual_range as *mut NSRange).write(NSRange::from(adjusted)) };
2262 }
2263 unsafe {
2264 let string: id = msg_send![class!(NSAttributedString), alloc];
2265 let string: id = msg_send![string, initWithString: ns_string(&selected_text)];
2266 Some(string)
2267 }
2268 })
2269 .flatten()
2270 .unwrap_or(nil)
2271}
2272
2273// We ignore which selector it asks us to do because the user may have
2274// bound the shortcut to something else.
2275extern "C" fn do_command_by_selector(this: &Object, _: Sel, _: Sel) {
2276 let state = unsafe { get_window_state(this) };
2277 let mut lock = state.as_ref().lock();
2278 let keystroke = lock.keystroke_for_do_command.take();
2279 let mut event_callback = lock.event_callback.take();
2280 drop(lock);
2281
2282 if let Some((keystroke, mut callback)) = keystroke.zip(event_callback.as_mut()) {
2283 let handled = (callback)(PlatformInput::KeyDown(KeyDownEvent {
2284 keystroke,
2285 is_held: false,
2286 }));
2287 state.as_ref().lock().do_command_handled = Some(!handled.propagate);
2288 }
2289
2290 state.as_ref().lock().event_callback = event_callback;
2291}
2292
2293extern "C" fn view_did_change_effective_appearance(this: &Object, _: Sel) {
2294 unsafe {
2295 let state = get_window_state(this);
2296 let mut lock = state.as_ref().lock();
2297 if let Some(mut callback) = lock.appearance_changed_callback.take() {
2298 drop(lock);
2299 callback();
2300 state.lock().appearance_changed_callback = Some(callback);
2301 }
2302 }
2303}
2304
2305extern "C" fn accepts_first_mouse(this: &Object, _: Sel, _: id) -> BOOL {
2306 let window_state = unsafe { get_window_state(this) };
2307 let mut lock = window_state.as_ref().lock();
2308 lock.first_mouse = true;
2309 YES
2310}
2311
2312extern "C" fn character_index_for_point(this: &Object, _: Sel, position: NSPoint) -> u64 {
2313 let position = screen_point_to_gpui_point(this, position);
2314 with_input_handler(this, |input_handler| {
2315 input_handler.character_index_for_point(position)
2316 })
2317 .flatten()
2318 .map(|index| index as u64)
2319 .unwrap_or(NSNotFound as u64)
2320}
2321
2322fn screen_point_to_gpui_point(this: &Object, position: NSPoint) -> Point<Pixels> {
2323 let frame = get_frame(this);
2324 let window_x = position.x - frame.origin.x;
2325 let window_y = frame.size.height - (position.y - frame.origin.y);
2326
2327 point(px(window_x as f32), px(window_y as f32))
2328}
2329
2330extern "C" fn dragging_entered(this: &Object, _: Sel, dragging_info: id) -> NSDragOperation {
2331 let window_state = unsafe { get_window_state(this) };
2332 let position = drag_event_position(&window_state, dragging_info);
2333 let paths = external_paths_from_event(dragging_info);
2334 if let Some(event) =
2335 paths.map(|paths| PlatformInput::FileDrop(FileDropEvent::Entered { position, paths }))
2336 && send_new_event(&window_state, event)
2337 {
2338 window_state.lock().external_files_dragged = true;
2339 return NSDragOperationCopy;
2340 }
2341 NSDragOperationNone
2342}
2343
2344extern "C" fn dragging_updated(this: &Object, _: Sel, dragging_info: id) -> NSDragOperation {
2345 let window_state = unsafe { get_window_state(this) };
2346 let position = drag_event_position(&window_state, dragging_info);
2347 if send_new_event(
2348 &window_state,
2349 PlatformInput::FileDrop(FileDropEvent::Pending { position }),
2350 ) {
2351 NSDragOperationCopy
2352 } else {
2353 NSDragOperationNone
2354 }
2355}
2356
2357extern "C" fn dragging_exited(this: &Object, _: Sel, _: id) {
2358 let window_state = unsafe { get_window_state(this) };
2359 send_new_event(
2360 &window_state,
2361 PlatformInput::FileDrop(FileDropEvent::Exited),
2362 );
2363 window_state.lock().external_files_dragged = false;
2364}
2365
2366extern "C" fn perform_drag_operation(this: &Object, _: Sel, dragging_info: id) -> BOOL {
2367 let window_state = unsafe { get_window_state(this) };
2368 let position = drag_event_position(&window_state, dragging_info);
2369 send_new_event(
2370 &window_state,
2371 PlatformInput::FileDrop(FileDropEvent::Submit { position }),
2372 )
2373 .to_objc()
2374}
2375
2376fn external_paths_from_event(dragging_info: *mut Object) -> Option<ExternalPaths> {
2377 let mut paths = SmallVec::new();
2378 let pasteboard: id = unsafe { msg_send![dragging_info, draggingPasteboard] };
2379 let filenames = unsafe { NSPasteboard::propertyListForType(pasteboard, NSFilenamesPboardType) };
2380 if filenames == nil {
2381 return None;
2382 }
2383 for file in unsafe { filenames.iter() } {
2384 let path = unsafe {
2385 let f = NSString::UTF8String(file);
2386 CStr::from_ptr(f).to_string_lossy().into_owned()
2387 };
2388 paths.push(PathBuf::from(path))
2389 }
2390 Some(ExternalPaths(paths))
2391}
2392
2393extern "C" fn conclude_drag_operation(this: &Object, _: Sel, _: id) {
2394 let window_state = unsafe { get_window_state(this) };
2395 send_new_event(
2396 &window_state,
2397 PlatformInput::FileDrop(FileDropEvent::Exited),
2398 );
2399}
2400
2401async fn synthetic_drag(
2402 window_state: Weak<Mutex<MacWindowState>>,
2403 drag_id: usize,
2404 event: MouseMoveEvent,
2405) {
2406 loop {
2407 Timer::after(Duration::from_millis(16)).await;
2408 if let Some(window_state) = window_state.upgrade() {
2409 let mut lock = window_state.lock();
2410 if lock.synthetic_drag_counter == drag_id {
2411 if let Some(mut callback) = lock.event_callback.take() {
2412 drop(lock);
2413 callback(PlatformInput::MouseMove(event.clone()));
2414 window_state.lock().event_callback = Some(callback);
2415 }
2416 } else {
2417 break;
2418 }
2419 }
2420 }
2421}
2422
2423fn send_new_event(window_state_lock: &Mutex<MacWindowState>, e: PlatformInput) -> bool {
2424 let window_state = window_state_lock.lock().event_callback.take();
2425 if let Some(mut callback) = window_state {
2426 callback(e);
2427 window_state_lock.lock().event_callback = Some(callback);
2428 true
2429 } else {
2430 false
2431 }
2432}
2433
2434fn drag_event_position(window_state: &Mutex<MacWindowState>, dragging_info: id) -> Point<Pixels> {
2435 let drag_location: NSPoint = unsafe { msg_send![dragging_info, draggingLocation] };
2436 convert_mouse_position(drag_location, window_state.lock().content_size().height)
2437}
2438
2439fn with_input_handler<F, R>(window: &Object, f: F) -> Option<R>
2440where
2441 F: FnOnce(&mut PlatformInputHandler) -> R,
2442{
2443 let window_state = unsafe { get_window_state(window) };
2444 let mut lock = window_state.as_ref().lock();
2445 if let Some(mut input_handler) = lock.input_handler.take() {
2446 drop(lock);
2447 let result = f(&mut input_handler);
2448 window_state.lock().input_handler = Some(input_handler);
2449 Some(result)
2450 } else {
2451 None
2452 }
2453}
2454
2455unsafe fn display_id_for_screen(screen: id) -> CGDirectDisplayID {
2456 unsafe {
2457 let device_description = NSScreen::deviceDescription(screen);
2458 let screen_number_key: id = NSString::alloc(nil).init_str("NSScreenNumber");
2459 let screen_number = device_description.objectForKey_(screen_number_key);
2460 let screen_number: NSUInteger = msg_send![screen_number, unsignedIntegerValue];
2461 screen_number as CGDirectDisplayID
2462 }
2463}
2464
2465extern "C" fn blurred_view_init_with_frame(this: &Object, _: Sel, frame: NSRect) -> id {
2466 unsafe {
2467 let view = msg_send![super(this, class!(NSVisualEffectView)), initWithFrame: frame];
2468 // Use a colorless semantic material. The default value `AppearanceBased`, though not
2469 // manually set, is deprecated.
2470 NSVisualEffectView::setMaterial_(view, NSVisualEffectMaterial::Selection);
2471 NSVisualEffectView::setState_(view, NSVisualEffectState::Active);
2472 view
2473 }
2474}
2475
2476extern "C" fn blurred_view_update_layer(this: &Object, _: Sel) {
2477 unsafe {
2478 let _: () = msg_send![super(this, class!(NSVisualEffectView)), updateLayer];
2479 let layer: id = msg_send![this, layer];
2480 if !layer.is_null() {
2481 remove_layer_background(layer);
2482 }
2483 }
2484}
2485
2486unsafe fn remove_layer_background(layer: id) {
2487 unsafe {
2488 let _: () = msg_send![layer, setBackgroundColor:nil];
2489
2490 let class_name: id = msg_send![layer, className];
2491 if class_name.isEqualToString("CAChameleonLayer") {
2492 // Remove the desktop tinting effect.
2493 let _: () = msg_send![layer, setHidden: YES];
2494 return;
2495 }
2496
2497 let filters: id = msg_send![layer, filters];
2498 if !filters.is_null() {
2499 // Remove the increased saturation.
2500 // The effect of a `CAFilter` or `CIFilter` is determined by its name, and the
2501 // `description` reflects its name and some parameters. Currently `NSVisualEffectView`
2502 // uses a `CAFilter` named "colorSaturate". If one day they switch to `CIFilter`, the
2503 // `description` will still contain "Saturat" ("... inputSaturation = ...").
2504 let test_string: id = NSString::alloc(nil).init_str("Saturat").autorelease();
2505 let count = NSArray::count(filters);
2506 for i in 0..count {
2507 let description: id = msg_send![filters.objectAtIndex(i), description];
2508 let hit: BOOL = msg_send![description, containsString: test_string];
2509 if hit == NO {
2510 continue;
2511 }
2512
2513 let all_indices = NSRange {
2514 location: 0,
2515 length: count,
2516 };
2517 let indices: id = msg_send![class!(NSMutableIndexSet), indexSet];
2518 let _: () = msg_send![indices, addIndexesInRange: all_indices];
2519 let _: () = msg_send![indices, removeIndex:i];
2520 let filtered: id = msg_send![filters, objectsAtIndexes: indices];
2521 let _: () = msg_send![layer, setFilters: filtered];
2522 break;
2523 }
2524 }
2525
2526 let sublayers: id = msg_send![layer, sublayers];
2527 if !sublayers.is_null() {
2528 let count = NSArray::count(sublayers);
2529 for i in 0..count {
2530 let sublayer = sublayers.objectAtIndex(i);
2531 remove_layer_background(sublayer);
2532 }
2533 }
2534 }
2535}
2536
2537extern "C" fn add_titlebar_accessory_view_controller(this: &Object, _: Sel, view_controller: id) {
2538 unsafe {
2539 let _: () = msg_send![super(this, class!(NSWindow)), addTitlebarAccessoryViewController: view_controller];
2540
2541 // Hide the native tab bar and set its height to 0, since we render our own.
2542 let accessory_view: id = msg_send![view_controller, view];
2543 let _: () = msg_send![accessory_view, setHidden: YES];
2544 let mut frame: NSRect = msg_send![accessory_view, frame];
2545 frame.size.height = 0.0;
2546 let _: () = msg_send![accessory_view, setFrame: frame];
2547 }
2548}
2549
2550extern "C" fn move_tab_to_new_window(this: &Object, _: Sel, _: id) {
2551 unsafe {
2552 let _: () = msg_send![super(this, class!(NSWindow)), moveTabToNewWindow:nil];
2553
2554 let window_state = get_window_state(this);
2555 let mut lock = window_state.as_ref().lock();
2556 if let Some(mut callback) = lock.move_tab_to_new_window_callback.take() {
2557 drop(lock);
2558 callback();
2559 window_state.lock().move_tab_to_new_window_callback = Some(callback);
2560 }
2561 }
2562}
2563
2564extern "C" fn merge_all_windows(this: &Object, _: Sel, _: id) {
2565 unsafe {
2566 let _: () = msg_send![super(this, class!(NSWindow)), mergeAllWindows:nil];
2567
2568 let window_state = get_window_state(this);
2569 let mut lock = window_state.as_ref().lock();
2570 if let Some(mut callback) = lock.merge_all_windows_callback.take() {
2571 drop(lock);
2572 callback();
2573 window_state.lock().merge_all_windows_callback = Some(callback);
2574 }
2575 }
2576}
2577
2578extern "C" fn select_next_tab(this: &Object, _sel: Sel, _id: id) {
2579 let window_state = unsafe { get_window_state(this) };
2580 let mut lock = window_state.as_ref().lock();
2581 if let Some(mut callback) = lock.select_next_tab_callback.take() {
2582 drop(lock);
2583 callback();
2584 window_state.lock().select_next_tab_callback = Some(callback);
2585 }
2586}
2587
2588extern "C" fn select_previous_tab(this: &Object, _sel: Sel, _id: id) {
2589 let window_state = unsafe { get_window_state(this) };
2590 let mut lock = window_state.as_ref().lock();
2591 if let Some(mut callback) = lock.select_previous_tab_callback.take() {
2592 drop(lock);
2593 callback();
2594 window_state.lock().select_previous_tab_callback = Some(callback);
2595 }
2596}
2597
2598extern "C" fn toggle_tab_bar(this: &Object, _sel: Sel, _id: id) {
2599 unsafe {
2600 let _: () = msg_send![super(this, class!(NSWindow)), toggleTabBar:nil];
2601
2602 let window_state = get_window_state(this);
2603 let mut lock = window_state.as_ref().lock();
2604 lock.move_traffic_light();
2605
2606 if let Some(mut callback) = lock.toggle_tab_bar_callback.take() {
2607 drop(lock);
2608 callback();
2609 window_state.lock().toggle_tab_bar_callback = Some(callback);
2610 }
2611 }
2612}