use std::time::Duration;
use anyhow::{Context, Result, bail};
use image::RgbaImage;
use pinray::{CaptureEvent, CaptureSession, CursorMode, FrameData, PixelFormat, SessionBuilder};
pub fn builder(embed_cursor: bool) -> SessionBuilder {
CaptureSession::builder()
.pixel_format(PixelFormat::Rgba8888)
.frame_rate(Some(10))
.cursor_mode(if embed_cursor { CursorMode::Embedded } else { CursorMode::Hidden })
}
pub fn take_frame(session: &mut CaptureSession, settle: Duration) -> Result<RgbaImage> {
let frame = wait_for_frame(session, settle)?;
let tight = frame
.to_tight_bytes()
.context("frame was not delivered as host memory")?;
if frame.pixel_format != PixelFormat::Rgba8888 {
bail!("unexpected pixel format {:?}", frame.pixel_format);
}
RgbaImage::from_raw(frame.width, frame.height, tight)
.context("frame buffer smaller than advertised dimensions")
}
fn wait_for_frame(session: &mut CaptureSession, settle: Duration) -> Result<pinray::VideoFrame> {
let deadline = std::time::Instant::now() + Duration::from_secs(120);
let mut latest: Option<pinray::VideoFrame> = None;
let mut first_seen: Option<std::time::Instant> = None;
let mut frames = 0u32;
while std::time::Instant::now() < deadline {
if let Some(first) = first_seen
&& first.elapsed() >= settle
{
break;
}
match session.next_event(Some(Duration::from_millis(100))) {
Ok(CaptureEvent::Video(frame)) => {
if matches!(frame.data, FrameData::Host(_)) {
if first_seen.is_none() {
first_seen = Some(std::time::Instant::now());
}
latest = Some(frame);
frames += 1;
if frames >= 12 {
break;
}
}
}
Ok(CaptureEvent::End) => break,
Ok(_) => {}
Err(pinray::PinrayError::Timeout(_)) => {}
Err(err) => return Err(err).context("waiting for a frame"),
}
}
latest.context("timed out waiting for a captured frame")
}