From 9285dbbd4943900e21725994c360017ea24a3c4f Mon Sep 17 00:00:00 2001 From: Helios Agent Date: Tue, 3 Mar 2026 16:49:57 +0100 Subject: [PATCH] fix(client): window screenshot via crop instead of PrintWindow, fix SW_MINIMIZE --- crates/client/src/screenshot.rs | 85 +++++++++++++------------------ crates/client/src/windows_mgmt.rs | 2 +- 2 files changed, 36 insertions(+), 51 deletions(-) diff --git a/crates/client/src/screenshot.rs b/crates/client/src/screenshot.rs index 3ea8063..cc633eb 100644 --- a/crates/client/src/screenshot.rs +++ b/crates/client/src/screenshot.rs @@ -117,68 +117,53 @@ pub fn take_screenshot() -> Result<(String, u32, u32), String> { } } -/// Capture a specific window using PrintWindow (works even if occluded). +/// 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; - use windows::Win32::Graphics::Gdi::{ - CreateCompatibleBitmap, CreateCompatibleDC, DeleteDC, DeleteObject, - GetDC, GetDIBits, PrintWindow, PW_RENDERFULLCONTENT, ReleaseDC, - SelectObject, BITMAPINFO, BITMAPINFOHEADER, DIB_RGB_COLORS, - }; + use windows::Win32::Foundation::{HWND, RECT}; use windows::Win32::UI::WindowsAndMessaging::GetWindowRect; let hwnd = HWND(window_id as isize); - - unsafe { + let (x, y, w, h) = unsafe { let mut rect = RECT::default(); GetWindowRect(hwnd, &mut rect).map_err(|e| format!("GetWindowRect failed: {e}"))?; - let width = (rect.right - rect.left) as u32; - let height = (rect.bottom - rect.top) as u32; - if width == 0 || height == 0 { - return Err(format!("Window has zero size: {width}x{height}")); - } + 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) + }; - let hdc_screen = GetDC(None); - let hdc_mem = CreateCompatibleDC(hdc_screen); - let hbm = CreateCompatibleBitmap(hdc_screen, width as i32, height as i32); - let old_obj = SelectObject(hdc_mem, hbm); + // 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}"))?; - // PrintWindow captures the window even if it's behind others - PrintWindow(hwnd, hdc_mem, PW_RENDERFULLCONTENT); + // 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}"))?; - let mut bmi = BITMAPINFO { - bmiHeader: BITMAPINFOHEADER { - biSize: std::mem::size_of::() as u32, - biWidth: width as i32, - biHeight: -(height as i32), - biPlanes: 1, - biBitCount: 32, - biCompression: 0, - biSizeImage: 0, - biXPelsPerMeter: 0, - biYPelsPerMeter: 0, - biClrUsed: 0, - biClrImportant: 0, - }, - bmiColors: [Default::default()], - }; + // 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; - let mut pixel_buf: Vec = vec![0u8; (width * height * 4) as usize]; - 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(None, hdc_screen); - - // BGRA → RGBA - for chunk in pixel_buf.chunks_exact_mut(4) { chunk.swap(0, 2); } - - let png_bytes = encode_png(&pixel_buf, width, height)?; - let b64 = base64::engine::general_purpose::STANDARD.encode(&png_bytes); - Ok((b64, width, height)) + // 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))] diff --git a/crates/client/src/windows_mgmt.rs b/crates/client/src/windows_mgmt.rs index d286f79..3776fb0 100644 --- a/crates/client/src/windows_mgmt.rs +++ b/crates/client/src/windows_mgmt.rs @@ -8,7 +8,7 @@ mod win_impl { use windows::Win32::Foundation::{BOOL, HWND, LPARAM}; use windows::Win32::UI::WindowsAndMessaging::{ BringWindowToTop, EnumWindows, GetWindowTextW, IsWindowVisible, SetForegroundWindow, - ShowWindow, SW_MAXIMIZE, SW_RESTORE, + ShowWindow, SW_MAXIMIZE, SW_MINIMIZE, SW_RESTORE, }; use windows::Win32::UI::Input::KeyboardAndMouse::{ keybd_event, KEYEVENTF_KEYUP, VK_MENU,