/// Screenshot capture — Windows GDI on Windows, stub on other platforms. use base64::Engine; #[cfg(windows)] pub fn take_screenshot() -> Result<(String, u32, u32), String> { use windows::Win32::Graphics::Gdi::{ BitBlt, CreateCompatibleBitmap, CreateCompatibleDC, DeleteDC, DeleteObject, GetDIBits, SelectObject, BITMAPINFO, BITMAPINFOHEADER, DIB_RGB_COLORS, SRCCOPY, }; use windows::Win32::UI::WindowsAndMessaging::GetDesktopWindow; use windows::Win32::Graphics::Gdi::GetWindowDC; use windows::Win32::Graphics::Gdi::ReleaseDC; unsafe { let hwnd = GetDesktopWindow(); let hdc_screen = GetWindowDC(hwnd); if hdc_screen.is_invalid() { return Err("GetWindowDC failed — cannot capture screen".to_string()); } // Get screen dimensions use windows::Win32::Graphics::Gdi::{GetDeviceCaps, HORZRES, VERTRES}; let width = GetDeviceCaps(hdc_screen, HORZRES) as u32; let height = GetDeviceCaps(hdc_screen, VERTRES) as u32; if width == 0 || height == 0 { ReleaseDC(hwnd, hdc_screen); return Err(format!("Invalid screen dimensions: {width}x{height}")); } // Create compatible DC and bitmap let hdc_mem = CreateCompatibleDC(hdc_screen); if hdc_mem.is_invalid() { ReleaseDC(hwnd, hdc_screen); return Err("CreateCompatibleDC failed".to_string()); } let hbm = CreateCompatibleBitmap(hdc_screen, width as i32, height as i32); if hbm.is_invalid() { DeleteDC(hdc_mem); ReleaseDC(hwnd, hdc_screen); return Err("CreateCompatibleBitmap failed".to_string()); } let old_obj = SelectObject(hdc_mem, hbm); // BitBlt the screen into our bitmap let blt_result = BitBlt( hdc_mem, 0, 0, width as i32, height as i32, hdc_screen, 0, 0, SRCCOPY, ); if blt_result.is_err() { SelectObject(hdc_mem, old_obj); DeleteObject(hbm); DeleteDC(hdc_mem); ReleaseDC(hwnd, hdc_screen); return Err("BitBlt failed — could not copy screen pixels".to_string()); } // Get raw pixel data via GetDIBits let mut bmi = BITMAPINFO { bmiHeader: BITMAPINFOHEADER { biSize: std::mem::size_of::() as u32, biWidth: width as i32, biHeight: -(height as i32), // negative = top-down biPlanes: 1, biBitCount: 32, biCompression: 0, // BI_RGB biSizeImage: 0, biXPelsPerMeter: 0, biYPelsPerMeter: 0, biClrUsed: 0, biClrImportant: 0, }, bmiColors: [Default::default()], }; let buf_size = (width * height * 4) as usize; let mut pixel_buf: Vec = vec![0u8; buf_size]; let lines = GetDIBits( hdc_mem, hbm, 0, height, Some(pixel_buf.as_mut_ptr() as *mut _), &mut bmi, DIB_RGB_COLORS, ); SelectObject(hdc_mem, old_obj); DeleteObject(hbm); DeleteDC(hdc_mem); ReleaseDC(hwnd, hdc_screen); if lines == 0 { return Err(format!("GetDIBits failed — returned 0 scan lines (expected {height})")); } // Convert BGRA → RGBA for chunk in pixel_buf.chunks_exact_mut(4) { chunk.swap(0, 2); // B <-> R } // Encode as PNG let png_bytes = encode_png(&pixel_buf, width, height)?; let b64 = base64::engine::general_purpose::STANDARD.encode(&png_bytes); Ok((b64, width, height)) } } /// Capture a specific window by cropping the full screen to its rect. #[cfg(windows)] pub fn take_window_screenshot(window_id: u64) -> Result<(String, u32, u32), String> { use windows::Win32::Foundation::{HWND, RECT}; use windows::Win32::UI::WindowsAndMessaging::GetWindowRect; let hwnd = HWND(window_id as isize); let (x, y, w, h) = unsafe { let mut rect = RECT::default(); GetWindowRect(hwnd, &mut rect).map_err(|e| format!("GetWindowRect failed: {e}"))?; let w = (rect.right - rect.left) as u32; let h = (rect.bottom - rect.top) as u32; if w == 0 || h == 0 { return Err(format!("Window has zero size: {w}x{h}")); } (rect.left, rect.top, w, h) }; // Take full screenshot and crop to window rect let (full_b64, full_w, full_h) = take_screenshot()?; let full_bytes = base64::engine::general_purpose::STANDARD .decode(&full_b64).map_err(|e| format!("base64 decode: {e}"))?; // Decode PNG back to raw RGBA let cursor = std::io::Cursor::new(&full_bytes); let decoder = png::Decoder::new(cursor); let mut reader = decoder.read_info().map_err(|e| format!("PNG decode: {e}"))?; let mut img_buf = vec![0u8; reader.output_buffer_size()]; reader.next_frame(&mut img_buf).map_err(|e| format!("PNG frame: {e}"))?; // Clamp window rect to screen bounds let x0 = (x.max(0) as u32).min(full_w); let y0 = (y.max(0) as u32).min(full_h); let x1 = ((x as u32 + w)).min(full_w); let y1 = ((y as u32 + h)).min(full_h); let cw = x1 - x0; let ch = y1 - y0; // Crop: 4 bytes per pixel (RGBA) let mut cropped = Vec::with_capacity((cw * ch * 4) as usize); for row in y0..y1 { let start = ((row * full_w + x0) * 4) as usize; let end = start + (cw * 4) as usize; cropped.extend_from_slice(&img_buf[start..end]); } let png_bytes = encode_png(&cropped, cw, ch)?; let b64 = base64::engine::general_purpose::STANDARD.encode(&png_bytes); Ok((b64, cw, ch)) } #[cfg(not(windows))] pub fn take_window_screenshot(_window_id: u64) -> Result<(String, u32, u32), String> { Err("Window screenshot only supported on Windows".to_string()) } #[cfg(not(windows))] pub fn take_screenshot() -> Result<(String, u32, u32), String> { // Stub for non-Windows builds // In a real scenario, could use X11/scrot on Linux let width = 1u32; let height = 1u32; let pixel_data = vec![0u8, 0u8, 0u8, 255u8]; // single black pixel RGBA let png_bytes = encode_png(&pixel_data, width, height)?; let b64 = base64::engine::general_purpose::STANDARD.encode(&png_bytes); Ok((b64, width, height)) } fn encode_png(rgba: &[u8], width: u32, height: u32) -> Result, String> { let mut buf = Vec::new(); { let mut encoder = png::Encoder::new(&mut buf, width, height); encoder.set_color(png::ColorType::Rgba); encoder.set_depth(png::BitDepth::Eight); let mut writer = encoder .write_header() .map_err(|e| format!("PNG header error: {e}"))?; writer .write_image_data(rgba) .map_err(|e| format!("PNG write error: {e}"))?; } Ok(buf) }