Sorry, the diff of this file is not supported yet
| 0.10.1 |
+1
-1
@@ -11,3 +11,3 @@ # This file is automatically @generated by Cargo. | ||
| name = "hidapi" | ||
| version = "1.2.3" | ||
| version = "1.2.4" | ||
| dependencies = [ | ||
@@ -14,0 +14,0 @@ "cc", |
+1
-1
@@ -15,3 +15,3 @@ # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO | ||
| name = "hidapi" | ||
| version = "1.2.3" | ||
| version = "1.2.4" | ||
| authors = ["Roland Ruckerbauer <roland.rucky@gmail.com>", "Osspial <osspial@gmail.com>", "Artyom Pavlov <newpavlov@gmail.com>", "mberndt123", "niklasad1"] | ||
@@ -18,0 +18,0 @@ build = "build.rs" |
| image: archlinux | ||
| packages: | ||
| - autoconf | ||
| - automake | ||
| - libtool | ||
| - libusb | ||
| - libudev0 | ||
| sources: | ||
@@ -3,0 +9,0 @@ - https://github.com/libusb/hidapi |
@@ -23,1 +23,3 @@ | ||
| libtool | ||
| .DS_Store |
| This file is mostly for the maintainer. | ||
| 1. Build hidapi.dll | ||
| 2. Build hidtest.exe in DEBUG and RELEASE | ||
| 3. Commit all | ||
| Updating a Version: | ||
| 1. Update VERSION file. | ||
| 2. HID_API_VERSION_MAJOR/HID_API_VERSION_MINOR/HID_API_VERSION_PATCH in hidapi.h. | ||
| 4. Run the Following | ||
| export VERSION=0.1.0 | ||
| export TAG_NAME=hidapi-$VERSION | ||
| git tag $TAG_NAME | ||
| git archive --format zip --prefix $TAG_NAME/ $TAG_NAME >../$TAG_NAME.zip | ||
| 5. Test the zip file. | ||
| 6. Run the following: | ||
| git push origin $TAG_NAME | ||
| Firing a new release: | ||
| 1. Update the Version (if not yet updated). | ||
| 2. Build hidapi.dll/.lib for x86/x64. | ||
| 3. Upload Windows binaries to Github release page. |
@@ -42,5 +42,38 @@ /******************************************************* | ||
| /** @brief Static/compile-time major version of the library. | ||
| @ingroup API | ||
| */ | ||
| #define HID_API_VERSION_MAJOR 0 | ||
| /** @brief Static/compile-time minor version of the library. | ||
| @ingroup API | ||
| */ | ||
| #define HID_API_VERSION_MINOR 10 | ||
| /** @brief Static/compile-time patch version of the library. | ||
| @ingroup API | ||
| */ | ||
| #define HID_API_VERSION_PATCH 1 | ||
| /* Helper macros */ | ||
| #define HID_API_AS_STR_IMPL(x) #x | ||
| #define HID_API_AS_STR(x) HID_API_AS_STR_IMPL(x) | ||
| #define HID_API_TO_VERSION_STR(v1, v2, v3) HID_API_AS_STR(v1.v2.v3) | ||
| /** @brief Static/compile-time string version of the library. | ||
| @ingroup API | ||
| */ | ||
| #define HID_API_VERSION_STR HID_API_TO_VERSION_STR(HID_API_VERSION_MAJOR, HID_API_VERSION_MINOR, HID_API_VERSION_PATCH) | ||
| #ifdef __cplusplus | ||
| extern "C" { | ||
| #endif | ||
| struct hid_api_version { | ||
| int major; | ||
| int minor; | ||
| int patch; | ||
| }; | ||
| struct hid_device_; | ||
@@ -67,6 +100,6 @@ typedef struct hid_device_ hid_device; /**< opaque hidapi structure */ | ||
| /** Usage Page for this Device/Interface | ||
| (Windows/Mac only). */ | ||
| (Windows/Mac/hidraw only) */ | ||
| unsigned short usage_page; | ||
| /** Usage for this Device/Interface | ||
| (Windows/Mac only).*/ | ||
| (Windows/Mac/hidraw only) */ | ||
| unsigned short usage; | ||
@@ -96,3 +129,3 @@ /** The USB interface which this logical device | ||
| being opened by different threads simultaneously. | ||
| @ingroup API | ||
@@ -444,2 +477,21 @@ | ||
| /** @brief Get a runtime version of the library. | ||
| @ingroup API | ||
| @returns | ||
| Pointer to statically allocated struct, that contains version. | ||
| */ | ||
| HID_API_EXPORT const struct hid_api_version* HID_API_CALL hid_version(void); | ||
| /** @brief Get a runtime version string of the library. | ||
| @ingroup API | ||
| @returns | ||
| Pointer to statically allocated string, that contains version string. | ||
| */ | ||
| HID_API_EXPORT const char* HID_API_CALL hid_version_str(void); | ||
| #ifdef __cplusplus | ||
@@ -446,0 +498,0 @@ } |
@@ -10,3 +10,3 @@ /******************************************************* | ||
| Copyright 2009 | ||
| This contents of this file may be used by anyone | ||
@@ -33,2 +33,5 @@ for any reason without any conditions and may be | ||
| { | ||
| (void)argc; | ||
| (void)argv; | ||
| int res; | ||
@@ -41,9 +44,12 @@ unsigned char buf[256]; | ||
| #ifdef WIN32 | ||
| UNREFERENCED_PARAMETER(argc); | ||
| UNREFERENCED_PARAMETER(argv); | ||
| #endif | ||
| struct hid_device_info *devs, *cur_dev; | ||
| struct hid_device_info *devs, *cur_dev; | ||
| printf("hidapi test/example tool. Compiled with hidapi version %s, runtime version %s.\n", HID_API_VERSION_STR, hid_version_str()); | ||
| if (hid_version()->major == HID_API_VERSION_MAJOR && hid_version()->minor == HID_API_VERSION_MINOR && hid_version()->patch == HID_API_VERSION_PATCH) { | ||
| printf("Compile-time version matches runtime version of hidapi.\n\n"); | ||
| } | ||
| else { | ||
| printf("Compile-time version is different than runtime version of hidapi.\n]n"); | ||
| } | ||
| if (hid_init()) | ||
@@ -53,3 +59,3 @@ return -1; | ||
| devs = hid_enumerate(0x0, 0x0); | ||
| cur_dev = devs; | ||
| cur_dev = devs; | ||
| while (cur_dev) { | ||
@@ -72,4 +78,4 @@ 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); | ||
| buf[1] = 0x81; | ||
| // Open the device using the VID, PID, | ||
@@ -115,3 +121,3 @@ // and optionally the Serial number. | ||
| hid_set_nonblocking(handle, 1); | ||
| // Try to read from the device. There should be no | ||
@@ -159,4 +165,4 @@ // data here, but execution should not block. | ||
| } | ||
| // Request state (cmd 0x81). The first byte is the report number (0x1). | ||
@@ -163,0 +169,0 @@ buf[0] = 0x1; |
+34
-13
@@ -48,3 +48,3 @@ /******************************************************* | ||
| #include <libusb.h> | ||
| #ifndef __ANDROID__ | ||
| #if !defined(__ANDROID__) && !defined(NO_ICONV) | ||
| #include <iconv.h> | ||
@@ -172,3 +172,3 @@ #endif | ||
| int shutdown_thread; | ||
| int cancelled; | ||
| int transfer_loop_finished; | ||
| struct libusb_transfer *transfer; | ||
@@ -185,2 +185,8 @@ | ||
| static struct hid_api_version api_version = { | ||
| .major = HID_API_VERSION_MAJOR, | ||
| .minor = HID_API_VERSION_MINOR, | ||
| .patch = HID_API_VERSION_PATCH | ||
| }; | ||
| static libusb_context *usb_context = NULL; | ||
@@ -401,3 +407,3 @@ | ||
| #ifndef __ANDROID__ /* we don't use iconv on Android */ | ||
| #if !defined(__ANDROID__) && !defined(NO_ICONV) /* we don't use iconv on Android, or when it is explicitly disabled */ | ||
| wchar_t wbuf[256]; | ||
@@ -428,3 +434,3 @@ /* iconv variables */ | ||
| #ifdef __ANDROID__ | ||
| #if defined(__ANDROID__) || defined(NO_ICONV) | ||
@@ -498,3 +504,12 @@ /* Bionic does not have iconv support nor wcsdup() function, so it | ||
| HID_API_EXPORT const struct hid_api_version* HID_API_CALL hid_version() | ||
| { | ||
| return &api_version; | ||
| } | ||
| HID_API_EXPORT const char* HID_API_CALL hid_version_str() | ||
| { | ||
| return HID_API_VERSION_STR; | ||
| } | ||
| int HID_API_EXPORT hid_init(void) | ||
@@ -781,9 +796,5 @@ { | ||
| dev->shutdown_thread = 1; | ||
| dev->cancelled = 1; | ||
| return; | ||
| } | ||
| else if (transfer->status == LIBUSB_TRANSFER_NO_DEVICE) { | ||
| dev->shutdown_thread = 1; | ||
| dev->cancelled = 1; | ||
| return; | ||
| } | ||
@@ -797,2 +808,7 @@ else if (transfer->status == LIBUSB_TRANSFER_TIMED_OUT) { | ||
| if (dev->shutdown_thread) { | ||
| dev->transfer_loop_finished = 1; | ||
| return; | ||
| } | ||
| /* Re-submit the transfer object. */ | ||
@@ -803,3 +819,3 @@ res = libusb_submit_transfer(transfer); | ||
| dev->shutdown_thread = 1; | ||
| dev->cancelled = 1; | ||
| dev->transfer_loop_finished = 1; | ||
| } | ||
@@ -847,2 +863,3 @@ } | ||
| res != LIBUSB_ERROR_INTERRUPTED) { | ||
| dev->shutdown_thread = 1; | ||
| break; | ||
@@ -857,4 +874,4 @@ } | ||
| while (!dev->cancelled) | ||
| libusb_handle_events_completed(usb_context, &dev->cancelled); | ||
| while (!dev->transfer_loop_finished) | ||
| libusb_handle_events_completed(usb_context, &dev->transfer_loop_finished); | ||
@@ -1095,4 +1112,2 @@ /* Now that the read thread is stopping, Wake any threads which are | ||
| { | ||
| int bytes_read = -1; | ||
| #if 0 | ||
@@ -1104,2 +1119,5 @@ int transferred; | ||
| #endif | ||
| /* by initialising this variable right here, GCC gives a compilation warning/error: */ | ||
| /* error: variable ‘bytes_read’ might be clobbered by ‘longjmp’ or ‘vfork’ [-Werror=clobbered] */ | ||
| int bytes_read; /* = -1; */ | ||
@@ -1109,2 +1127,4 @@ pthread_mutex_lock(&dev->mutex); | ||
| bytes_read = -1; | ||
| /* There's an input report queued up. Return it. */ | ||
@@ -1359,2 +1379,3 @@ if (dev->input_reports) { | ||
| { | ||
| (void)dev; | ||
| return L"hid_error is not implemented yet"; | ||
@@ -1361,0 +1382,0 @@ } |
+345
-85
@@ -72,2 +72,8 @@ /******************************************************* | ||
| static struct hid_api_version api_version = { | ||
| .major = HID_API_VERSION_MAJOR, | ||
| .minor = HID_API_VERSION_MINOR, | ||
| .patch = HID_API_VERSION_PATCH | ||
| }; | ||
| /* Global error message that is not specific to a device, e.g. for | ||
@@ -121,3 +127,16 @@ hid_open(). It is thread-local like errno. */ | ||
| /* See register_global_error, but you can pass 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); | ||
| va_end(args); | ||
| register_global_error(msg); | ||
| } | ||
| /* Set the last error for a device to be reported by hid_error(device). | ||
@@ -157,2 +176,59 @@ * The given error message will be copied (and decoded according to the | ||
| /* | ||
| * Gets the size of the HID item at the given position | ||
| * Returns 1 if successful, 0 if an invalid key | ||
| * Sets data_len and key_size when successful | ||
| */ | ||
| static int get_hid_item_size(__u8 *report_descriptor, unsigned int pos, __u32 size, int *data_len, int *key_size) | ||
| { | ||
| int key = report_descriptor[pos]; | ||
| int size_code; | ||
| /* | ||
| * This is a Long Item. The next byte contains the | ||
| * length of the data section (value) for this key. | ||
| * See the HID specification, version 1.11, section | ||
| * 6.2.2.3, titled "Long Items." | ||
| */ | ||
| if ((key & 0xf0) == 0xf0) { | ||
| if (pos + 1 < size) | ||
| { | ||
| *data_len = report_descriptor[pos + 1]; | ||
| *key_size = 3; | ||
| return 1; | ||
| } | ||
| *data_len = 0; /* malformed report */ | ||
| *key_size = 0; | ||
| } | ||
| /* | ||
| * This is a Short Item. The bottom two bits of the | ||
| * key contain the size code for the data section | ||
| * (value) for this key. Refer to the HID | ||
| * specification, version 1.11, section 6.2.2.2, | ||
| * titled "Short Items." | ||
| */ | ||
| size_code = key & 0x3; | ||
| switch (size_code) { | ||
| case 0: | ||
| case 1: | ||
| case 2: | ||
| *data_len = size_code; | ||
| *key_size = 1; | ||
| return 1; | ||
| case 3: | ||
| *data_len = 4; | ||
| *key_size = 1; | ||
| return 1; | ||
| default: | ||
| /* Can't ever happen since size_code is & 0x3 */ | ||
| *data_len = 0; | ||
| *key_size = 0; | ||
| break; | ||
| }; | ||
| /* malformed report */ | ||
| return 0; | ||
| } | ||
| /* uses_numbered_reports() returns 1 if report_descriptor describes a device | ||
@@ -162,3 +238,2 @@ which contains numbered reports. */ | ||
| unsigned int i = 0; | ||
| int size_code; | ||
| int data_len, key_size; | ||
@@ -176,39 +251,6 @@ | ||
| //printf("key: %02hhx\n", key); | ||
| /* Determine data_len and key_size */ | ||
| if (!get_hid_item_size(report_descriptor, i, size, &data_len, &key_size)) | ||
| return 0; /* malformed report */ | ||
| if ((key & 0xf0) == 0xf0) { | ||
| /* This is a Long Item. The next byte contains the | ||
| length of the data section (value) for this key. | ||
| See the HID specification, version 1.11, section | ||
| 6.2.2.3, titled "Long Items." */ | ||
| if (i+1 < size) | ||
| data_len = report_descriptor[i+1]; | ||
| else | ||
| data_len = 0; /* malformed report */ | ||
| key_size = 3; | ||
| } | ||
| else { | ||
| /* This is a Short Item. The bottom two bits of the | ||
| key contain the size code for the data section | ||
| (value) for this key. Refer to the HID | ||
| specification, version 1.11, section 6.2.2.2, | ||
| titled "Short Items." */ | ||
| size_code = key & 0x3; | ||
| switch (size_code) { | ||
| case 0: | ||
| case 1: | ||
| case 2: | ||
| data_len = size_code; | ||
| break; | ||
| case 3: | ||
| data_len = 4; | ||
| break; | ||
| default: | ||
| /* Can't ever happen since size_code is & 0x3 */ | ||
| data_len = 0; | ||
| break; | ||
| }; | ||
| key_size = 1; | ||
| } | ||
| /* Skip over this key and it's associated data */ | ||
@@ -223,2 +265,153 @@ i += data_len + key_size; | ||
| /* | ||
| * Get bytes from a HID Report Descriptor. | ||
| * Only call with a num_bytes of 0, 1, 2, or 4. | ||
| */ | ||
| static __u32 get_hid_report_bytes(__u8 *rpt, size_t len, size_t num_bytes, size_t cur) | ||
| { | ||
| /* Return if there aren't enough bytes. */ | ||
| if (cur + num_bytes >= len) | ||
| return 0; | ||
| if (num_bytes == 0) | ||
| return 0; | ||
| else if (num_bytes == 1) | ||
| return rpt[cur + 1]; | ||
| else if (num_bytes == 2) | ||
| return (rpt[cur + 2] * 256 + rpt[cur + 1]); | ||
| else if (num_bytes == 4) | ||
| return ( | ||
| rpt[cur + 4] * 0x01000000 + | ||
| rpt[cur + 3] * 0x00010000 + | ||
| rpt[cur + 2] * 0x00000100 + | ||
| rpt[cur + 1] * 0x00000001 | ||
| ); | ||
| else | ||
| return 0; | ||
| } | ||
| /* | ||
| * Retrieves the device's Usage Page and Usage from the report descriptor. | ||
| * The algorithm returns the current Usage Page/Usage pair whenever a new | ||
| * Collection is found and a Usage Local Item is currently in scope. | ||
| * Usage Local Items are consumed by each Main Item (See. 6.2.2.8). | ||
| * The algorithm should give similar results as Apple's: | ||
| * https://developer.apple.com/documentation/iokit/kiohiddeviceusagepairskey?language=objc | ||
| * Physical Collections are also matched (macOS does the same). | ||
| * | ||
| * This function can be called repeatedly until it returns non-0 | ||
| * Usage is found. pos is the starting point (initially 0) and will be updated | ||
| * to the next search position. | ||
| * | ||
| * The return value is 0 when a pair is found. | ||
| * 1 when finished processing descriptor. | ||
| * -1 on a malformed report. | ||
| */ | ||
| static int get_next_hid_usage(__u8 *report_descriptor, __u32 size, unsigned int *pos, unsigned short *usage_page, unsigned short *usage) | ||
| { | ||
| int data_len, key_size; | ||
| int initial = *pos == 0; /* Used to handle case where no top-level application collection is defined */ | ||
| int usage_pair_ready = 0; | ||
| /* Usage is a Local Item, it must be set before each Main Item (Collection) before a pair is returned */ | ||
| int usage_found = 0; | ||
| while (*pos < size) { | ||
| int key = report_descriptor[*pos]; | ||
| int key_cmd = key & 0xfc; | ||
| /* Determine data_len and key_size */ | ||
| if (!get_hid_item_size(report_descriptor, *pos, size, &data_len, &key_size)) | ||
| return -1; /* malformed report */ | ||
| switch (key_cmd) { | ||
| case 0x4: /* Usage Page 6.2.2.7 (Global) */ | ||
| *usage_page = get_hid_report_bytes(report_descriptor, size, data_len, *pos); | ||
| break; | ||
| case 0x8: /* Usage 6.2.2.8 (Local) */ | ||
| *usage = get_hid_report_bytes(report_descriptor, size, data_len, *pos); | ||
| usage_found = 1; | ||
| break; | ||
| case 0xa0: /* Collection 6.2.2.4 (Main) */ | ||
| /* A Usage Item (Local) must be found for the pair to be valid */ | ||
| if (usage_found) | ||
| usage_pair_ready = 1; | ||
| /* Usage is a Local Item, unset it */ | ||
| usage_found = 0; | ||
| break; | ||
| case 0x80: /* Input 6.2.2.4 (Main) */ | ||
| case 0x90: /* Output 6.2.2.4 (Main) */ | ||
| case 0xb0: /* Feature 6.2.2.4 (Main) */ | ||
| case 0xc0: /* End Collection 6.2.2.4 (Main) */ | ||
| /* Usage is a Local Item, unset it */ | ||
| usage_found = 0; | ||
| break; | ||
| } | ||
| /* Skip over this key and it's associated data */ | ||
| *pos += data_len + key_size; | ||
| /* Return usage pair */ | ||
| if (usage_pair_ready) | ||
| return 0; | ||
| } | ||
| /* If no top-level application collection is found and usage page/usage pair is found, pair is valid | ||
| https://docs.microsoft.com/en-us/windows-hardware/drivers/hid/top-level-collections */ | ||
| if (initial && usage_found) | ||
| return 0; /* success */ | ||
| return 1; /* finished processing */ | ||
| } | ||
| /* | ||
| * Retrieves the hidraw report descriptor from a file. | ||
| * When using this form, <sysfs_path>/device/report_descriptor, elevated priviledges are not required. | ||
| */ | ||
| static int get_hid_report_descriptor(const char *rpt_path, struct hidraw_report_descriptor *rpt_desc) | ||
| { | ||
| int rpt_handle; | ||
| ssize_t res; | ||
| rpt_handle = open(rpt_path, O_RDONLY); | ||
| if (rpt_handle < 0) { | ||
| register_global_error_format("open failed (%s): %s", rpt_path, strerror(errno)); | ||
| return -1; | ||
| } | ||
| /* | ||
| * Read in the Report Descriptor | ||
| * The sysfs file has a maximum size of 4096 (which is the same as HID_MAX_DESCRIPTOR_SIZE) so we should always | ||
| * be ok when reading the descriptor. | ||
| * In practice if the HID descriptor is any larger I suspect many other things will break. | ||
| */ | ||
| memset(rpt_desc, 0x0, sizeof(*rpt_desc)); | ||
| res = read(rpt_handle, rpt_desc->value, HID_MAX_DESCRIPTOR_SIZE); | ||
| if (res < 0) { | ||
| register_global_error_format("read failed (%s): %s", rpt_path, strerror(errno)); | ||
| } | ||
| rpt_desc->size = (__u32) res; | ||
| close(rpt_handle); | ||
| return (int) res; | ||
| } | ||
| static int get_hid_report_descriptor_from_sysfs(const char *sysfs_path, struct hidraw_report_descriptor *rpt_desc) | ||
| { | ||
| int res = -1; | ||
| /* Construct <sysfs_path>/device/report_descriptor */ | ||
| size_t rpt_path_len = strlen(sysfs_path) + 25 + 1; | ||
| char* rpt_path = (char*) calloc(1, rpt_path_len); | ||
| snprintf(rpt_path, rpt_path_len, "%s/device/report_descriptor", sysfs_path); | ||
| res = get_hid_report_descriptor(rpt_path, rpt_desc); | ||
| free(rpt_path); | ||
| return res; | ||
| } | ||
| /* | ||
| * The caller is responsible for free()ing the (newly-allocated) character | ||
@@ -228,3 +421,3 @@ * strings pointed to by serial_number_utf8 and product_name_utf8 after use. | ||
| static int | ||
| parse_uevent_info(const char *uevent, int *bus_type, | ||
| parse_uevent_info(const char *uevent, unsigned *bus_type, | ||
| unsigned short *vendor_id, unsigned short *product_id, | ||
@@ -288,4 +481,4 @@ char **serial_number_utf8, char **product_name_utf8) | ||
| int ret = -1; | ||
| char *serial_number_utf8 = NULL; | ||
| char *product_name_utf8 = NULL; | ||
| char *serial_number_utf8 = NULL; | ||
| char *product_name_utf8 = NULL; | ||
@@ -313,3 +506,3 @@ /* Create the udev object */ | ||
| unsigned short dev_pid; | ||
| int bus_type; | ||
| unsigned bus_type; | ||
| size_t retm; | ||
@@ -325,23 +518,4 @@ | ||
| if (bus_type == BUS_BLUETOOTH) { | ||
| 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; | ||
| } | ||
| } | ||
| else { | ||
| /* Standard USB device */ | ||
| if (bus_type == BUS_USB) { | ||
| /* This is a USB device. Find its parent USB Device node. */ | ||
@@ -368,6 +542,36 @@ parent = udev_device_get_parent_with_subsystem_devtype( | ||
| ret = (retm == (size_t)-1)? -1: 0; | ||
| goto end; | ||
| } | ||
| /* USB information parsed */ | ||
| goto end; | ||
| } | ||
| else { | ||
| /* Correctly handled below */ | ||
| } | ||
| } | ||
| /* 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; | ||
| } | ||
| } | ||
| } | ||
@@ -377,4 +581,4 @@ } | ||
| end: | ||
| free(serial_number_utf8); | ||
| free(product_name_utf8); | ||
| free(serial_number_utf8); | ||
| free(product_name_utf8); | ||
@@ -389,2 +593,12 @@ udev_device_unref(udev_dev); | ||
| HID_API_EXPORT const struct hid_api_version* HID_API_CALL hid_version() | ||
| { | ||
| return &api_version; | ||
| } | ||
| HID_API_EXPORT const char* HID_API_CALL hid_version_str() | ||
| { | ||
| return HID_API_VERSION_STR; | ||
| } | ||
| int HID_API_EXPORT hid_init(void) | ||
@@ -449,4 +663,5 @@ { | ||
| char *product_name_utf8 = NULL; | ||
| int bus_type; | ||
| unsigned bus_type; | ||
| int result; | ||
| struct hidraw_report_descriptor report_desc; | ||
@@ -482,5 +697,11 @@ /* Get the filename of the /sys entry for the device | ||
| if (bus_type != BUS_USB && bus_type != BUS_BLUETOOTH) { | ||
| /* We only know how to handle USB and BT devices. */ | ||
| goto next; | ||
| /* Filter out unhandled devices right away */ | ||
| switch (bus_type) { | ||
| case BUS_BLUETOOTH: | ||
| case BUS_I2C: | ||
| case BUS_USB: | ||
| break; | ||
| default: | ||
| goto next; | ||
| } | ||
@@ -534,18 +755,10 @@ | ||
| /* uhid USB devices | ||
| Since this is a virtual hid interface, no USB information will | ||
| be available. */ | ||
| if (!usb_dev) { | ||
| /* Free this device */ | ||
| free(cur_dev->serial_number); | ||
| free(cur_dev->path); | ||
| free(cur_dev); | ||
| /* Take it off the device list. */ | ||
| if (prev_dev) { | ||
| prev_dev->next = NULL; | ||
| cur_dev = prev_dev; | ||
| } | ||
| else { | ||
| cur_dev = root = NULL; | ||
| } | ||
| goto next; | ||
| /* Manufacturer and Product strings */ | ||
| cur_dev->manufacturer_string = wcsdup(L""); | ||
| cur_dev->product_string = utf8_to_wchar_t(product_name_utf8); | ||
| break; | ||
| } | ||
@@ -574,2 +787,3 @@ | ||
| case BUS_BLUETOOTH: | ||
| case BUS_I2C: | ||
| /* Manufacturer and Product strings */ | ||
@@ -586,2 +800,41 @@ cur_dev->manufacturer_string = wcsdup(L""); | ||
| } | ||
| /* 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; | ||
| } | ||
| } | ||
| } | ||
@@ -673,3 +926,3 @@ | ||
| /* If we have a good handle, return it. */ | ||
| if (dev->device_handle > 0) { | ||
| if (dev->device_handle >= 0) { | ||
| /* Set device error to none */ | ||
@@ -815,2 +1068,5 @@ register_device_error(dev, NULL); | ||
| { | ||
| (void)dev; | ||
| (void)data; | ||
| (void)length; | ||
| return -1; | ||
@@ -852,2 +1108,6 @@ } | ||
| { | ||
| (void)dev; | ||
| (void)string_index; | ||
| (void)string; | ||
| (void)maxlen; | ||
| return -1; | ||
@@ -854,0 +1114,0 @@ } |
+71
-25
@@ -39,2 +39,5 @@ /******************************************************* | ||
| /* As defined in AppKit.h, but we don't need the entire AppKit for a single constant. */ | ||
| extern const double NSAppKitVersionNumber; | ||
| /* Barrier implementation because Mac OSX doesn't have pthread_barrier. | ||
@@ -54,2 +57,4 @@ It also doesn't have clock_gettime(). So much for POSIX and SUSv2. | ||
| { | ||
| (void) attr; | ||
| if(count == 0) { | ||
@@ -184,3 +189,10 @@ errno = EINVAL; | ||
| static struct hid_api_version api_version = { | ||
| .major = HID_API_VERSION_MAJOR, | ||
| .minor = HID_API_VERSION_MINOR, | ||
| .patch = HID_API_VERSION_PATCH | ||
| }; | ||
| static IOHIDManagerRef hid_mgr = 0x0; | ||
| static int is_macos_10_10_or_greater = 0; | ||
@@ -270,4 +282,4 @@ | ||
| if (chars_copied == len) | ||
| buf[len] = 0; /* len is decremented above */ | ||
| if (chars_copied <= 0) | ||
| buf[0] = 0; | ||
| else | ||
@@ -381,2 +393,12 @@ buf[chars_copied] = 0; | ||
| HID_API_EXPORT const struct hid_api_version* HID_API_CALL hid_version() | ||
| { | ||
| return &api_version; | ||
| } | ||
| HID_API_EXPORT const char* HID_API_CALL hid_version_str() | ||
| { | ||
| return HID_API_VERSION_STR; | ||
| } | ||
| /* Initialize the IOHIDManager if necessary. This is the public function, and | ||
@@ -388,2 +410,3 @@ it is safe to call this function repeatedly. Return 0 for success and -1 | ||
| if (!hid_mgr) { | ||
| is_macos_10_10_or_greater = (NSAppKitVersionNumber >= 1343); /* NSAppKitVersionNumber10_10 */ | ||
| return init_hid_manager(); | ||
@@ -474,3 +497,3 @@ } | ||
| * for its interface. */ | ||
| bool is_usb_hid = get_int_property(dev, CFSTR(kUSBInterfaceClass)) == kUSBHIDClass; | ||
| int is_usb_hid = get_int_property(dev, CFSTR(kUSBInterfaceClass)) == kUSBHIDClass; | ||
| if (is_usb_hid) { | ||
@@ -488,7 +511,15 @@ /* Get the interface number */ | ||
| { | ||
| struct hid_device_info *root = NULL; | ||
| const int32_t primary_usage_page = get_int_property(device, CFSTR(kIOHIDPrimaryUsagePageKey)); | ||
| const int32_t primary_usage = get_int_property(device, CFSTR(kIOHIDPrimaryUsageKey)); | ||
| /* Primary should always be first, to match previous behavior. */ | ||
| struct hid_device_info *root = create_device_info_with_usage(device, primary_usage_page, primary_usage); | ||
| struct hid_device_info *cur = root; | ||
| if (!root) | ||
| return NULL; | ||
| CFArrayRef usage_pairs = get_usage_pairs(device); | ||
| if (usage_pairs != NULL) { | ||
| struct hid_device_info *cur = NULL; | ||
| struct hid_device_info *next = NULL; | ||
@@ -512,9 +543,7 @@ for (CFIndex i = 0; i < CFArrayGetCount(usage_pairs); i++) { | ||
| } | ||
| if (usage_page == primary_usage_page && usage == primary_usage) | ||
| continue; /* Already added. */ | ||
| next = create_device_info_with_usage(device, usage_page, usage); | ||
| if (cur == NULL) { | ||
| root = next; | ||
| } | ||
| else { | ||
| cur->next = next; | ||
| } | ||
| cur->next = next; | ||
| if (next != NULL) { | ||
@@ -526,10 +555,2 @@ cur = next; | ||
| if (root == NULL) { | ||
| /* error when generating or parsing usage pairs */ | ||
| int32_t usage_page = get_int_property(device, CFSTR(kIOHIDPrimaryUsagePageKey)); | ||
| int32_t usage = get_int_property(device, CFSTR(kIOHIDPrimaryUsageKey)); | ||
| root = create_device_info_with_usage(device, usage_page, usage); | ||
| } | ||
| return root; | ||
@@ -575,2 +596,5 @@ } | ||
| CFSetRef device_set = IOHIDManagerCopyDevices(hid_mgr); | ||
| if (device_set == NULL) { | ||
| return NULL; | ||
| } | ||
@@ -669,2 +693,5 @@ /* Convert the list into a C array so we can iterate easily. */ | ||
| { | ||
| (void) result; | ||
| (void) sender; | ||
| /* Stop the Run Loop for this device. */ | ||
@@ -684,2 +711,7 @@ hid_device *d = (hid_device*) context; | ||
| { | ||
| (void) result; | ||
| (void) sender; | ||
| (void) report_type; | ||
| (void) report_id; | ||
| struct input_report *rpt; | ||
@@ -843,3 +875,3 @@ hid_device *dev = (hid_device*) context; | ||
| printing the reference seems to work. */ | ||
| sprintf(str, "HIDAPI_%p", dev->device_handle); | ||
| sprintf(str, "HIDAPI_%p", (void*) dev->device_handle); | ||
| dev->run_loop_mode = | ||
@@ -934,3 +966,3 @@ CFStringCreateWithCString(NULL, str, kCFStringEncodingASCII); | ||
| if (res == kIOReturnSuccess) { | ||
| if (report_id == 0x0) { // 0 report number still present at the beginning | ||
| if (report_id == 0x0) { /* 0 report number still present at the beginning */ | ||
| report_length++; | ||
@@ -1111,4 +1143,6 @@ } | ||
| /* Disconnect the report callback before close. */ | ||
| if (!dev->disconnected) { | ||
| /* Disconnect the report callback before close. | ||
| See comment below. | ||
| */ | ||
| if (is_macos_10_10_or_greater || !dev->disconnected) { | ||
| IOHIDDeviceRegisterInputReportCallback( | ||
@@ -1137,4 +1171,10 @@ dev->device_handle, dev->input_report_buf, dev->max_input_report_len, | ||
| been unplugged. If it's been unplugged, then calling | ||
| IOHIDDeviceClose() will crash. */ | ||
| if (!dev->disconnected) { | ||
| IOHIDDeviceClose() will crash. | ||
| UPD: The crash part was true in/until some version of macOS. | ||
| Starting with macOS 10.15, there is an opposite effect in some environments: | ||
| crash happenes if IOHIDDeviceClose() is not called. | ||
| Not leaking a resource in all tested environments. | ||
| */ | ||
| if (is_macos_10_10_or_greater || !dev->disconnected) { | ||
| IOHIDDeviceClose(dev->device_handle, kIOHIDOptionsTypeSeizeDevice); | ||
@@ -1171,2 +1211,7 @@ } | ||
| { | ||
| (void) dev; | ||
| (void) string_index; | ||
| (void) string; | ||
| (void) maxlen; | ||
| /* TODO: */ | ||
@@ -1180,2 +1225,3 @@ | ||
| { | ||
| (void) dev; | ||
| /* TODO: */ | ||
@@ -1182,0 +1228,0 @@ |
@@ -80,2 +80,3 @@ #!/bin/bash | ||
| rm -f $EXEPATH/* | ||
| mkdir -p $EXEPATH | ||
@@ -82,0 +83,0 @@ # Copy the binary into the bundle. Use ../libtool to do this if it's |
+91
-47
@@ -82,2 +82,8 @@ /******************************************************* | ||
| static struct hid_api_version api_version = { | ||
| .major = HID_API_VERSION_MAJOR, | ||
| .minor = HID_API_VERSION_MINOR, | ||
| .patch = HID_API_VERSION_PATCH | ||
| }; | ||
| #ifndef HIDAPI_USE_DDK | ||
@@ -148,2 +154,3 @@ /* Since we're not building with the DDK, and the HID header | ||
| OVERLAPPED ol; | ||
| OVERLAPPED write_ol; | ||
| }; | ||
@@ -164,2 +171,4 @@ | ||
| dev->ol.hEvent = CreateEvent(NULL, FALSE, FALSE /*initial state f=nonsignaled*/, NULL); | ||
| memset(&dev->write_ol, 0, sizeof(dev->write_ol)); | ||
| dev->write_ol.hEvent = CreateEvent(NULL, FALSE, FALSE /*inital state f=nonsignaled*/, NULL); | ||
@@ -172,2 +181,3 @@ return dev; | ||
| CloseHandle(dev->ol.hEvent); | ||
| CloseHandle(dev->write_ol.hEvent); | ||
| CloseHandle(dev->device_handle); | ||
@@ -182,3 +192,3 @@ LocalFree(dev->last_error_str); | ||
| WCHAR *ptr, *msg; | ||
| (void)op; // unreferenced param | ||
| FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | | ||
@@ -190,3 +200,3 @@ FORMAT_MESSAGE_FROM_SYSTEM | | ||
| MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), | ||
| (LPVOID)&msg, 0/*sz*/, | ||
| (LPWSTR)&msg, 0/*sz*/, | ||
| NULL); | ||
@@ -216,2 +226,6 @@ | ||
| if (lib_handle) { | ||
| #if defined(__GNUC__) | ||
| # pragma GCC diagnostic push | ||
| # pragma GCC diagnostic ignored "-Wcast-function-type" | ||
| #endif | ||
| #define RESOLVE(x) x = (x##_)GetProcAddress(lib_handle, #x); if (!x) return -1; | ||
@@ -231,2 +245,5 @@ RESOLVE(HidD_GetAttributes); | ||
| #undef RESOLVE | ||
| #if defined(__GNUC__) | ||
| # pragma GCC diagnostic pop | ||
| #endif | ||
| } | ||
@@ -257,2 +274,12 @@ else | ||
| HID_API_EXPORT const struct hid_api_version* HID_API_CALL hid_version() | ||
| { | ||
| return &api_version; | ||
| } | ||
| HID_API_EXPORT const char* HID_API_CALL hid_version_str() | ||
| { | ||
| return HID_API_VERSION_STR; | ||
| } | ||
| int HID_API_EXPORT hid_init(void) | ||
@@ -413,3 +440,2 @@ { | ||
| HIDP_CAPS caps; | ||
| BOOLEAN res; | ||
| NTSTATUS nt_res; | ||
@@ -550,3 +576,3 @@ wchar_t wstr[WSTR_LEN]; /* TODO: Determine Size */ | ||
| if (serial_number) { | ||
| if (wcscmp(serial_number, cur_dev->serial_number) == 0) { | ||
| if (cur_dev->serial_number && wcscmp(serial_number, cur_dev->serial_number) == 0) { | ||
| path_to_open = cur_dev->path; | ||
@@ -643,8 +669,8 @@ break; | ||
| { | ||
| DWORD bytes_written; | ||
| DWORD bytes_written = 0; | ||
| int function_result = -1; | ||
| BOOL res; | ||
| BOOL overlapped = FALSE; | ||
| OVERLAPPED ol; | ||
| unsigned char *buf; | ||
| memset(&ol, 0, sizeof(ol)); | ||
@@ -669,3 +695,3 @@ /* Make sure the right number of bytes are passed to WriteFile. Windows | ||
| res = WriteFile(dev->device_handle, buf, length, NULL, &ol); | ||
| res = WriteFile(dev->device_handle, buf, (DWORD) length, NULL, &dev->write_ol); | ||
@@ -676,15 +702,27 @@ if (!res) { | ||
| register_error(dev, "WriteFile"); | ||
| bytes_written = -1; | ||
| goto end_of_function; | ||
| } | ||
| overlapped = TRUE; | ||
| } | ||
| /* Wait here until the write is done. This makes | ||
| hid_write() synchronous. */ | ||
| res = GetOverlappedResult(dev->device_handle, &ol, &bytes_written, TRUE/*wait*/); | ||
| if (!res) { | ||
| /* The Write operation failed. */ | ||
| register_error(dev, "WriteFile"); | ||
| bytes_written = -1; | ||
| goto end_of_function; | ||
| if (overlapped) { | ||
| /* Wait for the transaction to complete. This makes | ||
| hid_write() synchronous. */ | ||
| res = WaitForSingleObject(dev->write_ol.hEvent, 1000); | ||
| if (res != WAIT_OBJECT_0) { | ||
| /* There was a Timeout. */ | ||
| register_error(dev, "WriteFile/WaitForSingleObject Timeout"); | ||
| goto end_of_function; | ||
| } | ||
| /* Get the result. */ | ||
| res = GetOverlappedResult(dev->device_handle, &dev->write_ol, &bytes_written, FALSE/*wait*/); | ||
| if (res) { | ||
| function_result = bytes_written; | ||
| } | ||
| else { | ||
| /* The Write operation failed. */ | ||
| register_error(dev, "WriteFile"); | ||
| goto end_of_function; | ||
| } | ||
| } | ||
@@ -696,3 +734,3 @@ | ||
| return bytes_written; | ||
| return function_result; | ||
| } | ||
@@ -705,3 +743,4 @@ | ||
| size_t copy_len = 0; | ||
| BOOL res; | ||
| BOOL res = FALSE; | ||
| BOOL overlapped = FALSE; | ||
@@ -716,3 +755,3 @@ /* Copy the handle for convenience. */ | ||
| ResetEvent(ev); | ||
| res = ReadFile(dev->device_handle, dev->read_buf, dev->input_report_length, &bytes_read, &dev->ol); | ||
| res = ReadFile(dev->device_handle, dev->read_buf, (DWORD) dev->input_report_length, &bytes_read, &dev->ol); | ||
@@ -727,20 +766,25 @@ if (!res) { | ||
| } | ||
| } | ||
| overlapped = TRUE; | ||
| } | ||
| } | ||
| else { | ||
| overlapped = TRUE; | ||
| } | ||
| if (milliseconds >= 0) { | ||
| /* See if there is any data yet. */ | ||
| res = WaitForSingleObject(ev, milliseconds); | ||
| if (res != WAIT_OBJECT_0) { | ||
| /* There was no data this time. Return zero bytes available, | ||
| but leave the Overlapped I/O running. */ | ||
| return 0; | ||
| if (overlapped) { | ||
| if (milliseconds >= 0) { | ||
| /* See if there is any data yet. */ | ||
| res = WaitForSingleObject(ev, milliseconds); | ||
| if (res != WAIT_OBJECT_0) { | ||
| /* There was no data this time. Return zero bytes available, | ||
| but leave the Overlapped I/O running. */ | ||
| return 0; | ||
| } | ||
| } | ||
| /* Either WaitForSingleObject() told us that ReadFile has completed, or | ||
| we are in non-blocking mode. Get the number of bytes read. The actual | ||
| data has been copied to the data[] array which was passed to ReadFile(). */ | ||
| res = GetOverlappedResult(dev->device_handle, &dev->ol, &bytes_read, TRUE/*wait*/); | ||
| } | ||
| /* Either WaitForSingleObject() told us that ReadFile has completed, or | ||
| we are in non-blocking mode. Get the number of bytes read. The actual | ||
| data has been copied to the data[] array which was passed to ReadFile(). */ | ||
| res = GetOverlappedResult(dev->device_handle, &dev->ol, &bytes_read, TRUE/*wait*/); | ||
| /* Set pending back to false, even if GetOverlappedResult() returned error. */ | ||
@@ -772,3 +816,3 @@ dev->read_pending = FALSE; | ||
| return copy_len; | ||
| return (int) copy_len; | ||
| } | ||
@@ -789,3 +833,3 @@ | ||
| { | ||
| BOOL res = HidD_SetFeature(dev->device_handle, (PVOID)data, length); | ||
| BOOL res = HidD_SetFeature(dev->device_handle, (PVOID)data, (DWORD) length); | ||
| if (!res) { | ||
@@ -796,3 +840,3 @@ register_error(dev, "HidD_SetFeature"); | ||
| return length; | ||
| return (int) length; | ||
| } | ||
@@ -819,4 +863,4 @@ | ||
| IOCTL_HID_GET_FEATURE, | ||
| data, length, | ||
| data, length, | ||
| data, (DWORD) length, | ||
| data, (DWORD) length, | ||
| &bytes_returned, &ol); | ||
@@ -853,4 +897,4 @@ | ||
| { | ||
| BOOL res; | ||
| #if 0 | ||
| BOOL res; | ||
| res = HidD_GetInputReport(dev->device_handle, data, length); | ||
@@ -868,6 +912,6 @@ if (!res) { | ||
| BOOL res = DeviceIoControl(dev->device_handle, | ||
| res = DeviceIoControl(dev->device_handle, | ||
| IOCTL_HID_GET_INPUT_REPORT, | ||
| data, length, | ||
| data, length, | ||
| data, (DWORD) length, | ||
| data, (DWORD) length, | ||
| &bytes_returned, &ol); | ||
@@ -913,3 +957,3 @@ | ||
| res = HidD_GetManufacturerString(dev->device_handle, string, sizeof(wchar_t) * MIN(maxlen, MAX_STRING_WCHARS)); | ||
| res = HidD_GetManufacturerString(dev->device_handle, string, sizeof(wchar_t) * (DWORD) MIN(maxlen, MAX_STRING_WCHARS)); | ||
| if (!res) { | ||
@@ -927,3 +971,3 @@ register_error(dev, "HidD_GetManufacturerString"); | ||
| res = HidD_GetProductString(dev->device_handle, string, sizeof(wchar_t) * MIN(maxlen, MAX_STRING_WCHARS)); | ||
| res = HidD_GetProductString(dev->device_handle, string, sizeof(wchar_t) * (DWORD) MIN(maxlen, MAX_STRING_WCHARS)); | ||
| if (!res) { | ||
@@ -941,3 +985,3 @@ register_error(dev, "HidD_GetProductString"); | ||
| res = HidD_GetSerialNumberString(dev->device_handle, string, sizeof(wchar_t) * MIN(maxlen, MAX_STRING_WCHARS)); | ||
| res = HidD_GetSerialNumberString(dev->device_handle, string, sizeof(wchar_t) * (DWORD) MIN(maxlen, MAX_STRING_WCHARS)); | ||
| if (!res) { | ||
@@ -955,3 +999,3 @@ register_error(dev, "HidD_GetSerialNumberString"); | ||
| res = HidD_GetIndexedString(dev->device_handle, string_index, string, sizeof(wchar_t) * MIN(maxlen, MAX_STRING_WCHARS)); | ||
| res = HidD_GetIndexedString(dev->device_handle, string_index, string, sizeof(wchar_t) * (DWORD) MIN(maxlen, MAX_STRING_WCHARS)); | ||
| if (!res) { | ||
@@ -958,0 +1002,0 @@ register_error(dev, "HidD_GetIndexedString"); |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet