add libusb-specific hid_libusb_wrap_sys_device

Rationale: on Android one must use UsbManager, to access any
USB device. As a result, libraries like libusb can only use file descriptors
that are provided by UsbManager.
libusb has an API to use such file descriptors: hid_libusb_wrap_sys_device.
Having hid_libusb_wrap_sys_device currently is the only way to make hidapi
work on Android without root access and without custom Android builds.

Relevant info: https://github.com/libusb/libusb/pull/830/files
This commit is contained in:
Ihor Dutchak
2021-05-18 16:39:57 +03:00
parent 301139e3cc
commit aaf5f3d172
5 changed files with 337 additions and 198 deletions

View File

@@ -47,7 +47,10 @@ jobs:
- name: Check artifacts
uses: andstor/file-existence-action@v1
with:
files: "install/shared/lib/libhidapi.dylib, install/shared/include/hidapi/hidapi.h, install/framework/lib/hidapi.framework/hidapi, install/framework/lib/hidapi.framework/Headers/hidapi.h"
files: "install/shared/lib/libhidapi.dylib, \
install/shared/include/hidapi/hidapi.h, \
install/framework/lib/hidapi.framework/hidapi, \
install/framework/lib/hidapi.framework/Headers/hidapi.h"
allow_failure: true
ubuntu-cmake:
@@ -76,7 +79,14 @@ jobs:
- name: Check artifacts
uses: andstor/file-existence-action@v1
with:
files: "install/shared/lib/libhidapi-libusb.so, install/shared/lib/libhidapi-hidraw.so, install/shared/include/hidapi/hidapi.h, install/static/lib/libhidapi-libusb.a, install/static/lib/libhidapi-hidraw.a, install/static/include/hidapi/hidapi.h"
files: "install/shared/lib/libhidapi-libusb.so, \
install/shared/lib/libhidapi-hidraw.so, \
install/shared/include/hidapi/hidapi.h, \
install/shared/include/hidapi/hidapi_libusb.h, \
install/static/lib/libhidapi-libusb.a, \
install/static/lib/libhidapi-hidraw.a, \
install/static/include/hidapi/hidapi.h, \
install/static/include/hidapi/hidapi_libusb.h"
allow_failure: true
windows-cmake-msvc:

View File

@@ -3,6 +3,7 @@ cmake_minimum_required(VERSION 3.6.3 FATAL_ERROR)
add_library(hidapi_libusb
${HIDAPI_PUBLIC_HEADERS}
hid.c
hidapi_libusb.h
)
target_link_libraries(hidapi_libusb PUBLIC hidapi_include)
@@ -23,7 +24,7 @@ set_target_properties(hidapi_libusb
OUTPUT_NAME "hidapi-libusb"
VERSION ${PROJECT_VERSION}
SOVERSION ${PROJECT_VERSION_MAJOR}
PUBLIC_HEADER "${HIDAPI_PUBLIC_HEADERS}"
PUBLIC_HEADER "${HIDAPI_PUBLIC_HEADERS};hidapi_libusb.h"
)
# compatibility with find_package()

View File

@@ -29,6 +29,6 @@ libhidapi_la_LIBADD = $(LIBS_LIBUSB)
endif
hdrdir = $(includedir)/hidapi
hdr_HEADERS = $(top_srcdir)/hidapi/hidapi.h
hdr_HEADERS = $(top_srcdir)/hidapi/hidapi.h hidapi_libusb.h
EXTRA_DIST = Makefile-manual

View File

