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