hidapi-2.4.1/.github/workflows/rust.yml000064400000000000000000000056131046102023000162100ustar 00000000000000name: Rust on: push: branches: [ master ] pull_request: branches: [ master ] env: CARGO_TERM_COLOR: always jobs: build-linux: runs-on: ubuntu-latest env: DEBIAN_FRONTEND: noninteractive strategy: fail-fast: false # don't give up on the whole matrix if one variant fails matrix: linkage: ["static", "shared"] library: ["hidraw", "libusb"] steps: - name: checkout repository and submodules uses: actions/checkout@v2 with: submodules: recursive - name: Install dependencies run: | sudo apt-get update -y sudo apt-get install -y cmake libudev-dev libumockdev-dev umockdev - name: Build libusb run: | git clone https://github.com/libusb/libusb.git ./etc/libusb/ cd ./etc/libusb/ ./autogen.sh make sudo make install - name: Build hidapi run: | cd ./etc/hidapi/ mkdir ./build/ cd ./build/ cmake .. make sudo make install - name: List pkgconfig definitions run: grep -RHn ^ /usr/local/lib/pkgconfig - name: Build run: cargo build --no-default-features --features linux-${{ matrix.linkage }}-${{ matrix.library }} --verbose - name: Run tests run: cargo test --no-default-features --features linux-${{ matrix.linkage }}-${{ matrix.library }} --verbose build-linux-native: runs-on: ubuntu-latest env: DEBIAN_FRONTEND: noninteractive steps: - name: checkout repository and submodules uses: actions/checkout@v2 with: submodules: recursive - name: Install dependencies run: | sudo apt-get update -y sudo apt-get install -y libudev-dev - name: Build run: cargo build --no-default-features --features linux-native --verbose - name: Run tests run: cargo test --no-default-features --features linux-native --verbose build-windows: runs-on: windows-latest steps: - name: checkout repository and submodules uses: actions/checkout@v2 with: submodules: recursive - name: Install rust environment run: | curl.exe --proto "=https" --tlsv1.2 -L -o rustup-init.exe https://static.rust-lang.org/rustup/dist/x86_64-pc-windows-msvc/rustup-init.exe .\rustup-init.exe -y - name: Build run: cargo build --no-default-features --verbose - name: Run tests run: cargo test --no-default-features --verbose build-macos: runs-on: macos-latest steps: - name: checkout repository and submodules uses: actions/checkout@v2 with: submodules: recursive - name: Install rust environment run: | curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y - name: Build run: cargo build --no-default-features --verbose - name: Run tests run: cargo test --no-default-features --verbose hidapi-2.4.1/.gitignore000064400000000000000000000000471046102023000130570ustar 00000000000000target Cargo.lock .idea/ hidapi-rs.iml hidapi-2.4.1/.gitmodules000064400000000000000000000001301046102023000132350ustar 00000000000000[submodule "etc/hidapi"] path = etc/hidapi url = https://github.com/libusb/hidapi.git hidapi-2.4.1/.travis.yml000064400000000000000000000012351046102023000132000ustar 00000000000000language: rust matrix: include: - env: TARGET=x86_64-unknown-linux-gnu FEATURE_FLAGS="linux-static-libusb" - env: TARGET=x86_64-unknown-linux-gnu FEATURE_FLAGS="linux-static-hidraw" - env: TARGET=x86_64-unknown-linux-gnu FEATURE_FLAGS="linux-shared-libusb" - env: TARGET=x86_64-unknown-linux-gnu FEATURE_FLAGS="linux-shared-hidraw" - env: TARGET=x86_64-apple-darwin os: osx script: - cargo build --verbose --no-default-features --features="${FEATURE_FLAGS}" - cargo test --verbose --no-default-features --features="${FEATURE_FLAGS}" addons: apt: packages: - libusb-1.0-0-dev - libudev-dev - libhidapi-devhidapi-2.4.1/.vscode/settings.json000064400000000000000000000002751046102023000151660ustar 00000000000000{ "rust-analyzer.linkedProjects": [ "./Cargo.toml" ], "rust-analyzer.cargo.features": [ "linux-native" ], "rust-analyzer.cargo.noDefaultFeatures": true }hidapi-2.4.1/Cargo.lock0000644000000066050000000000100102600ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 3 [[package]] name = "autocfg" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" [[package]] name = "bitflags" version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[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 = "hidapi" version = "2.4.1" dependencies = [ "cc", "libc", "nix", "pkg-config", "udev", "winapi", ] [[package]] name = "libc" version = "0.2.141" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3304a64d199bb964be99741b7a14d26972741915b3649639149b2479bb46f4b5" [[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 = "memoffset" version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5de893c32cde5f383baa4c04c5d6dbdd735cfd4a794b0debdb2bb1b421da5ff4" dependencies = [ "autocfg", ] [[package]] name = "nix" version = "0.26.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bfdda3d196821d6af13126e40375cdf7da646a96114af134d5f417a9a1dc8e1a" dependencies = [ "bitflags", "cfg-if", "libc", "memoffset", "pin-utils", "static_assertions", ] [[package]] name = "pin-utils" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "pkg-config" version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160" [[package]] name = "static_assertions" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" [[package]] name = "udev" version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ebdbbd670373442a12fe9ef7aeb53aec4147a5a27a00bbc3ab639f08f48191a" dependencies = [ "libc", "libudev-sys", "pkg-config", ] [[package]] name = "winapi" version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" dependencies = [ "winapi-i686-pc-windows-gnu", "winapi-x86_64-pc-windows-gnu", ] [[package]] name = "winapi-i686-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" hidapi-2.4.1/Cargo.toml0000644000000033540000000000100103010ustar # 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.4.1" authors = [ "Roland Ruckerbauer ", "Osspial ", "Artyom Pavlov ", "mberndt123", "niklasad1", "Stefan Kerkmann", ] build = "build.rs" links = "hidapi" 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", ] [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 = [] [target."cfg(target_os = \"linux\")".dependencies.nix] version = "0.26" optional = true [target."cfg(target_os = \"linux\")".dependencies.udev] version = "0.7" optional = true [target."cfg(windows)".dependencies.winapi] version = "0.3.9" hidapi-2.4.1/Cargo.toml.orig0000644000000021550000000000100112360ustar [package] name = "hidapi" version = "2.4.1" 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" [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 = [] [dependencies] libc = "0.2" [target.'cfg(target_os = "linux")'.dependencies] udev = { version = "0.7", optional = true } nix = { version = "0.26", optional = true } [target.'cfg(windows)'.dependencies] winapi = "0.3.9" [build-dependencies] cc = "1.0" pkg-config = "0.3" [package.metadata.docs.rs] rustdoc-args = ["--cfg", "docsrs"] hidapi-2.4.1/Cargo.toml.orig000064400000000000000000000021551046102023000137600ustar 00000000000000[package] name = "hidapi" version = "2.4.1" 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" [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 = [] [dependencies] libc = "0.2" [target.'cfg(target_os = "linux")'.dependencies] udev = { version = "0.7", optional = true } nix = { version = "0.26", optional = true } [target.'cfg(windows)'.dependencies] winapi = "0.3.9" [build-dependencies] cc = "1.0" pkg-config = "0.3" [package.metadata.docs.rs] rustdoc-args = ["--cfg", "docsrs"] hidapi-2.4.1/LICENSE.txt000064400000000000000000000020501046102023000127060ustar 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.4.1/README.md000064400000000000000000000026201046102023000123450ustar 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.4.1/build.rs000064400000000000000000000156771046102023000125530ustar 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(); 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() { 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.4.1/examples/co2mon.rs000064400000000000000000000073371046102023000144610ustar 00000000000000/**************************************************************************** Copyright (c) 2015 Artyom Pavlov All Rights Reserved. This file is part of hidapi-rs, based on hidapi_rust by Roland Ruckerbauer. It's also based on the Oleg Bulatov's work (https://github.com/dmage/co2mon) ****************************************************************************/ //! Opens a KIT MT 8057 CO2 detector and reads data from it. This //! example will not work unless such device is plugged into your system. use hidapi::{HidApi, HidError}; use std::io; const CODE_TEMPERATURE: u8 = 0x42; const CODE_CONCENTRATION: u8 = 0x50; const HID_TIMEOUT: i32 = 5000; const DEV_VID: u16 = 0x04d9; const DEV_PID: u16 = 0xa052; const PACKET_SIZE: usize = 8; type Packet = [u8; PACKET_SIZE]; enum CO2Result { Temperature(f32), Concentration(u16), Unknown(u8, u16), Error(&'static str), } fn decode_temperature(value: u16) -> f32 { (value as f32) * 0.0625 - 273.15 } fn decrypt(buf: Packet) -> Packet { let mut res: [u8; PACKET_SIZE] = [ (buf[3] << 5) | (buf[2] >> 3), (buf[2] << 5) | (buf[4] >> 3), (buf[4] << 5) | (buf[0] >> 3), (buf[0] << 5) | (buf[7] >> 3), (buf[7] << 5) | (buf[1] >> 3), (buf[1] << 5) | (buf[6] >> 3), (buf[6] << 5) | (buf[5] >> 3), (buf[5] << 5) | (buf[3] >> 3), ]; let magic_word = b"Htemp99e"; for i in 0..PACKET_SIZE { let sub_val: u8 = (magic_word[i] << 4) | (magic_word[i] >> 4); res[i] = u8::wrapping_sub(res[i], sub_val); } res } fn decode_buf(buf: Packet) -> CO2Result { // Do we need to decrypt the data? let res = if buf[4] == 0x0d { buf } else { decrypt(buf) }; let kind = res[0]; let val = u16::from_be_bytes(res[1..3].try_into().unwrap()); let checksum = res[3]; let tail = res[4]; if tail != 0x0d { return CO2Result::Error("Unexpected data (data[4] != 0x0d)"); } let checksum_calc = res[0].wrapping_add(res[1]).wrapping_add(res[2]); if checksum != checksum_calc { return CO2Result::Error("Checksum error"); } match kind { CODE_TEMPERATURE => CO2Result::Temperature(decode_temperature(val)), CODE_CONCENTRATION => { if val > 3000 { CO2Result::Error("Concentration bigger than 3000 (uninitialized device?)") } else { CO2Result::Concentration(val) } } _ => CO2Result::Unknown(res[0], val), } } fn invalid_data_err(msg: impl Into) -> HidError { HidError::IoError { error: io::Error::new(io::ErrorKind::InvalidData, msg.into()), } } fn main() -> Result<(), HidError> { let api = HidApi::new()?; let dev = api.open(DEV_VID, DEV_PID)?; dev.send_feature_report(&[0; PACKET_SIZE])?; if let Some(manufacturer) = dev.get_manufacturer_string()? { println!("Manufacurer:\t{manufacturer}"); } if let Some(product) = dev.get_product_string()? { println!("Product:\t{product}"); } if let Some(serial_number) = dev.get_serial_number_string()? { println!("Serial number:\t{serial_number}"); } let mut buf = [0; PACKET_SIZE]; loop { let n = dev.read_timeout(&mut buf[..], HID_TIMEOUT)?; if n != PACKET_SIZE { let msg = format!("unexpected packet length: {n}/{PACKET_SIZE}"); return Err(invalid_data_err(msg)); } match decode_buf(buf) { CO2Result::Temperature(val) => println!("Temp:\t{val}"), CO2Result::Concentration(val) => println!("Conc:\t{val}"), CO2Result::Unknown(..) => (), CO2Result::Error(msg) => { return Err(invalid_data_err(msg)); } } } } hidapi-2.4.1/examples/logitech_gprox.rs000064400000000000000000000013301046102023000162640ustar 00000000000000/**************************************************************************** Copyright (c) 2022 ruabmbua All Rights Reserved. ****************************************************************************/ //! Sets the sidechannel volume of the logitech gpro x headset extern crate hidapi; use hidapi::HidApi; use std::error::Error; fn main() -> Result<(), Box> { let vol = std::env::args() .nth(1) .map(|arg| arg.parse::()) .ok_or("missing sidechannel volume arg")?? .min(100); let api = HidApi::new()?; let dev = api.open(0x046d, 0x0aaa)?; println!("Setting sidechannel volume to {}", vol); dev.write(&[0x11, 0xff, 0x05, 0x1c, vol])?; Ok(()) } hidapi-2.4.1/examples/lshid.rs000064400000000000000000000022071046102023000143560ustar 00000000000000/**************************************************************************** Copyright (c) 2015 Osspial All Rights Reserved. This file is part of hidapi-rs, based on hidapi_rust by Roland Ruckerbauer. ****************************************************************************/ //! Prints out a list of HID devices 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!( "VID: {:04x}, PID: {:04x}, Serial: {}, Product name: {}", device.vendor_id(), device.product_id(), match device.serial_number() { Some(s) => s, _ => "", }, match device.product_string() { Some(s) => s, _ => "", } ); } } Err(e) => { eprintln!("Error: {}", e); } } } hidapi-2.4.1/examples/open_first_device.rs000064400000000000000000000022571046102023000167470ustar 00000000000000/**************************************************************************** Copyright (c) 2018 Roland Ruckerbauer All Rights Reserved. This file is part of hidapi-rs, based on hidapi-rs by Osspial ****************************************************************************/ //! Opens the first hid device it can find, and reads data in a blocking fashion //! from it in an endless loop. extern crate hidapi; use hidapi::{HidApi, HidError}; fn main() { fn run() -> Result<(), HidError> { let hidapi = HidApi::new()?; let device_info = hidapi .device_list() .next() .expect("No devices are available!") .clone(); println!( "Opening device:\n VID: {:04x}, PID: {:04x}\n", device_info.vendor_id(), device_info.product_id() ); let device = device_info.open_device(&hidapi)?; let mut buf = vec![0; 64]; println!("Reading data from device ...\n"); loop { let len = device.read(&mut buf)?; println!("{:?}", &buf[..len]); } } if let Err(e) = run() { eprintln!("Error: {}", e); } } hidapi-2.4.1/examples/readhid.rs000064400000000000000000000017321046102023000146550ustar 00000000000000/**************************************************************************** Copyright (c) 2015 Osspial All Rights Reserved. This file is part of hidapi-rs, based on hidapi_rust by Roland Ruckerbauer. ****************************************************************************/ //! Opens a Thrustmaster T-Flight HOTAS X HID and reads data from it. This //! example will not work unless such an HID is plugged in to your system. //! Will update in the future to support all HIDs. extern crate hidapi; use hidapi::HidApi; fn main() { let api = HidApi::new().expect("Failed to create API instance"); let joystick = api.open(1103, 45320).expect("Failed to open device"); loop { let mut buf = [0u8; 256]; let res = joystick.read(&mut buf[..]).unwrap(); let mut data_string = String::new(); for u in &buf[..res] { data_string.push_str(&(u.to_string() + "\t")); } println!("{}", data_string); } } hidapi-2.4.1/examples/static_lifetime_bound.rs000064400000000000000000000023611046102023000176100ustar 00000000000000/**************************************************************************** Copyright (c) 2015 Osspial All Rights Reserved. This file is part of hidapi-rs, based on hidapi_rust by Roland Ruckerbauer. ****************************************************************************/ //! This example shows the added possibility (after version 0.4.1), //! to move devices into a function / or closure with static lifetime bounds. extern crate hidapi; use hidapi::{HidApi, HidDevice}; use std::rc::Rc; fn main() { let _dev = test_lt(); } fn requires_static_lt_bound(f: F) { f(); } fn test_lt() -> Rc { let api = HidApi::new().expect("Hidapi init failed"); let mut devices = api.device_list(); let dev_info = devices .next() .expect("There is not a single hid device available"); let dev = Rc::new( api.open(dev_info.vendor_id(), dev_info.product_id()) .expect("Can not open device"), ); let dev_1 = dev.clone(); requires_static_lt_bound(move || { println!("{:?}", dev_1.get_device_info().unwrap()); //, }, /// 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.4.1/src/ffi.rs000064400000000000000000000101761046102023000127740ustar 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 winapi::shared::guiddef::GUID; use super::*; extern "C" { pub fn hid_winapi_get_container_id( device: *mut HidDevice, container_id: *mut GUID, ) -> c_int; } } hidapi-2.4.1/src/hidapi/macos.rs000064400000000000000000000017201046102023000145630ustar 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.4.1/src/hidapi/windows.rs000064400000000000000000000013201046102023000151470ustar 00000000000000//! The extra behaviour for Windows use std::ptr::addr_of_mut; use winapi::shared::guiddef::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.4.1/src/hidapi.rs000064400000000000000000000240671046102023000134720ustar 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() -> HidResult> { let mut device_vector = Vec::with_capacity(8); let enumeration = unsafe { ffi::hid_enumerate(0, 0) }; { 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.4.1/src/lib.rs000064400000000000000000000522171046102023000130000ustar 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); //! }, //! } //! } //! ``` //! //! # 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 //! //! ## 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. extern crate libc; #[cfg(all(feature = "linux-native", target_os = "linux"))] extern crate nix; #[cfg(target_os = "windows")] extern crate winapi; #[cfg(target_os = "windows")] use winapi::shared::guiddef::GUID; mod error; mod ffi; #[cfg(hidapi)] mod hidapi; #[cfg(all(feature = "linux-native", target_os = "linux"))] #[cfg_attr(docsrs, doc(cfg(all(feature = "linux-native", target_os = "linux"))))] mod linux_native; #[cfg(target_os = "macos")] #[cfg_attr(docsrs, doc(cfg(target_os = "macos")))] mod macos; #[cfg(target_os = "windows")] #[cfg_attr(docsrs, doc(cfg(target_os = "windows")))] mod windows; 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(hidapi)] use crate::hidapi::HidApiBackend; #[cfg(all(feature = "linux-native", target_os = "linux"))] use linux_native::HidApiBackend; 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 device_list = HidApiBackend::get_hid_device_info_vector()?; Ok(HidApi { device_list }) } /// 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) pub fn refresh_devices(&mut self) -> HidResult<()> { let device_list = HidApiBackend::get_hid_device_info_vector()?; self.device_list = device_list; Ok(()) } /// Returns iterator containing information about attached HID 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 self.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() } } #[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(), }) } } /// A trait with the extra methods that are available on macOS #[cfg(target_os = "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; } /// A trait with the extra methods that are available on macOS #[cfg(target_os = "windows")] trait HidDeviceBackendWindows { /// Get the container ID for a HID device fn get_container_id(&self) -> HidResult; } #[cfg(not(any(target_os = "macos", target_os = "windows")))] trait HidDeviceBackend: HidDeviceBackendBase + Send {} #[cfg(target_os = "macos")] trait HidDeviceBackend: HidDeviceBackendBase + HidDeviceBackendMacos + Send {} #[cfg(target_os = "windows")] trait HidDeviceBackend: HidDeviceBackendBase + HidDeviceBackendWindows + Send {} /// Automatically implement the top trait #[cfg(not(any(target_os = "macos", target_os = "windows")))] impl HidDeviceBackend for T where T: HidDeviceBackendBase + Send {} /// Automatically implement the top trait #[cfg(target_os = "macos")] impl HidDeviceBackend for T where T: HidDeviceBackendBase + HidDeviceBackendMacos + Send {} /// Automatically implement the top trait #[cfg(target_os = "windows")] impl HidDeviceBackend for T where T: HidDeviceBackendBase + HidDeviceBackendWindows + Send {} 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() } /// 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). pub fn write(&self, data: &[u8]) -> HidResult { self.inner.write(data) } /// 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. pub fn read(&self, buf: &mut [u8]) -> HidResult { self.inner.read(buf) } /// 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. 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. pub fn send_feature_report(&self, data: &[u8]) -> HidResult<()> { self.inner.send_feature_report(data) } /// 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]`. 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.4.1/src/linux_native/ioctl.rs000064400000000000000000000011251046102023000160410ustar 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.4.1/src/linux_native.rs000064400000000000000000000473741046102023000147470ustar 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::{AsRawFd, 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() -> 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() .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()? .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 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.as_raw_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.4.1/src/macos.rs000064400000000000000000000022671046102023000133340ustar 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.4.1/src/windows.rs000064400000000000000000000006711046102023000137210ustar 00000000000000use winapi::shared::guiddef::GUID; use crate::{HidDevice, HidResult}; 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.4.1/tests/assets/mouse1.data000064400000000000000000000000321046102023000155710ustar 00000000000000 *&uhidapi-2.4.1/tests/assets/mouse2.data000064400000000000000000000002511046102023000155750ustar 00000000000000   )%uu 0 1&u 8%u 8 )%uu &*u u& u&