1use std::sync::Arc;
13
14use crate::Error;
15use crate::frame::Surface;
16
17mod channel;
18use channel::FrameChannel;
19
20#[cfg(not(target_os = "macos"))]
28type Keepalive = Box<dyn std::any::Any + Send>;
29#[cfg(target_os = "macos")]
30type Keepalive = Box<dyn std::any::Any>;
31
32#[cfg(target_os = "macos")]
33mod avfoundation;
34#[cfg(target_os = "macos")]
35mod screencapture;
36#[cfg(target_os = "macos")]
37mod surface;
38
39#[cfg(target_os = "linux")]
41mod v4l2;
42
43#[cfg(all(target_os = "linux", feature = "pipewire"))]
45mod pipewire;
46
47#[cfg(target_os = "windows")]
49mod mediafoundation;
50
51#[cfg(target_os = "windows")]
53mod desktopduplication;
54
55#[cfg(any(target_os = "linux", target_os = "windows"))]
57mod pump;
58
59#[derive(Clone, Debug, PartialEq, Eq)]
66#[non_exhaustive]
67pub enum Source {
68 Camera(Option<String>),
74
75 Display(Option<String>),
80
81 Window(String),
83
84 App(String),
87}
88
89impl Default for Source {
91 fn default() -> Self {
92 Self::Camera(None)
93 }
94}
95
96impl Source {
97 #[cfg(target_os = "macos")]
105 pub(crate) fn label(&self) -> String {
106 match self {
107 Self::Camera(None) => "camera".to_string(),
108 Self::Camera(Some(id)) => format!("camera:{id}"),
109 Self::Display(None) => "display".to_string(),
110 Self::Display(Some(id)) => format!("display:{id}"),
111 Self::Window(id) => format!("window:{id}"),
112 Self::App(id) => format!("app:{id}"),
113 }
114 }
115}
116
117#[derive(Clone, Debug)]
119pub struct Camera {
120 pub id: String,
122 pub name: String,
124}
125
126impl Camera {
127 pub fn source(&self) -> Source {
129 Source::Camera(Some(self.id.clone()))
130 }
131}
132
133#[derive(Clone, Debug)]
135pub struct Display {
136 pub id: String,
138 pub name: String,
140 pub width: u32,
143 pub height: u32,
145}
146
147impl Display {
148 pub fn source(&self) -> Source {
150 Source::Display(Some(self.id.clone()))
151 }
152}
153
154#[derive(Clone, Debug)]
156pub struct Window {
157 pub id: String,
159 pub title: String,
161 pub app: String,
163 pub width: u32,
166 pub height: u32,
168}
169
170impl Window {
171 pub fn source(&self) -> Source {
173 Source::Window(self.id.clone())
174 }
175}
176
177#[derive(Clone, Debug)]
179pub struct App {
180 pub id: String,
182 pub name: String,
184}
185
186impl App {
187 pub fn source(&self) -> Source {
189 Source::App(self.id.clone())
190 }
191}
192
193#[derive(Clone, Debug)]
199#[non_exhaustive]
200pub struct Config {
201 pub source: Source,
203 pub width: Option<u32>,
204 pub height: Option<u32>,
205 pub framerate: Option<u32>,
206 pub cursor: bool,
209}
210
211impl Default for Config {
212 fn default() -> Self {
213 Self {
214 source: Source::default(),
215 width: None,
216 height: None,
217 framerate: None,
218 cursor: true,
219 }
220 }
221}
222
223pub(crate) struct FrameStream {
232 chan: Arc<FrameChannel>,
233 width: u32,
234 height: u32,
235 framerate: Option<u32>,
236 device: String,
237 pending: Option<Surface>,
240 _backend: Keepalive,
243}
244
245impl FrameStream {
246 fn new(
248 chan: Arc<FrameChannel>,
249 width: u32,
250 height: u32,
251 framerate: Option<u32>,
252 device: String,
253 pending: Option<Surface>,
254 backend: Keepalive,
255 ) -> Self {
256 Self {
257 chan,
258 width,
259 height,
260 framerate,
261 device,
262 pending,
263 _backend: backend,
264 }
265 }
266
267 pub(crate) async fn read(&mut self) -> Option<Surface> {
270 if let Some(frame) = self.pending.take() {
271 return Some(frame);
272 }
273 self.chan.recv().await
274 }
275
276 pub(crate) fn width(&self) -> u32 {
277 self.width
278 }
279
280 pub(crate) fn height(&self) -> u32 {
281 self.height
282 }
283
284 pub(crate) fn framerate(&self) -> Option<u32> {
286 self.framerate
287 }
288
289 pub(crate) fn device(&self) -> &str {
290 &self.device
291 }
292}
293
294pub(crate) async fn open(config: &Config) -> Result<FrameStream, Error> {
296 match &config.source {
297 Source::Camera(device) => {
298 let _ = device;
299 #[cfg(target_os = "macos")]
300 {
301 avfoundation::open(config, device.as_deref()).await
302 }
303 #[cfg(target_os = "linux")]
304 {
305 v4l2::open(config, device.as_deref()).await
306 }
307 #[cfg(target_os = "windows")]
308 {
309 mediafoundation::open(config, device.as_deref()).await
310 }
311 #[cfg(not(any(target_os = "macos", target_os = "linux", target_os = "windows")))]
312 {
313 Err(Error::Unsupported("camera capture".to_string()))
314 }
315 }
316 Source::Display(device) => {
317 let _ = device;
318 #[cfg(target_os = "macos")]
319 {
320 screencapture::open_display(config, device.as_deref()).await
321 }
322 #[cfg(target_os = "windows")]
323 {
324 desktopduplication::open(config, device.as_deref()).await
325 }
326 #[cfg(all(target_os = "linux", feature = "pipewire"))]
327 {
328 pipewire::open(config, device.as_deref()).await
329 }
330 #[cfg(all(target_os = "linux", not(feature = "pipewire")))]
331 {
332 Err(Error::Unsupported(
333 "screen capture on Linux without the `pipewire` feature".to_string(),
334 ))
335 }
336 #[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "linux")))]
337 {
338 Err(Error::Unsupported("screen capture".to_string()))
339 }
340 }
341 Source::Window(id) => {
342 let _ = id;
343 #[cfg(target_os = "macos")]
344 {
345 screencapture::open_window(config, id).await
346 }
347 #[cfg(not(target_os = "macos"))]
348 {
349 Err(Error::Unsupported("window capture".to_string()))
350 }
351 }
352 Source::App(id) => {
353 let _ = id;
354 #[cfg(target_os = "macos")]
355 {
356 screencapture::open_app(config, id).await
357 }
358 #[cfg(not(target_os = "macos"))]
359 {
360 Err(Error::Unsupported("application capture".to_string()))
361 }
362 }
363 }
364}
365
366pub async fn cameras() -> Result<Vec<Camera>, Error> {
368 #[cfg(target_os = "macos")]
369 {
370 avfoundation::cameras()
371 }
372 #[cfg(target_os = "linux")]
373 {
374 blocking(v4l2::cameras).await
375 }
376 #[cfg(target_os = "windows")]
377 {
378 blocking(mediafoundation::cameras).await
379 }
380 #[cfg(not(any(target_os = "macos", target_os = "linux", target_os = "windows")))]
381 {
382 Err(Error::Unsupported("listing cameras".to_string()))
383 }
384}
385
386pub async fn displays() -> Result<Vec<Display>, Error> {
391 #[cfg(target_os = "macos")]
392 {
393 screencapture::displays().await
394 }
395 #[cfg(target_os = "windows")]
396 {
397 blocking(desktopduplication::displays).await
398 }
399 #[cfg(not(any(target_os = "macos", target_os = "windows")))]
400 {
401 Err(Error::Unsupported("listing displays".to_string()))
402 }
403}
404
405pub async fn windows() -> Result<Vec<Window>, Error> {
407 #[cfg(target_os = "macos")]
408 {
409 screencapture::windows().await
410 }
411 #[cfg(not(target_os = "macos"))]
412 {
413 Err(Error::Unsupported("listing windows".to_string()))
414 }
415}
416
417pub async fn apps() -> Result<Vec<App>, Error> {
419 #[cfg(target_os = "macos")]
420 {
421 screencapture::apps().await
422 }
423 #[cfg(not(target_os = "macos"))]
424 {
425 Err(Error::Unsupported("listing applications".to_string()))
426 }
427}
428
429#[cfg(any(target_os = "linux", target_os = "windows"))]
431async fn blocking<T, F>(f: F) -> Result<T, Error>
432where
433 F: FnOnce() -> Result<T, Error> + Send + 'static,
434 T: Send + 'static,
435{
436 tokio::task::spawn_blocking(f)
437 .await
438 .map_err(|err| Error::Codec(anyhow::anyhow!("capture enumeration thread failed: {err}")))?
439}