Initial GuruConnect implementation - Phase 1 MVP
- Agent: DXGI/GDI screen capture, mouse/keyboard input, WebSocket transport - Server: Axum relay, session management, REST API - Dashboard: React viewer components with TypeScript - Protocol: Protobuf definitions for all message types 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
150
agent/src/capture/gdi.rs
Normal file
150
agent/src/capture/gdi.rs
Normal file
@@ -0,0 +1,150 @@
|
||||
//! 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::{Context, Result};
|
||||
use std::time::Instant;
|
||||
|
||||
use windows::Win32::Graphics::Gdi::{
|
||||
BitBlt, CreateCompatibleBitmap, CreateCompatibleDC, DeleteDC, DeleteObject,
|
||||
GetDIBits, SelectObject, BITMAPINFO, BITMAPINFOHEADER, BI_RGB, DIB_RGB_COLORS,
|
||||
SRCCOPY, GetDC, ReleaseDC,
|
||||
};
|
||||
use windows::Win32::Foundation::HWND;
|
||||
|
||||
/// 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() {
|
||||
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
|
||||
let result = BitBlt(
|
||||
mem_dc,
|
||||
0,
|
||||
0,
|
||||
self.width as i32,
|
||||
self.height as i32,
|
||||
screen_dc,
|
||||
self.display.x,
|
||||
self.display.y,
|
||||
SRCCOPY,
|
||||
);
|
||||
|
||||
if !result.as_bool() {
|
||||
SelectObject(mem_dc, old_bitmap);
|
||||
DeleteObject(bitmap);
|
||||
DeleteDC(mem_dc);
|
||||
ReleaseDC(HWND::default(), screen_dc);
|
||||
anyhow::bail!("BitBlt failed");
|
||||
}
|
||||
|
||||
// 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);
|
||||
DeleteObject(bitmap);
|
||||
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
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user