rusty_duplication/capturer/
shared.rs

1use super::CapturerBuffer;
2use crate::{Capturer, Error, Monitor, Result};
3use std::ffi::CString;
4use std::slice;
5use windows::{
6  core::PCSTR,
7  Win32::{
8    Foundation::{CloseHandle, HANDLE, INVALID_HANDLE_VALUE},
9    System::Memory::{
10      CreateFileMappingA, MapViewOfFile, OpenFileMappingA, UnmapViewOfFile, FILE_MAP_ALL_ACCESS,
11      MEMORY_MAPPED_VIEW_ADDRESS, PAGE_READWRITE,
12    },
13  },
14};
15
16/// This is not clone-able.
17#[derive(Debug)]
18pub struct SharedMemory {
19  ptr: *mut u8,
20  len: usize,
21  file: HANDLE,
22}
23
24impl CapturerBuffer for SharedMemory {
25  #[inline]
26  fn as_bytes(&self) -> &[u8] {
27    unsafe { slice::from_raw_parts(self.ptr, self.len) }
28  }
29
30  #[inline]
31  fn as_bytes_mut(&mut self) -> &mut [u8] {
32    unsafe { slice::from_raw_parts_mut(self.ptr, self.len) }
33  }
34}
35
36impl Drop for SharedMemory {
37  fn drop(&mut self) {
38    // TODO: add debug log
39    unsafe {
40      UnmapViewOfFile(MEMORY_MAPPED_VIEW_ADDRESS {
41        Value: self.ptr as _,
42      })
43      .ok();
44      CloseHandle(self.file).ok();
45    }
46  }
47}
48
49/// Capture screen to a chunk of shared memory.
50///
51/// To create an instance, see [`SharedMemoryCapturer::create`] and [`SharedMemoryCapturer::open`].
52pub type SharedMemoryCapturer = Capturer<SharedMemory>;
53
54impl SharedMemoryCapturer {
55  /// Create an instance by creating a new shared memory with the provided name.
56  pub fn create(monitor: Monitor, name: &str) -> Result<Self> {
57    Self::new(monitor, move |len| unsafe {
58      let file = CreateFileMappingA(
59        INVALID_HANDLE_VALUE,
60        None,
61        PAGE_READWRITE,
62        0,
63        len as u32,
64        str_to_pc_str(name),
65      )
66      .map_err(Error::from_win_err(stringify!(CreateFileMappingA)))?;
67
68      Ok(SharedMemory {
69        ptr: map_view_of_file(file, len)?,
70        len,
71        file,
72      })
73    })
74  }
75
76  /// Create an instance by opening an existing shared memory with the provided name.
77  pub fn open(monitor: Monitor, name: &str) -> Result<Self> {
78    Self::new(monitor, move |len| unsafe {
79      let file = OpenFileMappingA(FILE_MAP_ALL_ACCESS.0, false, str_to_pc_str(name))
80        .map_err(Error::from_win_err(stringify!(OpenFileMappingA)))?;
81
82      Ok(SharedMemory {
83        ptr: map_view_of_file(file, len)?,
84        len,
85        file,
86      })
87    })
88  }
89}
90
91unsafe fn map_view_of_file(file: HANDLE, buffer_size: usize) -> Result<*mut u8> {
92  let buffer_ptr = MapViewOfFile(
93    file,                // handle to map object
94    FILE_MAP_ALL_ACCESS, // read/write permission
95    0,
96    0,
97    buffer_size,
98  );
99
100  if buffer_ptr.Value.is_null() {
101    CloseHandle(file).ok();
102    return Err(Error::last_win_err(stringify!(MapViewOfFile)));
103  }
104
105  Ok(buffer_ptr.Value as *mut u8)
106}
107
108#[inline]
109fn str_to_pc_str(s: &str) -> PCSTR {
110  let c_str = CString::new(s).unwrap(); // make the name null terminated
111  PCSTR(c_str.as_ptr() as _)
112}
113
114#[cfg(test)]
115mod tests {
116  use super::*;
117  use crate::{FrameInfoExt, Scanner};
118  use serial_test::serial;
119  use std::{thread, time::Duration};
120
121  #[test]
122  #[serial]
123  fn shared_capturer() {
124    let monitor = Scanner::new().unwrap().next().unwrap();
125    let mut capturer = SharedMemoryCapturer::create(monitor, "RustyDuplicationTest").unwrap();
126
127    // sleep for a while before capture to wait system to update the screen
128    thread::sleep(Duration::from_millis(50));
129
130    let info = capturer.capture().unwrap();
131    assert!(info.desktop_updated());
132
133    // ensure buffer not all zero
134    assert!(!capturer.buffer.as_bytes().iter().all(|&n| n == 0));
135
136    thread::sleep(Duration::from_millis(50));
137
138    // check mouse
139    let (frame_info, pointer_shape_info) = capturer.capture_with_pointer_shape().unwrap();
140    if frame_info.pointer_shape_updated() {
141      assert!(pointer_shape_info.is_some());
142      // make sure pointer shape buffer is not all zero
143      assert!(!capturer.pointer_shape_buffer.iter().all(|&n| n == 0));
144    } else {
145      panic!(
146        "Move your mouse to change the shape of the cursor during the test to check mouse capture"
147      );
148    }
149  }
150}