From 1f1d55ee29d63b97607543f069c344ea7ecfcad0 Mon Sep 17 00:00:00 2001 From: Matheus Afonso Martins Moreira Date: Sat, 31 Aug 2024 23:34:06 -0300 Subject: [PATCH 1/6] hid: define utf8 to wchar_t conversion function Most if not all libusb functions return UTF-8 encoded data but hidapi functions typically take and return wide character strings. Adapt one of the existing algorithms in the code base into a general conversion function. --- libusb/hid.c | 54 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/libusb/hid.c b/libusb/hid.c index a48ea9bf..694a567d 100644 --- a/libusb/hid.c +++ b/libusb/hid.c @@ -340,6 +340,60 @@ static int is_language_supported(libusb_device_handle *dev, uint16_t lang) return 0; } +static wchar_t *utf8_to_wchar(char *s) +{ + wchar_t *w = NULL; + +/* we don't use iconv on Android, or when it is explicitly disabled */ +#if defined(__ANDROID__) || defined(NO_ICONV) + + w = wcsdup(L"not implemented"); + +#else + size_t slen = strlen(s); + wchar_t *wbuf = malloc((slen + 1) * sizeof(wchar_t)); + if (!wbuf) { goto err; } + /* iconv variables */ + iconv_t ic; + size_t inbytes; + size_t outbytes; + size_t res; + char ** restrict inptr; + char *outptr; + /* buf does not need to be explicitly NULL-terminated because + it is only passed into iconv() which does not need it. */ + + /* Initialize iconv. */ + ic = iconv_open("WCHAR_T", "UTF-8"); + if (ic == (iconv_t)-1) { + LOG("iconv_open() failed\n"); + return NULL; + } + + /* Convert to native wchar_t (UTF-32 on glibc/BSD systems). */ + inptr = &s; + inbytes = slen; + outptr = (char*) wbuf; + outbytes = slen * sizeof(wchar_t); + res = iconv(ic, inptr, &inbytes, &outptr, &outbytes); + if (res == (size_t)-1) { + LOG("iconv() failed\n"); + goto err; + } + + /* Write the terminating NULL. */ + wbuf[slen] = 0; + + w = wbuf; + +err: + iconv_close(ic); + +#endif + + return w; +} + /* This function returns a newly allocated wide string containing the USB device string numbered by the index. The returned string must be freed From 69db339f24216745c42c1e7b055d2b35771a1f83 Mon Sep 17 00:00:00 2001 From: Matheus Afonso Martins Moreira Date: Sat, 31 Aug 2024 23:36:17 -0300 Subject: [PATCH 2/6] hid: define wchar_t libusb error function variant Returns libusb error names and strings converted to wide character strings. --- libusb/hid.c | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/libusb/hid.c b/libusb/hid.c index 694a567d..a5fa885c 100644 --- a/libusb/hid.c +++ b/libusb/hid.c @@ -394,6 +394,29 @@ static wchar_t *utf8_to_wchar(char *s) return w; } +static wchar_t *libusb_error_wchar(int e, const char * (*f)(int)) +{ + const char *cs; + char *s; + wchar_t *w; + + cs = f(e); + s = strdup(cs); + w = utf8_to_wchar(s); + + free(s); + + return w; +} + +static wchar_t *libusb_error_name_wchar(int error) { + return libusb_error_wchar(error, libusb_error_name); +} + +static wchar_t *libusb_strerror_wchar(int error) { + return libusb_error_wchar(error, libusb_strerror); +} + /* This function returns a newly allocated wide string containing the USB device string numbered by the index. The returned string must be freed From 277cac9001dfbbc32649a5cceb7d6521828b74b8 Mon Sep 17 00:00:00 2001 From: Matheus Afonso Martins Moreira Date: Sat, 31 Aug 2024 23:41:49 -0300 Subject: [PATCH 3/6] hid: add error data to hid_device structure Store libusb error code so it can be retrieved later. Includes the original error code as well as a context-specific message which the libusb documentation sometimes specifies for each function. Code which uses those functions are meant to set the contextual message whenever possible. The code is initialized to a success state which implies no errors yet. The contextual error message is initialized to NULL and is not freed when the device is closed. It is meant to point at string literals. --- libusb/hid.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/libusb/hid.c b/libusb/hid.c index a5fa885c..93e28167 100644 --- a/libusb/hid.c +++ b/libusb/hid.c @@ -123,6 +123,9 @@ struct hid_device_ { #ifdef DETACH_KERNEL_DRIVER int is_driver_detached; #endif + + int error; + const char *error_context; }; static struct hid_api_version api_version = { @@ -140,6 +143,8 @@ static hid_device *new_hid_device(void) { hid_device *dev = (hid_device*) calloc(1, sizeof(hid_device)); dev->blocking = 1; + dev->error = LIBUSB_SUCCESS; + dev->error_context = NULL; hidapi_thread_state_init(&dev->thread_state); From 9cc5ac027beb1ef0e3ade91a54ba238fe4bb67f5 Mon Sep 17 00:00:00 2001 From: Matheus Afonso Martins Moreira Date: Sat, 31 Aug 2024 23:44:32 -0300 Subject: [PATCH 4/6] hid: define easy error data setter function Sets all error data, including an optional contextual error message which is supposed to be a non-freeable constant string such as a string literal. Contextual error messages are meant to be used in the cases the libusb documentation goes into detail as to what happened. Passing NULL will produce a message with just the libusb_error_name and the libusb_strerror results. Passing a string literal will produce a message that contains the additional context in addition to the error name and message. --- libusb/hid.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/libusb/hid.c b/libusb/hid.c index 93e28167..9d55f28f 100644 --- a/libusb/hid.c +++ b/libusb/hid.c @@ -422,6 +422,11 @@ static wchar_t *libusb_strerror_wchar(int error) { return libusb_error_wchar(error, libusb_strerror); } +static void set_error(hid_device *dev, int error, const char *error_context) +{ + dev->error = error; + dev->error_context = error_context; +} /* This function returns a newly allocated wide string containing the USB device string numbered by the index. The returned string must be freed From ec4d74a57e971d81a6fac65d97c9ff624982f4c9 Mon Sep 17 00:00:00 2001 From: Matheus Afonso Martins Moreira Date: Sat, 31 Aug 2024 23:49:47 -0300 Subject: [PATCH 5/6] hid: set error data in send_feature_report Set error data when send_feature_report fails, including custom messages for the situations especially outlined in the libusb documentation for the libusb_control_transfer function. --- libusb/hid.c | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/libusb/hid.c b/libusb/hid.c index 9d55f28f..a0d00cf1 100644 --- a/libusb/hid.c +++ b/libusb/hid.c @@ -1669,8 +1669,31 @@ int HID_API_EXPORT hid_send_feature_report(hid_device *dev, const unsigned char (unsigned char *)data, length, 1000/*timeout millis*/); - if (res < 0) + if (res < 0) { + const char *context = NULL; + + switch (res) { + case LIBUSB_ERROR_TIMEOUT: + context = "Transfer timed out"; + break; + case LIBUSB_ERROR_PIPE: + context = "Control request not supported by device"; + break; + case LIBUSB_ERROR_NO_DEVICE: + context = "Device has disconnected"; + break; + case LIBUSB_ERROR_BUSY: + context = "Called from event handling context"; + break; + case LIBUSB_ERROR_INVALID_PARAM: + context = "Transfer size larger than supported"; + break; + } + + set_error(dev, res, context); + return -1; + } /* Account for the report ID */ if (skipped_report_id) From 9b6bef535fa0c007752e28504acc7431b6869693 Mon Sep 17 00:00:00 2001 From: Matheus Afonso Martins Moreira Date: Sat, 31 Aug 2024 23:43:26 -0300 Subject: [PATCH 6/6] hid: implement hid_error Compute a formatted error string containing the libusb error name, the error message as well as any contextual information. Return NULL if there are no errors or if memory allocation failed. --- libusb/hid.c | 33 ++++++++++++++++++++++++++++++--- 1 file changed, 30 insertions(+), 3 deletions(-) diff --git a/libusb/hid.c b/libusb/hid.c index a0d00cf1..987e37bd 100644 --- a/libusb/hid.c +++ b/libusb/hid.c @@ -1888,11 +1888,38 @@ int HID_API_EXPORT_CALL hid_get_report_descriptor(hid_device *dev, unsigned char return hid_get_report_descriptor_libusb(dev->device_handle, dev->interface, dev->report_descriptor_size, buf, buf_size); } - HID_API_EXPORT const wchar_t * HID_API_CALL hid_error(hid_device *dev) { - (void)dev; - return L"hid_error is not implemented yet"; + static const char format_simple[] = "%s: %s"; + static const char format_context[] = "%s: %s (%s)"; + const char *name, *message, *context, *format; + char *buffer; + wchar_t *w; + size_t len; + + if (dev->error == LIBUSB_SUCCESS) { + return NULL; + } + + name = libusb_error_name(dev->error); + message = libusb_strerror(dev->error); + context = dev->error_context; + format = context? format_context : format_simple; + + len = 1 + snprintf(NULL, 0, format, name, message, context); + + buffer = malloc(len); + if (!buffer) { + return NULL; + } + + snprintf(buffer, len, format, name, message, context); + + w = utf8_to_wchar(buffer); + + free(buffer); + + return w; }