macOS: Fix monitors connected via certain Thunderbolt hubs

Instead of panicking, raise a warning and return `None` or similar.

Co-Authored-By: RJ <rj@metabrew.com>
This commit is contained in:
Mads Marquart
2025-04-29 12:41:42 +02:00
committed by Kirill Chibisov
parent 5f1e9f6cc1
commit 80bddda641
3 changed files with 35 additions and 16 deletions

View File

@@ -65,3 +65,4 @@ changelog entry.
- On macOS, fixed `run_app_on_demand` returning without closing open windows. - On macOS, fixed `run_app_on_demand` returning without closing open windows.
- On macOS, fixed `VideoMode::refresh_rate_millihertz` for fractional refresh rates. - On macOS, fixed `VideoMode::refresh_rate_millihertz` for fractional refresh rates.
- On macOS, store monitor handle to avoid panics after going in/out of sleep. - On macOS, store monitor handle to avoid panics after going in/out of sleep.
- On macOS, allow certain invalid monitor handles and return `None` instead of panicking.

View File

@@ -291,17 +291,24 @@ impl MonitorHandle {
unsafe { unsafe {
let modes = { let modes = {
let array = ffi::CGDisplayCopyAllDisplayModes(self.display_id(), std::ptr::null()); let array = ffi::CGDisplayCopyAllDisplayModes(self.display_id(), std::ptr::null());
assert!(!array.is_null(), "failed to get list of display modes"); if array.is_null() {
let array_count = CFArrayGetCount(array); // Occasionally, certain CalDigit Thunderbolt Hubs report a spurious monitor
let modes: Vec<_> = (0..array_count) // during sleep/wake/cycling monitors. It tends to have null
.map(move |i| { // or 1 video mode only. See <https://github.com/bevyengine/bevy/issues/17827>.
let mode = CFArrayGetValueAtIndex(array, i) as *mut _; warn!(monitor = ?self, "failed to get a list of display modes");
ffi::CGDisplayModeRetain(mode); Vec::new()
mode } else {
}) let array_count = CFArrayGetCount(array);
.collect(); let modes: Vec<_> = (0..array_count)
CFRelease(array as *const _); .map(move |i| {
modes let mode = CFArrayGetValueAtIndex(array, i) as *mut _;
ffi::CGDisplayModeRetain(mode);
mode
})
.collect();
CFRelease(array as *const _);
modes
}
}; };
modes.into_iter().map(move |mode| { modes.into_iter().map(move |mode| {
@@ -346,9 +353,14 @@ impl MonitorHandle {
let uuid = self.uuid(); let uuid = self.uuid();
NSScreen::screens(mtm).into_iter().find(|screen| { NSScreen::screens(mtm).into_iter().find(|screen| {
let other_native_id = get_display_id(screen); let other_native_id = get_display_id(screen);
// Display ID just fetched from live NSScreen, should be fine to unwrap. if let Some(other) = MonitorHandle::new(other_native_id) {
let other = MonitorHandle::new(other_native_id).expect("invalid display ID"); uuid == other.uuid()
uuid == other.uuid() } else {
// Display ID was just fetched from live NSScreen, but can still result in `None`
// with certain Thunderbolt docked monitors.
warn!(other_native_id, "comparing against screen with invalid display ID");
false
}
}) })
} }
} }

View File

@@ -1592,8 +1592,14 @@ impl WindowDelegate {
// Allow directly accessing the current monitor internally without unwrapping. // Allow directly accessing the current monitor internally without unwrapping.
pub(crate) fn current_monitor_inner(&self) -> Option<MonitorHandle> { pub(crate) fn current_monitor_inner(&self) -> Option<MonitorHandle> {
let display_id = get_display_id(&*self.window().screen()?); let display_id = get_display_id(&*self.window().screen()?);
// Display ID just fetched from live NSScreen, should be fine to unwrap. if let Some(monitor) = MonitorHandle::new(display_id) {
Some(MonitorHandle::new(display_id).expect("invalid display ID")) Some(monitor)
} else {
// NOTE: Display ID was just fetched from live NSScreen, but can still result in `None`
// with certain Thunderbolt docked monitors.
warn!(display_id, "got screen with invalid display ID");
None
}
} }
#[inline] #[inline]