1 //! Safe wrapper for the `VIDIOC_QUERYCTRL` and `VIDIOC_QUERY_EXT_CTRL` ioctls.
2 use std::os::unix::io::AsRawFd;
3
4 use bitflags::bitflags;
5 use nix::errno::Errno;
6 use thiserror::Error;
7
8 use crate::bindings;
9 use crate::bindings::v4l2_query_ext_ctrl;
10 use crate::bindings::v4l2_queryctrl;
11
12 /// Index of a control that has been validated, i.e. which ID is within the range of
13 /// `V4L2_CTRL_ID_MASK`.
14 #[derive(Debug, PartialEq, Eq)]
15 pub struct CtrlId(u32);
16
17 #[derive(Debug, Error, PartialEq, Eq)]
18 pub enum CtrlIdError {
19 #[error("invalid control number: 0x{0:08x}")]
20 InvalidControl(u32),
21 }
22
23 impl CtrlId {
24 /// Create a new control index from its u32 representation, after validation.
new(ctrl: u32) -> Result<Self, CtrlIdError>25 pub fn new(ctrl: u32) -> Result<Self, CtrlIdError> {
26 if (ctrl & bindings::V4L2_CTRL_ID_MASK) != ctrl {
27 Err(CtrlIdError::InvalidControl(ctrl))
28 } else {
29 Ok(CtrlId(ctrl))
30 }
31 }
32 }
33
34 bitflags! {
35 #[derive(Clone, Copy, Debug)]
36 pub struct QueryCtrlFlags: u32 {
37 const NEXT = bindings::V4L2_CTRL_FLAG_NEXT_CTRL;
38 const COMPOUND = bindings::V4L2_CTRL_FLAG_NEXT_COMPOUND;
39 }
40 }
41
42 /// Decompose a u32 between its control ID and query flags parts.
parse_ctrl_id_and_flags(ctrl: u32) -> (CtrlId, QueryCtrlFlags)43 pub fn parse_ctrl_id_and_flags(ctrl: u32) -> (CtrlId, QueryCtrlFlags) {
44 (
45 CtrlId(ctrl & bindings::V4L2_CTRL_ID_MASK),
46 QueryCtrlFlags::from_bits_truncate(ctrl),
47 )
48 }
49
50 #[doc(hidden)]
51 mod ioctl {
52 use crate::bindings::v4l2_queryctrl;
53 nix::ioctl_readwrite!(vidioc_queryctrl, b'V', 36, v4l2_queryctrl);
54
55 use crate::bindings::v4l2_query_ext_ctrl;
56 nix::ioctl_readwrite!(vidioc_query_ext_ctrl, b'V', 103, v4l2_query_ext_ctrl);
57 }
58
59 #[derive(Debug, Error)]
60 pub enum QueryCtrlError {
61 #[error("ioctl error: {0}")]
62 IoctlError(Errno),
63 }
64
65 impl From<QueryCtrlError> for Errno {
from(err: QueryCtrlError) -> Self66 fn from(err: QueryCtrlError) -> Self {
67 match err {
68 QueryCtrlError::IoctlError(e) => e,
69 }
70 }
71 }
72
73 /// Safe wrapper around the `VIDIOC_QUERYCTRL` ioctl.
queryctrl<T: From<v4l2_queryctrl>>( fd: &impl AsRawFd, id: CtrlId, flags: QueryCtrlFlags, ) -> Result<T, QueryCtrlError>74 pub fn queryctrl<T: From<v4l2_queryctrl>>(
75 fd: &impl AsRawFd,
76 id: CtrlId,
77 flags: QueryCtrlFlags,
78 ) -> Result<T, QueryCtrlError> {
79 let mut qctrl: v4l2_queryctrl = v4l2_queryctrl {
80 id: id.0 | flags.bits(),
81 ..Default::default()
82 };
83
84 match unsafe { ioctl::vidioc_queryctrl(fd.as_raw_fd(), &mut qctrl) } {
85 Ok(_) => Ok(T::from(qctrl)),
86 Err(e) => Err(QueryCtrlError::IoctlError(e)),
87 }
88 }
89
90 /// Safe wrapper around the `VIDIOC_QUERYCTRL` ioctl.
query_ext_ctrl<T: From<v4l2_query_ext_ctrl>>( fd: &impl AsRawFd, id: CtrlId, flags: QueryCtrlFlags, ) -> Result<T, QueryCtrlError>91 pub fn query_ext_ctrl<T: From<v4l2_query_ext_ctrl>>(
92 fd: &impl AsRawFd,
93 id: CtrlId,
94 flags: QueryCtrlFlags,
95 ) -> Result<T, QueryCtrlError> {
96 let mut qctrl: v4l2_query_ext_ctrl = v4l2_query_ext_ctrl {
97 id: id.0 | flags.bits(),
98 ..Default::default()
99 };
100
101 match unsafe { ioctl::vidioc_query_ext_ctrl(fd.as_raw_fd(), &mut qctrl) } {
102 Ok(_) => Ok(T::from(qctrl)),
103 Err(e) => Err(QueryCtrlError::IoctlError(e)),
104 }
105 }
106
107 #[cfg(test)]
108 mod tests {
109 use super::*;
110
111 #[test]
test_ctrlid()112 fn test_ctrlid() {
113 assert_eq!(
114 CtrlId::new(bindings::V4L2_CID_AUDIO_VOLUME),
115 Ok(CtrlId(bindings::V4L2_CID_AUDIO_VOLUME))
116 );
117 assert_eq!(
118 CtrlId::new(bindings::V4L2_CTRL_FLAG_NEXT_CTRL),
119 Err(CtrlIdError::InvalidControl(
120 bindings::V4L2_CTRL_FLAG_NEXT_CTRL
121 ))
122 );
123 }
124 }
125