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 let native_window = lock.native_window;
2117 drop(lock);
2118 unsafe {
2119 let _: () = msg_send![native_window, resignKeyWindow];
2120 }
2121 return;
2122 }
2123
2124 let executor = lock.foreground_executor.clone();
2125 drop(lock);
2126
2127 // When a window becomes active, trigger an immediate synchronous frame request to prevent
2128 // tab flicker when switching between windows in native tabs mode.
2129 //
2130 // This is only done on subsequent activations (not the first) to ensure the initial focus
2131 // path is properly established. Without this guard, the focus state would remain unset until
2132 // the first mouse click, causing keybindings to be non-functional.
2133 if selector == sel!(windowDidBecomeKey:) && is_active {
2134 let window_state = unsafe { get_window_state(this) };
2135 let mut lock = window_state.lock();
2136
2137 if lock.activated_least_once {
2138 if let Some(mut callback) = lock.request_frame_callback.take() {
2139 lock.renderer.set_presents_with_transaction(true);
2140 lock.stop_display_link();
2141 drop(lock);
2142 callback(Default::default());
2143
2144 let mut lock = window_state.lock();
2145 lock.request_frame_callback = Some(callback);
2146 lock.renderer.set_presents_with_transaction(false);
2147 lock.start_display_link();
2148 }
2149 } else {
2150 lock.activated_least_once = true;
2151 }
2152 }
2153
2154 executor
2155 .spawn(async move {
2156 let mut lock = window_state.as_ref().lock();
2157 if is_active {
2158 lock.move_traffic_light();
2159 }
2160
2161 if let Some(mut callback) = lock.activate_callback.take() {
2162 drop(lock);
2163 callback(is_active);
2164 window_state.lock().activate_callback = Some(callback);
2165 };
2166 })
2167 .detach();
2168}
2169
2170extern "C" fn window_should_close(this: &Object, _: Sel, _: id) -> BOOL {
2171 let window_state = unsafe { get_window_state(this) };
2172 let mut lock = window_state.as_ref().lock();
2173 if let Some(mut callback) = lock.should_close_callback.take() {
2174 drop(lock);
2175 let should_close = callback();
2176 window_state.lock().should_close_callback = Some(callback);
2177 should_close as BOOL
2178 } else {
2179 YES
2180 }
2181}
2182
2183extern "C" fn close_window(this: &Object, _: Sel) {
2184 unsafe {
2185 let close_callback = {
2186 let window_state = get_window_state(this);
2187 let mut lock = window_state.as_ref().lock();
2188 lock.close_callback.take()
2189 };
2190
2191 if let Some(callback) = close_callback {
2192 callback();
2193 }
2194
2195 let _: () = msg_send![super(this, class!(NSWindow)), close];
2196 }
2197}
2198
2199extern "C" fn make_backing_layer(this: &Object, _: Sel) -> id {
2200 let window_state = unsafe { get_window_state(this) };
2201 let window_state = window_state.as_ref().lock();
2202 window_state.renderer.layer_ptr() as id
2203}
2204
2205extern "C" fn view_did_change_backing_properties(this: &Object, _: Sel) {
2206 let window_state = unsafe { get_window_state(this) };
2207 update_window_scale_factor(&window_state);
2208}
2209
2210extern "C" fn set_frame_size(this: &Object, _: Sel, size: NSSize) {
2211 fn convert(value: NSSize) -> Size<Pixels> {
2212 Size {
2213 width: px(value.width as f32),
2214 height: px(value.height as f32),
2215 }
2216 }
2217
2218 let window_state = unsafe { get_window_state(this) };
2219 let mut lock = window_state.as_ref().lock();
2220
2221 let new_size = convert(size);
2222 let old_size = unsafe {
2223 let old_frame: NSRect = msg_send![this, frame];
2224 convert(old_frame.size)
2225 };
2226
2227 if old_size == new_size {
2228 return;
2229 }
2230
2231 unsafe {
2232 let _: () = msg_send![super(this, class!(NSView)), setFrameSize: size];
2233 }
2234
2235 let scale_factor = lock.scale_factor();
2236 let drawable_size = new_size.to_device_pixels(scale_factor);
2237 lock.renderer.update_drawable_size(drawable_size);
2238
2239 if let Some(mut callback) = lock.resize_callback.take() {
2240 let content_size = lock.content_size();
2241 let scale_factor = lock.scale_factor();
2242 drop(lock);
2243 callback(content_size, scale_factor);
2244 window_state.lock().resize_callback = Some(callback);
2245 };
2246}
2247
2248extern "C" fn display_layer(this: &Object, _: Sel, _: id) {
2249 let window_state = unsafe { get_window_state(this) };
2250 let mut lock = window_state.lock();
2251 if let Some(mut callback) = lock.request_frame_callback.take() {
2252 lock.renderer.set_presents_with_transaction(true);
2253 lock.stop_display_link();
2254 drop(lock);
2255 callback(Default::default());
2256
2257 let mut lock = window_state.lock();
2258 lock.request_frame_callback = Some(callback);
2259 lock.renderer.set_presents_with_transaction(false);
2260 lock.start_display_link();
2261 }
2262}
2263
2264extern "C" fn step(view: *mut c_void) {
2265 let view = view as id;
2266 let window_state = unsafe { get_window_state(&*view) };
2267 let mut lock = window_state.lock();
2268
2269 if let Some(mut callback) = lock.request_frame_callback.take() {
2270 drop(lock);
2271 callback(Default::default());
2272 window_state.lock().request_frame_callback = Some(callback);
2273 }
2274}
2275
2276extern "C" fn valid_attributes_for_marked_text(_: &Object, _: Sel) -> id {
2277 unsafe { msg_send![class!(NSArray), array] }
2278}
2279
2280extern "C" fn has_marked_text(this: &Object, _: Sel) -> BOOL {
2281 let has_marked_text_result =
2282 with_input_handler(this, |input_handler| input_handler.marked_text_range()).flatten();
2283
2284 has_marked_text_result.is_some() as BOOL
2285}
2286
2287extern "C" fn marked_range(this: &Object, _: Sel) -> NSRange {
2288 let marked_range_result =
2289 with_input_handler(this, |input_handler| input_handler.marked_text_range()).flatten();
2290
2291 marked_range_result.map_or(NSRange::invalid(), |range| range.into())
2292}
2293
2294extern "C" fn selected_range(this: &Object, _: Sel) -> NSRange {
2295 let selected_range_result = with_input_handler(this, |input_handler| {
2296 input_handler.selected_text_range(false)
2297 })
2298 .flatten();
2299
2300 selected_range_result.map_or(NSRange::invalid(), |selection| selection.range.into())
2301}
2302
2303extern "C" fn first_rect_for_character_range(
2304 this: &Object,
2305 _: Sel,
2306 range: NSRange,
2307 _: id,
2308) -> NSRect {
2309 let frame = get_frame(this);
2310 with_input_handler(this, |input_handler| {
2311 input_handler.bounds_for_range(range.to_range()?)
2312 })
2313 .flatten()
2314 .map_or(
2315 NSRect::new(NSPoint::new(0., 0.), NSSize::new(0., 0.)),
2316 |bounds| {
2317 NSRect::new(
2318 NSPoint::new(
2319 frame.origin.x + bounds.origin.x.as_f32() as f64,
2320 frame.origin.y + frame.size.height
2321 - bounds.origin.y.as_f32() as f64
2322 - bounds.size.height.as_f32() as f64,
2323 ),
2324 NSSize::new(
2325 bounds.size.width.as_f32() as f64,
2326 bounds.size.height.as_f32() as f64,
2327 ),
2328 )
2329 },
2330 )
2331}
2332
2333fn get_frame(this: &Object) -> NSRect {
2334 unsafe {
2335 let state = get_window_state(this);
2336 let lock = state.lock();
2337 let mut frame = NSWindow::frame(lock.native_window);
2338 let content_layout_rect: CGRect = msg_send![lock.native_window, contentLayoutRect];
2339 let style_mask: NSWindowStyleMask = msg_send![lock.native_window, styleMask];
2340 if !style_mask.contains(NSWindowStyleMask::NSFullSizeContentViewWindowMask) {
2341 frame.origin.y -= frame.size.height - content_layout_rect.size.height;
2342 }
2343 frame
2344 }
2345}
2346
2347extern "C" fn insert_text(this: &Object, _: Sel, text: id, replacement_range: NSRange) {
2348 unsafe {
2349 let is_attributed_string: BOOL =
2350 msg_send![text, isKindOfClass: [class!(NSAttributedString)]];
2351 let text: id = if is_attributed_string == YES {
2352 msg_send![text, string]
2353 } else {
2354 text
2355 };
2356
2357 let text = text.to_str();
2358 let replacement_range = replacement_range.to_range();
2359 with_input_handler(this, |input_handler| {
2360 input_handler.replace_text_in_range(replacement_range, text)
2361 });
2362 }
2363}
2364
2365extern "C" fn set_marked_text(
2366 this: &Object,
2367 _: Sel,
2368 text: id,
2369 selected_range: NSRange,
2370 replacement_range: NSRange,
2371) {
2372 unsafe {
2373 let is_attributed_string: BOOL =
2374 msg_send![text, isKindOfClass: [class!(NSAttributedString)]];
2375 let text: id = if is_attributed_string == YES {
2376 msg_send![text, string]
2377 } else {
2378 text
2379 };
2380 let selected_range = selected_range.to_range();
2381 let replacement_range = replacement_range.to_range();
2382 let text = text.to_str();
2383 with_input_handler(this, |input_handler| {
2384 input_handler.replace_and_mark_text_in_range(replacement_range, text, selected_range)
2385 });
2386 }
2387}
2388extern "C" fn unmark_text(this: &Object, _: Sel) {
2389 with_input_handler(this, |input_handler| input_handler.unmark_text());
2390}
2391
2392extern "C" fn attributed_substring_for_proposed_range(
2393 this: &Object,
2394 _: Sel,
2395 range: NSRange,
2396 actual_range: *mut c_void,
2397) -> id {
2398 with_input_handler(this, |input_handler| {
2399 let range = range.to_range()?;
2400 if range.is_empty() {
2401 return None;
2402 }
2403 let mut adjusted: Option<Range<usize>> = None;
2404
2405 let selected_text = input_handler.text_for_range(range.clone(), &mut adjusted)?;
2406 if let Some(adjusted) = adjusted
2407 && adjusted != range
2408 {
2409 unsafe { (actual_range as *mut NSRange).write(NSRange::from(adjusted)) };
2410 }
2411 unsafe {
2412 let string: id = msg_send![class!(NSAttributedString), alloc];
2413 let string: id = msg_send![string, initWithString: ns_string(&selected_text)];
2414 Some(string)
2415 }
2416 })
2417 .flatten()
2418 .unwrap_or(nil)
2419}
2420
2421// We ignore which selector it asks us to do because the user may have
2422// bound the shortcut to something else.
2423extern "C" fn do_command_by_selector(this: &Object, _: Sel, _: Sel) {
2424 let state = unsafe { get_window_state(this) };
2425 let mut lock = state.as_ref().lock();
2426 let keystroke = lock.keystroke_for_do_command.take();
2427 let mut event_callback = lock.event_callback.take();
2428 drop(lock);
2429
2430 if let Some((keystroke, callback)) = keystroke.zip(event_callback.as_mut()) {
2431 let handled = (callback)(PlatformInput::KeyDown(KeyDownEvent {
2432 keystroke,
2433 is_held: false,
2434 prefer_character_input: false,
2435 }));
2436 state.as_ref().lock().do_command_handled = Some(!handled.propagate);
2437 }
2438
2439 state.as_ref().lock().event_callback = event_callback;
2440}
2441
2442extern "C" fn view_did_change_effective_appearance(this: &Object, _: Sel) {
2443 unsafe {
2444 let state = get_window_state(this);
2445 let mut lock = state.as_ref().lock();
2446 if let Some(mut callback) = lock.appearance_changed_callback.take() {
2447 drop(lock);
2448 callback();
2449 state.lock().appearance_changed_callback = Some(callback);
2450 }
2451 }
2452}
2453
2454extern "C" fn accepts_first_mouse(this: &Object, _: Sel, _: id) -> BOOL {
2455 let window_state = unsafe { get_window_state(this) };
2456 let mut lock = window_state.as_ref().lock();
2457 lock.first_mouse = true;
2458 YES
2459}
2460
2461extern "C" fn character_index_for_point(this: &Object, _: Sel, position: NSPoint) -> u64 {
2462 let position = screen_point_to_gpui_point(this, position);
2463 with_input_handler(this, |input_handler| {
2464 input_handler.character_index_for_point(position)
2465 })
2466 .flatten()
2467 .map(|index| index as u64)
2468 .unwrap_or(NSNotFound as u64)
2469}
2470
2471fn screen_point_to_gpui_point(this: &Object, position: NSPoint) -> Point<Pixels> {
2472 let frame = get_frame(this);
2473 let window_x = position.x - frame.origin.x;
2474 let window_y = frame.size.height - (position.y - frame.origin.y);
2475
2476 point(px(window_x as f32), px(window_y as f32))
2477}
2478
2479extern "C" fn dragging_entered(this: &Object, _: Sel, dragging_info: id) -> NSDragOperation {
2480 let window_state = unsafe { get_window_state(this) };
2481 let position = drag_event_position(&window_state, dragging_info);
2482 let paths = external_paths_from_event(dragging_info);
2483 if let Some(event) = paths.map(|paths| FileDropEvent::Entered { position, paths })
2484 && send_file_drop_event(window_state, event)
2485 {
2486 return NSDragOperationCopy;
2487 }
2488 NSDragOperationNone
2489}
2490
2491extern "C" fn dragging_updated(this: &Object, _: Sel, dragging_info: id) -> NSDragOperation {
2492 let window_state = unsafe { get_window_state(this) };
2493 let position = drag_event_position(&window_state, dragging_info);
2494 if send_file_drop_event(window_state, FileDropEvent::Pending { position }) {
2495 NSDragOperationCopy
2496 } else {
2497 NSDragOperationNone
2498 }
2499}
2500
2501extern "C" fn dragging_exited(this: &Object, _: Sel, _: id) {
2502 let window_state = unsafe { get_window_state(this) };
2503 send_file_drop_event(window_state, FileDropEvent::Exited);
2504}
2505
2506extern "C" fn perform_drag_operation(this: &Object, _: Sel, dragging_info: id) -> BOOL {
2507 let window_state = unsafe { get_window_state(this) };
2508 let position = drag_event_position(&window_state, dragging_info);
2509 send_file_drop_event(window_state, FileDropEvent::Submit { position }).to_objc()
2510}
2511
2512fn external_paths_from_event(dragging_info: *mut Object) -> Option<ExternalPaths> {
2513 let mut paths = SmallVec::new();
2514 let pasteboard: id = unsafe { msg_send![dragging_info, draggingPasteboard] };
2515 let filenames = unsafe { NSPasteboard::propertyListForType(pasteboard, NSFilenamesPboardType) };
2516 if filenames == nil {
2517 return None;
2518 }
2519 for file in unsafe { filenames.iter() } {
2520 let path = unsafe {
2521 let f = NSString::UTF8String(file);
2522 CStr::from_ptr(f).to_string_lossy().into_owned()
2523 };
2524 paths.push(PathBuf::from(path))
2525 }
2526 Some(ExternalPaths(paths))
2527}
2528
2529extern "C" fn conclude_drag_operation(this: &Object, _: Sel, _: id) {
2530 let window_state = unsafe { get_window_state(this) };
2531 send_file_drop_event(window_state, FileDropEvent::Exited);
2532}
2533
2534async fn synthetic_drag(
2535 window_state: Weak<Mutex<MacWindowState>>,
2536 drag_id: usize,
2537 event: MouseMoveEvent,
2538 executor: BackgroundExecutor,
2539) {
2540 loop {
2541 executor.timer(Duration::from_millis(16)).await;
2542 if let Some(window_state) = window_state.upgrade() {
2543 let mut lock = window_state.lock();
2544 if lock.synthetic_drag_counter == drag_id {
2545 if let Some(mut callback) = lock.event_callback.take() {
2546 drop(lock);
2547 callback(PlatformInput::MouseMove(event.clone()));
2548 window_state.lock().event_callback = Some(callback);
2549 }
2550 } else {
2551 break;
2552 }
2553 }
2554 }
2555}
2556
2557/// Sends the specified FileDropEvent using `PlatformInput::FileDrop` to the window
2558/// state and updates the window state according to the event passed.
2559fn send_file_drop_event(
2560 window_state: Arc<Mutex<MacWindowState>>,
2561 file_drop_event: FileDropEvent,
2562) -> bool {
2563 let external_files_dragged = match file_drop_event {
2564 FileDropEvent::Entered { .. } => Some(true),
2565 FileDropEvent::Exited => Some(false),
2566 _ => None,
2567 };
2568
2569 let mut lock = window_state.lock();
2570 if let Some(mut callback) = lock.event_callback.take() {
2571 drop(lock);
2572 callback(PlatformInput::FileDrop(file_drop_event));
2573 let mut lock = window_state.lock();
2574 lock.event_callback = Some(callback);
2575 if let Some(external_files_dragged) = external_files_dragged {
2576 lock.external_files_dragged = external_files_dragged;
2577 }
2578 true
2579 } else {
2580 false
2581 }
2582}
2583
2584fn drag_event_position(window_state: &Mutex<MacWindowState>, dragging_info: id) -> Point<Pixels> {
2585 let drag_location: NSPoint = unsafe { msg_send![dragging_info, draggingLocation] };
2586 convert_mouse_position(drag_location, window_state.lock().content_size().height)
2587}
2588
2589fn with_input_handler<F, R>(window: &Object, f: F) -> Option<R>
2590where
2591 F: FnOnce(&mut PlatformInputHandler) -> R,
2592{
2593 let window_state = unsafe { get_window_state(window) };
2594 let mut lock = window_state.as_ref().lock();
2595 if let Some(mut input_handler) = lock.input_handler.take() {
2596 drop(lock);
2597 let result = f(&mut input_handler);
2598 window_state.lock().input_handler = Some(input_handler);
2599 Some(result)
2600 } else {
2601 None
2602 }
2603}
2604
2605unsafe fn display_id_for_screen(screen: id) -> CGDirectDisplayID {
2606 unsafe {
2607 let device_description = NSScreen::deviceDescription(screen);
2608 let screen_number_key: id = ns_string("NSScreenNumber");
2609 let screen_number = device_description.objectForKey_(screen_number_key);
2610 let screen_number: NSUInteger = msg_send![screen_number, unsignedIntegerValue];
2611 screen_number as CGDirectDisplayID
2612 }
2613}
2614
2615extern "C" fn blurred_view_init_with_frame(this: &Object, _: Sel, frame: NSRect) -> id {
2616 unsafe {
2617 let view = msg_send![super(this, class!(NSVisualEffectView)), initWithFrame: frame];
2618 // Use a colorless semantic material. The default value `AppearanceBased`, though not
2619 // manually set, is deprecated.
2620 NSVisualEffectView::setMaterial_(view, NSVisualEffectMaterial::Selection);
2621 NSVisualEffectView::setState_(view, NSVisualEffectState::Active);
2622 view
2623 }
2624}
2625
2626extern "C" fn blurred_view_update_layer(this: &Object, _: Sel) {
2627 unsafe {
2628 let _: () = msg_send![super(this, class!(NSVisualEffectView)), updateLayer];
2629 let layer: id = msg_send![this, layer];
2630 if !layer.is_null() {
2631 remove_layer_background(layer);
2632 }
2633 }
2634}
2635
2636unsafe fn remove_layer_background(layer: id) {
2637 unsafe {
2638 let _: () = msg_send![layer, setBackgroundColor:nil];
2639
2640 let class_name: id = msg_send![layer, className];
2641 if class_name.isEqualToString("CAChameleonLayer") {
2642 // Remove the desktop tinting effect.
2643 let _: () = msg_send![layer, setHidden: YES];
2644 return;
2645 }
2646
2647 let filters: id = msg_send![layer, filters];
2648 if !filters.is_null() {
2649 // Remove the increased saturation.
2650 // The effect of a `CAFilter` or `CIFilter` is determined by its name, and the
2651 // `description` reflects its name and some parameters. Currently `NSVisualEffectView`
2652 // uses a `CAFilter` named "colorSaturate". If one day they switch to `CIFilter`, the
2653 // `description` will still contain "Saturat" ("... inputSaturation = ...").
2654 let test_string: id = ns_string("Saturat");
2655 let count = NSArray::count(filters);
2656 for i in 0..count {
2657 let description: id = msg_send![filters.objectAtIndex(i), description];
2658 let hit: BOOL = msg_send![description, containsString: test_string];
2659 if hit == NO {
2660 continue;
2661 }
2662
2663 let all_indices = NSRange {
2664 location: 0,
2665 length: count,
2666 };
2667 let indices: id = msg_send![class!(NSMutableIndexSet), indexSet];
2668 let _: () = msg_send![indices, addIndexesInRange: all_indices];
2669 let _: () = msg_send![indices, removeIndex:i];
2670 let filtered: id = msg_send![filters, objectsAtIndexes: indices];
2671 let _: () = msg_send![layer, setFilters: filtered];
2672 break;
2673 }
2674 }
2675
2676 let sublayers: id = msg_send![layer, sublayers];
2677 if !sublayers.is_null() {
2678 let count = NSArray::count(sublayers);
2679 for i in 0..count {
2680 let sublayer = sublayers.objectAtIndex(i);
2681 remove_layer_background(sublayer);
2682 }
2683 }
2684 }
2685}
2686
2687extern "C" fn add_titlebar_accessory_view_controller(this: &Object, _: Sel, view_controller: id) {
2688 unsafe {
2689 let _: () = msg_send![super(this, class!(NSWindow)), addTitlebarAccessoryViewController: view_controller];
2690
2691 // Hide the native tab bar and set its height to 0, since we render our own.
2692 let accessory_view: id = msg_send![view_controller, view];
2693 let _: () = msg_send![accessory_view, setHidden: YES];
2694 let mut frame: NSRect = msg_send![accessory_view, frame];
2695 frame.size.height = 0.0;
2696 let _: () = msg_send![accessory_view, setFrame: frame];
2697 }
2698}
2699
2700extern "C" fn move_tab_to_new_window(this: &Object, _: Sel, _: id) {
2701 unsafe {
2702 let _: () = msg_send![super(this, class!(NSWindow)), moveTabToNewWindow:nil];
2703
2704 let window_state = get_window_state(this);
2705 let mut lock = window_state.as_ref().lock();
2706 if let Some(mut callback) = lock.move_tab_to_new_window_callback.take() {
2707 drop(lock);
2708 callback();
2709 window_state.lock().move_tab_to_new_window_callback = Some(callback);
2710 }
2711 }
2712}
2713
2714extern "C" fn merge_all_windows(this: &Object, _: Sel, _: id) {
2715 unsafe {
2716 let _: () = msg_send![super(this, class!(NSWindow)), mergeAllWindows:nil];
2717
2718 let window_state = get_window_state(this);
2719 let mut lock = window_state.as_ref().lock();
2720 if let Some(mut callback) = lock.merge_all_windows_callback.take() {
2721 drop(lock);
2722 callback();
2723 window_state.lock().merge_all_windows_callback = Some(callback);
2724 }
2725 }
2726}
2727
2728extern "C" fn select_next_tab(this: &Object, _sel: Sel, _id: id) {
2729 let window_state = unsafe { get_window_state(this) };
2730 let mut lock = window_state.as_ref().lock();
2731 if let Some(mut callback) = lock.select_next_tab_callback.take() {
2732 drop(lock);
2733 callback();
2734 window_state.lock().select_next_tab_callback = Some(callback);
2735 }
2736}
2737
2738extern "C" fn select_previous_tab(this: &Object, _sel: Sel, _id: id) {
2739 let window_state = unsafe { get_window_state(this) };
2740 let mut lock = window_state.as_ref().lock();
2741 if let Some(mut callback) = lock.select_previous_tab_callback.take() {
2742 drop(lock);
2743 callback();
2744 window_state.lock().select_previous_tab_callback = Some(callback);
2745 }
2746}
2747
2748extern "C" fn toggle_tab_bar(this: &Object, _sel: Sel, _id: id) {
2749 unsafe {
2750 let _: () = msg_send![super(this, class!(NSWindow)), toggleTabBar:nil];
2751
2752 let window_state = get_window_state(this);
2753 let mut lock = window_state.as_ref().lock();
2754 lock.move_traffic_light();
2755
2756 if let Some(mut callback) = lock.toggle_tab_bar_callback.take() {
2757 drop(lock);
2758 callback();
2759 window_state.lock().toggle_tab_bar_callback = Some(callback);
2760 }
2761 }
2762}