#[cfg(target_os = "linux")]
pub fn winit_monitor_rects() -> Option<Vec<(i32, i32, u32, u32)>> {
crate::x11_display::winit_monitor_rects()
}
#[cfg(target_os = "windows")]
pub fn winit_monitor_rects() -> Option<Vec<(i32, i32, u32, u32)>> {
use windows::Win32::Foundation::{LPARAM, RECT};
use windows::Win32::Graphics::Gdi::{
EnumDisplayMonitors, GetMonitorInfoW, HDC, HMONITOR, MONITORINFO,
};
unsafe extern "system" fn collect(
monitor: HMONITOR,
_hdc: HDC,
_clip: *mut RECT,
data: LPARAM,
) -> windows::core::BOOL {
let out = unsafe { &mut *(data.0 as *mut Vec<Option<(i32, i32, u32, u32)>>) };
let mut info = MONITORINFO {
cbSize: size_of::<MONITORINFO>() as u32,
..Default::default()
};
let rect = if unsafe { GetMonitorInfoW(monitor, &mut info) }.as_bool() {
let r = info.rcMonitor;
Some((
r.left,
r.top,
(r.right - r.left).max(0) as u32,
(r.bottom - r.top).max(0) as u32,
))
} else {
None
};
out.push(rect);
true.into()
}
let mut out: Vec<Option<(i32, i32, u32, u32)>> = Vec::new();
let ok = unsafe {
EnumDisplayMonitors(
None,
None,
Some(collect),
LPARAM(std::ptr::from_mut(&mut out) as isize),
)
};
if !ok.as_bool() || out.is_empty() {
return None;
}
Some(
out.into_iter()
.map(|r| r.unwrap_or((i32::MIN, i32::MIN, 0, 0)))
.collect(),
)
}
#[cfg(target_os = "windows")]
pub fn winit_monitor_handles() -> Option<Vec<usize>> {
use windows::Win32::Foundation::{LPARAM, RECT};
use windows::Win32::Graphics::Gdi::{EnumDisplayMonitors, HDC, HMONITOR};
unsafe extern "system" fn collect(
monitor: HMONITOR,
_hdc: HDC,
_clip: *mut RECT,
data: LPARAM,
) -> windows::core::BOOL {
let out = unsafe { &mut *(data.0 as *mut Vec<usize>) };
out.push(monitor.0 as usize);
true.into()
}
let mut out: Vec<usize> = Vec::new();
let ok = unsafe {
EnumDisplayMonitors(
None,
None,
Some(collect),
LPARAM(std::ptr::from_mut(&mut out) as isize),
)
};
(ok.as_bool() && !out.is_empty()).then_some(out)
}
#[cfg(not(target_os = "windows"))]
pub fn winit_monitor_handles() -> Option<Vec<usize>> {
None
}
#[must_use]
pub fn handle_indices(sdl: &[usize], winit: &[usize]) -> Vec<usize> {
sdl.iter()
.enumerate()
.map(|(i, h)| winit.iter().position(|w| w == h).unwrap_or(i))
.collect()
}
#[cfg(target_os = "macos")]
pub fn winit_monitor_rects() -> Option<Vec<(i32, i32, u32, u32)>> {
use objc2_core_graphics::{CGDisplayBounds, CGError, CGGetActiveDisplayList};
let mut count: u32 = 0;
let sized = unsafe { CGGetActiveDisplayList(0, std::ptr::null_mut(), &mut count) };
if sized != CGError::Success || count == 0 {
return None;
}
let mut ids = vec![0u32; count as usize];
let filled = unsafe { CGGetActiveDisplayList(count, ids.as_mut_ptr(), &mut count) };
if filled != CGError::Success {
return None;
}
ids.truncate(count as usize);
Some(
ids.into_iter()
.map(|id| {
let b = CGDisplayBounds(id);
(
b.origin.x as i32,
b.origin.y as i32,
b.size.width.max(0.0) as u32,
b.size.height.max(0.0) as u32,
)
})
.collect(),
)
}
pub fn monitor_indices(
displays: &[(i32, i32, i32, i32)],
winit: &[(i32, i32, u32, u32)],
) -> Vec<usize> {
let mut out: Vec<Option<usize>> = displays
.iter()
.map(|&(x, y, w, h)| {
winit.iter().position(|&(wx, wy, ww, wh)| {
wx == x && wy == y && i64::from(ww) == i64::from(w) && i64::from(wh) == i64::from(h)
})
})
.collect();
let mut left: Vec<usize> = (0..displays.len()).filter(|&i| out[i].is_none()).collect();
let mut free: Vec<usize> = (0..winit.len())
.filter(|j| !out.contains(&Some(*j)))
.collect();
if !left.is_empty() && left.len() == free.len() {
left.sort_by_key(|&i| (displays[i].0, displays[i].1));
free.sort_by_key(|&j| (winit[j].0, winit[j].1));
for (i, j) in left.into_iter().zip(free) {
out[i] = Some(j);
}
}
out.into_iter()
.enumerate()
.map(|(i, j)| j.unwrap_or(i))
.collect()
}
#[cfg(test)]
mod tests {
use super::{handle_indices, monitor_indices};
#[test]
fn follows_winit_order_not_sdl_order() {
let sdl = [(1920, 0, 1920, 1080), (0, 0, 1920, 1080)];
let winit = [(0, 0, 1920, 1080), (1920, 0, 1920, 1080)];
assert_eq!(monitor_indices(&sdl, &winit), vec![1, 0]);
}
#[test]
fn same_order_is_identity() {
let sdl = [
(0, 0, 3840, 2160),
(3840, 0, 1920, 1080),
(5760, 0, 1280, 390),
];
let winit = [
(0, 0, 3840, 2160),
(3840, 0, 1920, 1080),
(5760, 0, 1280, 390),
];
assert_eq!(monitor_indices(&sdl, &winit), vec![0, 1, 2]);
}
#[test]
fn unmatched_display_keeps_its_index() {
let sdl = [(0, 0, 1920, 1080), (1920, 0, 1024, 768)];
let winit = [(0, 0, 1920, 1080)];
assert_eq!(monitor_indices(&sdl, &winit), vec![0, 1]);
}
#[test]
fn windows_primary_enumerated_last() {
let sdl = [
(0, 0, 3840, 2160), (5760, 0, 1920, 1080), (3840, 0, 1920, 1080), ];
let winit = [
(5760, 0, 1920, 1080),
(3840, 0, 1920, 1080),
(0, 0, 3840, 2160),
];
assert_eq!(monitor_indices(&sdl, &winit), vec![2, 0, 1]);
}
#[test]
fn scaled_layout_pairs_by_position() {
let sdl = [
(0, 0, 2560, 1440),
(4480, 0, 1920, 1080),
(2560, 0, 1920, 1080),
];
let winit = [
(5760, 0, 1920, 1080),
(3840, 0, 1920, 1080),
(0, 0, 3840, 2160),
];
assert_eq!(monitor_indices(&sdl, &winit), vec![2, 0, 1]);
}
#[test]
fn one_scaled_screen_among_exact_ones() {
let sdl = [
(0, 0, 2560, 1440),
(5760, 0, 1920, 1080),
(3840, 0, 1920, 1080),
];
let winit = [
(5760, 0, 1920, 1080),
(3840, 0, 1920, 1080),
(0, 0, 3840, 2160),
];
assert_eq!(monitor_indices(&sdl, &winit), vec![2, 0, 1]);
}
#[test]
fn scaled_vertical_stack() {
let sdl = [(0, 0, 1280, 720), (0, -720, 1280, 720)];
let winit = [(0, -1080, 1920, 1080), (0, 0, 1920, 1080)];
assert_eq!(monitor_indices(&sdl, &winit), vec![1, 0]);
}
#[test]
fn handles_join_exactly_whatever_the_rects() {
let (lg, monitor, dell) = (0x10, 0x20, 0x30);
assert_eq!(
handle_indices(&[lg, monitor, dell], &[monitor, dell, lg]),
vec![2, 0, 1]
);
assert_eq!(handle_indices(&[lg, 0x99], &[lg]), vec![0, 1]);
}
#[test]
fn leftover_count_mismatch_keeps_indices() {
let sdl = [(0, 0, 2560, 1440), (2560, 0, 1280, 720)];
let winit = [(0, 0, 3840, 2160)];
assert_eq!(monitor_indices(&sdl, &winit), vec![0, 1]);
}
}