scap_screen_capture.rs

  1//! Screen capture for Linux and Windows
  2use crate::{
  3    DevicePixels, ForegroundExecutor, ScreenCaptureFrame, ScreenCaptureSource, ScreenCaptureStream,
  4    Size, size,
  5};
  6use anyhow::{Context as _, Result, anyhow};
  7use futures::channel::oneshot;
  8use std::sync::Arc;
  9use std::sync::atomic::{self, AtomicBool};
 10
 11/// Populates the receiver with the screens that can be captured.
 12///
 13/// `scap_default_target_source` should be used instead on Wayland, since `scap_screen_sources`
 14/// won't return any results.
 15#[allow(dead_code)]
 16pub(crate) fn scap_screen_sources(
 17    foreground_executor: &ForegroundExecutor,
 18) -> oneshot::Receiver<Result<Vec<Box<dyn ScreenCaptureSource>>>> {
 19    let (sources_tx, sources_rx) = oneshot::channel();
 20    get_screen_targets(sources_tx);
 21    to_dyn_screen_capture_sources(sources_rx, foreground_executor)
 22}
 23
 24/// Starts screen capture for the default target, and populates the receiver with a single source
 25/// for it. The first frame of the screen capture is used to determine the size of the stream.
 26///
 27/// On Wayland (Linux), prompts the user to select a target, and populates the receiver with a
 28/// single screen capture source for their selection.
 29#[allow(dead_code)]
 30pub(crate) fn start_scap_default_target_source(
 31    foreground_executor: &ForegroundExecutor,
 32) -> oneshot::Receiver<Result<Vec<Box<dyn ScreenCaptureSource>>>> {
 33    let (sources_tx, sources_rx) = oneshot::channel();
 34    start_default_target_screen_capture(sources_tx);
 35    to_dyn_screen_capture_sources(sources_rx, foreground_executor)
 36}
 37
 38struct ScapCaptureSource {
 39    target: scap::Target,
 40    size: Size<DevicePixels>,
 41}
 42
 43/// Populates the sender with the screens available for capture.
 44fn get_screen_targets(sources_tx: oneshot::Sender<Result<Vec<ScapCaptureSource>>>) {
 45    // Due to use of blocking APIs, a new thread is used.
 46    std::thread::spawn(|| {
 47        let targets = match scap::get_all_targets() {
 48            Ok(targets) => targets,
 49            Err(err) => {
 50                sources_tx.send(Err(err)).ok();
 51                return;
 52            }
 53        };
 54        let sources = targets
 55            .iter()
 56            .filter_map(|target| match target {
 57                scap::Target::Display(display) => {
 58                    let size = Size {
 59                        width: DevicePixels(display.width as i32),
 60                        height: DevicePixels(display.height as i32),
 61                    };
 62                    Some(ScapCaptureSource {
 63                        target: target.clone(),
 64                        size,
 65                    })
 66                }
 67                scap::Target::Window(_) => None,
 68            })
 69            .collect::<Vec<_>>();
 70        sources_tx.send(Ok(sources)).ok();
 71    });
 72}
 73
 74impl ScreenCaptureSource for ScapCaptureSource {
 75    fn resolution(&self) -> Result<Size<DevicePixels>> {
 76        Ok(self.size)
 77    }
 78
 79    fn stream(
 80        &self,
 81        foreground_executor: &ForegroundExecutor,
 82        frame_callback: Box<dyn Fn(ScreenCaptureFrame) + Send>,
 83    ) -> oneshot::Receiver<Result<Box<dyn ScreenCaptureStream>>> {
 84        let (stream_tx, stream_rx) = oneshot::channel();
 85        let target = self.target.clone();
 86
 87        // Due to use of blocking APIs, a dedicated thread is used.
 88        std::thread::spawn(move || match new_scap_capturer(Some(target)) {
 89            Ok(mut capturer) => {
 90                capturer.start_capture();
 91                run_capture(capturer, frame_callback, stream_tx);
 92            }
 93            Err(e) => {
 94                stream_tx.send(Err(e)).ok();
 95            }
 96        });
 97
 98        to_dyn_screen_capture_stream(stream_rx, foreground_executor)
 99    }
100}
101
102struct ScapDefaultTargetCaptureSource {
103    // Sender populated by single call to `ScreenCaptureSource::stream`.
104    stream_call_tx: std::sync::mpsc::SyncSender<(
105        // Provides the result of `ScreenCaptureSource::stream`.
106        oneshot::Sender<Result<ScapStream>>,
107        // Callback for frames.
108        Box<dyn Fn(ScreenCaptureFrame) + Send>,
109    )>,
110    size: Size<DevicePixels>,
111}
112
113/// Starts screen capture on the default capture target, and populates the sender with the source.
114fn start_default_target_screen_capture(
115    sources_tx: oneshot::Sender<Result<Vec<ScapDefaultTargetCaptureSource>>>,
116) {
117    // Due to use of blocking APIs, a dedicated thread is used.
118    std::thread::spawn(|| {
119        let start_result = util::maybe!({
120            let mut capturer = new_scap_capturer(None)?;
121            capturer.start_capture();
122            let first_frame = capturer
123                .get_next_frame()
124                .context("Failed to get first frame of screenshare to get the size.")?;
125            let size = frame_size(&first_frame);
126            Ok((capturer, size))
127        });
128
129        match start_result {
130            Err(e) => {
131                sources_tx.send(Err(e)).ok();
132            }
133            Ok((capturer, size)) => {
134                let (stream_call_tx, stream_rx) = std::sync::mpsc::sync_channel(1);
135                sources_tx
136                    .send(Ok(vec![ScapDefaultTargetCaptureSource {
137                        stream_call_tx,
138                        size,
139                    }]))
140                    .ok();
141                let Ok((stream_tx, frame_callback)) = stream_rx.recv() else {
142                    return;
143                };
144                run_capture(capturer, frame_callback, stream_tx);
145            }
146        }
147    });
148}
149
150impl ScreenCaptureSource for ScapDefaultTargetCaptureSource {
151    fn resolution(&self) -> Result<Size<DevicePixels>> {
152        Ok(self.size)
153    }
154
155    fn stream(
156        &self,
157        foreground_executor: &ForegroundExecutor,
158        frame_callback: Box<dyn Fn(ScreenCaptureFrame) + Send>,
159    ) -> oneshot::Receiver<Result<Box<dyn ScreenCaptureStream>>> {
160        let (tx, rx) = oneshot::channel();
161        match self.stream_call_tx.try_send((tx, frame_callback)) {
162            Ok(()) => {}
163            Err(std::sync::mpsc::TrySendError::Full((tx, _)))
164            | Err(std::sync::mpsc::TrySendError::Disconnected((tx, _))) => {
165                // Note: support could be added for being called again after end of prior stream.
166                tx.send(Err(anyhow!(
167                    "Can't call ScapDefaultTargetCaptureSource::stream multiple times."
168                )))
169                .ok();
170            }
171        }
172        to_dyn_screen_capture_stream(rx, foreground_executor)
173    }
174}
175
176fn new_scap_capturer(target: Option<scap::Target>) -> Result<scap::capturer::Capturer> {
177    scap::capturer::Capturer::build(scap::capturer::Options {
178        fps: 60,
179        show_cursor: true,
180        show_highlight: true,
181        // Note that the actual frame output type may differ.
182        output_type: scap::frame::FrameType::YUVFrame,
183        output_resolution: scap::capturer::Resolution::Captured,
184        crop_area: None,
185        target,
186        excluded_targets: None,
187    })
188}
189
190fn run_capture(
191    mut capturer: scap::capturer::Capturer,
192    frame_callback: Box<dyn Fn(ScreenCaptureFrame) + Send>,
193    stream_tx: oneshot::Sender<Result<ScapStream>>,
194) {
195    let cancel_stream = Arc::new(AtomicBool::new(false));
196    let stream_send_result = stream_tx.send(Ok(ScapStream {
197        cancel_stream: cancel_stream.clone(),
198    }));
199    if let Err(_) = stream_send_result {
200        return;
201    }
202    while !cancel_stream.load(std::sync::atomic::Ordering::SeqCst) {
203        match capturer.get_next_frame() {
204            Ok(frame) => frame_callback(ScreenCaptureFrame(frame)),
205            Err(err) => {
206                log::error!("Halting screen capture due to error: {err}");
207                break;
208            }
209        }
210    }
211    capturer.stop_capture();
212}
213
214struct ScapStream {
215    cancel_stream: Arc<AtomicBool>,
216}
217
218impl ScreenCaptureStream for ScapStream {}
219
220impl Drop for ScapStream {
221    fn drop(&mut self) {
222        self.cancel_stream.store(true, atomic::Ordering::SeqCst);
223    }
224}
225
226fn frame_size(frame: &scap::frame::Frame) -> Size<DevicePixels> {
227    let (width, height) = match frame {
228        scap::frame::Frame::YUVFrame(frame) => (frame.width, frame.height),
229        scap::frame::Frame::RGB(frame) => (frame.width, frame.height),
230        scap::frame::Frame::RGBx(frame) => (frame.width, frame.height),
231        scap::frame::Frame::XBGR(frame) => (frame.width, frame.height),
232        scap::frame::Frame::BGRx(frame) => (frame.width, frame.height),
233        scap::frame::Frame::BGR0(frame) => (frame.width, frame.height),
234        scap::frame::Frame::BGRA(frame) => (frame.width, frame.height),
235    };
236    size(DevicePixels(width), DevicePixels(height))
237}
238
239/// This is used by `get_screen_targets` and `start_default_target_screen_capture` to turn their
240/// results into `Box<dyn ScreenCaptureSource>`. They need to `Send` their capture source, and so
241/// the capture source structs are used as `Box<dyn ScreenCaptureSource>` is not `Send`.
242fn to_dyn_screen_capture_sources<T: ScreenCaptureSource + 'static>(
243    sources_rx: oneshot::Receiver<Result<Vec<T>>>,
244    foreground_executor: &ForegroundExecutor,
245) -> oneshot::Receiver<Result<Vec<Box<dyn ScreenCaptureSource>>>> {
246    let (dyn_sources_tx, dyn_sources_rx) = oneshot::channel();
247    foreground_executor
248        .spawn(async move {
249            match sources_rx.await {
250                Ok(Ok(results)) => dyn_sources_tx
251                    .send(Ok(results
252                        .into_iter()
253                        .map(|source| Box::new(source) as Box<dyn ScreenCaptureSource>)
254                        .collect::<Vec<_>>()))
255                    .ok(),
256                Ok(Err(err)) => dyn_sources_tx.send(Err(err)).ok(),
257                Err(oneshot::Canceled) => None,
258            }
259        })
260        .detach();
261    dyn_sources_rx
262}
263
264/// Same motivation as `to_dyn_screen_capture_sources` above.
265fn to_dyn_screen_capture_stream<T: ScreenCaptureStream + 'static>(
266    sources_rx: oneshot::Receiver<Result<T>>,
267    foreground_executor: &ForegroundExecutor,
268) -> oneshot::Receiver<Result<Box<dyn ScreenCaptureStream>>> {
269    let (dyn_sources_tx, dyn_sources_rx) = oneshot::channel();
270    foreground_executor
271        .spawn(async move {
272            match sources_rx.await {
273                Ok(Ok(stream)) => dyn_sources_tx
274                    .send(Ok(Box::new(stream) as Box<dyn ScreenCaptureStream>))
275                    .ok(),
276                Ok(Err(err)) => dyn_sources_tx.send(Err(err)).ok(),
277                Err(oneshot::Canceled) => None,
278            }
279        })
280        .detach();
281    dyn_sources_rx
282}