use crate::capture::Capture;
use crate::frame::Frame;
use crate::{Error, Result};
use std::ffi::c_void;
use windows::core::PCWSTR;
use windows::Win32::Foundation::{BOOL, HWND, RECT};
use windows::Win32::Graphics::Gdi::{
CreateCompatibleBitmap, CreateCompatibleDC, DeleteDC, DeleteObject, GetDC, GetDIBits,
ReleaseDC, SelectObject, BITMAPINFO, BITMAPINFOHEADER, DIB_RGB_COLORS, HBITMAP, HDC, HGDIOBJ,
};
use windows::Win32::Storage::Xps::{PrintWindow, PRINT_WINDOW_FLAGS};
use windows::Win32::UI::WindowsAndMessaging::{FindWindowW, GetClientRect, PW_RENDERFULLCONTENT};
pub type WindowHandle = isize;
fn to_hwnd(hwnd: WindowHandle) -> HWND {
HWND(hwnd)
}
struct PrintCap {
hwnd: HWND,
hdc_win: HDC,
hdc_mem: HDC,
hbmp: HBITMAP,
old: HGDIOBJ,
w: usize,
h: usize,
bmi: BITMAPINFO,
}
impl PrintCap {
fn new(hwnd: WindowHandle) -> Self {
unsafe {
let hw = to_hwnd(hwnd);
let hdc_win = GetDC(hw);
let hdc_mem = CreateCompatibleDC(hdc_win);
let (w, h) = client_size(hw);
let hbmp = CreateCompatibleBitmap(hdc_win, w.max(1) as i32, h.max(1) as i32);
let old = SelectObject(hdc_mem, hbmp);
Self {
hwnd: hw,
hdc_win,
hdc_mem,
hbmp,
old,
w,
h,
bmi: make_bmi(w.max(1), h.max(1)),
}
}
}
fn resize_if_needed(&mut self) {
let (w, h) = client_size(self.hwnd);
if w == self.w && h == self.h {
return;
}
unsafe {
let new_hbmp = CreateCompatibleBitmap(self.hdc_win, w.max(1) as i32, h.max(1) as i32);
SelectObject(self.hdc_mem, new_hbmp);
let _ = DeleteObject(self.hbmp);
self.hbmp = new_hbmp;
self.bmi = make_bmi(w.max(1), h.max(1));
}
self.w = w;
self.h = h;
}
fn grab_into(&mut self, dst: &mut Vec<u8>) -> Result<()> {
self.resize_if_needed();
if self.w == 0 || self.h == 0 {
return Err(Error::Capture(
"window has an empty client area (minimized?)".into(),
));
}
unsafe {
let ok: BOOL = PrintWindow(
self.hwnd,
self.hdc_mem,
PRINT_WINDOW_FLAGS(PW_RENDERFULLCONTENT),
);
if !ok.as_bool() {
return Err(Error::Capture("PrintWindow failed".into()));
}
let ret = GetDIBits(
self.hdc_mem,
self.hbmp,
0,
self.h as u32,
Some(dst.as_mut_ptr() as *mut c_void),
&mut self.bmi,
DIB_RGB_COLORS,
);
if ret == 0 {
return Err(Error::Capture("GetDIBits failed".into()));
}
}
Ok(())
}
}
impl Drop for PrintCap {
fn drop(&mut self) {
unsafe {
SelectObject(self.hdc_mem, self.old);
let _ = DeleteObject(self.hbmp);
let _ = DeleteDC(self.hdc_mem);
ReleaseDC(self.hwnd, self.hdc_win);
}
}
}
fn client_size(hwnd: HWND) -> (usize, usize) {
unsafe {
let mut rc = RECT::default();
if GetClientRect(hwnd, &mut rc).is_err() {
return (0, 0);
}
(
(rc.right - rc.left).max(0) as usize,
(rc.bottom - rc.top).max(0) as usize,
)
}
}
fn make_bmi(w: usize, h: usize) -> BITMAPINFO {
unsafe {
let mut bmi: BITMAPINFO = std::mem::zeroed();
bmi.bmiHeader.biSize = std::mem::size_of::<BITMAPINFOHEADER>() as u32;
bmi.bmiHeader.biWidth = w as i32;
bmi.bmiHeader.biHeight = -(h as i32); bmi.bmiHeader.biPlanes = 1;
bmi.bmiHeader.biBitCount = 32;
bmi.bmiHeader.biCompression = 0; bmi
}
}
pub struct WindowCapture {
inner: PrintCap,
}
impl WindowCapture {
pub fn new(hwnd: WindowHandle) -> Self {
WindowCapture {
inner: PrintCap::new(hwnd),
}
}
pub fn from_title(title: &str) -> Result<Self> {
let wide: Vec<u16> = title.encode_utf16().chain(std::iter::once(0)).collect();
unsafe {
let hw = FindWindowW(PCWSTR::null(), PCWSTR(wide.as_ptr()));
if hw.0 == 0 {
return Err(Error::Capture(format!(
"no top-level window with title {title:?}"
)));
}
Ok(Self::new(hw.0 as isize))
}
}
pub fn size(&self) -> (usize, usize) {
(self.inner.w, self.inner.h)
}
}
impl Capture for WindowCapture {
fn grab(&mut self) -> Result<Frame> {
self.inner.resize_if_needed();
let (w, h) = (self.inner.w, self.inner.h);
if w == 0 || h == 0 {
return Err(Error::Capture(
"window has an empty client area (minimized?)".into(),
));
}
let mut pixels = vec![0u8; w * h * 4];
self.inner.grab_into(&mut pixels)?;
Ok(Frame::bgra8(w, h, pixels))
}
fn grab_into(&mut self, dst: &mut Frame) -> Result<bool> {
self.inner.resize_if_needed();
let (w, h) = (self.inner.w, self.inner.h);
if w == 0 || h == 0 {
return Err(Error::Capture(
"window has an empty client area (minimized?)".into(),
));
}
dst.prepare_bgra(w, h);
self.inner.grab_into(&mut dst.pixels)?;
Ok(true) }
fn backend(&self) -> &'static str {
"print-window"
}
}