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>
115 lines
2.8 KiB
Rust
115 lines
2.8 KiB
Rust
//! Screen capture module
|
|
//!
|
|
//! Provides DXGI Desktop Duplication for high-performance screen capture on Windows 8+,
|
|
//! with GDI fallback for legacy systems or edge cases.
|
|
|
|
mod display;
|
|
#[cfg(windows)]
|
|
mod dxgi;
|
|
#[cfg(windows)]
|
|
mod gdi;
|
|
|
|
pub use display::Display;
|
|
|
|
use anyhow::Result;
|
|
use std::time::Instant;
|
|
|
|
/// Captured frame data
|
|
#[derive(Debug)]
|
|
pub struct CapturedFrame {
|
|
/// Frame width in pixels
|
|
pub width: u32,
|
|
|
|
/// Frame height in pixels
|
|
pub height: u32,
|
|
|
|
/// Raw BGRA pixel data (4 bytes per pixel)
|
|
pub data: Vec<u8>,
|
|
|
|
/// Timestamp when frame was captured
|
|
pub timestamp: Instant,
|
|
|
|
/// Display ID this frame is from
|
|
pub display_id: u32,
|
|
|
|
/// Regions that changed since last frame (if available)
|
|
// Populated by capturers; not yet consumed by the encoder pipeline.
|
|
#[allow(dead_code)]
|
|
pub dirty_rects: Option<Vec<DirtyRect>>,
|
|
}
|
|
|
|
/// Rectangular region that changed
|
|
#[derive(Debug, Clone, Copy)]
|
|
pub struct DirtyRect {
|
|
pub x: u32,
|
|
pub y: u32,
|
|
pub width: u32,
|
|
pub height: u32,
|
|
}
|
|
|
|
/// Screen capturer trait
|
|
pub trait Capturer: Send {
|
|
/// Capture the next frame
|
|
///
|
|
/// Returns None if no new frame is available (screen unchanged)
|
|
fn capture(&mut self) -> Result<Option<CapturedFrame>>;
|
|
|
|
/// Get the current display info
|
|
#[allow(dead_code)]
|
|
fn display(&self) -> &Display;
|
|
|
|
/// Check if capturer is still valid (display may have changed)
|
|
#[allow(dead_code)]
|
|
fn is_valid(&self) -> bool;
|
|
}
|
|
|
|
/// Create a capturer for the specified display
|
|
#[cfg(windows)]
|
|
pub fn create_capturer(
|
|
display: Display,
|
|
use_dxgi: bool,
|
|
gdi_fallback: bool,
|
|
) -> Result<Box<dyn Capturer>> {
|
|
if use_dxgi {
|
|
match dxgi::DxgiCapturer::new(display.clone()) {
|
|
Ok(capturer) => {
|
|
tracing::info!("Using DXGI Desktop Duplication for capture");
|
|
return Ok(Box::new(capturer));
|
|
}
|
|
Err(e) => {
|
|
tracing::warn!("DXGI capture failed: {}, trying fallback", e);
|
|
if !gdi_fallback {
|
|
return Err(e);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// GDI fallback
|
|
tracing::info!("Using GDI for capture");
|
|
Ok(Box::new(gdi::GdiCapturer::new(display)?))
|
|
}
|
|
|
|
#[cfg(not(windows))]
|
|
pub fn create_capturer(
|
|
_display: Display,
|
|
_use_dxgi: bool,
|
|
_gdi_fallback: bool,
|
|
) -> Result<Box<dyn Capturer>> {
|
|
anyhow::bail!("Screen capture only supported on Windows")
|
|
}
|
|
|
|
/// Get all available displays
|
|
pub fn enumerate_displays() -> Result<Vec<Display>> {
|
|
display::enumerate_displays()
|
|
}
|
|
|
|
/// Get the primary display
|
|
pub fn primary_display() -> Result<Display> {
|
|
let displays = enumerate_displays()?;
|
|
displays
|
|
.into_iter()
|
|
.find(|d| d.is_primary)
|
|
.ok_or_else(|| anyhow::anyhow!("No primary display found"))
|
|
}
|