Files
guru-connect/agent/src/capture/gdi.rs
Mike Swanson d0de888dd1
All checks were successful
Build and Test / Build Agent (Windows) (push) Successful in 6m53s
Build and Test / Build Server (Linux) (push) Successful in 10m59s
Build and Test / Security Audit (push) Successful in 4m31s
Build and Test / Build Summary (push) Successful in 10s
style(agent): clear 77 pre-existing clippy -D warnings
CI never ran clippy on the agent crate (the build-server clippy job is
Linux-only and can't compile the Windows agent; build-agent only runs cargo
build), so 77 clippy -D-warnings errors had accumulated. Behavior-preserving
cleanup, code-reviewed APPROVED, locally verified (cargo clippy --workspace
--all-targets --all-features -- -D warnings exits 0; cargo test --workspace =
57 passed).

- let _ = on Win32 resource-teardown BOOL returns (gdi.rs); fallible
  BitBlt/GetDIBits stay error-handled
- removed unused imports/vars; idiom fixes (div_ceil, is_null, transmute
  annotations, match collapsing, useless_conversion)
- #[allow(dead_code)] + comment on genuine Task-6/7 scaffolding (vk consts,
  SpecialKey emission, SAS mgmt API, modifier tracking, GDI frame-diff fields)
- Cargo.lock: cargo pruned ~147 stale transitive entries (no version changes)

Follow-up: add cargo clippy -D warnings to the build-agent CI job so the agent
crate stays clippy-clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-30 08:51:45 -07:00

148 lines
4.5 KiB
Rust

//! 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<Self> {
Ok(Self {
width: display.width,
height: display.height,
display,
})
}
/// Capture the screen using GDI
fn capture_gdi(&self) -> Result<Vec<u8>> {
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::<BITMAPINFOHEADER>() 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<Option<CapturedFrame>> {
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
}
}