monitor: refactor MonitorHandle to store dyn object

This also alters `VideoMode` to be a regular object and not reference
the `MonitorHandle`, since it's a static data.

Given that `VideoMode` set may change during runtime keeping the
reference as a some sort of validity may not be idea and propagating
errors when changing video mode could be more reliable.
This commit is contained in:
Kirill Chibisov 2024-09-21 20:27:12 +03:00
parent be1baf164c
commit f1c5afd84e
43 changed files with 726 additions and 826 deletions

View file

@ -29,7 +29,7 @@ use crate::event_loop::{
ActiveEventLoop as RootActiveEventLoop, ControlFlow, DeviceEvents,
EventLoopProxy as CoreEventLoopProxy, OwnedDisplayHandle as CoreOwnedDisplayHandle,
};
use crate::monitor::MonitorHandle as RootMonitorHandle;
use crate::monitor::MonitorHandle as CoreMonitorHandle;
use crate::platform::macos::ActivationPolicy;
use crate::platform::pump_events::PumpStatus;
use crate::platform_impl::Window;
@ -114,13 +114,17 @@ impl RootActiveEventLoop for ActiveEventLoop {
Ok(RootCustomCursor { inner: CustomCursor::new(source.inner)? })
}
fn available_monitors(&self) -> Box<dyn Iterator<Item = RootMonitorHandle>> {
Box::new(monitor::available_monitors().into_iter().map(|inner| RootMonitorHandle { inner }))
fn available_monitors(&self) -> Box<dyn Iterator<Item = CoreMonitorHandle>> {
Box::new(
monitor::available_monitors()
.into_iter()
.map(|monitor| CoreMonitorHandle(Arc::new(monitor))),
)
}
fn primary_monitor(&self) -> Option<crate::monitor::MonitorHandle> {
let monitor = monitor::primary_monitor();
Some(RootMonitorHandle { inner: monitor })
Some(CoreMonitorHandle(Arc::new(monitor)))
}
fn listen_device_events(&self, _allowed: DeviceEvents) {}

View file

@ -24,4 +24,3 @@ pub(crate) use self::window::Window;
pub(crate) use self::window_delegate::PlatformSpecificWindowAttributes;
pub(crate) use crate::cursor::OnlyCursorImageSource as PlatformCustomCursorSource;
pub(crate) use crate::icon::NoIcon as PlatformIcon;
pub(crate) use crate::platform_impl::Fullscreen;

View file

@ -29,7 +29,7 @@ use objc2_foundation::{ns_string, NSNumber, NSPoint, NSRect};
use super::ffi;
use super::util::cgerr;
use crate::dpi::{LogicalPosition, PhysicalPosition, PhysicalSize};
use crate::monitor::VideoMode;
use crate::monitor::{MonitorHandleProvider, VideoMode};
#[derive(Clone)]
pub struct VideoModeHandle {
@ -54,7 +54,7 @@ impl std::hash::Hash for VideoModeHandle {
impl std::fmt::Debug for VideoModeHandle {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("VideoMode")
f.debug_struct("VideoModeHandle")
.field("mode", &self.mode)
.field("monitor", &self.monitor)
.finish()
@ -106,10 +106,113 @@ pub struct MonitorHandle(CGDirectDisplayID);
impl MonitorHandle {
/// Internal comparisons of [`MonitorHandle`]s are done first requesting a UUID for the handle.
fn uuid(&self) -> [u8; 16] {
fn uuid(&self) -> u128 {
let ptr = unsafe { ffi::CGDisplayCreateUUIDFromDisplayID(self.0) };
let cf_uuid = unsafe { CFRetained::from_raw(NonNull::new(ptr).unwrap()) };
unsafe { CFUUIDGetUUIDBytes(&cf_uuid) }.into()
u128::from_ne_bytes(unsafe { CFUUIDGetUUIDBytes(&cf_uuid) }.into())
}
pub fn new(id: CGDirectDisplayID) -> Self {
MonitorHandle(id)
}
fn refresh_rate_millihertz(&self) -> Option<NonZeroU32> {
let current_display_mode =
NativeDisplayMode(unsafe { CGDisplayCopyDisplayMode(self.0) }.unwrap());
refresh_rate_millihertz(self.0, &current_display_mode)
}
pub fn video_mode_handles(&self) -> impl Iterator<Item = VideoModeHandle> {
let refresh_rate_millihertz = self.refresh_rate_millihertz();
let monitor = self.clone();
unsafe {
let modes = {
let array = CGDisplayCopyAllDisplayModes(self.0, None)
.expect("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 CGDisplayMode;
CFRetained::retain(NonNull::new(mode).unwrap())
})
.collect();
modes
};
modes.into_iter().map(move |mode| {
let cg_refresh_rate_hertz = CGDisplayModeGetRefreshRate(Some(&mode)).round() as i64;
// CGDisplayModeGetRefreshRate returns 0.0 for any display that
// isn't a CRT
let refresh_rate_millihertz = if cg_refresh_rate_hertz > 0 {
NonZeroU32::new((cg_refresh_rate_hertz * 1000) as u32)
} else {
refresh_rate_millihertz
};
VideoModeHandle::new(
monitor.clone(),
NativeDisplayMode(mode),
refresh_rate_millihertz,
)
})
}
}
pub(crate) fn ns_screen(&self, mtm: MainThreadMarker) -> Option<Retained<NSScreen>> {
let uuid = self.uuid();
NSScreen::screens(mtm).into_iter().find(|screen| {
let other_native_id = get_display_id(screen);
let other = MonitorHandle::new(other_native_id);
uuid == other.uuid()
})
}
}
impl MonitorHandleProvider for MonitorHandle {
fn id(&self) -> u128 {
self.uuid()
}
fn native_id(&self) -> u64 {
self.0 as _
}
// TODO: Be smarter about this:
//
// <https://github.com/glfw/glfw/blob/57cbded0760a50b9039ee0cb3f3c14f60145567c/src/cocoa_monitor.m#L44-L126>
fn name(&self) -> Option<std::borrow::Cow<'_, str>> {
let screen_num = unsafe { CGDisplayModelNumber(self.0) };
Some(format!("Monitor #{screen_num}").into())
}
fn position(&self) -> Option<PhysicalPosition<i32>> {
// This is already in screen coordinates. If we were using `NSScreen`,
// then a conversion would've been needed:
// flip_window_screen_coordinates(self.ns_screen(mtm)?.frame())
let bounds = unsafe { CGDisplayBounds(self.0) };
let position = LogicalPosition::new(bounds.origin.x, bounds.origin.y);
Some(position.to_physical(self.scale_factor()))
}
fn scale_factor(&self) -> f64 {
run_on_main(|mtm| {
match self.ns_screen(mtm) {
Some(screen) => screen.backingScaleFactor() as f64,
None => 1.0, // default to 1.0 when we can't find the screen
}
})
}
fn current_video_mode(&self) -> Option<VideoMode> {
let mode = NativeDisplayMode(unsafe { CGDisplayCopyDisplayMode(self.0) }.unwrap());
let refresh_rate_millihertz = refresh_rate_millihertz(self.0, &mode);
Some(VideoModeHandle::new(self.clone(), mode, refresh_rate_millihertz).mode)
}
fn video_modes(&self) -> Box<dyn Iterator<Item = VideoMode>> {
Box::new(self.video_mode_handles().map(|mode| mode.mode))
}
}
@ -175,113 +278,13 @@ impl fmt::Debug for MonitorHandle {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("MonitorHandle")
.field("name", &self.name())
.field("native_identifier", &self.native_identifier())
.field("native_id", &self.native_id())
.field("position", &self.position())
.field("scale_factor", &self.scale_factor())
.finish_non_exhaustive()
}
}
impl MonitorHandle {
pub fn new(id: CGDirectDisplayID) -> Self {
MonitorHandle(id)
}
// TODO: Be smarter about this:
// <https://github.com/glfw/glfw/blob/57cbded0760a50b9039ee0cb3f3c14f60145567c/src/cocoa_monitor.m#L44-L126>
pub fn name(&self) -> Option<String> {
let screen_num = unsafe { CGDisplayModelNumber(self.0) };
Some(format!("Monitor #{screen_num}"))
}
#[inline]
pub fn native_identifier(&self) -> u32 {
self.0
}
#[inline]
pub fn position(&self) -> Option<PhysicalPosition<i32>> {
// This is already in screen coordinates. If we were using `NSScreen`,
// then a conversion would've been needed:
// flip_window_screen_coordinates(self.ns_screen(mtm)?.frame())
let bounds = unsafe { CGDisplayBounds(self.0) };
let position = LogicalPosition::new(bounds.origin.x, bounds.origin.y);
Some(position.to_physical(self.scale_factor()))
}
pub fn scale_factor(&self) -> f64 {
run_on_main(|mtm| {
match self.ns_screen(mtm) {
Some(screen) => screen.backingScaleFactor() as f64,
None => 1.0, // default to 1.0 when we can't find the screen
}
})
}
fn refresh_rate_millihertz(&self) -> Option<NonZeroU32> {
let current_display_mode =
NativeDisplayMode(unsafe { CGDisplayCopyDisplayMode(self.0) }.unwrap());
refresh_rate_millihertz(self.0, &current_display_mode)
}
pub fn current_video_mode(&self) -> Option<VideoMode> {
let mode = NativeDisplayMode(unsafe { CGDisplayCopyDisplayMode(self.0) }.unwrap());
let refresh_rate_millihertz = refresh_rate_millihertz(self.0, &mode);
Some(VideoModeHandle::new(self.clone(), mode, refresh_rate_millihertz).mode)
}
pub fn video_modes(&self) -> impl Iterator<Item = VideoMode> {
self.video_modes_handles().map(|handle| handle.mode)
}
pub(crate) fn video_modes_handles(&self) -> impl Iterator<Item = VideoModeHandle> {
let refresh_rate_millihertz = self.refresh_rate_millihertz();
let monitor = self.clone();
unsafe {
let modes = {
let array = CGDisplayCopyAllDisplayModes(self.0, None)
.expect("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 CGDisplayMode;
CFRetained::retain(NonNull::new(mode).unwrap())
})
.collect();
modes
};
modes.into_iter().map(move |mode| {
let cg_refresh_rate_hertz = CGDisplayModeGetRefreshRate(Some(&mode)).round() as i64;
// CGDisplayModeGetRefreshRate returns 0.0 for any display that
// isn't a CRT
let refresh_rate_millihertz = if cg_refresh_rate_hertz > 0 {
NonZeroU32::new((cg_refresh_rate_hertz * 1000) as u32)
} else {
refresh_rate_millihertz
};
VideoModeHandle::new(
monitor.clone(),
NativeDisplayMode(mode),
refresh_rate_millihertz,
)
})
}
}
pub(crate) fn ns_screen(&self, mtm: MainThreadMarker) -> Option<Retained<NSScreen>> {
let uuid = self.uuid();
NSScreen::screens(mtm).into_iter().find(|screen| {
let other_native_id = get_display_id(screen);
let other = MonitorHandle::new(other_native_id);
uuid == other.uuid()
})
}
}
pub(crate) fn get_display_id(screen: &NSScreen) -> u32 {
let key = ns_string!("NSScreenNumber");

View file

@ -1,5 +1,7 @@
#![allow(clippy::unnecessary_cast)]
use std::sync::Arc;
use dispatch2::MainThreadBound;
use dpi::{Position, Size};
use objc2::rc::{autoreleasepool, Retained};
@ -10,10 +12,10 @@ use objc2_foundation::NSObject;
use super::event_loop::ActiveEventLoop;
use super::window_delegate::WindowDelegate;
use crate::error::RequestError;
use crate::monitor::MonitorHandle as CoreMonitorHandle;
use crate::monitor::{Fullscreen, MonitorHandle as CoreMonitorHandle};
use crate::window::{
Cursor, Fullscreen, Icon, ImePurpose, Theme, UserAttentionType, Window as CoreWindow,
WindowAttributes, WindowButtons, WindowId, WindowLevel,
Cursor, Icon, ImePurpose, Theme, UserAttentionType, Window as CoreWindow, WindowAttributes,
WindowButtons, WindowId, WindowLevel,
};
#[derive(Debug)]
@ -206,11 +208,11 @@ impl CoreWindow for Window {
}
fn set_fullscreen(&self, fullscreen: Option<Fullscreen>) {
self.maybe_wait_on_main(|delegate| delegate.set_fullscreen(fullscreen.map(Into::into)))
self.maybe_wait_on_main(|delegate| delegate.set_fullscreen(fullscreen))
}
fn fullscreen(&self) -> Option<Fullscreen> {
self.maybe_wait_on_main(|delegate| delegate.fullscreen().map(Into::into))
self.maybe_wait_on_main(|delegate| delegate.fullscreen())
}
fn set_decorations(&self, decorations: bool) {
@ -307,21 +309,24 @@ impl CoreWindow for Window {
fn current_monitor(&self) -> Option<CoreMonitorHandle> {
self.maybe_wait_on_main(|delegate| {
delegate.current_monitor().map(|inner| CoreMonitorHandle { inner })
delegate.current_monitor().map(|monitor| CoreMonitorHandle(Arc::new(monitor)))
})
}
fn available_monitors(&self) -> Box<dyn Iterator<Item = CoreMonitorHandle>> {
self.maybe_wait_on_main(|delegate| {
Box::new(
delegate.available_monitors().into_iter().map(|inner| CoreMonitorHandle { inner }),
delegate
.available_monitors()
.into_iter()
.map(|monitor| CoreMonitorHandle(Arc::new(monitor))),
)
})
}
fn primary_monitor(&self) -> Option<CoreMonitorHandle> {
self.maybe_wait_on_main(|delegate| {
delegate.primary_monitor().map(|inner| CoreMonitorHandle { inner })
delegate.primary_monitor().map(|monitor| CoreMonitorHandle(Arc::new(monitor)))
})
}

View file

@ -42,13 +42,14 @@ use super::observer::RunLoop;
use super::util::cgerr;
use super::view::WinitView;
use super::window::{window_id, WinitPanel, WinitWindow};
use super::{ffi, Fullscreen, MonitorHandle};
use super::{ffi, MonitorHandle};
use crate::dpi::{
LogicalInsets, LogicalPosition, LogicalSize, PhysicalInsets, PhysicalPosition, PhysicalSize,
Position, Size,
};
use crate::error::{NotSupportedError, RequestError};
use crate::event::{SurfaceSizeWriter, WindowEvent};
use crate::monitor::{Fullscreen, MonitorHandle as CoreMonitorHandle, MonitorHandleProvider};
use crate::platform::macos::{OptionAsAlt, WindowExtMacOS};
use crate::window::{
Cursor, CursorGrabMode, Icon, ImePurpose, ResizeDirection, Theme, UserAttentionType,
@ -257,7 +258,9 @@ define_class!(
// Otherwise, we must've reached fullscreen by the user clicking
// on the green fullscreen button. Update state!
None => {
let current_monitor = self.current_monitor_inner();
let current_monitor = self
.current_monitor_inner()
.map(|monitor| CoreMonitorHandle(Arc::new(monitor)));
*fullscreen = Some(Fullscreen::Borderless(current_monitor));
},
}
@ -550,9 +553,10 @@ fn new_window(
mtm: MainThreadMarker,
) -> Option<Retained<NSWindow>> {
autoreleasepool(|_| {
let screen = match attrs.fullscreen.clone().map(Into::into) {
let screen = match attrs.fullscreen.clone() {
Some(Fullscreen::Borderless(Some(monitor)))
| Some(Fullscreen::Exclusive(monitor, _)) => {
let monitor = monitor.as_any().downcast_ref::<MonitorHandle>().unwrap();
monitor.ns_screen(mtm).or_else(|| NSScreen::mainScreen(mtm))
},
Some(Fullscreen::Borderless(None)) => NSScreen::mainScreen(mtm),
@ -871,7 +875,7 @@ impl WindowDelegate {
delegate.set_cursor(attrs.cursor);
// Set fullscreen mode after we setup everything
delegate.set_fullscreen(attrs.fullscreen.map(Into::into));
delegate.set_fullscreen(attrs.fullscreen);
// Setting the window as key has to happen *after* we set the fullscreen
// state, since otherwise we'll briefly see the window at normal size
@ -1455,17 +1459,18 @@ impl WindowDelegate {
// does not take a screen parameter, but uses the current screen)
if let Some(ref fullscreen) = fullscreen {
let new_screen = match fullscreen {
Fullscreen::Borderless(Some(monitor)) => monitor.clone(),
Fullscreen::Borderless(Some(monitor)) | Fullscreen::Exclusive(monitor, _) => {
let monitor = monitor.as_any().downcast_ref::<MonitorHandle>().unwrap();
monitor.ns_screen(mtm)
},
Fullscreen::Borderless(None) => {
if let Some(monitor) = self.current_monitor_inner() {
monitor
monitor.ns_screen(mtm)
} else {
return;
}
},
Fullscreen::Exclusive(monitor, _) => monitor.clone(),
}
.ns_screen(mtm)
.unwrap();
let old_screen = self.window().screen().unwrap();
@ -1487,7 +1492,7 @@ impl WindowDelegate {
// parameter, which is not consistent with the docs saying that it
// takes a `NSDictionary`..
let display_id = monitor.native_identifier();
let display_id = monitor.native_id() as _;
let mut fade_token = ffi::kCGDisplayFadeReservationInvalidToken;
@ -1514,8 +1519,9 @@ impl WindowDelegate {
cgerr(CGDisplayCapture(display_id)).unwrap();
}
let monitor = monitor.as_any().downcast_ref::<MonitorHandle>().unwrap();
let video_mode =
match monitor.video_modes_handles().find(|mode| &mode.mode == video_mode) {
match monitor.video_mode_handles().find(|mode| &mode.mode == video_mode) {
Some(video_mode) => video_mode,
None => return,
};
@ -1580,7 +1586,8 @@ impl WindowDelegate {
// State is restored by `window_did_exit_fullscreen`
toggle_fullscreen(self.window());
},
(Some(Fullscreen::Exclusive(ref monitor, _)), None) => {
(Some(Fullscreen::Exclusive(monitor, _)), None) => {
let monitor = monitor.as_any().downcast_ref::<MonitorHandle>().unwrap();
restore_and_release_display(monitor);
toggle_fullscreen(self.window());
},
@ -1603,7 +1610,7 @@ impl WindowDelegate {
let window_level = unsafe { CGShieldingWindowLevel() } as NSWindowLevel + 1;
self.window().setLevel(window_level);
},
(Some(Fullscreen::Exclusive(ref monitor, _)), Some(Fullscreen::Borderless(_))) => {
(Some(Fullscreen::Exclusive(monitor, _)), Some(Fullscreen::Borderless(_))) => {
let presentation_options = self.ivars().save_presentation_opts.get().unwrap_or(
NSApplicationPresentationOptions::FullScreen
| NSApplicationPresentationOptions::AutoHideDock
@ -1611,6 +1618,7 @@ impl WindowDelegate {
);
app.setPresentationOptions(presentation_options);
let monitor = monitor.as_any().downcast_ref::<MonitorHandle>().unwrap();
restore_and_release_display(monitor);
// Restore the normal window level following the Borderless fullscreen
@ -1815,11 +1823,11 @@ fn restore_and_release_display(monitor: &MonitorHandle) {
if available_monitors.contains(monitor) {
unsafe {
CGRestorePermanentDisplayConfiguration();
cgerr(CGDisplayRelease(monitor.native_identifier())).unwrap();
cgerr(CGDisplayRelease(monitor.native_id() as _)).unwrap();
};
} else {
warn!(
monitor = monitor.name(),
monitor = monitor.name().map(|name| name.to_string()),
"Tried to restore exclusive fullscreen on a monitor that is no longer available"
);
}