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