rusty_duplication/
monitor.rs

1use crate::{Error, FrameInfoExt, Result};
2use windows::{
3  core::Interface,
4  Win32::Graphics::{
5    Direct3D11::{
6      ID3D11Device, ID3D11DeviceContext, ID3D11Texture2D, D3D11_BIND_FLAG, D3D11_CPU_ACCESS_READ,
7      D3D11_RESOURCE_MISC_FLAG, D3D11_TEXTURE2D_DESC, D3D11_USAGE_STAGING,
8    },
9    Dxgi::{
10      Common::{DXGI_FORMAT_B8G8R8A8_UNORM, DXGI_SAMPLE_DESC},
11      IDXGIOutput1, IDXGIOutputDuplication, IDXGIResource, DXGI_OUTDUPL_DESC,
12      DXGI_OUTDUPL_FRAME_INFO, DXGI_OUTDUPL_POINTER_SHAPE_INFO, DXGI_OUTPUT_DESC,
13      DXGI_RESOURCE_PRIORITY_MAXIMUM,
14    },
15    Gdi::{GetMonitorInfoW, MONITORINFO},
16  },
17};
18
19/// Monitor context for screen duplication.
20/// This is stateless and immutable.
21///
22/// To create a new instance, use [`Scanner`](crate::Scanner).
23#[derive(Debug, Clone)]
24pub struct Monitor {
25  device: ID3D11Device,
26  device_context: ID3D11DeviceContext,
27  output: IDXGIOutput1,
28  output_duplication: IDXGIOutputDuplication,
29}
30
31impl Monitor {
32  #[inline]
33  pub(crate) fn new(
34    device: ID3D11Device,
35    device_context: ID3D11DeviceContext,
36    output: IDXGIOutput1,
37    output_duplication: IDXGIOutputDuplication,
38  ) -> Self {
39    Self {
40      device,
41      device_context,
42      output,
43      output_duplication,
44    }
45  }
46
47  /// This is usually used to check if the monitor is primary.
48  /// # Examples
49  /// ```
50  /// use rusty_duplication::{Scanner, MonitorInfoExt};
51  ///
52  /// let monitor = Scanner::new().unwrap().next().unwrap();
53  /// monitor.monitor_info().unwrap().is_primary();
54  /// ```
55  pub fn monitor_info(&self) -> Result<MONITORINFO> {
56    let h_monitor = self.dxgi_output_desc()?.Monitor;
57    let mut info = MONITORINFO {
58      cbSize: size_of::<MONITORINFO>() as u32,
59      ..Default::default()
60    };
61    if unsafe { GetMonitorInfoW(h_monitor, &mut info).as_bool() } {
62      Ok(info)
63    } else {
64      Err(Error::last_win_err(stringify!(GetMonitorInfoW)))
65    }
66  }
67
68  /// This is usually used to get the screen's position and size.
69  /// # Examples
70  /// ```
71  /// use rusty_duplication::{Scanner, OutputDescExt};
72  ///
73  /// let monitor = Scanner::new().unwrap().next().unwrap();
74  /// let desc = monitor.dxgi_output_desc().unwrap();
75  /// println!("{}x{}", desc.width(), desc.height());
76  /// ```
77  #[inline]
78  pub fn dxgi_output_desc(&self) -> Result<DXGI_OUTPUT_DESC> {
79    unsafe { self.output.GetDesc() }
80      .map_err(Error::from_win_err(stringify!(DXGI_OUTPUT_DESC.GetDesc)))
81  }
82
83  /// This is usually used to get the screen's pixel width/height and buffer size.
84  #[inline]
85  pub fn dxgi_outdupl_desc(&self) -> DXGI_OUTDUPL_DESC {
86    unsafe { self.output_duplication.GetDesc() }
87  }
88
89  pub(crate) fn create_texture(
90    &self,
91    dupl_desc: &DXGI_OUTDUPL_DESC,
92    output_desc: &DXGI_OUTPUT_DESC,
93  ) -> Result<(ID3D11Texture2D, D3D11_TEXTURE2D_DESC)> {
94    // create a readable texture description
95    let texture_desc = D3D11_TEXTURE2D_DESC {
96      BindFlags: D3D11_BIND_FLAG::default().0 as u32,
97      CPUAccessFlags: D3D11_CPU_ACCESS_READ.0 as u32,
98      MiscFlags: D3D11_RESOURCE_MISC_FLAG::default().0 as u32,
99      Usage: D3D11_USAGE_STAGING, // A resource that supports data transfer (copy) from the GPU to the CPU.
100      Width: match output_desc.Rotation.0 {
101        2 | 4 => dupl_desc.ModeDesc.Height,
102        _ => dupl_desc.ModeDesc.Width,
103      },
104      Height: match output_desc.Rotation.0 {
105        2 | 4 => dupl_desc.ModeDesc.Width,
106        _ => dupl_desc.ModeDesc.Height,
107      },
108      MipLevels: 1,
109      ArraySize: 1,
110      Format: DXGI_FORMAT_B8G8R8A8_UNORM,
111      SampleDesc: DXGI_SAMPLE_DESC {
112        Count: 1,
113        Quality: 0,
114      },
115    };
116
117    // create a texture in GPU memory
118    let mut texture: Option<ID3D11Texture2D> = None;
119    unsafe {
120      self
121        .device
122        .CreateTexture2D(&texture_desc, None, Some(&mut texture))
123    }
124    .map_err(Error::from_win_err(stringify!(
125      ID3D11Device.CreateTexture2D
126    )))?;
127    let texture = texture.unwrap();
128    // Lower priorities causes stuff to be needlessly copied from gpu to ram,
129    // causing huge ram usage on some systems.
130    // https://github.com/bryal/dxgcap-rs/blob/208d93368bc64aed783791242410459c878a10fb/src/lib.rs#L225
131    unsafe { texture.SetEvictionPriority(DXGI_RESOURCE_PRIORITY_MAXIMUM.0) };
132
133    Ok((texture, texture_desc))
134  }
135
136  /// Try to process the next frame with the provided `cb`.
137  fn process_next_frame<R>(
138    &self,
139    timeout_ms: u32,
140    texture: &ID3D11Texture2D,
141    cb: impl FnOnce(DXGI_OUTDUPL_FRAME_INFO) -> R,
142  ) -> Result<R> {
143    // acquire GPU texture
144    let mut frame_info = DXGI_OUTDUPL_FRAME_INFO::default();
145    let mut resource: Option<IDXGIResource> = None;
146    unsafe {
147      self
148        .output_duplication
149        .AcquireNextFrame(timeout_ms, &mut frame_info, &mut resource)
150    }
151    .map_err(Error::from_win_err(stringify!(
152      IDXGIOutputDuplication.AcquireNextFrame
153    )))?;
154    let new_texture: ID3D11Texture2D = resource.unwrap().cast().unwrap();
155
156    // copy texture using GPU
157    unsafe { self.device_context.CopyResource(texture, &new_texture) };
158
159    let r = cb(frame_info);
160
161    unsafe { self.output_duplication.ReleaseFrame() }.map_err(Error::from_win_err(stringify!(
162      IDXGIOutputDuplication.ReleaseFrame
163    )))?;
164
165    Ok(r)
166  }
167
168  /// Get the next frame without pointer shape.
169  ///
170  /// To get the pointer shape, use [`Self::next_frame_with_pointer_shape`].
171  #[inline]
172  pub(crate) fn next_frame(
173    &self,
174    timeout_ms: u32,
175    texture: &ID3D11Texture2D,
176  ) -> Result<DXGI_OUTDUPL_FRAME_INFO> {
177    self.process_next_frame(timeout_ms, texture, |r| r)
178  }
179
180  /// If the pointer shape is updated, the `Option<DXGI_OUTDUPL_POINTER_SHAPE_INFO>` will be [`Some`].
181  /// This will resize `pointer_shape_buffer` if needed and update it.
182  pub(crate) fn next_frame_with_pointer_shape(
183    &self,
184    timeout_ms: u32,
185    texture: &ID3D11Texture2D,
186    pointer_shape_buffer: &mut Vec<u8>,
187  ) -> Result<(
188    DXGI_OUTDUPL_FRAME_INFO,
189    Option<DXGI_OUTDUPL_POINTER_SHAPE_INFO>,
190  )> {
191    self
192      .process_next_frame(timeout_ms, texture, |frame_info| {
193        if !frame_info.pointer_shape_updated() {
194          return Ok((frame_info, None));
195        }
196
197        // resize buffer if needed
198        let pointer_shape_buffer_size = frame_info.PointerShapeBufferSize as usize;
199        if pointer_shape_buffer.len() < pointer_shape_buffer_size {
200          pointer_shape_buffer.resize(pointer_shape_buffer_size, 0);
201        }
202
203        // get pointer shape
204        let mut size: u32 = 0;
205        let mut pointer_shape_info = DXGI_OUTDUPL_POINTER_SHAPE_INFO::default();
206        unsafe {
207          self.output_duplication.GetFramePointerShape(
208            pointer_shape_buffer.len() as u32,
209            pointer_shape_buffer.as_mut_ptr() as *mut _,
210            &mut size,
211            &mut pointer_shape_info,
212          )
213        }
214        .map_err(Error::from_win_err(stringify!(
215          IDXGIOutputDuplication.GetFramePointerShape
216        )))?;
217        // fix buffer size
218        pointer_shape_buffer.truncate(size as usize);
219
220        Ok((frame_info, Some(pointer_shape_info)))
221      })
222      .and_then(|r| r)
223  }
224}
225
226#[cfg(test)]
227mod tests {
228  use crate::{MonitorInfoExt, Scanner};
229  use serial_test::serial;
230
231  #[test]
232  #[serial]
233  fn monitor() {
234    let contexts = Scanner::new().unwrap().collect::<Vec<_>>();
235
236    // make sure only one primary monitor
237    let mut primary_monitor_count = 0;
238    for c in &contexts {
239      if c.monitor_info().unwrap().is_primary() {
240        primary_monitor_count += 1;
241      }
242    }
243    assert_eq!(primary_monitor_count, 1);
244  }
245}