#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
#[error("{0}")]
pub struct NoDesktop(pub String);
pub fn check() -> Result<(), NoDesktop> {
imp::check()
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Monitor {
pub x: f32,
pub y: f32,
pub width: f32,
pub height: f32,
pub scale: f32,
}
pub const MAX_MONITORS: usize = 64;
pub fn monitors() -> Vec<Monitor> {
screens::monitors()
}
pub fn flip_y(x: f64, y: f64, width: f64, height: f64, primary_height: f64, scale: f64) -> Option<Monitor> {
let m = Monitor { x: x as f32, y: (primary_height - y - height) as f32, width: width as f32, height: height as f32, scale: scale as f32 };
let ok = [m.x, m.y, m.width, m.height, m.scale].iter().all(|v| v.is_finite()) && m.width >= 1.0 && m.height >= 1.0 && m.scale > 0.0;
ok.then_some(m)
}
#[cfg(target_os = "macos")]
mod screens {
use objc2::MainThreadMarker;
use objc2_app_kit::NSScreen;
pub fn monitors() -> Vec<super::Monitor> {
let Some(mtm) = MainThreadMarker::new() else { return Vec::new() };
let screens = NSScreen::screens(mtm);
let Some(primary) = screens.iter().next().map(|s| s.frame().size.height) else { return Vec::new() };
screens
.iter()
.take(super::MAX_MONITORS)
.filter_map(|s| {
let f = s.frame();
super::flip_y(f.origin.x, f.origin.y, f.size.width, f.size.height, primary, s.backingScaleFactor())
})
.collect()
}
}
#[cfg(target_os = "windows")]
#[allow(unsafe_code)]
mod screens {
use windows::Win32::Foundation::{LPARAM, RECT};
use windows::Win32::Graphics::Gdi::{EnumDisplayMonitors, GetMonitorInfoW, HDC, HMONITOR, MONITORINFO};
use windows::Win32::UI::HiDpi::{GetDpiForMonitor, MDT_EFFECTIVE_DPI};
use windows::core::BOOL;
const MONITORINFOF_PRIMARY: u32 = 1;
type Found = Vec<(super::Monitor, bool)>;
pub fn monitors() -> Vec<super::Monitor> {
let mut found: Found = Vec::new();
let _ = unsafe { EnumDisplayMonitors(None, None, Some(collect), LPARAM((&raw mut found) as isize)) };
found.sort_by_key(|(_, primary)| !primary);
found.into_iter().map(|(m, _)| m).collect()
}
unsafe extern "system" fn collect(monitor: HMONITOR, _: HDC, _: *mut RECT, data: LPARAM) -> BOOL {
let Some(found) = (unsafe { (data.0 as *mut Found).as_mut() }) else { return false.into() };
if found.len() >= super::MAX_MONITORS {
return false.into();
}
let mut info = MONITORINFO { cbSize: std::mem::size_of::<MONITORINFO>() as u32, ..Default::default() };
if !unsafe { GetMonitorInfoW(monitor, &raw mut info) }.as_bool() {
return true.into();
}
let (mut dx, mut dy) = (0u32, 0u32);
let dpi = unsafe { GetDpiForMonitor(monitor, MDT_EFFECTIVE_DPI, &raw mut dx, &raw mut dy) }.ok().map_or(96, |()| dx);
let scale = if dpi == 0 { 1.0 } else { dpi as f32 / 96.0 };
let r = info.rcMonitor;
let (w, h) = (r.right.saturating_sub(r.left) as f32, r.bottom.saturating_sub(r.top) as f32);
let m = super::Monitor { x: r.left as f32 / scale, y: r.top as f32 / scale, width: w / scale, height: h / scale, scale };
if m.width >= 1.0 && m.height >= 1.0 {
found.push((m, info.dwFlags & MONITORINFOF_PRIMARY != 0));
}
true.into()
}
}
#[cfg(not(any(target_os = "macos", target_os = "windows")))]
mod screens {
pub fn monitors() -> Vec<super::Monitor> {
Vec::new()
}
}
pub const WSF_VISIBLE: u32 = 0x0001;
pub fn from_window_station_flags(flags: Option<u32>) -> Result<(), NoDesktop> {
match flags {
Some(f) if f & WSF_VISIBLE == 0 => Err(NoDesktop(
"this program was started outside the signed-in desktop (by a service, a scheduled task that runs whether or not the user is signed in, or a remote shell running as SYSTEM), where Windows shows no windows. Start it from the desktop, or from a task that runs only when the user is signed in"
.into(),
)),
_ => Ok(()),
}
}
#[cfg(target_os = "windows")]
#[allow(unsafe_code)]
mod imp {
use windows::Win32::Foundation::HANDLE;
use windows::Win32::System::StationsAndDesktops::{GetProcessWindowStation, GetUserObjectInformationW, UOI_FLAGS, USEROBJECTFLAGS};
pub fn check() -> Result<(), super::NoDesktop> {
super::from_window_station_flags(flags())
}
fn flags() -> Option<u32> {
let station = unsafe { GetProcessWindowStation() }.ok()?;
let mut info = USEROBJECTFLAGS::default();
let size = std::mem::size_of::<USEROBJECTFLAGS>() as u32;
unsafe { GetUserObjectInformationW(HANDLE(station.0), UOI_FLAGS, Some((&raw mut info).cast()), size, None) }.ok()?;
Some(info.dwFlags)
}
}
#[cfg(not(target_os = "windows"))]
mod imp {
pub fn check() -> Result<(), super::NoDesktop> {
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_hidden_window_station_has_no_desktop() {
let e = from_window_station_flags(Some(0)).err();
assert!(e.is_some_and(|e| e.0.contains("outside the signed-in desktop")));
assert!(from_window_station_flags(Some(0x0002)).is_err());
}
#[test]
fn the_interactive_window_station_and_unknown_flags_pass() {
assert_eq!(from_window_station_flags(Some(WSF_VISIBLE)), Ok(()));
assert_eq!(from_window_station_flags(Some(WSF_VISIBLE | 0x0100)), Ok(()));
assert_eq!(from_window_station_flags(None), Ok(()));
}
#[test]
fn mac_frames_flip_to_y_down() {
assert_eq!(flip_y(0.0, 0.0, 1440.0, 900.0, 900.0, 2.0), Some(Monitor { x: 0.0, y: 0.0, width: 1440.0, height: 900.0, scale: 2.0 }));
let above = flip_y(-1920.0, 900.0, 1920.0, 1080.0, 900.0, 1.0);
assert_eq!(above, Some(Monitor { x: -1920.0, y: -1080.0, width: 1920.0, height: 1080.0, scale: 1.0 }));
assert_eq!(flip_y(0.0, 0.0, 0.0, 900.0, 900.0, 1.0), None);
assert_eq!(flip_y(f64::NAN, 0.0, 10.0, 10.0, 900.0, 1.0), None);
}
#[test]
fn monitors_off_the_main_thread_are_empty_not_a_crash() {
let off = std::thread::spawn(monitors).join().unwrap_or_default();
if cfg!(target_os = "macos") {
assert!(off.is_empty());
}
assert!(off.len() <= MAX_MONITORS);
}
#[test]
fn this_test_process_can_show_windows_or_says_why() {
if let Err(e) = check() {
assert!(!e.0.is_empty());
}
}
}