v4l2r 0.0.7

Safe and flexible abstraction over V4L2
Documentation
use std::os::unix::io::AsRawFd;

use bitflags::bitflags;
use enumn::N;
use nix::errno::Errno;
use thiserror::Error;

use crate::bindings;
use crate::bindings::v4l2_rect;
use crate::bindings::v4l2_selection;

#[derive(Debug, N, Clone, Copy, PartialEq, Eq)]
#[repr(u32)]
pub enum SelectionType {
    Capture = bindings::v4l2_buf_type_V4L2_BUF_TYPE_VIDEO_CAPTURE,
    Output = bindings::v4l2_buf_type_V4L2_BUF_TYPE_VIDEO_OUTPUT,
}

#[derive(Debug, N, Clone, Copy, PartialEq, Eq)]
#[repr(u32)]
pub enum SelectionTarget {
    Crop = bindings::V4L2_SEL_TGT_CROP,
    CropDefault = bindings::V4L2_SEL_TGT_CROP_DEFAULT,
    CropBounds = bindings::V4L2_SEL_TGT_CROP_BOUNDS,
    NativeSize = bindings::V4L2_SEL_TGT_NATIVE_SIZE,
    Compose = bindings::V4L2_SEL_TGT_COMPOSE,
    ComposeDefault = bindings::V4L2_SEL_TGT_COMPOSE_DEFAULT,
    ComposeBounds = bindings::V4L2_SEL_TGT_COMPOSE_BOUNDS,
    ComposePadded = bindings::V4L2_SEL_TGT_COMPOSE_PADDED,
}

bitflags! {
    #[derive(Clone, Copy, Debug)]
    pub struct SelectionFlags: u32 {
        const GE = bindings::V4L2_SEL_FLAG_GE;
        const LE = bindings::V4L2_SEL_FLAG_LE;
        const KEEP_CONFIG = bindings::V4L2_SEL_FLAG_KEEP_CONFIG;
    }
}

#[doc(hidden)]
mod ioctl {
    use crate::bindings::v4l2_selection;
    nix::ioctl_readwrite!(vidioc_g_selection, b'V', 94, v4l2_selection);
    nix::ioctl_readwrite!(vidioc_s_selection, b'V', 95, v4l2_selection);
}

#[derive(Debug, Error)]
pub enum GSelectionError {
    #[error("invalid type or target requested")]
    Invalid,
    #[error("ioctl error: {0}")]
    IoctlError(Errno),
}

impl From<GSelectionError> for Errno {
    fn from(err: GSelectionError) -> Self {
        match err {
            GSelectionError::Invalid => Errno::EINVAL,
            GSelectionError::IoctlError(e) => e,
        }
    }
}

/// Safe wrapper around the `VIDIOC_G_SELECTION` ioctl.
pub fn g_selection<R: From<v4l2_rect>>(
    fd: &impl AsRawFd,
    selection: SelectionType,
    target: SelectionTarget,
) -> Result<R, GSelectionError> {
    let mut sel = v4l2_selection {
        type_: selection as u32,
        target: target as u32,
        ..Default::default()
    };

    match unsafe { ioctl::vidioc_g_selection(fd.as_raw_fd(), &mut sel) } {
        Ok(_) => Ok(R::from(sel.r)),
        Err(Errno::EINVAL) => Err(GSelectionError::Invalid),
        Err(e) => Err(GSelectionError::IoctlError(e)),
    }
}

#[derive(Debug, Error)]
pub enum SSelectionError {
    #[error("invalid type or target requested")]
    Invalid,
    #[error("invalid range requested")]
    InvalidRange,
    #[error("cannot change selection rectangle currently")]
    Busy,
    #[error("ioctl error: {0}")]
    IoctlError(nix::Error),
}

impl From<SSelectionError> for Errno {
    fn from(err: SSelectionError) -> Self {
        match err {
            SSelectionError::Invalid => Errno::EINVAL,
            SSelectionError::InvalidRange => Errno::ERANGE,
            SSelectionError::Busy => Errno::EBUSY,
            SSelectionError::IoctlError(e) => e,
        }
    }
}

/// Safe wrapper around the `VIDIOC_S_SELECTION` ioctl.
pub fn s_selection<RI: Into<v4l2_rect>, RO: From<v4l2_rect>>(
    fd: &impl AsRawFd,
    selection: SelectionType,
    target: SelectionTarget,
    rect: RI,
    flags: SelectionFlags,
) -> Result<RO, SSelectionError> {
    let mut sel = v4l2_selection {
        type_: selection as u32,
        target: target as u32,
        flags: flags.bits(),
        r: rect.into(),
        ..Default::default()
    };

    match unsafe { ioctl::vidioc_s_selection(fd.as_raw_fd(), &mut sel) } {
        Ok(_) => Ok(RO::from(sel.r)),
        Err(Errno::EINVAL) => Err(SSelectionError::Invalid),
        Err(Errno::ERANGE) => Err(SSelectionError::InvalidRange),
        Err(Errno::EBUSY) => Err(SSelectionError::Busy),
        Err(e) => Err(SSelectionError::IoctlError(e)),
    }
}