diff --git a/.builds/freebsd.yml b/.builds/freebsd.yml index 1679b03c3..d2144c947 100644 --- a/.builds/freebsd.yml +++ b/.builds/freebsd.yml @@ -7,6 +7,7 @@ packages: - libtool - pkgconf - cmake +- meson - ninja sources: - https://github.com/libusb/hidapi @@ -15,20 +16,67 @@ tasks: cd hidapi echo Configure Autotools build ./bootstrap - ./configure + ./configure \ + --prefix="$PWD/install_autotools" echo Configure CMake build - mkdir -p build install_cmake - cmake -GNinja -B build -S . -DCMAKE_INSTALL_PREFIX=install_cmake + cmake -GNinja -B build/shared -S . \ + -DCMAKE_INSTALL_PREFIX="$PWD/install_cmake/shared" \ + -DHIDAPI_BUILD_HIDTEST=ON + cmake -GNinja -B build/static -S . \ + -DCMAKE_INSTALL_PREFIX="$PWD/install_cmake/static" \ + -DBUILD_SHARED_LIBS=FALSE \ + -DHIDAPI_BUILD_HIDTEST=ON + cmake -GNinja -B build/cxx -S . \ + -DCMAKE_BUILD_TYPE=Release \ + -DHIDAPI_BUILD_AS_CXX=ON \ + "-DCMAKE_CXX_FLAGS=-Wall -Wextra -Werror" + meson setup build/meson - build-autotools: | cd hidapi make - make DESTDIR=$PWD/root install + make install + export PKG_CONFIG_PATH="$PWD/install_autotools/libdata/pkgconfig" + export LD_LIBRARY_PATH="$PWD/install_autotools/lib" + for package in hidapi hidapi-hidraw; do + cc -Wall -Wextra -Werror \ + $(pkg-config --cflags "$package") \ + tests/pkg-config/test.c \ + -o "build/$package-pkg-config-test" \ + $(pkg-config --static --libs "$package") + "./build/$package-pkg-config-test" + done + cc -Wall -Wextra -Werror \ + $(pkg-config --cflags hidapi-libusb) \ + tests/pkg-config/test.c \ + -o build/hidapi-libusb-pkg-config-test \ + $(pkg-config --libs hidapi-libusb) + ./build/hidapi-libusb-pkg-config-test make clean - build-cmake: | - cd hidapi/build - ninja - ninja install - ninja clean + cd hidapi + cmake --build build/shared --target install + cmake --build build/static --target install + cmake --build build/cxx + cmake -GNinja -B build/shared_test -S hidtest \ + -Dhidapi_ROOT="$PWD/install_cmake/shared" \ + -DCMAKE_INSTALL_PREFIX="$PWD/install_cmake/shared_test" + cmake --build build/shared_test --target install + cmake -GNinja -B build/static_test -S hidtest \ + -Dhidapi_ROOT="$PWD/install_cmake/static" \ + -DCMAKE_INSTALL_PREFIX="$PWD/install_cmake/static_test" + cmake --build build/static_test --target install + test -x install_cmake/shared_test/bin/hidtest_hidraw + test -x install_cmake/shared_test/bin/hidtest_libusb + test -x install_cmake/static_test/bin/hidtest_hidraw + test -x install_cmake/static_test/bin/hidtest_libusb + cmake --build build/shared --target clean + cmake --build build/static --target clean + cmake --build build/cxx --target clean +- build-meson: | + cd hidapi + meson compile -C build/meson - build-manual: | - cd hidapi/libusb + cd hidapi/freebsd + gmake -f Makefile-manual + cd ../libusb gmake -f Makefile-manual diff --git a/.cirrus.yml b/.cirrus.yml index b4cf20166..aabd992a0 100644 --- a/.cirrus.yml +++ b/.cirrus.yml @@ -8,21 +8,9 @@ alpine_task: - make - make install -freebsd11_task: +freebsd_task: freebsd_instance: - image: freebsd-11-2-release-amd64 - install_script: - - pkg install -y - autoconf automake libiconv libtool pkgconf - script: - - ./bootstrap - - ./configure || { cat config.log; exit 1; } - - make - - make install - -freebsd12_task: - freebsd_instance: - image: freebsd-12-1-release-amd64 + image_family: freebsd-14-3 install_script: - pkg install -y autoconf automake libiconv libtool pkgconf diff --git a/BUILD.cmake.md b/BUILD.cmake.md index cf500e915..fc7f4fa18 100644 --- a/BUILD.cmake.md +++ b/BUILD.cmake.md @@ -92,7 +92,7 @@ HIDAPI-specific CMake variables: currently this option is only available on Windows, since only Windows backend has tests;
- Linux-specific variables + Linux and FreeBSD-specific variables - `HIDAPI_WITH_HIDRAW` - when set to TRUE, build HIDRAW-based implementation of HIDAPI (`hidapi-hidraw`), otherwise don't build it; defaults to TRUE; - `HIDAPI_WITH_LIBUSB` - when set to TRUE, build LIBUSB-based implementation of HIDAPI (`hidapi-libusb`), otherwise don't build it; defaults to TRUE; @@ -149,10 +149,10 @@ Available CMake targets after successful `find_package(hidapi)`: - `hidapi::winapi` - same as `hidapi::hidapi` on Windows; available only on Windows; - `hidapi::darwin` - same as `hidapi::hidapi` on macOS; available only on macOS; - `hidapi::libusb` - available when libusb backend is used/available; -- `hidapi::hidraw` - available when hidraw backend is used/available on Linux; +- `hidapi::hidraw` - available when hidraw backend is used/available on Linux or FreeBSD; -**NOTE**: on Linux often both `hidapi::libusb` and `hidapi::hidraw` backends are available; in that case `hidapi::hidapi` is an alias for **`hidapi::hidraw`**. The motivation is that `hidraw` backend is a native Linux kernel implementation of HID protocol, and supports various HID devices (USB, Bluetooth, I2C, etc.). If `hidraw` backend isn't built at all (`hidapi::libusb` is the only target) - `hidapi::hidapi` is an alias for `hidapi::libusb`. -If you're developing a cross-platform application and you are sure you need to use `libusb` backend on Linux, the simple way to achieve this is: +**NOTE**: on Linux and FreeBSD often both `hidapi::libusb` and `hidapi::hidraw` backends are available; in that case `hidapi::hidapi` is an alias for **`hidapi::hidraw`**. The motivation is that `hidraw` is the native kernel HID implementation and supports various HID devices (USB, Bluetooth, I2C, etc.). If `hidraw` backend isn't built at all (`hidapi::libusb` is the only target) - `hidapi::hidapi` is an alias for `hidapi::libusb`. +If you're developing a cross-platform application and you are sure you need to use `libusb` backend on Linux or FreeBSD, the simple way to achieve this is: ```cmake if(TARGET hidapi::libusb) target_link_libraries(my_project PRIVATE hidapi::libusb) @@ -183,7 +183,7 @@ Lets call this "larger project" a "host project". All of the variables described in [standalone build](#standalone-package-build) section can be used to control HIDAPI build in case of a subdirectory, e.g.: ```cmake -set(HIDAPI_WITH_LIBUSB FALSE) # surely will be used only on Linux +set(HIDAPI_WITH_LIBUSB FALSE) # used only on Linux and FreeBSD set(BUILD_SHARED_LIBS FALSE) # HIDAPI as static library on all platforms add_subdirectory(hidapi) ``` diff --git a/CMakeLists.txt b/CMakeLists.txt index b9758155b..ba3c9bb78 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 0bde02838..52deefe87 100644 --- a/Makefile.am +++ b/Makefile.am @@ -18,7 +18,7 @@ pkgconfig_DATA += pc/hidapi-darwin.pc endif if OS_FREEBSD -pkgconfig_DATA += pc/hidapi-libusb.pc +pkgconfig_DATA += pc/hidapi-hidraw.pc pc/hidapi-libusb.pc endif if OS_KFREEBSD @@ -44,7 +44,7 @@ SUBDIRS += mac endif if OS_FREEBSD -SUBDIRS += libusb +SUBDIRS += freebsd libusb endif if OS_KFREEBSD @@ -95,6 +95,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 70ba95c2d..554d685e6 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]) @@ -243,33 +247,44 @@ m4_define([HIDAPI_CONFIGURE_BACKEND_PC], [ -e "s|@HIDAPI_BACKEND_NAME@|$2|g" \ -e "s|@HIDAPI_BACKEND_DESCRIPTION@|$3|g" \ -e "s|@HIDAPI_BACKEND_LIBRARY_NAME@|$4|g" \ + -e "s|@HIDAPI_BACKEND_REQUIRES_PRIVATE@|$5|g" \ "$1" > "$1.tmp" && mv "$1.tmp" "$1" - ], [$5]) + ], [$6]) ]) HIDAPI_LIB_NAME=hidapi HIDAPI_LIBUSB_LIB_NAME=hidapi -if test "x$os" = "xlinux"; then +HIDAPI_GENERIC_REQUIRES_PRIVATE= +HIDAPI_HIDRAW_REQUIRES_PRIVATE= +if test "x$os" = "xlinux" || test "x$os" = "xfreebsd"; then HIDAPI_LIB_NAME=hidapi-hidraw HIDAPI_LIBUSB_LIB_NAME=hidapi-libusb + if test "x$os" = "xfreebsd"; then + HIDAPI_GENERIC_REQUIRES_PRIVATE="Requires.private: libusb-1.0 >= 1.0.9" + HIDAPI_HIDRAW_REQUIRES_PRIVATE="$HIDAPI_GENERIC_REQUIRES_PRIVATE" + fi HIDAPI_CONFIGURE_BACKEND_PC( [pc/hidapi-hidraw.pc], [hidapi-hidraw], [C Library for USB/Bluetooth HID device access from Linux, Mac OS X, FreeBSD, and Windows. This is the hidraw implementation.], - [hidapi-hidraw]) + [hidapi-hidraw], + [$hidapi_hidraw_requires_private], + [hidapi_hidraw_requires_private="$HIDAPI_HIDRAW_REQUIRES_PRIVATE"]) elif test "x$os" = "xdarwin"; then HIDAPI_CONFIGURE_BACKEND_PC( [pc/hidapi-darwin.pc], [hidapi-darwin], [C Library for USB/Bluetooth HID device access from Linux, Mac OS X, FreeBSD, and Windows. This is the Darwin implementation.], - [hidapi]) + [hidapi], + []) elif test "x$os" = "xwindows"; then HIDAPI_CONFIGURE_BACKEND_PC( [pc/hidapi-winapi.pc], [hidapi-winapi], [C Library for USB/Bluetooth HID device access from Linux, Mac OS X, FreeBSD, and Windows. This is the WinAPI implementation.], - [hidapi]) + [hidapi], + []) fi if test "x$os" != "xdarwin" && test "x$os" != "xwindows"; then HIDAPI_CONFIGURE_BACKEND_PC( @@ -277,11 +292,13 @@ if test "x$os" != "xdarwin" && test "x$os" != "xwindows"; then [hidapi-libusb], [C Library for USB HID device access from Linux, Mac OS X, FreeBSD, and Windows. This is the libusb implementation.], [$hidapi_libusb_lib_name], + [], [hidapi_libusb_lib_name="$HIDAPI_LIBUSB_LIB_NAME"]) fi m4_undefine([HIDAPI_CONFIGURE_BACKEND_PC]) AC_SUBST([HIDAPI_LIB_NAME]) +AC_SUBST([HIDAPI_GENERIC_REQUIRES_PRIVATE]) AC_CONFIG_FILES([pc/hidapi.pc]) AC_SUBST(LTLDFLAGS) @@ -290,6 +307,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..cff97092d --- /dev/null +++ b/freebsd/CMakeLists.txt @@ -0,0 +1,46 @@ +cmake_minimum_required(VERSION 3.6.3...3.25 FATAL_ERROR) + +add_library(hidapi_hidraw + ${HIDAPI_PUBLIC_HEADERS} + hid.c +) +if(HIDAPI_BUILD_AS_CXX) + set_source_files_properties(hid.c PROPERTIES LANGUAGE CXX) +endif() +target_link_libraries(hidapi_hidraw PUBLIC hidapi_include) + +find_package(Threads REQUIRED) + +include(FindPkgConfig) +pkg_check_modules(libusb REQUIRED IMPORTED_TARGET libusb-1.0>=1.0.9) + +target_link_libraries(hidapi_hidraw PRIVATE PkgConfig::libusb 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-backend-specific.pc.in" + OUTPUT_NAME "hidapi-hidraw" + LIBRARY_NAME "hidapi-hidraw" + DESCRIPTION "C Library for USB/Bluetooth HID device access from Linux, Mac OS X, FreeBSD, and Windows. This is the hidraw implementation." + REQUIRES_PRIVATE "libusb-1.0 >= 1.0.9" +) 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..8f4a4d19b --- /dev/null +++ b/freebsd/hid.c @@ -0,0 +1,1356 @@ +#include +#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); +} + +/* Similar 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); +} + +static void register_device_error_from_global(hid_device *dev) +{ + free(dev->error_str); + dev->error_str = global_error_str ? + wcsdup(global_error_str) : + wcsdup(L"Failed to query HID device information"); +} + +#define STR_TO_WSTR_BUFF_SIZE 256 +static wchar_t*str_to_wstr(char *str) { + wchar_t namebuffer[STR_TO_WSTR_BUFF_SIZE]; + size_t converted; + + if (!strlen(str)) + return NULL; + + converted = mbstowcs(namebuffer, str, STR_TO_WSTR_BUFF_SIZE - 1); + if (converted == (size_t) -1) + return NULL; + namebuffer[converted] = L'\0'; + return wcsdup(namebuffer); +} + +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}; + + 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, size_t 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_addr(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_addr: %s", libusb_strerror(error)); + 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, 1); + return handler; +} + +static int hid_get_udev_location_from_hidraw_idx(int idx, int *bus, int *addr, int *inf) { + char buff[256]; + size_t len; + int hidbus_idx, udev_idx, hub_addr, port; + + snprintf(buff, sizeof(buff), "dev.hidraw.%d.%%parent", idx); + len = sizeof(buff) - 1; + if (sysctlbyname(buff, buff, &len, NULL, 0)) + return 0; + if (len >= sizeof(buff)) + len = sizeof(buff) - 1; + buff[len] = '\0'; + + if (sscanf(buff, "hidbus%d", &hidbus_idx) != 1) + return 0; + snprintf(buff, sizeof(buff), "dev.hidbus.%d.%%parent", hidbus_idx); + len = sizeof(buff) - 1; + if (sysctlbyname(buff, buff, &len, NULL, 0)) + return 0; + if (len >= sizeof(buff)) + len = sizeof(buff) - 1; + buff[len] = '\0'; + + if (sscanf(buff, "usbhid%d", &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; + if (len >= sizeof(buff)) + len = sizeof(buff) - 1; + buff[len] = '\0'; + return sscanf(buff, "bus=%d hubaddr=%d port=%d devaddr=%d interface=%d", + bus, &hub_addr, &port, addr, inf) == 5; +} + +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}; + int bus, addr; + int len = 0; + + switch (rawinfo->hdi_bustype) { + case BUS_USB: + info->bus_type = HID_API_BUS_USB; + if (!hid_get_udev_location_from_hidraw_idx(idx, &bus, + &addr, &info->interface_number)) + break; + handler = hid_find_device_handle_by_bus_and_addr(bus, addr); + if (!handler) + break; + device = libusb_get_device(handler); + if (libusb_get_device_descriptor(device, &desc)) { + libusb_close(handler); + break; + } + if (desc.iManufacturer) { + len = libusb_get_string_descriptor_ascii(handler, desc.iManufacturer, buffer, sizeof(buffer)); + if (len > 0) { + if ((size_t) len >= sizeof(buffer)) + len = sizeof(buffer) - 1; + buffer[len] = '\0'; + info->manufacturer_string = str_to_wstr((char *)buffer); + } + } + 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; + case BUS_VIRTUAL: + info->bus_type = HID_API_BUS_VIRTUAL; + 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; + struct hid_device_info *info_template = NULL; + struct hid_device_info *head = NULL; + struct hid_device_info *tail = NULL; + unsigned short page = 0, usage = 0; + struct hid_usage_iterator iter; + + char devpath[256]; + + memset(&desc, 0, sizeof(desc)); + snprintf(devpath, sizeof(devpath), "/dev/hidraw%d", idx); + + fd = fd_prep == -1 ? open(devpath, O_RDONLY | 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_DEVICEINFO): %s", strerror(errno)); + goto end; + } + + info_template = (struct hid_device_info *) calloc(sizeof(struct hid_device_info), 1); + if (!info_template) { + register_global_error("Couldn't allocate memory"); + goto end; + } + info_template->path = strdup(devpath); + if (!info_template->path) { + register_global_error("Couldn't allocate memory"); + goto end; + } + info_template->vendor_id = devinfo.hdi_vendor; + info_template->product_id = devinfo.hdi_product; + info_template->serial_number = str_to_wstr(devinfo.hdi_uniq); + info_template->release_number = devinfo.hdi_version; + info_template->product_string = str_to_wstr(devinfo.hdi_name); + info_template->interface_number = -1; + info_template->manufacturer_string = NULL; + info_template->next = NULL; + hid_device_handle_bus_dependent(&devinfo, info_template, idx); + + error = ioctl(fd, HIDIOCGRDESCSIZE, &desc_size); + if (error == -1) { + register_global_error_format("ioctl(HIDIOCGRDESCSIZE): %s", strerror(errno)); + goto end; + } + if (desc_size <= 0 || (size_t) desc_size >= sizeof(desc.value)) { + errno = EOVERFLOW; + register_global_error("HIDIOCGRDESCSIZE returned an invalid report descriptor size"); + 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; + } + + memset(&iter, 0, sizeof(iter)); + + while (!get_next_hid_usage(desc.value, desc.size, &iter, &page, &usage)) { + struct hid_device_info *cur = (struct hid_device_info *) calloc(1, sizeof(struct hid_device_info)); + if (!cur) { + register_global_error("Couldn't allocate memory"); + goto end; + } + cur->path = strdup(info_template->path); + cur->vendor_id = info_template->vendor_id; + cur->product_id = info_template->product_id; + cur->serial_number = info_template->serial_number ? wcsdup(info_template->serial_number) : NULL; + cur->release_number = info_template->release_number; + cur->product_string = info_template->product_string ? wcsdup(info_template->product_string) : NULL; + cur->interface_number = info_template->interface_number; + cur->manufacturer_string = info_template->manufacturer_string ? wcsdup(info_template->manufacturer_string) : NULL; + cur->bus_type = info_template->bus_type; + cur->usage = usage; + cur->usage_page = page; + + if (!cur->path || + (info_template->serial_number && !cur->serial_number) || + (info_template->product_string && !cur->product_string) || + (info_template->manufacturer_string && !cur->manufacturer_string)) { + hid_free_enumeration(cur); + register_global_error("Couldn't allocate memory"); + goto end; + } + + if (tail) + tail->next = cur; + else + head = cur; + tail = cur; + } + + if (!tail) { + /* + * Keep devices with descriptors that have no top-level usage + * collection visible, matching the other hidraw backend. + */ + head = tail = info_template; + info_template = NULL; + } + + if (tail) { + /* + * Make the circular linked list, so the caller has the first + * element at result->next and can append using result as the tail. + */ + tail->next = head; + result = tail; + head = NULL; + } +end: + hid_free_enumeration(info_template); + hid_free_enumeration(head); + if (fd_prep == -1) + close(fd); + return result; +} + + +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; + + register_global_error(NULL); + + locale = setlocale(LC_CTYPE, NULL); + if (!locale) + setlocale(LC_CTYPE, ""); + + if (global_usb_context == NULL && (error = libusb_init(&global_usb_context))) { + global_usb_context = NULL; + register_global_error_format("libusb_init: %s", libusb_strerror(error)); + return -1; + } + + return 0; +} + +int HID_API_EXPORT hid_exit(void) +{ + register_global_error(NULL); + if (global_usb_context) { + libusb_exit(global_usb_context); + global_usb_context = NULL; + } + return 0; +} + +struct hid_device_info HID_API_EXPORT *hid_enumerate(unsigned short vendor_id, unsigned short product_id) +{ + struct hid_device_info *root; + struct hid_device_info *head; + struct hid_sysctl_iter iter; + + if (hid_init() != 0) + return NULL; + if (!hid_init_sysctl_iter(&iter, "dev.hidraw")) { + if (errno == ENOENT) { + if (vendor_id == 0 && product_id == 0) + register_global_error("No HID devices found in the system."); + else + register_global_error("No HID devices with requested VID/PID found in the system."); + } + return NULL; + } + + root = (struct hid_device_info *) calloc(1, sizeof(struct hid_device_info)); + if (!root) { + register_global_error("Couldn't allocate memory"); + return NULL; + } + head = root; + /* + * 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); + struct hid_device_info *failed; + if (!tmp) + continue; + if (vendor_id != 0 && vendor_id != tmp->vendor_id) { + failed = tmp->next; + tmp->next = NULL; + hid_free_enumeration(failed); + continue; + } + if (product_id != 0 && product_id != tmp->product_id) { + failed = tmp->next; + tmp->next = NULL; + hid_free_enumeration(failed); + continue; + } + root->next = tmp->next; + root = tmp; + tmp->next = NULL; + } + root = head->next; + free(head); + if (root == NULL) { + if (vendor_id == 0 && product_id == 0) + register_global_error("No HID devices found in the system."); + else + register_global_error("No HID devices with requested VID/PID found in the system."); + } + else { + register_global_error(NULL); + } + 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 (cur_dev->serial_number && 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; + + if (hid_init() != 0) + return NULL; + /* 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); + if (!dev->device_path) { + free(dev); + register_global_error("Couldn't allocate memory"); + return NULL; + } + 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. */ + register_global_error_format("Failed to open a device with path '%s': %s", path, strerror(errno)); + free((void *)dev->device_path); + free(dev); + 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; + size_t report_length = length; + struct hidraw_gen_descriptor desc; + + /* + * The uhid-compatible report ioctls do not change the descriptor's + * hidraw framing mode and are available on all supported FreeBSD releases. + */ + memset(&desc, 0, sizeof(desc)); + + if (!data || (length == 0)) { + errno = EINVAL; + register_device_error(dev, "Zero buffer/length"); + return -1; + } + if (length > UINT16_MAX) { + errno = EOVERFLOW; + register_device_error(dev, "Report is too large"); + return -1; + } + + register_device_error(dev, NULL); + + desc.hgd_maxlen = data[0] == 0 ? length - 1 : length; + desc.hgd_data = (void *)(data[0] == 0 ? data + 1 : data); + desc.hgd_report_type = HID_FEATURE_REPORT; + + res = ioctl(dev->device_handle, HIDRAW_SET_REPORT, &desc); + + if (res < 0) { + register_device_error_format(dev, "ioctl (SFEATURE): %s", strerror(errno)); + return -1; + } + + return report_length; +} + +int HID_API_EXPORT hid_get_feature_report(hid_device *dev, unsigned char *data, size_t length) +{ + int res; + unsigned char report_id; + struct hidraw_gen_descriptor desc; + + memset(&desc, 0, sizeof(desc)); + + if (!data || (length == 0)) { + errno = EINVAL; + register_device_error(dev, "Zero buffer/length"); + return -1; + } + if (length > UINT16_MAX) { + errno = EOVERFLOW; + register_device_error(dev, "Report is too large"); + return -1; + } + + report_id = data[0]; + register_device_error(dev, NULL); + + desc.hgd_maxlen = report_id == 0 ? length - 1 : length; + desc.hgd_data = report_id == 0 ? data + 1 : data; + desc.hgd_report_type = HID_FEATURE_REPORT; + + res = ioctl(dev->device_handle, HIDRAW_GET_REPORT, &desc); + + if (res < 0) { + register_device_error_format(dev, "ioctl (GFEATURE): %s", strerror(errno)); + return -1; + } + + length = desc.hgd_actlen + (report_id == 0); + 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; + size_t report_length = length; + struct hidraw_gen_descriptor desc; + + memset(&desc, 0, sizeof(desc)); + + if (!data || (length == 0)) { + errno = EINVAL; + register_device_error(dev, "Zero buffer/length"); + return -1; + } + if (length > UINT16_MAX) { + errno = EOVERFLOW; + register_device_error(dev, "Report is too large"); + return -1; + } + + register_device_error(dev, NULL); + + desc.hgd_maxlen = data[0] == 0 ? length - 1 : length; + desc.hgd_data = (void *)(data[0] == 0 ? data + 1 : data); + desc.hgd_report_type = HID_OUTPUT_REPORT; + res = ioctl(dev->device_handle, HIDRAW_SET_REPORT, &desc); + + if (res < 0) { + register_device_error_format(dev, "ioctl (SOUTPUT): %s", strerror(errno)); + return -1; + } + + return report_length; +} + +int HID_API_EXPORT HID_API_CALL hid_get_input_report(hid_device *dev, unsigned char *data, size_t length) +{ + int res; + unsigned char report_id; + struct hidraw_gen_descriptor desc; + + memset(&desc, 0, sizeof(desc)); + + if (!data || (length == 0)) { + errno = EINVAL; + register_device_error(dev, "Zero buffer/length"); + return -1; + } + if (length > UINT16_MAX) { + errno = EOVERFLOW; + register_device_error(dev, "Report is too large"); + return -1; + } + + report_id = data[0]; + register_device_error(dev, NULL); + + desc.hgd_maxlen = report_id == 0 ? length - 1 : length; + desc.hgd_data = report_id == 0 ? data + 1 : data; + desc.hgd_report_type = HID_INPUT_REPORT; + + res = ioctl(dev->device_handle, HIDRAW_GET_REPORT, &desc); + + if (res < 0) { + register_device_error_format(dev, "ioctl (GINPUT): %s", strerror(errno)); + return -1; + } + + length = desc.hgd_actlen + (report_id == 0); + 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 + if (realpath(dev->device_path, path_buffer) == NULL) { + register_device_error(dev, "Failed to resolve device path"); + return NULL; + } + if (sscanf(path_buffer, "/dev/hidraw%d", &idx) != 1) { + register_device_error(dev, "Resolved device path is not a hidraw device"); + return NULL; + } + 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; + register_device_error(dev, NULL); + } else { + register_device_error_from_global(dev); + } + } + + // 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; + } + + if (act_len < 0 || (size_t) act_len >= sizeof(desc.value)) { + errno = EOVERFLOW; + register_device_error(dev, "hid_get_report_descriptor: invalid report descriptor size"); + return -1; + } + + 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 ((size_t) act_len < buf_size) + buf_size = act_len; + + memcpy(buf, desc.value, buf_size); + return (int)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 b7ceae7ea..ddd65e2c7 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..2ad32e62b 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 @@ -34,6 +34,6 @@ $(COBJS): %.o: %.c clean: - rm -f $(OBJS) hidtest libhidapi.so ../hidtest/hidtest.o + rm -f $(OBJS) hidtest-libusb libhidapi-libusb.so ../hidtest/hidtest.o .PHONY: clean libs diff --git a/pc/hidapi-backend-specific.pc.in b/pc/hidapi-backend-specific.pc.in index ecf1b6e7c..ff7a6b392 100644 --- a/pc/hidapi-backend-specific.pc.in +++ b/pc/hidapi-backend-specific.pc.in @@ -8,4 +8,5 @@ Description: @HIDAPI_BACKEND_DESCRIPTION@ URL: https://github.com/libusb/hidapi Version: @VERSION@ Libs: -L${libdir} -l@HIDAPI_BACKEND_LIBRARY_NAME@ +@HIDAPI_BACKEND_REQUIRES_PRIVATE@ Cflags: -I${includedir}/hidapi diff --git a/pc/hidapi.pc.in b/pc/hidapi.pc.in index f9aaf82c1..dcbae5987 100644 --- a/pc/hidapi.pc.in +++ b/pc/hidapi.pc.in @@ -8,4 +8,5 @@ Description: C Library for USB/Bluetooth HID device access from Linux, Mac OS X, URL: https://github.com/libusb/hidapi Version: @VERSION@ Libs: -L${libdir} -l@HIDAPI_LIB_NAME@ +@HIDAPI_GENERIC_REQUIRES_PRIVATE@ Cflags: -I${includedir}/hidapi diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index fc804fd54..9c83c4a4b 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -55,7 +55,7 @@ endif() # Helper(s) function(hidapi_configure_pc PC_IN_FILE) - set(oneValueArgs OUTPUT_NAME LIBRARY_NAME DESCRIPTION) + set(oneValueArgs OUTPUT_NAME LIBRARY_NAME DESCRIPTION REQUIRES_PRIVATE) cmake_parse_arguments(HIDAPI_PC "" "${oneValueArgs}" "" ${ARGN}) file(MAKE_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/pc") @@ -86,10 +86,21 @@ function(hidapi_configure_pc PC_IN_FILE) set(HIDAPI_BACKEND_NAME "${PC_IN_FILENAME}") set(HIDAPI_BACKEND_DESCRIPTION "${HIDAPI_PC_DESCRIPTION}") set(HIDAPI_BACKEND_LIBRARY_NAME "${HIDAPI_LIB_NAME}") + if(HIDAPI_PC_REQUIRES_PRIVATE) + set(HIDAPI_BACKEND_REQUIRES_PRIVATE "Requires.private: ${HIDAPI_PC_REQUIRES_PRIVATE}") + else() + set(HIDAPI_BACKEND_REQUIRES_PRIVATE "") + endif() + set(HIDAPI_GENERIC_REQUIRES_PRIVATE "${HIDAPI_BACKEND_REQUIRES_PRIVATE}") set(PC_FILE "${CMAKE_CURRENT_BINARY_DIR}/pc/${PC_IN_FILENAME}.pc") configure_file("${PC_IN_FILE}" "${PC_FILE}" @ONLY) if(HIDAPI_INSTALL_TARGETS) - install(FILES "${PC_FILE}" DESTINATION "${CMAKE_INSTALL_LIBDIR}/pkgconfig/") + if(CMAKE_SYSTEM_NAME MATCHES "FreeBSD") + set(HIDAPI_PC_INSTALL_DIR "libdata/pkgconfig") + else() + set(HIDAPI_PC_INSTALL_DIR "${CMAKE_INSTALL_LIBDIR}/pkgconfig") + endif() + install(FILES "${PC_FILE}" DESTINATION "${HIDAPI_PC_INSTALL_DIR}") endif() endfunction() @@ -161,6 +172,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) @@ -202,8 +226,13 @@ endif() add_library(hidapi::hidapi ALIAS hidapi_${EXPORT_ALIAS}) get_target_property(HIDAPI_LIB_NAME hidapi_${EXPORT_ALIAS} OUTPUT_NAME) +set(HIDAPI_REQUIRES_PRIVATE "") +if(CMAKE_SYSTEM_NAME MATCHES "FreeBSD" AND EXPORT_ALIAS STREQUAL "hidraw") + set(HIDAPI_REQUIRES_PRIVATE "libusb-1.0 >= 1.0.9") +endif() hidapi_configure_pc("${PROJECT_ROOT}/pc/hidapi.pc.in" LIBRARY_NAME "${HIDAPI_LIB_NAME}" + REQUIRES_PRIVATE "${HIDAPI_REQUIRES_PRIVATE}" ) if(HIDAPI_INSTALL_TARGETS) diff --git a/subprojects/hidapi_build_cmake/CMakeLists.txt b/subprojects/hidapi_build_cmake/CMakeLists.txt index 4586ce6a6..05f0f140a 100644 --- a/subprojects/hidapi_build_cmake/CMakeLists.txt +++ b/subprojects/hidapi_build_cmake/CMakeLists.txt @@ -3,7 +3,7 @@ project(hidapi LANGUAGES C) file(MAKE_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/root") -foreach(ROOT_ELEMENT CMakeLists.txt hidapi src windows linux mac libusb pc VERSION) +foreach(ROOT_ELEMENT CMakeLists.txt hidapi src windows linux freebsd mac libusb pc VERSION) file(COPY "${CMAKE_CURRENT_LIST_DIR}/../../${ROOT_ELEMENT}" DESTINATION "${CMAKE_CURRENT_BINARY_DIR}/root/") endforeach() 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