From c1ada62bb3efea4bbca793893469ed33b39c86a3 Mon Sep 17 00:00:00 2001 From: SHENGYI HONG Date: Thu, 27 Mar 2025 16:42:37 +0800 Subject: [PATCH] Add hidraw backend for FreeBSD FreeBSD support hidraw in Kernel from 13.0. By using libusb only, we can only see the HID device from usb. To address this, we implement hidraw backend for FreeBSD. Just like Linux use libudev to handle usb specified HID stuff (like Manufacture), we use libusb to handle it. Sponsored-by: FreeBSD Foundation Sponsored-by: Framework Laptop. Inc --- CMakeLists.txt | 4 + Makefile.am | 7 +- configure.ac | 9 +- freebsd/.gitignore | 18 + freebsd/CMakeLists.txt | 38 ++ freebsd/Makefile-manual | 42 ++ freebsd/Makefile.am | 10 + freebsd/hid.c | 1202 +++++++++++++++++++++++++++++++++++++++ hidtest/CMakeLists.txt | 2 +- hidtest/Makefile.am | 12 + libusb/Makefile.am | 8 +- libusb/Makefile.freebsd | 8 +- src/CMakeLists.txt | 13 + testgui/Makefile.am | 11 + 14 files changed, 1372 insertions(+), 12 deletions(-) create mode 100644 freebsd/.gitignore create mode 100644 freebsd/CMakeLists.txt create mode 100644 freebsd/Makefile-manual create mode 100644 freebsd/Makefile.am create mode 100644 freebsd/hid.c diff --git a/CMakeLists.txt b/CMakeLists.txt index d7086813c..85430866d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -42,6 +42,10 @@ elseif(NOT WIN32) option(HIDAPI_WITH_HIDRAW "Build HIDRAW-based implementation of HIDAPI" ON) option(HIDAPI_WITH_LIBUSB "Build LIBUSB-based implementation of HIDAPI" ON) endif() + if(CMAKE_SYSTEM_NAME MATCHES "FreeBSD") + option(HIDAPI_WITH_HIDRAW "Build HIDRAW-based implementation of HIDAPI" ON) + option(HIDAPI_WITH_LIBUSB "Build LIBUSB-based implementation of HIDAPI" ON) + endif() if(CMAKE_SYSTEM_NAME MATCHES "NetBSD") option(HIDAPI_WITH_NETBSD "Build NetBSD/UHID implementation of HIDAPI" ON) endif() diff --git a/Makefile.am b/Makefile.am index 00bcb73cf..01c09fc1a 100644 --- a/Makefile.am +++ b/Makefile.am @@ -10,8 +10,12 @@ endif if OS_LINUX pkgconfig_DATA=pc/hidapi-hidraw.pc pc/hidapi-libusb.pc else +if OS_FREEBSD +pkgconfig_DATA=pc/hidapi-hidraw.pc pc/hidapi-libusb.pc +else pkgconfig_DATA=pc/hidapi.pc endif +endif SUBDIRS= @@ -24,7 +28,7 @@ SUBDIRS += mac endif if OS_FREEBSD -SUBDIRS += libusb +SUBDIRS += freebsd libusb endif if OS_KFREEBSD @@ -70,6 +74,7 @@ SCMCLEAN_TARGETS= \ libusb/Makefile.in \ Makefile.in \ linux/Makefile.in \ + freebsd/Makefile.in \ windows/Makefile.in \ m4/libtool.m4 \ m4/lt~obsolete.m4 \ diff --git a/configure.ac b/configure.ac index 1b2051052..bf2007d65 100644 --- a/configure.ac +++ b/configure.ac @@ -80,7 +80,7 @@ case $host in AC_MSG_RESULT([ (FreeBSD back-end)]) AC_DEFINE(OS_FREEBSD, 1, [FreeBSD implementation]) AC_SUBST(OS_FREEBSD) - backend="libusb" + backend="freebsd" os="freebsd" threads="pthreads" @@ -88,6 +88,10 @@ case $host in LDFLAGS="$LDFLAGS -L/usr/local/lib" LIBS="${LIBS}" PKG_CHECK_MODULES([libusb], [libusb-1.0 >= 1.0.9], true, [hidapi_lib_error libusb-1.0]) + # HIDAPI/hidraw libs + LIBS_HIDRAW_PR="${LIBS_HIDRAW_PR} $libusb_LIBS" + CFLAGS_HIDRAW="${CFLAGS_HIDRAW} $libusb_CFLAGS" + # HIDAPI/libusb libs LIBS_LIBUSB_PRIVATE="${LIBS_LIBUSB_PRIVATE} $libusb_LIBS" CFLAGS_LIBUSB="${CFLAGS_LIBUSB} $libusb_CFLAGS" AC_CHECK_LIB([iconv], [iconv_open], [LIBS_LIBUSB_PRIVATE="${LIBS_LIBUSB_PRIVATE} -liconv"], [hidapi_lib_error libiconv]) @@ -237,7 +241,7 @@ AM_CONDITIONAL(OS_WINDOWS, test "x$os" = xwindows) AC_CONFIG_HEADERS([config.h]) -if test "x$os" = "xlinux"; then +if test "x$os" = xlinux; then AC_CONFIG_FILES([pc/hidapi-hidraw.pc]) AC_CONFIG_FILES([pc/hidapi-libusb.pc]) else @@ -250,6 +254,7 @@ AC_CONFIG_FILES([Makefile \ hidtest/Makefile \ libusb/Makefile \ linux/Makefile \ + freebsd/Makefile \ mac/Makefile \ testgui/Makefile \ windows/Makefile]) diff --git a/freebsd/.gitignore b/freebsd/.gitignore new file mode 100644 index 000000000..127bf37d9 --- /dev/null +++ b/freebsd/.gitignore @@ -0,0 +1,18 @@ +Debug +Release +*.exp +*.ilk +*.lib +*.suo +*.vcproj.* +*.ncb +*.suo +*.dll +*.pdb +*.o +*.so +hidtest-hidraw +.deps +.libs +*.lo +*.la diff --git a/freebsd/CMakeLists.txt b/freebsd/CMakeLists.txt new file mode 100644 index 000000000..f3fdccc9d --- /dev/null +++ b/freebsd/CMakeLists.txt @@ -0,0 +1,38 @@ +cmake_minimum_required(VERSION 3.6.3...3.25 FATAL_ERROR) + +add_library(hidapi_hidraw + ${HIDAPI_PUBLIC_HEADERS} + hid.c +) +target_link_libraries(hidapi_hidraw PUBLIC hidapi_include) + +find_package(Threads REQUIRED) + +include(FindPkgConfig) +pkg_check_modules(libusb-1.0 REQUIRED IMPORTED_TARGET libusb-1.0>=1.0.9) + +target_link_libraries(hidapi_hidraw PRIVATE PkgConfig::libusb-1.0 Threads::Threads) + +set_target_properties(hidapi_hidraw + PROPERTIES + EXPORT_NAME "hidraw" + OUTPUT_NAME "hidapi-hidraw" + VERSION ${PROJECT_VERSION} + SOVERSION ${PROJECT_VERSION_MAJOR} + PUBLIC_HEADER "${HIDAPI_PUBLIC_HEADERS}" +) + +# compatibility with find_package() +add_library(hidapi::hidraw ALIAS hidapi_hidraw) +# compatibility with raw library link +add_library(hidapi-hidraw ALIAS hidapi_hidraw) + +if(HIDAPI_INSTALL_TARGETS) + install(TARGETS hidapi_hidraw EXPORT hidapi + LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}" + ARCHIVE DESTINATION "${CMAKE_INSTALL_LIBDIR}" + PUBLIC_HEADER DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/hidapi" + ) +endif() + +hidapi_configure_pc("${PROJECT_ROOT}/pc/hidapi-hidraw.pc.in") diff --git a/freebsd/Makefile-manual b/freebsd/Makefile-manual new file mode 100644 index 000000000..f0dada49b --- /dev/null +++ b/freebsd/Makefile-manual @@ -0,0 +1,42 @@ +########################################### +# Simple Makefile for HIDAPI test program +# +# Alan Ott +# Signal 11 Software +# 2010-06-01 +########################################### + +all: hidtest-hidraw libs + +libs: libhidapi-hidraw.so + +CC ?= gcc +CFLAGS ?= -Wall -g -fpic + +LDFLAGS ?= -Wall -g + + +COBJS = hid.o ../hidtest/test.o +OBJS = $(COBJS) +LIBS_USB = `pkg-config libusb-1.0 --libs` +LIBS = $(LIBS_USB) +INCLUDES ?= -I../hidapi `pkg-config libusb-1.0 --cflags` + + +# Console Test Program +hidtest-hidraw: $(COBJS) + $(CC) $(LDFLAGS) $^ $(LIBS_USB) -o $@ + +# Shared Libs +libhidapi-hidraw.so: $(COBJS) + $(CC) $(LDFLAGS) $(LIBS_USB) -shared -fpic -Wl,-soname,$@.0 $^ -o $@ + +# Objects +$(COBJS): %.o: %.c + $(CC) $(CFLAGS) -c $(INCLUDES) $< -o $@ + + +clean: + rm -f $(OBJS) hidtest-hidraw libhidapi-hidraw.so $(COBJS) + +.PHONY: clean libs diff --git a/freebsd/Makefile.am b/freebsd/Makefile.am new file mode 100644 index 000000000..230eeb75a --- /dev/null +++ b/freebsd/Makefile.am @@ -0,0 +1,10 @@ +lib_LTLIBRARIES = libhidapi-hidraw.la +libhidapi_hidraw_la_SOURCES = hid.c +libhidapi_hidraw_la_LDFLAGS = $(LTLDFLAGS) +AM_CPPFLAGS = -I$(top_srcdir)/hidapi/ $(CFLAGS_HIDRAW) +libhidapi_hidraw_la_LIBADD = $(LIBS_HIDRAW) + +hdrdir = $(includedir)/hidapi +hdr_HEADERS = $(top_srcdir)/hidapi/hidapi.h + +EXTRA_DIST = Makefile-manual diff --git a/freebsd/hid.c b/freebsd/hid.c new file mode 100644 index 000000000..3592ba0af --- /dev/null +++ b/freebsd/hid.c @@ -0,0 +1,1202 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "hidapi.h" + +static struct hid_api_version hid_api_ver = { + .major = HID_API_VERSION_MAJOR, + .minor = HID_API_VERSION_MINOR, + .patch = HID_API_VERSION_PATCH +}; + +static wchar_t *global_error_str = NULL; +static libusb_context *global_usb_context = NULL; + +struct hid_device_ { + int device_handle; + int blocking; + int idx; + wchar_t *error_str; + wchar_t *read_error_str; + const char *device_path; + struct hid_device_info* device_info; +}; + +static hid_device *new_hid_device(void) +{ + hid_device *dev = (hid_device*) calloc(1, sizeof(hid_device)); + if (dev == NULL) { + return NULL; + } + + dev->device_handle = -1; + dev->blocking = 1; + dev->error_str = NULL; + dev->read_error_str = NULL; + dev->device_info = NULL; + + return dev; +} + +/* The caller must free the returned string with free(). */ +static wchar_t *utf8_to_wchar_t(const char *utf8) +{ + wchar_t *ret = NULL; + + if (utf8) { + size_t wlen = mbstowcs(NULL, utf8, 0); + if ((size_t) -1 == wlen) { + return wcsdup(L""); + } + ret = (wchar_t*) calloc(wlen+1, sizeof(wchar_t)); + if (ret == NULL) { + /* as much as we can do at this point */ + return NULL; + } + mbstowcs(ret, utf8, wlen+1); + ret[wlen] = 0x0000; + } + + return ret; +} + + +/* Makes a copy of the given error message (and decoded according to the + * currently locale) into the wide string pointer pointed by error_str. + * The last stored error string is freed. + * Use register_error_str(NULL) to free the error message completely. */ +static void register_error_str(wchar_t **error_str, const char *msg) +{ + free(*error_str); + *error_str = utf8_to_wchar_t(msg); +} + +/* Semilar to register_error_str, but allows passing a format string with va_list args into this function. */ +static void register_error_str_vformat(wchar_t **error_str, const char *format, va_list args) +{ + char msg[256]; + vsnprintf(msg, sizeof(msg), format, args); + + register_error_str(error_str, msg); +} + +/* Set the last global error to be reported by hid_error(NULL). + * The given error message will be copied (and decoded according to the + * currently locale, so do not pass in string constants). + * The last stored global error message is freed. + * Use register_global_error(NULL) to indicate "no error". */ +static void register_global_error(const char *msg) +{ + register_error_str(&global_error_str, msg); +} + +/* Similar to register_global_error, but allows passing a format string into this function. */ +static void register_global_error_format(const char *format, ...) +{ + va_list args; + va_start(args, format); + register_error_str_vformat(&global_error_str, format, args); + va_end(args); +} + +/* Set the last error for a device to be reported by hid_error(dev). + * The given error message will be copied (and decoded according to the + * currently locale, so do not pass in string constants). + * The last stored device error message is freed. + * Use register_device_error(dev, NULL) to indicate "no error". */ +static void register_device_error(hid_device *dev, const char *msg) +{ + register_error_str(&dev->error_str, msg); +} + +/* Similar to register_device_error, but you can pass a format string into this function. */ +static void register_device_error_format(hid_device *dev, const char *format, ...) +{ + va_list args; + va_start(args, format); + register_error_str_vformat(&dev->error_str, format, args); + va_end(args); +} + + +struct hid_sysctl_iter { + int cur_oid[CTL_MAXNAME]; + size_t matched_len; + size_t oid_items; + int idx; +}; + +static int hid_get_name_from_mib(int *mib, int items, char *name_buffer, size_t buffer_size) { + int q_name_oid[CTL_MAXNAME] = {0}; + int result = -1; + + q_name_oid[0] = CTL_SYSCTL; + q_name_oid[1] = CTL_SYSCTL_NAME; + memcpy(q_name_oid + 2, mib, items * sizeof(int)); + + if (sysctl(q_name_oid, items + 2, name_buffer, &buffer_size, NULL, 0)) { + register_global_error_format("sysctl(CTL_SYSCTL_NAME): %s", strerror(errno)); + return 0; + } + + return 1; +} + +static int hid_get_dev_idx_from_mib(int *mib, int items) { + char name_buffer[256]; + int result = 0; + + if (hid_get_name_from_mib(mib, items, name_buffer, sizeof(name_buffer)) == 0) + return -1; + + return sscanf(name_buffer, "dev.hidraw.%d", &result) == 1 ? result : -1; +} + +static int hid_get_next_from_oid(int *oid, size_t *items, int total_items) { + int q_next_oid[CTL_MAXNAME], recv_oid[CTL_MAXNAME]; + int found = 0; + size_t cur_items = *items, recv_items; + q_next_oid[0] = CTL_SYSCTL; + q_next_oid[1] = CTL_SYSCTL_NEXTNOSKIP; + + memcpy(recv_oid, oid, cur_items * sizeof(int)); + + while (1) { + memcpy(q_next_oid + 2, recv_oid, cur_items * sizeof(int)); + recv_items = sizeof(recv_oid); + if (sysctl(q_next_oid, cur_items + 2, recv_oid, &recv_items, NULL, 0)) { + register_global_error_format("sysctl(CTL_SYSCTL_NEXT): %s", strerror(errno)); + break; + } + recv_items /= sizeof(int); + cur_items = recv_items; + if (recv_items < (total_items - 1) || + memcmp(recv_oid, oid, (total_items - 1) * sizeof(int))) + break; + if (recv_items != total_items) + continue; + memcpy(oid, recv_oid, recv_items * sizeof(int)); + *items = total_items; + found = 1; + break; + } + + return found; +} + +static int hid_init_sysctl_iter(struct hid_sysctl_iter *iter, const char *mib) { + iter->oid_items = CTL_MAXNAME; + if (sysctlnametomib(mib, iter->cur_oid, &iter->oid_items)) { + register_global_error_format("sysctlnametomib: %s", strerror(errno)); + return 0; + } + + iter->matched_len = iter->oid_items + 1; + iter->idx = -1; + return 1; +} + +static int hid_get_next_from_sysctl_iter(struct hid_sysctl_iter *iter) { + int found = hid_get_next_from_oid(iter->cur_oid, &iter->oid_items, iter->matched_len); + if (!found) + return 0; + iter->idx = hid_get_dev_idx_from_mib(iter->cur_oid, iter->oid_items); + return iter->idx == -1 ? 0 : 1; +} + +/* + * 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(const uint8_t *report_descriptor, uint32_t size, unsigned int pos, 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; +} + +/* + * Get bytes from a HID Report Descriptor. + * Only call with a num_bytes of 0, 1, 2, or 4. + */ +static uint32_t get_hid_report_bytes(const uint8_t *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; +} + +/* + * Iterates until the end of a Collection. + * Assumes that *pos is exactly at the beginning of a Collection. + * Skips all nested Collection, i.e. iterates until the end of current level Collection. + * + * The return value is non-0 when an end of current Collection is found, + * 0 when error is occured (broken Descriptor, end of a Collection is found before its begin, + * or no Collection is found at all). + */ +static int hid_iterate_over_collection(const uint8_t *report_descriptor, uint32_t size, unsigned int *pos, int *data_len, int *key_size) +{ + int collection_level = 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, size, *pos, data_len, key_size)) + return 0; /* malformed report */ + + switch (key_cmd) { + case 0xa0: /* Collection 6.2.2.4 (Main) */ + collection_level++; + break; + case 0xc0: /* End Collection 6.2.2.4 (Main) */ + collection_level--; + break; + } + + if (collection_level < 0) { + /* Broken descriptor or someone is using this function wrong, + * i.e. should be called exactly at the collection start */ + return 0; + } + + if (collection_level == 0) { + /* Found it! + * Also possible when called not at the collection start, but should not happen if used correctly */ + return 1; + } + + *pos += *data_len + *key_size; + } + + return 0; /* Did not find the end of a Collection */ +} + +struct hid_usage_iterator { + unsigned int pos; + int usage_page_found; + unsigned short usage_page; +}; + +/* + * 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(const uint8_t *report_descriptor, uint32_t size, struct hid_usage_iterator *ctx, unsigned short *usage_page, unsigned short *usage) +{ + int data_len, key_size; + int initial = ctx->pos == 0; /* Used to handle case where no top-level application collection is defined */ + + int usage_found = 0; + + while (ctx->pos < size) { + int key = report_descriptor[ctx->pos]; + int key_cmd = key & 0xfc; + + /* Determine data_len and key_size */ + if (!get_hid_item_size(report_descriptor, size, ctx->pos, &data_len, &key_size)) + return -1; /* malformed report */ + + switch (key_cmd) { + case 0x4: /* Usage Page 6.2.2.7 (Global) */ + ctx->usage_page = get_hid_report_bytes(report_descriptor, size, data_len, ctx->pos); + ctx->usage_page_found = 1; + break; + + case 0x8: /* Usage 6.2.2.8 (Local) */ + if (data_len == 4) { /* Usages 5.5 / Usage Page 6.2.2.7 */ + ctx->usage_page = get_hid_report_bytes(report_descriptor, size, 2, ctx->pos + 2); + ctx->usage_page_found = 1; + *usage = get_hid_report_bytes(report_descriptor, size, 2, ctx->pos); + usage_found = 1; + } + else { + *usage = get_hid_report_bytes(report_descriptor, size, data_len, ctx->pos); + usage_found = 1; + } + break; + + case 0xa0: /* Collection 6.2.2.4 (Main) */ + if (!hid_iterate_over_collection(report_descriptor, size, &ctx->pos, &data_len, &key_size)) { + return -1; + } + + /* A pair is valid - to be reported when Collection is found */ + if (usage_found && ctx->usage_page_found) { + *usage_page = ctx->usage_page; + return 0; + } + + break; + } + + /* Skip over this key and its associated data */ + ctx->pos += data_len + key_size; + } + + /* 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 && ctx->usage_page_found) { + *usage_page = ctx->usage_page; + return 0; /* success */ + } + + return 1; /* finished processing */ +} + +static libusb_device_handle *hid_find_device_handle_by_bus_and_port(int bus, int addr) { + libusb_device **list; + libusb_device_handle *handler = NULL; + int error, num_devs; + + error = libusb_get_device_list(global_usb_context, &list); + if (error < 0) { + register_global_error_format("hid_find_device_handle_by_bus_and_port: %s", libusb_strerror(errno)); + return NULL; + } + + num_devs = error; + + for (int i = 0; i < num_devs; ++i) { + libusb_device *dev = list[i]; + if (bus != libusb_get_bus_number(dev) || addr != libusb_get_device_address(dev)) + continue; + libusb_open(dev, &handler); + break; + } + + if (handler == NULL) { + register_global_error_format("Unable to find the device with bus:%2d and addr:%2d", bus, addr); + } + + libusb_free_device_list(list, 0); + return handler; +} + +static int hid_get_udev_location_from_hidraw_idx(int idx, int *bus, int *addr, int *inf) { + char buff[256]; + int oid[CTL_MAXNAME]; + size_t oid_size = CTL_MAXNAME; + size_t len; + int udev_idx; + + snprintf(buff, sizeof(buff), "dev.hidbus.%d.%%parent", idx); + len = sizeof(buff); + if (sysctlbyname(buff, buff, &len, NULL, 0)) + return 0; + buff[len] = '\0'; + + if (sscanf(buff, "usbhid%2d", &udev_idx) != 1) + return 0; + snprintf(buff, sizeof(buff), "dev.usbhid.%d.%%location", udev_idx); + len = sizeof(buff); + if (sysctlbyname(buff, buff, &len, NULL, 0)) + return 0; + buff[len] = '\0'; + return sscanf(buff, "bus=%d hubaddr=%2d port=%2d devaddr=%2d interface=%d", bus, addr, addr, addr, inf) == 4; +} + +static void hid_device_handle_bus_dependent(const struct hidraw_device_info *rawinfo, struct hid_device_info *info, int idx) { + libusb_device_handle *handler; + libusb_device *device; + libusb_device_descriptor desc; + uint8_t buffer[256] = {0}; + wchar_t namebuffer[256] = {0}; + int bus, addr; + int len = 0; + + switch (rawinfo->hdi_bustype) { + case BUS_USB: + hid_get_udev_location_from_hidraw_idx(idx,&bus, + &addr,&info->interface_number); + handler = hid_find_device_handle_by_bus_and_port(bus, addr); + if (!handler) + break; + device = libusb_get_device(handler); + if (libusb_get_device_descriptor(device, &desc)) { + libusb_close(handler); + } + len = libusb_get_string_descriptor_ascii(handler, desc.iManufacturer, buffer, sizeof(buffer)); + if (len == 0) { + libusb_close(handler); + break; + } + mbstowcs(namebuffer, (char *)buffer, sizeof(namebuffer)); + info->manufacturer_string = wcsdup(namebuffer); + info->bus_type = HID_API_BUS_USB; + libusb_close(handler); + break; + case BUS_BLUETOOTH: + info->bus_type = HID_API_BUS_BLUETOOTH; + break; + case BUS_I2C: + info->bus_type = HID_API_BUS_I2C; + break; + case BUS_SPI: + info->bus_type = HID_API_BUS_SPI; + break; + default: + info->bus_type = HID_API_BUS_UNKNOWN; + break; + } +} + +static struct hid_device_info *hid_create_device_info_by_hidraw_idx(int fd_prep, int idx) +{ + int error; + int desc_size; + int fd; + struct hidraw_device_info devinfo; + struct hidraw_report_descriptor desc; + struct hid_device_info *result = NULL, *template = NULL; + wchar_t namebuffer[256]; + char devpath[256]; + + snprintf(devpath, sizeof(devpath), "/dev/hidraw%d", idx); + + fd = fd_prep == -1 ? open(devpath, O_RDWR | O_CLOEXEC) : fd_prep; + if (fd == -1) { + register_global_error_format("open: %s", strerror(errno)); + return NULL; + } + + error = ioctl(fd, HIDRAW_GET_DEVICEINFO, &devinfo); + if (error == -1) { + register_global_error_format("ioctl(HIDRAW_GET_DEVICE_INFO): %s", strerror(errno)); + goto end; + } + + template = malloc(sizeof(struct hid_device_info)); + template->path = strdup(devpath); + template->vendor_id = devinfo.hdi_vendor; + template->product_id = devinfo.hdi_product; + mbstowcs(namebuffer, devinfo.hdi_uniq, sizeof(namebuffer)); + template->serial_number = strlen(devinfo.hdi_uniq) ? wcsdup(namebuffer) : NULL; + template->release_number = devinfo.hdi_version; + mbstowcs(namebuffer, devinfo.hdi_name, sizeof(namebuffer)); + template->product_string = strlen(devinfo.hdi_name) ? wcsdup(namebuffer) : NULL; + template->interface_number = -1; + template->manufacturer_string = NULL; + template->next = NULL; + hid_device_handle_bus_dependent(&devinfo, template, idx); + + error = ioctl(fd, HIDIOCGRDESCSIZE, &desc_size); + if (error == -1) { + register_global_error_format("ioctl(HIDIOCGRDESCSIZE): %s", strerror(errno)); + goto end; + } + desc.size = desc_size; + error = ioctl(fd, HIDIOCGRDESC, &desc); + if (error == -1) { + register_global_error_format("ioctl(HIDIOCGRDESC): %s", strerror(errno)); + goto end; + } + + unsigned short page = 0, usage = 0; + struct hid_usage_iterator iter; + memset(&iter, 0, sizeof(iter)); + struct hid_device_info *cur = template; + + while (!get_next_hid_usage(desc.value, desc.size, &iter, &page, &usage)) { + cur = cur->next = (struct hid_device_info *) calloc(1, sizeof(struct hid_device_info)); + if (!cur) + break; + cur->path = strdup(template->path); + cur->vendor_id = template->vendor_id; + cur->product_id = template->product_id; + cur->serial_number = template->serial_number ? wcsdup(template->serial_number) : NULL; + cur->release_number = template->release_number; + cur->product_string = template->product_string ? wcsdup(template->product_string) : NULL; + cur->interface_number = template->interface_number; + cur->manufacturer_string = template->manufacturer_string ? wcsdup(template->manufacturer_string) : NULL; + cur->bus_type = template->bus_type; + cur->usage = usage; + cur->usage_page = page; + } + /* + * make the circular linked list. so that caller can have + * the address of first element (cur->next) and last element(cur) + */ + cur->next = template->next; + template->next = NULL; +end: + if (template) + hid_free_enumeration(template); + if (fd_prep == -1) + close(fd); + return cur; +} + + +HID_API_EXPORT const struct hid_api_version* HID_API_CALL hid_version(void) +{ + return &hid_api_ver; +} + +HID_API_EXPORT const char* HID_API_CALL hid_version_str(void) +{ + return HID_API_VERSION_STR; +} + +int HID_API_EXPORT hid_init(void) +{ + const char *locale; + int error; + + locale = setlocale(LC_CTYPE, NULL); + if (!locale) + setlocale(LC_CTYPE, ""); + + if ((error = libusb_init(&global_usb_context))) + return error; + + return 0; +} + +int HID_API_EXPORT hid_exit(void) +{ + register_global_error(NULL); + libusb_exit(global_usb_context); + return 0; +} + +struct hid_device_info HID_API_EXPORT *hid_enumerate(unsigned short vendor_id, unsigned short product_id) +{ + struct hid_device_info *root = calloc(1, sizeof(struct hid_device_info)); + struct hid_device_info *head = root; + int oid[CTL_MAXNAME] = {0}; + size_t buf_len, oid_items = CTL_MAXNAME; + struct hid_sysctl_iter iter; + + if (!hid_init_sysctl_iter(&iter, "dev.hidraw")) + return NULL; + + /* + * we want to exactly match dev.hidraw.%d + */ + while(hid_get_next_from_sysctl_iter(&iter)) { + struct hid_device_info *tmp = hid_create_device_info_by_hidraw_idx(-1, iter.idx); + if (!tmp) + continue; + if ((vendor_id != 0 || product_id != 0) && + (vendor_id != tmp->vendor_id && product_id != tmp->product_id)) { + continue; + } + root->next = tmp->next; + root = tmp; + tmp->next = NULL; + } + root = head->next; + free(head); + return root; +} + +void HID_API_EXPORT hid_free_enumeration(struct hid_device_info *devs) +{ + struct hid_device_info *next; + + while(devs) { + next = devs->next; + free(devs->manufacturer_string); + free(devs->product_string); + free(devs->path); + free(devs->serial_number); + free(devs); + devs = next; + } +} + +hid_device * hid_open(unsigned short vendor_id, unsigned short product_id, const wchar_t *serial_number) +{ + struct hid_device_info *devs, *cur_dev; + const char *path_to_open = NULL; + hid_device *handle = NULL; + + /* register_global_error: global error is reset by hid_enumerate/hid_init */ + devs = hid_enumerate(vendor_id, product_id); + if (devs == NULL) { + /* register_global_error: global error is already set by hid_enumerate */ + return NULL; + } + + cur_dev = devs; + while (cur_dev) { + if (cur_dev->vendor_id == vendor_id && + cur_dev->product_id == product_id) { + if (serial_number) { + if (wcscmp(serial_number, cur_dev->serial_number) == 0) { + path_to_open = cur_dev->path; + break; + } + } + else { + path_to_open = cur_dev->path; + break; + } + } + cur_dev = cur_dev->next; + } + + if (path_to_open) { + /* Open the device */ + handle = hid_open_path(path_to_open); + } else { + register_global_error("Device with requested VID/PID/(SerialNumber) not found"); + } + + hid_free_enumeration(devs); + + return handle; +} + +hid_device * HID_API_EXPORT hid_open_path(const char *path) +{ + hid_device *dev = NULL; + + hid_init(); + /* register_global_error: global error is reset by hid_init */ + + dev = new_hid_device(); + if (!dev) { + errno = ENOMEM; + register_global_error("Couldn't allocate memory"); + return NULL; + } + + dev->device_path = strdup(path); + dev->device_handle = open(path, O_RDWR | O_CLOEXEC); + + if (dev->device_handle >= 0) { + int res, desc_size = 0; + + /* Make sure this is a HIDRAW device - responds to HIDIOCGRDESCSIZE */ + res = ioctl(dev->device_handle, HIDIOCGRDESCSIZE, &desc_size); + if (res < 0) { + register_global_error_format("ioctl(GRDESCSIZE) error for '%s', not a HIDRAW device?: %s", path, strerror(errno)); + hid_close(dev); + return NULL; + } + + return dev; + } + else { + /* Unable to open a device. */ + free(dev); + register_global_error_format("Failed to open a device with path '%s': %s", path, strerror(errno)); + return NULL; + } +} + + +int HID_API_EXPORT hid_write(hid_device *dev, const unsigned char *data, size_t length) +{ + int bytes_written; + + if (!data || (length == 0)) { + errno = EINVAL; + register_device_error(dev, "Zero buffer/length"); + return -1; + } + + bytes_written = write(dev->device_handle, data, length); + + register_device_error(dev, (bytes_written == -1)? strerror(errno): NULL); + + return bytes_written; +} + + +int HID_API_EXPORT hid_read_timeout(hid_device *dev, unsigned char *data, size_t length, int milliseconds) +{ + if (!data || (length == 0)) { + errno = EINVAL; + register_error_str(&dev->read_error_str, "Zero buffer/length"); + return -1; + } + + /* Set device error to none */ + register_error_str(&dev->read_error_str, NULL); + + int bytes_read; + + if (milliseconds >= 0) { + /* Milliseconds is either 0 (non-blocking) or > 0 (contains + a valid timeout). In both cases we want to call poll() + and wait for data to arrive. Don't rely on non-blocking + operation (O_NONBLOCK) since some kernels don't seem to + properly report device disconnection through read() when + in non-blocking mode. */ + int ret; + struct pollfd fds; + + fds.fd = dev->device_handle; + fds.events = POLLIN; + fds.revents = 0; + ret = poll(&fds, 1, milliseconds); + if (ret == 0) { + /* Timeout */ + return ret; + } + if (ret == -1) { + /* Error */ + register_error_str(&dev->read_error_str, strerror(errno)); + return ret; + } + else { + /* Check for errors on the file descriptor. This will + indicate a device disconnection. */ + if (fds.revents & (POLLERR | POLLHUP | POLLNVAL)) { + // We cannot use strerror() here as no -1 was returned from poll(). + errno = EIO; + register_error_str(&dev->read_error_str, "hid_read_timeout: unexpected poll error (device disconnected)"); + return -1; + } + } + } + + bytes_read = read(dev->device_handle, data, length); + if (bytes_read < 0) { + if (errno == EAGAIN || errno == EINPROGRESS) + bytes_read = 0; + else + register_error_str(&dev->read_error_str, strerror(errno)); + } + + return bytes_read; +} + +int HID_API_EXPORT hid_read(hid_device *dev, unsigned char *data, size_t length) +{ + return hid_read_timeout(dev, data, length, (dev->blocking)? -1: 0); +} + +HID_API_EXPORT const wchar_t * HID_API_CALL hid_read_error(hid_device *dev) +{ + if (dev->read_error_str == NULL) + return L"Success"; + return dev->read_error_str; +} + +int HID_API_EXPORT hid_set_nonblocking(hid_device *dev, int nonblock) +{ + /* Do all non-blocking in userspace using poll(), since it looks + like there's a bug in the kernel in some versions where + read() will not return -1 on disconnection of the USB device */ + + dev->blocking = !nonblock; + return 0; /* Success */ +} + + +int HID_API_EXPORT hid_send_feature_report(hid_device *dev, const unsigned char *data, size_t length) +{ + int res; +#ifndef HIDIOCGFEATURE + struct hidraw_gen_descriptor desc; +#endif + + + if (!data || (length == 0)) { + errno = EINVAL; + register_device_error(dev, "Zero buffer/length"); + return -1; + } + +#ifdef HIDIOCSFEATURE + res = ioctl(dev->device_handle, HIDIOCSFEATURE(length), data); + length = res; +#else + desc.hgd_maxlen = length; + desc.hgd_data = (void *)data; + desc.hgd_report_type = HID_FEATURE_REPORT; + + register_device_error(dev, NULL); + + res = ioctl(dev->device_handle, HIDRAW_SET_REPORT, &desc); +#endif + + if (res < 0) { + register_device_error_format(dev, "ioctl (SFEATURE): %s", strerror(errno)); + return -1; + } + + return length; +} + +int HID_API_EXPORT hid_get_feature_report(hid_device *dev, unsigned char *data, size_t length) +{ + int res; +#ifndef HIDIOCGFEATURE + struct hidraw_gen_descriptor desc; +#endif + + if (!data || (length == 0)) { + errno = EINVAL; + register_device_error(dev, "Zero buffer/length"); + return -1; + } + +#ifdef HIDIOCGFEATURE + res = ioctl(dev->device_handle, HIDIOCGFEATURE(length), data); + length = res; +#else + desc.hgd_maxlen = length; + desc.hgd_data = (void *)data; + desc.hgd_report_type = HID_FEATURE_REPORT; + + register_device_error(dev, NULL); + + res = ioctl(dev->device_handle, HIDRAW_GET_REPORT, &desc); + length = desc.hgd_actlen; +#endif + + if (res < 0) { + register_device_error_format(dev, "ioctl (GFEATURE): %s", strerror(errno)); + return -1; + } + + return length; +} + +int HID_API_EXPORT HID_API_CALL hid_send_output_report(hid_device *dev, const unsigned char *data, size_t length) +{ + int res; +#ifndef HIDIOCSOUTPUT + struct hidraw_gen_descriptor desc; +#endif + + if (!data || (length == 0)) { + errno = EINVAL; + register_device_error(dev, "Zero buffer/length"); + return -1; + } + +#ifdef HIDIOCSOUTPUT + res = ioctl(dev->device_handle, HIDIOCSOUTPUT(length), data); + length = res; +#else + desc.hgd_maxlen = length; + desc.hgd_data = (void *)data; + desc.hgd_report_type = HID_OUTPUT_REPORT; + res = ioctl(dev->device_handle, HIDRAW_SET_REPORT, &desc); +#endif + + if (res < 0) { + register_device_error_format(dev, "ioctl (SOUTPUT): %s", strerror(errno)); + return -1; + } + + return length; +} + +int HID_API_EXPORT HID_API_CALL hid_get_input_report(hid_device *dev, unsigned char *data, size_t length) +{ + int res; +#ifndef HIDIOCGINPUT + struct hidraw_gen_descriptor desc; +#endif + + if (!data || (length == 0)) { + errno = EINVAL; + register_device_error(dev, "Zero buffer/length"); + return -1; + } + +#ifdef HIDIOCGINPUT + res = ioctl(dev->device_handle, HIDIOCGINPUT(length), data); + length = res; +#else + desc.hgd_maxlen = length; + desc.hgd_data = (void *)data; + desc.hgd_report_type = HID_INPUT_REPORT; + + register_device_error(dev, NULL); + + res = ioctl(dev->device_handle, HIDRAW_GET_REPORT, &desc); + length = desc.hgd_actlen; +#endif + + if (res < 0) { + register_device_error_format(dev, "ioctl (GINPUT): %s", strerror(errno)); + return -1; + } + + return length; +} + +void HID_API_EXPORT hid_close(hid_device *dev) +{ + if (!dev) + return; + + close(dev->device_handle); + + free((void *)dev->device_path); + free(dev->error_str); + free(dev->read_error_str); + + 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) +{ + if (!string || !maxlen) { + errno = EINVAL; + 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) +{ + if (!string || !maxlen) { + errno = EINVAL; + 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) +{ + if (!string || !maxlen) { + errno = EINVAL; + 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) { + int idx; + char path_buffer[1024]; + + if (dev->device_info) { + register_device_error(dev, NULL); + } + else { + // Lazy initialize device_info + realpath(dev->device_path, path_buffer); + sscanf(path_buffer, "/dev/hidraw%d", &idx); + struct hid_device_info *devinfo = + hid_create_device_info_by_hidraw_idx(dev->device_handle, idx); + if (devinfo != NULL) { + dev->device_info = devinfo->next; + devinfo->next = NULL; + } else { + register_device_error(dev, "Failed to create device: maybe the permission is not correct?"); + } + } + + // hid_create_device_info_by_hidraw_idx 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) +{ + (void)string_index; + (void)string; + (void)maxlen; + + errno = ENOSYS; + register_device_error(dev, "hid_get_indexed_string: not supported by hidraw"); + + return -1; +} + +int HID_API_EXPORT_CALL hid_get_report_descriptor(hid_device *dev, unsigned char *buf, size_t buf_size) +{ + int error; + int act_len = 0; + struct hidraw_report_descriptor desc; + + if (!buf || !buf_size) { + errno = EINVAL; + register_device_error(dev, "Zero buffer/length"); + return -1; + } + + register_device_error(dev, NULL); + + error = ioctl(dev->device_handle ,HIDIOCGRDESCSIZE, &act_len); + if (error < 0) { + register_device_error_format(dev, "hid_get_report_descriptor: unable to do ioctl on %s: %s", dev->device_path, strerror(errno)); + /* error already registered */ + return error; + } + + desc.size = act_len; + error = ioctl(dev->device_handle, HIDIOCGRDESC, &desc); + if (error < 0) { + register_device_error_format(dev, "hid_get_report_descriptor: unable to do ioctl on %s: %s", dev->device_path, strerror(errno)); + /* error already registered */ + return error; + } + + if (act_len < buf_size) + buf_size = act_len; + + memcpy(buf, desc.value, buf_size); + return buf_size; +} + + +/* Passing in NULL means asking for the last global error message. */ +HID_API_EXPORT const wchar_t * HID_API_CALL hid_error(hid_device *dev) +{ + if (dev) { + if (dev->error_str == NULL) + return L"Success"; + return dev->error_str; + } + + if (global_error_str == NULL) + return L"Success"; + return global_error_str; +} diff --git a/hidtest/CMakeLists.txt b/hidtest/CMakeLists.txt index 19c50e1fb..a509fa4a1 100644 --- a/hidtest/CMakeLists.txt +++ b/hidtest/CMakeLists.txt @@ -17,7 +17,7 @@ else() endif() set(HIDAPI_HIDTEST_TARGETS) -if(NOT WIN32 AND NOT APPLE AND CMAKE_SYSTEM_NAME MATCHES "Linux") +if(NOT WIN32 AND NOT APPLE AND (CMAKE_SYSTEM_NAME MATCHES "Linux" OR CMAKE_SYSTEM_NAME MATCHES "FreeBSD")) if(TARGET hidapi::hidraw) add_executable(hidtest_hidraw test.c) target_link_libraries(hidtest_hidraw hidapi::hidraw) diff --git a/hidtest/Makefile.am b/hidtest/Makefile.am index b6013077a..9a20e6f4b 100644 --- a/hidtest/Makefile.am +++ b/hidtest/Makefile.am @@ -9,14 +9,26 @@ hidtest_hidraw_LDADD = $(top_builddir)/linux/libhidapi-hidraw.la hidtest_libusb_SOURCES = test.c hidtest_libusb_LDADD = $(top_builddir)/libusb/libhidapi-libusb.la + else +if OS_FREEBSD +noinst_PROGRAMS = hidtest-libusb hidtest-hidraw + +hidtest_hidraw_SOURCES = test.c +hidtest_hidraw_LDADD = $(top_builddir)/freebsd/libhidapi-hidraw.la + +hidtest_libusb_SOURCES = test.c +hidtest_libusb_LDADD = $(top_builddir)/libusb/libhidapi-libusb.la + +else # Other OS's noinst_PROGRAMS = hidtest hidtest_SOURCES = test.c hidtest_LDADD = $(top_builddir)/$(backend)/libhidapi.la +endif endif if OS_DARWIN diff --git a/libusb/Makefile.am b/libusb/Makefile.am index 6964ebbbc..87580302e 100644 --- a/libusb/Makefile.am +++ b/libusb/Makefile.am @@ -8,10 +8,10 @@ libhidapi_libusb_la_LIBADD = $(LIBS_LIBUSB) endif if OS_FREEBSD -lib_LTLIBRARIES = libhidapi.la -libhidapi_la_SOURCES = hid.c -libhidapi_la_LDFLAGS = $(LTLDFLAGS) -libhidapi_la_LIBADD = $(LIBS_LIBUSB) +lib_LTLIBRARIES = libhidapi-libusb.la +libhidapi_libusb_la_SOURCES = hid.c +libhidapi_libusb_la_LDFLAGS = $(LTLDFLAGS) +libhidapi_libusb_la_LIBADD = $(LIBS_LIBUSB) endif if OS_KFREEBSD diff --git a/libusb/Makefile.freebsd b/libusb/Makefile.freebsd index c52b355d1..007a425ad 100644 --- a/libusb/Makefile.freebsd +++ b/libusb/Makefile.freebsd @@ -6,9 +6,9 @@ # 2010-06-01 ########################################### -all: hidtest libs +all: hidtest-libusb libs -libs: libhidapi.so +libs: libhidapi-libusb.so CC ?= cc CFLAGS ?= -Wall -g -fPIC @@ -21,11 +21,11 @@ LIBS = -lusb -liconv -pthread # Console Test Program -hidtest: $(OBJS) +hidtest-libusb: $(OBJS) $(CC) $(CFLAGS) $(LDFLAGS) $^ -o $@ $(LIBS) # Shared Libs -libhidapi.so: $(COBJS) +libhidapi-libusb.so: $(COBJS) $(CC) $(LDFLAGS) -shared -Wl,-soname,$@.0 $^ -o $@ $(LIBS) # Objects diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index e663c3fd6..e5c71044e 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -141,6 +141,19 @@ else() set(HIDAPI_NEED_EXPORT_LIBUDEV TRUE) endif() endif() + elseif(CMAKE_SYSTEM_NAME MATCHES "FreeBSD") + if(NOT DEFINED HIDAPI_WITH_HIDRAW) + set(HIDAPI_WITH_HIDRAW ON) + endif() + if(HIDAPI_WITH_HIDRAW) + add_subdirectory("${PROJECT_ROOT}/freebsd" freebsd) + list(APPEND EXPORT_COMPONENTS hidraw) + set(EXPORT_ALIAS hidraw) + if(NOT BUILD_SHARED_LIBS) + set(HIDAPI_NEED_EXPORT_THREADS TRUE) + set(HIDAPI_NEED_EXPORT_LIBUSB TRUE) + endif() + endif() elseif(CMAKE_SYSTEM_NAME MATCHES "NetBSD") if(NOT DEFINED HIDAPI_WITH_NETBSD) set(HIDAPI_WITH_NETBSD ON) diff --git a/testgui/Makefile.am b/testgui/Makefile.am index 1c02f3f2c..da2a8f82e 100644 --- a/testgui/Makefile.am +++ b/testgui/Makefile.am @@ -11,12 +11,23 @@ hidapi_hidraw_testgui_LDADD = $(top_builddir)/linux/libhidapi-hidraw.la $(LIBS_T hidapi_libusb_testgui_SOURCES = test.cpp hidapi_libusb_testgui_LDADD = $(top_builddir)/libusb/libhidapi-libusb.la $(LIBS_TESTGUI) else +if OS_FREEBSD +bin_PROGRAMS = hidapi-hidraw-testgui hidapi-libusb-testgui + +hidapi_hidraw_testgui_SOURCES = test.cpp +hidapi_hidraw_testgui_LDADD = $(top_builddir)/freebsd/libhidapi-hidraw.la $(LIBS_TESTGUI) + +hidapi_libusb_testgui_SOURCES = test.cpp +hidapi_libusb_testgui_LDADD = $(top_builddir)/libusb/libhidapi-libusb.la $(LIBS_TESTGUI) +else + ## Other OS's bin_PROGRAMS = hidapi-testgui hidapi_testgui_SOURCES = test.cpp hidapi_testgui_LDADD = $(top_builddir)/$(backend)/libhidapi.la $(LIBS_TESTGUI) endif +endif if OS_DARWIN hidapi_testgui_SOURCES = test.cpp mac_support_cocoa.m mac_support.h