use core::ptr;
use crate::{MonitorId, MonitorInfo};
use super::ffi::{self, Bool32, DevModeW, Handle, Lparam, MonitorInfoExW, Rect, value};
use super::geometry;
use super::shell::Win32Shell;
unsafe extern "system" fn collect(
monitor: Handle,
_hdc: Handle,
_clip: *mut Rect,
data: Lparam,
) -> Bool32 {
let handles = unsafe { &mut *(data as *mut Vec<Handle>) };
handles.push(monitor);
1
}
pub(super) fn device_name(monitor: Handle) -> Option<String> {
let mut info = MonitorInfoExW::default();
let ok = unsafe { ffi::GetMonitorInfoW(monitor, &raw mut info) };
if ok == 0 {
return None;
}
Some(wide_to_string(&info.sz_device))
}
fn wide_to_string(buffer: &[u16]) -> String {
let end = buffer
.iter()
.position(|unit| *unit == 0)
.unwrap_or(buffer.len());
String::from_utf16_lossy(&buffer[..end])
}
impl Win32Shell {
pub(super) fn enumerate_monitors(&mut self) -> Vec<MonitorInfo> {
let mut handles: Vec<Handle> = Vec::new();
unsafe {
ffi::EnumDisplayMonitors(
ptr::null_mut(),
ptr::null(),
collect,
(&raw mut handles) as Lparam,
);
}
let mut monitors = Vec::with_capacity(handles.len());
let exact_refreshes = exact_refreshes();
for handle in handles {
let mut info = MonitorInfoExW::default();
if unsafe { ffi::GetMonitorInfoW(handle, &raw mut info) } == 0 {
continue;
}
let device = wide_to_string(&info.sz_device);
let bounds = geometry::desktop_rect(info.rc_monitor);
monitors.push(MonitorInfo {
id: self.monitor_id_for(&device),
scale_factor: geometry::scale_from_dpi(dpi_of(handle)),
refresh_millihertz: refresh_of(&exact_refreshes, &info.sz_device),
is_primary: info.dw_flags & value::MONITOR_PRIMARY != 0,
bounds,
work_area: geometry::desktop_rect(info.rc_work),
name: device,
});
}
if !monitors.iter().any(|monitor| monitor.is_primary)
&& let Some(first) = monitors.first_mut()
{
first.is_primary = true;
}
monitors
}
fn monitor_id_for(&mut self, device: &str) -> MonitorId {
if let Some(id) = self.monitor_id_of(device) {
return id;
}
let id = MonitorId(self.next_monitor_id);
self.next_monitor_id += 1;
self.monitor_ids.push((device.to_string(), id));
id
}
}
fn dpi_of(monitor: Handle) -> u32 {
let mut dpi_x = 0u32;
let mut dpi_y = 0u32;
let result = unsafe {
ffi::GetDpiForMonitor(
monitor,
value::MDT_EFFECTIVE_DPI,
&raw mut dpi_x,
&raw mut dpi_y,
)
};
if result < 0 || dpi_x == 0 {
return value::DEFAULT_DPI;
}
dpi_x
}
fn refresh_of(table: &[(String, u32)], device: &[u16; 32]) -> u32 {
let name = wide_to_string(device);
table
.iter()
.find(|(entry, _)| *entry == name)
.map(|(_, millihertz)| *millihertz)
.unwrap_or_else(|| integer_refresh_of(device))
}
const MIN_PLAUSIBLE_REFRESH_MHZ: u32 = 10_000;
fn exact_refreshes() -> Vec<(String, u32)> {
let mut path_count = 0u32;
let mut mode_count = 0u32;
let status = unsafe {
ffi::GetDisplayConfigBufferSizes(
value::QDC_ONLY_ACTIVE_PATHS,
&raw mut path_count,
&raw mut mode_count,
)
};
if status != value::ERROR_SUCCESS || path_count == 0 {
crcbl_core::log::warn!(
"win32: GetDisplayConfigBufferSizes failed ({status}); monitor refresh falls back to whole hertz"
);
return Vec::new();
}
let mut paths = vec![ffi::DisplayConfigPathInfo::default(); path_count as usize];
let mut modes = vec![ffi::DisplayConfigModeInfo::default(); mode_count as usize];
let status = unsafe {
ffi::QueryDisplayConfig(
value::QDC_ONLY_ACTIVE_PATHS,
&raw mut path_count,
paths.as_mut_ptr(),
&raw mut mode_count,
modes.as_mut_ptr(),
core::ptr::null_mut(),
)
};
if status != value::ERROR_SUCCESS {
crcbl_core::log::warn!(
"win32: QueryDisplayConfig failed ({status}); monitor refresh falls back to whole hertz"
);
return Vec::new();
}
paths.truncate(path_count as usize);
modes.truncate(mode_count as usize);
let mut refreshes = Vec::new();
for path in &paths {
let Some(mode_index) = path.target_info.target_mode_index() else {
continue;
};
let Some(mode) = modes.get(mode_index) else {
continue;
};
if mode.info_type != value::DISPLAYCONFIG_MODE_INFO_TYPE_TARGET {
continue;
}
let signal = mode.target_mode.target_video_signal_info;
let divider = (signal.additional_signal_info >> 16) & 0x3F;
let Some(millihertz) = signal.v_sync_freq.millihertz(divider) else {
continue;
};
if millihertz < MIN_PLAUSIBLE_REFRESH_MHZ {
continue;
}
let mut request = ffi::DisplayConfigSourceDeviceName {
header: ffi::DisplayConfigDeviceInfoHeader {
kind: value::DISPLAYCONFIG_DEVICE_INFO_GET_SOURCE_NAME,
size: core::mem::size_of::<ffi::DisplayConfigSourceDeviceName>() as u32,
adapter_id: path.source_info.adapter_id,
id: path.source_info.id,
},
..ffi::DisplayConfigSourceDeviceName::default()
};
let status = unsafe { ffi::DisplayConfigGetDeviceInfo(&raw mut request.header) };
if status != value::ERROR_SUCCESS {
continue;
}
let name = wide_to_string(&request.view_gdi_device_name);
if name.is_empty() {
continue;
}
crcbl_core::log::info!("win32: exact refresh for {name}: {millihertz} mHz");
refreshes.push((name, millihertz));
}
refreshes
}
fn integer_refresh_of(device: &[u16; 32]) -> u32 {
let mut mode = DevModeW::default();
let ok = unsafe {
ffi::EnumDisplaySettingsW(device.as_ptr(), value::ENUM_CURRENT_SETTINGS, &raw mut mode)
};
if ok == 0 {
return 0;
}
if mode.dm_display_frequency <= 1 {
return 0;
}
mode.dm_display_frequency.saturating_mul(1_000)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_device_name_stops_at_the_first_nul() {
let mut buffer = [0u16; 32];
for (slot, unit) in buffer.iter_mut().zip(r"\\.\DISPLAY1".encode_utf16()) {
*slot = unit;
}
buffer[20] = u16::from(b'X');
assert_eq!(wide_to_string(&buffer), r"\\.\DISPLAY1");
let full = [u16::from(b'A'); 32];
assert_eq!(wide_to_string(&full).len(), 32);
assert_eq!(wide_to_string(&[0u16; 32]), "");
}
}