#![cfg(windows)]
#![cfg_attr(docsrs, feature(doc_cfg))]
#![cfg_attr(docsrs, doc(cfg(windows)))]
use std::fmt;
use std::{mem, slice};
use windows::Win32::Graphics::Direct3D::D3D_FEATURE_LEVEL;
use windows::{
Win32::{
Foundation::{HMODULE, RECT},
Graphics::{
Direct3D::{D3D_DRIVER_TYPE_UNKNOWN, D3D_FEATURE_LEVEL_9_1},
Direct3D11::{
D3D11_CPU_ACCESS_READ, D3D11_CREATE_DEVICE_BGRA_SUPPORT, D3D11_SDK_VERSION,
D3D11_TEXTURE2D_DESC, D3D11_USAGE_STAGING, D3D11CreateDevice, ID3D11Device,
ID3D11DeviceContext, ID3D11Texture2D,
},
Dxgi::{
Common::{
DXGI_MODE_ROTATION_IDENTITY, DXGI_MODE_ROTATION_ROTATE90,
DXGI_MODE_ROTATION_ROTATE180, DXGI_MODE_ROTATION_ROTATE270,
DXGI_MODE_ROTATION_UNSPECIFIED,
},
CreateDXGIFactory1, DXGI_ERROR_ACCESS_DENIED, DXGI_ERROR_ACCESS_LOST,
DXGI_ERROR_NOT_FOUND, DXGI_ERROR_WAIT_TIMEOUT, DXGI_MAP_READ, DXGI_MAPPED_RECT,
DXGI_OUTDUPL_FRAME_INFO, DXGI_OUTDUPL_MOVE_RECT, DXGI_OUTPUT_DESC, IDXGIAdapter,
IDXGIAdapter1, IDXGIFactory1, IDXGIOutput, IDXGIOutput1, IDXGIOutputDuplication,
IDXGIResource, IDXGISurface1,
},
},
},
core::{Interface, Result as WindowsResult},
};
#[derive(Copy, Clone, Debug, PartialOrd, PartialEq, Eq, Ord)]
pub struct BGRA8 {
pub b: u8,
pub g: u8,
pub r: u8,
pub a: u8,
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub struct MoveRect {
pub source_point: (i32, i32),
pub destination_rect: (i32, i32, i32, i32), }
#[derive(Clone, Debug)]
pub struct FrameMetadata {
pub last_present_time: i64,
pub last_mouse_update_time: i64,
pub accumulated_frames: u32,
pub rects_coalesced: bool,
pub protected_content_masked_out: bool,
pub pointer_position: Option<(i32, i32)>,
pub pointer_visible: bool,
pub dirty_rects: Vec<(i32, i32, i32, i32)>, pub move_rects: Vec<MoveRect>,
}
impl FrameMetadata {
pub fn has_updates(&self) -> bool {
!self.dirty_rects.is_empty() || !self.move_rects.is_empty()
}
pub fn has_mouse_updates(&self) -> bool {
self.last_mouse_update_time > 0
}
pub fn total_change_count(&self) -> usize {
self.dirty_rects.len() + self.move_rects.len()
}
}
#[derive(Debug)]
pub enum CaptureError {
AccessDenied,
AccessLost,
RefreshFailure,
Timeout,
Fail(windows::core::Error),
}
impl fmt::Display for CaptureError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
CaptureError::AccessDenied => write!(f, "Access to output duplication was denied"),
CaptureError::AccessLost => write!(f, "Access to duplicated output was lost"),
CaptureError::RefreshFailure => write!(f, "Failed to refresh output duplication"),
CaptureError::Timeout => write!(f, "Capture operation timed out"),
CaptureError::Fail(msg) => write!(f, "Capture failed: {msg}"),
}
}
}
impl std::error::Error for CaptureError {}
impl From<windows::core::Error> for CaptureError {
fn from(err: windows::core::Error) -> Self {
CaptureError::Fail(err)
}
}
#[derive(Debug)]
pub enum OutputDuplicationError {
NoOutput,
DeviceError(windows::core::Error),
}
impl fmt::Display for OutputDuplicationError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
OutputDuplicationError::NoOutput => write!(f, "No suitable output display was found"),
OutputDuplicationError::DeviceError(err) => {
write!(f, "Failed to create D3D11 device: {err}")
}
}
}
}
impl std::error::Error for OutputDuplicationError {}
impl From<windows::core::Error> for OutputDuplicationError {
fn from(err: windows::core::Error) -> Self {
OutputDuplicationError::DeviceError(err)
}
}
#[deprecated(since = "1.2.0", note = "Use `HRESULT::is_err()` directly instead")]
pub fn hr_failed(hr: windows::core::HRESULT) -> bool {
hr.is_err()
}
fn create_dxgi_factory_1() -> WindowsResult<IDXGIFactory1> {
unsafe { CreateDXGIFactory1() }
}
const DEFAULT_D3D11_FEATURES: [D3D_FEATURE_LEVEL; 1] = [D3D_FEATURE_LEVEL_9_1];
fn d3d11_create_device(
adapter: Option<&IDXGIAdapter>,
feature_levels: Option<&[D3D_FEATURE_LEVEL]>,
) -> WindowsResult<(ID3D11Device, ID3D11DeviceContext)> {
let mut device: Option<ID3D11Device> = None;
let mut device_context: Option<ID3D11DeviceContext> = None;
unsafe {
D3D11CreateDevice(
adapter,
D3D_DRIVER_TYPE_UNKNOWN,
HMODULE::default(),
D3D11_CREATE_DEVICE_BGRA_SUPPORT,
feature_levels,
D3D11_SDK_VERSION,
Some(&mut device),
None,
Some(&mut device_context),
)?;
}
Ok((device.unwrap(), device_context.unwrap()))
}
fn get_output_at_index(
adapter: &IDXGIAdapter1,
index: usize,
) -> WindowsResult<Option<IDXGIOutput>> {
let mut current = 0usize;
for i in 0.. {
match unsafe { adapter.EnumOutputs(i) } {
Ok(output) => {
let desc: DXGI_OUTPUT_DESC = unsafe { output.GetDesc()? };
if desc.AttachedToDesktop.as_bool() {
if current == index {
return Ok(Some(output));
}
current += 1;
}
}
Err(_) => break,
}
}
Ok(None)
}
fn map_capture_error(e: windows::core::Error) -> CaptureError {
let code = e.code();
if code == DXGI_ERROR_ACCESS_LOST {
CaptureError::AccessLost
} else if code == DXGI_ERROR_WAIT_TIMEOUT {
CaptureError::Timeout
} else if code == DXGI_ERROR_ACCESS_DENIED {
CaptureError::AccessDenied
} else {
CaptureError::Fail(e)
}
}
struct DuplicatedOutput {
device: ID3D11Device,
device_context: ID3D11DeviceContext,
output: IDXGIOutput1,
output_duplication: IDXGIOutputDuplication,
}
impl DuplicatedOutput {
fn get_desc(&self) -> WindowsResult<DXGI_OUTPUT_DESC> {
unsafe { self.output.GetDesc() }
}
fn capture_frame_to_surface(
&mut self,
timeout_ms: u32,
with_metadata: bool,
) -> WindowsResult<(IDXGISurface1, Option<FrameMetadata>)> {
let mut resource: Option<IDXGIResource> = None;
let mut frame_info: DXGI_OUTDUPL_FRAME_INFO = unsafe { mem::zeroed() };
unsafe {
self.output_duplication
.AcquireNextFrame(timeout_ms, &mut frame_info, &mut resource)?
};
let metadata = if with_metadata {
Some(self.extract_frame_metadata(&frame_info)?)
} else {
None
};
let texture: ID3D11Texture2D = resource.unwrap().cast()?;
let mut desc = D3D11_TEXTURE2D_DESC::default();
unsafe { texture.GetDesc(&mut desc) };
desc.Usage = D3D11_USAGE_STAGING;
desc.BindFlags = 0;
desc.CPUAccessFlags = D3D11_CPU_ACCESS_READ.0 as u32;
desc.MiscFlags = 0;
let mut staged_texture: Option<ID3D11Texture2D> = None;
unsafe {
self.device
.CreateTexture2D(&desc, None, Some(&mut staged_texture))?
};
let staged_texture = staged_texture.unwrap();
unsafe { self.device_context.CopyResource(&staged_texture, &texture) };
unsafe { self.output_duplication.ReleaseFrame()? };
let surface: IDXGISurface1 = staged_texture.cast()?;
Ok((surface, metadata))
}
fn extract_frame_metadata(
&self,
frame_info: &DXGI_OUTDUPL_FRAME_INFO,
) -> WindowsResult<FrameMetadata> {
let mut dirty_rects = Vec::new();
let mut move_rects = Vec::new();
if frame_info.TotalMetadataBufferSize > 0 {
let mut dirty_rects_buffer_size = 0u32;
let dirty_result = unsafe {
self.output_duplication.GetFrameDirtyRects(
0,
std::ptr::null_mut(),
&mut dirty_rects_buffer_size,
)
};
if dirty_result.is_ok() && dirty_rects_buffer_size > 0 {
let dirty_rect_count = dirty_rects_buffer_size / mem::size_of::<RECT>() as u32;
let mut dirty_rects_buffer: Vec<RECT> =
vec![RECT::default(); dirty_rect_count as usize];
unsafe {
let get_result = self.output_duplication.GetFrameDirtyRects(
dirty_rects_buffer_size,
dirty_rects_buffer.as_mut_ptr(),
&mut dirty_rects_buffer_size,
);
if get_result.is_ok() {
dirty_rects = dirty_rects_buffer
.into_iter()
.map(|rect| (rect.left, rect.top, rect.right, rect.bottom))
.collect();
}
}
}
let mut move_rects_buffer_size = 0u32;
let move_result = unsafe {
self.output_duplication.GetFrameMoveRects(
0,
std::ptr::null_mut(),
&mut move_rects_buffer_size,
)
};
if move_result.is_ok() && move_rects_buffer_size > 0 {
let move_rect_count =
move_rects_buffer_size / mem::size_of::<DXGI_OUTDUPL_MOVE_RECT>() as u32;
let mut move_rects_buffer: Vec<DXGI_OUTDUPL_MOVE_RECT> =
vec![unsafe { mem::zeroed() }; move_rect_count as usize];
unsafe {
let get_result = self.output_duplication.GetFrameMoveRects(
move_rects_buffer_size,
move_rects_buffer.as_mut_ptr(),
&mut move_rects_buffer_size,
);
if get_result.is_ok() {
move_rects = move_rects_buffer
.into_iter()
.map(|move_rect| MoveRect {
source_point: (move_rect.SourcePoint.x, move_rect.SourcePoint.y),
destination_rect: (
move_rect.DestinationRect.left,
move_rect.DestinationRect.top,
move_rect.DestinationRect.right,
move_rect.DestinationRect.bottom,
),
})
.collect();
}
}
}
}
let pointer_position = if frame_info.PointerPosition.Visible.as_bool() {
Some((
frame_info.PointerPosition.Position.x,
frame_info.PointerPosition.Position.y,
))
} else {
None
};
Ok(FrameMetadata {
last_present_time: frame_info.LastPresentTime,
last_mouse_update_time: frame_info.LastMouseUpdateTime,
accumulated_frames: frame_info.AccumulatedFrames,
rects_coalesced: frame_info.RectsCoalesced.as_bool(),
protected_content_masked_out: frame_info.ProtectedContentMaskedOut.as_bool(),
pointer_position,
pointer_visible: frame_info.PointerPosition.Visible.as_bool(),
dirty_rects,
move_rects,
})
}
}
pub struct DXGIManager {
factory: IDXGIFactory1,
duplicated_output: Option<DuplicatedOutput>,
capture_source_index: usize,
timeout_ms: u32,
}
impl DXGIManager {
pub fn new(timeout_ms: u32) -> Result<Self, OutputDuplicationError> {
let factory = create_dxgi_factory_1()?;
let mut manager = Self {
factory,
duplicated_output: None,
capture_source_index: 0,
timeout_ms,
};
manager.acquire_output_duplication()?;
Ok(manager)
}
pub fn geometry(&self) -> (usize, usize) {
if let Some(ref output) = self.duplicated_output {
let output_desc = output.get_desc().expect("Failed to get output description");
let RECT {
left,
top,
right,
bottom,
} = output_desc.DesktopCoordinates;
((right - left) as usize, (bottom - top) as usize)
} else {
(0, 0)
}
}
pub fn set_capture_source_index(&mut self, cs: usize) {
let previous_index = self.capture_source_index;
self.capture_source_index = cs;
if self.acquire_output_duplication().is_err() && cs == 0 && cs != previous_index {
self.capture_source_index = previous_index;
let _ = self.acquire_output_duplication();
}
}
pub fn get_capture_source_index(&self) -> usize {
self.capture_source_index
}
pub fn set_timeout_ms(&mut self, timeout_ms: u32) {
self.timeout_ms = timeout_ms
}
pub fn get_timeout_ms(&self) -> u32 {
self.timeout_ms
}
pub fn acquire_output_duplication(&mut self) -> Result<(), OutputDuplicationError> {
self.duplicated_output = None;
for i in 0.. {
let adapter = match unsafe { self.factory.EnumAdapters1(i) } {
Ok(adapter) => adapter,
Err(e) if e.code() == DXGI_ERROR_NOT_FOUND => break,
Err(e) => return Err(e.into()),
};
let (mut d3d11_device, mut device_context) =
match d3d11_create_device(Some(&adapter.cast()?), Some(&DEFAULT_D3D11_FEATURES)) {
Ok(device) => device,
Err(_) => continue,
};
let output = match get_output_at_index(&adapter, self.capture_source_index)? {
Some(output) => output,
None => continue,
};
let output1: IDXGIOutput1 = output.cast()?;
let output_duplication = match unsafe { output1.DuplicateOutput(&d3d11_device) } {
Ok(dup) => dup,
Err(_) => {
match d3d11_create_device(Some(&adapter.cast()?), None) {
Ok((new_device, new_context)) => {
match unsafe { output1.DuplicateOutput(&new_device) } {
Ok(output_dup) => {
d3d11_device = new_device;
device_context = new_context;
output_dup
}
Err(_) => continue,
}
}
Err(_) => continue,
}
}
};
self.duplicated_output = Some(DuplicatedOutput {
device: d3d11_device,
device_context,
output: output1,
output_duplication,
});
return Ok(());
}
Err(OutputDuplicationError::NoOutput)
}
fn acquire_surface(
&mut self,
with_metadata: bool,
) -> Result<(IDXGISurface1, Option<FrameMetadata>), CaptureError> {
if self.duplicated_output.is_none() && self.acquire_output_duplication().is_err() {
return Err(CaptureError::RefreshFailure);
}
let timeout_ms = self.timeout_ms;
let dup = self.duplicated_output.as_mut().unwrap();
match dup.capture_frame_to_surface(timeout_ms, with_metadata) {
Ok(result) => Ok(result),
Err(e) => {
let err = map_capture_error(e);
if !matches!(err, CaptureError::Timeout) {
self.duplicated_output = None;
}
Err(err)
}
}
}
fn copy_surface_data<T: Copy + Send + Sync + Sized>(
&self,
surface: &IDXGISurface1,
) -> Result<(Vec<T>, (usize, usize)), CaptureError> {
let mut rect = DXGI_MAPPED_RECT::default();
unsafe { surface.Map(&mut rect, DXGI_MAP_READ)? };
let desc = self
.duplicated_output
.as_ref()
.ok_or(CaptureError::RefreshFailure)?
.get_desc()?;
let width = (desc.DesktopCoordinates.right - desc.DesktopCoordinates.left) as usize;
let height = (desc.DesktopCoordinates.bottom - desc.DesktopCoordinates.top) as usize;
let pitch = rect.Pitch as usize;
let source = rect.pBits;
let (rotated_width, rotated_height) = match desc.Rotation {
DXGI_MODE_ROTATION_ROTATE90 | DXGI_MODE_ROTATION_ROTATE270 => (height, width),
_ => (width, height),
};
let bytes_per_pixel = mem::size_of::<BGRA8>() / mem::size_of::<T>();
let source_slice = unsafe {
slice::from_raw_parts(source as *const T, pitch * height / mem::size_of::<T>())
};
let mut data_vec: Vec<T> =
Vec::with_capacity(rotated_width * rotated_height * bytes_per_pixel);
match desc.Rotation {
DXGI_MODE_ROTATION_IDENTITY | DXGI_MODE_ROTATION_UNSPECIFIED => {
for i in 0..height {
let start = i * pitch / mem::size_of::<T>();
let end = start + width * bytes_per_pixel;
data_vec.extend_from_slice(&source_slice[start..end]);
}
}
DXGI_MODE_ROTATION_ROTATE90 => {
for i in 0..width {
for j in (0..height).rev() {
let index = j * pitch / mem::size_of::<T>() + i * bytes_per_pixel;
data_vec.extend_from_slice(&source_slice[index..index + bytes_per_pixel]);
}
}
}
DXGI_MODE_ROTATION_ROTATE180 => {
for i in (0..height).rev() {
for j in (0..width).rev() {
let index = i * pitch / mem::size_of::<T>() + j * bytes_per_pixel;
data_vec.extend_from_slice(&source_slice[index..index + bytes_per_pixel]);
}
}
}
DXGI_MODE_ROTATION_ROTATE270 => {
for i in (0..width).rev() {
for j in 0..height {
let index = j * pitch / mem::size_of::<T>() + i * bytes_per_pixel;
data_vec.extend_from_slice(&source_slice[index..index + bytes_per_pixel]);
}
}
}
_ => {}
}
unsafe { surface.Unmap()? };
Ok((data_vec, (rotated_width, rotated_height)))
}
pub fn capture_frame(&mut self) -> Result<(Vec<BGRA8>, (usize, usize)), CaptureError> {
let (surface, _) = self.acquire_surface(false)?;
self.copy_surface_data(&surface)
}
pub fn capture_frame_components(&mut self) -> Result<(Vec<u8>, (usize, usize)), CaptureError> {
let (surface, _) = self.acquire_surface(false)?;
self.copy_surface_data(&surface)
}
pub fn capture_frame_fast(&mut self) -> Result<(Vec<u8>, (usize, usize)), CaptureError> {
let (surface, _) = self.acquire_surface(false)?;
let mut rect = DXGI_MAPPED_RECT::default();
unsafe { surface.Map(&mut rect, DXGI_MAP_READ)? };
let desc = self
.duplicated_output
.as_ref()
.ok_or(CaptureError::RefreshFailure)?
.get_desc()?;
let width = (desc.DesktopCoordinates.right - desc.DesktopCoordinates.left) as usize;
let height = (desc.DesktopCoordinates.bottom - desc.DesktopCoordinates.top) as usize;
let pitch = rect.Pitch as usize;
let source = rect.pBits;
let bytes_per_row = width * 4;
let mut data_vec = Vec::with_capacity(width * height * 4);
unsafe {
if pitch == bytes_per_row {
let total_bytes = width * height * 4;
let source_slice = slice::from_raw_parts(source as *const u8, total_bytes);
data_vec.extend_from_slice(source_slice);
} else {
let source_slice = slice::from_raw_parts(source as *const u8, pitch * height);
for row in 0..height {
let row_start = row * pitch;
let row_end = row_start + bytes_per_row;
data_vec.extend_from_slice(&source_slice[row_start..row_end]);
}
}
}
unsafe { surface.Unmap()? };
Ok((data_vec, (width, height)))
}
pub fn capture_frame_with_metadata(&mut self) -> CaptureFrameWithMetadataResult {
let (surface, metadata) = self.acquire_surface(true)?;
let (data, dims) = self.copy_surface_data::<BGRA8>(&surface)?;
Ok((data, dims, metadata.unwrap()))
}
pub fn capture_frame_components_with_metadata(
&mut self,
) -> CaptureFrameComponentsWithMetadataResult {
let (surface, metadata) = self.acquire_surface(true)?;
let (data, dims) = self.copy_surface_data::<u8>(&surface)?;
Ok((data, dims, metadata.unwrap()))
}
}
pub type CaptureFrameWithMetadataResult =
Result<(Vec<BGRA8>, (usize, usize), FrameMetadata), CaptureError>;
pub type CaptureFrameComponentsWithMetadataResult =
Result<(Vec<u8>, (usize, usize), FrameMetadata), CaptureError>;