Skip to content

Commit 73343bf

Browse files
committed
Expose monitor physical size
Currently only X11 and Wayland are supported.
1 parent 587ade8 commit 73343bf

File tree

8 files changed

+98
-12
lines changed

8 files changed

+98
-12
lines changed

src/monitor.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,14 @@ pub trait MonitorHandleProvider: AsAny + fmt::Debug + Send + Sync {
113113
#[cfg_attr(not(web_platform), doc = "detailed monitor permissions.")]
114114
fn position(&self) -> Option<PhysicalPosition<i32>>;
115115

116+
/// Returns physical size of monitor in millimeters.
117+
///
118+
/// ## Platform-specific
119+
///
120+
/// - **X11, **Wayland**, **MacOS**, **Windows**: Returns expected values.
121+
/// - **Other platforms:** Always returns [`None`].
122+
fn physical_size(&self) -> Option<PhysicalSize<u32>>;
123+
116124
/// Returns the scale factor of the underlying monitor. To map logical pixels to physical
117125
/// pixels and vice versa, use [`Window::scale_factor`].
118126
///

src/platform_impl/apple/appkit/monitor.rs

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@ use objc2_app_kit::NSScreen;
1212
use objc2_core_foundation::{CFArray, CFRetained, CFUUID};
1313
use objc2_core_graphics::{
1414
CGDirectDisplayID, CGDisplayBounds, CGDisplayCopyAllDisplayModes, CGDisplayCopyDisplayMode,
15-
CGDisplayMode, CGDisplayModelNumber, CGGetActiveDisplayList, CGMainDisplayID,
15+
CGDisplayMode, CGDisplayModelNumber, CGDisplayScreenSize, CGGetActiveDisplayList,
16+
CGMainDisplayID,
1617
};
1718
use objc2_core_video::{kCVReturnSuccess, CVDisplayLink, CVTimeFlags};
1819
use objc2_foundation::{ns_string, NSNumber, NSPoint, NSRect};
@@ -211,6 +212,18 @@ impl MonitorHandleProvider for MonitorHandle {
211212
Some(position.to_physical(self.scale_factor()))
212213
}
213214

215+
fn physical_size(&self) -> Option<PhysicalSize<u32>> {
216+
let size_float = unsafe { CGDisplayScreenSize(self.display_id()) };
217+
if size_float.width > 0.0 && size_float.height > 0.0 {
218+
Some(PhysicalSize {
219+
width: size_float.width.round() as _,
220+
height: size_float.height.round() as _,
221+
})
222+
} else {
223+
None
224+
}
225+
}
226+
214227
fn scale_factor(&self) -> f64 {
215228
run_on_main(|mtm| {
216229
match self.ns_screen(mtm) {

src/platform_impl/apple/uikit/monitor.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ use objc2::{available, MainThreadMarker, Message};
1010
use objc2_foundation::NSInteger;
1111
use objc2_ui_kit::{UIScreen, UIScreenMode};
1212

13-
use crate::dpi::PhysicalPosition;
13+
use crate::dpi::{PhysicalPosition, PhysicalSize};
1414
use crate::monitor::{MonitorHandleProvider, VideoMode};
1515

1616
// Workaround for `MainThreadBound` implementing almost no traits
@@ -110,6 +110,10 @@ impl MonitorHandleProvider for MonitorHandle {
110110
Some((bounds.origin.x as f64, bounds.origin.y as f64).into())
111111
}
112112

113+
fn physical_size(&self) -> Option<PhysicalSize<u32>> {
114+
None
115+
}
116+
113117
fn scale_factor(&self) -> f64 {
114118
self.ui_screen.get_on_main(|ui_screen| ui_screen.nativeScale()) as f64
115119
}

src/platform_impl/linux/wayland/output.rs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use sctk::output::{Mode, OutputData};
55
use sctk::reexports::client::protocol::wl_output::WlOutput;
66
use sctk::reexports::client::Proxy;
77

8-
use crate::dpi::{LogicalPosition, PhysicalPosition};
8+
use crate::dpi::{LogicalPosition, PhysicalPosition, PhysicalSize};
99
use crate::monitor::{MonitorHandleProvider as CoreMonitorHandle, VideoMode};
1010

1111
#[derive(Clone, Debug)]
@@ -51,6 +51,13 @@ impl CoreMonitorHandle for MonitorHandle {
5151
}))
5252
}
5353

54+
fn physical_size(&self) -> Option<PhysicalSize<u32>> {
55+
let output_data = self.proxy.data::<OutputData>().unwrap();
56+
let physical_size = output_data.with_output_info(|oi| oi.physical_size);
57+
58+
(physical_size.0 > 0 && physical_size.1 > 0).then_some(physical_size.into())
59+
}
60+
5461
fn scale_factor(&self) -> f64 {
5562
let output_data = self.proxy.data::<OutputData>().unwrap();
5663
output_data.scale_factor() as f64

src/platform_impl/linux/x11/monitor.rs

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use x11rb::protocol::randr::{self, ConnectionExt as _};
55
use x11rb::protocol::xproto;
66

77
use super::{util, X11Error, XConnection};
8-
use crate::dpi::PhysicalPosition;
8+
use crate::dpi::{PhysicalPosition, PhysicalSize};
99
use crate::monitor::{MonitorHandleProvider, VideoMode};
1010

1111
// Used for testing. This should always be committed as false.
@@ -41,6 +41,8 @@ pub struct MonitorHandle {
4141
pub(crate) position: (i32, i32),
4242
/// If the monitor is the primary one
4343
primary: bool,
44+
/// The physical size of a monitor in millimeters.
45+
pub(crate) physical_size: Option<PhysicalSize<u32>>,
4446
/// The DPI scale factor
4547
pub(crate) scale_factor: f64,
4648
/// Used to determine which windows are on this monitor
@@ -66,6 +68,10 @@ impl MonitorHandleProvider for MonitorHandle {
6668
Some(self.position.into())
6769
}
6870

71+
fn physical_size(&self) -> Option<PhysicalSize<u32>> {
72+
self.physical_size
73+
}
74+
6975
fn scale_factor(&self) -> f64 {
7076
self.scale_factor
7177
}
@@ -125,19 +131,30 @@ impl MonitorHandle {
125131
crtc: &randr::GetCrtcInfoReply,
126132
primary: bool,
127133
) -> Option<Self> {
128-
let (name, scale_factor, video_modes) = xconn.get_output_info(resources, crtc)?;
134+
let (name, physical_size, scale_factor, video_modes) =
135+
xconn.get_output_info(resources, crtc)?;
129136
let dimensions = (crtc.width as u32, crtc.height as u32);
130137
let position = (crtc.x as i32, crtc.y as i32);
131138

132139
let rect = util::AaRect::new(position, dimensions);
133140

134-
Some(MonitorHandle { id, name, scale_factor, position, primary, rect, video_modes })
141+
Some(MonitorHandle {
142+
id,
143+
name,
144+
physical_size,
145+
scale_factor,
146+
position,
147+
primary,
148+
rect,
149+
video_modes,
150+
})
135151
}
136152

137153
pub fn dummy() -> Self {
138154
MonitorHandle {
139155
id: 0,
140156
name: "<dummy monitor>".into(),
157+
physical_size: None,
141158
scale_factor: 1.0,
142159
position: (0, 0),
143160
primary: true,

src/platform_impl/linux/x11/util/randr.rs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ use std::num::NonZeroU16;
22
use std::str::FromStr;
33
use std::{env, str};
44

5+
use dpi::PhysicalSize;
56
use tracing::warn;
67
use x11rb::protocol::randr::{self, ConnectionExt as _};
78

@@ -10,6 +11,8 @@ use crate::dpi::validate_scale_factor;
1011
use crate::monitor::VideoMode;
1112
use crate::platform_impl::platform::x11::{monitor, VideoModeHandle};
1213

14+
pub type OutputInfo = (String, Option<PhysicalSize<u32>>, f64, Vec<VideoModeHandle>);
15+
1316
/// Represents values of `WINIT_HIDPI_FACTOR`.
1417
pub enum EnvVarDPI {
1518
Randr,
@@ -59,7 +62,7 @@ impl XConnection {
5962
&self,
6063
resources: &monitor::ScreenResources,
6164
crtc: &randr::GetCrtcInfoReply,
62-
) -> Option<(String, f64, Vec<VideoModeHandle>)> {
65+
) -> Option<OutputInfo> {
6366
let output_info = match self
6467
.xcb_connection()
6568
.randr_get_output_info(crtc.outputs[0], x11rb::CURRENT_TIME)
@@ -127,6 +130,9 @@ impl XConnection {
127130
},
128131
);
129132

133+
let physical_size = (output_info.mm_width > 0 && output_info.mm_height > 0)
134+
.then_some(PhysicalSize { width: output_info.mm_width, height: output_info.mm_height });
135+
130136
let scale_factor = match dpi_env {
131137
EnvVarDPI::Randr => calc_dpi_factor(
132138
(crtc.width.into(), crtc.height.into()),
@@ -153,7 +159,7 @@ impl XConnection {
153159
},
154160
};
155161

156-
Some((name, scale_factor, modes))
162+
Some((name, physical_size, scale_factor, modes))
157163
}
158164

159165
pub fn set_crtc_config(

src/platform_impl/web/monitor.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,10 @@ impl MonitorHandleProvider for MonitorHandle {
128128
self.id.unwrap_or_default()
129129
}
130130

131+
fn physical_size(&self) -> Option<PhysicalSize<u32>> {
132+
None
133+
}
134+
131135
fn scale_factor(&self) -> f64 {
132136
self.inner.queue(|inner| inner.scale_factor())
133137
}

src/platform_impl/windows/monitor.rs

Lines changed: 31 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,11 @@ use std::{io, iter, mem, ptr};
55

66
use windows_sys::Win32::Foundation::{BOOL, HWND, LPARAM, POINT, RECT};
77
use windows_sys::Win32::Graphics::Gdi::{
8-
EnumDisplayMonitors, EnumDisplaySettingsExW, GetMonitorInfoW, MonitorFromPoint,
9-
MonitorFromWindow, DEVMODEW, DM_BITSPERPEL, DM_DISPLAYFREQUENCY, DM_PELSHEIGHT, DM_PELSWIDTH,
10-
ENUM_CURRENT_SETTINGS, HDC, HMONITOR, MONITORINFO, MONITORINFOEXW, MONITOR_DEFAULTTONEAREST,
11-
MONITOR_DEFAULTTOPRIMARY,
8+
CreateDCW, DeleteDC, EnumDisplayMonitors, EnumDisplaySettingsExW, GetDeviceCaps,
9+
GetMonitorInfoW, MonitorFromPoint, MonitorFromWindow, DEVMODEW, DM_BITSPERPEL,
10+
DM_DISPLAYFREQUENCY, DM_PELSHEIGHT, DM_PELSWIDTH, ENUM_CURRENT_SETTINGS, HDC, HMONITOR,
11+
HORZSIZE, MONITORINFO, MONITORINFOEXW, MONITOR_DEFAULTTONEAREST, MONITOR_DEFAULTTOPRIMARY,
12+
VERTSIZE,
1213
};
1314

1415
use super::util::decode_wide;
@@ -185,6 +186,32 @@ impl MonitorHandleProvider for MonitorHandle {
185186
.ok()
186187
}
187188

189+
fn physical_size(&self) -> Option<PhysicalSize<u32>> {
190+
let monitor_info_ex_w = get_monitor_info(self.0).ok()?;
191+
192+
let hdc = unsafe {
193+
CreateDCW(
194+
monitor_info_ex_w.szDevice.as_ptr(),
195+
monitor_info_ex_w.szDevice.as_ptr(),
196+
std::ptr::null(),
197+
std::ptr::null(),
198+
)
199+
};
200+
201+
let width: u32 =
202+
unsafe { GetDeviceCaps(hdc, HORZSIZE.try_into().ok()?) }.try_into().ok()?;
203+
let height: u32 =
204+
unsafe { GetDeviceCaps(hdc, VERTSIZE.try_into().ok()?) }.try_into().ok()?;
205+
206+
unsafe {
207+
if DeleteDC(hdc) == 0 {
208+
tracing::error!("DeleteDC {:?} failed", hdc)
209+
}
210+
}
211+
212+
(width > 0 && height > 0).then_some(PhysicalSize { width, height })
213+
}
214+
188215
fn scale_factor(&self) -> f64 {
189216
dpi_to_scale_factor(get_monitor_dpi(self.0).unwrap_or(96))
190217
}

0 commit comments

Comments
 (0)