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}