use bevy_ecs::lifecycle::HookContext;
use bevy_ecs::prelude::*;
use bevy_ecs::world::DeferredWorld;
use std::fmt;
use tracing::warn;
use windows::Win32::Foundation::{LPARAM, RECT};
use windows::Win32::Graphics::Gdi::{
EnumDisplayMonitors, GetMonitorInfoW, HMONITOR, MONITORINFOEXW,
};
use windows::Win32::UI::HiDpi::{GetDpiForMonitor, MDT_EFFECTIVE_DPI};
use windows_core::BOOL;
fn on_monitor_add(mut world: DeferredWorld, context: HookContext) {
let entity = context.entity;
if world
.get::<crate::ecs::layout::Arrangement>(entity)
.is_none()
{
world
.commands()
.entity(entity)
.insert(crate::ecs::layout::Arrangement::default());
}
}
#[derive(Component, Clone)]
#[component(on_add = on_monitor_add)]
pub struct Monitor {
pub handle: isize,
pub bounds: RECT,
pub work_area: RECT,
pub dpi: u32,
pub is_primary: bool,
}
impl fmt::Debug for Monitor {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Monitor")
.field("handle", &self.handle)
.field(
"bounds",
&format_args!(
"({},{},{},{})",
self.bounds.left, self.bounds.top, self.bounds.right, self.bounds.bottom
),
)
.field(
"work_area",
&format_args!(
"({},{},{},{})",
self.work_area.left,
self.work_area.top,
self.work_area.right,
self.work_area.bottom
),
)
.field("dpi", &self.dpi)
.field("is_primary", &self.is_primary)
.finish()
}
}
impl PartialEq for Monitor {
fn eq(&self, other: &Self) -> bool {
self.handle == other.handle
}
}
impl Monitor {
pub fn from_hmonitor(hmonitor: HMONITOR) -> Result<Self, MonitorError> {
unsafe {
let mut monitor_info: MONITORINFOEXW = std::mem::zeroed();
monitor_info.monitorInfo.cbSize = std::mem::size_of::<MONITORINFOEXW>() as u32;
let success =
GetMonitorInfoW(hmonitor, &mut monitor_info.monitorInfo as *mut _ as *mut _);
if !success.as_bool() {
return Err(MonitorError::GetMonitorInfoFailed);
}
let mut dpi_x: u32 = 0;
let mut dpi_y: u32 = 0;
let hr = GetDpiForMonitor(hmonitor, MDT_EFFECTIVE_DPI, &mut dpi_x, &mut dpi_y);
if hr.is_err() {
return Err(MonitorError::GetDpiFailed);
}
Ok(Monitor {
handle: hmonitor.0 as isize,
bounds: monitor_info.monitorInfo.rcMonitor,
work_area: monitor_info.monitorInfo.rcWork,
dpi: dpi_x,
is_primary: (monitor_info.monitorInfo.dwFlags & 1) != 0,
})
}
}
pub fn physical_size(&self) -> (f32, f32) {
let width = (self.bounds.right - self.bounds.left) as f32;
let height = (self.bounds.bottom - self.bounds.top) as f32;
(width, height)
}
pub fn top_left(&self) -> (f32, f32) {
(self.bounds.left as f32, self.bounds.top as f32)
}
}
#[derive(Debug, Clone, Copy)]
pub enum MonitorError {
GetMonitorInfoFailed,
GetDpiFailed,
}
impl fmt::Display for MonitorError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
MonitorError::GetMonitorInfoFailed => write!(f, "GetMonitorInfoW failed"),
MonitorError::GetDpiFailed => write!(f, "GetDpiForMonitor failed"),
}
}
}
impl std::error::Error for MonitorError {}
pub fn enumerate_monitors() -> Vec<Monitor> {
let mut monitors = Vec::new();
extern "system" fn enum_proc(
hmonitor: HMONITOR,
_hdc: windows::Win32::Graphics::Gdi::HDC,
_rect: *mut RECT,
lparam: LPARAM,
) -> BOOL {
let monitors = unsafe { &mut *(lparam.0 as *mut Vec<Monitor>) };
if let Ok(monitor) = Monitor::from_hmonitor(hmonitor) {
monitors.push(monitor);
} else {
warn!(hmonitor = ?hmonitor, "Failed to get monitor info");
}
BOOL(1)
}
let monitors_ptr = &mut monitors as *mut Vec<Monitor> as isize;
let result = unsafe { EnumDisplayMonitors(None, None, Some(enum_proc), LPARAM(monitors_ptr)) };
if !result.as_bool() {
warn!("EnumDisplayMonitors failed");
return Vec::new();
}
monitors
}