async_hid/backend/hidraw/
mod.rs1mod descriptor;
2mod ioctl;
3mod utils;
4
5use std::fs::{OpenOptions, read_dir, read_to_string};
6use std::os::fd::{AsRawFd, OwnedFd};
7use std::os::unix::fs::OpenOptionsExt;
8use std::path::{Path, PathBuf};
9
10use futures_core::Stream;
11use nix::fcntl::OFlag;
12use nix::unistd::{read, write};
13
14use crate::backend::hidraw::descriptor::HidrawReportDescriptor;
15use crate::backend::hidraw::utils::{iter, TryIterExt};
16use crate::{ensure, DeviceInfo, ErrorSource, HidError, HidResult, SerialNumberExt, AccessMode};
17
18use crate::backend::hidraw::async_api::{AsyncFd, read_with, write_with};
19use crate::backend::hidraw::ioctl::hidraw_ioc_grdescsize;
20
21pub async fn enumerate() -> HidResult<impl Stream<Item = DeviceInfo> + Send + Unpin> {
22 let devices = read_dir("/sys/class/hidraw/")?
23 .map(|r| r.map(|e| e.path()))
24 .try_collect_vec()?;
25 let devices = devices
26 .into_iter()
27 .map(get_device_info_raw)
28 .filter_map(|r| {
29 r.map_err(|e| log::trace!("Failed to query device information\n\tbecause {e:?}"))
30 .ok()
31 })
32 .flatten();
33 Ok(iter(devices))
34}
35
36fn get_device_info_raw(path: PathBuf) -> HidResult<Vec<DeviceInfo>> {
37 let properties = read_to_string(path.join("uevent"))?;
38 let id = read_property(&properties, "DEVNAME")
39 .ok_or(HidError::custom("Can't find dev name"))
40 .and_then(mange_dev_name)?;
41
42 let properties = read_to_string(path.join("device/uevent"))?;
43
44 let (_bus, vendor_id, product_id) = read_property(&properties, "HID_ID")
45 .and_then(parse_hid_vid_pid)
46 .ok_or(HidError::custom("Can't find hid ids"))?;
47
48 let name = read_property(&properties, "HID_NAME")
49 .ok_or(HidError::custom("Can't find hid name"))?
50 .to_string();
51
52 let serial_number = read_property(&properties, "HID_UNIQ")
53 .filter(|s| !s.is_empty())
54 .map(str::to_string);
55
56 let info = DeviceInfo {
57 id: id.into(),
58 name,
59 product_id,
60 vendor_id,
61 usage_id: 0,
62 usage_page: 0,
63 private_data: BackendPrivateData { serial_number }
64 };
65
66 let results = HidrawReportDescriptor::from_syspath(&path)
67 .map(|descriptor| {
68 descriptor
69 .usages()
70 .map(|(usage_page, usage_id)| DeviceInfo {
71 usage_page,
72 usage_id,
73 ..info.clone()
74 })
75 .collect()
76 })
77 .unwrap_or_else(|_| vec![info]);
78 Ok(results)
79}
80
81fn read_property<'a>(properties: &'a str, key: &str) -> Option<&'a str> {
82 properties
83 .lines()
84 .filter_map(|l| l.split_once('='))
85 .find_map(|(k, v)| (k == key).then_some(v))
86}
87
88fn mange_dev_name(dev_name: &str) -> HidResult<PathBuf> {
89 let path = Path::new(dev_name);
90 if path.is_absolute() {
91 ensure!(
92 dev_name
93 .strip_prefix("/dev/")
94 .is_some_and(|z| !z.is_empty()),
95 HidError::custom("Absolute device paths must start with /dev/")
96 );
97 Ok(path.to_path_buf())
98 } else {
99 Ok(Path::new("/dev/").join(path))
100 }
101}
102
103fn parse_hid_vid_pid(s: &str) -> Option<(u16, u16, u16)> {
104 let mut elems = s.split(':').filter_map(|s| u16::from_str_radix(s, 16).ok());
105 let devtype = elems.next()?;
106 let vendor = elems.next()?;
107 let product = elems.next()?;
108
109 Some((devtype, vendor, product))
110}
111
112impl SerialNumberExt for DeviceInfo {
113 fn serial_number(&self) -> Option<&str> {
114 self.private_data
115 .serial_number
116 .as_ref()
117 .map(String::as_str)
118 }
119}
120
121
122#[derive(Debug)]
123pub struct BackendDevice {
124 fd: AsyncFd
125}
126
127impl BackendDevice {
128 pub async fn read_input_report(&self, buf: &mut [u8]) -> HidResult<usize> {
129 read_with(&self.fd, |fd| read(fd.as_raw_fd(), buf).map_err(BackendError::from))
130 .await
131 .map_err(HidError::from)
132 }
133
134 pub async fn write_output_report(&self, data: &[u8]) -> HidResult<()> {
135 ensure!(!data.is_empty(), HidError::zero_sized_data());
136 write_with(&self.fd, |fd| write(fd.as_raw_fd(), data).map_err(BackendError::from))
137 .await
138 .map_err(HidError::from)
139 .map(|i| debug_assert_eq!(i, data.len()))
140 }
141}
142
143pub async fn open(id: &BackendDeviceId, mode: AccessMode) -> HidResult<BackendDevice> {
144 let fd: OwnedFd = OpenOptions::new()
145 .read(mode.readable())
146 .write(mode.writeable())
147 .custom_flags((OFlag::O_CLOEXEC | OFlag::O_NONBLOCK).bits())
148 .open(id)?
149 .into();
150
151 let mut size = 0i32;
152 unsafe { hidraw_ioc_grdescsize(fd.as_raw_fd(), &mut size) }
153 .map_err(|e| HidError::custom(format!("ioctl(GRDESCSIZE) error for {:?}, not a HIDRAW device?: {}", id, e)))?;
154
155 Ok(BackendDevice { fd: AsyncFd::new(fd)? })
156}
157
158
159#[derive(Debug, Clone, Eq, PartialEq)]
160pub struct BackendPrivateData {
161 serial_number: Option<String>
162}
163pub type BackendDeviceId = PathBuf;
164pub type BackendError = std::io::Error;
165
166impl From<BackendError> for ErrorSource {
167 fn from(value: BackendError) -> Self {
168 ErrorSource::PlatformSpecific(value)
169 }
170}
171
172#[cfg(all(feature = "async-io", feature = "tokio"))]
173compile_error!("Only tokio or async-io can be active at the same time");
174
175#[cfg(feature = "async-io")]
176mod async_api {
177 use std::os::fd::OwnedFd;
178 use async_io::Async;
179
180 pub type AsyncFd = Async<OwnedFd>;
181
182 pub async fn read_with<R>(inner: &AsyncFd, op: impl FnMut(&OwnedFd) -> std::io::Result<R>) -> std::io::Result<R> {
183 inner.read_with(op).await
184 }
185
186 pub async fn write_with<R>(inner: &AsyncFd, op: impl FnMut(&OwnedFd) -> std::io::Result<R>) -> std::io::Result<R> {
187 inner.write_with(op).await
188 }
189}
190
191#[cfg(feature = "tokio")]
192mod async_api {
193 use std::os::fd::OwnedFd;
194 use tokio::io::Interest;
195
196 pub type AsyncFd = tokio::io::unix::AsyncFd<OwnedFd>;
197
198 pub async fn read_with<R>(inner: &AsyncFd, op: impl FnMut(&OwnedFd) -> std::io::Result<R>) -> std::io::Result<R> {
199 inner.async_io(Interest::READABLE, op).await
200 }
201
202 pub async fn write_with<R>(inner: &AsyncFd, op: impl FnMut(&OwnedFd) -> std::io::Result<R>) -> std::io::Result<R> {
203 inner.async_io(Interest::WRITABLE, op).await
204 }
205}
206
207