From 2e53533cc161d2bc6a0f579dccadf6c521999be7 Mon Sep 17 00:00:00 2001 From: daxpedda Date: Sun, 21 Jul 2024 00:01:43 +0200 Subject: [PATCH] Add `MonitorHandle::current_video_mode()` --- src/changelog/unreleased.md | 1 + src/monitor.rs | 6 ++ src/platform_impl/android/mod.rs | 8 +- src/platform_impl/apple/appkit/monitor.rs | 107 +++++++++++++--------- src/platform_impl/apple/uikit/monitor.rs | 10 ++ src/platform_impl/linux/mod.rs | 5 + src/platform_impl/linux/wayland/output.rs | 30 ++++-- src/platform_impl/linux/x11/monitor.rs | 22 ++--- src/platform_impl/linux/x11/util/randr.rs | 2 + src/platform_impl/orbital/mod.rs | 8 +- src/platform_impl/web/monitor.rs | 4 + src/platform_impl/windows/monitor.rs | 43 ++++++--- 12 files changed, 167 insertions(+), 79 deletions(-) diff --git a/src/changelog/unreleased.md b/src/changelog/unreleased.md index 0ffccfb9..98ab2dc7 100644 --- a/src/changelog/unreleased.md +++ b/src/changelog/unreleased.md @@ -61,6 +61,7 @@ changelog entry. the primary finger in a multi-touch interaction. - Implement `Clone`, `Copy`, `Debug`, `Deserialize`, `Eq`, `Hash`, `Ord`, `PartialEq`, `PartialOrd` and `Serialize` on many types. +- Add `MonitorHandle::current_video_mode()`. ### Changed diff --git a/src/monitor.rs b/src/monitor.rs index f25eb79e..d49742e3 100644 --- a/src/monitor.rs +++ b/src/monitor.rs @@ -201,6 +201,12 @@ impl MonitorHandle { self.inner.scale_factor() } + /// Returns the currently active video mode of this monitor. + #[inline] + pub fn current_video_mode(&self) -> Option { + self.inner.current_video_mode().map(|video_mode| VideoModeHandle { video_mode }) + } + /// Returns all fullscreen video modes supported by this monitor. #[inline] pub fn video_modes(&self) -> impl Iterator { diff --git a/src/platform_impl/android/mod.rs b/src/platform_impl/android/mod.rs index 527f2f05..1594f7d7 100644 --- a/src/platform_impl/android/mod.rs +++ b/src/platform_impl/android/mod.rs @@ -1003,17 +1003,21 @@ impl MonitorHandle { None } - pub fn video_modes(&self) -> impl Iterator { + pub fn current_video_mode(&self) -> Option { let size = self.size().into(); // FIXME this is not the real refresh rate // (it is guaranteed to support 32 bit color though) - std::iter::once(VideoModeHandle { + Some(VideoModeHandle { size, bit_depth: 32, refresh_rate_millihertz: 60000, monitor: self.clone(), }) } + + pub fn video_modes(&self) -> impl Iterator { + self.current_video_mode().into_iter() + } } #[derive(Clone, Debug, Eq, Hash, PartialEq)] diff --git a/src/platform_impl/apple/appkit/monitor.rs b/src/platform_impl/apple/appkit/monitor.rs index e78d84f0..6b793f76 100644 --- a/src/platform_impl/apple/appkit/monitor.rs +++ b/src/platform_impl/apple/appkit/monitor.rs @@ -80,6 +80,34 @@ impl Clone for NativeDisplayMode { } impl VideoModeHandle { + fn new(monitor: MonitorHandle, mode: NativeDisplayMode, refresh_rate_millihertz: u32) -> Self { + unsafe { + let pixel_encoding = + CFString::wrap_under_create_rule(ffi::CGDisplayModeCopyPixelEncoding(mode.0)) + .to_string(); + let bit_depth = if pixel_encoding.eq_ignore_ascii_case(ffi::IO32BitDirectPixels) { + 32 + } else if pixel_encoding.eq_ignore_ascii_case(ffi::IO16BitDirectPixels) { + 16 + } else if pixel_encoding.eq_ignore_ascii_case(ffi::kIO30BitDirectPixels) { + 30 + } else { + unimplemented!() + }; + + VideoModeHandle { + size: PhysicalSize::new( + ffi::CGDisplayModeGetPixelWidth(mode.0) as u32, + ffi::CGDisplayModeGetPixelHeight(mode.0) as u32, + ), + refresh_rate_millihertz, + bit_depth, + monitor: monitor.clone(), + native_mode: mode, + } + } + } + pub fn size(&self) -> PhysicalSize { self.size } @@ -212,29 +240,15 @@ impl MonitorHandle { } pub fn refresh_rate_millihertz(&self) -> Option { - unsafe { - let current_display_mode = NativeDisplayMode(CGDisplayCopyDisplayMode(self.0) as _); - let refresh_rate = ffi::CGDisplayModeGetRefreshRate(current_display_mode.0); - if refresh_rate > 0.0 { - return Some((refresh_rate * 1000.0).round() as u32); - } + let current_display_mode = + NativeDisplayMode(unsafe { CGDisplayCopyDisplayMode(self.0) } as _); + refresh_rate_millihertz(self.0, ¤t_display_mode) + } - let mut display_link = std::ptr::null_mut(); - if ffi::CVDisplayLinkCreateWithCGDisplay(self.0, &mut display_link) - != ffi::kCVReturnSuccess - { - return None; - } - let time = ffi::CVDisplayLinkGetNominalOutputVideoRefreshPeriod(display_link); - ffi::CVDisplayLinkRelease(display_link); - - // This value is indefinite if an invalid display link was specified - if time.flags & ffi::kCVTimeIsIndefinite != 0 { - return None; - } - - (time.time_scale as i64).checked_div(time.time_value).map(|v| (v * 1000) as u32) - } + pub fn current_video_mode(&self) -> Option { + let mode = NativeDisplayMode(unsafe { CGDisplayCopyDisplayMode(self.0) } as _); + let refresh_rate_millihertz = refresh_rate_millihertz(self.0, &mode).unwrap_or(0); + Some(VideoModeHandle::new(self.clone(), mode, refresh_rate_millihertz)) } pub fn video_modes(&self) -> impl Iterator { @@ -268,29 +282,11 @@ impl MonitorHandle { refresh_rate_millihertz }; - let pixel_encoding = - CFString::wrap_under_create_rule(ffi::CGDisplayModeCopyPixelEncoding(mode)) - .to_string(); - let bit_depth = if pixel_encoding.eq_ignore_ascii_case(ffi::IO32BitDirectPixels) { - 32 - } else if pixel_encoding.eq_ignore_ascii_case(ffi::IO16BitDirectPixels) { - 16 - } else if pixel_encoding.eq_ignore_ascii_case(ffi::kIO30BitDirectPixels) { - 30 - } else { - unimplemented!() - }; - - VideoModeHandle { - size: PhysicalSize::new( - ffi::CGDisplayModeGetPixelWidth(mode) as u32, - ffi::CGDisplayModeGetPixelHeight(mode) as u32, - ), + VideoModeHandle::new( + monitor.clone(), + NativeDisplayMode(mode), refresh_rate_millihertz, - bit_depth, - monitor: monitor.clone(), - native_mode: NativeDisplayMode(mode), - } + ) }) } } @@ -349,3 +345,26 @@ pub(crate) fn flip_window_screen_coordinates(frame: NSRect) -> NSPoint { let y = main_screen_height - frame.size.height - frame.origin.y; NSPoint::new(frame.origin.x, y) } + +fn refresh_rate_millihertz(id: CGDirectDisplayID, mode: &NativeDisplayMode) -> Option { + unsafe { + let refresh_rate = ffi::CGDisplayModeGetRefreshRate(mode.0); + if refresh_rate > 0.0 { + return Some((refresh_rate * 1000.0).round() as u32); + } + + let mut display_link = std::ptr::null_mut(); + if ffi::CVDisplayLinkCreateWithCGDisplay(id, &mut display_link) != ffi::kCVReturnSuccess { + return None; + } + let time = ffi::CVDisplayLinkGetNominalOutputVideoRefreshPeriod(display_link); + ffi::CVDisplayLinkRelease(display_link); + + // This value is indefinite if an invalid display link was specified + if time.flags & ffi::kCVTimeIsIndefinite != 0 { + return None; + } + + (time.time_scale as i64).checked_div(time.time_value).map(|v| (v * 1000) as u32) + } +} diff --git a/src/platform_impl/apple/uikit/monitor.rs b/src/platform_impl/apple/uikit/monitor.rs index 1871ffb1..2975afc7 100644 --- a/src/platform_impl/apple/uikit/monitor.rs +++ b/src/platform_impl/apple/uikit/monitor.rs @@ -182,6 +182,16 @@ impl MonitorHandle { Some(self.ui_screen.get_on_main(|ui_screen| refresh_rate_millihertz(ui_screen))) } + pub fn current_video_mode(&self) -> Option { + Some(run_on_main(|mtm| { + VideoModeHandle::new( + self.ui_screen(mtm).clone(), + self.ui_screen(mtm).currentMode().unwrap(), + mtm, + ) + })) + } + pub fn video_modes(&self) -> impl Iterator { run_on_main(|mtm| { let ui_screen = self.ui_screen(mtm); diff --git a/src/platform_impl/linux/mod.rs b/src/platform_impl/linux/mod.rs index 734b3289..69edafda 100644 --- a/src/platform_impl/linux/mod.rs +++ b/src/platform_impl/linux/mod.rs @@ -261,6 +261,11 @@ impl MonitorHandle { x11_or_wayland!(match self; MonitorHandle(m) => m.scale_factor() as _) } + #[inline] + pub fn current_video_mode(&self) -> Option { + x11_or_wayland!(match self; MonitorHandle(m) => m.current_video_mode()) + } + #[inline] pub fn video_modes(&self) -> Box> { x11_or_wayland!(match self; MonitorHandle(m) => Box::new(m.video_modes())) diff --git a/src/platform_impl/linux/wayland/output.rs b/src/platform_impl/linux/wayland/output.rs index 42537ced..8c793be6 100644 --- a/src/platform_impl/linux/wayland/output.rs +++ b/src/platform_impl/linux/wayland/output.rs @@ -1,4 +1,4 @@ -use sctk::output::OutputData; +use sctk::output::{Mode, OutputData}; use sctk::reexports::client::protocol::wl_output::WlOutput; use sctk::reexports::client::Proxy; @@ -73,6 +73,18 @@ impl MonitorHandle { output_data.scale_factor() } + #[inline] + pub fn current_video_mode(&self) -> Option { + let output_data = self.proxy.data::().unwrap(); + output_data.with_output_info(|info| { + let mode = info.modes.iter().find(|mode| mode.current).cloned(); + + mode.map(|mode| { + PlatformVideoModeHandle::Wayland(VideoModeHandle::new(self.clone(), mode)) + }) + }) + } + #[inline] pub fn video_modes(&self) -> impl Iterator { let output_data = self.proxy.data::().unwrap(); @@ -81,12 +93,7 @@ impl MonitorHandle { let monitor = self.clone(); modes.into_iter().map(move |mode| { - PlatformVideoModeHandle::Wayland(VideoModeHandle { - size: (mode.dimensions.0 as u32, mode.dimensions.1 as u32).into(), - refresh_rate_millihertz: mode.refresh_rate as u32, - bit_depth: 32, - monitor: monitor.clone(), - }) + PlatformVideoModeHandle::Wayland(VideoModeHandle::new(monitor.clone(), mode)) }) } } @@ -126,6 +133,15 @@ pub struct VideoModeHandle { } impl VideoModeHandle { + fn new(monitor: MonitorHandle, mode: Mode) -> Self { + VideoModeHandle { + size: (mode.dimensions.0 as u32, mode.dimensions.1 as u32).into(), + refresh_rate_millihertz: mode.refresh_rate as u32, + bit_depth: 32, + monitor: monitor.clone(), + } + } + #[inline] pub fn size(&self) -> PhysicalSize { self.size diff --git a/src/platform_impl/linux/x11/monitor.rs b/src/platform_impl/linux/x11/monitor.rs index 2a26a26d..0dd4abfc 100644 --- a/src/platform_impl/linux/x11/monitor.rs +++ b/src/platform_impl/linux/x11/monitor.rs @@ -18,6 +18,7 @@ impl XConnection { #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct VideoModeHandle { + pub(crate) current: bool, pub(crate) size: (u32, u32), pub(crate) bit_depth: u16, pub(crate) refresh_rate_millihertz: u32, @@ -59,8 +60,6 @@ pub struct MonitorHandle { position: (i32, i32), /// If the monitor is the primary one primary: bool, - /// The refresh rate used by monitor. - refresh_rate_millihertz: Option, /// The DPI scale factor pub(crate) scale_factor: f64, /// Used to determine which windows are on this monitor @@ -117,20 +116,11 @@ impl MonitorHandle { let dimensions = (crtc.width as u32, crtc.height as u32); let position = (crtc.x as i32, crtc.y as i32); - // Get the refresh rate of the current video mode. - let current_mode = crtc.mode; - let screen_modes = resources.modes(); - let refresh_rate_millihertz = screen_modes - .iter() - .find(|mode| mode.id == current_mode) - .and_then(mode_refresh_rate_millihertz); - let rect = util::AaRect::new(position, dimensions); Some(MonitorHandle { id, name, - refresh_rate_millihertz, scale_factor, dimensions, position, @@ -147,7 +137,6 @@ impl MonitorHandle { scale_factor: 1.0, dimensions: (1, 1), position: (0, 0), - refresh_rate_millihertz: None, primary: true, rect: util::AaRect::new((0, 0), (1, 1)), video_modes: Vec::new(), @@ -177,7 +166,9 @@ impl MonitorHandle { } pub fn refresh_rate_millihertz(&self) -> Option { - self.refresh_rate_millihertz + self.video_modes + .iter() + .find_map(|mode| mode.current.then_some(mode.refresh_rate_millihertz)) } #[inline] @@ -185,6 +176,11 @@ impl MonitorHandle { self.scale_factor } + #[inline] + pub fn current_video_mode(&self) -> Option { + self.video_modes.iter().find(|mode| mode.current).cloned().map(PlatformVideoModeHandle::X) + } + #[inline] pub fn video_modes(&self) -> impl Iterator { let monitor = self.clone(); diff --git a/src/platform_impl/linux/x11/util/randr.rs b/src/platform_impl/linux/x11/util/randr.rs index 19df178a..4f27801b 100644 --- a/src/platform_impl/linux/x11/util/randr.rs +++ b/src/platform_impl/linux/x11/util/randr.rs @@ -74,6 +74,7 @@ impl XConnection { let bit_depth = self.default_root().root_depth; let output_modes = &output_info.modes; let resource_modes = resources.modes(); + let current_mode = crtc.mode; let modes = resource_modes .iter() @@ -82,6 +83,7 @@ impl XConnection { .filter(|x| output_modes.iter().any(|id| x.id == *id)) .map(|mode| { VideoModeHandle { + current: mode.id == current_mode, size: (mode.width.into(), mode.height.into()), refresh_rate_millihertz: monitor::mode_refresh_rate_millihertz(mode) .unwrap_or(0), diff --git a/src/platform_impl/orbital/mod.rs b/src/platform_impl/orbital/mod.rs index 34a85553..4e0979cc 100644 --- a/src/platform_impl/orbital/mod.rs +++ b/src/platform_impl/orbital/mod.rs @@ -217,17 +217,21 @@ impl MonitorHandle { None } - pub fn video_modes(&self) -> impl Iterator { + pub fn current_video_mode(&self) -> Option { let size = self.size().into(); // FIXME this is not the real refresh rate // (it is guaranteed to support 32 bit color though) - std::iter::once(VideoModeHandle { + Some(VideoModeHandle { size, bit_depth: 32, refresh_rate_millihertz: 60000, monitor: self.clone(), }) } + + pub fn video_modes(&self) -> impl Iterator { + self.current_video_mode().into_iter() + } } #[derive(Clone, Debug, Eq, Hash, PartialEq)] diff --git a/src/platform_impl/web/monitor.rs b/src/platform_impl/web/monitor.rs index 8decd2a0..678056d7 100644 --- a/src/platform_impl/web/monitor.rs +++ b/src/platform_impl/web/monitor.rs @@ -89,6 +89,10 @@ impl MonitorHandle { }) } + pub fn current_video_mode(&self) -> Option { + Some(VideoModeHandle(self.clone())) + } + pub fn video_modes(&self) -> Once { iter::once(VideoModeHandle(self.clone())) } diff --git a/src/platform_impl/windows/monitor.rs b/src/platform_impl/windows/monitor.rs index 9a880db4..4f658167 100644 --- a/src/platform_impl/windows/monitor.rs +++ b/src/platform_impl/windows/monitor.rs @@ -59,6 +59,20 @@ impl std::fmt::Debug for VideoModeHandle { } impl VideoModeHandle { + fn new(monitor: MonitorHandle, mode: DEVMODEW) -> Self { + const REQUIRED_FIELDS: u32 = + DM_BITSPERPEL | DM_PELSWIDTH | DM_PELSHEIGHT | DM_DISPLAYFREQUENCY; + assert!(has_flag(mode.dmFields, REQUIRED_FIELDS)); + + VideoModeHandle { + size: (mode.dmPelsWidth, mode.dmPelsHeight), + bit_depth: mode.dmBitsPerPel as u16, + refresh_rate_millihertz: mode.dmDisplayFrequency * 1000, + monitor, + native_video_mode: Box::new(mode), + } + } + pub fn size(&self) -> PhysicalSize { self.size.into() } @@ -207,6 +221,23 @@ impl MonitorHandle { dpi_to_scale_factor(get_monitor_dpi(self.0).unwrap_or(96)) } + #[inline] + pub fn current_video_mode(&self) -> Option { + let monitor_info = get_monitor_info(self.0).ok()?; + let device_name = monitor_info.szDevice.as_ptr(); + unsafe { + let mut mode: DEVMODEW = mem::zeroed(); + mode.dmSize = mem::size_of_val(&mode) as u16; + if EnumDisplaySettingsExW(device_name, ENUM_CURRENT_SETTINGS, &mut mode, 0) + == false.into() + { + None + } else { + Some(VideoModeHandle::new(self.clone(), mode)) + } + } + } + #[inline] pub fn video_modes(&self) -> impl Iterator { // EnumDisplaySettingsExW can return duplicate values (or some of the @@ -233,19 +264,9 @@ impl MonitorHandle { break; } - const REQUIRED_FIELDS: u32 = - DM_BITSPERPEL | DM_PELSWIDTH | DM_PELSHEIGHT | DM_DISPLAYFREQUENCY; - assert!(has_flag(mode.dmFields, REQUIRED_FIELDS)); - // Use Ord impl of RootVideoModeHandle modes.insert(RootVideoModeHandle { - video_mode: VideoModeHandle { - size: (mode.dmPelsWidth, mode.dmPelsHeight), - bit_depth: mode.dmBitsPerPel as u16, - refresh_rate_millihertz: mode.dmDisplayFrequency * 1000, - monitor: self.clone(), - native_video_mode: Box::new(mode), - }, + video_mode: VideoModeHandle::new(self.clone(), mode), }); i += 1;