#![forbid(unsafe_code)]
use std::future::Future;
use std::os::fd::OwnedFd;
use std::pin::pin;
use std::sync::{Arc, Condvar, Mutex, PoisonError};
use std::task::{Context, Poll, Wake, Waker};
use crate::CaptureError;
use ashpd::desktop::screencast::{
CursorMode, OpenPipeWireRemoteOptions, Screencast, SelectSourcesOptions, SourceType,
StartCastOptions,
};
use ashpd::desktop::{CreateSessionOptions, PersistMode};
pub(crate) struct PortalStream {
pub(crate) node_id: u32,
pub(crate) remote_fd: OwnedFd,
}
pub(crate) fn open_portal_stream(source_type: SourceType) -> Result<PortalStream, ashpd::Error> {
block_on(open_portal_stream_async(source_type))
}
async fn open_portal_stream_async(source_type: SourceType) -> ashpd::Result<PortalStream> {
let proxy = Screencast::new().await?;
let session = proxy
.create_session(CreateSessionOptions::default())
.await?;
proxy
.select_sources(
&session,
SelectSourcesOptions::default()
.set_cursor_mode(CursorMode::Hidden)
.set_sources(Some(source_type.into()))
.set_multiple(false)
.set_persist_mode(PersistMode::DoNot),
)
.await?;
let response = proxy
.start(&session, None, StartCastOptions::default())
.await?
.response()?;
let node_id = response
.streams()
.first()
.ok_or(ashpd::Error::NoResponse)?
.pipe_wire_node_id();
let remote_fd = proxy
.open_pipe_wire_remote(&session, OpenPipeWireRemoteOptions::default())
.await?;
Ok(PortalStream { node_id, remote_fd })
}
pub(crate) fn probe_screencast() -> Result<(), ashpd::Error> {
block_on(async { Screencast::new().await.map(|_| ()) })
}
#[must_use]
pub(crate) const fn map_ashpd_error(err: &ashpd::Error) -> CaptureError {
match err {
ashpd::Error::Response(_) | ashpd::Error::Portal(_) => CaptureError::AccessDenied,
ashpd::Error::PortalNotFound(_) => CaptureError::NoBackend,
ashpd::Error::Zbus(_) | ashpd::Error::NoResponse | ashpd::Error::IO(_) => {
CaptureError::Backend
}
_ => CaptureError::Unsupported,
}
}
fn block_on<F: Future>(fut: F) -> F::Output {
struct ParkSignal {
ready: Mutex<bool>,
cvar: Condvar,
}
impl Wake for ParkSignal {
fn wake(self: Arc<Self>) {
*self.ready.lock().unwrap_or_else(PoisonError::into_inner) = true;
self.cvar.notify_one();
}
}
let signal = Arc::new(ParkSignal {
ready: Mutex::new(false),
cvar: Condvar::new(),
});
let waker = Waker::from(Arc::clone(&signal));
let mut cx = Context::from_waker(&waker);
let mut fut = pin!(fut);
loop {
match fut.as_mut().poll(&mut cx) {
Poll::Ready(out) => return out,
Poll::Pending => {
let mut ready = signal.ready.lock().unwrap_or_else(PoisonError::into_inner);
while !*ready {
ready = signal
.cvar
.wait(ready)
.unwrap_or_else(PoisonError::into_inner);
}
*ready = false;
}
}
}
}
#[cfg(test)]
#[path = "portal_tests.rs"]
mod tests;