1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
//! Windows GDI-based screen capture implementation
use crate::{
capture::traits::DefaultPixelConverter,
encoder::global_simd_converter,
error::{CaptureError, CaptureResult},
memory_pool::global_pool,
types::{CaptureRegion, PixelFormat, RawImage},
};
use windows::{
Win32::{
Foundation::HWND,
Graphics::Gdi::*,
UI::WindowsAndMessaging::*,
},
};
/// GDI-based screen capture
pub struct GdiCapture {
#[allow(dead_code)]
pixel_converter: DefaultPixelConverter,
}
impl GdiCapture {
/// Create a new GDI capturer
pub fn new() -> CaptureResult<Self> {
Ok(Self {
pixel_converter: DefaultPixelConverter,
})
}
/// Capture a display using GDI
pub fn capture_display(&self, display_index: usize) -> CaptureResult<RawImage> {
// Get display info
let displays = super::WindowsCapture::enumerate_displays()?;
let display = displays
.get(display_index)
.ok_or_else(|| CaptureError::DisplayNotFound(display_index))?;
// Capture the display region
self.capture_region(CaptureRegion {
x: display.x,
y: display.y,
width: display.width,
height: display.height,
})
}
/// Capture a specific region using GDI
pub fn capture_region(&self, region: CaptureRegion) -> CaptureResult<RawImage> {
unsafe {
// Get a DC for the entire virtual desktop (all monitors)
// GetDC(NULL) returns a DC for the entire virtual screen across all monitors
// Research confirms this works correctly with negative coordinates for multi-monitor setups
let desktop_dc = GetDC(HWND(std::ptr::null_mut()));
if desktop_dc.is_invalid() {
return Err(CaptureError::CaptureFailed(
"Failed to get screen DC for virtual desktop".to_string(),
));
}
eprintln!("[GDI] 🖥️ Created DC for virtual desktop (all monitors)");
eprintln!("[GDI] Capturing region: x={}, y={}, width={}, height={}",
region.x, region.y, region.width, region.height);
// Create a compatible DC
let mem_dc = CreateCompatibleDC(desktop_dc);
if mem_dc.is_invalid() {
ReleaseDC(HWND(std::ptr::null_mut()), desktop_dc);
return Err(CaptureError::CaptureFailed(
"Failed to create compatible DC".to_string(),
));
}
// Create a compatible bitmap
let hbitmap = CreateCompatibleBitmap(desktop_dc, region.width as i32, region.height as i32);
if hbitmap.is_invalid() {
let _ = DeleteDC(mem_dc);
ReleaseDC(HWND(std::ptr::null_mut()), desktop_dc);
return Err(CaptureError::CaptureFailed(
"Failed to create compatible bitmap".to_string(),
));
}
eprintln!("[GDI] ✅ Created compatible bitmap: {}x{}", region.width, region.height);
// Select the bitmap into the DC
let old_bitmap = SelectObject(mem_dc, hbitmap);
// Perform the bit-block transfer from desktop DC to memory DC
// This captures from the virtual desktop using absolute screen coordinates
// Negative coordinates are valid for monitors positioned left/above the primary
eprintln!("[GDI] 📸 Calling BitBlt:");
eprintln!("[GDI] Source: desktop_dc at ({}, {})", region.x, region.y);
eprintln!("[GDI] Destination: mem_dc at (0, 0)");
eprintln!("[GDI] Size: {}x{}", region.width, region.height);
let result = BitBlt(
mem_dc,
0,
0,
region.width as i32,
region.height as i32,
desktop_dc,
region.x,
region.y,
SRCCOPY,
);
if result.is_err() {
use windows::Win32::Foundation::GetLastError;
let error_code = GetLastError();
eprintln!("[GDI] ❌ BitBlt FAILED! Error code: {:?}", error_code);
SelectObject(mem_dc, old_bitmap);
let _ = DeleteObject(hbitmap);
let _ = DeleteDC(mem_dc);
ReleaseDC(HWND(std::ptr::null_mut()), desktop_dc);
return Err(CaptureError::CaptureFailed(
format!("BitBlt failed with error code: {:?}", error_code)
));
}
eprintln!("[GDI] ✅ BitBlt succeeded!");
// Get bitmap info
let mut bmp_info = BITMAPINFO {
bmiHeader: BITMAPINFOHEADER {
biSize: std::mem::size_of::<BITMAPINFOHEADER>() as u32,
biWidth: region.width as i32,
biHeight: -(region.height as i32), // Negative for top-down bitmap
biPlanes: 1,
biBitCount: 32,
biCompression: BI_RGB.0,
biSizeImage: 0,
biXPelsPerMeter: 0,
biYPelsPerMeter: 0,
biClrUsed: 0,
biClrImportant: 0,
},
..Default::default()
};
// Calculate buffer size
let pixel_count = (region.width * region.height) as usize;
let buffer_size = pixel_count * 4; // 4 bytes per pixel (BGRA)
// Get buffer from pool
let pool = global_pool();
let mut pooled_buffer = pool
.acquire(buffer_size)
.map_err(|_| CaptureError::MemoryAllocationFailed { size: buffer_size })?;
// Get the bitmap bits
let scan_lines = GetDIBits(
mem_dc,
hbitmap,
0,
region.height,
Some(pooled_buffer.data_mut().as_mut_ptr() as *mut _),
&mut bmp_info,
DIB_RGB_COLORS,
);
if scan_lines == 0 || scan_lines == -1 {
SelectObject(mem_dc, old_bitmap);
let _ = DeleteObject(hbitmap);
let _ = DeleteDC(mem_dc);
ReleaseDC(HWND(std::ptr::null_mut()), desktop_dc);
return Err(CaptureError::CaptureFailed(
"Failed to get bitmap bits".to_string(),
));
}
// Clean up GDI objects
SelectObject(mem_dc, old_bitmap);
let _ = DeleteObject(hbitmap);
let _ = DeleteDC(mem_dc);
let _ = DeleteDC(desktop_dc);
// Convert BGRA to RGBA using optimized SIMD implementation
global_simd_converter().convert_bgra_to_rgba(pooled_buffer.data_mut());
// Create RawImage from pooled buffer
let data = pooled_buffer.into_vec();
Ok(RawImage::new(
data,
region.width,
region.height,
PixelFormat::RGBA8,
))
}
}
/// Capture with cursor overlay
#[allow(dead_code)]
pub fn capture_with_cursor(&self, region: CaptureRegion) -> CaptureResult<RawImage> {
let image = self.capture_region(region)?;
unsafe {
// Get cursor info
let mut cursor_info = CURSORINFO {
cbSize: std::mem::size_of::<CURSORINFO>() as u32,
..Default::default()
};
if GetCursorInfo(&mut cursor_info).is_ok() {
if cursor_info.flags.0 & CURSOR_SHOWING.0 != 0 {
// Draw cursor onto the image
let cursor_x = cursor_info.ptScreenPos.x - region.x;
let cursor_y = cursor_info.ptScreenPos.y - region.y;
if cursor_x >= 0
&& cursor_y >= 0
&& cursor_x < region.width as i32
&& cursor_y < region.height as i32
{
// Here you would draw the cursor onto the image
// This is simplified - actual implementation would need to:
// 1. Get cursor icon data
// 2. Blend it with the captured image
log::debug!("Cursor at ({}, {})", cursor_x, cursor_y);
}
}
}
}
Ok(image)
}
/// Capture primary display
#[allow(dead_code)]
pub fn capture_primary(&self) -> CaptureResult<RawImage> {
unsafe {
let width = GetSystemMetrics(SM_CXSCREEN);
let height = GetSystemMetrics(SM_CYSCREEN);
self.capture_region(CaptureRegion {
x: 0,
y: 0,
width: width as u32,
height: height as u32,
})
}
}
/// Get virtual screen bounds (all monitors)
#[allow(dead_code)]
pub fn get_virtual_screen_bounds(&self) -> CaptureRegion {
unsafe {
let x = GetSystemMetrics(SM_XVIRTUALSCREEN);
let y = GetSystemMetrics(SM_YVIRTUALSCREEN);
let width = GetSystemMetrics(SM_CXVIRTUALSCREEN);
let height = GetSystemMetrics(SM_CYVIRTUALSCREEN);
CaptureRegion {
x,
y,
width: width as u32,
height: height as u32,
}
}
}
}