use super::image_utils::bgra_to_rgba;
use super::Screen;
use image::RgbaImage;
use std::mem;
use std::ops::Deref;
use std::ptr;
use windows::core::PCWSTR;
use windows::Win32::Foundation::{BOOL, LPARAM, RECT};
use windows::Win32::Graphics::Gdi::{
CreateCompatibleBitmap, CreateCompatibleDC, CreateDCW, DeleteDC, DeleteObject,
EnumDisplayMonitors, GetDIBits, GetMonitorInfoW, GetObjectW, SelectObject,
SetStretchBltMode, StretchBlt, BITMAP, BITMAPINFO, BITMAPINFOHEADER, DIB_RGB_COLORS, HBITMAP,
HDC, HGDIOBJ, HMONITOR, MONITORINFOEXW, RGBQUAD, SRCCOPY, STRETCH_HALFTONE,
};
struct DropBox<T, F: FnMut(T)> {
value: Option<T>,
drop_fn: Option<F>,
}
impl<T, F: FnMut(T)> DropBox<T, F> {
fn new(value: T, drop_fn: F) -> Self {
Self { value: Some(value), drop_fn: Some(drop_fn) }
}
}
impl<T, F: FnMut(T)> Deref for DropBox<T, F> {
type Target = T;
fn deref(&self) -> &Self::Target {
self.value.as_ref().expect("DropBox consumed")
}
}
impl<T, F: FnMut(T)> Drop for DropBox<T, F> {
fn drop(&mut self) {
if let (Some(v), Some(mut f)) = (self.value.take(), self.drop_fn.take()) {
f(v);
}
}
}
struct MonitorEntry {
id: u32,
rect: RECT,
}
pub fn all() -> Result<Vec<Screen>, String> {
let collected: *mut Vec<MonitorEntry> = Box::into_raw(Box::default());
let lparam = LPARAM(collected as isize);
let _ = unsafe {
EnumDisplayMonitors(
HDC::default(),
None,
Some(monitor_enum_proc),
lparam,
)
};
let entries = unsafe { *Box::from_raw(collected) };
Ok(entries
.into_iter()
.map(|e| Screen {
id: e.id,
x: e.rect.left,
y: e.rect.top,
width: (e.rect.right - e.rect.left) as u32,
height: (e.rect.bottom - e.rect.top) as u32,
scale_factor: 1.0,
})
.collect())
}
extern "system" fn monitor_enum_proc(
h_monitor: HMONITOR,
_hdc: HDC,
_rect: *mut RECT,
state: LPARAM,
) -> BOOL {
let state = unsafe { &mut *(state.0 as *mut Vec<MonitorEntry>) };
match monitor_info(h_monitor) {
Some((id, rect)) => {
state.push(MonitorEntry { id, rect });
}
None => {} }
BOOL(1)
}
fn monitor_info(h_monitor: HMONITOR) -> Option<(u32, RECT)> {
unsafe {
let mut info: MONITORINFOEXW = mem::zeroed();
info.monitorInfo.cbSize = mem::size_of::<MONITORINFOEXW>() as u32;
let ptr = <*mut _>::cast(&mut info);
if GetMonitorInfoW(h_monitor, ptr).as_bool() {
let name = device_name(&info);
let id = fnv1a(name.as_bytes());
Some((id, info.monitorInfo.rcMonitor))
} else {
None
}
}
}
fn device_name(info: &MONITORINFOEXW) -> String {
let u16s: Vec<u16> = info.szDevice.iter().copied().take_while(|&c| c != 0).collect();
String::from_utf16_lossy(&u16s).trim_end_matches('\0').to_string()
}
fn fnv1a(bytes: &[u8]) -> u32 {
let mut h: u32 = 0x811c9dc5;
for &b in bytes {
h ^= b as u32;
h = h.wrapping_mul(0x01000193);
}
h
}
fn monitor_by_id(id: u32) -> Result<MONITORINFOEXW, String> {
let collected: *mut Vec<MONITORINFOEXW> = Box::into_raw(Box::default());
let lparam = LPARAM(collected as isize);
unsafe {
EnumDisplayMonitors(HDC::default(), None, Some(collect_raw_proc), lparam);
}
let infos = unsafe { *Box::from_raw(collected) };
infos
.into_iter()
.find(|i| fnv1a(device_name(i).as_bytes()) == id)
.ok_or_else(|| format!("monitor with id {id} not found"))
}
extern "system" fn collect_raw_proc(
h_monitor: HMONITOR,
_hdc: HDC,
_rect: *mut RECT,
state: LPARAM,
) -> BOOL {
let state = unsafe { &mut *(state.0 as *mut Vec<MONITORINFOEXW>) };
unsafe {
let mut info: MONITORINFOEXW = mem::zeroed();
info.monitorInfo.cbSize = mem::size_of::<MONITORINFOEXW>() as u32;
let ptr = <*mut _>::cast(&mut info);
if GetMonitorInfoW(h_monitor, ptr).as_bool() {
state.push(info);
}
}
BOOL(1)
}
pub fn capture(screen: &Screen) -> Result<RgbaImage, String> {
let w = (screen.width as f32 * screen.scale_factor) as i32;
let h = (screen.height as f32 * screen.scale_factor) as i32;
capture_rect(screen.id, 0, 0, w, h)
}
pub fn capture_area(
screen: &Screen,
x: i32,
y: i32,
width: u32,
height: u32,
) -> Result<RgbaImage, String> {
let w = (width as f32 * screen.scale_factor) as i32;
let h = (height as f32 * screen.scale_factor) as i32;
let ax = (x as f32 * screen.scale_factor) as i32;
let ay = (y as f32 * screen.scale_factor) as i32;
capture_rect(screen.id, ax, ay, w, h)
}
fn capture_rect(
display_id: u32,
x: i32,
y: i32,
width: i32,
height: i32,
) -> Result<RgbaImage, String> {
if width <= 0 || height <= 0 {
return Err(format!("invalid capture size {width}x{height}"));
}
let monitor_info = monitor_by_id(display_id)?;
let device = device_name(&monitor_info);
let mut wide: Vec<u16> = device.encode_utf16().collect();
wide.push(0);
#[allow(non_camel_case_types)]
type CreatedHDC = windows::Win32::Graphics::Gdi::CreatedHDC;
let src_dc = unsafe {
CreateDCW(
PCWSTR(wide.as_ptr()),
PCWSTR(wide.as_ptr()),
PCWSTR(ptr::null()),
None,
)
};
if HDC(src_dc.0).is_invalid() {
return Err("CreateDCW failed".to_string());
}
let src_dc = DropBox::new(src_dc, |dc| unsafe { let _ = DeleteDC(dc); });
let mem_dc = unsafe { CreateCompatibleDC(*src_dc) };
if HDC(mem_dc.0).is_invalid() {
return Err("CreateCompatibleDC failed".to_string());
}
let mem_dc = DropBox::new(mem_dc, |dc| unsafe { let _ = DeleteDC(dc); });
let bitmap = unsafe { CreateCompatibleBitmap(*src_dc, width, height) };
if bitmap.is_invalid() {
return Err("CreateCompatibleBitmap failed".to_string());
}
let old_bmp = unsafe { SelectObject(*mem_dc, bitmap) };
struct BitmapGuard {
mem_dc: HDC,
bitmap: HBITMAP,
old_bmp: HGDIOBJ,
}
impl Drop for BitmapGuard {
fn drop(&mut self) {
unsafe {
let _ = SelectObject(self.mem_dc, self.old_bmp);
let _ = DeleteObject(self.bitmap);
}
}
}
let _guard = BitmapGuard { mem_dc: HDC(mem_dc.0), bitmap, old_bmp };
unsafe {
SetStretchBltMode(*mem_dc, STRETCH_HALFTONE);
StretchBlt(*mem_dc, 0, 0, width, height, *src_dc, x, y, width, height, SRCCOPY);
}
let mut info = BITMAPINFO {
bmiHeader: BITMAPINFOHEADER {
biSize: mem::size_of::<BITMAPINFOHEADER>() as u32,
biWidth: width,
biHeight: height,
biPlanes: 1,
biBitCount: 32,
biCompression: 0,
biSizeImage: 0,
biXPelsPerMeter: 0,
biYPelsPerMeter: 0,
biClrUsed: 0,
biClrImportant: 0,
},
bmiColors: [RGBQUAD::default(); 1],
};
let mut data = vec![0u8; (width as usize) * (height as usize) * 4];
let copied = unsafe {
GetDIBits(
*mem_dc,
_guard.bitmap,
0,
height as u32,
Some(data.as_mut_ptr() as *mut _),
&mut info,
DIB_RGB_COLORS,
)
};
if copied == 0 {
return Err("GetDIBits returned 0".to_string());
}
let row_len = (width as usize) * 4;
let mut rows: Vec<Vec<u8>> = data.chunks(row_len).map(|r| r.to_vec()).collect();
rows.reverse();
let flat: Vec<u8> = rows.into_iter().flatten().collect();
let mut bmp: BITMAP = unsafe { mem::zeroed() };
unsafe {
GetObjectW(
_guard.bitmap,
mem::size_of::<BITMAP>() as i32,
Some(<*mut _>::cast(&mut bmp)),
);
}
let real_w = bmp.bmWidth.max(width) as u32;
let real_h = bmp.bmHeight.max(height) as u32;
bgra_to_rgba(real_w, real_h, flat)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn fnv1a_is_stable() {
let d1: &[u8] = &[b'\\', b'\\', b'.', b'\\', b'D', b'I', b'S', b'P', b'L', b'A', b'Y', b'1'];
let d2: &[u8] = &[b'\\', b'\\', b'.', b'\\', b'D', b'I', b'S', b'P', b'L', b'A', b'Y', b'2'];
assert_eq!(fnv1a(d1), fnv1a(d1));
assert_ne!(fnv1a(d1), fnv1a(d2));
}
#[test]
fn device_name_basic() {
let mut info: MONITORINFOEXW = unsafe { mem::zeroed() };
let s: Vec<u16> = "\\\\.\\DISPLAY1".encode_utf16().collect();
for (i, c) in s.iter().enumerate() {
info.szDevice[i] = *c;
}
assert_eq!(device_name(&info), "\\\\.\\DISPLAY1");
}
}