1 //! Safe wrapper for the `VIDIOC_SUBSCRIBE_EVENT` and `VIDIOC_UNSUBSCRIBE_EVENT
2 //! ioctls.
3 
4 use std::convert::TryFrom;
5 use std::convert::TryInto;
6 use std::os::unix::io::AsRawFd;
7 
8 use bitflags::bitflags;
9 use nix::errno::Errno;
10 use thiserror::Error;
11 
12 use crate::bindings;
13 use crate::bindings::v4l2_event;
14 use crate::bindings::v4l2_event_subscription;
15 
16 bitflags! {
17     #[derive(Clone, Copy, Debug)]
18     pub struct SubscribeEventFlags: u32 {
19         const SEND_INITIAL = bindings::V4L2_EVENT_SUB_FL_SEND_INITIAL;
20         const ALLOW_FEEDBACK = bindings::V4L2_EVENT_SUB_FL_ALLOW_FEEDBACK;
21     }
22 
23 }
24 
25 #[derive(Debug)]
26 pub enum EventType {
27     VSync,
28     Eos,
29     Ctrl(u32),
30     FrameSync,
31     SourceChange(u32),
32     MotionDet,
33 }
34 
35 #[derive(Debug, Error)]
36 pub enum EventConversionError {
37     #[error("unrecognized event {0}")]
38     UnrecognizedEvent(u32),
39     #[error("unrecognized source change {0}")]
40     UnrecognizedSourceChange(u32),
41 }
42 
43 impl TryFrom<&v4l2_event_subscription> for EventType {
44     type Error = EventConversionError;
45 
try_from(event: &v4l2_event_subscription) -> Result<Self, Self::Error>46     fn try_from(event: &v4l2_event_subscription) -> Result<Self, Self::Error> {
47         Ok(match event.type_ {
48             bindings::V4L2_EVENT_VSYNC => EventType::VSync,
49             bindings::V4L2_EVENT_EOS => EventType::Eos,
50             bindings::V4L2_EVENT_CTRL => EventType::Ctrl(event.id),
51             bindings::V4L2_EVENT_FRAME_SYNC => EventType::FrameSync,
52             bindings::V4L2_EVENT_SOURCE_CHANGE => EventType::SourceChange(event.id),
53             bindings::V4L2_EVENT_MOTION_DET => EventType::MotionDet,
54             e => return Err(EventConversionError::UnrecognizedEvent(e)),
55         })
56     }
57 }
58 
59 bitflags! {
60     #[derive(Clone, Copy, Debug)]
61     pub struct SrcChanges: u32 {
62         const RESOLUTION = bindings::V4L2_EVENT_SRC_CH_RESOLUTION;
63     }
64 }
65 
66 #[derive(Debug)]
67 pub enum Event {
68     SrcChangeEvent(SrcChanges),
69     Eos,
70 }
71 
72 impl TryFrom<v4l2_event> for Event {
73     type Error = EventConversionError;
74 
try_from(value: v4l2_event) -> Result<Self, Self::Error>75     fn try_from(value: v4l2_event) -> Result<Self, Self::Error> {
76         Ok(match value.type_ {
77             bindings::V4L2_EVENT_VSYNC => todo!(),
78             bindings::V4L2_EVENT_EOS => Event::Eos,
79             bindings::V4L2_EVENT_CTRL => todo!(),
80             bindings::V4L2_EVENT_FRAME_SYNC => todo!(),
81             bindings::V4L2_EVENT_SOURCE_CHANGE => {
82                 let changes = unsafe { value.u.src_change.changes };
83                 Event::SrcChangeEvent(
84                     SrcChanges::from_bits(changes)
85                         .ok_or(EventConversionError::UnrecognizedSourceChange(changes))?,
86                 )
87             }
88             bindings::V4L2_EVENT_MOTION_DET => todo!(),
89             t => return Err(EventConversionError::UnrecognizedEvent(t)),
90         })
91     }
92 }
93 
build_v4l2_event_subscription( event: EventType, flags: SubscribeEventFlags, ) -> v4l2_event_subscription94 fn build_v4l2_event_subscription(
95     event: EventType,
96     flags: SubscribeEventFlags,
97 ) -> v4l2_event_subscription {
98     v4l2_event_subscription {
99         type_: match event {
100             EventType::VSync => bindings::V4L2_EVENT_VSYNC,
101             EventType::Eos => bindings::V4L2_EVENT_EOS,
102             EventType::Ctrl(_) => bindings::V4L2_EVENT_CTRL,
103             EventType::FrameSync => bindings::V4L2_EVENT_FRAME_SYNC,
104             EventType::SourceChange(_) => bindings::V4L2_EVENT_SOURCE_CHANGE,
105             EventType::MotionDet => bindings::V4L2_EVENT_MOTION_DET,
106         },
107         id: match event {
108             EventType::Ctrl(id) => id,
109             EventType::SourceChange(id) => id,
110             _ => 0,
111         },
112         flags: flags.bits(),
113         ..Default::default()
114     }
115 }
116 
117 #[doc(hidden)]
118 mod ioctl {
119     use crate::bindings::{v4l2_event, v4l2_event_subscription};
120 
121     nix::ioctl_read!(vidioc_dqevent, b'V', 89, v4l2_event);
122     nix::ioctl_write_ptr!(vidioc_subscribe_event, b'V', 90, v4l2_event_subscription);
123     nix::ioctl_write_ptr!(vidioc_unsubscribe_event, b'V', 91, v4l2_event_subscription);
124 }
125 
126 #[derive(Debug, Error)]
127 pub enum SubscribeEventError {
128     #[error("ioctl error: {0}")]
129     IoctlError(#[from] Errno),
130 }
131 
132 impl From<SubscribeEventError> for Errno {
from(err: SubscribeEventError) -> Self133     fn from(err: SubscribeEventError) -> Self {
134         match err {
135             SubscribeEventError::IoctlError(e) => e,
136         }
137     }
138 }
139 
140 /// Safe wrapper around the `VIDIOC_SUBSCRIBE_EVENT` ioctl.
subscribe_event( fd: &impl AsRawFd, event: EventType, flags: SubscribeEventFlags, ) -> Result<(), SubscribeEventError>141 pub fn subscribe_event(
142     fd: &impl AsRawFd,
143     event: EventType,
144     flags: SubscribeEventFlags,
145 ) -> Result<(), SubscribeEventError> {
146     let subscription = build_v4l2_event_subscription(event, flags);
147 
148     unsafe { ioctl::vidioc_subscribe_event(fd.as_raw_fd(), &subscription) }?;
149     Ok(())
150 }
151 
152 /// Safe wrapper around the `VIDIOC_UNSUBSCRIBE_EVENT` ioctl.
unsubscribe_event(fd: &impl AsRawFd, event: EventType) -> Result<(), SubscribeEventError>153 pub fn unsubscribe_event(fd: &impl AsRawFd, event: EventType) -> Result<(), SubscribeEventError> {
154     let subscription = build_v4l2_event_subscription(event, SubscribeEventFlags::empty());
155 
156     unsafe { ioctl::vidioc_unsubscribe_event(fd.as_raw_fd(), &subscription) }?;
157     Ok(())
158 }
159 
160 /// Safe wrapper around the `VIDIOC_UNSUBSCRIBE_EVENT` ioctl to unsubscribe from all events.
unsubscribe_all_events(fd: &impl AsRawFd) -> Result<(), SubscribeEventError>161 pub fn unsubscribe_all_events(fd: &impl AsRawFd) -> Result<(), SubscribeEventError> {
162     let subscription = v4l2_event_subscription {
163         type_: bindings::V4L2_EVENT_ALL,
164         ..Default::default()
165     };
166 
167     unsafe { ioctl::vidioc_unsubscribe_event(fd.as_raw_fd(), &subscription) }?;
168     Ok(())
169 }
170 
171 #[derive(Debug, Error)]
172 pub enum DqEventError {
173     #[error("no event ready for dequeue")]
174     NotReady,
175     #[error("error while converting event")]
176     EventConversionError,
177     #[error("unexpected ioctl error: {0}")]
178     IoctlError(Errno),
179 }
180 
181 impl From<Errno> for DqEventError {
from(error: Errno) -> Self182     fn from(error: Errno) -> Self {
183         match error {
184             Errno::ENOENT => Self::NotReady,
185             error => Self::IoctlError(error),
186         }
187     }
188 }
189 
190 impl From<DqEventError> for Errno {
from(err: DqEventError) -> Self191     fn from(err: DqEventError) -> Self {
192         match err {
193             DqEventError::NotReady => Errno::ENOENT,
194             DqEventError::EventConversionError => Errno::EINVAL,
195             DqEventError::IoctlError(e) => e,
196         }
197     }
198 }
199 
dqevent<O: TryFrom<v4l2_event>>(fd: &impl AsRawFd) -> Result<O, DqEventError>200 pub fn dqevent<O: TryFrom<v4l2_event>>(fd: &impl AsRawFd) -> Result<O, DqEventError> {
201     let mut event: v4l2_event = Default::default();
202 
203     match unsafe { ioctl::vidioc_dqevent(fd.as_raw_fd(), &mut event) } {
204         Ok(_) => Ok(event
205             .try_into()
206             .map_err(|_| DqEventError::EventConversionError)?),
207         Err(Errno::ENOENT) => Err(DqEventError::NotReady),
208         Err(e) => Err(DqEventError::IoctlError(e)),
209     }
210 }
211