Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,7 @@ objc2-ui-kit = { version = "0.3.1", default-features = false, features = [

# Windows
[target.'cfg(target_os = "windows")'.dependencies]
scopeguard = "1.2.0"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd prefer a helper struct with a custom Drop impl that calls RegCloseKey over the added dependency.

unicode-segmentation = "1.7.1"
windows-sys = { version = "0.59.0", features = [
"Win32_Devices_HumanInterfaceDevice",
Expand All @@ -254,6 +255,7 @@ windows-sys = { version = "0.59.0", features = [
"Win32_System_LibraryLoader",
"Win32_System_Ole",
"Win32_Security",
"Win32_System_Registry",
"Win32_System_SystemInformation",
"Win32_System_SystemServices",
"Win32_System_Threading",
Expand Down
1 change: 1 addition & 0 deletions src/changelog/unreleased.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ changelog entry.

### Added

- Add `MonitorHandle::physical_size()` that returns monitor physical size in millimeters.
- Add `ActiveEventLoop::create_proxy()`.
- On Web, add `ActiveEventLoopExtWeb::is_cursor_lock_raw()` to determine if
`DeviceEvent::MouseMotion` is returning raw data, not OS accelerated, when using
Expand Down
21 changes: 21 additions & 0 deletions src/monitor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,27 @@ pub trait MonitorHandleProvider: AsAny + fmt::Debug + Send + Sync {
#[cfg_attr(not(web_platform), doc = "detailed monitor permissions.")]
fn position(&self) -> Option<PhysicalPosition<i32>>;

/// Returns physical size of monitor in millimeters, where the
/// first field of a tuple is width and the second is height.
///
/// Returns `None` if the size of the monitor could not be determined.
///
/// Should be used with care, since not all corner cases may be handled.
///
/// If monitor is rotated by 90° or 270°, width and height will be swapped.
///
/// Every major platform gets this data from
/// [EDID](https://en.wikipedia.org/wiki/Extended_Display_Identification_Data),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: Use [EDID] and [EDID]: https://en.wikipedia.org/wiki/Extended_Display_Identification_Data further down, reads more cleanly IMO.

/// which contains display dimensions in centimeters, and
/// optionally higher precision in millimeters. Therefore, by
/// convention, we also return millimeters.
///
/// ## Platform-specific
///
/// - **Wayland:** Has centimeter precision.
/// - **iOS / Web / Android / Redox:** Unimplemented, always returns [`None`].
fn physical_size(&self) -> Option<(NonZeroU32, NonZeroU32)>;

/// Returns the scale factor of the underlying monitor. To map logical pixels to physical
/// pixels and vice versa, use [`Window::scale_factor`].
///
Expand Down
15 changes: 14 additions & 1 deletion src/platform_impl/apple/appkit/monitor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ use objc2_app_kit::NSScreen;
use objc2_core_foundation::{CFArray, CFRetained, CFUUID};
use objc2_core_graphics::{
CGDirectDisplayID, CGDisplayBounds, CGDisplayCopyAllDisplayModes, CGDisplayCopyDisplayMode,
CGDisplayMode, CGDisplayModelNumber, CGGetActiveDisplayList, CGMainDisplayID,
CGDisplayMode, CGDisplayModelNumber, CGDisplayScreenSize, CGGetActiveDisplayList,
CGMainDisplayID,
};
use objc2_core_video::{kCVReturnSuccess, CVDisplayLink, CVTimeFlags};
use objc2_foundation::{ns_string, NSNumber, NSPoint, NSRect};
Expand Down Expand Up @@ -211,6 +212,18 @@ impl MonitorHandleProvider for MonitorHandle {
Some(position.to_physical(self.scale_factor()))
}

fn physical_size(&self) -> Option<(NonZeroU32, NonZeroU32)> {
let size_float = unsafe { CGDisplayScreenSize(self.display_id()) };
if size_float.width > 0.0 && size_float.height > 0.0 {
Some((
NonZeroU32::new(size_float.width.round() as u32)?,
NonZeroU32::new(size_float.height.round() as u32)?,
))
} else {
None
}
}

fn scale_factor(&self) -> f64 {
run_on_main(|mtm| {
match self.ns_screen(mtm) {
Expand Down
7 changes: 7 additions & 0 deletions src/platform_impl/apple/uikit/monitor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,13 @@ impl MonitorHandleProvider for MonitorHandle {
Some((bounds.origin.x as f64, bounds.origin.y as f64).into())
}

fn physical_size(&self) -> Option<(NonZeroU32, NonZeroU32)> {
// NOTE: There is no way to query the PPI on iOS.
// TODO(madsmtm): Use a hardcoded mapping of device models to PPI.
// <https://stackoverflow.com/a/28573791>
None
}

fn scale_factor(&self) -> f64 {
self.ui_screen.get_on_main(|ui_screen| ui_screen.nativeScale()) as f64
}
Expand Down
10 changes: 10 additions & 0 deletions src/platform_impl/linux/wayland/output.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,16 @@ impl CoreMonitorHandle for MonitorHandle {
}))
}

fn physical_size(&self) -> Option<(NonZeroU32, NonZeroU32)> {
let output_data = self.proxy.data::<OutputData>().unwrap();
let (width_mm, height_mm) = output_data.with_output_info(|oi| oi.physical_size);

Some((
NonZeroU32::new(width_mm.try_into().ok()?)?,
NonZeroU32::new(height_mm.try_into().ok()?)?,
))
}

fn scale_factor(&self) -> f64 {
let output_data = self.proxy.data::<OutputData>().unwrap();
output_data.scale_factor() as f64
Expand Down
39 changes: 36 additions & 3 deletions src/platform_impl/linux/x11/monitor.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use std::num::NonZeroU32;

use x11rb::connection::RequestConnection;
use x11rb::protocol::randr::{self, ConnectionExt as _};
use x11rb::protocol::randr::{self, ConnectionExt as _, Rotation};
use x11rb::protocol::xproto;

use super::{util, X11Error, XConnection};
Expand Down Expand Up @@ -41,6 +41,8 @@ pub struct MonitorHandle {
pub(crate) position: (i32, i32),
/// If the monitor is the primary one
primary: bool,
/// The physical size of a monitor in millimeters.
pub(crate) physical_size: Option<(NonZeroU32, NonZeroU32)>,
/// The DPI scale factor
pub(crate) scale_factor: f64,
/// Used to determine which windows are on this monitor
Expand All @@ -66,6 +68,10 @@ impl MonitorHandleProvider for MonitorHandle {
Some(self.position.into())
}

fn physical_size(&self) -> Option<(NonZeroU32, NonZeroU32)> {
self.physical_size
}

fn scale_factor(&self) -> f64 {
self.scale_factor
}
Expand Down Expand Up @@ -125,19 +131,46 @@ impl MonitorHandle {
crtc: &randr::GetCrtcInfoReply,
primary: bool,
) -> Option<Self> {
let (name, scale_factor, video_modes) = xconn.get_output_info(resources, crtc)?;
let (name, mut physical_size, scale_factor, video_modes) =
xconn.get_output_info(resources, crtc)?;

let rotation = xconn
.xcb_connection()
.randr_get_crtc_info(id, x11rb::CURRENT_TIME)
.ok()?
.reply()
.ok()?
.rotation;

// By default, X11 window system is the only that does not
// swap width and height when display is rotated. To match
// behaviour with Windows and MacOS we do this manually.
if matches!(rotation, Rotation::ROTATE90 | Rotation::ROTATE270) {
physical_size = physical_size.map(|(width_mm, height_mm)| (height_mm, width_mm));
}

let dimensions = (crtc.width as u32, crtc.height as u32);
let position = (crtc.x as i32, crtc.y as i32);

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

Some(MonitorHandle { id, name, scale_factor, position, primary, rect, video_modes })
Some(MonitorHandle {
id,
name,
physical_size,
scale_factor,
position,
primary,
rect,
video_modes,
})
}

pub fn dummy() -> Self {
MonitorHandle {
id: 0,
name: "<dummy monitor>".into(),
physical_size: None,
scale_factor: 1.0,
position: (0, 0),
primary: true,
Expand Down
12 changes: 9 additions & 3 deletions src/platform_impl/linux/x11/util/randr.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use std::num::NonZeroU16;
use std::num::{NonZeroU16, NonZeroU32};
use std::str::FromStr;
use std::{env, str};

Expand All @@ -10,6 +10,8 @@ use crate::dpi::validate_scale_factor;
use crate::monitor::VideoMode;
use crate::platform_impl::platform::x11::{monitor, VideoModeHandle};

pub type OutputInfo = (String, Option<(NonZeroU32, NonZeroU32)>, f64, Vec<VideoModeHandle>);

/// Represents values of `WINIT_HIDPI_FACTOR`.
pub enum EnvVarDPI {
Randr,
Expand Down Expand Up @@ -59,7 +61,7 @@ impl XConnection {
&self,
resources: &monitor::ScreenResources,
crtc: &randr::GetCrtcInfoReply,
) -> Option<(String, f64, Vec<VideoModeHandle>)> {
) -> Option<OutputInfo> {
let output_info = match self
.xcb_connection()
.randr_get_output_info(crtc.outputs[0], x11rb::CURRENT_TIME)
Expand Down Expand Up @@ -127,6 +129,10 @@ impl XConnection {
},
);

let physical_size = NonZeroU32::new(output_info.mm_width).and_then(|mm_width| {
NonZeroU32::new(output_info.mm_height).map(|mm_height| (mm_width, mm_height))
});

let scale_factor = match dpi_env {
EnvVarDPI::Randr => calc_dpi_factor(
(crtc.width.into(), crtc.height.into()),
Expand All @@ -153,7 +159,7 @@ impl XConnection {
},
};

Some((name, scale_factor, modes))
Some((name, physical_size, scale_factor, modes))
}

pub fn set_crtc_config(
Expand Down
17 changes: 16 additions & 1 deletion src/platform_impl/web/monitor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use std::fmt::{self, Debug, Formatter};
use std::future::Future;
use std::hash::{Hash, Hasher};
use std::mem;
use std::num::NonZeroU16;
use std::num::{NonZeroU16, NonZeroU32};
use std::ops::{Deref, DerefMut};
use std::pin::Pin;
use std::rc::{Rc, Weak};
Expand Down Expand Up @@ -128,6 +128,21 @@ impl MonitorHandleProvider for MonitorHandle {
self.id.unwrap_or_default()
}

fn physical_size(&self) -> Option<(NonZeroU32, NonZeroU32)> {
// NOTE: Browsers expose only CSS-pixel which is different
// from the actual pixel. CSS mm also means logical
// millimeters, and not an actual millimeter and equivalent to
// approximately 3.78px. There are couple of ways to solve
// this problem. First one is to stick with approximation, and
// other is to explicitly ask user. Both are non-ideal, so no
// implementation provided.
// References:
//
// [1] <https://developer.mozilla.org/en-US/docs/Web/CSS/length#px>
// [2] <https://stackoverflow.com/questions/75533347/how-can-i-get-the-physical-size-of-a-pixel-in-millimeters-at-a-specific-window>
None
}

fn scale_factor(&self) -> f64 {
self.inner.queue(|inner| inner.scale_factor())
}
Expand Down
Loading
Loading