#![forbid(unsafe_code)]
use crate::CaptureError;
use crate::desktop::{DesktopCaptureSource, DesktopVideoCapture, DesktopVideoCaptureConfig};
use ashpd::desktop::screencast::SourceType;
use mediaway_common::{Bytes, CodecKind, Rational, StreamInfo, VideoFrame, VideoGeometry};
use crate::linux::screencast::{self, Session};
pub struct LinuxWindowCapture {
inner: Option<Session>,
}
impl LinuxWindowCapture {
pub fn open(config: &DesktopVideoCaptureConfig) -> Result<Self, CaptureError> {
let DesktopCaptureSource::Window { window: _ } = &config.source else {
return Err(CaptureError::Unsupported);
};
let session = screencast::open_session(SourceType::Window, "Window", config)?;
Ok(Self {
inner: Some(session),
})
}
}
impl DesktopVideoCapture for LinuxWindowCapture {
fn stream_info(&self) -> &StreamInfo {
#[allow(
clippy::option_if_let_else,
reason = "map_or_else forces 'static vs 'self lifetime clash"
)]
if let Some(inner) = self.inner.as_ref() {
inner.stream_info()
} else {
closed_stream_info()
}
}
fn poll_frame(&mut self) -> Result<Option<VideoFrame>, CaptureError> {
let inner = self.inner.as_ref().ok_or(CaptureError::Closed)?;
inner.poll_frame()
}
fn release_frame(&mut self) -> Result<(), CaptureError> {
if self.inner.is_none() {
return Err(CaptureError::Closed);
}
Ok(())
}
fn close(&mut self) -> Result<(), CaptureError> {
let Some(mut session) = self.inner.take() else {
return Err(CaptureError::Closed);
};
session.close();
Ok(())
}
}
impl Drop for LinuxWindowCapture {
fn drop(&mut self) {
let _ = self.close();
}
}
fn closed_stream_info() -> &'static StreamInfo {
use std::sync::OnceLock;
static INFO: OnceLock<StreamInfo> = OnceLock::new();
INFO.get_or_init(|| StreamInfo::Video {
id: 0,
codec: CodecKind::RawVideo,
time_base: Rational::new(1, 30),
geometry: VideoGeometry {
width: 0,
height: 0,
},
extra_data: Bytes::new(),
})
}
#[cfg(test)]
#[path = "window_tests.rs"]
mod tests;