use std::future::Future;
use std::pin::Pin;
use std::sync::atomic::{AtomicBool, AtomicU32, Ordering};
use std::sync::{Arc, Mutex};
use std::task::{Context, Poll, Waker};
use windows_sys::core::GUID;
use windows_sys::Win32::Devices::DeviceAndDriverInstallation::{
CM_Get_Device_IDW, CM_Get_Parent, SetupDiCreateDeviceInfoList, SetupDiDestroyDeviceInfoList,
SetupDiEnumDeviceInterfaces, SetupDiGetClassDevsW, SetupDiGetDeviceInterfaceDetailW,
SetupDiGetDevicePropertyW, SetupDiGetDeviceRegistryPropertyW, SetupDiOpenDeviceInterfaceW,
CR_SUCCESS, DIGCF_DEVICEINTERFACE, DIGCF_PRESENT, HDEVINFO, MAX_DEVICE_ID_LEN,
SETUP_DI_REGISTRY_PROPERTY, SPDRP_COMPATIBLEIDS, SPDRP_ENUMERATOR_NAME, SPDRP_HARDWAREID,
SP_DEVICE_INTERFACE_DATA, SP_DEVICE_INTERFACE_DETAIL_DATA_W, SP_DEVINFO_DATA,
};
use windows_sys::Win32::Devices::HumanInterfaceDevice::{
HidD_FreePreparsedData, HidD_GetAttributes, HidD_GetHidGuid, HidD_GetIndexedString,
HidD_GetInputReport, HidD_GetManufacturerString, HidD_GetPreparsedData, HidD_GetProductString,
HidD_GetSerialNumberString, HidD_SetFeature, HidD_SetNumInputBuffers, HidP_Feature,
HidP_GetButtonCaps, HidP_GetCaps, HidP_GetLinkCollectionNodes, HidP_GetValueCaps, HidP_Input,
HidP_Output, HIDD_ATTRIBUTES, HIDP_BUTTON_CAPS, HIDP_CAPS, HIDP_LINK_COLLECTION_NODE,
HIDP_REPORT_TYPE, HIDP_STATUS_SUCCESS, HIDP_VALUE_CAPS, PHIDP_PREPARSED_DATA,
};
use windows_sys::Win32::Devices::Properties::{
DEVPKEY_Device_ContainerId, DEVPROPTYPE, DEVPROP_TYPE_GUID,
};
use windows_sys::Win32::Foundation::{
CloseHandle, GetLastError, ERROR_ACCESS_DENIED, ERROR_DEVICE_NOT_CONNECTED,
ERROR_FILE_NOT_FOUND, ERROR_INSUFFICIENT_BUFFER, ERROR_INVALID_FUNCTION, ERROR_IO_INCOMPLETE,
ERROR_IO_PENDING, ERROR_NOT_SUPPORTED, ERROR_NO_MORE_ITEMS, ERROR_PATH_NOT_FOUND, GENERIC_READ,
GENERIC_WRITE, HANDLE, INVALID_HANDLE_VALUE, WAIT_OBJECT_0, WAIT_TIMEOUT,
};
use windows_sys::Win32::Storage::FileSystem::{
CreateFileW, ReadFile, WriteFile, FILE_FLAG_OVERLAPPED, FILE_SHARE_READ, FILE_SHARE_WRITE,
OPEN_EXISTING,
};
use windows_sys::Win32::System::Threading::{
CreateEventW, RegisterWaitForSingleObject, ResetEvent, SetEvent, UnregisterWaitEx,
WaitForSingleObject, INFINITE, WT_EXECUTEINWAITTHREAD, WT_EXECUTEONLYONCE,
};
use windows_sys::Win32::System::IO::{
CancelIoEx, DeviceIoControl, GetOverlappedResult, OVERLAPPED,
};
use super::{HidBackend, HidDeviceBackend};
use crate::descriptor::{CollectionKind, DescriptorBuilder, MainFlags, ReportKind};
use crate::error::{HidError, HidResult};
use crate::{BusType, DeviceInfo};
const DEFAULT_WRITE_TIMEOUT_MS: u32 = 1000;
const IOCTL_HID_GET_FEATURE: u32 = 0x000B_0192;
const IOCTL_HID_GET_INPUT_REPORT: u32 = 0x000B_01A2;
const MAX_STRING_WCHARS: usize = 256;
fn wide(s: &str) -> Vec<u16> {
s.encode_utf16().chain(core::iter::once(0)).collect()
}
fn utf16_until_nul(buf: &[u16]) -> Option<String> {
let len = buf.iter().position(|&c| c == 0).unwrap_or(buf.len());
(len > 0).then(|| String::from_utf16_lossy(&buf[..len]))
}
fn interface_number_from_path(path: &str, bus_type: BusType) -> i32 {
let lower = path.to_ascii_lowercase();
let Some(pos) = lower.find("&mi_") else {
return if bus_type == BusType::Usb { 0 } else { -1 };
};
let hex: String = lower[pos + 4..]
.chars()
.take_while(|c| c.is_ascii_hexdigit())
.collect();
if hex.is_empty() {
return if bus_type == BusType::Usb { 0 } else { -1 };
}
i32::from_str_radix(&hex, 16).unwrap_or(-1)
}
fn guid_to_bytes(guid: &GUID) -> [u8; 16] {
let mut out = [0u8; 16];
out[0..4].copy_from_slice(&guid.data1.to_le_bytes());
out[4..6].copy_from_slice(&guid.data2.to_le_bytes());
out[6..8].copy_from_slice(&guid.data3.to_le_bytes());
out[8..16].copy_from_slice(&guid.data4);
out
}
fn classify_bus(enumerator: &str, ids: &str) -> BusType {
let enumerator = enumerator.to_ascii_uppercase();
let ids = ids.to_ascii_uppercase();
if ids.contains("PNP0C50") {
return BusType::I2c;
}
if ids.contains("PNP0C51") {
return BusType::Spi;
}
if enumerator == "USB" || ids.contains("USB") {
return BusType::Usb;
}
if enumerator.starts_with("BTH") || ids.contains("BTHENUM") || ids.contains("BTHLEDEVICE") {
return BusType::Bluetooth;
}
BusType::Unknown
}
struct Handle(HANDLE);
unsafe impl Send for Handle {}
unsafe impl Sync for Handle {}
impl Handle {
fn raw(&self) -> HANDLE {
self.0
}
}
impl Drop for Handle {
fn drop(&mut self) {
unsafe { CloseHandle(self.0) };
}
}
#[derive(Clone, Copy)]
struct RawHandle(HANDLE);
unsafe impl Send for RawHandle {}
unsafe impl Sync for RawHandle {}
struct OverlappedIo {
ol: Box<OVERLAPPED>,
event: Handle,
}
unsafe impl Send for OverlappedIo {}
impl OverlappedIo {
fn new() -> HidResult<Self> {
let event = unsafe { CreateEventW(core::ptr::null(), 0, 0, core::ptr::null()) };
if event.is_null() {
return Err(HidError::last_os_error("CreateEventW"));
}
let mut ol: Box<OVERLAPPED> = Box::new(unsafe { core::mem::zeroed() });
ol.hEvent = event;
Ok(OverlappedIo {
ol,
event: Handle(event),
})
}
fn ol_mut(&mut self) -> *mut OVERLAPPED {
&mut *self.ol
}
}
struct DevInfoList(HDEVINFO);
impl Drop for DevInfoList {
fn drop(&mut self) {
unsafe { SetupDiDestroyDeviceInfoList(self.0) };
}
}
struct PreparsedData(PHIDP_PREPARSED_DATA);
impl PreparsedData {
fn get(handle: HANDLE) -> HidResult<Self> {
let mut pp: PHIDP_PREPARSED_DATA = 0;
if !unsafe { HidD_GetPreparsedData(handle, &mut pp) } {
return Err(HidError::last_os_error("HidD_GetPreparsedData"));
}
Ok(PreparsedData(pp))
}
fn caps(&self) -> HidResult<HIDP_CAPS> {
let mut caps = HIDP_CAPS::default();
if unsafe { HidP_GetCaps(self.0, &mut caps) } != HIDP_STATUS_SUCCESS {
return Err(HidError::backend("HidP_GetCaps failed"));
}
Ok(caps)
}
}
impl Drop for PreparsedData {
fn drop(&mut self) {
unsafe { HidD_FreePreparsedData(self.0) };
}
}
fn registry_property(
list: HDEVINFO,
devinfo: &SP_DEVINFO_DATA,
property: SETUP_DI_REGISTRY_PROPERTY,
) -> Option<Vec<u16>> {
let mut required = 0u32;
unsafe {
SetupDiGetDeviceRegistryPropertyW(
list,
devinfo,
property,
core::ptr::null_mut(),
core::ptr::null_mut(),
0,
&mut required,
);
}
if required == 0 {
return None;
}
let mut buf = vec![0u16; (required as usize).div_ceil(2)];
let ok = unsafe {
SetupDiGetDeviceRegistryPropertyW(
list,
devinfo,
property,
core::ptr::null_mut(),
buf.as_mut_ptr().cast(),
required,
core::ptr::null_mut(),
)
};
(ok != 0).then_some(buf)
}
fn device_instance_id(devinst: u32) -> Option<String> {
let mut buf = vec![0u16; MAX_DEVICE_ID_LEN as usize + 1];
let cr = unsafe { CM_Get_Device_IDW(devinst, buf.as_mut_ptr(), buf.len() as u32, 0) };
(cr == CR_SUCCESS).then(|| utf16_until_nul(&buf)).flatten()
}
fn bus_type_for_devnode(list: HDEVINFO, devinfo: &SP_DEVINFO_DATA) -> BusType {
let enumerator = registry_property(list, devinfo, SPDRP_ENUMERATOR_NAME)
.and_then(|b| utf16_until_nul(&b))
.unwrap_or_default();
let mut ids = String::new();
for prop in [SPDRP_HARDWAREID, SPDRP_COMPATIBLEIDS] {
if let Some(b) = registry_property(list, devinfo, prop) {
ids.push_str(&String::from_utf16_lossy(&b));
ids.push(' ');
}
}
let bus = classify_bus(&enumerator, &ids);
if bus != BusType::Unknown {
return bus;
}
let mut devinst = devinfo.DevInst;
for _ in 0..8 {
let mut parent = 0u32;
if unsafe { CM_Get_Parent(&mut parent, devinst, 0) } != CR_SUCCESS {
break;
}
if let Some(id) = device_instance_id(parent) {
let enumerator = id.split('\\').next().unwrap_or("");
let bus = classify_bus(enumerator, &id);
if bus != BusType::Unknown {
return bus;
}
}
devinst = parent;
}
BusType::Unknown
}
fn devnode_for_interface(path: &str) -> HidResult<(DevInfoList, SP_DEVINFO_DATA)> {
let raw = unsafe { SetupDiCreateDeviceInfoList(core::ptr::null(), core::ptr::null_mut()) };
if raw == INVALID_HANDLE_VALUE as HDEVINFO {
return Err(HidError::last_os_error("SetupDiCreateDeviceInfoList"));
}
let list = DevInfoList(raw);
let wpath = wide(path);
let mut iface = SP_DEVICE_INTERFACE_DATA {
cbSize: core::mem::size_of::<SP_DEVICE_INTERFACE_DATA>() as u32,
..Default::default()
};
if unsafe { SetupDiOpenDeviceInterfaceW(list.0, wpath.as_ptr(), 0, &mut iface) } == 0 {
return Err(HidError::last_os_error("SetupDiOpenDeviceInterfaceW"));
}
let mut devinfo = SP_DEVINFO_DATA {
cbSize: core::mem::size_of::<SP_DEVINFO_DATA>() as u32,
..Default::default()
};
let mut required = 0u32;
let ok = unsafe {
SetupDiGetDeviceInterfaceDetailW(
list.0,
&iface,
core::ptr::null_mut(),
0,
&mut required,
&mut devinfo,
)
};
if ok == 0 && unsafe { GetLastError() } != ERROR_INSUFFICIENT_BUFFER {
return Err(HidError::last_os_error("SetupDiGetDeviceInterfaceDetailW"));
}
Ok((list, devinfo))
}
fn open_interface(path: &str, access: u32) -> Result<Handle, u32> {
let wpath = wide(path);
let handle = unsafe {
CreateFileW(
wpath.as_ptr(),
access,
FILE_SHARE_READ | FILE_SHARE_WRITE,
core::ptr::null(),
OPEN_EXISTING,
FILE_FLAG_OVERLAPPED,
core::ptr::null_mut(),
)
};
if handle == INVALID_HANDLE_VALUE {
return Err(unsafe { GetLastError() });
}
Ok(Handle(handle))
}
fn query_device_info(handle: HANDLE, path: &str, bus_type: BusType) -> DeviceInfo {
let mut info = DeviceInfo {
path: path.to_string(),
interface_number: interface_number_from_path(path, bus_type),
bus_type,
..Default::default()
};
let mut attrs = HIDD_ATTRIBUTES {
Size: core::mem::size_of::<HIDD_ATTRIBUTES>() as u32,
..Default::default()
};
if unsafe { HidD_GetAttributes(handle, &mut attrs) } {
info.vendor_id = attrs.VendorID;
info.product_id = attrs.ProductID;
info.release_number = attrs.VersionNumber;
}
let mut buf = [0u16; MAX_STRING_WCHARS];
let byte_len = (MAX_STRING_WCHARS * 2) as u32;
unsafe {
if HidD_GetSerialNumberString(handle, buf.as_mut_ptr().cast(), byte_len) {
info.serial_number = utf16_until_nul(&buf);
}
buf = [0u16; MAX_STRING_WCHARS];
if HidD_GetManufacturerString(handle, buf.as_mut_ptr().cast(), byte_len) {
info.manufacturer_string = utf16_until_nul(&buf);
}
buf = [0u16; MAX_STRING_WCHARS];
if HidD_GetProductString(handle, buf.as_mut_ptr().cast(), byte_len) {
info.product_string = utf16_until_nul(&buf);
}
}
if let Ok(pp) = PreparsedData::get(handle) {
if let Ok(caps) = pp.caps() {
info.usage_page = caps.UsagePage;
info.usage = caps.Usage;
}
}
info
}
pub struct WinApi;
impl HidBackend for WinApi {
type Device = WinDevice;
fn new() -> HidResult<Self> {
Ok(WinApi)
}
fn enumerate(&self, vendor_id: u16, product_id: u16) -> HidResult<Vec<DeviceInfo>> {
let mut hid_guid: GUID = unsafe { core::mem::zeroed() };
unsafe { HidD_GetHidGuid(&mut hid_guid) };
let raw = unsafe {
SetupDiGetClassDevsW(
&hid_guid,
core::ptr::null(),
core::ptr::null_mut(),
DIGCF_PRESENT | DIGCF_DEVICEINTERFACE,
)
};
if raw == INVALID_HANDLE_VALUE as HDEVINFO {
return Err(HidError::last_os_error("SetupDiGetClassDevsW"));
}
let list = DevInfoList(raw);
let mut result = Vec::new();
for index in 0.. {
let mut iface = SP_DEVICE_INTERFACE_DATA {
cbSize: core::mem::size_of::<SP_DEVICE_INTERFACE_DATA>() as u32,
..Default::default()
};
let ok = unsafe {
SetupDiEnumDeviceInterfaces(list.0, core::ptr::null(), &hid_guid, index, &mut iface)
};
if ok == 0 {
if unsafe { GetLastError() } == ERROR_NO_MORE_ITEMS {
break;
}
return Err(HidError::last_os_error("SetupDiEnumDeviceInterfaces"));
}
let mut devinfo = SP_DEVINFO_DATA {
cbSize: core::mem::size_of::<SP_DEVINFO_DATA>() as u32,
..Default::default()
};
let Some(path) = interface_detail_path(list.0, &iface, &mut devinfo) else {
continue;
};
let Ok(handle) = open_interface(&path, 0) else {
continue;
};
let bus_type = bus_type_for_devnode(list.0, &devinfo);
let info = query_device_info(handle.raw(), &path, bus_type);
if info.matches(vendor_id, product_id) {
result.push(info);
}
}
Ok(result)
}
fn open_path(&self, path: &str) -> HidResult<WinDevice> {
WinDevice::open(path)
}
}
fn interface_detail_path(
list: HDEVINFO,
iface: &SP_DEVICE_INTERFACE_DATA,
devinfo: &mut SP_DEVINFO_DATA,
) -> Option<String> {
let mut required = 0u32;
unsafe {
SetupDiGetDeviceInterfaceDetailW(
list,
iface,
core::ptr::null_mut(),
0,
&mut required,
core::ptr::null_mut(),
);
}
let path_offset = core::mem::offset_of!(SP_DEVICE_INTERFACE_DETAIL_DATA_W, DevicePath);
if (required as usize) <= path_offset {
return None;
}
let mut storage = vec![0u32; (required as usize).div_ceil(4)];
let detail = storage.as_mut_ptr() as *mut SP_DEVICE_INTERFACE_DETAIL_DATA_W;
unsafe {
(*detail).cbSize = core::mem::size_of::<SP_DEVICE_INTERFACE_DETAIL_DATA_W>() as u32;
let ok = SetupDiGetDeviceInterfaceDetailW(
list,
iface,
detail,
required,
core::ptr::null_mut(),
devinfo,
);
if ok == 0 {
return None;
}
let chars = (required as usize - path_offset) / 2;
let path = core::slice::from_raw_parts(
core::ptr::addr_of!((*detail).DevicePath).cast::<u16>(),
chars,
);
utf16_until_nul(path)
}
}
struct ReadState {
io: OverlappedIo,
buf: Vec<u8>,
pending: bool,
wait: RawHandle,
}
struct ReadWake {
waker: Mutex<Option<Waker>>,
wait_registered: AtomicBool,
event: RawHandle,
}
unsafe extern "system" fn read_wait_callback(ctx: *mut core::ffi::c_void, _timer_fired: bool) {
let wake = unsafe { &*ctx.cast_const().cast::<ReadWake>() };
unsafe { SetEvent(wake.event.0) };
wake.wait_registered.store(false, Ordering::Release);
let waker = wake.waker.lock().unwrap_or_else(|e| e.into_inner()).take();
if let Some(waker) = waker {
waker.wake();
}
}
struct WriteState {
io: OverlappedIo,
buf: Vec<u8>,
}
pub struct WinDevice {
read: Mutex<ReadState>,
wake: Arc<ReadWake>,
write: Mutex<WriteState>,
handle: Handle,
path: String,
info: DeviceInfo,
feature_report_len: u16,
write_timeout_ms: AtomicU32,
}
impl WinDevice {
fn open(path: &str) -> HidResult<Self> {
let handle = match open_interface(path, GENERIC_READ | GENERIC_WRITE) {
Ok(h) => h,
Err(ERROR_ACCESS_DENIED) => open_interface(path, 0).map_err(|err| match err {
ERROR_FILE_NOT_FOUND | ERROR_PATH_NOT_FOUND => HidError::DeviceNotFound,
ERROR_ACCESS_DENIED => HidError::OpenFailed {
message: format!("{path}: access denied"),
},
_ => HidError::OpenFailed {
message: format!("{path}: {}", std::io::Error::from_raw_os_error(err as i32)),
},
})?,
Err(ERROR_FILE_NOT_FOUND | ERROR_PATH_NOT_FOUND) => {
return Err(HidError::DeviceNotFound)
}
Err(err) => {
return Err(HidError::OpenFailed {
message: format!("{path}: {}", std::io::Error::from_raw_os_error(err as i32)),
})
}
};
if !unsafe { HidD_SetNumInputBuffers(handle.raw(), 64) } {
return Err(HidError::last_os_error("HidD_SetNumInputBuffers"));
}
let pp = PreparsedData::get(handle.raw())?;
let caps = pp.caps()?;
drop(pp);
let bus_type = devnode_for_interface(path)
.map(|(list, devinfo)| bus_type_for_devnode(list.0, &devinfo))
.unwrap_or_default();
let info = query_device_info(handle.raw(), path, bus_type);
let read_io = OverlappedIo::new()?;
let wake = Arc::new(ReadWake {
waker: Mutex::new(None),
wait_registered: AtomicBool::new(false),
event: RawHandle(read_io.event.raw()),
});
let _ctx = Arc::into_raw(Arc::clone(&wake));
Ok(WinDevice {
read: Mutex::new(ReadState {
io: read_io,
buf: vec![0u8; (caps.InputReportByteLength as usize).max(1)],
pending: false,
wait: RawHandle(core::ptr::null_mut()),
}),
wake,
write: Mutex::new(WriteState {
io: OverlappedIo::new()?,
buf: vec![0u8; caps.OutputReportByteLength as usize],
}),
handle,
path: path.to_string(),
info,
feature_report_len: caps.FeatureReportByteLength,
write_timeout_ms: AtomicU32::new(DEFAULT_WRITE_TIMEOUT_MS),
})
}
fn io_error(operation: &'static str, err: u32) -> HidError {
if err == ERROR_DEVICE_NOT_CONNECTED {
return HidError::Disconnected;
}
HidError::io(operation, std::io::Error::from_raw_os_error(err as i32))
}
fn start_read(&self, st: &mut ReadState) -> HidResult<Option<u32>> {
if st.pending {
return Ok(None);
}
st.pending = true;
st.buf.fill(0);
unsafe { ResetEvent(st.io.event.raw()) };
let len = st.buf.len() as u32;
let buf_ptr = st.buf.as_mut_ptr();
let ol = st.io.ol_mut();
let mut bytes_read = 0u32;
let res = unsafe { ReadFile(self.handle.raw(), buf_ptr, len, &mut bytes_read, ol) };
if res == 0 {
let err = unsafe { GetLastError() };
if err != ERROR_IO_PENDING {
unsafe { CancelIoEx(self.handle.raw(), ol) };
st.pending = false;
return Err(Self::io_error("hid read", err));
}
return Ok(None);
}
st.pending = false;
Ok(Some(bytes_read))
}
fn copy_report(st: &ReadState, bytes_read: u32, buf: &mut [u8]) -> usize {
let mut report = &st.buf[..(bytes_read as usize).min(st.buf.len())];
if report.first() == Some(&0) {
report = &report[1..];
}
let copy_len = report.len().min(buf.len());
buf[..copy_len].copy_from_slice(&report[..copy_len]);
copy_len
}
fn get_report_len(first_byte: u8, bytes_returned: usize, buf_len: usize) -> usize {
let n = if first_byte == 0 {
bytes_returned + 1
} else {
bytes_returned
};
n.min(buf_len)
}
fn poll_read(&self, buf: &mut [u8], cx: &mut Context<'_>) -> Poll<HidResult<usize>> {
if buf.is_empty() {
return Poll::Ready(Err(HidError::InvalidData {
message: "read buffer must not be empty".into(),
}));
}
let mut st = self.read.lock().unwrap_or_else(|e| e.into_inner());
let bytes_read = match self.start_read(&mut st) {
Err(e) => return Poll::Ready(Err(e)),
Ok(Some(n)) => n,
Ok(None) => {
let ol = st.io.ol_mut();
let mut n = 0u32;
let res = unsafe { GetOverlappedResult(self.handle.raw(), ol, &mut n, 0) };
if res == 0 {
let err = unsafe { GetLastError() };
if err == ERROR_IO_INCOMPLETE {
return self.park_read(&mut st, cx);
}
st.pending = false;
return Poll::Ready(Err(Self::io_error("hid read", err)));
}
st.pending = false;
n
}
};
Poll::Ready(Ok(Self::copy_report(&st, bytes_read, buf)))
}
fn park_read(&self, st: &mut ReadState, cx: &mut Context<'_>) -> Poll<HidResult<usize>> {
*self.wake.waker.lock().unwrap_or_else(|e| e.into_inner()) = Some(cx.waker().clone());
if !self.wake.wait_registered.swap(true, Ordering::AcqRel) {
if !st.wait.0.is_null() {
unsafe { UnregisterWaitEx(st.wait.0, core::ptr::null_mut()) };
st.wait.0 = core::ptr::null_mut();
}
let mut wait: HANDLE = core::ptr::null_mut();
let ok = unsafe {
RegisterWaitForSingleObject(
&mut wait,
st.io.event.raw(),
Some(read_wait_callback),
Arc::as_ptr(&self.wake).cast(),
INFINITE,
WT_EXECUTEONLYONCE | WT_EXECUTEINWAITTHREAD,
)
};
if ok == 0 {
self.wake.wait_registered.store(false, Ordering::Release);
return Poll::Ready(Err(HidError::last_os_error("RegisterWaitForSingleObject")));
}
st.wait.0 = wait;
}
Poll::Pending
}
fn ioctl_get_report(
&self,
ioctl: u32,
buf: &mut [u8],
operation: &'static str,
) -> HidResult<usize> {
if buf.is_empty() {
return Err(HidError::InvalidData {
message: "buffer must contain a report ID byte".into(),
});
}
let mut io = OverlappedIo::new()?;
let ol = io.ol_mut();
let mut returned = 0u32;
let res = unsafe {
DeviceIoControl(
self.handle.raw(),
ioctl,
buf.as_ptr().cast(),
buf.len() as u32,
buf.as_mut_ptr().cast(),
buf.len() as u32,
&mut returned,
ol,
)
};
if res == 0 {
let err = unsafe { GetLastError() };
if err != ERROR_IO_PENDING {
return Err(Self::io_error(operation, err));
}
}
if unsafe { GetOverlappedResult(self.handle.raw(), ol, &mut returned, 1) } == 0 {
return Err(Self::io_error(operation, unsafe { GetLastError() }));
}
Ok(Self::get_report_len(buf[0], returned as usize, buf.len()))
}
fn reconstruct_descriptor(&self) -> HidResult<Vec<u8>> {
let pp = PreparsedData::get(self.handle.raw())?;
let caps = pp.caps()?;
let count = caps.NumberLinkCollectionNodes as usize;
if count == 0 {
return Err(HidError::backend("device reports no link collections"));
}
let mut nodes = vec![HIDP_LINK_COLLECTION_NODE::default(); count];
let mut len = count as u32;
let status = unsafe { HidP_GetLinkCollectionNodes(nodes.as_mut_ptr(), &mut len, pp.0) };
if status != HIDP_STATUS_SUCCESS {
return Err(HidError::backend("HidP_GetLinkCollectionNodes failed"));
}
nodes.truncate(len as usize);
let mut fields = collect_fields(&pp, &caps);
synthesize_padding(&caps, &mut fields);
let mut by_node: Vec<Vec<usize>> = vec![Vec::new(); nodes.len()];
for (i, f) in fields.iter().enumerate() {
if let Some(v) = by_node.get_mut(f.link as usize) {
v.push(i);
}
}
for v in &mut by_node {
v.sort_by_key(|&i| (fields[i].kind, fields[i].report_id, fields[i].order));
}
let mut b = DescriptorBuilder::new();
emit_collection(&mut b, &nodes, &by_node, &fields, 0, 0)?;
Ok(b.build())
}
pub fn container_id(&self) -> HidResult<[u8; 16]> {
let (list, devinfo) = devnode_for_interface(&self.path)?;
let mut guid: GUID = unsafe { core::mem::zeroed() };
let mut prop_type: DEVPROPTYPE = 0;
let ok = unsafe {
SetupDiGetDevicePropertyW(
list.0,
&devinfo,
&DEVPKEY_Device_ContainerId,
&mut prop_type,
(&mut guid as *mut GUID).cast(),
core::mem::size_of::<GUID>() as u32,
core::ptr::null_mut(),
0,
)
};
if ok == 0 {
return Err(HidError::last_os_error("SetupDiGetDevicePropertyW"));
}
if prop_type != DEVPROP_TYPE_GUID {
return Err(HidError::backend("container id property is not a GUID"));
}
Ok(guid_to_bytes(&guid))
}
pub fn set_write_timeout(&self, timeout_ms: u32) {
self.write_timeout_ms.store(timeout_ms, Ordering::Relaxed);
}
}
impl HidDeviceBackend for WinDevice {
type Read<'a> = ReadAsync<'a>;
fn write(&self, data: &[u8]) -> HidResult<usize> {
if data.is_empty() {
return Err(HidError::InvalidData {
message: "write data must contain a report ID byte".into(),
});
}
let mut st = self.write.lock().unwrap_or_else(|e| e.into_inner());
let (ptr, len) = if data.len() >= st.buf.len() {
(data.as_ptr(), data.len())
} else {
let n = data.len();
st.buf[..n].copy_from_slice(data);
st.buf[n..].fill(0);
(st.buf.as_ptr(), st.buf.len())
};
unsafe { ResetEvent(st.io.event.raw()) };
let ol = st.io.ol_mut();
let res = unsafe {
WriteFile(
self.handle.raw(),
ptr,
len as u32,
core::ptr::null_mut(),
ol,
)
};
if res == 0 {
let err = unsafe { GetLastError() };
if err != ERROR_IO_PENDING {
return Err(Self::io_error("hid write", err));
}
}
let timeout = self.write_timeout_ms.load(Ordering::Relaxed);
let wait = unsafe { WaitForSingleObject(st.io.event.raw(), timeout) };
if wait != WAIT_OBJECT_0 {
unsafe {
CancelIoEx(self.handle.raw(), ol);
let mut n = 0u32;
GetOverlappedResult(self.handle.raw(), ol, &mut n, 1);
}
return if wait == WAIT_TIMEOUT {
Err(HidError::backend(format!(
"hid write timed out after {timeout} ms"
)))
} else {
Err(HidError::last_os_error("WaitForSingleObject on write"))
};
}
let mut written = 0u32;
if unsafe { GetOverlappedResult(self.handle.raw(), ol, &mut written, 0) } == 0 {
return Err(Self::io_error("hid write", unsafe { GetLastError() }));
}
Ok(data.len())
}
fn read_async<'a>(&'a self, buf: &'a mut [u8]) -> ReadAsync<'a> {
ReadAsync { dev: self, buf }
}
fn send_feature_report(&self, data: &[u8]) -> HidResult<()> {
if data.is_empty() {
return Err(HidError::InvalidData {
message: "feature report must contain a report ID byte".into(),
});
}
let padded;
let buf: &[u8] = if data.len() >= self.feature_report_len as usize {
data
} else {
let mut v = vec![0u8; self.feature_report_len as usize];
v[..data.len()].copy_from_slice(data);
padded = v;
&padded
};
let ok =
unsafe { HidD_SetFeature(self.handle.raw(), buf.as_ptr().cast(), buf.len() as u32) };
if !ok {
return Err(Self::io_error("HidD_SetFeature", unsafe { GetLastError() }));
}
Ok(())
}
fn get_feature_report(&self, buf: &mut [u8]) -> HidResult<usize> {
self.ioctl_get_report(IOCTL_HID_GET_FEATURE, buf, "IOCTL_HID_GET_FEATURE")
}
fn get_input_report(&self, buf: &mut [u8]) -> HidResult<usize> {
match self.ioctl_get_report(
IOCTL_HID_GET_INPUT_REPORT,
buf,
"IOCTL_HID_GET_INPUT_REPORT",
) {
Err(HidError::Io { source, .. })
if matches!(
source.raw_os_error(),
Some(e) if e == ERROR_INVALID_FUNCTION as i32
|| e == ERROR_NOT_SUPPORTED as i32
) =>
{
let ok = unsafe {
HidD_GetInputReport(
self.handle.raw(),
buf.as_mut_ptr().cast(),
buf.len() as u32,
)
};
if !ok {
return Err(Self::io_error("HidD_GetInputReport", unsafe {
GetLastError()
}));
}
Ok(buf.len())
}
other => other,
}
}
fn get_manufacturer_string(&self) -> HidResult<Option<String>> {
Ok(self.info.manufacturer_string.clone())
}
fn get_product_string(&self) -> HidResult<Option<String>> {
Ok(self.info.product_string.clone())
}
fn get_serial_number_string(&self) -> HidResult<Option<String>> {
Ok(self.info.serial_number.clone())
}
fn get_indexed_string(&self, index: u32) -> HidResult<Option<String>> {
let mut buf = [0u16; MAX_STRING_WCHARS];
let ok = unsafe {
HidD_GetIndexedString(
self.handle.raw(),
index,
buf.as_mut_ptr().cast(),
(buf.len() * 2) as u32,
)
};
if !ok {
return Err(Self::io_error("HidD_GetIndexedString", unsafe {
GetLastError()
}));
}
Ok(utf16_until_nul(&buf))
}
fn get_report_descriptor(&self, buf: &mut [u8]) -> HidResult<usize> {
let bytes = self.reconstruct_descriptor()?;
let len = bytes.len().min(buf.len());
buf[..len].copy_from_slice(&bytes[..len]);
Ok(len)
}
fn get_device_info(&self) -> HidResult<DeviceInfo> {
Ok(self.info.clone())
}
}
impl Drop for WinDevice {
fn drop(&mut self) {
let st = self.read.get_mut().unwrap_or_else(|e| e.into_inner());
if !st.wait.0.is_null() {
unsafe { UnregisterWaitEx(st.wait.0, INVALID_HANDLE_VALUE) };
st.wait.0 = core::ptr::null_mut();
}
if st.pending {
unsafe {
let ol = st.io.ol_mut();
CancelIoEx(self.handle.raw(), ol);
let mut n = 0u32;
GetOverlappedResult(self.handle.raw(), ol, &mut n, 1);
}
}
unsafe { drop(Arc::from_raw(Arc::as_ptr(&self.wake))) };
}
}
pub struct ReadAsync<'a> {
dev: &'a WinDevice,
buf: &'a mut [u8],
}
impl Future for ReadAsync<'_> {
type Output = HidResult<usize>;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let this = self.get_mut();
this.dev.poll_read(this.buf, cx)
}
}
#[derive(Clone, Copy)]
struct Common {
usage_page: u16,
range: Option<(u16, u16)>,
usage: u16,
report_count: u16,
flags: MainFlags,
}
#[derive(Clone, Copy)]
enum FieldKind {
Button,
Value {
bit_size: u16,
logical_min: i32,
logical_max: i32,
physical_min: i32,
physical_max: i32,
unit: u32,
unit_exp: i32,
},
Padding { bits: u32 },
}
struct Field {
kind: ReportKind,
report_id: u8,
link: u16,
order: usize,
common: Common,
data: FieldKind,
}
impl Field {
fn layout(&self) -> (u32, u32) {
let Common {
range,
report_count,
flags,
..
} = self.common;
match self.data {
FieldKind::Button => {
if flags.is_variable() {
let count = match range {
Some((lo, hi)) => u32::from(hi.saturating_sub(lo)) + 1,
None => report_count.max(1) as u32,
};
(1, count)
} else {
let max = range.map(|(_, hi)| hi).unwrap_or(1);
(if max > 255 { 16 } else { 8 }, report_count.max(1) as u32)
}
}
FieldKind::Value { bit_size, .. } => {
let mut count = report_count.max(1) as u32;
if let Some((lo, hi)) = range {
count = count.max(u32::from(hi.saturating_sub(lo)) + 1);
}
(bit_size as u32, count)
}
FieldKind::Padding { bits } => (bits, 1),
}
}
}
macro_rules! caps_common {
($c:expr) => {{
let c = $c;
let (range, usage) = unsafe {
if c.IsRange {
(
Some((c.Anonymous.Range.UsageMin, c.Anonymous.Range.UsageMax)),
0,
)
} else {
(None, c.Anonymous.NotRange.Usage)
}
};
Common {
usage_page: c.UsagePage,
range,
usage,
report_count: c.ReportCount,
flags: MainFlags(c.BitField as u32),
}
}};
}
macro_rules! read_caps {
($get:ident, $ty:ty, $report_type:expr, $declared:expr, $pp:expr) => {{
let declared = $declared;
if declared == 0 {
Vec::new()
} else {
let mut caps = vec![<$ty>::default(); declared as usize];
let mut len = declared;
let status = unsafe { $get($report_type, caps.as_mut_ptr(), &mut len, $pp) };
if status == HIDP_STATUS_SUCCESS {
caps.truncate(len as usize);
caps
} else {
Vec::new()
}
}
}};
}
fn collect_fields(pp: &PreparsedData, caps: &HIDP_CAPS) -> Vec<Field> {
let per_kind: [(ReportKind, HIDP_REPORT_TYPE, u16, u16); 3] = [
(
ReportKind::Input,
HidP_Input,
caps.NumberInputButtonCaps,
caps.NumberInputValueCaps,
),
(
ReportKind::Output,
HidP_Output,
caps.NumberOutputButtonCaps,
caps.NumberOutputValueCaps,
),
(
ReportKind::Feature,
HidP_Feature,
caps.NumberFeatureButtonCaps,
caps.NumberFeatureValueCaps,
),
];
let mut fields = Vec::new();
for (kind, report_type, n_buttons, n_values) in per_kind {
let buttons = read_caps!(
HidP_GetButtonCaps,
HIDP_BUTTON_CAPS,
report_type,
n_buttons,
pp.0
);
for c in &buttons {
if c.IsAlias {
continue;
}
fields.push(Field {
kind,
report_id: c.ReportID,
link: c.LinkCollection,
order: fields.len(),
common: caps_common!(c),
data: FieldKind::Button,
});
}
let values = read_caps!(
HidP_GetValueCaps,
HIDP_VALUE_CAPS,
report_type,
n_values,
pp.0
);
for c in &values {
if c.IsAlias {
continue;
}
fields.push(Field {
kind,
report_id: c.ReportID,
link: c.LinkCollection,
order: fields.len(),
common: caps_common!(c),
data: FieldKind::Value {
bit_size: c.BitSize,
logical_min: c.LogicalMin,
logical_max: c.LogicalMax,
physical_min: c.PhysicalMin,
physical_max: c.PhysicalMax,
unit: c.Units,
unit_exp: c.UnitsExp as i32,
},
});
}
}
fields
}
fn synthesize_padding(caps: &HIDP_CAPS, fields: &mut Vec<Field>) {
for (kind, report_len) in [
(ReportKind::Input, caps.InputReportByteLength),
(ReportKind::Output, caps.OutputReportByteLength),
(ReportKind::Feature, caps.FeatureReportByteLength),
] {
if report_len < 2 {
continue;
}
let mut ids = std::collections::BTreeSet::new();
let mut bits = 0u64;
for f in fields.iter().filter(|f| f.kind == kind) {
ids.insert(f.report_id);
let (size, count) = f.layout();
bits += u64::from(size) * u64::from(count);
}
if ids.len() != 1 {
continue;
}
let declared = (u64::from(report_len) - 1) * 8;
if bits < declared {
fields.push(Field {
kind,
report_id: *ids.first().expect("one id"),
link: 0,
order: usize::MAX, common: Common {
usage_page: 0,
range: None,
usage: 0,
report_count: 1,
flags: MainFlags::CONSTANT,
},
data: FieldKind::Padding {
bits: (declared - bits) as u32,
},
});
}
}
}
fn emit_collection(
b: &mut DescriptorBuilder,
nodes: &[HIDP_LINK_COLLECTION_NODE],
by_node: &[Vec<usize>],
fields: &[Field],
index: usize,
depth: usize,
) -> HidResult<()> {
if depth > 64 {
return Err(HidError::backend("link collection tree too deep"));
}
let node = nodes[index];
let (page, usage) = (node.LinkUsagePage, node.LinkUsage);
let kind = CollectionKind::from_value((node._bitfield & 0xFF) as u8);
b.usage_page(page);
b.usage(usage as u32);
b.collection(kind);
for &i in &by_node[index] {
emit_field(b, &fields[i]);
}
let mut child = node.FirstChild as usize;
let mut guard = 0;
while child != 0 && child < nodes.len() && guard < nodes.len() {
emit_collection(b, nodes, by_node, fields, child, depth + 1)?;
child = nodes[child].NextSibling as usize;
guard += 1;
}
b.end_collection();
Ok(())
}
fn emit_field(b: &mut DescriptorBuilder, field: &Field) {
let (size, count) = field.layout();
if let FieldKind::Padding { .. } = field.data {
if field.report_id != 0 {
b.report_id(field.report_id);
}
b.report_size(size);
b.report_count(count);
emit_main(b, field.kind, MainFlags::CONSTANT);
return;
}
let Common {
usage_page,
range,
usage,
flags,
..
} = field.common;
b.usage_page(usage_page);
if field.report_id != 0 {
b.report_id(field.report_id);
}
match range {
Some((lo, hi)) => {
b.usage_minimum(lo as u32);
b.usage_maximum(hi as u32);
}
None => {
b.usage(usage as u32);
}
}
match field.data {
FieldKind::Button => {
b.logical_minimum(0);
if flags.is_variable() {
b.logical_maximum(1);
} else {
b.logical_maximum(range.map(|(_, hi)| hi).unwrap_or(1) as i32);
}
b.physical_minimum(0);
b.physical_maximum(0);
b.unit_exponent(0);
b.unit(0);
}
FieldKind::Value {
logical_min,
logical_max,
physical_min,
physical_max,
unit,
unit_exp,
..
} => {
b.logical_minimum(logical_min);
b.logical_maximum(logical_max);
b.physical_minimum(physical_min);
b.physical_maximum(physical_max);
b.unit_exponent(unit_exp);
b.unit(unit);
}
FieldKind::Padding { .. } => {}
}
b.report_size(size);
b.report_count(count);
emit_main(b, field.kind, flags);
}
fn emit_main(b: &mut DescriptorBuilder, kind: ReportKind, flags: MainFlags) {
match kind {
ReportKind::Input => b.input(flags),
ReportKind::Output => b.output(flags),
ReportKind::Feature => b.feature(flags),
};
}
impl core::fmt::Debug for WinApi {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("WinApi").finish()
}
}
impl core::fmt::Debug for WinDevice {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("WinDevice").finish_non_exhaustive()
}
}
impl core::fmt::Debug for ReadAsync<'_> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("ReadAsync").finish_non_exhaustive()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parses_interface_number_from_path() {
let path = r"\\?\hid#vid_046d&pid_c216&mi_01#7&2d80a3c5&0&0000#{4d1e55b2-f16f-11cf-88cb-001111000030}";
assert_eq!(interface_number_from_path(path, BusType::Usb), 1);
let upper =
r"\\?\HID#VID_046D&PID_C216&MI_0A#8&dead&0&0000#{4d1e55b2-f16f-11cf-88cb-001111000030}";
assert_eq!(interface_number_from_path(upper, BusType::Usb), 0x0A);
let no_mi = r"\\?\hid#vid_0603&pid_1020&col01#7&2f51268c&0&0000#{4d1e55b2-f16f-11cf-88cb-001111000030}";
assert_eq!(interface_number_from_path(no_mi, BusType::Usb), 0);
assert_eq!(interface_number_from_path(no_mi, BusType::Bluetooth), -1);
}
#[test]
fn converts_utf16_strings() {
let buf: Vec<u16> = "Logitech".encode_utf16().chain([0, 0x41]).collect();
assert_eq!(utf16_until_nul(&buf).as_deref(), Some("Logitech"));
assert_eq!(utf16_until_nul(&[0u16; 4]), None);
assert_eq!(utf16_until_nul(&[]), None);
let raw: Vec<u16> = "ab".encode_utf16().collect();
assert_eq!(utf16_until_nul(&raw).as_deref(), Some("ab"));
}
#[test]
fn classifies_bus_types() {
assert_eq!(classify_bus("USB", r"USB\VID_046D&PID_C216"), BusType::Usb);
assert_eq!(classify_bus("BTHENUM", ""), BusType::Bluetooth);
assert_eq!(classify_bus("BTHLEDEVICE", ""), BusType::Bluetooth);
assert_eq!(classify_bus("ACPI", r"ACPI\PNP0C50 PNP0C50"), BusType::I2c);
assert_eq!(classify_bus("ACPI", r"ACPI\PNP0C51"), BusType::Spi);
assert_eq!(classify_bus("ROOT", ""), BusType::Unknown);
}
#[test]
fn guid_bytes_use_little_endian_field_layout() {
let guid = GUID::from_u128(0x00112233_4455_6677_8899_aabbccddeeff);
assert_eq!(
guid_to_bytes(&guid),
[
0x33, 0x22, 0x11, 0x00, 0x55, 0x44, 0x77, 0x66, 0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF, ]
);
}
#[test]
fn get_report_len_matches_report_id_convention() {
assert_eq!(WinDevice::get_report_len(0x05, 8, 64), 8);
assert_eq!(WinDevice::get_report_len(0, 8, 64), 9);
assert_eq!(WinDevice::get_report_len(0, 64, 64), 64);
assert_eq!(WinDevice::get_report_len(0x05, 64, 64), 64);
}
#[test]
fn hid_ioctls_match_ddk_values() {
assert_eq!(IOCTL_HID_GET_FEATURE, (0x0B << 16) | (100 << 2) | 2);
assert_eq!(IOCTL_HID_GET_INPUT_REPORT, (0x0B << 16) | (104 << 2) | 2);
}
#[test]
fn padding_fills_single_report_devices() {
let caps = HIDP_CAPS {
InputReportByteLength: 9, ..Default::default()
};
let mut fields = vec![Field {
kind: ReportKind::Input,
report_id: 0,
link: 0,
order: 0,
common: Common {
usage_page: 1,
range: None,
usage: 0x30,
report_count: 2,
flags: MainFlags::VARIABLE,
},
data: FieldKind::Value {
bit_size: 8,
logical_min: 0,
logical_max: 255,
physical_min: 0,
physical_max: 0,
unit: 0,
unit_exp: 0,
},
}];
synthesize_padding(&caps, &mut fields);
assert_eq!(fields.len(), 2);
match fields[1].data {
FieldKind::Padding { bits } => assert_eq!(bits, 64 - 16),
_ => panic!("expected padding"),
}
}
}