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