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