All checks were successful
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>
148 lines
4.5 KiB
Rust
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
|
|
}
|
|
}
|