Add hid_get_device_info (#432)

- new API function: `struct hid_device_info * hid_get_device_info(hid_device *dev);` to get `hid_device_info` after the device already opened;
- reused existing implementation on Windows and macOS from enumeration routines to have the implementation;
- refactored libusb implementation to have a shared routine for `hid_enumerate` and `hid_get_device_info`;
- refactored hidraw implementation to have a shared routine for `hid_enumerate` and `hid_get_device_info`;

Resolves: #431
Closes: #164
Closes: #163
This commit is contained in:
Julian Waller
2022-08-13 17:01:57 +01:00
committed by GitHub
parent eaa5c7c6f1
commit 5c9f147a07
6 changed files with 692 additions and 429 deletions

View File

@@ -470,6 +470,21 @@ extern "C" {
*/
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.
@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.
*/
HID_API_EXPORT struct hid_device_info * 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

View File

@@ -51,6 +51,24 @@
#endif
//
void print_device(struct hid_device_info *cur_dev) {
printf("Device Found\n type: %04hx %04hx\n path: %s\n serial_number: %ls", cur_dev->vendor_id, cur_dev->product_id, cur_dev->path, cur_dev->serial_number);
printf("\n");
printf(" Manufacturer: %ls\n", cur_dev->manufacturer_string);
printf(" Product: %ls\n", cur_dev->product_string);
printf(" Release: %hx\n", cur_dev->release_number);
printf(" Interface: %d\n", cur_dev->interface_number);
printf(" Usage (page): 0x%hx (0x%hx)\n", cur_dev->usage, cur_dev->usage_page);
printf("\n");
}
void print_devices(struct hid_device_info *cur_dev) {
while (cur_dev) {
print_device(cur_dev);
cur_dev = cur_dev->next;
}
}
int main(int argc, char* argv[])
{
(void)argc;
@@ -63,7 +81,7 @@ int main(int argc, char* argv[])
hid_device *handle;
int i;
struct hid_device_info *devs, *cur_dev;
struct hid_device_info *devs;
printf("hidapi test/example tool. Compiled with hidapi version %s, runtime version %s.\n", HID_API_VERSION_STR, hid_version_str());
if (HID_API_VERSION == HID_API_MAKE_VERSION(hid_version()->major, hid_version()->minor, hid_version()->patch)) {
@@ -83,18 +101,7 @@ int main(int argc, char* argv[])
#endif
devs = hid_enumerate(0x0, 0x0);
cur_dev = devs;
while (cur_dev) {
printf("Device Found\n type: %04hx %04hx\n path: %s\n serial_number: %ls", cur_dev->vendor_id, cur_dev->product_id, cur_dev->path, cur_dev->serial_number);
printf("\n");
printf(" Manufacturer: %ls\n", cur_dev->manufacturer_string);
printf(" Product: %ls\n", cur_dev->product_string);
printf(" Release: %hx\n", cur_dev->release_number);
printf(" Interface: %d\n", cur_dev->interface_number);
printf(" Usage (page): 0x%hx (0x%hx)\n", cur_dev->usage, cur_dev->usage_page);
printf("\n");
cur_dev = cur_dev->next;
}
print_devices(devs);
hid_free_enumeration(devs);
// Set up the command buffer.
@@ -134,6 +141,13 @@ int main(int argc, char* argv[])
printf("Serial Number String: (%d) %ls", wstr[0], wstr);
printf("\n");
struct hid_device_info* info = hid_get_device_info(handle);
if (info == NULL) {
printf("Unable to get device info\n");
} else {
print_devices(info);
}
// Read Indexed String 1
wstr[0] = 0x0000;
res = hid_get_indexed_string(handle, 1, wstr, MAX_STR);

View File

@@ -146,18 +146,21 @@ 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;
/* 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 */
@@ -210,6 +213,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);
}
@@ -222,7 +227,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)
@@ -322,7 +326,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
@@ -484,9 +487,14 @@ err:
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);
@@ -499,7 +507,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 {
@@ -507,6 +515,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);
}
@@ -548,11 +562,111 @@ int HID_API_EXPORT hid_exit(void)
return 0;
}
/**
* 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)
{
unsigned char data[4096];
unsigned short page = 0, usage = 0;
/* Get the HID Report Descriptor. */
int 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) {
/* Parse the usage and usage page
out of the report descriptor. */
get_usage(data, res, &page, &usage);
}
else
LOG("libusb_control_transfer() for getting the HID report descriptor failed with %d: %s\n", res, libusb_error_name(res));
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)
{
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);
/* 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");
}
#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_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;
if (!handle) {
return cur_dev;
}
cur_dev->path = make_path(libusb_get_device(handle), config_number, interface_num);
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;
}
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;
@@ -589,27 +703,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
@@ -618,95 +717,45 @@ 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(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);
/* TODO: have a runtime check for this section. */
/* Release the interface */
res = libusb_release_interface(handle, interface_num);
if (res < 0)
LOG("Can't release the interface.\n");
/*
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) {
invasive_fill_device_info_usage(tmp, handle, intf_desc->bInterfaceNumber);
}
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");
}
#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 */
@@ -909,7 +958,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;
@@ -943,7 +992,8 @@ 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->input_endpoint = 0;
@@ -1017,7 +1067,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 */
@@ -1025,14 +1076,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);
}
}
}
@@ -1107,7 +1156,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;
@@ -1455,6 +1504,22 @@ 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_get_device_descriptor(libusb_get_device(dev->device_handle), &desc);
dev->device_info = create_device_info_for_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);
}
}
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;

