Single header C++23 wrapper for libusb/hidapi.
- Run
bootstrap.[bat|sh]. This will install doctest + hidapi, and create the project under the./builddirectory. - Run cmake on the
./builddirectory and toggleBUILD_TESTS. - Configure, generate, make.
The cmake project exports the hid::hid target, hence you can:
find_package (hid CONFIG REQUIRED)
target_link_libraries([YOUR_PROJECT_NAME] PRIVATE hid::hid)And then point the hid_DIR to the build or installation directory.
Alternatively, you can just copy include/hid/hid.hpp to your project.
#include <hid/hid.hpp>
#include <iostream>
int main(int argc, char** argv)
{
// Enumerate and print devices.
const auto device_infos = hid::enumerate();
for (const auto& device_info : device_infos)
std::wcout << device_info << "\n";
if (!device_infos.empty())
{
// Open the first device.
const auto device = hid::open(device_infos[0]);
if (device)
{
// Print its report descriptor.
const auto descriptor = device->report_descriptor();
std::wcout << "report_descriptor: ";
for (const auto value : *descriptor)
std::wcout << std::format(L"{:#04x}", value) << " ";
std::wcout << "\n";
// Set it to non-blocking.
device->set_nonblocking(true);
// Read from it.
if (const auto result = device->read())
{
std::wcout << "read: ";
for (const auto value : *result)
std::wcout << std::format(L"{:#04x}", value) << " ";
std::wcout << "\n";
}
else
std::wcout << "read failed with error: " << result.error() << "\n";
// Write to it.
std::vector<std::uint8_t> data(8, 0x00);
if (const auto result = device->write(data))
std::wcout << "wrote " << *result << " bytes.\n";
else
std::wcout << "write failed with error: " << result.error() << "\n";
}
else
std::wcout << "open failed with error: " << device.error() << "\n";
}
return 0;
}See the tests for more.
- Enums:
hid_bus_typeis wrapped tohid::bus_typewhich is a strongly-typed enum.
- Structs:
hid_device_infois wrapped tohid::device_infowhich contains (wide) strings instead of (wide) char arrays.hid_api_versionis aliased tohid::api_version.
- Functions:
- Free functions:
C Function Signature C++ Function Signature Comments hid_error (nullptr) -> const wchar_t*hid::error () -> std::wstringFree function for the null device case. Does not return an std::expected<...>since it is guaranteed to not fail.hid_init () -> inthid::init () -> std::expected<void, std::wstring>The unexpected state contains the result of hid::error().hid_exit () -> inthid::exit () -> std::expected<void, std::wstring>The unexpected state contains the result of hid::error().hid_enumerate (unsigned short, unsigned short) -> hid_device_info*hid::enumerate (std::uint16_t = 0, std::uint16_t = 0) -> std::vector <hid::device_info>Copies the hid_device_info*linked list to astd::vector<hid::device_info>and frees it. Does not return anstd::expected<...>since a null result does not necessarily imply a failure, but no matching devices. The user is responsible for callinghid::error()manually for checking the reason. Open to better solutions.hid_free_enumeration (hid_device_info*) -> voidN/A Called internally within hid::enumerate(...).hid_open (unsigned short, unsigned short, const wchar_t*) -> hid_device*hid::open (std::uint16_t, std::uint16_t, const std::optional <std::wstring>& = std::nullopt) -> std::expected<hid::device, std::wstring>The unexpected state contains the result of hid::error(). The reason to use a free function rather than thehid::deviceconstructors is that the constructors can not transmit errors unless they throw (which this library avoids).hid_open_path (const char*) -> hid_device*hid::open (const std::string&) -> std::expected<hid::device, std::wstring>Operator overloading enables using the same name for the two functions. The unexpected state contains the result of hid::error(). The reason to use a free function rather than thehid::deviceconstructors is that the constructors can not transmit errors unless they throw (which this library avoids).N/A hid::open (const hid::device_info&) -> std::expected<hid::device, std::wstring>Convenience for hid::open(device_info.path)and (if that fails) forhid::open(device_info.vendor_id, device_info.product_id, device_info.serial_number).hid_version () -> const hid_api_version*hid::version () -> const hid::api_version&Does not return an std::expected<...>since it is guaranteed to not fail.hid_version_str () -> const char*hid::version_str () -> std::stringDoes not return an std::expected<...>since it is guaranteed to not fail. - Device functions: Functions accepting a
hid_device*as their first parameter are members of thehid::deviceclass.C Function Signature C++ Function Signature Comments hid_close (hid_device*) -> voidhid::device::~device ()Destructor closes the device on scope exit. N/A hid::device::native () -> const hid_device*Accessor to native hid_device*. hid_get_device_info (hid_device*) -> hid_device_info*hid::device::device_info () -> std::expected<device_info, std::wstring>The unexpected state contains the result of hid::error().hid_get_serial_number_string (hid_device*, wchar_t*, size_t) -> inthid::device::serial_number (std::size_t = 256) -> std::expected<std::wstring, std::wstring>The unexpected state contains the result of hid::error(). Default maximum length is 256.hid_get_manufacturer_string (hid_device*, wchar_t*, size_t) -> inthid::device::manufacturer_string (std::size_t = 256) -> std::expected<std::wstring, std::wstring>The unexpected state contains the result of hid::error(). Default maximum length is 256.hid_get_product_string (hid_device*, wchar_t*, size_t) -> inthid::device::product_string (std::size_t = 256) -> std::expected<std::wstring, std::wstring>The unexpected state contains the result of hid::error(). Default maximum length is 256.hid_get_indexed_string (hid_device*, int, wchar_t*, size_t) -> inthid::device::indexed_string (std::int32_t, std::size_t = 256) -> std::expected<std::wstring, std::wstring>The unexpected state contains the result of hid::error(). Default maximum length is 256.hid_get_report_descriptor (hid_device*, unsigned char*, size_t) -> inthid::device::report_descriptor () -> std::expected< std::vector<std::uint8_t>, std::wstring>The unexpected state contains the result of hid::error().hid_get_input_report (hid_device*, unsigned char*, size_t) -> inthid::device::input_report (std::uint8_t id, std::size_t = 256) -> std::expected< std::vector<std::uint8_t>, std::wstring>The unexpected state contains the result of hid::error(). Default maximum length is 256.hid_get_feature_report (hid_device*, unsigned char*, size_t) -> inthid::device::feature_report (std::uint8_t id, std::size_t = 256) -> std::expected< std::vector<std::uint8_t>, std::wstring>The unexpected state contains the result of hid::error(). Default maximum length is 256.hid_read (hid_device*, unsigned char*, size_t) -> inthid::device::read (std::size_t = 256) -> std::expected< std::vector<std::uint8_t>, std::wstring>The unexpected state contains the result of hid::error(). Default maximum length is 256.hid_read_timeout (hid_device*, unsigned char*, size_t, int) -> inthid::device::read (std::chrono::duration< std::int32_t, std::milli>, std::size_t = 256) -> std::expected< std::vector<std::uint8_t>, std::wstring>The unexpected state contains the result of hid::error(). Default maximum length is 256.hid_write (hid_device*, const unsigned char*, size_t) -> inthid::device::write (const std::span<std::uint8_t>&) -> std::expected<std::int32_t, std::wstring>The unexpected state contains the result of hid::error().hid_send_feature_report (hid_device*, const unsigned char*, size_t) -> inthid::device::send_feature_report (const std::span<std::uint8_t>&) -> std::expected<std::int32_t, std::wstring>The unexpected state contains the result of hid::error().hid_set_nonblocking (hid_device*, int) -> inthid::device::set_nonblocking (bool) -> std::expected<void, std::wstring>The unexpected state contains the result of hid::error().hid_error (hid_device*) -> const wchar_t*hid::device::error () -> std::wstringMember function for the non-null device case. Does not return an std::expected<...>since it is guaranteed to not fail.
- Free functions:
- Platform-specific function wrappers.
- Consider casting/accepting output/input byte arrays to/as arbitrary types.
- Interacting with binary data could be eased through mapping text descriptors to binary descriptors and vice versa. This could also be used to determine and set the exact sizes of the reports without the user. See Frank Zhao's online parser as an example.