use core::fmt;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
#[non_exhaustive]
pub enum BusType {
#[default]
Unknown,
Usb,
Bluetooth,
I2c,
Spi,
}
impl fmt::Display for BusType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let name = match self {
BusType::Unknown => "Unknown",
BusType::Usb => "USB",
BusType::Bluetooth => "Bluetooth",
BusType::I2c => "I2C",
BusType::Spi => "SPI",
};
f.write_str(name)
}
}
#[derive(Debug, Clone, Default)]
pub struct DeviceInfo {
pub(crate) path: String,
pub(crate) vendor_id: u16,
pub(crate) product_id: u16,
pub(crate) serial_number: Option<String>,
pub(crate) release_number: u16,
pub(crate) manufacturer_string: Option<String>,
pub(crate) product_string: Option<String>,
pub(crate) usage_page: u16,
pub(crate) usage: u16,
pub(crate) interface_number: i32,
pub(crate) bus_type: BusType,
}
impl DeviceInfo {
pub fn path(&self) -> &str {
&self.path
}
pub fn vendor_id(&self) -> u16 {
self.vendor_id
}
pub fn product_id(&self) -> u16 {
self.product_id
}
pub fn serial_number(&self) -> Option<&str> {
self.serial_number.as_deref()
}
pub fn release_number(&self) -> u16 {
self.release_number
}
pub fn manufacturer_string(&self) -> Option<&str> {
self.manufacturer_string.as_deref()
}
pub fn product_string(&self) -> Option<&str> {
self.product_string.as_deref()
}
pub fn usage_page(&self) -> u16 {
self.usage_page
}
pub fn usage(&self) -> u16 {
self.usage
}
pub fn interface_number(&self) -> i32 {
self.interface_number
}
pub fn bus_type(&self) -> BusType {
self.bus_type
}
}
#[cfg(not(target_arch = "wasm32"))]
impl DeviceInfo {
pub(crate) fn matches(&self, vendor_id: u16, product_id: u16) -> bool {
(vendor_id == 0 || self.vendor_id == vendor_id)
&& (product_id == 0 || self.product_id == product_id)
}
#[cfg(any(feature = "nusb", target_os = "linux", target_os = "macos"))]
pub(crate) fn per_usage(self, usages: &[(u16, u16)]) -> Vec<DeviceInfo> {
let Some((last, rest)) = usages.split_last() else {
return vec![self];
};
let mut out = Vec::with_capacity(usages.len());
for &(usage_page, usage) in rest {
out.push(DeviceInfo {
usage_page,
usage,
..self.clone()
});
}
out.push(DeviceInfo {
usage_page: last.0,
usage: last.1,
..self
});
out
}
}
#[cfg(all(test, not(target_arch = "wasm32")))]
mod tests {
use super::*;
fn info(vendor_id: u16, product_id: u16) -> DeviceInfo {
DeviceInfo {
vendor_id,
product_id,
path: "p".into(),
..Default::default()
}
}
#[test]
fn zero_matches_any_id() {
let dev = info(0x046d, 0xc216);
assert!(dev.matches(0, 0));
assert!(dev.matches(0x046d, 0));
assert!(dev.matches(0, 0xc216));
assert!(dev.matches(0x046d, 0xc216));
assert!(!dev.matches(0x046e, 0));
assert!(!dev.matches(0, 0xc217));
}
#[cfg(any(feature = "nusb", target_os = "linux", target_os = "macos"))]
#[test]
fn no_usages_yields_the_entry_unchanged() {
let out = info(1, 2).per_usage(&[]);
assert_eq!(out.len(), 1);
assert_eq!((out[0].usage_page(), out[0].usage()), (0, 0));
}
#[cfg(any(feature = "nusb", target_os = "linux", target_os = "macos"))]
#[test]
fn one_entry_per_usage_pair_sharing_the_rest() {
let out = info(1, 2).per_usage(&[(0x01, 0x06), (0x0c, 0x01), (0xff00, 0x01)]);
assert_eq!(out.len(), 3);
assert_eq!(
out.iter()
.map(|d| (d.usage_page(), d.usage()))
.collect::<Vec<_>>(),
vec![(0x01, 0x06), (0x0c, 0x01), (0xff00, 0x01)]
);
assert!(out.iter().all(|d| d.vendor_id() == 1 && d.path() == "p"));
}
}