use winit::dpi::{PhysicalPosition, PhysicalSize};
use winit::window::Window;
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct DesktopRect {
pub x: f64,
pub y: f64,
pub width: f64,
pub height: f64,
}
impl DesktopRect {
pub fn right(&self) -> f64 {
self.x + self.width
}
pub fn bottom(&self) -> f64 {
self.y + self.height
}
pub fn overlap_area(&self, other: &DesktopRect) -> f64 {
let width = self.right().min(other.right()) - self.x.max(other.x);
let height = self.bottom().min(other.bottom()) - self.y.max(other.y);
width.max(0.0) * height.max(0.0)
}
pub fn intersection(&self, other: &DesktopRect) -> Option<DesktopRect> {
let x = self.x.max(other.x);
let y = self.y.max(other.y);
let width = self.right().min(other.right()) - x;
let height = self.bottom().min(other.bottom()) - y;
(width > 0.0 && height > 0.0).then_some(DesktopRect {
x,
y,
width,
height,
})
}
fn from_pixels(position: PhysicalPosition<i32>, size: PhysicalSize<u32>, unit: f64) -> Self {
DesktopRect {
x: position.x as f64 * unit,
y: position.y as f64 * unit,
width: size.width as f64 * unit,
height: size.height as f64 * unit,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct DesktopMonitor {
pub bounds: DesktopRect,
pub work_area: DesktopRect,
}
fn units_per_pixel(scale_factor: f64) -> f64 {
if cfg!(target_os = "macos") {
1.0 / scale_factor
} else {
1.0
}
}
pub fn units_per_logical(window: &Window) -> f64 {
let scale = window.scale_factor();
scale * units_per_pixel(scale)
}
pub fn window_frame(window: &Window) -> Option<(DesktopRect, DesktopRect)> {
let unit = units_per_pixel(window.scale_factor());
let outer = window.outer_position().ok()?;
let inner = window.inner_position().ok()?;
Some((
DesktopRect::from_pixels(outer, window.outer_size(), unit),
DesktopRect::from_pixels(inner, window.inner_size(), unit),
))
}
pub fn set_window_frame(window: &Window, position: (f64, f64), inner_size: (f64, f64)) {
let unit = units_per_pixel(window.scale_factor());
let _ = window.request_inner_size(PhysicalSize::new(
(inner_size.0 / unit).round().max(1.0) as u32,
(inner_size.1 / unit).round().max(1.0) as u32,
));
window.set_outer_position(PhysicalPosition::new(
(position.0 / unit).round() as i32,
(position.1 / unit).round() as i32,
));
}
pub fn monitors(window: &Window) -> Vec<DesktopMonitor> {
#[cfg(target_os = "macos")]
{
let _ = window;
macos::monitors()
}
#[cfg(not(target_os = "macos"))]
{
#[cfg(all(unix, not(target_os = "macos")))]
let x11_work_area = x11_work_area(window);
window
.available_monitors()
.map(|monitor| {
let bounds = DesktopRect::from_pixels(
monitor.position(),
monitor.size(),
units_per_pixel(monitor.scale_factor()),
);
#[cfg(target_os = "windows")]
if let Some(read) = win32::monitor(&monitor) {
return read;
}
#[cfg(all(unix, not(target_os = "macos")))]
if let Some(work_area) = x11_work_area.and_then(|area| area.intersection(&bounds)) {
return DesktopMonitor { bounds, work_area };
}
DesktopMonitor {
bounds,
work_area: bounds,
}
})
.collect()
}
}
#[cfg(all(unix, not(target_os = "macos")))]
fn x11_work_area(window: &Window) -> Option<DesktopRect> {
use raw_window_handle::HasDisplayHandle;
use x11rb::protocol::xproto::{AtomEnum, ConnectionExt as _};
let display = window.display_handle().ok()?.as_raw();
if crate::window_system::window_system_for_display_handle(&display)
!= crate::window_system::WindowSystem::X11
{
return None;
}
let connection = crate::x11::X11Connection::open().ok()?;
let atom = |name: &[u8]| {
let reply = connection
.conn()
.intern_atom(true, name)
.ok()?
.reply()
.ok()?;
(reply.atom != x11rb::NONE).then_some(reply.atom)
};
let cardinals = |property| {
connection
.get_property_full(connection.root(), property, AtomEnum::CARDINAL.into())
.ok()
.flatten()
.map(|value| value.as_u32s())
};
let desktop = atom(b"_NET_CURRENT_DESKTOP")
.and_then(cardinals)
.and_then(|value| value.first().copied())
.unwrap_or(0) as usize;
let areas = cardinals(atom(b"_NET_WORKAREA")?)?;
let (areas, _) = areas.as_chunks::<4>();
let [x, y, width, height] = *areas.get(desktop).or_else(|| areas.first())?;
Some(DesktopRect {
x: x as i32 as f64,
y: y as i32 as f64,
width: width as f64,
height: height as f64,
})
}
#[cfg(target_os = "windows")]
mod win32 {
use windows::Win32::Foundation::RECT;
use windows::Win32::Graphics::Gdi::{GetMonitorInfoW, HMONITOR, MONITORINFO};
use winit::monitor::MonitorHandle;
use winit::platform::windows::MonitorHandleExtWindows;
use super::{DesktopMonitor, DesktopRect};
pub(super) fn monitor(monitor: &MonitorHandle) -> Option<DesktopMonitor> {
let mut info = MONITORINFO {
cbSize: std::mem::size_of::<MONITORINFO>() as u32,
..Default::default()
};
let read = unsafe { GetMonitorInfoW(HMONITOR(monitor.hmonitor() as *mut _), &mut info) };
read.as_bool().then(|| DesktopMonitor {
bounds: rect(info.rcMonitor),
work_area: rect(info.rcWork),
})
}
fn rect(rect: RECT) -> DesktopRect {
DesktopRect {
x: rect.left as f64,
y: rect.top as f64,
width: (rect.right - rect.left) as f64,
height: (rect.bottom - rect.top) as f64,
}
}
}
#[cfg(target_os = "macos")]
mod macos {
use objc2::MainThreadMarker;
use objc2_app_kit::NSScreen;
use objc2_core_graphics::{CGDisplayBounds, CGMainDisplayID};
use objc2_foundation::NSRect;
use super::{DesktopMonitor, DesktopRect};
pub(super) fn monitors() -> Vec<DesktopMonitor> {
let Some(mtm) = MainThreadMarker::new() else {
return Vec::new();
};
let main_height = CGDisplayBounds(CGMainDisplayID()).size.height;
let flip = |rect: NSRect| DesktopRect {
x: rect.origin.x,
y: main_height - rect.origin.y - rect.size.height,
width: rect.size.width,
height: rect.size.height,
};
NSScreen::screens(mtm)
.iter()
.map(|screen| DesktopMonitor {
bounds: flip(screen.frame()),
work_area: flip(screen.visibleFrame()),
})
.collect()
}
}
#[cfg(test)]
mod tests {
use super::DesktopRect;
fn rect(x: f64, y: f64, width: f64, height: f64) -> DesktopRect {
DesktopRect {
x,
y,
width,
height,
}
}
#[test]
fn overlap_is_the_shared_area_and_zero_apart() {
let screen = rect(0.0, 0.0, 100.0, 100.0);
assert_eq!(
screen.overlap_area(&rect(50.0, 80.0, 100.0, 100.0)),
50.0 * 20.0
);
assert_eq!(screen.overlap_area(&rect(100.0, 0.0, 10.0, 10.0)), 0.0);
assert_eq!(screen.overlap_area(&rect(-200.0, -200.0, 10.0, 10.0)), 0.0);
}
#[test]
fn intersection_is_none_apart() {
let screen = rect(0.0, 0.0, 100.0, 100.0);
assert_eq!(
screen.intersection(&rect(-10.0, 30.0, 50.0, 200.0)),
Some(rect(0.0, 30.0, 40.0, 70.0))
);
assert_eq!(screen.intersection(&rect(100.0, 0.0, 10.0, 10.0)), None);
}
}