//! Screen capture for Linux and Windows use crate::{ DevicePixels, ForegroundExecutor, ScreenCaptureFrame, ScreenCaptureSource, ScreenCaptureStream, Size, SourceMetadata, size, }; use anyhow::{Context as _, Result, anyhow}; use futures::channel::oneshot; use scap::Target; use std::rc::Rc; use std::sync::Arc; use std::sync::atomic::{self, AtomicBool}; /// Populates the receiver with the screens that can be captured. /// /// `scap_default_target_source` should be used instead on Wayland, since `scap_screen_sources` /// won't return any results. #[allow(dead_code)] pub(crate) fn scap_screen_sources( foreground_executor: &ForegroundExecutor, ) -> oneshot::Receiver>>> { let (sources_tx, sources_rx) = oneshot::channel(); get_screen_targets(sources_tx); to_dyn_screen_capture_sources(sources_rx, foreground_executor) } /// Starts screen capture for the default target, and populates the receiver with a single source /// for it. The first frame of the screen capture is used to determine the size of the stream. /// /// On Wayland (Linux), prompts the user to select a target, and populates the receiver with a /// single screen capture source for their selection. #[allow(dead_code)] pub(crate) fn start_scap_default_target_source( foreground_executor: &ForegroundExecutor, ) -> oneshot::Receiver>>> { let (sources_tx, sources_rx) = oneshot::channel(); start_default_target_screen_capture(sources_tx); to_dyn_screen_capture_sources(sources_rx, foreground_executor) } struct ScapCaptureSource { target: scap::Display, size: Size, } /// Populates the sender with the screens available for capture. fn get_screen_targets(sources_tx: oneshot::Sender>>) { // Due to use of blocking APIs, a new thread is used. std::thread::spawn(|| { let targets = match scap::get_all_targets() { Ok(targets) => targets, Err(err) => { sources_tx.send(Err(err)).ok(); return; } }; let sources = targets .into_iter() .filter_map(|target| match target { scap::Target::Display(display) => { let size = Size { width: DevicePixels(display.width as i32), height: DevicePixels(display.height as i32), }; Some(ScapCaptureSource { target: display, size, }) } scap::Target::Window(_) => None, }) .collect::>(); sources_tx.send(Ok(sources)).ok(); }); } impl ScreenCaptureSource for ScapCaptureSource { fn metadata(&self) -> Result { Ok(SourceMetadata { resolution: self.size, label: Some(self.target.title.clone().into()), is_main: None, id: self.target.id as u64, }) } fn stream( &self, foreground_executor: &ForegroundExecutor, frame_callback: Box, ) -> oneshot::Receiver>> { let (stream_tx, stream_rx) = oneshot::channel(); let target = self.target.clone(); // Due to use of blocking APIs, a dedicated thread is used. std::thread::spawn(move || { match new_scap_capturer(Some(scap::Target::Display(target.clone()))) { Ok(mut capturer) => { capturer.start_capture(); run_capture(capturer, target.clone(), frame_callback, stream_tx); } Err(e) => { stream_tx.send(Err(e)).ok(); } } }); to_dyn_screen_capture_stream(stream_rx, foreground_executor) } } struct ScapDefaultTargetCaptureSource { // Sender populated by single call to `ScreenCaptureSource::stream`. stream_call_tx: std::sync::mpsc::SyncSender<( // Provides the result of `ScreenCaptureSource::stream`. oneshot::Sender>, // Callback for frames. Box, )>, target: scap::Display, size: Size, } /// Starts screen capture on the default capture target, and populates the sender with the source. fn start_default_target_screen_capture( sources_tx: oneshot::Sender>>, ) { // Due to use of blocking APIs, a dedicated thread is used. std::thread::spawn(|| { let start_result = util::maybe!({ let mut capturer = new_scap_capturer(None)?; capturer.start_capture(); let first_frame = capturer .get_next_frame() .context("Failed to get first frame of screenshare to get the size.")?; let size = frame_size(&first_frame); let target = capturer .target() .context("Unable to determine the target display.")?; let target = target.clone(); Ok((capturer, size, target)) }); match start_result { Ok((capturer, size, Target::Display(display))) => { let (stream_call_tx, stream_rx) = std::sync::mpsc::sync_channel(1); sources_tx .send(Ok(vec![ScapDefaultTargetCaptureSource { stream_call_tx, size, target: display.clone(), }])) .ok(); let Ok((stream_tx, frame_callback)) = stream_rx.recv() else { return; }; run_capture(capturer, display, frame_callback, stream_tx); } Err(e) => { sources_tx.send(Err(e)).ok(); } _ => { sources_tx .send(Err(anyhow!("The screen capture source is not a display"))) .ok(); } } }); } impl ScreenCaptureSource for ScapDefaultTargetCaptureSource { fn metadata(&self) -> Result { Ok(SourceMetadata { resolution: self.size, label: None, is_main: None, id: self.target.id as u64, }) } fn stream( &self, foreground_executor: &ForegroundExecutor, frame_callback: Box, ) -> oneshot::Receiver>> { let (tx, rx) = oneshot::channel(); match self.stream_call_tx.try_send((tx, frame_callback)) { Ok(()) => {} Err(std::sync::mpsc::TrySendError::Full((tx, _))) | Err(std::sync::mpsc::TrySendError::Disconnected((tx, _))) => { // Note: support could be added for being called again after end of prior stream. tx.send(Err(anyhow!( "Can't call ScapDefaultTargetCaptureSource::stream multiple times." ))) .ok(); } } to_dyn_screen_capture_stream(rx, foreground_executor) } } fn new_scap_capturer(target: Option) -> Result { scap::capturer::Capturer::build(scap::capturer::Options { fps: 60, show_cursor: true, show_highlight: true, // Note that the actual frame output type may differ. output_type: scap::frame::FrameType::YUVFrame, output_resolution: scap::capturer::Resolution::Captured, crop_area: None, target, excluded_targets: None, }) } fn run_capture( mut capturer: scap::capturer::Capturer, display: scap::Display, frame_callback: Box, stream_tx: oneshot::Sender>, ) { let cancel_stream = Arc::new(AtomicBool::new(false)); let size = Size { width: DevicePixels(display.width as i32), height: DevicePixels(display.height as i32), }; let stream_send_result = stream_tx.send(Ok(ScapStream { cancel_stream: cancel_stream.clone(), display, size, })); if let Err(_) = stream_send_result { return; } while !cancel_stream.load(std::sync::atomic::Ordering::SeqCst) { match capturer.get_next_frame() { Ok(frame) => frame_callback(ScreenCaptureFrame(frame)), Err(err) => { log::error!("Halting screen capture due to error: {err}"); break; } } } capturer.stop_capture(); } struct ScapStream { cancel_stream: Arc, display: scap::Display, size: Size, } impl ScreenCaptureStream for ScapStream { fn metadata(&self) -> Result { Ok(SourceMetadata { resolution: self.size, label: Some(self.display.title.clone().into()), is_main: None, id: self.display.id as u64, }) } } impl Drop for ScapStream { fn drop(&mut self) { self.cancel_stream.store(true, atomic::Ordering::SeqCst); } } fn frame_size(frame: &scap::frame::Frame) -> Size { let (width, height) = match frame { scap::frame::Frame::YUVFrame(frame) => (frame.width, frame.height), scap::frame::Frame::RGB(frame) => (frame.width, frame.height), scap::frame::Frame::RGBx(frame) => (frame.width, frame.height), scap::frame::Frame::XBGR(frame) => (frame.width, frame.height), scap::frame::Frame::BGRx(frame) => (frame.width, frame.height), scap::frame::Frame::BGR0(frame) => (frame.width, frame.height), scap::frame::Frame::BGRA(frame) => (frame.width, frame.height), }; size(DevicePixels(width), DevicePixels(height)) } /// This is used by `get_screen_targets` and `start_default_target_screen_capture` to turn their /// results into `Rc`. They need to `Send` their capture source, and so /// the capture source structs are used as `Rc` is not `Send`. fn to_dyn_screen_capture_sources( sources_rx: oneshot::Receiver>>, foreground_executor: &ForegroundExecutor, ) -> oneshot::Receiver>>> { let (dyn_sources_tx, dyn_sources_rx) = oneshot::channel(); foreground_executor .spawn(async move { match sources_rx.await { Ok(Ok(results)) => dyn_sources_tx .send(Ok(results .into_iter() .map(|source| Rc::new(source) as Rc) .collect::>())) .ok(), Ok(Err(err)) => dyn_sources_tx.send(Err(err)).ok(), Err(oneshot::Canceled) => None, } }) .detach(); dyn_sources_rx } /// Same motivation as `to_dyn_screen_capture_sources` above. fn to_dyn_screen_capture_stream( sources_rx: oneshot::Receiver>, foreground_executor: &ForegroundExecutor, ) -> oneshot::Receiver>> { let (dyn_sources_tx, dyn_sources_rx) = oneshot::channel(); foreground_executor .spawn(async move { match sources_rx.await { Ok(Ok(stream)) => dyn_sources_tx .send(Ok(Box::new(stream) as Box)) .ok(), Ok(Err(err)) => dyn_sources_tx.send(Err(err)).ok(), Err(oneshot::Canceled) => None, } }) .detach(); dyn_sources_rx }