From cb1937a8e7222edbacd6682e00568a5bed3e8949 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Wed, 21 Oct 2026 15:35:19 +0000 Subject: [PATCH] gpui_macos: Migrate display bindings to objc2 (#65714) Use retained NSScreen objects and objc2 main-thread checks while preserving refresh-rate and display-bounds fallbacks. Release Notes: - [GPUI] Migrated macOS display and display-link bindings to objc2 --- crates/gpui_macos/src/display.rs | 113 +++++++++++--------------- crates/gpui_macos/src/display_link.rs | 16 ++-- 2 files changed, 51 insertions(+), 78 deletions(-) diff --git a/crates/gpui_macos/src/display.rs b/crates/gpui_macos/src/display.rs index dfbd9ec1cd7..25d9d05ce89 100644 --- a/crates/gpui_macos/src/display.rs +++ b/crates/gpui_macos/src/display.rs @@ -1,17 +1,13 @@ -use crate::ns_string; use anyhow::Result; -use cocoa::{ - appkit::NSScreen, - base::{BOOL, NO, YES, id, nil}, - foundation::{NSArray, NSDictionary}, -}; use core_foundation::base::CFRelease; use core_foundation::uuid::{CFUUIDGetUUIDBytes, CFUUIDRef}; use core_graphics::display::{ CGDirectDisplayID, CGDisplay, CGDisplayBounds, CGGetActiveDisplayList, }; use gpui::{Bounds, DisplayId, Pixels, PlatformDisplay, point, px, refresh_interval_from_hz, size}; -use objc::{msg_send, sel, sel_impl}; +use objc2::{MainThreadMarker, msg_send, rc::Retained, sel}; +use objc2_app_kit::NSScreen; +use objc2_foundation::{NSObjectProtocol, ns_string}; use std::time::Duration; use uuid::Uuid; @@ -36,15 +32,15 @@ impl MacDisplay { // The following is what Chromium does too: // // https://chromium.googlesource.com/chromium/src/+/66.0.3359.158/ui/display/mac/screen_mac.mm#56 - unsafe { - let screens = NSScreen::screens(nil); - let screen = cocoa::foundation::NSArray::objectAtIndex(screens, 0); - let device_description = NSScreen::deviceDescription(screen); - let screen_number_key: id = ns_string("NSScreenNumber"); - let screen_number = device_description.objectForKey_(screen_number_key); - let screen_number: CGDirectDisplayID = msg_send![screen_number, unsignedIntegerValue]; - Self(screen_number) - } + let main_thread = MainThreadMarker::new().expect("displays require the main thread"); + let screens = NSScreen::screens(main_thread); + let screen = screens.firstObject().expect("no primary screen"); + let screen_number = screen + .deviceDescription() + .objectForKey(ns_string!("NSScreenNumber")) + .map(|number| unsafe { msg_send![&*number, unsignedIntValue] }) + .unwrap_or_default(); + Self(screen_number) } /// Obtains an iterator over all currently active system displays. @@ -128,51 +124,37 @@ impl PlatformDisplay for MacDisplay { } fn visible_bounds(&self) -> Bounds { - unsafe { - let dominated_screen = self.get_nsscreen(); + let Some(dominated_screen) = self.get_nsscreen() else { + return self.bounds(); + }; - if dominated_screen == nil { - return self.bounds(); - } + let screen_frame = dominated_screen.frame(); + let visible_frame = dominated_screen.visibleFrame(); - let screen_frame = NSScreen::frame(dominated_screen); - let visible_frame = NSScreen::visibleFrame(dominated_screen); + // Convert from bottom-left origin (AppKit) to top-left origin + let origin_y = + screen_frame.size.height - visible_frame.origin.y - visible_frame.size.height + + screen_frame.origin.y; - // Convert from bottom-left origin (AppKit) to top-left origin - let origin_y = - screen_frame.size.height - visible_frame.origin.y - visible_frame.size.height - + screen_frame.origin.y; - - Bounds { - origin: point( - px(visible_frame.origin.x as f32 - screen_frame.origin.x as f32), - px(origin_y as f32), - ), - size: size( - px(visible_frame.size.width as f32), - px(visible_frame.size.height as f32), - ), - } + Bounds { + origin: point( + px(visible_frame.origin.x as f32 - screen_frame.origin.x as f32), + px(origin_y as f32), + ), + size: size( + px(visible_frame.size.width as f32), + px(visible_frame.size.height as f32), + ), } } fn refresh_interval(&self) -> Option { // `maximumFramesPerSecond` is the ProMotion maximum, where the // display mode's rate is 0 on built-in panels. - let screen_hertz = unsafe { - let screen = self.get_nsscreen(); - let supports_maximum: BOOL = if screen == nil { - NO - } else { - msg_send![screen, respondsToSelector: sel!(maximumFramesPerSecond)] - }; - if supports_maximum == YES { - let frames_per_second: isize = msg_send![screen, maximumFramesPerSecond]; - frames_per_second as f64 - } else { - 0.0 - } - }; + let screen_hertz = self + .get_nsscreen() + .filter(|screen| screen.respondsToSelector(sel!(maximumFramesPerSecond))) + .map_or(0.0, |screen| screen.maximumFramesPerSecond() as f64); refresh_interval_from_hz(screen_hertz).or_else(|| { CGDisplay::new(self.0) .display_mode() @@ -183,20 +165,17 @@ impl PlatformDisplay for MacDisplay { impl MacDisplay { /// Find the NSScreen corresponding to this display - unsafe fn get_nsscreen(&self) -> id { - let screens = unsafe { NSScreen::screens(nil) }; - let count = unsafe { NSArray::count(screens) }; - let screen_number_key: id = unsafe { ns_string("NSScreenNumber") }; - - for i in 0..count { - let screen = unsafe { NSArray::objectAtIndex(screens, i) }; - let device_description = unsafe { NSScreen::deviceDescription(screen) }; - let screen_number = unsafe { device_description.objectForKey_(screen_number_key) }; - let screen_id: CGDirectDisplayID = msg_send![screen_number, unsignedIntegerValue]; - if screen_id == self.0 { - return screen; - } - } - nil + fn get_nsscreen(&self) -> Option> { + let main_thread = MainThreadMarker::new().expect("displays require the main thread"); + NSScreen::screens(main_thread).into_iter().find(|screen| { + screen + .deviceDescription() + .objectForKey(ns_string!("NSScreenNumber")) + .is_some_and(|number| { + let screen_id: CGDirectDisplayID = + unsafe { msg_send![&*number, unsignedIntValue] }; + screen_id == self.0 + }) + }) } } diff --git a/crates/gpui_macos/src/display_link.rs b/crates/gpui_macos/src/display_link.rs index 81b1b5eca85..cf26d7eb869 100644 --- a/crates/gpui_macos/src/display_link.rs +++ b/crates/gpui_macos/src/display_link.rs @@ -112,17 +112,11 @@ fn lock_registry() -> MutexGuard<'static, Registry> { } fn debug_assert_main_thread() { - #[cfg(debug_assertions)] - { - use objc::{class, msg_send, sel, sel_impl}; - let is_main_thread: objc::runtime::BOOL = - unsafe { msg_send![class!(NSThread), isMainThread] }; - debug_assert!( - is_main_thread == objc::runtime::YES, - "display link registry mutations must happen on the main thread; \ - the registry's lock ordering and state consistency depend on it" - ); - } + debug_assert!( + objc2::MainThreadMarker::new().is_some(), + "display link registry mutations must happen on the main thread; \ + the registry's lock ordering and state consistency depend on it" + ); } unsafe extern "C" fn display_link_output_callback(