diff --git a/src/changelog/unreleased.md b/src/changelog/unreleased.md index c839df5f3..5f15e1b6a 100644 --- a/src/changelog/unreleased.md +++ b/src/changelog/unreleased.md @@ -65,3 +65,4 @@ changelog entry. - 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, 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. diff --git a/src/platform_impl/macos/monitor.rs b/src/platform_impl/macos/monitor.rs index 7d17ca2b7..44e6316b6 100644 --- a/src/platform_impl/macos/monitor.rs +++ b/src/platform_impl/macos/monitor.rs @@ -291,17 +291,24 @@ impl MonitorHandle { unsafe { let modes = { let array = ffi::CGDisplayCopyAllDisplayModes(self.display_id(), std::ptr::null()); - assert!(!array.is_null(), "failed to get list of display modes"); - let array_count = CFArrayGetCount(array); - let modes: Vec<_> = (0..array_count) - .map(move |i| { - let mode = CFArrayGetValueAtIndex(array, i) as *mut _; - ffi::CGDisplayModeRetain(mode); - mode - }) - .collect(); - CFRelease(array as *const _); - modes + if array.is_null() { + // Occasionally, certain CalDigit Thunderbolt Hubs report a spurious monitor + // during sleep/wake/cycling monitors. It tends to have null + // or 1 video mode only. See . + warn!(monitor = ?self, "failed to get a list of display modes"); + Vec::new() + } else { + let array_count = CFArrayGetCount(array); + let modes: Vec<_> = (0..array_count) + .map(move |i| { + let mode = CFArrayGetValueAtIndex(array, i) as *mut _; + ffi::CGDisplayModeRetain(mode); + mode + }) + .collect(); + CFRelease(array as *const _); + modes + } }; modes.into_iter().map(move |mode| { @@ -346,9 +353,14 @@ impl MonitorHandle { let uuid = self.uuid(); NSScreen::screens(mtm).into_iter().find(|screen| { let other_native_id = get_display_id(screen); - // Display ID just fetched from live NSScreen, should be fine to unwrap. - let other = MonitorHandle::new(other_native_id).expect("invalid display ID"); - uuid == other.uuid() + if let Some(other) = MonitorHandle::new(other_native_id) { + 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 + } }) } } diff --git a/src/platform_impl/macos/window_delegate.rs b/src/platform_impl/macos/window_delegate.rs index abc140230..e0dc693c5 100644 --- a/src/platform_impl/macos/window_delegate.rs +++ b/src/platform_impl/macos/window_delegate.rs @@ -1592,8 +1592,14 @@ impl WindowDelegate { // Allow directly accessing the current monitor internally without unwrapping. pub(crate) fn current_monitor_inner(&self) -> Option { let display_id = get_display_id(&*self.window().screen()?); - // Display ID just fetched from live NSScreen, should be fine to unwrap. - Some(MonitorHandle::new(display_id).expect("invalid display ID")) + if let Some(monitor) = MonitorHandle::new(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]