hidapi-2.6.3/.cargo_vcs_info.json0000644000000001360000000000100123020ustar { "git": { "sha1": "ef8ee38edf81d8e48267e6c4f79cda57d8ca225a" }, "path_in_vcs": "" }hidapi-2.6.3/Cargo.lock0000644000000105340000000000100102600ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 3 [[package]] name = "bitflags" version = "2.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf" [[package]] name = "cc" version = "1.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" [[package]] name = "cfg-if" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "hermit-abi" version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5d3d0e0f38255e7fa3cf31335b3a56f05febd18025f4db5ef7a0cfb4f8da651f" [[package]] name = "hidapi" version = "2.6.3" dependencies = [ "cc", "cfg-if", "libc", "nix", "pkg-config", "udev", "windows-sys", ] [[package]] name = "io-lifetimes" version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" dependencies = [ "hermit-abi", "libc", "windows-sys", ] [[package]] name = "libc" version = "0.2.152" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13e3bf6590cbc649f4d1a3eefc9d5d6eb746f5200ffb04e5e142700b8faa56e7" [[package]] name = "libudev-sys" version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c8469b4a23b962c1396b9b451dda50ef5b283e8dd309d69033475fa9b334324" dependencies = [ "libc", "pkg-config", ] [[package]] name = "nix" version = "0.27.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2eb04e9c688eff1c89d72b407f168cf79bb9e867a9d3323ed6c01519eb9cc053" dependencies = [ "bitflags", "cfg-if", "libc", ] [[package]] name = "pkg-config" version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160" [[package]] name = "udev" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "50051c6e22be28ee6f217d50014f3bc29e81c20dc66ff7ca0d5c5226e1dcc5a1" dependencies = [ "io-lifetimes", "libc", "libudev-sys", "pkg-config", ] [[package]] name = "windows-sys" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" dependencies = [ "windows-targets", ] [[package]] name = "windows-targets" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" dependencies = [ "windows_aarch64_gnullvm", "windows_aarch64_msvc", "windows_i686_gnu", "windows_i686_msvc", "windows_x86_64_gnu", "windows_x86_64_gnullvm", "windows_x86_64_msvc", ] [[package]] name = "windows_aarch64_gnullvm" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" [[package]] name = "windows_aarch64_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" [[package]] name = "windows_i686_gnu" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" [[package]] name = "windows_i686_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" [[package]] name = "windows_x86_64_gnu" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" [[package]] name = "windows_x86_64_gnullvm" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" [[package]] name = "windows_x86_64_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" hidapi-2.6.3/Cargo.toml0000644000000056700000000000100103100ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2021" name = "hidapi" version = "2.6.3" authors = [ "Roland Ruckerbauer ", "Osspial ", "Artyom Pavlov ", "mberndt123", "niklasad1", "Stefan Kerkmann", ] build = "build.rs" links = "hidapi" include = [ "README.md", "LICENSE.txt", "build.rs", "/src", "/etc/hidapi/CMakeLists.txt", "/etc/hidapi/LICENSE*", "/etc/hidapi/VERSION", "/etc/hidapi/hidapi", "/etc/hidapi/libusb", "/etc/hidapi/src", "/etc/hidapi/udev", "/etc/hidapi/linux/CMakeLists.txt", "/etc/hidapi/linux/*.c", "/etc/hidapi/linux/*.h", "/etc/hidapi/mac/CMakeLists.txt", "/etc/hidapi/mac/*.c", "/etc/hidapi/mac/*.h", "/etc/hidapi/windows/CMakeLists.txt", "/etc/hidapi/windows/*.c", "/etc/hidapi/windows/*.h", ] autobins = false autoexamples = false autotests = false autobenches = false description = "Rust-y wrapper around hidapi" documentation = "https://docs.rs/hidapi" readme = "README.md" keywords = [ "hid", "api", "usb", "binding", "wrapper", ] license = "MIT" repository = "https://github.com/ruabmbua/hidapi-rs" [package.metadata.docs.rs] rustdoc-args = [ "--cfg", "docsrs", ] [lib] name = "hidapi" path = "src/lib.rs" [dependencies.cfg-if] version = "1" [dependencies.libc] version = "0.2" [build-dependencies.cc] version = "1.0" [build-dependencies.pkg-config] version = "0.3" [features] default = [ "linux-static-hidraw", "illumos-static-libusb", ] illumos-shared-libusb = [] illumos-static-libusb = [] linux-native = [ "dep:udev", "dep:nix", ] linux-shared-hidraw = [] linux-shared-libusb = [] linux-static-hidraw = [] linux-static-libusb = [] macos-shared-device = [] windows-native = [ "windows-sys/Win32_Devices_DeviceAndDriverInstallation", "windows-sys/Win32_Devices_HumanInterfaceDevice", "windows-sys/Win32_Devices_Properties", "windows-sys/Win32_Security", "windows-sys/Win32_Storage_EnhancedStorage", "windows-sys/Win32_Storage_FileSystem", "windows-sys/Win32_System_IO", "windows-sys/Win32_System_Threading", "windows-sys/Win32_UI_Shell_PropertiesSystem", ] [target.'cfg(target_os = "linux")'.dependencies.nix] version = "0.27" features = [ "fs", "ioctl", "poll", ] optional = true [target.'cfg(target_os = "linux")'.dependencies.udev] version = "0.8" optional = true [target."cfg(windows)".dependencies.windows-sys] version = "0.48" features = ["Win32_Foundation"] hidapi-2.6.3/Cargo.toml.orig0000644000000043130000000000100112400ustar [package] name = "hidapi" version = "2.6.3" authors = [ "Roland Ruckerbauer ", "Osspial ", "Artyom Pavlov ", "mberndt123", "niklasad1", "Stefan Kerkmann" ] repository = "https://github.com/ruabmbua/hidapi-rs" description = "Rust-y wrapper around hidapi" license = "MIT" keywords = ["hid", "api", "usb", "binding", "wrapper"] build = "build.rs" links = "hidapi" documentation = "https://docs.rs/hidapi" edition = "2021" include = [ "README.md", "LICENSE.txt", "build.rs", "/src", "/etc/hidapi/CMakeLists.txt", "/etc/hidapi/LICENSE*", "/etc/hidapi/VERSION", "/etc/hidapi/hidapi", "/etc/hidapi/libusb", "/etc/hidapi/src", "/etc/hidapi/udev", # Platform support files "/etc/hidapi/linux/CMakeLists.txt", "/etc/hidapi/linux/*.c", "/etc/hidapi/linux/*.h", "/etc/hidapi/mac/CMakeLists.txt", "/etc/hidapi/mac/*.c", "/etc/hidapi/mac/*.h", "/etc/hidapi/windows/CMakeLists.txt", "/etc/hidapi/windows/*.c", "/etc/hidapi/windows/*.h", ] [features] default = ["linux-static-hidraw", "illumos-static-libusb"] linux-static-libusb = [] linux-static-hidraw = [] linux-shared-libusb = [] linux-shared-hidraw = [] linux-native = ["dep:udev", "dep:nix"] illumos-static-libusb = [] illumos-shared-libusb = [] macos-shared-device = [] windows-native = [ "windows-sys/Win32_Devices_DeviceAndDriverInstallation", "windows-sys/Win32_Devices_HumanInterfaceDevice", "windows-sys/Win32_Devices_Properties", "windows-sys/Win32_Security", "windows-sys/Win32_Storage_EnhancedStorage", "windows-sys/Win32_Storage_FileSystem", "windows-sys/Win32_System_IO", "windows-sys/Win32_System_Threading", "windows-sys/Win32_UI_Shell_PropertiesSystem" ] [dependencies] libc = "0.2" cfg-if = "1" [target.'cfg(target_os = "linux")'.dependencies] udev = { version = "0.8", optional = true } nix = { version = "0.27", optional = true, features = ["fs", "ioctl", "poll"] } [target.'cfg(windows)'.dependencies] windows-sys = { version = "0.48", features = ["Win32_Foundation"] } [build-dependencies] cc = "1.0" pkg-config = "0.3" [package.metadata.docs.rs] rustdoc-args = ["--cfg", "docsrs"] hidapi-2.6.3/Cargo.toml.orig000064400000000000000000000043131046102023000137620ustar 00000000000000[package] name = "hidapi" version = "2.6.3" authors = [ "Roland Ruckerbauer ", "Osspial ", "Artyom Pavlov ", "mberndt123", "niklasad1", "Stefan Kerkmann" ] repository = "https://github.com/ruabmbua/hidapi-rs" description = "Rust-y wrapper around hidapi" license = "MIT" keywords = ["hid", "api", "usb", "binding", "wrapper"] build = "build.rs" links = "hidapi" documentation = "https://docs.rs/hidapi" edition = "2021" include = [ "README.md", "LICENSE.txt", "build.rs", "/src", "/etc/hidapi/CMakeLists.txt", "/etc/hidapi/LICENSE*", "/etc/hidapi/VERSION", "/etc/hidapi/hidapi", "/etc/hidapi/libusb", "/etc/hidapi/src", "/etc/hidapi/udev", # Platform support files "/etc/hidapi/linux/CMakeLists.txt", "/etc/hidapi/linux/*.c", "/etc/hidapi/linux/*.h", "/etc/hidapi/mac/CMakeLists.txt", "/etc/hidapi/mac/*.c", "/etc/hidapi/mac/*.h", "/etc/hidapi/windows/CMakeLists.txt", "/etc/hidapi/windows/*.c", "/etc/hidapi/windows/*.h", ] [features] default = ["linux-static-hidraw", "illumos-static-libusb"] linux-static-libusb = [] linux-static-hidraw = [] linux-shared-libusb = [] linux-shared-hidraw = [] linux-native = ["dep:udev", "dep:nix"] illumos-static-libusb = [] illumos-shared-libusb = [] macos-shared-device = [] windows-native = [ "windows-sys/Win32_Devices_DeviceAndDriverInstallation", "windows-sys/Win32_Devices_HumanInterfaceDevice", "windows-sys/Win32_Devices_Properties", "windows-sys/Win32_Security", "windows-sys/Win32_Storage_EnhancedStorage", "windows-sys/Win32_Storage_FileSystem", "windows-sys/Win32_System_IO", "windows-sys/Win32_System_Threading", "windows-sys/Win32_UI_Shell_PropertiesSystem" ] [dependencies] libc = "0.2" cfg-if = "1" [target.'cfg(target_os = "linux")'.dependencies] udev = { version = "0.8", optional = true } nix = { version = "0.27", optional = true, features = ["fs", "ioctl", "poll"] } [target.'cfg(windows)'.dependencies] windows-sys = { version = "0.48", features = ["Win32_Foundation"] } [build-dependencies] cc = "1.0" pkg-config = "0.3" [package.metadata.docs.rs] rustdoc-args = ["--cfg", "docsrs"] hidapi-2.6.3/LICENSE.txt000064400000000000000000000020501046102023000127120ustar 00000000000000Copyright 2017 The hidapi-rs Developers Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. hidapi-2.6.3/README.md000064400000000000000000000026201046102023000123510ustar 00000000000000# hidapi [![Version](https://img.shields.io/crates/v/hidapi.svg)](https://crates.io/crates/hidapi) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://github.com/Osspial/hidapi-rs/blob/master/LICENSE.txt) [![Documentation](https://docs.rs/hidapi/badge.svg)](https://docs.rs/hidapi) [![Chat](https://img.shields.io/badge/discord-devroom-blue.svg)](https://discordapp.com/invite/3ahhJGN) This crate provides a rust abstraction over the features of the C library [hidapi](https://github.com/libusb/hidapi). Based off of [hidapi-rs](https://github.com/Osspial/hidapi-rs) by Osspial. # Usage This crate is on [crates.io](https://crates.io/crates/hidapi) and can be used by adding `hidapi` to the dependencies in your project's `Cargo.toml`. # Example ```rust extern crate hidapi; let api = hidapi::HidApi::new().unwrap(); // Print out information about all connected devices for device in api.device_list() { println!("{:#?}", device); } // Connect to device using its VID and PID let (VID, PID) = (0x0123, 0x3456); let device = api.open(VID, PID).unwrap(); // Read data from device let mut buf = [0u8; 8]; let res = device.read(&mut buf[..]).unwrap(); println!("Read: {:?}", &buf[..res]); // Write data to device let buf = [0u8, 1, 2, 3, 4]; let res = device.write(&buf).unwrap(); println!("Wrote: {:?} byte(s)", res); ``` # Documentation Available at [docs.rs](https://docs.rs/hidapi). hidapi-2.6.3/build.rs000064400000000000000000000162131046102023000125420ustar 00000000000000// ************************************************************************** // Copyright (c) 2015 Roland Ruckerbauer All Rights Reserved. // // This file is part of hidapi_rust. // // hidapi_rust is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // hidapi_rust is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with hidapi_rust. If not, see . // ************************************************************************* extern crate cc; extern crate pkg_config; use std::env; fn main() { let target = env::var("TARGET").unwrap(); println!("cargo:rustc-check-cfg=cfg(hidapi)"); println!("cargo:rustc-check-cfg=cfg(libusb)"); if target.contains("linux") { compile_linux(); } else if target.contains("windows") { compile_windows(); } else if target.contains("darwin") { compile_macos(); } else if target.contains("freebsd") { compile_freebsd(); } else if target.contains("openbsd") { compile_openbsd(); } else if target.contains("illumos") { compile_illumos(); } else { panic!("Unsupported target os for hidapi-rs"); } } fn compile_linux() { // First check the features enabled for the crate. // Only one linux backend should be enabled at a time. let avail_backends: [(&'static str, Box); 5] = [ ( "LINUX_STATIC_HIDRAW", Box::new(|| { let mut config = cc::Build::new(); println!("cargo:rerun-if-changed=etc/hidapi/linux/hid.c"); config .file("etc/hidapi/linux/hid.c") .include("etc/hidapi/hidapi"); pkg_config::probe_library("libudev").expect("Unable to find libudev"); config.compile("libhidapi.a"); println!("cargo:rustc-cfg=hidapi"); }), ), ( "LINUX_STATIC_LIBUSB", Box::new(|| { let mut config = cc::Build::new(); println!("cargo:rerun-if-changed=etc/hidapi/linux/hid.c"); config .file("etc/hidapi/libusb/hid.c") .include("etc/hidapi/hidapi"); let lib = pkg_config::find_library("libusb-1.0").expect("Unable to find libusb-1.0"); for path in lib.include_paths { config.include( path.to_str() .expect("Failed to convert include path to str"), ); } config.compile("libhidapi.a"); println!("cargo:rustc-cfg=libusb"); println!("cargo:rustc-cfg=hidapi"); }), ), ( "LINUX_SHARED_HIDRAW", Box::new(|| { pkg_config::probe_library("hidapi-hidraw").expect("Unable to find hidapi-hidraw"); println!("cargo:rustc-cfg=hidapi"); }), ), ( "LINUX_SHARED_LIBUSB", Box::new(|| { pkg_config::probe_library("libusb-1.0").expect("Unable to find libusb-1.0"); pkg_config::probe_library("hidapi-libusb").expect("Unable to find hidapi-libusb"); println!("cargo:rustc-cfg=libusb"); println!("cargo:rustc-cfg=hidapi"); }), ), ( "LINUX_NATIVE", Box::new(|| { // The udev crate takes care of finding its library }), ), ]; let mut backends = avail_backends .iter() .filter(|f| env::var(format!("CARGO_FEATURE_{}", f.0)).is_ok()); if backends.clone().count() != 1 { panic!("Exactly one linux hidapi backend must be selected."); } // Build it! (backends.next().unwrap().1)(); } //#[cfg(all(feature = "shared-libusb", not(feature = "shared-hidraw")))] //fn compile_linux() { // //} // //#[cfg(all(feature = "shared-hidraw"))] //fn compile_linux() { // //} fn compile_freebsd() { pkg_config::probe_library("hidapi").expect("Unable to find hidapi"); println!("cargo:rustc-cfg=libusb"); println!("cargo:rustc-cfg=hidapi"); } fn compile_openbsd() { pkg_config::probe_library("hidapi-libusb").expect("Unable to find hidapi"); println!("cargo:rustc-cfg=libusb"); println!("cargo:rustc-cfg=hidapi"); } fn compile_illumos() { // First check the features enabled for the crate. // Only one illumos backend should be enabled at a time. let avail_backends: [(&'static str, Box); 2] = [ ( "ILLUMOS_STATIC_LIBUSB", Box::new(|| { let mut config = cc::Build::new(); config .file("etc/hidapi/libusb/hid.c") .include("etc/hidapi/hidapi"); let lib = pkg_config::find_library("libusb-1.0").expect("Unable to find libusb-1.0"); for path in lib.include_paths { config.include( path.to_str() .expect("Failed to convert include path to str"), ); } config.compile("libhidapi.a"); }), ), ( "ILLUMOS_SHARED_LIBUSB", Box::new(|| { pkg_config::probe_library("hidapi-libusb").expect("Unable to find hidapi-libusb"); }), ), ]; let mut backends = avail_backends .iter() .filter(|f| env::var(format!("CARGO_FEATURE_{}", f.0)).is_ok()); if backends.clone().count() != 1 { panic!("Exactly one illumos hidapi backend must be selected."); } // Build it! (backends.next().unwrap().1)(); println!("cargo:rustc-cfg=libusb"); println!("cargo:rustc-cfg=hidapi"); } fn compile_windows() { #[cfg(not(feature = "windows-native"))] { let linkage = env::var("CARGO_CFG_TARGET_FEATURE").unwrap_or_default(); let mut cc = cc::Build::new(); cc.file("etc/hidapi/windows/hid.c") .include("etc/hidapi/hidapi"); if linkage.contains("crt-static") { // https://doc.rust-lang.org/reference/linkage.html#static-and-dynamic-c-runtimes cc.static_crt(true); } cc.compile("libhidapi.a"); println!("cargo:rustc-link-lib=setupapi"); println!("cargo:rustc-cfg=hidapi"); } } fn compile_macos() { cc::Build::new() .file("etc/hidapi/mac/hid.c") .include("etc/hidapi/hidapi") .compile("libhidapi.a"); println!("cargo:rustc-cfg=hidapi"); println!("cargo:rustc-link-lib=framework=IOKit"); println!("cargo:rustc-link-lib=framework=CoreFoundation"); println!("cargo:rustc-link-lib=framework=AppKit") } hidapi-2.6.3/src/error.rs000064400000000000000000000050311046102023000133570ustar 00000000000000// ************************************************************************** // Copyright (c) 2018 Roland Ruckerbauer All Rights Reserved. // // This file is part of hidapi-rs, based on hidapi-rs by Osspial // ************************************************************************** use libc::wchar_t; use std::error::Error; use std::fmt::{Display, Formatter, Result}; use crate::DeviceInfo; #[derive(Debug)] pub enum HidError { HidApiError { message: String, }, HidApiErrorEmpty, FromWideCharError { wide_char: wchar_t, }, InitializationError, InvalidZeroSizeData, IncompleteSendError { sent: usize, all: usize, }, SetBlockingModeError { mode: &'static str, }, OpenHidDeviceWithDeviceInfoError { device_info: Box, }, /// An IO error or a system error that can be represented as such IoError { error: std::io::Error, }, } impl Display for HidError { fn fmt(&self, f: &mut Formatter<'_>) -> Result { match self { HidError::HidApiError { message } => write!(f, "hidapi error: {}", message), HidError::HidApiErrorEmpty => write!(f, "hidapi error: (could not get error message)"), HidError::FromWideCharError { wide_char } => { write!(f, "failed converting {:#X} to rust char", wide_char) } HidError::InitializationError => { write!(f, "Failed to initialize hidapi") } HidError::InvalidZeroSizeData => write!(f, "Invalid data: size can not be 0"), HidError::IncompleteSendError { sent, all } => write!( f, "Failed to send all data: only sent {} out of {} bytes", sent, all ), HidError::SetBlockingModeError { mode } => { write!(f, "Can not set blocking mode to '{}'", mode) } HidError::OpenHidDeviceWithDeviceInfoError { device_info } => { write!(f, "Can not open hid device with: {:?}", *device_info) } HidError::IoError { error } => { write!(f, "{error}") } } } } impl Error for HidError {} impl From for HidError { fn from(e: std::io::Error) -> Self { Self::IoError { error: e } } } #[cfg(all(feature = "linux-native", target_os = "linux"))] impl From for HidError { fn from(e: nix::errno::Errno) -> Self { Self::IoError { error: e.into() } } } hidapi-2.6.3/src/ffi.rs000064400000000000000000000101701046102023000127720ustar 00000000000000#![allow(unused_imports, dead_code)] /// ************************************************************************** /// Copyright (c) 2015 Osspial All Rights Reserved. /// /// This file is part of hidapi-rs, based on hidapi_rust by Roland Ruckerbauer. /// ************************************************************************* // For documentation look at the corresponding C header file hidapi.h use libc::{c_char, c_int, c_uchar, c_ushort, c_void, intptr_t, size_t, wchar_t}; type HidBusType = crate::BusType; pub type HidDevice = c_void; type LibusbContext = c_void; #[repr(C)] pub struct HidDeviceInfo { pub path: *mut c_char, pub vendor_id: c_ushort, pub product_id: c_ushort, pub serial_number: *mut wchar_t, pub release_number: c_ushort, pub manufacturer_string: *mut wchar_t, pub product_string: *mut wchar_t, pub usage_page: c_ushort, pub usage: c_ushort, pub interface_number: c_int, pub next: *mut HidDeviceInfo, pub bus_type: HidBusType, } #[allow(dead_code)] extern "C" { #[cfg_attr(target_os = "openbsd", link_name = "hidapi_hid_init")] pub fn hid_init() -> c_int; pub fn hid_exit() -> c_int; pub fn hid_enumerate(vendor_id: c_ushort, product_id: c_ushort) -> *mut HidDeviceInfo; pub fn hid_free_enumeration(hid_device_info: *mut HidDeviceInfo); pub fn hid_open( vendor_id: c_ushort, product_id: c_ushort, serial_number: *const wchar_t, ) -> *mut HidDevice; pub fn hid_open_path(path: *const c_char) -> *mut HidDevice; #[cfg(libusb)] pub fn hid_libusb_wrap_sys_device(sys_dev: intptr_t, interface_num: c_int) -> *mut HidDevice; #[cfg(all(libusb, not(target_os = "freebsd")))] pub fn libusb_set_option(ctx: *mut LibusbContext, option: c_int); pub fn hid_write(device: *mut HidDevice, data: *const c_uchar, length: size_t) -> c_int; pub fn hid_read_timeout( device: *mut HidDevice, data: *mut c_uchar, length: size_t, milleseconds: c_int, ) -> c_int; pub fn hid_read(device: *mut HidDevice, data: *mut c_uchar, length: size_t) -> c_int; pub fn hid_set_nonblocking(device: *mut HidDevice, nonblock: c_int) -> c_int; pub fn hid_send_feature_report( device: *mut HidDevice, data: *const c_uchar, length: size_t, ) -> c_int; pub fn hid_get_feature_report( device: *mut HidDevice, data: *mut c_uchar, length: size_t, ) -> c_int; pub fn hid_close(device: *mut HidDevice); pub fn hid_get_manufacturer_string( device: *mut HidDevice, string: *mut wchar_t, maxlen: size_t, ) -> c_int; pub fn hid_get_product_string( device: *mut HidDevice, string: *mut wchar_t, maxlen: size_t, ) -> c_int; pub fn hid_get_serial_number_string( device: *mut HidDevice, string: *mut wchar_t, maxlen: size_t, ) -> c_int; pub fn hid_get_device_info(device: *mut HidDevice) -> *mut HidDeviceInfo; pub fn hid_get_indexed_string( device: *mut HidDevice, string_index: c_int, string: *mut wchar_t, maxlen: size_t, ) -> c_int; pub fn hid_get_report_descriptor( hid_device: *mut HidDevice, buf: *mut c_uchar, buf_size: size_t, ) -> c_int; pub fn hid_error(device: *mut HidDevice) -> *const wchar_t; } // For documentation look at the corresponding C header file hidapi_darwin.h #[cfg(target_os = "macos")] pub mod macos { use super::*; extern "C" { pub fn hid_darwin_get_location_id(device: *mut HidDevice, location_id: *mut u32) -> c_int; pub fn hid_darwin_set_open_exclusive(open_exclusive: c_int); pub fn hid_darwin_get_open_exclusive() -> c_int; pub fn hid_darwin_is_device_open_exclusive(device: *mut HidDevice) -> c_int; } } // For documentation look at the corresponding C header file hidapi_winapi.h #[cfg(target_os = "windows")] pub mod windows { use super::*; use windows_sys::core::GUID; extern "C" { pub fn hid_winapi_get_container_id( device: *mut HidDevice, container_id: *mut GUID, ) -> c_int; } } hidapi-2.6.3/src/hidapi/macos.rs000064400000000000000000000017201046102023000145670ustar 00000000000000//! The extra beahviour for macOS use super::HidDevice; use crate::{ffi, HidDeviceBackendBase, HidDeviceBackendMacos, HidResult}; impl HidDeviceBackendMacos for HidDevice { fn get_location_id(&self) -> HidResult { let mut location_id: u32 = 0; let res = unsafe { ffi::macos::hid_darwin_get_location_id(self._hid_device, &mut location_id as *mut u32) }; if res == -1 { match self.check_error() { Ok(err) => Err(err), Err(err) => Err(err), } } else { Ok(location_id) } } fn is_open_exclusive(&self) -> HidResult { let res = unsafe { ffi::macos::hid_darwin_is_device_open_exclusive(self._hid_device) }; if res == -1 { match self.check_error() { Ok(err) => Err(err), Err(err) => Err(err), } } else { Ok(res == 1) } } } hidapi-2.6.3/src/hidapi/windows.rs000064400000000000000000000013111046102023000151530ustar 00000000000000//! The extra behaviour for Windows use std::ptr::addr_of_mut; use windows_sys::core::GUID; use super::HidDevice; use crate::{ffi, HidDeviceBackendBase, HidDeviceBackendWindows, HidResult}; impl HidDeviceBackendWindows for HidDevice { fn get_container_id(&self) -> HidResult { let mut container_id: GUID = unsafe { std::mem::zeroed() }; let res = unsafe { ffi::windows::hid_winapi_get_container_id(self._hid_device, addr_of_mut!(container_id)) }; if res == -1 { match self.check_error() { Ok(err) => Err(err), Err(err) => Err(err), } } else { Ok(container_id) } } } hidapi-2.6.3/src/hidapi.rs000064400000000000000000000241151046102023000134700ustar 00000000000000//! The implementation which uses the C library to perform operations use std::{ ffi::CStr, fmt::{self, Debug}, }; use libc::{c_int, size_t, wchar_t}; use crate::{ffi, DeviceInfo, HidDeviceBackendBase, HidError, HidResult, WcharString}; #[cfg(target_os = "macos")] mod macos; #[cfg(target_os = "windows")] mod windows; const STRING_BUF_LEN: usize = 128; pub struct HidApiBackend; impl HidApiBackend { pub fn get_hid_device_info_vector(vid: u16, pid: u16) -> HidResult> { let mut device_vector = Vec::with_capacity(8); let enumeration = unsafe { ffi::hid_enumerate(vid, pid) }; { let mut current_device = enumeration; while !current_device.is_null() { device_vector.push(unsafe { conv_hid_device_info(current_device)? }); current_device = unsafe { (*current_device).next }; } } if !enumeration.is_null() { unsafe { ffi::hid_free_enumeration(enumeration) }; } Ok(device_vector) } pub fn open(vid: u16, pid: u16) -> HidResult { let device = unsafe { ffi::hid_open(vid, pid, std::ptr::null()) }; if device.is_null() { match Self::check_error() { Ok(err) => Err(err), Err(e) => Err(e), } } else { Ok(HidDevice::from_raw(device)) } } pub fn open_serial(vid: u16, pid: u16, sn: &str) -> HidResult { let mut chars = sn.chars().map(|c| c as wchar_t).collect::>(); chars.push(0 as wchar_t); let device = unsafe { ffi::hid_open(vid, pid, chars.as_ptr()) }; if device.is_null() { match Self::check_error() { Ok(err) => Err(err), Err(e) => Err(e), } } else { Ok(HidDevice::from_raw(device)) } } pub fn open_path(device_path: &CStr) -> HidResult { let device = unsafe { ffi::hid_open_path(device_path.as_ptr()) }; if device.is_null() { match Self::check_error() { Ok(err) => Err(err), Err(e) => Err(e), } } else { Ok(HidDevice::from_raw(device)) } } pub fn check_error() -> HidResult { Ok(HidError::HidApiError { message: unsafe { match wchar_to_string(ffi::hid_error(std::ptr::null_mut())) { WcharString::String(s) => s, _ => return Err(HidError::HidApiErrorEmpty), } }, }) } } /// Converts a pointer to a `*const wchar_t` to a WcharString. unsafe fn wchar_to_string(wstr: *const wchar_t) -> WcharString { if wstr.is_null() { return WcharString::None; } let mut char_vector: Vec = Vec::with_capacity(8); let mut raw_vector: Vec = Vec::with_capacity(8); let mut index: isize = 0; let mut invalid_char = false; let o = |i| *wstr.offset(i); while o(index) != 0 { use std::char; raw_vector.push(*wstr.offset(index)); if !invalid_char { if let Some(c) = char::from_u32(o(index) as u32) { char_vector.push(c); } else { invalid_char = true; } } index += 1; } if !invalid_char { WcharString::String(char_vector.into_iter().collect()) } else { WcharString::Raw(raw_vector) } } /// Convert the CFFI `HidDeviceInfo` struct to a native `HidDeviceInfo` struct pub unsafe fn conv_hid_device_info(src: *mut ffi::HidDeviceInfo) -> HidResult { Ok(DeviceInfo { path: CStr::from_ptr((*src).path).to_owned(), vendor_id: (*src).vendor_id, product_id: (*src).product_id, serial_number: wchar_to_string((*src).serial_number), release_number: (*src).release_number, manufacturer_string: wchar_to_string((*src).manufacturer_string), product_string: wchar_to_string((*src).product_string), usage_page: (*src).usage_page, usage: (*src).usage, interface_number: (*src).interface_number, bus_type: (*src).bus_type, }) } /// Object for accessing HID device pub struct HidDevice { _hid_device: *mut ffi::HidDevice, } impl HidDevice { pub fn from_raw(device: *mut ffi::HidDevice) -> Self { Self { _hid_device: device, } } } unsafe impl Send for HidDevice {} impl Debug for HidDevice { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("HidDevice").finish() } } impl Drop for HidDevice { fn drop(&mut self) { unsafe { ffi::hid_close(self._hid_device) } } } impl HidDevice { /// Check size returned by other methods, if it's equal to -1 check for /// error and return Error, otherwise return size as unsigned number fn check_size(&self, res: i32) -> HidResult { if res == -1 { match self.check_error() { Ok(err) => Err(err), Err(e) => Err(e), } } else { Ok(res as usize) } } } impl HidDeviceBackendBase for HidDevice { fn check_error(&self) -> HidResult { Ok(HidError::HidApiError { message: unsafe { match wchar_to_string(ffi::hid_error(self._hid_device)) { WcharString::String(s) => s, _ => return Err(HidError::HidApiErrorEmpty), } }, }) } fn write(&self, data: &[u8]) -> HidResult { if data.is_empty() { return Err(HidError::InvalidZeroSizeData); } let res = unsafe { ffi::hid_write(self._hid_device, data.as_ptr(), data.len() as size_t) }; self.check_size(res) } fn read(&self, buf: &mut [u8]) -> HidResult { let res = unsafe { ffi::hid_read(self._hid_device, buf.as_mut_ptr(), buf.len() as size_t) }; self.check_size(res) } fn read_timeout(&self, buf: &mut [u8], timeout: i32) -> HidResult { let res = unsafe { ffi::hid_read_timeout( self._hid_device, buf.as_mut_ptr(), buf.len() as size_t, timeout, ) }; self.check_size(res) } fn send_feature_report(&self, data: &[u8]) -> HidResult<()> { if data.is_empty() { return Err(HidError::InvalidZeroSizeData); } let res = unsafe { ffi::hid_send_feature_report(self._hid_device, data.as_ptr(), data.len() as size_t) }; let res = self.check_size(res)?; if res != data.len() { Err(HidError::IncompleteSendError { sent: res, all: data.len(), }) } else { Ok(()) } } /// Set the first byte of `buf` to the 'Report ID' of the report to be read. /// Upon return, the first byte will still contain the Report ID, and the /// report data will start in `buf[1]`. fn get_feature_report(&self, buf: &mut [u8]) -> HidResult { let res = unsafe { ffi::hid_get_feature_report(self._hid_device, buf.as_mut_ptr(), buf.len() as size_t) }; self.check_size(res) } fn set_blocking_mode(&self, blocking: bool) -> HidResult<()> { let res = unsafe { ffi::hid_set_nonblocking(self._hid_device, if blocking { 0i32 } else { 1i32 }) }; if res == -1 { Err(HidError::SetBlockingModeError { mode: match blocking { true => "blocking", false => "not blocking", }, }) } else { Ok(()) } } fn get_manufacturer_string(&self) -> HidResult> { let mut buf = [0 as wchar_t; STRING_BUF_LEN]; let res = unsafe { ffi::hid_get_manufacturer_string( self._hid_device, buf.as_mut_ptr(), STRING_BUF_LEN as size_t, ) }; let res = self.check_size(res)?; unsafe { Ok(wchar_to_string(buf[..res].as_ptr()).into()) } } fn get_product_string(&self) -> HidResult> { let mut buf = [0 as wchar_t; STRING_BUF_LEN]; let res = unsafe { ffi::hid_get_product_string( self._hid_device, buf.as_mut_ptr(), STRING_BUF_LEN as size_t, ) }; let res = self.check_size(res)?; unsafe { Ok(wchar_to_string(buf[..res].as_ptr()).into()) } } fn get_serial_number_string(&self) -> HidResult> { let mut buf = [0 as wchar_t; STRING_BUF_LEN]; let res = unsafe { ffi::hid_get_serial_number_string( self._hid_device, buf.as_mut_ptr(), STRING_BUF_LEN as size_t, ) }; let res = self.check_size(res)?; unsafe { Ok(wchar_to_string(buf[..res].as_ptr()).into()) } } fn get_indexed_string(&self, index: i32) -> HidResult> { let mut buf = [0 as wchar_t; STRING_BUF_LEN]; let res = unsafe { ffi::hid_get_indexed_string( self._hid_device, index as c_int, buf.as_mut_ptr(), STRING_BUF_LEN, ) }; let res = self.check_size(res)?; unsafe { Ok(wchar_to_string(buf[..res].as_ptr()).into()) } } fn get_device_info(&self) -> HidResult { let raw_device = unsafe { ffi::hid_get_device_info(self._hid_device) }; if raw_device.is_null() { match self.check_error() { Ok(err) | Err(err) => return Err(err), } } unsafe { conv_hid_device_info(raw_device) } } fn get_report_descriptor(&self, buf: &mut [u8]) -> HidResult { let res = unsafe { ffi::hid_get_report_descriptor(self._hid_device, buf.as_mut_ptr(), buf.len()) }; self.check_size(res) } } hidapi-2.6.3/src/lib.rs000064400000000000000000000551761046102023000130130ustar 00000000000000// ************************************************************************** // Copyright (c) 2015 Osspial All Rights Reserved. // // This file is part of hidapi-rs, based on hidapi_rust by Roland Ruckerbauer. // ************************************************************************* //! This crate provides a rust abstraction over the features of the C library //! hidapi by [signal11](https://github.com/libusb/hidapi). //! //! # Usage //! //! This crate is [on crates.io](https://crates.io/crates/hidapi) and can be //! used by adding `hidapi` to the dependencies in your project's `Cargo.toml`. //! //! # Example //! //! ```rust,no_run //! extern crate hidapi; //! //! use hidapi::HidApi; //! //! fn main() { //! println!("Printing all available hid devices:"); //! //! match HidApi::new() { //! Ok(api) => { //! for device in api.device_list() { //! println!("{:04x}:{:04x}", device.vendor_id(), device.product_id()); //! } //! }, //! Err(e) => { //! eprintln!("Error: {}", e); //! }, //! } //! } //! ``` //! //! For more usage examples, please take a look at the `examples/` directory. //! //! # Feature flags //! //! - `linux-static-libusb`: uses statically linked `libusb` backend on Linux //! - `linux-static-hidraw`: uses statically linked `hidraw` backend on Linux (default) //! - `linux-shared-libusb`: uses dynamically linked `libusb` backend on Linux //! - `linux-shared-hidraw`: uses dynamically linked `hidraw` backend on Linux //! - `linux-native`: talks to hidraw directly without using the `hidapi` C library //! - `illumos-static-libusb`: uses statically linked `libusb` backend on Illumos (default) //! - `illumos-shared-libusb`: uses statically linked `hidraw` backend on Illumos //! - `macos-shared-device`: enables shared access to HID devices on MacOS //! - `windows-native`: talks to hid.dll directly without using the `hidapi` C library //! //! ## Linux backends //! //! On linux the libusb backends do not support [`DeviceInfo::usage()`] and [`DeviceInfo::usage_page()`]. //! The hidraw backend has support for them, but it might be buggy in older kernel versions. //! //! ## MacOS Shared device access //! //! Since `hidapi` 0.12 it is possible to open MacOS devices with shared access, so that multiple //! [`HidDevice`] handles can access the same physical device. For backward compatibility this is //! an opt-in that can be enabled with the `macos-shared-device` feature flag. #![cfg_attr(docsrs, feature(doc_cfg))] mod error; mod ffi; use cfg_if::cfg_if; use libc::wchar_t; use std::ffi::CStr; use std::ffi::CString; use std::fmt; use std::fmt::Debug; use std::sync::Mutex; pub use error::HidError; cfg_if! { if #[cfg(all(feature = "linux-native", target_os = "linux"))] { //#[cfg_attr(docsrs, doc(cfg(all(feature = "linux-native", target_os = "linux"))))] mod linux_native; use linux_native::HidApiBackend; } else if #[cfg(all(feature = "windows-native", target_os = "windows"))] { //#[cfg_attr(docsrs, doc(cfg(all(feature = "windows-native", target_os = "windows"))))] mod windows_native; use windows_native::HidApiBackend; } else if #[cfg(hidapi)] { mod hidapi; use hidapi::HidApiBackend; } else { compile_error!("No backend selected"); } } // Automatically implement the top trait cfg_if! { if #[cfg(target_os = "windows")] { #[cfg_attr(docsrs, doc(cfg(target_os = "windows")))] mod windows; use windows::GUID; /// A trait with the extra methods that are available on Windows trait HidDeviceBackendWindows { /// Get the container ID for a HID device fn get_container_id(&self) -> HidResult; } trait HidDeviceBackend: HidDeviceBackendBase + HidDeviceBackendWindows + Send {} impl HidDeviceBackend for T where T: HidDeviceBackendBase + HidDeviceBackendWindows + Send {} } else if #[cfg(target_os = "macos")] { #[cfg_attr(docsrs, doc(cfg(target_os = "macos")))] mod macos; /// A trait with the extra methods that are available on macOS trait HidDeviceBackendMacos { /// Get the location ID for a [`HidDevice`] device. fn get_location_id(&self) -> HidResult; /// Check if the device was opened in exclusive mode. fn is_open_exclusive(&self) -> HidResult; } trait HidDeviceBackend: HidDeviceBackendBase + HidDeviceBackendMacos + Send {} impl HidDeviceBackend for T where T: HidDeviceBackendBase + HidDeviceBackendMacos + Send {} } else { trait HidDeviceBackend: HidDeviceBackendBase + Send {} impl HidDeviceBackend for T where T: HidDeviceBackendBase + Send {} } } pub type HidResult = Result; pub const MAX_REPORT_DESCRIPTOR_SIZE: usize = 4096; #[derive(Debug, Clone, Copy, PartialEq, Eq)] enum InitState { NotInit, Init { enumerate: bool }, } static INIT_STATE: Mutex = Mutex::new(InitState::NotInit); fn lazy_init(do_enumerate: bool) -> HidResult<()> { let mut init_state = INIT_STATE.lock().unwrap(); match *init_state { InitState::NotInit => { #[cfg(all(libusb, not(target_os = "freebsd")))] if !do_enumerate { // Do not scan for devices in libusb_init() // Must be set before calling it. // This is needed on Android, where access to USB devices is limited unsafe { ffi::libusb_set_option(std::ptr::null_mut(), 2) } } // Initialize the HID #[cfg(hidapi)] if unsafe { ffi::hid_init() } == -1 { return Err(HidError::InitializationError); } #[cfg(all(target_os = "macos", feature = "macos-shared-device"))] unsafe { ffi::macos::hid_darwin_set_open_exclusive(0) } *init_state = InitState::Init { enumerate: do_enumerate, } } InitState::Init { enumerate } => { if enumerate != do_enumerate { panic!("Trying to initialize hidapi with enumeration={}, but it is already initialized with enumeration={}.", do_enumerate, enumerate) } } } Ok(()) } /// `hidapi` context. /// /// The `hidapi` C library is lazily initialized when creating the first instance, /// and never deinitialized. Therefore, it is allowed to create multiple `HidApi` /// instances. /// /// Each instance has its own device list cache. pub struct HidApi { device_list: Vec, } impl HidApi { /// Create a new hidapi context. /// /// Will also initialize the currently available device list. /// /// # Panics /// /// Panics if hidapi is already initialized in "without enumerate" mode /// (i.e. if `new_without_enumerate()` has been called before). pub fn new() -> HidResult { lazy_init(true)?; let mut api = HidApi { device_list: Vec::with_capacity(8), }; api.add_devices(0, 0)?; Ok(api) } /// Create a new hidapi context, in "do not enumerate" mode. /// /// This is needed on Android, where access to USB device enumeration is limited. /// /// # Panics /// /// Panics if hidapi is already initialized in "do enumerate" mode /// (i.e. if `new()` has been called before). pub fn new_without_enumerate() -> HidResult { lazy_init(false)?; Ok(HidApi { device_list: Vec::new(), }) } /// Refresh devices list and information about them (to access them use /// `device_list()` method) /// Identical to `reset_devices()` followed by `add_devices(0, 0)`. pub fn refresh_devices(&mut self) -> HidResult<()> { self.reset_devices()?; self.add_devices(0, 0)?; Ok(()) } /// Reset devices list. Intended to be used with the `add_devices` method. pub fn reset_devices(&mut self) -> HidResult<()> { self.device_list.clear(); Ok(()) } /// Indexes devices that match the given VID and PID filters. /// 0 indicates no filter. pub fn add_devices(&mut self, vid: u16, pid: u16) -> HidResult<()> { self.device_list .append(&mut HidApiBackend::get_hid_device_info_vector(vid, pid)?); Ok(()) } /// Returns iterator containing information about attached HID devices /// that have been indexed, either by `refresh_devices` or `add_devices`. pub fn device_list(&self) -> impl Iterator { self.device_list.iter() } /// Open a HID device using a Vendor ID (VID) and Product ID (PID). /// /// When multiple devices with the same vid and pid are available, then the /// first one found in the internal device list will be used. There are however /// no guarantees, which device this will be. pub fn open(&self, vid: u16, pid: u16) -> HidResult { let dev = HidApiBackend::open(vid, pid)?; Ok(HidDevice::from_backend(Box::new(dev))) } /// Open a HID device using a Vendor ID (VID), Product ID (PID) and /// a serial number. pub fn open_serial(&self, vid: u16, pid: u16, sn: &str) -> HidResult { let dev = HidApiBackend::open_serial(vid, pid, sn)?; Ok(HidDevice::from_backend(Box::new(dev))) } /// The path name be determined by inspecting the device list available with [HidApi::devices()](struct.HidApi.html#method.devices) /// /// Alternatively a platform-specific path name can be used (eg: /dev/hidraw0 on Linux). pub fn open_path(&self, device_path: &CStr) -> HidResult { let dev = HidApiBackend::open_path(device_path)?; Ok(HidDevice::from_backend(Box::new(dev))) } /// Open a HID device using libusb_wrap_sys_device. #[cfg(libusb)] pub fn wrap_sys_device(&self, sys_dev: isize, interface_num: i32) -> HidResult { let device = unsafe { ffi::hid_libusb_wrap_sys_device(sys_dev, interface_num) }; if device.is_null() { match HidApiBackend::check_error() { Ok(err) => Err(err), Err(e) => Err(e), } } else { let dev = hidapi::HidDevice::from_raw(device); Ok(HidDevice::from_backend(Box::new(dev))) } } /// Get the last non-device specific error, which happened in the underlying hidapi C library. /// To get the last device specific error, use [`HidDevice::check_error`]. /// /// The `Ok()` variant of the result will contain a [HidError::HidApiError](enum.HidError.html). /// /// When `Err()` is returned, then acquiring the error string from the hidapi C /// library failed. The contained [HidError](enum.HidError.html) is the cause, why no error could /// be fetched. #[cfg(hidapi)] #[deprecated(since = "2.2.3", note = "use the return values from the other methods")] pub fn check_error(&self) -> HidResult { HidApiBackend::check_error() } } #[allow(dead_code)] #[derive(Clone, PartialEq)] enum WcharString { String(String), #[cfg_attr(all(feature = "linux-native", target_os = "linux"), allow(dead_code))] Raw(Vec), None, } impl From for Option { fn from(val: WcharString) -> Self { match val { WcharString::String(s) => Some(s), _ => None, } } } /// The underlying HID bus type. #[repr(C)] #[derive(Copy, Clone, Debug)] pub enum BusType { Unknown = 0x00, Usb = 0x01, Bluetooth = 0x02, I2c = 0x03, Spi = 0x04, } /// Device information. Use accessors to extract information about Hid devices. /// /// Note: Methods like `serial_number()` may return None, if the conversion to a /// String failed internally. You can however access the raw hid representation of the /// string by calling `serial_number_raw()` #[derive(Clone)] pub struct DeviceInfo { path: CString, vendor_id: u16, product_id: u16, serial_number: WcharString, release_number: u16, manufacturer_string: WcharString, product_string: WcharString, #[allow(dead_code)] usage_page: u16, #[allow(dead_code)] usage: u16, interface_number: i32, bus_type: BusType, } impl DeviceInfo { pub fn path(&self) -> &CStr { &self.path } pub fn vendor_id(&self) -> u16 { self.vendor_id } pub fn product_id(&self) -> u16 { self.product_id } /// Try to call `serial_number_raw()`, if None is returned. pub fn serial_number(&self) -> Option<&str> { match self.serial_number { WcharString::String(ref s) => Some(s), _ => None, } } pub fn serial_number_raw(&self) -> Option<&[wchar_t]> { match self.serial_number { WcharString::Raw(ref s) => Some(s), _ => None, } } pub fn release_number(&self) -> u16 { self.release_number } /// Try to call `manufacturer_string_raw()`, if None is returned. pub fn manufacturer_string(&self) -> Option<&str> { match self.manufacturer_string { WcharString::String(ref s) => Some(s), _ => None, } } pub fn manufacturer_string_raw(&self) -> Option<&[wchar_t]> { match self.manufacturer_string { WcharString::Raw(ref s) => Some(s), _ => None, } } /// Try to call `product_string_raw()`, if None is returned. pub fn product_string(&self) -> Option<&str> { match self.product_string { WcharString::String(ref s) => Some(s), _ => None, } } pub fn product_string_raw(&self) -> Option<&[wchar_t]> { match self.product_string { WcharString::Raw(ref s) => Some(s), _ => None, } } /// Usage page is not available on linux libusb backends #[cfg(not(all(libusb, target_os = "linux")))] pub fn usage_page(&self) -> u16 { self.usage_page } /// Usage is not available on linux libusb backends #[cfg(not(all(libusb, target_os = "linux")))] 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 } /// Use the information contained in `DeviceInfo` to open /// and return a handle to a [HidDevice](struct.HidDevice.html). /// /// By default the device path is used to open the device. /// When no path is available, then vid, pid and serial number are used. /// If both path and serial number are not available, then this function will /// fail with [HidError::OpenHidDeviceWithDeviceInfoError](enum.HidError.html#variant.OpenHidDeviceWithDeviceInfoError). /// /// Note, that opening a device could still be done using [HidApi::open()](struct.HidApi.html#method.open) directly. pub fn open_device(&self, hidapi: &HidApi) -> HidResult { if !self.path.as_bytes().is_empty() { hidapi.open_path(self.path.as_c_str()) } else if let Some(sn) = self.serial_number() { hidapi.open_serial(self.vendor_id, self.product_id, sn) } else { Err(HidError::OpenHidDeviceWithDeviceInfoError { device_info: Box::new(self.clone()), }) } } } impl fmt::Debug for DeviceInfo { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("HidDeviceInfo") .field("vendor_id", &self.vendor_id) .field("product_id", &self.product_id) .finish() } } /// Trait which the different backends must implement trait HidDeviceBackendBase { #[cfg(hidapi)] fn check_error(&self) -> HidResult; fn write(&self, data: &[u8]) -> HidResult; fn read(&self, buf: &mut [u8]) -> HidResult; fn read_timeout(&self, buf: &mut [u8], timeout: i32) -> HidResult; fn send_feature_report(&self, data: &[u8]) -> HidResult<()>; fn get_feature_report(&self, buf: &mut [u8]) -> HidResult; fn set_blocking_mode(&self, blocking: bool) -> HidResult<()>; fn get_device_info(&self) -> HidResult; fn get_manufacturer_string(&self) -> HidResult>; fn get_product_string(&self) -> HidResult>; fn get_serial_number_string(&self) -> HidResult>; fn get_report_descriptor(&self, buf: &mut [u8]) -> HidResult; fn get_indexed_string(&self, _index: i32) -> HidResult> { Err(HidError::HidApiError { message: "get_indexed_string: not supported".to_string(), }) } } pub struct HidDevice { inner: Box, } impl Debug for HidDevice { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("HidDevice").finish_non_exhaustive() } } impl HidDevice { fn from_backend(inner: Box) -> Self { Self { inner } } } // Methods that use the backend impl HidDevice { /// Get the last error, which happened in the underlying hidapi C library. /// /// The `Ok()` variant of the result will contain a [HidError::HidApiError](enum.HidError.html). /// /// When `Err()` is returned, then acquiring the error string from the hidapi C /// library failed. The contained [HidError](enum.HidError.html) is the cause, why no error could /// be fetched. #[cfg(hidapi)] #[deprecated(since = "2.2.3", note = "use the return values from the other methods")] pub fn check_error(&self) -> HidResult { self.inner.check_error() } /// Write an Output report to a HID device. /// /// The first byte of `data` must contain the Report ID. For /// devices which only support a single report, this must be set /// to 0x0. The remaining bytes contain the report data. Since /// the Report ID is mandatory, calls to `write()` will always /// contain one more byte than the report contains. For example, /// if a hid report is 16 bytes long, 17 bytes must be passed to /// `write()`, the Report ID (or 0x0, for devices with a /// single report), followed by the report data (16 bytes). In /// this example, the length passed in would be 17. /// `write()` will send the data on the first OUT endpoint, if /// one exists. If it does not, it will send the data through /// the Control Endpoint (Endpoint 0). /// /// If successful, returns the actual number of bytes written. pub fn write(&self, data: &[u8]) -> HidResult { self.inner.write(data) } /// Read an Input report from a HID device. /// /// Input reports are returned to the host through the 'INTERRUPT IN' /// endpoint. The first byte will contain the Report number if the device /// uses numbered reports. /// /// If successful, returns the actual number of bytes read. pub fn read(&self, buf: &mut [u8]) -> HidResult { self.inner.read(buf) } /// Read an Input report from a HID device with timeout. /// /// Input reports are returned to the host through the 'INTERRUPT IN' /// endpoint. The first byte will contain the Report number if the device /// uses numbered reports. Timeout measured in milliseconds, set -1 for /// blocking wait. /// /// If successful, returns the actual number of bytes read. pub fn read_timeout(&self, buf: &mut [u8], timeout: i32) -> HidResult { self.inner.read_timeout(buf, timeout) } /// Send a Feature report to the device. /// /// Feature reports are sent over the Control endpoint as a /// Set_Report transfer. The first byte of `data` must contain the /// 'Report ID'. For devices which only support a single report, this must /// be set to 0x0. The remaining bytes contain the report data. Since the /// 'Report ID' is mandatory, calls to `send_feature_report()` will always /// contain one more byte than the report contains. For example, if a hid /// report is 16 bytes long, 17 bytes must be passed to /// `send_feature_report()`: 'the Report ID' (or 0x0, for devices which /// do not use numbered reports), followed by the report data (16 bytes). /// In this example, the length passed in would be 17. /// /// If successful, returns the actual number of bytes written. pub fn send_feature_report(&self, data: &[u8]) -> HidResult<()> { self.inner.send_feature_report(data) } /// Get a feature report from a HID device. /// /// Set the first byte of `buf` to the 'Report ID' of the report to be read. /// Upon return, the first byte will still contain the Report ID, and the /// report data will start in `buf[1]`. /// /// If successful, returns the number of bytes read plus one for the report ID (which is still /// in the first byte). pub fn get_feature_report(&self, buf: &mut [u8]) -> HidResult { self.inner.get_feature_report(buf) } /// Set the device handle to be in blocking or in non-blocking mode. In /// non-blocking mode calls to `read()` will return immediately with an empty /// slice if there is no data to be read. In blocking mode, `read()` will /// wait (block) until there is data to read before returning. /// Modes can be changed at any time. pub fn set_blocking_mode(&self, blocking: bool) -> HidResult<()> { self.inner.set_blocking_mode(blocking) } /// Get The Manufacturer String from a HID device. pub fn get_manufacturer_string(&self) -> HidResult> { self.inner.get_manufacturer_string() } /// Get The Manufacturer String from a HID device. pub fn get_product_string(&self) -> HidResult> { self.inner.get_product_string() } /// Get The Serial Number String from a HID device. pub fn get_serial_number_string(&self) -> HidResult> { self.inner.get_serial_number_string() } /// Get a string from a HID device, based on its string index. pub fn get_indexed_string(&self, index: i32) -> HidResult> { self.inner.get_indexed_string(index) } /// Get a report descriptor from a HID device /// /// User has to provide a preallocated buffer where the descriptor will be copied to. /// It is recommended to use a preallocated buffer of [`MAX_REPORT_DESCRIPTOR_SIZE`] size. pub fn get_report_descriptor(&self, buf: &mut [u8]) -> HidResult { self.inner.get_report_descriptor(buf) } /// Get [`DeviceInfo`] from a HID device. pub fn get_device_info(&self) -> HidResult { self.inner.get_device_info() } } hidapi-2.6.3/src/linux_native/ioctl.rs000064400000000000000000000011251046102023000160450ustar 00000000000000//! The IOCTL calls we need for the native linux backend use nix::{ioctl_read, ioctl_readwrite_buf}; // From linux/hidraw.h const HIDRAW_IOC_MAGIC: u8 = b'H'; const HIDRAW_IOC_GRDESCSIZE: u8 = 0x01; const HIDRAW_SET_FEATURE: u8 = 0x06; const HIDRAW_GET_FEATURE: u8 = 0x07; ioctl_read!( hidraw_ioc_grdescsize, HIDRAW_IOC_MAGIC, HIDRAW_IOC_GRDESCSIZE, libc::c_int ); ioctl_readwrite_buf!( hidraw_ioc_set_feature, HIDRAW_IOC_MAGIC, HIDRAW_SET_FEATURE, u8 ); ioctl_readwrite_buf!( hidraw_ioc_get_feature, HIDRAW_IOC_MAGIC, HIDRAW_GET_FEATURE, u8 ); hidapi-2.6.3/src/linux_native.rs000064400000000000000000000477751046102023000147600ustar 00000000000000//! This backend uses libudev to discover devices and then talks to hidraw directly mod ioctl; use std::{ cell::{Cell, Ref, RefCell}, ffi::{CStr, CString, OsStr, OsString}, fs::{File, OpenOptions}, io::{Cursor, Read, Seek, SeekFrom}, os::{ fd::{AsFd, AsRawFd, BorrowedFd, OwnedFd}, unix::{ffi::OsStringExt, fs::OpenOptionsExt}, }, path::{Path, PathBuf}, }; use nix::{ errno::Errno, poll::{poll, PollFd, PollFlags}, sys::stat::{fstat, major, minor}, unistd::{read, write}, }; use super::{BusType, DeviceInfo, HidDeviceBackendBase, HidError, HidResult, WcharString}; use ioctl::{hidraw_ioc_get_feature, hidraw_ioc_grdescsize, hidraw_ioc_set_feature}; // Bus values from linux/input.h const BUS_USB: u16 = 0x03; const BUS_BLUETOOTH: u16 = 0x05; const BUS_I2C: u16 = 0x18; const BUS_SPI: u16 = 0x1C; pub struct HidApiBackend; impl HidApiBackend { pub fn get_hid_device_info_vector(vid: u16, pid: u16) -> HidResult> { // The C version assumes these can't fail, and they should only fail in case // of memory allocation issues, at which point maybe we should panic let mut enumerator = match udev::Enumerator::new() { Ok(e) => e, Err(_) => return Ok(Vec::new()), }; enumerator.match_subsystem("hidraw").unwrap(); let scan = match enumerator.scan_devices() { Ok(s) => s, Err(_) => return Ok(Vec::new()), }; let devices = scan .filter_map(|device| device_to_hid_device_info(&device)) .flatten() .filter(|device| vid == 0 || device.vendor_id == vid) .filter(|device| pid == 0 || device.product_id == pid) .collect::>(); Ok(devices) } pub fn open(vid: u16, pid: u16) -> HidResult { HidDevice::open(vid, pid, None) } pub fn open_serial(vid: u16, pid: u16, sn: &str) -> HidResult { HidDevice::open(vid, pid, Some(sn)) } pub fn open_path(device_path: &CStr) -> HidResult { HidDevice::open_path(device_path) } } fn device_to_hid_device_info(raw_device: &udev::Device) -> Option> { let mut infos = Vec::new(); // We're given the hidraw device, but we actually want to go and check out // the info for the parent hid device. let device = match raw_device.parent_with_subsystem("hid") { Ok(Some(dev)) => dev, _ => return None, }; let (bus, vid, pid) = match device .property_value("HID_ID") .and_then(|s| s.to_str()) .and_then(parse_hid_vid_pid) { Some(t) => t, None => return None, }; let bus_type = match bus { BUS_USB => BusType::Usb, BUS_BLUETOOTH => BusType::Bluetooth, BUS_I2C => BusType::I2c, BUS_SPI => BusType::Spi, _ => return None, }; let name = match device.property_value("HID_NAME") { Some(name) => name, None => return None, }; let serial = match device.property_value("HID_UNIQ") { Some(serial) => serial, None => return None, }; let path = match raw_device .devnode() .map(|p| p.as_os_str().to_os_string().into_vec()) .map(CString::new) { Some(Ok(s)) => s, None | Some(Err(_)) => return None, }; // Thus far we've gathered all the common attributes. let info = DeviceInfo { path, vendor_id: vid, product_id: pid, serial_number: osstring_to_string(serial.into()), release_number: 0, manufacturer_string: WcharString::None, product_string: WcharString::None, usage_page: 0, usage: 0, interface_number: -1, bus_type, }; // USB has a bunch more information but everything else gets the same empty // manufacturer and the product we read from the property above. let info = match bus_type { BusType::Usb => fill_in_usb(raw_device, info, name), _ => DeviceInfo { manufacturer_string: WcharString::String("".into()), product_string: osstring_to_string(name.into()), ..info }, }; if let Ok(descriptor) = HidrawReportDescriptor::from_syspath(raw_device.syspath()) { let mut usages = descriptor.usages(); // Get the first usage page and usage for our current DeviceInfo if let Some((usage_page, usage)) = usages.next() { infos.push(DeviceInfo { usage_page, usage, ..info }); // Now we can create DeviceInfo for all the other usages for (usage_page, usage) in usages { let prev = infos.last().unwrap(); infos.push(DeviceInfo { usage_page, usage, ..prev.clone() }) } } } else { infos.push(info); } Some(infos) } /// Fill in the extra information that's available for a USB device. fn fill_in_usb(device: &udev::Device, info: DeviceInfo, name: &OsStr) -> DeviceInfo { let usb_dev = match device.parent_with_subsystem_devtype("usb", "usb_device") { Ok(Some(dev)) => dev, Ok(None) | Err(_) => { return DeviceInfo { manufacturer_string: WcharString::String("".into()), product_string: osstring_to_string(name.into()), ..info } } }; let manufacturer_string = attribute_as_wchar(&usb_dev, "manufacturer"); let product_string = attribute_as_wchar(&usb_dev, "product"); let release_number = attribute_as_u16(&usb_dev, "bcdDevice").unwrap_or(0); let interface_number = device .parent_with_subsystem_devtype("usb", "usb_interface") .ok() .flatten() .and_then(|ref dev| attribute_as_i32(dev, "bInterfaceNumber")) .unwrap_or(-1); DeviceInfo { release_number, manufacturer_string, product_string, interface_number, ..info } } #[derive(Default)] struct HidrawReportDescriptor(Vec); impl HidrawReportDescriptor { /// Open and parse given the "base" sysfs of the device pub fn from_syspath(syspath: &Path) -> HidResult { let path = syspath.join("device/report_descriptor"); let mut f = File::open(path)?; let mut buf = Vec::new(); f.read_to_end(&mut buf)?; Ok(HidrawReportDescriptor(buf)) } /// Create a descriptor from a slice /// /// It returns an error if the value slice is too large for it to be a HID /// descriptor #[cfg_attr(not(test), allow(dead_code))] pub fn from_slice(value: &[u8]) -> HidResult { Ok(HidrawReportDescriptor(value.to_vec())) } pub fn usages(&self) -> impl Iterator + '_ { UsageIterator { usage_page: 0, cursor: Cursor::new(&self.0), } } } /// Iterates over the values in a HidrawReportDescriptor struct UsageIterator<'a> { usage_page: u16, cursor: Cursor<&'a Vec>, } impl<'a> Iterator for UsageIterator<'a> { type Item = (u16, u16); fn next(&mut self) -> Option { let (usage_page, page) = match next_hid_usage(&mut self.cursor, self.usage_page) { Some(n) => n, None => return None, }; self.usage_page = usage_page; Some((usage_page, page)) } } // This comes from hidapi which apparently comes from Apple's implementation of // this fn next_hid_usage(cursor: &mut Cursor<&Vec>, mut usage_page: u16) -> Option<(u16, u16)> { let mut usage = None; let mut usage_pair = None; let initial = cursor.position() == 0; while let Some(Ok(key)) = cursor.bytes().next() { // The amount to skip is calculated based off of the start of the // iteration so we need to keep track of that. let position = cursor.position() - 1; let key_cmd = key & 0xfc; let (data_len, key_size) = match hid_item_size(key, cursor) { Some(v) => v, None => return None, }; match key_cmd { // Usage Page 6.2.2.7 (Global) 0x4 => { usage_page = match hid_report_bytes(cursor, data_len) { Ok(v) => v as u16, Err(_) => break, } } // Usage 6.2.2.8 (Local) 0x8 => { usage = match hid_report_bytes(cursor, data_len) { Ok(v) => Some(v as u16), Err(_) => break, } } // Collection 6.2.2.4 (Main) 0xa0 => { // Usage is a Local Item, unset it if let Some(u) = usage.take() { usage_pair = Some((usage_page, u)) } } // Input 6.2.2.4 (Main) 0x80 | // Output 6.2.2.4 (Main) 0x90 | // Feature 6.2.2.4 (Main) 0xb0 | // End Collection 6.2.2.4 (Main) 0xc0 => { // Usage is a Local Item, unset it usage.take(); } _ => {} } if cursor .seek(SeekFrom::Start(position + (data_len + key_size) as u64)) .is_err() { return None; } if let Some((usage_page, usage)) = usage_pair { return Some((usage_page, usage)); } } if let (true, Some(usage)) = (initial, usage) { return Some((usage_page, usage)); } None } /// Gets the size of the HID item at the given position /// /// Returns data_len and key_size when successful fn hid_item_size(key: u8, cursor: &mut Cursor<&Vec>) -> Option<(usize, usize)> { // Long Item. Next byte contains the length of the data section. if (key & 0xf0) == 0xf0 { if let Some(Ok(len)) = cursor.bytes().next() { return Some((len.into(), 3)); } // Malformed report return None; } // Short Item. Bottom two bits contains the size code match key & 0x03 { v @ 0..=2 => Some((v.into(), 1)), 3 => Some((4, 1)), _ => unreachable!(), // & 0x03 means this can't happen } } /// Get the bytes from a HID report descriptor /// /// Must only be called with `num_bytes` 0, 1, 2 or 4. fn hid_report_bytes(cursor: &mut Cursor<&Vec>, num_bytes: usize) -> HidResult { let mut bytes: [u8; 4] = [0; 4]; cursor.read_exact(&mut bytes[..num_bytes])?; Ok(u32::from_le_bytes(bytes)) } /// Get the attribute from the device and convert it into a [`WcharString`]. fn attribute_as_wchar(dev: &udev::Device, attr: &str) -> WcharString { dev.attribute_value(attr) .map(Into::into) .map(osstring_to_string) .unwrap_or(WcharString::None) } /// Get the attribute from the device and convert it into a i32 /// /// On error or if the attribute is not found, it returns None. fn attribute_as_i32(dev: &udev::Device, attr: &str) -> Option { dev.attribute_value(attr) .and_then(OsStr::to_str) .and_then(|v| i32::from_str_radix(v, 16).ok()) } /// Get the attribute from the device and convert it into a u16 /// /// On error or if the attribute is not found, it returns None. fn attribute_as_u16(dev: &udev::Device, attr: &str) -> Option { dev.attribute_value(attr) .and_then(OsStr::to_str) .and_then(|v| u16::from_str_radix(v, 16).ok()) } /// Convert a [`OsString`] into a [`WcharString`] fn osstring_to_string(s: OsString) -> WcharString { match s.into_string() { Ok(s) => WcharString::String(s), Err(_) => panic!("udev strings should always be utf8"), } } /// Parse a HID_ID string to find the bus type, the vendor and product id /// /// These strings would be of the format /// type vendor product /// 0003:000005AC:00008242 fn parse_hid_vid_pid(s: &str) -> Option<(u16, u16, u16)> { let mut elems = s.split(':').map(|s| u16::from_str_radix(s, 16)); let devtype = elems.next()?.ok()?; let vendor = elems.next()?.ok()?; let product = elems.next()?.ok()?; Some((devtype, vendor, product)) } /// Object for accessing the HID device pub struct HidDevice { blocking: Cell, fd: OwnedFd, info: RefCell>, } unsafe impl Send for HidDevice {} // API for the library to call us, or for internal uses impl HidDevice { pub(crate) fn open(vid: u16, pid: u16, sn: Option<&str>) -> HidResult { for device in HidApiBackend::get_hid_device_info_vector(0, 0)? .iter() .filter(|device| device.vendor_id == vid && device.product_id == pid) { match (sn, &device.serial_number) { (None, _) => return Self::open_path(&device.path), (Some(sn), WcharString::String(serial_number)) if sn == serial_number => { return Self::open_path(&device.path) } _ => continue, }; } Err(HidError::HidApiError { message: "device not found".into(), }) } pub(crate) fn open_path(device_path: &CStr) -> HidResult { // Paths on Linux can be anything but devnode paths are going to be ASCII let path = device_path.to_str().expect("path must be utf-8"); let fd: OwnedFd = match OpenOptions::new() .read(true) .write(true) .custom_flags(libc::O_CLOEXEC | libc::O_NONBLOCK) .open(path) { Ok(f) => f.into(), Err(e) => { return Err(HidError::HidApiError { message: format!("failed to open device with path {path}: {e}"), }); } }; let mut size = 0_i32; if let Err(e) = unsafe { hidraw_ioc_grdescsize(fd.as_raw_fd(), &mut size) } { return Err(HidError::HidApiError { message: format!("ioctl(GRDESCSIZE) error for {path}, not a HIDRAW device?: {e}"), }); } Ok(Self { blocking: Cell::new(true), fd, info: RefCell::new(None), }) } fn info(&self) -> HidResult> { if self.info.borrow().is_none() { let info = self.get_device_info()?; self.info.replace(Some(info)); } let info = self.info.borrow(); Ok(Ref::map(info, |i: &Option| i.as_ref().unwrap())) } } impl AsFd for HidDevice { fn as_fd(&self) -> BorrowedFd { self.fd.as_fd() } } impl HidDeviceBackendBase for HidDevice { fn write(&self, data: &[u8]) -> HidResult { if data.is_empty() { return Err(HidError::InvalidZeroSizeData); } Ok(write(self.fd.as_raw_fd(), data)?) } fn read(&self, buf: &mut [u8]) -> HidResult { // If the caller asked for blocking, -1 makes us wait forever let timeout = if self.blocking.get() { -1 } else { 0 }; self.read_timeout(buf, timeout) } fn read_timeout(&self, buf: &mut [u8], timeout: i32) -> HidResult { let pollfd = PollFd::new(&self.fd, PollFlags::POLLIN); let res = poll(&mut [pollfd], timeout)?; if res == 0 { return Ok(0); } let events = pollfd .revents() .map(|e| e.intersects(PollFlags::POLLERR | PollFlags::POLLHUP | PollFlags::POLLNVAL)); if events.is_none() || events == Some(true) { return Err(HidError::HidApiError { message: "unexpected poll error (device disconnected)".into(), }); } match read(self.fd.as_raw_fd(), buf) { Ok(w) => Ok(w), Err(Errno::EAGAIN) | Err(Errno::EINPROGRESS) => Ok(0), Err(e) => Err(e.into()), } } fn send_feature_report(&self, data: &[u8]) -> HidResult<()> { if data.is_empty() { return Err(HidError::InvalidZeroSizeData); } // Have to crate owned buffer, because its not safe to cast shared // reference to mutable reference, even if the underlying function never // tries to mutate it. let mut d = data.to_vec(); // The ioctl is marked as read-write so we need to mess with the // mutability even though nothing should get written let res = match unsafe { hidraw_ioc_set_feature(self.fd.as_raw_fd(), &mut d) } { Ok(n) => n as usize, Err(e) => { return Err(HidError::HidApiError { message: format!("ioctl (GFEATURE): {e}"), }) } }; if res != data.len() { return Err(HidError::IncompleteSendError { sent: res, all: data.len(), }); } Ok(()) } fn get_feature_report(&self, buf: &mut [u8]) -> HidResult { let res = match unsafe { hidraw_ioc_get_feature(self.fd.as_raw_fd(), buf) } { Ok(n) => n as usize, Err(e) => { return Err(HidError::HidApiError { message: format!("ioctl (GFEATURE): {e}"), }) } }; Ok(res) } fn set_blocking_mode(&self, blocking: bool) -> HidResult<()> { self.blocking.set(blocking); Ok(()) } fn get_manufacturer_string(&self) -> HidResult> { let info = self.info()?; Ok(info.manufacturer_string().map(str::to_string)) } fn get_product_string(&self) -> HidResult> { let info = self.info()?; Ok(info.product_string().map(str::to_string)) } fn get_serial_number_string(&self) -> HidResult> { let info = self.info()?; Ok(info.serial_number().map(str::to_string)) } fn get_device_info(&self) -> HidResult { // What we have is a descriptor to a file in /dev but we need a syspath // so we get the major/minor from there and generate our syspath let devnum = fstat(self.fd.as_raw_fd())?.st_rdev; let syspath: PathBuf = format!("/sys/dev/char/{}:{}", major(devnum), minor(devnum)).into(); // The clone is a bit silly but we can't implement Copy. Maybe it's not // much worse than doing the conversion to Rust from interacting with C. let device = udev::Device::from_syspath(&syspath)?; match device_to_hid_device_info(&device) { Some(info) => Ok(info[0].clone()), None => Err(HidError::HidApiError { message: "failed to create device info".into(), }), } } fn get_report_descriptor(&self, buf: &mut [u8]) -> HidResult { let devnum = fstat(self.fd.as_raw_fd())?.st_rdev; let syspath: PathBuf = format!("/sys/dev/char/{}:{}", major(devnum), minor(devnum)).into(); let descriptor = HidrawReportDescriptor::from_syspath(&syspath)?; let min_size = buf.len().min(descriptor.0.len()); buf[..min_size].copy_from_slice(&descriptor.0[..min_size]); Ok(min_size) } } #[cfg(test)] mod test { use super::*; #[test] fn test_parse_hid_vid_pid() { assert_eq!(None, parse_hid_vid_pid("Hello World")); assert_eq!(Some((1, 1, 1)), parse_hid_vid_pid("1:1:1")); assert_eq!(Some((0x11, 0x17, 0x18)), parse_hid_vid_pid("11:0017:00018")); } #[test] fn test_hidraw_report_descriptor_1() { let data = include_bytes!("../tests/assets/mouse1.data"); let desc = HidrawReportDescriptor::from_slice(&data[..]).expect("descriptor"); let values = desc.usages().collect::>(); assert_eq!(vec![(65468, 136)], values); } #[test] fn test_hidraw_report_descriptor_2() { let data = include_bytes!("../tests/assets/mouse2.data"); let desc = HidrawReportDescriptor::from_slice(&data[..]).expect("descriptor"); let values = desc.usages().collect::>(); let expected = vec![(1, 2), (1, 1), (1, 128), (12, 1), (65280, 14)]; assert_eq!(expected, values); } } hidapi-2.6.3/src/macos.rs000064400000000000000000000022671046102023000133400ustar 00000000000000use libc::c_int; use crate::ffi; use crate::{HidApi, HidDevice, HidResult}; impl HidApi { /// Changes the behavior of all further calls that open a new [`HidDevice`] /// like [`HidApi::open`] or [`HidApi::open_path`]. By default on Darwin /// platform all devices opened by [`HidApi`] are opened in exclusive mode. /// /// When `exclusive` is set to: /// * `false` - all further devices will be opened in non-exclusive mode. /// * `true` all further devices will be opened in exclusive mode. pub fn set_open_exclusive(&self, exclusive: bool) { unsafe { ffi::macos::hid_darwin_set_open_exclusive(exclusive as c_int) } } /// Get the current opening behavior set by [`HidApi::set_open_exclusive`]. pub fn get_open_exclusive(&self) -> bool { unsafe { ffi::macos::hid_darwin_get_open_exclusive() != 0 } } } impl HidDevice { /// Get the location ID for a [`HidDevice`] device. pub fn get_location_id(&self) -> HidResult { self.inner.get_location_id() } /// Check if the device was opened in exclusive mode. pub fn is_open_exclusive(&self) -> HidResult { self.inner.is_open_exclusive() } } hidapi-2.6.3/src/windows.rs000064400000000000000000000006661046102023000137310ustar 00000000000000use crate::{HidDevice, HidResult}; pub use windows_sys::core::GUID; impl HidDevice { /// Get the container ID for a HID device. /// /// This function returns the `DEVPKEY_Device_ContainerId` property of the /// given device. This can be used to correlate different interfaces/ports /// on the same hardware device. pub fn get_container_id(&self) -> HidResult { self.inner.get_container_id() } } hidapi-2.6.3/src/windows_native/descriptor/encoder.rs000064400000000000000000000555041046102023000210750ustar 00000000000000use crate::windows_native::descriptor::typedefs::{Caps, LinkCollectionNode}; use crate::windows_native::descriptor::types::{ItemNodeType, Items, MainItemNode, MainItems}; use crate::windows_native::error::{WinError, WinResult}; use crate::windows_native::utils::PeakIterExt; pub fn encode_descriptor( main_item_list: &[MainItemNode], caps_list: &[Caps], link_collection_nodes: &[LinkCollectionNode], ) -> WinResult> { // *********************************** // Encode the report descriptor output // *********************************** let mut writer = DescriptorWriter::default(); let mut last_report_id = 0; let mut last_usage_page = 0; let mut last_physical_min = 0; // If both, Physical Minimum and Physical Maximum are 0, the logical limits should be taken as physical limits according USB HID spec 1.11 chapter 6.2.2.7 let mut last_physical_max = 0; let mut last_unit_exponent = 0; // If Unit Exponent is Undefined it should be considered as 0 according USB HID spec 1.11 chapter 6.2.2.7 let mut last_unit = 0; // If the first nibble is 7, or second nibble of Unit is 0, the unit is None according USB HID spec 1.11 chapter 6.2.2.7 let mut inhibit_write_of_usage = false; // Needed in case of delimited usage print, before the normal collection or cap let mut report_count = 0; for (current, next) in main_item_list.iter().peaking() { let rt_idx = current.main_item_type; let caps_idx = current.caps_index; match current.main_item_type { MainItems::Collection => { if last_usage_page != link_collection_nodes[current.collection_index].link_usage_page { // Write "Usage Page" at the begin of a collection - except it refers the same table as wrote last last_usage_page = link_collection_nodes[current.collection_index].link_usage_page; writer.write(Items::GlobalUsagePage, last_usage_page)?; } if inhibit_write_of_usage { // Inhibit only once after DELIMITER statement inhibit_write_of_usage = false; } else { // Write "Usage" of collection writer.write( Items::LocalUsage, link_collection_nodes[current.collection_index].link_usage, )?; } // Write begin of "Collection" writer.write( Items::MainCollection, link_collection_nodes[current.collection_index].collection_type(), )?; } MainItems::CollectionEnd => { // Write "End Collection" writer.write(Items::MainCollectionEnd, 0)?; } MainItems::DelimiterOpen => { // Current.collection_index seems to always be != -1 -> removing branches compared to c // Write "Usage Page" inside of a collection delmiter section if last_usage_page != link_collection_nodes[current.collection_index].link_usage_page { last_usage_page = link_collection_nodes[current.collection_index].link_usage_page; writer.write( Items::GlobalUsagePage, link_collection_nodes[current.collection_index].collection_type(), )?; } // Write "Delimiter Open" writer.write(Items::LocalDelimiter, 1)?; // 1 = open set of aliased usages } MainItems::DelimiterUsage => { // Current.collection_index seems to always be != -1 -> removing branches compared to c // Write aliased collection "Usage" writer.write( Items::LocalUsage, link_collection_nodes[current.collection_index].link_usage, )?; } MainItems::DelimiterClose => { // Write "Delimiter Close" writer.write(Items::LocalDelimiter, 0)?; // 0 = close set of aliased usages // Inhibit next usage write inhibit_write_of_usage = true; } _ if current.node_type == ItemNodeType::Padding => { // Padding // The preparsed data doesn't contain any information about padding. Therefore all undefined gaps // in the reports are filled with the same style of constant padding. // Write "Report Size" with number of padding bits writer.write( Items::GlobalReportSize, current.last_bit - current.first_bit + 1, )?; // Write "Report Count" for padding always as 1 writer.write(Items::GlobalReportCount, 1)?; if rt_idx == MainItems::Input { // Write "Input" main item - We know it's Constant - We can only guess the other bits, but they don't matter in case of const writer.write(Items::MainInput, 0x03)?; // Const / Abs } else if rt_idx == MainItems::Output { // Write "Output" main item - We know it's Constant - We can only guess the other bits, but they don't matter in case of const writer.write(Items::MainOutput, 0x03)?; // Const / Abs } else if rt_idx == MainItems::Feature { // Write "Feature" main item - We know it's Constant - We can only guess the other bits, but they don't matter in case of const writer.write(Items::MainFeature, 0x03)?; // Const / Abs } report_count = 0; } _ if caps_list[caps_idx as usize].is_button_cap() => { let caps = caps_list[caps_idx as usize]; // Button // (The preparsed data contain different data for 1 bit Button caps, than for parametric Value caps) if last_report_id != caps.report_id { // Write "Report ID" if changed last_report_id = caps.report_id; writer.write(Items::GlobalReportId, last_report_id)?; } // Write "Usage Page" when changed if caps.usage_page != last_usage_page { last_usage_page = caps.usage_page; writer.write(Items::GlobalUsagePage, last_usage_page)?; } // Write only local report items for each cap, if ReportCount > 1 if caps.is_range() { report_count += caps.range().data_index_max - caps.range().data_index_min; } if inhibit_write_of_usage { // Inhibit only once after Delimiter - Reset flag inhibit_write_of_usage = false; } else if caps.is_range() { // Write range from "Usage Minimum" to "Usage Maximum" writer.write(Items::LocalUsageMinimum, caps.range().usage_min)?; writer.write(Items::LocalUsageMaximum, caps.range().usage_max)?; } else { // Write single "Usage" writer.write(Items::LocalUsage, caps.not_range().usage)?; } if caps.is_designator_range() { // Write physical descriptor indices range from "Designator Minimum" to "Designator Maximum" writer.write(Items::LocalDesignatorMinimum, caps.range().designator_min)?; writer.write(Items::LocalDesignatorMaximum, caps.range().designator_max)?; } else if caps.not_range().designator_index != 0 { // Designator set 0 is a special descriptor set (of the HID Physical Descriptor), // that specifies the number of additional descriptor sets. // Therefore Designator Index 0 can never be a useful reference for a control and we can inhibit it. // Write single "Designator Index" writer.write( Items::LocalDesignatorIndex, caps.not_range().designator_index, )?; } if caps.is_string_range() { // Write range of indices of the USB string descriptor, from "String Minimum" to "String Maximum" writer.write(Items::LocalStringMinimum, caps.range().string_min)?; writer.write(Items::LocalStringMaximum, caps.range().string_max)?; } else if caps.not_range().string_index != 0 { // String Index 0 is a special entry of the USB string descriptor, that contains a list of supported languages, // therefore Designator Index 0 can never be a useful reference for a control and we can inhibit it. // Write single "String Index" writer.write(Items::LocalString, caps.not_range().string_index)?; } if next.is_some_and(|next| { next.main_item_type == rt_idx && next.node_type == ItemNodeType::Cap && !caps.is_range() && // This node in list is no array !caps_list[next.caps_index as usize].is_range() && // Next node in list is no array caps_list[next.caps_index as usize].is_button_cap() && caps_list[next.caps_index as usize].usage_page == caps.usage_page && caps_list[next.caps_index as usize].report_id == caps.report_id && caps_list[next.caps_index as usize].bit_field == caps.bit_field }) { if next.unwrap().first_bit != current.first_bit { // In case of IsMultipleItemsForArray for multiple dedicated usages for a multi-button array, the report count should be incremented // Skip global items until any of them changes, than use ReportCount item to write the count of identical report fields report_count += 1; } } else { if caps.button().logical_min == 0 && caps.button().logical_max == 0 { // While a HID report descriptor must always contain LogicalMinimum and LogicalMaximum, // the preparsed data contain both fields set to zero, for the case of simple buttons // Write "Logical Minimum" set to 0 and "Logical Maximum" set to 1 writer.write(Items::GlobalLogicalMinimum, 0)?; writer.write(Items::GlobalLogicalMaximum, 1)?; } else { // Write logical range from "Logical Minimum" to "Logical Maximum" writer.write(Items::GlobalLogicalMinimum, caps.button().logical_min)?; writer.write(Items::GlobalLogicalMaximum, caps.button().logical_max)?; } // Write "Report Size" writer.write(Items::GlobalReportSize, caps.report_size)?; // Write "Report Count" if !caps.is_range() { // Variable bit field with one bit per button // In case of multiple usages with the same items, only "Usage" is written per cap, and "Report Count" is incremented writer.write(Items::GlobalReportCount, caps.report_count + report_count)?; } else { // Button array of "Report Size" x "Report Count writer.write(Items::GlobalReportCount, caps.report_count)?; } // Buttons have only 1 bit and therefore no physical limits/units -> Set to undefined state if last_physical_min != 0 { // Write "Physical Minimum", but only if changed last_physical_min = 0; writer.write(Items::GlobalPhysicalMinimum, last_physical_min)?; } if last_physical_max != 0 { // Write "Physical Maximum", but only if changed last_physical_max = 0; writer.write(Items::GlobalPhysicalMaximum, last_physical_max)?; } if last_unit_exponent != 0 { // Write "Unit Exponent", but only if changed last_unit_exponent = 0; writer.write(Items::GlobalUnitExponent, last_unit_exponent)?; } if last_unit != 0 { // Write "Unit",but only if changed last_unit = 0; writer.write(Items::GlobalUnit, last_unit)?; } // Write "Input" main item if rt_idx == MainItems::Input { writer.write(Items::MainInput, caps.bit_field)?; } // Write "Output" main item else if rt_idx == MainItems::Output { writer.write(Items::MainOutput, caps.bit_field)?; } // Write "Feature" main item else if rt_idx == MainItems::Feature { writer.write(Items::MainFeature, caps.bit_field)?; } report_count = 0; } } _ => { let mut caps = caps_list[caps_idx as usize]; if last_report_id != caps.report_id { // Write "Report ID" if changed last_report_id = caps.report_id; writer.write(Items::GlobalReportId, last_report_id)?; } // Write "Usage Page" if changed if caps.usage_page != last_usage_page { last_usage_page = caps.usage_page; writer.write(Items::GlobalUsagePage, last_usage_page)?; } if inhibit_write_of_usage { // Inhibit only once after Delimiter - Reset flag inhibit_write_of_usage = false; } else if caps.is_range() { // Write usage range from "Usage Minimum" to "Usage Maximum" writer.write(Items::LocalUsageMinimum, caps.range().usage_min)?; writer.write(Items::LocalUsageMaximum, caps.range().usage_max)?; } else { // Write single "Usage" writer.write(Items::LocalUsage, caps.not_range().usage)?; } if caps.is_designator_range() { // Write physical descriptor indices range from "Designator Minimum" to "Designator Maximum" writer.write(Items::LocalDesignatorMinimum, caps.range().designator_min)?; writer.write(Items::LocalDesignatorMaximum, caps.range().designator_max)?; } else if caps.not_range().designator_index != 0 { // Designator set 0 is a special descriptor set (of the HID Physical Descriptor), // that specifies the number of additional descriptor sets. // Therefore Designator Index 0 can never be a useful reference for a control and we can inhibit it. // Write single "Designator Index" writer.write( Items::LocalDesignatorIndex, caps.not_range().designator_index, )?; } if caps.is_string_range() { // Write range of indices of the USB string descriptor, from "String Minimum" to "String Maximum" writer.write(Items::LocalStringMinimum, caps.range().string_min)?; writer.write(Items::LocalStringMaximum, caps.range().string_max)?; } else if caps.not_range().string_index != 0 { // String Index 0 is a special entry of the USB string descriptor, that contains a list of supported languages, // therefore Designator Index 0 can never be a useful reference for a control and we can inhibit it. // Write single "String Index" writer.write(Items::LocalString, caps.not_range().string_index)?; } if (caps.bit_field & 0x02) != 0x02 { // In case of an value array overwrite "Report Count" caps.report_count = caps.range().data_index_max - caps.range().data_index_min + 1; } #[allow(clippy::blocks_in_if_conditions)] if next.is_some_and(|next| { let next_caps = caps_list .get(next.caps_index as usize) .copied() .unwrap_or_else(|| unsafe { std::mem::zeroed() }); next.main_item_type == rt_idx && next.node_type == ItemNodeType::Cap && !next_caps.is_button_cap() && !caps.is_range() && !next_caps.is_range() && next_caps.usage_page == caps.usage_page && next_caps.not_button().logical_min == caps.not_button().logical_min && next_caps.not_button().logical_max == caps.not_button().logical_max && next_caps.not_button().physical_min == caps.not_button().physical_min && next_caps.not_button().physical_max == caps.not_button().physical_max && next_caps.units_exp == caps.units_exp && next_caps.units == caps.units && next_caps.report_size == caps.report_size && next_caps.report_id == caps.report_id && next_caps.bit_field == caps.bit_field && next_caps.report_count == 1 && caps.report_count == 1 }) { // Skip global items until any of them changes, than use ReportCount item to write the count of identical report fields report_count += 1; } else { // Value // Write logical range from "Logical Minimum" to "Logical Maximum" writer.write(Items::GlobalLogicalMinimum, caps.not_button().logical_min)?; writer.write(Items::GlobalLogicalMaximum, caps.not_button().logical_max)?; if (last_physical_min != caps.not_button().physical_min) || (last_physical_max != caps.not_button().physical_max) { // Write range from "Physical Minimum" to " Physical Maximum", but only if one of them changed last_physical_min = caps.not_button().physical_min; last_physical_max = caps.not_button().physical_max; writer.write(Items::GlobalPhysicalMinimum, last_physical_min)?; writer.write(Items::GlobalPhysicalMaximum, last_physical_max)?; } if last_unit_exponent != caps.units_exp { // Write "Unit Exponent", but only if changed last_unit_exponent = caps.units_exp; writer.write(Items::GlobalUnitExponent, last_unit_exponent)?; } if last_unit != caps.units { // Write physical "Unit", but only if changed last_unit = caps.units; writer.write(Items::GlobalUnit, last_unit)?; } // Write "Report Size" writer.write(Items::GlobalReportSize, caps.report_size)?; // Write "Report Count" writer.write(Items::GlobalReportCount, caps.report_count + report_count)?; if rt_idx == MainItems::Input { // Write "Input" main item writer.write(Items::MainInput, caps.bit_field)?; } else if rt_idx == MainItems::Output { // Write "Output" main item writer.write(Items::MainOutput, caps.bit_field)?; } else if rt_idx == MainItems::Feature { // Write "Feature" main item writer.write(Items::MainFeature, caps.bit_field)?; } report_count = 0; } } } } Ok(writer.finish()) } #[derive(Default)] struct DescriptorWriter(Vec); impl DescriptorWriter { // Writes a short report descriptor item according USB HID spec 1.11 chapter 6.2.2.2 fn write(&mut self, item: Items, data: impl Into) -> WinResult<()> { let data = data.into(); match item { Items::MainCollectionEnd => { self.0.push(item as u8); } Items::GlobalLogicalMinimum | Items::GlobalLogicalMaximum | Items::GlobalPhysicalMinimum | Items::GlobalPhysicalMaximum => { if let Ok(data) = i8::try_from(data) { self.0.push((item as u8) + 0x01); self.0.extend(data.to_le_bytes()) } else if let Ok(data) = i16::try_from(data) { self.0.push((item as u8) + 0x02); self.0.extend(data.to_le_bytes()) } else if let Ok(data) = i32::try_from(data) { self.0.push((item as u8) + 0x03); self.0.extend(data.to_le_bytes()) } else { return Err(WinError::InvalidPreparsedData); } } _ => { if let Ok(data) = u8::try_from(data) { self.0.push((item as u8) + 0x01); self.0.extend(data.to_le_bytes()) } else if let Ok(data) = u16::try_from(data) { self.0.push((item as u8) + 0x02); self.0.extend(data.to_le_bytes()) } else if let Ok(data) = u32::try_from(data) { self.0.push((item as u8) + 0x03); self.0.extend(data.to_le_bytes()) } else { return Err(WinError::InvalidPreparsedData); } } } Ok(()) } fn finish(self) -> Vec { self.0 } } hidapi-2.6.3/src/windows_native/descriptor/mod.rs000064400000000000000000000675531046102023000202440ustar 00000000000000mod encoder; #[cfg(test)] mod tests; mod typedefs; mod types; use crate::windows_native::descriptor::encoder::encode_descriptor; use crate::windows_native::descriptor::typedefs::{Caps, HidpPreparsedData, LinkCollectionNode}; use crate::windows_native::descriptor::types::{ BitRange, ItemNodeType, MainItemNode, MainItems, ReportType, }; use crate::windows_native::error::{WinError, WinResult}; use crate::windows_native::hid::PreparsedData; use crate::windows_native::utils::PeakIterExt; use std::collections::HashMap; use std::ffi::c_void; use std::slice; pub fn get_descriptor(pp_data: &PreparsedData) -> WinResult> { unsafe { get_descriptor_ptr(pp_data.as_ptr()) } } unsafe fn get_descriptor_ptr(pp_data: *const c_void) -> WinResult> { let (header, caps_list, link_collection_nodes) = extract_structures(pp_data)?; let list = reconstruct_descriptor(header, caps_list, link_collection_nodes); encode_descriptor(&list, caps_list, link_collection_nodes) } unsafe fn extract_structures<'a>( pp_data: *const c_void, ) -> WinResult<(HidpPreparsedData, &'a [Caps], &'a [LinkCollectionNode])> { let header: *const HidpPreparsedData = pp_data as _; // Check if MagicKey is correct, to ensure that pp_data points to an valid preparse data structure ensure!( &(*header).magic_key == b"HidP KDR", Err(WinError::InvalidPreparsedData) ); let caps_ptr: *const Caps = header.offset(1) as _; let caps_len = ReportType::values() .into_iter() .map(|r| (*header).caps_info[r as usize].last_cap) .max() .unwrap() as usize; let link_ptr: *const LinkCollectionNode = ((caps_ptr as *const c_void) .offset((*header).first_byte_of_link_collection_array as isize)) as _; let link_len = (*header).number_link_collection_nodes as usize; Ok(( *header, slice::from_raw_parts(caps_ptr, caps_len), slice::from_raw_parts(link_ptr, link_len), )) } fn reconstruct_descriptor( header: HidpPreparsedData, caps_list: &[Caps], link_collection_nodes: &[LinkCollectionNode], ) -> Vec { // **************************************************************************************************************************** // Create lookup tables for the bit range of each report per collection (position of first bit and last bit in each collection) // coll_bit_range[COLLECTION_INDEX][REPORT_ID][INPUT/OUTPUT/FEATURE] // **************************************************************************************************************************** let mut coll_bit_range: HashMap<(usize, u8, ReportType), BitRange> = HashMap::new(); for rt_idx in ReportType::values() { let caps_info = header.caps_info[rt_idx as usize]; for caps_idx in caps_info.first_cap..caps_info.last_cap { let caps = caps_list[caps_idx as usize]; let range = caps.get_bit_range(); coll_bit_range .entry((caps.link_collection as usize, caps.report_id, rt_idx)) .and_modify(|r| *r = r.merge(range)) .or_insert(range); } } // ************************************************************************* // -Determine hierachy levels of each collections and store it in: // coll_levels[COLLECTION_INDEX] // -Determine number of direct childs of each collections and store it in: // coll_number_of_direct_childs[COLLECTION_INDEX] // ************************************************************************* let mut max_coll_level = 0; let mut coll_levels = vec![-1; link_collection_nodes.len()]; let mut coll_number_of_direct_childs = vec![0; link_collection_nodes.len()]; { let mut actual_coll_level = 0; let mut collection_node_idx = 0; while actual_coll_level >= 0 { coll_levels[collection_node_idx] = actual_coll_level; let node = link_collection_nodes[collection_node_idx]; if node.number_of_children > 0 && coll_levels[node.first_child as usize] == -1 { actual_coll_level += 1; coll_levels[collection_node_idx] = actual_coll_level; max_coll_level = max_coll_level.max(actual_coll_level); coll_number_of_direct_childs[collection_node_idx] += 1; collection_node_idx = node.first_child as usize; } else if node.next_sibling != 0 { coll_number_of_direct_childs[node.parent as usize] += 1; collection_node_idx = node.next_sibling as usize; } else { actual_coll_level -= 1; if actual_coll_level >= 0 { collection_node_idx = node.parent as usize; } } } } // ********************************************************************************* // Propagate the bit range of each report from the child collections to their parent // and store the merged result for the parent // ********************************************************************************* for actual_coll_level in (0..max_coll_level).rev() { for collection_node_idx in 0..link_collection_nodes.len() { if coll_levels[collection_node_idx] == actual_coll_level { let mut child_idx = link_collection_nodes[collection_node_idx].first_child as usize; while child_idx != 0 { for reportid_idx in 0..=255 { for rt_idx in ReportType::values() { if let Some(child) = coll_bit_range .get(&(child_idx, reportid_idx, rt_idx)) .copied() { coll_bit_range .entry((collection_node_idx, reportid_idx, rt_idx)) .and_modify(|r| *r = r.merge(child)) .or_insert(child); } child_idx = link_collection_nodes[child_idx].next_sibling as usize; } } } } } } // ************************************************************************************************* // Determine child collection order of the whole hierachy, based on previously determined bit ranges // and store it this index coll_child_order[COLLECTION_INDEX][DIRECT_CHILD_INDEX] // ************************************************************************************************* let mut coll_child_order: HashMap<(usize, u16), usize> = HashMap::new(); { let mut coll_parsed_flag = vec![false; link_collection_nodes.len()]; let mut actual_coll_level = 0; let mut collection_node_idx = 0; while actual_coll_level >= 0 { if coll_number_of_direct_childs[collection_node_idx] != 0 && !coll_parsed_flag [link_collection_nodes[collection_node_idx].first_child as usize] { coll_parsed_flag[link_collection_nodes[collection_node_idx].first_child as usize] = true; { // Create list of child collection indices // sorted reverse to the order returned to HidP_GetLinkCollectionNodeschild // which seems to match teh original order, as long as no bit position needs to be considered let mut child_idx = link_collection_nodes[collection_node_idx].first_child as usize; let mut child_count = coll_number_of_direct_childs[collection_node_idx] - 1; coll_child_order.insert((collection_node_idx, child_count), child_idx); while link_collection_nodes[child_idx].next_sibling != 0 { child_count -= 1; child_idx = link_collection_nodes[child_idx].next_sibling as usize; coll_child_order.insert((collection_node_idx, child_count), child_idx); } } if coll_number_of_direct_childs[collection_node_idx] > 1 { // Sort child collections indices by bit positions for rt_idx in ReportType::values() { for report_idx in 0..=255 { for child_idx in 1..coll_number_of_direct_childs[collection_node_idx] { // since the coll_bit_range array is not sorted, we need to reference the collection index in // our sorted coll_child_order array, and look up the corresponding bit ranges for comparing values to sort let prev_coll_idx = *coll_child_order .get(&(collection_node_idx, child_idx - 1)) .unwrap(); let cur_coll_idx = *coll_child_order .get(&(collection_node_idx, child_idx)) .unwrap(); let swap = coll_bit_range .get(&(prev_coll_idx, report_idx, rt_idx)) .map(|prev| prev.first_bit) .zip( coll_bit_range .get(&(cur_coll_idx, report_idx, rt_idx)) .map(|prev| prev.first_bit), ) .map_or(false, |(prev, cur)| prev > cur); if swap { coll_child_order.insert( (collection_node_idx, (child_idx - 1)), cur_coll_idx, ); coll_child_order .insert((collection_node_idx, child_idx), prev_coll_idx); } } } } } actual_coll_level += 1; collection_node_idx = link_collection_nodes[collection_node_idx].first_child as usize; } else if link_collection_nodes[collection_node_idx].next_sibling != 0 { collection_node_idx = link_collection_nodes[collection_node_idx].next_sibling as usize; } else { actual_coll_level -= 1; if actual_coll_level >= 0 { collection_node_idx = link_collection_nodes[collection_node_idx].parent as usize; } } } } // *************************************************************************************** // Create sorted main_item_list containing all the Collection and CollectionEnd main items // *************************************************************************************** let mut main_item_list: Vec = Vec::new(); { let mut coll_last_written_child = vec![-1i32; link_collection_nodes.len()]; let mut actual_coll_level = 0; let mut collection_node_idx = 0; let mut first_delimiter_node = false; main_item_list.push(MainItemNode::new( 0, 0, ItemNodeType::Collection, 0, collection_node_idx, MainItems::Collection, 0, )); while actual_coll_level >= 0 { if coll_number_of_direct_childs[collection_node_idx] != 0 && coll_last_written_child[collection_node_idx] == -1 { // Collection has child collections, but none is written to the list yet coll_last_written_child[collection_node_idx] = coll_child_order[&(collection_node_idx, 0)] as i32; collection_node_idx = coll_child_order[&(collection_node_idx, 0)]; // In a HID Report Descriptor, the first usage declared is the most preferred usage for the control. // While the order in the WIN32 capabiliy strutures is the opposite: // Here the preferred usage is the last aliased usage in the sequence. if link_collection_nodes[collection_node_idx].is_alias() && !first_delimiter_node { first_delimiter_node = true; main_item_list.push(MainItemNode::new( 0, 0, ItemNodeType::Collection, 0, collection_node_idx, MainItems::DelimiterUsage, 0, )); main_item_list.push(MainItemNode::new( 0, 0, ItemNodeType::Collection, 0, collection_node_idx, MainItems::DelimiterClose, 0, )); } else { main_item_list.push(MainItemNode::new( 0, 0, ItemNodeType::Collection, 0, collection_node_idx, MainItems::Collection, 0, )); actual_coll_level += 1; } } else if coll_number_of_direct_childs[collection_node_idx] > 1 && coll_last_written_child[collection_node_idx] != coll_child_order[&( collection_node_idx, coll_number_of_direct_childs[collection_node_idx] - 1, )] as i32 { // Collection has child collections, and this is not the first child let mut next_child = 1; while coll_last_written_child[collection_node_idx] != coll_child_order[&(collection_node_idx, (next_child - 1))] as i32 { next_child += 1; } coll_last_written_child[collection_node_idx] = coll_child_order[&(collection_node_idx, next_child)] as i32; collection_node_idx = coll_child_order[&(collection_node_idx, next_child)]; if link_collection_nodes[collection_node_idx].is_alias() && !first_delimiter_node { // Alliased Collection (First node in link_collection_nodes -> Last entry in report descriptor output) first_delimiter_node = true; main_item_list.push(MainItemNode::new( 0, 0, ItemNodeType::Collection, 0, collection_node_idx, MainItems::DelimiterUsage, 0, )); main_item_list.push(MainItemNode::new( 0, 0, ItemNodeType::Collection, 0, collection_node_idx, MainItems::DelimiterClose, 0, )); } else if link_collection_nodes[collection_node_idx].is_alias() && first_delimiter_node { main_item_list.insert( 1, MainItemNode::new( 0, 0, ItemNodeType::Collection, 0, collection_node_idx, MainItems::DelimiterUsage, 0, ), ); } else if !link_collection_nodes[collection_node_idx].is_alias() && first_delimiter_node { main_item_list.insert( 1, MainItemNode::new( 0, 0, ItemNodeType::Collection, 0, collection_node_idx, MainItems::DelimiterUsage, 0, ), ); main_item_list.insert( 1, MainItemNode::new( 0, 0, ItemNodeType::Collection, 0, collection_node_idx, MainItems::DelimiterClose, 0, ), ); first_delimiter_node = false; } if !link_collection_nodes[collection_node_idx].is_alias() { main_item_list.push(MainItemNode::new( 0, 0, ItemNodeType::Collection, 0, collection_node_idx, MainItems::Collection, 0, )); actual_coll_level += 1; } } else { actual_coll_level -= 1; main_item_list.push(MainItemNode::new( 0, 0, ItemNodeType::Collection, 0, collection_node_idx, MainItems::CollectionEnd, 0, )); collection_node_idx = link_collection_nodes[collection_node_idx].parent as usize; } } } // **************************************************************** // Inserted Input/Output/Feature main items into the main_item_list // in order of reconstructed bit positions // **************************************************************** for rt_idx in ReportType::values() { // Add all value caps to node list let mut first_delimiter_node = false; let caps_info = header.caps_info[rt_idx as usize]; for caps_idx in caps_info.first_cap..caps_info.last_cap { let caps = caps_list[caps_idx as usize]; let mut coll_begin = main_item_list .iter() .position(|node| node.collection_index == caps.link_collection as usize) .unwrap(); let (first_bit, last_bit) = { let range = caps.get_bit_range(); (range.first_bit, range.last_bit) }; for child_idx in 0..coll_number_of_direct_childs[caps.link_collection as usize] { // Determine in which section before/between/after child collection the item should be inserted let child_first_bit = coll_child_order .get(&(caps.link_collection as usize, child_idx)) .and_then(|i| coll_bit_range.get(&(*i, caps.report_id, rt_idx))) .map(|r| r.first_bit) .unwrap_or(0); if first_bit < child_first_bit { // Note, that the default value for undefined coll_bit_range is -1, which can't be greater than the bit position break; } let index = coll_child_order[&(caps.link_collection as usize, child_idx)]; coll_begin = main_item_list .iter() .rposition(|node| node.collection_index == index) .unwrap(); } let list_node = 1 + search_list( first_bit as i32, rt_idx.into(), caps.report_id, coll_begin, &main_item_list, ); // In a HID Report Descriptor, the first usage declared is the most preferred usage for the control. // While the order in the WIN32 capabiliy strutures is the opposite: // Here the preferred usage is the last aliased usage in the sequence. if caps.is_alias() && !first_delimiter_node { // Alliased Usage (First node in pp_data->caps -> Last entry in report descriptor output) first_delimiter_node = true; main_item_list.insert( list_node, MainItemNode::new( first_bit, last_bit, ItemNodeType::Cap, caps_idx as i32, caps.link_collection as usize, MainItems::DelimiterUsage, caps.report_id, ), ); main_item_list.insert( list_node, MainItemNode::new( first_bit, last_bit, ItemNodeType::Cap, caps_idx as i32, caps.link_collection as usize, MainItems::DelimiterClose, caps.report_id, ), ); } else if caps.is_alias() && first_delimiter_node { main_item_list.insert( list_node, MainItemNode::new( first_bit, last_bit, ItemNodeType::Cap, caps_idx as i32, caps.link_collection as usize, MainItems::DelimiterUsage, caps.report_id, ), ); } else if !caps.is_alias() && first_delimiter_node { // Alliased Collection (Last node in pp_data->caps -> First entry in report descriptor output) main_item_list.insert( list_node, MainItemNode::new( first_bit, last_bit, ItemNodeType::Cap, caps_idx as i32, caps.link_collection as usize, MainItems::DelimiterUsage, caps.report_id, ), ); main_item_list.insert( list_node, MainItemNode::new( first_bit, last_bit, ItemNodeType::Cap, caps_idx as i32, caps.link_collection as usize, MainItems::DelimiterOpen, caps.report_id, ), ); first_delimiter_node = false; } if !caps.is_alias() { main_item_list.insert( list_node, MainItemNode::new( first_bit, last_bit, ItemNodeType::Cap, caps_idx as i32, caps.link_collection as usize, rt_idx.into(), caps.report_id, ), ); } } } // *********************************************************** // Add const main items for padding to main_item_list // -To fill all bit gaps // -At each report end for 8bit padding // Note that information about the padding at the report end, // is not stored in the preparsed data, but in practice all // report descriptors seem to have it, as assumed here. // *********************************************************** { let mut last_bit_position: HashMap<(MainItems, u8), i32> = HashMap::new(); let mut last_report_item_lookup: HashMap<(MainItems, u8), usize> = HashMap::new(); let mut index = 0; while index < main_item_list.len() { let current = main_item_list[index]; if ReportType::try_from(current.main_item_type).is_ok() { let lbp = last_bit_position .get(&(current.main_item_type, current.report_id)) .copied() .unwrap_or(-1); let lrip = last_report_item_lookup .get(&(current.main_item_type, current.report_id)) .copied(); if lbp + 1 != current.first_bit as i32 && lrip.is_some_and(|i| main_item_list[i].first_bit != current.first_bit) { let list_node = search_list( lbp, current.main_item_type, current.report_id, lrip.unwrap(), &main_item_list, ); main_item_list.insert( list_node + 1, MainItemNode::new( (lbp + 1) as u16, current.first_bit - 1, ItemNodeType::Padding, -1, 0, current.main_item_type, current.report_id, ), ); index += 1; } last_bit_position.insert( (current.main_item_type, current.report_id), current.last_bit as i32, ); last_report_item_lookup.insert((current.main_item_type, current.report_id), index); } index += 1; } for rt_idx in ReportType::values() { for report_idx in 0..=255 { if let Some(lbp) = last_bit_position.get(&(rt_idx.into(), report_idx)) { let padding = 8 - ((*lbp + 1) % 8); if padding < 8 { // Insert padding item after item referenced in last_report_item_lookup let lrip = *last_report_item_lookup .get(&(rt_idx.into(), report_idx)) .unwrap(); main_item_list.insert( lrip + 1, MainItemNode::new( (lbp + 1) as u16, (lbp + padding) as u16, ItemNodeType::Padding, -1, 0, rt_idx.into(), report_idx, ), ); last_report_item_lookup .values_mut() .filter(|i| **i > lrip) .for_each(|i| *i += 1); } } } } } main_item_list } fn search_list( search_bit: i32, main_item_type: MainItems, report_id: u8, start: usize, list: &[MainItemNode], ) -> usize { list[start..] .iter() .peaking() .position(|(_, next)| { next.is_some_and(|next| { next.main_item_type == MainItems::Collection || next.main_item_type == MainItems::CollectionEnd || (next.last_bit as i32 >= search_bit && next.report_id == report_id && next.main_item_type == main_item_type) }) }) .unwrap() + start } hidapi-2.6.3/src/windows_native/descriptor/tests.rs000064400000000000000000000051731046102023000206150ustar 00000000000000use crate::windows_native::descriptor::get_descriptor_ptr; use std::fs::read_to_string; #[test] fn test_01() { execute_testcase("045E_02FF_0005_0001"); } #[test] fn test_02() { execute_testcase("046A_0011_0006_0001"); } #[test] fn test_03() { execute_testcase("046D_0A37_0001_000C"); } #[test] fn test_04() { execute_testcase("046D_B010_0001_000C"); } #[test] fn test_05() { execute_testcase("046D_B010_0001_FF00"); } #[test] fn test_06() { execute_testcase("046D_B010_0002_0001"); } #[test] fn test_07() { execute_testcase("046D_B010_0002_FF00"); } #[test] fn test_08() { execute_testcase("046D_B010_0006_0001"); } #[test] fn test_09() { execute_testcase("046D_C077_0002_0001"); } #[test] fn test_10() { execute_testcase("046D_C283_0004_0001"); } #[test] fn test_11() { execute_testcase("046D_C52F_0001_000C"); } #[test] fn test_12() { execute_testcase("046D_C52F_0001_FF00"); } #[test] fn test_13() { execute_testcase("046D_C52F_0002_0001"); } #[test] fn test_14() { execute_testcase("046D_C52F_0002_FF00"); } #[test] fn test_15() { execute_testcase("046D_C534_0001_000C"); } #[test] fn test_16() { execute_testcase("046D_C534_0001_FF00"); } #[test] fn test_17() { execute_testcase("046D_C534_0002_0001"); } #[test] fn test_18() { execute_testcase("046D_C534_0002_FF00"); } #[test] fn test_19() { execute_testcase("046D_C534_0006_0001"); } #[test] fn test_20() { execute_testcase("046D_C534_0080_0001"); } #[test] fn test_21() { execute_testcase("047F_C056_0001_000C"); } #[test] fn test_22() { execute_testcase("047F_C056_0003_FFA0"); } #[test] fn test_23() { execute_testcase("047F_C056_0005_000B"); } #[test] fn test_24() { execute_testcase("17CC_1130_0000_FF01"); } fn execute_testcase(filename: &str) { let source_path = format!("./tests/pp_data/{filename}.pp_data"); let expected_path = format!("./tests/pp_data/{filename}.expected"); println!("Testing: {:?} <-> {:?}", source_path, expected_path); let pp_data = decode_hex(&read_to_string(&source_path).unwrap()); let expected_descriptor = decode_hex(&read_to_string(&expected_path).unwrap()); let constructed_descriptor = unsafe { get_descriptor_ptr(pp_data.as_ptr() as _) }.unwrap(); assert_eq!(constructed_descriptor, expected_descriptor); } fn decode_hex(hex: &str) -> Vec { hex.lines() .flat_map(|line| { line.split(',') .map(|hex| hex.trim()) .filter(|hex| !hex.is_empty()) .map(|hex| hex.strip_prefix("0x").unwrap()) .map(|hex| u8::from_str_radix(hex, 16).unwrap()) }) .collect() } hidapi-2.6.3/src/windows_native/descriptor/typedefs.rs000064400000000000000000000113101046102023000212640ustar 00000000000000use crate::windows_native::descriptor::types::BitRange; use std::mem::size_of; // Reverse engineered typedefs for the internal structure of the preparsed data taken from // https://github.com/libusb/hidapi/blob/master/windows/hidapi_descriptor_reconstruct.h // https://github.com/libusb/hidapi/pull/306 #[macro_export] macro_rules! const_assert { ($x:expr $(,)?) => { #[allow(unknown_lints)] const _: [(); 0 - !{ const ASSERT: bool = $x; ASSERT } as usize] = []; }; } pub type Usage = u16; const_assert!(size_of::() == 16); #[derive(Copy, Clone)] #[repr(C)] pub struct LinkCollectionNode { pub link_usage: Usage, pub link_usage_page: Usage, pub parent: u16, pub number_of_children: u16, pub next_sibling: u16, pub first_child: u16, pub bits: u32, } impl LinkCollectionNode { pub fn is_alias(&self) -> bool { self.bits & 1u32 << 8 != 0 } pub fn collection_type(&self) -> u8 { (self.bits & 0xFFu32) as u8 } } const_assert!(size_of::() == 8); #[derive(Copy, Clone)] #[repr(C)] pub struct CapsInfo { pub first_cap: u16, pub number_of_caps: u16, pub last_cap: u16, pub report_byte_length: u16, } const_assert!(size_of::() == 8); #[derive(Copy, Clone)] #[repr(C)] pub struct UnknownToken { pub token: u8, _reserved: [u8; 3], pub bit_field: u32, } #[derive(Copy, Clone)] #[repr(C)] pub struct Button { pub logical_min: i32, pub logical_max: i32, } #[derive(Copy, Clone)] #[repr(C)] pub struct NotButton { pub has_nul: u8, _reserved: [u8; 3], pub logical_min: i32, pub logical_max: i32, pub physical_min: i32, pub physical_max: i32, } #[derive(Copy, Clone)] #[repr(C)] union MaybeButton { button: Button, not_button: NotButton, } #[derive(Copy, Clone)] #[repr(C)] pub struct Range { pub usage_min: Usage, pub usage_max: Usage, pub string_min: u16, pub string_max: u16, pub designator_min: u16, pub designator_max: u16, pub data_index_min: u16, pub data_index_max: u16, } #[derive(Copy, Clone)] #[repr(C)] pub struct NotRange { pub usage: Usage, _reserved1: Usage, pub string_index: u16, _reserved2: u16, pub designator_index: u16, _reserved3: u16, pub data_index: u16, _reserved4: u16, } #[derive(Copy, Clone)] #[repr(C)] union MaybeRange { range: Range, not_range: NotRange, } const_assert!(size_of::() == 104); #[derive(Copy, Clone)] #[repr(C)] pub struct Caps { pub usage_page: Usage, pub report_id: u8, pub bit_position: u8, pub report_size: u16, pub report_count: u16, pub byte_position: u16, pub bit_count: u16, pub bit_field: u32, pub next_byte_position: u16, pub link_collection: u16, pub link_usage_page: Usage, pub link_usage: Usage, pub flags: u8, _reserved: [u8; 3], pub unknown_tokens: [UnknownToken; 4], maybe_range: MaybeRange, maybe_button: MaybeButton, pub units: u32, pub units_exp: u32, } impl Caps { pub fn is_button_cap(&self) -> bool { self.flags & (1 << 2) != 0 } pub fn is_range(&self) -> bool { self.flags & (1 << 4) != 0 } pub fn is_alias(&self) -> bool { self.flags & (1 << 5) != 0 } pub fn is_string_range(&self) -> bool { self.flags & (1 << 6) != 0 } pub fn is_designator_range(&self) -> bool { self.flags & (1 << 7) != 0 } pub fn range(&self) -> Range { //Both union elements have the same size and are valid for all bit patterns unsafe { self.maybe_range.range } } pub fn not_range(&self) -> NotRange { //Both union elements have the same size and are valid for all bit patterns unsafe { self.maybe_range.not_range } } pub fn button(&self) -> Button { //Both union elements have the same size and are valid for all bit patterns unsafe { self.maybe_button.button } } pub fn not_button(&self) -> NotButton { //Both union elements have the same size and are valid for all bit patterns unsafe { self.maybe_button.not_button } } pub fn get_bit_range(&self) -> BitRange { let first_bit = (self.byte_position - 1) * 8 + self.bit_position as u16; let last_bit = first_bit + self.report_size * self.report_count - 1; BitRange { first_bit, last_bit, } } } #[derive(Copy, Clone)] #[repr(C)] pub struct HidpPreparsedData { pub magic_key: [u8; 8], pub usage: Usage, pub usage_page: Usage, _reserved: [u16; 2], pub caps_info: [CapsInfo; 3], pub first_byte_of_link_collection_array: u16, pub number_link_collection_nodes: u16, } hidapi-2.6.3/src/windows_native/descriptor/types.rs000064400000000000000000000073431046102023000206200ustar 00000000000000#![allow(dead_code)] use std::fmt::Debug; #[derive(Copy, Clone, Eq, PartialEq, Hash)] #[repr(u16)] pub enum ReportType { Input = 0x0, Output = 0x1, Feature = 0x2, } impl ReportType { pub const fn values() -> impl IntoIterator { [Self::Input, Self::Output, Self::Feature] } } #[derive(Debug, Copy, Clone, Eq, PartialEq)] #[repr(u8)] pub enum Items { MainInput = 0x80, // 1000 00 nn MainOutput = 0x90, // 1001 00 nn MainFeature = 0xB0, // 1011 00 nn MainCollection = 0xA0, // 1010 00 nn MainCollectionEnd = 0xC0, // 1100 00 nn GlobalUsagePage = 0x04, // 0000 01 nn GlobalLogicalMinimum = 0x14, // 0001 01 nn GlobalLogicalMaximum = 0x24, // 0010 01 nn GlobalPhysicalMinimum = 0x34, // 0011 01 nn GlobalPhysicalMaximum = 0x44, // 0100 01 nn GlobalUnitExponent = 0x54, // 0101 01 nn GlobalUnit = 0x64, // 0110 01 nn GlobalReportSize = 0x74, // 0111 01 nn GlobalReportId = 0x84, // 1000 01 nn GlobalReportCount = 0x94, // 1001 01 nn GlobalPush = 0xA4, // 1010 01 nn GlobalPop = 0xB4, // 1011 01 nn LocalUsage = 0x08, // 0000 10 nn LocalUsageMinimum = 0x18, // 0001 10 nn LocalUsageMaximum = 0x28, // 0010 10 nn LocalDesignatorIndex = 0x38, // 0011 10 nn LocalDesignatorMinimum = 0x48, // 0100 10 nn LocalDesignatorMaximum = 0x58, // 0101 10 nn LocalString = 0x78, // 0111 10 nn LocalStringMinimum = 0x88, // 1000 10 nn LocalStringMaximum = 0x98, // 1001 10 nn LocalDelimiter = 0xA8, // 1010 10 nn } #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] #[repr(u16)] pub enum MainItems { Input = ReportType::Input as u16, Output = ReportType::Output as u16, Feature = ReportType::Feature as u16, Collection, CollectionEnd, DelimiterOpen, DelimiterUsage, DelimiterClose, } impl From for MainItems { fn from(value: ReportType) -> Self { match value { ReportType::Input => Self::Input, ReportType::Output => Self::Output, ReportType::Feature => Self::Feature, } } } impl TryFrom for ReportType { type Error = (); fn try_from(value: MainItems) -> Result { match value { MainItems::Input => Ok(Self::Input), MainItems::Output => Ok(Self::Output), MainItems::Feature => Ok(Self::Feature), _ => Err(()), } } } #[derive(Default, Copy, Clone, Eq, PartialEq)] pub struct BitRange { pub first_bit: u16, pub last_bit: u16, } impl BitRange { pub fn merge(self, other: BitRange) -> BitRange { BitRange { first_bit: self.first_bit.min(other.first_bit), last_bit: self.last_bit.max(other.last_bit), } } } #[derive(Debug, Copy, Clone, Eq, PartialEq)] pub enum ItemNodeType { Cap, Padding, Collection, } #[derive(Debug, Copy, Clone, Eq, PartialEq)] pub struct MainItemNode { pub first_bit: u16, pub last_bit: u16, pub node_type: ItemNodeType, pub caps_index: i32, pub collection_index: usize, pub main_item_type: MainItems, pub report_id: u8, } impl MainItemNode { pub fn new( first_bit: u16, last_bit: u16, node_type: ItemNodeType, caps_index: i32, collection_index: usize, main_item_type: MainItems, report_id: u8, ) -> Self { Self { first_bit, last_bit, node_type, caps_index, collection_index, main_item_type, report_id, } } } hidapi-2.6.3/src/windows_native/dev_node.rs000064400000000000000000000044661046102023000170640ustar 00000000000000use crate::windows_native::error::{check_config, WinError, WinResult}; use crate::windows_native::string::U16Str; use crate::windows_native::types::{DeviceProperty, PropertyKey}; use std::ptr::null_mut; use windows_sys::Win32::Devices::DeviceAndDriverInstallation::{ CM_Get_DevNode_PropertyW, CM_Get_Parent, CM_Locate_DevNodeW, CM_LOCATE_DEVNODE_NORMAL, CR_BUFFER_SMALL, CR_SUCCESS, }; #[repr(transparent)] #[derive(Debug, Copy, Clone, Eq, PartialEq)] pub struct DevNode(u32); impl DevNode { pub fn from_device_id(device_id: &U16Str) -> WinResult { let mut node = 0; let cr = unsafe { CM_Locate_DevNodeW(&mut node, device_id.as_ptr(), CM_LOCATE_DEVNODE_NORMAL) }; check_config(cr, CR_SUCCESS)?; Ok(Self(node)) } pub fn parent(self) -> WinResult { let mut parent = 0; let cr = unsafe { CM_Get_Parent(&mut parent, self.0, 0) }; check_config(cr, CR_SUCCESS)?; Ok(Self(parent)) } fn get_property_size( self, property_key: impl PropertyKey, ) -> WinResult { let mut property_type = 0; let mut len = 0; let cr = unsafe { CM_Get_DevNode_PropertyW( self.0, property_key.as_ptr(), &mut property_type, null_mut(), &mut len, 0, ) }; check_config(cr, CR_BUFFER_SMALL)?; ensure!( property_type == T::TYPE, Err(WinError::WrongPropertyDataType) ); Ok(len as usize) } pub fn get_property(self, property_key: impl PropertyKey) -> WinResult { let size = self.get_property_size::(property_key)?; let mut property = T::create_sized(size); let mut property_type = 0; let mut len = size as u32; let cr = unsafe { CM_Get_DevNode_PropertyW( self.0, property_key.as_ptr(), &mut property_type, property.as_ptr_mut(), &mut len, 0, ) }; check_config(cr, CR_SUCCESS)?; ensure!(size == len as usize, Err(WinError::UnexpectedReturnSize)); property.validate(); Ok(property) } } hidapi-2.6.3/src/windows_native/device_info.rs000064400000000000000000000224431046102023000175460ustar 00000000000000use crate::windows_native::dev_node::DevNode; use crate::windows_native::error::WinResult; use crate::windows_native::hid::{get_hid_attributes, PreparsedData}; use crate::windows_native::interfaces::Interface; use crate::windows_native::string::{U16Str, U16String, U16StringList}; use crate::windows_native::types::{Handle, InternalBusType}; use crate::{BusType, DeviceInfo, WcharString}; use std::ffi::{c_void, CString}; use std::mem::{size_of, zeroed}; use windows_sys::Win32::Devices::HumanInterfaceDevice::{ HidD_GetManufacturerString, HidD_GetProductString, HidD_GetSerialNumberString, }; use windows_sys::Win32::Devices::Properties::{ DEVPKEY_Device_CompatibleIds, DEVPKEY_Device_HardwareIds, DEVPKEY_Device_InstanceId, DEVPKEY_Device_Manufacturer, DEVPKEY_NAME, }; use windows_sys::Win32::Foundation::{BOOLEAN, HANDLE}; use windows_sys::Win32::Storage::EnhancedStorage::{ PKEY_DeviceInterface_Bluetooth_DeviceAddress, PKEY_DeviceInterface_Bluetooth_Manufacturer, PKEY_DeviceInterface_Bluetooth_ModelNumber, }; fn read_string( func: unsafe extern "system" fn(HANDLE, *mut c_void, u32) -> BOOLEAN, handle: &Handle, ) -> WcharString { // Return empty string on failure to match the c implementation let mut string = [0u16; 256]; if unsafe { func( handle.as_raw(), string.as_mut_ptr() as _, (size_of::() * string.len()) as u32, ) } != 0 { U16Str::from_slice_list(&string) .map(WcharString::from) .next() .unwrap_or_else(|| WcharString::String(String::new())) } else { // WcharString::None WcharString::String(String::new()) } } pub fn get_device_info(path: &U16Str, handle: &Handle) -> DeviceInfo { let attrib = get_hid_attributes(handle); let caps = PreparsedData::load(handle) .and_then(|data| data.get_caps()) .unwrap_or(unsafe { zeroed() }); let mut dev = DeviceInfo { path: CString::new(path.to_string()).unwrap(), vendor_id: attrib.VendorID, product_id: attrib.ProductID, serial_number: read_string(HidD_GetSerialNumberString, handle), release_number: attrib.VersionNumber, manufacturer_string: read_string(HidD_GetManufacturerString, handle), product_string: read_string(HidD_GetProductString, handle), usage_page: caps.UsagePage, usage: caps.Usage, interface_number: -1, bus_type: BusType::Unknown, }; // If this fails just ignore it. The data might be incomplete but at least there is something let _ = get_internal_info(path, &mut dev); dev } fn get_internal_info(interface_path: &U16Str, dev: &mut DeviceInfo) -> WinResult<()> { let device_id: U16String = Interface::get_property(interface_path, DEVPKEY_Device_InstanceId)?; let dev_node = DevNode::from_device_id(&device_id)?.parent()?; let compatible_ids: U16StringList = dev_node.get_property(DEVPKEY_Device_CompatibleIds)?; let bus_type = compatible_ids .iter() .filter_map(|compatible_id| match compatible_id { // The hidapi c library uses `contains` instead of `starts_with`, // but as far as I can tell `starts_with` is a better choice // USB devices // https://docs.microsoft.com/windows-hardware/drivers/hid/plug-and-play-support // https://docs.microsoft.com/windows-hardware/drivers/install/standard-usb-identifiers id if id.starts_with_ignore_case("USB") => Some(InternalBusType::Usb), // Bluetooth devices // https://docs.microsoft.com/windows-hardware/drivers/bluetooth/installing-a-bluetooth-device id if id.starts_with_ignore_case("BTHENUM") => Some(InternalBusType::Bluetooth), id if id.starts_with_ignore_case("BTHLEDEVICE") => Some(InternalBusType::BluetoothLE), // I2C devices // https://docs.microsoft.com/windows-hardware/drivers/hid/plug-and-play-support-and-power-management id if id.starts_with_ignore_case("PNP0C50") => Some(InternalBusType::I2c), // SPI devices // https://docs.microsoft.com/windows-hardware/drivers/hid/plug-and-play-for-spi id if id.starts_with_ignore_case("PNP0C51") => Some(InternalBusType::Spi), _ => None, }) .next() .unwrap_or(InternalBusType::Unknown); dev.bus_type = bus_type.into(); match bus_type { InternalBusType::Usb => get_usb_info(dev, dev_node)?, InternalBusType::BluetoothLE => get_ble_info(dev, dev_node)?, _ => (), }; Ok(()) } fn get_usb_info(dev: &mut DeviceInfo, mut dev_node: DevNode) -> WinResult<()> { let mut device_id: U16String = dev_node.get_property(DEVPKEY_Device_InstanceId)?; device_id.make_uppercase_ascii(); // Check for Xbox Common Controller class (XUSB) device. // https://docs.microsoft.com/windows/win32/xinput/directinput-and-xusb-devices // https://docs.microsoft.com/windows/win32/xinput/xinput-and-directinput // if extract_int_token_value(&device_id, "IG_").is_some() { dev_node = dev_node.parent()?; } let mut hardware_ids: U16StringList = dev_node.get_property(DEVPKEY_Device_HardwareIds)?; // Get additional information from USB device's Hardware ID // https://docs.microsoft.com/windows-hardware/drivers/install/standard-usb-identifiers // https://docs.microsoft.com/windows-hardware/drivers/usbcon/enumeration-of-interfaces-not-grouped-in-collections // for hardware_id in hardware_ids.iter_mut() { hardware_id.make_uppercase_ascii(); if dev.release_number == 0 { if let Some(release_number) = extract_int_token_value(hardware_id, "REV_") { dev.release_number = release_number as u16; } } if dev.interface_number == -1 { if let Some(interface_number) = extract_int_token_value(hardware_id, "MI_") { dev.interface_number = interface_number as i32; } } } // Try to get USB device manufacturer string if not provided by HidD_GetManufacturerString. if dev.manufacturer_string().map_or(true, str::is_empty) { if let Ok(manufacturer_string) = dev_node.get_property::(DEVPKEY_Device_Manufacturer) { dev.manufacturer_string = (&*manufacturer_string).into(); } } // Try to get USB device serial number if not provided by HidD_GetSerialNumberString. if dev.serial_number().map_or(true, str::is_empty) { let mut usb_dev_node = dev_node; if dev.interface_number != -1 { // Get devnode parent to reach out composite parent USB device. // https://docs.microsoft.com/windows-hardware/drivers/usbcon/enumeration-of-the-composite-parent-device usb_dev_node = dev_node.parent()?; } let device_id: U16String = usb_dev_node.get_property(DEVPKEY_Device_InstanceId)?; // Extract substring after last '\\' of Instance ID. // For USB devices it may contain device's serial number. // https://docs.microsoft.com/windows-hardware/drivers/install/instance-ids // if let Some(start) = device_id .as_slice() .rsplit(|c| *c != b'&' as u16) .next() .and_then(|s| s.iter().rposition(|c| *c != b'\\' as u16)) { dev.serial_number = U16Str::from_slice(&device_id.as_slice()[(start + 1)..]).into(); } } if dev.interface_number == -1 { dev.interface_number = 0; } Ok(()) } // HidD_GetProductString/HidD_GetManufacturerString/HidD_GetSerialNumberString is not working for BLE HID devices // Request this info via dev node properties instead. // https://docs.microsoft.com/answers/questions/401236/hidd-getproductstring-with-ble-hid-device.html fn get_ble_info(dev: &mut DeviceInfo, dev_node: DevNode) -> WinResult<()> { if dev.manufacturer_string().map_or(true, str::is_empty) { if let Ok(manufacturer_string) = dev_node.get_property::(PKEY_DeviceInterface_Bluetooth_Manufacturer) { dev.manufacturer_string = manufacturer_string.into(); } } if dev.serial_number().map_or(true, str::is_empty) { if let Ok(serial_number) = dev_node.get_property::(PKEY_DeviceInterface_Bluetooth_DeviceAddress) { dev.serial_number = serial_number.into(); } } if dev.product_string().map_or(true, str::is_empty) { let product_string = dev_node .get_property::(PKEY_DeviceInterface_Bluetooth_ModelNumber) .or_else(|_| { // Fallback: Get devnode grandparent to reach out Bluetooth LE device node dev_node .parent() .and_then(|parent_dev_node| parent_dev_node.get_property(DEVPKEY_NAME)) }); if let Ok(product_string) = product_string { dev.product_string = product_string.into(); } } Ok(()) } fn extract_int_token_value(u16str: &U16Str, token: &str) -> Option { let start = u16str.find_index(token)? + token.encode_utf16().count(); char::decode_utf16(u16str.as_slice()[start..].iter().copied()) .map_while(|c| c.ok().and_then(|c| c.to_digit(16))) .reduce(|l, r| l * 16 + r) } hidapi-2.6.3/src/windows_native/error.rs000064400000000000000000000047141046102023000164260ustar 00000000000000use crate::HidError; use windows_sys::Win32::Devices::DeviceAndDriverInstallation::*; use windows_sys::Win32::Foundation::*; pub type WinResult = Result; #[derive(Debug, Clone, Eq, PartialEq)] pub enum WinError { Config(CONFIGRET), Win32(Win32Error), BufferTooSmall, InvalidDeviceId, InvalidDeviceNode, NoSuchValue, WrongPropertyDataType, UnexpectedReturnSize, InvalidPreparsedData, WaitTimedOut, } impl WinError { pub fn last() -> Self { Self::from(Win32Error::last()) } } impl From for HidError { fn from(value: WinError) -> Self { match value { WinError::Win32(Win32Error::Generic(err)) => HidError::IoError { error: std::io::Error::from_raw_os_error(err as _), }, err => HidError::HidApiError { message: format!("WinError: {:?}", err), }, } } } fn config_to_error(ret: CONFIGRET) -> WinError { match ret { CR_BUFFER_SMALL => WinError::BufferTooSmall, CR_INVALID_DEVICE_ID => WinError::InvalidDeviceId, CR_INVALID_DEVNODE => WinError::InvalidDeviceNode, CR_NO_SUCH_VALUE => WinError::NoSuchValue, ret => WinError::Config(ret), } } pub fn check_config(ret: CONFIGRET, expected: CONFIGRET) -> WinResult<()> { if ret == expected { Ok(()) } else { Err(config_to_error(ret)) } } pub fn check_boolean(ret: BOOLEAN) -> WinResult<()> { if ret == 0 { Err(Win32Error::last().into()) } else { Ok(()) } } #[derive(Debug, Copy, Clone, Eq, PartialEq)] pub enum Win32Error { Generic(WIN32_ERROR), Success, IoPending, WaitTimedOut, } impl Win32Error { pub fn last() -> Self { match unsafe { GetLastError() } { NO_ERROR => Self::Success, ERROR_IO_PENDING => Self::IoPending, ERROR_IO_INCOMPLETE | WAIT_TIMEOUT => Self::WaitTimedOut, code => Self::Generic(code), } } //pub fn is_error(self) -> bool { // !matches!(self, Win32Error::Success | Win32Error::IoPending) //} } impl From for WinError { fn from(value: Win32Error) -> Self { match value { Win32Error::WaitTimedOut => Self::WaitTimedOut, err => Self::Win32(err), } } } impl From for HidError { fn from(value: Win32Error) -> Self { HidError::from(WinError::from(value)) } } hidapi-2.6.3/src/windows_native/hid.rs000064400000000000000000000033331046102023000160350ustar 00000000000000use crate::windows_native::error::{check_boolean, WinError, WinResult}; use crate::windows_native::types::Handle; use std::ffi::c_void; use std::mem::{size_of, zeroed}; use windows_sys::core::GUID; use windows_sys::Win32::Devices::HumanInterfaceDevice::{ HidD_FreePreparsedData, HidD_GetAttributes, HidD_GetHidGuid, HidD_GetPreparsedData, HidP_GetCaps, HIDD_ATTRIBUTES, HIDP_CAPS, HIDP_STATUS_SUCCESS, }; pub fn get_interface_guid() -> GUID { unsafe { let mut guid = zeroed(); HidD_GetHidGuid(&mut guid); guid } } pub fn get_hid_attributes(handle: &Handle) -> HIDD_ATTRIBUTES { unsafe { let mut attrib = HIDD_ATTRIBUTES { Size: size_of::() as u32, ..zeroed() }; HidD_GetAttributes(handle.as_raw(), &mut attrib); attrib } } #[repr(transparent)] pub struct PreparsedData(isize); impl Drop for PreparsedData { fn drop(&mut self) { unsafe { HidD_FreePreparsedData(self.0); } } } impl PreparsedData { pub fn load(handle: &Handle) -> WinResult { let mut pp_data = 0; check_boolean(unsafe { HidD_GetPreparsedData(handle.as_raw(), &mut pp_data) })?; ensure!(pp_data != 0, Err(WinError::InvalidPreparsedData)); Ok(Self(pp_data)) } #[allow(dead_code)] pub fn as_ptr(&self) -> *const c_void { self.0 as _ } pub fn get_caps(&self) -> WinResult { unsafe { let mut caps = zeroed(); let r = HidP_GetCaps(self.0, &mut caps); ensure!( r == HIDP_STATUS_SUCCESS, Err(WinError::InvalidPreparsedData) ); Ok(caps) } } } hidapi-2.6.3/src/windows_native/interfaces.rs000064400000000000000000000063251046102023000174200ustar 00000000000000use crate::windows_native::error::{check_config, WinError, WinResult}; use crate::windows_native::hid::get_interface_guid; use crate::windows_native::string::{U16Str, U16StringList}; use crate::windows_native::types::{DeviceProperty, PropertyKey}; use std::ptr::{null, null_mut}; use windows_sys::core::GUID; use windows_sys::Win32::Devices::DeviceAndDriverInstallation::{ CM_Get_Device_Interface_ListW, CM_Get_Device_Interface_List_SizeW, CM_Get_Device_Interface_PropertyW, CM_GET_DEVICE_INTERFACE_LIST_PRESENT, CR_BUFFER_SMALL, CR_SUCCESS, }; pub struct Interface; impl Interface { fn get_property_size( interface: &U16Str, property_key: impl PropertyKey, ) -> WinResult { let mut property_type = 0; let mut len = 0; let cr = unsafe { CM_Get_Device_Interface_PropertyW( interface.as_ptr(), property_key.as_ptr(), &mut property_type, null_mut(), &mut len, 0, ) }; check_config(cr, CR_BUFFER_SMALL)?; ensure!( property_type == T::TYPE, Err(WinError::WrongPropertyDataType) ); Ok(len as usize) } pub fn get_property( interface: &U16Str, property_key: impl PropertyKey, ) -> WinResult { let size = Self::get_property_size::(interface, property_key)?; let mut property = T::create_sized(size); let mut property_type = 0; let mut len = size as u32; let cr = unsafe { CM_Get_Device_Interface_PropertyW( interface.as_ptr(), property_key.as_ptr(), &mut property_type, property.as_ptr_mut(), &mut len, 0, ) }; check_config(cr, CR_SUCCESS)?; ensure!(size == len as usize, Err(WinError::UnexpectedReturnSize)); property.validate(); Ok(property) } fn get_interface_list_length(interface: GUID) -> WinResult { let mut len = 0; let cr = unsafe { CM_Get_Device_Interface_List_SizeW( &mut len, &interface, null(), CM_GET_DEVICE_INTERFACE_LIST_PRESENT, ) }; check_config(cr, CR_SUCCESS)?; Ok(len as usize) } pub fn get_interface_list() -> WinResult { let interface_class_guid = get_interface_guid(); let mut device_interface_list = Vec::new(); loop { device_interface_list.resize(Self::get_interface_list_length(interface_class_guid)?, 0); let cr = unsafe { CM_Get_Device_Interface_ListW( &interface_class_guid, null(), device_interface_list.as_mut_ptr(), device_interface_list.len() as u32, CM_GET_DEVICE_INTERFACE_LIST_PRESENT, ) }; if cr == CR_SUCCESS { return Ok(U16StringList(device_interface_list)); } check_config(cr, CR_BUFFER_SMALL)?; } } } hidapi-2.6.3/src/windows_native/mod.rs000064400000000000000000000337661046102023000160650ustar 00000000000000//! The implementation which uses the the raw win32 api to perform operations macro_rules! ensure { ($cond:expr, $result:expr) => { if !($cond) { return $result; } }; } mod descriptor; mod dev_node; mod device_info; mod error; mod hid; mod interfaces; mod string; mod types; mod utils; use std::cell::{Cell, RefCell}; use std::ptr::{null, null_mut}; use std::{ ffi::CStr, fmt::{self, Debug}, }; use crate::windows_native::dev_node::DevNode; use crate::windows_native::device_info::get_device_info; use crate::windows_native::error::{check_boolean, Win32Error, WinError, WinResult}; use crate::windows_native::hid::{get_hid_attributes, PreparsedData}; use crate::windows_native::interfaces::Interface; use crate::windows_native::string::{U16Str, U16String}; use crate::windows_native::types::{Handle, Overlapped}; use crate::{DeviceInfo, HidDeviceBackendBase, HidDeviceBackendWindows, HidError, HidResult}; use windows_sys::core::GUID; use windows_sys::Win32::Devices::HumanInterfaceDevice::{ HidD_GetIndexedString, HidD_SetFeature, HidD_SetNumInputBuffers, }; use windows_sys::Win32::Devices::Properties::{ DEVPKEY_Device_ContainerId, DEVPKEY_Device_InstanceId, }; use windows_sys::Win32::Foundation::{GENERIC_READ, GENERIC_WRITE, INVALID_HANDLE_VALUE, TRUE}; 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::ResetEvent; use windows_sys::Win32::System::IO::{CancelIoEx, DeviceIoControl}; const STRING_BUF_LEN: usize = 128; pub struct HidApiBackend; impl HidApiBackend { pub fn get_hid_device_info_vector(vid: u16, pid: u16) -> HidResult> { Ok(enumerate_devices(vid, pid)?) } pub fn open(vid: u16, pid: u16) -> HidResult { open(vid, pid, None) } pub fn open_serial(vid: u16, pid: u16, sn: &str) -> HidResult { open(vid, pid, Some(sn)) } pub fn open_path(device_path: &CStr) -> HidResult { open_path(device_path) } } /// Object for accessing HID device pub struct HidDevice { device_handle: Handle, device_info: DeviceInfo, read_pending: Cell, blocking: Cell, read_state: RefCell, write_state: RefCell, feature_state: RefCell, } struct AsyncState { overlapped: Box, buffer: Vec, } impl AsyncState { fn new(report_size: usize) -> Self { Self { overlapped: Default::default(), buffer: vec![0u8; report_size], } } fn clear_buffer(&mut self) { self.buffer.fill(0) } fn fill_buffer(&mut self, data: &[u8]) { // Make sure the right number of bytes are passed to WriteFile. Windows // expects the number of bytes which are in the _longest_ report (plus // one for the report number) bytes even if the data is a report // which is shorter than that. Windows gives us this value in // caps.OutputReportByteLength. If a user passes in fewer bytes than this, // use cached temporary buffer which is the proper size. let data_size = data.len().min(self.buffer.len()); self.buffer[..data_size].copy_from_slice(&data[..data_size]); if data_size < self.buffer.len() { self.buffer[data_size..].fill(0); } } fn buffer_len(&self) -> usize { self.buffer.len() } fn buffer_ptr(&mut self) -> *mut u8 { self.buffer.as_mut_ptr() } } impl Debug for HidDevice { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("HidDevice").finish() } } impl HidDeviceBackendBase for HidDevice { fn write(&self, data: &[u8]) -> HidResult { ensure!(!data.is_empty(), Err(HidError::InvalidZeroSizeData)); let mut state = self.write_state.borrow_mut(); state.fill_buffer(data); let res = unsafe { WriteFile( self.device_handle.as_raw(), state.buffer_ptr(), state.buffer_len() as u32, null_mut(), state.overlapped.as_raw(), ) }; if res != TRUE { let err = Win32Error::last(); ensure!(err == Win32Error::IoPending, Err(err.into())); Ok(state .overlapped .get_result(&self.device_handle, Some(1000))?) } else { Ok(0) } } fn read(&self, buf: &mut [u8]) -> HidResult { self.read_timeout(buf, if self.blocking.get() { -1 } else { 0 }) } fn read_timeout(&self, buf: &mut [u8], timeout: i32) -> HidResult { ensure!(!buf.is_empty(), Err(HidError::InvalidZeroSizeData)); let mut bytes_read = 0; let mut io_runnig = false; let mut state = self.read_state.borrow_mut(); if !self.read_pending.get() { self.read_pending.set(true); state.clear_buffer(); let res = unsafe { ResetEvent(state.overlapped.event_handle()); ReadFile( self.device_handle.as_raw(), state.buffer_ptr() as _, state.buffer_len() as u32, &mut bytes_read, state.overlapped.as_raw(), ) }; if res != TRUE { let err = Win32Error::last(); if err != Win32Error::IoPending { unsafe { CancelIoEx(self.device_handle.as_raw(), state.overlapped.as_raw()) }; self.read_pending.set(false); return Err(err.into()); } io_runnig = true; } } else { io_runnig = true; } if io_runnig { let res = state .overlapped .get_result(&self.device_handle, u32::try_from(timeout).ok()); bytes_read = match res { Ok(written) => written as u32, //There was no data this time. Return zero bytes available, but leave the Overlapped I/O running. Err(WinError::WaitTimedOut) => return Ok(0), Err(err) => { self.read_pending.set(false); return Err(err.into()); } }; } self.read_pending.set(false); let mut copy_len = 0; if bytes_read > 0 { // If report numbers aren't being used, but Windows sticks a report // number (0x0) on the beginning of the report anyway. To make this // work like the other platforms, and to make it work more like the // HID spec, we'll skip over this byte. if state.buffer[0] == 0x0 { bytes_read -= 1; copy_len = usize::min(bytes_read as usize, buf.len()); buf[..copy_len].copy_from_slice(&state.buffer[1..(1 + copy_len)]); } else { copy_len = usize::min(bytes_read as usize, buf.len()); buf[..copy_len].copy_from_slice(&state.buffer[0..copy_len]); } } Ok(copy_len) } fn send_feature_report(&self, data: &[u8]) -> HidResult<()> { ensure!(!data.is_empty(), Err(HidError::InvalidZeroSizeData)); let mut state = self.feature_state.borrow_mut(); state.fill_buffer(data); check_boolean(unsafe { HidD_SetFeature( self.device_handle.as_raw(), state.buffer_ptr() as _, state.buffer_len() as u32, ) })?; Ok(()) } /// Set the first byte of `buf` to the 'Report ID' of the report to be read. /// Upon return, the first byte will still contain the Report ID, and the /// report data will start in `buf[1]`. fn get_feature_report(&self, buf: &mut [u8]) -> HidResult { #[allow(clippy::identity_op, clippy::double_parens)] const IOCTL_HID_GET_FEATURE: u32 = ((0x0000000b) << 16) | ((0) << 14) | ((100) << 2) | (2); ensure!(!buf.is_empty(), Err(HidError::InvalidZeroSizeData)); let mut state = self.feature_state.borrow_mut(); let mut bytes_returned = 0; let res = unsafe { ResetEvent(state.overlapped.event_handle()); DeviceIoControl( self.device_handle.as_raw(), IOCTL_HID_GET_FEATURE, buf.as_mut_ptr() as _, buf.len() as u32, buf.as_mut_ptr() as _, buf.len() as u32, &mut bytes_returned, state.overlapped.as_raw(), ) }; if res != TRUE { let err = Win32Error::last(); ensure!(err == Win32Error::IoPending, Err(err.into())) } bytes_returned = state.overlapped.get_result(&self.device_handle, None)? as u32; if buf[0] == 0x0 { bytes_returned += 1; } Ok(bytes_returned as usize) } fn set_blocking_mode(&self, blocking: bool) -> HidResult<()> { self.blocking.set(blocking); Ok(()) } fn get_manufacturer_string(&self) -> HidResult> { Ok(self.device_info.manufacturer_string().map(String::from)) } fn get_product_string(&self) -> HidResult> { Ok(self.device_info.product_string().map(String::from)) } fn get_serial_number_string(&self) -> HidResult> { Ok(self.device_info.serial_number().map(String::from)) } fn get_indexed_string(&self, index: i32) -> HidResult> { let mut buf = [0u16; STRING_BUF_LEN]; let res = unsafe { HidD_GetIndexedString( self.device_handle.as_raw(), index as u32, buf.as_mut_ptr() as _, STRING_BUF_LEN as u32, ) }; check_boolean(res)?; Ok(buf.split(|c| *c == 0).map(String::from_utf16_lossy).next()) } fn get_device_info(&self) -> HidResult { Ok(self.device_info.clone()) } fn get_report_descriptor(&self, buf: &mut [u8]) -> HidResult { let desc = descriptor::get_descriptor(&PreparsedData::load(&self.device_handle)?)?; let size = buf.len().min(desc.len()); buf[..size].copy_from_slice(&desc[..size]); Ok(size) } } impl HidDeviceBackendWindows for HidDevice { fn get_container_id(&self) -> HidResult { let path = U16String::try_from(self.device_info.path()).expect("device path is not valid unicode"); let device_id: U16String = Interface::get_property(&path, DEVPKEY_Device_InstanceId)?; let dev_node = DevNode::from_device_id(&device_id)?; let guid = dev_node.get_property(DEVPKEY_Device_ContainerId)?; Ok(guid) } } impl Drop for HidDevice { fn drop(&mut self) { unsafe { for state in [ &mut self.read_state, &mut self.write_state, &mut self.feature_state, ] { let mut state = state.borrow_mut(); if CancelIoEx(self.device_handle.as_raw(), state.overlapped.as_raw()) > 0 { _ = state.overlapped.get_result(&self.device_handle, None); } } } } } fn enumerate_devices(vendor_id: u16, product_id: u16) -> WinResult> { Ok(Interface::get_interface_list()? .iter() .filter_map(|device_interface| { let device_handle = open_device(device_interface, false).ok()?; let attrib = get_hid_attributes(&device_handle); ((vendor_id == 0 || attrib.VendorID == vendor_id) && (product_id == 0 || attrib.ProductID == product_id)) .then(|| get_device_info(device_interface, &device_handle)) }) .collect()) } fn open_device(path: &U16Str, open_rw: bool) -> WinResult { let handle = unsafe { CreateFileW( path.as_ptr(), match open_rw { true => GENERIC_WRITE | GENERIC_READ, false => 0, }, FILE_SHARE_READ | FILE_SHARE_WRITE, null(), OPEN_EXISTING, FILE_FLAG_OVERLAPPED, 0, ) }; ensure!( handle != INVALID_HANDLE_VALUE, Err(Win32Error::last().into()) ); Ok(Handle::from_raw(handle)) } fn open(vid: u16, pid: u16, sn: Option<&str>) -> HidResult { let dev = enumerate_devices(vid, pid)? .into_iter() .filter(|dev| dev.vendor_id == vid && dev.product_id == pid) .find(|dev| sn.map_or(true, |sn| dev.serial_number().is_some_and(|n| sn == n))) .ok_or(HidError::HidApiErrorEmpty)?; open_path(dev.path()) } fn open_path(device_path: &CStr) -> HidResult { let device_path = U16String::try_from(device_path).unwrap(); let handle = open_device(&device_path, true) // System devices, such as keyboards and mice, cannot be opened in // read-write mode, because the system takes exclusive control over // them. This is to prevent keyloggers. However, feature reports // can still be sent and received. Retry opening the device, but // without read/write access. .or_else(|_| open_device(&device_path, false))?; check_boolean(unsafe { HidD_SetNumInputBuffers(handle.as_raw(), 64) })?; let caps = PreparsedData::load(&handle)?.get_caps()?; let device_info = get_device_info(&device_path, &handle); let dev = HidDevice { device_handle: handle, blocking: Cell::new(true), read_pending: Cell::new(false), read_state: RefCell::new(AsyncState::new(caps.InputReportByteLength as usize)), write_state: RefCell::new(AsyncState::new(caps.OutputReportByteLength as usize)), feature_state: RefCell::new(AsyncState::new(caps.FeatureReportByteLength as usize)), device_info, }; Ok(dev) } hidapi-2.6.3/src/windows_native/string.rs000064400000000000000000000135461046102023000166060ustar 00000000000000use crate::windows_native::types::DeviceProperty; use crate::WcharString; use std::ffi::CStr; use std::fmt::{Debug, Formatter}; use std::iter::once; use std::mem::size_of; use std::ops::{Deref, DerefMut}; use std::str::Utf8Error; use windows_sys::core::PCWSTR; use windows_sys::Win32::Devices::Properties::{ DEVPROPTYPE, DEVPROP_TYPE_STRING, DEVPROP_TYPE_STRING_LIST, }; #[repr(transparent)] pub struct U16Str([u16]); impl U16Str { unsafe fn from_slice_unsafe(slice: &[u16]) -> &Self { let ptr: *const [u16] = slice; &*(ptr as *const Self) } unsafe fn from_slice_mut_unsafe(slice: &mut [u16]) -> &mut Self { let ptr: *mut [u16] = slice; &mut *(ptr as *mut Self) } pub fn from_slice(slice: &[u16]) -> &Self { assert!( slice.last().is_some_and(is_null), "Slice is not null terminated" ); debug_assert_eq!( slice.iter().filter(|c| is_null(c)).count(), 1, "Found null character in the middle" ); unsafe { Self::from_slice_unsafe(slice) } } pub fn from_slice_mut(slice: &mut [u16]) -> &mut Self { assert!( slice.last().is_some_and(is_null), "Slice is not null terminated" ); debug_assert_eq!( slice.iter().filter(|c| is_null(c)).count(), 1, "Found null character in the middle" ); unsafe { Self::from_slice_mut_unsafe(slice) } } pub fn from_slice_list(slice: &[u16]) -> impl Iterator { slice.split_inclusive(is_null).map(Self::from_slice) } pub fn from_slice_list_mut(slice: &mut [u16]) -> impl Iterator { slice.split_inclusive_mut(is_null).map(Self::from_slice_mut) } pub fn as_ptr(&self) -> PCWSTR { self.0.as_ptr() } pub fn as_slice(&self) -> &[u16] { &self.0[..self.0.len() - 1] } pub fn as_slice_mut(&mut self) -> &mut [u16] { let end = self.0.len() - 1; &mut self.0[..end] } pub fn make_uppercase_ascii(&mut self) { for c in self.as_slice_mut() { if let Ok(t) = u8::try_from(*c) { *c = t.to_ascii_uppercase().into(); } } } pub fn starts_with_ignore_case(&self, pattern: &str) -> bool { char::decode_utf16(self.as_slice().iter().copied()) .map(|r| r.unwrap_or(char::REPLACEMENT_CHARACTER)) .zip(pattern.chars()) .all(|(l, r)| l.eq_ignore_ascii_case(&r)) } pub fn find_index(&self, pattern: &str) -> Option { self.as_slice() .windows(pattern.encode_utf16().count()) .enumerate() .filter(|(_, ss)| { ss.iter() .copied() .zip(pattern.encode_utf16()) .all(|(l, r)| l == r) }) .map(|(i, _)| i) .next() } } impl ToString for U16Str { fn to_string(&self) -> String { String::from_utf16(self.as_slice()).expect("Invalid UTF-16") } } impl From<&U16Str> for WcharString { fn from(value: &U16Str) -> Self { String::from_utf16(value.as_slice()) .map(WcharString::String) .unwrap_or_else(|_| WcharString::Raw(value.as_slice().to_vec())) } } impl Debug for U16Str { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { for c in char::decode_utf16(self.as_slice().iter().copied()) { write!(f, "{}", c.unwrap_or(char::REPLACEMENT_CHARACTER))?; } Ok(()) } } pub struct U16String(Vec); impl Debug for U16String { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { write!(f, "{:?}", self.deref()) } } impl Deref for U16String { type Target = U16Str; fn deref(&self) -> &Self::Target { unsafe { U16Str::from_slice_unsafe(self.0.as_slice()) } } } impl DerefMut for U16String { fn deref_mut(&mut self) -> &mut Self::Target { unsafe { U16Str::from_slice_mut_unsafe(self.0.as_mut_slice()) } } } impl TryFrom<&CStr> for U16String { type Error = Utf8Error; fn try_from(value: &CStr) -> Result { Ok(Self( value.to_str()?.encode_utf16().chain(once(0)).collect(), )) } } impl From for WcharString { fn from(value: U16String) -> Self { (&*value).into() } } unsafe impl DeviceProperty for U16String { const TYPE: DEVPROPTYPE = DEVPROP_TYPE_STRING; fn create_sized(bytes: usize) -> Self { assert_eq!(bytes % size_of::(), 0); U16String(vec![0u16; bytes / size_of::()]) } fn as_ptr_mut(&mut self) -> *mut u8 { self.0.as_mut_ptr() as _ } fn validate(&self) { assert!( self.0.last().is_some_and(is_null), "Slice is not null terminated" ); debug_assert_eq!( self.0.iter().filter(|c| is_null(c)).count(), 1, "Found null character in the middle" ); } } pub struct U16StringList(pub Vec); unsafe impl DeviceProperty for U16StringList { const TYPE: DEVPROPTYPE = DEVPROP_TYPE_STRING_LIST; fn create_sized(bytes: usize) -> Self { assert_eq!(bytes % size_of::(), 0); U16StringList(vec![0u16; bytes / size_of::()]) } fn as_ptr_mut(&mut self) -> *mut u8 { self.0.as_mut_ptr() as _ } fn validate(&self) { assert!( self.0.last().is_some_and(is_null), "Slice is not null terminated" ); } } impl U16StringList { pub fn iter(&self) -> impl Iterator { U16Str::from_slice_list(self.0.as_slice()) } pub fn iter_mut(&mut self) -> impl Iterator { U16Str::from_slice_list_mut(self.0.as_mut_slice()) } } fn is_null(c: &u16) -> bool { *c == 0 } hidapi-2.6.3/src/windows_native/types.rs000064400000000000000000000070531046102023000164400ustar 00000000000000use crate::windows_native::error::{WinError, WinResult}; use crate::BusType; use std::mem::{size_of, zeroed}; use std::ptr::null; use windows_sys::core::GUID; use windows_sys::Win32::Devices::Properties::{DEVPROPKEY, DEVPROPTYPE, DEVPROP_TYPE_GUID}; use windows_sys::Win32::Foundation::{CloseHandle, FALSE, HANDLE, INVALID_HANDLE_VALUE, TRUE}; use windows_sys::Win32::System::Threading::{CreateEventW, INFINITE}; use windows_sys::Win32::System::IO::{GetOverlappedResultEx, OVERLAPPED}; use windows_sys::Win32::UI::Shell::PropertiesSystem::PROPERTYKEY; #[allow(clippy::missing_safety_doc)] pub unsafe trait DeviceProperty { const TYPE: DEVPROPTYPE; fn create_sized(bytes: usize) -> Self; fn as_ptr_mut(&mut self) -> *mut u8; fn validate(&self) {} } unsafe impl DeviceProperty for GUID { const TYPE: DEVPROPTYPE = DEVPROP_TYPE_GUID; fn create_sized(bytes: usize) -> Self { assert_eq!(bytes, size_of::()); GUID::from_u128(0) } fn as_ptr_mut(&mut self) -> *mut u8 { (self as *mut GUID) as *mut u8 } } pub trait PropertyKey: Copy { fn as_ptr(&self) -> *const DEVPROPKEY; } impl PropertyKey for DEVPROPKEY { fn as_ptr(&self) -> *const DEVPROPKEY { self } } impl PropertyKey for PROPERTYKEY { fn as_ptr(&self) -> *const DEVPROPKEY { self as *const PROPERTYKEY as _ } } #[derive(Debug, Copy, Clone, PartialEq, Eq)] pub enum InternalBusType { Unknown, Usb, Bluetooth, BluetoothLE, I2c, Spi, } impl From for BusType { fn from(value: InternalBusType) -> Self { match value { InternalBusType::Unknown => BusType::Unknown, InternalBusType::Usb => BusType::Usb, InternalBusType::Bluetooth => BusType::Bluetooth, InternalBusType::BluetoothLE => BusType::Bluetooth, InternalBusType::I2c => BusType::I2c, InternalBusType::Spi => BusType::Spi, } } } pub struct Handle(HANDLE); impl Handle { pub fn from_raw(handle: HANDLE) -> Self { Self(handle) } pub fn as_raw(&self) -> HANDLE { self.0 } } impl Drop for Handle { fn drop(&mut self) { if self.0 != INVALID_HANDLE_VALUE { unsafe { CloseHandle(self.0); } } self.0 = INVALID_HANDLE_VALUE; } } pub struct Overlapped(OVERLAPPED); impl Overlapped { pub fn event_handle(&self) -> HANDLE { self.0.hEvent } pub fn as_raw(&mut self) -> *mut OVERLAPPED { &mut self.0 } pub fn get_result(&mut self, handle: &Handle, timeout: Option) -> WinResult { let mut bytes_written = 0; let cr = unsafe { GetOverlappedResultEx( handle.as_raw(), self.as_raw(), &mut bytes_written, timeout.unwrap_or(INFINITE), FALSE, ) }; ensure!(cr == TRUE, Err(WinError::last())); Ok(bytes_written as usize) } } unsafe impl Send for Overlapped {} impl Default for Overlapped { fn default() -> Self { Overlapped(unsafe { OVERLAPPED { //todo check if event is null hEvent: CreateEventW(null(), FALSE, FALSE, null()), ..zeroed() } }) } } impl Drop for Overlapped { fn drop(&mut self) { if self.0.hEvent != INVALID_HANDLE_VALUE { unsafe { CloseHandle(self.0.hEvent); } } self.0.hEvent = INVALID_HANDLE_VALUE; } } hidapi-2.6.3/src/windows_native/utils.rs000064400000000000000000000011671046102023000164340ustar 00000000000000pub trait PeakIterExt { fn peaking(self) -> PeakingIter; } impl PeakIterExt for T { fn peaking(mut self) -> PeakingIter { PeakingIter { next: self.next(), inner: self, } } } pub struct PeakingIter { inner: T, next: Option, } impl> Iterator for PeakingIter { type Item = (I::Item, Option); fn next(&mut self) -> Option { let current = self.next.take(); self.next = self.inner.next(); current.map(|v| (v, self.next)) } }