Retrieving a buffer from windows_capture

use std::{
    io::{self, Write},
    sync::{Arc, Mutex},
};

use windows_capture::{
    capture::{Context, GraphicsCaptureApiHandler},
    frame::Frame,
    graphics_capture_api::InternalCaptureControl,
    settings::{ColorFormat, CursorCaptureSettings, DrawBorderSettings, Settings},
    window::Window,
};

pub struct Capture {
    buf: Arc<Mutex<Vec<u8>>>,
    once: bool,
}

pub struct Flags {
    pub buf: Arc<Mutex<Vec<u8>>>,
    pub once: bool,
}

impl GraphicsCaptureApiHandler for Capture {
    type Flags = Flags;

    type Error = Box<dyn std::error::Error + Send + Sync>;

    fn new(ctx: Context<Self::Flags>) -> Result<Self, Self::Error> {
        let flags = ctx.flags;
        Ok(Capture {
            buf: flags.buf,
            once: flags.once,
        })
    }

    fn on_frame_arrived(
        &mut self,
        frame: &mut Frame,
        capture_control: InternalCaptureControl,
    ) -> Result<(), Self::Error> {
        io::stdout().flush()?;
        let mut frame_buf = frame.buffer().unwrap();
        let buf = frame_buf.as_raw_buffer();

        let mut curr_buf = self.buf.lock().unwrap();
        curr_buf.clear();
        curr_buf.write(buf).unwrap();

        if self.once {
            capture_control.stop();
        }

        Ok(())
    }
}

pub fn capture(buf: Arc<Mutex<Vec<u8>>>) -> Result<(), CaptureError> {
    let window = Window::foreground();

    let Ok(window) = window else {
        return Err(CaptureError::NotFound);
    };

    let settings = Settings::new(
        window,
        CursorCaptureSettings::Default,
        DrawBorderSettings::WithoutBorder,
        ColorFormat::Rgba8,
        Flags {
            buf: buf.clone(),
            once: false,
        },
    );

    Capture::start_free_threaded(settings).map_err(|_| CaptureError::NotFound)?;
    Ok(())
}

#[cfg(test)]
pub fn capture_once(buf: Arc<Mutex<Vec<u8>>>) -> Result<(), CaptureError> {
    let window = Window::foreground();

    let Ok(window) = window else {
        return Err(CaptureError::NotFound);
    };

    let settings = Settings::new(
        window,
        CursorCaptureSettings::Default,
        DrawBorderSettings::WithoutBorder,
        ColorFormat::Rgba8,
        Flags {
            buf: buf.clone(),
            once: true,
        },
    );

    Capture::start(settings).map_err(|_| CaptureError::NotFound)?;
    Ok(())
}

#[derive(Debug)]
pub enum CaptureError {
    NotFound,
}

This code is working.
The capture function is entry point in there. It is called when i need to receive current window image.

The problem is i have to pass a buffer as flag and it feels like a hack to me. I guess flags should change behavior and not the functionality.

Is there any better way to write that?

As far as i can see i cannot return image just like i want. There is some function on handle that will return the struct and that's how i can do that, but the problem with this is that if i need the image again i will have to recapture the window.

And i need image at least every 250ms so i definitely want to avoid recapturing

As far as I can tell from a quick look, there's no way to avoid passing in your buffer via the Flags type. The GraphicsCaptureApiHandler specifies the signature to new(), and the Flags parameter is the only parameter. Maybe new() ought to take a second parameter, but it doesn't.