From ac96ef4bb6844a482c0f36e8215105c0138e3b1f Mon Sep 17 00:00:00 2001 From: Steven Stallion Date: Fri, 6 Jan 2023 22:30:30 -0600 Subject: [PATCH] Import libusb/hidapi 0.13.0 sources --- CHANGELOG.md | 2 + LICENSE | 2 +- cmd/lshid/main.go | 3 +- hid.go | 202 ++++++----- hid_darwin.c | 348 ++++++++++++++---- hid_darwin.go | 36 +- hid_libusb.c | 398 ++++++++++++++------- hid_linux.c | 877 ++++++++++++++++++++++++++-------------------- hid_windows.c | 223 +++++++++--- hidapi.h | 161 ++++++--- 10 files changed, 1493 insertions(+), 759 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cfb23eb..e4deebe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +- Imported libusb/hidapi 0.13.0 sources. + ## [0.12.4] - 2022-12-23 ### Fixed diff --git a/LICENSE b/LICENSE index 81187fc..7bd5f57 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2022 Steven Stallion +Copyright (c) 2023 Steven Stallion Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions diff --git a/cmd/lshid/main.go b/cmd/lshid/main.go index 1d0c09f..2e8ee38 100644 --- a/cmd/lshid/main.go +++ b/cmd/lshid/main.go @@ -1,4 +1,4 @@ -// Copyright (c) 2022 Steven Stallion +// Copyright (c) 2023 Steven Stallion // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions @@ -78,6 +78,7 @@ func main() { fmt.Printf("\tUsagePage %#x\n", info.UsagePage) fmt.Printf("\tUsage %#x\n", info.Usage) fmt.Printf("\tInterfaceNbr %d\n", info.InterfaceNbr) + fmt.Printf("\tBusType %s\n", info.BusType) fmt.Println() } return nil diff --git a/hid.go b/hid.go index 4000930..4b6d6d5 100644 --- a/hid.go +++ b/hid.go @@ -1,4 +1,4 @@ -// Copyright (c) 2022 Steven Stallion +// Copyright (c) 2023 Steven Stallion // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions @@ -41,6 +41,7 @@ import "C" import ( "errors" + "fmt" "io" "math" "time" @@ -88,50 +89,64 @@ func Exit() error { return nil } -// Error returns the last non-device-specific error that occurred. If no error -// occurred, nil is returned. -func Error() error { - wcs := C.hid_error(nil) - if wcs == nil { - return nil // no error - } - return errors.New(wcstogo(wcs)) -} +// BusType describes the underlying bus type. +type BusType int -// APIVersion describes the HIDAPI version. -type APIVersion struct { - Major int // Major version number - Minor int // Minor version number - Patch int // Patch version number +const ( + BusUnknown BusType = iota + BusUSB + BusBluetooth + BusI2C + BusSPI +) + +var _BusType_map = map[BusType]string{ + BusUnknown: "Unknown", + BusUSB: "USB", + BusBluetooth: "Bluetooth", + BusI2C: "I2C", + BusSPI: "SPI", } -// GetVersion returns the HIDAPI version. -func GetVersion() APIVersion { - v := C.hid_version() - return APIVersion{ - Major: int(v.major), - Minor: int(v.minor), - Patch: int(v.patch), +// String returns a string representation of the underlying bus type. +func (t BusType) String() string { + if str, ok := _BusType_map[t]; ok { + return str } + panic("invalid BusType") } -// GetVersion returns the HIDAPI version as a string. -func GetVersionStr() string { - return C.GoString(C.hid_version_str()) -} +var _ fmt.Stringer = BusType(0) // DeviceInfo describes a HID device attached to the system. type DeviceInfo struct { - Path string // Platform-Specific Device Path - VendorID uint16 // Device Vendor ID - ProductID uint16 // Device Product ID - SerialNbr string // Serial Number - ReleaseNbr uint16 // Device Version Number - MfrStr string // Manufacturer String - ProductStr string // Product String - UsagePage uint16 // Usage Page for Device/Interface - Usage uint16 // Usage for Device/Interface - InterfaceNbr int // USB Interface Number + Path string // Platform-Specific Device Path + VendorID uint16 // Device Vendor ID + ProductID uint16 // Device Product ID + SerialNbr string // Serial Number + ReleaseNbr uint16 // Device Version Number + MfrStr string // Manufacturer String + ProductStr string // Product String + UsagePage uint16 // Usage Page for Device/Interface + Usage uint16 // Usage for Device/Interface + InterfaceNbr int // USB Interface Number + BusType BusType // Underlying Bus Type +} + +func newDeviceInfo(p *C.struct_hid_device_info) *DeviceInfo { + return &DeviceInfo{ + Path: C.GoString(p.path), + VendorID: uint16(p.vendor_id), + ProductID: uint16(p.product_id), + SerialNbr: wcstogo(p.serial_number), + ReleaseNbr: uint16(p.release_number), + MfrStr: wcstogo(p.manufacturer_string), + ProductStr: wcstogo(p.product_string), + UsagePage: uint16(p.usage_page), + Usage: uint16(p.usage), + InterfaceNbr: int(p.interface_number), + BusType: BusType(p.bus_type), + } } // EnumFunc is the type of the function called for each HID device attached to @@ -148,19 +163,7 @@ func Enumerate(vid, pid uint16, enumFn EnumFunc) error { defer C.hid_free_enumeration(p) for p != nil { - info := &DeviceInfo{ - Path: C.GoString(p.path), - VendorID: uint16(p.vendor_id), - ProductID: uint16(p.product_id), - SerialNbr: wcstogo(p.serial_number), - ReleaseNbr: uint16(p.release_number), - MfrStr: wcstogo(p.manufacturer_string), - ProductStr: wcstogo(p.product_string), - UsagePage: uint16(p.usage_page), - Usage: uint16(p.usage), - InterfaceNbr: int(p.interface_number), - } - if err := enumFn(info); err != nil { + if err := enumFn(newDeviceInfo(p)); err != nil { return err } p = p.next @@ -173,6 +176,44 @@ type Device struct { handle *C.hid_device } +// Open opens a HID device attached to the system with a matching vendor ID, +// product ID, and serial number. It returns an open device handle and an +// error, if any. +func Open(vid, pid uint16, serial string) (*Device, error) { + wcs := gotowcs(serial) + defer C.free(unsafe.Pointer(wcs)) + + handle := C.hid_open(C.uint16_t(vid), C.uint16_t(pid), wcs) + if handle == nil { + return nil, wrapErr(Error()) + } + return &Device{handle}, nil +} + +// OpenFirst opens the first HID device attached to the system with a matching +// vendor ID, and product ID. It returns an open device handle and an error, +// if any. +func OpenFirst(vid, pid uint16) (*Device, error) { + handle := C.hid_open(C.uint16_t(vid), C.uint16_t(pid), nil) + if handle == nil { + return nil, wrapErr(Error()) + } + return &Device{handle}, nil +} + +// OpenPath opens the HID device attached to the system with the given path. +// It returns an open device handle and an error, if any. +func OpenPath(path string) (*Device, error) { + cs := C.CString(path) + defer C.free(unsafe.Pointer(cs)) + + handle := C.hid_open_path(cs) + if handle == nil { + return nil, wrapErr(Error()) + } + return &Device{handle}, nil +} + // Write sends an output report with len(b) bytes to the Device. It returns // the number of bytes written and an error, if any. // @@ -334,6 +375,15 @@ func (d *Device) GetSerialNbr() (string, error) { return wcstogo(wcs), nil } +// GetDeviceInfo returns device information and an error, if any. +func (d *Device) GetDeviceInfo() (*DeviceInfo, error) { + p := C.hid_get_device_info(d.handle) + if p == nil { + return nil, wrapErr(Error()) + } + return newDeviceInfo(p), nil +} + // GetIndexedStr returns a string descriptor by index and an error, if any. func (d *Device) GetIndexedStr(index int) (string, error) { wcs := (*C.wchar_t)(calloc(maxStrLen+1, C.sizeof_wchar_t)) @@ -358,40 +408,34 @@ func (d *Device) Error() error { var _ io.ReadWriteCloser = (*Device)(nil) -// Open opens a HID device attached to the system with a matching vendor ID, -// product ID, and serial number. It returns an open device handle and an -// error, if any. -func Open(vid, pid uint16, serial string) (*Device, error) { - wcs := gotowcs(serial) - defer C.free(unsafe.Pointer(wcs)) - - handle := C.hid_open(C.uint16_t(vid), C.uint16_t(pid), wcs) - if handle == nil { - return nil, wrapErr(Error()) +// Error returns the last non-device-specific error that occurred. If no error +// occurred, nil is returned. +func Error() error { + wcs := C.hid_error(nil) + if wcs == nil { + return nil // no error } - return &Device{handle}, nil + return errors.New(wcstogo(wcs)) } -// OpenFirst opens the first HID device attached to the system with a matching -// vendor ID, and product ID. It returns an open device handle and an error, -// if any. -func OpenFirst(vid, pid uint16) (*Device, error) { - handle := C.hid_open(C.uint16_t(vid), C.uint16_t(pid), nil) - if handle == nil { - return nil, wrapErr(Error()) - } - return &Device{handle}, nil +// APIVersion describes the HIDAPI version. +type APIVersion struct { + Major int // Major version number + Minor int // Minor version number + Patch int // Patch version number } -// OpenPath opens the HID device attached to the system with the given path. -// It returns an open device handle and an error, if any. -func OpenPath(path string) (*Device, error) { - cs := C.CString(path) - defer C.free(unsafe.Pointer(cs)) - - handle := C.hid_open_path(cs) - if handle == nil { - return nil, wrapErr(Error()) +// GetVersion returns the HIDAPI version. +func GetVersion() APIVersion { + v := C.hid_version() + return APIVersion{ + Major: int(v.major), + Minor: int(v.minor), + Patch: int(v.patch), } - return &Device{handle}, nil +} + +// GetVersion returns the HIDAPI version as a string. +func GetVersionStr() string { + return C.GoString(C.hid_version_str()) } diff --git a/hid_darwin.c b/hid_darwin.c index d78ac9b..303ec66 100644 --- a/hid_darwin.c +++ b/hid_darwin.c @@ -27,6 +27,7 @@ #include #include #include +#include #include #include #include @@ -118,13 +119,13 @@ static struct hid_api_version api_version = { static IOHIDManagerRef hid_mgr = 0x0; static int is_macos_10_10_or_greater = 0; static IOOptionBits device_open_options = 0; +static wchar_t *last_global_error_str = NULL; /* --- */ struct hid_device_ { IOHIDDeviceRef device_handle; IOOptionBits open_options; int blocking; - int uses_numbered_reports; int disconnected; CFStringRef run_loop_mode; CFRunLoopRef run_loop; @@ -132,6 +133,7 @@ struct hid_device_ { uint8_t *input_report_buf; CFIndex max_input_report_len; struct input_report *input_reports; + struct hid_device_info* device_info; pthread_t thread; pthread_mutex_t mutex; /* Protects input_reports */ @@ -139,22 +141,28 @@ struct hid_device_ { pthread_barrier_t barrier; /* Ensures correct startup sequence */ pthread_barrier_t shutdown_barrier; /* Ensures correct shutdown sequence */ int shutdown_thread; + wchar_t *last_error_str; }; static hid_device *new_hid_device(void) { hid_device *dev = (hid_device*) calloc(1, sizeof(hid_device)); + if (dev == NULL) { + return NULL; + } + dev->device_handle = NULL; dev->open_options = device_open_options; dev->blocking = 1; - dev->uses_numbered_reports = 0; dev->disconnected = 0; dev->run_loop_mode = NULL; dev->run_loop = NULL; dev->source = NULL; dev->input_report_buf = NULL; dev->input_reports = NULL; + dev->device_info = NULL; dev->shutdown_thread = 0; + dev->last_error_str = NULL; /* Thread objects */ pthread_mutex_init(&dev->mutex, NULL); @@ -187,6 +195,7 @@ static void free_hid_device(hid_device *dev) if (dev->source) CFRelease(dev->source); free(dev->input_report_buf); + hid_free_enumeration(dev->device_info); /* Clean up the thread objects */ pthread_barrier_destroy(&dev->shutdown_barrier); @@ -198,6 +207,88 @@ static void free_hid_device(hid_device *dev) free(dev); } + +/* The caller must free the returned string with free(). */ +static wchar_t *utf8_to_wchar_t(const char *utf8) +{ + wchar_t *ret = NULL; + + if (utf8) { + size_t wlen = mbstowcs(NULL, utf8, 0); + if ((size_t) -1 == wlen) { + return wcsdup(L""); + } + ret = (wchar_t*) calloc(wlen+1, sizeof(wchar_t)); + if (ret == NULL) { + /* as much as we can do at this point */ + return NULL; + } + mbstowcs(ret, utf8, wlen+1); + ret[wlen] = 0x0000; + } + + return ret; +} + + +/* Makes a copy of the given error message (and decoded according to the + * currently locale) into the wide string pointer pointed by error_str. + * The last stored error string is freed. + * Use register_error_str(NULL) to free the error message completely. */ +static void register_error_str(wchar_t **error_str, const char *msg) +{ + free(*error_str); + *error_str = utf8_to_wchar_t(msg); +} + +/* Similar to register_error_str, but allows passing a format string with va_list args into this function. */ +static void register_error_str_vformat(wchar_t **error_str, const char *format, va_list args) +{ + char msg[1024]; + vsnprintf(msg, sizeof(msg), format, args); + + register_error_str(error_str, msg); +} + +/* Set the last global error to be reported by hid_error(NULL). + * The given error message will be copied (and decoded according to the + * currently locale, so do not pass in string constants). + * The last stored global error message is freed. + * Use register_global_error(NULL) to indicate "no error". */ +static void register_global_error(const char *msg) +{ + register_error_str(&last_global_error_str, msg); +} + +/* Similar to register_global_error, but allows passing a format string into this function. */ +static void register_global_error_format(const char *format, ...) +{ + va_list args; + va_start(args, format); + register_error_str_vformat(&last_global_error_str, format, args); + va_end(args); +} + +/* Set the last error for a device to be reported by hid_error(dev). + * The given error message will be copied (and decoded according to the + * currently locale, so do not pass in string constants). + * The last stored device error message is freed. + * Use register_device_error(dev, NULL) to indicate "no error". */ +static void register_device_error(hid_device *dev, const char *msg) +{ + register_error_str(&dev->last_error_str, msg); +} + +/* Similar to register_device_error, but you can pass a format string into this function. */ +static void register_device_error_format(hid_device *dev, const char *format, ...) +{ + va_list args; + va_start(args, format); + register_error_str_vformat(&dev->last_error_str, format, args); + va_end(args); +} + + static CFArrayRef get_array_property(IOHIDDeviceRef device, CFStringRef key) { CFTypeRef ref = IOHIDDeviceGetProperty(device, key); @@ -322,6 +413,7 @@ static int init_hid_manager(void) return 0; } + register_global_error("Failed to create IOHIDManager"); return -1; } @@ -340,6 +432,8 @@ HID_API_EXPORT const char* HID_API_CALL hid_version_str() for failure. */ int HID_API_EXPORT hid_init(void) { + register_global_error(NULL); + if (!hid_mgr) { is_macos_10_10_or_greater = (NSAppKitVersionNumber >= 1343); /* NSAppKitVersionNumber10_10 */ hid_darwin_set_open_exclusive(1); /* Backward compatibility */ @@ -359,6 +453,9 @@ int HID_API_EXPORT hid_exit(void) hid_mgr = NULL; } + /* Free global error message */ + register_global_error(NULL); + return 0; } @@ -375,6 +472,7 @@ static struct hid_device_info *create_device_info_with_usage(IOHIDDeviceRef dev, unsigned short dev_pid; int BUF_LEN = 256; wchar_t buf[BUF_LEN]; + CFTypeRef transport_prop; struct hid_device_info *cur_dev; io_object_t iokit_dev; @@ -453,6 +551,22 @@ static struct hid_device_info *create_device_info_with_usage(IOHIDDeviceRef dev, cur_dev->interface_number = -1; } + /* Bus Type */ + transport_prop = IOHIDDeviceGetProperty(dev, CFSTR(kIOHIDTransportKey)); + + if (transport_prop != NULL && CFGetTypeID(transport_prop) == CFStringGetTypeID()) { + if (CFStringCompare((CFStringRef)transport_prop, CFSTR(kIOHIDTransportUSBValue), 0) == kCFCompareEqualTo) { + cur_dev->bus_type = HID_API_BUS_USB; + /* Match "Bluetooth", "BluetoothLowEnergy" and "Bluetooth Low Energy" strings */ + } else if (CFStringHasPrefix((CFStringRef)transport_prop, CFSTR(kIOHIDTransportBluetoothValue))) { + cur_dev->bus_type = HID_API_BUS_BLUETOOTH; + } else if (CFStringCompare((CFStringRef)transport_prop, CFSTR(kIOHIDTransportI2CValue), 0) == kCFCompareEqualTo) { + cur_dev->bus_type = HID_API_BUS_I2C; + } else if (CFStringCompare((CFStringRef)transport_prop, CFSTR(kIOHIDTransportSPIValue), 0) == kCFCompareEqualTo) { + cur_dev->bus_type = HID_API_BUS_SPI; + } + } + return cur_dev; } @@ -511,8 +625,10 @@ struct hid_device_info HID_API_EXPORT *hid_enumerate(unsigned short vendor_id, int i; /* Set up the HID Manager if it hasn't been done */ - if (hid_init() < 0) + if (hid_init() < 0) { return NULL; + } + /* register_global_error: global error is set/reset by hid_init */ /* give the IOHIDManager a chance to update itself */ process_pending_events(); @@ -540,14 +656,17 @@ struct hid_device_info HID_API_EXPORT *hid_enumerate(unsigned short vendor_id, } CFSetRef device_set = IOHIDManagerCopyDevices(hid_mgr); - if (device_set == NULL) { - return NULL; - } - /* Convert the list into a C array so we can iterate easily. */ - num_devices = CFSetGetCount(device_set); - IOHIDDeviceRef *device_array = (IOHIDDeviceRef*) calloc(num_devices, sizeof(IOHIDDeviceRef)); - CFSetGetValues(device_set, (const void **) device_array); + IOHIDDeviceRef *device_array = NULL; + + if (device_set != NULL) { + /* Convert the list into a C array so we can iterate easily. */ + num_devices = CFSetGetCount(device_set); + device_array = (IOHIDDeviceRef*) calloc(num_devices, sizeof(IOHIDDeviceRef)); + CFSetGetValues(device_set, (const void **) device_array); + } else { + num_devices = 0; + } /* Iterate over each device, making an entry for it. */ for (i = 0; i < num_devices; i++) { @@ -577,7 +696,16 @@ struct hid_device_info HID_API_EXPORT *hid_enumerate(unsigned short vendor_id, } free(device_array); - CFRelease(device_set); + if (device_set != NULL) + CFRelease(device_set); + + if (root == NULL) { + if (vendor_id == 0 && product_id == 0) { + register_global_error("No HID devices found in the system."); + } else { + register_global_error("No HID devices with requested VID/PID found in the system."); + } + } return root; } @@ -600,11 +728,18 @@ void HID_API_EXPORT hid_free_enumeration(struct hid_device_info *devs) hid_device * HID_API_EXPORT hid_open(unsigned short vendor_id, unsigned short product_id, const wchar_t *serial_number) { /* This function is identical to the Linux version. Platform independent. */ + struct hid_device_info *devs, *cur_dev; const char *path_to_open = NULL; hid_device * handle = NULL; + /* register_global_error: global error is reset by hid_enumerate/hid_init */ devs = hid_enumerate(vendor_id, product_id); + if (devs == NULL) { + /* register_global_error: global error is already set by hid_enumerate */ + return NULL; + } + cur_dev = devs; while (cur_dev) { if (cur_dev->vendor_id == vendor_id && @@ -624,8 +759,9 @@ hid_device * HID_API_EXPORT hid_open(unsigned short vendor_id, unsigned short pr } if (path_to_open) { - /* Open the device */ handle = hid_open_path(path_to_open); + } else { + register_global_error("Device with requested VID/PID/(SerialNumber) not found"); } hid_free_enumeration(devs); @@ -806,17 +942,25 @@ hid_device * HID_API_EXPORT hid_open_path(const char *path) hid_device *dev = NULL; io_registry_entry_t entry = MACH_PORT_NULL; IOReturn ret = kIOReturnInvalid; + char str[32]; /* Set up the HID Manager if it hasn't been done */ - if (hid_init() < 0) + if (hid_init() < 0) { goto return_error; + } + /* register_global_error: global error is set/reset by hid_init */ dev = new_hid_device(); + if (!dev) { + register_global_error("Couldn't allocate memory"); + return NULL; + } /* Get the IORegistry entry for the given path */ entry = hid_open_service_registry_from_path(path); if (entry == MACH_PORT_NULL) { /* Path wasn't valid (maybe device was removed?) */ + register_global_error("hid_open_path: device mach entry not found with the given path"); goto return_error; } @@ -824,42 +968,41 @@ hid_device * HID_API_EXPORT hid_open_path(const char *path) dev->device_handle = IOHIDDeviceCreate(kCFAllocatorDefault, entry); if (dev->device_handle == NULL) { /* Error creating the HID device */ + register_global_error("hid_open_path: failed to create IOHIDDevice from the mach entry"); goto return_error; } /* Open the IOHIDDevice */ ret = IOHIDDeviceOpen(dev->device_handle, dev->open_options); - if (ret == kIOReturnSuccess) { - char str[32]; + if (ret != kIOReturnSuccess) { + register_global_error_format("hid_open_path: failed to open IOHIDDevice from mach entry: (0x%08X) %s", ret, mach_error_string(ret)); + goto return_error; + } - /* Create the buffers for receiving data */ - dev->max_input_report_len = (CFIndex) get_max_report_length(dev->device_handle); - dev->input_report_buf = (uint8_t*) calloc(dev->max_input_report_len, sizeof(uint8_t)); + /* Create the buffers for receiving data */ + dev->max_input_report_len = (CFIndex) get_max_report_length(dev->device_handle); + dev->input_report_buf = (uint8_t*) calloc(dev->max_input_report_len, sizeof(uint8_t)); - /* Create the Run Loop Mode for this device. - printing the reference seems to work. */ - sprintf(str, "HIDAPI_%p", (void*) dev->device_handle); - dev->run_loop_mode = - CFStringCreateWithCString(NULL, str, kCFStringEncodingASCII); + /* Create the Run Loop Mode for this device. + printing the reference seems to work. */ + sprintf(str, "HIDAPI_%p", (void*) dev->device_handle); + dev->run_loop_mode = + CFStringCreateWithCString(NULL, str, kCFStringEncodingASCII); - /* Attach the device to a Run Loop */ - IOHIDDeviceRegisterInputReportCallback( - dev->device_handle, dev->input_report_buf, dev->max_input_report_len, - &hid_report_callback, dev); - IOHIDDeviceRegisterRemovalCallback(dev->device_handle, hid_device_removal_callback, dev); + /* Attach the device to a Run Loop */ + IOHIDDeviceRegisterInputReportCallback( + dev->device_handle, dev->input_report_buf, dev->max_input_report_len, + &hid_report_callback, dev); + IOHIDDeviceRegisterRemovalCallback(dev->device_handle, hid_device_removal_callback, dev); - /* Start the read thread */ - pthread_create(&dev->thread, NULL, read_thread, dev); + /* Start the read thread */ + pthread_create(&dev->thread, NULL, read_thread, dev); - /* Wait here for the read thread to be initialized. */ - pthread_barrier_wait(&dev->barrier); + /* Wait here for the read thread to be initialized. */ + pthread_barrier_wait(&dev->barrier); - IOObjectRelease(entry); - return dev; - } - else { - goto return_error; - } + IOObjectRelease(entry); + return dev; return_error: if (dev->device_handle != NULL) @@ -879,7 +1022,10 @@ static int set_report(hid_device *dev, IOHIDReportType type, const unsigned char IOReturn res; unsigned char report_id; + register_device_error(dev, NULL); + if (!data || (length == 0)) { + register_device_error(dev, strerror(EINVAL)); return -1; } @@ -894,6 +1040,7 @@ static int set_report(hid_device *dev, IOHIDReportType type, const unsigned char /* Avoid crash if the device has been unplugged. */ if (dev->disconnected) { + register_device_error(dev, "Device is disconnected"); return -1; } @@ -902,11 +1049,12 @@ static int set_report(hid_device *dev, IOHIDReportType type, const unsigned char report_id, data_to_send, length_to_send); - if (res == kIOReturnSuccess) { - return (int) length; + if (res != kIOReturnSuccess) { + register_device_error_format(dev, "IOHIDDeviceSetReport failed: (0x%08X) %s", res, mach_error_string(res)); + return -1; } - return -1; + return (int) length; } static int get_report(hid_device *dev, IOHIDReportType type, unsigned char *data, size_t length) @@ -916,6 +1064,8 @@ static int get_report(hid_device *dev, IOHIDReportType type, unsigned char *data IOReturn res = kIOReturnSuccess; const unsigned char report_id = data[0]; + register_device_error(dev, NULL); + if (report_id == 0x0) { /* Not using numbered Reports. Don't send the report number. */ @@ -925,6 +1075,7 @@ static int get_report(hid_device *dev, IOHIDReportType type, unsigned char *data /* Avoid crash if the device has been unplugged. */ if (dev->disconnected) { + register_device_error(dev, "Device is disconnected"); return -1; } @@ -933,14 +1084,16 @@ static int get_report(hid_device *dev, IOHIDReportType type, unsigned char *data report_id, report, &report_length); - if (res == kIOReturnSuccess) { - if (report_id == 0x0) { /* 0 report number still present at the beginning */ - report_length++; - } - return (int) report_length; + if (res != kIOReturnSuccess) { + register_device_error_format(dev, "IOHIDDeviceGetReport failed: (0x%08X) %s", res, mach_error_string(res)); + return -1; } - return -1; + if (report_id == 0x0) { /* 0 report number still present at the beginning */ + report_length++; + } + + return (int) report_length; } int HID_API_EXPORT hid_write(hid_device *dev, const unsigned char *data, size_t length) @@ -962,7 +1115,7 @@ static int return_data(hid_device *dev, unsigned char *data, size_t length) return (int) len; } -static int cond_wait(const hid_device *dev, pthread_cond_t *cond, pthread_mutex_t *mutex) +static int cond_wait(hid_device *dev, pthread_cond_t *cond, pthread_mutex_t *mutex) { while (!dev->input_reports) { int res = pthread_cond_wait(cond, mutex); @@ -975,14 +1128,15 @@ static int cond_wait(const hid_device *dev, pthread_cond_t *cond, pthread_mutex_ to sleep. See the pthread_cond_timedwait() man page for details. */ - if (dev->shutdown_thread || dev->disconnected) + if (dev->shutdown_thread || dev->disconnected) { return -1; + } } return 0; } -static int cond_timedwait(const hid_device *dev, pthread_cond_t *cond, pthread_mutex_t *mutex, const struct timespec *abstime) +static int cond_timedwait(hid_device *dev, pthread_cond_t *cond, pthread_mutex_t *mutex, const struct timespec *abstime) { while (!dev->input_reports) { int res = pthread_cond_timedwait(cond, mutex, abstime); @@ -995,8 +1149,9 @@ static int cond_timedwait(const hid_device *dev, pthread_cond_t *cond, pthread_m to sleep. See the pthread_cond_timedwait() man page for details. */ - if (dev->shutdown_thread || dev->disconnected) + if (dev->shutdown_thread || dev->disconnected) { return -1; + } } return 0; @@ -1020,6 +1175,7 @@ int HID_API_EXPORT hid_read_timeout(hid_device *dev, unsigned char *data, size_t /* Return if the device has been disconnected. */ if (dev->disconnected) { bytes_read = -1; + register_device_error(dev, "hid_read_timeout: device disconnected"); goto ret; } @@ -1028,6 +1184,7 @@ int HID_API_EXPORT hid_read_timeout(hid_device *dev, unsigned char *data, size_t has been an error. An error code of -1 should be returned. */ bytes_read = -1; + register_device_error(dev, "hid_read_timeout: thread shutdown"); goto ret; } @@ -1041,6 +1198,7 @@ int HID_API_EXPORT hid_read_timeout(hid_device *dev, unsigned char *data, size_t bytes_read = return_data(dev, data, length); else { /* There was an error, or a device disconnection. */ + register_device_error(dev, "hid_read_timeout: error waiting for more data"); bytes_read = -1; } } @@ -1059,12 +1217,14 @@ int HID_API_EXPORT hid_read_timeout(hid_device *dev, unsigned char *data, size_t } res = cond_timedwait(dev, &dev->condition, &dev->mutex, &ts); - if (res == 0) + if (res == 0) { bytes_read = return_data(dev, data, length); - else if (res == ETIMEDOUT) + } else if (res == ETIMEDOUT) { bytes_read = 0; - else + } else { + register_device_error(dev, "hid_read_timeout: error waiting for more data"); bytes_read = -1; + } } else { /* Purely non-blocking */ @@ -1101,7 +1261,7 @@ int HID_API_EXPORT hid_get_feature_report(hid_device *dev, unsigned char *data, } int HID_API_EXPORT HID_API_CALL hid_get_input_report(hid_device *dev, unsigned char *data, size_t length) -{ +{ return get_report(dev, kIOHIDReportTypeInput, data, length); } @@ -1161,17 +1321,72 @@ void HID_API_EXPORT hid_close(hid_device *dev) int HID_API_EXPORT_CALL hid_get_manufacturer_string(hid_device *dev, wchar_t *string, size_t maxlen) { - return get_manufacturer_string(dev->device_handle, string, maxlen); + if (!string || !maxlen) + { + register_device_error(dev, "Zero buffer/length"); + return -1; + } + + struct hid_device_info *info = hid_get_device_info(dev); + if (!info) + { + // hid_get_device_info will have set an error already + return -1; + } + + wcsncpy(string, info->manufacturer_string, maxlen); + string[maxlen - 1] = L'\0'; + + return 0; } int HID_API_EXPORT_CALL hid_get_product_string(hid_device *dev, wchar_t *string, size_t maxlen) { - return get_product_string(dev->device_handle, string, maxlen); + if (!string || !maxlen) { + register_device_error(dev, "Zero buffer/length"); + return -1; + } + + struct hid_device_info *info = hid_get_device_info(dev); + if (!info) { + // hid_get_device_info will have set an error already + return -1; + } + + wcsncpy(string, info->product_string, maxlen); + string[maxlen - 1] = L'\0'; + + return 0; } int HID_API_EXPORT_CALL hid_get_serial_number_string(hid_device *dev, wchar_t *string, size_t maxlen) { - return get_serial_number(dev->device_handle, string, maxlen); + if (!string || !maxlen) { + register_device_error(dev, "Zero buffer/length"); + return -1; + } + + struct hid_device_info *info = hid_get_device_info(dev); + if (!info) { + // hid_get_device_info will have set an error already + return -1; + } + + wcsncpy(string, info->serial_number, maxlen); + string[maxlen - 1] = L'\0'; + + return 0; +} + +HID_API_EXPORT struct hid_device_info *HID_API_CALL hid_get_device_info(hid_device *dev) { + if (!dev->device_info) { + dev->device_info = create_device_info(dev->device_handle); + if (!dev->device_info) { + register_device_error(dev, "Failed to create hid_device_info"); + } + } + + return dev->device_info; } int HID_API_EXPORT_CALL hid_get_indexed_string(hid_device *dev, int string_index, wchar_t *string, size_t maxlen) @@ -1181,9 +1396,8 @@ int HID_API_EXPORT_CALL hid_get_indexed_string(hid_device *dev, int string_index (void) string; (void) maxlen; - /* TODO: */ - - return 0; + register_device_error(dev, "hid_get_indexed_string: not available on this platform"); + return -1; } int HID_API_EXPORT_CALL hid_darwin_get_location_id(hid_device *dev, uint32_t *location_id) @@ -1193,6 +1407,7 @@ int HID_API_EXPORT_CALL hid_darwin_get_location_id(hid_device *dev, uint32_t *lo *location_id = (uint32_t) res; return 0; } else { + register_device_error(dev, "Failed to get IOHIDLocationID property"); return -1; } } @@ -1217,8 +1432,13 @@ int HID_API_EXPORT_CALL hid_darwin_is_device_open_exclusive(hid_device *dev) HID_API_EXPORT const wchar_t * HID_API_CALL hid_error(hid_device *dev) { - (void) dev; - /* TODO: */ + if (dev) { + if (dev->last_error_str == NULL) + return L"Success"; + return dev->last_error_str; + } - return L"hid_error is not implemented yet"; + if (last_global_error_str == NULL) + return L"Success"; + return last_global_error_str; } diff --git a/hid_darwin.go b/hid_darwin.go index 371b368..a02689c 100644 --- a/hid_darwin.go +++ b/hid_darwin.go @@ -1,4 +1,4 @@ -// Copyright (c) 2022 Steven Stallion +// Copyright (c) 2023 Steven Stallion // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions @@ -40,17 +40,14 @@ func (d *Device) GetLocationID() (uint32, error) { return uint32(id), nil } -// IsOpenExclusive returns if the device is in exclusive mode and an error, if -// any. -func (d *Device) IsOpenExclusive() (bool, error) { - res := C.hid_darwin_is_device_open_exclusive(d.handle) - switch res { - case -1: - return false, wrapErr(d.Error()) - case 0: - return false, nil +// SetOpenExclusive changes the default behavior for Open. If exclusive is +// false, devices will be opened in non-exclusive mode. +func SetOpenExclusive(exclusive bool) { + var open_exclusive C.int + if exclusive { + open_exclusive = 1 } - return true, nil + C.hid_darwin_set_open_exclusive(open_exclusive) } // GetOpenExclusive returns if exclusive mode is enabled. @@ -62,12 +59,15 @@ func GetOpenExclusive() bool { return true } -// SetOpenExclusive changes the default behavior for Open. If exclusive is -// false, devices will be opened in non-exclusive mode. -func SetOpenExclusive(exclusive bool) { - var open_exclusive C.int - if exclusive { - open_exclusive = 1 +// IsOpenExclusive returns if the device is in exclusive mode and an error, if +// any. +func (d *Device) IsOpenExclusive() (bool, error) { + res := C.hid_darwin_is_device_open_exclusive(d.handle) + switch res { + case -1: + return false, wrapErr(d.Error()) + case 0: + return false, nil } - C.hid_darwin_set_open_exclusive(open_exclusive) + return true, nil } diff --git a/hid_libusb.c b/hid_libusb.c index 38428fa..1425b66 100644 --- a/hid_libusb.c +++ b/hid_libusb.c @@ -148,18 +148,23 @@ struct hid_device_ { /* Handle to the actual device. */ libusb_device_handle *device_handle; + /* USB Configuration Number of the device */ + int config_number; + /* The interface number of the HID */ + int interface; + + uint16_t report_descriptor_size; + /* Endpoint information */ int input_endpoint; int output_endpoint; int input_ep_max_packet_size; - /* The interface number of the HID */ - int interface; - /* Indexes of Strings */ int manufacturer_index; int product_index; int serial_index; + struct hid_device_info* device_info; /* Whether blocking reads are used */ int blocking; /* boolean */ @@ -212,6 +217,8 @@ static void free_hid_device(hid_device *dev) pthread_cond_destroy(&dev->condition); pthread_mutex_destroy(&dev->mutex); + hid_free_enumeration(dev->device_info); + /* Free the device itself */ free(dev); } @@ -224,7 +231,6 @@ static void register_error(hid_device *dev, const char *op) } #endif -#ifdef INVASIVE_GET_USAGE /* Get bytes from a HID Report Descriptor. Only call with a num_bytes of 0, 1, 2, or 4. */ static uint32_t get_bytes(uint8_t *rpt, size_t len, size_t num_bytes, size_t cur) @@ -324,7 +330,6 @@ static int get_usage(uint8_t *report_descriptor, size_t size, return -1; /* failure */ } -#endif /* INVASIVE_GET_USAGE */ #if defined(__FreeBSD__) && __FreeBSD__ < 10 /* The libusb version included in FreeBSD < 10 doesn't have this function. In @@ -486,9 +491,14 @@ static wchar_t *get_usb_string(libusb_device_handle *dev, uint8_t idx) return str; } -static char *make_path(libusb_device *dev, int interface_number, int config_number) +/** + Max length of the result: "000-000.000.000.000.000.000.000:000.000" (39 chars). + 64 is used for simplicity/alignment. +*/ +static void get_path(char (*result)[64], libusb_device *dev, int config_number, int interface_number) { - char str[64]; /* max length "000-000.000.000.000.000.000.000:000.000" */ + char *str = *result; + /* Note that USB3 port count limit is 7; use 8 here for alignment */ uint8_t port_numbers[8] = {0, 0, 0, 0, 0, 0, 0, 0}; int num_ports = libusb_get_port_numbers(dev, port_numbers, 8); @@ -501,7 +511,7 @@ static char *make_path(libusb_device *dev, int interface_number, int config_numb n += snprintf(&str[n], sizeof(":000.000"), ":%u.%u", (uint8_t)config_number, (uint8_t)interface_number); str[n] = '\0'; } else { - /* USB3.0 specs limit number of ports to 7 and buffer size here is 8 */ + /* Likely impossible, but check: USB3.0 specs limit number of ports to 7 and buffer size here is 8 */ if (num_ports == LIBUSB_ERROR_OVERFLOW) { LOG("make_path() failed. buffer overflow error\n"); } else { @@ -509,6 +519,12 @@ static char *make_path(libusb_device *dev, int interface_number, int config_numb } str[0] = '\0'; } +} + +static char *make_path(libusb_device *dev, int config_number, int interface_number) +{ + char str[64]; + get_path(&str, dev, config_number, interface_number); return strdup(str); } @@ -550,11 +566,186 @@ int HID_API_EXPORT hid_exit(void) return 0; } +static int hid_get_report_descriptor_libusb(libusb_device_handle *handle, int interface_num, uint16_t expected_report_descriptor_size, unsigned char *buf, size_t buf_size) +{ + unsigned char tmp[HID_API_MAX_REPORT_DESCRIPTOR_SIZE]; + + if (expected_report_descriptor_size > HID_API_MAX_REPORT_DESCRIPTOR_SIZE) + expected_report_descriptor_size = HID_API_MAX_REPORT_DESCRIPTOR_SIZE; + + /* Get the HID Report Descriptor. + See USB HID Specificatin, sectin 7.1.1 + */ + int res = libusb_control_transfer(handle, LIBUSB_ENDPOINT_IN|LIBUSB_RECIPIENT_INTERFACE, LIBUSB_REQUEST_GET_DESCRIPTOR, (LIBUSB_DT_REPORT << 8), interface_num, tmp, expected_report_descriptor_size, 5000); + if (res < 0) { + LOG("libusb_control_transfer() for getting the HID Report descriptor failed with %d: %s\n", res, libusb_error_name(res)); + return -1; + } + + if (res > (int)buf_size) + res = (int)buf_size; + + memcpy(buf, tmp, (size_t)res); + return res; +} + +/** + * Requires an opened device with *claimed interface*. + */ +static void fill_device_info_usage(struct hid_device_info *cur_dev, libusb_device_handle *handle, int interface_num, uint16_t expected_report_descriptor_size) +{ + unsigned char hid_report_descriptor[HID_API_MAX_REPORT_DESCRIPTOR_SIZE]; + unsigned short page = 0, usage = 0; + + int res = hid_get_report_descriptor_libusb(handle, interface_num, expected_report_descriptor_size, hid_report_descriptor, sizeof(hid_report_descriptor)); + if (res >= 0) { + /* Parse the usage and usage page + out of the report descriptor. */ + get_usage(hid_report_descriptor, res, &page, &usage); + } + + cur_dev->usage_page = page; + cur_dev->usage = usage; +} + +#ifdef INVASIVE_GET_USAGE +static void invasive_fill_device_info_usage(struct hid_device_info *cur_dev, libusb_device_handle *handle, int interface_num, uint16_t report_descriptor_size) +{ + int res = 0; + +#ifdef DETACH_KERNEL_DRIVER + int detached = 0; + /* Usage Page and Usage */ + res = libusb_kernel_driver_active(handle, interface_num); + if (res == 1) { + res = libusb_detach_kernel_driver(handle, interface_num); + if (res < 0) + LOG("Couldn't detach kernel driver, even though a kernel driver was attached.\n"); + else + detached = 1; + } +#endif + + res = libusb_claim_interface(handle, interface_num); + if (res >= 0) { + fill_device_info_usage(cur_dev, handle, interface_num, report_descriptor_size); + + /* Release the interface */ + res = libusb_release_interface(handle, interface_num); + if (res < 0) + LOG("Can't release the interface.\n"); + } + else + LOG("Can't claim interface: (%d) %s\n", res, libusb_error_name(res)); + +#ifdef DETACH_KERNEL_DRIVER + /* Re-attach kernel driver if necessary. */ + if (detached) { + res = libusb_attach_kernel_driver(handle, interface_num); + if (res < 0) + LOG("Couldn't re-attach kernel driver.\n"); + } +#endif +} +#endif /* INVASIVE_GET_USAGE */ + +/** + * Create and fill up most of hid_device_info fields. + * usage_page/usage is not filled up. + */ +static struct hid_device_info * create_device_info_for_device(libusb_device *device, libusb_device_handle *handle, struct libusb_device_descriptor *desc, int config_number, int interface_num) +{ + struct hid_device_info *cur_dev = calloc(1, sizeof(struct hid_device_info)); + if (cur_dev == NULL) { + return NULL; + } + + /* VID/PID */ + cur_dev->vendor_id = desc->idVendor; + cur_dev->product_id = desc->idProduct; + + cur_dev->release_number = desc->bcdDevice; + + cur_dev->interface_number = interface_num; + + cur_dev->bus_type = HID_API_BUS_USB; + + cur_dev->path = make_path(device, config_number, interface_num); + + if (!handle) { + return cur_dev; + } + + if (desc->iSerialNumber > 0) + cur_dev->serial_number = get_usb_string(handle, desc->iSerialNumber); + + /* Manufacturer and Product strings */ + if (desc->iManufacturer > 0) + cur_dev->manufacturer_string = get_usb_string(handle, desc->iManufacturer); + if (desc->iProduct > 0) + cur_dev->product_string = get_usb_string(handle, desc->iProduct); + + return cur_dev; +} + +static uint16_t get_report_descriptor_size_from_interface_descriptors(const struct libusb_interface_descriptor *intf_desc) +{ + int i = 0; + int found_hid_report_descriptor = 0; + uint16_t result = HID_API_MAX_REPORT_DESCRIPTOR_SIZE; + const unsigned char *extra = intf_desc->extra; + int extra_length = intf_desc->extra_length; + + /* + "extra" contains a HID descriptor + See section 6.2.1 of HID 1.1 specification. + */ + + while (extra_length >= 2) { /* Descriptor header: bLength/bDescriptorType */ + if (extra[1] == LIBUSB_DT_HID) { /* bDescriptorType */ + if (extra_length < 6) { + LOG("Broken HID descriptor: not enough data\n"); + break; + } + unsigned char bNumDescriptors = extra[5]; + if (extra_length < (6 + 3 * bNumDescriptors)) { + LOG("Broken HID descriptor: not enough data for Report metadata\n"); + break; + } + for (i = 0; i < bNumDescriptors; i++) { + if (extra[6 + 3 * i] == LIBUSB_DT_REPORT) { + result = (uint16_t)extra[6 + 3 * i + 2] << 8 | extra[6 + 3 * i + 1]; + found_hid_report_descriptor = 1; + break; + } + } + + if (!found_hid_report_descriptor) { + /* We expect to find exactly 1 HID descriptor (LIBUSB_DT_HID) + which should contain exactly one HID Report Descriptor metadata (LIBUSB_DT_REPORT). */ + LOG("Broken HID descriptor: missing Report descriptor\n"); + } + break; + } + + if (extra[0] == 0) { /* bLength */ + LOG("Broken HID Interface descriptors: zero-sized descriptor\n"); + break; + } + + /* Iterate over to the next Descriptor */ + extra_length -= extra[0]; + extra += extra[0]; + } + + return result; +} + struct hid_device_info HID_API_EXPORT *hid_enumerate(unsigned short vendor_id, unsigned short product_id) { libusb_device **devs; libusb_device *dev; - libusb_device_handle *handle; + libusb_device_handle *handle = NULL; ssize_t num_devs; int i = 0; @@ -591,27 +782,12 @@ struct hid_device_info HID_API_EXPORT *hid_enumerate(unsigned short vendor_id, const struct libusb_interface_descriptor *intf_desc; intf_desc = &intf->altsetting[k]; if (intf_desc->bInterfaceClass == LIBUSB_CLASS_HID) { - int interface_num = intf_desc->bInterfaceNumber; struct hid_device_info *tmp; - /* VID/PID match. Create the record. */ - tmp = (struct hid_device_info*) calloc(1, sizeof(struct hid_device_info)); - if (cur_dev) { - cur_dev->next = tmp; - } - else { - root = tmp; - } - cur_dev = tmp; - - /* Fill out the record */ - cur_dev->next = NULL; - cur_dev->path = make_path(dev, interface_num, conf_desc->bConfigurationValue); - res = libusb_open(dev, &handle); - if (res >= 0) { #ifdef __ANDROID__ + if (handle) { /* There is (a potential) libusb Android backend, in which device descriptor is not accurate up until the device is opened. https://github.com/libusb/libusb/pull/874#discussion_r632801373 @@ -620,95 +796,47 @@ struct hid_device_info HID_API_EXPORT *hid_enumerate(unsigned short vendor_id, having it here won't do any harm, since reading the device descriptor is as cheap as copy 18 bytes of data. */ libusb_get_device_descriptor(dev, &desc); + } #endif - /* Serial Number */ - if (desc.iSerialNumber > 0) - cur_dev->serial_number = - get_usb_string(handle, desc.iSerialNumber); - - /* Manufacturer and Product strings */ - if (desc.iManufacturer > 0) - cur_dev->manufacturer_string = - get_usb_string(handle, desc.iManufacturer); - if (desc.iProduct > 0) - cur_dev->product_string = - get_usb_string(handle, desc.iProduct); - + tmp = create_device_info_for_device(dev, handle, &desc, conf_desc->bConfigurationValue, intf_desc->bInterfaceNumber); + if (tmp) { #ifdef INVASIVE_GET_USAGE -{ - /* - This section is removed because it is too - invasive on the system. Getting a Usage Page - and Usage requires parsing the HID Report - descriptor. Getting a HID Report descriptor - involves claiming the interface. Claiming the - interface involves detaching the kernel driver. - Detaching the kernel driver is hard on the system - because it will unclaim interfaces (if another - app has them claimed) and the re-attachment of - the driver will sometimes change /dev entry names. - It is for these reasons that this section is - #if 0. For composite devices, use the interface - field in the hid_device_info struct to distinguish - between interfaces. */ - unsigned char data[256]; -#ifdef DETACH_KERNEL_DRIVER - int detached = 0; - /* Usage Page and Usage */ - res = libusb_kernel_driver_active(handle, interface_num); - if (res == 1) { - res = libusb_detach_kernel_driver(handle, interface_num); - if (res < 0) - LOG("Couldn't detach kernel driver, even though a kernel driver was attached.\n"); - else - detached = 1; - } -#endif - res = libusb_claim_interface(handle, interface_num); - if (res >= 0) { - /* Get the HID Report Descriptor. */ - res = libusb_control_transfer(handle, LIBUSB_ENDPOINT_IN|LIBUSB_RECIPIENT_INTERFACE, LIBUSB_REQUEST_GET_DESCRIPTOR, (LIBUSB_DT_REPORT << 8)|interface_num, 0, data, sizeof(data), 5000); - if (res >= 0) { - unsigned short page=0, usage=0; - /* Parse the usage and usage page - out of the report descriptor. */ - get_usage(data, res, &page, &usage); - cur_dev->usage_page = page; - cur_dev->usage = usage; - } - else - LOG("libusb_control_transfer() for getting the HID report failed with %d\n", res); - - /* Release the interface */ - res = libusb_release_interface(handle, interface_num); - if (res < 0) - LOG("Can't release the interface.\n"); - } - else - LOG("Can't claim interface %d\n", res); -#ifdef DETACH_KERNEL_DRIVER - /* Re-attach kernel driver if necessary. */ - if (detached) { - res = libusb_attach_kernel_driver(handle, interface_num); - if (res < 0) - LOG("Couldn't re-attach kernel driver.\n"); + /* TODO: have a runtime check for this section. */ + + /* + This section is removed because it is too + invasive on the system. Getting a Usage Page + and Usage requires parsing the HID Report + descriptor. Getting a HID Report descriptor + involves claiming the interface. Claiming the + interface involves detaching the kernel driver. + Detaching the kernel driver is hard on the system + because it will unclaim interfaces (if another + app has them claimed) and the re-attachment of + the driver will sometimes change /dev entry names. + It is for these reasons that this section is + optional. For composite devices, use the interface + field in the hid_device_info struct to distinguish + between interfaces. */ + if (handle) { + uint16_t report_descriptor_size = get_report_descriptor_size_from_interface_descriptors(intf_desc); + + invasive_fill_device_info_usage(tmp, handle, intf_desc->bInterfaceNumber, report_descriptor_size); } -#endif -} #endif /* INVASIVE_GET_USAGE */ - libusb_close(handle); + if (cur_dev) { + cur_dev->next = tmp; + } + else { + root = tmp; + } + cur_dev = tmp; } - /* VID/PID */ - cur_dev->vendor_id = dev_vid; - cur_dev->product_id = dev_pid; - - /* Release Number */ - cur_dev->release_number = desc.bcdDevice; - /* Interface Number */ - cur_dev->interface_number = interface_num; + if (res >= 0) + libusb_close(handle); } } /* altsettings */ } /* interfaces */ @@ -832,7 +960,7 @@ static void read_callback(struct libusb_transfer *transfer) /* Re-submit the transfer object. */ res = libusb_submit_transfer(transfer); if (res != 0) { - LOG("Unable to submit URB. libusb error code: %d\n", res); + LOG("Unable to submit URB: (%d) %s\n", res, libusb_error_name(res)); dev->shutdown_thread = 1; dev->transfer_loop_finished = 1; } @@ -841,6 +969,7 @@ static void read_callback(struct libusb_transfer *transfer) static void *read_thread(void *param) { + int res; hid_device *dev = param; uint8_t *buf; const size_t length = dev->input_ep_max_packet_size; @@ -859,18 +988,22 @@ static void *read_thread(void *param) /* Make the first submission. Further submissions are made from inside read_callback() */ - libusb_submit_transfer(dev->transfer); + res = libusb_submit_transfer(dev->transfer); + if(res < 0) { + LOG("libusb_submit_transfer failed: %d %s. Stopping read_thread from running\n", res, libusb_error_name(res)); + dev->shutdown_thread = 1; + dev->transfer_loop_finished = 1; + } /* Notify the main thread that the read thread is up and running. */ pthread_barrier_wait(&dev->barrier); /* Handle all the events. */ while (!dev->shutdown_thread) { - int res; res = libusb_handle_events(usb_context); if (res < 0) { /* There was an error. */ - LOG("read_thread(): libusb reports error # %d\n", res); + LOG("read_thread(): (%d) %s\n", res, libusb_error_name(res)); /* Break out of this loop only on fatal error.*/ if (res != LIBUSB_ERROR_BUSY && @@ -911,7 +1044,7 @@ static void *read_thread(void *param) } -static int hidapi_initialize_device(hid_device *dev, const struct libusb_interface_descriptor *intf_desc) +static int hidapi_initialize_device(hid_device *dev, int config_number, const struct libusb_interface_descriptor *intf_desc) { int i =0; int res = 0; @@ -925,7 +1058,7 @@ static int hidapi_initialize_device(hid_device *dev, const struct libusb_interfa if (libusb_kernel_driver_active(dev->device_handle, intf_desc->bInterfaceNumber) == 1) { res = libusb_detach_kernel_driver(dev->device_handle, intf_desc->bInterfaceNumber); if (res < 0) { - LOG("Unable to detach Kernel Driver\n"); + LOG("Unable to detach Kernel Driver: (%d) %s\n", res, libusb_error_name(res)); return 0; } else { @@ -936,7 +1069,15 @@ static int hidapi_initialize_device(hid_device *dev, const struct libusb_interfa #endif res = libusb_claim_interface(dev->device_handle, intf_desc->bInterfaceNumber); if (res < 0) { - LOG("can't claim interface %d: %d\n", intf_desc->bInterfaceNumber, res); + LOG("can't claim interface %d: (%d) %s\n", intf_desc->bInterfaceNumber, res, libusb_error_name(res)); + +#ifdef DETACH_KERNEL_DRIVER + if (dev->is_driver_detached) { + res = libusb_attach_kernel_driver(dev->device_handle, intf_desc->bInterfaceNumber); + if (res < 0) + LOG("Failed to reattach the driver to kernel: (%d) %s\n", res, libusb_error_name(res)); + } +#endif return 0; } @@ -945,9 +1086,12 @@ static int hidapi_initialize_device(hid_device *dev, const struct libusb_interfa dev->product_index = desc.iProduct; dev->serial_index = desc.iSerialNumber; - /* Store off the interface number */ + /* Store off the USB information */ + dev->config_number = config_number; dev->interface = intf_desc->bInterfaceNumber; + dev->report_descriptor_size = get_report_descriptor_size_from_interface_descriptors(intf_desc); + dev->input_endpoint = 0; dev->input_ep_max_packet_size = 0; dev->output_endpoint = 0; @@ -1019,7 +1163,8 @@ hid_device * HID_API_EXPORT hid_open_path(const char *path) for (k = 0; k < intf->num_altsetting && !good_open; k++) { const struct libusb_interface_descriptor *intf_desc = &intf->altsetting[k]; if (intf_desc->bInterfaceClass == LIBUSB_CLASS_HID) { - char *dev_path = make_path(usb_dev, intf_desc->bInterfaceNumber, conf_desc->bConfigurationValue); + char dev_path[64]; + get_path(&dev_path, usb_dev, conf_desc->bConfigurationValue, intf_desc->bInterfaceNumber); if (!strcmp(dev_path, path)) { /* Matched Paths. Open this device */ @@ -1027,14 +1172,12 @@ hid_device * HID_API_EXPORT hid_open_path(const char *path) res = libusb_open(usb_dev, &dev->device_handle); if (res < 0) { LOG("can't open device\n"); - free(dev_path); break; } - good_open = hidapi_initialize_device(dev, intf_desc); + good_open = hidapi_initialize_device(dev, conf_desc->bConfigurationValue, intf_desc); if (!good_open) libusb_close(dev->device_handle); } - free(dev_path); } } } @@ -1109,7 +1252,7 @@ HID_API_EXPORT hid_device * HID_API_CALL hid_libusb_wrap_sys_device(intptr_t sys goto err; } - if (!hidapi_initialize_device(dev, selected_intf_desc)) + if (!hidapi_initialize_device(dev, conf_desc->bConfigurationValue, selected_intf_desc)) goto err; return dev; @@ -1457,6 +1600,23 @@ int HID_API_EXPORT_CALL hid_get_serial_number_string(hid_device *dev, wchar_t *s return hid_get_indexed_string(dev, dev->serial_index, string, maxlen); } +HID_API_EXPORT struct hid_device_info *HID_API_CALL hid_get_device_info(hid_device *dev) { + if (!dev->device_info) { + struct libusb_device_descriptor desc; + libusb_device *usb_device = libusb_get_device(dev->device_handle); + libusb_get_device_descriptor(usb_device, &desc); + + dev->device_info = create_device_info_for_device(usb_device, dev->device_handle, &desc, dev->config_number, dev->interface); + // device error already set by create_device_info_for_device, if any + + if (dev->device_info) { + fill_device_info_usage(dev->device_info, dev->device_handle, dev->interface, dev->report_descriptor_size); + } + } + + return dev->device_info; +} + int HID_API_EXPORT_CALL hid_get_indexed_string(hid_device *dev, int string_index, wchar_t *string, size_t maxlen) { wchar_t *str; diff --git a/hid_linux.c b/hid_linux.c index a437a0d..24fc548 100644 --- a/hid_linux.c +++ b/hid_linux.c @@ -70,27 +70,11 @@ #define HIDIOCGINPUT(len) _IOC(_IOC_WRITE|_IOC_READ, 'H', 0x0A, len) #endif -/* USB HID device property names */ -const char *device_string_names[] = { - "manufacturer", - "product", - "serial", -}; - -/* Symbolic names for the properties above */ -enum device_string_id { - DEVICE_STRING_MANUFACTURER, - DEVICE_STRING_PRODUCT, - DEVICE_STRING_SERIAL, - - DEVICE_STRING_COUNT, -}; - struct hid_device_ { int device_handle; int blocking; - int uses_numbered_reports; wchar_t *last_error_str; + struct hid_device_info* device_info; }; static struct hid_api_version api_version = { @@ -99,17 +83,20 @@ static struct hid_api_version api_version = { .patch = HID_API_VERSION_PATCH }; -/* Global error message that is not specific to a device, e.g. for - hid_open(). It is thread-local like errno. */ -__thread wchar_t *last_global_error_str = NULL; +static wchar_t *last_global_error_str = NULL; + static hid_device *new_hid_device(void) { hid_device *dev = (hid_device*) calloc(1, sizeof(hid_device)); + if (dev == NULL) { + return NULL; + } + dev->device_handle = -1; dev->blocking = 1; - dev->uses_numbered_reports = 0; dev->last_error_str = NULL; + dev->device_info = NULL; return dev; } @@ -126,6 +113,10 @@ static wchar_t *utf8_to_wchar_t(const char *utf8) return wcsdup(L""); } ret = (wchar_t*) calloc(wlen+1, sizeof(wchar_t)); + if (ret == NULL) { + /* as much as we can do at this point */ + return NULL; + } mbstowcs(ret, utf8, wlen+1); ret[wlen] = 0x0000; } @@ -134,6 +125,25 @@ static wchar_t *utf8_to_wchar_t(const char *utf8) } +/* Makes a copy of the given error message (and decoded according to the + * currently locale) into the wide string pointer pointed by error_str. + * The last stored error string is freed. + * Use register_error_str(NULL) to free the error message completely. */ +static void register_error_str(wchar_t **error_str, const char *msg) +{ + free(*error_str); + *error_str = utf8_to_wchar_t(msg); +} + +/* Semilar to register_error_str, but allows passing a format string with va_list args into this function. */ +static void register_error_str_vformat(wchar_t **error_str, const char *format, va_list args) +{ + char msg[256]; + vsnprintf(msg, sizeof(msg), format, args); + + register_error_str(error_str, msg); +} + /* Set the last global error to be reported by hid_error(NULL). * The given error message will be copied (and decoded according to the * currently locale, so do not pass in string constants). @@ -141,51 +151,35 @@ static wchar_t *utf8_to_wchar_t(const char *utf8) * Use register_global_error(NULL) to indicate "no error". */ static void register_global_error(const char *msg) { - if (last_global_error_str) - free(last_global_error_str); - - last_global_error_str = utf8_to_wchar_t(msg); + register_error_str(&last_global_error_str, msg); } -/* See register_global_error, but you can pass a format string into this function. */ +/* Similar to register_global_error, but allows passing a format string into this function. */ static void register_global_error_format(const char *format, ...) { va_list args; va_start(args, format); - - char msg[100]; - vsnprintf(msg, sizeof(msg), format, args); - + register_error_str_vformat(&last_global_error_str, format, args); va_end(args); - - register_global_error(msg); } -/* Set the last error for a device to be reported by hid_error(device). +/* Set the last error for a device to be reported by hid_error(dev). * The given error message will be copied (and decoded according to the * currently locale, so do not pass in string constants). - * The last stored global error message is freed. - * Use register_device_error(device, NULL) to indicate "no error". */ + * The last stored device error message is freed. + * Use register_device_error(dev, NULL) to indicate "no error". */ static void register_device_error(hid_device *dev, const char *msg) { - if (dev->last_error_str) - free(dev->last_error_str); - - dev->last_error_str = utf8_to_wchar_t(msg); + register_error_str(&dev->last_error_str, msg); } -/* See register_device_error, but you can pass a format string into this function. */ +/* Similar to register_device_error, but you can pass a format string into this function. */ static void register_device_error_format(hid_device *dev, const char *format, ...) { va_list args; va_start(args, format); - - char msg[100]; - vsnprintf(msg, sizeof(msg), format, args); - + register_error_str_vformat(&dev->last_error_str, format, args); va_end(args); - - register_device_error(dev, msg); } /* Get an attribute value from a udev_device and return it as a whar_t @@ -252,34 +246,6 @@ static int get_hid_item_size(__u8 *report_descriptor, unsigned int pos, __u32 si return 0; } -/* uses_numbered_reports() returns 1 if report_descriptor describes a device - which contains numbered reports. */ -static int uses_numbered_reports(__u8 *report_descriptor, __u32 size) { - unsigned int i = 0; - int data_len, key_size; - - while (i < size) { - int key = report_descriptor[i]; - - /* Check for the Report ID key */ - if (key == 0x85/*Report ID*/) { - /* This device has a Report ID, which means it uses - numbered reports. */ - return 1; - } - - /* Determine data_len and key_size */ - if (!get_hid_item_size(report_descriptor, i, size, &data_len, &key_size)) - return 0; /* malformed report */ - - /* Skip over this key and its associated data */ - i += data_len + key_size; - } - - /* Didn't find a Report ID key. Device doesn't use numbered reports. */ - return 0; -} - /* * Get bytes from a HID Report Descriptor. * Only call with a num_bytes of 0, 1, 2, or 4. @@ -394,7 +360,7 @@ static int get_hid_report_descriptor(const char *rpt_path, struct hidraw_report_ int rpt_handle; ssize_t res; - rpt_handle = open(rpt_path, O_RDONLY); + rpt_handle = open(rpt_path, O_RDONLY | O_CLOEXEC); if (rpt_handle < 0) { register_global_error_format("open failed (%s): %s", rpt_path, strerror(errno)); return -1; @@ -417,6 +383,7 @@ static int get_hid_report_descriptor(const char *rpt_path, struct hidraw_report_ return (int) res; } +/* return size of the descriptor, or -1 on failure */ static int get_hid_report_descriptor_from_sysfs(const char *sysfs_path, struct hidraw_report_descriptor *rpt_desc) { int res = -1; @@ -431,16 +398,106 @@ static int get_hid_report_descriptor_from_sysfs(const char *sysfs_path, struct h return res; } +/* return non-zero if successfully parsed */ +static int parse_hid_vid_pid_from_uevent(const char *uevent, unsigned *bus_type, unsigned short *vendor_id, unsigned short *product_id) +{ + char tmp[1024]; + size_t uevent_len = strlen(uevent); + if (uevent_len > sizeof(tmp) - 1) + uevent_len = sizeof(tmp) - 1; + memcpy(tmp, uevent, sizeof(tmp)); + tmp[uevent_len] = '\0'; + + char *saveptr = NULL; + char *line; + char *key; + char *value; + + line = strtok_r(tmp, "\n", &saveptr); + while (line != NULL) { + /* line: "KEY=value" */ + key = line; + value = strchr(line, '='); + if (!value) { + goto next_line; + } + *value = '\0'; + value++; + + if (strcmp(key, "HID_ID") == 0) { + /** + * type vendor product + * HID_ID=0003:000005AC:00008242 + **/ + int ret = sscanf(value, "%x:%hx:%hx", bus_type, vendor_id, product_id); + if (ret == 3) { + return 1; + } + } + +next_line: + line = strtok_r(NULL, "\n", &saveptr); + } + + register_global_error("Couldn't find/parse HID_ID"); + return 0; +} + +/* return non-zero if successfully parsed */ +static int parse_hid_vid_pid_from_uevent_path(const char *uevent_path, unsigned *bus_type, unsigned short *vendor_id, unsigned short *product_id) +{ + int handle; + ssize_t res; + + handle = open(uevent_path, O_RDONLY | O_CLOEXEC); + if (handle < 0) { + register_global_error_format("open failed (%s): %s", uevent_path, strerror(errno)); + return 0; + } + + char buf[1024]; + res = read(handle, buf, sizeof(buf)); + close(handle); + + if (res < 0) { + register_global_error_format("read failed (%s): %s", uevent_path, strerror(errno)); + return 0; + } + + buf[res] = '\0'; + return parse_hid_vid_pid_from_uevent(buf, bus_type, vendor_id, product_id); +} + +/* return non-zero if successfully read/parsed */ +static int parse_hid_vid_pid_from_sysfs(const char *sysfs_path, unsigned *bus_type, unsigned short *vendor_id, unsigned short *product_id) +{ + int res = 0; + /* Construct /device/uevent */ + size_t uevent_path_len = strlen(sysfs_path) + 14 + 1; + char* uevent_path = (char*) calloc(1, uevent_path_len); + snprintf(uevent_path, uevent_path_len, "%s/device/uevent", sysfs_path); + + res = parse_hid_vid_pid_from_uevent_path(uevent_path, bus_type, vendor_id, product_id); + free(uevent_path); + + return res; +} + /* * The caller is responsible for free()ing the (newly-allocated) character * strings pointed to by serial_number_utf8 and product_name_utf8 after use. */ -static int -parse_uevent_info(const char *uevent, unsigned *bus_type, +static int parse_uevent_info(const char *uevent, unsigned *bus_type, unsigned short *vendor_id, unsigned short *product_id, char **serial_number_utf8, char **product_name_utf8) { - char *tmp = strdup(uevent); + char tmp[1024]; + size_t uevent_len = strlen(uevent); + if (uevent_len > sizeof(tmp) - 1) + uevent_len = sizeof(tmp) - 1; + memcpy(tmp, uevent, sizeof(tmp)); + tmp[uevent_len] = '\0'; + char *saveptr = NULL; char *line; char *key; @@ -484,109 +541,203 @@ parse_uevent_info(const char *uevent, unsigned *bus_type, line = strtok_r(NULL, "\n", &saveptr); } - free(tmp); return (found_id && found_name && found_serial); } -static int get_device_string(hid_device *dev, enum device_string_id key, wchar_t *string, size_t maxlen) +static struct hid_device_info * create_device_info_for_device(struct udev_device *raw_dev) { - struct udev *udev; - struct udev_device *udev_dev, *parent, *hid_dev; - struct stat s; - int ret = -1; + struct hid_device_info *root = NULL; + struct hid_device_info *cur_dev = NULL; + + const char *sysfs_path; + const char *dev_path; + const char *str; + struct udev_device *hid_dev; /* The device's HID udev node. */ + struct udev_device *usb_dev; /* The device's USB udev node. */ + struct udev_device *intf_dev; /* The device's interface (in the USB sense). */ + unsigned short dev_vid; + unsigned short dev_pid; char *serial_number_utf8 = NULL; char *product_name_utf8 = NULL; + unsigned bus_type; + int result; + struct hidraw_report_descriptor report_desc; - /* Create the udev object */ - udev = udev_new(); - if (!udev) { - register_global_error("Couldn't create udev context"); - return -1; + sysfs_path = udev_device_get_syspath(raw_dev); + dev_path = udev_device_get_devnode(raw_dev); + + hid_dev = udev_device_get_parent_with_subsystem_devtype( + raw_dev, + "hid", + NULL); + + if (!hid_dev) { + /* Unable to find parent hid device. */ + goto end; } - /* Get the dev_t (major/minor numbers) from the file handle. */ - ret = fstat(dev->device_handle, &s); - if (-1 == ret) - return ret; - /* Open a udev device from the dev_t. 'c' means character device. */ - udev_dev = udev_device_new_from_devnum(udev, 'c', s.st_rdev); - if (udev_dev) { - hid_dev = udev_device_get_parent_with_subsystem_devtype( - udev_dev, - "hid", - NULL); - if (hid_dev) { - unsigned short dev_vid; - unsigned short dev_pid; - unsigned bus_type; - size_t retm; - - ret = parse_uevent_info( - udev_device_get_sysattr_value(hid_dev, "uevent"), - &bus_type, - &dev_vid, - &dev_pid, - &serial_number_utf8, - &product_name_utf8); - - /* Standard USB device */ - if (bus_type == BUS_USB) { - /* This is a USB device. Find its parent USB Device node. */ - parent = udev_device_get_parent_with_subsystem_devtype( - udev_dev, - "usb", - "usb_device"); - if (parent) { - const char *str; - const char *key_str = NULL; - - if (key >= 0 && key < DEVICE_STRING_COUNT) { - key_str = device_string_names[key]; - } else { - ret = -1; - goto end; - } - - str = udev_device_get_sysattr_value(parent, key_str); - if (str) { - /* Convert the string from UTF-8 to wchar_t */ - retm = mbstowcs(string, str, maxlen); - ret = (retm == (size_t)-1)? -1: 0; - } - - /* USB information parsed */ - goto end; - } - else { - /* Correctly handled below */ - } + result = parse_uevent_info( + udev_device_get_sysattr_value(hid_dev, "uevent"), + &bus_type, + &dev_vid, + &dev_pid, + &serial_number_utf8, + &product_name_utf8); + + if (!result) { + /* parse_uevent_info() failed for at least one field. */ + goto end; + } + + /* Filter out unhandled devices right away */ + switch (bus_type) { + case BUS_BLUETOOTH: + case BUS_I2C: + case BUS_USB: + case BUS_SPI: + break; + + default: + goto end; + } + + /* Create the record. */ + root = (struct hid_device_info*) calloc(1, sizeof(struct hid_device_info)); + if (!root) + goto end; + + cur_dev = root; + + /* Fill out the record */ + cur_dev->next = NULL; + cur_dev->path = dev_path? strdup(dev_path): NULL; + + /* VID/PID */ + cur_dev->vendor_id = dev_vid; + cur_dev->product_id = dev_pid; + + /* Serial Number */ + cur_dev->serial_number = utf8_to_wchar_t(serial_number_utf8); + + /* Release Number */ + cur_dev->release_number = 0x0; + + /* Interface Number */ + cur_dev->interface_number = -1; + + switch (bus_type) { + case BUS_USB: + /* The device pointed to by raw_dev contains information about + the hidraw device. In order to get information about the + USB device, get the parent device with the + subsystem/devtype pair of "usb"/"usb_device". This will + be several levels up the tree, but the function will find + it. */ + usb_dev = udev_device_get_parent_with_subsystem_devtype( + raw_dev, + "usb", + "usb_device"); + + /* uhid USB devices + * Since this is a virtual hid interface, no USB information will + * be available. */ + if (!usb_dev) { + /* Manufacturer and Product strings */ + cur_dev->manufacturer_string = wcsdup(L""); + cur_dev->product_string = utf8_to_wchar_t(product_name_utf8); + break; } - /* USB information not available (uhid) or another type of HID bus */ - switch (bus_type) { - case BUS_BLUETOOTH: - case BUS_I2C: - case BUS_USB: - switch (key) { - case DEVICE_STRING_MANUFACTURER: - wcsncpy(string, L"", maxlen); - ret = 0; - break; - case DEVICE_STRING_PRODUCT: - retm = mbstowcs(string, product_name_utf8, maxlen); - ret = (retm == (size_t)-1)? -1: 0; - break; - case DEVICE_STRING_SERIAL: - retm = mbstowcs(string, serial_number_utf8, maxlen); - ret = (retm == (size_t)-1)? -1: 0; - break; - case DEVICE_STRING_COUNT: - default: - ret = -1; - break; - } + cur_dev->manufacturer_string = copy_udev_string(usb_dev, "manufacturer"); + cur_dev->product_string = copy_udev_string(usb_dev, "product"); + + cur_dev->bus_type = HID_API_BUS_USB; + + str = udev_device_get_sysattr_value(usb_dev, "bcdDevice"); + cur_dev->release_number = (str)? strtol(str, NULL, 16): 0x0; + + /* Get a handle to the interface's udev node. */ + intf_dev = udev_device_get_parent_with_subsystem_devtype( + raw_dev, + "usb", + "usb_interface"); + if (intf_dev) { + str = udev_device_get_sysattr_value(intf_dev, "bInterfaceNumber"); + cur_dev->interface_number = (str)? strtol(str, NULL, 16): -1; } + + break; + + case BUS_BLUETOOTH: + cur_dev->manufacturer_string = wcsdup(L""); + cur_dev->product_string = utf8_to_wchar_t(product_name_utf8); + + cur_dev->bus_type = HID_API_BUS_BLUETOOTH; + + break; + case BUS_I2C: + cur_dev->manufacturer_string = wcsdup(L""); + cur_dev->product_string = utf8_to_wchar_t(product_name_utf8); + + cur_dev->bus_type = HID_API_BUS_I2C; + + break; + + case BUS_SPI: + cur_dev->manufacturer_string = wcsdup(L""); + cur_dev->product_string = utf8_to_wchar_t(product_name_utf8); + + cur_dev->bus_type = HID_API_BUS_SPI; + + break; + + default: + /* Unknown device type - this should never happen, as we + * check for USB and Bluetooth devices above */ + break; + } + + /* Usage Page and Usage */ + result = get_hid_report_descriptor_from_sysfs(sysfs_path, &report_desc); + if (result >= 0) { + unsigned short page = 0, usage = 0; + unsigned int pos = 0; + /* + * Parse the first usage and usage page + * out of the report descriptor. + */ + if (!get_next_hid_usage(report_desc.value, report_desc.size, &pos, &page, &usage)) { + cur_dev->usage_page = page; + cur_dev->usage = usage; + } + + /* + * Parse any additional usage and usage pages + * out of the report descriptor. + */ + while (!get_next_hid_usage(report_desc.value, report_desc.size, &pos, &page, &usage)) { + /* Create new record for additional usage pairs */ + struct hid_device_info *tmp = (struct hid_device_info*) calloc(1, sizeof(struct hid_device_info)); + struct hid_device_info *prev_dev = cur_dev; + + if (!tmp) + continue; + cur_dev->next = tmp; + cur_dev = tmp; + + /* Update fields */ + cur_dev->path = dev_path? strdup(dev_path): NULL; + cur_dev->vendor_id = dev_vid; + cur_dev->product_id = dev_pid; + cur_dev->serial_number = prev_dev->serial_number? wcsdup(prev_dev->serial_number): NULL; + cur_dev->release_number = prev_dev->release_number; + cur_dev->interface_number = prev_dev->interface_number; + cur_dev->manufacturer_string = prev_dev->manufacturer_string? wcsdup(prev_dev->manufacturer_string): NULL; + cur_dev->product_string = prev_dev->product_string? wcsdup(prev_dev->product_string): NULL; + cur_dev->usage_page = page; + cur_dev->usage = usage; + cur_dev->bus_type = prev_dev->bus_type; } } @@ -594,12 +745,47 @@ static int get_device_string(hid_device *dev, enum device_string_id key, wchar_t free(serial_number_utf8); free(product_name_utf8); + return root; +} + +static struct hid_device_info * create_device_info_for_hid_device(hid_device *dev) { + struct udev *udev; + struct udev_device *udev_dev; + struct stat s; + int ret = -1; + struct hid_device_info *root = NULL; + + register_device_error(dev, NULL); + + /* Get the dev_t (major/minor numbers) from the file handle. */ + ret = fstat(dev->device_handle, &s); + if (-1 == ret) { + register_device_error(dev, "Failed to stat device handle"); + return NULL; + } + + /* Create the udev object */ + udev = udev_new(); + if (!udev) { + register_device_error(dev, "Couldn't create udev context"); + return NULL; + } + + /* Open a udev device from the dev_t. 'c' means character device. */ + udev_dev = udev_device_new_from_devnum(udev, 'c', s.st_rdev); + if (udev_dev) { + root = create_device_info_for_device(udev_dev); + } + + if (!root) { + /* TODO: have a better error reporting via create_device_info_for_device */ + register_device_error(dev, "Couldn't create hid_device_info"); + } + udev_device_unref(udev_dev); - /* parent and hid_dev don't need to be (and can't be) unref'd. - I'm not sure why, but they'll throw double-free() errors. */ udev_unref(udev); - return ret; + return root; } HID_API_EXPORT const struct hid_api_version* HID_API_CALL hid_version() @@ -616,6 +802,9 @@ int HID_API_EXPORT hid_init(void) { const char *locale; + /* indicate no error */ + register_global_error(NULL); + /* Set the locale if it's not set. */ locale = setlocale(LC_CTYPE, NULL); if (!locale) @@ -632,7 +821,6 @@ int HID_API_EXPORT hid_exit(void) return 0; } - struct hid_device_info HID_API_EXPORT *hid_enumerate(unsigned short vendor_id, unsigned short product_id) { struct udev *udev; @@ -641,9 +829,9 @@ struct hid_device_info HID_API_EXPORT *hid_enumerate(unsigned short vendor_id, struct hid_device_info *root = NULL; /* return object */ struct hid_device_info *cur_dev = NULL; - struct hid_device_info *prev_dev = NULL; /* previous device */ hid_init(); + /* register_global_error: global error is reset by hid_init */ /* Create the udev object */ udev = udev_new(); @@ -661,202 +849,62 @@ struct hid_device_info HID_API_EXPORT *hid_enumerate(unsigned short vendor_id, create a udev_device record for it */ udev_list_entry_foreach(dev_list_entry, devices) { const char *sysfs_path; - const char *dev_path; - const char *str; + unsigned short dev_vid = 0; + unsigned short dev_pid = 0; + unsigned bus_type = 0; struct udev_device *raw_dev; /* The device's hidraw udev node. */ - struct udev_device *hid_dev; /* The device's HID udev node. */ - struct udev_device *usb_dev; /* The device's USB udev node. */ - struct udev_device *intf_dev; /* The device's interface (in the USB sense). */ - unsigned short dev_vid; - unsigned short dev_pid; - char *serial_number_utf8 = NULL; - char *product_name_utf8 = NULL; - unsigned bus_type; - int result; - struct hidraw_report_descriptor report_desc; + struct hid_device_info * tmp; /* Get the filename of the /sys entry for the device and create a udev_device object (dev) representing it */ sysfs_path = udev_list_entry_get_name(dev_list_entry); - raw_dev = udev_device_new_from_syspath(udev, sysfs_path); - dev_path = udev_device_get_devnode(raw_dev); - - hid_dev = udev_device_get_parent_with_subsystem_devtype( - raw_dev, - "hid", - NULL); - - if (!hid_dev) { - /* Unable to find parent hid device. */ - goto next; - } - - result = parse_uevent_info( - udev_device_get_sysattr_value(hid_dev, "uevent"), - &bus_type, - &dev_vid, - &dev_pid, - &serial_number_utf8, - &product_name_utf8); - - if (!result) { - /* parse_uevent_info() failed for at least one field. */ - goto next; - } + if (!sysfs_path) + continue; - /* Filter out unhandled devices right away */ - switch (bus_type) { - case BUS_BLUETOOTH: - case BUS_I2C: - case BUS_USB: - break; + if (vendor_id != 0 || product_id != 0) { + if (!parse_hid_vid_pid_from_sysfs(sysfs_path, &bus_type, &dev_vid, &dev_pid)) + continue; - default: - goto next; + if (vendor_id != 0 && vendor_id != dev_vid) + continue; + if (product_id != 0 && product_id != dev_pid) + continue; } - /* Check the VID/PID against the arguments */ - if ((vendor_id == 0x0 || vendor_id == dev_vid) && - (product_id == 0x0 || product_id == dev_pid)) { - struct hid_device_info *tmp; + raw_dev = udev_device_new_from_syspath(udev, sysfs_path); + if (!raw_dev) + continue; - /* VID/PID match. Create the record. */ - tmp = (struct hid_device_info*) calloc(1, sizeof(struct hid_device_info)); + tmp = create_device_info_for_device(raw_dev); + if (tmp) { if (cur_dev) { cur_dev->next = tmp; } else { root = tmp; } - prev_dev = cur_dev; cur_dev = tmp; - /* Fill out the record */ - cur_dev->next = NULL; - cur_dev->path = dev_path? strdup(dev_path): NULL; - - /* VID/PID */ - cur_dev->vendor_id = dev_vid; - cur_dev->product_id = dev_pid; - - /* Serial Number */ - cur_dev->serial_number = utf8_to_wchar_t(serial_number_utf8); - - /* Release Number */ - cur_dev->release_number = 0x0; - - /* Interface Number */ - cur_dev->interface_number = -1; - - switch (bus_type) { - case BUS_USB: - /* The device pointed to by raw_dev contains information about - the hidraw device. In order to get information about the - USB device, get the parent device with the - subsystem/devtype pair of "usb"/"usb_device". This will - be several levels up the tree, but the function will find - it. */ - usb_dev = udev_device_get_parent_with_subsystem_devtype( - raw_dev, - "usb", - "usb_device"); - - /* uhid USB devices - Since this is a virtual hid interface, no USB information will - be available. */ - if (!usb_dev) { - /* Manufacturer and Product strings */ - cur_dev->manufacturer_string = wcsdup(L""); - cur_dev->product_string = utf8_to_wchar_t(product_name_utf8); - break; - } - - /* Manufacturer and Product strings */ - cur_dev->manufacturer_string = copy_udev_string(usb_dev, device_string_names[DEVICE_STRING_MANUFACTURER]); - cur_dev->product_string = copy_udev_string(usb_dev, device_string_names[DEVICE_STRING_PRODUCT]); - - /* Release Number */ - str = udev_device_get_sysattr_value(usb_dev, "bcdDevice"); - cur_dev->release_number = (str)? strtol(str, NULL, 16): 0x0; - - /* Get a handle to the interface's udev node. */ - intf_dev = udev_device_get_parent_with_subsystem_devtype( - raw_dev, - "usb", - "usb_interface"); - if (intf_dev) { - str = udev_device_get_sysattr_value(intf_dev, "bInterfaceNumber"); - cur_dev->interface_number = (str)? strtol(str, NULL, 16): -1; - } - - break; - - case BUS_BLUETOOTH: - case BUS_I2C: - /* Manufacturer and Product strings */ - cur_dev->manufacturer_string = wcsdup(L""); - cur_dev->product_string = utf8_to_wchar_t(product_name_utf8); - - break; - - default: - /* Unknown device type - this should never happen, as we - * check for USB and Bluetooth devices above */ - break; - } - - /* Usage Page and Usage */ - result = get_hid_report_descriptor_from_sysfs(sysfs_path, &report_desc); - if (result >= 0) { - unsigned short page = 0, usage = 0; - unsigned int pos = 0; - /* - * Parse the first usage and usage page - * out of the report descriptor. - */ - if (!get_next_hid_usage(report_desc.value, report_desc.size, &pos, &page, &usage)) { - cur_dev->usage_page = page; - cur_dev->usage = usage; - } - - /* - * Parse any additional usage and usage pages - * out of the report descriptor. - */ - while (!get_next_hid_usage(report_desc.value, report_desc.size, &pos, &page, &usage)) { - /* Create new record for additional usage pairs */ - tmp = (struct hid_device_info*) calloc(1, sizeof(struct hid_device_info)); - cur_dev->next = tmp; - prev_dev = cur_dev; - cur_dev = tmp; - - /* Update fields */ - cur_dev->path = strdup(dev_path); - cur_dev->vendor_id = dev_vid; - cur_dev->product_id = dev_pid; - cur_dev->serial_number = prev_dev->serial_number? wcsdup(prev_dev->serial_number): NULL; - cur_dev->release_number = prev_dev->release_number; - cur_dev->interface_number = prev_dev->interface_number; - cur_dev->manufacturer_string = prev_dev->manufacturer_string? wcsdup(prev_dev->manufacturer_string): NULL; - cur_dev->product_string = prev_dev->product_string? wcsdup(prev_dev->product_string): NULL; - cur_dev->usage_page = page; - cur_dev->usage = usage; - } + /* move the pointer to the tail of returnd list */ + while (cur_dev->next != NULL) { + cur_dev = cur_dev->next; } } - next: - free(serial_number_utf8); - free(product_name_utf8); udev_device_unref(raw_dev); - /* hid_dev, usb_dev and intf_dev don't need to be (and can't be) - unref()d. It will cause a double-free() error. I'm not - sure why. */ } /* Free the enumerator and udev objects. */ udev_enumerate_unref(enumerate); udev_unref(udev); + if (root == NULL) { + if (vendor_id == 0 && product_id == 0) { + register_global_error("No HID devices found in the system."); + } else { + register_global_error("No HID devices with requested VID/PID found in the system."); + } + } + return root; } @@ -876,14 +924,17 @@ void HID_API_EXPORT hid_free_enumeration(struct hid_device_info *devs) hid_device * hid_open(unsigned short vendor_id, unsigned short product_id, const wchar_t *serial_number) { - /* Set global error to none */ - register_global_error(NULL); - struct hid_device_info *devs, *cur_dev; const char *path_to_open = NULL; hid_device *handle = NULL; + /* register_global_error: global error is reset by hid_enumerate/hid_init */ devs = hid_enumerate(vendor_id, product_id); + if (devs == NULL) { + /* register_global_error: global error is already set by hid_enumerate */ + return NULL; + } + cur_dev = devs; while (cur_dev) { if (cur_dev->vendor_id == vendor_id && @@ -906,7 +957,7 @@ hid_device * hid_open(unsigned short vendor_id, unsigned short product_id, const /* Open the device */ handle = hid_open_path(path_to_open); } else { - register_global_error("No such device"); + register_global_error("Device with requested VID/PID/(SerialNumber) not found"); } hid_free_enumeration(devs); @@ -916,52 +967,36 @@ hid_device * hid_open(unsigned short vendor_id, unsigned short product_id, const hid_device * HID_API_EXPORT hid_open_path(const char *path) { - /* Set global error to none */ - register_global_error(NULL); - hid_device *dev = NULL; hid_init(); + /* register_global_error: global error is reset by hid_init */ dev = new_hid_device(); + if (!dev) { + register_global_error("Couldn't allocate memory"); + return NULL; + } - /* OPEN HERE */ - dev->device_handle = open(path, O_RDWR); + dev->device_handle = open(path, O_RDWR | O_CLOEXEC); - /* If we have a good handle, return it. */ if (dev->device_handle >= 0) { - /* Set device error to none */ - register_device_error(dev, NULL); - - /* Get the report descriptor */ int res, desc_size = 0; - struct hidraw_report_descriptor rpt_desc; - - memset(&rpt_desc, 0x0, sizeof(rpt_desc)); - /* Get Report Descriptor Size */ + /* Make sure this is a HIDRAW device - responds to HIDIOCGRDESCSIZE */ res = ioctl(dev->device_handle, HIDIOCGRDESCSIZE, &desc_size); - if (res < 0) - register_device_error_format(dev, "ioctl (GRDESCSIZE): %s", strerror(errno)); - - /* Get Report Descriptor */ - rpt_desc.size = desc_size; - res = ioctl(dev->device_handle, HIDIOCGRDESC, &rpt_desc); if (res < 0) { - register_device_error_format(dev, "ioctl (GRDESC): %s", strerror(errno)); - } else { - /* Determine if this device uses numbered reports. */ - dev->uses_numbered_reports = - uses_numbered_reports(rpt_desc.value, - rpt_desc.size); + hid_close(dev); + register_device_error_format(dev, "ioctl(GRDESCSIZE) error for '%s', not a HIDRAW device?: %s", path, strerror(errno)); + return NULL; } return dev; } else { - /* Unable to open any devices. */ - register_global_error(strerror(errno)); + /* Unable to open a device. */ free(dev); + register_global_error_format("Failed to open a device with path '%s': %s", path, strerror(errno)); return NULL; } } @@ -1018,9 +1053,11 @@ int HID_API_EXPORT hid_read_timeout(hid_device *dev, unsigned char *data, size_t else { /* Check for errors on the file descriptor. This will indicate a device disconnection. */ - if (fds.revents & (POLLERR | POLLHUP | POLLNVAL)) + if (fds.revents & (POLLERR | POLLHUP | POLLNVAL)) { // We cannot use strerror() here as no -1 was returned from poll(). + register_device_error(dev, "hid_read_timeout: unexpected poll error (device disconnected)"); return -1; + } } } @@ -1055,6 +1092,8 @@ int HID_API_EXPORT hid_send_feature_report(hid_device *dev, const unsigned char { int res; + register_device_error(dev, NULL); + res = ioctl(dev->device_handle, HIDIOCSFEATURE(length), data); if (res < 0) register_device_error_format(dev, "ioctl (SFEATURE): %s", strerror(errno)); @@ -1066,6 +1105,8 @@ int HID_API_EXPORT hid_get_feature_report(hid_device *dev, unsigned char *data, { int res; + register_device_error(dev, NULL); + res = ioctl(dev->device_handle, HIDIOCGFEATURE(length), data); if (res < 0) register_device_error_format(dev, "ioctl (GFEATURE): %s", strerror(errno)); @@ -1077,6 +1118,8 @@ int HID_API_EXPORT HID_API_CALL hid_get_input_report(hid_device *dev, unsigned c { int res; + register_device_error(dev, NULL); + res = ioctl(dev->device_handle, HIDIOCGINPUT(length), data); if (res < 0) register_device_error_format(dev, "ioctl (GINPUT): %s", strerror(errno)); @@ -1089,38 +1132,108 @@ void HID_API_EXPORT hid_close(hid_device *dev) if (!dev) return; - int ret = close(dev->device_handle); - - register_global_error((ret == -1)? strerror(errno): NULL); + close(dev->device_handle); /* Free the device error message */ register_device_error(dev, NULL); + hid_free_enumeration(dev->device_info); + free(dev); } int HID_API_EXPORT_CALL hid_get_manufacturer_string(hid_device *dev, wchar_t *string, size_t maxlen) { - return get_device_string(dev, DEVICE_STRING_MANUFACTURER, string, maxlen); + if (!string || !maxlen) { + register_device_error(dev, "Zero buffer/length"); + return -1; + } + + struct hid_device_info *info = hid_get_device_info(dev); + if (!info) { + // hid_get_device_info will have set an error already + return -1; + } + + if (info->manufacturer_string) { + wcsncpy(string, info->manufacturer_string, maxlen); + string[maxlen - 1] = L'\0'; + } + else { + string[0] = L'\0'; + } + + return 0; } int HID_API_EXPORT_CALL hid_get_product_string(hid_device *dev, wchar_t *string, size_t maxlen) { - return get_device_string(dev, DEVICE_STRING_PRODUCT, string, maxlen); + if (!string || !maxlen) { + register_device_error(dev, "Zero buffer/length"); + return -1; + } + + struct hid_device_info *info = hid_get_device_info(dev); + if (!info) { + // hid_get_device_info will have set an error already + return -1; + } + + if (info->product_string) { + wcsncpy(string, info->product_string, maxlen); + string[maxlen - 1] = L'\0'; + } + else { + string[0] = L'\0'; + } + + return 0; } int HID_API_EXPORT_CALL hid_get_serial_number_string(hid_device *dev, wchar_t *string, size_t maxlen) { - return get_device_string(dev, DEVICE_STRING_SERIAL, string, maxlen); + if (!string || !maxlen) { + register_device_error(dev, "Zero buffer/length"); + return -1; + } + + struct hid_device_info *info = hid_get_device_info(dev); + if (!info) { + // hid_get_device_info will have set an error already + return -1; + } + + if (info->serial_number) { + wcsncpy(string, info->serial_number, maxlen); + string[maxlen - 1] = L'\0'; + } + else { + string[0] = L'\0'; + } + + return 0; +} + + +HID_API_EXPORT struct hid_device_info *HID_API_CALL hid_get_device_info(hid_device *dev) { + if (!dev->device_info) { + // Lazy initialize device_info + dev->device_info = create_device_info_for_hid_device(dev); + } + + // create_device_info_for_hid_device will set an error if needed + return dev->device_info; } int HID_API_EXPORT_CALL hid_get_indexed_string(hid_device *dev, int string_index, wchar_t *string, size_t maxlen) { - (void)dev; (void)string_index; (void)string; (void)maxlen; + + register_device_error(dev, "hid_get_indexed_string: not supported by hidraw"); + return -1; } diff --git a/hid_windows.c b/hid_windows.c index da47ff6..3769276 100644 --- a/hid_windows.c +++ b/hid_windows.c @@ -189,6 +189,11 @@ struct hid_device_ { static hid_device *new_hid_device() { hid_device *dev = (hid_device*) calloc(1, sizeof(hid_device)); + + if (dev == NULL) { + return NULL; + } + dev->device_handle = INVALID_HANDLE_VALUE; dev->blocking = TRUE; dev->output_report_length = 0; @@ -224,9 +229,6 @@ static void free_hid_device(hid_device *dev) static void register_winapi_error_to_buffer(wchar_t **error_buffer, const WCHAR *op) { - if (!error_buffer) - return; - free(*error_buffer); *error_buffer = NULL; @@ -281,19 +283,8 @@ static void register_winapi_error_to_buffer(wchar_t **error_buffer, const WCHAR } } -static void register_winapi_error(hid_device *dev, const WCHAR *op) -{ - if (!dev) - return; - - register_winapi_error_to_buffer(&dev->last_error_str, op); -} - static void register_string_error_to_buffer(wchar_t **error_buffer, const WCHAR *string_error) { - if (!error_buffer) - return; - free(*error_buffer); *error_buffer = NULL; @@ -302,14 +293,28 @@ static void register_string_error_to_buffer(wchar_t **error_buffer, const WCHAR } } -static void register_string_error(hid_device *dev, const WCHAR *string_error) +static void register_winapi_error(hid_device *dev, const WCHAR *op) { - if (!dev) - return; + register_winapi_error_to_buffer(&dev->last_error_str, op); +} +static void register_string_error(hid_device *dev, const WCHAR *string_error) +{ register_string_error_to_buffer(&dev->last_error_str, string_error); } +static wchar_t *last_global_error_str = NULL; + +static void register_global_winapi_error(const WCHAR *op) +{ + register_winapi_error_to_buffer(&last_global_error_str, op); +} + +static void register_global_error(const WCHAR *string_error) +{ + register_string_error_to_buffer(&last_global_error_str, string_error); +} + static HANDLE open_device(const wchar_t *path, BOOL open_rw) { HANDLE handle; @@ -339,9 +344,11 @@ HID_API_EXPORT const char* HID_API_CALL hid_version_str() int HID_API_EXPORT hid_init(void) { + register_global_error(NULL); #ifndef HIDAPI_USE_DDK if (!hidapi_initialized) { if (lookup_functions() < 0) { + register_global_winapi_error(L"resolve DLL functions"); return -1; } hidapi_initialized = TRUE; @@ -356,6 +363,7 @@ int HID_API_EXPORT hid_exit(void) free_library_handles(); hidapi_initialized = FALSE; #endif + register_global_error(NULL); return 0; } @@ -502,12 +510,43 @@ static void hid_internal_get_info(const wchar_t* interface_path, struct hid_devi /* Normalize to upper case */ for (wchar_t* p = compatible_id; *p; ++p) *p = towupper(*p); + /* 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 */ + if (wcsstr(compatible_id, L"USB") != NULL) { + dev->bus_type = HID_API_BUS_USB; + break; + } + + /* Bluetooth devices + https://docs.microsoft.com/windows-hardware/drivers/bluetooth/installing-a-bluetooth-device */ + if (wcsstr(compatible_id, L"BTHENUM") != NULL) { + dev->bus_type = HID_API_BUS_BLUETOOTH; + break; + } + /* Bluetooth LE devices */ if (wcsstr(compatible_id, L"BTHLEDEVICE") != NULL) { /* 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 */ hid_internal_get_ble_info(dev, dev_node); + + dev->bus_type = HID_API_BUS_BLUETOOTH; + break; + } + + /* I2C devices + https://docs.microsoft.com/windows-hardware/drivers/hid/plug-and-play-support-and-power-management */ + if (wcsstr(compatible_id, L"PNP0C50") != NULL) { + dev->bus_type = HID_API_BUS_I2C; + break; + } + + /* SPI devices + https://docs.microsoft.com/windows-hardware/drivers/hid/plug-and-play-for-spi */ + if (wcsstr(compatible_id, L"PNP0C51") != NULL) { + dev->bus_type = HID_API_BUS_SPI; break; } } @@ -523,6 +562,9 @@ static char *hid_internal_UTF16toUTF8(const wchar_t *src) int len = WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, src, -1, NULL, 0, NULL, NULL); if (len) { dst = (char*)calloc(len, sizeof(char)); + if (dst == NULL) { + return NULL; + } WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, src, -1, dst, len, NULL, NULL); } @@ -535,6 +577,9 @@ static wchar_t *hid_internal_UTF8toUTF16(const char *src) int len = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, src, -1, NULL, 0); if (len) { dst = (wchar_t*)calloc(len, sizeof(wchar_t)); + if (dst == NULL) { + return NULL; + } MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, src, -1, dst, len); } @@ -608,8 +653,10 @@ struct hid_device_info HID_API_EXPORT * HID_API_CALL hid_enumerate(unsigned shor wchar_t* device_interface_list = NULL; DWORD len; - if (hid_init() < 0) + if (hid_init() < 0) { + /* register_global_error: global error is reset by hid_init */ return NULL; + } /* Retrieve HID Interface Class GUID https://docs.microsoft.com/windows-hardware/drivers/install/guid-devinterface-hid */ @@ -621,6 +668,7 @@ struct hid_device_info HID_API_EXPORT * HID_API_CALL hid_enumerate(unsigned shor do { cr = CM_Get_Device_Interface_List_SizeW(&len, &interface_class_guid, NULL, CM_GET_DEVICE_INTERFACE_LIST_PRESENT); if (cr != CR_SUCCESS) { + register_global_error(L"Failed to get size of HID device interface list"); break; } @@ -630,9 +678,13 @@ struct hid_device_info HID_API_EXPORT * HID_API_CALL hid_enumerate(unsigned shor device_interface_list = (wchar_t*)calloc(len, sizeof(wchar_t)); if (device_interface_list == NULL) { + register_global_error(L"Failed to allocate memory for HID device interface list"); return NULL; } cr = CM_Get_Device_Interface_ListW(&interface_class_guid, NULL, device_interface_list, len, CM_GET_DEVICE_INTERFACE_LIST_PRESENT); + if (cr != CR_SUCCESS && cr != CR_BUFFER_SMALL) { + register_global_error(L"Failed to get HID device interface list"); + } } while (cr == CR_BUFFER_SMALL); if (cr != CR_SUCCESS) { @@ -684,6 +736,14 @@ struct hid_device_info HID_API_EXPORT * HID_API_CALL hid_enumerate(unsigned shor CloseHandle(device_handle); } + if (root == NULL) { + if (vendor_id == 0 && product_id == 0) { + register_global_error(L"No HID devices found in the system."); + } else { + register_global_error(L"No HID devices with requested VID/PID found in the system."); + } + } + end_of_function: free(device_interface_list); @@ -712,8 +772,10 @@ HID_API_EXPORT hid_device * HID_API_CALL hid_open(unsigned short vendor_id, unsi const char *path_to_open = NULL; hid_device *handle = NULL; + /* register_global_error: global error is reset by hid_enumerate/hid_init */ devs = hid_enumerate(vendor_id, product_id); if (!devs) { + /* register_global_error: global error is already set by hid_enumerate */ return NULL; } @@ -738,6 +800,8 @@ HID_API_EXPORT hid_device * HID_API_CALL hid_open(unsigned short vendor_id, unsi if (path_to_open) { /* Open the device */ handle = hid_open_path(path_to_open); + } else { + register_global_error(L"Device with requested VID/PID/(SerialNumber) not found"); } hid_free_enumeration(devs); @@ -753,12 +817,16 @@ HID_API_EXPORT hid_device * HID_API_CALL hid_open_path(const char *path) PHIDP_PREPARSED_DATA pp_data = NULL; HIDP_CAPS caps; - if (hid_init() < 0) + if (hid_init() < 0) { + /* register_global_error: global error is reset by hid_init */ goto end_of_function; + } interface_path = hid_internal_UTF8toUTF16(path); - if (!interface_path) + if (!interface_path) { + register_string_error(dev, L"Path conversion failure"); goto end_of_function; + } /* Open a handle to the device */ device_handle = open_device(interface_path, TRUE); @@ -773,23 +841,36 @@ HID_API_EXPORT hid_device * HID_API_CALL hid_open_path(const char *path) device_handle = open_device(interface_path, FALSE); /* Check the validity of the limited device_handle. */ - if (device_handle == INVALID_HANDLE_VALUE) + if (device_handle == INVALID_HANDLE_VALUE) { + register_global_winapi_error(L"open_device"); goto end_of_function; + } } /* Set the Input Report buffer size to 64 reports. */ - if (!HidD_SetNumInputBuffers(device_handle, 64)) + if (!HidD_SetNumInputBuffers(device_handle, 64)) { + register_global_winapi_error(L"set input buffers"); goto end_of_function; + } /* Get the Input Report length for the device. */ - if (!HidD_GetPreparsedData(device_handle, &pp_data)) + if (!HidD_GetPreparsedData(device_handle, &pp_data)) { + register_global_winapi_error(L"get preparsed data"); goto end_of_function; + } - if (HidP_GetCaps(pp_data, &caps) != HIDP_STATUS_SUCCESS) + if (HidP_GetCaps(pp_data, &caps) != HIDP_STATUS_SUCCESS) { + register_global_error(L"HidP_GetCaps"); goto end_of_function; + } dev = new_hid_device(); + if (dev == NULL) { + register_global_error(L"hid_device allocation error"); + goto end_of_function; + } + dev->device_handle = device_handle; device_handle = INVALID_HANDLE_VALUE; @@ -819,11 +900,13 @@ int HID_API_EXPORT HID_API_CALL hid_write(hid_device *dev, const unsigned char * unsigned char *buf; - if (!data || (length==0)) { + if (!data || !length) { register_string_error(dev, L"Zero buffer/length"); return function_result; } + register_string_error(dev, NULL); + /* 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 @@ -887,6 +970,13 @@ int HID_API_EXPORT HID_API_CALL hid_read_timeout(hid_device *dev, unsigned char BOOL res = FALSE; BOOL overlapped = FALSE; + if (!data || !length) { + register_string_error(dev, L"Zero buffer/length"); + return -1; + } + + register_string_error(dev, NULL); + /* Copy the handle for convenience. */ HANDLE ev = dev->ol.hEvent; @@ -977,6 +1067,13 @@ int HID_API_EXPORT HID_API_CALL hid_send_feature_report(hid_device *dev, const u unsigned char *buf; size_t length_to_send; + if (!data || !length) { + register_string_error(dev, L"Zero buffer/length"); + return -1; + } + + register_string_error(dev, NULL); + /* Windows expects at least caps.FeatureReportByteLength bytes passed to HidD_SetFeature(), even if the report is shorter. Any less sent and the function fails with error ERROR_INVALID_PARAMETER set. Any more @@ -1012,6 +1109,13 @@ static int hid_get_report(hid_device *dev, DWORD report_type, unsigned char *dat OVERLAPPED ol; memset(&ol, 0, sizeof(ol)); + if (!data || !length) { + register_string_error(dev, L"Zero buffer/length"); + return -1; + } + + register_string_error(dev, NULL); + res = DeviceIoControl(dev->device_handle, report_type, data, (DWORD) length, @@ -1068,66 +1172,74 @@ void HID_API_EXPORT HID_API_CALL hid_close(hid_device *dev) int HID_API_EXPORT_CALL HID_API_CALL hid_get_manufacturer_string(hid_device *dev, wchar_t *string, size_t maxlen) { - if (!dev->device_info) - { - register_string_error(dev, L"NULL device/info"); + if (!string || !maxlen) { + register_string_error(dev, L"Zero buffer/length"); return -1; } - if (!string || !maxlen) - { - register_string_error(dev, L"Zero buffer/length"); + if (!dev->device_info) { + register_string_error(dev, L"NULL device info"); return -1; } wcsncpy(string, dev->device_info->manufacturer_string, maxlen); string[maxlen - 1] = L'\0'; + register_string_error(dev, NULL); + return 0; } int HID_API_EXPORT_CALL HID_API_CALL hid_get_product_string(hid_device *dev, wchar_t *string, size_t maxlen) { - if (!dev->device_info) - { - register_string_error(dev, L"NULL device/info"); + if (!string || !maxlen) { + register_string_error(dev, L"Zero buffer/length"); return -1; } - if (!string || !maxlen) - { - register_string_error(dev, L"Zero buffer/length"); + if (!dev->device_info) { + register_string_error(dev, L"NULL device info"); return -1; } - wcsncpy(string, dev->device_info->product_string, maxlen); string[maxlen - 1] = L'\0'; + register_string_error(dev, NULL); + return 0; } int HID_API_EXPORT_CALL HID_API_CALL hid_get_serial_number_string(hid_device *dev, wchar_t *string, size_t maxlen) { - if (!dev->device_info) - { - register_string_error(dev, L"NULL device/info"); + if (!string || !maxlen) { + register_string_error(dev, L"Zero buffer/length"); return -1; } - if (!string || !maxlen) - { - register_string_error(dev, L"Zero buffer/length"); + if (!dev->device_info) { + register_string_error(dev, L"NULL device info"); return -1; } - wcsncpy(string, dev->device_info->serial_number, maxlen); string[maxlen - 1] = L'\0'; + register_string_error(dev, NULL); + return 0; } +HID_API_EXPORT struct hid_device_info * HID_API_CALL hid_get_device_info(hid_device *dev) { + if (!dev->device_info) + { + register_string_error(dev, L"NULL device info"); + return NULL; + } + + return dev->device_info; +} + int HID_API_EXPORT_CALL HID_API_CALL hid_get_indexed_string(hid_device *dev, int string_index, wchar_t *string, size_t maxlen) { BOOL res; @@ -1138,6 +1250,8 @@ int HID_API_EXPORT_CALL HID_API_CALL hid_get_indexed_string(hid_device *dev, int return -1; } + register_string_error(dev, NULL); + return 0; } @@ -1149,31 +1263,29 @@ int HID_API_EXPORT_CALL hid_winapi_get_container_id(hid_device *dev, GUID *conta DEVPROPTYPE property_type; ULONG len; - if (!container_id) - { + if (!container_id) { register_string_error(dev, L"Invalid Container ID"); return -1; } + register_string_error(dev, NULL); + interface_path = hid_internal_UTF8toUTF16(dev->device_info->path); - if (!interface_path) - { + if (!interface_path) { register_string_error(dev, L"Path conversion failure"); goto end; } /* Get the device id from interface path */ device_id = hid_internal_get_device_interface_property(interface_path, &DEVPKEY_Device_InstanceId, DEVPROP_TYPE_STRING); - if (!device_id) - { + if (!device_id) { register_string_error(dev, L"Failed to get device interface property InstanceId"); goto end; } /* Open devnode from device id */ cr = CM_Locate_DevNodeW(&dev_node, (DEVINSTID_W)device_id, CM_LOCATE_DEVNODE_NORMAL); - if (cr != CR_SUCCESS) - { + if (cr != CR_SUCCESS) { register_string_error(dev, L"Failed to locate device node"); goto end; } @@ -1203,8 +1315,9 @@ HID_API_EXPORT const wchar_t * HID_API_CALL hid_error(hid_device *dev) return (wchar_t*)dev->last_error_str; } - /* Global error messages are not (yet) implemented on Windows. */ - return L"hid_error for global errors is not implemented yet"; + if (last_global_error_str == NULL) + return L"Success"; + return last_global_error_str; } #ifdef __cplusplus diff --git a/hidapi.h b/hidapi.h index 959c912..2b261f1 100644 --- a/hidapi.h +++ b/hidapi.h @@ -48,7 +48,7 @@ @ingroup API */ -#define HID_API_VERSION_MINOR 12 +#define HID_API_VERSION_MINOR 13 /** @brief Static/compile-time patch version of the library. @ingroup API @@ -88,6 +88,14 @@ */ #define HID_API_VERSION_STR HID_API_TO_VERSION_STR(HID_API_VERSION_MAJOR, HID_API_VERSION_MINOR, HID_API_VERSION_PATCH) +/** @brief Maximum expected HID Report descriptor size in bytes. + + Since version 0.13.0, @ref HID_API_VERSION >= HID_API_MAKE_VERSION(0, 13, 0) + + @ingroup API +*/ +#define HID_API_MAX_REPORT_DESCRIPTOR_SIZE 4096 + #ifdef __cplusplus extern "C" { #endif @@ -100,6 +108,37 @@ extern "C" { struct hid_device_; typedef struct hid_device_ hid_device; /**< opaque hidapi structure */ + /** @brief HID underlying bus types. + + @ingroup API + */ + typedef enum { + /* Unknown bus type */ + HID_API_BUS_UNKNOWN = 0x00, + + /* USB bus + Specifications: + https://usb.org/hid */ + HID_API_BUS_USB = 0x01, + + /* Bluetooth or Bluetooth LE bus + Specifications: + https://www.bluetooth.com/specifications/specs/human-interface-device-profile-1-1-1/ + https://www.bluetooth.com/specifications/specs/hid-service-1-0/ + https://www.bluetooth.com/specifications/specs/hid-over-gatt-profile-1-0/ */ + HID_API_BUS_BLUETOOTH = 0x02, + + /* I2C bus + Specifications: + https://docs.microsoft.com/previous-versions/windows/hardware/design/dn642101(v=vs.85) */ + HID_API_BUS_I2C = 0x03, + + /* SPI bus + Specifications: + https://www.microsoft.com/download/details.aspx?id=103325 */ + HID_API_BUS_SPI = 0x04, + } hid_bus_type; + /** hidapi info structure */ struct hid_device_info { /** Platform-specific device path */ @@ -135,6 +174,11 @@ extern "C" { /** Pointer to the next device */ struct hid_device_info *next; + + /** Underlying bus type + Since version 0.13.0, @ref HID_API_VERSION >= HID_API_MAKE_VERSION(0, 13, 0) + */ + hid_bus_type bus_type; }; @@ -151,6 +195,7 @@ extern "C" { @returns This function returns 0 on success and -1 on error. + Call hid_error(NULL) to get the failure reason. */ int HID_API_EXPORT HID_API_CALL hid_init(void); @@ -162,7 +207,7 @@ extern "C" { @ingroup API - @returns + @returns This function returns 0 on success and -1 on error. */ int HID_API_EXPORT HID_API_CALL hid_exit(void); @@ -182,21 +227,25 @@ extern "C" { @param product_id The Product ID (PID) of the types of device to open. - @returns - This function returns a pointer to a linked list of type - struct #hid_device_info, containing information about the HID devices - attached to the system, or NULL in the case of failure. Free - this linked list by calling hid_free_enumeration(). + @returns + This function returns a pointer to a linked list of type + struct #hid_device_info, containing information about the HID devices + attached to the system, + or NULL in the case of failure or if no HID devices present in the system. + Call hid_error(NULL) to get the failure reason. + + @note The returned value by this function must to be freed by calling hid_free_enumeration(), + when not needed anymore. */ struct hid_device_info HID_API_EXPORT * HID_API_CALL hid_enumerate(unsigned short vendor_id, unsigned short product_id); /** @brief Free an enumeration Linked List - This function frees a linked list created by hid_enumerate(). + This function frees a linked list created by hid_enumerate(). @ingroup API - @param devs Pointer to a list of struct_device returned from - hid_enumerate(). + @param devs Pointer to a list of struct_device returned from + hid_enumerate(). */ void HID_API_EXPORT HID_API_CALL hid_free_enumeration(struct hid_device_info *devs); @@ -206,17 +255,19 @@ extern "C" { If @p serial_number is NULL, the first device with the specified VID and PID is opened. - This function sets the return value of hid_error(). - @ingroup API @param vendor_id The Vendor ID (VID) of the device to open. @param product_id The Product ID (PID) of the device to open. @param serial_number The Serial Number of the device to open - (Optionally NULL). + (Optionally NULL). @returns This function returns a pointer to a #hid_device object on success or NULL on failure. + Call hid_error(NULL) to get the failure reason. + + @note The returned object must be freed by calling hid_close(), + when not needed anymore. */ HID_API_EXPORT hid_device * HID_API_CALL hid_open(unsigned short vendor_id, unsigned short product_id, const wchar_t *serial_number); @@ -226,14 +277,16 @@ extern "C" { platform-specific path name can be used (eg: /dev/hidraw0 on Linux). - This function sets the return value of hid_error(). - @ingroup API - @param path The path name of the device to open + @param path The path name of the device to open @returns This function returns a pointer to a #hid_device object on success or NULL on failure. + Call hid_error(NULL) to get the failure reason. + + @note The returned object must be freed by calling hid_close(), + when not needed anymore. */ HID_API_EXPORT hid_device * HID_API_CALL hid_open_path(const char *path); @@ -253,8 +306,6 @@ extern "C" { one exists. If it does not, it will send the data through the Control Endpoint (Endpoint 0). - This function sets the return value of hid_error(). - @ingroup API @param dev A device handle returned from hid_open(). @param data The data to send, including the report number as @@ -264,6 +315,7 @@ extern "C" { @returns This function returns the actual number of bytes written and -1 on error. + Call hid_error(dev) to get the failure reason. */ int HID_API_EXPORT HID_API_CALL hid_write(hid_device *dev, const unsigned char *data, size_t length); @@ -273,8 +325,6 @@ extern "C" { to the host through the INTERRUPT IN endpoint. The first byte will contain the Report number if the device uses numbered reports. - This function sets the return value of hid_error(). - @ingroup API @param dev A device handle returned from hid_open(). @param data A buffer to put the read data into. @@ -285,7 +335,9 @@ extern "C" { @returns This function returns the actual number of bytes read and - -1 on error. If no packet was available to be read within + -1 on error. + Call hid_error(dev) to get the failure reason. + If no packet was available to be read within the timeout period, this function returns 0. */ int HID_API_EXPORT HID_API_CALL hid_read_timeout(hid_device *dev, unsigned char *data, size_t length, int milliseconds); @@ -293,11 +345,9 @@ extern "C" { /** @brief Read an Input report from a HID device. Input reports are returned - to the host through the INTERRUPT IN endpoint. The first byte will + to the host through the INTERRUPT IN endpoint. The first byte will contain the Report number if the device uses numbered reports. - This function sets the return value of hid_error(). - @ingroup API @param dev A device handle returned from hid_open(). @param data A buffer to put the read data into. @@ -307,7 +357,9 @@ extern "C" { @returns This function returns the actual number of bytes read and - -1 on error. If no packet was available to be read and + -1 on error. + Call hid_error(dev) to get the failure reason. + If no packet was available to be read and the handle is in non-blocking mode, this function returns 0. */ int HID_API_EXPORT HID_API_CALL hid_read(hid_device *dev, unsigned char *data, size_t length); @@ -329,6 +381,7 @@ extern "C" { @returns This function returns 0 on success and -1 on error. + Call hid_error(dev) to get the failure reason. */ int HID_API_EXPORT HID_API_CALL hid_set_nonblocking(hid_device *dev, int nonblock); @@ -347,8 +400,6 @@ extern "C" { report data (16 bytes). In this example, the length passed in would be 17. - This function sets the return value of hid_error(). - @ingroup API @param dev A device handle returned from hid_open(). @param data The data to send, including the report number as @@ -359,6 +410,7 @@ extern "C" { @returns This function returns the actual number of bytes written and -1 on error. + Call hid_error(dev) to get the failure reason. */ int HID_API_EXPORT HID_API_CALL hid_send_feature_report(hid_device *dev, const unsigned char *data, size_t length); @@ -370,8 +422,6 @@ extern "C" { still contain the Report ID, and the report data will start in data[1]. - This function sets the return value of hid_error(). - @ingroup API @param dev A device handle returned from hid_open(). @param data A buffer to put the read data into, including @@ -386,6 +436,7 @@ extern "C" { This function returns the number of bytes read plus one for the report ID (which is still in the first byte), or -1 on error. + Call hid_error(dev) to get the failure reason. */ int HID_API_EXPORT HID_API_CALL hid_get_feature_report(hid_device *dev, unsigned char *data, size_t length); @@ -400,7 +451,7 @@ extern "C" { start in data[1]. @ingroup API - @param device A device handle returned from hid_open(). + @param dev A device handle returned from hid_open(). @param data A buffer to put the read data into, including the Report ID. Set the first byte of @p data[] to the Report ID of the report to be read, or set it to zero @@ -413,13 +464,12 @@ extern "C" { This function returns the number of bytes read plus one for the report ID (which is still in the first byte), or -1 on error. + Call hid_error(dev) to get the failure reason. */ int HID_API_EXPORT HID_API_CALL hid_get_input_report(hid_device *dev, unsigned char *data, size_t length); /** @brief Close a HID device. - This function sets the return value of hid_error(). - @ingroup API @param dev A device handle returned from hid_open(). */ @@ -434,6 +484,7 @@ extern "C" { @returns This function returns 0 on success and -1 on error. + Call hid_error(dev) to get the failure reason. */ int HID_API_EXPORT_CALL hid_get_manufacturer_string(hid_device *dev, wchar_t *string, size_t maxlen); @@ -446,6 +497,7 @@ extern "C" { @returns This function returns 0 on success and -1 on error. + Call hid_error(dev) to get the failure reason. */ int HID_API_EXPORT_CALL hid_get_product_string(hid_device *dev, wchar_t *string, size_t maxlen); @@ -458,9 +510,27 @@ extern "C" { @returns This function returns 0 on success and -1 on error. + Call hid_error(dev) to get the failure reason. */ int HID_API_EXPORT_CALL hid_get_serial_number_string(hid_device *dev, wchar_t *string, size_t maxlen); + /** @brief Get The struct #hid_device_info from a HID device. + + Since version 0.13.0, @ref HID_API_VERSION >= HID_API_MAKE_VERSION(0, 13, 0) + + @ingroup API + @param dev A device handle returned from hid_open(). + + @returns + This function returns a pointer to the struct #hid_device_info + for this hid_device, or NULL in the case of failure. + Call hid_error(dev) to get the failure reason. + This struct is valid until the device is closed with hid_close(). + + @note The returned object is owned by the @p dev, and SHOULD NOT be freed by the user. + */ + struct hid_device_info HID_API_EXPORT * HID_API_CALL hid_get_device_info(hid_device *dev); + /** @brief Get a string from a HID device, based on its string index. @ingroup API @@ -471,32 +541,41 @@ extern "C" { @returns This function returns 0 on success and -1 on error. + Call hid_error(dev) to get the failure reason. */ int HID_API_EXPORT_CALL hid_get_indexed_string(hid_device *dev, int string_index, wchar_t *string, size_t maxlen); /** @brief Get a string describing the last error which occurred. - Whether a function sets the last error is noted in its - documentation. These functions will reset the last error - to NULL before their execution. + This function is intended for logging/debugging purposes. + + This function guarantees to never return NULL. + If there was no error in the last function call - + the returned string clearly indicates that. - Strings returned from hid_error() must not be freed by the user! + Any HIDAPI function that can explicitly indicate an execution failure + (e.g. by an error code, or by returning NULL) - may set the error string, + to be returned by this function. - This function is thread-safe, and error messages are thread-local. + Strings returned from hid_error() must not be freed by the user, + i.e. owned by HIDAPI library. + Device-specific error string may remain allocated at most until hid_close() is called. + Global error string may remain allocated at most until hid_exit() is called. @ingroup API @param dev A device handle returned from hid_open(), or NULL to get the last non-device-specific error - (e.g. for errors in hid_open() itself). + (e.g. for errors in hid_open() or hid_enumerate()). @returns - This function returns a string containing the last error - which occurred or NULL if none has occurred. + A string describing the last error (if any). */ HID_API_EXPORT const wchar_t* HID_API_CALL hid_error(hid_device *dev); /** @brief Get a runtime version of the library. + This function is thread-safe. + @ingroup API @returns @@ -507,6 +586,8 @@ extern "C" { /** @brief Get a runtime version string of the library. + This function is thread-safe. + @ingroup API @returns