//! GDI screen capture fallback //! //! Uses Windows GDI (Graphics Device Interface) for screen capture. //! Slower than DXGI but works on older systems and edge cases. use super::{CapturedFrame, Capturer, Display}; use anyhow::Result; use std::time::Instant; use windows::Win32::Foundation::HWND; use windows::Win32::Graphics::Gdi::{ BitBlt, CreateCompatibleBitmap, CreateCompatibleDC, DeleteDC, DeleteObject, GetDC, GetDIBits, ReleaseDC, SelectObject, BITMAPINFO, BITMAPINFOHEADER, BI_RGB, DIB_RGB_COLORS, SRCCOPY, }; /// GDI-based screen capturer pub struct GdiCapturer { display: Display, width: u32, height: u32, } impl GdiCapturer { /// Create a new GDI capturer for the specified display pub fn new(display: Display) -> Result { Ok(Self { width: display.width, height: display.height, display, }) } /// Capture the screen using GDI fn capture_gdi(&self) -> Result> { unsafe { // Get device context for the entire screen let screen_dc = GetDC(HWND::default()); if screen_dc.is_invalid() { anyhow::bail!("Failed to get screen DC"); } // Create compatible DC and bitmap let mem_dc = CreateCompatibleDC(screen_dc); if mem_dc.is_invalid() { ReleaseDC(HWND::default(), screen_dc); anyhow::bail!("Failed to create compatible DC"); } let bitmap = CreateCompatibleBitmap(screen_dc, self.width as i32, self.height as i32); if bitmap.is_invalid() { let _ = DeleteDC(mem_dc); ReleaseDC(HWND::default(), screen_dc); anyhow::bail!("Failed to create compatible bitmap"); } // Select bitmap into memory DC let old_bitmap = SelectObject(mem_dc, bitmap); // Copy screen to memory DC if let Err(e) = BitBlt( mem_dc, 0, 0, self.width as i32, self.height as i32, screen_dc, self.display.x, self.display.y, SRCCOPY, ) { SelectObject(mem_dc, old_bitmap); let _ = DeleteObject(bitmap); let _ = DeleteDC(mem_dc); ReleaseDC(HWND::default(), screen_dc); anyhow::bail!("BitBlt failed: {}", e); } // Prepare bitmap info for GetDIBits let mut bmi = BITMAPINFO { bmiHeader: BITMAPINFOHEADER { biSize: std::mem::size_of::() as u32, biWidth: self.width as i32, biHeight: -(self.height as i32), // Negative for top-down biPlanes: 1, biBitCount: 32, biCompression: BI_RGB.0, biSizeImage: 0, biXPelsPerMeter: 0, biYPelsPerMeter: 0, biClrUsed: 0, biClrImportant: 0, }, bmiColors: [Default::default()], }; // Allocate buffer for pixel data let buffer_size = (self.width * self.height * 4) as usize; let mut data = vec![0u8; buffer_size]; // Get the bits let lines = GetDIBits( mem_dc, bitmap, 0, self.height, Some(data.as_mut_ptr() as *mut _), &mut bmi, DIB_RGB_COLORS, ); // Cleanup SelectObject(mem_dc, old_bitmap); let _ = DeleteObject(bitmap); let _ = DeleteDC(mem_dc); ReleaseDC(HWND::default(), screen_dc); if lines == 0 { anyhow::bail!("GetDIBits failed"); } Ok(data) } } } impl Capturer for GdiCapturer { fn capture(&mut self) -> Result> { let data = self.capture_gdi()?; Ok(Some(CapturedFrame { width: self.width, height: self.height, data, timestamp: Instant::now(), display_id: self.display.id, dirty_rects: None, // GDI doesn't provide dirty rects })) } fn display(&self) -> &Display { &self.display } fn is_valid(&self) -> bool { true } }