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