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>;