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