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