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