Skip to main content

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