mod descriptor;
mod ioctl;
mod uevent;
use std::fs::{read_dir, read_to_string, OpenOptions};
use std::io::ErrorKind;
use std::os::fd::{AsRawFd, OwnedFd};
use std::os::unix::fs::OpenOptionsExt;
use std::path::{Component, Path, PathBuf};
use std::sync::Arc;
use futures_lite::stream::{iter, unfold, Boxed};
use futures_lite::StreamExt;
use log::{debug, trace, warn};
use nix::fcntl::OFlag;
use nix::libc::EIO;
use nix::sys::socket::{bind, recvfrom, socket, AddressFamily, NetlinkAddr, SockFlag, SockProtocol, SockType};
use nix::unistd::{access, read, write, AccessFlags};
use crate::backend::hidraw::async_api::{read_with, write_with, AsyncFd};
use crate::backend::hidraw::descriptor::HidrawReportDescriptor;
use crate::backend::hidraw::ioctl::{hidraw_ioc_get_feature, hidraw_ioc_set_feature, hidraw_ioc_grdescsize};
use crate::backend::hidraw::uevent::{Action, UEvent};
use crate::backend::{Backend, DeviceInfoStream};
use crate::utils::TryIterExt;
use crate::{ensure, AsyncHidRead, AsyncHidWrite, AsyncHidFeatureHandle, DeviceEvent, DeviceId, DeviceInfo, HidError, HidResult};
#[derive(Default)]
pub struct HidRawBackend;
impl Backend for HidRawBackend {
type Reader = HidDevice;
type Writer = HidDevice;
type FeatureHandle = HidDevice;
async fn enumerate(&self) -> HidResult<DeviceInfoStream> {
let devices = read_dir("/sys/class/hidraw/")?
.map(|r| r.map(|e| e.path()).and_then(|p| p.canonicalize()))
.try_collect_vec()?;
let devices = devices.into_iter().map(get_device_info_raw).try_flatten();
Ok(iter(devices).boxed())
}
fn watch(&self) -> HidResult<Boxed<DeviceEvent>> {
const MONITOR_GROUP_KERNEL: u32 = 1;
const MONITOR_GROUP_UDEV: u32 = 2;
let socket = socket(
AddressFamily::Netlink,
SockType::Datagram,
SockFlag::SOCK_CLOEXEC | SockFlag::SOCK_NONBLOCK,
SockProtocol::NetlinkKObjectUEvent
)?;
let group = match access("/run/udev/control", AccessFlags::F_OK) {
Ok(_) => {
trace!("Udev deamon seems to be running, binding to udev monitor group");
MONITOR_GROUP_UDEV
}
Err(err) => {
trace!("Udev deamon seems not to be running ({:?}), binding to kernel monitor group", err);
MONITOR_GROUP_KERNEL
}
};
bind(socket.as_raw_fd(), &NetlinkAddr::new(0, group))?;
Ok(unfold((AsyncFd::new(socket)?, vec![0u8; 4096]), |(socket, mut buf)| async move {
loop {
let size = match read_with(&socket, |fd| {
recvfrom::<NetlinkAddr>(fd.as_raw_fd(), &mut buf).map_err(std::io::Error::from)
})
.await
{
Ok((size, _)) => size,
Err(err) => {
warn!("Reading uevent failed: {}", err);
continue;
}
};
let event = match UEvent::parse(&buf[0..size]) {
Ok(event) => event,
Err(reason) => {
debug!("Failed to parse uevent: {}", reason);
continue;
}
};
if event.subsystem != "hidraw" {
continue;
}
let dev_path = event
.dev_path
.components()
.filter(|c| *c != Component::RootDir)
.fold(PathBuf::from("/sys/"), |a, b| a.join(b));
let id = DeviceId::DevPath(dev_path);
let event = match event.action {
Action::Add => DeviceEvent::Connected(id),
Action::Remove => DeviceEvent::Disconnected(id),
Action::Other(a) => {
trace!("Unknown hidraw event: {}", a);
continue;
}
};
return Some((event, (socket, buf)));
}
})
.boxed())
}
async fn query_info(&self, id: &DeviceId) -> HidResult<Vec<DeviceInfo>> {
let DeviceId::DevPath(id) = id;
get_device_info_raw(id.clone())
}
async fn open(&self, id: &DeviceId, read: bool, write: bool) -> HidResult<(Option<Self::Reader>, Option<Self::Writer>)> {
let DeviceId::DevPath(id) = id;
let properties = read_to_string(id.join("uevent")).map_err(|err| match err {
err if err.kind() == ErrorKind::NotFound => HidError::NotConnected,
err => err.into()
})?;
let id = read_property(&properties, "DEVNAME")
.ok_or(HidError::message("Can't find dev name"))
.and_then(mange_dev_name)?;
let fd: OwnedFd = OpenOptions::new()
.read(read)
.write(write)
.custom_flags((OFlag::O_CLOEXEC | OFlag::O_NONBLOCK).bits())
.open(&id)
.map_err(|err| match err {
err if err.kind() == ErrorKind::NotFound => HidError::NotConnected,
err => err.into()
})?
.into();
let mut size = 0i32;
unsafe { hidraw_ioc_grdescsize(fd.as_raw_fd(), &mut size) }
.map_err(|e| HidError::message(format!("ioctl(GRDESCSIZE) error for {:?}, not a HIDRAW device?: {}", id, e)))?;
let device = HidDevice(Arc::new(AsyncFd::new(fd)?));
Ok((read.then(|| device.clone()), write.then(|| device.clone())))
}
async fn open_feature_handle(&self, id: &DeviceId) -> HidResult<Self::FeatureHandle> {
let (_, writer) = self.open(id, true, true).await?;
let device = writer.ok_or(HidError::message("Failed to open device for feature report"))?;
Ok(device)
}
}
fn get_device_info_raw(path: PathBuf) -> HidResult<Vec<DeviceInfo>> {
let properties = read_to_string(path.join("device/uevent")).map_err(|err| match err {
err if err.kind() == ErrorKind::NotFound => HidError::NotConnected,
err => err.into()
})?;
let (_bus, vendor_id, product_id) = read_property(&properties, "HID_ID")
.and_then(parse_hid_vid_pid)
.ok_or(HidError::message("Can't find hid ids"))?;
let name = read_property(&properties, "HID_NAME")
.ok_or(HidError::message("Can't find hid name"))?
.to_string();
let manufacturer = find_manufacturer_string(&path);
let serial_number = read_property(&properties, "HID_UNIQ")
.filter(|s| !s.is_empty())
.map(str::to_string);
let info = DeviceInfo {
id: DeviceId::DevPath(path.clone()),
name,
manufacturer,
product_id,
vendor_id,
usage_id: 0,
usage_page: 0,
serial_number
};
let results = HidrawReportDescriptor::from_syspath(&path)
.map(|descriptor| {
descriptor
.usages()
.map(|(usage_page, usage_id)| DeviceInfo {
usage_page,
usage_id,
..info.clone()
})
.collect()
})
.unwrap_or_else(|_| vec![info]);
Ok(results)
}
fn read_property<'a>(properties: &'a str, key: &str) -> Option<&'a str> {
properties
.lines()
.filter_map(|l| l.split_once('='))
.find_map(|(k, v)| (k == key).then_some(v))
}
fn mange_dev_name(dev_name: &str) -> HidResult<PathBuf> {
let path = Path::new(dev_name);
if path.is_absolute() {
ensure!(
dev_name
.strip_prefix("/dev/")
.is_some_and(|z| !z.is_empty()),
HidError::message("Absolute device paths must start with /dev/")
);
Ok(path.to_path_buf())
} else {
Ok(Path::new("/dev/").join(path))
}
}
fn parse_hid_vid_pid(s: &str) -> Option<(u16, u16, u16)> {
let mut elems = s.split(':').filter_map(|s| u16::from_str_radix(s, 16).ok());
let devtype = elems.next()?;
let vendor = elems.next()?;
let product = elems.next()?;
Some((devtype, vendor, product))
}
fn find_manufacturer_string(hidraw_path: &Path) -> Option<String> {
let mut current = hidraw_path.join("device");
loop {
let mfr_path = current.join("manufacturer");
if let Ok(mfr) = read_to_string(&mfr_path) {
let trimmed = mfr.trim().to_string();
if !trimmed.is_empty() {
return Some(trimmed);
}
}
current = current.parent()?.to_path_buf();
if current == Path::new("/sys") || current == Path::new("/") {
return None;
}
}
}
#[derive(Debug, Clone)]
#[repr(transparent)]
pub struct HidDevice(Arc<AsyncFd>);
impl AsyncHidRead for HidDevice {
async fn read_input_report<'a>(&'a mut self, buf: &'a mut [u8]) -> HidResult<usize> {
read_with(&self.0, |fd| read(fd.as_raw_fd(), buf).map_err(std::io::Error::from))
.await
.map_err(|err| match err {
err if err.raw_os_error() == Some(EIO) => HidError::Disconnected,
err => err.into()
})
}
}
impl AsyncHidWrite for HidDevice {
async fn write_output_report<'a>(&'a mut self, buf: &'a [u8]) -> HidResult<()> {
write_with(&self.0, |fd| write(fd, buf).map_err(std::io::Error::from))
.await
.map_err(|err| match err {
err if err.raw_os_error() == Some(EIO) => HidError::Disconnected,
err => err.into()
})
.map(|i| debug_assert_eq!(i, buf.len()))
}
}
impl AsyncHidFeatureHandle for HidDevice {
async fn read_feature_report<'a>(&'a mut self, buf: &'a mut [u8]) -> HidResult<usize> {
ensure!(!buf.is_empty(), HidError::message("Buffer cannot be empty"));
let result = write_with(&self.0, |fd| {
unsafe { hidraw_ioc_get_feature(fd.as_raw_fd(), buf) }.map_err(std::io::Error::from)
})
.await
.map_err(|e| HidError::message(format!("ioctl(GET_FEATURE) error: {}", e)))?;
Ok(result as usize)
}
async fn write_feature_report<'a>(&'a mut self, buf: &'a [u8]) -> HidResult<()> {
ensure!(!buf.is_empty(), HidError::message("Buffer cannot be empty"));
let mut data = buf.to_vec();
write_with(&self.0, |fd| {
unsafe { hidraw_ioc_set_feature(fd.as_raw_fd(), &mut data) }.map_err(std::io::Error::from)
})
.await
.map_err(|e| HidError::message(format!("ioctl(SET_FEATURE) error: {}", e)))?;
Ok(())
}
}
#[cfg(all(feature = "async-io", feature = "tokio"))]
compile_error!("Only tokio or async-io can be active at the same time");
#[cfg(feature = "async-io")]
mod async_api {
use std::os::fd::OwnedFd;
use async_io::Async;
pub type AsyncFd = Async<OwnedFd>;
pub async fn read_with<R>(inner: &AsyncFd, op: impl FnMut(&OwnedFd) -> std::io::Result<R>) -> std::io::Result<R> {
inner.read_with(op).await
}
pub async fn write_with<R>(inner: &AsyncFd, op: impl FnMut(&OwnedFd) -> std::io::Result<R>) -> std::io::Result<R> {
inner.write_with(op).await
}
}
#[cfg(feature = "tokio")]
mod async_api {
use std::os::fd::OwnedFd;
use tokio::io::Interest;
pub type AsyncFd = tokio::io::unix::AsyncFd<OwnedFd>;
pub async fn read_with<R>(inner: &AsyncFd, op: impl FnMut(&OwnedFd) -> std::io::Result<R>) -> std::io::Result<R> {
inner.async_io(Interest::READABLE, op).await
}
pub async fn write_with<R>(inner: &AsyncFd, op: impl FnMut(&OwnedFd) -> std::io::Result<R>) -> std::io::Result<R> {
inner.async_io(Interest::WRITABLE, op).await
}
}