diff --git a/libusb/hid.c b/libusb/hid.c index a48ea9bfa..987e37bd3 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); @@ -340,6 +345,88 @@ 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; +} + +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); +} + +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 @@ -1582,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) @@ -1778,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; }