| //! Safe wrapper for the `VIDIOC_SUBSCRIBE_EVENT` and `VIDIOC_UNSUBSCRIBE_EVENT |
| //! ioctls. |
| |
| use std::os::unix::io::AsRawFd; |
| use std::{ |
| convert::{TryFrom, TryInto}, |
| mem, |
| }; |
| |
| use bitflags::bitflags; |
| use nix::errno::Errno; |
| use thiserror::Error; |
| |
| use crate::bindings; |
| use crate::bindings::v4l2_event; |
| use crate::bindings::v4l2_event_subscription; |
| |
| bitflags! { |
| #[derive(Clone, Copy, Debug)] |
| pub struct SubscribeEventFlags: u32 { |
| const SEND_INITIAL = bindings::V4L2_EVENT_SUB_FL_SEND_INITIAL; |
| const ALLOW_FEEDBACK = bindings::V4L2_EVENT_SUB_FL_ALLOW_FEEDBACK; |
| } |
| |
| } |
| |
| #[derive(Debug)] |
| pub enum EventType { |
| VSync, |
| Eos, |
| Ctrl(u32), |
| FrameSync, |
| SourceChange(u32), |
| MotionDet, |
| } |
| |
| #[derive(Debug, Error)] |
| pub enum EventConversionError { |
| #[error("unrecognized event {0}")] |
| UnrecognizedEvent(u32), |
| #[error("unrecognized source change {0}")] |
| UnrecognizedSourceChange(u32), |
| } |
| |
| impl TryFrom<&v4l2_event_subscription> for EventType { |
| type Error = EventConversionError; |
| |
| fn try_from(event: &v4l2_event_subscription) -> Result<Self, Self::Error> { |
| Ok(match event.type_ { |
| bindings::V4L2_EVENT_VSYNC => EventType::VSync, |
| bindings::V4L2_EVENT_EOS => EventType::Eos, |
| bindings::V4L2_EVENT_CTRL => EventType::Ctrl(event.id), |
| bindings::V4L2_EVENT_FRAME_SYNC => EventType::FrameSync, |
| bindings::V4L2_EVENT_SOURCE_CHANGE => EventType::SourceChange(event.id), |
| bindings::V4L2_EVENT_MOTION_DET => EventType::MotionDet, |
| e => return Err(EventConversionError::UnrecognizedEvent(e)), |
| }) |
| } |
| } |
| |
| bitflags! { |
| #[derive(Clone, Copy, Debug)] |
| pub struct SrcChanges: u32 { |
| const RESOLUTION = bindings::V4L2_EVENT_SRC_CH_RESOLUTION; |
| } |
| } |
| |
| #[derive(Debug)] |
| pub enum Event { |
| SrcChangeEvent(SrcChanges), |
| Eos, |
| } |
| |
| impl TryFrom<v4l2_event> for Event { |
| type Error = EventConversionError; |
| |
| fn try_from(value: v4l2_event) -> Result<Self, Self::Error> { |
| Ok(match value.type_ { |
| bindings::V4L2_EVENT_VSYNC => todo!(), |
| bindings::V4L2_EVENT_EOS => Event::Eos, |
| bindings::V4L2_EVENT_CTRL => todo!(), |
| bindings::V4L2_EVENT_FRAME_SYNC => todo!(), |
| bindings::V4L2_EVENT_SOURCE_CHANGE => { |
| let changes = unsafe { value.u.src_change.changes }; |
| Event::SrcChangeEvent( |
| SrcChanges::from_bits(changes) |
| .ok_or(EventConversionError::UnrecognizedSourceChange(changes))?, |
| ) |
| } |
| bindings::V4L2_EVENT_MOTION_DET => todo!(), |
| t => return Err(EventConversionError::UnrecognizedEvent(t)), |
| }) |
| } |
| } |
| |
| fn build_v4l2_event_subscription( |
| event: EventType, |
| flags: SubscribeEventFlags, |
| ) -> v4l2_event_subscription { |
| v4l2_event_subscription { |
| type_: match event { |
| EventType::VSync => bindings::V4L2_EVENT_VSYNC, |
| EventType::Eos => bindings::V4L2_EVENT_EOS, |
| EventType::Ctrl(_) => bindings::V4L2_EVENT_CTRL, |
| EventType::FrameSync => bindings::V4L2_EVENT_FRAME_SYNC, |
| EventType::SourceChange(_) => bindings::V4L2_EVENT_SOURCE_CHANGE, |
| EventType::MotionDet => bindings::V4L2_EVENT_MOTION_DET, |
| }, |
| id: match event { |
| EventType::Ctrl(id) => id, |
| EventType::SourceChange(id) => id, |
| _ => 0, |
| }, |
| flags: flags.bits(), |
| ..unsafe { mem::zeroed() } |
| } |
| } |
| |
| #[doc(hidden)] |
| mod ioctl { |
| use crate::bindings::{v4l2_event, v4l2_event_subscription}; |
| |
| nix::ioctl_read!(vidioc_dqevent, b'V', 89, v4l2_event); |
| nix::ioctl_write_ptr!(vidioc_subscribe_event, b'V', 90, v4l2_event_subscription); |
| nix::ioctl_write_ptr!(vidioc_unsubscribe_event, b'V', 91, v4l2_event_subscription); |
| } |
| |
| #[derive(Debug, Error)] |
| pub enum SubscribeEventError { |
| #[error("ioctl error: {0}")] |
| IoctlError(#[from] Errno), |
| } |
| |
| impl From<SubscribeEventError> for Errno { |
| fn from(err: SubscribeEventError) -> Self { |
| match err { |
| SubscribeEventError::IoctlError(e) => e, |
| } |
| } |
| } |
| |
| /// Safe wrapper around the `VIDIOC_SUBSCRIBE_EVENT` ioctl. |
| pub fn subscribe_event( |
| fd: &impl AsRawFd, |
| event: EventType, |
| flags: SubscribeEventFlags, |
| ) -> Result<(), SubscribeEventError> { |
| let subscription = build_v4l2_event_subscription(event, flags); |
| |
| unsafe { ioctl::vidioc_subscribe_event(fd.as_raw_fd(), &subscription) }?; |
| Ok(()) |
| } |
| |
| /// Safe wrapper around the `VIDIOC_UNSUBSCRIBE_EVENT` ioctl. |
| pub fn unsubscribe_event(fd: &impl AsRawFd, event: EventType) -> Result<(), SubscribeEventError> { |
| let subscription = build_v4l2_event_subscription(event, SubscribeEventFlags::empty()); |
| |
| unsafe { ioctl::vidioc_unsubscribe_event(fd.as_raw_fd(), &subscription) }?; |
| Ok(()) |
| } |
| |
| /// Safe wrapper around the `VIDIOC_UNSUBSCRIBE_EVENT` ioctl to unsubscribe from all events. |
| pub fn unsubscribe_all_events(fd: &impl AsRawFd) -> Result<(), SubscribeEventError> { |
| let subscription = v4l2_event_subscription { |
| type_: bindings::V4L2_EVENT_ALL, |
| ..unsafe { mem::zeroed() } |
| }; |
| |
| unsafe { ioctl::vidioc_unsubscribe_event(fd.as_raw_fd(), &subscription) }?; |
| Ok(()) |
| } |
| |
| #[derive(Debug, Error)] |
| pub enum DqEventError { |
| #[error("no event ready for dequeue")] |
| NotReady, |
| #[error("error while converting event")] |
| EventConversionError, |
| #[error("unexpected ioctl error: {0}")] |
| IoctlError(Errno), |
| } |
| |
| impl From<Errno> for DqEventError { |
| fn from(error: Errno) -> Self { |
| match error { |
| Errno::ENOENT => Self::NotReady, |
| error => Self::IoctlError(error), |
| } |
| } |
| } |
| |
| impl From<DqEventError> for Errno { |
| fn from(err: DqEventError) -> Self { |
| match err { |
| DqEventError::NotReady => Errno::ENOENT, |
| DqEventError::EventConversionError => Errno::EINVAL, |
| DqEventError::IoctlError(e) => e, |
| } |
| } |
| } |
| |
| pub fn dqevent<O: TryFrom<v4l2_event>>(fd: &impl AsRawFd) -> Result<O, DqEventError> { |
| // Safe because this struct is expected to be initialized to 0. |
| let mut event: v4l2_event = unsafe { mem::zeroed() }; |
| |
| match unsafe { ioctl::vidioc_dqevent(fd.as_raw_fd(), &mut event) } { |
| Ok(_) => Ok(event |
| .try_into() |
| .map_err(|_| DqEventError::EventConversionError)?), |
| Err(Errno::ENOENT) => Err(DqEventError::NotReady), |
| Err(e) => Err(DqEventError::IoctlError(e)), |
| } |
| } |