@@ -49,7 +49,7 @@
#include <iconv.h>
#endif
#include "hidapi.h"
#include "hidapi_libusb.h"
#if defined(__ANDROID__) && __ANDROID_API__ < __ANDROID_API_N__
@@ -569,12 +569,16 @@ struct hid_device_info HID_API_EXPORT *hid_enumerate(unsigned short vendor_id,
struct libusb_device_descriptor desc;
struct libusb_config_descriptor *conf_desc = NULL;
int j, k;
int interface_num = 0;
int res = libusb_get_device_descriptor(dev, &desc);
unsigned short dev_vid = desc.idVendor;
unsigned short dev_pid = desc.idProduct;
if ((vendor_id != 0x0 && vendor_id != dev_vid) ||
(product_id != 0x0 && product_id != dev_pid)) {
continue;
}
res = libusb_get_active_config_descriptor(dev, &conf_desc);
if (res < 0)
libusb_get_config_descriptor(dev, 0, &conf_desc);
@@ -585,129 +589,124 @@ 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) {
interface_num = intf_desc->bInterfaceNumber;
int interface_num = intf_desc->bInterfaceNumber;
struct hid_device_info *tmp;
/* 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));
if (cur_dev) {
cur_dev->next = tmp;
}
else {
root = tmp;
}
cur_dev = 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);
/* Fill out the record */
cur_dev->next = NULL;
cur_dev->path = make_path(dev, interface_num, conf_desc->bConfigurationValue);
res = libusb_open(dev, &handle);
res = libusb_open(dev, &handle);
if (res >= 0) {
if (res >= 0) {
#ifdef __ANDROID__
/* 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
A workaround is to re-read the descriptor again.
Even if it is not going to be accepted into libusb master,
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);
/* 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
A workaround is to re-read the descriptor again.
Even if it is not going to be accepted into libusb master,
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);
/* 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);
/* 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);
#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];
/*
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.");
else
detached = 1;
}
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);
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) {
/* 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");
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("Can't claim interface %d\n", res);
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");
}
/* 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);
}
/* 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;
libusb_close(handle);
}
/* 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;
}
} /* altsettings */
} /* interfaces */
@@ -910,13 +909,95 @@ static void *read_thread(void *param)
}
static int hidapi_initialize_device(hid_device *dev, const struct libusb_interface_descriptor *intf_desc)
{
int i =0;
int res = 0;
struct libusb_device_descriptor desc;
libusb_get_device_descriptor(libusb_get_device(dev->device_handle), &desc);
#ifdef DETACH_KERNEL_DRIVER
/* Detach the kernel driver, but only if the
device is managed by the kernel */
dev->is_driver_detached = 0;
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) {
libusb_close(dev->device_handle);
LOG("Unable to detach Kernel Driver\n");
return 0;
}
else {
dev->is_driver_detached = 1;
LOG("Driver successfully detached from kernel.\n");
}
}
#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);
return 0;
}
/* Store off the string descriptor indexes */
dev->manufacturer_index = desc.iManufacturer;
dev->product_index = desc.iProduct;
dev->serial_index = desc.iSerialNumber;
/* Store off the interface number */
dev->interface = intf_desc->bInterfaceNumber;
dev->input_endpoint = 0;
dev->input_ep_max_packet_size = 0;
dev->output_endpoint = 0;
/* Find the INPUT and OUTPUT endpoints. An
OUTPUT endpoint is not required. */
for (i = 0; i < intf_desc->bNumEndpoints; i++) {
const struct libusb_endpoint_descriptor *ep
= &intf_desc->endpoint[i];
/* Determine the type and direction of this
endpoint. */
int is_interrupt =
(ep->bmAttributes & LIBUSB_TRANSFER_TYPE_MASK)
== LIBUSB_TRANSFER_TYPE_INTERRUPT;
int is_output =
(ep->bEndpointAddress & LIBUSB_ENDPOINT_DIR_MASK)
== LIBUSB_ENDPOINT_OUT;
int is_input =
(ep->bEndpointAddress & LIBUSB_ENDPOINT_DIR_MASK)
== LIBUSB_ENDPOINT_IN;
/* Decide whether to use it for input or output. */
if (dev->input_endpoint == 0 &&
is_interrupt && is_input) {
/* Use this endpoint for INPUT */
dev->input_endpoint = ep->bEndpointAddress;
dev->input_ep_max_packet_size = ep->wMaxPacketSize;
}
if (dev->output_endpoint == 0 &&
is_interrupt && is_output) {
/* Use this endpoint for OUTPUT */
dev->output_endpoint = ep->bEndpointAddress;
}
}
pthread_create(&dev->thread, NULL, read_thread, dev);
/* Wait here for the read thread to be initialized. */
pthread_barrier_wait(&dev->barrier);
return 1;
}
hid_device * HID_API_EXPORT hid_open_path(const char *path)
{
hid_device *dev = NULL;
libusb_device **devs;
libusb_device *usb_dev;
int res;
libusb_device **devs = NULL;
libusb_device *usb_dev = NULL;
int res = 0;
int d = 0;
int good_open = 0;
@@ -926,19 +1007,16 @@ hid_device * HID_API_EXPORT hid_open_path(const char *path)
dev = new_hid_device();
libusb_get_device_list(usb_context, &devs);
while ((usb_dev = devs[d++]) != NULL) {
struct libusb_device_descriptor desc;
while ((usb_dev = devs[d++]) != NULL && !good_open) {
struct libusb_config_descriptor *conf_desc = NULL;
int i,j,k;
libusb_get_device_descriptor(usb_dev, &desc);
int j,k;
if (libusb_get_active_config_descriptor(usb_dev, &conf_desc) < 0)
continue;
for (j = 0; j < conf_desc->bNumInterfaces; j++) {
for (j = 0; j < conf_desc->bNumInterfaces && !good_open; j++) {
const struct libusb_interface *intf = &conf_desc->interface[j];
for (k = 0; k < intf->num_altsetting; k++) {
const struct libusb_interface_descriptor *intf_desc;
intf_desc = &intf->altsetting[k];
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);
if (!strcmp(dev_path, path)) {
@@ -951,93 +1029,15 @@ hid_device * HID_API_EXPORT hid_open_path(const char *path)
free(dev_path);
break;
}
good_open = 1;
#ifdef __ANDROID__
/* See remark in hid_enumerate */
libusb_get_device_descriptor(usb_dev, &desc);
#endif
#ifdef DETACH_KERNEL_DRIVER
/* Detach the kernel driver, but only if the
device is managed by the kernel */
dev->is_driver_detached = 0;
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) {
libusb_close(dev->device_handle);
LOG("Unable to detach Kernel Driver\n");
free(dev_path);
good_open = 0;
break;
}
else {
dev->is_driver_detached = 1;
LOG("Driver successfully detached from kernel.\n");
}
}
#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);
free(dev_path);
good_open = hidapi_initialize_device(dev, intf_desc);
if (!good_open)
libusb_close(dev->device_handle);
good_open = 0;
break;
}
/* Store off the string descriptor indexes */
dev->manufacturer_index = desc.iManufacturer;
dev->product_index = desc.iProduct;
dev->serial_index = desc.iSerialNumber;
/* Store off the interface number */
dev->interface = intf_desc->bInterfaceNumber;
/* Find the INPUT and OUTPUT endpoints. An
OUTPUT endpoint is not required. */
for (i = 0; i < intf_desc->bNumEndpoints; i++) {
const struct libusb_endpoint_descriptor *ep
= &intf_desc->endpoint[i];
/* Determine the type and direction of this
endpoint. */
int is_interrupt =
(ep->bmAttributes & LIBUSB_TRANSFER_TYPE_MASK)
== LIBUSB_TRANSFER_TYPE_INTERRUPT;
int is_output =
(ep->bEndpointAddress & LIBUSB_ENDPOINT_DIR_MASK)
== LIBUSB_ENDPOINT_OUT;
int is_input =
(ep->bEndpointAddress & LIBUSB_ENDPOINT_DIR_MASK)
== LIBUSB_ENDPOINT_IN;
/* Decide whether to use it for input or output. */
if (dev->input_endpoint == 0 &&
is_interrupt && is_input) {
/* Use this endpoint for INPUT */
dev->input_endpoint = ep->bEndpointAddress;
dev->input_ep_max_packet_size = ep->wMaxPacketSize;
}
if (dev->output_endpoint == 0 &&
is_interrupt && is_output) {
/* Use this endpoint for OUTPUT */
dev->output_endpoint = ep->bEndpointAddress;
}
}
pthread_create(&dev->thread, NULL, read_thread, dev);
/* Wait here for the read thread to be initialized. */
pthread_barrier_wait(&dev->barrier);
}
free(dev_path);
}
}
}
libusb_free_config_descriptor(conf_desc);
}
libusb_free_device_list(devs, 1);
@@ -1054,6 +1054,80 @@ hid_device * HID_API_EXPORT hid_open_path(const char *path)
}
HID_API_EXPORT hid_device * HID_API_CALL hid_libusb_wrap_sys_device(intptr_t sys_dev, int interface_num)
{
/* 0x01000107 is a LIBUSB_API_VERSION for 1.0.23 - version when libusb_wrap_sys_device was introduced */
#if (!defined(HIDAPI_TARGET_LIBUSB_API_VERSION) || HIDAPI_TARGET_LIBUSB_API_VERSION >= 0x01000107) && (LIBUSB_API_VERSION >= 0x01000107)
hid_device *dev = NULL;
struct libusb_config_descriptor *conf_desc = NULL;
const struct libusb_interface_descriptor *selected_intf_desc = NULL;
int res = 0;
int j = 0, k = 0;
if(hid_init() < 0)
return NULL;
dev = new_hid_device();
res = libusb_wrap_sys_device(usb_context, sys_dev, &dev->device_handle);
if (res < 0) {
LOG("libusb_wrap_sys_device failed: %d %s\n", res, libusb_error_name(res));
goto err;
}
res = libusb_get_active_config_descriptor(libusb_get_device(dev->device_handle), &conf_desc);
if (res < 0)
libusb_get_config_descriptor(libusb_get_device(dev->device_handle), 0, &conf_desc);
if (!conf_desc) {
LOG("Failed to get configuration descriptor: %d %s\n", res, libusb_error_name(res));
goto err;
}
/* find matching HID interface */
for (j = 0; j < conf_desc->bNumInterfaces && !selected_intf_desc; j++) {
const struct libusb_interface *intf = &conf_desc->interface[j];
for (k = 0; k < intf->num_altsetting; k++) {
const struct libusb_interface_descriptor *intf_desc = &intf->altsetting[k];
if (intf_desc->bInterfaceClass == LIBUSB_CLASS_HID) {
if (interface_num < 0 || interface_num == intf_desc->bInterfaceNumber) {
selected_intf_desc = intf_desc;
break;
}
}
}
}
if (!selected_intf_desc) {
if (interface_num < 0) {
LOG("Sys USB device doesn't contain a HID interface\n");
}
else {
LOG("Sys USB device doesn't contain a HID interface with number %d\n", interface_num);
}
goto err;
}
if (!hidapi_initialize_device(dev, selected_intf_desc))
goto err;
return dev;
err:
if (conf_desc)
libusb_free_config_descriptor(conf_desc);
if (dev->device_handle)
libusb_close(dev->device_handle);
free_hid_device(dev);
#else
(void)sys_dev;
(void)interface_num;
LOG("libusb_wrap_sys_device is not available\n");
#endif
return NULL;
}
int HID_API_EXPORT hid_write(hid_device *dev, const unsigned char *data, size_t length)
{
int res;

54
libusb/hidapi_libusb.h Normal file
View File

@@ -0,0 +1,54 @@
/*******************************************************
HIDAPI - Multi-Platform library for
communication with HID devices.
libusb/hidapi Team
Copyright 2021, All Rights Reserved.
At the discretion of the user of this library,
this software may be licensed under the terms of the
GNU General Public License v3, a BSD-Style license, or the
original HIDAPI license as outlined in the LICENSE.txt,
LICENSE-gpl3.txt, LICENSE-bsd.txt, and LICENSE-orig.txt
files located at the root of the source distribution.
These files may also be found in the public source
code repository located at:
https://github.com/libusb/hidapi .
********************************************************/
/** @file
* @defgroup API hidapi API
*/
#ifndef HIDAPI_LIBUSB_H__
#define HIDAPI_LIBUSB_H__
#include <stdint.h>
#include "hidapi.h"
#ifdef __cplusplus
extern "C" {
#endif
/** @brief Open a HID device using libusb_wrap_sys_device.
See https://libusb.sourceforge.io/api-1.0/group__libusb__dev.html#ga98f783e115ceff4eaf88a60e6439563c,
for details on libusb_wrap_sys_device.
@ingroup API
@param sys_dev Platform-specific file descriptor that can be recognised by libusb.
@param interface_num USB interface number of the device to be used as HID interface.
Pass -1 to select first HID interface of the device.
@returns
This function returns a pointer to a #hid_device object on
success or NULL on failure.
*/
HID_API_EXPORT hid_device * HID_API_CALL hid_libusb_wrap_sys_device(intptr_t sys_dev, int interface_num);
#ifdef __cplusplus
}
#endif
#endif