View File

@@ -68,27 +68,12 @@
#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 = {
@@ -107,6 +92,7 @@ static hid_device *new_hid_device(void)
dev->blocking = 1;
dev->uses_numbered_reports = 0;
dev->last_error_str = NULL;
dev->device_info = NULL;
return dev;
}
@@ -421,6 +407,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;
@@ -435,16 +422,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);
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 <sysfs_path>/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;
@@ -488,119 +565,186 @@ next_line:
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;
if (!string || !maxlen) {
register_device_error(dev, "Zero buffer/length");
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;
}
register_device_error(dev, NULL);
result = parse_uevent_info(
udev_device_get_sysattr_value(hid_dev, "uevent"),
&bus_type,
&dev_vid,
&dev_pid,
&serial_number_utf8,
&product_name_utf8);
/* 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 ret;
if (!result) {
/* parse_uevent_info() failed for at least one field. */
goto end;
}
/* Create the udev object */
udev = udev_new();
if (!udev) {
register_device_error(dev, "Couldn't create udev context");
return -1;
/* Filter out unhandled devices right away */
switch (bus_type) {
case BUS_BLUETOOTH:
case BUS_I2C:
case BUS_USB:
break;
default:
goto end;
}
/* 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;
/* Create the record. */
root = (struct hid_device_info*) calloc(1, sizeof(struct hid_device_info));
if (!root)
goto end;
ret = parse_uevent_info(
udev_device_get_sysattr_value(hid_dev, "uevent"),
&bus_type,
&dev_vid,
&dev_pid,
&serial_number_utf8,
&product_name_utf8);
cur_dev = root;
/* 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;
/* Fill out the record */
cur_dev->next = NULL;
cur_dev->path = dev_path? strdup(dev_path): NULL;
if (key >= 0 && key < DEVICE_STRING_COUNT) {
key_str = device_string_names[key];
} else {
ret = -1;
goto end;
}
/* VID/PID */
cur_dev->vendor_id = dev_vid;
cur_dev->product_id = dev_pid;
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;
}
/* Serial Number */
cur_dev->serial_number = utf8_to_wchar_t(serial_number_utf8);
/* USB information parsed */
goto end;
}
else {
/* Correctly handled below */
}
/* 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;
}
/* Manufacturer and Product strings */
cur_dev->manufacturer_string = copy_udev_string(usb_dev, "manufacturer");
cur_dev->product_string = copy_udev_string(usb_dev, "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 */
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;
}
}
@@ -608,12 +752,47 @@ end:
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()
@@ -649,7 +828,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;
@@ -658,7 +836,6 @@ 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 */
@@ -679,197 +856,49 @@ 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);
if (!sysfs_path)
continue;
if (vendor_id != 0 || product_id != 0) {
if (!parse_hid_vid_pid_from_sysfs(sysfs_path, &bus_type, &dev_vid, &dev_pid))
continue;
if (vendor_id != 0 && vendor_id != dev_vid)
continue;
if (product_id != 0 && product_id != dev_pid)
continue;
}
raw_dev = udev_device_new_from_syspath(udev, sysfs_path);
dev_path = udev_device_get_devnode(raw_dev);
if (!raw_dev)
continue;
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;
}
/* Filter out unhandled devices right away */
switch (bus_type) {
case BUS_BLUETOOTH:
case BUS_I2C:
case BUS_USB:
break;
default:
goto next;
}
/* 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;
/* 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);
@@ -1125,23 +1154,93 @@ void HID_API_EXPORT hid_close(hid_device *dev)
/* 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)

View File

@@ -132,6 +132,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 */
@@ -154,6 +155,7 @@ static hid_device *new_hid_device(void)
dev->source = NULL;
dev->input_report_buf = NULL;
dev->input_reports = NULL;
dev->device_info = NULL;
dev->shutdown_thread = 0;
/* Thread objects */
@@ -187,6 +189,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);
@@ -1161,17 +1164,74 @@ 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);
}
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)

View File

@@ -1141,13 +1141,13 @@ 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;
}
@@ -1161,13 +1161,13 @@ int HID_API_EXPORT_CALL HID_API_CALL hid_get_manufacturer_string(hid_device *dev
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;
}
@@ -1181,13 +1181,13 @@ int HID_API_EXPORT_CALL HID_API_CALL hid_get_product_string(hid_device *dev, wch
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;
}
@@ -1199,6 +1199,16 @@ int HID_API_EXPORT_CALL HID_API_CALL hid_get_serial_number_string(hid_device *de
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;