async-hid 0.5.0

A async library for interacting with HID devices
Documentation
use std::ffi::CStr;
use std::mem::transmute;

use objc2_core_foundation::{kCFAllocatorNull, CFArray, CFDictionary, CFNumber, CFRetained, CFString, CFStringBuiltInEncodings};
use objc2_io_kit::{
    kIOHIDDeviceUsageKey, kIOHIDDeviceUsagePageKey, kIOHIDDeviceUsagePairsKey, kIOHIDPrimaryUsageKey, kIOHIDPrimaryUsagePageKey, kIOHIDProductIDKey,
    kIOHIDProductKey, kIOHIDSerialNumberKey, kIOHIDVendorIDKey, kIOReturnSuccess, IOHIDDevice, IORegistryEntryGetRegistryEntryID,
};

use crate::{ensure, DeviceId, DeviceInfo, HidError, HidResult};

pub fn get_device_info(device: &IOHIDDevice) -> HidResult<Vec<DeviceInfo>> {
    let name = device
        .property(&property_key(kIOHIDProductKey))
        .and_then(|p| p.downcast_ref::<CFString>().map(|p| p.to_string()))
        .unwrap_or_default();

    let product_id = device
        .property(&property_key(kIOHIDProductIDKey))
        .ok_or(HidError::message("Failed to get the product id"))?
        .downcast_ref::<CFNumber>()
        .and_then(CFNumber::as_i32)
        .unwrap() as u16;

    let vendor_id = device
        .property(&property_key(kIOHIDVendorIDKey))
        .ok_or(HidError::message("Failed to get the vendor id"))?
        .downcast_ref::<CFNumber>()
        .and_then(CFNumber::as_i32)
        .unwrap() as u16;

    let primary_usage_page = device
        .property(&property_key(kIOHIDPrimaryUsagePageKey))
        .ok_or(HidError::message("Failed to get the primary usage page"))?
        .downcast_ref::<CFNumber>()
        .and_then(CFNumber::as_i32)
        .unwrap() as u16;

    let primary_usage_id = device
        .property(&property_key(kIOHIDPrimaryUsageKey))
        .ok_or(HidError::message("Failed to get the primary usage id"))?
        .downcast_ref::<CFNumber>()
        .and_then(CFNumber::as_i32)
        .unwrap() as u16;

    let serial_number = device
        .property(&property_key(kIOHIDSerialNumberKey))
        .and_then(|p| p.downcast_ref::<CFString>().map(|p| p.to_string()));

    let primary_info = DeviceInfo {
        id: get_device_id(device)?,
        name,
        product_id,
        vendor_id,
        usage_id: primary_usage_id,
        usage_page: primary_usage_page,
        serial_number,
    };

    let mut result = vec![primary_info.clone()];
    result.extend(unsafe {
        device
            .property(&property_key(kIOHIDDeviceUsagePairsKey))
            .iter()
            .flat_map(|p| {
                p.downcast_ref::<CFArray>()
                    .map(|p| transmute::<&CFArray, &CFArray<CFDictionary<CFString, CFNumber>>>(p))
                    .unwrap()
                    .iter()
                    .map(|dict| {
                        let usage = dict
                            .get(&property_key(kIOHIDDeviceUsageKey))
                            .and_then(|p| p.as_i32())
                            .unwrap() as u16;
                        let usage_page = dict
                            .get(&property_key(kIOHIDDeviceUsagePageKey))
                            .and_then(|p| p.as_i32())
                            .unwrap() as u16;
                        (usage, usage_page)
                    })
            })
            .filter(|(usage, usage_page)| (*usage_page != primary_usage_page) || (*usage != primary_usage_id))
            .map(move |(usage, usage_page)| DeviceInfo {
                usage_id: usage,
                usage_page,
                ..primary_info.clone()
            })
    });

    Ok(result)
}

pub fn property_key(key: &'static CStr) -> CFRetained<CFString> {
    unsafe { CFString::with_c_string_no_copy(None, key.as_ptr(), CFStringBuiltInEncodings::EncodingUTF8.0, kCFAllocatorNull).unwrap() }
}

pub fn get_device_id(device: &IOHIDDevice) -> HidResult<DeviceId> {
    let mut id = 0;
    let port = device.service();
    ensure!(port != 0, HidError::message("Failed to get mach port"));
    ensure!(
        unsafe { IORegistryEntryGetRegistryEntryID(port, &mut id) } == kIOReturnSuccess,
        HidError::message("Failed to retrieve entry id")
    );
    Ok(DeviceId::RegistryEntryId(id))
}