dxgi_capture_rs/lib.rs
1//! High-performance screen capturing with DXGI Desktop Duplication API for Windows.
2//!
3//! This library provides a Rust interface to the Windows DXGI Desktop Duplication API,
4//! enabling efficient screen capture with minimal performance overhead.
5//!
6//! # Features
7//!
8//! - **High Performance**: Direct access to DXGI Desktop Duplication API
9//! - **Multiple Monitor Support**: Capture from any available display
10//! - **Flexible Output**: Get pixel data as [`BGRA8`] or raw component bytes
11//! - **Frame Metadata**: Access dirty rectangles, moved rectangles, and timing information
12//! - **Comprehensive Error Handling**: Robust error types for production use
13//! - **Windows Optimized**: Specifically designed for Windows platforms
14//!
15//! # Platform Requirements
16//!
17//! - Windows 8 or later (DXGI 1.2+ required)
18//! - Compatible graphics driver supporting Desktop Duplication
19//! - Active desktop session (not suitable for headless environments)
20//!
21//! # Quick Start
22//!
23//! ```rust,no_run
24//! use dxgi_capture_rs::{DXGIManager, CaptureError};
25//!
26//! fn main() -> Result<(), Box<dyn std::error::Error>> {
27//! let mut manager = DXGIManager::new(1000)?;
28//!
29//! match manager.capture_frame() {
30//! Ok((pixels, (width, height))) => {
31//! println!("Captured {}x{} frame", width, height);
32//! // Process pixels (Vec<BGRA8>)
33//! }
34//! Err(CaptureError::Timeout) => {
35//! // No new frame - normal occurrence
36//! }
37//! Err(e) => eprintln!("Capture failed: {:?}", e),
38//! }
39//! Ok(())
40//! }
41//! ```
42//!
43//! # Multi-Monitor Support
44//!
45//! ```rust,no_run
46//! # use dxgi_capture_rs::DXGIManager;
47//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
48//! let mut manager = DXGIManager::new(1000)?;
49//!
50//! manager.set_capture_source_index(0); // Primary monitor
51//! let (pixels, dimensions) = manager.capture_frame()?;
52//!
53//! manager.set_capture_source_index(1); // Secondary monitor
54//! let (pixels, dimensions) = manager.capture_frame()?;
55//! # Ok(())
56//! # }
57//! ```
58//!
59//! # Frame Metadata for Streaming Applications
60//!
61//! The library provides detailed frame metadata including dirty rectangles and moved rectangles,
62//! which is crucial for optimizing streaming and remote desktop applications.
63//!
64//! ```rust,no_run
65//! # use dxgi_capture_rs::DXGIManager;
66//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
67//! let mut manager = DXGIManager::new(1000)?;
68//!
69//! match manager.capture_frame_with_metadata() {
70//! Ok((pixels, (width, height), metadata)) => {
71//! // Only process frame if there are actual changes
72//! if metadata.has_updates() {
73//! println!("Frame has {} dirty rects and {} move rects",
74//! metadata.dirty_rects.len(), metadata.move_rects.len());
75//!
76//! // Process moved rectangles first (as per Microsoft recommendation)
77//! for move_rect in &metadata.move_rects {
78//! let (src_x, src_y) = move_rect.source_point;
79//! let (dst_left, dst_top, dst_right, dst_bottom) = move_rect.destination_rect;
80//!
81//! // Copy pixels from source to destination
82//! // This is much more efficient than re-encoding the entire area
83//! copy_rectangle(&pixels, src_x, src_y, dst_left, dst_top,
84//! dst_right - dst_left, dst_bottom - dst_top);
85//! }
86//!
87//! // Then process dirty rectangles
88//! for &(left, top, right, bottom) in &metadata.dirty_rects {
89//! let width = (right - left) as usize;
90//! let height = (bottom - top) as usize;
91//!
92//! // Only encode/transmit the changed region
93//! encode_region(&pixels, left as usize, top as usize, width, height);
94//! }
95//! }
96//!
97//! // Check for mouse cursor updates
98//! if metadata.has_mouse_updates() {
99//! if let Some((x, y)) = metadata.pointer_position {
100//! println!("Mouse cursor at ({}, {}), visible: {}", x, y, metadata.pointer_visible);
101//! }
102//! }
103//! }
104//! Err(e) => eprintln!("Capture failed: {:?}", e),
105//! }
106//!
107//! # fn copy_rectangle(pixels: &[dxgi_capture_rs::BGRA8], src_x: i32, src_y: i32,
108//! # dst_x: i32, dst_y: i32, width: i32, height: i32) {}
109//! # fn encode_region(pixels: &[dxgi_capture_rs::BGRA8], x: usize, y: usize, width: usize, height: usize) {}
110//! # Ok(())
111//! # }
112//! ```
113//!
114//! # Error Handling
115//!
116//! ```rust,no_run
117//! # use dxgi_capture_rs::{DXGIManager, CaptureError};
118//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
119//! let mut manager = DXGIManager::new(1000)?;
120//!
121//! match manager.capture_frame() {
122//! Ok((pixels, dimensions)) => { /* Process successful capture */ }
123//! Err(CaptureError::Timeout) => { /* No new frame - normal */ }
124//! Err(CaptureError::AccessDenied) => { /* Protected content */ }
125//! Err(CaptureError::AccessLost) => { /* Display mode changed */ }
126//! Err(e) => eprintln!("Capture failed: {:?}", e),
127//! }
128//! # Ok(())
129//! # }
130//! ```
131//!
132//! # Performance Considerations
133//!
134//! - Use appropriate timeout values based on your frame rate requirements
135//! - Consider using [`DXGIManager::capture_frame_components`] for raw byte data
136//! - Memory usage scales with screen resolution
137//! - The library automatically handles screen rotation
138//! - Use metadata to optimize streaming by only processing changed regions
139//! - Process move rectangles before dirty rectangles for correct visual output
140//!
141//! # Thread Safety
142//!
143//! [`DXGIManager`] is not thread-safe. Create separate instances for each thread
144//! if you need concurrent capture operations.
145
146#![cfg(windows)]
147#![cfg_attr(docsrs, feature(doc_cfg))]
148#![cfg_attr(docsrs, doc(cfg(windows)))]
149
150use std::fmt;
151use std::{mem, slice};
152use windows::Win32::Graphics::Direct3D::D3D_FEATURE_LEVEL;
153use windows::{
154 Win32::{
155 Foundation::{HMODULE, RECT},
156 Graphics::{
157 Direct3D::{D3D_DRIVER_TYPE_UNKNOWN, D3D_FEATURE_LEVEL_9_1},
158 Direct3D11::{
159 D3D11_CPU_ACCESS_READ, D3D11_CREATE_DEVICE_BGRA_SUPPORT, D3D11_SDK_VERSION,
160 D3D11_TEXTURE2D_DESC, D3D11_USAGE_STAGING, D3D11CreateDevice, ID3D11Device,
161 ID3D11DeviceContext, ID3D11Texture2D,
162 },
163 Dxgi::{
164 Common::{
165 DXGI_MODE_ROTATION_IDENTITY, DXGI_MODE_ROTATION_ROTATE90,
166 DXGI_MODE_ROTATION_ROTATE180, DXGI_MODE_ROTATION_ROTATE270,
167 DXGI_MODE_ROTATION_UNSPECIFIED,
168 },
169 CreateDXGIFactory1, DXGI_ERROR_ACCESS_DENIED, DXGI_ERROR_ACCESS_LOST,
170 DXGI_ERROR_NOT_FOUND, DXGI_ERROR_WAIT_TIMEOUT, DXGI_MAP_READ, DXGI_MAPPED_RECT,
171 DXGI_OUTDUPL_FRAME_INFO, DXGI_OUTDUPL_MOVE_RECT, DXGI_OUTPUT_DESC, IDXGIAdapter,
172 IDXGIAdapter1, IDXGIFactory1, IDXGIOutput, IDXGIOutput1, IDXGIOutputDuplication,
173 IDXGIResource, IDXGISurface1,
174 },
175 },
176 },
177 core::{Interface, Result as WindowsResult},
178};
179
180/// A pixel color in BGRA8 format.
181///
182/// Each channel can hold values from 0 to 255. The channels are ordered as BGRA
183/// to match the Windows DXGI format.
184#[derive(Copy, Clone, Debug, PartialOrd, PartialEq, Eq, Ord)]
185pub struct BGRA8 {
186 /// Blue channel (0-255)
187 pub b: u8,
188 /// Green channel (0-255)
189 pub g: u8,
190 /// Red channel (0-255)
191 pub r: u8,
192 /// Alpha channel (0-255, where 0 is transparent and 255 is opaque)
193 pub a: u8,
194}
195
196/// Represents a rectangle that has been moved from one location to another.
197///
198/// This structure describes a region that was moved from a source location to
199/// a destination location, which is useful for optimizing screen updates in
200/// streaming applications.
201#[derive(Copy, Clone, Debug, PartialEq, Eq)]
202pub struct MoveRect {
203 /// The source point where the content was moved from (top-left corner)
204 pub source_point: (i32, i32),
205 /// The destination rectangle where the content was moved to
206 pub destination_rect: (i32, i32, i32, i32), // (left, top, right, bottom)
207}
208
209/// Metadata about a captured frame.
210///
211/// This structure contains timing information, dirty regions, moved regions,
212/// and other metadata that can help optimize screen capture and streaming
213/// applications.
214#[derive(Clone, Debug)]
215pub struct FrameMetadata {
216 /// Timestamp of the last desktop image update (Windows performance counter)
217 pub last_present_time: i64,
218 /// Timestamp of the last mouse update (Windows performance counter)
219 pub last_mouse_update_time: i64,
220 /// Number of frames accumulated since the last processed frame
221 pub accumulated_frames: u32,
222 /// Whether dirty regions were coalesced and may contain unmodified pixels
223 pub rects_coalesced: bool,
224 /// Whether protected content was masked out in the captured frame
225 pub protected_content_masked_out: bool,
226 /// Mouse cursor position and visibility
227 pub pointer_position: Option<(i32, i32)>,
228 /// Whether the mouse cursor is visible
229 pub pointer_visible: bool,
230 /// List of dirty rectangles that have changed since the last frame
231 pub dirty_rects: Vec<(i32, i32, i32, i32)>, // (left, top, right, bottom)
232 /// List of move rectangles that have been moved since the last frame
233 pub move_rects: Vec<MoveRect>,
234}
235
236impl FrameMetadata {
237 /// Returns true if the frame contains any updates (dirty regions or moves)
238 pub fn has_updates(&self) -> bool {
239 !self.dirty_rects.is_empty() || !self.move_rects.is_empty()
240 }
241
242 /// Returns true if the mouse cursor has been updated
243 pub fn has_mouse_updates(&self) -> bool {
244 self.last_mouse_update_time > 0
245 }
246
247 /// Returns the total number of changed regions
248 pub fn total_change_count(&self) -> usize {
249 self.dirty_rects.len() + self.move_rects.len()
250 }
251}
252
253/// Errors that can occur during screen capture operations.
254#[derive(Debug)]
255pub enum CaptureError {
256 /// Access to the output duplication was denied.
257 ///
258 /// This typically occurs when attempting to capture protected content,
259 /// such as fullscreen video with DRM protection.
260 ///
261 /// **Recovery**: Check if protected content is being displayed.
262 AccessDenied,
263
264 /// Access to the duplicated output was lost.
265 ///
266 /// This occurs when the display configuration changes, such as:
267 /// - Switching between windowed and fullscreen mode
268 /// - Changing display resolution
269 /// - Connecting/disconnecting monitors
270 /// - Graphics driver updates
271 ///
272 /// **Recovery**: Recreate the [`DXGIManager`] instance.
273 AccessLost,
274
275 /// Failed to refresh the output duplication after a previous error.
276 ///
277 /// **Recovery**: Recreate the [`DXGIManager`] instance or wait before retrying.
278 RefreshFailure,
279
280 /// The capture operation timed out.
281 ///
282 /// This is a normal occurrence indicating that no new frame was available
283 /// within the specified timeout period.
284 ///
285 /// **Recovery**: This is not an error condition. Simply retry the capture.
286 Timeout,
287
288 /// A general or unexpected failure occurred.
289 ///
290 /// **Recovery**: Log the error message and consider recreating the [`DXGIManager`].
291 Fail(windows::core::Error),
292}
293
294impl fmt::Display for CaptureError {
295 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
296 match self {
297 CaptureError::AccessDenied => write!(f, "Access to output duplication was denied"),
298 CaptureError::AccessLost => write!(f, "Access to duplicated output was lost"),
299 CaptureError::RefreshFailure => write!(f, "Failed to refresh output duplication"),
300 CaptureError::Timeout => write!(f, "Capture operation timed out"),
301 CaptureError::Fail(msg) => write!(f, "Capture failed: {msg}"),
302 }
303 }
304}
305
306impl std::error::Error for CaptureError {}
307
308impl From<windows::core::Error> for CaptureError {
309 fn from(err: windows::core::Error) -> Self {
310 CaptureError::Fail(err)
311 }
312}
313
314/// Errors that can occur during output duplication initialization.
315#[derive(Debug)]
316pub enum OutputDuplicationError {
317 /// No suitable output display was found.
318 ///
319 /// This occurs when no displays are connected, all displays are disabled,
320 /// or the graphics driver doesn't support Desktop Duplication.
321 ///
322 /// **Recovery**: Ensure a display is connected and graphics drivers support Desktop Duplication.
323 NoOutput,
324
325 /// Failed to create the D3D11 device or duplicate the output.
326 ///
327 /// This can occur due to graphics driver issues, insufficient system resources,
328 /// or incompatible graphics hardware.
329 ///
330 /// **Recovery**: Check graphics driver installation and system resources.
331 DeviceError(windows::core::Error),
332}
333
334impl fmt::Display for OutputDuplicationError {
335 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
336 match self {
337 OutputDuplicationError::NoOutput => write!(f, "No suitable output display was found"),
338 OutputDuplicationError::DeviceError(err) => {
339 write!(f, "Failed to create D3D11 device: {err}")
340 }
341 }
342 }
343}
344
345impl std::error::Error for OutputDuplicationError {}
346
347impl From<windows::core::Error> for OutputDuplicationError {
348 fn from(err: windows::core::Error) -> Self {
349 OutputDuplicationError::DeviceError(err)
350 }
351}
352
353/// Checks whether a Windows HRESULT represents a failure condition.
354///
355/// # Deprecation
356///
357/// This function is a trivial wrapper around [`windows::core::HRESULT::is_err`].
358/// Use `hr.is_err()` directly instead.
359///
360/// # Examples
361///
362/// ```rust
363/// use dxgi_capture_rs::hr_failed;
364/// use windows::core::HRESULT;
365/// use windows::Win32::Foundation::{S_OK, E_FAIL};
366///
367/// // Success codes
368/// assert!(!hr_failed(S_OK));
369/// assert!(!hr_failed(HRESULT(1)));
370///
371/// // Failure codes
372/// assert!(hr_failed(E_FAIL));
373/// assert!(hr_failed(HRESULT(-1)));
374/// ```
375#[deprecated(since = "1.2.0", note = "Use `HRESULT::is_err()` directly instead")]
376pub fn hr_failed(hr: windows::core::HRESULT) -> bool {
377 hr.is_err()
378}
379
380// ---------------------------------------------------------------------------
381// Internal helpers
382// ---------------------------------------------------------------------------
383
384fn create_dxgi_factory_1() -> WindowsResult<IDXGIFactory1> {
385 unsafe { CreateDXGIFactory1() }
386}
387
388const DEFAULT_D3D11_FEATURES: [D3D_FEATURE_LEVEL; 1] = [D3D_FEATURE_LEVEL_9_1];
389
390fn d3d11_create_device(
391 adapter: Option<&IDXGIAdapter>,
392 feature_levels: Option<&[D3D_FEATURE_LEVEL]>,
393) -> WindowsResult<(ID3D11Device, ID3D11DeviceContext)> {
394 let mut device: Option<ID3D11Device> = None;
395 let mut device_context: Option<ID3D11DeviceContext> = None;
396
397 unsafe {
398 D3D11CreateDevice(
399 adapter,
400 D3D_DRIVER_TYPE_UNKNOWN,
401 HMODULE::default(),
402 D3D11_CREATE_DEVICE_BGRA_SUPPORT,
403 feature_levels,
404 D3D11_SDK_VERSION,
405 Some(&mut device),
406 None,
407 Some(&mut device_context),
408 )?;
409 }
410
411 Ok((device.unwrap(), device_context.unwrap()))
412}
413
414/// Enumerates the desktop-attached outputs for a given adapter and returns
415/// only the one at the requested index (if it exists).
416fn get_output_at_index(
417 adapter: &IDXGIAdapter1,
418 index: usize,
419) -> WindowsResult<Option<IDXGIOutput>> {
420 let mut current = 0usize;
421 for i in 0.. {
422 match unsafe { adapter.EnumOutputs(i) } {
423 Ok(output) => {
424 let desc: DXGI_OUTPUT_DESC = unsafe { output.GetDesc()? };
425 if desc.AttachedToDesktop.as_bool() {
426 if current == index {
427 return Ok(Some(output));
428 }
429 current += 1;
430 }
431 }
432 Err(_) => break,
433 }
434 }
435 Ok(None)
436}
437
438/// Maps a Windows error from a capture operation into the appropriate
439/// [`CaptureError`] variant.
440fn map_capture_error(e: windows::core::Error) -> CaptureError {
441 let code = e.code();
442 if code == DXGI_ERROR_ACCESS_LOST {
443 CaptureError::AccessLost
444 } else if code == DXGI_ERROR_WAIT_TIMEOUT {
445 CaptureError::Timeout
446 } else if code == DXGI_ERROR_ACCESS_DENIED {
447 CaptureError::AccessDenied
448 } else {
449 CaptureError::Fail(e)
450 }
451}
452
453// ---------------------------------------------------------------------------
454// DuplicatedOutput — internal handle to a single duplicated output
455// ---------------------------------------------------------------------------
456
457struct DuplicatedOutput {
458 device: ID3D11Device,
459 device_context: ID3D11DeviceContext,
460 output: IDXGIOutput1,
461 output_duplication: IDXGIOutputDuplication,
462}
463
464impl DuplicatedOutput {
465 fn get_desc(&self) -> WindowsResult<DXGI_OUTPUT_DESC> {
466 unsafe { self.output.GetDesc() }
467 }
468
469 /// Acquires a frame, optionally extracts metadata, copies it to a staging
470 /// texture, releases the DXGI frame, and returns the mapped surface.
471 fn capture_frame_to_surface(
472 &mut self,
473 timeout_ms: u32,
474 with_metadata: bool,
475 ) -> WindowsResult<(IDXGISurface1, Option<FrameMetadata>)> {
476 let mut resource: Option<IDXGIResource> = None;
477 let mut frame_info: DXGI_OUTDUPL_FRAME_INFO = unsafe { mem::zeroed() };
478
479 unsafe {
480 self.output_duplication
481 .AcquireNextFrame(timeout_ms, &mut frame_info, &mut resource)?
482 };
483
484 let metadata = if with_metadata {
485 Some(self.extract_frame_metadata(&frame_info)?)
486 } else {
487 None
488 };
489
490 let texture: ID3D11Texture2D = resource.unwrap().cast()?;
491 let mut desc = D3D11_TEXTURE2D_DESC::default();
492 unsafe { texture.GetDesc(&mut desc) };
493 desc.Usage = D3D11_USAGE_STAGING;
494 desc.BindFlags = 0;
495 desc.CPUAccessFlags = D3D11_CPU_ACCESS_READ.0 as u32;
496 desc.MiscFlags = 0;
497
498 let mut staged_texture: Option<ID3D11Texture2D> = None;
499 unsafe {
500 self.device
501 .CreateTexture2D(&desc, None, Some(&mut staged_texture))?
502 };
503 let staged_texture = staged_texture.unwrap();
504
505 unsafe { self.device_context.CopyResource(&staged_texture, &texture) };
506
507 unsafe { self.output_duplication.ReleaseFrame()? };
508
509 let surface: IDXGISurface1 = staged_texture.cast()?;
510 Ok((surface, metadata))
511 }
512
513 fn extract_frame_metadata(
514 &self,
515 frame_info: &DXGI_OUTDUPL_FRAME_INFO,
516 ) -> WindowsResult<FrameMetadata> {
517 let mut dirty_rects = Vec::new();
518 let mut move_rects = Vec::new();
519
520 if frame_info.TotalMetadataBufferSize > 0 {
521 // Get dirty rectangles
522 let mut dirty_rects_buffer_size = 0u32;
523 let dirty_result = unsafe {
524 self.output_duplication.GetFrameDirtyRects(
525 0,
526 std::ptr::null_mut(),
527 &mut dirty_rects_buffer_size,
528 )
529 };
530
531 if dirty_result.is_ok() && dirty_rects_buffer_size > 0 {
532 let dirty_rect_count = dirty_rects_buffer_size / mem::size_of::<RECT>() as u32;
533 let mut dirty_rects_buffer: Vec<RECT> =
534 vec![RECT::default(); dirty_rect_count as usize];
535 unsafe {
536 let get_result = self.output_duplication.GetFrameDirtyRects(
537 dirty_rects_buffer_size,
538 dirty_rects_buffer.as_mut_ptr(),
539 &mut dirty_rects_buffer_size,
540 );
541 if get_result.is_ok() {
542 dirty_rects = dirty_rects_buffer
543 .into_iter()
544 .map(|rect| (rect.left, rect.top, rect.right, rect.bottom))
545 .collect();
546 }
547 }
548 }
549
550 // Get move rectangles
551 let mut move_rects_buffer_size = 0u32;
552 let move_result = unsafe {
553 self.output_duplication.GetFrameMoveRects(
554 0,
555 std::ptr::null_mut(),
556 &mut move_rects_buffer_size,
557 )
558 };
559
560 if move_result.is_ok() && move_rects_buffer_size > 0 {
561 let move_rect_count =
562 move_rects_buffer_size / mem::size_of::<DXGI_OUTDUPL_MOVE_RECT>() as u32;
563 let mut move_rects_buffer: Vec<DXGI_OUTDUPL_MOVE_RECT> =
564 vec![unsafe { mem::zeroed() }; move_rect_count as usize];
565 unsafe {
566 let get_result = self.output_duplication.GetFrameMoveRects(
567 move_rects_buffer_size,
568 move_rects_buffer.as_mut_ptr(),
569 &mut move_rects_buffer_size,
570 );
571 if get_result.is_ok() {
572 move_rects = move_rects_buffer
573 .into_iter()
574 .map(|move_rect| MoveRect {
575 source_point: (move_rect.SourcePoint.x, move_rect.SourcePoint.y),
576 destination_rect: (
577 move_rect.DestinationRect.left,
578 move_rect.DestinationRect.top,
579 move_rect.DestinationRect.right,
580 move_rect.DestinationRect.bottom,
581 ),
582 })
583 .collect();
584 }
585 }
586 }
587 }
588
589 let pointer_position = if frame_info.PointerPosition.Visible.as_bool() {
590 Some((
591 frame_info.PointerPosition.Position.x,
592 frame_info.PointerPosition.Position.y,
593 ))
594 } else {
595 None
596 };
597
598 Ok(FrameMetadata {
599 last_present_time: frame_info.LastPresentTime,
600 last_mouse_update_time: frame_info.LastMouseUpdateTime,
601 accumulated_frames: frame_info.AccumulatedFrames,
602 rects_coalesced: frame_info.RectsCoalesced.as_bool(),
603 protected_content_masked_out: frame_info.ProtectedContentMaskedOut.as_bool(),
604 pointer_position,
605 pointer_visible: frame_info.PointerPosition.Visible.as_bool(),
606 dirty_rects,
607 move_rects,
608 })
609 }
610}
611
612// ---------------------------------------------------------------------------
613// DXGIManager — public API
614// ---------------------------------------------------------------------------
615
616/// The main manager for handling DXGI desktop duplication.
617///
618/// `DXGIManager` provides a high-level interface to the Windows DXGI Desktop
619/// Duplication API, enabling efficient screen capture operations. It manages
620/// the underlying DXGI resources and provides methods to capture screen content
621/// as pixel data.
622///
623/// # Usage
624///
625/// The typical workflow involves:
626/// 1. Creating a manager with [`DXGIManager::new`]
627/// 2. Optionally configuring the capture source and timeout
628/// 3. Capturing frames using [`DXGIManager::capture_frame`] or [`DXGIManager::capture_frame_components`]
629///
630/// # Examples
631///
632/// ## Basic Usage
633///
634/// ```rust,no_run
635/// use dxgi_capture_rs::DXGIManager;
636///
637/// let mut manager = DXGIManager::new(1000)?;
638/// let (width, height) = manager.geometry();
639///
640/// match manager.capture_frame() {
641/// Ok((pixels, (w, h))) => {
642/// println!("Captured {}x{} frame with {} pixels", w, h, pixels.len());
643/// }
644/// Err(e) => {
645/// eprintln!("Capture failed: {:?}", e);
646/// }
647/// }
648/// # Ok::<(), Box<dyn std::error::Error>>(())
649/// ```
650///
651/// ## Multi-Monitor Setup
652///
653/// ```rust,no_run
654/// use dxgi_capture_rs::DXGIManager;
655///
656/// let mut manager = DXGIManager::new(1000)?;
657///
658/// // Capture from primary display (default)
659/// manager.set_capture_source_index(0);
660/// let primary_frame = manager.capture_frame();
661///
662/// // Capture from secondary display (if available)
663/// manager.set_capture_source_index(1);
664/// let secondary_frame = manager.capture_frame();
665/// # Ok::<(), Box<dyn std::error::Error>>(())
666/// ```
667///
668/// ## Timeout Configuration
669///
670/// ```rust,no_run
671/// use dxgi_capture_rs::DXGIManager;
672///
673/// let mut manager = DXGIManager::new(500)?;
674///
675/// // Adjust timeout for different scenarios
676/// manager.set_timeout_ms(100); // Fast polling
677/// manager.set_timeout_ms(2000); // Slower polling
678/// manager.set_timeout_ms(0); // No timeout (immediate return)
679/// # Ok::<(), Box<dyn std::error::Error>>(())
680/// ```
681///
682/// # Thread Safety
683///
684/// `DXGIManager` is not thread-safe. If you need to capture from multiple
685/// threads, create separate instances for each thread.
686///
687/// # Resource Management
688///
689/// The manager automatically handles cleanup of DXGI resources when dropped.
690/// However, if you encounter [`CaptureError::AccessLost`], you should create
691/// a new manager instance to re-establish the connection to the display system.
692pub struct DXGIManager {
693 factory: IDXGIFactory1,
694 duplicated_output: Option<DuplicatedOutput>,
695 capture_source_index: usize,
696 timeout_ms: u32,
697}
698
699impl DXGIManager {
700 /// Creates a new `DXGIManager` instance.
701 ///
702 /// This initializes the DXGI factory and sets up the necessary resources
703 /// for screen capture. The `timeout_ms` parameter specifies the default
704 /// timeout for frame capture operations.
705 ///
706 /// # Errors
707 ///
708 /// Returns an error if the DXGI manager cannot be initialized, which
709 /// typically occurs if the required graphics components are not available.
710 pub fn new(timeout_ms: u32) -> Result<Self, OutputDuplicationError> {
711 let factory = create_dxgi_factory_1()?;
712 let mut manager = Self {
713 factory,
714 duplicated_output: None,
715 capture_source_index: 0,
716 timeout_ms,
717 };
718 manager.acquire_output_duplication()?;
719 Ok(manager)
720 }
721
722 /// Returns the screen geometry (width, height) of the current capture source.
723 ///
724 /// Returns the width and height of the display being captured, in pixels.
725 /// This corresponds to the resolution of the selected capture source.
726 ///
727 /// # Returns
728 ///
729 /// A tuple `(width, height)` where:
730 /// - `width` is the horizontal resolution in pixels
731 /// - `height` is the vertical resolution in pixels
732 ///
733 /// # Examples
734 ///
735 /// ```rust,no_run
736 /// use dxgi_capture_rs::DXGIManager;
737 ///
738 /// let manager = DXGIManager::new(1000)?;
739 /// let (width, height) = manager.geometry();
740 /// println!("Display resolution: {}x{}", width, height);
741 /// # Ok::<(), Box<dyn std::error::Error>>(())
742 /// ```
743 pub fn geometry(&self) -> (usize, usize) {
744 if let Some(ref output) = self.duplicated_output {
745 let output_desc = output.get_desc().expect("Failed to get output description");
746 let RECT {
747 left,
748 top,
749 right,
750 bottom,
751 } = output_desc.DesktopCoordinates;
752 ((right - left) as usize, (bottom - top) as usize)
753 } else {
754 (0, 0)
755 }
756 }
757
758 /// Sets the capture source index to select which display to capture from.
759 ///
760 /// In multi-monitor setups, this method allows you to choose which display
761 /// to capture from. Index 0 always refers to the primary display, while
762 /// indices 1 and higher refer to secondary displays.
763 ///
764 /// # Arguments
765 ///
766 /// * `cs` - The capture source index:
767 /// - `0` = Primary display (default)
768 /// - `1` = First secondary display
769 /// - `2` = Second secondary display, etc.
770 ///
771 /// # Examples
772 ///
773 /// ```rust,no_run
774 /// use dxgi_capture_rs::DXGIManager;
775 ///
776 /// let mut manager = DXGIManager::new(1000)?;
777 ///
778 /// // Capture from primary display (default)
779 /// manager.set_capture_source_index(0);
780 /// let primary_frame = manager.capture_frame();
781 ///
782 /// // Switch to secondary display
783 /// manager.set_capture_source_index(1);
784 /// let secondary_frame = manager.capture_frame();
785 /// # Ok::<(), Box<dyn std::error::Error>>(())
786 /// ```
787 ///
788 /// # Notes
789 ///
790 /// - Setting an invalid index (e.g., for a non-existent display) will not
791 /// cause an immediate error, but subsequent capture operations may fail
792 /// - This method automatically reinitializes the capture system for the new display
793 /// - The geometry may change when switching between displays of different resolutions
794 pub fn set_capture_source_index(&mut self, cs: usize) {
795 let previous_index = self.capture_source_index;
796 self.capture_source_index = cs;
797
798 if self.acquire_output_duplication().is_err() && cs == 0 && cs != previous_index {
799 self.capture_source_index = previous_index;
800 let _ = self.acquire_output_duplication();
801 }
802 }
803
804 /// Gets the current capture source index.
805 ///
806 /// Returns the index of the display currently being used for capture operations.
807 ///
808 /// # Returns
809 ///
810 /// The current capture source index:
811 /// - `0` = Primary display
812 /// - `1` = First secondary display
813 /// - `2` = Second secondary display, etc.
814 ///
815 /// # Examples
816 ///
817 /// ```rust,no_run
818 /// use dxgi_capture_rs::DXGIManager;
819 ///
820 /// let mut manager = DXGIManager::new(1000)?;
821 ///
822 /// // Initially set to primary display
823 /// assert_eq!(manager.get_capture_source_index(), 0);
824 ///
825 /// // Switch to secondary display
826 /// manager.set_capture_source_index(1);
827 /// assert_eq!(manager.get_capture_source_index(), 1);
828 /// # Ok::<(), Box<dyn std::error::Error>>(())
829 /// ```
830 pub fn get_capture_source_index(&self) -> usize {
831 self.capture_source_index
832 }
833
834 /// Sets the timeout for capture operations.
835 ///
836 /// This timeout determines how long capture operations will wait for a new
837 /// frame to become available before returning with a timeout error.
838 ///
839 /// # Arguments
840 ///
841 /// * `timeout_ms` - The timeout in milliseconds:
842 /// - `0` = No timeout (immediate return if no frame available)
843 /// - `1-1000` = Short timeout for real-time applications
844 /// - `1000-5000` = Medium timeout for interactive applications
845 /// - `>5000` = Long timeout for less frequent captures
846 ///
847 /// # Examples
848 ///
849 /// ```rust,no_run
850 /// use dxgi_capture_rs::DXGIManager;
851 ///
852 /// let mut manager = DXGIManager::new(1000)?;
853 ///
854 /// // Set short timeout for real-time capture
855 /// manager.set_timeout_ms(100);
856 ///
857 /// // Set no timeout for immediate return
858 /// manager.set_timeout_ms(0);
859 ///
860 /// // Set longer timeout for less frequent captures
861 /// manager.set_timeout_ms(5000);
862 /// # Ok::<(), Box<dyn std::error::Error>>(())
863 /// ```
864 pub fn set_timeout_ms(&mut self, timeout_ms: u32) {
865 self.timeout_ms = timeout_ms
866 }
867
868 /// Gets the current timeout value for capture operations.
869 ///
870 /// Returns the timeout in milliseconds that capture operations will wait
871 /// for a new frame to become available.
872 ///
873 /// # Returns
874 ///
875 /// The current timeout in milliseconds:
876 /// - `0` = No timeout (immediate return)
877 /// - `>0` = Timeout in milliseconds
878 ///
879 /// # Examples
880 ///
881 /// ```rust,no_run
882 /// use dxgi_capture_rs::DXGIManager;
883 ///
884 /// let mut manager = DXGIManager::new(1000)?;
885 ///
886 /// // Check initial timeout
887 /// assert_eq!(manager.get_timeout_ms(), 1000);
888 ///
889 /// // Change timeout and verify
890 /// manager.set_timeout_ms(500);
891 /// assert_eq!(manager.get_timeout_ms(), 500);
892 /// # Ok::<(), Box<dyn std::error::Error>>(())
893 /// ```
894 pub fn get_timeout_ms(&self) -> u32 {
895 self.timeout_ms
896 }
897
898 /// Reinitializes the output duplication for the selected capture source.
899 ///
900 /// This method is automatically called when needed, but can be called manually
901 /// to recover from certain error conditions. It reinitializes the DXGI
902 /// Desktop Duplication system for the currently selected capture source.
903 ///
904 /// # Returns
905 ///
906 /// Returns `Ok(())` on success, or `Err(OutputDuplicationError)` if the
907 /// reinitialization fails.
908 ///
909 /// # Errors
910 ///
911 /// - [`OutputDuplicationError::NoOutput`] if no suitable display is found
912 /// - [`OutputDuplicationError::DeviceError`] if device creation fails
913 ///
914 /// # Examples
915 ///
916 /// ```rust,no_run
917 /// use dxgi_capture_rs::{DXGIManager, CaptureError};
918 ///
919 /// let mut manager = DXGIManager::new(1000)?;
920 ///
921 /// // Manually reinitialize if needed
922 /// match manager.acquire_output_duplication() {
923 /// Ok(()) => println!("Successfully reinitialized"),
924 /// Err(e) => println!("Failed to reinitialize: {:?}", e),
925 /// }
926 /// # Ok::<(), Box<dyn std::error::Error>>(())
927 /// ```
928 pub fn acquire_output_duplication(&mut self) -> Result<(), OutputDuplicationError> {
929 // Drop any existing output duplication first, releasing the COM
930 // resources before attempting to acquire new ones.
931 self.duplicated_output = None;
932
933 for i in 0.. {
934 let adapter = match unsafe { self.factory.EnumAdapters1(i) } {
935 Ok(adapter) => adapter,
936 Err(e) if e.code() == DXGI_ERROR_NOT_FOUND => break,
937 Err(e) => return Err(e.into()),
938 };
939
940 let (mut d3d11_device, mut device_context) =
941 match d3d11_create_device(Some(&adapter.cast()?), Some(&DEFAULT_D3D11_FEATURES)) {
942 Ok(device) => device,
943 Err(_) => continue,
944 };
945
946 // Only look up and duplicate the single output we actually need.
947 let output = match get_output_at_index(&adapter, self.capture_source_index)? {
948 Some(output) => output,
949 None => continue,
950 };
951
952 let output1: IDXGIOutput1 = output.cast()?;
953
954 let output_duplication = match unsafe { output1.DuplicateOutput(&d3d11_device) } {
955 Ok(dup) => dup,
956 Err(_) => {
957 // Retry creating the device without any features.
958 match d3d11_create_device(Some(&adapter.cast()?), None) {
959 Ok((new_device, new_context)) => {
960 // Retry duplication with the newly created device.
961 match unsafe { output1.DuplicateOutput(&new_device) } {
962 Ok(output_dup) => {
963 d3d11_device = new_device;
964 device_context = new_context;
965 output_dup
966 }
967 Err(_) => continue,
968 }
969 }
970 Err(_) => continue,
971 }
972 }
973 };
974
975 self.duplicated_output = Some(DuplicatedOutput {
976 device: d3d11_device,
977 device_context,
978 output: output1,
979 output_duplication,
980 });
981 return Ok(());
982 }
983 Err(OutputDuplicationError::NoOutput)
984 }
985
986 // -----------------------------------------------------------------------
987 // Internal capture helpers
988 // -----------------------------------------------------------------------
989
990 /// Acquires a frame surface, optionally with metadata. On recoverable
991 /// DXGI errors the internal `duplicated_output` is reset so the next
992 /// capture attempt will re-acquire.
993 fn acquire_surface(
994 &mut self,
995 with_metadata: bool,
996 ) -> Result<(IDXGISurface1, Option<FrameMetadata>), CaptureError> {
997 if self.duplicated_output.is_none() && self.acquire_output_duplication().is_err() {
998 return Err(CaptureError::RefreshFailure);
999 }
1000
1001 let timeout_ms = self.timeout_ms;
1002 let dup = self.duplicated_output.as_mut().unwrap();
1003
1004 match dup.capture_frame_to_surface(timeout_ms, with_metadata) {
1005 Ok(result) => Ok(result),
1006 Err(e) => {
1007 let err = map_capture_error(e);
1008 // On non-timeout errors, drop the output so it is re-acquired.
1009 if !matches!(err, CaptureError::Timeout) {
1010 self.duplicated_output = None;
1011 }
1012 Err(err)
1013 }
1014 }
1015 }
1016
1017 /// Reads pixel data from a mapped surface, handling rotation. This is the
1018 /// single source of truth for the rotation-aware copy logic. `T` is either
1019 /// [`BGRA8`] or `u8`.
1020 fn copy_surface_data<T: Copy + Send + Sync + Sized>(
1021 &self,
1022 surface: &IDXGISurface1,
1023 ) -> Result<(Vec<T>, (usize, usize)), CaptureError> {
1024 let mut rect = DXGI_MAPPED_RECT::default();
1025 unsafe { surface.Map(&mut rect, DXGI_MAP_READ)? };
1026
1027 let desc = self
1028 .duplicated_output
1029 .as_ref()
1030 .ok_or(CaptureError::RefreshFailure)?
1031 .get_desc()?;
1032 let width = (desc.DesktopCoordinates.right - desc.DesktopCoordinates.left) as usize;
1033 let height = (desc.DesktopCoordinates.bottom - desc.DesktopCoordinates.top) as usize;
1034
1035 let pitch = rect.Pitch as usize;
1036 let source = rect.pBits;
1037
1038 let (rotated_width, rotated_height) = match desc.Rotation {
1039 DXGI_MODE_ROTATION_ROTATE90 | DXGI_MODE_ROTATION_ROTATE270 => (height, width),
1040 _ => (width, height),
1041 };
1042
1043 let bytes_per_pixel = mem::size_of::<BGRA8>() / mem::size_of::<T>();
1044 let source_slice = unsafe {
1045 slice::from_raw_parts(source as *const T, pitch * height / mem::size_of::<T>())
1046 };
1047
1048 let mut data_vec: Vec<T> =
1049 Vec::with_capacity(rotated_width * rotated_height * bytes_per_pixel);
1050
1051 match desc.Rotation {
1052 DXGI_MODE_ROTATION_IDENTITY | DXGI_MODE_ROTATION_UNSPECIFIED => {
1053 for i in 0..height {
1054 let start = i * pitch / mem::size_of::<T>();
1055 let end = start + width * bytes_per_pixel;
1056 data_vec.extend_from_slice(&source_slice[start..end]);
1057 }
1058 }
1059 DXGI_MODE_ROTATION_ROTATE90 => {
1060 for i in 0..width {
1061 for j in (0..height).rev() {
1062 let index = j * pitch / mem::size_of::<T>() + i * bytes_per_pixel;
1063 data_vec.extend_from_slice(&source_slice[index..index + bytes_per_pixel]);
1064 }
1065 }
1066 }
1067 DXGI_MODE_ROTATION_ROTATE180 => {
1068 for i in (0..height).rev() {
1069 for j in (0..width).rev() {
1070 let index = i * pitch / mem::size_of::<T>() + j * bytes_per_pixel;
1071 data_vec.extend_from_slice(&source_slice[index..index + bytes_per_pixel]);
1072 }
1073 }
1074 }
1075 DXGI_MODE_ROTATION_ROTATE270 => {
1076 for i in (0..width).rev() {
1077 for j in 0..height {
1078 let index = j * pitch / mem::size_of::<T>() + i * bytes_per_pixel;
1079 data_vec.extend_from_slice(&source_slice[index..index + bytes_per_pixel]);
1080 }
1081 }
1082 }
1083 _ => {}
1084 }
1085
1086 unsafe { surface.Unmap()? };
1087
1088 Ok((data_vec, (rotated_width, rotated_height)))
1089 }
1090
1091 // -----------------------------------------------------------------------
1092 // Public capture methods
1093 // -----------------------------------------------------------------------
1094
1095 /// Captures a single frame and returns it as a `Vec<BGRA8>`.
1096 ///
1097 /// This method captures the current screen content and returns it as a vector
1098 /// of [`BGRA8`] pixels along with the frame dimensions. The method waits for
1099 /// a new frame to become available, up to the configured timeout.
1100 ///
1101 /// # Returns
1102 ///
1103 /// On success, returns `Ok((pixels, (width, height)))` where:
1104 /// - `pixels` is a `Vec<BGRA8>` containing the pixel data
1105 /// - `width` and `height` are the frame dimensions in pixels
1106 /// - Pixels are stored in row-major order (left-to-right, top-to-bottom)
1107 ///
1108 /// # Examples
1109 ///
1110 /// ```rust,no_run
1111 /// use dxgi_capture_rs::{DXGIManager, CaptureError};
1112 ///
1113 /// let mut manager = DXGIManager::new(1000)?;
1114 ///
1115 /// match manager.capture_frame() {
1116 /// Ok((pixels, (width, height))) => {
1117 /// println!("Captured {}x{} frame with {} pixels", width, height, pixels.len());
1118 /// }
1119 /// Err(CaptureError::Timeout) => {
1120 /// // No new frame available within timeout
1121 /// }
1122 /// Err(e) => eprintln!("Capture failed: {:?}", e),
1123 /// }
1124 /// # Ok::<(), Box<dyn std::error::Error>>(())
1125 /// ```
1126 pub fn capture_frame(&mut self) -> Result<(Vec<BGRA8>, (usize, usize)), CaptureError> {
1127 let (surface, _) = self.acquire_surface(false)?;
1128 self.copy_surface_data(&surface)
1129 }
1130
1131 /// Captures a single frame and returns it as a `Vec<u8>`.
1132 ///
1133 /// This method captures the current screen content and returns it as a vector
1134 /// of raw bytes representing the pixel components. Each pixel is represented
1135 /// by 4 consecutive bytes in BGRA order.
1136 ///
1137 /// # Returns
1138 ///
1139 /// On success, returns `Ok((components, (width, height)))` where:
1140 /// - `components` is a `Vec<u8>` containing the raw pixel component data
1141 /// - `width` and `height` are the frame dimensions in pixels
1142 /// - Components are stored as [B, G, R, A, B, G, R, A, ...] in row-major order
1143 ///
1144 /// # Examples
1145 ///
1146 /// ```rust,no_run
1147 /// use dxgi_capture_rs::DXGIManager;
1148 ///
1149 /// let mut manager = DXGIManager::new(1000)?;
1150 ///
1151 /// match manager.capture_frame_components() {
1152 /// Ok((components, (width, height))) => {
1153 /// println!("Captured {}x{} frame with {} bytes", width, height, components.len());
1154 /// }
1155 /// Err(e) => eprintln!("Capture failed: {:?}", e),
1156 /// }
1157 /// # Ok::<(), Box<dyn std::error::Error>>(())
1158 /// ```
1159 pub fn capture_frame_components(&mut self) -> Result<(Vec<u8>, (usize, usize)), CaptureError> {
1160 let (surface, _) = self.acquire_surface(false)?;
1161 self.copy_surface_data(&surface)
1162 }
1163
1164 /// Captures a single frame with minimal overhead for performance-critical applications.
1165 ///
1166 /// This method provides the fastest possible screen capture by minimizing memory
1167 /// allocations and copying. Returns raw pixel data without rotation handling.
1168 ///
1169 /// # Returns
1170 ///
1171 /// On success, returns `Ok((pixels, (width, height)))` where:
1172 /// - `pixels` is a `Vec<u8>` containing raw BGRA pixel data
1173 /// - `width` and `height` are the frame dimensions in pixels
1174 /// - Data is in the native orientation (no rotation correction)
1175 ///
1176 /// # Examples
1177 ///
1178 /// ```rust,no_run
1179 /// use dxgi_capture_rs::DXGIManager;
1180 ///
1181 /// let mut manager = DXGIManager::new(100)?;
1182 ///
1183 /// match manager.capture_frame_fast() {
1184 /// Ok((pixels, (width, height))) => {
1185 /// println!("Fast captured {}x{} frame", width, height);
1186 /// }
1187 /// Err(e) => eprintln!("Fast capture failed: {:?}", e),
1188 /// }
1189 /// # Ok::<(), Box<dyn std::error::Error>>(())
1190 /// ```
1191 pub fn capture_frame_fast(&mut self) -> Result<(Vec<u8>, (usize, usize)), CaptureError> {
1192 let (surface, _) = self.acquire_surface(false)?;
1193
1194 let mut rect = DXGI_MAPPED_RECT::default();
1195 unsafe { surface.Map(&mut rect, DXGI_MAP_READ)? };
1196
1197 let desc = self
1198 .duplicated_output
1199 .as_ref()
1200 .ok_or(CaptureError::RefreshFailure)?
1201 .get_desc()?;
1202 let width = (desc.DesktopCoordinates.right - desc.DesktopCoordinates.left) as usize;
1203 let height = (desc.DesktopCoordinates.bottom - desc.DesktopCoordinates.top) as usize;
1204
1205 let pitch = rect.Pitch as usize;
1206 let source = rect.pBits;
1207
1208 let bytes_per_row = width * 4;
1209 let mut data_vec = Vec::with_capacity(width * height * 4);
1210
1211 unsafe {
1212 if pitch == bytes_per_row {
1213 let total_bytes = width * height * 4;
1214 let source_slice = slice::from_raw_parts(source as *const u8, total_bytes);
1215 data_vec.extend_from_slice(source_slice);
1216 } else {
1217 let source_slice = slice::from_raw_parts(source as *const u8, pitch * height);
1218 for row in 0..height {
1219 let row_start = row * pitch;
1220 let row_end = row_start + bytes_per_row;
1221 data_vec.extend_from_slice(&source_slice[row_start..row_end]);
1222 }
1223 }
1224 }
1225
1226 unsafe { surface.Unmap()? };
1227
1228 Ok((data_vec, (width, height)))
1229 }
1230
1231 /// Captures a single frame and returns it as `Vec<BGRA8>` along with frame metadata.
1232 ///
1233 /// This method captures the current screen content and returns it as a vector
1234 /// of [`BGRA8`] pixels along with comprehensive metadata about the frame, including
1235 /// dirty rectangles, moved rectangles, and timing information.
1236 ///
1237 /// # Returns
1238 ///
1239 /// On success, returns `Ok((pixels, (width, height), metadata))` where:
1240 /// - `pixels` is a `Vec<BGRA8>` containing the pixel data
1241 /// - `width` and `height` are the frame dimensions in pixels
1242 /// - `metadata` is a [`FrameMetadata`] struct containing detailed frame information
1243 ///
1244 /// # Examples
1245 ///
1246 /// ```rust,no_run
1247 /// use dxgi_capture_rs::{DXGIManager, CaptureError};
1248 ///
1249 /// let mut manager = DXGIManager::new(1000)?;
1250 ///
1251 /// match manager.capture_frame_with_metadata() {
1252 /// Ok((pixels, (width, height), metadata)) => {
1253 /// println!("Captured {}x{} frame with {} dirty rects and {} move rects",
1254 /// width, height, metadata.dirty_rects.len(), metadata.move_rects.len());
1255 /// }
1256 /// Err(CaptureError::Timeout) => {
1257 /// // No new frame available within timeout
1258 /// }
1259 /// Err(e) => eprintln!("Capture failed: {:?}", e),
1260 /// }
1261 /// # Ok::<(), Box<dyn std::error::Error>>(())
1262 /// ```
1263 pub fn capture_frame_with_metadata(&mut self) -> CaptureFrameWithMetadataResult {
1264 let (surface, metadata) = self.acquire_surface(true)?;
1265 let (data, dims) = self.copy_surface_data::<BGRA8>(&surface)?;
1266 Ok((data, dims, metadata.unwrap()))
1267 }
1268
1269 /// Captures a single frame and returns it as `Vec<u8>` along with frame metadata.
1270 ///
1271 /// This method captures the current screen content and returns it as a vector
1272 /// of raw bytes representing the pixel components along with comprehensive
1273 /// metadata about the frame.
1274 ///
1275 /// # Returns
1276 ///
1277 /// On success, returns `Ok((components, (width, height), metadata))` where:
1278 /// - `components` is a `Vec<u8>` containing the raw pixel component data
1279 /// - `width` and `height` are the frame dimensions in pixels
1280 /// - `metadata` is a [`FrameMetadata`] struct containing detailed frame information
1281 ///
1282 /// # Examples
1283 ///
1284 /// ```rust,no_run
1285 /// use dxgi_capture_rs::DXGIManager;
1286 ///
1287 /// let mut manager = DXGIManager::new(1000)?;
1288 ///
1289 /// match manager.capture_frame_components_with_metadata() {
1290 /// Ok((components, (width, height), metadata)) => {
1291 /// println!("Captured {}x{} frame with {} bytes", width, height, components.len());
1292 /// if metadata.has_updates() {
1293 /// println!("Frame has {} total changes", metadata.total_change_count());
1294 /// }
1295 /// }
1296 /// Err(e) => eprintln!("Capture failed: {:?}", e),
1297 /// }
1298 /// # Ok::<(), Box<dyn std::error::Error>>(())
1299 /// ```
1300 pub fn capture_frame_components_with_metadata(
1301 &mut self,
1302 ) -> CaptureFrameComponentsWithMetadataResult {
1303 let (surface, metadata) = self.acquire_surface(true)?;
1304 let (data, dims) = self.copy_surface_data::<u8>(&surface)?;
1305 Ok((data, dims, metadata.unwrap()))
1306 }
1307}
1308
1309pub type CaptureFrameWithMetadataResult =
1310 Result<(Vec<BGRA8>, (usize, usize), FrameMetadata), CaptureError>;
1311
1312pub type CaptureFrameComponentsWithMetadataResult =
1313 Result<(Vec<u8>, (usize, usize), FrameMetadata), CaptureError>;