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