Browse Source

Updated hidapi to 0.14.0 release

Upstream: https://github.com/libusb/hidapi/releases/tag/hidapi-0.14.0
Sam Lantinga 1 year ago
parent
commit
3b7b8f3c09
100 changed files with 10826 additions and 7664 deletions
  1. 31 0
      src/hidapi/.appveyor.yml
  2. 34 0
      src/hidapi/.builds/freebsd.yml
  3. 18 0
      src/hidapi/.builds/netbsd.yml
  4. 19 0
      src/hidapi/.builds/openbsd.yml
  5. 33 0
      src/hidapi/.cirrus.yml
  6. 7 0
      src/hidapi/.gitattributes
  7. 540 0
      src/hidapi/.github/workflows/builds.yml
  8. 196 0
      src/hidapi/.github/workflows/checks.yml
  9. 58 0
      src/hidapi/.github/workflows/docs.yaml
  10. 32 0
      src/hidapi/.gitignore
  11. 3 1
      src/hidapi/AUTHORS.txt
  12. 114 0
      src/hidapi/BUILD.autotools.md
  13. 280 0
      src/hidapi/BUILD.cmake.md
  14. 127 0
      src/hidapi/BUILD.md
  15. 105 0
      src/hidapi/CMakeLists.txt
  16. 15 11
      src/hidapi/HACKING.txt
  17. 5 5
      src/hidapi/Makefile.am
  18. 196 0
      src/hidapi/README.md
  19. 0 339
      src/hidapi/README.txt
  20. 1 0
      src/hidapi/VERSION
  21. 0 1443
      src/hidapi/android/hid.cpp
  22. 0 39
      src/hidapi/android/hid.h
  23. 0 16
      src/hidapi/android/jni/Android.mk
  24. 0 2
      src/hidapi/android/jni/Application.mk
  25. 0 14
      src/hidapi/android/project.properties
  26. 44 24
      src/hidapi/configure.ac
  27. 31 0
      src/hidapi/dist/hidapi.podspec
  28. BIN
      src/hidapi/documentation/cmake-gui-drop-down.png
  29. BIN
      src/hidapi/documentation/cmake-gui-highlights.png
  30. 1963 869
      src/hidapi/doxygen/Doxyfile
  31. 13 0
      src/hidapi/doxygen/main_page.md
  32. 261 58
      src/hidapi/hidapi/hidapi.h
  33. 17 0
      src/hidapi/hidtest/.gitignore
  34. 40 0
      src/hidapi/hidtest/CMakeLists.txt
  35. 11 3
      src/hidapi/hidtest/Makefile.am
  36. 0 194
      src/hidapi/hidtest/hidtest.cpp
  37. 316 0
      src/hidapi/hidtest/test.c
  38. 0 32
      src/hidapi/ios/Makefile-manual
  39. 0 9
      src/hidapi/ios/Makefile.am
  40. 0 996
      src/hidapi/ios/hid.m
  41. 8 0
      src/hidapi/libusb/.gitignore
  42. 107 0
      src/hidapi/libusb/CMakeLists.txt
  43. 4 0
      src/hidapi/libusb/Makefile-manual
  44. 8 1
      src/hidapi/libusb/Makefile.am
  45. 4 11
      src/hidapi/libusb/Makefile.freebsd
  46. 39 0
      src/hidapi/libusb/Makefile.haiku
  47. 5 12
      src/hidapi/libusb/Makefile.linux
  48. 644 673
      src/hidapi/libusb/hid.c
  49. 56 0
      src/hidapi/libusb/hidapi_libusb.h
  50. 0 3
      src/hidapi/libusb/hidusb.cpp
  51. 18 0
      src/hidapi/linux/.gitignore
  52. 38 0
      src/hidapi/linux/CMakeLists.txt
  53. 5 12
      src/hidapi/linux/Makefile-manual
  54. 0 59
      src/hidapi/linux/README.txt
  55. 840 484
      src/hidapi/linux/hid.c
  56. 0 3
      src/hidapi/linux/hidraw.cpp
  57. 5 0
      src/hidapi/m4/.gitignore
  58. 17 0
      src/hidapi/mac/.gitignore
  59. 48 0
      src/hidapi/mac/CMakeLists.txt
  60. 6 11
      src/hidapi/mac/Makefile-manual
  61. 935 662
      src/hidapi/mac/hid.c
  62. 98 0
      src/hidapi/mac/hidapi_darwin.h
  63. 22 0
      src/hidapi/meson.build
  64. 1 0
      src/hidapi/pc/.gitignore
  65. 1 0
      src/hidapi/pc/hidapi-hidraw.pc.in
  66. 1 0
      src/hidapi/pc/hidapi-libusb.pc.in
  67. 1 0
      src/hidapi/pc/hidapi.pc.in
  68. 193 0
      src/hidapi/src/CMakeLists.txt
  69. 61 0
      src/hidapi/src/cmake/hidapi-config.cmake.in
  70. 2 0
      src/hidapi/subprojects/README.md
  71. 10 0
      src/hidapi/subprojects/hidapi_build_cmake/CMakeLists.txt
  72. 20 0
      src/hidapi/testgui/.gitignore
  73. 1 1
      src/hidapi/testgui/Makefile.mingw
  74. 2 1
      src/hidapi/testgui/copy_to_bundle.sh
  75. 0 134
      src/hidapi/testgui/mac_support.cpp
  76. 11 2
      src/hidapi/testgui/mac_support_cocoa.m
  77. 0 2
      src/hidapi/testgui/start.sh
  78. 20 20
      src/hidapi/testgui/testgui.sln
  79. 217 217
      src/hidapi/testgui/testgui.vcproj
  80. 36 0
      src/hidapi/udev/69-hid.rules
  81. 0 33
      src/hidapi/udev/99-hid.rules
  82. 17 0
      src/hidapi/windows/.gitignore
  83. 63 0
      src/hidapi/windows/CMakeLists.txt
  84. 0 1
      src/hidapi/windows/Makefile.am
  85. 6 11
      src/hidapi/windows/Makefile.mingw
  86. 0 17
      src/hidapi/windows/ddk_build/hidapi.def
  87. 0 49
      src/hidapi/windows/ddk_build/makefile
  88. 0 23
      src/hidapi/windows/ddk_build/sources
  89. 752 724
      src/hidapi/windows/hid.c
  90. 35 0
      src/hidapi/windows/hidapi.rc
  91. 41 29
      src/hidapi/windows/hidapi.sln
  92. 200 201
      src/hidapi/windows/hidapi.vcproj
  93. 200 0
      src/hidapi/windows/hidapi.vcxproj
  94. 14 16
      src/hidapi/windows/hidapi_cfgmgr32.h
  95. 987 0
      src/hidapi/windows/hidapi_descriptor_reconstruct.c
  96. 238 0
      src/hidapi/windows/hidapi_descriptor_reconstruct.h
  97. 7 0
      src/hidapi/windows/hidapi_hidpi.h
  98. 2 1
      src/hidapi/windows/hidapi_hidsdi.h
  99. 74 0
      src/hidapi/windows/hidapi_winapi.h
  100. 196 196
      src/hidapi/windows/hidtest.vcproj

+ 31 - 0
src/hidapi/.appveyor.yml

@@ -0,0 +1,31 @@
+environment:
+  matrix:
+  - BUILD_ENV: msbuild
+    arch: x64
+  - BUILD_ENV: msbuild
+    arch: Win32
+  - BUILD_ENV: cygwin
+
+for:
+  -
+    matrix:
+      only:
+        - BUILD_ENV: msbuild
+
+    os: Visual Studio 2015
+
+    build_script:
+      - cmd: msbuild .\windows\hidapi.sln /p:Configuration=Release /p:Platform=%arch% /logger:"C:\Program Files\AppVeyor\BuildAgent\Appveyor.MSBuildLogger.dll"
+
+  -
+    matrix:
+      only:
+        - BUILD_ENV: cygwin
+
+    os: Visual Studio 2022
+
+    install:
+      - cmd: C:\cygwin64\setup-x86_64.exe --quiet-mode --no-shortcuts --upgrade-also --packages autoconf,automake
+
+    build_script:
+      - cmd: C:\cygwin64\bin\bash -exlc "cd $APPVEYOR_BUILD_FOLDER; ./bootstrap; ./configure; make"

+ 34 - 0
src/hidapi/.builds/freebsd.yml

@@ -0,0 +1,34 @@
+image: freebsd/latest
+packages:
+- autoconf
+- automake
+- gmake
+- libiconv
+- libtool
+- pkgconf
+- cmake
+- ninja
+sources:
+- https://github.com/libusb/hidapi
+tasks:
+- configure: |
+    cd hidapi
+    echo Configure Autotools build
+    ./bootstrap
+    ./configure
+    echo Configure CMake build
+    mkdir -p build install_cmake
+    cmake -GNinja -B build -S . -DCMAKE_INSTALL_PREFIX=install_cmake
+- build-autotools: |
+    cd hidapi
+    make
+    make DESTDIR=$PWD/root install
+    make clean
+- build-cmake: |
+    cd hidapi/build
+    ninja
+    ninja install
+    ninja clean
+- build-manual: |
+    cd hidapi/libusb
+    gmake -f Makefile-manual

+ 18 - 0
src/hidapi/.builds/netbsd.yml

@@ -0,0 +1,18 @@
+image: netbsd/latest
+packages:
+- cmake
+- pkgconf
+- libusb1
+- libiconv
+sources:
+- https://github.com/libusb/hidapi
+tasks:
+- configure: |
+    cd hidapi
+    mkdir -p build install
+    cmake -B build -S . -DCMAKE_INSTALL_PREFIX=install
+- build: |
+    cd hidapi/build
+    make
+    make install
+    make clean

+ 19 - 0
src/hidapi/.builds/openbsd.yml

@@ -0,0 +1,19 @@
+image: openbsd/latest
+packages:
+- cmake
+- pkgconf
+- libusb1--
+- libiconv
+- ninja
+sources:
+- https://github.com/libusb/hidapi
+tasks:
+- configure: |
+    cd hidapi
+    mkdir -p build install
+    cmake -GNinja -B build -S . -DCMAKE_INSTALL_PREFIX=install
+- build: |
+    cd hidapi/build
+    ninja
+    ninja install
+    ninja clean

+ 33 - 0
src/hidapi/.cirrus.yml

@@ -0,0 +1,33 @@
+alpine_task:
+        container:
+                image: alpine:latest
+        install_script: apk add autoconf automake g++ gcc libusb-dev libtool linux-headers eudev-dev make musl-dev
+        script:
+                - ./bootstrap
+                - ./configure || { cat config.log; exit 1; }
+                - make
+                - make install
+
+freebsd11_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
+        install_script:
+                - pkg install -y
+                  autoconf automake libiconv libtool pkgconf
+        script:
+                - ./bootstrap
+                - ./configure || { cat config.log; exit 1; }
+                - make
+                - make install

+ 7 - 0
src/hidapi/.gitattributes

@@ -0,0 +1,7 @@
+* text=auto
+
+*.sln text eol=crlf
+*.vcproj text eol=crlf
+
+bootstrap text eol=lf
+configure.ac text eol=lf

+ 540 - 0
src/hidapi/.github/workflows/builds.yml

@@ -0,0 +1,540 @@
+name: GitHub Builds
+
+on: [push, pull_request]
+
+env:
+  NIX_COMPILE_FLAGS: -Wall -Wextra -pedantic -Werror
+  MSVC_COMPILE_FLAGS: /W4 /WX
+
+jobs:
+  macos-automake:
+
+    runs-on: macos-latest
+
+    steps:
+    - uses: actions/checkout@v3
+    - name: Install build tools
+      run: brew install autoconf automake libtool
+    - name: Configure Automake
+      run: |
+        ./bootstrap
+        ./configure --prefix=$(pwd)/install
+    - name: Build Automake
+      run: |
+        make
+        make install
+    - name: Clean build
+      run: make clean
+    - name: Build Manual makefile
+      working-directory: mac
+      run: make -f Makefile-manual
+
+
+  macos-cmake:
+
+    runs-on: macos-latest
+
+    steps:
+    - uses: actions/checkout@v3
+      with:
+        path: hidapisrc
+    - name: Install dependencies
+      run: brew install meson ninja
+    - name: Configure CMake
+      run: |
+        rm -rf build install
+        cmake -B build/shared -S hidapisrc -DCMAKE_BUILD_TYPE=RelWithDebInfo -DHIDAPI_ENABLE_ASAN=ON -DCMAKE_INSTALL_PREFIX=install/shared -DHIDAPI_BUILD_HIDTEST=ON "-DCMAKE_C_FLAGS=${NIX_COMPILE_FLAGS}"
+        cmake -B build/static -S hidapisrc -DCMAKE_BUILD_TYPE=RelWithDebInfo -DHIDAPI_ENABLE_ASAN=ON -DCMAKE_INSTALL_PREFIX=install/static -DBUILD_SHARED_LIBS=FALSE -DHIDAPI_BUILD_HIDTEST=ON "-DCMAKE_C_FLAGS=${NIX_COMPILE_FLAGS}"
+        cmake -B build/framework -S hidapisrc -DCMAKE_BUILD_TYPE=RelWithDebInfo -DHIDAPI_ENABLE_ASAN=ON -DCMAKE_INSTALL_PREFIX=install/framework -DCMAKE_FRAMEWORK=ON -DHIDAPI_BUILD_HIDTEST=ON "-DCMAKE_C_FLAGS=${NIX_COMPILE_FLAGS}"
+    - name: Build CMake Shared
+      working-directory: build/shared
+      run: make install
+    - name: Build CMake Static
+      working-directory: build/static
+      run: make install
+    - name: Build CMake Framework
+      working-directory: build/framework
+      run: make install
+    - name: Check artifacts
+      uses: andstor/file-existence-action@v2
+      with:
+        files: "install/shared/lib/libhidapi.dylib, \
+                install/shared/include/hidapi/hidapi.h, \
+                install/shared/include/hidapi/hidapi_darwin.h, \
+                install/static/lib/libhidapi.a, \
+                install/static/include/hidapi/hidapi.h, \
+                install/static/include/hidapi/hidapi_darwin.h, \
+                install/framework/lib/hidapi.framework/hidapi, \
+                install/framework/lib/hidapi.framework/Headers/hidapi.h, \
+                install/framework/lib/hidapi.framework/Headers/hidapi_darwin.h"
+        fail: true
+    - name: Check CMake Export Package Shared
+      run: |
+        cmake \
+          -B build/shared_test \
+          -S hidapisrc/hidtest \
+          -Dhidapi_ROOT=install/shared \
+          -DCMAKE_INSTALL_PREFIX=install/shared_test \
+          "-DCMAKE_C_FLAGS=${NIX_COMPILE_FLAGS}"
+        cd build/shared_test
+        make install
+    - name: Check CMake Export Package Static
+      run: |
+        cmake \
+          -B build/static_test \
+          -S hidapisrc/hidtest \
+          -Dhidapi_ROOT=install/static \
+          -DCMAKE_INSTALL_PREFIX=install/static_test \
+          "-DCMAKE_C_FLAGS=${NIX_COMPILE_FLAGS}"
+        cd build/static_test
+        make install
+
+    - name: Check Meson build
+      run: |
+        meson setup build_meson hidapisrc
+        cd build_meson
+        ninja
+
+
+  ubuntu-cmake:
+
+    runs-on: ubuntu-latest
+
+    steps:
+    - uses: actions/checkout@v3
+      with:
+        path: hidapisrc
+    - name: Install dependencies
+      run: |
+        sudo apt update
+        sudo apt install libudev-dev libusb-1.0-0-dev python3-pip ninja-build
+        sudo -H pip3 install meson
+    - name: Configure CMake
+      run: |
+        rm -rf build install
+        cmake -B build/shared -S hidapisrc -DCMAKE_BUILD_TYPE=RelWithDebInfo -DHIDAPI_ENABLE_ASAN=ON -DCMAKE_INSTALL_PREFIX=install/shared -DHIDAPI_BUILD_HIDTEST=ON "-DCMAKE_C_FLAGS=${NIX_COMPILE_FLAGS}"
+        cmake -B build/static -S hidapisrc -DCMAKE_BUILD_TYPE=RelWithDebInfo -DHIDAPI_ENABLE_ASAN=ON -DCMAKE_INSTALL_PREFIX=install/static -DBUILD_SHARED_LIBS=FALSE -DHIDAPI_BUILD_HIDTEST=ON "-DCMAKE_C_FLAGS=${NIX_COMPILE_FLAGS}"
+    - name: Build CMake Shared
+      working-directory: build/shared
+      run: make install
+    - name: Build CMake Static
+      working-directory: build/static
+      run: make install
+    - name: Check artifacts
+      uses: andstor/file-existence-action@v2
+      with:
+        files: "install/shared/lib/libhidapi-libusb.so, \
+                install/shared/lib/libhidapi-hidraw.so, \
+                install/shared/include/hidapi/hidapi.h, \
+                install/shared/include/hidapi/hidapi_libusb.h, \
+                install/static/lib/libhidapi-libusb.a, \
+                install/static/lib/libhidapi-hidraw.a, \
+                install/static/include/hidapi/hidapi.h, \
+                install/static/include/hidapi/hidapi_libusb.h"
+        fail: true
+    - name: Check CMake Export Package Shared
+      run: |
+        cmake \
+          -B build/shared_test \
+          -S hidapisrc/hidtest \
+          -Dhidapi_ROOT=install/shared \
+          -DCMAKE_INSTALL_PREFIX=install/shared_test \
+          "-DCMAKE_C_FLAGS=${NIX_COMPILE_FLAGS}"
+        cd build/shared_test
+        make install
+    - name: Check CMake Export Package Static
+      run: |
+        cmake \
+          -B build/static_test \
+          -S hidapisrc/hidtest \
+          -Dhidapi_ROOT=install/static \
+          -DCMAKE_INSTALL_PREFIX=install/static_test \
+          "-DCMAKE_C_FLAGS=${NIX_COMPILE_FLAGS}"
+        cd build/static_test
+        make install
+
+    - name: Check Meson build
+      run: |
+        meson setup build_meson hidapisrc
+        cd build_meson
+        ninja
+
+
+  windows-cmake:
+
+    runs-on: windows-latest
+
+    steps:
+    - uses: actions/checkout@v3
+      with:
+        path: hidapisrc
+    - name: Install dependencies
+      run: |
+        choco install ninja
+        pip3 install meson
+        refreshenv
+    - name: Configure CMake MSVC
+      shell: cmd
+      run: |
+        cmake -B build\msvc -S hidapisrc -DCMAKE_BUILD_TYPE=RelWithDebInfo -DHIDAPI_WITH_TESTS=ON -DHIDAPI_BUILD_PP_DATA_DUMP=ON -DHIDAPI_ENABLE_ASAN=ON -DCMAKE_INSTALL_PREFIX=install\msvc -DHIDAPI_BUILD_HIDTEST=ON "-DCMAKE_C_FLAGS=%MSVC_COMPILE_FLAGS%"
+    - name: Build CMake MSVC
+      working-directory: build/msvc
+      run: cmake --build . --config RelWithDebInfo --target install
+    - name: Check artifacts MSVC
+      uses: andstor/file-existence-action@v2
+      with:
+        files: "install/msvc/lib/hidapi.lib, \
+                install/msvc/bin/hidapi.dll, \
+                install/msvc/include/hidapi/hidapi.h, \
+                install/msvc/include/hidapi/hidapi_winapi.h"
+        fail: true
+    - name: Check CMake Export Package
+      shell: cmd
+      run: |
+        cmake ^
+          -B build\msvc_test ^
+          -S hidapisrc\hidtest ^
+          -Dhidapi_ROOT=install\msvc ^
+          -DCMAKE_INSTALL_PREFIX=install\msvc_test ^
+          "-DCMAKE_C_FLAGS=%MSVC_COMPILE_FLAGS%"
+        cd build\msvc_test
+        cmake --build . --target install
+    - name: Run CTest MSVC
+      shell: cmd
+      working-directory: build/msvc
+      run: ctest -C RelWithDebInfo --rerun-failed --output-on-failure
+
+    - name: Configure CMake NMake
+      shell: cmd
+      run: |
+        call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat"
+        cmake -G"NMake Makefiles" -B build\nmake -S hidapisrc -DCMAKE_BUILD_TYPE=RelWithDebInfo -DHIDAPI_WITH_TESTS=ON -DHIDAPI_BUILD_PP_DATA_DUMP=ON -DHIDAPI_ENABLE_ASAN=ON -DCMAKE_INSTALL_PREFIX=install\nmake -DHIDAPI_BUILD_HIDTEST=ON "-DCMAKE_C_FLAGS=%MSVC_COMPILE_FLAGS%"
+    - name: Build CMake NMake
+      working-directory: build\nmake
+      shell: cmd
+      run: |
+        call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat"
+        nmake install
+    - name: Check artifacts NMake
+      uses: andstor/file-existence-action@v2
+      with:
+        files: "install/nmake/lib/hidapi.lib, \
+                install/nmake/bin/hidapi.dll, \
+                install/nmake/include/hidapi/hidapi.h, \
+                install/nmake/include/hidapi/hidapi_winapi.h"
+        fail: true
+    - name: Check CMake Export Package NMake
+      shell: cmd
+      run: |
+        call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat"
+        cmake ^
+          -G"NMake Makefiles" ^
+          -B build\nmake_test ^
+          -S hidapisrc\hidtest ^
+          -Dhidapi_ROOT=install\nmake ^
+          -DCMAKE_INSTALL_PREFIX=install\nmake_test ^
+          "-DCMAKE_C_FLAGS=%MSVC_COMPILE_FLAGS%"
+        cd build\nmake_test
+        nmake install
+    - name: Run CTest NMake
+      working-directory: build\nmake
+      run: ctest --rerun-failed --output-on-failure
+
+    - name: Configure CMake MinGW
+      shell: cmd
+      run: |
+        cmake -G"MinGW Makefiles" -B build\mingw -S hidapisrc -DCMAKE_BUILD_TYPE=RelWithDebInfo -DHIDAPI_WITH_TESTS=ON -DHIDAPI_BUILD_PP_DATA_DUMP=ON -DCMAKE_INSTALL_PREFIX=install\mingw -DHIDAPI_BUILD_HIDTEST=ON "-DCMAKE_C_FLAGS=%NIX_COMPILE_FLAGS%"
+    - name: Build CMake MinGW
+      working-directory: build\mingw
+      run: cmake --build . --target install
+    - name: Check artifacts MinGW
+      uses: andstor/file-existence-action@v2
+      with:
+        files: "install/mingw/lib/libhidapi.dll.a, \
+                install/mingw/bin/libhidapi.dll, \
+                install/mingw/include/hidapi/hidapi.h, \
+                install/mingw/include/hidapi/hidapi_winapi.h"
+        fail: true
+    - name: Check CMake Export Package MinGW
+      shell: cmd
+      run: |
+        cmake ^
+          -G"MinGW Makefiles" ^
+          -B build\mingw_test ^
+          -S hidapisrc\hidtest ^
+          -Dhidapi_ROOT=install\mingw ^
+          -DCMAKE_INSTALL_PREFIX=install\mingw_test ^
+          "-DCMAKE_C_FLAGS=%NIX_COMPILE_FLAGS%"
+        cd build\mingw_test
+        cmake --build . --target install
+    - name: Run CTest MinGW
+      working-directory: build\mingw
+      run: ctest --rerun-failed --output-on-failure
+
+    - name: Check Meson build
+      shell: cmd
+      run: |
+        call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat"
+        meson setup build_meson hidapisrc
+        cd build_meson
+        ninja
+
+
+  windows-msbuild:
+
+    runs-on: windows-latest
+
+    steps:
+    - uses: actions/checkout@v3
+    - uses: microsoft/setup-msbuild@v1.1
+    - name: MSBuild x86
+      run: msbuild windows\hidapi.sln /p:Configuration=Release /p:Platform=Win32
+    - name: Check artifacts x86
+      uses: andstor/file-existence-action@v2
+      with:
+        files: "windows/Release/hidapi.dll, windows/Release/hidapi.lib, windows/Release/hidapi.pdb"
+        fail: true
+    - name: MSBuild x64
+      run: msbuild windows\hidapi.sln /p:Configuration=Release /p:Platform=x64
+    - name: Check artifacts x64
+      uses: andstor/file-existence-action@v2
+      with:
+        files: "windows/x64/Release/hidapi.dll, windows/x64/Release/hidapi.lib, windows/x64/Release/hidapi.pdb"
+        fail: true
+    - name: Gather artifacts
+      run: |
+        md artifacts
+        md artifacts\x86
+        md artifacts\x64
+        md artifacts\include
+        Copy-Item "windows\Release\hidapi.dll","windows\Release\hidapi.lib","windows\Release\hidapi.pdb" -Destination "artifacts\x86"
+        Copy-Item "windows\x64\Release\hidapi.dll","windows\x64\Release\hidapi.lib","windows\x64\Release\hidapi.pdb" -Destination "artifacts\x64"
+        Copy-Item "hidapi\hidapi.h","windows\hidapi_winapi.h" -Destination "artifacts\include"
+    - name: Upload artifacts
+      uses: actions/upload-artifact@v3
+      with:
+        name: hidapi-win
+        path: artifacts/
+        retention-days: ${{ (github.event_name == 'pull_request' || github.ref_name != 'master') && 7 || 90 }}
+
+
+  fedora-mingw:
+
+    runs-on: ubuntu-latest
+    container: fedora:latest
+    steps:
+    - uses: actions/checkout@v3
+      with:
+        path: hidapisrc
+    - name: Install dependencies
+      run: sudo dnf install -y autoconf automake libtool mingw64-gcc cmake ninja-build make
+    - name: Configure CMake
+      run: |
+        rm -rf build install
+        mingw64-cmake -B build/shared-cmake -S hidapisrc -GNinja -DCMAKE_BUILD_TYPE=RelWithDebInfo -DCMAKE_INSTALL_PREFIX=install/shared-cmake -DHIDAPI_BUILD_HIDTEST=ON "-DCMAKE_C_FLAGS=${NIX_COMPILE_FLAGS}"
+        mingw64-cmake -B build/static-cmake -S hidapisrc -GNinja -DCMAKE_BUILD_TYPE=RelWithDebInfo -DCMAKE_INSTALL_PREFIX=install/static-cmake -DBUILD_SHARED_LIBS=FALSE -DHIDAPI_BUILD_HIDTEST=ON "-DCMAKE_C_FLAGS=${NIX_COMPILE_FLAGS}"
+    - name: Configure Automake
+      working-directory: hidapisrc
+      run: |
+        ./bootstrap
+        mingw64-configure
+    - name: Build CMake Shared
+      working-directory: build/shared-cmake
+      run: ninja install
+    - name: Build CMake Static
+      working-directory: build/static-cmake
+      run: ninja install
+    - name: Build Automake
+      working-directory: hidapisrc
+      run: |
+        make
+        make DESTDIR=$PWD/../install/automake install
+        make clean
+    - name: Build manual Makefile
+      working-directory: hidapisrc/windows
+      run: make -f Makefile-manual OS=MINGW CC=x86_64-w64-mingw32-gcc
+    - name: Check artifacts
+      uses: andstor/file-existence-action@v2
+      with:
+        files: "install/shared-cmake/bin/libhidapi.dll, \
+                install/shared-cmake/lib/libhidapi.dll.a, \
+                install/shared-cmake/include/hidapi/hidapi.h, \
+                install/shared-cmake/include/hidapi/hidapi_winapi.h, \
+                install/static-cmake/lib/libhidapi.a, \
+                install/static-cmake/include/hidapi/hidapi.h, \
+                install/static-cmake/include/hidapi/hidapi_winapi.h"
+        fail: true
+    - name: Check CMake Export Package Shared
+      run: |
+        mingw64-cmake \
+          -GNinja \
+          -B build/shared_test \
+          -S hidapisrc/hidtest \
+          -Dhidapi_DIR=$PWD/install/shared-cmake/lib/cmake/hidapi \
+          -DCMAKE_INSTALL_PREFIX=install/shared_test \
+          "-DCMAKE_C_FLAGS=${NIX_COMPILE_FLAGS}"
+        cd build/shared_test
+        ninja install
+    - name: Check CMake Export Package Static
+      run: |
+        mingw64-cmake \
+          -GNinja \
+          -B build/static_test \
+          -S hidapisrc/hidtest \
+          -Dhidapi_DIR=$PWD/install/static-cmake/lib/cmake/hidapi \
+          -DCMAKE_INSTALL_PREFIX=install/static_test \
+          "-DCMAKE_C_FLAGS=${NIX_COMPILE_FLAGS}"
+        cd build/static_test
+        ninja install
+
+
+  archlinux:
+
+    runs-on: ubuntu-latest
+    container: archlinux:latest
+    steps:
+    - uses: actions/checkout@v3
+      with:
+        path: hidapisrc
+    - name: Install dependencies
+      run: |
+        pacman -Sy
+        pacman -S --noconfirm gcc pkg-config autoconf automake libtool libusb libudev0 cmake make
+    - name: Configure CMake
+      run: |
+        rm -rf build install
+        cmake -B build/shared-cmake -S hidapisrc -DCMAKE_BUILD_TYPE=RelWithDebInfo -DCMAKE_INSTALL_PREFIX=install/shared-cmake -DHIDAPI_BUILD_HIDTEST=ON "-DCMAKE_C_FLAGS=${NIX_COMPILE_FLAGS}"
+        cmake -B build/static-cmake -S hidapisrc -DCMAKE_BUILD_TYPE=RelWithDebInfo -DCMAKE_INSTALL_PREFIX=install/static-cmake -DBUILD_SHARED_LIBS=FALSE -DHIDAPI_BUILD_HIDTEST=ON "-DCMAKE_C_FLAGS=${NIX_COMPILE_FLAGS}"
+    - name: Configure Automake
+      working-directory: hidapisrc
+      run: |
+        ./bootstrap
+        ./configure
+    - name: Build CMake Shared
+      working-directory: build/shared-cmake
+      run: make install
+    - name: Build CMake Static
+      working-directory: build/static-cmake
+      run: make install
+    - name: Build Automake
+      working-directory: hidapisrc
+      run: |
+        make
+        make DESTDIR=$PWD/../install/automake install
+        make clean
+    - name: Build manual Makefile
+      run: |
+        cd hidapisrc/linux
+        make -f Makefile-manual
+        cd ../libusb
+        make -f Makefile-manual
+    - name: Check artifacts
+      uses: andstor/file-existence-action@v2
+      with:
+        files: "install/shared-cmake/lib/libhidapi-libusb.so, \
+                install/shared-cmake/lib/libhidapi-hidraw.so, \
+                install/shared-cmake/include/hidapi/hidapi.h, \
+                install/shared-cmake/include/hidapi/hidapi_libusb.h, \
+                install/static-cmake/lib/libhidapi-libusb.a, \
+                install/static-cmake/lib/libhidapi-hidraw.a, \
+                install/static-cmake/include/hidapi/hidapi.h, \
+                install/static-cmake/include/hidapi/hidapi_libusb.h"
+        fail: true
+    - name: Check CMake Export Package Shared
+      run: |
+        cmake \
+          -B build/shared_test \
+          -S hidapisrc/hidtest \
+          -Dhidapi_ROOT=install/shared-cmake \
+          -DCMAKE_INSTALL_PREFIX=install/shared_test \
+          "-DCMAKE_C_FLAGS=${NIX_COMPILE_FLAGS}"
+        cd build/shared_test
+        make install
+    - name: Check CMake Export Package Static
+      run: |
+        cmake \
+          -B build/static_test \
+          -S hidapisrc/hidtest \
+          -Dhidapi_ROOT=install/static-cmake \
+          -DCMAKE_INSTALL_PREFIX=install/static_test \
+          "-DCMAKE_C_FLAGS=${NIX_COMPILE_FLAGS}"
+        cd build/static_test
+        make install
+
+
+  alpine:
+
+    runs-on: ubuntu-latest
+    container: alpine:edge
+    env:
+      # A bug in musl: https://www.openwall.com/lists/musl/2020/01/20/2
+      ALPINE_COMPILE_FLAGS: ${NIX_COMPILE_FLAGS} -Wno-overflow
+    steps:
+    - uses: actions/checkout@v3
+      with:
+        path: hidapisrc
+    - name: Install dependencies
+      run: |
+        apk add gcc musl-dev autoconf automake libtool eudev-dev libusb-dev linux-headers cmake ninja make
+    - name: Configure CMake
+      run: |
+        rm -rf build install
+        cmake -B build/shared-cmake -S hidapisrc -GNinja -DCMAKE_BUILD_TYPE=RelWithDebInfo -DCMAKE_INSTALL_PREFIX=install/shared-cmake -DHIDAPI_BUILD_HIDTEST=ON "-DCMAKE_C_FLAGS=${ALPINE_COMPILE_FLAGS}"
+        cmake -B build/static-cmake -S hidapisrc -GNinja -DCMAKE_BUILD_TYPE=RelWithDebInfo -DCMAKE_INSTALL_PREFIX=install/static-cmake -DBUILD_SHARED_LIBS=FALSE -DHIDAPI_BUILD_HIDTEST=ON "-DCMAKE_C_FLAGS=${ALPINE_COMPILE_FLAGS}"
+    - name: Configure Automake
+      working-directory: hidapisrc
+      run: |
+        ./bootstrap
+        ./configure
+    - name: Build CMake Shared
+      working-directory: build/shared-cmake
+      run: ninja install
+    - name: Build CMake Static
+      working-directory: build/static-cmake
+      run: ninja install
+    - name: Build Automake
+      working-directory: hidapisrc
+      run: |
+        make
+        make DESTDIR=$PWD/../install/automake install
+        make clean
+    - name: Build manual Makefile
+      run: |
+        cd hidapisrc/linux
+        make -f Makefile-manual
+        cd ../libusb
+        make -f Makefile-manual
+    - name: Check artifacts
+      uses: andstor/file-existence-action@v2
+      with:
+        files: "install/shared-cmake/lib/libhidapi-libusb.so, \
+                install/shared-cmake/lib/libhidapi-hidraw.so, \
+                install/shared-cmake/include/hidapi/hidapi.h, \
+                install/shared-cmake/include/hidapi/hidapi_libusb.h, \
+                install/static-cmake/lib/libhidapi-libusb.a, \
+                install/static-cmake/lib/libhidapi-hidraw.a, \
+                install/static-cmake/include/hidapi/hidapi.h, \
+                install/static-cmake/include/hidapi/hidapi_libusb.h"
+        fail: true
+    - name: Check CMake Export Package Shared
+      run: |
+        cmake \
+          -GNinja \
+          -B build/shared_test \
+          -S hidapisrc/hidtest \
+          -Dhidapi_ROOT=install/shared-cmake \
+          -DCMAKE_INSTALL_PREFIX=install/shared_test \
+          "-DCMAKE_C_FLAGS=${ALPINE_COMPILE_FLAGS}"
+        cd build/shared_test
+        ninja install
+    - name: Check CMake Export Package Static
+      run: |
+        cmake \
+          -GNinja \
+          -B build/static_test \
+          -S hidapisrc/hidtest \
+          -Dhidapi_ROOT=install/static-cmake \
+          -DCMAKE_INSTALL_PREFIX=install/static_test \
+          "-DCMAKE_C_FLAGS=${ALPINE_COMPILE_FLAGS}"
+        cd build/static_test
+        ninja install

+ 196 - 0
src/hidapi/.github/workflows/checks.yml

@@ -0,0 +1,196 @@
+name: Checks
+run-name: Code checks for '${{ github.ref_name }}'
+
+# General comment:
+# Coverity doesn't support merging or including reports from multible machine/platforms (at least not officially).
+# But otherwise there is no good way to keep the issues from all platforms at Coverity Scans at once.
+# This script uses undocumented (but appears to be working) hack:
+#  The build logs from one machine/platform gets moved to a next once,
+#  and "fixed" so that cov-build can append logs from the next platform.
+#  The "fix" is based on the fact, that Coverity perfectly allows appending logs from multiple builds
+#  that are done *on the same host* machine.
+
+on:
+  # On-demand run
+  workflow_dispatch:
+  # Weekly run
+  schedule:
+    - cron: '30 5 * * 0'
+
+jobs:
+  coverity-windows:
+    runs-on: windows-latest
+
+    steps:
+    - uses: actions/checkout@v3
+      with:
+        path: src
+    - name: Setup MSVC
+      uses: TheMrMilchmann/setup-msvc-dev@v2.0.0
+      with:
+        arch: x64
+    - name: Configure
+      run: |
+        cmake -B build -S src -G"NMake Makefiles" -DCMAKE_BUILD_TYPE=RelWithDebInfo -DHIDAPI_WITH_TESTS=ON -DHIDAPI_BUILD_HIDTEST=ON
+    - name: Lookup Coverity Build Tool hash
+      id: coverity-cache-lookup
+      run: |
+        $coverity_hash=Invoke-RestMethod -Uri https://scan.coverity.com/download/cxx/win64 -Method Post -Body @{token='${{ secrets.COVERITY_SCAN_TOKEN }}';project='hidapi';md5=1}
+        echo "coverity_hash=$coverity_hash" >> $Env:GITHUB_OUTPUT
+    - name: Get cached Coverity Build Tool
+      id: cov-build-cache
+      uses: actions/cache@v3
+      with:
+        path: cov-root
+        key: cov-root-cxx-win64-${{ steps.coverity-cache-lookup.outputs.coverity_hash }}
+    - name: Get and configure Coverity
+      if: steps.cov-build-cache.outputs.cache-hit != 'true'
+      run: |
+        Invoke-WebRequest -Uri https://scan.coverity.com/download/cxx/win64 -OutFile coverity.zip -Method Post -Body @{token='${{ secrets.COVERITY_SCAN_TOKEN }}';project='hidapi'}
+        Remove-Item 'cov-root' -Recurse -Force -ErrorAction SilentlyContinue
+        Expand-Archive coverity.zip -DestinationPath cov-root
+
+        $cov_root=Get-ChildItem -Path 'cov-root'
+        $Env:PATH += ";$($cov_root.FullName)\bin"
+        cov-configure -msvc
+    - name: Make Coverity available in PATH
+      run: |
+        $cov_root=Get-ChildItem -Path 'cov-root'
+        echo "$($cov_root.FullName)\bin" >> $Env:GITHUB_PATH
+    - name: Build with Coverity
+      working-directory: build
+      run: |
+        cov-build --dir cov-int nmake
+        Rename-Item ".\cov-int\emit\$(hostname)" hostname
+    - name: Backup Coverity logs
+      uses: actions/upload-artifact@v3
+      with:
+        name: coverity-logs-windows
+        path: build/cov-int
+        retention-days: 7
+
+
+  coverity-macos:
+    runs-on: macos-latest
+    needs: [coverity-windows]
+
+    steps:
+    - uses: actions/checkout@v3
+      with:
+        path: src
+    - name: Install dependencies
+      run: brew install ninja
+    - name: Configure
+      run: |
+        cmake -B build -S src -GNinja -DCMAKE_BUILD_TYPE=RelWithDebInfo -DHIDAPI_WITH_TESTS=ON -DHIDAPI_BUILD_HIDTEST=ON -DCMAKE_C_COMPILER=clang
+    - uses: actions/download-artifact@v3
+      with:
+        name: coverity-logs-windows
+        path: build/cov-int
+    - name: Fixup cov-int
+      run: |
+        rm -f build/cov-int/emit/hostname/emit-db.lock build/cov-int/emit/hostname/emit-db.write-lock
+        mv build/cov-int/emit/hostname build/cov-int/emit/$(hostname)
+    - name: Lookup Coverity Build Tool hash
+      id: coverity-cache-lookup
+      shell: bash
+      run: |
+        hash=$(curl https://scan.coverity.com/download/cxx/Darwin --data "token=${{ secrets.COVERITY_SCAN_TOKEN }}&project=hidapi&md5=1")
+        echo "coverity_hash=${hash}" >> $GITHUB_OUTPUT
+    - name: Get cached Coverity Build Tool
+      id: cov-build-cache
+      uses: actions/cache@v3
+      with:
+        path: cov-root
+        key: cov-root-cxx-Darwin-${{ steps.coverity-cache-lookup.outputs.coverity_hash }}
+    - name: Get and configure Coverity
+      if: steps.cov-build-cache.outputs.cache-hit != 'true'
+      run: |
+        curl https://scan.coverity.com/download/cxx/Darwin --output coverity.dmg --data "token=${{ secrets.COVERITY_SCAN_TOKEN }}&project=hidapi"
+        hdiutil attach coverity.dmg -mountroot coverity
+        export COV_DIR_NAME=$(ls -1 --color=never coverity)
+        rm -rf cov-root
+        mkdir cov-root
+        cp ./coverity/${COV_DIR_NAME}/${COV_DIR_NAME}.sh cov-root/
+        cd cov-root/
+        ./${COV_DIR_NAME}.sh
+        ./bin/cov-configure --clang
+    - name: Make Coverity available in PATH
+      run: echo "$(pwd)/cov-root/bin" >> $GITHUB_PATH
+    - name: Build with Coverity
+      working-directory: build
+      run: |
+        cov-build --dir cov-int --append-log ninja
+        mv cov-int/emit/$(hostname) cov-int/emit/hostname
+    - name: Backup Coverity logs
+      uses: actions/upload-artifact@v3
+      with:
+        name: coverity-logs-windows-macos
+        path: build/cov-int
+        retention-days: 7
+
+
+  coverity-ubuntu:
+    runs-on: ubuntu-latest
+    needs: [coverity-macos]
+
+    steps:
+    - uses: actions/checkout@v3
+      with:
+        path: src
+    - name: Install dependencies
+      run: sudo apt install libudev-dev libusb-1.0-0-dev ninja-build
+    - name: Configure
+      run: |
+        cmake -B build -S src -GNinja -DCMAKE_BUILD_TYPE=RelWithDebInfo -DHIDAPI_WITH_TESTS=ON -DHIDAPI_BUILD_HIDTEST=ON -DCMAKE_C_COMPILER=gcc
+    - uses: actions/download-artifact@v3
+      with:
+        name: coverity-logs-windows-macos
+        path: build/cov-int
+    - name: Fixup cov-int
+      run: |
+        rm -f build/cov-int/emit/hostname/emit-db.lock build/cov-int/emit/hostname/emit-db.write-lock
+        mv build/cov-int/emit/hostname build/cov-int/emit/$(hostname)
+    - name: Lookup Coverity Build Tool hash
+      id: coverity-cache-lookup
+      shell: bash
+      run: |
+        hash=$(curl https://scan.coverity.com/download/cxx/linux64 --data "token=${{ secrets.COVERITY_SCAN_TOKEN }}&project=hidapi&md5=1")
+        echo "coverity_hash=${hash}" >> $GITHUB_OUTPUT
+    - name: Get cached Coverity Build Tool
+      id: cov-build-cache
+      uses: actions/cache@v3
+      with:
+        path: cov-root
+        key: cov-root-cxx-linux64-${{ steps.coverity-cache-lookup.outputs.coverity_hash }}
+    - name: Get and configure Coverity
+      if: steps.cov-build-cache.outputs.cache-hit != 'true'
+      run: |
+        curl https://scan.coverity.com/download/cxx/linux64 --output coverity.tar.gz --data "token=${{ secrets.COVERITY_SCAN_TOKEN }}&project=hidapi"
+        rm -rf cov-root
+        mkdir cov-root
+        tar -xzf coverity.tar.gz --strip 1 -C cov-root
+        ./cov-root/bin/cov-configure --gcc
+    - name: Make Coverity available in PATH
+      run: echo "$(pwd)/cov-root/bin" >> $GITHUB_PATH
+    - name: Build with Coverity
+      working-directory: build
+      run: |
+        cov-build --dir cov-int --append-log ninja
+    - name: Submit results to Coverity Scan
+      working-directory: build
+      run: |
+        tar -czf cov-int.tar.gz cov-int
+        curl --form token=${{ secrets.COVERITY_SCAN_TOKEN }} \
+          --form email=${{ secrets.COVERITY_SCAN_EMAIL }} \
+          --form file=@cov-int.tar.gz \
+          --form version="$GITHUB_SHA" \
+          --form description="Automatic HIDAPI build" \
+          https://scan.coverity.com/builds?project=hidapi
+        mv cov-int/emit/$(hostname) cov-int/emit/hostname
+    - name: Backup Coverity logs
+      uses: actions/upload-artifact@v3
+      with:
+        name: coverity-logs-windows-macos-linux
+        path: build/cov-int
+        retention-days: 7

+ 58 - 0
src/hidapi/.github/workflows/docs.yaml

@@ -0,0 +1,58 @@
+name: Docs
+
+on:
+  push:
+    branches: [master]
+  pull_request:
+    branches: [master]
+
+jobs:
+  build:
+    runs-on: ubuntu-latest
+    steps:
+
+      - name: Install Doxygen static libclang deps
+        run: sudo apt-get install libclang1-12 libclang-cpp12
+
+      - name: Install Doxygen from SF binary archives
+        env:
+          DOXYGEN_VERSION: '1.9.6'
+        run: |
+          mkdir .doxygen && cd .doxygen
+          curl -L https://sourceforge.net/projects/doxygen/files/rel-$DOXYGEN_VERSION/doxygen-$DOXYGEN_VERSION.linux.bin.tar.gz > doxygen.tar.gz
+          gunzip doxygen.tar.gz
+          tar xf doxygen.tar
+          cd doxygen-$DOXYGEN_VERSION
+          sudo make install
+
+      - uses: actions/checkout@v3
+
+      - run: doxygen
+        working-directory: doxygen
+
+      - name: Save doxygen docs as artifact
+        uses: actions/upload-artifact@v3
+        with:
+          name: HIDAPI_doxygen_docs
+          path: ${{ github.workspace }}/doxygen/html
+
+  deploy-docs:
+    runs-on: ubuntu-latest
+    needs: [build]
+    if: github.ref_type == 'branch' && github.ref_name == 'master'
+    concurrency:
+      group: "github-pages-deploy"
+      cancel-in-progress: true
+    steps:
+      - name: downlod artifact
+        uses: actions/download-artifact@v3
+        with:
+          name: HIDAPI_doxygen_docs
+          path: docs
+
+      - name: upload to github pages
+        uses: peaceiris/actions-gh-pages@v3
+        with:
+          github_token: ${{ secrets.GITHUB_TOKEN }}
+          publish_dir: ./docs
+          force_orphan: true

+ 32 - 0
src/hidapi/.gitignore

@@ -0,0 +1,32 @@
+
+# Autotools-added generated files
+Makefile.in
+aclocal.m4
+ar-lib
+autom4te.cache/
+config.*
+configure
+configure~
+compile
+depcomp
+install-sh
+libusb/Makefile.in
+linux/Makefile.in
+ltmain.sh
+mac/Makefile.in
+missing
+testgui/Makefile.in
+windows/Makefile.in
+
+Makefile
+stamp-h1
+libtool
+
+# macOS
+.DS_Store
+
+# Qt Creator
+CMakeLists.txt.user
+
+# doxgen output
+doxygen/html/

+ 3 - 1
src/hidapi/AUTHORS.txt

@@ -10,7 +10,9 @@ Ludovic Rousseau <rousseau@debian.org>:
 	Bug fixes
 	Correctness fixes
 
+libusb/hidapi Team:
+	Development/maintainance since June 4th 2019
 
 For a comprehensive list of contributions, see the commit list at github:
-	https://github.com/libusb/hidapi/commits/master
+	https://github.com/libusb/hidapi/graphs/contributors
 

+ 114 - 0
src/hidapi/BUILD.autotools.md

@@ -0,0 +1,114 @@
+# Building HIDAPI using Autotools (deprecated)
+
+---
+**NOTE**: for all intentions and purposes the Autotools build scripts for HIDAPI are _deprecated_ and going to be obsolete in the future.
+HIDAPI Team recommends using CMake build for HIDAPI.
+If you are already using Autotools build scripts provided by HIDAPI,
+consider switching to CMake build scripts as soon as possible.
+
+---
+
+To be able to use Autotools to build HIDAPI, it has to be [installed](#installing-autotools)/available in the system.
+
+Make sure you've checked [prerequisites](BUILD.md#prerequisites) and installed all required dependencies.
+
+## Installing Autotools
+
+HIDAPI uses few specific tools/packages from Autotools: `autoconf`, `automake`, `libtool`.
+
+On different platforms or package managers, those could be named a bit differently or packaged together.
+You'll have to check the documentation/package list for your specific package manager.
+
+### Linux
+
+On Ubuntu the tools are available via APT:
+
+```sh
+sudo apt install autoconf automake libtool
+```
+
+### FreeBSD
+
+FreeBSD Autotools can be installed as:
+
+```sh
+pkg_add -r autotools
+```
+
+Additionally, on FreeBSD you will need to install GNU make:
+```sh
+pkg_add -r gmake
+```
+
+## Building HIDAPI with Autotools
+
+A simple command list, to build HIDAPI with Autotools as a _shared library_ and install in into your system:
+
+```sh
+./bootstrap # this prepares the configure script
+./configure
+make # build the library
+make install # as root, or using sudo, this will install hidapi into your system
+```
+
+`./configure` can take several arguments which control the build. A few commonly used options:
+```sh
+	--enable-testgui
+		# Enable the build of Foxit-based Test GUI. This requires Fox toolkit to
+		# be installed/available. See README.md#test-gui for remarks.
+
+	--prefix=/usr
+		# Specify where you want the output headers and libraries to
+		# be installed. The example above will put the headers in
+		# /usr/include and the binaries in /usr/lib. The default is to
+		# install into /usr/local which is fine on most systems.
+
+	--disable-shared
+		# By default, both shared and static libraries are going to be built/installed.
+		# This option disables shared library build, if only static library is required.
+```
+
+
+## Cross Compiling
+
+This section talks about cross compiling HIDAPI for Linux using Autotools.
+This is useful for using HIDAPI on embedded Linux targets. These
+instructions assume the most raw kind of embedded Linux build, where all
+prerequisites will need to be built first. This process will of course vary
+based on your embedded Linux build system if you are using one, such as
+OpenEmbedded or Buildroot.
+
+For the purpose of this section, it will be assumed that the following
+environment variables are exported.
+```sh
+$ export STAGING=$HOME/out
+$ export HOST=arm-linux
+```
+
+`STAGING` and `HOST` can be modified to suit your setup.
+
+### Prerequisites
+
+Depending on what backend you want to cross-compile, you also need to prepare the dependencies:
+`libusb` for libusb HIDAPI backend, or `libudev` for hidraw HIDAPI backend.
+
+An example of cross-compiling `libusb`. From `libusb` source directory, run:
+```sh
+./configure --host=$HOST --prefix=$STAGING
+make
+make install
+```
+
+An example of cross-comping `libudev` is not covered by this section.
+Check `libudev`'s documentation for details.
+
+### Building HIDAPI
+
+Build HIDAPI:
+```sh
+PKG_CONFIG_DIR= \
+PKG_CONFIG_LIBDIR=$STAGING/lib/pkgconfig:$STAGING/share/pkgconfig \
+PKG_CONFIG_SYSROOT_DIR=$STAGING \
+./configure --host=$HOST --prefix=$STAGING
+# make / make install - same as for a regular build
+```

+ 280 - 0
src/hidapi/BUILD.cmake.md

@@ -0,0 +1,280 @@
+# Building HIDAPI using CMake
+
+To build HIDAPI with CMake, it has to be [installed](#installing-cmake)/available in the system.
+
+Make sure you've checked [prerequisites](BUILD.md#prerequisites) and installed all required dependencies.
+
+HIDAPI CMake build system allows you to build HIDAPI in two generally different ways:
+1) As a [standalone package/library](#standalone-package-build);
+2) As [part of a larger CMake project](#hidapi-as-a-subdirectory).
+
+**TL;DR**: if you're experienced developer and have been working with CMake projects or have been written some of your own -
+most of this document may not be of interest for you; just check variables names, its default values and the target names.
+
+## Installing CMake
+
+CMake can be installed either using your system's package manager,
+or by downloading an installer/prebuilt version from the [official website](https://cmake.org/download/).
+
+On most \*nix systems, the prefered way to install CMake is via package manager,
+e.g. `sudo apt install cmake`.
+
+On Windows CMake could be provided by your development environment (e.g. by Visual Studio Installer or MinGW installer),
+or you may install it system-wise using the installer from the official website.
+
+On macOS CMake may be installed by Homebrew/MacPorts or using the installer from the official website.
+
+## Standalone package build
+
+To build HIDAPI as a standalone package, you follow [general steps](https://cmake.org/runningcmake/) of building any CMake project.
+
+An example of building HIDAPI with CMake:
+```sh
+# precondition: create a <build dir> somewhere on the filesystem (preferably outside of the HIDAPI source)
+# this is the place where all intermediate/build files are going to be located
+cd <build dir>
+# configure the build
+cmake <HIDAPI source dir>
+# build it!
+cmake --build .
+# install library; by default installs into /usr/local/
+cmake --build . --target install
+# NOTE: you need to run install command as root, to be able to install into /usr/local/
+```
+Such invocation will use the default (as per CMake magic) compiler/build environment available in your system.
+
+You may pass some additional CMake variables to control the build configuration as `-D<CMake Variable>=value`.
+E.g.:
+```sh
+# install command now would install things into /usr
+cmake <HIDAPI source dir> -DCMAKE_INSTALL_PREFIX=/usr
+```
+
+<details>
+  <summary>Using a specific CMake generator</summary>
+
+An example of using `Ninja` as a CMake generator:
+
+```sh
+cd <build dir>
+# configure the build
+cmake -GNinja <HIDAPI source dir>
+# we know, that CMake has generated build files for Ninja,
+# so we can use `ninja` directly, instead of `cmake --build .`
+ninja
+# install library
+ninja install
+```
+
+`-G` here specifies a native build system CMake would generate build files for.
+Check [CMake Documentation](https://cmake.org/cmake/help/latest/manual/cmake-generators.7.html) for a list of available generators (system-specific).
+
+</details><br>
+
+Some of the [standard](https://cmake.org/cmake/help/latest/manual/cmake-variables.7.html) CMake variables you may want to use to configure a build:
+
+- [`CMAKE_INSTALL_PREFIX`](https://cmake.org/cmake/help/latest/variable/CMAKE_INSTALL_PREFIX.html) - prefix where `install` target would install the library(ies);
+- [`CMAKE_BUILD_TYPE`](https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html) - standard possible values: `Debug`, `Release`, `RelWithDebInfo`, `MinSizeRel`; Defaults to `Release` for HIDAPI, if not specified;
+- [`BUILD_SHARED_LIBS`](https://cmake.org/cmake/help/latest/variable/BUILD_SHARED_LIBS.html) - when set to TRUE, HIDAPI is built as a shared library, otherwise build statically; Defaults to `TRUE` for HIDAPI, if not specified;
+
+<details>
+  <summary>macOS-specific variables</summary>
+
+  - [`CMAKE_FRAMEWORK`](https://cmake.org/cmake/help/latest/variable/CMAKE_FRAMEWORK.html) - (since CMake 3.15) when set to TRUE, HIDAPI is built as a framework library, otherwise build as a regular static/shared library; Defaults to `FALSE` for HIDAPI, if not specified;
+  - [`CMAKE_OSX_DEPLOYMENT_TARGET`](https://cmake.org/cmake/help/latest/variable/CMAKE_OSX_DEPLOYMENT_TARGET.html) - minimum version of the target platform (e.g. macOS or iOS) on which the target binaries are to be deployed; defaults to a maximum supported target platform by currently used XCode/Toolchain;
+
+</details><br>
+
+HIDAPI-specific CMake variables:
+
+- `HIDAPI_BUILD_HIDTEST` - when set to TRUE, build a small test application `hidtest`;
+- `HIDAPI_WITH_TESTS` - when set to TRUE, build all (unit-)tests;
+currently this option is only available on Windows, since only Windows backend has tests;
+
+<details>
+  <summary>Linux-specific variables</summary>
+
+  - `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;
+
+  **NOTE**: at least one of `HIDAPI_WITH_HIDRAW` or `HIDAPI_WITH_LIBUSB` has to be set to TRUE.
+
+</details><br>
+
+To see all most-useful CMake variables available for HIDAPI, one of the most convenient ways is too use [`cmake-gui`](https://cmake.org/cmake/help/latest/manual/cmake-gui.1.html) tool ([example](https://cmake.org/runningcmake/)).
+
+_NOTE_: HIDAPI packages built by CMake can be used with `pkg-config`, as if built with [Autotools](BUILD.autotools.md).
+
+### MSVC and Ninja
+It is possible to build a CMake project (including HIDAPI) using MSVC compiler and Ninja (for medium and larger projects it is so much faster than msbuild).
+
+For that:
+1) Open cmd.exe;
+2) Setup MSVC build environment variables, e.g.: `vcvarsall.bat x64`, where:
+	- `vcvarsall.bat` is an environment setup script of your MSVC toolchain installation;<br>For MSVC 2019 Community edition it is located at: `C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Auxiliary\Build\`;
+	- `x64` -a target architecture to build;
+3) Follow general build steps, and use `Ninja` as a generator.
+
+### Using HIDAPI in a CMake project
+
+When HIDAPI is used as a standalone package (either installed into the system or built manually and installed elsewhere), the simplest way to use it is as showed in the example:
+
+```cmake
+project(my_application)
+
+add_executable(my_application main.c)
+
+find_package(hidapi REQUIRED)
+target_link_libraries(my_application PRIVATE hidapi::hidapi)
+```
+
+If HIDAPI isn't installed in your system, or `find_package` cannot find HIDAPI by default for any other reasons,
+the recommended way manually specify which HIDAPI package to use is via `hidapi_ROOT` CMake variable, e.g.:
+`-Dhidapi_ROOT=<path to HIDAPI installation prefix>`.
+
+_NOTE_: usage of `hidapi_ROOT` is only possible (and recommended) with CMake 3.12 and higher. For older versions of CMake you'd need to specify [`CMAKE_PREFIX_PATH`](https://cmake.org/cmake/help/latest/variable/CMAKE_PREFIX_PATH.html#variable:CMAKE_PREFIX_PATH) instead.
+
+Check with [`find_package`](https://cmake.org/cmake/help/latest/command/find_package.html) documentation if you need more details.
+
+Available CMake targets after successful `find_package(hidapi)`:
+- `hidapi::hidapi` - indented to be used in most cases;
+- `hidapi::include` - if you need only to include `<hidapi.h>` but not link against the library;
+- `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;
+
+**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:
+```cmake
+if(TARGET hidapi::libusb)
+    target_link_libraries(my_project PRIVATE hidapi::libusb)
+else()
+    target_link_libraries(my_project PRIVATE hidapi::hidapi)
+endif()
+```
+
+## HIDAPI as a subdirectory
+
+HIDAPI can be easily used as a subdirectory of a larger CMake project:
+```cmake
+# root CMakeLists.txt
+cmake_minimum_required(VERSION 3.4.3 FATAL_ERROR)
+
+add_subdirectory(hidapi)
+add_subdirectory(my_application)
+
+# my_application/CMakeLists.txt
+project(my_application)
+
+add_executable(my_application main.c)
+
+# NOTE: no `find_package` is required, since HIDAPI targets are already a part of the project tree
+target_link_libraries(my_application PRIVATE hidapi::hidapi)
+```
+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(BUILD_SHARED_LIBS FALSE) # HIDAPI as static library on all platforms
+add_subdirectory(hidapi)
+```
+
+<details>
+  <summary>NOTE</summary>
+
+  If you project happen to use `BUILD_SHARED_LIBS` as a `CACHE` variable globally for you project, setting it as simple variable, as showed above _will have not affect_ up until _CMake 3.13_. See [CMP0077](https://cmake.org/cmake/help/latest/policy/CMP0077.html) for details.
+</details><br>
+
+There are several important differences in the behavior of HIDAPI CMake build system when CMake is built as standalone package vs subdirectory build:
+
+1) In _standalone build_ a number of standard and HIDAPI-specific variables are marked as _cache variables_ or _options_.
+This is done for convenience: when you're building HIDAPI as a standalone package and using tools like `cmake-gui` - those are highlighted as variables that can be changed and has some short description/documentation. E.g.:
+![an example of highlighted variables in cmake-gui](documentation/cmake-gui-highlights.png "cmake-gui highlighted variables")<br>
+E.g.2:<br>
+![an example of drop-down menu in cmake-gui](documentation/cmake-gui-drop-down.png "cmake-gui drop-down menu")<br>
+When HIDAPI is built as a _subdirectory_ - **_none of the variables are marked for cache or as options_** by HIDAPI.
+This is done to let the host project's developer decide what is important (what needs to be highlighted) and what's not.
+
+2) The default behavior/default value for some of the variables is a bit different:
+	- by default, none of HIDAPI targets are [installed](https://cmake.org/cmake/help/latest/command/install.html); if required, HIDAPI targets can be installed by host project _after_ including HIDAPI subdirectory (requires CMake 3.13 or later); **or**, the default installation can be enabled by setting `HIDAPI_INSTALL_TARGETS` variable _before_ including HIDAPI subdirectory.
+		HIDAPI uses [GNUInstallDirs](https://cmake.org/cmake/help/latest/module/GNUInstallDirs.html) to specify install locations. Variables like `CMAKE_INSTALL_LIBDIR` can be used to control HIDAPI's installation locations. E.g.:
+		```cmake
+		# enable the installation if you need it
+		set(HIDAPI_INSTALL_TARGETS ON)
+		# (optionally) change default installation locations if it makes sense for your target platform, etc.
+		set(CMAKE_INSTALL_LIBDIR "lib64")
+		add_subdirectory(hidapi)
+		```
+	- HIDAPI prints its version during the configuration when built as a standalone package; to enable this for subdirectory builds - set `HIDAPI_PRINT_VERSION` to TRUE before including HIDAPI;
+
+3) In a subdirectory build, HIDAPI _doesn't modify or set any of the CMake variables_ that may change the build behavior.
+    For instance, in a _standalone build_, if CMAKE_BUILD_TYPE or BUILD_SHARED_LIBS variables are not set, those are defaulted to "Release" and "TRUE" explicitly.
+    In a _subdirectory build_, even if not set, those variables remain unchanged, so a host project's developer has a full control over the HIDAPI build configuration.
+
+Available CMake targets after `add_subdirectory(hidapi)` _are the same as in case of [standalone build](#standalone-package-build)_, and a few additional ones:
+- `hidapi_include` - the interface library; `hidapi::hidapi` is an alias of it;
+- `hidapi_winapi` - library target on Windows; `hidapi::winapi` is an alias of it;
+- `hidapi_darwin` - library target on macOS; `hidapi::darwin` is an alias of it;
+- `hidapi_libusb` - library target for libusb backend; `hidapi::libusb` is an alias of it;
+- `hidapi_hidraw` - library target for hidraw backend; `hidapi::hidraw` is an alias of it;
+- `hidapi-libusb` - an alias of `hidapi_libusb` for compatibility with raw library name;
+- `hidapi-hidraw` - an alias of `hidapi_hidraw` for compatibility with raw library name;
+- `hidapi` - an alias of `hidapi_winapi` or `hidapi_darwin` on Windows or macOS respectfully.
+
+Advanced:
+- Why would I need additional targets described in this section above, if I already have alias targets compatible with `find_package`?
+  - an example:
+    ```cmake
+    add_subdirectory(hidapi)
+    if(TARGET hidapi_libusb)
+      # see libusb/hid.c for usage of `NO_ICONV`
+      target_compile_definitions(hidapi_libusb PRIVATE NO_ICONV)
+    endif()
+    ```
+
+## Both Shared and Static build
+
+If you're a former (or present) user of Autotools build scripts for HIDAPI, or you're a package manager maintainer and you're often working with those - you're likely asking how to build HIDAPI with CMake and get both Shared and Static libraries (as would be done by Autotools: `./configure --enable-static --enable-shared ...`).
+
+CMake doesn't have such option of-the-box and it is decided not to introduce any manual CMake-level workarounds for HIDAPI on this matter.
+
+If you want to mimic the Autotools behavior, it is possible by building/installing first the static version of the library and then shared version of the library. The installation folder (`CMAKE_INSTALL_PREFIX`) should point to the same directory for both variants, that way:
+- both static and shared library binaries will be available and usable;
+- a single header file(s) for both of them;
+- Autotools/pkg-config (`.pc`) files will be generated and usable _as if_ generated by Autotools natively and build configured with both `-enable-static --enable-shared` options;
+- CMake package scripts will be generated and fully usable, but _only the last build installed_, i.e. if the last was installed Shared version of the binary - CMake targets found by `find_package(hidapi)` would point to a Shared binaries.
+
+There is a historical discussion, why such solution is simplest/preferable: https://github.com/libusb/hidapi/issues/424
+
+#### TL;DR/Sample
+
+```sh
+# First - configure/build
+
+# Static libraries
+cmake -S <HIDAPI source dir> -B "<build dir>/static" -DCMAKE_INSTALL_PREFIX=<your installation prefix> -DBUILD_SHARED_LIBS=FALSE
+cmake --build "<build dir>/static"
+# Shared libraries
+cmake -S <HIDAPI source dir> -B "<build dir>/shared" -DCMAKE_INSTALL_PREFIX=<your installation prefix> -DBUILD_SHARED_LIBS=TRUE
+cmake --build "<build dir>/shared"
+
+# (Optionally) change the installation destination.
+# NOTE1: this is supported by CMake only on UNIX platforms
+#  See https://cmake.org/cmake/help/latest/envvar/DESTDIR.html
+# NOTE2: this is not the same as `CMAKE_INSTALL_PREFIX` set above
+# NOTE3: this is only required if you have a staging dir other than the final runtime dir,
+#  e.g. during cross-compilation
+export DESTDIR="$STAGING_DIR"
+
+#
+# Install the libraries
+# NOTE: order of installation matters - install Shared variant *the last*
+
+# Static libraries
+cmake --install "<build dir>/static"
+# Shared libraries
+cmake --install "<build dir>/shared"
+
+```

+ 127 - 0
src/hidapi/BUILD.md

@@ -0,0 +1,127 @@
+# Building HIDAPI from Source
+
+## Table of content
+
+* [Intro](#intro)
+* [Prerequisites](#prerequisites)
+    * [Linux](#linux)
+    * [FreeBSD](#freebsd)
+    * [Mac](#mac)
+    * [Windows](#windows)
+* [Embedding HIDAPI directly into your source tree](#embedding-hidapi-directly-into-your-source-tree)
+* [Building the manual way on Unix platforms](#building-the-manual-way-on-unix-platforms)
+* [Building on Windows](#building-on-windows)
+
+## Intro
+
+For various reasons, you may need to build HIDAPI on your own.
+
+It can be done in several different ways:
+- using [CMake](BUILD.cmake.md);
+- using [Autotools](BUILD.autotools.md) (deprecated);
+- using [manual makefiles](#building-the-manual-way-on-unix-platforms);
+- using `Meson` (requires CMake);
+
+**Autotools** build system is historically the first mature build system for
+HIDAPI. The most common usage of it is in its separate README: [BUILD.autotools.md](BUILD.autotools.md).<br/>
+NOTE: for all intentions and purposes the Autotools build scripts for HIDAPI are _deprecated_ and going to be obsolete in the future.
+HIDAPI Team recommends using CMake build for HIDAPI.
+
+**CMake** build system is de facto an industry standard for many open-source and proprietary projects and solutions.
+HIDAPI is one of the projects which use the power of CMake to its advantage.
+More documentation is available in its separate README: [BUILD.cmake.md](BUILD.cmake.md).
+
+**Meson** build system for HIDAPI is designed as a [wrapper](https://mesonbuild.com/CMake-module.html) over CMake build script.
+It is present for the convenience of Meson users who need to use HIDAPI and need to be sure HIDAPI is built in accordance with officially supported build scripts.<br>
+In the Meson script of your project you need a `hidapi = subproject('hidapi')` subproject, and `hidapi.get_variable('hidapi_dep')` as your dependency.
+There are also backend/platform-specific dependencies available: `hidapi_winapi`, `hidapi_darwin`, `hidapi_hidraw`, `hidapi_libusb`.
+
+If you don't know where to start to build HIDAPI, we recommend starting with [CMake](BUILD.cmake.md) build.
+
+## Prerequisites:
+
+Regardless of what build system you choose to use, there are specific dependencies for each platform/backend.
+
+### Linux:
+
+Depending on which backend you're going to build, you'll need to install
+additional development packages. For `linux/hidraw` backend, you need a
+development package for `libudev`. For `libusb` backend, naturally, you need
+`libusb` development package.
+
+On Debian/Ubuntu systems these can be installed by running:
+```sh
+# required only by hidraw backend
+sudo apt install libudev-dev
+# required only by libusb backend
+sudo apt install libusb-1.0-0-dev
+```
+
+### FreeBSD:
+
+On FreeBSD, you will need to install libiconv. This is done by running
+the following:
+```sh
+pkg_add -r libiconv
+```
+
+### Mac:
+
+Make sure you have XCode installed and its Command Line Tools.
+
+### Windows:
+
+You just need a compiler. You may use Visual Studio or Cygwin/MinGW,
+depending on which environment is best for your needs.
+
+## Embedding HIDAPI directly into your source tree
+
+Instead of using one of the provided standalone build systems,
+you may want to integrate HIDAPI directly into your source tree.
+
+---
+If your project uses CMake as a build system, it is safe to add HIDAPI as a [subdirectory](BUILD.cmake.md#hidapi-as-a-subdirectory).
+
+---
+If _the only option_ that works for you is adding HIDAPI sources directly
+to your project's build system, then you need:
+- include a _single source file_ into your project's build system,
+depending on your platform and the backend you want to use:
+    - [`windows\hid.c`](windows/hid.c);
+    - [`linux/hid.c`](linux/hid.c);
+    - [`libusb/hid.c`](libusb/hid.c);
+    - [`mac/hid.c`](mac/hid.c);
+- add a [`hidapi`](hidapi) folder to the include path when building `hid.c`;
+- make the platform/backend specific [dependencies](#prerequisites) available during the compilation/linking, when building `hid.c`;
+
+NOTE: the above doesn't guarantee that having a copy of `<backend>/hid.c` and `hidapi/hidapi.h` is enough to build HIDAPI.
+The only guarantee that `<backend>/hid.c` includes all necessary sources to compile it as a single file.
+
+Check the manual makefiles for a simple example/reference of what are the dependencies of each specific backend.
+
+## Building the manual way on Unix platforms
+
+Manual Makefiles are provided mostly to give the user an idea what it takes
+to build a program which embeds HIDAPI directly inside of it. These should
+really be used as examples only. If you want to build a system-wide shared
+library, use one of the build systems mentioned above.
+
+To build HIDAPI using the manual Makefiles, change the directory
+of your platform and run make. For example, on Linux run:
+```sh
+cd linux/
+make -f Makefile-manual
+```
+
+## Building on Windows
+
+To build the HIDAPI DLL on Windows using Visual Studio, build the `.sln` file
+in the `windows/` directory.
+
+To build HIDAPI using MinGW or Cygwin using Autotools, use general Autotools
+ [instruction](BUILD.autotools.md).
+
+Any windows builds (MSVC or MinGW/Cygwin) are also supported by [CMake](BUILD.cmake.md).
+
+If you are looking for information regarding DDK build of HIDAPI:
+- the build has been broken for a while and now the support files are obsolete.

+ 105 - 0
src/hidapi/CMakeLists.txt

@@ -0,0 +1,105 @@
+cmake_minimum_required(VERSION 3.1.3 FATAL_ERROR)
+
+if(NOT CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR)
+    add_subdirectory(src)
+    # compatibility with find_package() vs add_subdirectory
+    set(hidapi_VERSION "${hidapi_VERSION}" PARENT_SCOPE)
+    return()
+endif()
+# All of the below in this file is meant for a standalone build.
+# When building as a subdirectory of a larger project, most of the options may not make sense for it,
+# so it is up to developer to configure those, e.g.:
+#
+# # a subfolder of a master project, e.g.: 3rdparty/hidapi/CMakeLists.txt
+#
+# set(HIDAPI_WITH_HIDRAW OFF)
+# set(CMAKE_FRAMEWORK ON)
+# # and keep everything else to their defaults
+# add_subdirectory(hidapi)
+#
+
+set(DEFAULT_CMAKE_BUILD_TYPES "Debug" "Release" "MinSizeRel" "RelWithDebInfo")
+if(NOT DEFINED CMAKE_BUILD_TYPE OR NOT CMAKE_BUILD_TYPE)
+    set(CMAKE_BUILD_TYPE "Release" CACHE STRING "${DEFAULT_CMAKE_BUILD_TYPES}" FORCE)
+endif()
+# This part is for convenience, when used one of the standard build types with cmake-gui
+list(FIND DEFAULT_CMAKE_BUILD_TYPES "${CMAKE_BUILD_TYPE}" _build_type_index)
+if(${_build_type_index} GREATER -1)
+    # set it optionally, so a custom CMAKE_BUILD_TYPE can be used as well, if needed
+    set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS ${DEFAULT_CMAKE_BUILD_TYPES})
+endif()
+unset(_build_type_index)
+#
+
+project(hidapi LANGUAGES C)
+
+if(APPLE)
+    if(NOT CMAKE_VERSION VERSION_LESS "3.15")
+        option(CMAKE_FRAMEWORK "Build macOS/iOS Framework version of the library" OFF)
+    endif()
+elseif(NOT WIN32)
+    if(CMAKE_SYSTEM_NAME MATCHES "Linux")
+        option(HIDAPI_WITH_HIDRAW "Build HIDRAW-based implementation of HIDAPI" ON)
+        option(HIDAPI_WITH_LIBUSB "Build LIBUSB-based implementation of HIDAPI" ON)
+    endif()
+endif()
+
+option(BUILD_SHARED_LIBS "Build shared version of the libraries, otherwise build statically" ON)
+
+set(HIDAPI_INSTALL_TARGETS ON)
+set(HIDAPI_PRINT_VERSION ON)
+
+set(IS_DEBUG_BUILD OFF)
+if(CMAKE_BUILD_TYPE STREQUAL "Debug")
+    set(IS_DEBUG_BUILD ON)
+endif()
+
+option(HIDAPI_ENABLE_ASAN "Build HIDAPI with ASAN address sanitizer instrumentation" OFF)
+
+if(HIDAPI_ENABLE_ASAN)
+    set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fsanitize=address")
+    if(MSVC)
+        # the default is to have "/INCREMENTAL" which causes a warning when "-fsanitize=address" is present
+        set(CMAKE_EXE_LINKER_FLAGS_DEBUG "${CMAKE_EXE_LINKER_FLAGS_DEBUG} /INCREMENTAL:NO")
+        set(CMAKE_SHARED_LINKER_FLAGS_DEBUG "${CMAKE_SHARED_LINKER_FLAGS_DEBUG} /INCREMENTAL:NO")
+        set(CMAKE_EXE_LINKER_FLAGS_RELWITHDEBINFO "${CMAKE_EXE_LINKER_FLAGS_RELWITHDEBINFO} /INCREMENTAL:NO")
+        set(CMAKE_SHARED_LINKER_FLAGS_RELWITHDEBINFO "${CMAKE_SHARED_LINKER_FLAGS_RELWITHDEBINFO} /INCREMENTAL:NO")
+    endif()
+endif()
+
+if(WIN32)
+    # so far only Windows has tests
+    option(HIDAPI_WITH_TESTS "Build HIDAPI (unit-)tests" ${IS_DEBUG_BUILD})
+else()
+    set(HIDAPI_WITH_TESTS OFF)
+endif()
+
+if(HIDAPI_WITH_TESTS)
+    enable_testing()
+endif()
+
+if(WIN32)
+    option(HIDAPI_BUILD_PP_DATA_DUMP "Build small Windows console application pp_data_dump.exe" ${IS_DEBUG_BUILD})
+endif()
+
+add_subdirectory(src)
+
+option(HIDAPI_BUILD_HIDTEST "Build small console test application hidtest" ${IS_DEBUG_BUILD})
+if(HIDAPI_BUILD_HIDTEST)
+    add_subdirectory(hidtest)
+endif()
+
+if(HIDAPI_ENABLE_ASAN)
+    if(NOT MSVC)
+        # MSVC doesn't recognize those options, other compilers - requiring it
+        foreach(HIDAPI_TARGET hidapi_winapi hidapi_darwin hidapi_hidraw hidapi_libusb hidtest_hidraw hidtest_libusb hidtest)
+            if(TARGET ${HIDAPI_TARGET})
+                if(BUILD_SHARED_LIBS)
+                    target_link_options(${HIDAPI_TARGET} PRIVATE -fsanitize=address)
+                else()
+                    target_link_options(${HIDAPI_TARGET} PUBLIC -fsanitize=address)
+                endif()
+            endif()
+        endforeach()
+    endif()
+endif()

+ 15 - 11
src/hidapi/HACKING.txt

@@ -1,15 +1,19 @@
 This file is mostly for the maintainer.
 
-1. Build hidapi.dll
-2. Build hidtest.exe in DEBUG and RELEASE
-3. Commit all
+Updating a Version:
+1. Update VERSION file.
+2. HID_API_VERSION_MAJOR/HID_API_VERSION_MINOR/HID_API_VERSION_PATCH in hidapi.h.
 
-4. Run the Following
-	export VERSION=0.1.0
-	export TAG_NAME=hidapi-$VERSION
-	git tag $TAG_NAME
-	git archive --format zip --prefix $TAG_NAME/ $TAG_NAME >../$TAG_NAME.zip
-5. Test the zip file.
-6. Run the following:
-	git push origin $TAG_NAME
+Before firing a new release:
+1. Run the "Checks" Githtub Action
+2. Make sure no defects are found at: https://scan.coverity.com/projects/hidapi
+3. Fix if any
 
+Firing a new release:
+1. Update the Version (if not yet updated).
+2. Prepare the Release Notes.
+3. Store the Release Notes into a file.
+4. Create locally an annotated git tag with release notes attached, e.g.: `git tag -aF ../hidapi_release_notes hidapi-<VERSION>`
+5. Push newly created tag: `git push origin hidapi-<VERSION>`
+6. Grab the hidapi-win.zip from Summary page of "GitHub Builds" Action for latest master build.
+7. Create a Github Release with hidapi-win.zip attached, for newly created tag.

+ 5 - 5
src/hidapi/Makefile.am

@@ -23,10 +23,6 @@ if OS_DARWIN
 SUBDIRS += mac
 endif
 
-if OS_IOS
-SUBDIRS += ios
-endif
-
 if OS_FREEBSD
 SUBDIRS += libusb
 endif
@@ -35,6 +31,10 @@ if OS_KFREEBSD
 SUBDIRS += libusb
 endif
 
+if OS_HAIKU
+SUBDIRS += libusb
+endif
+
 if OS_WINDOWS
 SUBDIRS += windows
 endif
@@ -48,7 +48,7 @@ endif
 EXTRA_DIST = udev doxygen
 
 dist_doc_DATA = \
- README.txt \
+ README.md \
  AUTHORS.txt \
  LICENSE-bsd.txt \
  LICENSE-gpl3.txt \

+ 196 - 0
src/hidapi/README.md

@@ -0,0 +1,196 @@
+## HIDAPI library for Windows, Linux, FreeBSD and macOS
+
+| CI instance          | Status |
+|----------------------|--------|
+| `Linux/macOS/Windows (master)` | [![GitHub Builds](https://github.com/libusb/hidapi/workflows/GitHub%20Builds/badge.svg?branch=master)](https://github.com/libusb/hidapi/actions/workflows/builds.yml?query=branch%3Amaster) |
+| `Windows (master)` | [![Build status](https://ci.appveyor.com/api/projects/status/xfmr5fo8w0re8ded/branch/master?svg=true)](https://ci.appveyor.com/project/libusb/hidapi/branch/master) |
+| `BSD, last build (branch/PR)` | [![builds.sr.ht status](https://builds.sr.ht/~z3ntu/hidapi.svg)](https://builds.sr.ht/~z3ntu/hidapi) |
+| `Coverity Scan (last)` | ![Coverity Scan](https://scan.coverity.com/projects/583/badge.svg) |
+
+HIDAPI is a multi-platform library which allows an application to interface
+with USB and Bluetooth HID-Class devices on Windows, Linux, FreeBSD, and macOS.
+HIDAPI can be either built as a shared library (`.so`, `.dll` or `.dylib`) or
+can be embedded directly into a target application by adding a _single source_
+file (per platform) and a single header.<br>
+See [remarks](BUILD.md#embedding-hidapi-directly-into-your-source-tree) on embedding _directly_ into your build system.
+
+HIDAPI library was originally developed by Alan Ott ([signal11](https://github.com/signal11)).
+
+It was moved to [libusb/hidapi](https://github.com/libusb/hidapi) on June 4th, 2019, in order to merge important bugfixes and continue development of the library.
+
+## Table of Contents
+
+* [About](#about)
+    * [Test GUI](#test-gui)
+    * [Console Test App](#console-test-app)
+* [What Does the API Look Like?](#what-does-the-api-look-like)
+* [License](#license)
+* [Installing HIDAPI](#installing-hidapi)
+* [Build from Source](#build-from-source)
+
+
+## About
+
+### HIDAPI has four back-ends:
+* Windows (using `hid.dll`)
+* Linux/hidraw (using the Kernel's hidraw driver)
+* libusb (using libusb-1.0 - Linux/BSD/other UNIX-like systems)
+* macOS (using IOHidManager)
+
+On Linux, either the hidraw or the libusb back-end can be used. There are
+tradeoffs, and the functionality supported is slightly different. Both are
+built by default. It is up to the application linking to hidapi to choose
+the backend at link time by linking to either `libhidapi-libusb` or
+`libhidapi-hidraw`.
+
+Note that you will need to install an udev rule file with your application
+for unprivileged users to be able to access HID devices with hidapi. Refer
+to the [69-hid.rules](udev/69-hid.rules) file in the `udev` directory
+for an example.
+
+#### __Linux/hidraw__ (`linux/hid.c`):
+
+This back-end uses the hidraw interface in the Linux kernel, and supports
+both USB and Bluetooth HID devices. It requires kernel version at least 2.6.39
+to build. In addition, it will only communicate with devices which have hidraw
+nodes associated with them.
+Keyboards, mice, and some other devices which are blacklisted from having
+hidraw nodes will not work. Fortunately, for nearly all the uses of hidraw,
+this is not a problem.
+
+#### __Linux/FreeBSD/libusb__ (`libusb/hid.c`):
+
+This back-end uses libusb-1.0 to communicate directly to a USB device. This
+back-end will of course not work with Bluetooth devices.
+
+### Test GUI
+
+HIDAPI also comes with a Test GUI. The Test GUI is cross-platform and uses
+Fox Toolkit <http://www.fox-toolkit.org>.  It will build on every platform
+which HIDAPI supports.  Since it relies on a 3rd party library, building it
+is optional but it is useful when debugging hardware.
+
+NOTE: Test GUI based on Fox Toolkit is not actively developed nor supported
+by HIDAPI team. It is kept as a historical artifact. It may even work sometime
+or on some platforms, but it is not going to get any new features or bugfixes.
+
+Instructions for installing Fox-Toolkit on each platform is not provided.
+Make sure to use Fox-Toolkit v1.6 if you choose to use it.
+
+### Console Test App
+
+If you want to play around with your HID device before starting
+any development with HIDAPI and using a GUI app is not an option for you, you may try [`hidapitester`](https://github.com/todbot/hidapitester).
+
+This app has a console interface for most of the features supported
+by HIDAPI library.
+
+## What Does the API Look Like?
+
+The API provides the most commonly used HID functions including sending
+and receiving of input, output, and feature reports. The sample program,
+which communicates with a heavily hacked up version of the Microchip USB
+Generic HID sample looks like this (with error checking removed for
+simplicity):
+
+**Warning: Only run the code you understand, and only when it conforms to the
+device spec. Writing data (`hid_write`) at random to your HID devices can break them.**
+
+```c
+#include <stdio.h> // printf
+#include <wchar.h> // wchar_t
+
+#include <hidapi.h>
+
+#define MAX_STR 255
+
+int main(int argc, char* argv[])
+{
+	int res;
+	unsigned char buf[65];
+	wchar_t wstr[MAX_STR];
+	hid_device *handle;
+	int i;
+
+	// Initialize the hidapi library
+	res = hid_init();
+
+	// Open the device using the VID, PID,
+	// and optionally the Serial number.
+	handle = hid_open(0x4d8, 0x3f, NULL);
+	if (!handle) {
+		printf("Unable to open device\n");
+		hid_exit();
+ 		return 1;
+	}
+
+	// Read the Manufacturer String
+	res = hid_get_manufacturer_string(handle, wstr, MAX_STR);
+	printf("Manufacturer String: %ls\n", wstr);
+
+	// Read the Product String
+	res = hid_get_product_string(handle, wstr, MAX_STR);
+	printf("Product String: %ls\n", wstr);
+
+	// Read the Serial Number String
+	res = hid_get_serial_number_string(handle, wstr, MAX_STR);
+	printf("Serial Number String: (%d) %ls\n", wstr[0], wstr);
+
+	// Read Indexed String 1
+	res = hid_get_indexed_string(handle, 1, wstr, MAX_STR);
+	printf("Indexed String 1: %ls\n", wstr);
+
+	// Toggle LED (cmd 0x80). The first byte is the report number (0x0).
+	buf[0] = 0x0;
+	buf[1] = 0x80;
+	res = hid_write(handle, buf, 65);
+
+	// Request state (cmd 0x81). The first byte is the report number (0x0).
+	buf[0] = 0x0;
+	buf[1] = 0x81;
+	res = hid_write(handle, buf, 65);
+
+	// Read requested state
+	res = hid_read(handle, buf, 65);
+
+	// Print out the returned buffer.
+	for (i = 0; i < 4; i++)
+		printf("buf[%d]: %d\n", i, buf[i]);
+
+	// Close the device
+	hid_close(handle);
+
+	// Finalize the hidapi library
+	res = hid_exit();
+
+	return 0;
+}
+```
+
+You can also use [hidtest/test.c](hidtest/test.c)
+as a starting point for your applications.
+
+
+## License
+
+HIDAPI may be used by one of three licenses as outlined in [LICENSE.txt](LICENSE.txt).
+
+## Installing HIDAPI
+
+If you want to build your own application that uses HID devices with HIDAPI,
+you need to get HIDAPI development package.
+
+Depending on what your development environment is, HIDAPI likely to be provided
+by your package manager.
+
+For instance on Ubuntu, HIDAPI is available via APT:
+```sh
+sudo apt install libhidapi-dev
+```
+
+HIDAPI package name for other systems/package managers may differ.
+Check the documentation/package list of your package manager.
+
+## Build from Source
+
+Check [BUILD.md](BUILD.md) for details.

+ 0 - 339
src/hidapi/README.txt

@@ -1,339 +0,0 @@
-         HIDAPI library for Windows, Linux, FreeBSD and Mac OS X
-        =========================================================
-
-About
-======
-
-HIDAPI is a multi-platform library which allows an application to interface
-with USB and Bluetooth HID-Class devices on Windows, Linux, FreeBSD, and Mac
-OS X.  HIDAPI can be either built as a shared library (.so or .dll) or
-can be embedded directly into a target application by adding a single source
-file (per platform) and a single header.
-
-HIDAPI has four back-ends:
-	* Windows (using hid.dll)
-	* Linux/hidraw (using the Kernel's hidraw driver)
-	* Linux/libusb (using libusb-1.0)
-	* FreeBSD (using libusb-1.0)
-	* Mac (using IOHidManager)
-
-On Linux, either the hidraw or the libusb back-end can be used. There are
-tradeoffs, and the functionality supported is slightly different.
-
-Linux/hidraw (linux/hid.c):
-This back-end uses the hidraw interface in the Linux kernel.  While this
-back-end will support both USB and Bluetooth, it has some limitations on
-kernels prior to 2.6.39, including the inability to send or receive feature
-reports.  In addition, it will only communicate with devices which have
-hidraw nodes associated with them.  Keyboards, mice, and some other devices
-which are blacklisted from having hidraw nodes will not work. Fortunately,
-for nearly all the uses of hidraw, this is not a problem.
-
-Linux/FreeBSD/libusb (libusb/hid.c):
-This back-end uses libusb-1.0 to communicate directly to a USB device. This
-back-end will of course not work with Bluetooth devices.
-
-HIDAPI also comes with a Test GUI. The Test GUI is cross-platform and uses
-Fox Toolkit (http://www.fox-toolkit.org).  It will build on every platform
-which HIDAPI supports.  Since it relies on a 3rd party library, building it
-is optional but recommended because it is so useful when debugging hardware.
-
-What Does the API Look Like?
-=============================
-The API provides the the most commonly used HID functions including sending
-and receiving of input, output, and feature reports.  The sample program,
-which communicates with a heavily hacked up version of the Microchip USB
-Generic HID sample looks like this (with error checking removed for
-simplicity):
-
-#ifdef WIN32
-#include <windows.h>
-#endif
-#include <stdio.h>
-#include <stdlib.h>
-#include "hidapi.h"
-
-#define MAX_STR 255
-
-int main(int argc, char* argv[])
-{
-	int res;
-	unsigned char buf[65];
-	wchar_t wstr[MAX_STR];
-	hid_device *handle;
-	int i;
-
-	// Initialize the hidapi library
-	res = hid_init();
-
-	// Open the device using the VID, PID,
-	// and optionally the Serial number.
-	handle = hid_open(0x4d8, 0x3f, NULL);
-
-	// Read the Manufacturer String
-	res = hid_get_manufacturer_string(handle, wstr, MAX_STR);
-	wprintf(L"Manufacturer String: %s\n", wstr);
-
-	// Read the Product String
-	res = hid_get_product_string(handle, wstr, MAX_STR);
-	wprintf(L"Product String: %s\n", wstr);
-
-	// Read the Serial Number String
-	res = hid_get_serial_number_string(handle, wstr, MAX_STR);
-	wprintf(L"Serial Number String: (%d) %s\n", wstr[0], wstr);
-
-	// Read Indexed String 1
-	res = hid_get_indexed_string(handle, 1, wstr, MAX_STR);
-	wprintf(L"Indexed String 1: %s\n", wstr);
-
-	// Toggle LED (cmd 0x80). The first byte is the report number (0x0).
-	buf[0] = 0x0;
-	buf[1] = 0x80;
-	res = hid_write(handle, buf, 65);
-
-	// Request state (cmd 0x81). The first byte is the report number (0x0).
-	buf[0] = 0x0;
-	buf[1] = 0x81;
-	res = hid_write(handle, buf, 65);
-
-	// Read requested state
-	res = hid_read(handle, buf, 65);
-
-	// Print out the returned buffer.
-	for (i = 0; i < 4; i++)
-		printf("buf[%d]: %d\n", i, buf[i]);
-
-	// Finalize the hidapi library
-	res = hid_exit();
-
-	return 0;
-}
-
-If you have your own simple test programs which communicate with standard
-hardware development boards (such as those from Microchip, TI, Atmel,
-FreeScale and others), please consider sending me something like the above
-for inclusion into the HIDAPI source.  This will help others who have the
-same hardware as you do.
-
-License
-========
-HIDAPI may be used by one of three licenses as outlined in LICENSE.txt.
-
-Download
-=========
-HIDAPI can be downloaded from github
-	git clone git://github.com/libusb/hidapi.git
-
-Build Instructions
-===================
-
-This section is long. Don't be put off by this. It's not long because it's
-complicated to build HIDAPI; it's quite the opposite.  This section is long
-because of the flexibility of HIDAPI and the large number of ways in which
-it can be built and used.  You will likely pick a single build method.
-
-HIDAPI can be built in several different ways. If you elect to build a
-shared library, you will need to build it from the HIDAPI source
-distribution.  If you choose instead to embed HIDAPI directly into your
-application, you can skip the building and look at the provided platform
-Makefiles for guidance.  These platform Makefiles are located in linux/
-libusb/ mac/ and windows/ and are called Makefile-manual.  In addition,
-Visual Studio projects are provided.  Even if you're going to embed HIDAPI
-into your project, it is still beneficial to build the example programs.
-
-
-Prerequisites:
----------------
-
-	Linux:
-	-------
-	On Linux, you will need to install development packages for libudev,
-	libusb and optionally Fox-toolkit (for the test GUI). On
-	Debian/Ubuntu systems these can be installed by running:
-	    sudo apt-get install libudev-dev libusb-1.0-0-dev libfox-1.6-dev
-
-	If you downloaded the source directly from the git repository (using
-	git clone), you'll need Autotools:
-	    sudo apt-get install autotools-dev autoconf automake libtool
-
-	FreeBSD:
-	---------
-	On FreeBSD you will need to install GNU make, libiconv, and
-	optionally Fox-Toolkit (for the test GUI). This is done by running
-	the following:
-	    pkg_add -r gmake libiconv fox16
-
-	If you downloaded the source directly from the git repository (using
-	git clone), you'll need Autotools:
-	    pkg_add -r autotools
-
-	Mac:
-	-----
-	On Mac, you will need to install Fox-Toolkit if you wish to build
-	the Test GUI. There are two ways to do this, and each has a slight
-	complication. Which method you use depends on your use case.
-
-	If you wish to build the Test GUI just for your own testing on your
-	own computer, then the easiest method is to install Fox-Toolkit
-	using ports:
-		sudo port install fox
-
-	If you wish to build the TestGUI app bundle to redistribute to
-	others, you will need to install Fox-toolkit from source.  This is
-	because the version of fox that gets installed using ports uses the
-	ports X11 libraries which are not compatible with the Apple X11
-	libraries.  If you install Fox with ports and then try to distribute
-	your built app bundle, it will simply fail to run on other systems.
-	To install Fox-Toolkit manually, download the source package from
-	http://www.fox-toolkit.org, extract it, and run the following from
-	within the extracted source:
-		./configure && make && make install
-
-	Windows:
-	---------
-	On Windows, if you want to build the test GUI, you will need to get
-	the hidapi-externals.zip package from the download site.  This
-	contains pre-built binaries for Fox-toolkit.  Extract
-	hidapi-externals.zip just outside of hidapi, so that
-	hidapi-externals and hidapi are on the same level, as shown:
-
-	     Parent_Folder
-	       |
-	       +hidapi
-	       +hidapi-externals
-
-	Again, this step is not required if you do not wish to build the
-	test GUI.
-
-
-Building HIDAPI into a shared library on Unix Platforms:
----------------------------------------------------------
-
-On Unix-like systems such as Linux, FreeBSD, Mac, and even Windows, using
-Mingw or Cygwin, the easiest way to build a standard system-installed shared
-library is to use the GNU Autotools build system.  If you checked out the
-source from the git repository, run the following:
-
-	./bootstrap
-	./configure
-	make
-	make install     <----- as root, or using sudo
-
-If you downloaded a source package (ie: if you did not run git clone), you
-can skip the ./bootstrap step.
-
-./configure can take several arguments which control the build. The two most
-likely to be used are:
-	--enable-testgui
-		Enable build of the Test GUI. This requires Fox toolkit to
-		be installed.  Instructions for installing Fox-Toolkit on
-		each platform are in the Prerequisites section above.
-
-	--prefix=/usr
-		Specify where you want the output headers and libraries to
-		be installed. The example above will put the headers in
-		/usr/include and the binaries in /usr/lib. The default is to
-		install into /usr/local which is fine on most systems.
-
-Building the manual way on Unix platforms:
--------------------------------------------
-
-Manual Makefiles are provided mostly to give the user and idea what it takes
-to build a program which embeds HIDAPI directly inside of it. These should
-really be used as examples only. If you want to build a system-wide shared
-library, use the Autotools method described above.
-
-	To build HIDAPI using the manual makefiles, change to the directory
-	of your platform and run make. For example, on Linux run:
-		cd linux/
-		make -f Makefile-manual
-
-	To build the Test GUI using the manual makefiles:
-		cd testgui/
-		make -f Makefile-manual
-
-Building on Windows:
----------------------
-
-To build the HIDAPI DLL on Windows using Visual Studio, build the .sln file
-in the windows/ directory.
-
-To build the Test GUI on windows using Visual Studio, build the .sln file in
-the testgui/ directory.
-
-To build HIDAPI using MinGW or Cygwin using Autotools, use the instructions
-in the section titled "Building HIDAPI into a shared library on Unix
-Platforms" above.  Note that building the Test GUI with MinGW or Cygwin will
-require the Windows procedure in the Prerequisites section above (ie:
-hidapi-externals.zip).
-
-To build HIDAPI using MinGW using the Manual Makefiles, see the section
-"Building the manual way on Unix platforms" above.
-
-HIDAPI can also be built using the Windows DDK (now also called the Windows
-Driver Kit or WDK). This method was originally required for the HIDAPI build
-but not anymore. However, some users still prefer this method. It is not as
-well supported anymore but should still work. Patches are welcome if it does
-not. To build using the DDK:
-
-   1. Install the Windows Driver Kit (WDK) from Microsoft.
-   2. From the Start menu, in the Windows Driver Kits folder, select Build
-      Environments, then your operating system, then the x86 Free Build
-      Environment (or one that is appropriate for your system).
-   3. From the console, change directory to the windows/ddk_build/ directory,
-      which is part of the HIDAPI distribution.
-   4. Type build.
-   5. You can find the output files (DLL and LIB) in a subdirectory created
-      by the build system which is appropriate for your environment. On
-      Windows XP, this directory is objfre_wxp_x86/i386.
-
-Cross Compiling
-================
-
-This section talks about cross compiling HIDAPI for Linux using autotools.
-This is useful for using HIDAPI on embedded Linux targets.  These
-instructions assume the most raw kind of embedded Linux build, where all
-prerequisites will need to be built first.  This process will of course vary
-based on your embedded Linux build system if you are using one, such as
-OpenEmbedded or Buildroot.
-
-For the purpose of this section, it will be assumed that the following
-environment variables are exported.
-
-	$ export STAGING=$HOME/out
-	$ export HOST=arm-linux
-
-STAGING and HOST can be modified to suit your setup.
-
-Prerequisites
---------------
-
-Note that the build of libudev is the very basic configuration.
-
-Build Libusb. From the libusb source directory, run:
-	./configure --host=$HOST --prefix=$STAGING
-	make
-	make install
-
-Build libudev. From the libudev source directory, run:
-	./configure --disable-gudev --disable-introspection --disable-hwdb \
-		 --host=$HOST --prefix=$STAGING
-	make
-	make install
-
-Building HIDAPI
-----------------
-
-Build HIDAPI:
-
-	PKG_CONFIG_DIR= \
-	PKG_CONFIG_LIBDIR=$STAGING/lib/pkgconfig:$STAGING/share/pkgconfig \
-	PKG_CONFIG_SYSROOT_DIR=$STAGING \
-	./configure --host=$HOST --prefix=$STAGING
-
-
-Signal 11 Software - 2010-04-11
-                     2010-07-28
-                     2011-09-10
-                     2012-05-01
-                     2012-07-03

+ 1 - 0
src/hidapi/VERSION

@@ -0,0 +1 @@
+0.14.0

+ 0 - 1443
src/hidapi/android/hid.cpp

@@ -1,1443 +0,0 @@
-/*
-  Simple DirectMedia Layer
-  Copyright (C) 2022 Valve Corporation
-
-  This software is provided 'as-is', without any express or implied
-  warranty.  In no event will the authors be held liable for any damages
-  arising from the use of this software.
-
-  Permission is granted to anyone to use this software for any purpose,
-  including commercial applications, and to alter it and redistribute it
-  freely, subject to the following restrictions:
-
-  1. The origin of this software must not be misrepresented; you must not
-     claim that you wrote the original software. If you use this software
-     in a product, an acknowledgment in the product documentation would be
-     appreciated but is not required.
-  2. Altered source versions must be plainly marked as such, and must not be
-     misrepresented as being the original software.
-  3. This notice may not be removed or altered from any source distribution.
-*/
-#include "SDL_internal.h"
-
-// Purpose: A wrapper implementing "HID" API for Android
-//
-//          This layer glues the hidapi API to Android's USB and BLE stack.
-
-#include "hid.h"
-
-// Common to stub version and non-stub version of functions
-#include <jni.h>
-#include <android/log.h>
-
-#define TAG "hidapi"
-
-// Have error log always available
-#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, TAG, __VA_ARGS__)
-
-#ifdef DEBUG
-#define LOGV(...) __android_log_print(ANDROID_LOG_VERBOSE, TAG, __VA_ARGS__)
-#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, TAG, __VA_ARGS__)
-#else
-#define LOGV(...)
-#define LOGD(...)
-#endif
-
-#define SDL_JAVA_PREFIX                                 org_libsdl_app
-#define CONCAT1(prefix, class, function)                CONCAT2(prefix, class, function)
-#define CONCAT2(prefix, class, function)                Java_ ## prefix ## _ ## class ## _ ## function
-#define HID_DEVICE_MANAGER_JAVA_INTERFACE(function)     CONCAT1(SDL_JAVA_PREFIX, HIDDeviceManager, function)
-
-
-#ifndef SDL_HIDAPI_DISABLED
-
-#include "../../core/android/SDL_android.h"
-
-#define hid_init                        PLATFORM_hid_init
-#define hid_exit                        PLATFORM_hid_exit
-#define hid_enumerate                   PLATFORM_hid_enumerate
-#define hid_free_enumeration            PLATFORM_hid_free_enumeration
-#define hid_open                        PLATFORM_hid_open
-#define hid_open_path                   PLATFORM_hid_open_path
-#define hid_write                       PLATFORM_hid_write
-#define hid_read_timeout                PLATFORM_hid_read_timeout
-#define hid_read                        PLATFORM_hid_read
-#define hid_set_nonblocking             PLATFORM_hid_set_nonblocking
-#define hid_send_feature_report         PLATFORM_hid_send_feature_report
-#define hid_get_feature_report          PLATFORM_hid_get_feature_report
-#define hid_close                       PLATFORM_hid_close
-#define hid_get_manufacturer_string     PLATFORM_hid_get_manufacturer_string
-#define hid_get_product_string          PLATFORM_hid_get_product_string
-#define hid_get_serial_number_string    PLATFORM_hid_get_serial_number_string
-#define hid_get_indexed_string          PLATFORM_hid_get_indexed_string
-#define hid_error                       PLATFORM_hid_error
-
-#include <pthread.h>
-#include <errno.h>	// For ETIMEDOUT and ECONNRESET
-#include <stdlib.h> // For malloc() and free()
-
-#include "../hidapi/hidapi.h"
-
-typedef uint32_t uint32;
-typedef uint64_t uint64;
-
-
-struct hid_device_
-{
-	int m_nId;
-	int m_nDeviceRefCount;
-};
-
-static JavaVM *g_JVM;
-static pthread_key_t g_ThreadKey;
-
-template<class T>
-class hid_device_ref
-{
-public:
-	hid_device_ref( T *pObject = nullptr ) : m_pObject( nullptr )
-	{
-		SetObject( pObject );
-	}
-
-	hid_device_ref( const hid_device_ref &rhs ) : m_pObject( nullptr )
-	{
-		SetObject( rhs.GetObject() );
-	}
-
-	~hid_device_ref()
-	{
-		SetObject( nullptr );
-	}
-
-	void SetObject( T *pObject )
-	{
-		if ( m_pObject && m_pObject->DecrementRefCount() == 0 )
-		{
-			delete m_pObject;
-		}
-
-		m_pObject = pObject;
-
-		if ( m_pObject )
-		{
-			m_pObject->IncrementRefCount();
-		}
-	}
-
-	hid_device_ref &operator =( T *pObject )
-	{
-		SetObject( pObject );
-		return *this;
-	}
-
-	hid_device_ref &operator =( const hid_device_ref &rhs )
-	{
-		SetObject( rhs.GetObject() );
-		return *this;
-	}
-
-	T *GetObject() const
-	{
-		return m_pObject;
-	}
-
-	T* operator->() const
-	{
-		return m_pObject;
-	}
-
-	operator bool() const
-	{
-		return ( m_pObject != nullptr );
-	}
-
-private:
-	T *m_pObject;
-};
-
-class hid_mutex_guard
-{
-public:
-	hid_mutex_guard( pthread_mutex_t *pMutex ) : m_pMutex( pMutex )
-	{
-		pthread_mutex_lock( m_pMutex );
-	}
-	~hid_mutex_guard()
-	{
-		pthread_mutex_unlock( m_pMutex );
-	}
-
-private:
-	pthread_mutex_t *m_pMutex;
-};
-
-class hid_buffer
-{
-public:
-	hid_buffer() : m_pData( nullptr ), m_nSize( 0 ), m_nAllocated( 0 )
-	{
-	}
-
-	hid_buffer( const uint8_t *pData, size_t nSize ) : m_pData( nullptr ), m_nSize( 0 ), m_nAllocated( 0 )
-	{
-		assign( pData, nSize );
-	}
-
-	~hid_buffer()
-	{
-		delete[] m_pData;
-	}
-
-	void assign( const uint8_t *pData, size_t nSize )
-	{
-		if ( nSize > m_nAllocated )
-		{
-			delete[] m_pData;
-			m_pData = new uint8_t[ nSize ];
-			m_nAllocated = nSize;
-		}
-
-		m_nSize = nSize;
-		SDL_memcpy( m_pData, pData, nSize );
-	}
-
-	void clear()
-	{
-		m_nSize = 0;
-	}
-
-	size_t size() const
-	{
-		return m_nSize;
-	}
-
-	const uint8_t *data() const
-	{
-		return m_pData;
-	}
-
-private:
-	uint8_t *m_pData;
-	size_t m_nSize;
-	size_t m_nAllocated;
-};
-
-class hid_buffer_pool
-{
-public:
-	hid_buffer_pool() : m_nSize( 0 ), m_pHead( nullptr ), m_pTail( nullptr ), m_pFree( nullptr )
-	{
-	}
-
-	~hid_buffer_pool()
-	{
-		clear();
-
-		while ( m_pFree )
-		{
-			hid_buffer_entry *pEntry = m_pFree;
-			m_pFree = m_pFree->m_pNext;
-			delete pEntry;
-		}
-	}
-
-	size_t size() const { return m_nSize; }
-
-	const hid_buffer &front() const { return m_pHead->m_buffer; }
-
-	void pop_front()
-	{
-		hid_buffer_entry *pEntry = m_pHead;
-		if ( pEntry )
-		{
-			m_pHead = pEntry->m_pNext;
-			if ( !m_pHead )
-			{
-				m_pTail = nullptr;
-			}
-			pEntry->m_pNext = m_pFree;
-			m_pFree = pEntry;
-			--m_nSize;
-		}
-	}
-
-	void emplace_back( const uint8_t *pData, size_t nSize )
-	{
-		hid_buffer_entry *pEntry;
-
-		if ( m_pFree )
-		{
-			pEntry = m_pFree;
-			m_pFree = m_pFree->m_pNext;
-		}
-		else
-		{
-			pEntry = new hid_buffer_entry;
-		}
-		pEntry->m_pNext = nullptr;
-
-		if ( m_pTail )
-		{
-			m_pTail->m_pNext = pEntry;
-		}
-		else
-		{
-			m_pHead = pEntry;
-		}
-		m_pTail = pEntry;
-
-		pEntry->m_buffer.assign( pData, nSize );
-		++m_nSize;
-	}
-
-	void clear()
-	{
-		while ( size() > 0 )
-		{
-			pop_front();
-		}
-	}
-
-private:
-	struct hid_buffer_entry
-	{
-		hid_buffer m_buffer;
-		hid_buffer_entry *m_pNext;
-	};
-
-	size_t m_nSize;
-	hid_buffer_entry *m_pHead;
-	hid_buffer_entry *m_pTail;
-	hid_buffer_entry *m_pFree;
-};
-
-static jbyteArray NewByteArray( JNIEnv* env, const uint8_t *pData, size_t nDataLen )
-{
-	jbyteArray array = env->NewByteArray( (jsize)nDataLen );
-	jbyte *pBuf = env->GetByteArrayElements( array, NULL );
-	SDL_memcpy( pBuf, pData, nDataLen );
-	env->ReleaseByteArrayElements( array, pBuf, 0 );
-
-	return array;
-}
-
-static char *CreateStringFromJString( JNIEnv *env, const jstring &sString )
-{
-	size_t nLength = env->GetStringUTFLength( sString );
-	const char *pjChars = env->GetStringUTFChars( sString, NULL );
-	char *psString = (char*)malloc( nLength + 1 );
-	SDL_memcpy( psString, pjChars, nLength );
-	psString[ nLength ] = '\0';
-	env->ReleaseStringUTFChars( sString, pjChars );
-	return psString;
-}
-
-static wchar_t *CreateWStringFromJString( JNIEnv *env, const jstring &sString )
-{
-	size_t nLength = env->GetStringLength( sString );
-	const jchar *pjChars = env->GetStringChars( sString, NULL );
-	wchar_t *pwString = (wchar_t*)malloc( ( nLength + 1 ) * sizeof( wchar_t ) );
-	wchar_t *pwChars = pwString;
-	for ( size_t iIndex = 0; iIndex < nLength; ++iIndex )
-	{
-		pwChars[ iIndex ] = pjChars[ iIndex ];
-	}
-	pwString[ nLength ] = '\0';
-	env->ReleaseStringChars( sString, pjChars );
-	return pwString;
-}
-
-static wchar_t *CreateWStringFromWString( const wchar_t *pwSrc )
-{
-	size_t nLength = SDL_wcslen( pwSrc );
-	wchar_t *pwString = (wchar_t*)malloc( ( nLength + 1 ) * sizeof( wchar_t ) );
-	SDL_memcpy( pwString, pwSrc, nLength * sizeof( wchar_t ) );
-	pwString[ nLength ] = '\0';
-	return pwString;
-}
-
-static hid_device_info *CopyHIDDeviceInfo( const hid_device_info *pInfo )
-{
-	hid_device_info *pCopy = new hid_device_info;
-	*pCopy = *pInfo;
-	pCopy->path = SDL_strdup( pInfo->path );
-	pCopy->product_string = CreateWStringFromWString( pInfo->product_string );
-	pCopy->manufacturer_string = CreateWStringFromWString( pInfo->manufacturer_string );
-	pCopy->serial_number = CreateWStringFromWString( pInfo->serial_number );
-	return pCopy;
-}
-
-static void FreeHIDDeviceInfo( hid_device_info *pInfo )
-{
-	free( pInfo->path );
-	free( pInfo->serial_number );
-	free( pInfo->manufacturer_string );
-	free( pInfo->product_string );
-	delete pInfo;
-}
-
-static jclass  g_HIDDeviceManagerCallbackClass;
-static jobject g_HIDDeviceManagerCallbackHandler;
-static jmethodID g_midHIDDeviceManagerInitialize;
-static jmethodID g_midHIDDeviceManagerOpen;
-static jmethodID g_midHIDDeviceManagerSendOutputReport;
-static jmethodID g_midHIDDeviceManagerSendFeatureReport;
-static jmethodID g_midHIDDeviceManagerGetFeatureReport;
-static jmethodID g_midHIDDeviceManagerClose;
-static bool g_initialized = false;
-
-static uint64_t get_timespec_ms( const struct timespec &ts )
-{
-	return (uint64_t)ts.tv_sec * 1000 + ts.tv_nsec / 1000000;
-}
-
-static void ExceptionCheck( JNIEnv *env, const char *pszClassName, const char *pszMethodName )
-{
-	if ( env->ExceptionCheck() )
-	{
-		// Get our exception
-		jthrowable jExcept = env->ExceptionOccurred();
-
-		// Clear the exception so we can call JNI again
-		env->ExceptionClear();
-
-		// Get our exception message
-		jclass jExceptClass = env->GetObjectClass( jExcept );
-		jmethodID jMessageMethod = env->GetMethodID( jExceptClass, "getMessage", "()Ljava/lang/String;" );
-		jstring jMessage = (jstring)( env->CallObjectMethod( jExcept, jMessageMethod ) );
-		const char *pszMessage = env->GetStringUTFChars( jMessage, NULL );
-
-		// ...and log it.
-		LOGE( "%s%s%s threw an exception: %s",
-			pszClassName ? pszClassName : "",
-			pszClassName ? "::" : "",
-			pszMethodName, pszMessage );
-
-		// Cleanup
-		env->ReleaseStringUTFChars( jMessage, pszMessage );
-		env->DeleteLocalRef( jMessage );
-		env->DeleteLocalRef( jExceptClass );
-		env->DeleteLocalRef( jExcept );
-	}
-}
-
-class CHIDDevice
-{
-public:
-	CHIDDevice( int nDeviceID, hid_device_info *pInfo )
-	{
-		m_nId = nDeviceID;
-		m_pInfo = pInfo;
-
-		// The Bluetooth Steam Controller needs special handling
-		const int VALVE_USB_VID	= 0x28DE;
-		const int D0G_BLE2_PID = 0x1106;
-		if ( pInfo->vendor_id == VALVE_USB_VID && pInfo->product_id == D0G_BLE2_PID )
-		{
-			m_bIsBLESteamController = true;
-		}
-	}
-
-	~CHIDDevice()
-	{
-		FreeHIDDeviceInfo( m_pInfo );
-
-		// Note that we don't delete m_pDevice, as the app may still have a reference to it
-	}
-
-	int IncrementRefCount()
-	{
-		int nValue;
-		pthread_mutex_lock( &m_refCountLock );
-		nValue = ++m_nRefCount;
-		pthread_mutex_unlock( &m_refCountLock );
-		return nValue;
-	}
-
-	int DecrementRefCount()
-	{
-		int nValue;
-		pthread_mutex_lock( &m_refCountLock );
-		nValue = --m_nRefCount;
-		pthread_mutex_unlock( &m_refCountLock );
-		return nValue;
-	}
-
-	int GetId()
-	{
-		return m_nId;
-	}
-
-	const hid_device_info *GetDeviceInfo()
-	{
-		return m_pInfo;
-	}
-
-	hid_device *GetDevice()
-	{
-		return m_pDevice;
-	}
-
-	void ExceptionCheck( JNIEnv *env, const char *pszMethodName )
-	{
-		::ExceptionCheck( env, "CHIDDevice", pszMethodName );
-	}
-
-	bool BOpen()
-	{
-		// Make sure thread is attached to JVM/env
-		JNIEnv *env;
-		g_JVM->AttachCurrentThread( &env, NULL );
-		pthread_setspecific( g_ThreadKey, (void*)env );
-
-		if ( !g_HIDDeviceManagerCallbackHandler )
-		{
-			LOGV( "Device open without callback handler" );
-			return false;
-		}
-
-		m_bIsWaitingForOpen = false;
-		m_bOpenResult = env->CallBooleanMethod( g_HIDDeviceManagerCallbackHandler, g_midHIDDeviceManagerOpen, m_nId );
-		ExceptionCheck( env, "BOpen" );
-
-		if ( m_bIsWaitingForOpen )
-		{
-			hid_mutex_guard cvl( &m_cvLock );
-
-			const int OPEN_TIMEOUT_SECONDS = 60;
-			struct timespec ts, endtime;
-			clock_gettime( CLOCK_REALTIME, &ts );
-			endtime = ts;
-			endtime.tv_sec += OPEN_TIMEOUT_SECONDS;
-			do
-			{
-				if ( pthread_cond_timedwait( &m_cv, &m_cvLock, &endtime ) != 0 )
-				{
-					break;
-				}
-			}
-			while ( m_bIsWaitingForOpen && get_timespec_ms( ts ) < get_timespec_ms( endtime ) );
-		}
-
-		if ( !m_bOpenResult )
-		{
-			if ( m_bIsWaitingForOpen )
-			{
-				LOGV( "Device open failed - timed out waiting for device permission" );
-			}
-			else
-			{
-				LOGV( "Device open failed" );
-			}
-			return false;
-		}
-
-		m_pDevice = new hid_device;
-		m_pDevice->m_nId = m_nId;
-		m_pDevice->m_nDeviceRefCount = 1;
-		LOGD("Creating device %d (%p), refCount = 1\n", m_pDevice->m_nId, m_pDevice);
-		return true;
-	}
-
-	void SetOpenPending()
-	{
-		m_bIsWaitingForOpen = true;
-	}
-
-	void SetOpenResult( bool bResult )
-	{
-		if ( m_bIsWaitingForOpen )
-		{
-			m_bOpenResult = bResult;
-			m_bIsWaitingForOpen = false;
-			pthread_cond_signal( &m_cv );
-		}
-	}
-
-	void ProcessInput( const uint8_t *pBuf, size_t nBufSize )
-	{
-		hid_mutex_guard l( &m_dataLock );
-
-		size_t MAX_REPORT_QUEUE_SIZE = 16;
-		if ( m_vecData.size() >= MAX_REPORT_QUEUE_SIZE )
-		{
-			m_vecData.pop_front();
-		}
-		m_vecData.emplace_back( pBuf, nBufSize );
-	}
-
-	int GetInput( unsigned char *data, size_t length )
-	{
-		hid_mutex_guard l( &m_dataLock );
-
-		if ( m_vecData.size() == 0 )
-		{
-//			LOGV( "hid_read_timeout no data available" );
-			return 0;
-		}
-
-		const hid_buffer &buffer = m_vecData.front();
-		size_t nDataLen = buffer.size() > length ? length : buffer.size();
-		if ( m_bIsBLESteamController )
-		{
-			data[0] = 0x03;
-			SDL_memcpy( data + 1, buffer.data(), nDataLen );
-			++nDataLen;
-		}
-		else
-		{
-			SDL_memcpy( data, buffer.data(), nDataLen );
-		}
-		m_vecData.pop_front();
-
-//		LOGV("Read %u bytes", nDataLen);
-//		LOGV("%02x %02x %02x %02x %02x %02x %02x %02x ....",
-//			 data[0], data[1], data[2], data[3],
-//			 data[4], data[5], data[6], data[7]);
-
-		return (int)nDataLen;
-	}
-
-	int SendOutputReport( const unsigned char *pData, size_t nDataLen )
-	{
-		// Make sure thread is attached to JVM/env
-		JNIEnv *env;
-		g_JVM->AttachCurrentThread( &env, NULL );
-		pthread_setspecific( g_ThreadKey, (void*)env );
-
-		int nRet = -1;
-		if ( g_HIDDeviceManagerCallbackHandler )
-		{
-			jbyteArray pBuf = NewByteArray( env, pData, nDataLen );
-			nRet = env->CallIntMethod( g_HIDDeviceManagerCallbackHandler, g_midHIDDeviceManagerSendOutputReport, m_nId, pBuf );
-			ExceptionCheck( env, "SendOutputReport" );
-			env->DeleteLocalRef( pBuf );
-		}
-		else
-		{
-			LOGV( "SendOutputReport without callback handler" );
-		}
-		return nRet;
-	}
-
-	int SendFeatureReport( const unsigned char *pData, size_t nDataLen )
-	{
-		// Make sure thread is attached to JVM/env
-		JNIEnv *env;
-		g_JVM->AttachCurrentThread( &env, NULL );
-		pthread_setspecific( g_ThreadKey, (void*)env );
-
-		int nRet = -1;
-		if ( g_HIDDeviceManagerCallbackHandler )
-		{
-			jbyteArray pBuf = NewByteArray( env, pData, nDataLen );
-			nRet = env->CallIntMethod( g_HIDDeviceManagerCallbackHandler, g_midHIDDeviceManagerSendFeatureReport, m_nId, pBuf );
-			ExceptionCheck( env, "SendFeatureReport" );
-			env->DeleteLocalRef( pBuf );
-		}
-		else
-		{
-			LOGV( "SendFeatureReport without callback handler" );
-		}
-		return nRet;
-	}
-
-	void ProcessFeatureReport( const uint8_t *pBuf, size_t nBufSize )
-	{
-		hid_mutex_guard cvl( &m_cvLock );
-		if ( m_bIsWaitingForFeatureReport )
-		{
-			m_featureReport.assign( pBuf, nBufSize );
-
-			m_bIsWaitingForFeatureReport = false;
-			m_nFeatureReportError = 0;
-			pthread_cond_signal( &m_cv );
-		}
-	}
-
-	int GetFeatureReport( unsigned char *pData, size_t nDataLen )
-	{
-		// Make sure thread is attached to JVM/env
-		JNIEnv *env;
-		g_JVM->AttachCurrentThread( &env, NULL );
-		pthread_setspecific( g_ThreadKey, (void*)env );
-
-		if ( !g_HIDDeviceManagerCallbackHandler )
-		{
-			LOGV( "GetFeatureReport without callback handler" );
-			return -1;
-		}
-
-		{
-			hid_mutex_guard cvl( &m_cvLock );
-			if ( m_bIsWaitingForFeatureReport )
-			{
-				LOGV( "Get feature report already ongoing... bail" );
-				return -1; // Read already ongoing, we currently do not serialize, TODO
-			}
-			m_bIsWaitingForFeatureReport = true;
-		}
-
-		jbyteArray pBuf = NewByteArray( env, pData, nDataLen );
-		int nRet = env->CallBooleanMethod( g_HIDDeviceManagerCallbackHandler, g_midHIDDeviceManagerGetFeatureReport, m_nId, pBuf ) ? 0 : -1;
-		ExceptionCheck( env, "GetFeatureReport" );
-		env->DeleteLocalRef( pBuf );
-		if ( nRet < 0 )
-		{
-			LOGV( "GetFeatureReport failed" );
-			m_bIsWaitingForFeatureReport = false;
-			return -1;
-		}
-
-		{
-			hid_mutex_guard cvl( &m_cvLock );
-			if ( m_bIsWaitingForFeatureReport )
-			{
-				LOGV("=== Going to sleep" );
-				// Wait in CV until we are no longer waiting for a feature report.
-				const int FEATURE_REPORT_TIMEOUT_SECONDS = 2;
-				struct timespec ts, endtime;
-				clock_gettime( CLOCK_REALTIME, &ts );
-				endtime = ts;
-				endtime.tv_sec += FEATURE_REPORT_TIMEOUT_SECONDS;
-				do
-				{
-					if ( pthread_cond_timedwait( &m_cv, &m_cvLock, &endtime ) != 0 )
-					{
-						break;
-					}
-				}
-				while ( m_bIsWaitingForFeatureReport && get_timespec_ms( ts ) < get_timespec_ms( endtime ) );
-
-				// We are back
-				if ( m_bIsWaitingForFeatureReport )
-				{
-					m_nFeatureReportError = -ETIMEDOUT;
-					m_bIsWaitingForFeatureReport = false;
-				}
-				LOGV( "=== Got feature report err=%d", m_nFeatureReportError );
-				if ( m_nFeatureReportError != 0 )
-				{
-					return m_nFeatureReportError;
-				}
-			}
-
-			size_t uBytesToCopy = m_featureReport.size() > nDataLen ? nDataLen : m_featureReport.size();
-			SDL_memcpy( pData, m_featureReport.data(), uBytesToCopy );
-			m_featureReport.clear();
-			LOGV( "=== Got %u bytes", uBytesToCopy );
-
-			return (int)uBytesToCopy;
-		}
-	}
-
-	void Close( bool bDeleteDevice )
-	{
-		// Make sure thread is attached to JVM/env
-		JNIEnv *env;
-		g_JVM->AttachCurrentThread( &env, NULL );
-		pthread_setspecific( g_ThreadKey, (void*)env );
-
-		if ( g_HIDDeviceManagerCallbackHandler )
-		{
-			env->CallVoidMethod( g_HIDDeviceManagerCallbackHandler, g_midHIDDeviceManagerClose, m_nId );
-			ExceptionCheck( env, "Close" );
-		}
-
-		hid_mutex_guard dataLock( &m_dataLock );
-		m_vecData.clear();
-
-		// Clean and release pending feature report reads
-		hid_mutex_guard cvLock( &m_cvLock );
-		m_featureReport.clear();
-		m_bIsWaitingForFeatureReport = false;
-		m_nFeatureReportError = -ECONNRESET;
-		pthread_cond_broadcast( &m_cv );
-
-		if ( bDeleteDevice )
-		{
-			delete m_pDevice;
-			m_pDevice = nullptr;
-		}
-	}
-
-private:
-	pthread_mutex_t m_refCountLock = PTHREAD_MUTEX_INITIALIZER;
-	int m_nRefCount = 0;
-	int m_nId = 0;
-	hid_device_info *m_pInfo = nullptr;
-	hid_device *m_pDevice = nullptr;
-	bool m_bIsBLESteamController = false;
-
-	pthread_mutex_t m_dataLock = PTHREAD_MUTEX_INITIALIZER; // This lock has to be held to access m_vecData
-	hid_buffer_pool m_vecData;
-
-	// For handling get_feature_report
-	pthread_mutex_t m_cvLock = PTHREAD_MUTEX_INITIALIZER; // This lock has to be held to access any variables below
-	pthread_cond_t m_cv = PTHREAD_COND_INITIALIZER;
-	bool m_bIsWaitingForOpen = false;
-	bool m_bOpenResult = false;
-	bool m_bIsWaitingForFeatureReport = false;
-	int m_nFeatureReportError = 0;
-	hid_buffer m_featureReport;
-
-public:
-	hid_device_ref<CHIDDevice> next;
-};
-
-class CHIDDevice;
-static pthread_mutex_t g_DevicesMutex = PTHREAD_MUTEX_INITIALIZER;
-static pthread_mutex_t g_DevicesRefCountMutex = PTHREAD_MUTEX_INITIALIZER;
-static hid_device_ref<CHIDDevice> g_Devices;
-
-static hid_device_ref<CHIDDevice> FindDevice( int nDeviceId )
-{
-	hid_device_ref<CHIDDevice> pDevice;
-
-	hid_mutex_guard l( &g_DevicesMutex );
-	for ( pDevice = g_Devices; pDevice; pDevice = pDevice->next )
-	{
-		if ( pDevice->GetId() == nDeviceId )
-		{
-			break;
-		}
-	}
-	return pDevice;
-}
-
-static void ThreadDestroyed(void* value)
-{
-	/* The thread is being destroyed, detach it from the Java VM and set the g_ThreadKey value to NULL as required */
-	JNIEnv *env = (JNIEnv*) value;
-	if (env != NULL) {
-		g_JVM->DetachCurrentThread();
-		pthread_setspecific(g_ThreadKey, NULL);
-	}
-}
-
-
-extern "C"
-JNIEXPORT void JNICALL HID_DEVICE_MANAGER_JAVA_INTERFACE(HIDDeviceRegisterCallback)(JNIEnv *env, jobject thiz);
-
-extern "C"
-JNIEXPORT void JNICALL HID_DEVICE_MANAGER_JAVA_INTERFACE(HIDDeviceReleaseCallback)(JNIEnv *env, jobject thiz);
-
-extern "C"
-JNIEXPORT void JNICALL HID_DEVICE_MANAGER_JAVA_INTERFACE(HIDDeviceConnected)(JNIEnv *env, jobject thiz, int nDeviceID, jstring sIdentifier, int nVendorId, int nProductId, jstring sSerialNumber, int nReleaseNumber, jstring sManufacturer, jstring sProduct, int nInterface, int nInterfaceClass, int nInterfaceSubclass, int nInterfaceProtocol );
-
-extern "C"
-JNIEXPORT void JNICALL HID_DEVICE_MANAGER_JAVA_INTERFACE(HIDDeviceOpenPending)(JNIEnv *env, jobject thiz, int nDeviceID);
-
-extern "C"
-JNIEXPORT void JNICALL HID_DEVICE_MANAGER_JAVA_INTERFACE(HIDDeviceOpenResult)(JNIEnv *env, jobject thiz, int nDeviceID, bool bOpened);
-
-extern "C"
-JNIEXPORT void JNICALL HID_DEVICE_MANAGER_JAVA_INTERFACE(HIDDeviceDisconnected)(JNIEnv *env, jobject thiz, int nDeviceID);
-
-extern "C"
-JNIEXPORT void JNICALL HID_DEVICE_MANAGER_JAVA_INTERFACE(HIDDeviceInputReport)(JNIEnv *env, jobject thiz, int nDeviceID, jbyteArray value);
-
-extern "C"
-JNIEXPORT void JNICALL HID_DEVICE_MANAGER_JAVA_INTERFACE(HIDDeviceFeatureReport)(JNIEnv *env, jobject thiz, int nDeviceID, jbyteArray value);
-
-
-extern "C"
-JNIEXPORT void JNICALL HID_DEVICE_MANAGER_JAVA_INTERFACE(HIDDeviceRegisterCallback)(JNIEnv *env, jobject thiz )
-{
-	LOGV( "HIDDeviceRegisterCallback()");
-
-	env->GetJavaVM( &g_JVM );
-
-	/*
-	 * Create mThreadKey so we can keep track of the JNIEnv assigned to each thread
-	 * Refer to http://developer.android.com/guide/practices/design/jni.html for the rationale behind this
-	 */
-	if (pthread_key_create(&g_ThreadKey, ThreadDestroyed) != 0) {
-		__android_log_print(ANDROID_LOG_ERROR, TAG, "Error initializing pthread key");
-	}
-
-	if ( g_HIDDeviceManagerCallbackHandler != NULL )
-	{
-		env->DeleteGlobalRef( g_HIDDeviceManagerCallbackClass );
-		g_HIDDeviceManagerCallbackClass = NULL;
-		env->DeleteGlobalRef( g_HIDDeviceManagerCallbackHandler );
-		g_HIDDeviceManagerCallbackHandler = NULL;
-	}
-
-	g_HIDDeviceManagerCallbackHandler = env->NewGlobalRef( thiz );
-	jclass objClass = env->GetObjectClass( thiz );
-	if ( objClass )
-	{
-		g_HIDDeviceManagerCallbackClass = reinterpret_cast< jclass >( env->NewGlobalRef( objClass ) );
-		g_midHIDDeviceManagerInitialize = env->GetMethodID( g_HIDDeviceManagerCallbackClass, "initialize", "(ZZ)Z" );
-		if ( !g_midHIDDeviceManagerInitialize )
-		{
-			__android_log_print(ANDROID_LOG_ERROR, TAG, "HIDDeviceRegisterCallback: callback class missing initialize" );
-		}
-		g_midHIDDeviceManagerOpen = env->GetMethodID( g_HIDDeviceManagerCallbackClass, "openDevice", "(I)Z" );
-		if ( !g_midHIDDeviceManagerOpen )
-		{
-			__android_log_print(ANDROID_LOG_ERROR, TAG, "HIDDeviceRegisterCallback: callback class missing openDevice" );
-		}
-		g_midHIDDeviceManagerSendOutputReport = env->GetMethodID( g_HIDDeviceManagerCallbackClass, "sendOutputReport", "(I[B)I" );
-		if ( !g_midHIDDeviceManagerSendOutputReport )
-		{
-			__android_log_print(ANDROID_LOG_ERROR, TAG, "HIDDeviceRegisterCallback: callback class missing sendOutputReport" );
-		}
-		g_midHIDDeviceManagerSendFeatureReport = env->GetMethodID( g_HIDDeviceManagerCallbackClass, "sendFeatureReport", "(I[B)I" );
-		if ( !g_midHIDDeviceManagerSendFeatureReport )
-		{
-			__android_log_print(ANDROID_LOG_ERROR, TAG, "HIDDeviceRegisterCallback: callback class missing sendFeatureReport" );
-		}
-		g_midHIDDeviceManagerGetFeatureReport = env->GetMethodID( g_HIDDeviceManagerCallbackClass, "getFeatureReport", "(I[B)Z" );
-		if ( !g_midHIDDeviceManagerGetFeatureReport )
-		{
-			__android_log_print(ANDROID_LOG_ERROR, TAG, "HIDDeviceRegisterCallback: callback class missing getFeatureReport" );
-		}
-		g_midHIDDeviceManagerClose = env->GetMethodID( g_HIDDeviceManagerCallbackClass, "closeDevice", "(I)V" );
-		if ( !g_midHIDDeviceManagerClose )
-		{
-			__android_log_print(ANDROID_LOG_ERROR, TAG, "HIDDeviceRegisterCallback: callback class missing closeDevice" );
-		}
-		env->DeleteLocalRef( objClass );
-	}
-}
-
-extern "C"
-JNIEXPORT void JNICALL HID_DEVICE_MANAGER_JAVA_INTERFACE(HIDDeviceReleaseCallback)(JNIEnv *env, jobject thiz)
-{
-	LOGV("HIDDeviceReleaseCallback");
-	if ( env->IsSameObject( thiz, g_HIDDeviceManagerCallbackHandler ) )
-	{
-		env->DeleteGlobalRef( g_HIDDeviceManagerCallbackClass );
-		g_HIDDeviceManagerCallbackClass = NULL;
-		env->DeleteGlobalRef( g_HIDDeviceManagerCallbackHandler );
-		g_HIDDeviceManagerCallbackHandler = NULL;
-		g_initialized = false;
-	}
-}
-
-extern "C"
-JNIEXPORT void JNICALL HID_DEVICE_MANAGER_JAVA_INTERFACE(HIDDeviceConnected)(JNIEnv *env, jobject thiz, int nDeviceID, jstring sIdentifier, int nVendorId, int nProductId, jstring sSerialNumber, int nReleaseNumber, jstring sManufacturer, jstring sProduct, int nInterface, int nInterfaceClass, int nInterfaceSubclass, int nInterfaceProtocol )
-{
-	LOGV( "HIDDeviceConnected() id=%d VID/PID = %.4x/%.4x, interface %d\n", nDeviceID, nVendorId, nProductId, nInterface );
-
-	hid_device_info *pInfo = new hid_device_info;
-	SDL_memset( pInfo, 0, sizeof( *pInfo ) );
-	pInfo->path = CreateStringFromJString( env, sIdentifier );
-	pInfo->vendor_id = nVendorId;
-	pInfo->product_id = nProductId;
-	pInfo->serial_number = CreateWStringFromJString( env, sSerialNumber );
-	pInfo->release_number = nReleaseNumber;
-	pInfo->manufacturer_string = CreateWStringFromJString( env, sManufacturer );
-	pInfo->product_string = CreateWStringFromJString( env, sProduct );
-	pInfo->interface_number = nInterface;
-	pInfo->interface_class = nInterfaceClass;
-	pInfo->interface_subclass = nInterfaceSubclass;
-	pInfo->interface_protocol = nInterfaceProtocol;
-
-	hid_device_ref<CHIDDevice> pDevice( new CHIDDevice( nDeviceID, pInfo ) );
-
-	hid_mutex_guard l( &g_DevicesMutex );
-	hid_device_ref<CHIDDevice> pLast, pCurr;
-	for ( pCurr = g_Devices; pCurr; pLast = pCurr, pCurr = pCurr->next )
-	{
-		continue;
-	}
-	if ( pLast )
-	{
-		pLast->next = pDevice;
-	}
-	else
-	{
-		g_Devices = pDevice;
-	}
-}
-
-extern "C"
-JNIEXPORT void JNICALL HID_DEVICE_MANAGER_JAVA_INTERFACE(HIDDeviceOpenPending)(JNIEnv *env, jobject thiz, int nDeviceID)
-{
-	LOGV( "HIDDeviceOpenPending() id=%d\n", nDeviceID );
-	hid_device_ref<CHIDDevice> pDevice = FindDevice( nDeviceID );
-	if ( pDevice )
-	{
-		pDevice->SetOpenPending();
-	}
-}
-
-extern "C"
-JNIEXPORT void JNICALL HID_DEVICE_MANAGER_JAVA_INTERFACE(HIDDeviceOpenResult)(JNIEnv *env, jobject thiz, int nDeviceID, bool bOpened)
-{
-	LOGV( "HIDDeviceOpenResult() id=%d, result=%s\n", nDeviceID, bOpened ? "true" : "false" );
-	hid_device_ref<CHIDDevice> pDevice = FindDevice( nDeviceID );
-	if ( pDevice )
-	{
-		pDevice->SetOpenResult( bOpened );
-	}
-}
-
-extern "C"
-JNIEXPORT void JNICALL HID_DEVICE_MANAGER_JAVA_INTERFACE(HIDDeviceDisconnected)(JNIEnv *env, jobject thiz, int nDeviceID)
-{
-	LOGV( "HIDDeviceDisconnected() id=%d\n", nDeviceID );
-	hid_device_ref<CHIDDevice> pDevice;
-	{
-		hid_mutex_guard l( &g_DevicesMutex );
-		hid_device_ref<CHIDDevice> pLast, pCurr;
-		for ( pCurr = g_Devices; pCurr; pLast = pCurr, pCurr = pCurr->next )
-		{
-			if ( pCurr->GetId() == nDeviceID )
-			{
-				pDevice = pCurr;
-
-				if ( pLast )
-				{
-					pLast->next = pCurr->next;
-				}
-				else
-				{
-					g_Devices = pCurr->next;
-				}
-			}
-		}
-	}
-	if ( pDevice )
-	{
-		pDevice->Close( false );
-	}
-}
-
-extern "C"
-JNIEXPORT void JNICALL HID_DEVICE_MANAGER_JAVA_INTERFACE(HIDDeviceInputReport)(JNIEnv *env, jobject thiz, int nDeviceID, jbyteArray value)
-{
-	jbyte *pBuf = env->GetByteArrayElements(value, NULL);
-	jsize nBufSize = env->GetArrayLength(value);
-
-//	LOGV( "HIDDeviceInput() id=%d len=%u\n", nDeviceID, nBufSize );
-	hid_device_ref<CHIDDevice> pDevice = FindDevice( nDeviceID );
-	if ( pDevice )
-	{
-		pDevice->ProcessInput( reinterpret_cast< const uint8_t* >( pBuf ), nBufSize );
-	}
-
-	env->ReleaseByteArrayElements(value, pBuf, 0);
-}
-
-extern "C"
-JNIEXPORT void JNICALL HID_DEVICE_MANAGER_JAVA_INTERFACE(HIDDeviceFeatureReport)(JNIEnv *env, jobject thiz, int nDeviceID, jbyteArray value)
-{
-	jbyte *pBuf = env->GetByteArrayElements(value, NULL);
-	jsize nBufSize = env->GetArrayLength(value);
-
-	LOGV( "HIDDeviceFeatureReport() id=%d len=%u\n", nDeviceID, nBufSize );
-	hid_device_ref<CHIDDevice> pDevice = FindDevice( nDeviceID );
-	if ( pDevice )
-	{
-		pDevice->ProcessFeatureReport( reinterpret_cast< const uint8_t* >( pBuf ), nBufSize );
-	}
-
-	env->ReleaseByteArrayElements(value, pBuf, 0);
-}
-
-//////////////////////////////////////////////////////////////////////////////////////////////////////////////
-//////////////////////////////////////////////////////////////////////////////////////////////////////////////
-//////////////////////////////////////////////////////////////////////////////////////////////////////////////
-
-extern "C"
-{
-
-int hid_init(void)
-{
-	if ( !g_initialized )
-	{
-		// HIDAPI doesn't work well with Android < 4.3
-		if (SDL_GetAndroidSDKVersion() >= 18) {
-			// Make sure thread is attached to JVM/env
-			JNIEnv *env;
-			g_JVM->AttachCurrentThread( &env, NULL );
-			pthread_setspecific( g_ThreadKey, (void*)env );
-
-			if ( !g_HIDDeviceManagerCallbackHandler )
-			{
-				LOGV( "hid_init() without callback handler" );
-				return -1;
-			}
-
-			// Bluetooth is currently only used for Steam Controllers, so check that hint
-			// before initializing Bluetooth, which will prompt the user for permission.
-			bool init_usb = true;
-			bool init_bluetooth = false;
-			if (SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_STEAM, SDL_FALSE)) {
-				if (SDL_GetAndroidSDKVersion() < 31 ||
-					Android_JNI_RequestPermission("android.permission.BLUETOOTH_CONNECT")) {
-					init_bluetooth = true;
-				}
-			}
-			env->CallBooleanMethod( g_HIDDeviceManagerCallbackHandler, g_midHIDDeviceManagerInitialize, init_usb, init_bluetooth );
-			ExceptionCheck( env, NULL, "hid_init" );
-		}
-		g_initialized = true;	// Regardless of result, so it's only called once
-	}
-	return 0;
-}
-
-struct hid_device_info HID_API_EXPORT * HID_API_CALL hid_enumerate(unsigned short vendor_id, unsigned short product_id)
-{
-	struct hid_device_info *root = NULL;
-	const char *hint = SDL_GetHint(SDL_HINT_HIDAPI_IGNORE_DEVICES);
-
-	hid_mutex_guard l( &g_DevicesMutex );
-	for ( hid_device_ref<CHIDDevice> pDevice = g_Devices; pDevice; pDevice = pDevice->next )
-	{
-		const hid_device_info *info = pDevice->GetDeviceInfo();
-
-		/* See if there are any devices we should skip in enumeration */
-		if (hint) {
-			char vendor_match[16], product_match[16];
-			SDL_snprintf(vendor_match, sizeof(vendor_match), "0x%.4x/0x0000", info->vendor_id);
-			SDL_snprintf(product_match, sizeof(product_match), "0x%.4x/0x%.4x", info->vendor_id, info->product_id);
-			if (SDL_strcasestr(hint, vendor_match) || SDL_strcasestr(hint, product_match)) {
-				continue;
-			}
-		}
-
-		if ( ( vendor_id == 0x0 || info->vendor_id == vendor_id ) &&
-		     ( product_id == 0x0 || info->product_id == product_id ) )
-		{
-			hid_device_info *dev = CopyHIDDeviceInfo( info );
-			dev->next = root;
-			root = dev;
-		}
-	}
-	return root;
-}
-
-void  HID_API_EXPORT HID_API_CALL hid_free_enumeration(struct hid_device_info *devs)
-{
-	while ( devs )
-	{
-		struct hid_device_info *next = devs->next;
-		FreeHIDDeviceInfo( devs );
-		devs = next;
-	}
-}
-
-HID_API_EXPORT hid_device * HID_API_CALL hid_open(unsigned short vendor_id, unsigned short product_id, const wchar_t *serial_number)
-{
-	// TODO: Implement
-	return NULL;
-}
-
-HID_API_EXPORT hid_device * HID_API_CALL hid_open_path(const char *path, int bExclusive)
-{
-	LOGV( "hid_open_path( %s )", path );
-
-	hid_device_ref< CHIDDevice > pDevice;
-	{
-		hid_mutex_guard r( &g_DevicesRefCountMutex );
-		hid_mutex_guard l( &g_DevicesMutex );
-		for ( hid_device_ref<CHIDDevice> pCurr = g_Devices; pCurr; pCurr = pCurr->next )
-		{
-			if ( SDL_strcmp( pCurr->GetDeviceInfo()->path, path ) == 0 )
-			{
-				hid_device *pValue = pCurr->GetDevice();
-				if ( pValue )
-				{
-					++pValue->m_nDeviceRefCount;
-					LOGD("Incrementing device %d (%p), refCount = %d\n", pValue->m_nId, pValue, pValue->m_nDeviceRefCount);
-					return pValue;
-				}
-
-				// Hold a shared pointer to the controller for the duration
-				pDevice = pCurr;
-				break;
-			}
-		}
-	}
-	if ( pDevice && pDevice->BOpen() )
-	{
-		return pDevice->GetDevice();
-	}
-	return NULL;
-}
-
-int  HID_API_EXPORT HID_API_CALL hid_write(hid_device *device, const unsigned char *data, size_t length)
-{
-	if ( device )
-	{
-		LOGV( "hid_write id=%d length=%u", device->m_nId, length );
-		hid_device_ref<CHIDDevice> pDevice = FindDevice( device->m_nId );
-		if ( pDevice )
-		{
-			return pDevice->SendOutputReport( data, length );
-		}
-	}
-	return -1; // Controller was disconnected
-}
-
-static uint32_t getms()
-{
-	struct timeval now;
-
-	gettimeofday(&now, NULL);
-	return (uint32_t)(now.tv_sec * 1000 + now.tv_usec / 1000);
-}
-
-static void delayms(uint32_t ms)
-{
-    int was_error;
-
-    struct timespec elapsed, tv;
-
-    /* Set the timeout interval */
-    elapsed.tv_sec = ms / 1000;
-    elapsed.tv_nsec = (ms % 1000) * 1000000;
-    do {
-        errno = 0;
-
-        tv.tv_sec = elapsed.tv_sec;
-        tv.tv_nsec = elapsed.tv_nsec;
-        was_error = nanosleep(&tv, &elapsed);
-    } while (was_error && (errno == EINTR));
-}
-
-int HID_API_EXPORT HID_API_CALL hid_read_timeout(hid_device *device, unsigned char *data, size_t length, int milliseconds)
-{
-	if ( device )
-	{
-//		LOGV( "hid_read_timeout id=%d length=%u timeout=%d", device->m_nId, length, milliseconds );
-		hid_device_ref<CHIDDevice> pDevice = FindDevice( device->m_nId );
-		if ( pDevice )
-		{
-			int nResult = pDevice->GetInput( data, length );
-			if ( nResult == 0 && milliseconds > 0 )
-			{
-				uint32_t start = getms();
-				do
-				{
-					delayms( 1 );
-					nResult = pDevice->GetInput( data, length );
-				} while ( nResult == 0 && ( getms() - start ) < milliseconds );
-			}
-			return nResult;
-		}
-		LOGV( "controller was disconnected" );
-	}
-	return -1; // Controller was disconnected
-}
-
-// TODO: Implement blocking
-int  HID_API_EXPORT HID_API_CALL hid_read(hid_device *device, unsigned char *data, size_t length)
-{
-	LOGV( "hid_read id=%d length=%u", device->m_nId, length );
-	return hid_read_timeout( device, data, length, 0 );
-}
-
-// TODO: Implement?
-int  HID_API_EXPORT HID_API_CALL hid_set_nonblocking(hid_device *device, int nonblock)
-{
-	return -1;
-}
-
-int HID_API_EXPORT HID_API_CALL hid_send_feature_report(hid_device *device, const unsigned char *data, size_t length)
-{
-	if ( device )
-	{
-		LOGV( "hid_send_feature_report id=%d length=%u", device->m_nId, length );
-		hid_device_ref<CHIDDevice> pDevice = FindDevice( device->m_nId );
-		if ( pDevice )
-		{
-			return pDevice->SendFeatureReport( data, length );
-		}
-	}
-	return -1; // Controller was disconnected
-}
-
-
-// Synchronous operation. Will block until completed.
-int HID_API_EXPORT HID_API_CALL hid_get_feature_report(hid_device *device, unsigned char *data, size_t length)
-{
-	if ( device )
-	{
-		LOGV( "hid_get_feature_report id=%d length=%u", device->m_nId, length );
-		hid_device_ref<CHIDDevice> pDevice = FindDevice( device->m_nId );
-		if ( pDevice )
-		{
-			return pDevice->GetFeatureReport( data, length );
-		}
-	}
-	return -1; // Controller was disconnected
-}
-
-
-void HID_API_EXPORT HID_API_CALL hid_close(hid_device *device)
-{
-	if ( device )
-	{
-		LOGV( "hid_close id=%d", device->m_nId );
-		hid_mutex_guard r( &g_DevicesRefCountMutex );
-		LOGD("Decrementing device %d (%p), refCount = %d\n", device->m_nId, device, device->m_nDeviceRefCount - 1);
-		if ( --device->m_nDeviceRefCount == 0 )
-		{
-			hid_device_ref<CHIDDevice> pDevice = FindDevice( device->m_nId );
-			if ( pDevice )
-			{
-				pDevice->Close( true );
-			}
-			else
-			{
-				delete device;
-			}
-			LOGD("Deleted device %p\n", device);
-		}
-	}
-}
-
-int HID_API_EXPORT_CALL hid_get_manufacturer_string(hid_device *device, wchar_t *string, size_t maxlen)
-{
-	if ( device )
-	{
-		hid_device_ref<CHIDDevice> pDevice = FindDevice( device->m_nId );
-		if ( pDevice )
-		{
-			wcsncpy( string, pDevice->GetDeviceInfo()->manufacturer_string, maxlen );
-			return 0;
-		}
-	}
-	return -1;
-}
-
-int HID_API_EXPORT_CALL hid_get_product_string(hid_device *device, wchar_t *string, size_t maxlen)
-{
-	if ( device )
-	{
-		hid_device_ref<CHIDDevice> pDevice = FindDevice( device->m_nId );
-		if ( pDevice )
-		{
-			wcsncpy( string, pDevice->GetDeviceInfo()->product_string, maxlen );
-			return 0;
-		}
-	}
-	return -1;
-}
-
-int HID_API_EXPORT_CALL hid_get_serial_number_string(hid_device *device, wchar_t *string, size_t maxlen)
-{
-	if ( device )
-	{
-		hid_device_ref<CHIDDevice> pDevice = FindDevice( device->m_nId );
-		if ( pDevice )
-		{
-			wcsncpy( string, pDevice->GetDeviceInfo()->serial_number, maxlen );
-			return 0;
-		}
-	}
-	return -1;
-}
-
-int HID_API_EXPORT_CALL hid_get_indexed_string(hid_device *device, int string_index, wchar_t *string, size_t maxlen)
-{
-	return -1;
-}
-
-HID_API_EXPORT const wchar_t* HID_API_CALL hid_error(hid_device *device)
-{
-	return NULL;
-}
-
-int hid_exit(void)
-{
-	return 0;
-}
-
-}
-
-#else
-
-extern "C"
-JNIEXPORT void JNICALL HID_DEVICE_MANAGER_JAVA_INTERFACE(HIDDeviceRegisterCallback)(JNIEnv *env, jobject thiz);
-
-extern "C"
-JNIEXPORT void JNICALL HID_DEVICE_MANAGER_JAVA_INTERFACE(HIDDeviceReleaseCallback)(JNIEnv *env, jobject thiz);
-
-extern "C"
-JNIEXPORT void JNICALL HID_DEVICE_MANAGER_JAVA_INTERFACE(HIDDeviceConnected)(JNIEnv *env, jobject thiz, int nDeviceID, jstring sIdentifier, int nVendorId, int nProductId, jstring sSerialNumber, int nReleaseNumber, jstring sManufacturer, jstring sProduct, int nInterface, int nInterfaceClass, int nInterfaceSubclass, int nInterfaceProtocol );
-
-extern "C"
-JNIEXPORT void JNICALL HID_DEVICE_MANAGER_JAVA_INTERFACE(HIDDeviceOpenPending)(JNIEnv *env, jobject thiz, int nDeviceID);
-
-extern "C"
-JNIEXPORT void JNICALL HID_DEVICE_MANAGER_JAVA_INTERFACE(HIDDeviceOpenResult)(JNIEnv *env, jobject thiz, int nDeviceID, bool bOpened);
-
-extern "C"
-JNIEXPORT void JNICALL HID_DEVICE_MANAGER_JAVA_INTERFACE(HIDDeviceDisconnected)(JNIEnv *env, jobject thiz, int nDeviceID);
-
-extern "C"
-JNIEXPORT void JNICALL HID_DEVICE_MANAGER_JAVA_INTERFACE(HIDDeviceInputReport)(JNIEnv *env, jobject thiz, int nDeviceID, jbyteArray value);
-
-extern "C"
-JNIEXPORT void JNICALL HID_DEVICE_MANAGER_JAVA_INTERFACE(HIDDeviceFeatureReport)(JNIEnv *env, jobject thiz, int nDeviceID, jbyteArray value);
-
-
-extern "C"
-JNIEXPORT void JNICALL HID_DEVICE_MANAGER_JAVA_INTERFACE(HIDDeviceRegisterCallback)(JNIEnv *env, jobject thiz )
-{
-	LOGV("Stub HIDDeviceRegisterCallback()");
-}
-
-extern "C"
-JNIEXPORT void JNICALL HID_DEVICE_MANAGER_JAVA_INTERFACE(HIDDeviceReleaseCallback)(JNIEnv *env, jobject thiz)
-{
-	LOGV("Stub HIDDeviceReleaseCallback()");
-}
-
-extern "C"
-JNIEXPORT void JNICALL HID_DEVICE_MANAGER_JAVA_INTERFACE(HIDDeviceConnected)(JNIEnv *env, jobject thiz, int nDeviceID, jstring sIdentifier, int nVendorId, int nProductId, jstring sSerialNumber, int nReleaseNumber, jstring sManufacturer, jstring sProduct, int nInterface, int nInterfaceClass, int nInterfaceSubclass, int nInterfaceProtocol )
-{
-	LOGV("Stub HIDDeviceConnected() id=%d VID/PID = %.4x/%.4x, interface %d\n", nDeviceID, nVendorId, nProductId, nInterface);
-}
-
-extern "C"
-JNIEXPORT void JNICALL HID_DEVICE_MANAGER_JAVA_INTERFACE(HIDDeviceOpenPending)(JNIEnv *env, jobject thiz, int nDeviceID)
-{
-	LOGV("Stub HIDDeviceOpenPending() id=%d\n", nDeviceID);
-}
-
-extern "C"
-JNIEXPORT void JNICALL HID_DEVICE_MANAGER_JAVA_INTERFACE(HIDDeviceOpenResult)(JNIEnv *env, jobject thiz, int nDeviceID, bool bOpened)
-{
-	LOGV("Stub HIDDeviceOpenResult() id=%d, result=%s\n", nDeviceID, bOpened ? "true" : "false");
-}
-
-extern "C"
-JNIEXPORT void JNICALL HID_DEVICE_MANAGER_JAVA_INTERFACE(HIDDeviceDisconnected)(JNIEnv *env, jobject thiz, int nDeviceID)
-{
-	LOGV("Stub HIDDeviceDisconnected() id=%d\n", nDeviceID);
-}
-
-extern "C"
-JNIEXPORT void JNICALL HID_DEVICE_MANAGER_JAVA_INTERFACE(HIDDeviceInputReport)(JNIEnv *env, jobject thiz, int nDeviceID, jbyteArray value)
-{
-	LOGV("Stub HIDDeviceInput() id=%d len=%u\n", nDeviceID, nBufSize);
-}
-
-extern "C"
-JNIEXPORT void JNICALL HID_DEVICE_MANAGER_JAVA_INTERFACE(HIDDeviceFeatureReport)(JNIEnv *env, jobject thiz, int nDeviceID, jbyteArray value)
-{
-	LOGV("Stub HIDDeviceFeatureReport() id=%d len=%u\n", nDeviceID, nBufSize);
-}
-
-#endif /* SDL_HIDAPI_DISABLED */
-
-extern "C"
-JNINativeMethod HIDDeviceManager_tab[8] = {
-        { "HIDDeviceRegisterCallback", "()V", (void*)HID_DEVICE_MANAGER_JAVA_INTERFACE(HIDDeviceRegisterCallback) },
-        { "HIDDeviceReleaseCallback", "()V", (void*)HID_DEVICE_MANAGER_JAVA_INTERFACE(HIDDeviceReleaseCallback) },
-        { "HIDDeviceConnected", "(ILjava/lang/String;IILjava/lang/String;ILjava/lang/String;Ljava/lang/String;IIII)V", (void*)HID_DEVICE_MANAGER_JAVA_INTERFACE(HIDDeviceConnected) },
-        { "HIDDeviceOpenPending", "(I)V", (void*)HID_DEVICE_MANAGER_JAVA_INTERFACE(HIDDeviceOpenPending) },
-        { "HIDDeviceOpenResult", "(IZ)V", (void*)HID_DEVICE_MANAGER_JAVA_INTERFACE(HIDDeviceOpenResult) },
-        { "HIDDeviceDisconnected", "(I)V", (void*)HID_DEVICE_MANAGER_JAVA_INTERFACE(HIDDeviceDisconnected) },
-        { "HIDDeviceInputReport", "(I[B)V", (void*)HID_DEVICE_MANAGER_JAVA_INTERFACE(HIDDeviceInputReport) },
-        { "HIDDeviceFeatureReport", "(I[B)V", (void*)HID_DEVICE_MANAGER_JAVA_INTERFACE(HIDDeviceFeatureReport) }
-};

+ 0 - 39
src/hidapi/android/hid.h

@@ -1,39 +0,0 @@
-/*
-  Simple DirectMedia Layer
-  Copyright (C) 2022 Valve Corporation
-
-  This software is provided 'as-is', without any express or implied
-  warranty.  In no event will the authors be held liable for any damages
-  arising from the use of this software.
-
-  Permission is granted to anyone to use this software for any purpose,
-  including commercial applications, and to alter it and redistribute it
-  freely, subject to the following restrictions:
-
-  1. The origin of this software must not be misrepresented; you must not
-     claim that you wrote the original software. If you use this software
-     in a product, an acknowledgment in the product documentation would be
-     appreciated but is not required.
-  2. Altered source versions must be plainly marked as such, and must not be
-     misrepresented as being the original software.
-  3. This notice may not be removed or altered from any source distribution.
-*/
-
-// Purpose: Exporting table containing HIDDeviceManager native methods
-
-#ifndef SDL_android_hid_h_
-#define SDL_android_hid_h_
-
-#include <jni.h>
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-extern JNINativeMethod HIDDeviceManager_tab[8];
-
-#ifdef __cplusplus
-}
-#endif
-
-#endif

+ 0 - 16
src/hidapi/android/jni/Android.mk

@@ -1,16 +0,0 @@
-LOCAL_PATH:= $(call my-dir)
-
-HIDAPI_ROOT_REL:= ../..
-HIDAPI_ROOT_ABS:= $(LOCAL_PATH)/../..
-
-include $(CLEAR_VARS)
-
-LOCAL_CPPFLAGS += -std=c++11
-
-LOCAL_SRC_FILES := \
-  $(HIDAPI_ROOT_REL)/android/hid.cpp
-
-LOCAL_MODULE := libhidapi
-LOCAL_LDLIBS := -llog
-
-include $(BUILD_SHARED_LIBRARY)

+ 0 - 2
src/hidapi/android/jni/Application.mk

@@ -1,2 +0,0 @@
-APP_STL := gnustl_static
-APP_ABI := armeabi-v7a

+ 0 - 14
src/hidapi/android/project.properties

@@ -1,14 +0,0 @@
-# This file is automatically generated by Android Tools.
-# Do not modify this file -- YOUR CHANGES WILL BE ERASED!
-#
-# This file must be checked in Version Control Systems.
-#
-# To customize properties used by the Ant build system edit
-# "ant.properties", and override values to adapt the script to your
-# project structure.
-#
-# To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home):
-#proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt
-
-# Project target.
-target=android-21

+ 44 - 24
src/hidapi/configure.ac

@@ -1,13 +1,9 @@
 AC_PREREQ(2.63)
 
-# Version number. This is currently the only place.
-m4_define([HIDAPI_MAJOR],   0)
-m4_define([HIDAPI_MINOR],   8)
-m4_define([HIDAPI_RELEASE], 0)
-m4_define([HIDAPI_RC],      -rc1)
-m4_define([VERSION_STRING], HIDAPI_MAJOR[.]HIDAPI_MINOR[.]HIDAPI_RELEASE[]HIDAPI_RC)
+AC_INIT([hidapi],[m4_normalize(m4_builtin([include], VERSION))],[https://github.com/libusb/hidapi/issues])
 
-AC_INIT([hidapi],[VERSION_STRING],[alan@signal11.us])
+echo "This build script for HIDAPI is deprecated."
+echo "Consider using CMake instead."
 
 # Library soname version
 # Follow the following rules (particularly the ones in the second link):
@@ -20,7 +16,6 @@ LTLDFLAGS="-version-info ${lt_current}:${lt_revision}:${lt_age}"
 
 AC_CONFIG_MACRO_DIR([m4])
 AM_INIT_AUTOMAKE([foreign -Wall -Werror])
-AC_CONFIG_MACRO_DIR([m4])
 
 m4_ifdef([AM_PROG_AR], [AM_PROG_AR])
 LT_INIT
@@ -63,14 +58,14 @@ case $host in
 
 	# HIDAPI/hidraw libs
 	PKG_CHECK_MODULES([libudev], [libudev], true, [hidapi_lib_error libudev])
-	LIBS_HIDRAW_PR+=" $libudev_LIBS"
-	CFLAGS_HIDRAW+=" $libudev_CFLAGS"
+	LIBS_HIDRAW_PR="${LIBS_HIDRAW_PR} $libudev_LIBS"
+	CFLAGS_HIDRAW="${CFLAGS_HIDRAW} $libudev_CFLAGS"
 
 	# HIDAPI/libusb libs
-	AC_CHECK_LIB([rt], [clock_gettime], [LIBS_LIBUSB_PRIVATE+=" -lrt"], [hidapi_lib_error librt])
+	AC_CHECK_LIB([rt], [clock_gettime], [LIBS_LIBUSB_PRIVATE="${LIBS_LIBUSB_PRIVATE} -lrt"], [hidapi_lib_error librt])
 	PKG_CHECK_MODULES([libusb], [libusb-1.0 >= 1.0.9], true, [hidapi_lib_error libusb-1.0])
-	LIBS_LIBUSB_PRIVATE+=" $libusb_LIBS"
-	CFLAGS_LIBUSB+=" $libusb_CFLAGS"
+	LIBS_LIBUSB_PRIVATE="${LIBS_LIBUSB_PRIVATE} $libusb_LIBS"
+	CFLAGS_LIBUSB="${CFLAGS_LIBUSB} $libusb_CFLAGS"
 	;;
 *-darwin*)
 	AC_MSG_RESULT([ (Mac OS X back-end)])
@@ -79,7 +74,7 @@ case $host in
 	backend="mac"
 	os="darwin"
 	threads="pthreads"
-	LIBS="${LIBS} -framework IOKit -framework CoreFoundation"
+	LIBS="${LIBS} -framework IOKit -framework CoreFoundation -framework AppKit"
 	;;
 *-freebsd*)
 	AC_MSG_RESULT([ (FreeBSD back-end)])
@@ -92,9 +87,10 @@ case $host in
 	CFLAGS="$CFLAGS -I/usr/local/include"
 	LDFLAGS="$LDFLAGS -L/usr/local/lib"
 	LIBS="${LIBS}"
-	AC_CHECK_LIB([usb], [libusb_init], [LIBS_LIBUSB_PRIVATE="${LIBS_LIBUSB_PRIVATE} -lusb"], [hidapi_lib_error libusb])
+	PKG_CHECK_MODULES([libusb], [libusb-1.0 >= 1.0.9], true, [hidapi_lib_error libusb-1.0])
+	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])
-	echo libs_priv: $LIBS_LIBUSB_PRIVATE
 	;;
 *-kfreebsd*)
 	AC_MSG_RESULT([ (kFreeBSD back-end)])
@@ -104,8 +100,22 @@ case $host in
 	os="kfreebsd"
 	threads="pthreads"
 
-	AC_CHECK_LIB([usb], [libusb_init], [LIBS_LIBUSB_PRIVATE="${LIBS_LIBUSB_PRIVATE} -lusb"], [hidapi_lib_error libusb])
-	echo libs_priv: $LIBS_LIBUSB_PRIVATE
+	PKG_CHECK_MODULES([libusb], [libusb-1.0 >= 1.0.9], true, [hidapi_lib_error libusb-1.0])
+	LIBS_LIBUSB_PRIVATE="${LIBS_LIBUSB_PRIVATE} $libusb_LIBS"
+	CFLAGS_LIBUSB="${CFLAGS_LIBUSB} $libusb_CFLAGS"
+	;;
+*-*-haiku)
+	AC_MSG_RESULT([ (Haiku back-end)])
+	AC_DEFINE(OS_HAIKU, 1, [Haiku implementation])
+	AC_SUBST(OS_HAIKU)
+	backend="libusb"
+	os="haiku"
+	threads="pthreads"
+
+	PKG_CHECK_MODULES([libusb], [libusb-1.0 >= 1.0.9], true, [hidapi_lib_error libusb-1.0])
+	LIBS_LIBUSB_PRIVATE="${LIBS_LIBUSB_PRIVATE} $libusb_LIBS"
+	CFLAGS_LIBUSB="${CFLAGS_LIBUSB} $libusb_CFLAGS"
+	AC_CHECK_LIB([iconv], [libiconv_open], [LIBS_LIBUSB_PRIVATE="${LIBS_LIBUSB_PRIVATE} -liconv"], [hidapi_lib_error libiconv])
 	;;
 *-mingw*)
 	AC_MSG_RESULT([ (Windows back-end, using MinGW)])
@@ -113,6 +123,15 @@ case $host in
 	os="windows"
 	threads="windows"
 	win_implementation="mingw"
+	LDFLAGS="${LDFLAGS} -static-libgcc"
+	;;
+*-msys*)
+	AC_MSG_RESULT([ (Windows back-end, using MSYS2)])
+	backend="windows"
+	os="windows"
+	threads="windows"
+	win_implementation="mingw"
+	LDFLAGS="${LDFLAGS} -static-libgcc"
 	;;
 *-cygwin*)
 	AC_MSG_RESULT([ (Windows back-end, using Cygwin)])
@@ -136,7 +155,7 @@ if test "x$os" = xwindows; then
 	AC_DEFINE(OS_WINDOWS, 1, [Windows implementations])
 	AC_SUBST(OS_WINDOWS)
 	LDFLAGS="${LDFLAGS} -no-undefined"
-	LIBS="${LIBS} -lsetupapi"
+	LIBS="${LIBS}"
 fi
 
 if test "x$threads" = xpthreads; then
@@ -175,15 +194,15 @@ mkdir testgui/TestGUI.app/Contents/MacOS/
 
 if test "x$testgui_enabled" != "xno"; then
 	if test "x$os" = xdarwin; then
-		# On Mac OS, don't use pkg-config.
+		# On Mac OS, do not use pkg-config.
 		AC_CHECK_PROG([foxconfig], [fox-config], [fox-config], false)
 		if test "x$foxconfig" = "xfalse"; then
 			hidapi_prog_error fox-config "FOX Toolkit"
 		fi
-		LIBS_TESTGUI+=`$foxconfig --libs`
-		LIBS_TESTGUI+=" -framework Cocoa -L/usr/X11R6/lib"
-		CFLAGS_TESTGUI+=`$foxconfig --cflags`
-		OBJCFLAGS+=" -x objective-c++"
+		LIBS_TESTGUI="${LIBS_TESTGUI} `$foxconfig --libs`"
+		LIBS_TESTGUI="${LIBS_TESTGUI} -framework Cocoa -L/usr/X11R6/lib"
+		CFLAGS_TESTGUI="${CFLAGS_TESTGUI} `$foxconfig --cflags`"
+		OBJCFLAGS="${OBJCFLAGS} -x objective-c++"
 	elif test "x$os" = xwindows; then
 		# On Windows, just set the paths for Fox toolkit
 		if test "x$win_implementation" = xmingw; then
@@ -213,6 +232,7 @@ AM_CONDITIONAL(OS_LINUX, test "x$os" = xlinux)
 AM_CONDITIONAL(OS_DARWIN, test "x$os" = xdarwin)
 AM_CONDITIONAL(OS_FREEBSD, test "x$os" = xfreebsd)
 AM_CONDITIONAL(OS_KFREEBSD, test "x$os" = xkfreebsd)
+AM_CONDITIONAL(OS_HAIKU, test "x$os" = xhaiku)
 AM_CONDITIONAL(OS_WINDOWS, test "x$os" = xwindows)
 
 AC_CONFIG_HEADERS([config.h])

+ 31 - 0
src/hidapi/dist/hidapi.podspec

@@ -0,0 +1,31 @@
+Pod::Spec.new do |spec|
+
+  spec.name         = "hidapi"
+  spec.version      = File.read('../VERSION')
+  spec.summary      = "A Simple library for communicating with USB and Bluetooth HID devices on Linux, Mac and Windows."
+
+  spec.description  = <<-DESC
+  HIDAPI is a multi-platform library which allows an application to interface with USB and Bluetooth HID-Class devices on Windows, Linux, FreeBSD, and macOS. HIDAPI can be either built as a shared library (.so, .dll or .dylib) or can be embedded directly into a target application by adding a single source file (per platform) and a single header.
+                   DESC
+
+  spec.homepage     = "https://github.com/libusb/hidapi"
+
+  spec.license      = { :type=> "GNU GPLv3 or BSD or HIDAPI original", :file => "LICENSE.txt" }
+
+  spec.authors      = { "Alan Ott" => "alan@signal11.us",
+                        "Ludovic Rousseau" => "rousseau@debian.org",
+                        "libusb/hidapi Team" => "https://github.com/libusb/hidapi/blob/master/AUTHORS.txt",
+                      }
+
+  spec.platform     = :osx
+  spec.osx.deployment_target = "10.7"
+
+  spec.source       = { :git => "https://github.com/libusb/hidapi.git", :tag => "hidapi-#{spec.version}" }
+
+  spec.source_files = "mac/hid.c", "hidapi/hidapi.h", "mac/hidapi_darwin.h"
+
+  spec.public_header_files = "hidapi/hidapi.h", "mac/hidapi_darwin.h"
+
+  spec.frameworks   = "IOKit", "CoreFoundation", "AppKit"
+
+end

BIN
src/hidapi/documentation/cmake-gui-drop-down.png


BIN
src/hidapi/documentation/cmake-gui-highlights.png


+ 1963 - 869
src/hidapi/doxygen/Doxyfile

@@ -1,96 +1,151 @@
-# Doxyfile 1.7.1
+# Doxyfile 1.9.6
 
 # This file describes the settings to be used by the documentation system
-# doxygen (www.doxygen.org) for a project
+# doxygen (www.doxygen.org) for a project.
 #
-# All text after a hash (#) is considered a comment and will be ignored
+# All text after a double hash (##) is considered a comment and is placed in
+# front of the TAG it is preceding.
+#
+# All text after a single hash (#) is considered a comment and will be ignored.
 # The format is:
-#       TAG = value [value, ...]
-# For lists items can also be appended using:
-#       TAG += value [value, ...]
-# Values that contain spaces should be placed between quotes (" ")
+# TAG = value [value, ...]
+# For lists, items can also be appended using:
+# TAG += value [value, ...]
+# Values that contain spaces should be placed between quotes (\" \").
+#
+# Note:
+#
+# Use doxygen to compare the used configuration file with the template
+# configuration file:
+# doxygen -x [configFile]
+# Use doxygen to compare the used configuration file with the template
+# configuration file without replacing the environment variables or CMake type
+# replacement variables:
+# doxygen -x_noenv [configFile]
 
 #---------------------------------------------------------------------------
 # Project related configuration options
 #---------------------------------------------------------------------------
 
-# This tag specifies the encoding used for all characters in the config file
-# that follow. The default is UTF-8 which is also the encoding used for all
+# This tag specifies the encoding used for all characters in the configuration
+# file that follow. The default is UTF-8 which is also the encoding used for all
 # text before the first occurrence of this tag. Doxygen uses libiconv (or the
 # iconv built into libc) for the transcoding. See
-# http://www.gnu.org/software/libiconv for the list of possible encodings.
+# https://www.gnu.org/software/libiconv/ for the list of possible encodings.
+# The default value is: UTF-8.
 
 DOXYFILE_ENCODING      = UTF-8
 
-# The PROJECT_NAME tag is a single word (or a sequence of words surrounded
-# by quotes) that should identify the project.
+# The PROJECT_NAME tag is a single word (or a sequence of words surrounded by
+# double-quotes, unless you are using Doxywizard) that should identify the
+# project for which the documentation is generated. This name is used in the
+# title of most generated pages and in a few other places.
+# The default value is: My Project.
 
 PROJECT_NAME           = hidapi
 
-# The PROJECT_NUMBER tag can be used to enter a project or revision number.
-# This could be handy for archiving the generated documentation or
-# if some version control system is used.
+# The PROJECT_NUMBER tag can be used to enter a project or revision number. This
+# could be handy for archiving the generated documentation or if some version
+# control system is used.
 
 PROJECT_NUMBER         =
 
-# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute)
-# base path where the generated documentation will be put.
-# If a relative path is entered, it will be relative to the location
-# where doxygen was started. If left blank the current directory will be used.
+# Using the PROJECT_BRIEF tag one can provide an optional one line description
+# for a project that appears at the top of each page and should give viewer a
+# quick idea about the purpose of the project. Keep the description short.
+
+PROJECT_BRIEF          =
+
+# With the PROJECT_LOGO tag one can specify a logo or an icon that is included
+# in the documentation. The maximum height of the logo should not exceed 55
+# pixels and the maximum width should not exceed 200 pixels. Doxygen will copy
+# the logo to the output directory.
+
+PROJECT_LOGO           =
+
+# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) path
+# into which the generated documentation will be written. If a relative path is
+# entered, it will be relative to the location where doxygen was started. If
+# left blank the current directory will be used.
 
 OUTPUT_DIRECTORY       =
 
-# If the CREATE_SUBDIRS tag is set to YES, then doxygen will create
-# 4096 sub-directories (in 2 levels) under the output directory of each output
-# format and will distribute the generated files over these directories.
-# Enabling this option can be useful when feeding doxygen a huge amount of
-# source files, where putting all generated files in the same directory would
-# otherwise cause performance problems for the file system.
+# If the CREATE_SUBDIRS tag is set to YES then doxygen will create up to 4096
+# sub-directories (in 2 levels) under the output directory of each output format
+# and will distribute the generated files over these directories. Enabling this
+# option can be useful when feeding doxygen a huge amount of source files, where
+# putting all generated files in the same directory would otherwise causes
+# performance problems for the file system. Adapt CREATE_SUBDIRS_LEVEL to
+# control the number of sub-directories.
+# The default value is: NO.
 
 CREATE_SUBDIRS         = NO
 
+# Controls the number of sub-directories that will be created when
+# CREATE_SUBDIRS tag is set to YES. Level 0 represents 16 directories, and every
+# level increment doubles the number of directories, resulting in 4096
+# directories at level 8 which is the default and also the maximum value. The
+# sub-directories are organized in 2 levels, the first level always has a fixed
+# number of 16 directories.
+# Minimum value: 0, maximum value: 8, default value: 8.
+# This tag requires that the tag CREATE_SUBDIRS is set to YES.
+
+CREATE_SUBDIRS_LEVEL   = 8
+
+# If the ALLOW_UNICODE_NAMES tag is set to YES, doxygen will allow non-ASCII
+# characters to appear in the names of generated files. If set to NO, non-ASCII
+# characters will be escaped, for example _xE3_x81_x84 will be used for Unicode
+# U+3044.
+# The default value is: NO.
+
+ALLOW_UNICODE_NAMES    = NO
+
 # The OUTPUT_LANGUAGE tag is used to specify the language in which all
 # documentation generated by doxygen is written. Doxygen will use this
 # information to generate all constant output in the proper language.
-# The default language is English, other supported languages are:
-# Afrikaans, Arabic, Brazilian, Catalan, Chinese, Chinese-Traditional,
-# Croatian, Czech, Danish, Dutch, Esperanto, Farsi, Finnish, French, German,
-# Greek, Hungarian, Italian, Japanese, Japanese-en (Japanese with English
-# messages), Korean, Korean-en, Lithuanian, Norwegian, Macedonian, Persian,
-# Polish, Portuguese, Romanian, Russian, Serbian, Serbian-Cyrilic, Slovak,
-# Slovene, Spanish, Swedish, Ukrainian, and Vietnamese.
+# Possible values are: Afrikaans, Arabic, Armenian, Brazilian, Bulgarian,
+# Catalan, Chinese, Chinese-Traditional, Croatian, Czech, Danish, Dutch, English
+# (United States), Esperanto, Farsi (Persian), Finnish, French, German, Greek,
+# Hindi, Hungarian, Indonesian, Italian, Japanese, Japanese-en (Japanese with
+# English messages), Korean, Korean-en (Korean with English messages), Latvian,
+# Lithuanian, Macedonian, Norwegian, Persian (Farsi), Polish, Portuguese,
+# Romanian, Russian, Serbian, Serbian-Cyrillic, Slovak, Slovene, Spanish,
+# Swedish, Turkish, Ukrainian and Vietnamese.
+# The default value is: English.
 
 OUTPUT_LANGUAGE        = English
 
-# If the BRIEF_MEMBER_DESC tag is set to YES (the default) Doxygen will
-# include brief member descriptions after the members that are listed in
-# the file and class documentation (similar to JavaDoc).
-# Set to NO to disable this.
+# If the BRIEF_MEMBER_DESC tag is set to YES, doxygen will include brief member
+# descriptions after the members that are listed in the file and class
+# documentation (similar to Javadoc). Set to NO to disable this.
+# The default value is: YES.
 
 BRIEF_MEMBER_DESC      = YES
 
-# If the REPEAT_BRIEF tag is set to YES (the default) Doxygen will prepend
-# the brief description of a member or function before the detailed description.
-# Note: if both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the
+# If the REPEAT_BRIEF tag is set to YES, doxygen will prepend the brief
+# description of a member or function before the detailed description
+#
+# Note: If both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the
 # brief descriptions will be completely suppressed.
+# The default value is: YES.
 
 REPEAT_BRIEF           = YES
 
-# This tag implements a quasi-intelligent brief description abbreviator
-# that is used to form the text in various listings. Each string
-# in this list, if found as the leading text of the brief description, will be
-# stripped from the text and the result after processing the whole list, is
-# used as the annotated text. Otherwise, the brief description is used as-is.
-# If left blank, the following values are used ("$name" is automatically
-# replaced with the name of the entity): "The $name class" "The $name widget"
-# "The $name file" "is" "provides" "specifies" "contains"
-# "represents" "a" "an" "the"
+# This tag implements a quasi-intelligent brief description abbreviator that is
+# used to form the text in various listings. Each string in this list, if found
+# as the leading text of the brief description, will be stripped from the text
+# and the result, after processing the whole list, is used as the annotated
+# text. Otherwise, the brief description is used as-is. If left blank, the
+# following values are used ($name is automatically replaced with the name of
+# the entity):The $name class, The $name widget, The $name file, is, provides,
+# specifies, contains, represents, a, an and the.
 
 ABBREVIATE_BRIEF       =
 
 # If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then
-# Doxygen will generate a detailed section even if there is only a brief
+# doxygen will generate a detailed section even if there is only a brief
 # description.
+# The default value is: NO.
 
 ALWAYS_DETAILED_SEC    = NO
 
@@ -98,531 +153,831 @@ ALWAYS_DETAILED_SEC    = NO
 # inherited members of a class in the documentation of that class as if those
 # members were ordinary class members. Constructors, destructors and assignment
 # operators of the base classes will not be shown.
+# The default value is: NO.
 
 INLINE_INHERITED_MEMB  = NO
 
-# If the FULL_PATH_NAMES tag is set to YES then Doxygen will prepend the full
-# path before files name in the file list and in the header files. If set
-# to NO the shortest path that makes the file name unique will be used.
+# If the FULL_PATH_NAMES tag is set to YES, doxygen will prepend the full path
+# before files name in the file list and in the header files. If set to NO the
+# shortest path that makes the file name unique will be used
+# The default value is: YES.
 
 FULL_PATH_NAMES        = YES
 
-# If the FULL_PATH_NAMES tag is set to YES then the STRIP_FROM_PATH tag
-# can be used to strip a user-defined part of the path. Stripping is
-# only done if one of the specified strings matches the left-hand part of
-# the path. The tag can be used to show relative paths in the file list.
-# If left blank the directory from which doxygen is run is used as the
-# path to strip.
+# The STRIP_FROM_PATH tag can be used to strip a user-defined part of the path.
+# Stripping is only done if one of the specified strings matches the left-hand
+# part of the path. The tag can be used to show relative paths in the file list.
+# If left blank the directory from which doxygen is run is used as the path to
+# strip.
+#
+# Note that you can specify absolute paths here, but also relative paths, which
+# will be relative from the directory where doxygen is started.
+# This tag requires that the tag FULL_PATH_NAMES is set to YES.
 
-STRIP_FROM_PATH        =
+STRIP_FROM_PATH        = ../
 
-# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of
-# the path mentioned in the documentation of a class, which tells
-# the reader which header file to include in order to use a class.
-# If left blank only the name of the header file containing the class
-# definition is used. Otherwise one should specify the include paths that
-# are normally passed to the compiler using the -I flag.
+# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of the
+# path mentioned in the documentation of a class, which tells the reader which
+# header file to include in order to use a class. If left blank only the name of
+# the header file containing the class definition is used. Otherwise one should
+# specify the list of include paths that are normally passed to the compiler
+# using the -I flag.
 
 STRIP_FROM_INC_PATH    =
 
-# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter
-# (but less readable) file names. This can be useful is your file systems
-# doesn't support long names like on DOS, Mac, or CD-ROM.
+# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter (but
+# less readable) file names. This can be useful is your file systems doesn't
+# support long names like on DOS, Mac, or CD-ROM.
+# The default value is: NO.
 
 SHORT_NAMES            = NO
 
-# If the JAVADOC_AUTOBRIEF tag is set to YES then Doxygen
-# will interpret the first line (until the first dot) of a JavaDoc-style
-# comment as the brief description. If set to NO, the JavaDoc
-# comments will behave just like regular Qt-style comments
-# (thus requiring an explicit @brief command for a brief description.)
+# If the JAVADOC_AUTOBRIEF tag is set to YES then doxygen will interpret the
+# first line (until the first dot) of a Javadoc-style comment as the brief
+# description. If set to NO, the Javadoc-style will behave just like regular Qt-
+# style comments (thus requiring an explicit @brief command for a brief
+# description.)
+# The default value is: NO.
 
 JAVADOC_AUTOBRIEF      = NO
 
-# If the QT_AUTOBRIEF tag is set to YES then Doxygen will
-# interpret the first line (until the first dot) of a Qt-style
-# comment as the brief description. If set to NO, the comments
-# will behave just like regular Qt-style comments (thus requiring
-# an explicit \brief command for a brief description.)
+# If the JAVADOC_BANNER tag is set to YES then doxygen will interpret a line
+# such as
+# /***************
+# as being the beginning of a Javadoc-style comment "banner". If set to NO, the
+# Javadoc-style will behave just like regular comments and it will not be
+# interpreted by doxygen.
+# The default value is: NO.
+
+JAVADOC_BANNER         = NO
+
+# If the QT_AUTOBRIEF tag is set to YES then doxygen will interpret the first
+# line (until the first dot) of a Qt-style comment as the brief description. If
+# set to NO, the Qt-style will behave just like regular Qt-style comments (thus
+# requiring an explicit \brief command for a brief description.)
+# The default value is: NO.
 
 QT_AUTOBRIEF           = NO
 
-# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make Doxygen
-# treat a multi-line C++ special comment block (i.e. a block of //! or ///
-# comments) as a brief description. This used to be the default behaviour.
-# The new default is to treat a multi-line C++ comment block as a detailed
-# description. Set this tag to YES if you prefer the old behaviour instead.
+# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make doxygen treat a
+# multi-line C++ special comment block (i.e. a block of //! or /// comments) as
+# a brief description. This used to be the default behavior. The new default is
+# to treat a multi-line C++ comment block as a detailed description. Set this
+# tag to YES if you prefer the old behavior instead.
+#
+# Note that setting this tag to YES also means that rational rose comments are
+# not recognized any more.
+# The default value is: NO.
 
 MULTILINE_CPP_IS_BRIEF = NO
 
-# If the INHERIT_DOCS tag is set to YES (the default) then an undocumented
-# member inherits the documentation from any documented member that it
-# re-implements.
+# By default Python docstrings are displayed as preformatted text and doxygen's
+# special commands cannot be used. By setting PYTHON_DOCSTRING to NO the
+# doxygen's special commands can be used and the contents of the docstring
+# documentation blocks is shown as doxygen documentation.
+# The default value is: YES.
+
+PYTHON_DOCSTRING       = YES
+
+# If the INHERIT_DOCS tag is set to YES then an undocumented member inherits the
+# documentation from any documented member that it re-implements.
+# The default value is: YES.
 
 INHERIT_DOCS           = YES
 
-# If the SEPARATE_MEMBER_PAGES tag is set to YES, then doxygen will produce
-# a new page for each member. If set to NO, the documentation of a member will
-# be part of the file/class/namespace that contains it.
+# If the SEPARATE_MEMBER_PAGES tag is set to YES then doxygen will produce a new
+# page for each member. If set to NO, the documentation of a member will be part
+# of the file/class/namespace that contains it.
+# The default value is: NO.
 
 SEPARATE_MEMBER_PAGES  = NO
 
-# The TAB_SIZE tag can be used to set the number of spaces in a tab.
-# Doxygen uses this value to replace tabs by spaces in code fragments.
+# The TAB_SIZE tag can be used to set the number of spaces in a tab. Doxygen
+# uses this value to replace tabs by spaces in code fragments.
+# Minimum value: 1, maximum value: 16, default value: 4.
 
 TAB_SIZE               = 8
 
-# This tag can be used to specify a number of aliases that acts
-# as commands in the documentation. An alias has the form "name=value".
-# For example adding "sideeffect=\par Side Effects:\n" will allow you to
-# put the command \sideeffect (or @sideeffect) in the documentation, which
-# will result in a user-defined paragraph with heading "Side Effects:".
-# You can put \n's in the value part of an alias to insert newlines.
+# This tag can be used to specify a number of aliases that act as commands in
+# the documentation. An alias has the form:
+# name=value
+# For example adding
+# "sideeffect=@par Side Effects:^^"
+# will allow you to put the command \sideeffect (or @sideeffect) in the
+# documentation, which will result in a user-defined paragraph with heading
+# "Side Effects:". Note that you cannot put \n's in the value part of an alias
+# to insert newlines (in the resulting output). You can put ^^ in the value part
+# of an alias to insert a newline as if a physical newline was in the original
+# file. When you need a literal { or } or , in the value part of an alias you
+# have to escape them by means of a backslash (\), this can lead to conflicts
+# with the commands \{ and \} for these it is advised to use the version @{ and
+# @} or use a double escape (\\{ and \\})
 
 ALIASES                =
 
-# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C
-# sources only. Doxygen will then generate output that is more tailored for C.
-# For instance, some of the names that are used will be different. The list
-# of all members will be omitted, etc.
+# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources
+# only. Doxygen will then generate output that is more tailored for C. For
+# instance, some of the names that are used will be different. The list of all
+# members will be omitted, etc.
+# The default value is: NO.
 
 OPTIMIZE_OUTPUT_FOR_C  = YES
 
-# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java
-# sources only. Doxygen will then generate output that is more tailored for
-# Java. For instance, namespaces will be presented as packages, qualified
-# scopes will look different, etc.
+# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java or
+# Python sources only. Doxygen will then generate output that is more tailored
+# for that language. For instance, namespaces will be presented as packages,
+# qualified scopes will look different, etc.
+# The default value is: NO.
 
 OPTIMIZE_OUTPUT_JAVA   = NO
 
 # Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran
-# sources only. Doxygen will then generate output that is more tailored for
-# Fortran.
+# sources. Doxygen will then generate output that is tailored for Fortran.
+# The default value is: NO.
 
 OPTIMIZE_FOR_FORTRAN   = NO
 
 # Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL
-# sources. Doxygen will then generate output that is tailored for
-# VHDL.
+# sources. Doxygen will then generate output that is tailored for VHDL.
+# The default value is: NO.
 
 OPTIMIZE_OUTPUT_VHDL   = NO
 
+# Set the OPTIMIZE_OUTPUT_SLICE tag to YES if your project consists of Slice
+# sources only. Doxygen will then generate output that is more tailored for that
+# language. For instance, namespaces will be presented as modules, types will be
+# separated into more groups, etc.
+# The default value is: NO.
+
+OPTIMIZE_OUTPUT_SLICE  = NO
+
 # Doxygen selects the parser to use depending on the extension of the files it
-# parses. With this tag you can assign which parser to use for a given extension.
-# Doxygen has a built-in mapping, but you can override or extend it using this
-# tag. The format is ext=language, where ext is a file extension, and language
-# is one of the parsers supported by doxygen: IDL, Java, Javascript, CSharp, C,
-# C++, D, PHP, Objective-C, Python, Fortran, VHDL, C, C++. For instance to make
-# doxygen treat .inc files as Fortran files (default is PHP), and .f files as C
-# (default is Fortran), use: inc=Fortran f=C. Note that for custom extensions
-# you also need to set FILE_PATTERNS otherwise the files are not read by doxygen.
+# parses. With this tag you can assign which parser to use for a given
+# extension. Doxygen has a built-in mapping, but you can override or extend it
+# using this tag. The format is ext=language, where ext is a file extension, and
+# language is one of the parsers supported by doxygen: IDL, Java, JavaScript,
+# Csharp (C#), C, C++, Lex, D, PHP, md (Markdown), Objective-C, Python, Slice,
+# VHDL, Fortran (fixed format Fortran: FortranFixed, free formatted Fortran:
+# FortranFree, unknown formatted Fortran: Fortran. In the later case the parser
+# tries to guess whether the code is fixed or free formatted code, this is the
+# default for Fortran type files). For instance to make doxygen treat .inc files
+# as Fortran files (default is PHP), and .f files as C (default is Fortran),
+# use: inc=Fortran f=C.
+#
+# Note: For files without extension you can use no_extension as a placeholder.
+#
+# Note that for custom extensions you also need to set FILE_PATTERNS otherwise
+# the files are not read by doxygen. When specifying no_extension you should add
+# * to the FILE_PATTERNS.
+#
+# Note see also the list of default file extension mappings.
 
 EXTENSION_MAPPING      =
 
+# If the MARKDOWN_SUPPORT tag is enabled then doxygen pre-processes all comments
+# according to the Markdown format, which allows for more readable
+# documentation. See https://daringfireball.net/projects/markdown/ for details.
+# The output of markdown processing is further processed by doxygen, so you can
+# mix doxygen, HTML, and XML commands with Markdown formatting. Disable only in
+# case of backward compatibilities issues.
+# The default value is: YES.
+
+MARKDOWN_SUPPORT       = NO
+
+# When the TOC_INCLUDE_HEADINGS tag is set to a non-zero value, all headings up
+# to that level are automatically included in the table of contents, even if
+# they do not have an id attribute.
+# Note: This feature currently applies only to Markdown headings.
+# Minimum value: 0, maximum value: 99, default value: 5.
+# This tag requires that the tag MARKDOWN_SUPPORT is set to YES.
+
+TOC_INCLUDE_HEADINGS   = 5
+
+# When enabled doxygen tries to link words that correspond to documented
+# classes, or namespaces to their corresponding documentation. Such a link can
+# be prevented in individual cases by putting a % sign in front of the word or
+# globally by setting AUTOLINK_SUPPORT to NO.
+# The default value is: YES.
+
+AUTOLINK_SUPPORT       = YES
+
 # If you use STL classes (i.e. std::string, std::vector, etc.) but do not want
-# to include (a tag file for) the STL sources as input, then you should
-# set this tag to YES in order to let doxygen match functions declarations and
-# definitions whose arguments contain STL classes (e.g. func(std::string); v.s.
-# func(std::string) {}). This also make the inheritance and collaboration
+# to include (a tag file for) the STL sources as input, then you should set this
+# tag to YES in order to let doxygen match functions declarations and
+# definitions whose arguments contain STL classes (e.g. func(std::string);
+# versus func(std::string) {}). This also make the inheritance and collaboration
 # diagrams that involve STL classes more complete and accurate.
+# The default value is: NO.
 
 BUILTIN_STL_SUPPORT    = NO
 
 # If you use Microsoft's C++/CLI language, you should set this option to YES to
 # enable parsing support.
+# The default value is: NO.
 
 CPP_CLI_SUPPORT        = NO
 
-# Set the SIP_SUPPORT tag to YES if your project consists of sip sources only.
-# Doxygen will parse them like normal C++ but will assume all classes use public
-# instead of private inheritance when no explicit protection keyword is present.
+# Set the SIP_SUPPORT tag to YES if your project consists of sip (see:
+# https://www.riverbankcomputing.com/software/sip/intro) sources only. Doxygen
+# will parse them like normal C++ but will assume all classes use public instead
+# of private inheritance when no explicit protection keyword is present.
+# The default value is: NO.
 
 SIP_SUPPORT            = NO
 
-# For Microsoft's IDL there are propget and propput attributes to indicate getter
-# and setter methods for a property. Setting this option to YES (the default)
-# will make doxygen to replace the get and set methods by a property in the
-# documentation. This will only work if the methods are indeed getting or
-# setting a simple type. If this is not the case, or you want to show the
-# methods anyway, you should set this option to NO.
+# For Microsoft's IDL there are propget and propput attributes to indicate
+# getter and setter methods for a property. Setting this option to YES will make
+# doxygen to replace the get and set methods by a property in the documentation.
+# This will only work if the methods are indeed getting or setting a simple
+# type. If this is not the case, or you want to show the methods anyway, you
+# should set this option to NO.
+# The default value is: YES.
 
 IDL_PROPERTY_SUPPORT   = YES
 
 # If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC
-# tag is set to YES, then doxygen will reuse the documentation of the first
+# tag is set to YES then doxygen will reuse the documentation of the first
 # member in the group (if any) for the other members of the group. By default
 # all members of a group must be documented explicitly.
+# The default value is: NO.
 
 DISTRIBUTE_GROUP_DOC   = NO
 
-# Set the SUBGROUPING tag to YES (the default) to allow class member groups of
-# the same type (for instance a group of public functions) to be put as a
-# subgroup of that type (e.g. under the Public Functions section). Set it to
-# NO to prevent subgrouping. Alternatively, this can be done per class using
-# the \nosubgrouping command.
+# If one adds a struct or class to a group and this option is enabled, then also
+# any nested class or struct is added to the same group. By default this option
+# is disabled and one has to add nested compounds explicitly via \ingroup.
+# The default value is: NO.
+
+GROUP_NESTED_COMPOUNDS = NO
+
+# Set the SUBGROUPING tag to YES to allow class member groups of the same type
+# (for instance a group of public functions) to be put as a subgroup of that
+# type (e.g. under the Public Functions section). Set it to NO to prevent
+# subgrouping. Alternatively, this can be done per class using the
+# \nosubgrouping command.
+# The default value is: YES.
 
 SUBGROUPING            = YES
 
-# When TYPEDEF_HIDES_STRUCT is enabled, a typedef of a struct, union, or enum
-# is documented as struct, union, or enum with the name of the typedef. So
+# When the INLINE_GROUPED_CLASSES tag is set to YES, classes, structs and unions
+# are shown inside the group in which they are included (e.g. using \ingroup)
+# instead of on a separate page (for HTML and Man pages) or section (for LaTeX
+# and RTF).
+#
+# Note that this feature does not work in combination with
+# SEPARATE_MEMBER_PAGES.
+# The default value is: NO.
+
+INLINE_GROUPED_CLASSES = NO
+
+# When the INLINE_SIMPLE_STRUCTS tag is set to YES, structs, classes, and unions
+# with only public data fields or simple typedef fields will be shown inline in
+# the documentation of the scope in which they are defined (i.e. file,
+# namespace, or group documentation), provided this scope is documented. If set
+# to NO, structs, classes, and unions are shown on a separate page (for HTML and
+# Man pages) or section (for LaTeX and RTF).
+# The default value is: NO.
+
+INLINE_SIMPLE_STRUCTS  = NO
+
+# When TYPEDEF_HIDES_STRUCT tag is enabled, a typedef of a struct, union, or
+# enum is documented as struct, union, or enum with the name of the typedef. So
 # typedef struct TypeS {} TypeT, will appear in the documentation as a struct
 # with name TypeT. When disabled the typedef will appear as a member of a file,
-# namespace, or class. And the struct will be named TypeS. This can typically
-# be useful for C code in case the coding convention dictates that all compound
+# namespace, or class. And the struct will be named TypeS. This can typically be
+# useful for C code in case the coding convention dictates that all compound
 # types are typedef'ed and only the typedef is referenced, never the tag name.
+# The default value is: NO.
 
 TYPEDEF_HIDES_STRUCT   = NO
 
-# The SYMBOL_CACHE_SIZE determines the size of the internal cache use to
-# determine which symbols to keep in memory and which to flush to disk.
-# When the cache is full, less often used symbols will be written to disk.
-# For small to medium size projects (<1000 input files) the default value is
-# probably good enough. For larger projects a too small cache size can cause
-# doxygen to be busy swapping symbols to and from disk most of the time
-# causing a significant performance penality.
-# If the system has enough physical memory increasing the cache will improve the
-# performance by keeping more symbols in memory. Note that the value works on
-# a logarithmic scale so increasing the size by one will rougly double the
-# memory usage. The cache size is given by this formula:
-# 2^(16+SYMBOL_CACHE_SIZE). The valid range is 0..9, the default is 0,
-# corresponding to a cache size of 2^16 = 65536 symbols
-
-SYMBOL_CACHE_SIZE      = 0
+# The size of the symbol lookup cache can be set using LOOKUP_CACHE_SIZE. This
+# cache is used to resolve symbols given their name and scope. Since this can be
+# an expensive process and often the same symbol appears multiple times in the
+# code, doxygen keeps a cache of pre-resolved symbols. If the cache is too small
+# doxygen will become slower. If the cache is too large, memory is wasted. The
+# cache size is given by this formula: 2^(16+LOOKUP_CACHE_SIZE). The valid range
+# is 0..9, the default is 0, corresponding to a cache size of 2^16=65536
+# symbols. At the end of a run doxygen will report the cache usage and suggest
+# the optimal cache size from a speed point of view.
+# Minimum value: 0, maximum value: 9, default value: 0.
+
+LOOKUP_CACHE_SIZE      = 0
+
+# The NUM_PROC_THREADS specifies the number of threads doxygen is allowed to use
+# during processing. When set to 0 doxygen will based this on the number of
+# cores available in the system. You can set it explicitly to a value larger
+# than 0 to get more control over the balance between CPU load and processing
+# speed. At this moment only the input processing can be done using multiple
+# threads. Since this is still an experimental feature the default is set to 1,
+# which effectively disables parallel processing. Please report any issues you
+# encounter. Generating dot graphs in parallel is controlled by the
+# DOT_NUM_THREADS setting.
+# Minimum value: 0, maximum value: 32, default value: 1.
+
+NUM_PROC_THREADS       = 1
 
 #---------------------------------------------------------------------------
 # Build related configuration options
 #---------------------------------------------------------------------------
 
-# If the EXTRACT_ALL tag is set to YES doxygen will assume all entities in
-# documentation are documented, even if no documentation was available.
-# Private class members and static file members will be hidden unless
-# the EXTRACT_PRIVATE and EXTRACT_STATIC tags are set to YES
+# If the EXTRACT_ALL tag is set to YES, doxygen will assume all entities in
+# documentation are documented, even if no documentation was available. Private
+# class members and static file members will be hidden unless the
+# EXTRACT_PRIVATE respectively EXTRACT_STATIC tags are set to YES.
+# Note: This will also disable the warnings about undocumented members that are
+# normally produced when WARNINGS is set to YES.
+# The default value is: NO.
 
 EXTRACT_ALL            = NO
 
-# If the EXTRACT_PRIVATE tag is set to YES all private members of a class
-# will be included in the documentation.
+# If the EXTRACT_PRIVATE tag is set to YES, all private members of a class will
+# be included in the documentation.
+# The default value is: NO.
 
 EXTRACT_PRIVATE        = NO
 
-# If the EXTRACT_STATIC tag is set to YES all static members of a file
-# will be included in the documentation.
+# If the EXTRACT_PRIV_VIRTUAL tag is set to YES, documented private virtual
+# methods of a class will be included in the documentation.
+# The default value is: NO.
+
+EXTRACT_PRIV_VIRTUAL   = NO
+
+# If the EXTRACT_PACKAGE tag is set to YES, all members with package or internal
+# scope will be included in the documentation.
+# The default value is: NO.
+
+EXTRACT_PACKAGE        = NO
+
+# If the EXTRACT_STATIC tag is set to YES, all static members of a file will be
+# included in the documentation.
+# The default value is: NO.
 
 EXTRACT_STATIC         = NO
 
-# If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs)
-# defined locally in source files will be included in the documentation.
-# If set to NO only classes defined in header files are included.
+# If the EXTRACT_LOCAL_CLASSES tag is set to YES, classes (and structs) defined
+# locally in source files will be included in the documentation. If set to NO,
+# only classes defined in header files are included. Does not have any effect
+# for Java sources.
+# The default value is: YES.
 
 EXTRACT_LOCAL_CLASSES  = YES
 
-# This flag is only useful for Objective-C code. When set to YES local
-# methods, which are defined in the implementation section but not in
-# the interface are included in the documentation.
-# If set to NO (the default) only methods in the interface are included.
+# This flag is only useful for Objective-C code. If set to YES, local methods,
+# which are defined in the implementation section but not in the interface are
+# included in the documentation. If set to NO, only methods in the interface are
+# included.
+# The default value is: NO.
 
 EXTRACT_LOCAL_METHODS  = NO
 
 # If this flag is set to YES, the members of anonymous namespaces will be
 # extracted and appear in the documentation as a namespace called
-# 'anonymous_namespace{file}', where file will be replaced with the base
-# name of the file that contains the anonymous namespace. By default
-# anonymous namespace are hidden.
+# 'anonymous_namespace{file}', where file will be replaced with the base name of
+# the file that contains the anonymous namespace. By default anonymous namespace
+# are hidden.
+# The default value is: NO.
 
 EXTRACT_ANON_NSPACES   = NO
 
-# If the HIDE_UNDOC_MEMBERS tag is set to YES, Doxygen will hide all
-# undocumented members of documented classes, files or namespaces.
-# If set to NO (the default) these members will be included in the
-# various overviews, but no documentation section is generated.
-# This option has no effect if EXTRACT_ALL is enabled.
+# If this flag is set to YES, the name of an unnamed parameter in a declaration
+# will be determined by the corresponding definition. By default unnamed
+# parameters remain unnamed in the output.
+# The default value is: YES.
+
+RESOLVE_UNNAMED_PARAMS = YES
+
+# If the HIDE_UNDOC_MEMBERS tag is set to YES, doxygen will hide all
+# undocumented members inside documented classes or files. If set to NO these
+# members will be included in the various overviews, but no documentation
+# section is generated. This option has no effect if EXTRACT_ALL is enabled.
+# The default value is: NO.
 
 HIDE_UNDOC_MEMBERS     = NO
 
-# If the HIDE_UNDOC_CLASSES tag is set to YES, Doxygen will hide all
-# undocumented classes that are normally visible in the class hierarchy.
-# If set to NO (the default) these classes will be included in the various
-# overviews. This option has no effect if EXTRACT_ALL is enabled.
+# If the HIDE_UNDOC_CLASSES tag is set to YES, doxygen will hide all
+# undocumented classes that are normally visible in the class hierarchy. If set
+# to NO, these classes will be included in the various overviews. This option
+# will also hide undocumented C++ concepts if enabled. This option has no effect
+# if EXTRACT_ALL is enabled.
+# The default value is: NO.
 
 HIDE_UNDOC_CLASSES     = NO
 
-# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, Doxygen will hide all
-# friend (class|struct|union) declarations.
-# If set to NO (the default) these declarations will be included in the
+# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, doxygen will hide all friend
+# declarations. If set to NO, these declarations will be included in the
 # documentation.
+# The default value is: NO.
 
 HIDE_FRIEND_COMPOUNDS  = NO
 
-# If the HIDE_IN_BODY_DOCS tag is set to YES, Doxygen will hide any
-# documentation blocks found inside the body of a function.
-# If set to NO (the default) these blocks will be appended to the
-# function's detailed documentation block.
+# If the HIDE_IN_BODY_DOCS tag is set to YES, doxygen will hide any
+# documentation blocks found inside the body of a function. If set to NO, these
+# blocks will be appended to the function's detailed documentation block.
+# The default value is: NO.
 
 HIDE_IN_BODY_DOCS      = NO
 
-# The INTERNAL_DOCS tag determines if documentation
-# that is typed after a \internal command is included. If the tag is set
-# to NO (the default) then the documentation will be excluded.
-# Set it to YES to include the internal documentation.
+# The INTERNAL_DOCS tag determines if documentation that is typed after a
+# \internal command is included. If the tag is set to NO then the documentation
+# will be excluded. Set it to YES to include the internal documentation.
+# The default value is: NO.
 
 INTERNAL_DOCS          = NO
 
-# If the CASE_SENSE_NAMES tag is set to NO then Doxygen will only generate
-# file names in lower-case letters. If set to YES upper-case letters are also
-# allowed. This is useful if you have classes or files whose names only differ
-# in case and if your file system supports case sensitive file names. Windows
-# and Mac users are advised to set this option to NO.
+# With the correct setting of option CASE_SENSE_NAMES doxygen will better be
+# able to match the capabilities of the underlying filesystem. In case the
+# filesystem is case sensitive (i.e. it supports files in the same directory
+# whose names only differ in casing), the option must be set to YES to properly
+# deal with such files in case they appear in the input. For filesystems that
+# are not case sensitive the option should be set to NO to properly deal with
+# output files written for symbols that only differ in casing, such as for two
+# classes, one named CLASS and the other named Class, and to also support
+# references to files without having to specify the exact matching casing. On
+# Windows (including Cygwin) and MacOS, users should typically set this option
+# to NO, whereas on Linux or other Unix flavors it should typically be set to
+# YES.
+# Possible values are: SYSTEM, NO and YES.
+# The default value is: SYSTEM.
 
 CASE_SENSE_NAMES       = YES
 
-# If the HIDE_SCOPE_NAMES tag is set to NO (the default) then Doxygen
-# will show members with their full class and namespace scopes in the
-# documentation. If set to YES the scope will be hidden.
+# If the HIDE_SCOPE_NAMES tag is set to NO then doxygen will show members with
+# their full class and namespace scopes in the documentation. If set to YES, the
+# scope will be hidden.
+# The default value is: NO.
 
 HIDE_SCOPE_NAMES       = NO
 
-# If the SHOW_INCLUDE_FILES tag is set to YES (the default) then Doxygen
-# will put a list of the files that are included by a file in the documentation
-# of that file.
+# If the HIDE_COMPOUND_REFERENCE tag is set to NO (default) then doxygen will
+# append additional text to a page's title, such as Class Reference. If set to
+# YES the compound reference will be hidden.
+# The default value is: NO.
+
+HIDE_COMPOUND_REFERENCE= NO
+
+# If the SHOW_HEADERFILE tag is set to YES then the documentation for a class
+# will show which file needs to be included to use the class.
+# The default value is: YES.
+
+SHOW_HEADERFILE        = YES
+
+# If the SHOW_INCLUDE_FILES tag is set to YES then doxygen will put a list of
+# the files that are included by a file in the documentation of that file.
+# The default value is: YES.
 
 SHOW_INCLUDE_FILES     = YES
 
-# If the FORCE_LOCAL_INCLUDES tag is set to YES then Doxygen
-# will list include files with double quotes in the documentation
-# rather than with sharp brackets.
+# If the SHOW_GROUPED_MEMB_INC tag is set to YES then Doxygen will add for each
+# grouped member an include statement to the documentation, telling the reader
+# which file to include in order to use the member.
+# The default value is: NO.
+
+SHOW_GROUPED_MEMB_INC  = NO
+
+# If the FORCE_LOCAL_INCLUDES tag is set to YES then doxygen will list include
+# files with double quotes in the documentation rather than with sharp brackets.
+# The default value is: NO.
 
 FORCE_LOCAL_INCLUDES   = NO
 
-# If the INLINE_INFO tag is set to YES (the default) then a tag [inline]
-# is inserted in the documentation for inline members.
+# If the INLINE_INFO tag is set to YES then a tag [inline] is inserted in the
+# documentation for inline members.
+# The default value is: YES.
 
 INLINE_INFO            = YES
 
-# If the SORT_MEMBER_DOCS tag is set to YES (the default) then doxygen
-# will sort the (detailed) documentation of file and class members
-# alphabetically by member name. If set to NO the members will appear in
-# declaration order.
+# If the SORT_MEMBER_DOCS tag is set to YES then doxygen will sort the
+# (detailed) documentation of file and class members alphabetically by member
+# name. If set to NO, the members will appear in declaration order.
+# The default value is: YES.
 
 SORT_MEMBER_DOCS       = YES
 
-# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the
-# brief documentation of file, namespace and class members alphabetically
-# by member name. If set to NO (the default) the members will appear in
-# declaration order.
+# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the brief
+# descriptions of file, namespace and class members alphabetically by member
+# name. If set to NO, the members will appear in declaration order. Note that
+# this will also influence the order of the classes in the class list.
+# The default value is: NO.
 
 SORT_BRIEF_DOCS        = NO
 
-# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen
-# will sort the (brief and detailed) documentation of class members so that
-# constructors and destructors are listed first. If set to NO (the default)
-# the constructors will appear in the respective orders defined by
-# SORT_MEMBER_DOCS and SORT_BRIEF_DOCS.
-# This tag will be ignored for brief docs if SORT_BRIEF_DOCS is set to NO
-# and ignored for detailed docs if SORT_MEMBER_DOCS is set to NO.
+# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen will sort the
+# (brief and detailed) documentation of class members so that constructors and
+# destructors are listed first. If set to NO the constructors will appear in the
+# respective orders defined by SORT_BRIEF_DOCS and SORT_MEMBER_DOCS.
+# Note: If SORT_BRIEF_DOCS is set to NO this option is ignored for sorting brief
+# member documentation.
+# Note: If SORT_MEMBER_DOCS is set to NO this option is ignored for sorting
+# detailed member documentation.
+# The default value is: NO.
 
 SORT_MEMBERS_CTORS_1ST = NO
 
-# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the
-# hierarchy of group names into alphabetical order. If set to NO (the default)
-# the group names will appear in their defined order.
+# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the hierarchy
+# of group names into alphabetical order. If set to NO the group names will
+# appear in their defined order.
+# The default value is: NO.
 
 SORT_GROUP_NAMES       = NO
 
-# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be
-# sorted by fully-qualified names, including namespaces. If set to
-# NO (the default), the class list will be sorted only by class name,
-# not including the namespace part.
+# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be sorted by
+# fully-qualified names, including namespaces. If set to NO, the class list will
+# be sorted only by class name, not including the namespace part.
 # Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES.
-# Note: This option applies only to the class list, not to the
-# alphabetical list.
+# Note: This option applies only to the class list, not to the alphabetical
+# list.
+# The default value is: NO.
 
 SORT_BY_SCOPE_NAME     = NO
 
-# The GENERATE_TODOLIST tag can be used to enable (YES) or
-# disable (NO) the todo list. This list is created by putting \todo
-# commands in the documentation.
+# If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to do proper
+# type resolution of all parameters of a function it will reject a match between
+# the prototype and the implementation of a member function even if there is
+# only one candidate or it is obvious which candidate to choose by doing a
+# simple string match. By disabling STRICT_PROTO_MATCHING doxygen will still
+# accept a match between prototype and implementation in such cases.
+# The default value is: NO.
+
+STRICT_PROTO_MATCHING  = NO
+
+# The GENERATE_TODOLIST tag can be used to enable (YES) or disable (NO) the todo
+# list. This list is created by putting \todo commands in the documentation.
+# The default value is: YES.
 
 GENERATE_TODOLIST      = YES
 
-# The GENERATE_TESTLIST tag can be used to enable (YES) or
-# disable (NO) the test list. This list is created by putting \test
-# commands in the documentation.
+# The GENERATE_TESTLIST tag can be used to enable (YES) or disable (NO) the test
+# list. This list is created by putting \test commands in the documentation.
+# The default value is: YES.
 
 GENERATE_TESTLIST      = YES
 
-# The GENERATE_BUGLIST tag can be used to enable (YES) or
-# disable (NO) the bug list. This list is created by putting \bug
-# commands in the documentation.
+# The GENERATE_BUGLIST tag can be used to enable (YES) or disable (NO) the bug
+# list. This list is created by putting \bug commands in the documentation.
+# The default value is: YES.
 
 GENERATE_BUGLIST       = YES
 
-# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or
-# disable (NO) the deprecated list. This list is created by putting
-# \deprecated commands in the documentation.
+# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or disable (NO)
+# the deprecated list. This list is created by putting \deprecated commands in
+# the documentation.
+# The default value is: YES.
 
 GENERATE_DEPRECATEDLIST= YES
 
-# The ENABLED_SECTIONS tag can be used to enable conditional
-# documentation sections, marked by \if sectionname ... \endif.
+# The ENABLED_SECTIONS tag can be used to enable conditional documentation
+# sections, marked by \if <section_label> ... \endif and \cond <section_label>
+# ... \endcond blocks.
 
 ENABLED_SECTIONS       =
 
-# The MAX_INITIALIZER_LINES tag determines the maximum number of lines
-# the initial value of a variable or define consists of for it to appear in
-# the documentation. If the initializer consists of more lines than specified
-# here it will be hidden. Use a value of 0 to hide initializers completely.
-# The appearance of the initializer of individual variables and defines in the
-# documentation can be controlled using \showinitializer or \hideinitializer
-# command in the documentation regardless of this setting.
+# The MAX_INITIALIZER_LINES tag determines the maximum number of lines that the
+# initial value of a variable or macro / define can have for it to appear in the
+# documentation. If the initializer consists of more lines than specified here
+# it will be hidden. Use a value of 0 to hide initializers completely. The
+# appearance of the value of individual variables and macros / defines can be
+# controlled using \showinitializer or \hideinitializer command in the
+# documentation regardless of this setting.
+# Minimum value: 0, maximum value: 10000, default value: 30.
 
 MAX_INITIALIZER_LINES  = 30
 
-# Set the SHOW_USED_FILES tag to NO to disable the list of files generated
-# at the bottom of the documentation of classes and structs. If set to YES the
+# Set the SHOW_USED_FILES tag to NO to disable the list of files generated at
+# the bottom of the documentation of classes and structs. If set to YES, the
 # list will mention the files that were used to generate the documentation.
+# The default value is: YES.
 
 SHOW_USED_FILES        = YES
 
-# If the sources in your project are distributed over multiple directories
-# then setting the SHOW_DIRECTORIES tag to YES will show the directory hierarchy
-# in the documentation. The default is NO.
-
-SHOW_DIRECTORIES       = NO
-
-# Set the SHOW_FILES tag to NO to disable the generation of the Files page.
-# This will remove the Files entry from the Quick Index and from the
-# Folder Tree View (if specified). The default is YES.
+# Set the SHOW_FILES tag to NO to disable the generation of the Files page. This
+# will remove the Files entry from the Quick Index and from the Folder Tree View
+# (if specified).
+# The default value is: YES.
 
 SHOW_FILES             = YES
 
-# Set the SHOW_NAMESPACES tag to NO to disable the generation of the
-# Namespaces page.
-# This will remove the Namespaces entry from the Quick Index
-# and from the Folder Tree View (if specified). The default is YES.
+# Set the SHOW_NAMESPACES tag to NO to disable the generation of the Namespaces
+# page. This will remove the Namespaces entry from the Quick Index and from the
+# Folder Tree View (if specified).
+# The default value is: YES.
 
 SHOW_NAMESPACES        = YES
 
 # The FILE_VERSION_FILTER tag can be used to specify a program or script that
 # doxygen should invoke to get the current version for each file (typically from
 # the version control system). Doxygen will invoke the program by executing (via
-# popen()) the command <command> <input-file>, where <command> is the value of
-# the FILE_VERSION_FILTER tag, and <input-file> is the name of an input file
-# provided by doxygen. Whatever the program writes to standard output
-# is used as the file version. See the manual for examples.
+# popen()) the command command input-file, where command is the value of the
+# FILE_VERSION_FILTER tag, and input-file is the name of an input file provided
+# by doxygen. Whatever the program writes to standard output is used as the file
+# version. For an example see the documentation.
 
 FILE_VERSION_FILTER    =
 
 # The LAYOUT_FILE tag can be used to specify a layout file which will be parsed
 # by doxygen. The layout file controls the global structure of the generated
-# output files in an output format independent way. The create the layout file
-# that represents doxygen's defaults, run doxygen with the -l option.
-# You can optionally specify a file name after the option, if omitted
-# DoxygenLayout.xml will be used as the name of the layout file.
+# output files in an output format independent way. To create the layout file
+# that represents doxygen's defaults, run doxygen with the -l option. You can
+# optionally specify a file name after the option, if omitted DoxygenLayout.xml
+# will be used as the name of the layout file. See also section "Changing the
+# layout of pages" for information.
+#
+# Note that if you run doxygen from a directory containing a file called
+# DoxygenLayout.xml, doxygen will parse it automatically even if the LAYOUT_FILE
+# tag is left empty.
 
 LAYOUT_FILE            =
 
+# The CITE_BIB_FILES tag can be used to specify one or more bib files containing
+# the reference definitions. This must be a list of .bib files. The .bib
+# extension is automatically appended if omitted. This requires the bibtex tool
+# to be installed. See also https://en.wikipedia.org/wiki/BibTeX for more info.
+# For LaTeX the style of the bibliography can be controlled using
+# LATEX_BIB_STYLE. To use this feature you need bibtex and perl available in the
+# search path. See also \cite for info how to create references.
+
+CITE_BIB_FILES         =
+
 #---------------------------------------------------------------------------
-# configuration options related to warning and progress messages
+# Configuration options related to warning and progress messages
 #---------------------------------------------------------------------------
 
-# The QUIET tag can be used to turn on/off the messages that are generated
-# by doxygen. Possible values are YES and NO. If left blank NO is used.
+# The QUIET tag can be used to turn on/off the messages that are generated to
+# standard output by doxygen. If QUIET is set to YES this implies that the
+# messages are off.
+# The default value is: NO.
 
 QUIET                  = NO
 
 # The WARNINGS tag can be used to turn on/off the warning messages that are
-# generated by doxygen. Possible values are YES and NO. If left blank
-# NO is used.
+# generated to standard error (stderr) by doxygen. If WARNINGS is set to YES
+# this implies that the warnings are on.
+#
+# Tip: Turn warnings on while writing the documentation.
+# The default value is: YES.
 
 WARNINGS               = YES
 
-# If WARN_IF_UNDOCUMENTED is set to YES, then doxygen will generate warnings
-# for undocumented members. If EXTRACT_ALL is set to YES then this flag will
-# automatically be disabled.
+# If the WARN_IF_UNDOCUMENTED tag is set to YES then doxygen will generate
+# warnings for undocumented members. If EXTRACT_ALL is set to YES then this flag
+# will automatically be disabled.
+# The default value is: YES.
 
 WARN_IF_UNDOCUMENTED   = YES
 
-# If WARN_IF_DOC_ERROR is set to YES, doxygen will generate warnings for
-# potential errors in the documentation, such as not documenting some
-# parameters in a documented function, or documenting parameters that
-# don't exist or using markup commands wrongly.
+# If the WARN_IF_DOC_ERROR tag is set to YES, doxygen will generate warnings for
+# potential errors in the documentation, such as documenting some parameters in
+# a documented function twice, or documenting parameters that don't exist or
+# using markup commands wrongly.
+# The default value is: YES.
 
 WARN_IF_DOC_ERROR      = YES
 
-# This WARN_NO_PARAMDOC option can be abled to get warnings for
-# functions that are documented, but have no documentation for their parameters
-# or return value. If set to NO (the default) doxygen will only warn about
-# wrong or incomplete parameter documentation, but not about the absence of
-# documentation.
+# If WARN_IF_INCOMPLETE_DOC is set to YES, doxygen will warn about incomplete
+# function parameter documentation. If set to NO, doxygen will accept that some
+# parameters have no documentation without warning.
+# The default value is: YES.
+
+WARN_IF_INCOMPLETE_DOC = YES
+
+# This WARN_NO_PARAMDOC option can be enabled to get warnings for functions that
+# are documented, but have no documentation for their parameters or return
+# value. If set to NO, doxygen will only warn about wrong parameter
+# documentation, but not about the absence of documentation. If EXTRACT_ALL is
+# set to YES then this flag will automatically be disabled. See also
+# WARN_IF_INCOMPLETE_DOC
+# The default value is: NO.
 
 WARN_NO_PARAMDOC       = NO
 
-# The WARN_FORMAT tag determines the format of the warning messages that
-# doxygen can produce. The string should contain the $file, $line, and $text
-# tags, which will be replaced by the file and line number from which the
-# warning originated and the warning text. Optionally the format may contain
-# $version, which will be replaced by the version of the file (if it could
-# be obtained via FILE_VERSION_FILTER)
+# If WARN_IF_UNDOC_ENUM_VAL option is set to YES, doxygen will warn about
+# undocumented enumeration values. If set to NO, doxygen will accept
+# undocumented enumeration values. If EXTRACT_ALL is set to YES then this flag
+# will automatically be disabled.
+# The default value is: NO.
+
+WARN_IF_UNDOC_ENUM_VAL = NO
+
+# If the WARN_AS_ERROR tag is set to YES then doxygen will immediately stop when
+# a warning is encountered. If the WARN_AS_ERROR tag is set to FAIL_ON_WARNINGS
+# then doxygen will continue running as if WARN_AS_ERROR tag is set to NO, but
+# at the end of the doxygen process doxygen will return with a non-zero status.
+# Possible values are: NO, YES and FAIL_ON_WARNINGS.
+# The default value is: NO.
+
+WARN_AS_ERROR          = NO
+
+# The WARN_FORMAT tag determines the format of the warning messages that doxygen
+# can produce. The string should contain the $file, $line, and $text tags, which
+# will be replaced by the file and line number from which the warning originated
+# and the warning text. Optionally the format may contain $version, which will
+# be replaced by the version of the file (if it could be obtained via
+# FILE_VERSION_FILTER)
+# See also: WARN_LINE_FORMAT
+# The default value is: $file:$line: $text.
 
 WARN_FORMAT            = "$file:$line: $text"
 
-# The WARN_LOGFILE tag can be used to specify a file to which warning
-# and error messages should be written. If left blank the output is written
-# to stderr.
+# In the $text part of the WARN_FORMAT command it is possible that a reference
+# to a more specific place is given. To make it easier to jump to this place
+# (outside of doxygen) the user can define a custom "cut" / "paste" string.
+# Example:
+# WARN_LINE_FORMAT = "'vi $file +$line'"
+# See also: WARN_FORMAT
+# The default value is: at line $line of file $file.
+
+WARN_LINE_FORMAT       = "at line $line of file $file"
+
+# The WARN_LOGFILE tag can be used to specify a file to which warning and error
+# messages should be written. If left blank the output is written to standard
+# error (stderr). In case the file specified cannot be opened for writing the
+# warning and error messages are written to standard error. When as file - is
+# specified the warning and error messages are written to standard output
+# (stdout).
 
 WARN_LOGFILE           =
 
 #---------------------------------------------------------------------------
-# configuration options related to the input files
+# Configuration options related to the input files
 #---------------------------------------------------------------------------
 
-# The INPUT tag can be used to specify the files and/or directories that contain
-# documented source files. You may enter file names like "myfile.cpp" or
-# directories like "/usr/src/myproject". Separate the files or directories
-# with spaces.
+# The INPUT tag is used to specify the files and/or directories that contain
+# documented source files. You may enter file names like myfile.cpp or
+# directories like /usr/src/myproject. Separate the files or directories with
+# spaces. See also FILE_PATTERNS and EXTENSION_MAPPING
+# Note: If this tag is empty the current directory is searched.
 
-INPUT                  = ../hidapi
+INPUT                  = ../hidapi \
+                         ./main_page.md
 
 # This tag can be used to specify the character encoding of the source files
-# that doxygen parses. Internally doxygen uses the UTF-8 encoding, which is
-# also the default input encoding. Doxygen uses libiconv (or the iconv built
-# into libc) for the transcoding. See http://www.gnu.org/software/libiconv for
-# the list of possible encodings.
+# that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses
+# libiconv (or the iconv built into libc) for the transcoding. See the libiconv
+# documentation (see:
+# https://www.gnu.org/software/libiconv/) for the list of possible encodings.
+# See also: INPUT_FILE_ENCODING
+# The default value is: UTF-8.
 
 INPUT_ENCODING         = UTF-8
 
+# This tag can be used to specify the character encoding of the source files
+# that doxygen parses The INPUT_FILE_ENCODING tag can be used to specify
+# character encoding on a per file pattern basis. Doxygen will compare the file
+# name with each pattern and apply the encoding instead of the default
+# INPUT_ENCODING) if there is a match. The character encodings are a list of the
+# form: pattern=encoding (like *.php=ISO-8859-1). See cfg_input_encoding
+# "INPUT_ENCODING" for further information on supported encodings.
+
+INPUT_FILE_ENCODING    =
+
 # If the value of the INPUT tag contains directories, you can use the
-# FILE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp
-# and *.h) to filter out the source-files in the directories. If left
-# blank the following patterns are tested:
-# *.c *.cc *.cxx *.cpp *.c++ *.java *.ii *.ixx *.ipp *.i++ *.inl *.h *.hh *.hxx
-# *.hpp *.h++ *.idl *.odl *.cs *.php *.php3 *.inc *.m *.mm *.py *.f90
+# FILE_PATTERNS tag to specify one or more wildcard patterns (like *.cpp and
+# *.h) to filter out the source-files in the directories.
+#
+# Note that for custom extensions or not directly supported extensions you also
+# need to set EXTENSION_MAPPING for the extension otherwise the files are not
+# read by doxygen.
+#
+# Note the list of default checked file patterns might differ from the list of
+# default file extension mappings.
+#
+# If left blank the following patterns are tested:*.c, *.cc, *.cxx, *.cpp,
+# *.c++, *.java, *.ii, *.ixx, *.ipp, *.i++, *.inl, *.idl, *.ddl, *.odl, *.h,
+# *.hh, *.hxx, *.hpp, *.h++, *.l, *.cs, *.d, *.php, *.php4, *.php5, *.phtml,
+# *.inc, *.m, *.markdown, *.md, *.mm, *.dox (to be provided as doxygen C
+# comment), *.py, *.pyw, *.f90, *.f95, *.f03, *.f08, *.f18, *.f, *.for, *.vhd,
+# *.vhdl, *.ucf, *.qsf and *.ice.
 
 FILE_PATTERNS          =
 
-# The RECURSIVE tag can be used to turn specify whether or not subdirectories
-# should be searched for input files as well. Possible values are YES and NO.
-# If left blank NO is used.
+# The RECURSIVE tag can be used to specify whether or not subdirectories should
+# be searched for input files as well.
+# The default value is: NO.
 
 RECURSIVE              = NO
 
-# The EXCLUDE tag can be used to specify files and/or directories that should
+# The EXCLUDE tag can be used to specify files and/or directories that should be
 # excluded from the INPUT source files. This way you can easily exclude a
 # subdirectory from a directory tree whose root is specified with the INPUT tag.
+#
+# Note that relative paths are relative to the directory from which doxygen is
+# run.
 
 EXCLUDE                =
 
-# The EXCLUDE_SYMLINKS tag can be used select whether or not files or
-# directories that are symbolic links (a Unix filesystem feature) are excluded
+# The EXCLUDE_SYMLINKS tag can be used to select whether or not files or
+# directories that are symbolic links (a Unix file system feature) are excluded
 # from the input.
+# The default value is: NO.
 
 EXCLUDE_SYMLINKS       = NO
 
 # If the value of the INPUT tag contains directories, you can use the
 # EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude
-# certain files from those directories. Note that the wildcards are matched
-# against the file with absolute path, so to exclude all test directories
-# for example use the pattern */test/*
+# certain files from those directories.
+#
+# Note that the wildcards are matched against the file with absolute path, so to
+# exclude all test directories for example use the pattern */test/*
 
 EXCLUDE_PATTERNS       =
 
@@ -630,689 +985,1278 @@ EXCLUDE_PATTERNS       =
 # (namespaces, classes, functions, etc.) that should be excluded from the
 # output. The symbol name can be a fully qualified name, a word, or if the
 # wildcard * is used, a substring. Examples: ANamespace, AClass,
-# AClass::ANamespace, ANamespace::*Test
+# ANamespace::AClass, ANamespace::*Test
+#
+# Note that the wildcards are matched against the file with absolute path, so to
+# exclude all test directories use the pattern */test/*
 
-EXCLUDE_SYMBOLS        =
+EXCLUDE_SYMBOLS        = HID_API_AS_STR_IMPL \
+                         HID_API_AS_STR \
+                         HID_API_TO_VERSION_STR
 
-# The EXAMPLE_PATH tag can be used to specify one or more files or
-# directories that contain example code fragments that are included (see
-# the \include command).
+# The EXAMPLE_PATH tag can be used to specify one or more files or directories
+# that contain example code fragments that are included (see the \include
+# command).
 
-EXAMPLE_PATH           =
+EXAMPLE_PATH           = ../hidtest
 
 # If the value of the EXAMPLE_PATH tag contains directories, you can use the
-# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp
-# and *.h) to filter out the source-files in the directories. If left
-# blank all files are included.
+# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp and
+# *.h) to filter out the source-files in the directories. If left blank all
+# files are included.
 
-EXAMPLE_PATTERNS       =
+EXAMPLE_PATTERNS       = *.c
 
 # If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be
-# searched for input files to be used with the \include or \dontinclude
-# commands irrespective of the value of the RECURSIVE tag.
-# Possible values are YES and NO. If left blank NO is used.
+# searched for input files to be used with the \include or \dontinclude commands
+# irrespective of the value of the RECURSIVE tag.
+# The default value is: NO.
 
 EXAMPLE_RECURSIVE      = NO
 
-# The IMAGE_PATH tag can be used to specify one or more files or
-# directories that contain image that are included in the documentation (see
-# the \image command).
+# The IMAGE_PATH tag can be used to specify one or more files or directories
+# that contain images that are to be included in the documentation (see the
+# \image command).
 
 IMAGE_PATH             =
 
 # The INPUT_FILTER tag can be used to specify a program that doxygen should
 # invoke to filter for each input file. Doxygen will invoke the filter program
-# by executing (via popen()) the command <filter> <input-file>, where <filter>
-# is the value of the INPUT_FILTER tag, and <input-file> is the name of an
-# input file. Doxygen will then use the output that the filter program writes
-# to standard output.
-# If FILTER_PATTERNS is specified, this tag will be
-# ignored.
+# by executing (via popen()) the command:
+#
+# <filter> <input-file>
+#
+# where <filter> is the value of the INPUT_FILTER tag, and <input-file> is the
+# name of an input file. Doxygen will then use the output that the filter
+# program writes to standard output. If FILTER_PATTERNS is specified, this tag
+# will be ignored.
+#
+# Note that the filter must not add or remove lines; it is applied before the
+# code is scanned, but not when the output code is generated. If lines are added
+# or removed, the anchors will not be placed correctly.
+#
+# Note that doxygen will use the data processed and written to standard output
+# for further processing, therefore nothing else, like debug statements or used
+# commands (so in case of a Windows batch file always use @echo OFF), should be
+# written to standard output.
+#
+# Note that for custom extensions or not directly supported extensions you also
+# need to set EXTENSION_MAPPING for the extension otherwise the files are not
+# properly processed by doxygen.
 
 INPUT_FILTER           =
 
 # The FILTER_PATTERNS tag can be used to specify filters on a per file pattern
-# basis.
-# Doxygen will compare the file name with each pattern and apply the
-# filter if there is a match.
-# The filters are a list of the form:
-# pattern=filter (like *.cpp=my_cpp_filter). See INPUT_FILTER for further
-# info on how filters are used. If FILTER_PATTERNS is empty, INPUT_FILTER
-# is applied to all files.
+# basis. Doxygen will compare the file name with each pattern and apply the
+# filter if there is a match. The filters are a list of the form: pattern=filter
+# (like *.cpp=my_cpp_filter). See INPUT_FILTER for further information on how
+# filters are used. If the FILTER_PATTERNS tag is empty or if none of the
+# patterns match the file name, INPUT_FILTER is applied.
+#
+# Note that for custom extensions or not directly supported extensions you also
+# need to set EXTENSION_MAPPING for the extension otherwise the files are not
+# properly processed by doxygen.
 
 FILTER_PATTERNS        =
 
 # If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using
-# INPUT_FILTER) will be used to filter the input files when producing source
-# files to browse (i.e. when SOURCE_BROWSER is set to YES).
+# INPUT_FILTER) will also be used to filter the input files that are used for
+# producing the source files to browse (i.e. when SOURCE_BROWSER is set to YES).
+# The default value is: NO.
 
 FILTER_SOURCE_FILES    = NO
 
+# The FILTER_SOURCE_PATTERNS tag can be used to specify source filters per file
+# pattern. A pattern will override the setting for FILTER_PATTERN (if any) and
+# it is also possible to disable source filtering for a specific pattern using
+# *.ext= (so without naming a filter).
+# This tag requires that the tag FILTER_SOURCE_FILES is set to YES.
+
+FILTER_SOURCE_PATTERNS =
+
+# If the USE_MDFILE_AS_MAINPAGE tag refers to the name of a markdown file that
+# is part of the input, its contents will be placed on the main page
+# (index.html). This can be useful if you have a project on for instance GitHub
+# and want to reuse the introduction page also for the doxygen output.
+
+USE_MDFILE_AS_MAINPAGE = main_page.md
+
+# The Fortran standard specifies that for fixed formatted Fortran code all
+# characters from position 72 are to be considered as comment. A common
+# extension is to allow longer lines before the automatic comment starts. The
+# setting FORTRAN_COMMENT_AFTER will also make it possible that longer lines can
+# be processed before the automatic comment starts.
+# Minimum value: 7, maximum value: 10000, default value: 72.
+
+FORTRAN_COMMENT_AFTER  = 72
+
 #---------------------------------------------------------------------------
-# configuration options related to source browsing
+# Configuration options related to source browsing
 #---------------------------------------------------------------------------
 
-# If the SOURCE_BROWSER tag is set to YES then a list of source files will
-# be generated. Documented entities will be cross-referenced with these sources.
-# Note: To get rid of all source code in the generated output, make sure also
-# VERBATIM_HEADERS is set to NO.
+# If the SOURCE_BROWSER tag is set to YES then a list of source files will be
+# generated. Documented entities will be cross-referenced with these sources.
+#
+# Note: To get rid of all source code in the generated output, make sure that
+# also VERBATIM_HEADERS is set to NO.
+# The default value is: NO.
 
 SOURCE_BROWSER         = NO
 
-# Setting the INLINE_SOURCES tag to YES will include the body
-# of functions and classes directly in the documentation.
+# Setting the INLINE_SOURCES tag to YES will include the body of functions,
+# classes and enums directly into the documentation.
+# The default value is: NO.
 
 INLINE_SOURCES         = NO
 
-# Setting the STRIP_CODE_COMMENTS tag to YES (the default) will instruct
-# doxygen to hide any special comment blocks from generated source code
-# fragments. Normal C and C++ comments will always remain visible.
+# Setting the STRIP_CODE_COMMENTS tag to YES will instruct doxygen to hide any
+# special comment blocks from generated source code fragments. Normal C, C++ and
+# Fortran comments will always remain visible.
+# The default value is: YES.
 
 STRIP_CODE_COMMENTS    = YES
 
-# If the REFERENCED_BY_RELATION tag is set to YES
-# then for each documented function all documented
-# functions referencing it will be listed.
+# If the REFERENCED_BY_RELATION tag is set to YES then for each documented
+# entity all documented functions referencing it will be listed.
+# The default value is: NO.
 
 REFERENCED_BY_RELATION = NO
 
-# If the REFERENCES_RELATION tag is set to YES
-# then for each documented function all documented entities
-# called/used by that function will be listed.
+# If the REFERENCES_RELATION tag is set to YES then for each documented function
+# all documented entities called/used by that function will be listed.
+# The default value is: NO.
 
 REFERENCES_RELATION    = NO
 
-# If the REFERENCES_LINK_SOURCE tag is set to YES (the default)
-# and SOURCE_BROWSER tag is set to YES, then the hyperlinks from
-# functions in REFERENCES_RELATION and REFERENCED_BY_RELATION lists will
-# link to the source code.
-# Otherwise they will link to the documentation.
+# If the REFERENCES_LINK_SOURCE tag is set to YES and SOURCE_BROWSER tag is set
+# to YES then the hyperlinks from functions in REFERENCES_RELATION and
+# REFERENCED_BY_RELATION lists will link to the source code. Otherwise they will
+# link to the documentation.
+# The default value is: YES.
 
 REFERENCES_LINK_SOURCE = YES
 
-# If the USE_HTAGS tag is set to YES then the references to source code
-# will point to the HTML generated by the htags(1) tool instead of doxygen
-# built-in source browser. The htags tool is part of GNU's global source
-# tagging system (see http://www.gnu.org/software/global/global.html). You
-# will need version 4.8.6 or higher.
+# If SOURCE_TOOLTIPS is enabled (the default) then hovering a hyperlink in the
+# source code will show a tooltip with additional information such as prototype,
+# brief description and links to the definition and documentation. Since this
+# will make the HTML file larger and loading of large files a bit slower, you
+# can opt to disable this feature.
+# The default value is: YES.
+# This tag requires that the tag SOURCE_BROWSER is set to YES.
+
+SOURCE_TOOLTIPS        = YES
+
+# If the USE_HTAGS tag is set to YES then the references to source code will
+# point to the HTML generated by the htags(1) tool instead of doxygen built-in
+# source browser. The htags tool is part of GNU's global source tagging system
+# (see https://www.gnu.org/software/global/global.html). You will need version
+# 4.8.6 or higher.
+#
+# To use it do the following:
+# - Install the latest version of global
+# - Enable SOURCE_BROWSER and USE_HTAGS in the configuration file
+# - Make sure the INPUT points to the root of the source tree
+# - Run doxygen as normal
+#
+# Doxygen will invoke htags (and that will in turn invoke gtags), so these
+# tools must be available from the command line (i.e. in the search path).
+#
+# The result: instead of the source browser generated by doxygen, the links to
+# source code will now point to the output of htags.
+# The default value is: NO.
+# This tag requires that the tag SOURCE_BROWSER is set to YES.
 
 USE_HTAGS              = NO
 
-# If the VERBATIM_HEADERS tag is set to YES (the default) then Doxygen
-# will generate a verbatim copy of the header file for each class for
-# which an include is specified. Set to NO to disable this.
+# If the VERBATIM_HEADERS tag is set the YES then doxygen will generate a
+# verbatim copy of the header file for each class for which an include is
+# specified. Set to NO to disable this.
+# See also: Section \class.
+# The default value is: YES.
 
 VERBATIM_HEADERS       = YES
 
+# If the CLANG_ASSISTED_PARSING tag is set to YES then doxygen will use the
+# clang parser (see:
+# http://clang.llvm.org/) for more accurate parsing at the cost of reduced
+# performance. This can be particularly helpful with template rich C++ code for
+# which doxygen's built-in parser lacks the necessary type information.
+# Note: The availability of this option depends on whether or not doxygen was
+# generated with the -Duse_libclang=ON option for CMake.
+# The default value is: NO.
+
+CLANG_ASSISTED_PARSING = NO
+
+# If the CLANG_ASSISTED_PARSING tag is set to YES and the CLANG_ADD_INC_PATHS
+# tag is set to YES then doxygen will add the directory of each input to the
+# include path.
+# The default value is: YES.
+# This tag requires that the tag CLANG_ASSISTED_PARSING is set to YES.
+
+CLANG_ADD_INC_PATHS    = YES
+
+# If clang assisted parsing is enabled you can provide the compiler with command
+# line options that you would normally use when invoking the compiler. Note that
+# the include paths will already be set by doxygen for the files and directories
+# specified with INPUT and INCLUDE_PATH.
+# This tag requires that the tag CLANG_ASSISTED_PARSING is set to YES.
+
+CLANG_OPTIONS          =
+
+# If clang assisted parsing is enabled you can provide the clang parser with the
+# path to the directory containing a file called compile_commands.json. This
+# file is the compilation database (see:
+# http://clang.llvm.org/docs/HowToSetupToolingForLLVM.html) containing the
+# options used when the source files were built. This is equivalent to
+# specifying the -p option to a clang tool, such as clang-check. These options
+# will then be passed to the parser. Any options specified with CLANG_OPTIONS
+# will be added as well.
+# Note: The availability of this option depends on whether or not doxygen was
+# generated with the -Duse_libclang=ON option for CMake.
+
+CLANG_DATABASE_PATH    =
+
 #---------------------------------------------------------------------------
-# configuration options related to the alphabetical class index
+# Configuration options related to the alphabetical class index
 #---------------------------------------------------------------------------
 
-# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index
-# of all compounds will be generated. Enable this if the project
-# contains a lot of classes, structs, unions or interfaces.
+# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index of all
+# compounds will be generated. Enable this if the project contains a lot of
+# classes, structs, unions or interfaces.
+# The default value is: YES.
 
 ALPHABETICAL_INDEX     = YES
 
-# If the alphabetical index is enabled (see ALPHABETICAL_INDEX) then
-# the COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns
-# in which this list will be split (can be a number in the range [1..20])
-
-COLS_IN_ALPHA_INDEX    = 5
-
-# In case all classes in a project start with a common prefix, all
-# classes will be put under the same header in the alphabetical index.
-# The IGNORE_PREFIX tag can be used to specify one or more prefixes that
-# should be ignored while generating the index headers.
+# The IGNORE_PREFIX tag can be used to specify a prefix (or a list of prefixes)
+# that should be ignored while generating the index headers. The IGNORE_PREFIX
+# tag works for classes, function and member names. The entity will be placed in
+# the alphabetical list under the first letter of the entity name that remains
+# after removing the prefix.
+# This tag requires that the tag ALPHABETICAL_INDEX is set to YES.
 
 IGNORE_PREFIX          =
 
 #---------------------------------------------------------------------------
-# configuration options related to the HTML output
+# Configuration options related to the HTML output
 #---------------------------------------------------------------------------
 
-# If the GENERATE_HTML tag is set to YES (the default) Doxygen will
-# generate HTML output.
+# If the GENERATE_HTML tag is set to YES, doxygen will generate HTML output
+# The default value is: YES.
 
 GENERATE_HTML          = YES
 
-# The HTML_OUTPUT tag is used to specify where the HTML docs will be put.
-# If a relative path is entered the value of OUTPUT_DIRECTORY will be
-# put in front of it. If left blank `html' will be used as the default path.
+# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. If a
+# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of
+# it.
+# The default directory is: html.
+# This tag requires that the tag GENERATE_HTML is set to YES.
 
 HTML_OUTPUT            = html
 
-# The HTML_FILE_EXTENSION tag can be used to specify the file extension for
-# each generated HTML page (for example: .htm,.php,.asp). If it is left blank
-# doxygen will generate files with .html extension.
+# The HTML_FILE_EXTENSION tag can be used to specify the file extension for each
+# generated HTML page (for example: .htm, .php, .asp).
+# The default value is: .html.
+# This tag requires that the tag GENERATE_HTML is set to YES.
 
 HTML_FILE_EXTENSION    = .html
 
-# The HTML_HEADER tag can be used to specify a personal HTML header for
-# each generated HTML page. If it is left blank doxygen will generate a
+# The HTML_HEADER tag can be used to specify a user-defined HTML header file for
+# each generated HTML page. If the tag is left blank doxygen will generate a
 # standard header.
+#
+# To get valid HTML the header file that includes any scripts and style sheets
+# that doxygen needs, which is dependent on the configuration options used (e.g.
+# the setting GENERATE_TREEVIEW). It is highly recommended to start with a
+# default header using
+# doxygen -w html new_header.html new_footer.html new_stylesheet.css
+# YourConfigFile
+# and then modify the file new_header.html. See also section "Doxygen usage"
+# for information on how to generate the default header that doxygen normally
+# uses.
+# Note: The header is subject to change so you typically have to regenerate the
+# default header when upgrading to a newer version of doxygen. For a description
+# of the possible markers and block names see the documentation.
+# This tag requires that the tag GENERATE_HTML is set to YES.
 
 HTML_HEADER            =
 
-# The HTML_FOOTER tag can be used to specify a personal HTML footer for
-# each generated HTML page. If it is left blank doxygen will generate a
-# standard footer.
+# The HTML_FOOTER tag can be used to specify a user-defined HTML footer for each
+# generated HTML page. If the tag is left blank doxygen will generate a standard
+# footer. See HTML_HEADER for more information on how to generate a default
+# footer and what special commands can be used inside the footer. See also
+# section "Doxygen usage" for information on how to generate the default footer
+# that doxygen normally uses.
+# This tag requires that the tag GENERATE_HTML is set to YES.
 
 HTML_FOOTER            =
 
-# The HTML_STYLESHEET tag can be used to specify a user-defined cascading
-# style sheet that is used by each HTML page. It can be used to
-# fine-tune the look of the HTML output. If the tag is left blank doxygen
-# will generate a default style sheet. Note that doxygen will try to copy
-# the style sheet file to the HTML output directory, so don't put your own
-# stylesheet in the HTML output directory as well, or it will be erased!
+# The HTML_STYLESHEET tag can be used to specify a user-defined cascading style
+# sheet that is used by each HTML page. It can be used to fine-tune the look of
+# the HTML output. If left blank doxygen will generate a default style sheet.
+# See also section "Doxygen usage" for information on how to generate the style
+# sheet that doxygen normally uses.
+# Note: It is recommended to use HTML_EXTRA_STYLESHEET instead of this tag, as
+# it is more robust and this tag (HTML_STYLESHEET) will in the future become
+# obsolete.
+# This tag requires that the tag GENERATE_HTML is set to YES.
 
 HTML_STYLESHEET        =
 
-# The HTML_COLORSTYLE_HUE tag controls the color of the HTML output.
-# Doxygen will adjust the colors in the stylesheet and background images
-# according to this color. Hue is specified as an angle on a colorwheel,
-# see http://en.wikipedia.org/wiki/Hue for more information.
-# For instance the value 0 represents red, 60 is yellow, 120 is green,
-# 180 is cyan, 240 is blue, 300 purple, and 360 is red again.
-# The allowed range is 0 to 359.
+# The HTML_EXTRA_STYLESHEET tag can be used to specify additional user-defined
+# cascading style sheets that are included after the standard style sheets
+# created by doxygen. Using this option one can overrule certain style aspects.
+# This is preferred over using HTML_STYLESHEET since it does not replace the
+# standard style sheet and is therefore more robust against future updates.
+# Doxygen will copy the style sheet files to the output directory.
+# Note: The order of the extra style sheet files is of importance (e.g. the last
+# style sheet in the list overrules the setting of the previous ones in the
+# list).
+# Note: Since the styling of scrollbars can currently not be overruled in
+# Webkit/Chromium, the styling will be left out of the default doxygen.css if
+# one or more extra stylesheets have been specified. So if scrollbar
+# customization is desired it has to be added explicitly. For an example see the
+# documentation.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_EXTRA_STYLESHEET  =
+
+# The HTML_EXTRA_FILES tag can be used to specify one or more extra images or
+# other source files which should be copied to the HTML output directory. Note
+# that these files will be copied to the base HTML output directory. Use the
+# $relpath^ marker in the HTML_HEADER and/or HTML_FOOTER files to load these
+# files. In the HTML_STYLESHEET file, use the file name only. Also note that the
+# files will be copied as-is; there are no commands or markers available.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_EXTRA_FILES       =
+
+# The HTML_COLORSTYLE tag can be used to specify if the generated HTML output
+# should be rendered with a dark or light theme.
+# Possible values are: LIGHT always generate light mode output, DARK always
+# generate dark mode output, AUTO_LIGHT automatically set the mode according to
+# the user preference, use light mode if no preference is set (the default),
+# AUTO_DARK automatically set the mode according to the user preference, use
+# dark mode if no preference is set and TOGGLE allow to user to switch between
+# light and dark mode via a button.
+# The default value is: AUTO_LIGHT.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_COLORSTYLE        = AUTO_LIGHT
+
+# The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen
+# will adjust the colors in the style sheet and background images according to
+# this color. Hue is specified as an angle on a color-wheel, see
+# https://en.wikipedia.org/wiki/Hue for more information. For instance the value
+# 0 represents red, 60 is yellow, 120 is green, 180 is cyan, 240 is blue, 300
+# purple, and 360 is red again.
+# Minimum value: 0, maximum value: 359, default value: 220.
+# This tag requires that the tag GENERATE_HTML is set to YES.
 
 HTML_COLORSTYLE_HUE    = 220
 
-# The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of
-# the colors in the HTML output. For a value of 0 the output will use
-# grayscales only. A value of 255 will produce the most vivid colors.
+# The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of the colors
+# in the HTML output. For a value of 0 the output will use gray-scales only. A
+# value of 255 will produce the most vivid colors.
+# Minimum value: 0, maximum value: 255, default value: 100.
+# This tag requires that the tag GENERATE_HTML is set to YES.
 
 HTML_COLORSTYLE_SAT    = 100
 
-# The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to
-# the luminance component of the colors in the HTML output. Values below
-# 100 gradually make the output lighter, whereas values above 100 make
-# the output darker. The value divided by 100 is the actual gamma applied,
-# so 80 represents a gamma of 0.8, The value 220 represents a gamma of 2.2,
-# and 100 does not change the gamma.
+# The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to the
+# luminance component of the colors in the HTML output. Values below 100
+# gradually make the output lighter, whereas values above 100 make the output
+# darker. The value divided by 100 is the actual gamma applied, so 80 represents
+# a gamma of 0.8, The value 220 represents a gamma of 2.2, and 100 does not
+# change the gamma.
+# Minimum value: 40, maximum value: 240, default value: 80.
+# This tag requires that the tag GENERATE_HTML is set to YES.
 
 HTML_COLORSTYLE_GAMMA  = 80
 
 # If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML
-# page will contain the date and time when the page was generated. Setting
-# this to NO can help when comparing the output of multiple runs.
+# page will contain the date and time when the page was generated. Setting this
+# to YES can help to show when doxygen was last run and thus if the
+# documentation is up to date.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
 
 HTML_TIMESTAMP         = YES
 
-# If the HTML_ALIGN_MEMBERS tag is set to YES, the members of classes,
-# files or namespaces will be aligned in HTML using tables. If set to
-# NO a bullet list will be used.
+# If the HTML_DYNAMIC_MENUS tag is set to YES then the generated HTML
+# documentation will contain a main index with vertical navigation menus that
+# are dynamically created via JavaScript. If disabled, the navigation index will
+# consists of multiple levels of tabs that are statically embedded in every HTML
+# page. Disable this option to support browsers that do not have JavaScript,
+# like the Qt help browser.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_HTML is set to YES.
 
-HTML_ALIGN_MEMBERS     = YES
+HTML_DYNAMIC_MENUS     = YES
 
 # If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML
 # documentation will contain sections that can be hidden and shown after the
-# page has loaded. For this to work a browser that supports
-# JavaScript and DHTML is required (for instance Mozilla 1.0+, Firefox
-# Netscape 6.0+, Internet explorer 5.0+, Konqueror, or Safari).
+# page has loaded.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
 
 HTML_DYNAMIC_SECTIONS  = NO
 
-# If the GENERATE_DOCSET tag is set to YES, additional index files
-# will be generated that can be used as input for Apple's Xcode 3
-# integrated development environment, introduced with OSX 10.5 (Leopard).
-# To create a documentation set, doxygen will generate a Makefile in the
-# HTML output directory. Running make will produce the docset in that
-# directory and running "make install" will install the docset in
-# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find
-# it at startup.
-# See http://developer.apple.com/tools/creatingdocsetswithdoxygen.html
-# for more information.
+# With HTML_INDEX_NUM_ENTRIES one can control the preferred number of entries
+# shown in the various tree structured indices initially; the user can expand
+# and collapse entries dynamically later on. Doxygen will expand the tree to
+# such a level that at most the specified number of entries are visible (unless
+# a fully collapsed tree already exceeds this amount). So setting the number of
+# entries 1 will produce a full collapsed tree by default. 0 is a special value
+# representing an infinite number of entries and will result in a full expanded
+# tree by default.
+# Minimum value: 0, maximum value: 9999, default value: 100.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_INDEX_NUM_ENTRIES = 100
+
+# If the GENERATE_DOCSET tag is set to YES, additional index files will be
+# generated that can be used as input for Apple's Xcode 3 integrated development
+# environment (see:
+# https://developer.apple.com/xcode/), introduced with OSX 10.5 (Leopard). To
+# create a documentation set, doxygen will generate a Makefile in the HTML
+# output directory. Running make will produce the docset in that directory and
+# running make install will install the docset in
+# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find it at
+# startup. See https://developer.apple.com/library/archive/featuredarticles/Doxy
+# genXcode/_index.html for more information.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
 
 GENERATE_DOCSET        = NO
 
-# When GENERATE_DOCSET tag is set to YES, this tag determines the name of the
-# feed. A documentation feed provides an umbrella under which multiple
-# documentation sets from a single provider (such as a company or product suite)
-# can be grouped.
+# This tag determines the name of the docset feed. A documentation feed provides
+# an umbrella under which multiple documentation sets from a single provider
+# (such as a company or product suite) can be grouped.
+# The default value is: Doxygen generated docs.
+# This tag requires that the tag GENERATE_DOCSET is set to YES.
 
 DOCSET_FEEDNAME        = "Doxygen generated docs"
 
-# When GENERATE_DOCSET tag is set to YES, this tag specifies a string that
-# should uniquely identify the documentation set bundle. This should be a
-# reverse domain-name style string, e.g. com.mycompany.MyDocSet. Doxygen
-# will append .docset to the name.
+# This tag determines the URL of the docset feed. A documentation feed provides
+# an umbrella under which multiple documentation sets from a single provider
+# (such as a company or product suite) can be grouped.
+# This tag requires that the tag GENERATE_DOCSET is set to YES.
+
+DOCSET_FEEDURL         =
+
+# This tag specifies a string that should uniquely identify the documentation
+# set bundle. This should be a reverse domain-name style string, e.g.
+# com.mycompany.MyDocSet. Doxygen will append .docset to the name.
+# The default value is: org.doxygen.Project.
+# This tag requires that the tag GENERATE_DOCSET is set to YES.
 
 DOCSET_BUNDLE_ID       = org.doxygen.Project
 
-# When GENERATE_PUBLISHER_ID tag specifies a string that should uniquely identify
+# The DOCSET_PUBLISHER_ID tag specifies a string that should uniquely identify
 # the documentation publisher. This should be a reverse domain-name style
 # string, e.g. com.mycompany.MyDocSet.documentation.
+# The default value is: org.doxygen.Publisher.
+# This tag requires that the tag GENERATE_DOCSET is set to YES.
 
 DOCSET_PUBLISHER_ID    = org.doxygen.Publisher
 
-# The GENERATE_PUBLISHER_NAME tag identifies the documentation publisher.
+# The DOCSET_PUBLISHER_NAME tag identifies the documentation publisher.
+# The default value is: Publisher.
+# This tag requires that the tag GENERATE_DOCSET is set to YES.
 
 DOCSET_PUBLISHER_NAME  = Publisher
 
-# If the GENERATE_HTMLHELP tag is set to YES, additional index files
-# will be generated that can be used as input for tools like the
-# Microsoft HTML help workshop to generate a compiled HTML help file (.chm)
-# of the generated HTML documentation.
+# If the GENERATE_HTMLHELP tag is set to YES then doxygen generates three
+# additional HTML index files: index.hhp, index.hhc, and index.hhk. The
+# index.hhp is a project file that can be read by Microsoft's HTML Help Workshop
+# on Windows. In the beginning of 2021 Microsoft took the original page, with
+# a.o. the download links, offline the HTML help workshop was already many years
+# in maintenance mode). You can download the HTML help workshop from the web
+# archives at Installation executable (see:
+# http://web.archive.org/web/20160201063255/http://download.microsoft.com/downlo
+# ad/0/A/9/0A939EF6-E31C-430F-A3DF-DFAE7960D564/htmlhelp.exe).
+#
+# The HTML Help Workshop contains a compiler that can convert all HTML output
+# generated by doxygen into a single compiled HTML file (.chm). Compiled HTML
+# files are now used as the Windows 98 help format, and will replace the old
+# Windows help format (.hlp) on all Windows platforms in the future. Compressed
+# HTML files also contain an index, a table of contents, and you can search for
+# words in the documentation. The HTML workshop also contains a viewer for
+# compressed HTML files.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
 
 GENERATE_HTMLHELP      = NO
 
-# If the GENERATE_HTMLHELP tag is set to YES, the CHM_FILE tag can
-# be used to specify the file name of the resulting .chm file. You
-# can add a path in front of the file if the result should not be
+# The CHM_FILE tag can be used to specify the file name of the resulting .chm
+# file. You can add a path in front of the file if the result should not be
 # written to the html output directory.
+# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
 
 CHM_FILE               =
 
-# If the GENERATE_HTMLHELP tag is set to YES, the HHC_LOCATION tag can
-# be used to specify the location (absolute path including file name) of
-# the HTML help compiler (hhc.exe). If non-empty doxygen will try to run
-# the HTML help compiler on the generated index.hhp.
+# The HHC_LOCATION tag can be used to specify the location (absolute path
+# including file name) of the HTML help compiler (hhc.exe). If non-empty,
+# doxygen will try to run the HTML help compiler on the generated index.hhp.
+# The file has to be specified with full path.
+# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
 
 HHC_LOCATION           =
 
-# If the GENERATE_HTMLHELP tag is set to YES, the GENERATE_CHI flag
-# controls if a separate .chi index file is generated (YES) or that
-# it should be included in the master .chm file (NO).
+# The GENERATE_CHI flag controls if a separate .chi index file is generated
+# (YES) or that it should be included in the main .chm file (NO).
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
 
 GENERATE_CHI           = NO
 
-# If the GENERATE_HTMLHELP tag is set to YES, the CHM_INDEX_ENCODING
-# is used to encode HtmlHelp index (hhk), content (hhc) and project file
-# content.
+# The CHM_INDEX_ENCODING is used to encode HtmlHelp index (hhk), content (hhc)
+# and project file content.
+# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
 
 CHM_INDEX_ENCODING     =
 
-# If the GENERATE_HTMLHELP tag is set to YES, the BINARY_TOC flag
-# controls whether a binary table of contents is generated (YES) or a
-# normal table of contents (NO) in the .chm file.
+# The BINARY_TOC flag controls whether a binary table of contents is generated
+# (YES) or a normal table of contents (NO) in the .chm file. Furthermore it
+# enables the Previous and Next buttons.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
 
 BINARY_TOC             = NO
 
-# The TOC_EXPAND flag can be set to YES to add extra items for group members
-# to the contents of the HTML help documentation and to the tree view.
+# The TOC_EXPAND flag can be set to YES to add extra items for group members to
+# the table of contents of the HTML help documentation and to the tree view.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
 
 TOC_EXPAND             = NO
 
 # If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and
-# QHP_VIRTUAL_FOLDER are set, an additional index file will be generated
-# that can be used as input for Qt's qhelpgenerator to generate a
-# Qt Compressed Help (.qch) of the generated HTML documentation.
+# QHP_VIRTUAL_FOLDER are set, an additional index file will be generated that
+# can be used as input for Qt's qhelpgenerator to generate a Qt Compressed Help
+# (.qch) of the generated HTML documentation.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
 
 GENERATE_QHP           = NO
 
-# If the QHG_LOCATION tag is specified, the QCH_FILE tag can
-# be used to specify the file name of the resulting .qch file.
-# The path specified is relative to the HTML output folder.
+# If the QHG_LOCATION tag is specified, the QCH_FILE tag can be used to specify
+# the file name of the resulting .qch file. The path specified is relative to
+# the HTML output folder.
+# This tag requires that the tag GENERATE_QHP is set to YES.
 
 QCH_FILE               =
 
-# The QHP_NAMESPACE tag specifies the namespace to use when generating
-# Qt Help Project output. For more information please see
-# http://doc.trolltech.com/qthelpproject.html#namespace
+# The QHP_NAMESPACE tag specifies the namespace to use when generating Qt Help
+# Project output. For more information please see Qt Help Project / Namespace
+# (see:
+# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#namespace).
+# The default value is: org.doxygen.Project.
+# This tag requires that the tag GENERATE_QHP is set to YES.
 
 QHP_NAMESPACE          = org.doxygen.Project
 
-# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating
-# Qt Help Project output. For more information please see
-# http://doc.trolltech.com/qthelpproject.html#virtual-folders
+# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating Qt
+# Help Project output. For more information please see Qt Help Project / Virtual
+# Folders (see:
+# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#virtual-folders).
+# The default value is: doc.
+# This tag requires that the tag GENERATE_QHP is set to YES.
 
 QHP_VIRTUAL_FOLDER     = doc
 
-# If QHP_CUST_FILTER_NAME is set, it specifies the name of a custom filter to
-# add. For more information please see
-# http://doc.trolltech.com/qthelpproject.html#custom-filters
+# If the QHP_CUST_FILTER_NAME tag is set, it specifies the name of a custom
+# filter to add. For more information please see Qt Help Project / Custom
+# Filters (see:
+# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom-filters).
+# This tag requires that the tag GENERATE_QHP is set to YES.
 
 QHP_CUST_FILTER_NAME   =
 
-# The QHP_CUST_FILT_ATTRS tag specifies the list of the attributes of the
-# custom filter to add. For more information please see
-# <a href="http://doc.trolltech.com/qthelpproject.html#custom-filters">
-# Qt Help Project / Custom Filters</a>.
+# The QHP_CUST_FILTER_ATTRS tag specifies the list of the attributes of the
+# custom filter to add. For more information please see Qt Help Project / Custom
+# Filters (see:
+# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom-filters).
+# This tag requires that the tag GENERATE_QHP is set to YES.
 
 QHP_CUST_FILTER_ATTRS  =
 
 # The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this
-# project's
-# filter section matches.
-# <a href="http://doc.trolltech.com/qthelpproject.html#filter-attributes">
-# Qt Help Project / Filter Attributes</a>.
+# project's filter section matches. Qt Help Project / Filter Attributes (see:
+# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#filter-attributes).
+# This tag requires that the tag GENERATE_QHP is set to YES.
 
 QHP_SECT_FILTER_ATTRS  =
 
-# If the GENERATE_QHP tag is set to YES, the QHG_LOCATION tag can
-# be used to specify the location of Qt's qhelpgenerator.
-# If non-empty doxygen will try to run qhelpgenerator on the generated
-# .qhp file.
+# The QHG_LOCATION tag can be used to specify the location (absolute path
+# including file name) of Qt's qhelpgenerator. If non-empty doxygen will try to
+# run qhelpgenerator on the generated .qhp file.
+# This tag requires that the tag GENERATE_QHP is set to YES.
 
 QHG_LOCATION           =
 
-# If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files
-#  will be generated, which together with the HTML files, form an Eclipse help
-# plugin. To install this plugin and make it available under the help contents
-# menu in Eclipse, the contents of the directory containing the HTML and XML
-# files needs to be copied into the plugins directory of eclipse. The name of
-# the directory within the plugins directory should be the same as
-# the ECLIPSE_DOC_ID value. After copying Eclipse needs to be restarted before
-# the help appears.
+# If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files will be
+# generated, together with the HTML files, they form an Eclipse help plugin. To
+# install this plugin and make it available under the help contents menu in
+# Eclipse, the contents of the directory containing the HTML and XML files needs
+# to be copied into the plugins directory of eclipse. The name of the directory
+# within the plugins directory should be the same as the ECLIPSE_DOC_ID value.
+# After copying Eclipse needs to be restarted before the help appears.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
 
 GENERATE_ECLIPSEHELP   = NO
 
-# A unique identifier for the eclipse help plugin. When installing the plugin
-# the directory name containing the HTML and XML files should also have
-# this name.
+# A unique identifier for the Eclipse help plugin. When installing the plugin
+# the directory name containing the HTML and XML files should also have this
+# name. Each documentation set should have its own identifier.
+# The default value is: org.doxygen.Project.
+# This tag requires that the tag GENERATE_ECLIPSEHELP is set to YES.
 
 ECLIPSE_DOC_ID         = org.doxygen.Project
 
-# The DISABLE_INDEX tag can be used to turn on/off the condensed index at
-# top of each HTML page. The value NO (the default) enables the index and
-# the value YES disables it.
+# If you want full control over the layout of the generated HTML pages it might
+# be necessary to disable the index and replace it with your own. The
+# DISABLE_INDEX tag can be used to turn on/off the condensed index (tabs) at top
+# of each HTML page. A value of NO enables the index and the value YES disables
+# it. Since the tabs in the index contain the same information as the navigation
+# tree, you can set this option to YES if you also set GENERATE_TREEVIEW to YES.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
 
 DISABLE_INDEX          = NO
 
-# This tag can be used to set the number of enum values (range [1..20])
-# that doxygen will group on one line in the generated HTML documentation.
-
-ENUM_VALUES_PER_LINE   = 4
-
 # The GENERATE_TREEVIEW tag is used to specify whether a tree-like index
-# structure should be generated to display hierarchical information.
-# If the tag value is set to YES, a side panel will be generated
-# containing a tree-like index structure (just like the one that
-# is generated for HTML Help). For this to work a browser that supports
-# JavaScript, DHTML, CSS and frames is required (i.e. any modern browser).
-# Windows users are probably better off using the HTML help feature.
+# structure should be generated to display hierarchical information. If the tag
+# value is set to YES, a side panel will be generated containing a tree-like
+# index structure (just like the one that is generated for HTML Help). For this
+# to work a browser that supports JavaScript, DHTML, CSS and frames is required
+# (i.e. any modern browser). Windows users are probably better off using the
+# HTML help feature. Via custom style sheets (see HTML_EXTRA_STYLESHEET) one can
+# further fine tune the look of the index (see "Fine-tuning the output"). As an
+# example, the default style sheet generated by doxygen has an example that
+# shows how to put an image at the root of the tree instead of the PROJECT_NAME.
+# Since the tree basically has the same information as the tab index, you could
+# consider setting DISABLE_INDEX to YES when enabling this option.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
 
 GENERATE_TREEVIEW      = NO
 
-# By enabling USE_INLINE_TREES, doxygen will generate the Groups, Directories,
-# and Class Hierarchy pages using a tree view instead of an ordered list.
+# When both GENERATE_TREEVIEW and DISABLE_INDEX are set to YES, then the
+# FULL_SIDEBAR option determines if the side bar is limited to only the treeview
+# area (value NO) or if it should extend to the full height of the window (value
+# YES). Setting this to YES gives a layout similar to
+# https://docs.readthedocs.io with more room for contents, but less room for the
+# project logo, title, and description. If either GENERATE_TREEVIEW or
+# DISABLE_INDEX is set to NO, this option has no effect.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+FULL_SIDEBAR           = NO
 
-USE_INLINE_TREES       = NO
+# The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values that
+# doxygen will group on one line in the generated HTML documentation.
+#
+# Note that a value of 0 will completely suppress the enum values from appearing
+# in the overview section.
+# Minimum value: 0, maximum value: 20, default value: 4.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+ENUM_VALUES_PER_LINE   = 4
 
-# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be
-# used to set the initial width (in pixels) of the frame in which the tree
-# is shown.
+# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be used
+# to set the initial width (in pixels) of the frame in which the tree is shown.
+# Minimum value: 0, maximum value: 1500, default value: 250.
+# This tag requires that the tag GENERATE_HTML is set to YES.
 
 TREEVIEW_WIDTH         = 250
 
-# When the EXT_LINKS_IN_WINDOW option is set to YES doxygen will open
-# links to external symbols imported via tag files in a separate window.
+# If the EXT_LINKS_IN_WINDOW option is set to YES, doxygen will open links to
+# external symbols imported via tag files in a separate window.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
 
 EXT_LINKS_IN_WINDOW    = NO
 
-# Use this tag to change the font size of Latex formulas included
-# as images in the HTML documentation. The default is 10. Note that
-# when you change the font size after a successful doxygen run you need
-# to manually remove any form_*.png images from the HTML output directory
-# to force them to be regenerated.
+# If the OBFUSCATE_EMAILS tag is set to YES, doxygen will obfuscate email
+# addresses.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_HTML is set to YES.
 
-FORMULA_FONTSIZE       = 10
+OBFUSCATE_EMAILS       = YES
+
+# If the HTML_FORMULA_FORMAT option is set to svg, doxygen will use the pdf2svg
+# tool (see https://github.com/dawbarton/pdf2svg) or inkscape (see
+# https://inkscape.org) to generate formulas as SVG images instead of PNGs for
+# the HTML output. These images will generally look nicer at scaled resolutions.
+# Possible values are: png (the default) and svg (looks nicer but requires the
+# pdf2svg or inkscape tool).
+# The default value is: png.
+# This tag requires that the tag GENERATE_HTML is set to YES.
 
-# Use the FORMULA_TRANPARENT tag to determine whether or not the images
-# generated for formulas are transparent PNGs. Transparent PNGs are
-# not supported properly for IE 6.0, but are supported on all modern browsers.
-# Note that when changing this option you need to delete any form_*.png files
-# in the HTML output before the changes have effect.
+HTML_FORMULA_FORMAT    = png
 
-FORMULA_TRANSPARENT    = YES
+# Use this tag to change the font size of LaTeX formulas included as images in
+# the HTML documentation. When you change the font size after a successful
+# doxygen run you need to manually remove any form_*.png images from the HTML
+# output directory to force them to be regenerated.
+# Minimum value: 8, maximum value: 50, default value: 10.
+# This tag requires that the tag GENERATE_HTML is set to YES.
 
-# When the SEARCHENGINE tag is enabled doxygen will generate a search box
-# for the HTML output. The underlying search engine uses javascript
-# and DHTML and should work on any modern browser. Note that when using
-# HTML help (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets
-# (GENERATE_DOCSET) there is already a search function so this one should
-# typically be disabled. For large projects the javascript based search engine
-# can be slow, then enabling SERVER_BASED_SEARCH may provide a better solution.
+FORMULA_FONTSIZE       = 10
+
+# The FORMULA_MACROFILE can contain LaTeX \newcommand and \renewcommand commands
+# to create new LaTeX commands to be used in formulas as building blocks. See
+# the section "Including formulas" for details.
+
+FORMULA_MACROFILE      =
+
+# Enable the USE_MATHJAX option to render LaTeX formulas using MathJax (see
+# https://www.mathjax.org) which uses client side JavaScript for the rendering
+# instead of using pre-rendered bitmaps. Use this if you do not have LaTeX
+# installed or if you want to formulas look prettier in the HTML output. When
+# enabled you may also need to install MathJax separately and configure the path
+# to it using the MATHJAX_RELPATH option.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+USE_MATHJAX            = NO
+
+# With MATHJAX_VERSION it is possible to specify the MathJax version to be used.
+# Note that the different versions of MathJax have different requirements with
+# regards to the different settings, so it is possible that also other MathJax
+# settings have to be changed when switching between the different MathJax
+# versions.
+# Possible values are: MathJax_2 and MathJax_3.
+# The default value is: MathJax_2.
+# This tag requires that the tag USE_MATHJAX is set to YES.
+
+MATHJAX_VERSION        = MathJax_2
+
+# When MathJax is enabled you can set the default output format to be used for
+# the MathJax output. For more details about the output format see MathJax
+# version 2 (see:
+# http://docs.mathjax.org/en/v2.7-latest/output.html) and MathJax version 3
+# (see:
+# http://docs.mathjax.org/en/latest/web/components/output.html).
+# Possible values are: HTML-CSS (which is slower, but has the best
+# compatibility. This is the name for Mathjax version 2, for MathJax version 3
+# this will be translated into chtml), NativeMML (i.e. MathML. Only supported
+# for NathJax 2. For MathJax version 3 chtml will be used instead.), chtml (This
+# is the name for Mathjax version 3, for MathJax version 2 this will be
+# translated into HTML-CSS) and SVG.
+# The default value is: HTML-CSS.
+# This tag requires that the tag USE_MATHJAX is set to YES.
+
+MATHJAX_FORMAT         = HTML-CSS
+
+# When MathJax is enabled you need to specify the location relative to the HTML
+# output directory using the MATHJAX_RELPATH option. The destination directory
+# should contain the MathJax.js script. For instance, if the mathjax directory
+# is located at the same level as the HTML output directory, then
+# MATHJAX_RELPATH should be ../mathjax. The default value points to the MathJax
+# Content Delivery Network so you can quickly see the result without installing
+# MathJax. However, it is strongly recommended to install a local copy of
+# MathJax from https://www.mathjax.org before deployment. The default value is:
+# - in case of MathJax version 2: https://cdn.jsdelivr.net/npm/mathjax@2
+# - in case of MathJax version 3: https://cdn.jsdelivr.net/npm/mathjax@3
+# This tag requires that the tag USE_MATHJAX is set to YES.
+
+MATHJAX_RELPATH        = https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.5/
+
+# The MATHJAX_EXTENSIONS tag can be used to specify one or more MathJax
+# extension names that should be enabled during MathJax rendering. For example
+# for MathJax version 2 (see
+# https://docs.mathjax.org/en/v2.7-latest/tex.html#tex-and-latex-extensions):
+# MATHJAX_EXTENSIONS = TeX/AMSmath TeX/AMSsymbols
+# For example for MathJax version 3 (see
+# http://docs.mathjax.org/en/latest/input/tex/extensions/index.html):
+# MATHJAX_EXTENSIONS = ams
+# This tag requires that the tag USE_MATHJAX is set to YES.
+
+MATHJAX_EXTENSIONS     =
+
+# The MATHJAX_CODEFILE tag can be used to specify a file with javascript pieces
+# of code that will be used on startup of the MathJax code. See the MathJax site
+# (see:
+# http://docs.mathjax.org/en/v2.7-latest/output.html) for more details. For an
+# example see the documentation.
+# This tag requires that the tag USE_MATHJAX is set to YES.
+
+MATHJAX_CODEFILE       =
+
+# When the SEARCHENGINE tag is enabled doxygen will generate a search box for
+# the HTML output. The underlying search engine uses javascript and DHTML and
+# should work on any modern browser. Note that when using HTML help
+# (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets (GENERATE_DOCSET)
+# there is already a search function so this one should typically be disabled.
+# For large projects the javascript based search engine can be slow, then
+# enabling SERVER_BASED_SEARCH may provide a better solution. It is possible to
+# search using the keyboard; to jump to the search box use <access key> + S
+# (what the <access key> is depends on the OS and browser, but it is typically
+# <CTRL>, <ALT>/<option>, or both). Inside the search box use the <cursor down
+# key> to jump into the search results window, the results can be navigated
+# using the <cursor keys>. Press <Enter> to select an item or <escape> to cancel
+# the search. The filter options can be selected when the cursor is inside the
+# search box by pressing <Shift>+<cursor down>. Also here use the <cursor keys>
+# to select a filter and <Enter> or <escape> to activate or cancel the filter
+# option.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_HTML is set to YES.
 
 SEARCHENGINE           = YES
 
 # When the SERVER_BASED_SEARCH tag is enabled the search engine will be
-# implemented using a PHP enabled web server instead of at the web client
-# using Javascript. Doxygen will generate the search PHP script and index
-# file to put on the web server. The advantage of the server
-# based approach is that it scales better to large projects and allows
-# full text search. The disadvances is that it is more difficult to setup
-# and does not have live searching capabilities.
+# implemented using a web server instead of a web client using JavaScript. There
+# are two flavors of web server based searching depending on the EXTERNAL_SEARCH
+# setting. When disabled, doxygen will generate a PHP script for searching and
+# an index file used by the script. When EXTERNAL_SEARCH is enabled the indexing
+# and searching needs to be provided by external tools. See the section
+# "External Indexing and Searching" for details.
+# The default value is: NO.
+# This tag requires that the tag SEARCHENGINE is set to YES.
 
 SERVER_BASED_SEARCH    = NO
 
+# When EXTERNAL_SEARCH tag is enabled doxygen will no longer generate the PHP
+# script for searching. Instead the search results are written to an XML file
+# which needs to be processed by an external indexer. Doxygen will invoke an
+# external search engine pointed to by the SEARCHENGINE_URL option to obtain the
+# search results.
+#
+# Doxygen ships with an example indexer (doxyindexer) and search engine
+# (doxysearch.cgi) which are based on the open source search engine library
+# Xapian (see:
+# https://xapian.org/).
+#
+# See the section "External Indexing and Searching" for details.
+# The default value is: NO.
+# This tag requires that the tag SEARCHENGINE is set to YES.
+
+EXTERNAL_SEARCH        = NO
+
+# The SEARCHENGINE_URL should point to a search engine hosted by a web server
+# which will return the search results when EXTERNAL_SEARCH is enabled.
+#
+# Doxygen ships with an example indexer (doxyindexer) and search engine
+# (doxysearch.cgi) which are based on the open source search engine library
+# Xapian (see:
+# https://xapian.org/). See the section "External Indexing and Searching" for
+# details.
+# This tag requires that the tag SEARCHENGINE is set to YES.
+
+SEARCHENGINE_URL       =
+
+# When SERVER_BASED_SEARCH and EXTERNAL_SEARCH are both enabled the unindexed
+# search data is written to a file for indexing by an external tool. With the
+# SEARCHDATA_FILE tag the name of this file can be specified.
+# The default file is: searchdata.xml.
+# This tag requires that the tag SEARCHENGINE is set to YES.
+
+SEARCHDATA_FILE        = searchdata.xml
+
+# When SERVER_BASED_SEARCH and EXTERNAL_SEARCH are both enabled the
+# EXTERNAL_SEARCH_ID tag can be used as an identifier for the project. This is
+# useful in combination with EXTRA_SEARCH_MAPPINGS to search through multiple
+# projects and redirect the results back to the right project.
+# This tag requires that the tag SEARCHENGINE is set to YES.
+
+EXTERNAL_SEARCH_ID     =
+
+# The EXTRA_SEARCH_MAPPINGS tag can be used to enable searching through doxygen
+# projects other than the one defined by this configuration file, but that are
+# all added to the same external search index. Each project needs to have a
+# unique id set via EXTERNAL_SEARCH_ID. The search mapping then maps the id of
+# to a relative location where the documentation can be found. The format is:
+# EXTRA_SEARCH_MAPPINGS = tagname1=loc1 tagname2=loc2 ...
+# This tag requires that the tag SEARCHENGINE is set to YES.
+
+EXTRA_SEARCH_MAPPINGS  =
+
 #---------------------------------------------------------------------------
-# configuration options related to the LaTeX output
+# Configuration options related to the LaTeX output
 #---------------------------------------------------------------------------
 
-# If the GENERATE_LATEX tag is set to YES (the default) Doxygen will
-# generate Latex output.
+# If the GENERATE_LATEX tag is set to YES, doxygen will generate LaTeX output.
+# The default value is: YES.
 
 GENERATE_LATEX         = NO
 
-# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put.
-# If a relative path is entered the value of OUTPUT_DIRECTORY will be
-# put in front of it. If left blank `latex' will be used as the default path.
+# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. If a
+# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of
+# it.
+# The default directory is: latex.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
 
 LATEX_OUTPUT           = latex
 
 # The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be
-# invoked. If left blank `latex' will be used as the default command name.
-# Note that when enabling USE_PDFLATEX this option is only used for
-# generating bitmaps for formulas in the HTML output, but not in the
-# Makefile that is written to the output directory.
+# invoked.
+#
+# Note that when not enabling USE_PDFLATEX the default is latex when enabling
+# USE_PDFLATEX the default is pdflatex and when in the later case latex is
+# chosen this is overwritten by pdflatex. For specific output languages the
+# default can have been set differently, this depends on the implementation of
+# the output language.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
 
 LATEX_CMD_NAME         = latex
 
-# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to
-# generate index for LaTeX. If left blank `makeindex' will be used as the
-# default command name.
+# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to generate
+# index for LaTeX.
+# Note: This tag is used in the Makefile / make.bat.
+# See also: LATEX_MAKEINDEX_CMD for the part in the generated output file
+# (.tex).
+# The default file is: makeindex.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
 
 MAKEINDEX_CMD_NAME     = makeindex
 
-# If the COMPACT_LATEX tag is set to YES Doxygen generates more compact
-# LaTeX documents. This may be useful for small projects and may help to
-# save some trees in general.
+# The LATEX_MAKEINDEX_CMD tag can be used to specify the command name to
+# generate index for LaTeX. In case there is no backslash (\) as first character
+# it will be automatically added in the LaTeX code.
+# Note: This tag is used in the generated output file (.tex).
+# See also: MAKEINDEX_CMD_NAME for the part in the Makefile / make.bat.
+# The default value is: makeindex.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
 
-COMPACT_LATEX          = NO
+LATEX_MAKEINDEX_CMD    = \makeindex
 
-# The PAPER_TYPE tag can be used to set the paper type that is used
-# by the printer. Possible values are: a4, a4wide, letter, legal and
-# executive. If left blank a4wide will be used.
+# If the COMPACT_LATEX tag is set to YES, doxygen generates more compact LaTeX
+# documents. This may be useful for small projects and may help to save some
+# trees in general.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
 
-PAPER_TYPE             = a4wide
+COMPACT_LATEX          = NO
 
-# The EXTRA_PACKAGES tag can be to specify one or more names of LaTeX
-# packages that should be included in the LaTeX output.
+# The PAPER_TYPE tag can be used to set the paper type that is used by the
+# printer.
+# Possible values are: a4 (210 x 297 mm), letter (8.5 x 11 inches), legal (8.5 x
+# 14 inches) and executive (7.25 x 10.5 inches).
+# The default value is: a4.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+PAPER_TYPE             = a4
+
+# The EXTRA_PACKAGES tag can be used to specify one or more LaTeX package names
+# that should be included in the LaTeX output. The package can be specified just
+# by its name or with the correct syntax as to be used with the LaTeX
+# \usepackage command. To get the times font for instance you can specify :
+# EXTRA_PACKAGES=times or EXTRA_PACKAGES={times}
+# To use the option intlimits with the amsmath package you can specify:
+# EXTRA_PACKAGES=[intlimits]{amsmath}
+# If left blank no extra packages will be included.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
 
 EXTRA_PACKAGES         =
 
-# The LATEX_HEADER tag can be used to specify a personal LaTeX header for
-# the generated latex document. The header should contain everything until
-# the first chapter. If it is left blank doxygen will generate a
-# standard header. Notice: only use this tag if you know what you are doing!
+# The LATEX_HEADER tag can be used to specify a user-defined LaTeX header for
+# the generated LaTeX document. The header should contain everything until the
+# first chapter. If it is left blank doxygen will generate a standard header. It
+# is highly recommended to start with a default header using
+# doxygen -w latex new_header.tex new_footer.tex new_stylesheet.sty
+# and then modify the file new_header.tex. See also section "Doxygen usage" for
+# information on how to generate the default header that doxygen normally uses.
+#
+# Note: Only use a user-defined header if you know what you are doing!
+# Note: The header is subject to change so you typically have to regenerate the
+# default header when upgrading to a newer version of doxygen. The following
+# commands have a special meaning inside the header (and footer): For a
+# description of the possible markers and block names see the documentation.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
 
 LATEX_HEADER           =
 
-# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated
-# is prepared for conversion to pdf (using ps2pdf). The pdf file will
-# contain links (just like the HTML output) instead of page references
-# This makes the output suitable for online browsing using a pdf viewer.
+# The LATEX_FOOTER tag can be used to specify a user-defined LaTeX footer for
+# the generated LaTeX document. The footer should contain everything after the
+# last chapter. If it is left blank doxygen will generate a standard footer. See
+# LATEX_HEADER for more information on how to generate a default footer and what
+# special commands can be used inside the footer. See also section "Doxygen
+# usage" for information on how to generate the default footer that doxygen
+# normally uses. Note: Only use a user-defined footer if you know what you are
+# doing!
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_FOOTER           =
+
+# The LATEX_EXTRA_STYLESHEET tag can be used to specify additional user-defined
+# LaTeX style sheets that are included after the standard style sheets created
+# by doxygen. Using this option one can overrule certain style aspects. Doxygen
+# will copy the style sheet files to the output directory.
+# Note: The order of the extra style sheet files is of importance (e.g. the last
+# style sheet in the list overrules the setting of the previous ones in the
+# list).
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_EXTRA_STYLESHEET =
+
+# The LATEX_EXTRA_FILES tag can be used to specify one or more extra images or
+# other source files which should be copied to the LATEX_OUTPUT output
+# directory. Note that the files will be copied as-is; there are no commands or
+# markers available.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_EXTRA_FILES      =
+
+# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated is
+# prepared for conversion to PDF (using ps2pdf or pdflatex). The PDF file will
+# contain links (just like the HTML output) instead of page references. This
+# makes the output suitable for online browsing using a PDF viewer.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
 
 PDF_HYPERLINKS         = YES
 
-# If the USE_PDFLATEX tag is set to YES, pdflatex will be used instead of
-# plain latex in the generated Makefile. Set this option to YES to get a
-# higher quality PDF documentation.
+# If the USE_PDFLATEX tag is set to YES, doxygen will use the engine as
+# specified with LATEX_CMD_NAME to generate the PDF file directly from the LaTeX
+# files. Set this option to YES, to get a higher quality PDF documentation.
+#
+# See also section LATEX_CMD_NAME for selecting the engine.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
 
 USE_PDFLATEX           = YES
 
-# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \\batchmode.
-# command to the generated LaTeX files. This will instruct LaTeX to keep
-# running if errors occur, instead of asking the user for help.
-# This option is also used when generating formulas in HTML.
+# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \batchmode
+# command to the generated LaTeX files. This will instruct LaTeX to keep running
+# if errors occur, instead of asking the user for help.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
 
 LATEX_BATCHMODE        = NO
 
-# If LATEX_HIDE_INDICES is set to YES then doxygen will not
-# include the index chapters (such as File Index, Compound Index, etc.)
-# in the output.
+# If the LATEX_HIDE_INDICES tag is set to YES then doxygen will not include the
+# index chapters (such as File Index, Compound Index, etc.) in the output.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
 
 LATEX_HIDE_INDICES     = NO
 
-# If LATEX_SOURCE_CODE is set to YES then doxygen will include
-# source code with syntax highlighting in the LaTeX output.
-# Note that which sources are shown also depends on other settings
-# such as SOURCE_BROWSER.
+# The LATEX_BIB_STYLE tag can be used to specify the style to use for the
+# bibliography, e.g. plainnat, or ieeetr. See
+# https://en.wikipedia.org/wiki/BibTeX and \cite for more info.
+# The default value is: plain.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
 
-LATEX_SOURCE_CODE      = NO
+LATEX_BIB_STYLE        = plain
+
+# If the LATEX_TIMESTAMP tag is set to YES then the footer of each generated
+# page will contain the date and time when the page was generated. Setting this
+# to NO can help when comparing the output of multiple runs.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_TIMESTAMP        = NO
+
+# The LATEX_EMOJI_DIRECTORY tag is used to specify the (relative or absolute)
+# path from which the emoji images will be read. If a relative path is entered,
+# it will be relative to the LATEX_OUTPUT directory. If left blank the
+# LATEX_OUTPUT directory will be used.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_EMOJI_DIRECTORY  =
 
 #---------------------------------------------------------------------------
-# configuration options related to the RTF output
+# Configuration options related to the RTF output
 #---------------------------------------------------------------------------
 
-# If the GENERATE_RTF tag is set to YES Doxygen will generate RTF output
-# The RTF output is optimized for Word 97 and may not look very pretty with
-# other RTF readers or editors.
+# If the GENERATE_RTF tag is set to YES, doxygen will generate RTF output. The
+# RTF output is optimized for Word 97 and may not look too pretty with other RTF
+# readers/editors.
+# The default value is: NO.
 
 GENERATE_RTF           = NO
 
-# The RTF_OUTPUT tag is used to specify where the RTF docs will be put.
-# If a relative path is entered the value of OUTPUT_DIRECTORY will be
-# put in front of it. If left blank `rtf' will be used as the default path.
+# The RTF_OUTPUT tag is used to specify where the RTF docs will be put. If a
+# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of
+# it.
+# The default directory is: rtf.
+# This tag requires that the tag GENERATE_RTF is set to YES.
 
 RTF_OUTPUT             = rtf
 
-# If the COMPACT_RTF tag is set to YES Doxygen generates more compact
-# RTF documents. This may be useful for small projects and may help to
-# save some trees in general.
+# If the COMPACT_RTF tag is set to YES, doxygen generates more compact RTF
+# documents. This may be useful for small projects and may help to save some
+# trees in general.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_RTF is set to YES.
 
 COMPACT_RTF            = NO
 
-# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated
-# will contain hyperlink fields. The RTF file will
-# contain links (just like the HTML output) instead of page references.
-# This makes the output suitable for online browsing using WORD or other
-# programs which support those fields.
-# Note: wordpad (write) and others do not support links.
+# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated will
+# contain hyperlink fields. The RTF file will contain links (just like the HTML
+# output) instead of page references. This makes the output suitable for online
+# browsing using Word or some other Word compatible readers that support those
+# fields.
+#
+# Note: WordPad (write) and others do not support links.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_RTF is set to YES.
 
 RTF_HYPERLINKS         = NO
 
 # Load stylesheet definitions from file. Syntax is similar to doxygen's
-# config file, i.e. a series of assignments. You only have to provide
+# configuration file, i.e. a series of assignments. You only have to provide
 # replacements, missing definitions are set to their default value.
+#
+# See also section "Doxygen usage" for information on how to generate the
+# default style sheet that doxygen normally uses.
+# This tag requires that the tag GENERATE_RTF is set to YES.
 
 RTF_STYLESHEET_FILE    =
 
-# Set optional variables used in the generation of an rtf document.
-# Syntax is similar to doxygen's config file.
+# Set optional variables used in the generation of an RTF document. Syntax is
+# similar to doxygen's configuration file. A template extensions file can be
+# generated using doxygen -e rtf extensionFile.
+# This tag requires that the tag GENERATE_RTF is set to YES.
 
 RTF_EXTENSIONS_FILE    =
 
 #---------------------------------------------------------------------------
-# configuration options related to the man page output
+# Configuration options related to the man page output
 #---------------------------------------------------------------------------
 
-# If the GENERATE_MAN tag is set to YES (the default) Doxygen will
-# generate man pages
+# If the GENERATE_MAN tag is set to YES, doxygen will generate man pages for
+# classes and files.
+# The default value is: NO.
 
 GENERATE_MAN           = NO
 
-# The MAN_OUTPUT tag is used to specify where the man pages will be put.
-# If a relative path is entered the value of OUTPUT_DIRECTORY will be
-# put in front of it. If left blank `man' will be used as the default path.
+# The MAN_OUTPUT tag is used to specify where the man pages will be put. If a
+# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of
+# it. A directory man3 will be created inside the directory specified by
+# MAN_OUTPUT.
+# The default directory is: man.
+# This tag requires that the tag GENERATE_MAN is set to YES.
 
 MAN_OUTPUT             = man
 
-# The MAN_EXTENSION tag determines the extension that is added to
-# the generated man pages (default is the subroutine's section .3)
+# The MAN_EXTENSION tag determines the extension that is added to the generated
+# man pages. In case the manual section does not start with a number, the number
+# 3 is prepended. The dot (.) at the beginning of the MAN_EXTENSION tag is
+# optional.
+# The default value is: .3.
+# This tag requires that the tag GENERATE_MAN is set to YES.
 
 MAN_EXTENSION          = .3
 
-# If the MAN_LINKS tag is set to YES and Doxygen generates man output,
-# then it will generate one additional man file for each entity
-# documented in the real man page(s). These additional files
-# only source the real man page, but without them the man command
-# would be unable to find the correct page. The default is NO.
+# The MAN_SUBDIR tag determines the name of the directory created within
+# MAN_OUTPUT in which the man pages are placed. If defaults to man followed by
+# MAN_EXTENSION with the initial . removed.
+# This tag requires that the tag GENERATE_MAN is set to YES.
+
+MAN_SUBDIR             =
+
+# If the MAN_LINKS tag is set to YES and doxygen generates man output, then it
+# will generate one additional man file for each entity documented in the real
+# man page(s). These additional files only source the real man page, but without
+# them the man command would be unable to find the correct page.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_MAN is set to YES.
 
 MAN_LINKS              = NO
 
 #---------------------------------------------------------------------------
-# configuration options related to the XML output
+# Configuration options related to the XML output
 #---------------------------------------------------------------------------
 
-# If the GENERATE_XML tag is set to YES Doxygen will
-# generate an XML file that captures the structure of
-# the code including all documentation.
+# If the GENERATE_XML tag is set to YES, doxygen will generate an XML file that
+# captures the structure of the code including all documentation.
+# The default value is: NO.
 
 GENERATE_XML           = NO
 
-# The XML_OUTPUT tag is used to specify where the XML pages will be put.
-# If a relative path is entered the value of OUTPUT_DIRECTORY will be
-# put in front of it. If left blank `xml' will be used as the default path.
+# The XML_OUTPUT tag is used to specify where the XML pages will be put. If a
+# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of
+# it.
+# The default directory is: xml.
+# This tag requires that the tag GENERATE_XML is set to YES.
 
 XML_OUTPUT             = xml
 
-# The XML_SCHEMA tag can be used to specify an XML schema,
-# which can be used by a validating XML parser to check the
-# syntax of the XML files.
+# If the XML_PROGRAMLISTING tag is set to YES, doxygen will dump the program
+# listings (including syntax highlighting and cross-referencing information) to
+# the XML output. Note that enabling this will significantly increase the size
+# of the XML output.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_XML is set to YES.
 
-XML_SCHEMA             =
+XML_PROGRAMLISTING     = YES
 
-# The XML_DTD tag can be used to specify an XML DTD,
-# which can be used by a validating XML parser to check the
-# syntax of the XML files.
+# If the XML_NS_MEMB_FILE_SCOPE tag is set to YES, doxygen will include
+# namespace members in file scope as well, matching the HTML output.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_XML is set to YES.
 
-XML_DTD                =
+XML_NS_MEMB_FILE_SCOPE = NO
 
-# If the XML_PROGRAMLISTING tag is set to YES Doxygen will
-# dump the program listings (including syntax highlighting
-# and cross-referencing information) to the XML output. Note that
-# enabling this will significantly increase the size of the XML output.
+#---------------------------------------------------------------------------
+# Configuration options related to the DOCBOOK output
+#---------------------------------------------------------------------------
 
-XML_PROGRAMLISTING     = YES
+# If the GENERATE_DOCBOOK tag is set to YES, doxygen will generate Docbook files
+# that can be used to generate PDF.
+# The default value is: NO.
+
+GENERATE_DOCBOOK       = NO
+
+# The DOCBOOK_OUTPUT tag is used to specify where the Docbook pages will be put.
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be put in
+# front of it.
+# The default directory is: docbook.
+# This tag requires that the tag GENERATE_DOCBOOK is set to YES.
+
+DOCBOOK_OUTPUT         = docbook
 
 #---------------------------------------------------------------------------
-# configuration options for the AutoGen Definitions output
+# Configuration options for the AutoGen Definitions output
 #---------------------------------------------------------------------------
 
-# If the GENERATE_AUTOGEN_DEF tag is set to YES Doxygen will
-# generate an AutoGen Definitions (see autogen.sf.net) file
-# that captures the structure of the code including all
-# documentation. Note that this feature is still experimental
-# and incomplete at the moment.
+# If the GENERATE_AUTOGEN_DEF tag is set to YES, doxygen will generate an
+# AutoGen Definitions (see http://autogen.sourceforge.net/) file that captures
+# the structure of the code including all documentation. Note that this feature
+# is still experimental and incomplete at the moment.
+# The default value is: NO.
 
 GENERATE_AUTOGEN_DEF   = NO
 
 #---------------------------------------------------------------------------
-# configuration options related to the Perl module output
+# Configuration options related to the Perl module output
 #---------------------------------------------------------------------------
 
-# If the GENERATE_PERLMOD tag is set to YES Doxygen will
-# generate a Perl module file that captures the structure of
-# the code including all documentation. Note that this
-# feature is still experimental and incomplete at the
-# moment.
+# If the GENERATE_PERLMOD tag is set to YES, doxygen will generate a Perl module
+# file that captures the structure of the code including all documentation.
+#
+# Note that this feature is still experimental and incomplete at the moment.
+# The default value is: NO.
 
 GENERATE_PERLMOD       = NO
 
-# If the PERLMOD_LATEX tag is set to YES Doxygen will generate
-# the necessary Makefile rules, Perl scripts and LaTeX code to be able
-# to generate PDF and DVI output from the Perl module output.
+# If the PERLMOD_LATEX tag is set to YES, doxygen will generate the necessary
+# Makefile rules, Perl scripts and LaTeX code to be able to generate PDF and DVI
+# output from the Perl module output.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_PERLMOD is set to YES.
 
 PERLMOD_LATEX          = NO
 
-# If the PERLMOD_PRETTY tag is set to YES the Perl module output will be
-# nicely formatted so it can be parsed by a human reader.
-# This is useful
-# if you want to understand what is going on.
-# On the other hand, if this
-# tag is set to NO the size of the Perl module output will be much smaller
-# and Perl will parse it just the same.
+# If the PERLMOD_PRETTY tag is set to YES, the Perl module output will be nicely
+# formatted so it can be parsed by a human reader. This is useful if you want to
+# understand what is going on. On the other hand, if this tag is set to NO, the
+# size of the Perl module output will be much smaller and Perl will parse it
+# just the same.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_PERLMOD is set to YES.
 
 PERLMOD_PRETTY         = YES
 
-# The names of the make variables in the generated doxyrules.make file
-# are prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX.
-# This is useful so different doxyrules.make files included by the same
-# Makefile don't overwrite each other's variables.
+# The names of the make variables in the generated doxyrules.make file are
+# prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX. This is useful
+# so different doxyrules.make files included by the same Makefile don't
+# overwrite each other's variables.
+# This tag requires that the tag GENERATE_PERLMOD is set to YES.
 
 PERLMOD_MAKEVAR_PREFIX =
 
@@ -1320,311 +2264,461 @@ PERLMOD_MAKEVAR_PREFIX =
 # Configuration options related to the preprocessor
 #---------------------------------------------------------------------------
 
-# If the ENABLE_PREPROCESSING tag is set to YES (the default) Doxygen will
-# evaluate all C-preprocessor directives found in the sources and include
-# files.
+# If the ENABLE_PREPROCESSING tag is set to YES, doxygen will evaluate all
+# C-preprocessor directives found in the sources and include files.
+# The default value is: YES.
 
 ENABLE_PREPROCESSING   = YES
 
-# If the MACRO_EXPANSION tag is set to YES Doxygen will expand all macro
-# names in the source code. If set to NO (the default) only conditional
-# compilation will be performed. Macro expansion can be done in a controlled
-# way by setting EXPAND_ONLY_PREDEF to YES.
+# If the MACRO_EXPANSION tag is set to YES, doxygen will expand all macro names
+# in the source code. If set to NO, only conditional compilation will be
+# performed. Macro expansion can be done in a controlled way by setting
+# EXPAND_ONLY_PREDEF to YES.
+# The default value is: NO.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
 
 MACRO_EXPANSION        = NO
 
-# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES
-# then the macro expansion is limited to the macros specified with the
-# PREDEFINED and EXPAND_AS_DEFINED tags.
+# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES then
+# the macro expansion is limited to the macros specified with the PREDEFINED and
+# EXPAND_AS_DEFINED tags.
+# The default value is: NO.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
 
 EXPAND_ONLY_PREDEF     = NO
 
-# If the SEARCH_INCLUDES tag is set to YES (the default) the includes files
-# in the INCLUDE_PATH (see below) will be search if a #include is found.
+# If the SEARCH_INCLUDES tag is set to YES, the include files in the
+# INCLUDE_PATH will be searched if a #include is found.
+# The default value is: YES.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
 
 SEARCH_INCLUDES        = YES
 
 # The INCLUDE_PATH tag can be used to specify one or more directories that
-# contain include files that are not input files but should be processed by
-# the preprocessor.
+# contain include files that are not input files but should be processed by the
+# preprocessor. Note that the INCLUDE_PATH is not recursive, so the setting of
+# RECURSIVE has no effect here.
+# This tag requires that the tag SEARCH_INCLUDES is set to YES.
 
 INCLUDE_PATH           =
 
 # You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard
 # patterns (like *.h and *.hpp) to filter out the header-files in the
-# directories. If left blank, the patterns specified with FILE_PATTERNS will
-# be used.
+# directories. If left blank, the patterns specified with FILE_PATTERNS will be
+# used.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
 
 INCLUDE_FILE_PATTERNS  =
 
-# The PREDEFINED tag can be used to specify one or more macro names that
-# are defined before the preprocessor is started (similar to the -D option of
-# gcc). The argument of the tag is a list of macros of the form: name
-# or name=definition (no spaces). If the definition and the = are
-# omitted =1 is assumed. To prevent a macro definition from being
-# undefined via #undef or recursively expanded use the := operator
-# instead of the = operator.
+# The PREDEFINED tag can be used to specify one or more macro names that are
+# defined before the preprocessor is started (similar to the -D option of e.g.
+# gcc). The argument of the tag is a list of macros of the form: name or
+# name=definition (no spaces). If the definition and the "=" are omitted, "=1"
+# is assumed. To prevent a macro definition from being undefined via #undef or
+# recursively expanded use the := operator instead of the = operator.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
 
 PREDEFINED             =
 
-# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then
-# this tag can be used to specify a list of macro names that should be expanded.
-# The macro definition that is found in the sources will be used.
-# Use the PREDEFINED tag if you want to use a different macro definition.
+# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then this
+# tag can be used to specify a list of macro names that should be expanded. The
+# macro definition that is found in the sources will be used. Use the PREDEFINED
+# tag if you want to use a different macro definition that overrules the
+# definition found in the source code.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
 
 EXPAND_AS_DEFINED      =
 
-# If the SKIP_FUNCTION_MACROS tag is set to YES (the default) then
-# doxygen's preprocessor will remove all function-like macros that are alone
-# on a line, have an all uppercase name, and do not end with a semicolon. Such
-# function macros are typically used for boiler-plate code, and will confuse
-# the parser if not removed.
+# If the SKIP_FUNCTION_MACROS tag is set to YES then doxygen's preprocessor will
+# remove all references to function-like macros that are alone on a line, have
+# an all uppercase name, and do not end with a semicolon. Such function macros
+# are typically used for boiler-plate code, and will confuse the parser if not
+# removed.
+# The default value is: YES.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
 
 SKIP_FUNCTION_MACROS   = YES
 
 #---------------------------------------------------------------------------
-# Configuration::additions related to external references
+# Configuration options related to external references
 #---------------------------------------------------------------------------
 
-# The TAGFILES option can be used to specify one or more tagfiles.
-# Optionally an initial location of the external documentation
-# can be added for each tagfile. The format of a tag file without
-# this location is as follows:
-#
+# The TAGFILES tag can be used to specify one or more tag files. For each tag
+# file the location of the external documentation should be added. The format of
+# a tag file without this location is as follows:
 # TAGFILES = file1 file2 ...
 # Adding location for the tag files is done as follows:
-#
 # TAGFILES = file1=loc1 "file2 = loc2" ...
-# where "loc1" and "loc2" can be relative or absolute paths or
-# URLs. If a location is present for each tag, the installdox tool
-# does not have to be run to correct the links.
-# Note that each tag file must have a unique name
-# (where the name does NOT include the path)
-# If a tag file is not located in the directory in which doxygen
-# is run, you must also specify the path to the tagfile here.
+# where loc1 and loc2 can be relative or absolute paths or URLs. See the
+# section "Linking to external documentation" for more information about the use
+# of tag files.
+# Note: Each tag file must have a unique name (where the name does NOT include
+# the path). If a tag file is not located in the directory in which doxygen is
+# run, you must also specify the path to the tagfile here.
 
 TAGFILES               =
 
-# When a file name is specified after GENERATE_TAGFILE, doxygen will create
-# a tag file that is based on the input files it reads.
+# When a file name is specified after GENERATE_TAGFILE, doxygen will create a
+# tag file that is based on the input files it reads. See section "Linking to
+# external documentation" for more information about the usage of tag files.
 
 GENERATE_TAGFILE       =
 
-# If the ALLEXTERNALS tag is set to YES all external classes will be listed
-# in the class index. If set to NO only the inherited external classes
-# will be listed.
+# If the ALLEXTERNALS tag is set to YES, all external class will be listed in
+# the class index. If set to NO, only the inherited external classes will be
+# listed.
+# The default value is: NO.
 
 ALLEXTERNALS           = NO
 
-# If the EXTERNAL_GROUPS tag is set to YES all external groups will be listed
-# in the modules index. If set to NO, only the current project's groups will
-# be listed.
+# If the EXTERNAL_GROUPS tag is set to YES, all external groups will be listed
+# in the modules index. If set to NO, only the current project's groups will be
+# listed.
+# The default value is: YES.
 
 EXTERNAL_GROUPS        = YES
 
-# The PERL_PATH should be the absolute path and name of the perl script
-# interpreter (i.e. the result of `which perl').
+# If the EXTERNAL_PAGES tag is set to YES, all external pages will be listed in
+# the related pages index. If set to NO, only the current project's pages will
+# be listed.
+# The default value is: YES.
 
-PERL_PATH              = /usr/bin/perl
+EXTERNAL_PAGES         = YES
 
 #---------------------------------------------------------------------------
 # Configuration options related to the dot tool
 #---------------------------------------------------------------------------
 
-# If the CLASS_DIAGRAMS tag is set to YES (the default) Doxygen will
-# generate a inheritance diagram (in HTML, RTF and LaTeX) for classes with base
-# or super classes. Setting the tag to NO turns the diagrams off. Note that
-# this option is superseded by the HAVE_DOT option below. This is only a
-# fallback. It is recommended to install and use dot, since it yields more
-# powerful graphs.
-
-CLASS_DIAGRAMS         = YES
+# You can include diagrams made with dia in doxygen documentation. Doxygen will
+# then run dia to produce the diagram and insert it in the documentation. The
+# DIA_PATH tag allows you to specify the directory where the dia binary resides.
+# If left empty dia is assumed to be found in the default search path.
 
-# You can define message sequence charts within doxygen comments using the \msc
-# command. Doxygen will then run the mscgen tool (see
-# http://www.mcternan.me.uk/mscgen/) to produce the chart and insert it in the
-# documentation. The MSCGEN_PATH tag allows you to specify the directory where
-# the mscgen tool resides. If left empty the tool is assumed to be found in the
-# default search path.
+DIA_PATH               =
 
-MSCGEN_PATH            =
-
-# If set to YES, the inheritance and collaboration graphs will hide
-# inheritance and usage relations if the target is undocumented
-# or is not a class.
+# If set to YES the inheritance and collaboration graphs will hide inheritance
+# and usage relations if the target is undocumented or is not a class.
+# The default value is: YES.
 
 HIDE_UNDOC_RELATIONS   = YES
 
 # If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is
-# available from the path. This tool is part of Graphviz, a graph visualization
-# toolkit from AT&T and Lucent Bell Labs. The other options in this section
-# have no effect if this option is set to NO (the default)
+# available from the path. This tool is part of Graphviz (see:
+# http://www.graphviz.org/), a graph visualization toolkit from AT&T and Lucent
+# Bell Labs. The other options in this section have no effect if this option is
+# set to NO
+# The default value is: NO.
 
 HAVE_DOT               = NO
 
-# The DOT_NUM_THREADS specifies the number of dot invocations doxygen is
-# allowed to run in parallel. When set to 0 (the default) doxygen will
-# base this on the number of processors available in the system. You can set it
-# explicitly to a value larger than 0 to get control over the balance
-# between CPU load and processing speed.
+# The DOT_NUM_THREADS specifies the number of dot invocations doxygen is allowed
+# to run in parallel. When set to 0 doxygen will base this on the number of
+# processors available in the system. You can set it explicitly to a value
+# larger than 0 to get control over the balance between CPU load and processing
+# speed.
+# Minimum value: 0, maximum value: 32, default value: 0.
+# This tag requires that the tag HAVE_DOT is set to YES.
 
 DOT_NUM_THREADS        = 0
 
-# By default doxygen will write a font called FreeSans.ttf to the output
-# directory and reference it in all dot files that doxygen generates. This
-# font does not include all possible unicode characters however, so when you need
-# these (or just want a differently looking font) you can specify the font name
-# using DOT_FONTNAME. You need need to make sure dot is able to find the font,
-# which can be done by putting it in a standard location or by setting the
-# DOTFONTPATH environment variable or by setting DOT_FONTPATH to the directory
-# containing the font.
+# DOT_COMMON_ATTR is common attributes for nodes, edges and labels of
+# subgraphs. When you want a differently looking font in the dot files that
+# doxygen generates you can specify fontname, fontcolor and fontsize attributes.
+# For details please see <a href=https://graphviz.org/doc/info/attrs.html>Node,
+# Edge and Graph Attributes specification</a> You need to make sure dot is able
+# to find the font, which can be done by putting it in a standard location or by
+# setting the DOTFONTPATH environment variable or by setting DOT_FONTPATH to the
+# directory containing the font. Default graphviz fontsize is 14.
+# The default value is: fontname=Helvetica,fontsize=10.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_COMMON_ATTR        = "fontname=FreeSans.ttf,fontsize=10"
+
+# DOT_EDGE_ATTR is concatenated with DOT_COMMON_ATTR. For elegant style you can
+# add 'arrowhead=open, arrowtail=open, arrowsize=0.5'. <a
+# href=https://graphviz.org/doc/info/arrows.html>Complete documentation about
+# arrows shapes.</a>
+# The default value is: labelfontname=Helvetica,labelfontsize=10.
+# This tag requires that the tag HAVE_DOT is set to YES.
 
-DOT_FONTNAME           = FreeSans.ttf
+DOT_EDGE_ATTR          = "labelfontname=FreeSans.ttf,labelfontsize=10"
 
-# The DOT_FONTSIZE tag can be used to set the size of the font of dot graphs.
-# The default size is 10pt.
+# DOT_NODE_ATTR is concatenated with DOT_COMMON_ATTR. For view without boxes
+# around nodes set 'shape=plain' or 'shape=plaintext' <a
+# href=https://www.graphviz.org/doc/info/shapes.html>Shapes specification</a>
+# The default value is: shape=box,height=0.2,width=0.4.
+# This tag requires that the tag HAVE_DOT is set to YES.
 
-DOT_FONTSIZE           = 10
+DOT_NODE_ATTR          = "shape=box,height=0.2,width=0.4"
 
-# By default doxygen will tell dot to use the output directory to look for the
-# FreeSans.ttf font (which doxygen will put there itself). If you specify a
-# different font using DOT_FONTNAME you can set the path where dot
-# can find it using this tag.
+# You can set the path where dot can find font specified with fontname in
+# DOT_COMMON_ATTR and others dot attributes.
+# This tag requires that the tag HAVE_DOT is set to YES.
 
 DOT_FONTPATH           =
 
-# If the CLASS_GRAPH and HAVE_DOT tags are set to YES then doxygen
-# will generate a graph for each documented class showing the direct and
-# indirect inheritance relations. Setting this tag to YES will force the
-# the CLASS_DIAGRAMS tag to NO.
+# If the CLASS_GRAPH tag is set to YES (or GRAPH) then doxygen will generate a
+# graph for each documented class showing the direct and indirect inheritance
+# relations. In case HAVE_DOT is set as well dot will be used to draw the graph,
+# otherwise the built-in generator will be used. If the CLASS_GRAPH tag is set
+# to TEXT the direct and indirect inheritance relations will be shown as texts /
+# links.
+# Possible values are: NO, YES, TEXT and GRAPH.
+# The default value is: YES.
 
 CLASS_GRAPH            = YES
 
-# If the COLLABORATION_GRAPH and HAVE_DOT tags are set to YES then doxygen
-# will generate a graph for each documented class showing the direct and
-# indirect implementation dependencies (inheritance, containment, and
-# class references variables) of the class with other documented classes.
+# If the COLLABORATION_GRAPH tag is set to YES then doxygen will generate a
+# graph for each documented class showing the direct and indirect implementation
+# dependencies (inheritance, containment, and class references variables) of the
+# class with other documented classes.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
 
 COLLABORATION_GRAPH    = YES
 
-# If the GROUP_GRAPHS and HAVE_DOT tags are set to YES then doxygen
-# will generate a graph for groups, showing the direct groups dependencies
+# If the GROUP_GRAPHS tag is set to YES then doxygen will generate a graph for
+# groups, showing the direct groups dependencies. See also the chapter Grouping
+# in the manual.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
 
 GROUP_GRAPHS           = YES
 
-# If the UML_LOOK tag is set to YES doxygen will generate inheritance and
+# If the UML_LOOK tag is set to YES, doxygen will generate inheritance and
 # collaboration diagrams in a style similar to the OMG's Unified Modeling
 # Language.
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
 
 UML_LOOK               = NO
 
-# If set to YES, the inheritance and collaboration graphs will show the
-# relations between templates and their instances.
+# If the UML_LOOK tag is enabled, the fields and methods are shown inside the
+# class node. If there are many fields or methods and many nodes the graph may
+# become too big to be useful. The UML_LIMIT_NUM_FIELDS threshold limits the
+# number of items for each type to make the size more manageable. Set this to 0
+# for no limit. Note that the threshold may be exceeded by 50% before the limit
+# is enforced. So when you set the threshold to 10, up to 15 fields may appear,
+# but if the number exceeds 15, the total amount of fields shown is limited to
+# 10.
+# Minimum value: 0, maximum value: 100, default value: 10.
+# This tag requires that the tag UML_LOOK is set to YES.
+
+UML_LIMIT_NUM_FIELDS   = 10
+
+# If the DOT_UML_DETAILS tag is set to NO, doxygen will show attributes and
+# methods without types and arguments in the UML graphs. If the DOT_UML_DETAILS
+# tag is set to YES, doxygen will add type and arguments for attributes and
+# methods in the UML graphs. If the DOT_UML_DETAILS tag is set to NONE, doxygen
+# will not generate fields with class member information in the UML graphs. The
+# class diagrams will look similar to the default class diagrams but using UML
+# notation for the relationships.
+# Possible values are: NO, YES and NONE.
+# The default value is: NO.
+# This tag requires that the tag UML_LOOK is set to YES.
+
+DOT_UML_DETAILS        = NO
+
+# The DOT_WRAP_THRESHOLD tag can be used to set the maximum number of characters
+# to display on a single line. If the actual line length exceeds this threshold
+# significantly it will wrapped across multiple lines. Some heuristics are apply
+# to avoid ugly line breaks.
+# Minimum value: 0, maximum value: 1000, default value: 17.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_WRAP_THRESHOLD     = 17
+
+# If the TEMPLATE_RELATIONS tag is set to YES then the inheritance and
+# collaboration graphs will show the relations between templates and their
+# instances.
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
 
 TEMPLATE_RELATIONS     = NO
 
-# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDE_GRAPH, and HAVE_DOT
-# tags are set to YES then doxygen will generate a graph for each documented
-# file showing the direct and indirect include dependencies of the file with
-# other documented files.
+# If the INCLUDE_GRAPH, ENABLE_PREPROCESSING and SEARCH_INCLUDES tags are set to
+# YES then doxygen will generate a graph for each documented file showing the
+# direct and indirect include dependencies of the file with other documented
+# files.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
 
 INCLUDE_GRAPH          = YES
 
-# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDED_BY_GRAPH, and
-# HAVE_DOT tags are set to YES then doxygen will generate a graph for each
-# documented header file showing the documented files that directly or
-# indirectly include this file.
+# If the INCLUDED_BY_GRAPH, ENABLE_PREPROCESSING and SEARCH_INCLUDES tags are
+# set to YES then doxygen will generate a graph for each documented file showing
+# the direct and indirect include dependencies of the file with other documented
+# files.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
 
 INCLUDED_BY_GRAPH      = YES
 
-# If the CALL_GRAPH and HAVE_DOT options are set to YES then
-# doxygen will generate a call dependency graph for every global function
-# or class method. Note that enabling this option will significantly increase
-# the time of a run. So in most cases it will be better to enable call graphs
-# for selected functions only using the \callgraph command.
+# If the CALL_GRAPH tag is set to YES then doxygen will generate a call
+# dependency graph for every global function or class method.
+#
+# Note that enabling this option will significantly increase the time of a run.
+# So in most cases it will be better to enable call graphs for selected
+# functions only using the \callgraph command. Disabling a call graph can be
+# accomplished by means of the command \hidecallgraph.
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
 
 CALL_GRAPH             = NO
 
-# If the CALLER_GRAPH and HAVE_DOT tags are set to YES then
-# doxygen will generate a caller dependency graph for every global function
-# or class method. Note that enabling this option will significantly increase
-# the time of a run. So in most cases it will be better to enable caller
-# graphs for selected functions only using the \callergraph command.
+# If the CALLER_GRAPH tag is set to YES then doxygen will generate a caller
+# dependency graph for every global function or class method.
+#
+# Note that enabling this option will significantly increase the time of a run.
+# So in most cases it will be better to enable caller graphs for selected
+# functions only using the \callergraph command. Disabling a caller graph can be
+# accomplished by means of the command \hidecallergraph.
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
 
 CALLER_GRAPH           = NO
 
-# If the GRAPHICAL_HIERARCHY and HAVE_DOT tags are set to YES then doxygen
-# will graphical hierarchy of all classes instead of a textual one.
+# If the GRAPHICAL_HIERARCHY tag is set to YES then doxygen will graphical
+# hierarchy of all classes instead of a textual one.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
 
 GRAPHICAL_HIERARCHY    = YES
 
-# If the DIRECTORY_GRAPH, SHOW_DIRECTORIES and HAVE_DOT tags are set to YES
-# then doxygen will show the dependencies a directory has on other directories
-# in a graphical way. The dependency relations are determined by the #include
-# relations between the files in the directories.
+# If the DIRECTORY_GRAPH tag is set to YES then doxygen will show the
+# dependencies a directory has on other directories in a graphical way. The
+# dependency relations are determined by the #include relations between the
+# files in the directories.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
 
 DIRECTORY_GRAPH        = YES
 
+# The DIR_GRAPH_MAX_DEPTH tag can be used to limit the maximum number of levels
+# of child directories generated in directory dependency graphs by dot.
+# Minimum value: 1, maximum value: 25, default value: 1.
+# This tag requires that the tag DIRECTORY_GRAPH is set to YES.
+
+DIR_GRAPH_MAX_DEPTH    = 1
+
 # The DOT_IMAGE_FORMAT tag can be used to set the image format of the images
-# generated by dot. Possible values are png, jpg, or gif
-# If left blank png will be used.
+# generated by dot. For an explanation of the image formats see the section
+# output formats in the documentation of the dot tool (Graphviz (see:
+# http://www.graphviz.org/)).
+# Note: If you choose svg you need to set HTML_FILE_EXTENSION to xhtml in order
+# to make the SVG files visible in IE 9+ (other browsers do not have this
+# requirement).
+# Possible values are: png, jpg, gif, svg, png:gd, png:gd:gd, png:cairo,
+# png:cairo:gd, png:cairo:cairo, png:cairo:gdiplus, png:gdiplus and
+# png:gdiplus:gdiplus.
+# The default value is: png.
+# This tag requires that the tag HAVE_DOT is set to YES.
 
 DOT_IMAGE_FORMAT       = png
 
-# The tag DOT_PATH can be used to specify the path where the dot tool can be
+# If DOT_IMAGE_FORMAT is set to svg, then this option can be set to YES to
+# enable generation of interactive SVG images that allow zooming and panning.
+#
+# Note that this requires a modern browser other than Internet Explorer. Tested
+# and working are Firefox, Chrome, Safari, and Opera.
+# Note: For IE 9+ you need to set HTML_FILE_EXTENSION to xhtml in order to make
+# the SVG files visible. Older versions of IE do not have SVG support.
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+INTERACTIVE_SVG        = NO
+
+# The DOT_PATH tag can be used to specify the path where the dot tool can be
 # found. If left blank, it is assumed the dot tool can be found in the path.
+# This tag requires that the tag HAVE_DOT is set to YES.
 
 DOT_PATH               =
 
 # The DOTFILE_DIRS tag can be used to specify one or more directories that
-# contain dot files that are included in the documentation (see the
-# \dotfile command).
+# contain dot files that are included in the documentation (see the \dotfile
+# command).
+# This tag requires that the tag HAVE_DOT is set to YES.
 
 DOTFILE_DIRS           =
 
-# The DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of
-# nodes that will be shown in the graph. If the number of nodes in a graph
-# becomes larger than this value, doxygen will truncate the graph, which is
-# visualized by representing a node as a red box. Note that doxygen if the
-# number of direct children of the root node in a graph is already larger than
-# DOT_GRAPH_MAX_NODES then the graph will not be shown at all. Also note
-# that the size of a graph can be further restricted by MAX_DOT_GRAPH_DEPTH.
+# The MSCFILE_DIRS tag can be used to specify one or more directories that
+# contain msc files that are included in the documentation (see the \mscfile
+# command).
+
+MSCFILE_DIRS           =
+
+# The DIAFILE_DIRS tag can be used to specify one or more directories that
+# contain dia files that are included in the documentation (see the \diafile
+# command).
+
+DIAFILE_DIRS           =
+
+# When using plantuml, the PLANTUML_JAR_PATH tag should be used to specify the
+# path where java can find the plantuml.jar file or to the filename of jar file
+# to be used. If left blank, it is assumed PlantUML is not used or called during
+# a preprocessing step. Doxygen will generate a warning when it encounters a
+# \startuml command in this case and will not generate output for the diagram.
+
+PLANTUML_JAR_PATH      =
+
+# When using plantuml, the PLANTUML_CFG_FILE tag can be used to specify a
+# configuration file for plantuml.
+
+PLANTUML_CFG_FILE      =
+
+# When using plantuml, the specified paths are searched for files specified by
+# the !include statement in a plantuml block.
+
+PLANTUML_INCLUDE_PATH  =
+
+# The DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of nodes
+# that will be shown in the graph. If the number of nodes in a graph becomes
+# larger than this value, doxygen will truncate the graph, which is visualized
+# by representing a node as a red box. Note that doxygen if the number of direct
+# children of the root node in a graph is already larger than
+# DOT_GRAPH_MAX_NODES then the graph will not be shown at all. Also note that
+# the size of a graph can be further restricted by MAX_DOT_GRAPH_DEPTH.
+# Minimum value: 0, maximum value: 10000, default value: 50.
+# This tag requires that the tag HAVE_DOT is set to YES.
 
 DOT_GRAPH_MAX_NODES    = 50
 
-# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the
-# graphs generated by dot. A depth value of 3 means that only nodes reachable
-# from the root by following a path via at most 3 edges will be shown. Nodes
-# that lay further from the root node will be omitted. Note that setting this
-# option to 1 or 2 may greatly reduce the computation time needed for large
-# code bases. Also note that the size of a graph can be further restricted by
+# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the graphs
+# generated by dot. A depth value of 3 means that only nodes reachable from the
+# root by following a path via at most 3 edges will be shown. Nodes that lay
+# further from the root node will be omitted. Note that setting this option to 1
+# or 2 may greatly reduce the computation time needed for large code bases. Also
+# note that the size of a graph can be further restricted by
 # DOT_GRAPH_MAX_NODES. Using a depth of 0 means no depth restriction.
+# Minimum value: 0, maximum value: 1000, default value: 0.
+# This tag requires that the tag HAVE_DOT is set to YES.
 
 MAX_DOT_GRAPH_DEPTH    = 0
 
-# Set the DOT_TRANSPARENT tag to YES to generate images with a transparent
-# background. This is disabled by default, because dot on Windows does not
-# seem to support this out of the box. Warning: Depending on the platform used,
-# enabling this option may lead to badly anti-aliased labels on the edges of
-# a graph (i.e. they become hard to read).
-
-DOT_TRANSPARENT        = NO
-
-# Set the DOT_MULTI_TARGETS tag to YES allow dot to generate multiple output
+# Set the DOT_MULTI_TARGETS tag to YES to allow dot to generate multiple output
 # files in one run (i.e. multiple -o and -T options on the command line). This
-# makes dot run faster, but since only newer versions of dot (>1.8.10)
-# support this, this feature is disabled by default.
+# makes dot run faster, but since only newer versions of dot (>1.8.10) support
+# this, this feature is disabled by default.
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
 
 DOT_MULTI_TARGETS      = YES
 
-# If the GENERATE_LEGEND tag is set to YES (the default) Doxygen will
-# generate a legend page explaining the meaning of the various boxes and
-# arrows in the dot generated graphs.
+# If the GENERATE_LEGEND tag is set to YES doxygen will generate a legend page
+# explaining the meaning of the various boxes and arrows in the dot generated
+# graphs.
+# Note: This tag requires that UML_LOOK isn't set, i.e. the doxygen internal
+# graphical representation for inheritance and collaboration diagrams is used.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
 
 GENERATE_LEGEND        = YES
 
-# If the DOT_CLEANUP tag is set to YES (the default) Doxygen will
-# remove the intermediate dot files that are used to generate
-# the various graphs.
+# If the DOT_CLEANUP tag is set to YES, doxygen will remove the intermediate
+# files that are used to generate the various graphs.
+#
+# Note: This setting is not only used for dot files but also for msc temporary
+# files.
+# The default value is: YES.
 
 DOT_CLEANUP            = YES

+ 13 - 0
src/hidapi/doxygen/main_page.md

@@ -0,0 +1,13 @@
+# HIDAPI Doxygen output
+
+This site is dedicated to hosting an [API reference for the HIDAPI library](#API).
+
+For general information, see the [source repository](https://github.com/libusb/hidapi#readme).
+
+There are also build instructions hosted on github:
+
+- [Building from source](https://github.com/libusb/hidapi/blob/master/BUILD.md)
+- [Using CMake](https://github.com/libusb/hidapi/blob/master/BUILD.cmake.md)
+- [Using Autotools (deprecated)](https://github.com/libusb/hidapi/blob/master/BUILD.autotools.md)
+
+\example test.c contains a basic example usage of the HIDAPI library.

+ 261 - 58
src/hidapi/hidapi/hidapi.h

@@ -5,9 +5,9 @@
  Alan Ott
  Signal 11 Software
 
- 8/22/2009
+ libusb/hidapi Team
 
- Copyright 2009, All Rights Reserved.
+ Copyright 2023, All Rights Reserved.
 
  At the discretion of the user of this library,
  this software may be licensed under the terms of the
@@ -29,36 +29,123 @@
 
 #include <wchar.h>
 
-#ifdef SDL_hidapi_h_
-#define SDL_HIDAPI_IMPLEMENTATION
-#define hid_device_info SDL_hid_device_info
-#endif
-
-#if defined(_WIN32) && !defined(NAMESPACE) && !defined(SDL_HIDAPI_IMPLEMENTATION) /* SDL: don't export hidapi syms */
+/* #480: this is to be refactored properly for v1.0 */
+#ifdef _WIN32
+   #ifndef HID_API_NO_EXPORT_DEFINE
       #define HID_API_EXPORT __declspec(dllexport)
-      #define HID_API_CALL
-#else
-#ifndef HID_API_EXPORT
-      #define HID_API_EXPORT /**< API export macro */
-#endif
-#ifndef HID_API_CALL
-      #define HID_API_CALL /**< API call macro */
+   #endif
 #endif
+#ifndef HID_API_EXPORT
+   #define HID_API_EXPORT /**< API export macro */
 #endif
+/* To be removed in v1.0 */
+#define HID_API_CALL /**< API call macro */
 
 #define HID_API_EXPORT_CALL HID_API_EXPORT HID_API_CALL /**< API export and call macro*/
 
-#if defined(__cplusplus) && !defined(NAMESPACE)
+/** @brief Static/compile-time major version of the library.
+
+	@ingroup API
+*/
+#define HID_API_VERSION_MAJOR 0
+/** @brief Static/compile-time minor version of the library.
+
+	@ingroup API
+*/
+#define HID_API_VERSION_MINOR 14
+/** @brief Static/compile-time patch version of the library.
+
+	@ingroup API
+*/
+#define HID_API_VERSION_PATCH 0
+
+/* Helper macros */
+#define HID_API_AS_STR_IMPL(x) #x
+#define HID_API_AS_STR(x) HID_API_AS_STR_IMPL(x)
+#define HID_API_TO_VERSION_STR(v1, v2, v3) HID_API_AS_STR(v1.v2.v3)
+
+/** @brief Coverts a version as Major/Minor/Patch into a number:
+	<8 bit major><16 bit minor><8 bit patch>.
+
+	This macro was added in version 0.12.0.
+
+	Convenient function to be used for compile-time checks, like:
+	@code{.c}
+	#if HID_API_VERSION >= HID_API_MAKE_VERSION(0, 12, 0)
+	@endcode
+
+	@ingroup API
+*/
+#define HID_API_MAKE_VERSION(mj, mn, p) (((mj) << 24) | ((mn) << 8) | (p))
+
+/** @brief Static/compile-time version of the library.
+
+	This macro was added in version 0.12.0.
+
+	@see @ref HID_API_MAKE_VERSION.
+
+	@ingroup API
+*/
+#define HID_API_VERSION HID_API_MAKE_VERSION(HID_API_VERSION_MAJOR, HID_API_VERSION_MINOR, HID_API_VERSION_PATCH)
+
+/** @brief Static/compile-time string version of the library.
+
+	@ingroup API
+*/
+#define HID_API_VERSION_STR HID_API_TO_VERSION_STR(HID_API_VERSION_MAJOR, HID_API_VERSION_MINOR, HID_API_VERSION_PATCH)
+
+/** @brief Maximum expected HID Report descriptor size in bytes.
+
+	Since version 0.13.0, @ref HID_API_VERSION >= HID_API_MAKE_VERSION(0, 13, 0)
+
+	@ingroup API
+*/
+#define HID_API_MAX_REPORT_DESCRIPTOR_SIZE 4096
+
+#ifdef __cplusplus
 extern "C" {
 #endif
-#ifdef NAMESPACE
-namespace NAMESPACE {
-#endif
+		/** A structure to hold the version numbers. */
+		struct hid_api_version {
+			int major; /**< major version number */
+			int minor; /**< minor version number */
+			int patch; /**< patch version number */
+		};
 
 		struct hid_device_;
 		typedef struct hid_device_ hid_device; /**< opaque hidapi structure */
 
-#ifndef SDL_HIDAPI_IMPLEMENTATION
+		/** @brief HID underlying bus types.
+
+			@ingroup API
+		*/
+		typedef enum {
+			/** Unknown bus type */
+			HID_API_BUS_UNKNOWN = 0x00,
+
+			/** USB bus
+			   Specifications:
+			   https://usb.org/hid */
+			HID_API_BUS_USB = 0x01,
+
+			/** Bluetooth or Bluetooth LE bus
+			   Specifications:
+			   https://www.bluetooth.com/specifications/specs/human-interface-device-profile-1-1-1/
+			   https://www.bluetooth.com/specifications/specs/hid-service-1-0/
+			   https://www.bluetooth.com/specifications/specs/hid-over-gatt-profile-1-0/ */
+			HID_API_BUS_BLUETOOTH = 0x02,
+
+			/** I2C bus
+			   Specifications:
+			   https://docs.microsoft.com/previous-versions/windows/hardware/design/dn642101(v=vs.85) */
+			HID_API_BUS_I2C = 0x03,
+
+			/** SPI bus
+			   Specifications:
+			   https://www.microsoft.com/download/details.aspx?id=103325 */
+			HID_API_BUS_SPI = 0x04,
+		} hid_bus_type;
+
 		/** hidapi info structure */
 		struct hid_device_info {
 			/** Platform-specific device path */
@@ -77,29 +164,27 @@ namespace NAMESPACE {
 			/** Product string */
 			wchar_t *product_string;
 			/** Usage Page for this Device/Interface
-			    (Windows/Mac only). */
+			    (Windows/Mac/hidraw only) */
 			unsigned short usage_page;
 			/** Usage for this Device/Interface
-			    (Windows/Mac only).*/
+			    (Windows/Mac/hidraw only) */
 			unsigned short usage;
 			/** The USB interface which this logical device
 			    represents.
 
-				* Valid on both Linux implementations in all cases.
-				* Valid on the Windows implementation only if the device
-				  contains more than one interface. */
+			    Valid only if the device is a USB HID device.
+			    Set to -1 in all other cases.
+			*/
 			int interface_number;
 
-			/** Additional information about the USB interface.
-			    Valid on libusb and Android implementations. */
-			int interface_class;
-			int interface_subclass;
-			int interface_protocol;
-
 			/** Pointer to the next device */
 			struct hid_device_info *next;
+
+			/** Underlying bus type
+			    Since version 0.13.0, @ref HID_API_VERSION >= HID_API_MAKE_VERSION(0, 13, 0)
+			*/
+			hid_bus_type bus_type;
 		};
-#endif /* !SDL_HIDAPI_IMPLEMENTATION */
 
 
 		/** @brief Initialize the HIDAPI library.
@@ -115,6 +200,7 @@ namespace NAMESPACE {
 
 			@returns
 				This function returns 0 on success and -1 on error.
+				Call hid_error(NULL) to get the failure reason.
 		*/
 		int HID_API_EXPORT HID_API_CALL hid_init(void);
 
@@ -126,7 +212,7 @@ namespace NAMESPACE {
 
 			@ingroup API
 
-		    @returns
+			@returns
 				This function returns 0 on success and -1 on error.
 		*/
 		int HID_API_EXPORT HID_API_CALL hid_exit(void);
@@ -146,21 +232,25 @@ namespace NAMESPACE {
 			@param product_id The Product ID (PID) of the types of
 				device to open.
 
-		    @returns
-		    	This function returns a pointer to a linked list of type
-		    	struct #hid_device_info, containing information about the HID devices
-		    	attached to the system, or NULL in the case of failure. Free
-		    	this linked list by calling hid_free_enumeration().
+			@returns
+				This function returns a pointer to a linked list of type
+				struct #hid_device_info, containing information about the HID devices
+				attached to the system,
+				or NULL in the case of failure or if no HID devices present in the system.
+				Call hid_error(NULL) to get the failure reason.
+
+			@note The returned value by this function must to be freed by calling hid_free_enumeration(),
+			      when not needed anymore.
 		*/
 		struct hid_device_info HID_API_EXPORT * HID_API_CALL hid_enumerate(unsigned short vendor_id, unsigned short product_id);
 
 		/** @brief Free an enumeration Linked List
 
-		    This function frees a linked list created by hid_enumerate().
+			This function frees a linked list created by hid_enumerate().
 
 			@ingroup API
-		    @param devs Pointer to a list of struct_device returned from
-		    	      hid_enumerate().
+			@param devs Pointer to a list of struct_device returned from
+			            hid_enumerate().
 		*/
 		void  HID_API_EXPORT HID_API_CALL hid_free_enumeration(struct hid_device_info *devs);
 
@@ -174,11 +264,15 @@ namespace NAMESPACE {
 			@param vendor_id The Vendor ID (VID) of the device to open.
 			@param product_id The Product ID (PID) of the device to open.
 			@param serial_number The Serial Number of the device to open
-				               (Optionally NULL).
+			                     (Optionally NULL).
 
 			@returns
 				This function returns a pointer to a #hid_device object on
 				success or NULL on failure.
+				Call hid_error(NULL) to get the failure reason.
+
+			@note The returned object must be freed by calling hid_close(),
+			      when not needed anymore.
 		*/
 		HID_API_EXPORT hid_device * HID_API_CALL hid_open(unsigned short vendor_id, unsigned short product_id, const wchar_t *serial_number);
 
@@ -189,13 +283,17 @@ namespace NAMESPACE {
 			Linux).
 
 			@ingroup API
-		    @param path The path name of the device to open
+			@param path The path name of the device to open
 
 			@returns
 				This function returns a pointer to a #hid_device object on
 				success or NULL on failure.
+				Call hid_error(NULL) to get the failure reason.
+
+			@note The returned object must be freed by calling hid_close(),
+			      when not needed anymore.
 		*/
-		HID_API_EXPORT hid_device * HID_API_CALL hid_open_path(const char *path, int bExclusive /* = false */);
+		HID_API_EXPORT hid_device * HID_API_CALL hid_open_path(const char *path);
 
 		/** @brief Write an Output report to a HID device.
 
@@ -222,6 +320,7 @@ namespace NAMESPACE {
 			@returns
 				This function returns the actual number of bytes written and
 				-1 on error.
+				Call hid_error(dev) to get the failure reason.
 		*/
 		int  HID_API_EXPORT HID_API_CALL hid_write(hid_device *dev, const unsigned char *data, size_t length);
 
@@ -241,7 +340,9 @@ namespace NAMESPACE {
 
 			@returns
 				This function returns the actual number of bytes read and
-				-1 on error. If no packet was available to be read within
+				-1 on error.
+				Call hid_error(dev) to get the failure reason.
+				If no packet was available to be read within
 				the timeout period, this function returns 0.
 		*/
 		int HID_API_EXPORT HID_API_CALL hid_read_timeout(hid_device *dev, unsigned char *data, size_t length, int milliseconds);
@@ -249,7 +350,7 @@ namespace NAMESPACE {
 		/** @brief Read an Input report from a HID device.
 
 			Input reports are returned
-		    to the host through the INTERRUPT IN endpoint. The first byte will
+			to the host through the INTERRUPT IN endpoint. The first byte will
 			contain the Report number if the device uses numbered reports.
 
 			@ingroup API
@@ -261,7 +362,9 @@ namespace NAMESPACE {
 
 			@returns
 				This function returns the actual number of bytes read and
-				-1 on error. If no packet was available to be read and
+				-1 on error.
+				Call hid_error(dev) to get the failure reason.
+				If no packet was available to be read and
 				the handle is in non-blocking mode, this function returns 0.
 		*/
 		int  HID_API_EXPORT HID_API_CALL hid_read(hid_device *dev, unsigned char *data, size_t length);
@@ -283,6 +386,7 @@ namespace NAMESPACE {
 
 			@returns
 				This function returns 0 on success and -1 on error.
+				Call hid_error(dev) to get the failure reason.
 		*/
 		int  HID_API_EXPORT HID_API_CALL hid_set_nonblocking(hid_device *dev, int nonblock);
 
@@ -311,6 +415,7 @@ namespace NAMESPACE {
 			@returns
 				This function returns the actual number of bytes written and
 				-1 on error.
+				Call hid_error(dev) to get the failure reason.
 		*/
 		int HID_API_EXPORT HID_API_CALL hid_send_feature_report(hid_device *dev, const unsigned char *data, size_t length);
 
@@ -336,9 +441,38 @@ namespace NAMESPACE {
 				This function returns the number of bytes read plus
 				one for the report ID (which is still in the first
 				byte), or -1 on error.
+				Call hid_error(dev) to get the failure reason.
 		*/
 		int HID_API_EXPORT HID_API_CALL hid_get_feature_report(hid_device *dev, unsigned char *data, size_t length);
 
+		/** @brief Get a input report from a HID device.
+
+			Since version 0.10.0, @ref HID_API_VERSION >= HID_API_MAKE_VERSION(0, 10, 0)
+
+			Set the first byte of @p data[] to the Report ID of the
+			report to be read. Make sure to allow space for this
+			extra byte in @p data[]. Upon return, the first byte will
+			still contain the Report ID, and the report data will
+			start in data[1].
+
+			@ingroup API
+			@param dev A device handle returned from hid_open().
+			@param data A buffer to put the read data into, including
+				the Report ID. Set the first byte of @p data[] to the
+				Report ID of the report to be read, or set it to zero
+				if your device does not use numbered reports.
+			@param length The number of bytes to read, including an
+				extra byte for the report ID. The buffer can be longer
+				than the actual report.
+
+			@returns
+				This function returns the number of bytes read plus
+				one for the report ID (which is still in the first
+				byte), or -1 on error.
+				Call hid_error(dev) to get the failure reason.
+		*/
+		int HID_API_EXPORT HID_API_CALL hid_get_input_report(hid_device *dev, unsigned char *data, size_t length);
+
 		/** @brief Close a HID device.
 
 			@ingroup API
@@ -355,6 +489,7 @@ namespace NAMESPACE {
 
 			@returns
 				This function returns 0 on success and -1 on error.
+				Call hid_error(dev) to get the failure reason.
 		*/
 		int HID_API_EXPORT_CALL hid_get_manufacturer_string(hid_device *dev, wchar_t *string, size_t maxlen);
 
@@ -367,6 +502,7 @@ namespace NAMESPACE {
 
 			@returns
 				This function returns 0 on success and -1 on error.
+				Call hid_error(dev) to get the failure reason.
 		*/
 		int HID_API_EXPORT_CALL hid_get_product_string(hid_device *dev, wchar_t *string, size_t maxlen);
 
@@ -379,9 +515,27 @@ namespace NAMESPACE {
 
 			@returns
 				This function returns 0 on success and -1 on error.
+				Call hid_error(dev) to get the failure reason.
 		*/
 		int HID_API_EXPORT_CALL hid_get_serial_number_string(hid_device *dev, wchar_t *string, size_t maxlen);
 
+		/** @brief Get The struct #hid_device_info from a HID device.
+
+			Since version 0.13.0, @ref HID_API_VERSION >= HID_API_MAKE_VERSION(0, 13, 0)
+
+			@ingroup API
+			@param dev A device handle returned from hid_open().
+
+			@returns
+				This function returns a pointer to the struct #hid_device_info
+				for this hid_device, or NULL in the case of failure.
+				Call hid_error(dev) to get the failure reason.
+				This struct is valid until the device is closed with hid_close().
+
+			@note The returned object is owned by the @p dev, and SHOULD NOT be freed by the user.
+		*/
+		struct hid_device_info HID_API_EXPORT * HID_API_CALL hid_get_device_info(hid_device *dev);
+
 		/** @brief Get a string from a HID device, based on its string index.
 
 			@ingroup API
@@ -392,30 +546,79 @@ namespace NAMESPACE {
 
 			@returns
 				This function returns 0 on success and -1 on error.
+				Call hid_error(dev) to get the failure reason.
 		*/
 		int HID_API_EXPORT_CALL hid_get_indexed_string(hid_device *dev, int string_index, wchar_t *string, size_t maxlen);
 
-		/** @brief Get a string describing the last error which occurred.
+		/** @brief Get a report descriptor from a HID device.
+
+			Since version 0.14.0, @ref HID_API_VERSION >= HID_API_MAKE_VERSION(0, 14, 0)
+
+			User has to provide a preallocated buffer where descriptor will be copied to.
+			The recommended size for preallocated buffer is @ref HID_API_MAX_REPORT_DESCRIPTOR_SIZE bytes.
 
 			@ingroup API
 			@param dev A device handle returned from hid_open().
+			@param buf The buffer to copy descriptor into.
+			@param buf_size The size of the buffer in bytes.
 
 			@returns
-				This function returns a string containing the last error
-				which occurred or NULL if none has occurred.
+				This function returns non-negative number of bytes actually copied, or -1 on error.
+		*/
+		int HID_API_EXPORT_CALL hid_get_report_descriptor(hid_device *dev, unsigned char *buf, size_t buf_size);
+
+		/** @brief Get a string describing the last error which occurred.
+
+			This function is intended for logging/debugging purposes.
+
+			This function guarantees to never return NULL.
+			If there was no error in the last function call -
+			the returned string clearly indicates that.
+
+			Any HIDAPI function that can explicitly indicate an execution failure
+			(e.g. by an error code, or by returning NULL) - may set the error string,
+			to be returned by this function.
+
+			Strings returned from hid_error() must not be freed by the user,
+			i.e. owned by HIDAPI library.
+			Device-specific error string may remain allocated at most until hid_close() is called.
+			Global error string may remain allocated at most until hid_exit() is called.
+
+			@ingroup API
+			@param dev A device handle returned from hid_open(),
+			  or NULL to get the last non-device-specific error
+			  (e.g. for errors in hid_open() or hid_enumerate()).
+
+			@returns
+				A string describing the last error (if any).
 		*/
 		HID_API_EXPORT const wchar_t* HID_API_CALL hid_error(hid_device *dev);
 
-#if defined(__IOS__) || defined(__TVOS__)
-		HID_API_EXPORT void HID_API_CALL hid_ble_scan(int active);
-#endif
+		/** @brief Get a runtime version of the library.
 
-#if defined(__cplusplus) && !defined(NAMESPACE)
-}
-#endif
-#ifdef NAMESPACE
+			This function is thread-safe.
+
+			@ingroup API
+
+			@returns
+				Pointer to statically allocated struct, that contains version.
+		*/
+		HID_API_EXPORT const  struct hid_api_version* HID_API_CALL hid_version(void);
+
+
+		/** @brief Get a runtime version string of the library.
+
+			This function is thread-safe.
+
+			@ingroup API
+
+			@returns
+				Pointer to statically allocated string, that contains version string.
+		*/
+		HID_API_EXPORT const char* HID_API_CALL hid_version_str(void);
+
+#ifdef __cplusplus
 }
 #endif
 
 #endif
-

+ 17 - 0
src/hidapi/hidtest/.gitignore

@@ -0,0 +1,17 @@
+Debug
+Release
+*.exp
+*.ilk
+*.lib
+*.suo
+*.vcproj.*
+*.ncb
+*.suo
+*.dll
+*.pdb
+*.o
+.deps/
+.libs/
+hidtest-hidraw
+hidtest-libusb
+hidtest

+ 40 - 0
src/hidapi/hidtest/CMakeLists.txt

@@ -0,0 +1,40 @@
+cmake_minimum_required(VERSION 3.1.3 FATAL_ERROR)
+project(hidtest C)
+
+if(CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR)
+    # hidtest is build as a standalone project
+
+    if(POLICY CMP0074)
+        # allow using hidapi_ROOT if CMake supports it
+        cmake_policy(SET CMP0074 NEW)
+    endif()
+
+    find_package(hidapi 0.12 REQUIRED)
+    message(STATUS "Using HIDAPI: ${hidapi_VERSION}")
+else()
+    # hidtest is built as part of the main HIDAPI build
+    message(STATUS "Building hidtest")
+endif()
+
+set(HIDAPI_HIDTEST_TARGETS)
+if(NOT WIN32 AND NOT APPLE AND CMAKE_SYSTEM_NAME MATCHES "Linux")
+    if(TARGET hidapi::hidraw)
+        add_executable(hidtest_hidraw test.c)
+        target_link_libraries(hidtest_hidraw hidapi::hidraw)
+        list(APPEND HIDAPI_HIDTEST_TARGETS hidtest_hidraw)
+    endif()
+    if(TARGET hidapi::libusb)
+        add_executable(hidtest_libusb test.c)
+        target_compile_definitions(hidtest_libusb PRIVATE USING_HIDAPI_LIBUSB)
+        target_link_libraries(hidtest_libusb hidapi::libusb)
+        list(APPEND HIDAPI_HIDTEST_TARGETS hidtest_libusb)
+    endif()
+else()
+    add_executable(hidtest test.c)
+    target_link_libraries(hidtest hidapi::hidapi)
+    list(APPEND HIDAPI_HIDTEST_TARGETS hidtest)
+endif()
+
+install(TARGETS ${HIDAPI_HIDTEST_TARGETS}
+    RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}"
+)

+ 11 - 3
src/hidapi/hidtest/Makefile.am

@@ -4,17 +4,25 @@ AM_CPPFLAGS = -I$(top_srcdir)/hidapi/
 if OS_LINUX
 noinst_PROGRAMS = hidtest-libusb hidtest-hidraw
 
-hidtest_hidraw_SOURCES = hidtest.cpp
+hidtest_hidraw_SOURCES = test.c
 hidtest_hidraw_LDADD = $(top_builddir)/linux/libhidapi-hidraw.la
 
-hidtest_libusb_SOURCES = hidtest.cpp
+hidtest_libusb_SOURCES = test.c
 hidtest_libusb_LDADD = $(top_builddir)/libusb/libhidapi-libusb.la
 else
 
 # Other OS's
 noinst_PROGRAMS = hidtest
 
-hidtest_SOURCES = hidtest.cpp
+hidtest_SOURCES = test.c
 hidtest_LDADD = $(top_builddir)/$(backend)/libhidapi.la
 
 endif
+
+if OS_DARWIN
+AM_CPPFLAGS += -I$(top_srcdir)/mac/
+endif
+
+if OS_WINDOWS
+AM_CPPFLAGS += -I$(top_srcdir)/windows/
+endif

+ 0 - 194
src/hidapi/hidtest/hidtest.cpp

@@ -1,194 +0,0 @@
-/*******************************************************
- Windows HID simplification
-
- Alan Ott
- Signal 11 Software
-
- 8/22/2009
-
- Copyright 2009
- 
- This contents of this file may be used by anyone
- for any reason without any conditions and may be
- used as a starting point for your own applications
- which use HIDAPI.
-********************************************************/
-
-#include <stdio.h>
-#include <wchar.h>
-#include <string.h>
-#include <stdlib.h>
-#include "hidapi.h"
-
-// Headers needed for sleeping.
-#ifdef _WIN32
-	#include <windows.h>
-#else
-	#include <unistd.h>
-#endif
-
-int main(int argc, char* argv[])
-{
-	int res;
-	unsigned char buf[256];
-	#define MAX_STR 255
-	wchar_t wstr[MAX_STR];
-	hid_device *handle;
-	int i;
-
-#ifdef WIN32
-	UNREFERENCED_PARAMETER(argc);
-	UNREFERENCED_PARAMETER(argv);
-#endif
-
-	struct hid_device_info *devs, *cur_dev;
-	
-	if (hid_init())
-		return -1;
-
-	devs = hid_enumerate(0x0, 0x0);
-	cur_dev = devs;	
-	while (cur_dev) {
-		printf("Device Found\n  type: %04hx %04hx\n  path: %s\n  serial_number: %ls", cur_dev->vendor_id, cur_dev->product_id, cur_dev->path, cur_dev->serial_number);
-		printf("\n");
-		printf("  Manufacturer: %ls\n", cur_dev->manufacturer_string);
-		printf("  Product:      %ls\n", cur_dev->product_string);
-		printf("  Release:      %hx\n", cur_dev->release_number);
-		printf("  Interface:    %d\n",  cur_dev->interface_number);
-		printf("\n");
-		cur_dev = cur_dev->next;
-	}
-	hid_free_enumeration(devs);
-
-	// Set up the command buffer.
-	memset(buf,0x00,sizeof(buf));
-	buf[0] = 0x01;
-	buf[1] = 0x81;
-	
-
-	// Open the device using the VID, PID,
-	// and optionally the Serial number.
-	////handle = hid_open(0x4d8, 0x3f, L"12345");
-	handle = hid_open(0x4d8, 0x3f, NULL);
-	if (!handle) {
-		printf("unable to open device\n");
- 		return 1;
-	}
-
-	// Read the Manufacturer String
-	wstr[0] = 0x0000;
-	res = hid_get_manufacturer_string(handle, wstr, MAX_STR);
-	if (res < 0)
-		printf("Unable to read manufacturer string\n");
-	printf("Manufacturer String: %ls\n", wstr);
-
-	// Read the Product String
-	wstr[0] = 0x0000;
-	res = hid_get_product_string(handle, wstr, MAX_STR);
-	if (res < 0)
-		printf("Unable to read product string\n");
-	printf("Product String: %ls\n", wstr);
-
-	// Read the Serial Number String
-	wstr[0] = 0x0000;
-	res = hid_get_serial_number_string(handle, wstr, MAX_STR);
-	if (res < 0)
-		printf("Unable to read serial number string\n");
-	printf("Serial Number String: (%d) %ls", wstr[0], wstr);
-	printf("\n");
-
-	// Read Indexed String 1
-	wstr[0] = 0x0000;
-	res = hid_get_indexed_string(handle, 1, wstr, MAX_STR);
-	if (res < 0)
-		printf("Unable to read indexed string 1\n");
-	printf("Indexed String 1: %ls\n", wstr);
-
-	// Set the hid_read() function to be non-blocking.
-	hid_set_nonblocking(handle, 1);
-	
-	// Try to read from the device. There shoud be no
-	// data here, but execution should not block.
-	res = hid_read(handle, buf, 17);
-
-	// Send a Feature Report to the device
-	buf[0] = 0x2;
-	buf[1] = 0xa0;
-	buf[2] = 0x0a;
-	buf[3] = 0x00;
-	buf[4] = 0x00;
-	res = hid_send_feature_report(handle, buf, 17);
-	if (res < 0) {
-		printf("Unable to send a feature report.\n");
-	}
-
-	memset(buf,0,sizeof(buf));
-
-	// Read a Feature Report from the device
-	buf[0] = 0x2;
-	res = hid_get_feature_report(handle, buf, sizeof(buf));
-	if (res < 0) {
-		printf("Unable to get a feature report.\n");
-		printf("%ls", hid_error(handle));
-	}
-	else {
-		// Print out the returned buffer.
-		printf("Feature Report\n   ");
-		for (i = 0; i < res; i++)
-			printf("%02hhx ", buf[i]);
-		printf("\n");
-	}
-
-	memset(buf,0,sizeof(buf));
-
-	// Toggle LED (cmd 0x80). The first byte is the report number (0x1).
-	buf[0] = 0x1;
-	buf[1] = 0x80;
-	res = hid_write(handle, buf, 17);
-	if (res < 0) {
-		printf("Unable to write()\n");
-		printf("Error: %ls\n", hid_error(handle));
-	}
-	
-
-	// Request state (cmd 0x81). The first byte is the report number (0x1).
-	buf[0] = 0x1;
-	buf[1] = 0x81;
-	hid_write(handle, buf, 17);
-	if (res < 0)
-		printf("Unable to write() (2)\n");
-
-	// Read requested state. hid_read() has been set to be
-	// non-blocking by the call to hid_set_nonblocking() above.
-	// This loop demonstrates the non-blocking nature of hid_read().
-	res = 0;
-	while (res == 0) {
-		res = hid_read(handle, buf, sizeof(buf));
-		if (res == 0)
-			printf("waiting...\n");
-		if (res < 0)
-			printf("Unable to read()\n");
-		#ifdef WIN32
-		Sleep(500);
-		#else
-		usleep(500*1000);
-		#endif
-	}
-
-	printf("Data read:\n   ");
-	// Print out the returned buffer.
-	for (i = 0; i < res; i++)
-		printf("%02hhx ", buf[i]);
-	printf("\n");
-
-	hid_close(handle);
-
-	/* Free static HIDAPI objects. */
-	hid_exit();
-
-#ifdef WIN32
-	system("pause");
-#endif
-
-	return 0;
-}

+ 316 - 0
src/hidapi/hidtest/test.c

@@ -0,0 +1,316 @@
+/*******************************************************
+ HIDAPI - Multi-Platform library for
+ communication with HID devices.
+
+ Alan Ott
+ Signal 11 Software
+
+ libusb/hidapi Team
+
+ Copyright 2022.
+
+ This contents of this file may be used by anyone
+ for any reason without any conditions and may be
+ used as a starting point for your own applications
+ which use HIDAPI.
+********************************************************/
+
+#include <stdio.h>
+#include <wchar.h>
+#include <string.h>
+#include <stdlib.h>
+
+#include <hidapi.h>
+
+// Headers needed for sleeping.
+#ifdef _WIN32
+	#include <windows.h>
+#else
+	#include <unistd.h>
+#endif
+
+// Fallback/example
+#ifndef HID_API_MAKE_VERSION
+#define HID_API_MAKE_VERSION(mj, mn, p) (((mj) << 24) | ((mn) << 8) | (p))
+#endif
+#ifndef HID_API_VERSION
+#define HID_API_VERSION HID_API_MAKE_VERSION(HID_API_VERSION_MAJOR, HID_API_VERSION_MINOR, HID_API_VERSION_PATCH)
+#endif
+
+//
+// Sample using platform-specific headers
+#if defined(__APPLE__) && HID_API_VERSION >= HID_API_MAKE_VERSION(0, 12, 0)
+#include <hidapi_darwin.h>
+#endif
+
+#if defined(_WIN32) && HID_API_VERSION >= HID_API_MAKE_VERSION(0, 12, 0)
+#include <hidapi_winapi.h>
+#endif
+
+#if defined(USING_HIDAPI_LIBUSB) && HID_API_VERSION >= HID_API_MAKE_VERSION(0, 12, 0)
+#include <hidapi_libusb.h>
+#endif
+//
+
+const char *hid_bus_name(hid_bus_type bus_type) {
+	static const char *const HidBusTypeName[] = {
+		"Unknown",
+		"USB",
+		"Bluetooth",
+		"I2C",
+		"SPI",
+	};
+
+	if ((int)bus_type < 0)
+		bus_type = HID_API_BUS_UNKNOWN;
+	if ((int)bus_type >= (int)(sizeof(HidBusTypeName) / sizeof(HidBusTypeName[0])))
+		bus_type = HID_API_BUS_UNKNOWN;
+
+	return HidBusTypeName[bus_type];
+}
+
+void print_device(struct hid_device_info *cur_dev) {
+	printf("Device Found\n  type: %04hx %04hx\n  path: %s\n  serial_number: %ls", cur_dev->vendor_id, cur_dev->product_id, cur_dev->path, cur_dev->serial_number);
+	printf("\n");
+	printf("  Manufacturer: %ls\n", cur_dev->manufacturer_string);
+	printf("  Product:      %ls\n", cur_dev->product_string);
+	printf("  Release:      %hx\n", cur_dev->release_number);
+	printf("  Interface:    %d\n",  cur_dev->interface_number);
+	printf("  Usage (page): 0x%hx (0x%hx)\n", cur_dev->usage, cur_dev->usage_page);
+	printf("  Bus type: %d (%s)\n", cur_dev->bus_type, hid_bus_name(cur_dev->bus_type));
+	printf("\n");
+}
+
+void print_hid_report_descriptor_from_device(hid_device *device) {
+	unsigned char descriptor[HID_API_MAX_REPORT_DESCRIPTOR_SIZE];
+	int res = 0;
+
+	printf("  Report Descriptor: ");
+	res = hid_get_report_descriptor(device, descriptor, sizeof(descriptor));
+	if (res < 0) {
+		printf("error getting: %ls", hid_error(device));
+	}
+	else {
+		printf("(%d bytes)", res);
+	}
+	for (int i = 0; i < res; i++) {
+		if (i % 10 == 0) {
+			printf("\n");
+		}
+		printf("0x%02x, ", descriptor[i]);
+	}
+	printf("\n");
+}
+
+void print_hid_report_descriptor_from_path(const char *path) {
+	hid_device *device = hid_open_path(path);
+	if (device) {
+		print_hid_report_descriptor_from_device(device);
+		hid_close(device);
+	}
+	else {
+		printf("  Report Descriptor: Unable to open device by path\n");
+	}
+}
+
+void print_devices(struct hid_device_info *cur_dev) {
+	for (; cur_dev; cur_dev = cur_dev->next) {
+		print_device(cur_dev);
+	}
+}
+
+void print_devices_with_descriptor(struct hid_device_info *cur_dev) {
+	for (; cur_dev; cur_dev = cur_dev->next) {
+		print_device(cur_dev);
+		print_hid_report_descriptor_from_path(cur_dev->path);
+	}
+}
+
+int main(int argc, char* argv[])
+{
+	(void)argc;
+	(void)argv;
+
+	int res;
+	unsigned char buf[256];
+	#define MAX_STR 255
+	wchar_t wstr[MAX_STR];
+	hid_device *handle;
+	int i;
+
+	struct hid_device_info *devs;
+
+	printf("hidapi test/example tool. Compiled with hidapi version %s, runtime version %s.\n", HID_API_VERSION_STR, hid_version_str());
+	if (HID_API_VERSION == HID_API_MAKE_VERSION(hid_version()->major, hid_version()->minor, hid_version()->patch)) {
+		printf("Compile-time version matches runtime version of hidapi.\n\n");
+	}
+	else {
+		printf("Compile-time version is different than runtime version of hidapi.\n]n");
+	}
+
+	if (hid_init())
+		return -1;
+
+#if defined(__APPLE__) && HID_API_VERSION >= HID_API_MAKE_VERSION(0, 12, 0)
+	// To work properly needs to be called before hid_open/hid_open_path after hid_init.
+	// Best/recommended option - call it right after hid_init.
+	hid_darwin_set_open_exclusive(0);
+#endif
+
+	devs = hid_enumerate(0x0, 0x0);
+	print_devices_with_descriptor(devs);
+	hid_free_enumeration(devs);
+
+	// Set up the command buffer.
+	memset(buf,0x00,sizeof(buf));
+	buf[0] = 0x01;
+	buf[1] = 0x81;
+
+
+	// Open the device using the VID, PID,
+	// and optionally the Serial number.
+	////handle = hid_open(0x4d8, 0x3f, L"12345");
+	handle = hid_open(0x4d8, 0x3f, NULL);
+	if (!handle) {
+		printf("unable to open device\n");
+		hid_exit();
+ 		return 1;
+	}
+
+	// Read the Manufacturer String
+	wstr[0] = 0x0000;
+	res = hid_get_manufacturer_string(handle, wstr, MAX_STR);
+	if (res < 0)
+		printf("Unable to read manufacturer string\n");
+	printf("Manufacturer String: %ls\n", wstr);
+
+	// Read the Product String
+	wstr[0] = 0x0000;
+	res = hid_get_product_string(handle, wstr, MAX_STR);
+	if (res < 0)
+		printf("Unable to read product string\n");
+	printf("Product String: %ls\n", wstr);
+
+	// Read the Serial Number String
+	wstr[0] = 0x0000;
+	res = hid_get_serial_number_string(handle, wstr, MAX_STR);
+	if (res < 0)
+		printf("Unable to read serial number string\n");
+	printf("Serial Number String: (%d) %ls\n", wstr[0], wstr);
+
+	print_hid_report_descriptor_from_device(handle);
+
+	struct hid_device_info* info = hid_get_device_info(handle);
+	if (info == NULL) {
+		printf("Unable to get device info\n");
+	} else {
+		print_devices(info);
+	}
+
+	// Read Indexed String 1
+	wstr[0] = 0x0000;
+	res = hid_get_indexed_string(handle, 1, wstr, MAX_STR);
+	if (res < 0)
+		printf("Unable to read indexed string 1\n");
+	printf("Indexed String 1: %ls\n", wstr);
+
+	// Set the hid_read() function to be non-blocking.
+	hid_set_nonblocking(handle, 1);
+
+	// Try to read from the device. There should be no
+	// data here, but execution should not block.
+	res = hid_read(handle, buf, 17);
+
+	// Send a Feature Report to the device
+	buf[0] = 0x2;
+	buf[1] = 0xa0;
+	buf[2] = 0x0a;
+	buf[3] = 0x00;
+	buf[4] = 0x00;
+	res = hid_send_feature_report(handle, buf, 17);
+	if (res < 0) {
+		printf("Unable to send a feature report.\n");
+	}
+
+	memset(buf,0,sizeof(buf));
+
+	// Read a Feature Report from the device
+	buf[0] = 0x2;
+	res = hid_get_feature_report(handle, buf, sizeof(buf));
+	if (res < 0) {
+		printf("Unable to get a feature report: %ls\n", hid_error(handle));
+	}
+	else {
+		// Print out the returned buffer.
+		printf("Feature Report\n   ");
+		for (i = 0; i < res; i++)
+			printf("%02x ", (unsigned int) buf[i]);
+		printf("\n");
+	}
+
+	memset(buf,0,sizeof(buf));
+
+	// Toggle LED (cmd 0x80). The first byte is the report number (0x1).
+	buf[0] = 0x1;
+	buf[1] = 0x80;
+	res = hid_write(handle, buf, 17);
+	if (res < 0) {
+		printf("Unable to write(): %ls\n", hid_error(handle));
+	}
+
+
+	// Request state (cmd 0x81). The first byte is the report number (0x1).
+	buf[0] = 0x1;
+	buf[1] = 0x81;
+	hid_write(handle, buf, 17);
+	if (res < 0) {
+		printf("Unable to write()/2: %ls\n", hid_error(handle));
+	}
+
+	// Read requested state. hid_read() has been set to be
+	// non-blocking by the call to hid_set_nonblocking() above.
+	// This loop demonstrates the non-blocking nature of hid_read().
+	res = 0;
+	i = 0;
+	while (res == 0) {
+		res = hid_read(handle, buf, sizeof(buf));
+		if (res == 0) {
+			printf("waiting...\n");
+		}
+		if (res < 0) {
+			printf("Unable to read(): %ls\n", hid_error(handle));
+			break;
+		}
+
+		i++;
+		if (i >= 10) { /* 10 tries by 500 ms - 5 seconds of waiting*/
+			printf("read() timeout\n");
+			break;
+		}
+
+#ifdef _WIN32
+		Sleep(500);
+#else
+		usleep(500*1000);
+#endif
+	}
+
+	if (res > 0) {
+		printf("Data read:\n   ");
+		// Print out the returned buffer.
+		for (i = 0; i < res; i++)
+			printf("%02x ", (unsigned int) buf[i]);
+		printf("\n");
+	}
+
+	hid_close(handle);
+
+	/* Free static HIDAPI objects. */
+	hid_exit();
+
+#ifdef _WIN32
+	system("pause");
+#endif
+
+	return 0;
+}

+ 0 - 32
src/hidapi/ios/Makefile-manual

@@ -1,32 +0,0 @@
-###########################################
-# Simple Makefile for HIDAPI test program
-#
-# Alan Ott
-# Signal 11 Software
-# 2010-07-03
-###########################################
-
-all: hidtest
-
-CC=gcc
-CXX=g++
-COBJS=hid.o
-CPPOBJS=../hidtest/hidtest.o
-OBJS=$(COBJS) $(CPPOBJS)
-CFLAGS+=-I../hidapi -Wall -g -c 
-LIBS=-framework CoreBluetooth -framework CoreFoundation
-
-
-hidtest: $(OBJS)
-	g++ -Wall -g $^ $(LIBS) -o hidtest
-
-$(COBJS): %.o: %.c
-	$(CC) $(CFLAGS) $< -o $@
-
-$(CPPOBJS): %.o: %.cpp
-	$(CXX) $(CFLAGS) $< -o $@
-
-clean:
-	rm -f *.o hidtest $(CPPOBJS)
-
-.PHONY: clean

+ 0 - 9
src/hidapi/ios/Makefile.am

@@ -1,9 +0,0 @@
-lib_LTLIBRARIES = libhidapi.la
-libhidapi_la_SOURCES = hid.m
-libhidapi_la_LDFLAGS = $(LTLDFLAGS)
-AM_CPPFLAGS = -I$(top_srcdir)/hidapi/
-
-hdrdir = $(includedir)/hidapi
-hdr_HEADERS = $(top_srcdir)/hidapi/hidapi.h
-
-EXTRA_DIST = Makefile-manual

+ 0 - 996
src/hidapi/ios/hid.m

@@ -1,996 +0,0 @@
-/*
-  Simple DirectMedia Layer
-  Copyright (C) 2021 Valve Corporation
-
-  This software is provided 'as-is', without any express or implied
-  warranty.  In no event will the authors be held liable for any damages
-  arising from the use of this software.
-
-  Permission is granted to anyone to use this software for any purpose,
-  including commercial applications, and to alter it and redistribute it
-  freely, subject to the following restrictions:
-
-  1. The origin of this software must not be misrepresented; you must not
-     claim that you wrote the original software. If you use this software
-     in a product, an acknowledgment in the product documentation would be
-     appreciated but is not required.
-  2. Altered source versions must be plainly marked as such, and must not be
-     misrepresented as being the original software.
-  3. This notice may not be removed or altered from any source distribution.
-*/
-#include "SDL_internal.h"
-
-#ifndef SDL_HIDAPI_DISABLED
-
-
-#define hid_init                        PLATFORM_hid_init
-#define hid_exit                        PLATFORM_hid_exit
-#define hid_enumerate                   PLATFORM_hid_enumerate
-#define hid_free_enumeration            PLATFORM_hid_free_enumeration
-#define hid_open                        PLATFORM_hid_open
-#define hid_open_path                   PLATFORM_hid_open_path
-#define hid_write                       PLATFORM_hid_write
-#define hid_read_timeout                PLATFORM_hid_read_timeout
-#define hid_read                        PLATFORM_hid_read
-#define hid_set_nonblocking             PLATFORM_hid_set_nonblocking
-#define hid_send_feature_report         PLATFORM_hid_send_feature_report
-#define hid_get_feature_report          PLATFORM_hid_get_feature_report
-#define hid_close                       PLATFORM_hid_close
-#define hid_get_manufacturer_string     PLATFORM_hid_get_manufacturer_string
-#define hid_get_product_string          PLATFORM_hid_get_product_string
-#define hid_get_serial_number_string    PLATFORM_hid_get_serial_number_string
-#define hid_get_indexed_string          PLATFORM_hid_get_indexed_string
-#define hid_error                       PLATFORM_hid_error
-
-#include <CoreBluetooth/CoreBluetooth.h>
-#include <QuartzCore/QuartzCore.h>
-#import <UIKit/UIKit.h>
-#import <mach/mach_time.h>
-#include <pthread.h>
-#include <sys/time.h>
-#include <unistd.h>
-#include "../hidapi/hidapi.h"
-
-#define VALVE_USB_VID       0x28DE
-#define D0G_BLE2_PID        0x1106
-
-typedef uint32_t uint32;
-typedef uint64_t uint64;
-
-// enables detailed NSLog logging of feature reports
-#define FEATURE_REPORT_LOGGING	0
-
-#define REPORT_SEGMENT_DATA_FLAG	0x80
-#define REPORT_SEGMENT_LAST_FLAG	0x40
-
-#define VALVE_SERVICE		@"100F6C32-1735-4313-B402-38567131E5F3"
-
-// (READ/NOTIFICATIONS)
-#define VALVE_INPUT_CHAR	@"100F6C33-1735-4313-B402-38567131E5F3"
-
-//  (READ/WRITE)
-#define VALVE_REPORT_CHAR	@"100F6C34-1735-4313-B402-38567131E5F3"
-
-// TODO: create CBUUID's in __attribute__((constructor)) rather than doing [CBUUID UUIDWithString:...] everywhere
-
-#pragma pack(push,1)
-
-typedef struct
-{
-	uint8_t		segmentHeader;
-	uint8_t		featureReportMessageID;
-	uint8_t		length;
-	uint8_t		settingIdentifier;
-	union {
-		uint16_t	usPayload;
-		uint32_t	uPayload;
-		uint64_t	ulPayload;
-		uint8_t		ucPayload[15];
-	};
-} bluetoothSegment;
-
-typedef struct {
-	uint8_t		id;
-	union {
-		bluetoothSegment segment;
-		struct {
-			uint8_t		segmentHeader;
-			uint8_t		featureReportMessageID;
-			uint8_t		length;
-			uint8_t		settingIdentifier;
-			union {
-				uint16_t	usPayload;
-				uint32_t	uPayload;
-				uint64_t	ulPayload;
-				uint8_t		ucPayload[15];
-			};
-		};
-	};
-} hidFeatureReport;
-
-#pragma pack(pop)
-
-size_t GetBluetoothSegmentSize(bluetoothSegment *segment)
-{
-    return segment->length + 3;
-}
-
-#define RingBuffer_cbElem   19
-#define RingBuffer_nElem    4096
-
-typedef struct {
-	int _first, _last;
-	uint8_t _data[ ( RingBuffer_nElem * RingBuffer_cbElem ) ];
-	pthread_mutex_t accessLock;
-} RingBuffer;
-
-static void RingBuffer_init( RingBuffer *this )
-{
-    this->_first = -1;
-    this->_last = 0;
-    pthread_mutex_init( &this->accessLock, 0 );
-}
-
-static bool RingBuffer_write( RingBuffer *this, const uint8_t *src )
-{
-    pthread_mutex_lock( &this->accessLock );
-    memcpy( &this->_data[ this->_last ], src, RingBuffer_cbElem );
-    if ( this->_first == -1 )
-    {
-        this->_first = this->_last;
-    }
-    this->_last = ( this->_last + RingBuffer_cbElem ) % (RingBuffer_nElem * RingBuffer_cbElem);
-    if ( this->_last == this->_first )
-    {
-        this->_first = ( this->_first + RingBuffer_cbElem ) % (RingBuffer_nElem * RingBuffer_cbElem);
-        pthread_mutex_unlock( &this->accessLock );
-        return false;
-    }
-    pthread_mutex_unlock( &this->accessLock );
-    return true;
-}
-
-static bool RingBuffer_read( RingBuffer *this, uint8_t *dst )
-{
-    pthread_mutex_lock( &this->accessLock );
-    if ( this->_first == -1 )
-    {
-        pthread_mutex_unlock( &this->accessLock );
-        return false;
-    }
-    memcpy( dst, &this->_data[ this->_first ], RingBuffer_cbElem );
-    this->_first = ( this->_first + RingBuffer_cbElem ) % (RingBuffer_nElem * RingBuffer_cbElem);
-    if ( this->_first == this->_last )
-    {
-        this->_first = -1;
-    }
-    pthread_mutex_unlock( &this->accessLock );
-    return true;
-}
-
-
-#pragma mark HIDBLEDevice Definition
-
-typedef enum
-{
-	BLEDeviceWaitState_None,
-	BLEDeviceWaitState_Waiting,
-	BLEDeviceWaitState_Complete,
-	BLEDeviceWaitState_Error
-} BLEDeviceWaitState;
-
-@interface HIDBLEDevice : NSObject <CBPeripheralDelegate>
-{
-	RingBuffer _inputReports;
-	uint8_t	_featureReport[20];
-	BLEDeviceWaitState	_waitStateForReadFeatureReport;
-	BLEDeviceWaitState	_waitStateForWriteFeatureReport;
-}
-
-@property (nonatomic, readwrite) bool connected;
-@property (nonatomic, readwrite) bool ready;
-
-@property (nonatomic, strong) CBPeripheral     *bleSteamController;
-@property (nonatomic, strong) CBCharacteristic *bleCharacteristicInput;
-@property (nonatomic, strong) CBCharacteristic *bleCharacteristicReport;
-
-- (id)initWithPeripheral:(CBPeripheral *)peripheral;
-
-@end
-
-
-@interface HIDBLEManager : NSObject <CBCentralManagerDelegate>
-
-@property (nonatomic) int nPendingScans;
-@property (nonatomic) int nPendingPairs;
-@property (nonatomic, strong) CBCentralManager *centralManager;
-@property (nonatomic, strong) NSMapTable<CBPeripheral *, HIDBLEDevice *> *deviceMap;
-@property (nonatomic, retain) dispatch_queue_t bleSerialQueue;
-
-+ (instancetype)sharedInstance;
-- (void)startScan:(int)duration;
-- (void)stopScan;
-- (int)updateConnectedSteamControllers:(BOOL) bForce;
-- (void)appWillResignActiveNotification:(NSNotification *)note;
-- (void)appDidBecomeActiveNotification:(NSNotification *)note;
-
-@end
-
-
-// singleton class - access using HIDBLEManager.sharedInstance
-@implementation HIDBLEManager
-
-+ (instancetype)sharedInstance
-{
-	static HIDBLEManager *sharedInstance = nil;
-	static dispatch_once_t onceToken;
-	dispatch_once(&onceToken, ^{
-		sharedInstance = [HIDBLEManager new];
-		sharedInstance.nPendingScans = 0;
-		sharedInstance.nPendingPairs = 0;
-
-        // Bluetooth is currently only used for Steam Controllers, so check that hint
-        // before initializing Bluetooth, which will prompt the user for permission.
-		if ( SDL_GetHintBoolean( SDL_HINT_JOYSTICK_HIDAPI_STEAM, SDL_FALSE ) )
-		{
-			[[NSNotificationCenter defaultCenter] addObserver:sharedInstance selector:@selector(appWillResignActiveNotification:) name: UIApplicationWillResignActiveNotification object:nil];
-			[[NSNotificationCenter defaultCenter] addObserver:sharedInstance selector:@selector(appDidBecomeActiveNotification:) name:UIApplicationDidBecomeActiveNotification object:nil];
-
-			// receive reports on a high-priority serial-queue. optionally put writes on the serial queue to avoid logical
-			// race conditions talking to the controller from multiple threads, although BLE fragmentation/assembly means
-			// that we can still screw this up.
-			// most importantly we need to consume reports at a high priority to avoid the OS thinking we aren't really
-			// listening to the BLE device, as iOS on slower devices may stop delivery of packets to the app WITHOUT ACTUALLY
-			// DISCONNECTING FROM THE DEVICE if we don't react quickly enough to their delivery.
-			// see also the error-handling states in the peripheral delegate to re-open the device if it gets closed
-			sharedInstance.bleSerialQueue = dispatch_queue_create( "com.valvesoftware.steamcontroller.ble", DISPATCH_QUEUE_SERIAL );
-			dispatch_set_target_queue( sharedInstance.bleSerialQueue, dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_HIGH, 0 ) );
-
-			// creating a CBCentralManager will always trigger a future centralManagerDidUpdateState:
-			// where any scanning gets started or connecting to existing peripherals happens, it's never already in a
-			// powered-on state for a newly launched application.
-			sharedInstance.centralManager = [[CBCentralManager alloc] initWithDelegate:sharedInstance queue:sharedInstance.bleSerialQueue];
-		}
-		sharedInstance.deviceMap = [[NSMapTable alloc] initWithKeyOptions:NSMapTableWeakMemory valueOptions:NSMapTableStrongMemory capacity:4];
-	});
-	return sharedInstance;
-}
-
-// called for NSNotification UIApplicationWillResignActiveNotification
-- (void)appWillResignActiveNotification:(NSNotification *)note
-{
-	// we'll get resign-active notification if pairing is happening.
-	if ( self.nPendingPairs > 0 )
-		return;
-
-	for ( CBPeripheral *peripheral in self.deviceMap )
-	{
-		HIDBLEDevice *steamController = [self.deviceMap objectForKey:peripheral];
-		if ( steamController )
-		{
-			steamController.connected = NO;
-			steamController.ready = NO;
-			[self.centralManager cancelPeripheralConnection:peripheral];
-		}
-	}
-	[self.deviceMap removeAllObjects];
-}
-
-// called for NSNotification UIApplicationDidBecomeActiveNotification
-//  whenever the application comes back from being inactive, trigger a 20s pairing scan and reconnect
-//  any devices that may have paired while we were inactive.
-- (void)appDidBecomeActiveNotification:(NSNotification *)note
-{
-	[self updateConnectedSteamControllers:true];
-	[self startScan:20];
-}
-
-- (int)updateConnectedSteamControllers:(BOOL) bForce
-{
-	static uint64_t s_unLastUpdateTick = 0;
-	static mach_timebase_info_data_t s_timebase_info;
-
-	if ( self.centralManager == nil )
-    {
-		return 0;
-    }
-
-	if (s_timebase_info.denom == 0)
-	{
-		mach_timebase_info( &s_timebase_info );
-	}
-
-	uint64_t ticksNow = mach_approximate_time();
-	if ( !bForce && ( ( (ticksNow - s_unLastUpdateTick) * s_timebase_info.numer ) / s_timebase_info.denom ) < (5ull * NSEC_PER_SEC) )
-		return (int)self.deviceMap.count;
-
-	// we can see previously connected BLE peripherals but can't connect until the CBCentralManager
-	// is fully powered up - only do work when we are in that state
-	if ( self.centralManager.state != CBManagerStatePoweredOn )
-		return (int)self.deviceMap.count;
-
-	// only update our last-check-time if we actually did work, otherwise there can be a long delay during initial power-up
-	s_unLastUpdateTick = mach_approximate_time();
-
-	// if a pair is in-flight, the central manager may still give it back via retrieveConnected... and
-	// cause the SDL layer to attempt to initialize it while some of its endpoints haven't yet been established
-	if ( self.nPendingPairs > 0 )
-		return (int)self.deviceMap.count;
-
-	NSArray<CBPeripheral *> *peripherals = [self.centralManager retrieveConnectedPeripheralsWithServices: @[ [CBUUID UUIDWithString:@"180A"]]];
-	for ( CBPeripheral *peripheral in peripherals )
-	{
-		// we already know this peripheral
-		if ( [self.deviceMap objectForKey: peripheral] != nil )
-			continue;
-
-		NSLog( @"connected peripheral: %@", peripheral );
-		if ( [peripheral.name isEqualToString:@"SteamController"] )
-		{
-			self.nPendingPairs += 1;
-			HIDBLEDevice *steamController = [[HIDBLEDevice alloc] initWithPeripheral:peripheral];
-			[self.deviceMap setObject:steamController forKey:peripheral];
-			[self.centralManager connectPeripheral:peripheral options:nil];
-		}
-	}
-
-	return (int)self.deviceMap.count;
-}
-
-// manual API for folks to start & stop scanning
-- (void)startScan:(int)duration
-{
-	if ( self.centralManager == nil )
-	{
-		return;
-	}
-
-	NSLog( @"BLE: requesting scan for %d seconds", duration );
-	@synchronized (self)
-	{
-		if ( _nPendingScans++ == 0 )
-		{
-			[self.centralManager scanForPeripheralsWithServices:nil options:nil];
-		}
-	}
-
-	if ( duration != 0 )
-	{
-		dispatch_after( dispatch_time( DISPATCH_TIME_NOW, (int64_t)(duration * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
-			[self stopScan];
-		});
-	}
-}
-
-- (void)stopScan
-{
-	if ( self.centralManager == nil )
-	{
-		return;
-	}
-
-	NSLog( @"BLE: stopping scan" );
-	@synchronized (self)
-	{
-		if ( --_nPendingScans <= 0 )
-		{
-			_nPendingScans = 0;
-			[self.centralManager stopScan];
-		}
-	}
-}
-
-
-#pragma mark CBCentralManagerDelegate Implementation
-
-// called whenever the BLE hardware state changes.
-- (void)centralManagerDidUpdateState:(CBCentralManager *)central
-{
-	switch ( central.state )
-	{
-		case CBCentralManagerStatePoweredOn:
-		{
-			NSLog( @"CoreBluetooth BLE hardware is powered on and ready" );
-
-			// at startup, if we have no already attached peripherals, do a 20s scan for new unpaired devices,
-			// otherwise callers should occaisionally do additional scans. we don't want to continuously be
-			// scanning because it drains battery, causes other nearby people to have a hard time pairing their
-			// Steam Controllers, and may also trigger firmware weirdness when a device attempts to start
-			// the pairing sequence multiple times concurrently
-			if ( [self updateConnectedSteamControllers:false] == 0 )
-			{
-				// TODO: we could limit our scan to only peripherals supporting the SteamController service, but
-				//  that service doesn't currently fit in the base advertising packet, we'd need to put it into an
-				//  extended scan packet. Useful optimization downstream, but not currently necessary
-				//	NSArray *services = @[[CBUUID UUIDWithString:VALVE_SERVICE]];
-				[self startScan:20];
-			}
-			break;
-		}
-
-		case CBCentralManagerStatePoweredOff:
-			NSLog( @"CoreBluetooth BLE hardware is powered off" );
-			break;
-
-		case CBCentralManagerStateUnauthorized:
-			NSLog( @"CoreBluetooth BLE state is unauthorized" );
-			break;
-
-		case CBCentralManagerStateUnknown:
-			NSLog( @"CoreBluetooth BLE state is unknown" );
-			break;
-
-		case CBCentralManagerStateUnsupported:
-			NSLog( @"CoreBluetooth BLE hardware is unsupported on this platform" );
-			break;
-
-		case CBCentralManagerStateResetting:
-			NSLog( @"CoreBluetooth BLE manager is resetting" );
-			break;
-	}
-}
-
-- (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral
-{
-	HIDBLEDevice *steamController = [_deviceMap objectForKey:peripheral];
-	steamController.connected = YES;
-	self.nPendingPairs -= 1;
-}
-
-- (void)centralManager:(CBCentralManager *)central didFailToConnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error
-{
-	NSLog( @"Failed to connect: %@", error );
-	[_deviceMap removeObjectForKey:peripheral];
-	self.nPendingPairs -= 1;
-}
-
-- (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI
-{
-	NSString *localName = [advertisementData objectForKey:CBAdvertisementDataLocalNameKey];
-	NSString *log = [NSString stringWithFormat:@"Found '%@'", localName];
-
-	if ( [localName isEqualToString:@"SteamController"] )
-	{
-		NSLog( @"%@ : %@ - %@", log, peripheral, advertisementData );
-		self.nPendingPairs += 1;
-		HIDBLEDevice *steamController = [[HIDBLEDevice alloc] initWithPeripheral:peripheral];
-		[self.deviceMap setObject:steamController forKey:peripheral];
-		[self.centralManager connectPeripheral:peripheral options:nil];
-	}
-}
-
-- (void)centralManager:(CBCentralManager *)central didDisconnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error
-{
-	HIDBLEDevice *steamController = [self.deviceMap objectForKey:peripheral];
-	if ( steamController )
-	{
-		steamController.connected = NO;
-		steamController.ready = NO;
-		[self.deviceMap removeObjectForKey:peripheral];
-	}
-}
-
-@end
-
-
-// Core Bluetooth devices calling back on event boundaries of their run-loops. so annoying.
-static void process_pending_events()
-{
-	CFRunLoopRunResult res;
-	do
-	{
-		res = CFRunLoopRunInMode( kCFRunLoopDefaultMode, 0.001, FALSE );
-	}
-	while( res != kCFRunLoopRunFinished && res != kCFRunLoopRunTimedOut );
-}
-
-@implementation HIDBLEDevice
-
-- (id)init
-{
-	if ( self = [super init] )
-	{
-        RingBuffer_init( &_inputReports );
-		self.bleSteamController = nil;
-		self.bleCharacteristicInput = nil;
-		self.bleCharacteristicReport = nil;
-		_connected = NO;
-		_ready = NO;
-	}
-	return self;
-}
-
-- (id)initWithPeripheral:(CBPeripheral *)peripheral
-{
-	if ( self = [super init] )
-	{
-        RingBuffer_init( &_inputReports );
-		_connected = NO;
-		_ready = NO;
-		self.bleSteamController = peripheral;
-		if ( peripheral )
-		{
-			peripheral.delegate = self;
-		}
-		self.bleCharacteristicInput = nil;
-		self.bleCharacteristicReport = nil;
-	}
-	return self;
-}
-
-- (void)setConnected:(bool)connected
-{
-	_connected = connected;
-	if ( _connected )
-	{
-		[_bleSteamController discoverServices:nil];
-	}
-	else
-	{
-		NSLog( @"Disconnected" );
-	}
-}
-
-- (size_t)read_input_report:(uint8_t *)dst
-{
-	if ( RingBuffer_read( &_inputReports, dst+1 ) )
-	{
-		*dst = 0x03;
-		return 20;
-	}
-	return 0;
-}
-
-- (int)send_report:(const uint8_t *)data length:(size_t)length
-{
-	[_bleSteamController writeValue:[NSData dataWithBytes:data length:length] forCharacteristic:_bleCharacteristicReport type:CBCharacteristicWriteWithResponse];
-	return (int)length;
-}
-
-- (int)send_feature_report:(hidFeatureReport *)report
-{
-#if FEATURE_REPORT_LOGGING
-	uint8_t *reportBytes = (uint8_t *)report;
-
-	NSLog( @"HIDBLE:send_feature_report (%02zu/19) [%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x]", GetBluetoothSegmentSize( report->segment ),
-		  reportBytes[1], reportBytes[2], reportBytes[3], reportBytes[4], reportBytes[5], reportBytes[6],
-		  reportBytes[7], reportBytes[8], reportBytes[9], reportBytes[10], reportBytes[11], reportBytes[12],
-		  reportBytes[13], reportBytes[14], reportBytes[15], reportBytes[16], reportBytes[17], reportBytes[18],
-		  reportBytes[19] );
-#endif
-
-	int sendSize = (int)GetBluetoothSegmentSize( &report->segment );
-	if ( sendSize > 20 )
-		sendSize = 20;
-
-#if 1
-	// fire-and-forget - we are going to not wait for the response here because all Steam Controller BLE send_feature_report's are ignored,
-	//  except errors.
-	[_bleSteamController writeValue:[NSData dataWithBytes:&report->segment length:sendSize] forCharacteristic:_bleCharacteristicReport type:CBCharacteristicWriteWithResponse];
-
-	// pretend we received a result anybody cares about
-	return 19;
-
-#else
-	// this is technically the correct send_feature_report logic if you want to make sure it gets through and is
-	// acknowledged or errors out
-	_waitStateForWriteFeatureReport = BLEDeviceWaitState_Waiting;
-	[_bleSteamController writeValue:[NSData dataWithBytes:&report->segment length:sendSize
-									 ] forCharacteristic:_bleCharacteristicReport type:CBCharacteristicWriteWithResponse];
-
-	while ( _waitStateForWriteFeatureReport == BLEDeviceWaitState_Waiting )
-	{
-		process_pending_events();
-	}
-
-	if ( _waitStateForWriteFeatureReport == BLEDeviceWaitState_Error )
-	{
-		_waitStateForWriteFeatureReport = BLEDeviceWaitState_None;
-		return -1;
-	}
-
-	_waitStateForWriteFeatureReport = BLEDeviceWaitState_None;
-	return 19;
-#endif
-}
-
-- (int)get_feature_report:(uint8_t)feature into:(uint8_t *)buffer
-{
-	_waitStateForReadFeatureReport = BLEDeviceWaitState_Waiting;
-	[_bleSteamController readValueForCharacteristic:_bleCharacteristicReport];
-
-	while ( _waitStateForReadFeatureReport == BLEDeviceWaitState_Waiting )
-		process_pending_events();
-
-	if ( _waitStateForReadFeatureReport == BLEDeviceWaitState_Error )
-	{
-		_waitStateForReadFeatureReport = BLEDeviceWaitState_None;
-		return -1;
-	}
-
-	memcpy( buffer, _featureReport, sizeof(_featureReport) );
-
-	_waitStateForReadFeatureReport = BLEDeviceWaitState_None;
-
-#if FEATURE_REPORT_LOGGING
-	NSLog( @"HIDBLE:get_feature_report (19) [%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x]",
-		  buffer[1], buffer[2], buffer[3], buffer[4], buffer[5], buffer[6],
-		  buffer[7], buffer[8], buffer[9], buffer[10], buffer[11], buffer[12],
-		  buffer[13], buffer[14], buffer[15], buffer[16], buffer[17], buffer[18],
-		  buffer[19] );
-#endif
-
-	return 19;
-}
-
-#pragma mark CBPeripheralDelegate Implementation
-
-- (void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error
-{
-	for (CBService *service in peripheral.services)
-	{
-		NSLog( @"Found Service: %@", service );
-		if ( [service.UUID isEqual:[CBUUID UUIDWithString:VALVE_SERVICE]] )
-		{
-			[peripheral discoverCharacteristics:nil forService:service];
-		}
-	}
-}
-
-- (void)peripheral:(CBPeripheral *)peripheral didDiscoverDescriptorsForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error
-{
-	// nothing yet needed here, enable for logging
-	if ( /* DISABLES CODE */ (0) )
-	{
-		for ( CBDescriptor *descriptor in characteristic.descriptors )
-		{
-			NSLog( @" - Descriptor '%@'", descriptor );
-		}
-	}
-}
-
-- (void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error
-{
-	if ([service.UUID isEqual:[CBUUID UUIDWithString:VALVE_SERVICE]])
-	{
-		for (CBCharacteristic *aChar in service.characteristics)
-		{
-			NSLog( @"Found Characteristic %@", aChar );
-
-			if ( [aChar.UUID isEqual:[CBUUID UUIDWithString:VALVE_INPUT_CHAR]] )
-			{
-				self.bleCharacteristicInput = aChar;
-			}
-			else if ( [aChar.UUID isEqual:[CBUUID UUIDWithString:VALVE_REPORT_CHAR]] )
-			{
-				self.bleCharacteristicReport = aChar;
-				[self.bleSteamController discoverDescriptorsForCharacteristic: aChar];
-			}
-		}
-	}
-}
-
-- (void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error
-{
-	static uint64_t s_ticksLastOverflowReport = 0;
-
-	// receiving an input report is the final indicator that the user accepted a pairing
-	// request and that we successfully established notification. CoreBluetooth has no
-	// notification of the pairing acknowledgement, which is a bad oversight.
-	if ( self.ready == NO )
-	{
-		self.ready = YES;
-		HIDBLEManager.sharedInstance.nPendingPairs -= 1;
-	}
-
-	if ( [characteristic.UUID isEqual:_bleCharacteristicInput.UUID] )
-	{
-		NSData *data = [characteristic value];
-		if ( data.length != 19 )
-		{
-			NSLog( @"HIDBLE: incoming data is %lu bytes should be exactly 19", (unsigned long)data.length );
-		}
-		if ( !RingBuffer_write( &_inputReports, (const uint8_t *)data.bytes ) )
-		{
-			uint64_t ticksNow = mach_approximate_time();
-			if ( ticksNow - s_ticksLastOverflowReport > (5ull * NSEC_PER_SEC / 10) )
-			{
-				NSLog( @"HIDBLE: input report buffer overflow" );
-				s_ticksLastOverflowReport = ticksNow;
-			}
-		}
-	}
-	else if ( [characteristic.UUID isEqual:_bleCharacteristicReport.UUID] )
-	{
-		memset( _featureReport, 0, sizeof(_featureReport) );
-
-		if ( error != nil )
-		{
-			NSLog( @"HIDBLE: get_feature_report error: %@", error );
-			_waitStateForReadFeatureReport = BLEDeviceWaitState_Error;
-		}
-		else
-		{
-			NSData *data = [characteristic value];
-			if ( data.length != 20 )
-			{
-				NSLog( @"HIDBLE: incoming data is %lu bytes should be exactly 20", (unsigned long)data.length );
-			}
-			memcpy( _featureReport, data.bytes, MIN( data.length, sizeof(_featureReport) ) );
-			_waitStateForReadFeatureReport = BLEDeviceWaitState_Complete;
-		}
-	}
-}
-
-- (void)peripheral:(CBPeripheral *)peripheral didWriteValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error
-{
-	if ( [characteristic.UUID isEqual:[CBUUID UUIDWithString:VALVE_REPORT_CHAR]] )
-	{
-		if ( error != nil )
-		{
-			NSLog( @"HIDBLE: write_feature_report error: %@", error );
-			_waitStateForWriteFeatureReport = BLEDeviceWaitState_Error;
-		}
-		else
-		{
-			_waitStateForWriteFeatureReport = BLEDeviceWaitState_Complete;
-		}
-	}
-}
-
-- (void)peripheral:(CBPeripheral *)peripheral didUpdateNotificationStateForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error
-{
-	NSLog( @"didUpdateNotifcationStateForCharacteristic %@ (%@)", characteristic, error );
-}
-
-@end
-
-
-#pragma mark hid_api implementation
-
-struct hid_device_ {
-	void *device_handle;
-	int blocking;
-	hid_device *next;
-};
-
-int HID_API_EXPORT HID_API_CALL hid_init(void)
-{
-	return ( HIDBLEManager.sharedInstance == nil ) ? -1 : 0;
-}
-
-int HID_API_EXPORT HID_API_CALL hid_exit(void)
-{
-	return 0;
-}
-
-void HID_API_EXPORT HID_API_CALL hid_ble_scan( int bStart )
-{
-	HIDBLEManager *bleManager = HIDBLEManager.sharedInstance;
-	if ( bStart )
-	{
-		[bleManager startScan:0];
-	}
-	else
-	{
-		[bleManager stopScan];
-	}
-}
-
-HID_API_EXPORT hid_device * HID_API_CALL hid_open(unsigned short vendor_id, unsigned short product_id, const wchar_t *serial_number)
-{
-	return NULL;
-}
-
-HID_API_EXPORT hid_device * HID_API_CALL hid_open_path( const char *path, int bExclusive /* = false */ )
-{
-	hid_device *result = NULL;
-	NSString *nssPath = [NSString stringWithUTF8String:path];
-	HIDBLEManager *bleManager = HIDBLEManager.sharedInstance;
-	NSEnumerator<HIDBLEDevice *> *devices = [bleManager.deviceMap objectEnumerator];
-
-	for ( HIDBLEDevice *device in devices )
-	{
-		// we have the device but it hasn't found its service or characteristics until it is connected
-		if ( !device.ready || !device.connected || !device.bleCharacteristicInput )
-			continue;
-
-		if ( [device.bleSteamController.identifier.UUIDString isEqualToString:nssPath] )
-		{
-			result = (hid_device *)malloc( sizeof( hid_device ) );
-			memset( result, 0, sizeof( hid_device ) );
-			result->device_handle = (void*)CFBridgingRetain( device );
-			result->blocking = NO;
-			// enable reporting input events on the characteristic
-			[device.bleSteamController setNotifyValue:YES forCharacteristic:device.bleCharacteristicInput];
-			return result;
-		}
-	}
-	return result;
-}
-
-void  HID_API_EXPORT hid_free_enumeration(struct hid_device_info *devs)
-{
-	/* This function is identical to the Linux version. Platform independent. */
-	struct hid_device_info *d = devs;
-	while (d) {
-		struct hid_device_info *next = d->next;
-		free(d->path);
-		free(d->serial_number);
-		free(d->manufacturer_string);
-		free(d->product_string);
-		free(d);
-		d = next;
-	}
-}
-
-int HID_API_EXPORT hid_set_nonblocking(hid_device *dev, int nonblock)
-{
-	/* All Nonblocking operation is handled by the library. */
-	dev->blocking = !nonblock;
-
-	return 0;
-}
-
-struct hid_device_info  HID_API_EXPORT *hid_enumerate(unsigned short vendor_id, unsigned short product_id)
-{ @autoreleasepool {
-	struct hid_device_info *root = NULL;
-	const char *hint = SDL_GetHint(SDL_HINT_HIDAPI_IGNORE_DEVICES);
-
-	/* See if there are any devices we should skip in enumeration */
-	if (hint) {
-		char vendor_match[16], product_match[16];
-		SDL_snprintf(vendor_match, sizeof(vendor_match), "0x%.4x/0x0000", VALVE_USB_VID);
-		SDL_snprintf(product_match, sizeof(product_match), "0x%.4x/0x%.4x", VALVE_USB_VID, D0G_BLE2_PID);
-		if (SDL_strcasestr(hint, vendor_match) || SDL_strcasestr(hint, product_match)) {
-			return NULL;
-		}
-	}
-
-	if ( ( vendor_id == 0 && product_id == 0 ) ||
-		 ( vendor_id == VALVE_USB_VID && product_id == D0G_BLE2_PID ) )
-	{
-		HIDBLEManager *bleManager = HIDBLEManager.sharedInstance;
-		[bleManager updateConnectedSteamControllers:false];
-		NSEnumerator<HIDBLEDevice *> *devices = [bleManager.deviceMap objectEnumerator];
-		for ( HIDBLEDevice *device in devices )
-		{
-			// there are several brief windows in connecting to an already paired device and
-			// one long window waiting for users to confirm pairing where we don't want
-			// to consider a device ready - if we hand it back to SDL or another
-			// Steam Controller consumer, their additional SC setup work will fail
-			// in unusual/silent ways and we can actually corrupt the BLE stack for
-			// the entire system and kill the appletv remote's Menu button (!)
-			if ( device.bleSteamController.state != CBPeripheralStateConnected ||
-				 device.connected == NO || device.ready == NO )
-			{
-				if ( device.ready == NO && device.bleCharacteristicInput != nil )
-				{
-					// attempt to register for input reports. this call will silently fail
-					// until the pairing finalizes with user acceptance. oh, apple.
-					[device.bleSteamController setNotifyValue:YES forCharacteristic:device.bleCharacteristicInput];
-				}
-				continue;
-			}
-			struct hid_device_info *device_info = (struct hid_device_info *)malloc( sizeof(struct hid_device_info) );
-			memset( device_info, 0, sizeof(struct hid_device_info) );
-			device_info->next = root;
-			root = device_info;
-			device_info->path = strdup( device.bleSteamController.identifier.UUIDString.UTF8String );
-			device_info->vendor_id = VALVE_USB_VID;
-			device_info->product_id = D0G_BLE2_PID;
-			device_info->product_string = wcsdup( L"Steam Controller" );
-			device_info->manufacturer_string = wcsdup( L"Valve Corporation" );
-		}
-	}
-	return root;
-}}
-
-int HID_API_EXPORT_CALL hid_get_manufacturer_string(hid_device *dev, wchar_t *string, size_t maxlen)
-{
-	static wchar_t s_wszManufacturer[] = L"Valve Corporation";
-	wcsncpy( string, s_wszManufacturer, sizeof(s_wszManufacturer)/sizeof(s_wszManufacturer[0]) );
-	return 0;
-}
-
-int HID_API_EXPORT_CALL hid_get_product_string(hid_device *dev, wchar_t *string, size_t maxlen)
-{
-	static wchar_t s_wszProduct[] = L"Steam Controller";
-	wcsncpy( string, s_wszProduct, sizeof(s_wszProduct)/sizeof(s_wszProduct[0]) );
-	return 0;
-}
-
-int HID_API_EXPORT_CALL hid_get_serial_number_string(hid_device *dev, wchar_t *string, size_t maxlen)
-{
-	static wchar_t s_wszSerial[] = L"12345";
-	wcsncpy( string, s_wszSerial, sizeof(s_wszSerial)/sizeof(s_wszSerial[0]) );
-	return 0;
-}
-
-int HID_API_EXPORT_CALL hid_get_indexed_string(hid_device *dev, int string_index, wchar_t *string, size_t maxlen)
-{
-	return -1;
-}
-
-int HID_API_EXPORT hid_write(hid_device *dev, const unsigned char *data, size_t length)
-{
-    HIDBLEDevice *device_handle = (__bridge HIDBLEDevice *)dev->device_handle;
-
-	if ( !device_handle.connected )
-		return -1;
-
-	return [device_handle send_report:data length:length];
-}
-
-void HID_API_EXPORT hid_close(hid_device *dev)
-{
-    HIDBLEDevice *device_handle = CFBridgingRelease( dev->device_handle );
-
-	// disable reporting input events on the characteristic
-	if ( device_handle.connected ) {
-		[device_handle.bleSteamController setNotifyValue:NO forCharacteristic:device_handle.bleCharacteristicInput];
-	}
-
-	free( dev );
-}
-
-int HID_API_EXPORT hid_send_feature_report(hid_device *dev, const unsigned char *data, size_t length)
-{
-    HIDBLEDevice *device_handle = (__bridge HIDBLEDevice *)dev->device_handle;
-
-	if ( !device_handle.connected )
-		return -1;
-
-	return [device_handle send_feature_report:(hidFeatureReport *)(void *)data];
-}
-
-int HID_API_EXPORT hid_get_feature_report(hid_device *dev, unsigned char *data, size_t length)
-{
-    HIDBLEDevice *device_handle = (__bridge HIDBLEDevice *)dev->device_handle;
-
-	if ( !device_handle.connected )
-		return -1;
-
-	size_t written = [device_handle get_feature_report:data[0] into:data];
-
-	return written == length-1 ? (int)length : (int)written;
-}
-
-int HID_API_EXPORT hid_read(hid_device *dev, unsigned char *data, size_t length)
-{
-    HIDBLEDevice *device_handle = (__bridge HIDBLEDevice *)dev->device_handle;
-
-	if ( !device_handle.connected )
-		return -1;
-
-	return hid_read_timeout(dev, data, length, 0);
-}
-
-int HID_API_EXPORT hid_read_timeout(hid_device *dev, unsigned char *data, size_t length, int milliseconds)
-{
-    HIDBLEDevice *device_handle = (__bridge HIDBLEDevice *)dev->device_handle;
-
-	if ( !device_handle.connected )
-		return -1;
-
-	if ( milliseconds != 0 )
-	{
-		NSLog( @"hid_read_timeout with non-zero wait" );
-	}
-	int result = (int)[device_handle read_input_report:data];
-#if FEATURE_REPORT_LOGGING
-	NSLog( @"HIDBLE:hid_read_timeout (%d) [%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x]", result,
-		  data[1], data[2], data[3], data[4], data[5], data[6],
-		  data[7], data[8], data[9], data[10], data[11], data[12],
-		  data[13], data[14], data[15], data[16], data[17], data[18],
-		  data[19] );
-#endif
-	return result;
-}
-
-HID_API_EXPORT const wchar_t* HID_API_CALL hid_error(hid_device *dev)
-{
-	return NULL;
-}
-
-#endif /* !SDL_HIDAPI_DISABLED */

+ 8 - 0
src/hidapi/libusb/.gitignore

@@ -0,0 +1,8 @@
+*.o
+*.so
+*.la
+*.lo
+*.a
+.libs
+.deps
+hidtest-libusb

+ 107 - 0
src/hidapi/libusb/CMakeLists.txt

@@ -0,0 +1,107 @@
+cmake_minimum_required(VERSION 3.6.3 FATAL_ERROR)
+
+list(APPEND HIDAPI_PUBLIC_HEADERS "hidapi_libusb.h")
+
+add_library(hidapi_libusb
+    ${HIDAPI_PUBLIC_HEADERS}
+    hid.c
+)
+target_link_libraries(hidapi_libusb PUBLIC hidapi_include)
+
+if(TARGET usb-1.0)
+    target_link_libraries(hidapi_libusb PRIVATE usb-1.0)
+else()
+    include(FindPkgConfig)
+    pkg_check_modules(libusb REQUIRED IMPORTED_TARGET libusb-1.0>=1.0.9)
+    target_link_libraries(hidapi_libusb PRIVATE PkgConfig::libusb)
+endif()
+
+find_package(Threads REQUIRED)
+target_link_libraries(hidapi_libusb PRIVATE Threads::Threads)
+
+if(HIDAPI_NO_ICONV)
+    target_compile_definitions(hidapi_libusb PRIVATE NO_ICONV)
+else()
+    if(NOT ANDROID)
+        include(CheckCSourceCompiles)
+
+        if(NOT CMAKE_VERSION VERSION_LESS 3.11)
+            message(STATUS "Check for Iconv")
+            find_package(Iconv)
+            if(Iconv_FOUND)
+                if(NOT Iconv_IS_BUILT_IN)
+                    target_link_libraries(hidapi_libusb PRIVATE Iconv::Iconv)
+                    set(CMAKE_REQUIRED_LIBRARIES "Iconv::Iconv")
+                    if(NOT BUILD_SHARED_LIBS)
+                        set(HIDAPI_NEED_EXPORT_ICONV TRUE PARENT_SCOPE)
+                    endif()
+                endif()
+            else()
+                message(STATUS "Iconv Explicitly check '-liconv'")
+                # Sometime the build environment is not setup
+                # in a way CMake can find Iconv on its own by default.
+                # But if we simply link against iconv (-liconv), the build may succeed
+                # due to other compiler/link flags.
+                set(CMAKE_REQUIRED_LIBRARIES "iconv")
+                check_c_source_compiles("
+                    #include <stddef.h>
+                    #include <iconv.h>
+                    int main() {
+                        char *a, *b;
+                        size_t i, j;
+                        iconv_t ic;
+                        ic = iconv_open(\"to\", \"from\");
+                        iconv(ic, &a, &i, &b, &j);
+                        iconv_close(ic);
+                    }
+                    "
+                    Iconv_EXPLICITLY_AT_ENV)
+                if(Iconv_EXPLICITLY_AT_ENV)
+                    message(STATUS "Iconv Explicitly check '-liconv' - Available")
+                    target_link_libraries(hidapi_libusb PRIVATE iconv)
+                else()
+                    message(FATAL_ERROR "Iconv is not found, make sure to provide it in the build environment")
+                endif()
+            endif()
+        else()
+            # otherwise there is 2 options:
+            # 1) iconv is provided by Standard C library and the build will be just fine
+            # 2) The _user_ has to provide additiona compilation options for this project/target
+        endif()
+
+        # check for error: "conflicting types for 'iconv'"
+        check_c_source_compiles("#include<iconv.h>
+            extern size_t iconv (iconv_t cd, const char **inbuf, size_t *inbytesleft, char **outbuf, size_t *outbytesleft);
+            int main() {}"
+        HIDAPI_ICONV_CONST)
+        if(HIDAPI_ICONV_CONST)
+            target_compile_definitions(hidapi_libusb PRIVATE "ICONV_CONST=const")
+        endif()
+    else()
+        # On Android Iconv is disabled on the code level anyway, so no issue;
+    endif()
+endif()
+
+set_target_properties(hidapi_libusb
+    PROPERTIES
+        EXPORT_NAME "libusb"
+        OUTPUT_NAME "hidapi-libusb"
+        VERSION ${PROJECT_VERSION}
+        SOVERSION ${PROJECT_VERSION_MAJOR}
+        PUBLIC_HEADER "${HIDAPI_PUBLIC_HEADERS}"
+)
+
+# compatibility with find_package()
+add_library(hidapi::libusb ALIAS hidapi_libusb)
+# compatibility with raw library link
+add_library(hidapi-libusb ALIAS hidapi_libusb)
+
+if(HIDAPI_INSTALL_TARGETS)
+    install(TARGETS hidapi_libusb 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-libusb.pc.in")

+ 4 - 0
src/hidapi/libusb/Makefile-manual

@@ -10,6 +10,10 @@ ifeq ($(OS), FreeBSD)
 	FILE=Makefile.freebsd
 endif
 
+ifeq ($(OS), Haiku)
+	FILE=Makefile.haiku
+endif
+
 ifeq ($(FILE), )
 all:
 	$(error Your platform ${OS} is not supported by hidapi/libusb at this time.)

+ 8 - 1
src/hidapi/libusb/Makefile.am

@@ -21,7 +21,14 @@ libhidapi_la_LDFLAGS = $(LTLDFLAGS)
 libhidapi_la_LIBADD = $(LIBS_LIBUSB)
 endif
 
+if OS_HAIKU
+lib_LTLIBRARIES = libhidapi.la
+libhidapi_la_SOURCES = hid.c
+libhidapi_la_LDFLAGS = $(LTLDFLAGS)
+libhidapi_la_LIBADD = $(LIBS_LIBUSB)
+endif
+
 hdrdir = $(includedir)/hidapi
-hdr_HEADERS = $(top_srcdir)/hidapi/hidapi.h
+hdr_HEADERS = $(top_srcdir)/hidapi/hidapi.h hidapi_libusb.h
 
 EXTRA_DIST = Makefile-manual

+ 4 - 11
src/hidapi/libusb/Makefile.freebsd

@@ -13,20 +13,16 @@ libs: libhidapi.so
 CC       ?= cc
 CFLAGS   ?= -Wall -g -fPIC
 
-CXX      ?= c++
-CXXFLAGS ?= -Wall -g
-
-COBJS     = hid.o
-CPPOBJS   = ../hidtest/hidtest.o
-OBJS      = $(COBJS) $(CPPOBJS)
-INCLUDES  = -I../hidapi -I/usr/local/include
+COBJS     = hid.o ../hidtest/test.o
+OBJS      = $(COBJS)
+INCLUDES  = -I../hidapi -I. -I/usr/local/include
 LDFLAGS   = -L/usr/local/lib
 LIBS      = -lusb -liconv -pthread
 
 
 # Console Test Program
 hidtest: $(OBJS)
-	$(CXX) $(CXXFLAGS) $(LDFLAGS) $^ -o $@ $(LIBS)
+	$(CC) $(CFLAGS) $(LDFLAGS) $^ -o $@ $(LIBS)
 
 # Shared Libs
 libhidapi.so: $(COBJS)
@@ -36,9 +32,6 @@ libhidapi.so: $(COBJS)
 $(COBJS): %.o: %.c
 	$(CC) $(CFLAGS) -c $(INCLUDES) $< -o $@
 
-$(CPPOBJS): %.o: %.cpp
-	$(CXX) $(CXXFLAGS) -c $(INCLUDES) $< -o $@
-
 
 clean:
 	rm -f $(OBJS) hidtest libhidapi.so ../hidtest/hidtest.o

+ 39 - 0
src/hidapi/libusb/Makefile.haiku

@@ -0,0 +1,39 @@
+###########################################
+# Simple Makefile for HIDAPI test program
+#
+# Alan Ott
+# Signal 11 Software
+# 2010-06-01
+###########################################
+
+all: hidtest libs
+
+libs: libhidapi.so
+
+CC       ?= cc
+CFLAGS   ?= -Wall -g -fPIC
+
+COBJS     = hid.o ../hidtest/test.o
+OBJS      = $(COBJS)
+INCLUDES  = -I../hidapi -I. -I/usr/local/include
+LDFLAGS   = -L/usr/local/lib
+LIBS      = -lusb -liconv -pthread
+
+
+# Console Test Program
+hidtest: $(OBJS)
+    $(CC) $(CFLAGS) $(LDFLAGS) $^ -o $@ $(LIBS)
+
+# Shared Libs
+libhidapi.so: $(COBJS)
+    $(CC) $(LDFLAGS) -shared -Wl,-soname,$@.0 $^ -o $@ $(LIBS)
+
+# Objects
+$(COBJS): %.o: %.c
+    $(CC) $(CFLAGS) -c $(INCLUDES) $< -o $@
+
+
+clean:
+    rm -f $(OBJS) hidtest libhidapi.so ../hidtest/hidtest.o
+
+.PHONY: clean libs

+ 5 - 12
src/hidapi/libusb/Makefile.linux

@@ -13,23 +13,19 @@ libs: libhidapi-libusb.so
 CC       ?= gcc
 CFLAGS   ?= -Wall -g -fpic
 
-CXX      ?= g++
-CXXFLAGS ?= -Wall -g -fpic
-
 LDFLAGS  ?= -Wall -g
 
 COBJS_LIBUSB = hid.o
-COBJS = $(COBJS_LIBUSB)
-CPPOBJS   = ../hidtest/hidtest.o
-OBJS      = $(COBJS) $(CPPOBJS)
+COBJS = $(COBJS_LIBUSB) ../hidtest/test.o
+OBJS      = $(COBJS)
 LIBS_USB  = `pkg-config libusb-1.0 --libs` -lrt -lpthread
 LIBS      = $(LIBS_USB)
-INCLUDES ?= -I../hidapi `pkg-config libusb-1.0 --cflags`
+INCLUDES ?= -I../hidapi -I. `pkg-config libusb-1.0 --cflags`
 
 
 # Console Test Program
-hidtest-libusb: $(COBJS_LIBUSB) $(CPPOBJS)
-	$(CXX) $(LDFLAGS) $^ $(LIBS_USB) -o $@
+hidtest-libusb: $(COBJS)
+	$(CC) $(LDFLAGS) $^ $(LIBS_USB) -o $@
 
 # Shared Libs
 libhidapi-libusb.so: $(COBJS_LIBUSB)
@@ -39,9 +35,6 @@ libhidapi-libusb.so: $(COBJS_LIBUSB)
 $(COBJS): %.o: %.c
 	$(CC) $(CFLAGS) -c $(INCLUDES) $< -o $@
 
-$(CPPOBJS): %.o: %.cpp
-	$(CXX) $(CXXFLAGS) -c $(INCLUDES) $< -o $@
-
 
 clean:
 	rm -f $(OBJS) hidtest-libusb libhidapi-libusb.so ../hidtest/hidtest.o

+ 644 - 673
src/hidapi/libusb/hid.c

@@ -5,12 +5,9 @@
  Alan Ott
  Signal 11 Software
 
- 8/22/2009
- Linux Version - 6/2/2010
- Libusb Version - 8/13/2010
- FreeBSD Version - 11/1/2011
+ libusb/hidapi Team
 
- Copyright 2009, All Rights Reserved.
+ Copyright 2022, All Rights Reserved.
 
  At the discretion of the user of this library,
  this software may be licensed under the terms of the
@@ -23,124 +20,99 @@
         https://github.com/libusb/hidapi .
 ********************************************************/
 
-/* This file is heavily modified from the original libusb.c, for portability.
- * Last upstream update was from July 25, 2019, Git commit 93dca807.
- */
-
-#include "SDL_internal.h"
-#include "../../thread/SDL_systhread.h"
-
-#ifdef realloc
-#undef realloc
-#endif
-#define realloc	SDL_realloc
-#ifdef snprintf
-#undef snprintf
-#endif
-#define snprintf SDL_snprintf
-#ifdef strdup
-#undef strdup
-#endif
-#define strdup  SDL_strdup
-#ifdef strncpy
-#undef strncpy
-#endif
-#define strncpy SDL_strlcpy
-#ifdef tolower
-#undef tolower
-#endif
-#define tolower SDL_tolower
-#ifdef wcsncpy
-#undef wcsncpy
-#endif
-#define wcsncpy SDL_wcslcpy
-
-#ifndef HAVE_WCSDUP
-#ifdef HAVE__WCSDUP
-#define wcsdup _wcsdup
-#else
-#define wcsdup _dupwcs
-static wchar_t *_dupwcs(const wchar_t *src)
-{
-    wchar_t *dst = NULL;
-    if (src) {
-        size_t len = SDL_wcslen(src) + 1;
-        len *= sizeof(wchar_t);
-        dst = (wchar_t *) malloc(len);
-        if (dst) memcpy(dst, src, len);
-    }
-    return dst;
-}
-#endif
-#endif /* HAVE_WCSDUP */
-
+#define _GNU_SOURCE /* needed for wcsdup() before glibc 2.10 */
+
+/* C */
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <locale.h>
+#include <errno.h>
+
+/* Unix */
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/ioctl.h>
+#include <sys/utsname.h>
+#include <fcntl.h>
+#include <pthread.h>
+#include <wchar.h>
+
+/* GNU / LibUSB */
 #include <libusb.h>
-#ifndef _WIN32
-#define HAVE_SETLOCALE
-#include <locale.h> /* setlocale */
+#if !defined(__ANDROID__) && !defined(NO_ICONV)
+#include <iconv.h>
+#ifndef ICONV_CONST
+#define ICONV_CONST
+#endif
 #endif
 
-#include "../hidapi/hidapi.h"
+#include "hidapi_libusb.h"
 
-#ifdef NAMESPACE
-namespace NAMESPACE
-{
-#endif
+#if defined(__ANDROID__) && __ANDROID_API__ < __ANDROID_API_N__
 
 /* Barrier implementation because Android/Bionic don't have pthread_barrier.
    This implementation came from Brent Priddy and was posted on
    StackOverflow. It is used with his permission. */
-
-typedef struct _SDL_ThreadBarrier
-{
-	SDL_Mutex *mutex;
-	SDL_Condition *cond;
-	Uint32 count;
-	Uint32 trip_count;
-} SDL_ThreadBarrier;
-
-static int SDL_CreateThreadBarrier(SDL_ThreadBarrier *barrier, Uint32 count)
+typedef int pthread_barrierattr_t;
+typedef struct pthread_barrier {
+    pthread_mutex_t mutex;
+    pthread_cond_t cond;
+    int count;
+    int trip_count;
+} pthread_barrier_t;
+
+static int pthread_barrier_init(pthread_barrier_t *barrier, const pthread_barrierattr_t *attr, unsigned int count)
 {
-	SDL_assert(barrier != NULL);
-	SDL_assert(count != 0);
+	if(count == 0) {
+		errno = EINVAL;
+		return -1;
+	}
 
-	barrier->mutex = SDL_CreateMutex();
-	if (barrier->mutex == NULL) {
-		return -1; /* Error set by CreateMutex */
+	if(pthread_mutex_init(&barrier->mutex, 0) < 0) {
+		return -1;
 	}
-	barrier->cond = SDL_CreateCondition();
-	if (barrier->cond == NULL) {
-		return -1; /* Error set by CreateCond */
+	if(pthread_cond_init(&barrier->cond, 0) < 0) {
+		pthread_mutex_destroy(&barrier->mutex);
+		return -1;
 	}
-
 	barrier->trip_count = count;
 	barrier->count = 0;
 
 	return 0;
 }
 
-static void SDL_DestroyThreadBarrier(SDL_ThreadBarrier *barrier)
+static int pthread_barrier_destroy(pthread_barrier_t *barrier)
 {
-	SDL_DestroyCondition(barrier->cond);
-	SDL_DestroyMutex(barrier->mutex);
+	pthread_cond_destroy(&barrier->cond);
+	pthread_mutex_destroy(&barrier->mutex);
+	return 0;
 }
 
-static int SDL_WaitThreadBarrier(SDL_ThreadBarrier *barrier)
+static int pthread_barrier_wait(pthread_barrier_t *barrier)
 {
-	SDL_LockMutex(barrier->mutex);
-	barrier->count += 1;
-	if (barrier->count >= barrier->trip_count) {
+	pthread_mutex_lock(&barrier->mutex);
+	++(barrier->count);
+	if(barrier->count >= barrier->trip_count)
+	{
 		barrier->count = 0;
-		SDL_BroadcastCondition(barrier->cond);
-		SDL_UnlockMutex(barrier->mutex);
+		pthread_cond_broadcast(&barrier->cond);
+		pthread_mutex_unlock(&barrier->mutex);
 		return 1;
 	}
-	SDL_WaitCondition(barrier->cond, barrier->mutex);
-	SDL_UnlockMutex(barrier->mutex);
-	return 0;
+	else
+	{
+		pthread_cond_wait(&barrier->cond, &(barrier->mutex));
+		pthread_mutex_unlock(&barrier->mutex);
+		return 0;
+	}
 }
 
-#if defined(__cplusplus) && !defined(NAMESPACE)
+#endif
+
+#ifdef __cplusplus
 extern "C" {
 #endif
 
@@ -174,38 +146,49 @@ struct hid_device_ {
 	/* Handle to the actual device. */
 	libusb_device_handle *device_handle;
 
+	/* USB Configuration Number of the device */
+	int config_number;
+	/* The interface number of the HID */
+	int interface;
+
+	uint16_t report_descriptor_size;
+
 	/* Endpoint information */
 	int input_endpoint;
 	int output_endpoint;
 	int input_ep_max_packet_size;
 
-	/* The interface number of the HID */
-	int interface;
-	int detached_driver;
-
 	/* Indexes of Strings */
 	int manufacturer_index;
 	int product_index;
 	int serial_index;
+	struct hid_device_info* device_info;
 
 	/* Whether blocking reads are used */
 	int blocking; /* boolean */
 
 	/* Read thread objects */
-	SDL_Thread *thread;
-	SDL_Mutex *mutex; /* Protects input_reports */
-	SDL_Condition *condition;
-	SDL_ThreadBarrier barrier; /* Ensures correct startup sequence */
+	pthread_t thread;
+	pthread_mutex_t mutex; /* Protects input_reports */
+	pthread_cond_t condition;
+	pthread_barrier_t barrier; /* Ensures correct startup sequence */
 	int shutdown_thread;
 	int transfer_loop_finished;
 	struct libusb_transfer *transfer;
 
-	/* Quirks */
-	int skip_output_report_id;
-	int no_output_reports_on_intr_ep;
-
 	/* List of received input reports. */
 	struct input_report *input_reports;
+
+	/* Was kernel driver detached by libusb */
+#ifdef DETACH_KERNEL_DRIVER
+	int is_driver_detached;
+#endif
+};
+
+static struct hid_api_version api_version = {
+	.major = HID_API_VERSION_MAJOR,
+	.minor = HID_API_VERSION_MINOR,
+	.patch = HID_API_VERSION_PATCH
 };
 
 static libusb_context *usb_context = NULL;
@@ -218,9 +201,9 @@ static hid_device *new_hid_device(void)
 	hid_device *dev = (hid_device*) calloc(1, sizeof(hid_device));
 	dev->blocking = 1;
 
-	dev->mutex = SDL_CreateMutex();
-	dev->condition = SDL_CreateCondition();
-	SDL_CreateThreadBarrier(&dev->barrier, 2);
+	pthread_mutex_init(&dev->mutex, NULL);
+	pthread_cond_init(&dev->condition, NULL);
+	pthread_barrier_init(&dev->barrier, NULL, 2);
 
 	return dev;
 }
@@ -228,9 +211,11 @@ static hid_device *new_hid_device(void)
 static void free_hid_device(hid_device *dev)
 {
 	/* Clean up the thread objects */
-	SDL_DestroyThreadBarrier(&dev->barrier);
-	SDL_DestroyCondition(dev->condition);
-	SDL_DestroyMutex(dev->mutex);
+	pthread_barrier_destroy(&dev->barrier);
+	pthread_cond_destroy(&dev->condition);
+	pthread_mutex_destroy(&dev->mutex);
+
+	hid_free_enumeration(dev->device_info);
 
 	/* Free the device itself */
 	free(dev);
@@ -240,10 +225,10 @@ static void free_hid_device(hid_device *dev)
 /*TODO: Implement this function on hidapi/libusb.. */
 static void register_error(hid_device *dev, const char *op)
 {
+
 }
 #endif
 
-#ifdef INVASIVE_GET_USAGE
 /* Get bytes from a HID Report Descriptor.
    Only call with a num_bytes of 0, 1, 2, or 4. */
 static uint32_t get_bytes(uint8_t *rpt, size_t len, size_t num_bytes, size_t cur)
@@ -319,7 +304,7 @@ static int get_usage(uint8_t *report_descriptor, size_t size,
 				/* Can't ever happen since size_code is & 0x3 */
 				data_len = 0;
 				break;
-			}
+			};
 			key_size = 1;
 		}
 
@@ -343,7 +328,6 @@ static int get_usage(uint8_t *report_descriptor, size_t size,
 
 	return -1; /* failure */
 }
-#endif /* INVASIVE_GET_USAGE */
 
 #if defined(__FreeBSD__) && __FreeBSD__ < 10
 /* The libusb version included in FreeBSD < 10 doesn't have this function. In
@@ -363,6 +347,7 @@ static inline int libusb_get_string_descriptor(libusb_device_handle *dev,
 		(LIBUSB_DT_STRING << 8) | descriptor_index,
 		lang_id, data, (uint16_t) length, 1000);
 }
+
 #endif
 
 
@@ -421,16 +406,15 @@ static wchar_t *get_usb_string(libusb_device_handle *dev, uint8_t idx)
 	int len;
 	wchar_t *str = NULL;
 
-#ifndef NO_ICONV
+#if !defined(__ANDROID__) && !defined(NO_ICONV) /* we don't use iconv on Android, or when it is explicitly disabled */
 	wchar_t wbuf[256];
-	SDL_iconv_t ic;
+	/* iconv variables */
+	iconv_t ic;
 	size_t inbytes;
 	size_t outbytes;
 	size_t res;
-	const char *inptr;
+	ICONV_CONST char *inptr;
 	char *outptr;
-#else
-	int i;
 #endif
 
 	/* Determine which language to use. */
@@ -445,12 +429,12 @@ static wchar_t *get_usb_string(libusb_device_handle *dev, uint8_t idx)
 			lang,
 			(unsigned char*)buf,
 			sizeof(buf));
-	if (len < 0)
+	if (len < 2) /* we always skip first 2 bytes */
 		return NULL;
 
-#ifdef NO_ICONV /* original hidapi code for NO_ICONV : */
+#if defined(__ANDROID__) || defined(NO_ICONV)
 
-	/* Bionic does not have wchar_t iconv support, so it
+	/* Bionic does not have iconv support nor wcsdup() function, so it
 	   has to be done manually.  The following code will only work for
 	   code points that can be represented as a single UTF-16 character,
 	   and will incorrectly convert any code points which require more
@@ -459,19 +443,21 @@ static wchar_t *get_usb_string(libusb_device_handle *dev, uint8_t idx)
 	   Skip over the first character (2-bytes).  */
 	len -= 2;
 	str = (wchar_t*) malloc((len / 2 + 1) * sizeof(wchar_t));
+	int i;
 	for (i = 0; i < len / 2; i++) {
 		str[i] = buf[i * 2 + 2] | (buf[i * 2 + 3] << 8);
 	}
 	str[len / 2] = 0x00000000;
 
 #else
+
 	/* buf does not need to be explicitly NULL-terminated because
 	   it is only passed into iconv() which does not need it. */
 
 	/* Initialize iconv. */
-	ic = SDL_iconv_open("WCHAR_T", "UTF-16LE");
-	if (ic == (SDL_iconv_t)-1) {
-		LOG("SDL_iconv_open() failed\n");
+	ic = iconv_open("WCHAR_T", "UTF-16LE");
+	if (ic == (iconv_t)-1) {
+		LOG("iconv_open() failed\n");
 		return NULL;
 	}
 
@@ -481,9 +467,9 @@ static wchar_t *get_usb_string(libusb_device_handle *dev, uint8_t idx)
 	inbytes = len-2;
 	outptr = (char*) wbuf;
 	outbytes = sizeof(wbuf);
-	res = SDL_iconv(ic, &inptr, &inbytes, &outptr, &outbytes);
+	res = iconv(ic, &inptr, &inbytes, &outptr, &outbytes);
 	if (res == (size_t)-1) {
-		LOG("SDL_iconv() failed\n");
+		LOG("iconv() failed\n");
 		goto err;
 	}
 
@@ -496,145 +482,73 @@ static wchar_t *get_usb_string(libusb_device_handle *dev, uint8_t idx)
 	str = wcsdup(wbuf);
 
 err:
-	SDL_iconv_close(ic);
+	iconv_close(ic);
+
 #endif
 
 	return str;
 }
 
-struct usb_string_cache_entry {
-	uint16_t vid;
-	uint16_t pid;
-	wchar_t *vendor;
-	wchar_t *product;
-};
-
-static struct usb_string_cache_entry *usb_string_cache = NULL;
-static size_t usb_string_cache_size = 0;
-static size_t usb_string_cache_insert_pos = 0;
-
-static int usb_string_cache_grow()
+/**
+  Max length of the result: "000-000.000.000.000.000.000.000:000.000" (39 chars).
+  64 is used for simplicity/alignment.
+*/
+static void get_path(char (*result)[64], libusb_device *dev, int config_number, int interface_number)
 {
-	struct usb_string_cache_entry *new_cache;
-	size_t allocSize;
-	size_t new_cache_size;
-
-	new_cache_size = usb_string_cache_size + 8;
-	allocSize = sizeof(struct usb_string_cache_entry) * new_cache_size;
-	new_cache = (struct usb_string_cache_entry *)realloc(usb_string_cache, allocSize);
-	if (!new_cache)
-		return -1;
+	char *str = *result;
 
-	usb_string_cache = new_cache;
-	usb_string_cache_size = new_cache_size;
-
-	return 0;
-}
+	/* Note that USB3 port count limit is 7; use 8 here for alignment */
+	uint8_t port_numbers[8] = {0, 0, 0, 0, 0, 0, 0, 0};
+	int num_ports = libusb_get_port_numbers(dev, port_numbers, 8);
 
-static void usb_string_cache_destroy()
-{
-	size_t i;
-	for (i = 0; i < usb_string_cache_insert_pos; i++) {
-		free(usb_string_cache[i].vendor);
-		free(usb_string_cache[i].product);
+	if (num_ports > 0) {
+		int n = snprintf(str, sizeof("000-000"), "%u-%u", libusb_get_bus_number(dev), port_numbers[0]);
+		for (uint8_t i = 1; i < num_ports; i++) {
+			n += snprintf(&str[n], sizeof(".000"), ".%u", port_numbers[i]);
+		}
+		n += snprintf(&str[n], sizeof(":000.000"), ":%u.%u", (uint8_t)config_number, (uint8_t)interface_number);
+		str[n] = '\0';
+	} else {
+		/* Likely impossible, but check: USB3.0 specs limit number of ports to 7 and buffer size here is 8 */
+		if (num_ports == LIBUSB_ERROR_OVERFLOW) {
+			LOG("make_path() failed. buffer overflow error\n");
+		} else {
+			LOG("make_path() failed. unknown error\n");
+		}
+		str[0] = '\0';
 	}
-	free(usb_string_cache);
-
-	usb_string_cache = NULL;
-	usb_string_cache_size = 0;
-	usb_string_cache_insert_pos = 0;
 }
 
-static struct usb_string_cache_entry *usb_string_cache_insert()
+static char *make_path(libusb_device *dev, int config_number, int interface_number)
 {
-	struct usb_string_cache_entry *new_entry = NULL;
-	if (usb_string_cache_insert_pos >= usb_string_cache_size) {
-		if (usb_string_cache_grow() < 0)
-			return NULL;
-	}
-	new_entry = &usb_string_cache[usb_string_cache_insert_pos];
-	usb_string_cache_insert_pos++;
-	return new_entry;
+	char str[64];
+	get_path(&str, dev, config_number, interface_number);
+	return strdup(str);
 }
 
-static int usb_string_can_cache(uint16_t vid, uint16_t pid)
+HID_API_EXPORT const struct hid_api_version* HID_API_CALL hid_version(void)
 {
-	if (!vid || !pid) {
-		/* We can't cache these, they aren't unique */
-		return 0;
-	}
-
-	if (vid == 0x0f0d && pid == 0x00dc) {
-		/* HORI reuses this VID/PID for many different products */
-		return 0;
-	}
-
-	/* We can cache these strings */
-	return 1;
+	return &api_version;
 }
 
-static const struct usb_string_cache_entry *usb_string_cache_find(struct libusb_device_descriptor *desc, struct libusb_device_handle *handle)
+HID_API_EXPORT const char* HID_API_CALL hid_version_str(void)
 {
-	struct usb_string_cache_entry *entry = NULL;
-	size_t i;
-
-	/* Search for existing string cache entry */
-	for (i = 0; i < usb_string_cache_insert_pos; i++) {
-		entry = &usb_string_cache[i];
-		if (entry->vid != desc->idVendor)
-			continue;
-		if (entry->pid != desc->idProduct)
-			continue;
-		return entry;
-	}
-
-	/* Not found, create one. */
-	entry = usb_string_cache_insert();
-	if (!entry)
-		return NULL;
-
-	entry->vid = desc->idVendor;
-	entry->pid = desc->idProduct;
-	if (desc->iManufacturer > 0)
-		entry->vendor = get_usb_string(handle, desc->iManufacturer);
-	else
-		entry->vendor = NULL;
-	if (desc->iProduct > 0)
-		entry->product = get_usb_string(handle, desc->iProduct);
-	else
-		entry->product = NULL;
-
-	return entry;
+	return HID_API_VERSION_STR;
 }
 
-static char *make_path(libusb_device *dev, int interface_number)
-{
-	char str[64];
-	snprintf(str, sizeof(str), "%04x:%04x:%02x",
-		libusb_get_bus_number(dev),
-		libusb_get_device_address(dev),
-		interface_number);
-	str[sizeof(str)-1] = '\0';
-
-	return strdup(str);
-}
-
-
 int HID_API_EXPORT hid_init(void)
 {
 	if (!usb_context) {
+		const char *locale;
+
 		/* Init Libusb */
 		if (libusb_init(&usb_context))
 			return -1;
 
-#ifdef HAVE_SETLOCALE
 		/* Set the locale if it's not set. */
-		{
-			const char *locale = setlocale(LC_CTYPE, NULL);
-			if (!locale)
-				setlocale(LC_CTYPE, "");
-		}
-#endif
+		locale = setlocale(LC_CTYPE, NULL);
+		if (!locale)
+			setlocale(LC_CTYPE, "");
 	}
 
 	return 0;
@@ -642,8 +556,6 @@ int HID_API_EXPORT hid_init(void)
 
 int HID_API_EXPORT hid_exit(void)
 {
-	usb_string_cache_destroy();
-
 	if (usb_context) {
 		libusb_exit(usb_context);
 		usb_context = NULL;
@@ -652,116 +564,191 @@ int HID_API_EXPORT hid_exit(void)
 	return 0;
 }
 
-static int is_xbox360(unsigned short vendor_id, const struct libusb_interface_descriptor *intf_desc)
+static int hid_get_report_descriptor_libusb(libusb_device_handle *handle, int interface_num, uint16_t expected_report_descriptor_size, unsigned char *buf, size_t buf_size)
 {
-	static const int XB360_IFACE_SUBCLASS = 93;
-	static const int XB360_IFACE_PROTOCOL = 1; /* Wired */
-	static const int XB360W_IFACE_PROTOCOL = 129; /* Wireless */
-	static const int SUPPORTED_VENDORS[] = {
-		0x0079, /* GPD Win 2 */
-		0x044f, /* Thrustmaster */
-		0x045e, /* Microsoft */
-		0x046d, /* Logitech */
-		0x056e, /* Elecom */
-		0x06a3, /* Saitek */
-		0x0738, /* Mad Catz */
-		0x07ff, /* Mad Catz */
-		0x0e6f, /* PDP */
-		0x0f0d, /* Hori */
-		0x1038, /* SteelSeries */
-		0x11c9, /* Nacon */
-		0x12ab, /* Unknown */
-		0x1430, /* RedOctane */
-		0x146b, /* BigBen */
-		0x1532, /* Razer Sabertooth */
-		0x15e4, /* Numark */
-		0x162e, /* Joytech */
-		0x1689, /* Razer Onza */
-		0x1949, /* Lab126, Inc. */
-		0x1bad, /* Harmonix */
-		0x20d6, /* PowerA */
-		0x24c6, /* PowerA */
-		0x2c22, /* Qanba */
-		0x2dc8, /* 8BitDo */
-        0x9886, /* ASTRO Gaming */
-	};
-
-	if (intf_desc->bInterfaceClass == LIBUSB_CLASS_VENDOR_SPEC &&
-	    intf_desc->bInterfaceSubClass == XB360_IFACE_SUBCLASS &&
-	    (intf_desc->bInterfaceProtocol == XB360_IFACE_PROTOCOL ||
-	     intf_desc->bInterfaceProtocol == XB360W_IFACE_PROTOCOL)) {
-		int i;
-		for (i = 0; i < sizeof(SUPPORTED_VENDORS)/sizeof(SUPPORTED_VENDORS[0]); ++i) {
-			if (vendor_id == SUPPORTED_VENDORS[i]) {
-				return 1;
-			}
-		}
+	unsigned char tmp[HID_API_MAX_REPORT_DESCRIPTOR_SIZE];
+
+	if (expected_report_descriptor_size > HID_API_MAX_REPORT_DESCRIPTOR_SIZE)
+		expected_report_descriptor_size = HID_API_MAX_REPORT_DESCRIPTOR_SIZE;
+
+	/* Get the HID Report Descriptor.
+	   See USB HID Specificatin, sectin 7.1.1
+	*/
+	int res = libusb_control_transfer(handle, LIBUSB_ENDPOINT_IN|LIBUSB_RECIPIENT_INTERFACE, LIBUSB_REQUEST_GET_DESCRIPTOR, (LIBUSB_DT_REPORT << 8), interface_num, tmp, expected_report_descriptor_size, 5000);
+	if (res < 0) {
+		LOG("libusb_control_transfer() for getting the HID Report descriptor failed with %d: %s\n", res, libusb_error_name(res));
+		return -1;
 	}
-	return 0;
+
+	if (res > (int)buf_size)
+		res = (int)buf_size;
+
+	memcpy(buf, tmp, (size_t)res);
+	return res;
 }
 
-static int is_xboxone(unsigned short vendor_id, const struct libusb_interface_descriptor *intf_desc)
+/**
+ * Requires an opened device with *claimed interface*.
+ */
+static void fill_device_info_usage(struct hid_device_info *cur_dev, libusb_device_handle *handle, int interface_num, uint16_t expected_report_descriptor_size)
 {
-	static const int XB1_IFACE_SUBCLASS = 71;
-	static const int XB1_IFACE_PROTOCOL = 208;
-	static const int SUPPORTED_VENDORS[] = {
-		0x044f, /* Thrustmaster */
-		0x045e, /* Microsoft */
-		0x0738, /* Mad Catz */
-		0x0e6f, /* PDP */
-		0x0f0d, /* Hori */
-        0x10f5, /* Turtle Beach */
-		0x1532, /* Razer Wildcat */
-		0x20d6, /* PowerA */
-		0x24c6, /* PowerA */
-		0x2dc8, /* 8BitDo */
-		0x2e24, /* Hyperkin */
-        0x3537, /* GameSir */
-	};
-
-	if (intf_desc->bInterfaceNumber == 0 &&
-	    intf_desc->bInterfaceClass == LIBUSB_CLASS_VENDOR_SPEC &&
-	    intf_desc->bInterfaceSubClass == XB1_IFACE_SUBCLASS &&
-	    intf_desc->bInterfaceProtocol == XB1_IFACE_PROTOCOL) {
-		int i;
-		for (i = 0; i < sizeof(SUPPORTED_VENDORS)/sizeof(SUPPORTED_VENDORS[0]); ++i) {
-			if (vendor_id == SUPPORTED_VENDORS[i]) {
-				return 1;
-			}
-		}
+	unsigned char hid_report_descriptor[HID_API_MAX_REPORT_DESCRIPTOR_SIZE];
+	unsigned short page = 0, usage = 0;
+
+	int res = hid_get_report_descriptor_libusb(handle, interface_num, expected_report_descriptor_size, hid_report_descriptor, sizeof(hid_report_descriptor));
+	if (res >= 0) {
+		/* Parse the usage and usage page
+		   out of the report descriptor. */
+		get_usage(hid_report_descriptor, res,  &page, &usage);
 	}
-	return 0;
+
+	cur_dev->usage_page = page;
+	cur_dev->usage = usage;
 }
 
-static int should_enumerate_interface(unsigned short vendor_id, const struct libusb_interface_descriptor *intf_desc)
+#ifdef INVASIVE_GET_USAGE
+static void invasive_fill_device_info_usage(struct hid_device_info *cur_dev, libusb_device_handle *handle, int interface_num, uint16_t report_descriptor_size)
 {
-	//printf("Checking interface 0x%x %d/%d/%d/%d\n", vendor_id, intf_desc->bInterfaceNumber, intf_desc->bInterfaceClass, intf_desc->bInterfaceSubClass, intf_desc->bInterfaceProtocol);
+	int res = 0;
 
-	if (intf_desc->bInterfaceClass == LIBUSB_CLASS_HID)
-		return 1;
+#ifdef DETACH_KERNEL_DRIVER
+	int detached = 0;
+	/* Usage Page and Usage */
+	res = libusb_kernel_driver_active(handle, interface_num);
+	if (res == 1) {
+		res = libusb_detach_kernel_driver(handle, interface_num);
+		if (res < 0)
+			LOG("Couldn't detach kernel driver, even though a kernel driver was attached.\n");
+		else
+			detached = 1;
+	}
+#endif
 
-	/* Also enumerate Xbox 360 controllers */
-	if (is_xbox360(vendor_id, intf_desc))
-		return 1;
+	res = libusb_claim_interface(handle, interface_num);
+	if (res >= 0) {
+		fill_device_info_usage(cur_dev, handle, interface_num, report_descriptor_size);
 
-	/* Also enumerate Xbox One controllers */
-	if (is_xboxone(vendor_id, intf_desc))
-		return 1;
+		/* Release the interface */
+		res = libusb_release_interface(handle, interface_num);
+		if (res < 0)
+			LOG("Can't release the interface.\n");
+	}
+	else
+		LOG("Can't claim interface: (%d) %s\n", res, libusb_error_name(res));
 
-	return 0;
+#ifdef DETACH_KERNEL_DRIVER
+	/* Re-attach kernel driver if necessary. */
+	if (detached) {
+		res = libusb_attach_kernel_driver(handle, interface_num);
+		if (res < 0)
+			LOG("Couldn't re-attach kernel driver.\n");
+	}
+#endif
+}
+#endif /* INVASIVE_GET_USAGE */
+
+/**
+ * Create and fill up most of hid_device_info fields.
+ * usage_page/usage is not filled up.
+ */
+static struct hid_device_info * create_device_info_for_device(libusb_device *device, libusb_device_handle *handle, struct libusb_device_descriptor *desc, int config_number, int interface_num)
+{
+	struct hid_device_info *cur_dev = calloc(1, sizeof(struct hid_device_info));
+	if (cur_dev == NULL) {
+		return NULL;
+	}
+
+	/* VID/PID */
+	cur_dev->vendor_id = desc->idVendor;
+	cur_dev->product_id = desc->idProduct;
+
+	cur_dev->release_number = desc->bcdDevice;
+
+	cur_dev->interface_number = interface_num;
+
+	cur_dev->bus_type = HID_API_BUS_USB;
+
+	cur_dev->path = make_path(device, config_number, interface_num);
+
+	if (!handle) {
+		return cur_dev;
+	}
+
+	if (desc->iSerialNumber > 0)
+		cur_dev->serial_number = get_usb_string(handle, desc->iSerialNumber);
+
+	/* Manufacturer and Product strings */
+	if (desc->iManufacturer > 0)
+		cur_dev->manufacturer_string = get_usb_string(handle, desc->iManufacturer);
+	if (desc->iProduct > 0)
+		cur_dev->product_string = get_usb_string(handle, desc->iProduct);
+
+	return cur_dev;
+}
+
+static uint16_t get_report_descriptor_size_from_interface_descriptors(const struct libusb_interface_descriptor *intf_desc)
+{
+	int i = 0;
+	int found_hid_report_descriptor = 0;
+	uint16_t result = HID_API_MAX_REPORT_DESCRIPTOR_SIZE;
+	const unsigned char *extra = intf_desc->extra;
+	int extra_length = intf_desc->extra_length;
+
+	/*
+	 "extra" contains a HID descriptor
+	 See section 6.2.1 of HID 1.1 specification.
+	*/
+
+	while (extra_length >= 2) { /* Descriptor header: bLength/bDescriptorType */
+		if (extra[1] == LIBUSB_DT_HID) { /* bDescriptorType */
+			if (extra_length < 6) {
+				LOG("Broken HID descriptor: not enough data\n");
+				break;
+			}
+			unsigned char bNumDescriptors = extra[5];
+			if (extra_length < (6 + 3 * bNumDescriptors)) {
+				LOG("Broken HID descriptor: not enough data for Report metadata\n");
+				break;
+			}
+			for (i = 0; i < bNumDescriptors; i++) {
+				if (extra[6 + 3 * i] == LIBUSB_DT_REPORT) {
+					result = (uint16_t)extra[6 + 3 * i + 2] << 8 | extra[6 + 3 * i + 1];
+					found_hid_report_descriptor = 1;
+					break;
+				}
+			}
+
+			if (!found_hid_report_descriptor) {
+				/* We expect to find exactly 1 HID descriptor (LIBUSB_DT_HID)
+				   which should contain exactly one HID Report Descriptor metadata (LIBUSB_DT_REPORT). */
+				LOG("Broken HID descriptor: missing Report descriptor\n");
+			}
+			break;
+		}
+
+		if (extra[0] == 0) { /* bLength */
+			LOG("Broken HID Interface descriptors: zero-sized descriptor\n");
+			break;
+		}
+
+		/* Iterate over to the next Descriptor */
+		extra_length -= extra[0];
+		extra += extra[0];
+	}
+
+	return result;
 }
 
 struct hid_device_info  HID_API_EXPORT *hid_enumerate(unsigned short vendor_id, unsigned short product_id)
 {
 	libusb_device **devs;
 	libusb_device *dev;
-	libusb_device_handle *handle;
+	libusb_device_handle *handle = NULL;
 	ssize_t num_devs;
 	int i = 0;
 
 	struct hid_device_info *root = NULL; /* return object */
 	struct hid_device_info *cur_dev = NULL;
-	const char *hint = SDL_GetHint(SDL_HINT_HIDAPI_IGNORE_DEVICES);
 
 	if(hid_init() < 0)
 		return NULL;
@@ -773,85 +760,48 @@ struct hid_device_info  HID_API_EXPORT *hid_enumerate(unsigned short vendor_id,
 		struct libusb_device_descriptor desc;
 		struct libusb_config_descriptor *conf_desc = NULL;
 		int j, k;
-		int interface_num = 0;
 
 		int res = libusb_get_device_descriptor(dev, &desc);
 		unsigned short dev_vid = desc.idVendor;
 		unsigned short dev_pid = desc.idProduct;
 
-		/* See if there are any devices we should skip in enumeration */
-		if (hint) {
-			char vendor_match[16], product_match[16];
-			SDL_snprintf(vendor_match, sizeof(vendor_match), "0x%.4x/0x0000", dev_vid);
-			SDL_snprintf(product_match, sizeof(product_match), "0x%.4x/0x%.4x", dev_vid, dev_pid);
-			if (SDL_strcasestr(hint, vendor_match) || SDL_strcasestr(hint, product_match)) {
-				continue;
-			}
+		if ((vendor_id != 0x0 && vendor_id != dev_vid) ||
+		    (product_id != 0x0 && product_id != dev_pid)) {
+			continue;
 		}
 
 		res = libusb_get_active_config_descriptor(dev, &conf_desc);
 		if (res < 0)
 			libusb_get_config_descriptor(dev, 0, &conf_desc);
 		if (conf_desc) {
-
 			for (j = 0; j < conf_desc->bNumInterfaces; j++) {
 				const struct libusb_interface *intf = &conf_desc->interface[j];
 				for (k = 0; k < intf->num_altsetting; k++) {
 					const struct libusb_interface_descriptor *intf_desc;
 					intf_desc = &intf->altsetting[k];
-					if (should_enumerate_interface(dev_vid, intf_desc)) {
-						interface_num = intf_desc->bInterfaceNumber;
-
-						/* Check the VID/PID against the arguments */
-						if ((vendor_id == 0x0 || vendor_id == dev_vid) &&
-						    (product_id == 0x0 || product_id == dev_pid)) {
-							res = libusb_open(dev, &handle);
-
-							if (res >= 0) {
-								struct hid_device_info *tmp;
-								const struct usb_string_cache_entry *string_cache;
-
-								/* VID/PID match. Create the record. */
-								tmp = (struct hid_device_info*) calloc(1, sizeof(struct hid_device_info));
-								if (cur_dev) {
-									cur_dev->next = tmp;
-								}
-								else {
-									root = tmp;
-								}
-								cur_dev = tmp;
-
-								/* Fill out the record */
-								cur_dev->next = NULL;
-								cur_dev->path = make_path(dev, interface_num);
-
-								/* Serial Number */
-								if (desc.iSerialNumber > 0)
-									cur_dev->serial_number =
-										get_usb_string(handle, desc.iSerialNumber);
-
-								/* Manufacturer and Product strings */
-								if (usb_string_can_cache(dev_vid, dev_pid)) {
-									string_cache = usb_string_cache_find(&desc, handle);
-									if (string_cache) {
-										if (string_cache->vendor) {
-											cur_dev->manufacturer_string = wcsdup(string_cache->vendor);
-										}
-										if (string_cache->product) {
-											cur_dev->product_string = wcsdup(string_cache->product);
-										}
-									}
-								} else {
-									if (desc.iManufacturer > 0)
-										cur_dev->manufacturer_string =
-											get_usb_string(handle, desc.iManufacturer);
-									if (desc.iProduct > 0)
-										cur_dev->product_string =
-											get_usb_string(handle, desc.iProduct);
-								}
+					if (intf_desc->bInterfaceClass == LIBUSB_CLASS_HID) {
+						struct hid_device_info *tmp;
+
+						res = libusb_open(dev, &handle);
+
+#ifdef __ANDROID__
+						if (handle) {
+							/* There is (a potential) libusb Android backend, in which
+							   device descriptor is not accurate up until the device is opened.
+							   https://github.com/libusb/libusb/pull/874#discussion_r632801373
+							   A workaround is to re-read the descriptor again.
+							   Even if it is not going to be accepted into libusb master,
+							   having it here won't do any harm, since reading the device descriptor
+							   is as cheap as copy 18 bytes of data. */
+							libusb_get_device_descriptor(dev, &desc);
+						}
+#endif
 
+						tmp = create_device_info_for_device(dev, handle, &desc, conf_desc->bConfigurationValue, intf_desc->bInterfaceNumber);
+						if (tmp) {
 #ifdef INVASIVE_GET_USAGE
-{
+							/* TODO: have a runtime check for this section. */
+
 							/*
 							This section is removed because it is too
 							invasive on the system. Getting a Usage Page
@@ -864,72 +814,28 @@ struct hid_device_info  HID_API_EXPORT *hid_enumerate(unsigned short vendor_id,
 							app has them claimed) and the re-attachment of
 							the driver will sometimes change /dev entry names.
 							It is for these reasons that this section is
-							#if 0. For composite devices, use the interface
+							optional. For composite devices, use the interface
 							field in the hid_device_info struct to distinguish
 							between interfaces. */
-								unsigned char data[256];
-#ifdef DETACH_KERNEL_DRIVER
-								int detached = 0;
-								/* Usage Page and Usage */
-								res = libusb_kernel_driver_active(handle, interface_num);
-								if (res == 1) {
-									res = libusb_detach_kernel_driver(handle, interface_num);
-									if (res < 0)
-										LOG("Couldn't detach kernel driver, even though a kernel driver was attached.");
-									else
-										detached = 1;
-								}
-#endif
-								res = libusb_claim_interface(handle, interface_num);
-								if (res >= 0) {
-									/* Get the HID Report Descriptor. */
-									res = libusb_control_transfer(handle, LIBUSB_ENDPOINT_IN|LIBUSB_RECIPIENT_INTERFACE, LIBUSB_REQUEST_GET_DESCRIPTOR, (LIBUSB_DT_REPORT << 8)|interface_num, 0, data, sizeof(data), 5000);
-									if (res >= 0) {
-										unsigned short page=0, usage=0;
-										/* Parse the usage and usage page
-										   out of the report descriptor. */
-										get_usage(data, res,  &page, &usage);
-										cur_dev->usage_page = page;
-										cur_dev->usage = usage;
-									}
-									else
-										LOG("libusb_control_transfer() for getting the HID report failed with %d\n", res);
-
-									/* Release the interface */
-									res = libusb_release_interface(handle, interface_num);
-									if (res < 0)
-										LOG("Can't release the interface.\n");
-								}
-								else
-									LOG("Can't claim interface %d\n", res);
-#ifdef DETACH_KERNEL_DRIVER
-								/* Re-attach kernel driver if necessary. */
-								if (detached) {
-									res = libusb_attach_kernel_driver(handle, interface_num);
-									if (res < 0)
-										LOG("Couldn't re-attach kernel driver.\n");
-								}
-#endif
-}
-#endif /* INVASIVE_GET_USAGE */
-
-								libusb_close(handle);
-
-								/* VID/PID */
-								cur_dev->vendor_id = dev_vid;
-								cur_dev->product_id = dev_pid;
+							if (handle) {
+								uint16_t report_descriptor_size = get_report_descriptor_size_from_interface_descriptors(intf_desc);
 
-								/* Release Number */
-								cur_dev->release_number = desc.bcdDevice;
+								invasive_fill_device_info_usage(tmp, handle, intf_desc->bInterfaceNumber, report_descriptor_size);
+							}
+#endif /* INVASIVE_GET_USAGE */
 
-								/* Interface Number */
-								cur_dev->interface_number = interface_num;
-								cur_dev->interface_class = intf_desc->bInterfaceClass;
-								cur_dev->interface_subclass = intf_desc->bInterfaceSubClass;
-								cur_dev->interface_protocol = intf_desc->bInterfaceProtocol;
+							if (cur_dev) {
+								cur_dev->next = tmp;
+							}
+							else {
+								root = tmp;
+							}
+							cur_dev = tmp;
+						}
 
-							} else
-								LOG("Can't open device 0x%.4x/0x%.4x during enumeration: %d\n", dev_vid, dev_pid, res);
+						if (res >= 0) {
+							libusb_close(handle);
+							handle = NULL;
 						}
 					}
 				} /* altsettings */
@@ -985,7 +891,7 @@ hid_device * hid_open(unsigned short vendor_id, unsigned short product_id, const
 
 	if (path_to_open) {
 		/* Open the device */
-		handle = hid_open_path(path_to_open, 0);
+		handle = hid_open_path(path_to_open);
 	}
 
 	hid_free_enumeration(devs);
@@ -993,9 +899,9 @@ hid_device * hid_open(unsigned short vendor_id, unsigned short product_id, const
 	return handle;
 }
 
-static void LIBUSB_CALL read_callback(struct libusb_transfer *transfer)
+static void read_callback(struct libusb_transfer *transfer)
 {
-	hid_device *dev = (hid_device *)transfer->user_data;
+	hid_device *dev = transfer->user_data;
 	int res;
 
 	if (transfer->status == LIBUSB_TRANSFER_COMPLETED) {
@@ -1006,13 +912,13 @@ static void LIBUSB_CALL read_callback(struct libusb_transfer *transfer)
 		rpt->len = transfer->actual_length;
 		rpt->next = NULL;
 
-		SDL_LockMutex(dev->mutex);
+		pthread_mutex_lock(&dev->mutex);
 
 		/* Attach the new report object to the end of the list. */
 		if (dev->input_reports == NULL) {
 			/* The list is empty. Put it at the root. */
 			dev->input_reports = rpt;
-			SDL_SignalCondition(dev->condition);
+			pthread_cond_signal(&dev->condition);
 		}
 		else {
 			/* Find the end of the list and attach. */
@@ -1031,7 +937,7 @@ static void LIBUSB_CALL read_callback(struct libusb_transfer *transfer)
 				return_data(dev, NULL, 0);
 			}
 		}
-		SDL_UnlockMutex(dev->mutex);
+		pthread_mutex_unlock(&dev->mutex);
 	}
 	else if (transfer->status == LIBUSB_TRANSFER_CANCELLED) {
 		dev->shutdown_thread = 1;
@@ -1054,17 +960,17 @@ static void LIBUSB_CALL read_callback(struct libusb_transfer *transfer)
 	/* Re-submit the transfer object. */
 	res = libusb_submit_transfer(transfer);
 	if (res != 0) {
-		LOG("Unable to submit URB. libusb error code: %d\n", res);
+		LOG("Unable to submit URB: (%d) %s\n", res, libusb_error_name(res));
 		dev->shutdown_thread = 1;
 		dev->transfer_loop_finished = 1;
 	}
 }
 
 
-static int SDLCALL read_thread(void *param)
+static void *read_thread(void *param)
 {
 	int res;
-	hid_device *dev = (hid_device *)param;
+	hid_device *dev = param;
 	uint8_t *buf;
 	const size_t length = dev->input_ep_max_packet_size;
 
@@ -1090,14 +996,14 @@ static int SDLCALL read_thread(void *param)
 	}
 
 	/* Notify the main thread that the read thread is up and running. */
-	SDL_WaitThreadBarrier(&dev->barrier);
+	pthread_barrier_wait(&dev->barrier);
 
 	/* Handle all the events. */
 	while (!dev->shutdown_thread) {
 		res = libusb_handle_events(usb_context);
 		if (res < 0) {
 			/* There was an error. */
-			LOG("read_thread(): libusb reports error # %d\n", res);
+			LOG("read_thread(): (%d) %s\n", res, libusb_error_name(res));
 
 			/* Break out of this loop only on fatal error.*/
 			if (res != LIBUSB_ERROR_BUSY &&
@@ -1122,9 +1028,9 @@ static int SDLCALL read_thread(void *param)
 	   make sure that a thread which is about to go to sleep waiting on
 	   the condition actually will go to sleep before the condition is
 	   signaled. */
-	SDL_LockMutex(dev->mutex);
-	SDL_BroadcastCondition(dev->condition);
-	SDL_UnlockMutex(dev->mutex);
+	pthread_mutex_lock(&dev->mutex);
+	pthread_cond_broadcast(&dev->condition);
+	pthread_mutex_unlock(&dev->mutex);
 
 	/* The dev->transfer->buffer and dev->transfer objects are cleaned up
 	   in hid_close(). They are not cleaned up here because this thread
@@ -1134,88 +1040,109 @@ static int SDLCALL read_thread(void *param)
 	   since hid_close() calls libusb_cancel_transfer(), on these objects,
 	   they can not be cleaned up here. */
 
-	return 0;
+	return NULL;
 }
 
-static void init_xbox360(libusb_device_handle *device_handle, unsigned short idVendor, unsigned short idProduct, struct libusb_config_descriptor *conf_desc)
-{
-	if ((idVendor == 0x05ac && idProduct == 0x055b) /* Gamesir-G3w */ ||
-	    idVendor == 0x0f0d /* Hori Xbox controllers */) {
-		unsigned char data[20];
-
-		/* The HORIPAD FPS for Nintendo Switch requires this to enable input reports.
-		   This VID/PID is also shared with other HORI controllers, but they all seem
-		   to be fine with this as well.
-		 */
-		libusb_control_transfer(device_handle, 0xC1, 0x01, 0x100, 0x0, data, sizeof(data), 100);
-	}
-}
 
-static void init_xboxone(libusb_device_handle *device_handle, unsigned short idVendor, unsigned short idProduct, struct libusb_config_descriptor *conf_desc)
+static int hidapi_initialize_device(hid_device *dev, int config_number, const struct libusb_interface_descriptor *intf_desc)
 {
-	static const int VENDOR_MICROSOFT = 0x045e;
-	static const int XB1_IFACE_SUBCLASS = 71;
-	static const int XB1_IFACE_PROTOCOL = 208;
-	int j, k, res;
+	int i =0;
+	int res = 0;
+	struct libusb_device_descriptor desc;
+	libusb_get_device_descriptor(libusb_get_device(dev->device_handle), &desc);
 
-	for (j = 0; j < conf_desc->bNumInterfaces; j++) {
-		const struct libusb_interface *intf = &conf_desc->interface[j];
-		for (k = 0; k < intf->num_altsetting; k++) {
-			const struct libusb_interface_descriptor *intf_desc = &intf->altsetting[k];
-			if (intf_desc->bInterfaceClass == LIBUSB_CLASS_VENDOR_SPEC &&
-			    intf_desc->bInterfaceSubClass == XB1_IFACE_SUBCLASS &&
-			    intf_desc->bInterfaceProtocol == XB1_IFACE_PROTOCOL) {
-				int bSetAlternateSetting = 0;
-
-				/* Newer Microsoft Xbox One controllers have a high speed alternate setting */
-				if (idVendor == VENDOR_MICROSOFT &&
-				    intf_desc->bInterfaceNumber == 0 && intf_desc->bAlternateSetting == 1) {
-					bSetAlternateSetting = 1;
-				} else if (intf_desc->bInterfaceNumber != 0 && intf_desc->bAlternateSetting == 0) {
-					bSetAlternateSetting = 1;
-				}
-
-				if (bSetAlternateSetting) {
-					res = libusb_claim_interface(device_handle, intf_desc->bInterfaceNumber);
-					if (res < 0) {
-						LOG("can't claim interface %d: %d\n", intf_desc->bInterfaceNumber, res);
-						continue;
-					}
-
-					LOG("Setting alternate setting for VID/PID 0x%x/0x%x interface %d to %d\n",  idVendor, idProduct, intf_desc->bInterfaceNumber, intf_desc->bAlternateSetting);
+#ifdef DETACH_KERNEL_DRIVER
+	/* Detach the kernel driver, but only if the
+	   device is managed by the kernel */
+	dev->is_driver_detached = 0;
+	if (libusb_kernel_driver_active(dev->device_handle, intf_desc->bInterfaceNumber) == 1) {
+		res = libusb_detach_kernel_driver(dev->device_handle, intf_desc->bInterfaceNumber);
+		if (res < 0) {
+			LOG("Unable to detach Kernel Driver: (%d) %s\n", res, libusb_error_name(res));
+			return 0;
+		}
+		else {
+			dev->is_driver_detached = 1;
+			LOG("Driver successfully detached from kernel.\n");
+		}
+	}
+#endif
+	res = libusb_claim_interface(dev->device_handle, intf_desc->bInterfaceNumber);
+	if (res < 0) {
+		LOG("can't claim interface %d: (%d) %s\n", intf_desc->bInterfaceNumber, res, libusb_error_name(res));
 
-					res = libusb_set_interface_alt_setting(device_handle, intf_desc->bInterfaceNumber, intf_desc->bAlternateSetting);
-					if (res < 0) {
-						LOG("xbox init: can't set alt setting %d: %d\n", intf_desc->bInterfaceNumber, res);
-					}
+#ifdef DETACH_KERNEL_DRIVER
+		if (dev->is_driver_detached) {
+			res = libusb_attach_kernel_driver(dev->device_handle, intf_desc->bInterfaceNumber);
+			if (res < 0)
+				LOG("Failed to reattach the driver to kernel: (%d) %s\n", res, libusb_error_name(res));
+		}
+#endif
+		return 0;
+	}
 
-					libusb_release_interface(device_handle, intf_desc->bInterfaceNumber);
-				}
-			}
+	/* Store off the string descriptor indexes */
+	dev->manufacturer_index = desc.iManufacturer;
+	dev->product_index      = desc.iProduct;
+	dev->serial_index       = desc.iSerialNumber;
+
+	/* Store off the USB information */
+	dev->config_number = config_number;
+	dev->interface = intf_desc->bInterfaceNumber;
+
+	dev->report_descriptor_size = get_report_descriptor_size_from_interface_descriptors(intf_desc);
+
+	dev->input_endpoint = 0;
+	dev->input_ep_max_packet_size = 0;
+	dev->output_endpoint = 0;
+
+	/* Find the INPUT and OUTPUT endpoints. An
+	   OUTPUT endpoint is not required. */
+	for (i = 0; i < intf_desc->bNumEndpoints; i++) {
+		const struct libusb_endpoint_descriptor *ep
+			= &intf_desc->endpoint[i];
+
+		/* Determine the type and direction of this
+		   endpoint. */
+		int is_interrupt =
+			(ep->bmAttributes & LIBUSB_TRANSFER_TYPE_MASK)
+		      == LIBUSB_TRANSFER_TYPE_INTERRUPT;
+		int is_output =
+			(ep->bEndpointAddress & LIBUSB_ENDPOINT_DIR_MASK)
+		      == LIBUSB_ENDPOINT_OUT;
+		int is_input =
+			(ep->bEndpointAddress & LIBUSB_ENDPOINT_DIR_MASK)
+		      == LIBUSB_ENDPOINT_IN;
+
+		/* Decide whether to use it for input or output. */
+		if (dev->input_endpoint == 0 &&
+		    is_interrupt && is_input) {
+			/* Use this endpoint for INPUT */
+			dev->input_endpoint = ep->bEndpointAddress;
+			dev->input_ep_max_packet_size = ep->wMaxPacketSize;
+		}
+		if (dev->output_endpoint == 0 &&
+		    is_interrupt && is_output) {
+			/* Use this endpoint for OUTPUT */
+			dev->output_endpoint = ep->bEndpointAddress;
 		}
 	}
-}
 
-static void calculate_device_quirks(hid_device *dev, unsigned short idVendor, unsigned short idProduct)
-{
-	static const int VENDOR_SONY = 0x054c;
-	static const int PRODUCT_PS3_CONTROLLER = 0x0268;
-	static const int PRODUCT_NAVIGATION_CONTROLLER = 0x042f;
+	pthread_create(&dev->thread, NULL, read_thread, dev);
 
-	if (idVendor == VENDOR_SONY &&
-	    (idProduct == PRODUCT_PS3_CONTROLLER || idProduct == PRODUCT_NAVIGATION_CONTROLLER)) {
-		dev->skip_output_report_id = 1;
-		dev->no_output_reports_on_intr_ep = 1;
-	}
+	/* Wait here for the read thread to be initialized. */
+	pthread_barrier_wait(&dev->barrier);
+	return 1;
 }
 
-hid_device * HID_API_EXPORT hid_open_path(const char *path, int bExclusive)
+
+hid_device * HID_API_EXPORT hid_open_path(const char *path)
 {
 	hid_device *dev = NULL;
 
-	libusb_device **devs;
-	libusb_device *usb_dev;
-	int res;
+	libusb_device **devs = NULL;
+	libusb_device *usb_dev = NULL;
+	int res = 0;
 	int d = 0;
 	int good_open = 0;
 
@@ -1225,129 +1152,36 @@ hid_device * HID_API_EXPORT hid_open_path(const char *path, int bExclusive)
 	dev = new_hid_device();
 
 	libusb_get_device_list(usb_context, &devs);
-	while ((usb_dev = devs[d++]) != NULL) {
-		struct libusb_device_descriptor desc;
+	while ((usb_dev = devs[d++]) != NULL && !good_open) {
 		struct libusb_config_descriptor *conf_desc = NULL;
-		int i,j,k;
+		int j,k;
 
-		libusb_get_device_descriptor(usb_dev, &desc);
-
-		res = libusb_get_active_config_descriptor(usb_dev, &conf_desc);
-		if (res < 0)
-			libusb_get_config_descriptor(usb_dev, 0, &conf_desc);
-		if (!conf_desc)
+		if (libusb_get_active_config_descriptor(usb_dev, &conf_desc) < 0)
 			continue;
 		for (j = 0; j < conf_desc->bNumInterfaces && !good_open; j++) {
 			const struct libusb_interface *intf = &conf_desc->interface[j];
 			for (k = 0; k < intf->num_altsetting && !good_open; k++) {
-				const struct libusb_interface_descriptor *intf_desc;
-				intf_desc = &intf->altsetting[k];
-				if (should_enumerate_interface(desc.idVendor, intf_desc)) {
-					char *dev_path = make_path(usb_dev, intf_desc->bInterfaceNumber);
+				const struct libusb_interface_descriptor *intf_desc = &intf->altsetting[k];
+				if (intf_desc->bInterfaceClass == LIBUSB_CLASS_HID) {
+					char dev_path[64];
+					get_path(&dev_path, usb_dev, conf_desc->bConfigurationValue, intf_desc->bInterfaceNumber);
 					if (!strcmp(dev_path, path)) {
-						int detached_driver = 0;
-
 						/* Matched Paths. Open this device */
 
 						/* OPEN HERE */
 						res = libusb_open(usb_dev, &dev->device_handle);
 						if (res < 0) {
 							LOG("can't open device\n");
-							free(dev_path);
 							break;
 						}
-						good_open = 1;
-
-#ifdef DETACH_KERNEL_DRIVER
-						/* Detach the kernel driver, but only if the
-						   device is managed by the kernel */
-						if (libusb_kernel_driver_active(dev->device_handle, intf_desc->bInterfaceNumber) == 1) {
-							res = libusb_detach_kernel_driver(dev->device_handle, intf_desc->bInterfaceNumber);
-							if (res < 0) {
-								libusb_close(dev->device_handle);
-								LOG("Unable to detach Kernel Driver\n");
-								free(dev_path);
-								good_open = 0;
-								break;
-							}
-							detached_driver = 1;
-						}
-#endif
-
-						res = libusb_claim_interface(dev->device_handle, intf_desc->bInterfaceNumber);
-						if (res < 0) {
-							LOG("can't claim interface %d: %d\n", intf_desc->bInterfaceNumber, res);
-							free(dev_path);
+						good_open = hidapi_initialize_device(dev, conf_desc->bConfigurationValue, intf_desc);
+						if (!good_open)
 							libusb_close(dev->device_handle);
-							good_open = 0;
-							break;
-						}
-
-						/* Initialize XBox 360 controllers */
-						if (is_xbox360(desc.idVendor, intf_desc)) {
-							init_xbox360(dev->device_handle, desc.idVendor, desc.idProduct, conf_desc);
-						}
-
-						/* Initialize XBox One controllers */
-						if (is_xboxone(desc.idVendor, intf_desc)) {
-							init_xboxone(dev->device_handle, desc.idVendor, desc.idProduct, conf_desc);
-						}
-
-						/* Store off the string descriptor indexes */
-						dev->manufacturer_index = desc.iManufacturer;
-						dev->product_index      = desc.iProduct;
-						dev->serial_index       = desc.iSerialNumber;
-
-						/* Store off the interface number */
-						dev->interface = intf_desc->bInterfaceNumber;
-						dev->detached_driver = detached_driver;
-
-						/* Find the INPUT and OUTPUT endpoints. An
-						   OUTPUT endpoint is not required. */
-						for (i = 0; i < intf_desc->bNumEndpoints; i++) {
-							const struct libusb_endpoint_descriptor *ep
-								= &intf_desc->endpoint[i];
-
-							/* Determine the type and direction of this
-							   endpoint. */
-							int is_interrupt =
-								(ep->bmAttributes & LIBUSB_TRANSFER_TYPE_MASK)
-							      == LIBUSB_TRANSFER_TYPE_INTERRUPT;
-							int is_output =
-								(ep->bEndpointAddress & LIBUSB_ENDPOINT_DIR_MASK)
-							      == LIBUSB_ENDPOINT_OUT;
-							int is_input =
-								(ep->bEndpointAddress & LIBUSB_ENDPOINT_DIR_MASK)
-							      == LIBUSB_ENDPOINT_IN;
-
-							/* Decide whether to use it for input or output. */
-							if (dev->input_endpoint == 0 &&
-							    is_interrupt && is_input) {
-								/* Use this endpoint for INPUT */
-								dev->input_endpoint = ep->bEndpointAddress;
-								dev->input_ep_max_packet_size = ep->wMaxPacketSize;
-							}
-							if (dev->output_endpoint == 0 &&
-							    is_interrupt && is_output) {
-								/* Use this endpoint for OUTPUT */
-								dev->output_endpoint = ep->bEndpointAddress;
-							}
-						}
-
-						calculate_device_quirks(dev, desc.idVendor, desc.idProduct);
-
-						dev->thread = SDL_CreateThreadInternal(read_thread, "libusb", 0, dev);
-
-						/* Wait here for the read thread to be initialized. */
-						SDL_WaitThreadBarrier(&dev->barrier);
-
 					}
-					free(dev_path);
 				}
 			}
 		}
 		libusb_free_config_descriptor(conf_desc);
-
 	}
 
 	libusb_free_device_list(devs, 1);
@@ -1364,20 +1198,100 @@ hid_device * HID_API_EXPORT hid_open_path(const char *path, int bExclusive)
 }
 
 
+HID_API_EXPORT hid_device * HID_API_CALL hid_libusb_wrap_sys_device(intptr_t sys_dev, int interface_num)
+{
+/* 0x01000107 is a LIBUSB_API_VERSION for 1.0.23 - version when libusb_wrap_sys_device was introduced */
+#if (!defined(HIDAPI_TARGET_LIBUSB_API_VERSION) || HIDAPI_TARGET_LIBUSB_API_VERSION >= 0x01000107) && (LIBUSB_API_VERSION >= 0x01000107)
+	hid_device *dev = NULL;
+	struct libusb_config_descriptor *conf_desc = NULL;
+	const struct libusb_interface_descriptor *selected_intf_desc = NULL;
+	int res = 0;
+	int j = 0, k = 0;
+
+	if(hid_init() < 0)
+		return NULL;
+
+	dev = new_hid_device();
+
+	res = libusb_wrap_sys_device(usb_context, sys_dev, &dev->device_handle);
+	if (res < 0) {
+		LOG("libusb_wrap_sys_device failed: %d %s\n", res, libusb_error_name(res));
+		goto err;
+	}
+
+	res = libusb_get_active_config_descriptor(libusb_get_device(dev->device_handle), &conf_desc);
+	if (res < 0)
+		libusb_get_config_descriptor(libusb_get_device(dev->device_handle), 0, &conf_desc);
+
+	if (!conf_desc) {
+		LOG("Failed to get configuration descriptor: %d %s\n", res, libusb_error_name(res));
+		goto err;
+	}
+
+	/* find matching HID interface */
+	for (j = 0; j < conf_desc->bNumInterfaces && !selected_intf_desc; j++) {
+		const struct libusb_interface *intf = &conf_desc->interface[j];
+		for (k = 0; k < intf->num_altsetting; k++) {
+			const struct libusb_interface_descriptor *intf_desc = &intf->altsetting[k];
+			if (intf_desc->bInterfaceClass == LIBUSB_CLASS_HID) {
+				if (interface_num < 0 || interface_num == intf_desc->bInterfaceNumber) {
+					selected_intf_desc = intf_desc;
+					break;
+				}
+			}
+		}
+	}
+
+	if (!selected_intf_desc) {
+		if (interface_num < 0) {
+			LOG("Sys USB device doesn't contain a HID interface\n");
+		}
+		else {
+			LOG("Sys USB device doesn't contain a HID interface with number %d\n", interface_num);
+		}
+		goto err;
+	}
+
+	if (!hidapi_initialize_device(dev, conf_desc->bConfigurationValue, selected_intf_desc))
+		goto err;
+
+	return dev;
+
+err:
+	if (conf_desc)
+		libusb_free_config_descriptor(conf_desc);
+	if (dev->device_handle)
+		libusb_close(dev->device_handle);
+	free_hid_device(dev);
+#else
+	(void)sys_dev;
+	(void)interface_num;
+	LOG("libusb_wrap_sys_device is not available\n");
+#endif
+	return NULL;
+}
+
+
 int HID_API_EXPORT hid_write(hid_device *dev, const unsigned char *data, size_t length)
 {
 	int res;
+	int report_number;
+	int skipped_report_id = 0;
 
-	if (dev->output_endpoint <= 0 || dev->no_output_reports_on_intr_ep) {
-		int report_number = data[0];
-		int skipped_report_id = 0;
+	if (!data || (length ==0)) {
+		return -1;
+	}
 
-		if (report_number == 0x0 || dev->skip_output_report_id) {
-			data++;
-			length--;
-			skipped_report_id = 1;
-		}
+	report_number = data[0];
+
+	if (report_number == 0x0) {
+		data++;
+		length--;
+		skipped_report_id = 1;
+	}
 
+
+	if (dev->output_endpoint <= 0) {
 		/* No interrupt out endpoint. Use the Control Endpoint */
 		res = libusb_control_transfer(dev->device_handle,
 			LIBUSB_REQUEST_TYPE_CLASS|LIBUSB_RECIPIENT_INTERFACE|LIBUSB_ENDPOINT_OUT,
@@ -1407,6 +1321,9 @@ int HID_API_EXPORT hid_write(hid_device *dev, const unsigned char *data, size_t
 		if (res < 0)
 			return -1;
 
+		if (skipped_report_id)
+			actual_length++;
+
 		return actual_length;
 	}
 }
@@ -1419,7 +1336,7 @@ static int return_data(hid_device *dev, unsigned char *data, size_t length)
 	   return buffer (data), and delete the liked list item. */
 	struct input_report *rpt = dev->input_reports;
 	size_t len = (length < rpt->len)? length: rpt->len;
-	if (data && len > 0)
+	if (len > 0)
 		memcpy(data, rpt->data, len);
 	dev->input_reports = rpt->next;
 	free(rpt->data);
@@ -1427,13 +1344,12 @@ static int return_data(hid_device *dev, unsigned char *data, size_t length)
 	return len;
 }
 
-#if 0 /* TODO: pthread_cleanup SDL? */
 static void cleanup_mutex(void *param)
 {
-	hid_device *dev = (hid_device *)param;
-	SDL_UnlockMutex(dev->mutex);
+	hid_device *dev = param;
+	pthread_mutex_unlock(&dev->mutex);
 }
-#endif
+
 
 int HID_API_EXPORT hid_read_timeout(hid_device *dev, unsigned char *data, size_t length, int milliseconds)
 {
@@ -1443,10 +1359,12 @@ int HID_API_EXPORT hid_read_timeout(hid_device *dev, unsigned char *data, size_t
 	LOG("transferred: %d\n", transferred);
 	return transferred;
 #endif
-	int bytes_read;
+	/* by initialising this variable right here, GCC gives a compilation warning/error: */
+	/* error: variable ‘bytes_read’ might be clobbered by ‘longjmp’ or ‘vfork’ [-Werror=clobbered] */
+	int bytes_read; /* = -1; */
 
-	SDL_LockMutex(dev->mutex);
-	/* TODO: pthread_cleanup SDL? */
+	pthread_mutex_lock(&dev->mutex);
+	pthread_cleanup_push(&cleanup_mutex, dev);
 
 	bytes_read = -1;
 
@@ -1467,7 +1385,7 @@ int HID_API_EXPORT hid_read_timeout(hid_device *dev, unsigned char *data, size_t
 	if (milliseconds == -1) {
 		/* Blocking */
 		while (!dev->input_reports && !dev->shutdown_thread) {
-			SDL_WaitCondition(dev->condition, dev->mutex);
+			pthread_cond_wait(&dev->condition, &dev->mutex);
 		}
 		if (dev->input_reports) {
 			bytes_read = return_data(dev, data, length);
@@ -1476,9 +1394,17 @@ int HID_API_EXPORT hid_read_timeout(hid_device *dev, unsigned char *data, size_t
 	else if (milliseconds > 0) {
 		/* Non-blocking, but called with timeout. */
 		int res;
+		struct timespec ts;
+		clock_gettime(CLOCK_REALTIME, &ts);
+		ts.tv_sec += milliseconds / 1000;
+		ts.tv_nsec += (milliseconds % 1000) * 1000000;
+		if (ts.tv_nsec >= 1000000000L) {
+			ts.tv_sec++;
+			ts.tv_nsec -= 1000000000L;
+		}
 
 		while (!dev->input_reports && !dev->shutdown_thread) {
-			res = SDL_WaitConditionTimeout(dev->condition, dev->mutex, milliseconds);
+			res = pthread_cond_timedwait(&dev->condition, &dev->mutex, &ts);
 			if (res == 0) {
 				if (dev->input_reports) {
 					bytes_read = return_data(dev, data, length);
@@ -1489,7 +1415,7 @@ int HID_API_EXPORT hid_read_timeout(hid_device *dev, unsigned char *data, size_t
 				   or the read thread was shutdown. Run the
 				   loop again (ie: don't break). */
 			}
-			else if (res == SDL_MUTEX_TIMEDOUT) {
+			else if (res == ETIMEDOUT) {
 				/* Timed out. */
 				bytes_read = 0;
 				break;
@@ -1507,8 +1433,8 @@ int HID_API_EXPORT hid_read_timeout(hid_device *dev, unsigned char *data, size_t
 	}
 
 ret:
-	SDL_UnlockMutex(dev->mutex);
-	/* TODO: pthread_cleanup SDL? */
+	pthread_mutex_unlock(&dev->mutex);
+	pthread_cleanup_pop(0);
 
 	return bytes_read;
 }
@@ -1586,11 +1512,38 @@ int HID_API_EXPORT hid_get_feature_report(hid_device *dev, unsigned char *data,
 	return res;
 }
 
+int HID_API_EXPORT HID_API_CALL hid_get_input_report(hid_device *dev, unsigned char *data, size_t length)
+{
+	int res = -1;
+	int skipped_report_id = 0;
+	int report_number = data[0];
+
+	if (report_number == 0x0) {
+		/* Offset the return buffer by 1, so that the report ID
+		   will remain in byte 0. */
+		data++;
+		length--;
+		skipped_report_id = 1;
+	}
+	res = libusb_control_transfer(dev->device_handle,
+		LIBUSB_REQUEST_TYPE_CLASS|LIBUSB_RECIPIENT_INTERFACE|LIBUSB_ENDPOINT_IN,
+		0x01/*HID get_report*/,
+		(1/*HID Input*/ << 8) | report_number,
+		dev->interface,
+		(unsigned char *)data, length,
+		1000/*timeout millis*/);
+
+	if (res < 0)
+		return -1;
+
+	if (skipped_report_id)
+		res++;
+
+	return res;
+}
 
 void HID_API_EXPORT hid_close(hid_device *dev)
 {
-	int status;
-
 	if (!dev)
 		return;
 
@@ -1599,7 +1552,7 @@ void HID_API_EXPORT hid_close(hid_device *dev)
 	libusb_cancel_transfer(dev->transfer);
 
 	/* Wait for read_thread() to end. */
-	SDL_WaitThread(dev->thread, &status);
+	pthread_join(dev->thread, NULL);
 
 	/* Clean up the Transfer objects allocated in read_thread(). */
 	free(dev->transfer->buffer);
@@ -1609,12 +1562,12 @@ void HID_API_EXPORT hid_close(hid_device *dev)
 	/* release the interface */
 	libusb_release_interface(dev->device_handle, dev->interface);
 
+	/* reattach the kernel driver if it was detached */
 #ifdef DETACH_KERNEL_DRIVER
-	/* Re-attach kernel driver if necessary. */
-	if (dev->detached_driver) {
+	if (dev->is_driver_detached) {
 		int res = libusb_attach_kernel_driver(dev->device_handle, dev->interface);
 		if (res < 0)
-			LOG("Couldn't re-attach kernel driver.\n");
+			LOG("Failed to reattach the driver to kernel.\n");
 	}
 #endif
 
@@ -1622,11 +1575,11 @@ void HID_API_EXPORT hid_close(hid_device *dev)
 	libusb_close(dev->device_handle);
 
 	/* Clear out the queue of received reports. */
-	SDL_LockMutex(dev->mutex);
+	pthread_mutex_lock(&dev->mutex);
 	while (dev->input_reports) {
 		return_data(dev, NULL, 0);
 	}
-	SDL_UnlockMutex(dev->mutex);
+	pthread_mutex_unlock(&dev->mutex);
 
 	free_hid_device(dev);
 }
@@ -1647,6 +1600,23 @@ int HID_API_EXPORT_CALL hid_get_serial_number_string(hid_device *dev, wchar_t *s
 	return hid_get_indexed_string(dev, dev->serial_index, string, maxlen);
 }
 
+HID_API_EXPORT struct hid_device_info *HID_API_CALL hid_get_device_info(hid_device *dev) {
+	if (!dev->device_info) {
+		struct libusb_device_descriptor desc;
+		libusb_device *usb_device = libusb_get_device(dev->device_handle);
+		libusb_get_device_descriptor(usb_device, &desc);
+
+		dev->device_info = create_device_info_for_device(usb_device, dev->device_handle, &desc, dev->config_number, dev->interface);
+		// device error already set by create_device_info_for_device, if any
+
+		if (dev->device_info) {
+			fill_device_info_usage(dev->device_info, dev->device_handle, dev->interface, dev->report_descriptor_size);
+		}
+	}
+
+	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)
 {
 	wchar_t *str;
@@ -1663,9 +1633,16 @@ int HID_API_EXPORT_CALL hid_get_indexed_string(hid_device *dev, int string_index
 }
 
 
+int HID_API_EXPORT_CALL hid_get_report_descriptor(hid_device *dev, unsigned char *buf, size_t buf_size)
+{
+	return hid_get_report_descriptor_libusb(dev->device_handle, dev->interface, dev->report_descriptor_size, buf, buf_size);
+}
+
+
 HID_API_EXPORT const wchar_t * HID_API_CALL  hid_error(hid_device *dev)
 {
-	return NULL;
+	(void)dev;
+	return L"hid_error is not implemented yet";
 }
 
 
@@ -1754,7 +1731,7 @@ static struct lang_map_entry lang_map[] = {
 	LANG("Lithuanian", "lt", 0x0427),
 	LANG("F.Y.R.O. Macedonia", "mk", 0x042F),
 	LANG("Malay - Malaysia", "ms_my", 0x043E),
-	LANG("Malay ??? Brunei", "ms_bn", 0x083E),
+	LANG("Malay  Brunei", "ms_bn", 0x083E),
 	LANG("Maltese", "mt", 0x043A),
 	LANG("Marathi", "mr", 0x044E),
 	LANG("Norwegian - Bokml", "no_no", 0x0414),
@@ -1805,7 +1782,7 @@ static struct lang_map_entry lang_map[] = {
 	LANG("Ukrainian", "uk", 0x0422),
 	LANG("Urdu", "ur", 0x0420),
 	LANG("Uzbek - Cyrillic", "uz_uz", 0x0843),
-	LANG("Uzbek ??? Latin", "uz_uz", 0x0443),
+	LANG("Uzbek  Latin", "uz_uz", 0x0443),
 	LANG("Vietnamese", "vi", 0x042A),
 	LANG("Xhosa", "xh", 0x0434),
 	LANG("Yiddish", "yi", 0x043D),
@@ -1815,15 +1792,13 @@ static struct lang_map_entry lang_map[] = {
 
 uint16_t get_usb_code_for_current_locale(void)
 {
-	char *locale = NULL;
+	char *locale;
 	char search_string[64];
 	char *ptr;
 	struct lang_map_entry *lang;
 
 	/* Get the current locale. */
-#ifdef HAVE_SETLOCALE
 	locale = setlocale(0, NULL);
-#endif
 	if (!locale)
 		return 0x0;
 
@@ -1878,10 +1853,6 @@ uint16_t get_usb_code_for_current_locale(void)
 	return 0x0;
 }
 
-#if defined(__cplusplus) && !defined(NAMESPACE)
-}
-#endif
-
-#ifdef NAMESPACE
+#ifdef __cplusplus
 }
 #endif

+ 56 - 0
src/hidapi/libusb/hidapi_libusb.h

@@ -0,0 +1,56 @@
+/*******************************************************
+ HIDAPI - Multi-Platform library for
+ communication with HID devices.
+
+ libusb/hidapi Team
+
+ Copyright 2021, All Rights Reserved.
+
+ At the discretion of the user of this library,
+ this software may be licensed under the terms of the
+ GNU General Public License v3, a BSD-Style license, or the
+ original HIDAPI license as outlined in the LICENSE.txt,
+ LICENSE-gpl3.txt, LICENSE-bsd.txt, and LICENSE-orig.txt
+ files located at the root of the source distribution.
+ These files may also be found in the public source
+ code repository located at:
+        https://github.com/libusb/hidapi .
+********************************************************/
+
+/** @file
+ * @defgroup API hidapi API
+
+ * Since version 0.11.0, @ref HID_API_VERSION >= HID_API_MAKE_VERSION(0, 11, 0).
+ */
+
+#ifndef HIDAPI_LIBUSB_H__
+#define HIDAPI_LIBUSB_H__
+
+#include <stdint.h>
+
+#include "hidapi.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+		/** @brief Open a HID device using libusb_wrap_sys_device.
+			See https://libusb.sourceforge.io/api-1.0/group__libusb__dev.html#ga98f783e115ceff4eaf88a60e6439563c,
+			for details on libusb_wrap_sys_device.
+
+			@ingroup API
+			@param sys_dev Platform-specific file descriptor that can be recognised by libusb.
+			@param interface_num USB interface number of the device to be used as HID interface.
+			Pass -1 to select first HID interface of the device.
+
+			@returns
+				This function returns a pointer to a #hid_device object on
+				success or NULL on failure.
+		*/
+		HID_API_EXPORT hid_device * HID_API_CALL hid_libusb_wrap_sys_device(intptr_t sys_dev, int interface_num);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif

+ 0 - 3
src/hidapi/libusb/hidusb.cpp

@@ -1,3 +0,0 @@
-
-#define NAMESPACE HIDUSB
-#include "hid.c"

+ 18 - 0
src/hidapi/linux/.gitignore

@@ -0,0 +1,18 @@
+Debug
+Release
+*.exp
+*.ilk
+*.lib
+*.suo
+*.vcproj.*
+*.ncb
+*.suo
+*.dll
+*.pdb
+*.o
+*.so
+hidtest-hidraw
+.deps
+.libs
+*.lo
+*.la

+ 38 - 0
src/hidapi/linux/CMakeLists.txt

@@ -0,0 +1,38 @@
+cmake_minimum_required(VERSION 3.6.3 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(libudev REQUIRED IMPORTED_TARGET libudev)
+
+target_link_libraries(hidapi_hidraw PRIVATE PkgConfig::libudev 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")

+ 5 - 12
src/hidapi/linux/Makefile-manual

@@ -13,23 +13,19 @@ libs: libhidapi-hidraw.so
 CC       ?= gcc
 CFLAGS   ?= -Wall -g -fpic
 
-CXX      ?= g++
-CXXFLAGS ?= -Wall -g -fpic
-
 LDFLAGS  ?= -Wall -g
 
 
-COBJS     = hid.o
-CPPOBJS   = ../hidtest/hidtest.o
-OBJS      = $(COBJS) $(CPPOBJS)
+COBJS     = hid.o ../hidtest/test.o
+OBJS      = $(COBJS)
 LIBS_UDEV = `pkg-config libudev --libs` -lrt
 LIBS      = $(LIBS_UDEV)
 INCLUDES ?= -I../hidapi `pkg-config libusb-1.0 --cflags`
 
 
 # Console Test Program
-hidtest-hidraw: $(COBJS) $(CPPOBJS)
-	$(CXX) $(LDFLAGS) $^ $(LIBS_UDEV) -o $@
+hidtest-hidraw: $(COBJS)
+	$(CC) $(LDFLAGS) $^ $(LIBS_UDEV) -o $@
 
 # Shared Libs
 libhidapi-hidraw.so: $(COBJS)
@@ -39,11 +35,8 @@ libhidapi-hidraw.so: $(COBJS)
 $(COBJS): %.o: %.c
 	$(CC) $(CFLAGS) -c $(INCLUDES) $< -o $@
 
-$(CPPOBJS): %.o: %.cpp
-	$(CXX) $(CXXFLAGS) -c $(INCLUDES) $< -o $@
-
 
 clean:
-	rm -f $(OBJS) hidtest-hidraw libhidapi-hidraw.so ../hidtest/hidtest.o
+	rm -f $(OBJS) hidtest-hidraw libhidapi-hidraw.so $(COBJS)
 
 .PHONY: clean libs

+ 0 - 59
src/hidapi/linux/README.txt

@@ -1,59 +0,0 @@
-
-There are two implementations of HIDAPI for Linux. One (linux/hid.c) uses the
-Linux hidraw driver, and the other (libusb/hid.c) uses libusb. Which one you
-use depends on your application. Complete functionality of the hidraw
-version depends on patches to the Linux kernel which are not currently in
-the mainline. These patches have to do with sending and receiving feature
-reports. The libusb implementation uses libusb to talk directly to the
-device, bypassing any Linux HID driver. The disadvantage of the libusb
-version is that it will only work with USB devices, while the hidraw
-implementation will work with Bluetooth devices as well.
-
-To use HIDAPI, simply drop either linux/hid.c or libusb/hid.c into your
-application and build using the build parameters in the Makefile.
-
-
-Libusb Implementation notes
-----------------------------
-For the libusb implementation, libusb-1.0 must be installed. Libusb 1.0 is
-different than the legacy libusb 0.1 which is installed on many systems. To
-install libusb-1.0 on Ubuntu and other Debian-based systems, run:
-	sudo apt-get install libusb-1.0-0-dev
-
-
-Hidraw Implementation notes
-----------------------------
-For the hidraw implementation, libudev headers and libraries are required to
-build hidapi programs.  To install libudev libraries on Ubuntu,
-and other Debian-based systems, run:
-	sudo apt-get install libudev-dev
-
-On Redhat-based systems, run the following as root:
-	yum install libudev-devel
-
-Unfortunately, the hidraw driver, which the linux version of hidapi is based
-on, contains bugs in kernel versions < 2.6.36, which the client application
-should be aware of.
-
-Bugs (hidraw implementation only):
------------------------------------
-On Kernel versions < 2.6.34, if your device uses numbered reports, an extra
-byte will be returned at the beginning of all reports returned from read()
-for hidraw devices. This is worked around in the library. No action should be
-necessary in the client library.
-
-On Kernel versions < 2.6.35, reports will only be sent using a Set_Report
-transfer on the CONTROL endpoint. No data will ever be sent on an Interrupt
-Out endpoint if one exists. This is fixed in 2.6.35. In 2.6.35, OUTPUT
-reports will be sent to the device on the first INTERRUPT OUT endpoint if it
-exists; If it does not exist, OUTPUT reports will be sent on the CONTROL
-endpoint.
-
-On Kernel versions < 2.6.36, add an extra byte containing the report number
-to sent reports if numbered reports are used, and the device does not
-contain an INTERRPUT OUT endpoint for OUTPUT transfers.  For example, if
-your device uses numbered reports and wants to send {0x2 0xff 0xff 0xff} to
-the device (0x2 is the report number), you must send {0x2 0x2 0xff 0xff
-0xff}. If your device has the optional Interrupt OUT endpoint, this does not
-apply (but really on 2.6.35 only, because 2.6.34 won't use the interrupt
-out endpoint).

+ 840 - 484
src/hidapi/linux/hid.c

@@ -5,10 +5,9 @@
  Alan Ott
  Signal 11 Software
 
- 8/22/2009
- Linux Version - 6/2/2009
+ libusb/hidapi Team
 
- Copyright 2009, All Rights Reserved.
+ Copyright 2022, All Rights Reserved.
 
  At the discretion of the user of this library,
  this software may be licensed under the terms of the
@@ -20,12 +19,6 @@
  code repository located at:
         https://github.com/libusb/hidapi .
 ********************************************************/
-#include "SDL_internal.h"
-
-
-#ifndef _GNU_SOURCE
-#define _GNU_SOURCE /* needed for wcsdup() before glibc 2.10 */
-#endif
 
 /* C */
 #include <stdio.h>
@@ -49,15 +42,15 @@
 #include <linux/input.h>
 #include <libudev.h>
 
-#include "../hidapi/hidapi.h"
-
-#ifdef NAMESPACE
-namespace NAMESPACE
-{
-#endif
+#include "hidapi.h"
 
-/* Definitions from linux/hidraw.h. Since these are new, some distros
-   may not have header files which contain them. */
+#ifdef HIDAPI_ALLOW_BUILD_WORKAROUND_KERNEL_2_6_39
+/* This definitions first appeared in Linux Kernel 2.6.39 in linux/hidraw.h.
+    hidapi doesn't support kernels older than that,
+    so we don't define macros below explicitly, to fail builds on old kernels.
+    For those who really need this as a workaround (e.g. to be able to build on old build machines),
+    can workaround by defining the macro above.
+*/
 #ifndef HIDIOCSFEATURE
 #define HIDIOCSFEATURE(len)    _IOC(_IOC_WRITE|_IOC_READ, 'H', 0x06, len)
 #endif
@@ -65,60 +58,43 @@ namespace NAMESPACE
 #define HIDIOCGFEATURE(len)    _IOC(_IOC_WRITE|_IOC_READ, 'H', 0x07, len)
 #endif
 
-/* USB HID device property names */
-const char *device_string_names[] = {
-	"manufacturer",
-	"product",
-	"serial",
-};
+#endif
 
-/* Symbolic names for the properties above */
-enum device_string_id {
-	DEVICE_STRING_MANUFACTURER,
-	DEVICE_STRING_PRODUCT,
-	DEVICE_STRING_SERIAL,
 
-	DEVICE_STRING_COUNT,
-};
+// HIDIOCGINPUT is not defined in Linux kernel headers < 5.11.
+// This definition is from hidraw.h in Linux >= 5.11.
+// https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=f43d3870cafa2a0f3854c1819c8385733db8f9ae
+#ifndef HIDIOCGINPUT
+#define HIDIOCGINPUT(len)    _IOC(_IOC_WRITE|_IOC_READ, 'H', 0x0A, len)
+#endif
 
 struct hid_device_ {
 	int device_handle;
 	int blocking;
-	int uses_numbered_reports;
-	int needs_ble_hack;
+	wchar_t *last_error_str;
+	struct hid_device_info* device_info;
 };
 
+static struct hid_api_version api_version = {
+	.major = HID_API_VERSION_MAJOR,
+	.minor = HID_API_VERSION_MINOR,
+	.patch = HID_API_VERSION_PATCH
+};
 
-static __u32 kernel_version = 0;
-
-static __u32 detect_kernel_version(void)
-{
-	struct utsname name;
-	int major, minor, release;
-	int ret;
-
-	uname(&name);
-	ret = sscanf(name.release, "%d.%d.%d", &major, &minor, &release);
-	if (ret == 3) {
-		return KERNEL_VERSION(major, minor, release);
-	}
-
-	ret = sscanf(name.release, "%d.%d", &major, &minor);
-	if (ret == 2) {
-		return KERNEL_VERSION(major, minor, 0);
-	}
+static wchar_t *last_global_error_str = NULL;
 
-	printf("Couldn't determine kernel version from version string \"%s\"\n", name.release);
-	return 0;
-}
 
 static hid_device *new_hid_device(void)
 {
-	hid_device *dev = (hid_device *)calloc(1, sizeof(hid_device));
+	hid_device *dev = (hid_device*) calloc(1, sizeof(hid_device));
+	if (dev == NULL) {
+		return NULL;
+	}
+
 	dev->device_handle = -1;
 	dev->blocking = 1;
-	dev->uses_numbered_reports = 0;
-	dev->needs_ble_hack = 0;
+	dev->last_error_str = NULL;
+	dev->device_info = NULL;
 
 	return dev;
 }
@@ -134,7 +110,11 @@ static wchar_t *utf8_to_wchar_t(const char *utf8)
 		if ((size_t) -1 == wlen) {
 			return wcsdup(L"");
 		}
-		ret = (wchar_t *)calloc(wlen+1, sizeof(wchar_t));
+		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;
 	}
@@ -142,6 +122,64 @@ static wchar_t *utf8_to_wchar_t(const char *utf8)
 	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(&last_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(&last_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->last_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->last_error_str, format, args);
+	va_end(args);
+}
+
 /* Get an attribute value from a udev_device and return it as a whar_t
    string. The returned string must be freed with free() when done.*/
 static wchar_t *copy_udev_string(struct udev_device *dev, const char *udev_name)
@@ -149,78 +187,337 @@ static wchar_t *copy_udev_string(struct udev_device *dev, const char *udev_name)
 	return utf8_to_wchar_t(udev_device_get_sysattr_value(dev, udev_name));
 }
 
-/* uses_numbered_reports() returns 1 if report_descriptor describes a device
-   which contains numbered reports. */
-static int uses_numbered_reports(__u8 *report_descriptor, __u32 size) {
-	unsigned int i = 0;
+/*
+ * Gets the size of the HID item at the given position
+ * Returns 1 if successful, 0 if an invalid key
+ * Sets data_len and key_size when successful
+ */
+static int get_hid_item_size(__u8 *report_descriptor, unsigned int pos, __u32 size, int *data_len, int *key_size)
+{
+	int key = report_descriptor[pos];
 	int size_code;
-	int data_len, key_size;
 
-	while (i < size) {
-		int key = report_descriptor[i];
-
-		/* Check for the Report ID key */
-		if (key == 0x85/*Report ID*/) {
-			/* This device has a Report ID, which means it uses
-			   numbered reports. */
+	/*
+	 * 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 __u32 get_hid_report_bytes(__u8 *rpt, size_t len, size_t num_bytes, size_t cur)
+{
+	/* Return if there aren't enough bytes. */
+	if (cur + num_bytes >= len)
+		return 0;
 
-		//printf("key: %02hhx\n", key);
-
-		if ((key & 0xf0) == 0xf0) {
-			/* This is a Long Item. The next byte contains the
-			   length of the data section (value) for this key.
-			   See the HID specification, version 1.11, section
-			   6.2.2.3, titled "Long Items." */
-			if (i+1 < size)
-				data_len = report_descriptor[i+1];
-			else
-				data_len = 0; /* malformed report */
-			key_size = 3;
+	if (num_bytes == 0)
+		return 0;
+	else if (num_bytes == 1)
+		return rpt[cur + 1];
+	else if (num_bytes == 2)
+		return (rpt[cur + 2] * 256 + rpt[cur + 1]);
+	else if (num_bytes == 4)
+		return (
+			rpt[cur + 4] * 0x01000000 +
+			rpt[cur + 3] * 0x00010000 +
+			rpt[cur + 2] * 0x00000100 +
+			rpt[cur + 1] * 0x00000001
+		);
+	else
+		return 0;
+}
+
+/*
+ * Retrieves the device's Usage Page and Usage from the report descriptor.
+ * The algorithm returns the current Usage Page/Usage pair whenever a new
+ * Collection is found and a Usage Local Item is currently in scope.
+ * Usage Local Items are consumed by each Main Item (See. 6.2.2.8).
+ * The algorithm should give similar results as Apple's:
+ *   https://developer.apple.com/documentation/iokit/kiohiddeviceusagepairskey?language=objc
+ * Physical Collections are also matched (macOS does the same).
+ *
+ * This function can be called repeatedly until it returns non-0
+ * Usage is found. pos is the starting point (initially 0) and will be updated
+ * to the next search position.
+ *
+ * The return value is 0 when a pair is found.
+ * 1 when finished processing descriptor.
+ * -1 on a malformed report.
+ */
+static int get_next_hid_usage(__u8 *report_descriptor, __u32 size, unsigned int *pos, unsigned short *usage_page, unsigned short *usage)
+{
+	int data_len, key_size;
+	int initial = *pos == 0; /* Used to handle case where no top-level application collection is defined */
+	int usage_pair_ready = 0;
+
+	/* Usage is a Local Item, it must be set before each Main Item (Collection) before a pair is returned */
+	int usage_found = 0;
+
+	while (*pos < size) {
+		int key = report_descriptor[*pos];
+		int key_cmd = key & 0xfc;
+
+		/* Determine data_len and key_size */
+		if (!get_hid_item_size(report_descriptor, *pos, size, &data_len, &key_size))
+			return -1; /* malformed report */
+
+		switch (key_cmd) {
+		case 0x4: /* Usage Page 6.2.2.7 (Global) */
+			*usage_page = get_hid_report_bytes(report_descriptor, size, data_len, *pos);
+			break;
+
+		case 0x8: /* Usage 6.2.2.8 (Local) */
+			*usage = get_hid_report_bytes(report_descriptor, size, data_len, *pos);
+			usage_found = 1;
+			break;
+
+		case 0xa0: /* Collection 6.2.2.4 (Main) */
+			/* A Usage Item (Local) must be found for the pair to be valid */
+			if (usage_found)
+				usage_pair_ready = 1;
+
+			/* Usage is a Local Item, unset it */
+			usage_found = 0;
+			break;
+
+		case 0x80: /* Input 6.2.2.4 (Main) */
+		case 0x90: /* Output 6.2.2.4 (Main) */
+		case 0xb0: /* Feature 6.2.2.4 (Main) */
+		case 0xc0: /* End Collection 6.2.2.4 (Main) */
+			/* Usage is a Local Item, unset it */
+			usage_found = 0;
+			break;
 		}
-		else {
-			/* This is a Short Item. The bottom two bits of the
-			   key contain the size code for the data section
-			   (value) for this key.  Refer to the HID
-			   specification, version 1.11, section 6.2.2.2,
-			   titled "Short Items." */
-			size_code = key & 0x3;
-			switch (size_code) {
-			case 0:
-			case 1:
-			case 2:
-				data_len = size_code;
-				break;
-			case 3:
-				data_len = 4;
-				break;
-			default:
-				/* Can't ever happen since size_code is & 0x3 */
-				data_len = 0;
-				break;
+
+		/* Skip over this key and its associated data */
+		*pos += data_len + key_size;
+
+		/* Return usage pair */
+		if (usage_pair_ready)
+			return 0;
+	}
+
+	/* If no top-level application collection is found and usage page/usage pair is found, pair is valid
+	   https://docs.microsoft.com/en-us/windows-hardware/drivers/hid/top-level-collections */
+	if (initial && usage_found)
+		return 0; /* success */
+
+	return 1; /* finished processing */
+}
+
+/*
+ * Retrieves the hidraw report descriptor from a file.
+ * When using this form, <sysfs_path>/device/report_descriptor, elevated priviledges are not required.
+ */
+static int get_hid_report_descriptor(const char *rpt_path, struct hidraw_report_descriptor *rpt_desc)
+{
+	int rpt_handle;
+	ssize_t res;
+
+	rpt_handle = open(rpt_path, O_RDONLY | O_CLOEXEC);
+	if (rpt_handle < 0) {
+		register_global_error_format("open failed (%s): %s", rpt_path, strerror(errno));
+		return -1;
+	}
+
+	/*
+	 * Read in the Report Descriptor
+	 * The sysfs file has a maximum size of 4096 (which is the same as HID_MAX_DESCRIPTOR_SIZE) so we should always
+	 * be ok when reading the descriptor.
+	 * In practice if the HID descriptor is any larger I suspect many other things will break.
+	 */
+	memset(rpt_desc, 0x0, sizeof(*rpt_desc));
+	res = read(rpt_handle, rpt_desc->value, HID_MAX_DESCRIPTOR_SIZE);
+	if (res < 0) {
+		register_global_error_format("read failed (%s): %s", rpt_path, strerror(errno));
+	}
+	rpt_desc->size = (__u32) res;
+
+	close(rpt_handle);
+	return (int) res;
+}
+
+/* return size of the descriptor, or -1 on failure */
+static int get_hid_report_descriptor_from_sysfs(const char *sysfs_path, struct hidraw_report_descriptor *rpt_desc)
+{
+	int res = -1;
+	/* Construct <sysfs_path>/device/report_descriptor */
+	size_t rpt_path_len = strlen(sysfs_path) + 25 + 1;
+	char* rpt_path = (char*) calloc(1, rpt_path_len);
+	snprintf(rpt_path, rpt_path_len, "%s/device/report_descriptor", sysfs_path);
+
+	res = get_hid_report_descriptor(rpt_path, rpt_desc);
+	free(rpt_path);
+
+	return res;
+}
+
+/* return non-zero if successfully parsed */
+static int parse_hid_vid_pid_from_uevent(const char *uevent, unsigned *bus_type, unsigned short *vendor_id, unsigned short *product_id)
+{
+	char tmp[1024];
+	size_t uevent_len = strlen(uevent);
+	if (uevent_len > sizeof(tmp) - 1)
+		uevent_len = sizeof(tmp) - 1;
+	memcpy(tmp, uevent, uevent_len);
+	tmp[uevent_len] = '\0';
+
+	char *saveptr = NULL;
+	char *line;
+	char *key;
+	char *value;
+
+	line = strtok_r(tmp, "\n", &saveptr);
+	while (line != NULL) {
+		/* line: "KEY=value" */
+		key = line;
+		value = strchr(line, '=');
+		if (!value) {
+			goto next_line;
+		}
+		*value = '\0';
+		value++;
+
+		if (strcmp(key, "HID_ID") == 0) {
+			/**
+			 *        type vendor   product
+			 * HID_ID=0003:000005AC:00008242
+			 **/
+			int ret = sscanf(value, "%x:%hx:%hx", bus_type, vendor_id, product_id);
+			if (ret == 3) {
+				return 1;
 			}
-			key_size = 1;
 		}
 
-		/* Skip over this key and it's associated data */
-		i += data_len + key_size;
+next_line:
+		line = strtok_r(NULL, "\n", &saveptr);
 	}
 
-	/* Didn't find a Report ID key. Device doesn't use numbered reports. */
+	register_global_error("Couldn't find/parse HID_ID");
 	return 0;
 }
 
+/* return non-zero if successfully parsed */
+static int parse_hid_vid_pid_from_uevent_path(const char *uevent_path, unsigned *bus_type, unsigned short *vendor_id, unsigned short *product_id)
+{
+	int handle;
+	ssize_t res;
+
+	handle = open(uevent_path, O_RDONLY | O_CLOEXEC);
+	if (handle < 0) {
+		register_global_error_format("open failed (%s): %s", uevent_path, strerror(errno));
+		return 0;
+	}
+
+	char buf[1024];
+	res = read(handle, buf, sizeof(buf) - 1); /* -1 for '\0' at the end */
+	close(handle);
+
+	if (res < 0) {
+		register_global_error_format("read failed (%s): %s", uevent_path, strerror(errno));
+		return 0;
+	}
+
+	buf[res] = '\0';
+	return parse_hid_vid_pid_from_uevent(buf, bus_type, vendor_id, product_id);
+}
+
+/* return non-zero if successfully read/parsed */
+static int parse_hid_vid_pid_from_sysfs(const char *sysfs_path, unsigned *bus_type, unsigned short *vendor_id, unsigned short *product_id)
+{
+	int res = 0;
+	/* Construct <sysfs_path>/device/uevent */
+	size_t uevent_path_len = strlen(sysfs_path) + 14 + 1;
+	char* uevent_path = (char*) calloc(1, uevent_path_len);
+	snprintf(uevent_path, uevent_path_len, "%s/device/uevent", sysfs_path);
+
+	res = parse_hid_vid_pid_from_uevent_path(uevent_path, bus_type, vendor_id, product_id);
+	free(uevent_path);
+
+	return res;
+}
+
+static int get_hid_report_descriptor_from_hidraw(hid_device *dev, struct hidraw_report_descriptor *rpt_desc)
+{
+	int desc_size = 0;
+
+	/* Get Report Descriptor Size */
+	int res = ioctl(dev->device_handle, HIDIOCGRDESCSIZE, &desc_size);
+	if (res < 0) {
+		register_device_error_format(dev, "ioctl(GRDESCSIZE): %s", strerror(errno));
+		return res;
+	}
+
+	/* Get Report Descriptor */
+	memset(rpt_desc, 0x0, sizeof(*rpt_desc));
+	rpt_desc->size = desc_size;
+	res = ioctl(dev->device_handle, HIDIOCGRDESC, rpt_desc);
+	if (res < 0) {
+		register_device_error_format(dev, "ioctl(GRDESC): %s", strerror(errno));
+	}
+
+	return res;
+}
+
 /*
  * The caller is responsible for free()ing the (newly-allocated) character
  * strings pointed to by serial_number_utf8 and product_name_utf8 after use.
  */
-static int
-parse_uevent_info(const char *uevent, unsigned *bus_type,
+static int parse_uevent_info(const char *uevent, unsigned *bus_type,
 	unsigned short *vendor_id, unsigned short *product_id,
 	char **serial_number_utf8, char **product_name_utf8)
 {
-	char *tmp;
+	char tmp[1024];
+	size_t uevent_len = strlen(uevent);
+	if (uevent_len > sizeof(tmp) - 1)
+		uevent_len = sizeof(tmp) - 1;
+	memcpy(tmp, uevent, uevent_len);
+	tmp[uevent_len] = '\0';
+
 	char *saveptr = NULL;
 	char *line;
 	char *key;
@@ -230,15 +527,6 @@ parse_uevent_info(const char *uevent, unsigned *bus_type,
 	int found_serial = 0;
 	int found_name = 0;
 
-	if (!uevent) {
-		return 0;
-	}
-
-	tmp = strdup(uevent);
-	if (!tmp) {
-		return 0;
-	}
-
 	line = strtok_r(tmp, "\n", &saveptr);
 	while (line != NULL) {
 		/* line: "KEY=value" */
@@ -273,206 +561,286 @@ next_line:
 		line = strtok_r(NULL, "\n", &saveptr);
 	}
 
-	free(tmp);
 	return (found_id && found_name && found_serial);
 }
 
-static int is_BLE(hid_device *dev)
+
+static struct hid_device_info * create_device_info_for_device(struct udev_device *raw_dev)
 {
-	struct udev *udev;
-	struct udev_device *udev_dev, *hid_dev;
-	struct stat s;
-	int ret;
+	struct hid_device_info *root = NULL;
+	struct hid_device_info *cur_dev = NULL;
 
-	/* Create the udev object */
-	udev = udev_new();
-	if (!udev) {
-		printf("Can't create udev\n");
-		return -1;
+	const char *sysfs_path;
+	const char *dev_path;
+	const char *str;
+	struct udev_device *hid_dev; /* The device's HID udev node. */
+	struct udev_device *usb_dev; /* The device's USB udev node. */
+	struct udev_device *intf_dev; /* The device's interface (in the USB sense). */
+	unsigned short dev_vid;
+	unsigned short dev_pid;
+	char *serial_number_utf8 = NULL;
+	char *product_name_utf8 = NULL;
+	unsigned bus_type;
+	int result;
+	struct hidraw_report_descriptor report_desc;
+
+	sysfs_path = udev_device_get_syspath(raw_dev);
+	dev_path = udev_device_get_devnode(raw_dev);
+
+	hid_dev = udev_device_get_parent_with_subsystem_devtype(
+		raw_dev,
+		"hid",
+		NULL);
+
+	if (!hid_dev) {
+		/* Unable to find parent hid device. */
+		goto end;
 	}
 
-	/* Get the dev_t (major/minor numbers) from the file handle. */
-	if (fstat(dev->device_handle, &s) < 0) {
-		udev_unref(udev);
-		return -1;
+	result = parse_uevent_info(
+		udev_device_get_sysattr_value(hid_dev, "uevent"),
+		&bus_type,
+		&dev_vid,
+		&dev_pid,
+		&serial_number_utf8,
+		&product_name_utf8);
+
+	if (!result) {
+		/* parse_uevent_info() failed for at least one field. */
+		goto end;
 	}
 
-	/* Open a udev device from the dev_t. 'c' means character device. */
-	ret = 0;
-	udev_dev = udev_device_new_from_devnum(udev, 'c', s.st_rdev);
-	if (udev_dev) {
-		hid_dev = udev_device_get_parent_with_subsystem_devtype(
-			udev_dev,
-			"hid",
-			NULL);
-		if (hid_dev) {
-			unsigned short dev_vid = 0;
-			unsigned short dev_pid = 0;
-			unsigned bus_type = 0;
-			char *serial_number_utf8 = NULL;
-			char *product_name_utf8 = NULL;
-
-			parse_uevent_info(
-			           udev_device_get_sysattr_value(hid_dev, "uevent"),
-			           &bus_type,
-			           &dev_vid,
-			           &dev_pid,
-			           &serial_number_utf8,
-			           &product_name_utf8);
-			free(serial_number_utf8);
-			free(product_name_utf8);
-
-			if (bus_type == BUS_BLUETOOTH) {
-				/* Right now the Steam Controller is the only BLE device that we send feature reports to */
-				if (dev_vid == 0x28de /* Valve */) {
-					ret = 1;
-				}
+	/* Filter out unhandled devices right away */
+	switch (bus_type) {
+		case BUS_BLUETOOTH:
+		case BUS_I2C:
+		case BUS_USB:
+		case BUS_SPI:
+			break;
+
+		default:
+			goto end;
+	}
+
+	/* Create the record. */
+	root = (struct hid_device_info*) calloc(1, sizeof(struct hid_device_info));
+	if (!root)
+		goto end;
+
+	cur_dev = root;
+
+	/* Fill out the record */
+	cur_dev->next = NULL;
+	cur_dev->path = dev_path? strdup(dev_path): NULL;
+
+	/* VID/PID */
+	cur_dev->vendor_id = dev_vid;
+	cur_dev->product_id = dev_pid;
+
+	/* Serial Number */
+	cur_dev->serial_number = utf8_to_wchar_t(serial_number_utf8);
+
+	/* Release Number */
+	cur_dev->release_number = 0x0;
+
+	/* Interface Number */
+	cur_dev->interface_number = -1;
+
+	switch (bus_type) {
+		case BUS_USB:
+			/* The device pointed to by raw_dev contains information about
+				the hidraw device. In order to get information about the
+				USB device, get the parent device with the
+				subsystem/devtype pair of "usb"/"usb_device". This will
+				be several levels up the tree, but the function will find
+				it. */
+			usb_dev = udev_device_get_parent_with_subsystem_devtype(
+					raw_dev,
+					"usb",
+					"usb_device");
+
+			/* uhid USB devices
+			 * Since this is a virtual hid interface, no USB information will
+			 * be available. */
+			if (!usb_dev) {
+				/* Manufacturer and Product strings */
+				cur_dev->manufacturer_string = wcsdup(L"");
+				cur_dev->product_string = utf8_to_wchar_t(product_name_utf8);
+				break;
+			}
+
+			cur_dev->manufacturer_string = copy_udev_string(usb_dev, "manufacturer");
+			cur_dev->product_string = copy_udev_string(usb_dev, "product");
+
+			cur_dev->bus_type = HID_API_BUS_USB;
+
+			str = udev_device_get_sysattr_value(usb_dev, "bcdDevice");
+			cur_dev->release_number = (str)? strtol(str, NULL, 16): 0x0;
+
+			/* Get a handle to the interface's udev node. */
+			intf_dev = udev_device_get_parent_with_subsystem_devtype(
+					raw_dev,
+					"usb",
+					"usb_interface");
+			if (intf_dev) {
+				str = udev_device_get_sysattr_value(intf_dev, "bInterfaceNumber");
+				cur_dev->interface_number = (str)? strtol(str, NULL, 16): -1;
 			}
 
-			/* hid_dev doesn't need to be (and can't be) unref'd.
-			   I'm not sure why, but it'll throw double-free() errors. */
+			break;
+
+		case BUS_BLUETOOTH:
+			cur_dev->manufacturer_string = wcsdup(L"");
+			cur_dev->product_string = utf8_to_wchar_t(product_name_utf8);
+
+			cur_dev->bus_type = HID_API_BUS_BLUETOOTH;
+
+			break;
+		case BUS_I2C:
+			cur_dev->manufacturer_string = wcsdup(L"");
+			cur_dev->product_string = utf8_to_wchar_t(product_name_utf8);
+
+			cur_dev->bus_type = HID_API_BUS_I2C;
+
+			break;
+
+		case BUS_SPI:
+			cur_dev->manufacturer_string = wcsdup(L"");
+			cur_dev->product_string = utf8_to_wchar_t(product_name_utf8);
+
+			cur_dev->bus_type = HID_API_BUS_SPI;
+
+			break;
+
+		default:
+			/* Unknown device type - this should never happen, as we
+			 * check for USB and Bluetooth devices above */
+			break;
+	}
+
+	/* Usage Page and Usage */
+	result = get_hid_report_descriptor_from_sysfs(sysfs_path, &report_desc);
+	if (result >= 0) {
+		unsigned short page = 0, usage = 0;
+		unsigned int pos = 0;
+		/*
+		 * Parse the first usage and usage page
+		 * out of the report descriptor.
+		 */
+		if (!get_next_hid_usage(report_desc.value, report_desc.size, &pos, &page, &usage)) {
+			cur_dev->usage_page = page;
+			cur_dev->usage = usage;
+		}
+
+		/*
+		 * Parse any additional usage and usage pages
+		 * out of the report descriptor.
+		 */
+		while (!get_next_hid_usage(report_desc.value, report_desc.size, &pos, &page, &usage)) {
+			/* Create new record for additional usage pairs */
+			struct hid_device_info *tmp = (struct hid_device_info*) calloc(1, sizeof(struct hid_device_info));
+			struct hid_device_info *prev_dev = cur_dev;
+
+			if (!tmp)
+				continue;
+			cur_dev->next = tmp;
+			cur_dev = tmp;
+
+			/* Update fields */
+			cur_dev->path = dev_path? strdup(dev_path): NULL;
+			cur_dev->vendor_id = dev_vid;
+			cur_dev->product_id = dev_pid;
+			cur_dev->serial_number = prev_dev->serial_number? wcsdup(prev_dev->serial_number): NULL;
+			cur_dev->release_number = prev_dev->release_number;
+			cur_dev->interface_number = prev_dev->interface_number;
+			cur_dev->manufacturer_string = prev_dev->manufacturer_string? wcsdup(prev_dev->manufacturer_string): NULL;
+			cur_dev->product_string = prev_dev->product_string? wcsdup(prev_dev->product_string): NULL;
+			cur_dev->usage_page = page;
+			cur_dev->usage = usage;
+			cur_dev->bus_type = prev_dev->bus_type;
 		}
-		udev_device_unref(udev_dev);
 	}
 
-	udev_unref(udev);
+end:
+	free(serial_number_utf8);
+	free(product_name_utf8);
 
-	return ret;
+	return root;
 }
 
-static int get_device_string(hid_device *dev, enum device_string_id key, wchar_t *string, size_t maxlen)
-{
+static struct hid_device_info * create_device_info_for_hid_device(hid_device *dev) {
 	struct udev *udev;
-	struct udev_device *udev_dev, *parent, *hid_dev;
+	struct udev_device *udev_dev;
 	struct stat s;
 	int ret = -1;
-	char *serial_number_utf8 = NULL;
-	char *product_name_utf8 = NULL;
-	char *tmp;
+	struct hid_device_info *root = NULL;
 
-	/* Create the udev object */
-	udev = udev_new();
-	if (!udev) {
-		printf("Can't create udev\n");
-		return -1;
-	}
+	register_device_error(dev, NULL);
 
 	/* Get the dev_t (major/minor numbers) from the file handle. */
 	ret = fstat(dev->device_handle, &s);
 	if (-1 == ret) {
-		udev_unref(udev);
-		return ret;
+		register_device_error(dev, "Failed to stat device handle");
+		return NULL;
 	}
+
+	/* Create the udev object */
+	udev = udev_new();
+	if (!udev) {
+		register_device_error(dev, "Couldn't create udev context");
+		return NULL;
+	}
+
 	/* Open a udev device from the dev_t. 'c' means character device. */
 	udev_dev = udev_device_new_from_devnum(udev, 'c', s.st_rdev);
 	if (udev_dev) {
-		hid_dev = udev_device_get_parent_with_subsystem_devtype(
-			udev_dev,
-			"hid",
-			NULL);
-		if (hid_dev) {
-			unsigned short dev_vid;
-			unsigned short dev_pid;
-			unsigned bus_type;
-			size_t retm;
-
-			ret = parse_uevent_info(
-			           udev_device_get_sysattr_value(hid_dev, "uevent"),
-			           &bus_type,
-			           &dev_vid,
-			           &dev_pid,
-			           &serial_number_utf8,
-			           &product_name_utf8);
-
-			if (bus_type == BUS_BLUETOOTH) {
-				switch (key) {
-					case DEVICE_STRING_MANUFACTURER:
-						wcsncpy(string, L"", maxlen);
-						ret = 0;
-						break;
-					case DEVICE_STRING_PRODUCT:
-						retm = mbstowcs(string, product_name_utf8, maxlen);
-						ret = (retm == (size_t)-1)? -1: 0;
-						break;
-					case DEVICE_STRING_SERIAL:
-						/* Bluetooth serial numbers are often the bluetooth device address
-						   and we want that with the colons stripped out, which is the correct
-						   serial number for PS4 controllers
-						 */
-						while ((tmp = strchr(serial_number_utf8, ':')) != NULL) {
-							memmove(tmp, tmp+1, strlen(tmp));
-						}
-						retm = mbstowcs(string, serial_number_utf8, maxlen);
-						ret = (retm == (size_t)-1)? -1: 0;
-						break;
-					case DEVICE_STRING_COUNT:
-					default:
-						ret = -1;
-						break;
-				}
-			}
-			else {
-				/* This is a USB device. Find its parent USB Device node. */
-				parent = udev_device_get_parent_with_subsystem_devtype(
-					   udev_dev,
-					   "usb",
-					   "usb_device");
-				if (parent) {
-					const char *str;
-					const char *key_str = NULL;
-
-					if ((int)key >= 0 && (int)key < DEVICE_STRING_COUNT) {
-						key_str = device_string_names[key];
-					} else {
-						ret = -1;
-						goto end;
-					}
-
-					str = udev_device_get_sysattr_value(parent, key_str);
-					if (str) {
-						/* Convert the string from UTF-8 to wchar_t */
-						retm = mbstowcs(string, str, maxlen);
-						ret = (retm == (size_t)-1)? -1: 0;
-						goto end;
-					}
-				}
-			}
-		}
+		root = create_device_info_for_device(udev_dev);
 	}
 
-end:
-	free(serial_number_utf8);
-	free(product_name_utf8);
+	if (!root) {
+		/* TODO: have a better error reporting via create_device_info_for_device */
+		register_device_error(dev, "Couldn't create hid_device_info");
+	}
 
 	udev_device_unref(udev_dev);
-	/* parent and hid_dev don't need to be (and can't be) unref'd.
-	   I'm not sure why, but they'll throw double-free() errors. */
 	udev_unref(udev);
 
-	return ret;
+	return root;
+}
+
+HID_API_EXPORT const struct hid_api_version* HID_API_CALL hid_version(void)
+{
+	return &api_version;
+}
+
+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;
 
+	/* indicate no error */
+	register_global_error(NULL);
+
 	/* Set the locale if it's not set. */
 	locale = setlocale(LC_CTYPE, NULL);
 	if (!locale)
 		setlocale(LC_CTYPE, "");
 
-	kernel_version = detect_kernel_version();
-
 	return 0;
 }
 
 int HID_API_EXPORT hid_exit(void)
 {
-	/* Nothing to do for this in the Linux/hidraw implementation. */
+	/* Free global error message */
+	register_global_error(NULL);
+
 	return 0;
 }
 
-
 struct hid_device_info  HID_API_EXPORT *hid_enumerate(unsigned short vendor_id, unsigned short product_id)
 {
 	struct udev *udev;
@@ -481,15 +849,14 @@ struct hid_device_info  HID_API_EXPORT *hid_enumerate(unsigned short vendor_id,
 
 	struct hid_device_info *root = NULL; /* return object */
 	struct hid_device_info *cur_dev = NULL;
-	struct hid_device_info *prev_dev = NULL; /* previous device */
-	const char *hint = SDL_GetHint(SDL_HINT_HIDAPI_IGNORE_DEVICES);
 
 	hid_init();
+	/* register_global_error: global error is reset by hid_init */
 
 	/* Create the udev object */
 	udev = udev_new();
 	if (!udev) {
-		printf("Can't create udev\n");
+		register_global_error("Couldn't create udev context");
 		return NULL;
 	}
 
@@ -502,178 +869,62 @@ struct hid_device_info  HID_API_EXPORT *hid_enumerate(unsigned short vendor_id,
 	   create a udev_device record for it */
 	udev_list_entry_foreach(dev_list_entry, devices) {
 		const char *sysfs_path;
-		const char *dev_path;
-		const char *str;
+		unsigned short dev_vid = 0;
+		unsigned short dev_pid = 0;
+		unsigned bus_type = 0;
 		struct udev_device *raw_dev; /* The device's hidraw udev node. */
-		struct udev_device *hid_dev; /* The device's HID udev node. */
-		struct udev_device *usb_dev; /* The device's USB udev node. */
-		struct udev_device *intf_dev; /* The device's interface (in the USB sense). */
-		unsigned short dev_vid;
-		unsigned short dev_pid;
-		char *serial_number_utf8 = NULL;
-		char *product_name_utf8 = NULL;
-		unsigned bus_type;
-		int result;
+		struct hid_device_info * tmp;
 
 		/* Get the filename of the /sys entry for the device
 		   and create a udev_device object (dev) representing it */
 		sysfs_path = udev_list_entry_get_name(dev_list_entry);
-		raw_dev = udev_device_new_from_syspath(udev, sysfs_path);
-		dev_path = udev_device_get_devnode(raw_dev);
-
-		hid_dev = udev_device_get_parent_with_subsystem_devtype(
-			raw_dev,
-			"hid",
-			NULL);
-
-		if (!hid_dev) {
-			/* Unable to find parent hid device. */
-			goto next;
-		}
-
-		result = parse_uevent_info(
-			udev_device_get_sysattr_value(hid_dev, "uevent"),
-			&bus_type,
-			&dev_vid,
-			&dev_pid,
-			&serial_number_utf8,
-			&product_name_utf8);
-
-		if (!result) {
-			/* parse_uevent_info() failed for at least one field. */
-			goto next;
-		}
-
-		if (bus_type != BUS_USB && bus_type != BUS_BLUETOOTH) {
-			/* We only know how to handle USB and BT devices. */
-			goto next;
-		}
+		if (!sysfs_path)
+			continue;
 
-		if (access(dev_path, R_OK|W_OK) != 0) {
-			/* We can't open this device, ignore it */
-			goto next;
-		}
+		if (vendor_id != 0 || product_id != 0) {
+			if (!parse_hid_vid_pid_from_sysfs(sysfs_path, &bus_type, &dev_vid, &dev_pid))
+				continue;
 
-		/* See if there are any devices we should skip in enumeration */
-		if (hint) {
-			char vendor_match[16], product_match[16];
-			SDL_snprintf(vendor_match, sizeof(vendor_match), "0x%.4x/0x0000", dev_vid);
-			SDL_snprintf(product_match, sizeof(product_match), "0x%.4x/0x%.4x", dev_vid, dev_pid);
-			if (SDL_strcasestr(hint, vendor_match) || SDL_strcasestr(hint, product_match)) {
+			if (vendor_id != 0 && vendor_id != dev_vid)
+				continue;
+			if (product_id != 0 && product_id != dev_pid)
 				continue;
-			}
 		}
 
-		/* Check the VID/PID against the arguments */
-		if ((vendor_id == 0x0 || vendor_id == dev_vid) &&
-		    (product_id == 0x0 || product_id == dev_pid)) {
-			struct hid_device_info *tmp;
+		raw_dev = udev_device_new_from_syspath(udev, sysfs_path);
+		if (!raw_dev)
+			continue;
 
-			/* VID/PID match. Create the record. */
-			tmp = (struct hid_device_info *)calloc(1, sizeof(struct hid_device_info));
+		tmp = create_device_info_for_device(raw_dev);
+		if (tmp) {
 			if (cur_dev) {
 				cur_dev->next = tmp;
 			}
 			else {
 				root = tmp;
 			}
-			prev_dev = cur_dev;
 			cur_dev = tmp;
 
-			/* Fill out the record */
-			cur_dev->next = NULL;
-			cur_dev->path = dev_path? strdup(dev_path): NULL;
-
-			/* VID/PID */
-			cur_dev->vendor_id = dev_vid;
-			cur_dev->product_id = dev_pid;
-
-			/* Serial Number */
-			cur_dev->serial_number = utf8_to_wchar_t(serial_number_utf8);
-
-			/* Release Number */
-			cur_dev->release_number = 0x0;
-
-			/* Interface Number */
-			cur_dev->interface_number = -1;
-
-			switch (bus_type) {
-				case BUS_USB:
-					/* The device pointed to by raw_dev contains information about
-					   the hidraw device. In order to get information about the
-					   USB device, get the parent device with the
-					   subsystem/devtype pair of "usb"/"usb_device". This will
-					   be several levels up the tree, but the function will find
-					   it. */
-					usb_dev = udev_device_get_parent_with_subsystem_devtype(
-							raw_dev,
-							"usb",
-							"usb_device");
-
-					if (!usb_dev) {
-						/* Free this device */
-						free(cur_dev->serial_number);
-						free(cur_dev->path);
-						free(cur_dev);
-
-						/* Take it off the device list. */
-						if (prev_dev) {
-							prev_dev->next = NULL;
-							cur_dev = prev_dev;
-						}
-						else {
-							cur_dev = root = NULL;
-						}
-
-						goto next;
-					}
-
-					/* Manufacturer and Product strings */
-					cur_dev->manufacturer_string = copy_udev_string(usb_dev, device_string_names[DEVICE_STRING_MANUFACTURER]);
-					cur_dev->product_string = copy_udev_string(usb_dev, device_string_names[DEVICE_STRING_PRODUCT]);
-
-					/* Release Number */
-					str = udev_device_get_sysattr_value(usb_dev, "bcdDevice");
-					cur_dev->release_number = (str)? strtol(str, NULL, 16): 0x0;
-
-					/* Get a handle to the interface's udev node. */
-					intf_dev = udev_device_get_parent_with_subsystem_devtype(
-							raw_dev,
-							"usb",
-							"usb_interface");
-					if (intf_dev) {
-						str = udev_device_get_sysattr_value(intf_dev, "bInterfaceNumber");
-						cur_dev->interface_number = (str)? strtol(str, NULL, 16): -1;
-					}
-
-					break;
-
-				case BUS_BLUETOOTH:
-					/* Manufacturer and Product strings */
-					cur_dev->manufacturer_string = wcsdup(L"");
-					cur_dev->product_string = utf8_to_wchar_t(product_name_utf8);
-
-					break;
-
-				default:
-					/* Unknown device type - this should never happen, as we
-					 * check for USB and Bluetooth devices above */
-					break;
+			/* move the pointer to the tail of returnd list */
+			while (cur_dev->next != NULL) {
+				cur_dev = cur_dev->next;
 			}
 		}
 
-	next:
-		free(serial_number_utf8);
-		free(product_name_utf8);
 		udev_device_unref(raw_dev);
-		/* hid_dev, usb_dev and intf_dev don't need to be (and can't be)
-		   unref()d.  It will cause a double-free() error.  I'm not
-		   sure why.  */
 	}
 	/* Free the enumerator and udev objects. */
 	udev_enumerate_unref(enumerate);
 	udev_unref(udev);
 
+	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.");
+		}
+	}
+
 	return root;
 }
 
@@ -697,7 +948,13 @@ hid_device * hid_open(unsigned short vendor_id, unsigned short product_id, const
 	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 &&
@@ -718,7 +975,9 @@ hid_device * hid_open(unsigned short vendor_id, unsigned short product_id, const
 
 	if (path_to_open) {
 		/* Open the device */
-		handle = hid_open_path(path_to_open, 0);
+		handle = hid_open_path(path_to_open);
+	} else {
+		register_global_error("Device with requested VID/PID/(SerialNumber) not found");
 	}
 
 	hid_free_enumeration(devs);
@@ -726,51 +985,38 @@ hid_device * hid_open(unsigned short vendor_id, unsigned short product_id, const
 	return handle;
 }
 
-hid_device * HID_API_EXPORT hid_open_path(const char *path, int bExclusive)
+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) {
+		register_global_error("Couldn't allocate memory");
+		return NULL;
+	}
 
-	/* OPEN HERE */
 	dev->device_handle = open(path, O_RDWR | O_CLOEXEC);
 
-	/* If we have a good handle, return it. */
 	if (dev->device_handle >= 0) {
-
-		/* Get the report descriptor */
 		int res, desc_size = 0;
-		struct hidraw_report_descriptor rpt_desc;
-
-		memset(&rpt_desc, 0x0, sizeof(rpt_desc));
 
-		/* Get Report Descriptor Size */
+		/* Make sure this is a HIDRAW device - responds to HIDIOCGRDESCSIZE */
 		res = ioctl(dev->device_handle, HIDIOCGRDESCSIZE, &desc_size);
-		if (res < 0)
-			perror("HIDIOCGRDESCSIZE");
-
-
-		/* Get Report Descriptor */
-		rpt_desc.size = desc_size;
-		res = ioctl(dev->device_handle, HIDIOCGRDESC, &rpt_desc);
 		if (res < 0) {
-			perror("HIDIOCGRDESC");
-		} else {
-			/* Determine if this device uses numbered reports. */
-			dev->uses_numbered_reports =
-				uses_numbered_reports(rpt_desc.value,
-				                      rpt_desc.size);
+			hid_close(dev);
+			register_global_error_format("ioctl(GRDESCSIZE) error for '%s', not a HIDRAW device?: %s", path, strerror(errno));
+			return NULL;
 		}
 
-		dev->needs_ble_hack = (is_BLE(dev) == 1);
-
 		return dev;
 	}
 	else {
-		/* Unable to open any devices. */
+		/* 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;
 	}
 }
@@ -780,14 +1026,25 @@ int HID_API_EXPORT hid_write(hid_device *dev, const unsigned char *data, size_t
 {
 	int bytes_written;
 
+	if (!data || (length == 0)) {
+		errno = EINVAL;
+		register_device_error(dev, strerror(errno));
+		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)
 {
+	/* Set device error to none */
+	register_device_error(dev, NULL);
+
 	int bytes_read;
 
 	if (milliseconds >= 0) {
@@ -804,29 +1061,32 @@ int HID_API_EXPORT hid_read_timeout(hid_device *dev, unsigned char *data, size_t
 		fds.events = POLLIN;
 		fds.revents = 0;
 		ret = poll(&fds, 1, milliseconds);
-		if (ret == -1 || ret == 0) {
-			/* Error or timeout */
+		if (ret == 0) {
+			/* Timeout */
+			return ret;
+		}
+		if (ret == -1) {
+			/* Error */
+			register_device_error(dev, strerror(errno));
 			return ret;
 		}
 		else {
 			/* Check for errors on the file descriptor. This will
 			   indicate a device disconnection. */
-			if (fds.revents & (POLLERR | POLLHUP | POLLNVAL))
+			if (fds.revents & (POLLERR | POLLHUP | POLLNVAL)) {
+				// We cannot use strerror() here as no -1 was returned from poll().
+				register_device_error(dev, "hid_read_timeout: unexpected poll error (device disconnected)");
 				return -1;
+			}
 		}
 	}
 
 	bytes_read = read(dev->device_handle, data, length);
-	if (bytes_read < 0 && (errno == EAGAIN || errno == EINPROGRESS))
-		bytes_read = 0;
-
-	if (bytes_read >= 0 &&
-	    kernel_version != 0 &&
-	    kernel_version < KERNEL_VERSION(2,6,34) &&
-	    dev->uses_numbered_reports) {
-		/* Work around a kernel bug. Chop off the first byte. */
-		memmove(data, data+1, bytes_read);
-		bytes_read--;
+	if (bytes_read < 0) {
+		if (errno == EAGAIN || errno == EINPROGRESS)
+			bytes_read = 0;
+		else
+			register_device_error(dev, strerror(errno));
 	}
 
 	return bytes_read;
@@ -847,90 +1107,186 @@ int HID_API_EXPORT hid_set_nonblocking(hid_device *dev, int nonblock)
 	return 0; /* Success */
 }
 
+
 int HID_API_EXPORT hid_send_feature_report(hid_device *dev, const unsigned char *data, size_t length)
 {
-	static const int MAX_RETRIES = 50;
-	int retry;
 	int res;
 
-	for (retry = 0; retry < MAX_RETRIES; ++retry) {
-		res = ioctl(dev->device_handle, HIDIOCSFEATURE(length), data);
-		if (res < 0 && errno == EPIPE) {
-			/* Try again... */
-			continue;
-		}
+	register_device_error(dev, NULL);
+
+	res = ioctl(dev->device_handle, HIDIOCSFEATURE(length), data);
+	if (res < 0)
+		register_device_error_format(dev, "ioctl (SFEATURE): %s", strerror(errno));
 
-		if (res < 0)
-			perror("ioctl (SFEATURE)");
-		break;
-	}
 	return res;
 }
 
 int HID_API_EXPORT hid_get_feature_report(hid_device *dev, unsigned char *data, size_t length)
 {
 	int res;
-	unsigned char report = data[0];
+
+	register_device_error(dev, NULL);
 
 	res = ioctl(dev->device_handle, HIDIOCGFEATURE(length), data);
 	if (res < 0)
-		perror("ioctl (GFEATURE)");
-	else if (dev->needs_ble_hack) {
-		/* Versions of BlueZ before 5.56 don't include the report in the data,
-		 * and versions of BlueZ >= 5.56 include 2 copies of the report.
-		 * We'll fix it so that there is a single copy of the report in both cases
-		 */
-		if (data[0] == report && data[1] == report) {
-			memmove(&data[0], &data[1], res);
-		} else if (data[0] != report) {
-			memmove(&data[1], &data[0], res);
-			data[0] = report;
-			++res;
-		}
-	}
+		register_device_error_format(dev, "ioctl (GFEATURE): %s", strerror(errno));
+
 	return res;
 }
 
+int HID_API_EXPORT HID_API_CALL hid_get_input_report(hid_device *dev, unsigned char *data, size_t length)
+{
+	int res;
+
+	register_device_error(dev, NULL);
+
+	res = ioctl(dev->device_handle, HIDIOCGINPUT(length), data);
+	if (res < 0)
+		register_device_error_format(dev, "ioctl (GINPUT): %s", strerror(errno));
+
+	return res;
+}
 
 void HID_API_EXPORT hid_close(hid_device *dev)
 {
 	if (!dev)
 		return;
+
 	close(dev->device_handle);
+
+	/* Free the device error message */
+	register_device_error(dev, NULL);
+
+	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)
 {
-	return get_device_string(dev, DEVICE_STRING_MANUFACTURER, string, maxlen);
+	if (!string || !maxlen) {
+		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)
 {
-	return get_device_string(dev, DEVICE_STRING_PRODUCT, string, maxlen);
+	if (!string || !maxlen) {
+		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)
 {
-	return get_device_string(dev, DEVICE_STRING_SERIAL, string, maxlen);
+	if (!string || !maxlen) {
+		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) {
+	if (!dev->device_info) {
+		// Lazy initialize device_info
+		dev->device_info = create_device_info_for_hid_device(dev);
+	}
+
+	// create_device_info_for_hid_device 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)dev;
 	(void)string_index;
 	(void)string;
 	(void)maxlen;
+
+	register_device_error(dev, "hid_get_indexed_string: not supported by hidraw");
+
 	return -1;
 }
 
 
-HID_API_EXPORT const wchar_t * HID_API_CALL  hid_error(hid_device *dev)
+int HID_API_EXPORT_CALL hid_get_report_descriptor(hid_device *dev, unsigned char *buf, size_t buf_size)
 {
-	return NULL;
+	struct hidraw_report_descriptor rpt_desc;
+	int res = get_hid_report_descriptor_from_hidraw(dev, &rpt_desc);
+	if (res < 0) {
+		/* error already registered */
+		return res;
+	}
+
+	if (rpt_desc.size < buf_size) {
+		buf_size = (size_t) rpt_desc.size;
+	}
+
+	memcpy(buf, rpt_desc.value, buf_size);
+
+	return (int) buf_size;
 }
 
-#ifdef NAMESPACE
+
+/* 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->last_error_str == NULL)
+			return L"Success";
+		return dev->last_error_str;
+	}
+
+	if (last_global_error_str == NULL)
+		return L"Success";
+	return last_global_error_str;
 }
-#endif

+ 0 - 3
src/hidapi/linux/hidraw.cpp

@@ -1,3 +0,0 @@
-
-#define NAMESPACE HIDRAW
-#include "hid.c"

+ 5 - 0
src/hidapi/m4/.gitignore

@@ -0,0 +1,5 @@
+# Ignore All, except pkg.m4, and of course this file.
+*
+!.gitignore
+!pkg.m4
+!ax_pthread.m4

+ 17 - 0
src/hidapi/mac/.gitignore

@@ -0,0 +1,17 @@
+Debug
+Release
+*.exp
+*.ilk
+*.lib
+*.suo
+*.vcproj.*
+*.ncb
+*.suo
+*.dll
+*.pdb
+*.o
+hidapi-hidtest
+.deps
+.libs
+*.la
+*.lo

+ 48 - 0
src/hidapi/mac/CMakeLists.txt

@@ -0,0 +1,48 @@
+cmake_minimum_required(VERSION 3.4.3 FATAL_ERROR)
+
+list(APPEND HIDAPI_PUBLIC_HEADERS "hidapi_darwin.h")
+
+add_library(hidapi_darwin
+    ${HIDAPI_PUBLIC_HEADERS}
+    hid.c
+)
+
+find_package(Threads REQUIRED)
+
+target_link_libraries(hidapi_darwin
+    PUBLIC hidapi_include
+    PRIVATE Threads::Threads
+    PRIVATE "-framework IOKit" "-framework CoreFoundation" "-framework AppKit"
+)
+
+set_target_properties(hidapi_darwin
+    PROPERTIES
+        EXPORT_NAME "darwin"
+        OUTPUT_NAME "hidapi"
+        VERSION ${PROJECT_VERSION}
+        SOVERSION ${PROJECT_VERSION_MAJOR}
+        MACHO_COMPATIBILITY_VERSION ${PROJECT_VERSION_MAJOR}
+        FRAMEWORK_VERSION ${PROJECT_VERSION_MAJOR}
+        PUBLIC_HEADER "${HIDAPI_PUBLIC_HEADERS}"
+)
+
+# compatibility with find_package()
+add_library(hidapi::darwin ALIAS hidapi_darwin)
+# compatibility with raw library link
+add_library(hidapi ALIAS hidapi_darwin)
+
+set(PUBLIC_HEADER_DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}")
+if(NOT CMAKE_FRAMEWORK)
+    set(PUBLIC_HEADER_DESTINATION "${PUBLIC_HEADER_DESTINATION}/hidapi")
+endif()
+
+if(HIDAPI_INSTALL_TARGETS)
+    install(TARGETS hidapi_darwin EXPORT hidapi
+        LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}"
+        ARCHIVE DESTINATION "${CMAKE_INSTALL_LIBDIR}"
+        FRAMEWORK DESTINATION "${CMAKE_INSTALL_LIBDIR}"
+        PUBLIC_HEADER DESTINATION "${PUBLIC_HEADER_DESTINATION}"
+    )
+endif()
+
+hidapi_configure_pc("${PROJECT_ROOT}/pc/hidapi.pc.in")

+ 6 - 11
src/hidapi/mac/Makefile-manual

@@ -9,24 +9,19 @@
 all: hidtest
 
 CC=gcc
-CXX=g++
-COBJS=hid.o
-CPPOBJS=../hidtest/hidtest.o
-OBJS=$(COBJS) $(CPPOBJS)
-CFLAGS+=-I../hidapi -Wall -g -c 
-LIBS=-framework IOKit -framework CoreFoundation
+COBJS=hid.o ../hidtest/test.o
+OBJS=$(COBJS)
+CFLAGS+=-I../hidapi -I. -Wall -g -c
+LIBS=-framework IOKit -framework CoreFoundation -framework AppKit
 
 
 hidtest: $(OBJS)
-	g++ -Wall -g $^ $(LIBS) -o hidtest
+	$(CC) -Wall -g $^ $(LIBS) -o hidtest
 
 $(COBJS): %.o: %.c
 	$(CC) $(CFLAGS) $< -o $@
 
-$(CPPOBJS): %.o: %.cpp
-	$(CXX) $(CFLAGS) $< -o $@
-
 clean:
-	rm -f *.o hidtest $(CPPOBJS)
+	rm -f *.o hidtest
 
 .PHONY: clean

+ 935 - 662
src/hidapi/mac/hid.c

@@ -1,60 +1,67 @@
 /*******************************************************
  HIDAPI - Multi-Platform library for
  communication with HID devices.
- 
+
  Alan Ott
  Signal 11 Software
- 
- 2010-07-03
- 
- Copyright 2010, All Rights Reserved.
- 
+
+ libusb/hidapi Team
+
+ Copyright 2022, All Rights Reserved.
+
  At the discretion of the user of this library,
  this software may be licensed under the terms of the
- GNU Public License v3, a BSD-Style license, or the
+ GNU General Public License v3, a BSD-Style license, or the
  original HIDAPI license as outlined in the LICENSE.txt,
  LICENSE-gpl3.txt, LICENSE-bsd.txt, and LICENSE-orig.txt
  files located at the root of the source distribution.
  These files may also be found in the public source
  code repository located at:
- https://github.com/libusb/hidapi .
- ********************************************************/
-#include "SDL_internal.h"
-
+        https://github.com/libusb/hidapi .
+********************************************************/
 
 /* See Apple Technical Note TN2187 for details on IOHidManager. */
 
 #include <IOKit/hid/IOHIDManager.h>
 #include <IOKit/hid/IOHIDKeys.h>
+#include <IOKit/IOKitLib.h>
+#include <IOKit/usb/USBSpec.h>
 #include <CoreFoundation/CoreFoundation.h>
+#include <mach/mach_error.h>
+#include <stdbool.h>
 #include <wchar.h>
+#include <locale.h>
 #include <pthread.h>
 #include <sys/time.h>
 #include <unistd.h>
+#include <dlfcn.h>
 
-#include "../hidapi/hidapi.h"
+#include "hidapi_darwin.h"
 
-#define VALVE_USB_VID		0x28DE
+/* As defined in AppKit.h, but we don't need the entire AppKit for a single constant. */
+extern const double NSAppKitVersionNumber;
 
 /* Barrier implementation because Mac OSX doesn't have pthread_barrier.
- It also doesn't have clock_gettime(). So much for POSIX and SUSv2.
- This implementation came from Brent Priddy and was posted on
- StackOverflow. It is used with his permission. */
+   It also doesn't have clock_gettime(). So much for POSIX and SUSv2.
+   This implementation came from Brent Priddy and was posted on
+   StackOverflow. It is used with his permission. */
 typedef int pthread_barrierattr_t;
 typedef struct pthread_barrier {
-	pthread_mutex_t mutex;
-	pthread_cond_t cond;
-	int count;
-	int trip_count;
+    pthread_mutex_t mutex;
+    pthread_cond_t cond;
+    int count;
+    int trip_count;
 } pthread_barrier_t;
 
 static int pthread_barrier_init(pthread_barrier_t *barrier, const pthread_barrierattr_t *attr, unsigned int count)
 {
+	(void) attr;
+
 	if(count == 0) {
 		errno = EINVAL;
 		return -1;
 	}
-	
+
 	if(pthread_mutex_init(&barrier->mutex, 0) < 0) {
 		return -1;
 	}
@@ -64,7 +71,7 @@ static int pthread_barrier_init(pthread_barrier_t *barrier, const pthread_barrie
 	}
 	barrier->trip_count = count;
 	barrier->count = 0;
-	
+
 	return 0;
 }
 
@@ -103,10 +110,23 @@ struct input_report {
 	struct input_report *next;
 };
 
+static struct hid_api_version api_version = {
+	.major = HID_API_VERSION_MAJOR,
+	.minor = HID_API_VERSION_MINOR,
+	.patch = HID_API_VERSION_PATCH
+};
+
+/* - Run context - */
+static	IOHIDManagerRef hid_mgr = 0x0;
+static	int is_macos_10_10_or_greater = 0;
+static	IOOptionBits device_open_options = 0;
+static	wchar_t *last_global_error_str = NULL;
+/* --- */
+
 struct hid_device_ {
 	IOHIDDeviceRef device_handle;
+	IOOptionBits open_options;
 	int blocking;
-	int uses_numbered_reports;
 	int disconnected;
 	CFStringRef run_loop_mode;
 	CFRunLoopRef run_loop;
@@ -114,113 +134,177 @@ struct hid_device_ {
 	uint8_t *input_report_buf;
 	CFIndex max_input_report_len;
 	struct input_report *input_reports;
-	
+	struct hid_device_info* device_info;
+
 	pthread_t thread;
 	pthread_mutex_t mutex; /* Protects input_reports */
 	pthread_cond_t condition;
 	pthread_barrier_t barrier; /* Ensures correct startup sequence */
 	pthread_barrier_t shutdown_barrier; /* Ensures correct shutdown sequence */
 	int shutdown_thread;
+	wchar_t *last_error_str;
 };
 
-struct hid_device_list_node
-{
-	struct hid_device_ *dev;
-	struct hid_device_list_node *next;
-};
-
-static 	IOHIDManagerRef hid_mgr = 0x0;
-static 	struct hid_device_list_node *device_list = 0x0;
-
 static hid_device *new_hid_device(void)
 {
-	hid_device *dev = (hid_device*)calloc(1, sizeof(hid_device));
+	hid_device *dev = (hid_device*) calloc(1, sizeof(hid_device));
+	if (dev == NULL) {
+		return NULL;
+	}
+
 	dev->device_handle = NULL;
+	dev->open_options = device_open_options;
 	dev->blocking = 1;
-	dev->uses_numbered_reports = 0;
 	dev->disconnected = 0;
 	dev->run_loop_mode = NULL;
 	dev->run_loop = NULL;
 	dev->source = NULL;
 	dev->input_report_buf = NULL;
 	dev->input_reports = NULL;
+	dev->device_info = NULL;
 	dev->shutdown_thread = 0;
-	
+	dev->last_error_str = NULL;
+
 	/* Thread objects */
 	pthread_mutex_init(&dev->mutex, NULL);
 	pthread_cond_init(&dev->condition, NULL);
 	pthread_barrier_init(&dev->barrier, NULL, 2);
 	pthread_barrier_init(&dev->shutdown_barrier, NULL, 2);
-	
+
 	return dev;
 }
 
 static void free_hid_device(hid_device *dev)
 {
-	struct input_report *rpt;
 	if (!dev)
 		return;
-	
+
 	/* Delete any input reports still left over. */
-	rpt = dev->input_reports;
+	struct input_report *rpt = dev->input_reports;
 	while (rpt) {
 		struct input_report *next = rpt->next;
 		free(rpt->data);
 		free(rpt);
 		rpt = next;
 	}
-	
+
 	/* Free the string and the report buffer. The check for NULL
-	 is necessary here as CFRelease() doesn't handle NULL like
-	 free() and others do. */
+	   is necessary here as CFRelease() doesn't handle NULL like
+	   free() and others do. */
 	if (dev->run_loop_mode)
 		CFRelease(dev->run_loop_mode);
 	if (dev->source)
 		CFRelease(dev->source);
 	free(dev->input_report_buf);
+	hid_free_enumeration(dev->device_info);
 
-	if (device_list) {
-		if (device_list->dev == dev) {
-			device_list = device_list->next;
-		}
-		else {
-			struct hid_device_list_node *node = device_list;
-			while (node) {
-				if (node->next && node->next->dev == dev) {
-					struct hid_device_list_node *new_next = node->next->next;
-					free(node->next);
-					node->next = new_next;
-					break;
-				}
-
-				node = node->next;
-			}
-		}
-	}
-	
 	/* Clean up the thread objects */
 	pthread_barrier_destroy(&dev->shutdown_barrier);
 	pthread_barrier_destroy(&dev->barrier);
 	pthread_cond_destroy(&dev->condition);
 	pthread_mutex_destroy(&dev->mutex);
-	
+
 	/* Free the structure itself. */
 	free(dev);
 }
 
-#if 0
-static void register_error(hid_device *device, const char *op)
+
+/* 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[1024];
+	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(&last_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(&last_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->last_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->last_error_str, format, args);
+	va_end(args);
 }
-#endif
 
 
+static CFArrayRef get_array_property(IOHIDDeviceRef device, CFStringRef key)
+{
+	CFTypeRef ref = IOHIDDeviceGetProperty(device, key);
+	if (ref != NULL && CFGetTypeID(ref) == CFArrayGetTypeID()) {
+		return (CFArrayRef)ref;
+	} else {
+		return NULL;
+	}
+}
+
 static int32_t get_int_property(IOHIDDeviceRef device, CFStringRef key)
 {
 	CFTypeRef ref;
-	int32_t value;
-	
+	int32_t value = 0;
+
 	ref = IOHIDDeviceGetProperty(device, key);
 	if (ref) {
 		if (CFGetTypeID(ref) == CFNumberGetTypeID()) {
@@ -231,6 +315,41 @@ static int32_t get_int_property(IOHIDDeviceRef device, CFStringRef key)
 	return 0;
 }
 
+static bool try_get_int_property(IOHIDDeviceRef device, CFStringRef key, int32_t *out_val)
+{
+	bool result = false;
+	CFTypeRef ref;
+
+	ref = IOHIDDeviceGetProperty(device, key);
+	if (ref) {
+		if (CFGetTypeID(ref) == CFNumberGetTypeID()) {
+			result = CFNumberGetValue((CFNumberRef) ref, kCFNumberSInt32Type, out_val);
+		}
+	}
+	return result;
+}
+
+static bool try_get_ioregistry_int_property(io_service_t service, CFStringRef property, int32_t *out_val)
+{
+	bool result = false;
+	CFTypeRef ref = IORegistryEntryCreateCFProperty(service, property, kCFAllocatorDefault, 0);
+
+	if (ref) {
+		if (CFGetTypeID(ref) == CFNumberGetTypeID()) {
+			result = CFNumberGetValue(ref, kCFNumberSInt32Type, out_val);
+		}
+
+		CFRelease(ref);
+	}
+
+	return result;
+}
+
+static CFArrayRef get_usage_pairs(IOHIDDeviceRef device)
+{
+	return get_array_property(device, CFSTR(kIOHIDDeviceUsagePairsKey));
+}
+
 static unsigned short get_vendor_id(IOHIDDeviceRef device)
 {
 	return get_int_property(device, CFSTR(kIOHIDVendorIDKey));
@@ -241,7 +360,6 @@ static unsigned short get_product_id(IOHIDDeviceRef device)
 	return get_int_property(device, CFSTR(kIOHIDProductIDKey));
 }
 
-
 static int32_t get_max_report_length(IOHIDDeviceRef device)
 {
 	return get_int_property(device, CFSTR(kIOHIDMaxInputReportSizeKey));
@@ -250,77 +368,44 @@ static int32_t get_max_report_length(IOHIDDeviceRef device)
 static int get_string_property(IOHIDDeviceRef device, CFStringRef prop, wchar_t *buf, size_t len)
 {
 	CFStringRef str;
-	
+
 	if (!len)
 		return 0;
 
-	if (CFGetTypeID(prop) != CFStringGetTypeID())
-		return 0;
+	str = (CFStringRef) IOHIDDeviceGetProperty(device, prop);
 
-	str = (CFStringRef)IOHIDDeviceGetProperty(device, prop);
-	
 	buf[0] = 0;
-	
-	if (str && CFGetTypeID(str) == CFStringGetTypeID()) {
-		CFIndex used_buf_len, chars_copied;
-		CFRange range;
+
+	if (str) {
 		CFIndex str_len = CFStringGetLength(str);
+		CFRange range;
+		CFIndex used_buf_len;
+		CFIndex chars_copied;
+
 		len --;
+
 		range.location = 0;
-		range.length = (str_len > len)? len: str_len;
+		range.length = ((size_t) str_len > len)? len: (size_t) str_len;
 		chars_copied = CFStringGetBytes(str,
-										range,
-										kCFStringEncodingUTF32LE,
-										(char)'?',
-										FALSE,
-										(UInt8*)buf,
-										len,
-										&used_buf_len);
-		
-		buf[chars_copied] = 0;
-		return (int)chars_copied;
-	}
-	else
-		return 0;
-	
-}
+			range,
+			kCFStringEncodingUTF32LE,
+			(char) '?',
+			FALSE,
+			(UInt8*)buf,
+			len * sizeof(wchar_t),
+			&used_buf_len);
+
+		if (chars_copied <= 0)
+			buf[0] = 0;
+		else
+			buf[chars_copied] = 0;
 
-static int get_string_property_utf8(IOHIDDeviceRef device, CFStringRef prop, char *buf, size_t len)
-{
-	CFStringRef str;
-	if (!len)
 		return 0;
-	
-	if (CFGetTypeID(prop) != CFStringGetTypeID())
-		return 0;
-
-	str = (CFStringRef)IOHIDDeviceGetProperty(device, prop);
-	
-	buf[0] = 0;
-	
-	if (str && CFGetTypeID(str) == CFStringGetTypeID()) {
-		CFIndex used_buf_len, chars_copied;
-		CFRange range;
-		CFIndex str_len = CFStringGetLength(str);
-		len--;
-		range.location = 0;
-		range.length = (str_len > len)? len: str_len;
-		chars_copied = CFStringGetBytes(str,
-										range,
-										kCFStringEncodingUTF8,
-										(char)'?',
-										FALSE,
-										(UInt8*)buf,
-										len,
-										&used_buf_len);
-		
-		buf[chars_copied] = 0;
-		return (int)used_buf_len;
 	}
 	else
-		return 0;
-}
+		return -1;
 
+}
 
 static int get_serial_number(IOHIDDeviceRef device, wchar_t *buf, size_t len)
 {
@@ -342,151 +427,50 @@ static int get_product_string(IOHIDDeviceRef device, wchar_t *buf, size_t len)
 static wchar_t *dup_wcs(const wchar_t *s)
 {
 	size_t len = wcslen(s);
-	wchar_t *ret = (wchar_t *)malloc((len+1)*sizeof(wchar_t));
+	wchar_t *ret = (wchar_t*) malloc((len+1)*sizeof(wchar_t));
 	wcscpy(ret, s);
-	
-	return ret;
-}
-
-
-static int make_path(IOHIDDeviceRef device, char *buf, size_t len)
-{
-	int res;
-	unsigned short vid, pid;
-	char transport[32];
-	
-	buf[0] = '\0';
-	
-	res = get_string_property_utf8(
-								   device, CFSTR(kIOHIDTransportKey),
-								   transport, sizeof(transport));
-	
-	if (!res)
-		return -1;
-	
-	vid = get_vendor_id(device);
-	pid = get_product_id(device);
-	
-	res = snprintf(buf, len, "%s_%04hx_%04hx_%p",
-				   transport, vid, pid, device);
-	
-	
-	buf[len-1] = '\0';
-	return res+1;
-}
-
-static void hid_device_removal_callback(void *context, IOReturn result,
-                                        void *sender, IOHIDDeviceRef hid_ref)
-{
-	// The device removal callback is sometimes called even after being
-	// unregistered, leading to a crash when trying to access fields in
-	// the already freed hid_device. We keep a linked list of all created
-	// hid_device's so that the one being removed can be checked against
-	// the list to see if it really hasn't been closed yet and needs to
-	// be dealt with here.
-	struct hid_device_list_node *node = device_list;
-	while (node) {
-		if (node->dev->device_handle == hid_ref) {
-			node->dev->disconnected = 1;
-			CFRunLoopStop(node->dev->run_loop);
-			break;
-		}
-
-		node = node->next;
-	}
-}
-
-static CFDictionaryRef
-create_usage_match(const UInt32 page, const UInt32 usage, int *okay)
-{
-	CFDictionaryRef retval = NULL;
-	CFNumberRef pageNumRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &page);
-	CFNumberRef usageNumRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &usage);
-	const void *keys[2] = { (void *) CFSTR(kIOHIDDeviceUsagePageKey), (void *) CFSTR(kIOHIDDeviceUsageKey) };
-	const void *vals[2] = { (void *) pageNumRef, (void *) usageNumRef };
-
-	if (pageNumRef && usageNumRef) {
-		retval = CFDictionaryCreate(kCFAllocatorDefault, keys, vals, 2, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
-	}
-
-	if (pageNumRef) {
-		CFRelease(pageNumRef);
-	}
-	if (usageNumRef) {
-		CFRelease(usageNumRef);
-	}
-
-	if (!retval) {
-		*okay = 0;
-	}
-
-	return retval;
-}
-
-static CFDictionaryRef
-create_vendor_match(const UInt32 vendor, int *okay)
-{
-	CFDictionaryRef retval = NULL;
-	CFNumberRef vidNumRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &vendor);
-	const void *keys[1] = { (void *) CFSTR(kIOHIDVendorIDKey) };
-	const void *vals[1] = { (void *) vidNumRef };
-
-	if (vidNumRef) {
-		retval = CFDictionaryCreate(kCFAllocatorDefault, keys, vals, 1, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
-		CFRelease(vidNumRef);
-	}
-
-	if (!retval) {
-		*okay = 0;
-	}
 
-	return retval;
+	return ret;
 }
 
 /* Initialize the IOHIDManager. Return 0 for success and -1 for failure. */
 static int init_hid_manager(void)
 {
-	int okay = 1;
-	const void *vals[] = {
-		(void *) create_usage_match(kHIDPage_GenericDesktop, kHIDUsage_GD_Joystick, &okay),
-		(void *) create_usage_match(kHIDPage_GenericDesktop, kHIDUsage_GD_GamePad, &okay),
-		(void *) create_usage_match(kHIDPage_GenericDesktop, kHIDUsage_GD_MultiAxisController, &okay),
-		(void *) create_vendor_match(VALVE_USB_VID, &okay),
-	};
-	const size_t numElements = SDL_arraysize(vals);
-	CFArrayRef matchingArray = okay ? CFArrayCreate(kCFAllocatorDefault, vals, numElements, &kCFTypeArrayCallBacks) : NULL;
-	size_t i;
-
-	for (i = 0; i < numElements; i++) {
-		if (vals[i]) {
-			CFRelease((CFTypeRef) vals[i]);
-		}
-	}
-
 	/* Initialize all the HID Manager Objects */
 	hid_mgr = IOHIDManagerCreate(kCFAllocatorDefault, kIOHIDOptionsTypeNone);
 	if (hid_mgr) {
-		IOHIDManagerSetDeviceMatchingMultiple(hid_mgr, matchingArray);
+		IOHIDManagerSetDeviceMatching(hid_mgr, NULL);
 		IOHIDManagerScheduleWithRunLoop(hid_mgr, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);
-		IOHIDManagerRegisterDeviceRemovalCallback(hid_mgr, hid_device_removal_callback, NULL);
-	}
-	
-	if (matchingArray != NULL) {
-		CFRelease(matchingArray);
+		return 0;
 	}
 
-	return hid_mgr ? 0 : -1;
+	register_global_error("Failed to create IOHIDManager");
+	return -1;
+}
+
+HID_API_EXPORT const struct hid_api_version* HID_API_CALL hid_version(void)
+{
+	return &api_version;
+}
+
+HID_API_EXPORT const char* HID_API_CALL hid_version_str(void)
+{
+	return HID_API_VERSION_STR;
 }
 
 /* Initialize the IOHIDManager if necessary. This is the public function, and
- it is safe to call this function repeatedly. Return 0 for success and -1
- for failure. */
+   it is safe to call this function repeatedly. Return 0 for success and -1
+   for failure. */
 int HID_API_EXPORT hid_init(void)
 {
+	register_global_error(NULL);
+
 	if (!hid_mgr) {
+		is_macos_10_10_or_greater = (NSAppKitVersionNumber >= 1343); /* NSAppKitVersionNumber10_10 */
+		hid_darwin_set_open_exclusive(1); /* Backward compatibility */
 		return init_hid_manager();
 	}
-	
+
 	/* Already initialized. */
 	return 0;
 }
@@ -499,7 +483,10 @@ int HID_API_EXPORT hid_exit(void)
 		CFRelease(hid_mgr);
 		hid_mgr = NULL;
 	}
-	
+
+	/* Free global error message */
+	register_global_error(NULL);
+
 	return 0;
 }
 
@@ -510,124 +497,296 @@ static void process_pending_events(void) {
 	} while(res != kCFRunLoopRunFinished && res != kCFRunLoopRunTimedOut);
 }
 
+static int read_usb_interface_from_hid_service_parent(io_service_t hid_service)
+{
+	int32_t result = -1;
+	bool success = false;
+	io_registry_entry_t current = IO_OBJECT_NULL;
+	kern_return_t res;
+	int parent_number = 0;
+
+	res = IORegistryEntryGetParentEntry(hid_service, kIOServicePlane, &current);
+	while (KERN_SUCCESS == res
+			/* Only search up to 3 parent entries.
+			 * With the default driver - the parent-of-interest supposed to be the first one,
+			 * but lets assume some custom drivers or so, with deeper tree. */
+			&& parent_number < 3) {
+		io_registry_entry_t parent = IO_OBJECT_NULL;
+		int32_t interface_number = -1;
+		parent_number++;
+
+		success = try_get_ioregistry_int_property(current, CFSTR(kUSBInterfaceNumber), &interface_number);
+		if (success) {
+			result = interface_number;
+			break;
+		}
+
+		res = IORegistryEntryGetParentEntry(current, kIOServicePlane, &parent);
+		if (parent) {
+			IOObjectRelease(current);
+			current = parent;
+		}
+
+	}
+
+	if (current) {
+		IOObjectRelease(current);
+		current = IO_OBJECT_NULL;
+	}
+
+	return result;
+}
+
+static struct hid_device_info *create_device_info_with_usage(IOHIDDeviceRef dev, int32_t usage_page, int32_t usage)
+{
+	unsigned short dev_vid;
+	unsigned short dev_pid;
+	int BUF_LEN = 256;
+	wchar_t buf[BUF_LEN];
+	CFTypeRef transport_prop;
+
+	struct hid_device_info *cur_dev;
+	io_service_t hid_service;
+	kern_return_t res;
+	uint64_t entry_id = 0;
+
+	if (dev == NULL) {
+		return NULL;
+	}
+
+	cur_dev = (struct hid_device_info *)calloc(1, sizeof(struct hid_device_info));
+	if (cur_dev == NULL) {
+		return NULL;
+	}
+
+	dev_vid = get_vendor_id(dev);
+	dev_pid = get_product_id(dev);
+
+	cur_dev->usage_page = usage_page;
+	cur_dev->usage = usage;
+
+	/* Fill out the record */
+	cur_dev->next = NULL;
+
+	/* Fill in the path (as a unique ID of the service entry) */
+	cur_dev->path = NULL;
+	hid_service = IOHIDDeviceGetService(dev);
+	if (hid_service != MACH_PORT_NULL) {
+		res = IORegistryEntryGetRegistryEntryID(hid_service, &entry_id);
+	}
+	else {
+		res = KERN_INVALID_ARGUMENT;
+	}
+
+	if (res == KERN_SUCCESS) {
+		/* max value of entry_id(uint64_t) is 18446744073709551615 which is 20 characters long,
+		   so for (max) "path" string 'DevSrvsID:18446744073709551615' we would need
+		   9+1+20+1=31 bytes buffer, but allocate 32 for simple alignment */
+		const size_t path_len = 32;
+		cur_dev->path = calloc(1, path_len);
+		if (cur_dev->path != NULL) {
+			snprintf(cur_dev->path, path_len, "DevSrvsID:%llu", entry_id);
+		}
+	}
+
+	if (cur_dev->path == NULL) {
+		/* for whatever reason, trying to keep it a non-NULL string */
+		cur_dev->path = strdup("");
+	}
+
+	/* Serial Number */
+	get_serial_number(dev, buf, BUF_LEN);
+	cur_dev->serial_number = dup_wcs(buf);
+
+	/* Manufacturer and Product strings */
+	get_manufacturer_string(dev, buf, BUF_LEN);
+	cur_dev->manufacturer_string = dup_wcs(buf);
+	get_product_string(dev, buf, BUF_LEN);
+	cur_dev->product_string = dup_wcs(buf);
+
+	/* VID/PID */
+	cur_dev->vendor_id = dev_vid;
+	cur_dev->product_id = dev_pid;
+
+	/* Release Number */
+	cur_dev->release_number = get_int_property(dev, CFSTR(kIOHIDVersionNumberKey));
+
+	/* Interface Number.
+	 * We can only retrieve the interface number for USB HID devices.
+	 * See below */
+	cur_dev->interface_number = -1;
+
+	/* Bus Type */
+	transport_prop = IOHIDDeviceGetProperty(dev, CFSTR(kIOHIDTransportKey));
+
+	if (transport_prop != NULL && CFGetTypeID(transport_prop) == CFStringGetTypeID()) {
+		if (CFStringCompare((CFStringRef)transport_prop, CFSTR(kIOHIDTransportUSBValue), 0) == kCFCompareEqualTo) {
+			int32_t interface_number = -1;
+			cur_dev->bus_type = HID_API_BUS_USB;
+
+			/* A IOHIDDeviceRef used to have this simple property,
+			 * until macOS 13.3 - we will try to use it. */
+			if (try_get_int_property(dev, CFSTR(kUSBInterfaceNumber), &interface_number)) {
+				cur_dev->interface_number = interface_number;
+			} else {
+				/* Otherwise fallback to io_service_t property.
+				 * (of one of the parent services). */
+				cur_dev->interface_number = read_usb_interface_from_hid_service_parent(hid_service);
+
+				/* If the above doesn't work -
+				 * no (known) fallback exists at this point. */
+			}
+
+		/* Match "Bluetooth", "BluetoothLowEnergy" and "Bluetooth Low Energy" strings */
+		} else if (CFStringHasPrefix((CFStringRef)transport_prop, CFSTR(kIOHIDTransportBluetoothValue))) {
+			cur_dev->bus_type = HID_API_BUS_BLUETOOTH;
+		} else if (CFStringCompare((CFStringRef)transport_prop, CFSTR(kIOHIDTransportI2CValue), 0) == kCFCompareEqualTo) {
+			cur_dev->bus_type = HID_API_BUS_I2C;
+		} else  if (CFStringCompare((CFStringRef)transport_prop, CFSTR(kIOHIDTransportSPIValue), 0) == kCFCompareEqualTo) {
+			cur_dev->bus_type = HID_API_BUS_SPI;
+		}
+	}
+
+	return cur_dev;
+}
+
+static struct hid_device_info *create_device_info(IOHIDDeviceRef device)
+{
+	const int32_t primary_usage_page = get_int_property(device, CFSTR(kIOHIDPrimaryUsagePageKey));
+	const int32_t primary_usage = get_int_property(device, CFSTR(kIOHIDPrimaryUsageKey));
+
+	/* Primary should always be first, to match previous behavior. */
+	struct hid_device_info *root = create_device_info_with_usage(device, primary_usage_page, primary_usage);
+	struct hid_device_info *cur = root;
+
+	if (!root)
+		return NULL;
+
+	CFArrayRef usage_pairs = get_usage_pairs(device);
+
+	if (usage_pairs != NULL) {
+		struct hid_device_info *next = NULL;
+		for (CFIndex i = 0; i < CFArrayGetCount(usage_pairs); i++) {
+			CFTypeRef dict = CFArrayGetValueAtIndex(usage_pairs, i);
+			if (CFGetTypeID(dict) != CFDictionaryGetTypeID()) {
+				continue;
+			}
+
+			CFTypeRef usage_page_ref, usage_ref;
+			int32_t usage_page, usage;
+
+			if (!CFDictionaryGetValueIfPresent((CFDictionaryRef)dict, CFSTR(kIOHIDDeviceUsagePageKey), &usage_page_ref) ||
+			    !CFDictionaryGetValueIfPresent((CFDictionaryRef)dict, CFSTR(kIOHIDDeviceUsageKey), &usage_ref) ||
+					CFGetTypeID(usage_page_ref) != CFNumberGetTypeID() ||
+					CFGetTypeID(usage_ref) != CFNumberGetTypeID() ||
+					!CFNumberGetValue((CFNumberRef)usage_page_ref, kCFNumberSInt32Type, &usage_page) ||
+					!CFNumberGetValue((CFNumberRef)usage_ref, kCFNumberSInt32Type, &usage)) {
+					continue;
+			}
+			if (usage_page == primary_usage_page && usage == primary_usage)
+				continue; /* Already added. */
+
+			next = create_device_info_with_usage(device, usage_page, usage);
+			cur->next = next;
+			if (next != NULL) {
+				cur = next;
+			}
+		}
+	}
+
+	return root;
+}
+
 struct hid_device_info  HID_API_EXPORT *hid_enumerate(unsigned short vendor_id, unsigned short product_id)
 {
-	struct hid_device_info *root = NULL; // return object
+	struct hid_device_info *root = NULL; /* return object */
 	struct hid_device_info *cur_dev = NULL;
 	CFIndex num_devices;
-	CFSetRef device_set;
-	IOHIDDeviceRef *device_array;
 	int i;
-	const char *hint = SDL_GetHint(SDL_HINT_HIDAPI_IGNORE_DEVICES);
-	
+
 	/* Set up the HID Manager if it hasn't been done */
-	if (hid_init() < 0)
+	if (hid_init() < 0) {
 		return NULL;
-	
+	}
+	/* register_global_error: global error is set/reset by hid_init */
+
 	/* give the IOHIDManager a chance to update itself */
 	process_pending_events();
-	
+
 	/* Get a list of the Devices */
-	device_set = IOHIDManagerCopyDevices(hid_mgr);
-	if (!device_set)
-		return NULL;
+	CFMutableDictionaryRef matching = NULL;
+	if (vendor_id != 0 || product_id != 0) {
+		matching = CFDictionaryCreateMutable(kCFAllocatorDefault, kIOHIDOptionsTypeNone, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
+
+		if (matching && vendor_id != 0) {
+			CFNumberRef v = CFNumberCreate(kCFAllocatorDefault, kCFNumberShortType, &vendor_id);
+			CFDictionarySetValue(matching, CFSTR(kIOHIDVendorIDKey), v);
+			CFRelease(v);
+		}
 
-	/* Convert the list into a C array so we can iterate easily. */	
-	num_devices = CFSetGetCount(device_set);
-	if (!num_devices) {
-		CFRelease(device_set);
-		return NULL;
+		if (matching && product_id != 0) {
+			CFNumberRef p = CFNumberCreate(kCFAllocatorDefault, kCFNumberShortType, &product_id);
+			CFDictionarySetValue(matching, CFSTR(kIOHIDProductIDKey), p);
+			CFRelease(p);
+		}
+	}
+	IOHIDManagerSetDeviceMatching(hid_mgr, matching);
+	if (matching != NULL) {
+		CFRelease(matching);
+	}
+
+	CFSetRef device_set = IOHIDManagerCopyDevices(hid_mgr);
+
+	IOHIDDeviceRef *device_array = NULL;
+
+	if (device_set != NULL) {
+		/* Convert the list into a C array so we can iterate easily. */
+		num_devices = CFSetGetCount(device_set);
+		device_array = (IOHIDDeviceRef*) calloc(num_devices, sizeof(IOHIDDeviceRef));
+		CFSetGetValues(device_set, (const void **) device_array);
+	} else {
+		num_devices = 0;
 	}
-	device_array = (IOHIDDeviceRef*)calloc(num_devices, sizeof(IOHIDDeviceRef));
-	CFSetGetValues(device_set, (const void **) device_array);
-	
-	/* Iterate over each device, making an entry for it. */	
+
+	/* Iterate over each device, making an entry for it. */
 	for (i = 0; i < num_devices; i++) {
-		unsigned short dev_vid;
-		unsigned short dev_pid;
-#define BUF_LEN 256
-		wchar_t buf[BUF_LEN];
-		char cbuf[BUF_LEN];
-		
+
 		IOHIDDeviceRef dev = device_array[i];
-		
 		if (!dev) {
 			continue;
 		}
 
-#if 0 // Prefer direct HID support as that has extended functionality
-#ifdef SDL_JOYSTICK_MFI
-		// We want to prefer Game Controller support where available,
-		// as Apple will likely be requiring that for supported devices.
-		extern SDL_bool IOS_SupportedHIDDevice(IOHIDDeviceRef device);
-		if (IOS_SupportedHIDDevice(dev)) {
+		struct hid_device_info *tmp = create_device_info(dev);
+		if (tmp == NULL) {
 			continue;
 		}
-#endif
-#endif
-
-		dev_vid = get_vendor_id(dev);
-		dev_pid = get_product_id(dev);
-		
-		/* See if there are any devices we should skip in enumeration */
-		if (hint) {
-			char vendor_match[16], product_match[16];
-			SDL_snprintf(vendor_match, sizeof(vendor_match), "0x%.4x/0x0000", dev_vid);
-			SDL_snprintf(product_match, sizeof(product_match), "0x%.4x/0x%.4x", dev_vid, dev_pid);
-			if (SDL_strcasestr(hint, vendor_match) || SDL_strcasestr(hint, product_match)) {
-				continue;
-			}
-		}
 
-		/* Check the VID/PID against the arguments */
-		if ((vendor_id == 0x0 || dev_vid == vendor_id) &&
-		    (product_id == 0x0 || dev_pid == product_id)) {
-			struct hid_device_info *tmp;
+		if (cur_dev) {
+			cur_dev->next = tmp;
+		}
+		else {
+			root = tmp;
+		}
+		cur_dev = tmp;
 
-			/* VID/PID match. Create the record. */
-			tmp = (struct hid_device_info *)calloc(1, sizeof(struct hid_device_info));
-			if (cur_dev) {
-				cur_dev->next = tmp;
-			}
-			else {
-				root = tmp;
-			}
-			cur_dev = tmp;
-			
-			// Get the Usage Page and Usage for this device.
-			cur_dev->usage_page = get_int_property(dev, CFSTR(kIOHIDPrimaryUsagePageKey));
-			cur_dev->usage = get_int_property(dev, CFSTR(kIOHIDPrimaryUsageKey));
-			
-			/* Fill out the record */
-			cur_dev->next = NULL;
-			make_path(dev, cbuf, sizeof(cbuf));
-			cur_dev->path = strdup(cbuf);
-			
-			/* Serial Number */
-			get_serial_number(dev, buf, BUF_LEN);
-			cur_dev->serial_number = dup_wcs(buf);
-			
-			/* Manufacturer and Product strings */
-			get_manufacturer_string(dev, buf, BUF_LEN);
-			cur_dev->manufacturer_string = dup_wcs(buf);
-			get_product_string(dev, buf, BUF_LEN);
-			cur_dev->product_string = dup_wcs(buf);
-			
-			/* VID/PID */
-			cur_dev->vendor_id = dev_vid;
-			cur_dev->product_id = dev_pid;
-			
-			/* Release Number */
-			cur_dev->release_number = get_int_property(dev, CFSTR(kIOHIDVersionNumberKey));
-			
-			/* Interface Number (Unsupported on Mac)*/
-			cur_dev->interface_number = -1;
+		/* move the pointer to the tail of returnd list */
+		while (cur_dev->next != NULL) {
+			cur_dev = cur_dev->next;
 		}
 	}
-	
+
 	free(device_array);
-	CFRelease(device_set);
-	
+	if (device_set != NULL)
+		CFRelease(device_set);
+
+	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.");
+		}
+	}
+
 	return root;
 }
 
@@ -649,11 +808,18 @@ void  HID_API_EXPORT hid_free_enumeration(struct hid_device_info *devs)
 hid_device * HID_API_EXPORT hid_open(unsigned short vendor_id, unsigned short product_id, const wchar_t *serial_number)
 {
 	/* This function is identical to the Linux version. Platform independent. */
+
 	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 &&
@@ -671,37 +837,56 @@ hid_device * HID_API_EXPORT hid_open(unsigned short vendor_id, unsigned short pr
 		}
 		cur_dev = cur_dev->next;
 	}
-	
+
 	if (path_to_open) {
-		/* Open the device */
-		handle = hid_open_path(path_to_open, 0);
+		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;
 }
 
+static void hid_device_removal_callback(void *context, IOReturn result,
+                                        void *sender)
+{
+	(void) result;
+	(void) sender;
+
+	/* Stop the Run Loop for this device. */
+	hid_device *d = (hid_device*) context;
+
+	d->disconnected = 1;
+	CFRunLoopStop(d->run_loop);
+}
+
 /* The Run Loop calls this function for each input report received.
- This function puts the data into a linked list to be picked up by
- hid_read(). */
+   This function puts the data into a linked list to be picked up by
+   hid_read(). */
 static void hid_report_callback(void *context, IOReturn result, void *sender,
-								IOHIDReportType report_type, uint32_t report_id,
-								uint8_t *report, CFIndex report_length)
+                         IOHIDReportType report_type, uint32_t report_id,
+                         uint8_t *report, CFIndex report_length)
 {
+	(void) result;
+	(void) sender;
+	(void) report_type;
+	(void) report_id;
+
 	struct input_report *rpt;
-	hid_device *dev = (hid_device *)context;
-	
+	hid_device *dev = (hid_device*) context;
+
 	/* Make a new Input Report object */
-	rpt = (struct input_report *)calloc(1, sizeof(struct input_report));
-	rpt->data = (uint8_t *)calloc(1, report_length);
+	rpt = (struct input_report*) calloc(1, sizeof(struct input_report));
+	rpt->data = (uint8_t*) calloc(1, report_length);
 	memcpy(rpt->data, report, report_length);
 	rpt->len = report_length;
 	rpt->next = NULL;
-	
+
 	/* Lock this section */
 	pthread_mutex_lock(&dev->mutex);
-	
+
 	/* Attach the new report object to the end of the list. */
 	if (dev->input_reports == NULL) {
 		/* The list is empty. Put it at the root. */
@@ -716,58 +901,58 @@ static void hid_report_callback(void *context, IOReturn result, void *sender,
 			num_queued++;
 		}
 		cur->next = rpt;
-		
+
 		/* Pop one off if we've reached 30 in the queue. This
-		 way we don't grow forever if the user never reads
-		 anything from the device. */
+		   way we don't grow forever if the user never reads
+		   anything from the device. */
 		if (num_queued > 30) {
 			return_data(dev, NULL, 0);
 		}
 	}
-	
+
 	/* Signal a waiting thread that there is data. */
 	pthread_cond_signal(&dev->condition);
-	
+
 	/* Unlock */
 	pthread_mutex_unlock(&dev->mutex);
-	
+
 }
 
-/* This gets called when the read_thred's run loop gets signaled by
- hid_close(), and serves to stop the read_thread's run loop. */
+/* This gets called when the read_thread's run loop gets signaled by
+   hid_close(), and serves to stop the read_thread's run loop. */
 static void perform_signal_callback(void *context)
 {
-	hid_device *dev = (hid_device *)context;
-	CFRunLoopStop(dev->run_loop); //TODO: CFRunLoopGetCurrent()
+	hid_device *dev = (hid_device*) context;
+	CFRunLoopStop(dev->run_loop); /*TODO: CFRunLoopGetCurrent()*/
 }
 
 static void *read_thread(void *param)
 {
-	hid_device *dev = (hid_device *)param;
-	CFRunLoopSourceContext ctx;
-    SInt32 code;
-	
+	hid_device *dev = (hid_device*) param;
+	SInt32 code;
+
 	/* Move the device's run loop to this thread. */
 	IOHIDDeviceScheduleWithRunLoop(dev->device_handle, CFRunLoopGetCurrent(), dev->run_loop_mode);
-	
+
 	/* Create the RunLoopSource which is used to signal the
-	 event loop to stop when hid_close() is called. */
+	   event loop to stop when hid_close() is called. */
+	CFRunLoopSourceContext ctx;
 	memset(&ctx, 0, sizeof(ctx));
 	ctx.version = 0;
 	ctx.info = dev;
 	ctx.perform = &perform_signal_callback;
 	dev->source = CFRunLoopSourceCreate(kCFAllocatorDefault, 0/*order*/, &ctx);
 	CFRunLoopAddSource(CFRunLoopGetCurrent(), dev->source, dev->run_loop_mode);
-	
+
 	/* Store off the Run Loop so it can be stopped from hid_close()
-	 and on device disconnection. */
+	   and on device disconnection. */
 	dev->run_loop = CFRunLoopGetCurrent();
-	
+
 	/* Notify the main thread that the read thread is up and running. */
 	pthread_barrier_wait(&dev->barrier);
-	
+
 	/* Run the Event Loop. CFRunLoopRunInMode() will dispatch HID input
-	 reports into the hid_report_callback(). */
+	   reports into the hid_report_callback(). */
 	while (!dev->shutdown_thread && !dev->disconnected) {
 		code = CFRunLoopRunInMode(dev->run_loop_mode, 1000/*sec*/, FALSE);
 		/* Return if the device has been disconnected */
@@ -775,171 +960,220 @@ static void *read_thread(void *param)
 			dev->disconnected = 1;
 			break;
 		}
-		
-		
+
+
 		/* Break if The Run Loop returns Finished or Stopped. */
 		if (code != kCFRunLoopRunTimedOut &&
 		    code != kCFRunLoopRunHandledSource) {
 			/* There was some kind of error. Setting
-			 shutdown seems to make sense, but
-			 there may be something else more appropriate */
+			   shutdown seems to make sense, but
+			   there may be something else more appropriate */
 			dev->shutdown_thread = 1;
 			break;
 		}
 	}
-	
+
 	/* Now that the read thread is stopping, Wake any threads which are
-	 waiting on data (in hid_read_timeout()). Do this under a mutex to
-	 make sure that a thread which is about to go to sleep waiting on
-	 the condition acutally will go to sleep before the condition is
-	 signaled. */
+	   waiting on data (in hid_read_timeout()). Do this under a mutex to
+	   make sure that a thread which is about to go to sleep waiting on
+	   the condition actually will go to sleep before the condition is
+	   signaled. */
 	pthread_mutex_lock(&dev->mutex);
 	pthread_cond_broadcast(&dev->condition);
 	pthread_mutex_unlock(&dev->mutex);
-	
+
 	/* Wait here until hid_close() is called and makes it past
-	 the call to CFRunLoopWakeUp(). This thread still needs to
-	 be valid when that function is called on the other thread. */
+	   the call to CFRunLoopWakeUp(). This thread still needs to
+	   be valid when that function is called on the other thread. */
 	pthread_barrier_wait(&dev->shutdown_barrier);
-	
+
 	return NULL;
 }
 
-hid_device * HID_API_EXPORT hid_open_path(const char *path, int bExclusive)
+/* \p path must be one of:
+     - in format 'DevSrvsID:<RegistryEntryID>' (as returned by hid_enumerate);
+     - a valid path to an IOHIDDevice in the IOService plane (as returned by IORegistryEntryGetPath,
+       e.g.: "IOService:/AppleACPIPlatformExpert/PCI0@0/AppleACPIPCI/EHC1@1D,7/AppleUSBEHCI/PLAYSTATION(R)3 Controller@fd120000/IOUSBInterface@0/IOUSBHIDDriver");
+   Second format is for compatibility with paths accepted by older versions of HIDAPI.
+*/
+static io_registry_entry_t hid_open_service_registry_from_path(const char *path)
+{
+	if (path == NULL)
+		return MACH_PORT_NULL;
+
+	/* Get the IORegistry entry for the given path */
+	if (strncmp("DevSrvsID:", path, 10) == 0) {
+		char *endptr;
+		uint64_t entry_id = strtoull(path + 10, &endptr, 10);
+		if (*endptr == '\0') {
+			return IOServiceGetMatchingService((mach_port_t) 0, IORegistryEntryIDMatching(entry_id));
+		}
+	}
+	else {
+		/* Fallback to older format of the path */
+		return IORegistryEntryFromPath((mach_port_t) 0, path);
+	}
+
+	return MACH_PORT_NULL;
+}
+
+hid_device * HID_API_EXPORT hid_open_path(const char *path)
 {
-	int i;
 	hid_device *dev = NULL;
-	CFIndex num_devices;
-	CFSetRef device_set;
-	IOHIDDeviceRef *device_array;
+	io_registry_entry_t entry = MACH_PORT_NULL;
+	IOReturn ret = kIOReturnInvalid;
+	char str[32];
 
-	dev = new_hid_device();
-	
 	/* Set up the HID Manager if it hasn't been done */
-	if (hid_init() < 0)
+	if (hid_init() < 0) {
 		return NULL;
+	}
+	/* register_global_error: global error is set/reset by hid_init */
 
-#if 0 /* We have a path because the IOHIDManager is already updated */
-	/* give the IOHIDManager a chance to update itself */
-	process_pending_events();
-#endif
-
-	device_set = IOHIDManagerCopyDevices(hid_mgr);
-	if (!device_set)
+	dev = new_hid_device();
+	if (!dev) {
+		register_global_error("Couldn't allocate memory");
 		return NULL;
-	
-	num_devices = CFSetGetCount(device_set);
-	device_array = (IOHIDDeviceRef *)calloc(num_devices, sizeof(IOHIDDeviceRef));
-	CFSetGetValues(device_set, (const void **) device_array);	
-	for (i = 0; i < num_devices; i++) {
-		char cbuf[BUF_LEN];
-		IOHIDDeviceRef os_dev = device_array[i];
-		
-		make_path(os_dev, cbuf, sizeof(cbuf));
-		if (!strcmp(cbuf, path)) {
-			// Matched Paths. Open this Device.
-			IOReturn ret = IOHIDDeviceOpen(os_dev, kIOHIDOptionsTypeNone);
-			if (ret == kIOReturnSuccess) {
-				char str[32];
-				struct hid_device_list_node *node;
-				
-				free(device_array);
-				CFRelease(device_set);
-				dev->device_handle = os_dev;
-				
-				/* Create the buffers for receiving data */
-				dev->max_input_report_len = (CFIndex) get_max_report_length(os_dev);
-				SDL_assert(dev->max_input_report_len > 0);
-				dev->input_report_buf = (uint8_t *)calloc(dev->max_input_report_len, sizeof(uint8_t));
-				
-				/* Create the Run Loop Mode for this device.
-				 printing the reference seems to work. */
-				snprintf(str, sizeof(str), "HIDAPI_%p", os_dev);
-				dev->run_loop_mode = 
-				CFStringCreateWithCString(NULL, str, kCFStringEncodingASCII);
-				
-				/* Attach the device to a Run Loop */
-				IOHIDDeviceRegisterInputReportCallback(
-													   os_dev, dev->input_report_buf, dev->max_input_report_len,
-													   &hid_report_callback, dev);
-
-				node = (struct hid_device_list_node *)calloc(1, sizeof(struct hid_device_list_node));
-				node->dev = dev;
-				node->next = device_list;
-				device_list = node;
-
-				/* Start the read thread */
-				pthread_create(&dev->thread, NULL, read_thread, dev);
-				
-				/* Wait here for the read thread to be initialized. */
-				pthread_barrier_wait(&dev->barrier);
-				
-				return dev;
-			}
-			else {
-				goto return_error;
-			}
-		}
 	}
-	
+
+	/* Get the IORegistry entry for the given path */
+	entry = hid_open_service_registry_from_path(path);
+	if (entry == MACH_PORT_NULL) {
+		/* Path wasn't valid (maybe device was removed?) */
+		register_global_error("hid_open_path: device mach entry not found with the given path");
+		goto return_error;
+	}
+
+	/* Create an IOHIDDevice for the entry */
+	dev->device_handle = IOHIDDeviceCreate(kCFAllocatorDefault, entry);
+	if (dev->device_handle == NULL) {
+		/* Error creating the HID device */
+		register_global_error("hid_open_path: failed to create IOHIDDevice from the mach entry");
+		goto return_error;
+	}
+
+	/* Open the IOHIDDevice */
+	ret = IOHIDDeviceOpen(dev->device_handle, dev->open_options);
+	if (ret != kIOReturnSuccess) {
+		register_global_error_format("hid_open_path: failed to open IOHIDDevice from mach entry: (0x%08X) %s", ret, mach_error_string(ret));
+		goto return_error;
+	}
+
+	/* Create the buffers for receiving data */
+	dev->max_input_report_len = (CFIndex) get_max_report_length(dev->device_handle);
+	dev->input_report_buf = (uint8_t*) calloc(dev->max_input_report_len, sizeof(uint8_t));
+
+	/* Create the Run Loop Mode for this device.
+	   printing the reference seems to work. */
+	snprintf(str, sizeof(str), "HIDAPI_%p", (void*) dev->device_handle);
+	dev->run_loop_mode =
+		CFStringCreateWithCString(NULL, str, kCFStringEncodingASCII);
+
+	/* Attach the device to a Run Loop */
+	IOHIDDeviceRegisterInputReportCallback(
+		dev->device_handle, dev->input_report_buf, dev->max_input_report_len,
+		&hid_report_callback, dev);
+	IOHIDDeviceRegisterRemovalCallback(dev->device_handle, hid_device_removal_callback, dev);
+
+	/* Start the read thread */
+	pthread_create(&dev->thread, NULL, read_thread, dev);
+
+	/* Wait here for the read thread to be initialized. */
+	pthread_barrier_wait(&dev->barrier);
+
+	IOObjectRelease(entry);
+	return dev;
+
 return_error:
-	free(device_array);
-	CFRelease(device_set);
+	if (dev->device_handle != NULL)
+		CFRelease(dev->device_handle);
+
+	if (entry != MACH_PORT_NULL)
+		IOObjectRelease(entry);
+
 	free_hid_device(dev);
 	return NULL;
 }
 
 static int set_report(hid_device *dev, IOHIDReportType type, const unsigned char *data, size_t length)
 {
-	const char *pass_through_magic = "MAGIC0";
-	size_t pass_through_magic_length = strlen(pass_through_magic);
-	unsigned char report_id = data[0];
-	const unsigned char *data_to_send;
-	size_t length_to_send;
+	const unsigned char *data_to_send = data;
+	CFIndex length_to_send = length;
 	IOReturn res;
-	
-	/* Return if the device has been disconnected. */
-   	if (dev->disconnected)
-   		return -1;
-	
+	unsigned char report_id;
+
+	register_device_error(dev, NULL);
+
+	if (!data || (length == 0)) {
+		register_device_error(dev, strerror(EINVAL));
+		return -1;
+	}
+
+	report_id = data[0];
+
 	if (report_id == 0x0) {
 		/* Not using numbered Reports.
-		 Don't send the report number. */
+		   Don't send the report number. */
 		data_to_send = data+1;
 		length_to_send = length-1;
 	}
-	else if (length > 6 && memcmp(data, pass_through_magic, pass_through_magic_length) == 0) {
-		report_id = data[pass_through_magic_length];
-		data_to_send = data+pass_through_magic_length;
-		length_to_send = length-pass_through_magic_length;
+
+	/* Avoid crash if the device has been unplugged. */
+	if (dev->disconnected) {
+		register_device_error(dev, "Device is disconnected");
+		return -1;
 	}
-	else {
-		/* Using numbered Reports.
-		 Send the Report Number */
-		data_to_send = data;
-		length_to_send = length;
-	}
-	
-	if (!dev->disconnected) {
-		res = IOHIDDeviceSetReport(dev->device_handle,
-								   type,
-								   report_id, /* Report ID*/
-								   data_to_send, length_to_send);
-		
-		if (res == kIOReturnSuccess) {
-			return (int)length;
-		}
-		else if (res == kIOReturnUnsupported) {
-			/*printf("kIOReturnUnsupported\n");*/
-			return -1;
-		}
-		else {
-			/*printf("0x%x\n", res);*/
-			return -1;
-		}
+
+	res = IOHIDDeviceSetReport(dev->device_handle,
+	                           type,
+	                           report_id,
+	                           data_to_send, length_to_send);
+
+	if (res != kIOReturnSuccess) {
+		register_device_error_format(dev, "IOHIDDeviceSetReport failed: (0x%08X) %s", res, mach_error_string(res));
+		return -1;
 	}
-	
-	return -1;
+
+	return (int) length;
+}
+
+static int get_report(hid_device *dev, IOHIDReportType type, unsigned char *data, size_t length)
+{
+	unsigned char *report = data;
+	CFIndex report_length = length;
+	IOReturn res = kIOReturnSuccess;
+	const unsigned char report_id = data[0];
+
+	register_device_error(dev, NULL);
+
+	if (report_id == 0x0) {
+		/* Not using numbered Reports.
+		   Don't send the report number. */
+		report = data+1;
+		report_length = length-1;
+	}
+
+	/* Avoid crash if the device has been unplugged. */
+	if (dev->disconnected) {
+		register_device_error(dev, "Device is disconnected");
+		return -1;
+	}
+
+	res = IOHIDDeviceGetReport(dev->device_handle,
+	                           type,
+	                           report_id,
+	                           report, &report_length);
+
+	if (res != kIOReturnSuccess) {
+		register_device_error_format(dev, "IOHIDDeviceGetReport failed: (0x%08X) %s", res, mach_error_string(res));
+		return -1;
+	}
+
+	if (report_id == 0x0) { /* 0 report number still present at the beginning */
+		report_length++;
+	}
+
+	return (int) report_length;
 }
 
 int HID_API_EXPORT hid_write(hid_device *dev, const unsigned char *data, size_t length)
@@ -951,90 +1185,91 @@ int HID_API_EXPORT hid_write(hid_device *dev, const unsigned char *data, size_t
 static int return_data(hid_device *dev, unsigned char *data, size_t length)
 {
 	/* Copy the data out of the linked list item (rpt) into the
-	 return buffer (data), and delete the liked list item. */
+	   return buffer (data), and delete the liked list item. */
 	struct input_report *rpt = dev->input_reports;
-	size_t len = 0;
-	if (rpt != NULL) {
-		len = (length < rpt->len)? length: rpt->len;
-		memcpy(data, rpt->data, len);
-		dev->input_reports = rpt->next;
-		free(rpt->data);
-		free(rpt);
-	}
-	return (int)len;
+	size_t len = (length < rpt->len)? length: rpt->len;
+	memcpy(data, rpt->data, len);
+	dev->input_reports = rpt->next;
+	free(rpt->data);
+	free(rpt);
+	return (int) len;
 }
 
-static int cond_wait(const hid_device *dev, pthread_cond_t *cond, pthread_mutex_t *mutex)
+static int cond_wait(hid_device *dev, pthread_cond_t *cond, pthread_mutex_t *mutex)
 {
 	while (!dev->input_reports) {
 		int res = pthread_cond_wait(cond, mutex);
 		if (res != 0)
 			return res;
-		
+
 		/* A res of 0 means we may have been signaled or it may
-		 be a spurious wakeup. Check to see that there's acutally
-		 data in the queue before returning, and if not, go back
-		 to sleep. See the pthread_cond_timedwait() man page for
-		 details. */
-		
-		if (dev->shutdown_thread || dev->disconnected)
+		   be a spurious wakeup. Check to see that there's actually
+		   data in the queue before returning, and if not, go back
+		   to sleep. See the pthread_cond_timedwait() man page for
+		   details. */
+
+		if (dev->shutdown_thread || dev->disconnected) {
 			return -1;
+		}
 	}
-	
+
 	return 0;
 }
 
-static int cond_timedwait(const hid_device *dev, pthread_cond_t *cond, pthread_mutex_t *mutex, const struct timespec *abstime)
+static int cond_timedwait(hid_device *dev, pthread_cond_t *cond, pthread_mutex_t *mutex, const struct timespec *abstime)
 {
 	while (!dev->input_reports) {
 		int res = pthread_cond_timedwait(cond, mutex, abstime);
 		if (res != 0)
 			return res;
-		
+
 		/* A res of 0 means we may have been signaled or it may
-		 be a spurious wakeup. Check to see that there's acutally
-		 data in the queue before returning, and if not, go back
-		 to sleep. See the pthread_cond_timedwait() man page for
-		 details. */
-		
-		if (dev->shutdown_thread || dev->disconnected)
+		   be a spurious wakeup. Check to see that there's actually
+		   data in the queue before returning, and if not, go back
+		   to sleep. See the pthread_cond_timedwait() man page for
+		   details. */
+
+		if (dev->shutdown_thread || dev->disconnected) {
 			return -1;
+		}
 	}
-	
+
 	return 0;
-	
+
 }
 
 int HID_API_EXPORT hid_read_timeout(hid_device *dev, unsigned char *data, size_t length, int milliseconds)
 {
 	int bytes_read = -1;
-	
+
 	/* Lock the access to the report list. */
 	pthread_mutex_lock(&dev->mutex);
-	
+
 	/* There's an input report queued up. Return it. */
 	if (dev->input_reports) {
 		/* Return the first one */
 		bytes_read = return_data(dev, data, length);
 		goto ret;
 	}
-	
+
 	/* Return if the device has been disconnected. */
 	if (dev->disconnected) {
 		bytes_read = -1;
+		register_device_error(dev, "hid_read_timeout: device disconnected");
 		goto ret;
 	}
-	
+
 	if (dev->shutdown_thread) {
 		/* This means the device has been closed (or there
-		 has been an error. An error code of -1 should
-		 be returned. */
+		   has been an error. An error code of -1 should
+		   be returned. */
 		bytes_read = -1;
+		register_device_error(dev, "hid_read_timeout: thread shutdown");
 		goto ret;
 	}
-	
+
 	/* There is no data. Go to sleep and wait for data. */
-	
+
 	if (milliseconds == -1) {
 		/* Blocking */
 		int res;
@@ -1043,6 +1278,7 @@ int HID_API_EXPORT hid_read_timeout(hid_device *dev, unsigned char *data, size_t
 			bytes_read = return_data(dev, data, length);
 		else {
 			/* There was an error, or a device disconnection. */
+			register_device_error(dev, "hid_read_timeout: error waiting for more data");
 			bytes_read = -1;
 		}
 	}
@@ -1059,20 +1295,22 @@ int HID_API_EXPORT hid_read_timeout(hid_device *dev, unsigned char *data, size_t
 			ts.tv_sec++;
 			ts.tv_nsec -= 1000000000L;
 		}
-		
+
 		res = cond_timedwait(dev, &dev->condition, &dev->mutex, &ts);
-		if (res == 0)
+		if (res == 0) {
 			bytes_read = return_data(dev, data, length);
-		else if (res == ETIMEDOUT)
+		} else if (res == ETIMEDOUT) {
 			bytes_read = 0;
-		else
+		} else {
+			register_device_error(dev, "hid_read_timeout:  error waiting for more data");
 			bytes_read = -1;
+		}
 	}
 	else {
 		/* Purely non-blocking */
 		bytes_read = 0;
 	}
-	
+
 ret:
 	/* Unlock */
 	pthread_mutex_unlock(&dev->mutex);
@@ -1088,7 +1326,7 @@ int HID_API_EXPORT hid_set_nonblocking(hid_device *dev, int nonblock)
 {
 	/* All Nonblocking operation is handled by the library. */
 	dev->blocking = !nonblock;
-	
+
 	return 0;
 }
 
@@ -1099,180 +1337,215 @@ int HID_API_EXPORT hid_send_feature_report(hid_device *dev, const unsigned char
 
 int HID_API_EXPORT hid_get_feature_report(hid_device *dev, unsigned char *data, size_t length)
 {
-	CFIndex len = length;
-	IOReturn res;
-	int skipped_report_id = 0, report_number;
-
-	/* Return if the device has been unplugged. */
-	if (dev->disconnected)
-		return -1;
-	
-	report_number = data[0];
-	if (report_number == 0x0) {
-		/* Offset the return buffer by 1, so that the report ID
-		 will remain in byte 0. */
-		data++;
-		len--;
-		skipped_report_id = 1;
-	}
-	
-	res = IOHIDDeviceGetReport(dev->device_handle,
-	                           kIOHIDReportTypeFeature,
-	                           report_number, /* Report ID */
-	                           data, &len);
-	if (res != kIOReturnSuccess)
-		return -1;
-
-	if (skipped_report_id)
-		len++;
-
-	return (int)len;
+	return get_report(dev, kIOHIDReportTypeFeature, data, length);
 }
 
+int HID_API_EXPORT HID_API_CALL hid_get_input_report(hid_device *dev, unsigned char *data, size_t length)
+{
+	return get_report(dev, kIOHIDReportTypeInput, data, length);
+}
 
 void HID_API_EXPORT hid_close(hid_device *dev)
 {
 	if (!dev)
 		return;
-	
-	/* Disconnect the report callback before close. */
-	if (!dev->disconnected) {
+
+	/* Disconnect the report callback before close.
+	   See comment below.
+	*/
+	if (is_macos_10_10_or_greater || !dev->disconnected) {
 		IOHIDDeviceRegisterInputReportCallback(
-											   dev->device_handle, dev->input_report_buf, dev->max_input_report_len,
-											   NULL, dev);
+			dev->device_handle, dev->input_report_buf, dev->max_input_report_len,
+			NULL, dev);
+		IOHIDDeviceRegisterRemovalCallback(dev->device_handle, NULL, dev);
 		IOHIDDeviceUnscheduleFromRunLoop(dev->device_handle, dev->run_loop, dev->run_loop_mode);
 		IOHIDDeviceScheduleWithRunLoop(dev->device_handle, CFRunLoopGetMain(), kCFRunLoopDefaultMode);
 	}
-	
+
 	/* Cause read_thread() to stop. */
 	dev->shutdown_thread = 1;
-	
+
 	/* Wake up the run thread's event loop so that the thread can exit. */
 	CFRunLoopSourceSignal(dev->source);
 	CFRunLoopWakeUp(dev->run_loop);
-	
+
 	/* Notify the read thread that it can shut down now. */
 	pthread_barrier_wait(&dev->shutdown_barrier);
-	
+
 	/* Wait for read_thread() to end. */
 	pthread_join(dev->thread, NULL);
-	
+
 	/* Close the OS handle to the device, but only if it's not
-	 been unplugged. If it's been unplugged, then calling
-	 IOHIDDeviceClose() will crash. */
-	if (!dev->disconnected) {
-		IOHIDDeviceClose(dev->device_handle, kIOHIDOptionsTypeNone);
+	   been unplugged. If it's been unplugged, then calling
+	   IOHIDDeviceClose() will crash.
+
+	   UPD: The crash part was true in/until some version of macOS.
+	   Starting with macOS 10.15, there is an opposite effect in some environments:
+	   crash happenes if IOHIDDeviceClose() is not called.
+	   Not leaking a resource in all tested environments.
+	*/
+	if (is_macos_10_10_or_greater || !dev->disconnected) {
+		IOHIDDeviceClose(dev->device_handle, dev->open_options);
 	}
-	
+
 	/* Clear out the queue of received reports. */
 	pthread_mutex_lock(&dev->mutex);
 	while (dev->input_reports) {
 		return_data(dev, NULL, 0);
 	}
 	pthread_mutex_unlock(&dev->mutex);
-	
+	CFRelease(dev->device_handle);
+
 	free_hid_device(dev);
 }
 
 int HID_API_EXPORT_CALL hid_get_manufacturer_string(hid_device *dev, wchar_t *string, size_t maxlen)
 {
-	return get_manufacturer_string(dev->device_handle, string, maxlen);
+	if (!string || !maxlen)
+	{
+		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;
+	}
+
+	wcsncpy(string, info->manufacturer_string, maxlen);
+	string[maxlen - 1] = L'\0';
+
+	return 0;
 }
 
 int HID_API_EXPORT_CALL hid_get_product_string(hid_device *dev, wchar_t *string, size_t maxlen)
 {
-	return get_product_string(dev->device_handle, string, maxlen);
+	if (!string || !maxlen) {
+		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;
+	}
+
+	wcsncpy(string, info->product_string, maxlen);
+	string[maxlen - 1] = L'\0';
+
+	return 0;
 }
 
 int HID_API_EXPORT_CALL hid_get_serial_number_string(hid_device *dev, wchar_t *string, size_t maxlen)
 {
-	return get_serial_number(dev->device_handle, string, maxlen);
-}
+	if (!string || !maxlen) {
+		register_device_error(dev, "Zero buffer/length");
+		return -1;
+	}
 
-int HID_API_EXPORT_CALL hid_get_indexed_string(hid_device *dev, int string_index, wchar_t *string, size_t maxlen)
-{
-	// TODO:
-	
-	return 0;
-}
+	struct hid_device_info *info = hid_get_device_info(dev);
+	if (!info) {
+		// hid_get_device_info will have set an error already
+		return -1;
+	}
 
+	wcsncpy(string, info->serial_number, maxlen);
+	string[maxlen - 1] = L'\0';
 
-HID_API_EXPORT const wchar_t * HID_API_CALL  hid_error(hid_device *dev)
-{
-	// TODO:
-	
-	return NULL;
+	return 0;
 }
 
+HID_API_EXPORT struct hid_device_info *HID_API_CALL hid_get_device_info(hid_device *dev) {
+	if (!dev->device_info) {
+		dev->device_info = create_device_info(dev->device_handle);
+		if (!dev->device_info) {
+			register_device_error(dev, "Failed to create hid_device_info");
+		}
+	}
 
+	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) dev;
+	(void) string_index;
+	(void) string;
+	(void) maxlen;
 
+	register_device_error(dev, "hid_get_indexed_string: not available on this platform");
+	return -1;
+}
 
-
-#if 0
-static int32_t get_location_id(IOHIDDeviceRef device)
+int HID_API_EXPORT_CALL hid_darwin_get_location_id(hid_device *dev, uint32_t *location_id)
 {
-	return get_int_property(device, CFSTR(kIOHIDLocationIDKey));
+	int res = get_int_property(dev->device_handle, CFSTR(kIOHIDLocationIDKey));
+	if (res != 0) {
+		*location_id = (uint32_t) res;
+		return 0;
+	} else {
+		register_device_error(dev, "Failed to get IOHIDLocationID property");
+		return -1;
+	}
 }
 
-static int32_t get_usage(IOHIDDeviceRef device)
+void HID_API_EXPORT_CALL hid_darwin_set_open_exclusive(int open_exclusive)
 {
-	int32_t res;
-	res = get_int_property(device, CFSTR(kIOHIDDeviceUsageKey));
-	if (!res)
-		res = get_int_property(device, CFSTR(kIOHIDPrimaryUsageKey));
-	return res;
+	device_open_options = (open_exclusive == 0) ? kIOHIDOptionsTypeNone : kIOHIDOptionsTypeSeizeDevice;
 }
 
-static int32_t get_usage_page(IOHIDDeviceRef device)
+int HID_API_EXPORT_CALL hid_darwin_get_open_exclusive(void)
 {
-	int32_t res;
-	res = get_int_property(device, CFSTR(kIOHIDDeviceUsagePageKey));
-	if (!res)
-		res = get_int_property(device, CFSTR(kIOHIDPrimaryUsagePageKey));
-	return res;
+	return (device_open_options == kIOHIDOptionsTypeSeizeDevice) ? 1 : 0;
 }
 
-static int get_transport(IOHIDDeviceRef device, wchar_t *buf, size_t len)
+int HID_API_EXPORT_CALL hid_darwin_is_device_open_exclusive(hid_device *dev)
 {
-	return get_string_property(device, CFSTR(kIOHIDTransportKey), buf, len);
+	if (!dev)
+		return -1;
+
+	return (dev->open_options == kIOHIDOptionsTypeSeizeDevice) ? 1 : 0;
 }
 
+int HID_API_EXPORT_CALL hid_get_report_descriptor(hid_device *dev, unsigned char *buf, size_t buf_size)
+{
+	CFTypeRef ref = IOHIDDeviceGetProperty(dev->device_handle, CFSTR(kIOHIDReportDescriptorKey));
+	if (ref != NULL && CFGetTypeID(ref) == CFDataGetTypeID()) {
+		CFDataRef report_descriptor = (CFDataRef) ref;
+		const UInt8 *descriptor_buf = CFDataGetBytePtr(report_descriptor);
+		CFIndex descriptor_buf_len = CFDataGetLength(report_descriptor);
+		size_t copy_len = (size_t) descriptor_buf_len;
+
+		if (descriptor_buf == NULL || descriptor_buf_len < 0) {
+			register_device_error(dev, "Zero buffer/length");
+			return -1;
+		}
 
-int main(void)
+		if (buf_size < copy_len) {
+			copy_len = buf_size;
+		}
+
+		memcpy(buf, descriptor_buf, copy_len);
+		return copy_len;
+	}
+	else {
+		register_device_error(dev, "Failed to get kIOHIDReportDescriptorKey property");
+		return -1;
+	}
+}
+
+HID_API_EXPORT const wchar_t * HID_API_CALL  hid_error(hid_device *dev)
 {
-	IOHIDManagerRef mgr;
-	int i;
-	
-	mgr = IOHIDManagerCreate(kCFAllocatorDefault, kIOHIDOptionsTypeNone);
-	IOHIDManagerSetDeviceMatching(mgr, NULL);
-	IOHIDManagerOpen(mgr, kIOHIDOptionsTypeNone);
-	
-	CFSetRef device_set = IOHIDManagerCopyDevices(mgr);
-	
-	CFIndex num_devices = CFSetGetCount(device_set);
-	IOHIDDeviceRef *device_array = calloc(num_devices, sizeof(IOHIDDeviceRef));
-	CFSetGetValues(device_set, (const void **) device_array);
-	
-	for (i = 0; i < num_devices; i++) {
-		IOHIDDeviceRef dev = device_array[i];
-		printf("Device: %p\n", dev);
-		printf("  %04hx %04hx\n", get_vendor_id(dev), get_product_id(dev));
-		
-		wchar_t serial[256], buf[256];
-		char cbuf[256];
-		get_serial_number(dev, serial, 256);
-		
-		
-		printf("  Serial: %ls\n", serial);
-		printf("  Loc: %ld\n", get_location_id(dev));
-		get_transport(dev, buf, 256);
-		printf("  Trans: %ls\n", buf);
-		make_path(dev, cbuf, 256);
-		printf("  Path: %s\n", cbuf);
-		
-	}
-	
-	return 0;
+	if (dev) {
+		if (dev->last_error_str == NULL)
+			return L"Success";
+		return dev->last_error_str;
+	}
+
+	if (last_global_error_str == NULL)
+		return L"Success";
+	return last_global_error_str;
 }
-#endif

+ 98 - 0
src/hidapi/mac/hidapi_darwin.h

@@ -0,0 +1,98 @@
+/*******************************************************
+ HIDAPI - Multi-Platform library for
+ communication with HID devices.
+
+ libusb/hidapi Team
+
+ Copyright 2022, All Rights Reserved.
+
+ At the discretion of the user of this library,
+ this software may be licensed under the terms of the
+ GNU General Public License v3, a BSD-Style license, or the
+ original HIDAPI license as outlined in the LICENSE.txt,
+ LICENSE-gpl3.txt, LICENSE-bsd.txt, and LICENSE-orig.txt
+ files located at the root of the source distribution.
+ These files may also be found in the public source
+ code repository located at:
+        https://github.com/libusb/hidapi .
+********************************************************/
+
+/** @file
+ * @defgroup API hidapi API
+
+ * Since version 0.12.0, @ref HID_API_VERSION >= HID_API_MAKE_VERSION(0, 12, 0)
+ */
+
+#ifndef HIDAPI_DARWIN_H__
+#define HIDAPI_DARWIN_H__
+
+#include <stdint.h>
+
+#include "hidapi.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+		/** @brief Get the location ID for a HID device.
+
+			Since version 0.12.0, @ref HID_API_VERSION >= HID_API_MAKE_VERSION(0, 12, 0)
+
+			@ingroup API
+			@param dev A device handle returned from hid_open().
+			@param location_id The device's location ID on return.
+
+			@returns
+				This function returns 0 on success and -1 on error.
+		*/
+		int HID_API_EXPORT_CALL hid_darwin_get_location_id(hid_device *dev, uint32_t *location_id);
+
+
+		/** @brief Changes the behavior of all further calls to @ref hid_open or @ref hid_open_path.
+
+			By default on Darwin platform all devices opened by HIDAPI with @ref hid_open or @ref hid_open_path
+			are opened in exclusive mode (see kIOHIDOptionsTypeSeizeDevice).
+
+			Since version 0.12.0, @ref HID_API_VERSION >= HID_API_MAKE_VERSION(0, 12, 0)
+
+			@ingroup API
+			@param open_exclusive When set to 0 - all further devices will be opened
+				in non-exclusive mode. Otherwise - all further devices will be opened
+				in exclusive mode.
+
+			@note During the initialisation by @ref hid_init - this property is set to 1 (TRUE).
+			This is done to preserve full backward compatibility with previous behavior.
+
+			@note Calling this function before @ref hid_init or after @ref hid_exit has no effect.
+		*/
+		void HID_API_EXPORT_CALL hid_darwin_set_open_exclusive(int open_exclusive);
+
+		/** @brief Getter for option set by @ref hid_darwin_set_open_exclusive.
+
+			Since version 0.12.0, @ref HID_API_VERSION >= HID_API_MAKE_VERSION(0, 12, 0)
+
+			@ingroup API
+			@return 1 if all further devices will be opened in exclusive mode.
+
+			@note Value returned by this function before calling to @ref hid_init or after @ref hid_exit
+			is not reliable.
+		*/
+		int HID_API_EXPORT_CALL hid_darwin_get_open_exclusive(void);
+
+		/** @brief Check how the device was opened.
+
+			Since version 0.12.0, @ref HID_API_VERSION >= HID_API_MAKE_VERSION(0, 12, 0)
+
+			@ingroup API
+			@param dev A device to get property from.
+
+			@return 1 if the device is opened in exclusive mode, 0 - opened in non-exclusive,
+			-1 - if dev is invalid.
+		*/
+		int HID_API_EXPORT_CALL hid_darwin_is_device_open_exclusive(hid_device *dev);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif

+ 22 - 0
src/hidapi/meson.build

@@ -0,0 +1,22 @@
+project('hidapi', meson_version: '>=0.57.0', version: files('VERSION'))
+
+cmake = import('cmake')
+
+hidapi_build_options = cmake.subproject_options()
+hidapi_build_options.set_install(true)
+
+hidapi_build = cmake.subproject('hidapi_build_cmake', options: hidapi_build_options)
+
+if (hidapi_build.target_list().contains('hidapi_winapi'))
+	hidapi_winapi_dep = hidapi_build.dependency('hidapi_winapi')
+	hidapi_dep = hidapi_winapi_dep
+elif (hidapi_build.target_list().contains('hidapi_darwin'))
+	hidapi_darwin_dep = hidapi_build.dependency('hidapi_darwin')
+	hidapi_dep = hidapi_darwin_dep
+elif (hidapi_build.target_list().contains('hidapi_hidraw'))
+	hidapi_hidraw_dep = hidapi_build.dependency('hidapi_hidraw')
+	hidapi_dep = hidapi_hidraw_dep
+elif (hidapi_build.target_list().contains('hidapi_libusb'))
+	hidapi_libusb_dep = hidapi_build.dependency('hidapi_libusb')
+	hidapi_dep = hidapi_libusb_dep
+endif

+ 1 - 0
src/hidapi/pc/.gitignore

@@ -0,0 +1 @@
+*.pc

+ 1 - 0
src/hidapi/pc/hidapi-hidraw.pc.in

@@ -5,6 +5,7 @@ includedir=@includedir@
 
 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.
+URL: https://github.com/libusb/hidapi
 Version: @VERSION@
 Libs: -L${libdir} -lhidapi-hidraw
 Cflags: -I${includedir}/hidapi

+ 1 - 0
src/hidapi/pc/hidapi-libusb.pc.in

@@ -5,6 +5,7 @@ includedir=@includedir@
 
 Name: hidapi-libusb
 Description: C Library for USB HID device access from Linux, Mac OS X, FreeBSD, and Windows. This is the libusb implementation.
+URL: https://github.com/libusb/hidapi
 Version: @VERSION@
 Libs: -L${libdir} -lhidapi-libusb
 Cflags: -I${includedir}/hidapi

+ 1 - 0
src/hidapi/pc/hidapi.pc.in

@@ -5,6 +5,7 @@ includedir=@includedir@
 
 Name: hidapi
 Description: C Library for USB/Bluetooth HID device access from Linux, Mac OS X, FreeBSD, and Windows.
+URL: https://github.com/libusb/hidapi
 Version: @VERSION@
 Libs: -L${libdir} -lhidapi
 Cflags: -I${includedir}/hidapi

+ 193 - 0
src/hidapi/src/CMakeLists.txt

@@ -0,0 +1,193 @@
+get_filename_component(PROJECT_ROOT "${CMAKE_CURRENT_LIST_DIR}/.." ABSOLUTE)
+
+# Read version from file
+file(READ "${PROJECT_ROOT}/VERSION" RAW_VERSION_STR)
+string(REGEX MATCH "^([0-9]+\\.[0-9]+\\.[0-9]+)(.*)" VERSION_STR "${RAW_VERSION_STR}")
+
+if(NOT VERSION_STR)
+    message(FATAL_ERROR "Broken VERSION file, couldn't parse '${PROJECT_ROOT}/VERSION' with content: '${RAW_VERSION_STR}'")
+endif()
+
+set(VERSION "${CMAKE_MATCH_1}")
+string(STRIP "${CMAKE_MATCH_2}" VERSION_SUFFIX)
+# compatibility with find_package() vs add_subdirectory
+set(hidapi_VERSION "${VERSION}" PARENT_SCOPE)
+#
+
+if(DEFINED HIDAPI_PRINT_VERSION AND HIDAPI_PRINT_VERSION)
+    set(HIDAPI_PRINT_VERSION "hidapi: v${VERSION}")
+    if(VERSION_SUFFIX)
+        set(HIDAPI_PRINT_VERSION "${HIDAPI_PRINT_VERSION} (${VERSION_SUFFIX})")
+    endif()
+    message(STATUS "${HIDAPI_PRINT_VERSION}")
+endif()
+
+project(hidapi VERSION "${VERSION}" LANGUAGES C)
+
+# Defaults and required options
+
+if(NOT DEFINED HIDAPI_WITH_TESTS)
+    set(HIDAPI_WITH_TESTS OFF)
+endif()
+if(NOT DEFINED BUILD_SHARED_LIBS)
+    set(BUILD_SHARED_LIBS ON)
+endif()
+if(NOT DEFINED HIDAPI_INSTALL_TARGETS)
+    set(HIDAPI_INSTALL_TARGETS OFF)
+endif()
+if(NOT DEFINED CMAKE_POSITION_INDEPENDENT_CODE)
+    set(CMAKE_POSITION_INDEPENDENT_CODE ON)
+endif()
+
+get_directory_property(IS_EXCLUDE_FROM_ALL EXCLUDE_FROM_ALL)
+if(IS_EXCLUDE_FROM_ALL)
+    if(HIDAPI_INSTALL_TARGETS)
+        message(WARNING "Installing EXCLUDE_FROM_ALL targets in an undefined behavior in CMake.\nDon't add 'hidapi' sundirectory with 'EXCLUDE_FROM_ALL' property, or don't set 'HIDAPI_INSTALL_TARGETS' to TRUE.")
+    endif()
+endif()
+
+# Helper(s)
+
+function(hidapi_configure_pc PC_IN_FILE)
+    file(MAKE_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/pc")
+
+    set(VERSION "${VERSION}${VERSION_SUFFIX}")
+    set(prefix "${CMAKE_INSTALL_PREFIX}")
+    set(exec_prefix "\${prefix}")
+    if(IS_ABSOLUTE "${CMAKE_INSTALL_LIBDIR}")
+        set(libdir "${CMAKE_INSTALL_LIBDIR}")
+    else()
+        set(libdir "\${exec_prefix}/${CMAKE_INSTALL_LIBDIR}")
+    endif()
+    if(IS_ABSOLUTE "${CMAKE_INSTALL_INCLUDEDIR}")
+        set(includedir "${CMAKE_INSTALL_INCLUDEDIR}")
+    else()
+        set(includedir "\${prefix}/${CMAKE_INSTALL_INCLUDEDIR}")
+    endif()
+
+    get_filename_component(PC_IN_FILENAME "${PC_IN_FILE}" NAME_WE)
+    set(PC_FILE "${CMAKE_CURRENT_BINARY_DIR}/pc/${PC_IN_FILENAME}.pc")
+    configure_file("${PC_IN_FILE}" "${PC_FILE}" @ONLY)
+
+    install(FILES "${PC_FILE}" DESTINATION "${CMAKE_INSTALL_LIBDIR}/pkgconfig/")
+endfunction()
+
+# The library
+
+if(HIDAPI_INSTALL_TARGETS)
+    include(GNUInstallDirs)
+endif()
+
+add_library(hidapi_include INTERFACE)
+target_include_directories(hidapi_include INTERFACE
+    "$<BUILD_INTERFACE:${PROJECT_ROOT}/hidapi>"
+)
+if(APPLE AND CMAKE_FRAMEWORK)
+    # FIXME: https://github.com/libusb/hidapi/issues/492: it is untrivial to set the include path for Framework correctly
+else()
+    target_include_directories(hidapi_include INTERFACE
+        "$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}/hidapi>"
+    )
+endif()
+set_target_properties(hidapi_include PROPERTIES EXPORT_NAME "include")
+set(HIDAPI_PUBLIC_HEADERS "${PROJECT_ROOT}/hidapi/hidapi.h")
+
+add_library(hidapi::include ALIAS hidapi_include)
+
+if(HIDAPI_INSTALL_TARGETS)
+    install(TARGETS hidapi_include EXPORT hidapi)
+endif()
+
+set(EXPORT_ALIAS)
+set(EXPORT_COMPONENTS)
+
+set(HIDAPI_NEED_EXPORT_THREADS FALSE)
+set(HIDAPI_NEED_EXPORT_LIBUSB FALSE)
+set(HIDAPI_NEED_EXPORT_LIBUDEV FALSE)
+set(HIDAPI_NEED_EXPORT_ICONV FALSE)
+
+if(WIN32)
+    target_include_directories(hidapi_include INTERFACE
+        "$<BUILD_INTERFACE:${PROJECT_ROOT}/windows>"
+    )
+    add_subdirectory("${PROJECT_ROOT}/windows" windows)
+    set(EXPORT_ALIAS winapi)
+    list(APPEND EXPORT_COMPONENTS winapi)
+elseif(APPLE)
+    target_include_directories(hidapi_include INTERFACE
+        "$<BUILD_INTERFACE:${PROJECT_ROOT}/mac>"
+    )
+    add_subdirectory("${PROJECT_ROOT}/mac" mac)
+    set(EXPORT_ALIAS darwin)
+    list(APPEND EXPORT_COMPONENTS darwin)
+    if(NOT BUILD_SHARED_LIBS)
+        set(HIDAPI_NEED_EXPORT_THREADS TRUE)
+    endif()
+else()
+    if(NOT DEFINED HIDAPI_WITH_LIBUSB)
+        set(HIDAPI_WITH_LIBUSB ON)
+    endif()
+    if(CMAKE_SYSTEM_NAME MATCHES "Linux")
+        if(NOT DEFINED HIDAPI_WITH_HIDRAW)
+            set(HIDAPI_WITH_HIDRAW ON)
+        endif()
+        if(HIDAPI_WITH_HIDRAW)
+            add_subdirectory("${PROJECT_ROOT}/linux" linux)
+            list(APPEND EXPORT_COMPONENTS hidraw)
+            set(EXPORT_ALIAS hidraw)
+            if(NOT BUILD_SHARED_LIBS)
+                set(HIDAPI_NEED_EXPORT_THREADS TRUE)
+                set(HIDAPI_NEED_EXPORT_LIBUDEV TRUE)
+            endif()
+        endif()
+    else()
+        set(HIDAPI_WITH_LIBUSB ON)
+    endif()
+    if(HIDAPI_WITH_LIBUSB)
+        target_include_directories(hidapi_include INTERFACE
+            "$<BUILD_INTERFACE:${PROJECT_ROOT}/libusb>"
+        )
+        if(NOT DEFINED HIDAPI_NO_ICONV)
+            set(HIDAPI_NO_ICONV OFF)
+        endif()
+        add_subdirectory("${PROJECT_ROOT}/libusb" libusb)
+        list(APPEND EXPORT_COMPONENTS libusb)
+        if(NOT EXPORT_ALIAS)
+            set(EXPORT_ALIAS libusb)
+        endif()
+        if(NOT BUILD_SHARED_LIBS)
+            set(HIDAPI_NEED_EXPORT_THREADS TRUE)
+            if(NOT TARGET usb-1.0)
+                set(HIDAPI_NEED_EXPORT_LIBUSB TRUE)
+            endif()
+        endif()
+    elseif(NOT TARGET hidapi_hidraw)
+        message(FATAL_ERROR "Select at least one option to build: HIDAPI_WITH_LIBUSB or HIDAPI_WITH_HIDRAW")
+    endif()
+endif()
+
+add_library(hidapi::hidapi ALIAS hidapi_${EXPORT_ALIAS})
+
+if(HIDAPI_INSTALL_TARGETS)
+    include(CMakePackageConfigHelpers)
+    set(EXPORT_DENERATED_LOCATION "${CMAKE_BINARY_DIR}/export_generated")
+    set(EXPORT_DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/hidapi")
+    write_basic_package_version_file("${EXPORT_DENERATED_LOCATION}/hidapi-config-version.cmake"
+        COMPATIBILITY SameMajorVersion
+    )
+    configure_package_config_file("cmake/hidapi-config.cmake.in" "${EXPORT_DENERATED_LOCATION}/hidapi-config.cmake"
+        INSTALL_DESTINATION "${EXPORT_DESTINATION}"
+        NO_SET_AND_CHECK_MACRO
+    )
+
+    install(EXPORT hidapi
+        DESTINATION "${EXPORT_DESTINATION}"
+        NAMESPACE hidapi::
+        FILE "libhidapi.cmake"
+    )
+    install(FILES
+            "${EXPORT_DENERATED_LOCATION}/hidapi-config-version.cmake"
+            "${EXPORT_DENERATED_LOCATION}/hidapi-config.cmake"
+        DESTINATION "${EXPORT_DESTINATION}"
+    )
+endif()

+ 61 - 0
src/hidapi/src/cmake/hidapi-config.cmake.in

@@ -0,0 +1,61 @@
+@PACKAGE_INIT@
+
+set(hidapi_VERSION_MAJOR "@hidapi_VERSION_MAJOR@")
+set(hidapi_VERSION_MINOR "@hidapi_VERSION_MINOR@")
+set(hidapi_VERSION_PATCH "@hidapi_VERSION_PATCH@")
+set(hidapi_VERSION "@hidapi_VERSION@")
+set(hidapi_VERSION_STR "@hidapi_VERSION@@VERSION_SUFFIX@")
+
+set(hidapi_FOUND FALSE)
+
+set(HIDAPI_NEED_EXPORT_THREADS @HIDAPI_NEED_EXPORT_THREADS@)
+set(HIDAPI_NEED_EXPORT_LIBUSB @HIDAPI_NEED_EXPORT_LIBUSB@)
+set(HIDAPI_NEED_EXPORT_LIBUDEV @HIDAPI_NEED_EXPORT_LIBUDEV@)
+set(HIDAPI_NEED_EXPORT_ICONV @HIDAPI_NEED_EXPORT_ICONV@)
+
+if(HIDAPI_NEED_EXPORT_THREADS)
+  if(CMAKE_VERSION VERSION_LESS 3.4.3)
+    message(FATAL_ERROR "This file relies on consumers using CMake 3.4.3 or greater.")
+  endif()
+  find_package(Threads REQUIRED)
+endif()
+
+if(HIDAPI_NEED_EXPORT_LIBUSB OR HIDAPI_NEED_EXPORT_LIBUDEV)
+  if(CMAKE_VERSION VERSION_LESS 3.6.3)
+    message(FATAL_ERROR "This file relies on consumers using CMake 3.6.3 or greater.")
+  endif()
+  find_package(PkgConfig)
+  if(HIDAPI_NEED_EXPORT_LIBUSB)
+    pkg_check_modules(libusb REQUIRED IMPORTED_TARGET libusb-1.0>=1.0.9)
+  endif()
+  if(HIDAPI_NEED_EXPORT_LIBUDEV)
+    pkg_check_modules(libudev REQUIRED IMPORTED_TARGET libudev)
+  endif()
+endif()
+
+if(HIDAPI_NEED_EXPORT_ICONV)
+  if(CMAKE_VERSION VERSION_LESS 3.11)
+    message(WARNING "HIDAPI requires CMake target Iconv::Iconv, make sure to provide it")
+  else()
+    find_package(Iconv REQUIRED)
+  endif()
+endif()
+
+include("${CMAKE_CURRENT_LIST_DIR}/libhidapi.cmake")
+
+set(hidapi_FOUND TRUE)
+
+foreach(_component @EXPORT_COMPONENTS@)
+  if(TARGET hidapi::${_component})
+    set(hidapi_${_component}_FOUND TRUE)
+  endif()
+endforeach()
+
+check_required_components(hidapi)
+
+if(NOT TARGET hidapi::hidapi)
+  add_library(hidapi::hidapi INTERFACE IMPORTED)
+  set_target_properties(hidapi::hidapi PROPERTIES
+    INTERFACE_LINK_LIBRARIES hidapi::@EXPORT_ALIAS@
+  )
+endif()

+ 2 - 0
src/hidapi/subprojects/README.md

@@ -0,0 +1,2 @@
+This folder is used only to support [meson.build](../meson.build) `subproject` command
+which would only look for a subproject in a "subprojects" directory.

+ 10 - 0
src/hidapi/subprojects/hidapi_build_cmake/CMakeLists.txt

@@ -0,0 +1,10 @@
+cmake_minimum_required(VERSION 3.1.3 FATAL_ERROR)
+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)
+  file(COPY "${CMAKE_CURRENT_LIST_DIR}/../../${ROOT_ELEMENT}" DESTINATION "${CMAKE_CURRENT_BINARY_DIR}/root/")
+endforeach()
+
+add_subdirectory("${CMAKE_CURRENT_BINARY_DIR}/root" hidapi_root)

+ 20 - 0
src/hidapi/testgui/.gitignore

@@ -0,0 +1,20 @@
+Debug
+Release
+*.exp
+*.ilk
+*.lib
+*.suo
+*.vcproj.*
+*.ncb
+*.suo
+*.dll
+*.pdb
+*.o
+hidapi-testgui
+hidapi-hidraw-testgui
+hidapi-libusb-testgui
+.deps
+.libs
+*.la
+*.lo
+TestGUI.app

+ 1 - 1
src/hidapi/testgui/Makefile.mingw

@@ -14,7 +14,7 @@ COBJS=../windows/hid.o
 CPPOBJS=test.o
 OBJS=$(COBJS) $(CPPOBJS)
 CFLAGS=-I../hidapi -I../../hidapi-externals/fox/include -g -c
-LIBS= -mwindows -lsetupapi -L../../hidapi-externals/fox/lib -Wl,-Bstatic -lFOX-1.6 -Wl,-Bdynamic -lgdi32 -Wl,--enable-auto-import -static-libgcc -static-libstdc++ -lkernel32
+LIBS= -mwindows -L../../hidapi-externals/fox/lib -Wl,-Bstatic -lFOX-1.6 -Wl,-Bdynamic -lgdi32 -Wl,--enable-auto-import -static-libgcc -static-libstdc++ -lkernel32
 
 
 hidapi-testgui: $(OBJS)

+ 2 - 1
src/hidapi/testgui/copy_to_bundle.sh

@@ -77,9 +77,10 @@ function copydeps {
 }
 
 rm -f $EXEPATH/*
+mkdir -p $EXEPATH
 
 # Copy the binary into the bundle. Use ../libtool to do this if it's
-# available beacuse if $EXE_NAME was built with autotools, it will be
+# available because if $EXE_NAME was built with autotools, it will be
 # necessary.  If ../libtool not available, just use cp to do the copy, but
 # only if $EXE_NAME is a binary.
 if [ -x ../libtool ]; then

+ 0 - 134
src/hidapi/testgui/mac_support.cpp

@@ -1,134 +0,0 @@
-/*******************************
- Mac support for HID Test GUI
- 
- Alan Ott
- Signal 11 Software
-
- Some of this code is from Apple Documentation, most notably
- http://developer.apple.com/legacy/mac/library/documentation/AppleScript/Conceptual/AppleEvents/AppleEvents.pdf 
-*******************************/
-
-#include <Carbon/Carbon.h>
-#include <fx.h>
-
-
-extern FXMainWindow *g_main_window;
-
-static pascal OSErr HandleQuitMessage(const AppleEvent *theAppleEvent, AppleEvent 
-									  *reply, long handlerRefcon) 
-{
-	puts("Quitting\n");
-	FXApp::instance()->exit();
-	return 0;
-}
-
-static pascal OSErr HandleReopenMessage(const AppleEvent *theAppleEvent, AppleEvent 
-									  *reply, long handlerRefcon) 
-{
-	puts("Showing");
-	g_main_window->show();
-	return 0;
-}
-
-static pascal OSErr HandleWildCardMessage(const AppleEvent *theAppleEvent, AppleEvent 
-									  *reply, long handlerRefcon) 
-{
-	puts("WildCard\n");
-	return 0;
-}
-
-OSStatus AEHandler(EventHandlerCallRef inCaller, EventRef inEvent, void* inRefcon) 
-{ 
-    Boolean     release = false; 
-    EventRecord eventRecord; 
-    OSErr       ignoreErrForThisSample; 
-	
-    // Events of type kEventAppleEvent must be removed from the queue 
-    //  before being passed to AEProcessAppleEvent. 
-    if (IsEventInQueue(GetMainEventQueue(), inEvent)) 
-    { 
-        // RemoveEventFromQueue will release the event, which will 
-        //  destroy it if we don't retain it first. 
-        RetainEvent(inEvent); 
-        release = true; 
-        RemoveEventFromQueue(GetMainEventQueue(), inEvent); 
-    } 
-    // Convert the event ref to the type AEProcessAppleEvent expects. 
-    ConvertEventRefToEventRecord(inEvent, &eventRecord); 
-    ignoreErrForThisSample = AEProcessAppleEvent(&eventRecord); 
-    if (release) 
-        ReleaseEvent(inEvent); 
-    // This Carbon event has been handled, even if no AppleEvent handlers 
-    //  were installed for the Apple event. 
-    return noErr; 
-}
-
-static void HandleEvent(EventRecord *event) 
-{ 
-	//printf("What: %d message %x\n", event->what, event->message);
-	if (event->what == osEvt) {
-		if (((event->message >> 24) & 0xff) == suspendResumeMessage) {
-			if (event->message & resumeFlag) {
-				g_main_window->show();				
-			}
-		}
-	}
-
-#if 0
-    switch (event->what) 
-    { 
-        case mouseDown: 
-            //HandleMouseDown(event); 
-            break; 
-        case keyDown: 
-        case autoKey: 
-            //HandleKeyPress(event); 
-            break; 
-        case kHighLevelEvent: 
-			puts("Calling ProcessAppleEvent\n");
-            AEProcessAppleEvent(event); 
-            break; 
-    } 
-#endif
-} 
-
-void
-init_apple_message_system()
-{
-	OSErr err;
-	static const EventTypeSpec appleEvents[] = 
-	{
-		{ kEventClassAppleEvent, kEventAppleEvent }
-	};
-	
-	/* Install the handler for Apple Events */
-	InstallApplicationEventHandler(NewEventHandlerUPP(AEHandler), 
-	              GetEventTypeCount(appleEvents), appleEvents, 0, NULL); 
-
-	/* Install handlers for the individual Apple Events that come
-	   from the Dock icon: the Reopen (click), and the Quit messages. */
-	err = AEInstallEventHandler(kCoreEventClass, kAEQuitApplication, 
-	              NewAEEventHandlerUPP(HandleQuitMessage), 0, false);
-	err = AEInstallEventHandler(kCoreEventClass, kAEReopenApplication, 
-	              NewAEEventHandlerUPP(HandleReopenMessage), 0, false);
-#if 0
-	// Left as an example of a wild card match.
-	err = AEInstallEventHandler(kCoreEventClass, typeWildCard, 
-	              NewAEEventHandlerUPP(HandleWildMessage), 0, false);
-#endif
-}
-
-void
-check_apple_events()
-{
-	RgnHandle       cursorRgn = NULL; 
-	Boolean         gotEvent=TRUE; 
-	EventRecord     event; 
-
-	while (gotEvent) { 
-		gotEvent = WaitNextEvent(everyEvent, &event, 0L/*timeout*/, cursorRgn); 
-		if (gotEvent) { 
-			HandleEvent(&event); 
-		} 
-	}
-}

+ 11 - 2
src/hidapi/testgui/mac_support_cocoa.m

@@ -8,10 +8,19 @@
 #include <fx.h>
 #import <Cocoa/Cocoa.h>
 
+#ifndef MAC_OS_X_VERSION_10_12
+#define MAC_OS_X_VERSION_10_12 101200
+#endif
+
+// macOS 10.12 deprecated NSAnyEventMask in favor of NSEventMaskAny
+#if MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_12
+#define NSEventMaskAny NSAnyEventMask
+#endif
+
 extern FXMainWindow *g_main_window;
 
 
-@interface MyAppDelegate : NSObject
+@interface MyAppDelegate : NSObject<NSApplicationDelegate>
 {
 } 
 @end
@@ -77,7 +86,7 @@ check_apple_events()
 
 	NSAutoreleasePool *pool = [NSAutoreleasePool new];
 	while (1) {
-		NSEvent* event = [NSApp nextEventMatchingMask:NSAnyEventMask
+		NSEvent* event = [NSApp nextEventMatchingMask:NSEventMaskAny
 		                        untilDate:nil
                                         inMode:NSDefaultRunLoopMode
                                         dequeue:YES];

+ 0 - 2
src/hidapi/testgui/start.sh

@@ -1,2 +0,0 @@
-#!/bin/bash
-xterm -e /Users/alan/work/hidapi/testgui/TestGUI.app/Contents/MacOS/tg

+ 20 - 20
src/hidapi/testgui/testgui.sln

@@ -1,20 +1,20 @@
-
-Microsoft Visual Studio Solution File, Format Version 10.00
-# Visual C++ Express 2008
-Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "testgui", "testgui.vcproj", "{08769AC3-785A-4DDC-BFC7-1775414B7AB7}"
-EndProject
-Global
-	GlobalSection(SolutionConfigurationPlatforms) = preSolution
-		Debug|Win32 = Debug|Win32
-		Release|Win32 = Release|Win32
-	EndGlobalSection
-	GlobalSection(ProjectConfigurationPlatforms) = postSolution
-		{08769AC3-785A-4DDC-BFC7-1775414B7AB7}.Debug|Win32.ActiveCfg = Debug|Win32
-		{08769AC3-785A-4DDC-BFC7-1775414B7AB7}.Debug|Win32.Build.0 = Debug|Win32
-		{08769AC3-785A-4DDC-BFC7-1775414B7AB7}.Release|Win32.ActiveCfg = Release|Win32
-		{08769AC3-785A-4DDC-BFC7-1775414B7AB7}.Release|Win32.Build.0 = Release|Win32
-	EndGlobalSection
-	GlobalSection(SolutionProperties) = preSolution
-		HideSolutionNode = FALSE
-	EndGlobalSection
-EndGlobal
+
+Microsoft Visual Studio Solution File, Format Version 10.00
+# Visual C++ Express 2008
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "testgui", "testgui.vcproj", "{08769AC3-785A-4DDC-BFC7-1775414B7AB7}"
+EndProject
+Global
+	GlobalSection(SolutionConfigurationPlatforms) = preSolution
+		Debug|Win32 = Debug|Win32
+		Release|Win32 = Release|Win32
+	EndGlobalSection
+	GlobalSection(ProjectConfigurationPlatforms) = postSolution
+		{08769AC3-785A-4DDC-BFC7-1775414B7AB7}.Debug|Win32.ActiveCfg = Debug|Win32
+		{08769AC3-785A-4DDC-BFC7-1775414B7AB7}.Debug|Win32.Build.0 = Debug|Win32
+		{08769AC3-785A-4DDC-BFC7-1775414B7AB7}.Release|Win32.ActiveCfg = Release|Win32
+		{08769AC3-785A-4DDC-BFC7-1775414B7AB7}.Release|Win32.Build.0 = Release|Win32
+	EndGlobalSection
+	GlobalSection(SolutionProperties) = preSolution
+		HideSolutionNode = FALSE
+	EndGlobalSection
+EndGlobal

+ 217 - 217
src/hidapi/testgui/testgui.vcproj

@@ -1,217 +1,217 @@
-<?xml version="1.0" encoding="Windows-1252"?>
-<VisualStudioProject
-	ProjectType="Visual C++"
-	Version="9.00"
-	Name="testgui"
-	ProjectGUID="{08769AC3-785A-4DDC-BFC7-1775414B7AB7}"
-	RootNamespace="testgui"
-	Keyword="Win32Proj"
-	TargetFrameworkVersion="196613"
-	>
-	<Platforms>
-		<Platform
-			Name="Win32"
-		/>
-	</Platforms>
-	<ToolFiles>
-	</ToolFiles>
-	<Configurations>
-		<Configuration
-			Name="Debug|Win32"
-			OutputDirectory="$(SolutionDir)$(ConfigurationName)"
-			IntermediateDirectory="$(ConfigurationName)"
-			ConfigurationType="1"
-			CharacterSet="1"
-			>
-			<Tool
-				Name="VCPreBuildEventTool"
-			/>
-			<Tool
-				Name="VCCustomBuildTool"
-			/>
-			<Tool
-				Name="VCXMLDataGeneratorTool"
-			/>
-			<Tool
-				Name="VCWebServiceProxyGeneratorTool"
-			/>
-			<Tool
-				Name="VCMIDLTool"
-			/>
-			<Tool
-				Name="VCCLCompilerTool"
-				Optimization="0"
-				AdditionalIncludeDirectories="&quot;..\..\hidapi-externals\fox\include&quot;;..\hidapi"
-				PreprocessorDefinitions="WIN32;_DEBUG;_WINDOWS"
-				MinimalRebuild="true"
-				BasicRuntimeChecks="3"
-				RuntimeLibrary="3"
-				UsePrecompiledHeader="0"
-				WarningLevel="3"
-				DebugInformationFormat="4"
-			/>
-			<Tool
-				Name="VCManagedResourceCompilerTool"
-			/>
-			<Tool
-				Name="VCResourceCompilerTool"
-			/>
-			<Tool
-				Name="VCPreLinkEventTool"
-			/>
-			<Tool
-				Name="VCLinkerTool"
-				AdditionalDependencies="setupapi.lib fox-1.6.lib"
-				OutputFile="$(ProjectName).exe"
-				LinkIncremental="2"
-				AdditionalLibraryDirectories="..\hidapi\objfre_wxp_x86\i386;&quot;..\..\hidapi-externals\fox\lib&quot;"
-				GenerateDebugInformation="true"
-				SubSystem="2"
-				EntryPointSymbol="mainCRTStartup"
-				TargetMachine="1"
-			/>
-			<Tool
-				Name="VCALinkTool"
-			/>
-			<Tool
-				Name="VCManifestTool"
-			/>
-			<Tool
-				Name="VCXDCMakeTool"
-			/>
-			<Tool
-				Name="VCBscMakeTool"
-			/>
-			<Tool
-				Name="VCFxCopTool"
-			/>
-			<Tool
-				Name="VCAppVerifierTool"
-			/>
-			<Tool
-				Name="VCPostBuildEventTool"
-				CommandLine=""
-			/>
-		</Configuration>
-		<Configuration
-			Name="Release|Win32"
-			OutputDirectory="$(SolutionDir)$(ConfigurationName)"
-			IntermediateDirectory="$(ConfigurationName)"
-			ConfigurationType="1"
-			CharacterSet="1"
-			WholeProgramOptimization="1"
-			>
-			<Tool
-				Name="VCPreBuildEventTool"
-			/>
-			<Tool
-				Name="VCCustomBuildTool"
-			/>
-			<Tool
-				Name="VCXMLDataGeneratorTool"
-			/>
-			<Tool
-				Name="VCWebServiceProxyGeneratorTool"
-			/>
-			<Tool
-				Name="VCMIDLTool"
-			/>
-			<Tool
-				Name="VCCLCompilerTool"
-				Optimization="2"
-				EnableIntrinsicFunctions="true"
-				AdditionalIncludeDirectories="&quot;..\..\hidapi-externals\fox\include&quot;;..\hidapi"
-				PreprocessorDefinitions="WIN32;NDEBUG;_WINDOWS"
-				RuntimeLibrary="2"
-				EnableFunctionLevelLinking="true"
-				UsePrecompiledHeader="0"
-				WarningLevel="3"
-				DebugInformationFormat="3"
-			/>
-			<Tool
-				Name="VCManagedResourceCompilerTool"
-			/>
-			<Tool
-				Name="VCResourceCompilerTool"
-			/>
-			<Tool
-				Name="VCPreLinkEventTool"
-			/>
-			<Tool
-				Name="VCLinkerTool"
-				AdditionalDependencies="setupapi.lib fox-1.6.lib"
-				OutputFile="$(ProjectName).exe"
-				LinkIncremental="1"
-				AdditionalLibraryDirectories="..\hidapi\objfre_wxp_x86\i386;&quot;..\..\hidapi-externals\fox\lib&quot;"
-				GenerateDebugInformation="true"
-				SubSystem="2"
-				OptimizeReferences="2"
-				EnableCOMDATFolding="2"
-				EntryPointSymbol="mainCRTStartup"
-				TargetMachine="1"
-			/>
-			<Tool
-				Name="VCALinkTool"
-			/>
-			<Tool
-				Name="VCManifestTool"
-			/>
-			<Tool
-				Name="VCXDCMakeTool"
-			/>
-			<Tool
-				Name="VCBscMakeTool"
-			/>
-			<Tool
-				Name="VCFxCopTool"
-			/>
-			<Tool
-				Name="VCAppVerifierTool"
-			/>
-			<Tool
-				Name="VCPostBuildEventTool"
-				CommandLine=""
-			/>
-		</Configuration>
-	</Configurations>
-	<References>
-	</References>
-	<Files>
-		<Filter
-			Name="Source Files"
-			Filter="cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx"
-			UniqueIdentifier="{4FC737F1-C7A5-4376-A066-2A32D752A2FF}"
-			>
-			<File
-				RelativePath="..\windows\hid.c"
-				>
-			</File>
-			<File
-				RelativePath=".\test.cpp"
-				>
-			</File>
-		</Filter>
-		<Filter
-			Name="Header Files"
-			Filter="h;hpp;hxx;hm;inl;inc;xsd"
-			UniqueIdentifier="{93995380-89BD-4b04-88EB-625FBE52EBFB}"
-			>
-			<File
-				RelativePath="..\hidapi\hidapi.h"
-				>
-			</File>
-		</Filter>
-		<Filter
-			Name="Resource Files"
-			Filter="rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav"
-			UniqueIdentifier="{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}"
-			>
-		</Filter>
-		<File
-			RelativePath=".\ReadMe.txt"
-			>
-		</File>
-	</Files>
-	<Globals>
-	</Globals>
-</VisualStudioProject>
+<?xml version="1.0" encoding="Windows-1252"?>
+<VisualStudioProject
+	ProjectType="Visual C++"
+	Version="9.00"
+	Name="testgui"
+	ProjectGUID="{08769AC3-785A-4DDC-BFC7-1775414B7AB7}"
+	RootNamespace="testgui"
+	Keyword="Win32Proj"
+	TargetFrameworkVersion="196613"
+	>
+	<Platforms>
+		<Platform
+			Name="Win32"
+		/>
+	</Platforms>
+	<ToolFiles>
+	</ToolFiles>
+	<Configurations>
+		<Configuration
+			Name="Debug|Win32"
+			OutputDirectory="$(SolutionDir)$(ConfigurationName)"
+			IntermediateDirectory="$(ConfigurationName)"
+			ConfigurationType="1"
+			CharacterSet="1"
+			>
+			<Tool
+				Name="VCPreBuildEventTool"
+			/>
+			<Tool
+				Name="VCCustomBuildTool"
+			/>
+			<Tool
+				Name="VCXMLDataGeneratorTool"
+			/>
+			<Tool
+				Name="VCWebServiceProxyGeneratorTool"
+			/>
+			<Tool
+				Name="VCMIDLTool"
+			/>
+			<Tool
+				Name="VCCLCompilerTool"
+				Optimization="0"
+				AdditionalIncludeDirectories="&quot;..\..\hidapi-externals\fox\include&quot;;..\hidapi"
+				PreprocessorDefinitions="WIN32;_DEBUG;_WINDOWS"
+				MinimalRebuild="true"
+				BasicRuntimeChecks="3"
+				RuntimeLibrary="3"
+				UsePrecompiledHeader="0"
+				WarningLevel="3"
+				DebugInformationFormat="4"
+			/>
+			<Tool
+				Name="VCManagedResourceCompilerTool"
+			/>
+			<Tool
+				Name="VCResourceCompilerTool"
+			/>
+			<Tool
+				Name="VCPreLinkEventTool"
+			/>
+			<Tool
+				Name="VCLinkerTool"
+				AdditionalDependencies="fox-1.6.lib"
+				OutputFile="$(ProjectName).exe"
+				LinkIncremental="2"
+				AdditionalLibraryDirectories="..\hidapi\objfre_wxp_x86\i386;&quot;..\..\hidapi-externals\fox\lib&quot;"
+				GenerateDebugInformation="true"
+				SubSystem="2"
+				EntryPointSymbol="mainCRTStartup"
+				TargetMachine="1"
+			/>
+			<Tool
+				Name="VCALinkTool"
+			/>
+			<Tool
+				Name="VCManifestTool"
+			/>
+			<Tool
+				Name="VCXDCMakeTool"
+			/>
+			<Tool
+				Name="VCBscMakeTool"
+			/>
+			<Tool
+				Name="VCFxCopTool"
+			/>
+			<Tool
+				Name="VCAppVerifierTool"
+			/>
+			<Tool
+				Name="VCPostBuildEventTool"
+				CommandLine=""
+			/>
+		</Configuration>
+		<Configuration
+			Name="Release|Win32"
+			OutputDirectory="$(SolutionDir)$(ConfigurationName)"
+			IntermediateDirectory="$(ConfigurationName)"
+			ConfigurationType="1"
+			CharacterSet="1"
+			WholeProgramOptimization="1"
+			>
+			<Tool
+				Name="VCPreBuildEventTool"
+			/>
+			<Tool
+				Name="VCCustomBuildTool"
+			/>
+			<Tool
+				Name="VCXMLDataGeneratorTool"
+			/>
+			<Tool
+				Name="VCWebServiceProxyGeneratorTool"
+			/>
+			<Tool
+				Name="VCMIDLTool"
+			/>
+			<Tool
+				Name="VCCLCompilerTool"
+				Optimization="2"
+				EnableIntrinsicFunctions="true"
+				AdditionalIncludeDirectories="&quot;..\..\hidapi-externals\fox\include&quot;;..\hidapi"
+				PreprocessorDefinitions="WIN32;NDEBUG;_WINDOWS"
+				RuntimeLibrary="2"
+				EnableFunctionLevelLinking="true"
+				UsePrecompiledHeader="0"
+				WarningLevel="3"
+				DebugInformationFormat="3"
+			/>
+			<Tool
+				Name="VCManagedResourceCompilerTool"
+			/>
+			<Tool
+				Name="VCResourceCompilerTool"
+			/>
+			<Tool
+				Name="VCPreLinkEventTool"
+			/>
+			<Tool
+				Name="VCLinkerTool"
+				AdditionalDependencies="fox-1.6.lib"
+				OutputFile="$(ProjectName).exe"
+				LinkIncremental="1"
+				AdditionalLibraryDirectories="..\hidapi\objfre_wxp_x86\i386;&quot;..\..\hidapi-externals\fox\lib&quot;"
+				GenerateDebugInformation="true"
+				SubSystem="2"
+				OptimizeReferences="2"
+				EnableCOMDATFolding="2"
+				EntryPointSymbol="mainCRTStartup"
+				TargetMachine="1"
+			/>
+			<Tool
+				Name="VCALinkTool"
+			/>
+			<Tool
+				Name="VCManifestTool"
+			/>
+			<Tool
+				Name="VCXDCMakeTool"
+			/>
+			<Tool
+				Name="VCBscMakeTool"
+			/>
+			<Tool
+				Name="VCFxCopTool"
+			/>
+			<Tool
+				Name="VCAppVerifierTool"
+			/>
+			<Tool
+				Name="VCPostBuildEventTool"
+				CommandLine=""
+			/>
+		</Configuration>
+	</Configurations>
+	<References>
+	</References>
+	<Files>
+		<Filter
+			Name="Source Files"
+			Filter="cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx"
+			UniqueIdentifier="{4FC737F1-C7A5-4376-A066-2A32D752A2FF}"
+			>
+			<File
+				RelativePath="..\windows\hid.c"
+				>
+			</File>
+			<File
+				RelativePath=".\test.cpp"
+				>
+			</File>
+		</Filter>
+		<Filter
+			Name="Header Files"
+			Filter="h;hpp;hxx;hm;inl;inc;xsd"
+			UniqueIdentifier="{93995380-89BD-4b04-88EB-625FBE52EBFB}"
+			>
+			<File
+				RelativePath="..\hidapi\hidapi.h"
+				>
+			</File>
+		</Filter>
+		<Filter
+			Name="Resource Files"
+			Filter="rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav"
+			UniqueIdentifier="{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}"
+			>
+		</Filter>
+		<File
+			RelativePath=".\ReadMe.txt"
+			>
+		</File>
+	</Files>
+	<Globals>
+	</Globals>
+</VisualStudioProject>

+ 36 - 0
src/hidapi/udev/69-hid.rules

@@ -0,0 +1,36 @@
+# This is a sample udev file for HIDAPI devices which lets unprivileged
+# users who are physically present at the system (not remote users) access
+# HID devices.
+
+# If you are using the libusb implementation of hidapi (libusb/hid.c), then
+# use something like the following line, substituting the VID and PID with
+# those of your device.
+
+# HIDAPI/libusb
+SUBSYSTEMS=="usb", ATTRS{idVendor}=="04d8", ATTRS{idProduct}=="003f", TAG+="uaccess"
+
+# If you are using the hidraw implementation (linux/hid.c), then do something
+# like the following, substituting the VID and PID with your device.
+
+# HIDAPI/hidraw
+KERNEL=="hidraw*", ATTRS{idVendor}=="04d8", ATTRS{idProduct}=="003f", TAG+="uaccess"
+
+# Once done, optionally rename this file for your application, and drop it into
+# /etc/udev/rules.d/.
+# NOTE: these rules must have priorty before 73-seat-late.rules.
+# (Small discussion/explanation in systemd repo:
+#  https://github.com/systemd/systemd/issues/4288#issuecomment-348166161)
+# for example, name the file /etc/udev/rules.d/70-my-application-hid.rules.
+# Then, replug your device or run:
+# sudo udevadm control --reload-rules && sudo udevadm trigger
+
+# Note that the hexadecimal values for VID and PID are case sensitive and
+# must be lower case.
+
+# TAG+="uaccess" only gives permission to physically present users, which
+# is appropriate in most scenarios. If you require access to the device
+# from a remote session (e.g. over SSH), add
+# GROUP="plugdev", MODE="660"
+# to the end of the udev rule lines, add your user to the plugdev group with:
+# usermod -aG plugdev USERNAME
+# then log out and log back in (or restart the system).

+ 0 - 33
src/hidapi/udev/99-hid.rules

@@ -1,33 +0,0 @@
-# This is a sample udev file for HIDAPI devices which changes the permissions
-# to 0666 (world readable/writable) for a specified device on Linux systems.
-
-
-# If you are using the libusb implementation of hidapi (libusb/hid.c), then
-# use something like the following line, substituting the VID and PID with
-# those of your device. Note that for kernels before 2.6.24, you will need
-# to substitute "usb" with "usb_device". It shouldn't hurt to use two lines
-# (one each way) for compatibility with older systems.
-
-# HIDAPI/libusb
-SUBSYSTEM=="usb", ATTRS{idVendor}=="04d8", ATTRS{idProduct}=="003f", MODE="0666"
-
-
-# If you are using the hidraw implementation (linux/hid.c), then do something
-# like the following, substituting the VID and PID with your device. Busnum 1
-# is USB.
-
-# HIDAPI/hidraw
-KERNEL=="hidraw*", ATTRS{busnum}=="1", ATTRS{idVendor}=="04d8", ATTRS{idProduct}=="003f", MODE="0666"
-
-# Once done, optionally rename this file for your device, and drop it into
-# /etc/udev/rules.d and unplug and re-plug your device. This is all that is
-# necessary to see the new permissions. Udev does not have to be restarted.
-
-# Note that the hexadecimal values for VID and PID are case sensitive and
-# must be lower case.
-
-# If you think permissions of 0666 are too loose, then see:
-# http://reactivated.net/writing_udev_rules.html for more information on finer
-# grained permission setting. For example, it might be sufficient to just
-# set the group or user owner for specific devices (for example the plugdev
-# group on some systems).

+ 17 - 0
src/hidapi/windows/.gitignore

@@ -0,0 +1,17 @@
+Debug
+Release
+.vs/
+*.exp
+*.ilk
+*.lib
+*.suo
+*.vcproj.*
+*.vcxproj.*
+*.ncb
+*.suo
+*.dll
+*.pdb
+.deps
+.libs
+*.lo
+*.la

+ 63 - 0
src/hidapi/windows/CMakeLists.txt

@@ -0,0 +1,63 @@
+list(APPEND HIDAPI_PUBLIC_HEADERS "hidapi_winapi.h")
+
+set(SOURCES
+    hid.c
+    hidapi_cfgmgr32.h
+    hidapi_descriptor_reconstruct.c
+    hidapi_descriptor_reconstruct.h
+    hidapi_hidclass.h
+    hidapi_hidpi.h
+    hidapi_hidsdi.h
+)
+
+if(BUILD_SHARED_LIBS)
+    list(APPEND SOURCES hidapi.rc)
+endif()
+
+add_library(hidapi_winapi
+    ${HIDAPI_PUBLIC_HEADERS}
+    ${SOURCES}
+)
+target_link_libraries(hidapi_winapi
+    PUBLIC hidapi_include
+)
+
+if(NOT BUILD_SHARED_LIBS)
+    target_compile_definitions(hidapi_winapi
+        # prevent marking functions as __declspec(dllexport) for static library build
+        # #480: this should be refactored for v1.0
+        PUBLIC HID_API_NO_EXPORT_DEFINE
+    )
+endif()
+
+set_target_properties(hidapi_winapi
+    PROPERTIES
+        EXPORT_NAME "winapi"
+        OUTPUT_NAME "hidapi"
+        VERSION ${PROJECT_VERSION}
+        PUBLIC_HEADER "${HIDAPI_PUBLIC_HEADERS}"
+)
+
+# compatibility with find_package()
+add_library(hidapi::winapi ALIAS hidapi_winapi)
+# compatibility with raw library link
+add_library(hidapi ALIAS hidapi_winapi)
+
+if(HIDAPI_INSTALL_TARGETS)
+    install(TARGETS hidapi_winapi EXPORT hidapi
+        RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}"
+        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.pc.in")
+
+if(HIDAPI_WITH_TESTS)
+     add_subdirectory(test)
+endif()
+
+if(DEFINED HIDAPI_BUILD_PP_DATA_DUMP AND HIDAPI_BUILD_PP_DATA_DUMP)
+    add_subdirectory(pp_data_dump)
+endif()

+ 0 - 1
src/hidapi/windows/Makefile.am

@@ -8,7 +8,6 @@ hdrdir = $(includedir)/hidapi
 hdr_HEADERS = $(top_srcdir)/hidapi/hidapi.h
 
 EXTRA_DIST = \
-  ddk_build \
   hidapi.vcproj \
   hidtest.vcproj \
   Makefile-manual \

+ 6 - 11
src/hidapi/windows/Makefile.mingw

@@ -9,16 +9,14 @@
 all: hidtest libhidapi.dll
 
 CC=gcc
-CXX=g++
-COBJS=hid.o
-CPPOBJS=../hidtest/hidtest.o
-OBJS=$(COBJS) $(CPPOBJS)
-CFLAGS=-I../hidapi -g -c
-LIBS= -lsetupapi
-DLL_LDFLAGS = -mwindows -lsetupapi
+COBJS=hid.o ../hidtest/test.o
+OBJS=$(COBJS)
+CFLAGS=-I../hidapi -I. -g -c
+LIBS=
+DLL_LDFLAGS = -mwindows
 
 hidtest: $(OBJS)
-	g++ -g $^ $(LIBS) -o hidtest
+	$(CC) -g $^ $(LIBS) -o hidtest
 
 libhidapi.dll: $(OBJS)
 	$(CC) -g $^ $(DLL_LDFLAGS) -o libhidapi.dll
@@ -26,9 +24,6 @@ libhidapi.dll: $(OBJS)
 $(COBJS): %.o: %.c
 	$(CC) $(CFLAGS) $< -o $@
 
-$(CPPOBJS): %.o: %.cpp
-	$(CXX) $(CFLAGS) $< -o $@
-
 clean:
 	rm *.o ../hidtest/*.o hidtest.exe
 

+ 0 - 17
src/hidapi/windows/ddk_build/hidapi.def

@@ -1,17 +0,0 @@
-LIBRARY   hidapi
-EXPORTS
-   hid_open    @1
-   hid_write   @2
-   hid_read    @3
-   hid_close   @4
-   hid_get_product_string @5
-   hid_get_manufacturer_string @6
-   hid_get_serial_number_string @7
-   hid_get_indexed_string @8
-   hid_error @9
-   hid_set_nonblocking @10
-   hid_enumerate @11
-   hid_open_path @12
-   hid_send_feature_report @13
-   hid_get_feature_report @14
-   

+ 0 - 49
src/hidapi/windows/ddk_build/makefile

@@ -1,49 +0,0 @@
-#############################################################################
-#
-#               Copyright (C) Microsoft Corporation 1995, 1996
-#       All Rights Reserved.
-#
-#       MAKEFILE for HID directory
-#
-#############################################################################
-
-!IFDEF WIN95_BUILD
-
-ROOT=..\..\..\..
-
-VERSIONLIST = debug retail
-IS_32 = TRUE
-IS_SDK = TRUE
-IS_PRIVATE = TRUE
-IS_SDK = TRUE
-IS_DDK = TRUE
-WIN32 = TRUE
-COMMONMKFILE = hidapi.mk
-
-!include $(ROOT)\dev\master.mk
-
-
-!ELSE
-
-#
-# DO NOT EDIT THIS FILE!!!  Edit .\sources. if you want to add a new source
-# file to this component.  This file merely indirects to the real make file
-# that is shared by all the driver components of the Windows NT DDK
-#
-
-!IF DEFINED(_NT_TARGET_VERSION)
-!	IF $(_NT_TARGET_VERSION)>=0x501
-!		INCLUDE $(NTMAKEENV)\makefile.def
-!	ELSE
-#               Only warn once per directory
-!               INCLUDE $(NTMAKEENV)\makefile.plt
-!               IF "$(BUILD_PASS)"=="PASS1"
-!		    message BUILDMSG: Warning : The sample "$(MAKEDIR)" is not valid for the current OS target.
-!               ENDIF
-!	ENDIF
-!ELSE
-!	INCLUDE $(NTMAKEENV)\makefile.def
-!ENDIF
-
-!ENDIF
-

+ 0 - 23
src/hidapi/windows/ddk_build/sources

@@ -1,23 +0,0 @@
-TARGETNAME=hidapi
-TARGETTYPE=DYNLINK
-UMTYPE=console
-UMENTRY=main
-
-MSC_WARNING_LEVEL=/W3 /WX
-
-TARGETLIBS=$(SDK_LIB_PATH)\hid.lib \
-           $(SDK_LIB_PATH)\setupapi.lib \
-           $(SDK_LIB_PATH)\kernel32.lib \
-           $(SDK_LIB_PATH)\comdlg32.lib
-
-USE_MSVCRT=1
-
-INCLUDES= ..\..\hidapi
-SOURCES= ..\hid.c \
-
-
-TARGET_DESTINATION=retail
-
-MUI=0
-MUI_COMMENT="HID Interface DLL"
-

+ 752 - 724
src/hidapi/windows/hid.c

@@ -5,9 +5,9 @@
  Alan Ott
  Signal 11 Software
 
- 8/22/2009
+ libusb/hidapi Team
 
- Copyright 2009, All Rights Reserved.
+ Copyright 2022, All Rights Reserved.
 
  At the discretion of the user of this library,
  this software may be licensed under the terms of the
@@ -19,98 +19,61 @@
  code repository located at:
         https://github.com/libusb/hidapi .
 ********************************************************/
-#include "SDL_internal.h"
 
+#if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS)
+/* Do not warn about wcsncpy usage.
+   https://docs.microsoft.com/cpp/c-runtime-library/security-features-in-the-crt */
+#define _CRT_SECURE_NO_WARNINGS
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include "hidapi_winapi.h"
 
 #include <windows.h>
 
-#ifndef _WIN32_WINNT_WIN8
-#define _WIN32_WINNT_WIN8   0x0602
+#ifndef _NTDEF_
+typedef LONG NTSTATUS;
 #endif
 
-#if 0 /* can cause redefinition errors on some toolchains */
 #ifdef __MINGW32__
 #include <ntdef.h>
 #include <winbase.h>
+#define WC_ERR_INVALID_CHARS 0x00000080
 #endif
 
 #ifdef __CYGWIN__
 #include <ntdef.h>
+#include <wctype.h>
 #define _wcsdup wcsdup
 #endif
-#endif /* */
-
-#ifndef _NTDEF_
-typedef LONG NTSTATUS;
-#endif
-
-/* The maximum number of characters that can be passed into the
-   HidD_Get*String() functions without it failing.*/
-#define MAX_STRING_WCHARS 0xFFF
 
 /*#define HIDAPI_USE_DDK*/
 
-/* The timeout in milliseconds for waiting on WriteFile to
-   complete in hid_write. The longest observed time to do a output
-   report that we've seen is ~200-250ms so let's double that */
-#define HID_WRITE_TIMEOUT_MILLISECONDS 500
-
-/* We will only enumerate devices that match these usages */
-#define USAGE_PAGE_GENERIC_DESKTOP 0x0001
-#define USAGE_JOYSTICK 0x0004
-#define USAGE_GAMEPAD 0x0005
-#define USAGE_MULTIAXISCONTROLLER 0x0008
-#define USB_VENDOR_VALVE 0x28de
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-	#include <setupapi.h>
-	#include <winioctl.h>
-	#include <devpropdef.h>
-	#include "hidapi_cfgmgr32.h"
-	#include "hidapi_hidclass.h"
-	#include "hidapi_hidsdi.h"
-
-#ifdef __cplusplus
-} /* extern "C" */
-#endif
-
-#include "../hidapi/hidapi.h"
-
-/*#include <stdio.h>*/
-/*#include <stdlib.h>*/
-
-/* SDL C runtime functions */
-
-#define calloc SDL_calloc
-#define free SDL_free
-#define malloc SDL_malloc
-#define memcpy SDL_memcpy
-#define memset SDL_memset
-#define strcmp SDL_strcmp
-#define strlen SDL_strlen
-#define strstr SDL_strstr
-#define strtol SDL_strtol
-#define towupper SDL_toupper
-#define wcscmp SDL_wcscmp
-#define wcslen SDL_wcslen
-#define _wcsdup SDL_wcsdup
-#define wcsstr SDL_wcsstr
+#include "hidapi_cfgmgr32.h"
+#include "hidapi_hidclass.h"
+#include "hidapi_hidsdi.h"
 
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
 
+#ifdef MIN
 #undef MIN
+#endif
 #define MIN(x,y) ((x) < (y)? (x): (y))
 
-#ifdef _MSC_VER
-	/* Yes, we have some unreferenced formal parameters */
-	#pragma warning(disable:4100)
-#endif
-
-#ifdef __cplusplus
-extern "C" {
-#endif
+/* MAXIMUM_USB_STRING_LENGTH from usbspec.h is 255 */
+/* BLUETOOTH_DEVICE_NAME_SIZE from bluetoothapis.h is 256 */
+#define MAX_STRING_WCHARS 256
 
+static struct hid_api_version api_version = {
+	.major = HID_API_VERSION_MAJOR,
+	.minor = HID_API_VERSION_MINOR,
+	.patch = HID_API_VERSION_PATCH
+};
 
 #ifndef HIDAPI_USE_DDK
 /* Since we're not building with the DDK, and the HID header
@@ -131,7 +94,6 @@ static HidD_GetPreparsedData_ HidD_GetPreparsedData;
 static HidD_FreePreparsedData_ HidD_FreePreparsedData;
 static HidP_GetCaps_ HidP_GetCaps;
 static HidD_SetNumInputBuffers_ HidD_SetNumInputBuffers;
-static HidD_SetOutputReport_ HidD_SetOutputReport;
 
 static CM_Locate_DevNodeW_ CM_Locate_DevNodeW = NULL;
 static CM_Get_Parent_ CM_Get_Parent = NULL;
@@ -144,7 +106,7 @@ static HMODULE hid_lib_handle = NULL;
 static HMODULE cfgmgr32_lib_handle = NULL;
 static BOOLEAN hidapi_initialized = FALSE;
 
-static void free_library_handles(void)
+static void free_library_handles()
 {
 	if (hid_lib_handle)
 		FreeLibrary(hid_lib_handle);
@@ -154,11 +116,7 @@ static void free_library_handles(void)
 	cfgmgr32_lib_handle = NULL;
 }
 
-#ifdef __GNUC__
-# pragma GCC diagnostic push
-# pragma GCC diagnostic ignored "-Wcast-function-type"
-#endif
-static int lookup_functions(void)
+static int lookup_functions()
 {
 	hid_lib_handle = LoadLibraryW(L"hid.dll");
 	if (hid_lib_handle == NULL) {
@@ -170,6 +128,10 @@ static int lookup_functions(void)
 		goto err;
 	}
 
+#if defined(__GNUC__)
+# pragma GCC diagnostic push
+# pragma GCC diagnostic ignored "-Wcast-function-type"
+#endif
 #define RESOLVE(lib_handle, x) x = (x##_)GetProcAddress(lib_handle, #x); if (!x) goto err;
 
 	RESOLVE(hid_lib_handle, HidD_GetHidGuid);
@@ -185,7 +147,6 @@ static int lookup_functions(void)
 	RESOLVE(hid_lib_handle, HidD_FreePreparsedData);
 	RESOLVE(hid_lib_handle, HidP_GetCaps);
 	RESOLVE(hid_lib_handle, HidD_SetNumInputBuffers);
-	RESOLVE(hid_lib_handle, HidD_SetOutputReport);
 
 	RESOLVE(cfgmgr32_lib_handle, CM_Locate_DevNodeW);
 	RESOLVE(cfgmgr32_lib_handle, CM_Get_Parent);
@@ -195,6 +156,9 @@ static int lookup_functions(void)
 	RESOLVE(cfgmgr32_lib_handle, CM_Get_Device_Interface_ListW);
 
 #undef RESOLVE
+#if defined(__GNUC__)
+# pragma GCC diagnostic pop
+#endif
 
 	return 0;
 
@@ -202,9 +166,6 @@ err:
 	free_library_handles();
 	return -1;
 }
-#ifdef __GNUC__
-# pragma GCC diagnostic pop
-#endif
 
 #endif /* HIDAPI_USE_DDK */
 
@@ -212,50 +173,41 @@ struct hid_device_ {
 		HANDLE device_handle;
 		BOOL blocking;
 		USHORT output_report_length;
+		unsigned char *write_buf;
 		size_t input_report_length;
-		void *last_error_str;
-		DWORD last_error_num;
+		USHORT feature_report_length;
+		unsigned char *feature_buf;
+		wchar_t *last_error_str;
 		BOOL read_pending;
 		char *read_buf;
 		OVERLAPPED ol;
 		OVERLAPPED write_ol;
-		BOOL use_hid_write_output_report;
+		struct hid_device_info* device_info;
 };
 
-static BOOL IsWindowsVersionOrGreater(WORD wMajorVersion, WORD wMinorVersion, WORD wServicePackMajor)
-{
-	OSVERSIONINFOEXW osvi;
-	DWORDLONG const dwlConditionMask = VerSetConditionMask(
-		VerSetConditionMask(
-			VerSetConditionMask(
-				0, VER_MAJORVERSION, VER_GREATER_EQUAL ),
-			VER_MINORVERSION, VER_GREATER_EQUAL ),
-		VER_SERVICEPACKMAJOR, VER_GREATER_EQUAL );
-
-	memset(&osvi, 0, sizeof(osvi));
-	osvi.dwOSVersionInfoSize = sizeof( osvi );
-	osvi.dwMajorVersion = wMajorVersion;
-	osvi.dwMinorVersion = wMinorVersion;
-	osvi.wServicePackMajor = wServicePackMajor;
-
-	return VerifyVersionInfoW(&osvi, VER_MAJORVERSION | VER_MINORVERSION | VER_SERVICEPACKMAJOR, dwlConditionMask) != FALSE;
-}
-
-static hid_device *new_hid_device(void)
+static hid_device *new_hid_device()
 {
 	hid_device *dev = (hid_device*) calloc(1, sizeof(hid_device));
+
+	if (dev == NULL) {
+		return NULL;
+	}
+
 	dev->device_handle = INVALID_HANDLE_VALUE;
 	dev->blocking = TRUE;
 	dev->output_report_length = 0;
+	dev->write_buf = NULL;
 	dev->input_report_length = 0;
+	dev->feature_report_length = 0;
+	dev->feature_buf = NULL;
 	dev->last_error_str = NULL;
-	dev->last_error_num = 0;
 	dev->read_pending = FALSE;
 	dev->read_buf = NULL;
 	memset(&dev->ol, 0, sizeof(dev->ol));
 	dev->ol.hEvent = CreateEvent(NULL, FALSE, FALSE /*initial state f=nonsignaled*/, NULL);
 	memset(&dev->write_ol, 0, sizeof(dev->write_ol));
-	dev->write_ol.hEvent = CreateEvent(NULL, FALSE, FALSE /*initial state f=nonsignaled*/, NULL);
+	dev->write_ol.hEvent = CreateEvent(NULL, FALSE, FALSE /*inital state f=nonsignaled*/, NULL);
+	dev->device_info = NULL;
 
 	return dev;
 }
@@ -265,54 +217,123 @@ static void free_hid_device(hid_device *dev)
 	CloseHandle(dev->ol.hEvent);
 	CloseHandle(dev->write_ol.hEvent);
 	CloseHandle(dev->device_handle);
-	LocalFree(dev->last_error_str);
+	free(dev->last_error_str);
+	dev->last_error_str = NULL;
+	free(dev->write_buf);
+	free(dev->feature_buf);
 	free(dev->read_buf);
+	hid_free_enumeration(dev->device_info);
 	free(dev);
 }
 
-static void register_error(hid_device *device, const char *op)
+static void register_winapi_error_to_buffer(wchar_t **error_buffer, const WCHAR *op)
 {
-	WCHAR *ptr, *msg;
+	free(*error_buffer);
+	*error_buffer = NULL;
+
+	/* Only clear out error messages if NULL is passed into op */
+	if (!op) {
+		return;
+	}
 
-	DWORD count = FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER |
-		FORMAT_MESSAGE_FROM_SYSTEM |
-		FORMAT_MESSAGE_IGNORE_INSERTS,
+	WCHAR system_err_buf[1024];
+	DWORD error_code = GetLastError();
+
+	DWORD system_err_len = FormatMessageW(
+		FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
 		NULL,
-		GetLastError(),
+		error_code,
 		MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
-		(LPWSTR)&msg, 0/*sz*/,
+		system_err_buf, ARRAYSIZE(system_err_buf),
 		NULL);
-	if (!count)
+
+	DWORD op_len = (DWORD)wcslen(op);
+
+	DWORD op_prefix_len =
+		op_len
+		+ 15 /*: (0x00000000) */
+		;
+	DWORD msg_len =
+		+ op_prefix_len
+		+ system_err_len
+		;
+
+	*error_buffer = (WCHAR *)calloc(msg_len + 1, sizeof (WCHAR));
+	WCHAR *msg = *error_buffer;
+
+	if (!msg)
+		return;
+
+	int printf_written = swprintf(msg, msg_len + 1, L"%.*ls: (0x%08X) %.*ls", (int)op_len, op, error_code, (int)system_err_len, system_err_buf);
+
+	if (printf_written < 0)
+	{
+		/* Highly unlikely */
+		msg[0] = L'\0';
 		return;
+	}
 
 	/* Get rid of the CR and LF that FormatMessage() sticks at the
 	   end of the message. Thanks Microsoft! */
-	ptr = msg;
-	while (*ptr) {
-		if (*ptr == '\r') {
-			*ptr = 0x0000;
-			break;
-		}
-		ptr++;
+	while(msg[msg_len-1] == L'\r' || msg[msg_len-1] == L'\n' || msg[msg_len-1] == L' ')
+	{
+		msg[msg_len-1] = L'\0';
+		msg_len--;
 	}
+}
 
-	/* Store the message off in the Device entry so that
-	   the hid_error() function can pick it up. */
-	LocalFree(device->last_error_str);
-	device->last_error_str = msg;
+#if defined(__GNUC__)
+# pragma GCC diagnostic push
+# pragma GCC diagnostic ignored "-Warray-bounds"
+#endif
+/* A bug in GCC/mingw gives:
+ * error: array subscript 0 is outside array bounds of 'wchar_t *[0]' {aka 'short unsigned int *[]'} [-Werror=array-bounds]
+ * |         free(*error_buffer);
+ * Which doesn't make sense in this context. */
+
+static void register_string_error_to_buffer(wchar_t **error_buffer, const WCHAR *string_error)
+{
+	free(*error_buffer);
+	*error_buffer = NULL;
+
+	if (string_error) {
+		*error_buffer = _wcsdup(string_error);
+	}
+}
+
+#if defined(__GNUC__)
+# pragma GCC diagnostic pop
+#endif
+
+static void register_winapi_error(hid_device *dev, const WCHAR *op)
+{
+	register_winapi_error_to_buffer(&dev->last_error_str, op);
 }
 
-static HANDLE open_device(const char *path, BOOL enumerate, BOOL bExclusive )
+static void register_string_error(hid_device *dev, const WCHAR *string_error)
+{
+	register_string_error_to_buffer(&dev->last_error_str, string_error);
+}
+
+static wchar_t *last_global_error_str = NULL;
+
+static void register_global_winapi_error(const WCHAR *op)
+{
+	register_winapi_error_to_buffer(&last_global_error_str, op);
+}
+
+static void register_global_error(const WCHAR *string_error)
+{
+	register_string_error_to_buffer(&last_global_error_str, string_error);
+}
+
+static HANDLE open_device(const wchar_t *path, BOOL open_rw)
 {
 	HANDLE handle;
-	// Opening with access 0 causes keyboards to stop responding in some system configurations
-	// http://steamcommunity.com/discussions/forum/1/1843493219428923893
-	// Thanks to co-wie (Ka-wei Low <kawei@mac.com>) for help narrowing down the problem on his system
-	//DWORD desired_access = (enumerate)? 0: (GENERIC_WRITE | GENERIC_READ);
-	DWORD desired_access = ( GENERIC_WRITE | GENERIC_READ );
-	DWORD share_mode = bExclusive ? 0 : ( FILE_SHARE_READ | FILE_SHARE_WRITE );
-
-	handle = CreateFileA(path,
+	DWORD desired_access = (open_rw)? (GENERIC_WRITE | GENERIC_READ): 0;
+	DWORD share_mode = FILE_SHARE_READ|FILE_SHARE_WRITE;
+
+	handle = CreateFileW(path,
 		desired_access,
 		share_mode,
 		NULL,
@@ -323,11 +344,23 @@ static HANDLE open_device(const char *path, BOOL enumerate, BOOL bExclusive )
 	return handle;
 }
 
+HID_API_EXPORT const struct hid_api_version* HID_API_CALL hid_version(void)
+{
+	return &api_version;
+}
+
+HID_API_EXPORT const char* HID_API_CALL hid_version_str(void)
+{
+	return HID_API_VERSION_STR;
+}
+
 int HID_API_EXPORT hid_init(void)
 {
+	register_global_error(NULL);
 #ifndef HIDAPI_USE_DDK
 	if (!hidapi_initialized) {
 		if (lookup_functions() < 0) {
+			register_global_winapi_error(L"resolve DLL functions");
 			return -1;
 		}
 		hidapi_initialized = TRUE;
@@ -342,6 +375,7 @@ int HID_API_EXPORT hid_exit(void)
 	free_library_handles();
 	hidapi_initialized = FALSE;
 #endif
+	register_global_error(NULL);
 	return 0;
 }
 
@@ -387,65 +421,178 @@ static void* hid_internal_get_device_interface_property(const wchar_t* interface
 	return property_value;
 }
 
-static void hid_internal_get_ble_info(struct hid_device_info* dev, DEVINST dev_node)
+static void hid_internal_towupper(wchar_t* string)
 {
-	wchar_t *manufacturer_string, *serial_number, *product_string;
-	/* Manufacturer String */
-	manufacturer_string = hid_internal_get_devnode_property(dev_node, (const DEVPROPKEY*)&PKEY_DeviceInterface_Bluetooth_Manufacturer, DEVPROP_TYPE_STRING);
-	if (manufacturer_string) {
-		free(dev->manufacturer_string);
-		dev->manufacturer_string = manufacturer_string;
+	for (wchar_t* p = string; *p; ++p) *p = towupper(*p);
+}
+
+static int hid_internal_extract_int_token_value(wchar_t* string, const wchar_t* token)
+{
+	int token_value;
+	wchar_t* startptr, * endptr;
+
+	startptr = wcsstr(string, token);
+	if (!startptr)
+		return -1;
+
+	startptr += wcslen(token);
+	token_value = wcstol(startptr, &endptr, 16);
+	if (endptr == startptr)
+		return -1;
+
+	return token_value;
+}
+
+static void hid_internal_get_usb_info(struct hid_device_info* dev, DEVINST dev_node)
+{
+	wchar_t *device_id = NULL, *hardware_ids = NULL;
+
+	device_id = hid_internal_get_devnode_property(dev_node, &DEVPKEY_Device_InstanceId, DEVPROP_TYPE_STRING);
+	if (!device_id)
+		goto end;
+
+	/* Normalize to upper case */
+	hid_internal_towupper(device_id);
+
+	/* Check for Xbox Common Controller class (XUSB) device.
+	   https://docs.microsoft.com/windows/win32/xinput/directinput-and-xusb-devices
+	   https://docs.microsoft.com/windows/win32/xinput/xinput-and-directinput
+	*/
+	if (hid_internal_extract_int_token_value(device_id, L"IG_") != -1) {
+		/* Get devnode parent to reach out USB device. */
+		if (CM_Get_Parent(&dev_node, dev_node, 0) != CR_SUCCESS)
+			goto end;
 	}
 
-	/* Serial Number String (MAC Address) */
-	serial_number = hid_internal_get_devnode_property(dev_node, (const DEVPROPKEY*)&PKEY_DeviceInterface_Bluetooth_DeviceAddress, DEVPROP_TYPE_STRING);
-	if (serial_number) {
-		free(dev->serial_number);
-		dev->serial_number = serial_number;
+	/* Get the hardware ids from devnode */
+	hardware_ids = hid_internal_get_devnode_property(dev_node, &DEVPKEY_Device_HardwareIds, DEVPROP_TYPE_STRING_LIST);
+	if (!hardware_ids)
+		goto end;
+
+	/* Get additional information from USB device's Hardware ID
+	   https://docs.microsoft.com/windows-hardware/drivers/install/standard-usb-identifiers
+	   https://docs.microsoft.com/windows-hardware/drivers/usbcon/enumeration-of-interfaces-not-grouped-in-collections
+	*/
+	for (wchar_t* hardware_id = hardware_ids; *hardware_id; hardware_id += wcslen(hardware_id) + 1) {
+		/* Normalize to upper case */
+		hid_internal_towupper(hardware_id);
+
+		if (dev->release_number == 0) {
+			/* USB_DEVICE_DESCRIPTOR.bcdDevice value. */
+			int release_number = hid_internal_extract_int_token_value(hardware_id, L"REV_");
+			if (release_number != -1) {
+				dev->release_number = (unsigned short)release_number;
+			}
+		}
+
+		if (dev->interface_number == -1) {
+			/* USB_INTERFACE_DESCRIPTOR.bInterfaceNumber value. */
+			int interface_number = hid_internal_extract_int_token_value(hardware_id, L"MI_");
+			if (interface_number != -1) {
+				dev->interface_number = interface_number;
+			}
+		}
 	}
 
-	/* Get devnode grandparent to reach out Bluetooth LE device node */
-	if (CM_Get_Parent(&dev_node, dev_node, 0) != CR_SUCCESS)
-		return;
+	/* Try to get USB device manufacturer string if not provided by HidD_GetManufacturerString. */
+	if (wcslen(dev->manufacturer_string) == 0) {
+		wchar_t* manufacturer_string = hid_internal_get_devnode_property(dev_node, &DEVPKEY_Device_Manufacturer, DEVPROP_TYPE_STRING);
+		if (manufacturer_string) {
+			free(dev->manufacturer_string);
+			dev->manufacturer_string = manufacturer_string;
+		}
+	}
 
-	/* Product String */
-	product_string = hid_internal_get_devnode_property(dev_node, &DEVPKEY_NAME, DEVPROP_TYPE_STRING);
-	if (product_string) {
-		free(dev->product_string);
-		dev->product_string = product_string;
+	/* Try to get USB device serial number if not provided by HidD_GetSerialNumberString. */
+	if (wcslen(dev->serial_number) == 0) {
+		DEVINST usb_dev_node = dev_node;
+		if (dev->interface_number != -1) {
+			/* Get devnode parent to reach out composite parent USB device.
+			   https://docs.microsoft.com/windows-hardware/drivers/usbcon/enumeration-of-the-composite-parent-device
+			*/
+			if (CM_Get_Parent(&usb_dev_node, dev_node, 0) != CR_SUCCESS)
+				goto end;
+		}
+
+		/* Get the device id of the USB device. */
+		free(device_id);
+		device_id = hid_internal_get_devnode_property(usb_dev_node, &DEVPKEY_Device_InstanceId, DEVPROP_TYPE_STRING);
+		if (!device_id)
+			goto end;
+
+		/* Extract substring after last '\\' of Instance ID.
+		   For USB devices it may contain device's serial number.
+		   https://docs.microsoft.com/windows-hardware/drivers/install/instance-ids
+		*/
+		for (wchar_t *ptr = device_id + wcslen(device_id); ptr > device_id; --ptr) {
+			/* Instance ID is unique only within the scope of the bus.
+			   For USB devices it means that serial number is not available. Skip. */
+			if (*ptr == L'&')
+				break;
+
+			if (*ptr == L'\\') {
+				free(dev->serial_number);
+				dev->serial_number = _wcsdup(ptr + 1);
+				break;
+			}
+		}
 	}
-}
 
-#if 0
-/* USB Device Interface Number.
-   It can be parsed out of the Hardware ID if a USB device is has multiple interfaces (composite device).
-   See https://docs.microsoft.com/windows-hardware/drivers/hid/hidclass-hardware-ids-for-top-level-collections
-   and https://docs.microsoft.com/windows-hardware/drivers/install/standard-usb-identifiers
+	/* If we can't get the interface number, it means that there is only one interface. */
+	if (dev->interface_number == -1)
+		dev->interface_number = 0;
 
-   hardware_id is always expected to be uppercase.
+end:
+	free(device_id);
+	free(hardware_ids);
+}
+
+/* HidD_GetProductString/HidD_GetManufacturerString/HidD_GetSerialNumberString is not working for BLE HID devices
+   Request this info via dev node properties instead.
+   https://docs.microsoft.com/answers/questions/401236/hidd-getproductstring-with-ble-hid-device.html
 */
-static int hid_internal_get_interface_number(const wchar_t* hardware_id)
+static void hid_internal_get_ble_info(struct hid_device_info* dev, DEVINST dev_node)
 {
-	int interface_number;
-	wchar_t *startptr, *endptr;
-	const wchar_t *interface_token = L"&MI_";
+	if (wcslen(dev->manufacturer_string) == 0) {
+		/* Manufacturer Name String (UUID: 0x2A29) */
+		wchar_t* manufacturer_string = hid_internal_get_devnode_property(dev_node, (const DEVPROPKEY*)&PKEY_DeviceInterface_Bluetooth_Manufacturer, DEVPROP_TYPE_STRING);
+		if (manufacturer_string) {
+			free(dev->manufacturer_string);
+			dev->manufacturer_string = manufacturer_string;
+		}
+	}
 
-	startptr = wcsstr(hardware_id, interface_token);
-	if (!startptr)
-		return -1;
+	if (wcslen(dev->serial_number) == 0) {
+		/* Serial Number String (UUID: 0x2A25) */
+		wchar_t* serial_number = hid_internal_get_devnode_property(dev_node, (const DEVPROPKEY*)&PKEY_DeviceInterface_Bluetooth_DeviceAddress, DEVPROP_TYPE_STRING);
+		if (serial_number) {
+			free(dev->serial_number);
+			dev->serial_number = serial_number;
+		}
+	}
 
-	startptr += wcslen(interface_token);
-	interface_number = wcstol(startptr, &endptr, 16);
-	if (endptr == startptr)
-		return -1;
+	if (wcslen(dev->product_string) == 0) {
+		/* Model Number String (UUID: 0x2A24) */
+		wchar_t* product_string = hid_internal_get_devnode_property(dev_node, (const DEVPROPKEY*)&PKEY_DeviceInterface_Bluetooth_ModelNumber, DEVPROP_TYPE_STRING);
+		if (!product_string) {
+			DEVINST parent_dev_node = 0;
+			/* Fallback: Get devnode grandparent to reach out Bluetooth LE device node */
+			if (CM_Get_Parent(&parent_dev_node, dev_node, 0) == CR_SUCCESS) {
+				/* Device Name (UUID: 0x2A00) */
+				product_string = hid_internal_get_devnode_property(parent_dev_node, &DEVPKEY_NAME, DEVPROP_TYPE_STRING);
+			}
+		}
 
-	return interface_number;
+		if (product_string) {
+			free(dev->product_string);
+			dev->product_string = product_string;
+		}
+	}
 }
 
 static void hid_internal_get_info(const wchar_t* interface_path, struct hid_device_info* dev)
 {
-	wchar_t *device_id = NULL, *compatible_ids = NULL, *hardware_ids = NULL;
-	wchar_t *id;
+	wchar_t *device_id = NULL, *compatible_ids = NULL;
 	CONFIGRET cr;
 	DEVINST dev_node;
 
@@ -459,23 +606,6 @@ static void hid_internal_get_info(const wchar_t* interface_path, struct hid_devi
 	if (cr != CR_SUCCESS)
 		goto end;
 
-	/* Get the hardware ids from devnode */
-	hardware_ids = hid_internal_get_devnode_property(dev_node, &DEVPKEY_Device_HardwareIds, DEVPROP_TYPE_STRING_LIST);
-	if (!hardware_ids)
-		goto end;
-
-	/* Search for interface number in hardware ids */
-	for (id = hardware_ids; *id; id += wcslen(id) + 1) {
-		/* Normalize to upper case */
-		wchar_t* p = id;
-		for (; *p; ++p) *p = towupper(*p);
-
-		dev->interface_number = hid_internal_get_interface_number(id);
-
-		if (dev->interface_number != -1)
-			break;
-	}
-
 	/* Get devnode parent */
 	cr = CM_Get_Parent(&dev_node, dev_node, 0);
 	if (cr != CR_SUCCESS)
@@ -487,54 +617,49 @@ static void hid_internal_get_info(const wchar_t* interface_path, struct hid_devi
 		goto end;
 
 	/* Now we can parse parent's compatible IDs to find out the device bus type */
-	for (id = compatible_ids; *id; id += wcslen(id) + 1) {
+	for (wchar_t* compatible_id = compatible_ids; *compatible_id; compatible_id += wcslen(compatible_id) + 1) {
 		/* Normalize to upper case */
-		wchar_t* p = id;
-		for (; *p; ++p) *p = towupper(*p);
+		hid_internal_towupper(compatible_id);
 
 		/* USB devices
 		   https://docs.microsoft.com/windows-hardware/drivers/hid/plug-and-play-support
 		   https://docs.microsoft.com/windows-hardware/drivers/install/standard-usb-identifiers */
-		if (wcsstr(id, L"USB") != NULL) {
+		if (wcsstr(compatible_id, L"USB") != NULL) {
 			dev->bus_type = HID_API_BUS_USB;
+			hid_internal_get_usb_info(dev, dev_node);
 			break;
 		}
 
 		/* Bluetooth devices
 		   https://docs.microsoft.com/windows-hardware/drivers/bluetooth/installing-a-bluetooth-device */
-		if (wcsstr(id, L"BTHENUM") != NULL) {
+		if (wcsstr(compatible_id, L"BTHENUM") != NULL) {
 			dev->bus_type = HID_API_BUS_BLUETOOTH;
 			break;
 		}
 
 		/* Bluetooth LE devices */
-		if (wcsstr(id, L"BTHLEDEVICE") != NULL) {
-			/* HidD_GetProductString/HidD_GetManufacturerString/HidD_GetSerialNumberString is not working for BLE HID devices
-			   Request this info via dev node properties instead.
-			   https://docs.microsoft.com/answers/questions/401236/hidd-getproductstring-with-ble-hid-device.html */
-			hid_internal_get_ble_info(dev, dev_node);
-
+		if (wcsstr(compatible_id, L"BTHLEDEVICE") != NULL) {
 			dev->bus_type = HID_API_BUS_BLUETOOTH;
+			hid_internal_get_ble_info(dev, dev_node);
 			break;
 		}
 
 		/* I2C devices
 		   https://docs.microsoft.com/windows-hardware/drivers/hid/plug-and-play-support-and-power-management */
-		if (wcsstr(id, L"PNP0C50") != NULL) {
+		if (wcsstr(compatible_id, L"PNP0C50") != NULL) {
 			dev->bus_type = HID_API_BUS_I2C;
 			break;
 		}
 
 		/* SPI devices
 		   https://docs.microsoft.com/windows-hardware/drivers/hid/plug-and-play-for-spi */
-		if (wcsstr(id, L"PNP0C51") != NULL) {
+		if (wcsstr(compatible_id, L"PNP0C51") != NULL) {
 			dev->bus_type = HID_API_BUS_SPI;
 			break;
 		}
 	}
 end:
 	free(device_id);
-	free(hardware_ids);
 	free(compatible_ids);
 }
 
@@ -552,7 +677,6 @@ static char *hid_internal_UTF16toUTF8(const wchar_t *src)
 
 	return dst;
 }
-#endif /* 0 */
 
 static wchar_t *hid_internal_UTF8toUTF16(const char *src)
 {
@@ -569,299 +693,148 @@ static wchar_t *hid_internal_UTF8toUTF16(const char *src)
 	return dst;
 }
 
-static int hid_get_bluetooth_info(const char *path, struct hid_device_info* dev)
+static struct hid_device_info *hid_internal_get_device_info(const wchar_t *path, HANDLE handle)
 {
-	wchar_t *interface_path = NULL, *device_id = NULL, *compatible_ids = NULL;
-	wchar_t *id;
-	CONFIGRET cr;
-	DEVINST dev_node;
-	int is_bluetooth = 0;
+	struct hid_device_info *dev = NULL; /* return object */
+	HIDD_ATTRIBUTES attrib;
+	PHIDP_PREPARSED_DATA pp_data = NULL;
+	HIDP_CAPS caps;
+	wchar_t string[MAX_STRING_WCHARS];
 
-	/* Get the device id from interface path */
-	interface_path = hid_internal_UTF8toUTF16(path);
-	device_id = hid_internal_get_device_interface_property(interface_path, &DEVPKEY_Device_InstanceId, DEVPROP_TYPE_STRING);
-	if (!device_id)
-		goto end;
+	/* Create the record. */
+	dev = (struct hid_device_info*)calloc(1, sizeof(struct hid_device_info));
 
-	/* Open devnode from device id */
-	cr = CM_Locate_DevNodeW(&dev_node, (DEVINSTID_W)device_id, CM_LOCATE_DEVNODE_NORMAL);
-	if (cr != CR_SUCCESS)
-		goto end;
+	if (dev == NULL) {
+		return NULL;
+	}
 
-	/* Get devnode parent */
-	cr = CM_Get_Parent(&dev_node, dev_node, 0);
-	if (cr != CR_SUCCESS)
-		goto end;
+	/* Fill out the record */
+	dev->next = NULL;
+	dev->path = hid_internal_UTF16toUTF8(path);
+	dev->interface_number = -1;
 
-	/* Get the compatible ids from parent devnode */
-	compatible_ids = hid_internal_get_devnode_property(dev_node, &DEVPKEY_Device_CompatibleIds, DEVPROP_TYPE_STRING_LIST);
-	if (!compatible_ids)
-		goto end;
+	attrib.Size = sizeof(HIDD_ATTRIBUTES);
+	if (HidD_GetAttributes(handle, &attrib)) {
+		/* VID/PID */
+		dev->vendor_id = attrib.VendorID;
+		dev->product_id = attrib.ProductID;
 
-	/* Now we can parse parent's compatible IDs to find out the device bus type */
-	for (id = compatible_ids; *id; id += wcslen(id) + 1) {
-		/* Normalize to upper case */
-		wchar_t* p = id;
-		for (; *p; ++p) *p = (wchar_t)towupper(*p);
+		/* Release Number */
+		dev->release_number = attrib.VersionNumber;
+	}
 
-		/* USB devices
-		   https://docs.microsoft.com/windows-hardware/drivers/hid/plug-and-play-support
-		   https://docs.microsoft.com/windows-hardware/drivers/install/standard-usb-identifiers */
-		if (wcsstr(id, L"USB") != NULL) {
-			/*dev->bus_type = HID_API_BUS_USB;*/
-			break;
+	/* Get the Usage Page and Usage for this device. */
+	if (HidD_GetPreparsedData(handle, &pp_data)) {
+		if (HidP_GetCaps(pp_data, &caps) == HIDP_STATUS_SUCCESS) {
+			dev->usage_page = caps.UsagePage;
+			dev->usage = caps.Usage;
 		}
 
-		/* Bluetooth devices
-		   https://docs.microsoft.com/windows-hardware/drivers/bluetooth/installing-a-bluetooth-device */
-		if (wcsstr(id, L"BTHENUM") != NULL) {
-			/*dev->bus_type = HID_API_BUS_BLUETOOTH;*/
-			is_bluetooth = 1;
-			break;
-		}
+		HidD_FreePreparsedData(pp_data);
+	}
 
-		/* Bluetooth LE devices */
-		if (wcsstr(id, L"BTHLEDEVICE") != NULL) {
-			/* HidD_GetProductString/HidD_GetManufacturerString/HidD_GetSerialNumberString is not working for BLE HID devices
-			   Request this info via dev node properties instead.
-			   https://docs.microsoft.com/answers/questions/401236/hidd-getproductstring-with-ble-hid-device.html */
-			if (dev)
-				hid_internal_get_ble_info(dev, dev_node);
-
-			/*dev->bus_type = HID_API_BUS_BLUETOOTH;*/
-			is_bluetooth = 1;
-			break;
-		}
+	/* Serial Number */
+	string[0] = L'\0';
+	HidD_GetSerialNumberString(handle, string, sizeof(string));
+	string[MAX_STRING_WCHARS - 1] = L'\0';
+	dev->serial_number = _wcsdup(string);
 
-		/* I2C devices
-		   https://docs.microsoft.com/windows-hardware/drivers/hid/plug-and-play-support-and-power-management */
-		if (wcsstr(id, L"PNP0C50") != NULL) {
-			/*dev->bus_type = HID_API_BUS_I2C;*/
-			break;
-		}
+	/* Manufacturer String */
+	string[0] = L'\0';
+	HidD_GetManufacturerString(handle, string, sizeof(string));
+	string[MAX_STRING_WCHARS - 1] = L'\0';
+	dev->manufacturer_string = _wcsdup(string);
 
-		/* SPI devices
-		   https://docs.microsoft.com/windows-hardware/drivers/hid/plug-and-play-for-spi */
-		if (wcsstr(id, L"PNP0C51") != NULL) {
-			/*dev->bus_type = HID_API_BUS_SPI;*/
-			break;
-		}
-	}
-end:
-	free(interface_path);
-	free(device_id);
-	free(compatible_ids);
-	return is_bluetooth;
-}
+	/* Product String */
+	string[0] = L'\0';
+	HidD_GetProductString(handle, string, sizeof(string));
+	string[MAX_STRING_WCHARS - 1] = L'\0';
+	dev->product_string = _wcsdup(string);
 
-static int hid_blacklist(unsigned short vendor_id, unsigned short product_id)
-{
-    size_t i;
-    static const struct { unsigned short vid; unsigned short pid; } known_bad[] = {
-        /* Causes deadlock when asking for device details... */
-        { 0x1B1C, 0x1B3D },  /* Corsair Gaming keyboard */
-        { 0x1532, 0x0109 },  /* Razer Lycosa Gaming keyboard */
-        { 0x1532, 0x010B },  /* Razer Arctosa Gaming keyboard */
-        { 0x045E, 0x0822 },  /* Microsoft Precision Mouse */
-        { 0x0D8C, 0x0014 },  /* Sharkoon Skiller SGH2 headset */
-        { 0x1CCF, 0x0000 },  /* All Konami Amusement Devices */
-
-        /* Turns into an Android controller when enumerated... */
-        { 0x0738, 0x2217 }   /* SPEEDLINK COMPETITION PRO */
-    };
-
-    for (i = 0; i < (sizeof(known_bad)/sizeof(known_bad[0])); i++) {
-        if ((vendor_id == known_bad[i].vid) && (product_id == known_bad[i].pid || known_bad[i].pid == 0x0000)) {
-            return 1;
-        }
-    }
-
-    return 0;
+	hid_internal_get_info(path, dev);
+
+	return dev;
 }
 
 struct hid_device_info HID_API_EXPORT * HID_API_CALL hid_enumerate(unsigned short vendor_id, unsigned short product_id)
 {
-	BOOL res;
 	struct hid_device_info *root = NULL; /* return object */
 	struct hid_device_info *cur_dev = NULL;
+	GUID interface_class_guid;
+	CONFIGRET cr;
+	wchar_t* device_interface_list = NULL;
+	DWORD len;
 
-	/* Windows objects for interacting with the driver. */
-	GUID InterfaceClassGuid = {0x4d1e55b2, 0xf16f, 0x11cf, {0x88, 0xcb, 0x00, 0x11, 0x11, 0x00, 0x00, 0x30} };
-	SP_DEVINFO_DATA devinfo_data;
-	SP_DEVICE_INTERFACE_DATA device_interface_data;
-	SP_DEVICE_INTERFACE_DETAIL_DATA_A *device_interface_detail_data = NULL;
-	HDEVINFO device_info_set = INVALID_HANDLE_VALUE;
-	int device_index = 0;
-	const char *hint = SDL_GetHint(SDL_HINT_HIDAPI_IGNORE_DEVICES);
-
-	if (hid_init() < 0)
+	if (hid_init() < 0) {
+		/* register_global_error: global error is reset by hid_init */
 		return NULL;
+	}
 
-	/* Initialize the Windows objects. */
-	memset(&devinfo_data, 0x0, sizeof(devinfo_data));
-	devinfo_data.cbSize = sizeof(SP_DEVINFO_DATA);
-	device_interface_data.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA);
-
-	/* Get information for all the devices belonging to the HID class. */
-	device_info_set = SetupDiGetClassDevsA(&InterfaceClassGuid, NULL, NULL, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE);
-
-	/* Iterate over each device in the HID class, looking for the right one. */
-
-	for (;;) {
-		HANDLE write_handle = INVALID_HANDLE_VALUE;
-		DWORD required_size = 0;
-		HIDD_ATTRIBUTES attrib;
-
-		res = SetupDiEnumDeviceInterfaces(device_info_set,
-			NULL,
-			&InterfaceClassGuid,
-			device_index,
-			&device_interface_data);
-
-		if (!res) {
-			/* A return of FALSE from this function means that
-			   there are no more devices. */
+	/* Retrieve HID Interface Class GUID
+	   https://docs.microsoft.com/windows-hardware/drivers/install/guid-devinterface-hid */
+	HidD_GetHidGuid(&interface_class_guid);
+
+	/* Get the list of all device interfaces belonging to the HID class. */
+	/* Retry in case of list was changed between calls to
+	  CM_Get_Device_Interface_List_SizeW and CM_Get_Device_Interface_ListW */
+	do {
+		cr = CM_Get_Device_Interface_List_SizeW(&len, &interface_class_guid, NULL, CM_GET_DEVICE_INTERFACE_LIST_PRESENT);
+		if (cr != CR_SUCCESS) {
+			register_global_error(L"Failed to get size of HID device interface list");
 			break;
 		}
 
-		/* Call with 0-sized detail size, and let the function
-		   tell us how long the detail struct needs to be. The
-		   size is put in &required_size. */
-		res = SetupDiGetDeviceInterfaceDetailA(device_info_set,
-			&device_interface_data,
-			NULL,
-			0,
-			&required_size,
-			NULL);
-
-		/* Allocate a long enough structure for device_interface_detail_data. */
-		device_interface_detail_data = (SP_DEVICE_INTERFACE_DETAIL_DATA_A*) malloc(required_size);
-		device_interface_detail_data->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA_A);
-
-		/* Get the detailed data for this device. The detail data gives us
-		   the device path for this device, which is then passed into
-		   CreateFile() to get a handle to the device. */
-		res = SetupDiGetDeviceInterfaceDetailA(device_info_set,
-			&device_interface_data,
-			device_interface_detail_data,
-			required_size,
-			NULL,
-			NULL);
-
-		if (!res) {
-			/* register_error(dev, "Unable to call SetupDiGetDeviceInterfaceDetail");
-			   Continue to the next device. */
-			goto cont;
+		if (device_interface_list != NULL) {
+			free(device_interface_list);
 		}
 
-		/* XInput devices don't get real HID reports and are better handled by the raw input driver */
-		if (strstr(device_interface_detail_data->DevicePath, "&ig_") != NULL) {
-			goto cont;
+		device_interface_list = (wchar_t*)calloc(len, sizeof(wchar_t));
+		if (device_interface_list == NULL) {
+			register_global_error(L"Failed to allocate memory for HID device interface list");
+			return NULL;
 		}
-
-		/* Make sure this device is of Setup Class "HIDClass" and has a
-		   driver bound to it. */
-		/* In the main HIDAPI tree this is a loop which will erroneously open
-			devices that aren't HID class. Please preserve this delta if we ever
-			update to take new changes */
-		{
-			char driver_name[256];
-
-			/* Populate devinfo_data. This function will return failure
-			   when there are no more interfaces left. */
-			res = SetupDiEnumDeviceInfo(device_info_set, device_index, &devinfo_data);
-
-			if (!res)
-				goto cont;
-
-			res = SetupDiGetDeviceRegistryPropertyA(device_info_set, &devinfo_data,
-			               SPDRP_CLASS, NULL, (PBYTE)driver_name, sizeof(driver_name), NULL);
-			if (!res)
-				goto cont;
-
-			if (strcmp(driver_name, "HIDClass") == 0) {
-				/* See if there's a driver bound. */
-				res = SetupDiGetDeviceRegistryPropertyA(device_info_set, &devinfo_data,
-				           SPDRP_DRIVER, NULL, (PBYTE)driver_name, sizeof(driver_name), NULL);
-				if (!res)
-					goto cont;
-			}
-			else
-			{
-				goto cont;
-			}
+		cr = CM_Get_Device_Interface_ListW(&interface_class_guid, NULL, device_interface_list, len, CM_GET_DEVICE_INTERFACE_LIST_PRESENT);
+		if (cr != CR_SUCCESS && cr != CR_BUFFER_SMALL) {
+			register_global_error(L"Failed to get HID device interface list");
 		}
+	} while (cr == CR_BUFFER_SMALL);
+
+	if (cr != CR_SUCCESS) {
+		goto end_of_function;
+	}
 
-		//wprintf(L"HandleName: %s\n", device_interface_detail_data->DevicePath);
+	/* Iterate over each device interface in the HID class, looking for the right one. */
+	for (wchar_t* device_interface = device_interface_list; *device_interface; device_interface += wcslen(device_interface) + 1) {
+		HANDLE device_handle = INVALID_HANDLE_VALUE;
+		HIDD_ATTRIBUTES attrib;
 
-		/* Open a handle to the device */
-		write_handle = open_device(device_interface_detail_data->DevicePath, TRUE, FALSE);
+		/* Open read-only handle to the device */
+		device_handle = open_device(device_interface, FALSE);
 
-		/* Check validity of write_handle. */
-		if (write_handle == INVALID_HANDLE_VALUE) {
+		/* Check validity of device_handle. */
+		if (device_handle == INVALID_HANDLE_VALUE) {
 			/* Unable to open the device. */
-			//register_error(dev, "CreateFile");
-			goto cont;
+			continue;
 		}
 
-
 		/* Get the Vendor ID and Product ID for this device. */
 		attrib.Size = sizeof(HIDD_ATTRIBUTES);
-		HidD_GetAttributes(write_handle, &attrib);
-		//wprintf(L"Product/Vendor: %x %x\n", attrib.ProductID, attrib.VendorID);
-
-		/* See if there are any devices we should skip in enumeration */
-		if (hint) {
-			char vendor_match[16], product_match[16];
-			SDL_snprintf(vendor_match, sizeof(vendor_match), "0x%.4x/0x0000", attrib.VendorID);
-			SDL_snprintf(product_match, sizeof(product_match), "0x%.4x/0x%.4x", attrib.VendorID, attrib.ProductID);
-			if (SDL_strcasestr(hint, vendor_match) || SDL_strcasestr(hint, product_match)) {
-				continue;
-			}
+		if (!HidD_GetAttributes(device_handle, &attrib)) {
+			goto cont_close;
 		}
 
 		/* Check the VID/PID to see if we should add this
 		   device to the enumeration list. */
 		if ((vendor_id == 0x0 || attrib.VendorID == vendor_id) &&
-		    (product_id == 0x0 || attrib.ProductID == product_id) &&
-			!hid_blacklist(attrib.VendorID, attrib.ProductID)) {
-
-			#define WSTR_LEN 512
-			const char *str;
-			struct hid_device_info *tmp;
-			PHIDP_PREPARSED_DATA pp_data = NULL;
-			HIDP_CAPS caps;
-			BOOLEAN hidp_res;
-			NTSTATUS nt_res;
-			wchar_t wstr[WSTR_LEN]; /* TODO: Determine Size */
-			size_t len;
-
-			/* Get the Usage Page and Usage for this device. */
-			hidp_res = HidD_GetPreparsedData(write_handle, &pp_data);
-			if (hidp_res) {
-				nt_res = HidP_GetCaps(pp_data, &caps);
-				HidD_FreePreparsedData(pp_data);
-				if (nt_res != HIDP_STATUS_SUCCESS) {
-					goto cont_close;
-				}
-			}
-			else {
-				goto cont_close;
-			}
+		    (product_id == 0x0 || attrib.ProductID == product_id)) {
 
-			/* SDL Modification: Ignore the device if it's not a gamepad. This limits compatibility
-			   risk from devices that may respond poorly to our string queries below. */
-			if (attrib.VendorID != USB_VENDOR_VALVE) {
-				if (caps.UsagePage != USAGE_PAGE_GENERIC_DESKTOP) {
-					goto cont_close;
-				}
-				if (caps.Usage != USAGE_JOYSTICK && caps.Usage != USAGE_GAMEPAD && caps.Usage != USAGE_MULTIAXISCONTROLLER) {
-					goto cont_close;
-				}
+			/* VID/PID match. Create the record. */
+			struct hid_device_info *tmp = hid_internal_get_device_info(device_interface, device_handle);
+
+			if (tmp == NULL) {
+				goto cont_close;
 			}
 
-			/* VID/PID match. Create the record. */
-			tmp = (struct hid_device_info*) calloc(1, sizeof(struct hid_device_info));
 			if (cur_dev) {
 				cur_dev->next = tmp;
 			}
@@ -869,86 +842,24 @@ struct hid_device_info HID_API_EXPORT * HID_API_CALL hid_enumerate(unsigned shor
 				root = tmp;
 			}
 			cur_dev = tmp;
-
-			/* Fill out the record */
-			cur_dev->usage_page = caps.UsagePage;
-			cur_dev->usage = caps.Usage;
-			cur_dev->next = NULL;
-			str = device_interface_detail_data->DevicePath;
-			if (str) {
-				len = strlen(str);
-				cur_dev->path = (char*) calloc(len+1, sizeof(char));
-				memcpy(cur_dev->path, str, len+1);
-			}
-			else
-				cur_dev->path = NULL;
-
-			/* Serial Number */
-			hidp_res = HidD_GetSerialNumberString(write_handle, wstr, sizeof(wstr));
-			wstr[WSTR_LEN-1] = 0x0000;
-			if (hidp_res) {
-				cur_dev->serial_number = _wcsdup(wstr);
-			}
-
-			/* Manufacturer String */
-			hidp_res = HidD_GetManufacturerString(write_handle, wstr, sizeof(wstr));
-			wstr[WSTR_LEN-1] = 0x0000;
-			if (hidp_res) {
-				cur_dev->manufacturer_string = _wcsdup(wstr);
-			}
-
-			/* Product String */
-			hidp_res = HidD_GetProductString(write_handle, wstr, sizeof(wstr));
-			wstr[WSTR_LEN-1] = 0x0000;
-			if (hidp_res) {
-				cur_dev->product_string = _wcsdup(wstr);
-			}
-
-			/* VID/PID */
-			cur_dev->vendor_id = attrib.VendorID;
-			cur_dev->product_id = attrib.ProductID;
-
-			/* Release Number */
-			cur_dev->release_number = attrib.VersionNumber;
-
-			/* Interface Number. It can sometimes be parsed out of the path
-			   on Windows if a device has multiple interfaces. See
-			   http://msdn.microsoft.com/en-us/windows/hardware/gg487473 or
-			   search for "Hardware IDs for HID Devices" at MSDN. If it's not
-			   in the path, it's set to -1. */
-			cur_dev->interface_number = -1;
-			if (cur_dev->path) {
-				char *interface_component = strstr(cur_dev->path, "&mi_");
-				if (interface_component) {
-					char *hex_str = interface_component + 4;
-					char *endptr = NULL;
-					cur_dev->interface_number = strtol(hex_str, &endptr, 16);
-					if (endptr == hex_str) {
-						/* The parsing failed. Set interface_number to -1. */
-						cur_dev->interface_number = -1;
-					}
-				}
-			}
-
-			/* Get the Bluetooth device info */
-			hid_get_bluetooth_info(cur_dev->path, cur_dev);
 		}
 
 cont_close:
-		CloseHandle(write_handle);
-cont:
-		/* We no longer need the detail data. It can be freed */
-		free(device_interface_detail_data);
-
-		device_index++;
+		CloseHandle(device_handle);
+	}
 
+	if (root == NULL) {
+		if (vendor_id == 0 && product_id == 0) {
+			register_global_error(L"No HID devices found in the system.");
+		} else {
+			register_global_error(L"No HID devices with requested VID/PID found in the system.");
+		}
 	}
 
-	/* Close the device information handle. */
-	SetupDiDestroyDeviceInfoList(device_info_set);
+end_of_function:
+	free(device_interface_list);
 
 	return root;
-
 }
 
 void  HID_API_EXPORT HID_API_CALL hid_free_enumeration(struct hid_device_info *devs)
@@ -966,7 +877,6 @@ void  HID_API_EXPORT HID_API_CALL hid_free_enumeration(struct hid_device_info *d
 	}
 }
 
-
 HID_API_EXPORT hid_device * HID_API_CALL hid_open(unsigned short vendor_id, unsigned short product_id, const wchar_t *serial_number)
 {
 	/* TODO: Merge this functions with the Linux version. This function should be platform independent. */
@@ -974,13 +884,19 @@ HID_API_EXPORT hid_device * HID_API_CALL hid_open(unsigned short vendor_id, unsi
 	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) {
+		/* 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) {
+				if (cur_dev->serial_number && wcscmp(serial_number, cur_dev->serial_number) == 0) {
 					path_to_open = cur_dev->path;
 					break;
 				}
@@ -995,7 +911,9 @@ HID_API_EXPORT hid_device * HID_API_CALL hid_open(unsigned short vendor_id, unsi
 
 	if (path_to_open) {
 		/* Open the device */
-		handle = hid_open_path(path_to_open, 0);
+		handle = hid_open_path(path_to_open);
+	} else {
+		register_global_error(L"Device with requested VID/PID/(SerialNumber) not found");
 	}
 
 	hid_free_enumeration(devs);
@@ -1003,151 +921,173 @@ HID_API_EXPORT hid_device * HID_API_CALL hid_open(unsigned short vendor_id, unsi
 	return handle;
 }
 
-HID_API_EXPORT hid_device * HID_API_CALL hid_open_path(const char *path, int bExclusive)
+HID_API_EXPORT hid_device * HID_API_CALL hid_open_path(const char *path)
 {
-	hid_device *dev;
-	HIDP_CAPS caps;
+	hid_device *dev = NULL;
+	wchar_t* interface_path = NULL;
+	HANDLE device_handle = INVALID_HANDLE_VALUE;
 	PHIDP_PREPARSED_DATA pp_data = NULL;
-	BOOLEAN res;
-	NTSTATUS nt_res;
+	HIDP_CAPS caps;
 
 	if (hid_init() < 0) {
-		return NULL;
+		/* register_global_error: global error is reset by hid_init */
+		goto end_of_function;
 	}
 
-	dev = new_hid_device();
+	interface_path = hid_internal_UTF8toUTF16(path);
+	if (!interface_path) {
+		register_global_error(L"Path conversion failure");
+		goto end_of_function;
+	}
 
 	/* Open a handle to the device */
-	dev->device_handle = open_device(path, FALSE, bExclusive);
+	device_handle = open_device(interface_path, TRUE);
 
 	/* Check validity of write_handle. */
-	if (dev->device_handle == INVALID_HANDLE_VALUE) {
-		/* Unable to open the device. */
-		register_error(dev, "CreateFile");
-		goto err;
+	if (device_handle == INVALID_HANDLE_VALUE) {
+		/* System devices, such as keyboards and mice, cannot be opened in
+		   read-write mode, because the system takes exclusive control over
+		   them.  This is to prevent keyloggers.  However, feature reports
+		   can still be sent and received.  Retry opening the device, but
+		   without read/write access. */
+		device_handle = open_device(interface_path, FALSE);
+
+		/* Check the validity of the limited device_handle. */
+		if (device_handle == INVALID_HANDLE_VALUE) {
+			register_global_winapi_error(L"open_device");
+			goto end_of_function;
+		}
 	}
 
 	/* Set the Input Report buffer size to 64 reports. */
-	res = HidD_SetNumInputBuffers(dev->device_handle, 64);
-	if (!res) {
-		register_error(dev, "HidD_SetNumInputBuffers");
-		goto err;
+	if (!HidD_SetNumInputBuffers(device_handle, 64)) {
+		register_global_winapi_error(L"set input buffers");
+		goto end_of_function;
 	}
 
 	/* Get the Input Report length for the device. */
-	res = HidD_GetPreparsedData(dev->device_handle, &pp_data);
-	if (!res) {
-		register_error(dev, "HidD_GetPreparsedData");
-		goto err;
+	if (!HidD_GetPreparsedData(device_handle, &pp_data)) {
+		register_global_winapi_error(L"get preparsed data");
+		goto end_of_function;
 	}
-	nt_res = HidP_GetCaps(pp_data, &caps);
-	if (nt_res != HIDP_STATUS_SUCCESS) {
-		register_error(dev, "HidP_GetCaps");
-		goto err_pp_data;
+
+	if (HidP_GetCaps(pp_data, &caps) != HIDP_STATUS_SUCCESS) {
+		register_global_error(L"HidP_GetCaps");
+		goto end_of_function;
 	}
-	dev->output_report_length = caps.OutputReportByteLength;
-	dev->input_report_length = caps.InputReportByteLength;
-	HidD_FreePreparsedData(pp_data);
 
-	/* On Windows 7, we need to use hid_write_output_report() over Bluetooth */
-	if (dev->output_report_length > 512) {
-		dev->use_hid_write_output_report = !IsWindowsVersionOrGreater( HIBYTE( _WIN32_WINNT_WIN8 ), LOBYTE( _WIN32_WINNT_WIN8 ), 0 );
+	dev = new_hid_device();
+
+	if (dev == NULL) {
+		register_global_error(L"hid_device allocation error");
+		goto end_of_function;
 	}
 
+	dev->device_handle = device_handle;
+	device_handle = INVALID_HANDLE_VALUE;
+
+	dev->output_report_length = caps.OutputReportByteLength;
+	dev->input_report_length = caps.InputReportByteLength;
+	dev->feature_report_length = caps.FeatureReportByteLength;
 	dev->read_buf = (char*) malloc(dev->input_report_length);
+	dev->device_info = hid_internal_get_device_info(interface_path, dev->device_handle);
 
-	return dev;
+end_of_function:
+	free(interface_path);
+	CloseHandle(device_handle);
 
-err_pp_data:
+	if (pp_data) {
 		HidD_FreePreparsedData(pp_data);
-err:
-		free_hid_device(dev);
-		return NULL;
-}
+	}
 
-int HID_API_EXPORT HID_API_CALL hid_write_output_report(hid_device *dev, const unsigned char *data, size_t length)
-{
-	BOOL res;
-	res = HidD_SetOutputReport(dev->device_handle, (void *)data, (ULONG)length);
-	if (res)
-		return (int)length;
-	else
-		return -1;
+	return dev;
 }
 
-static int hid_write_timeout(hid_device *dev, const unsigned char *data, size_t length, int milliseconds)
+int HID_API_EXPORT HID_API_CALL hid_write(hid_device *dev, const unsigned char *data, size_t length)
 {
-	DWORD bytes_written;
+	DWORD bytes_written = 0;
+	int function_result = -1;
 	BOOL res;
+	BOOL overlapped = FALSE;
+
 	unsigned char *buf;
 
-	if (dev->use_hid_write_output_report) {
-		return hid_write_output_report(dev, data, length);
+	if (!data || !length) {
+		register_string_error(dev, L"Zero buffer/length");
+		return function_result;
 	}
 
+	register_string_error(dev, NULL);
+
 	/* Make sure the right number of bytes are passed to WriteFile. Windows
 	   expects the number of bytes which are in the _longest_ report (plus
 	   one for the report number) bytes even if the data is a report
 	   which is shorter than that. Windows gives us this value in
 	   caps.OutputReportByteLength. If a user passes in fewer bytes than this,
-	   create a temporary buffer which is the proper size. */
+	   use cached temporary buffer which is the proper size. */
 	if (length >= dev->output_report_length) {
 		/* The user passed the right number of bytes. Use the buffer as-is. */
 		buf = (unsigned char *) data;
 	} else {
-		/* Create a temporary buffer and copy the user's data
-		   into it, padding the rest with zeros. */
-		buf = (unsigned char *) malloc(dev->output_report_length);
+		if (dev->write_buf == NULL)
+			dev->write_buf = (unsigned char *) malloc(dev->output_report_length);
+		buf = dev->write_buf;
 		memcpy(buf, data, length);
 		memset(buf + length, 0, dev->output_report_length - length);
 		length = dev->output_report_length;
 	}
 
-	res = WriteFile( dev->device_handle, buf, ( DWORD ) length, NULL, &dev->write_ol );
+	res = WriteFile(dev->device_handle, buf, (DWORD) length, NULL, &dev->write_ol);
+
 	if (!res) {
 		if (GetLastError() != ERROR_IO_PENDING) {
 			/* WriteFile() failed. Return error. */
-			register_error(dev, "WriteFile");
-			bytes_written = (DWORD) -1;
+			register_winapi_error(dev, L"WriteFile");
 			goto end_of_function;
 		}
+		overlapped = TRUE;
 	}
 
-	/* Wait here until the write is done. This makes hid_write() synchronous. */
-	res = WaitForSingleObject(dev->write_ol.hEvent, milliseconds);
-	if (res != WAIT_OBJECT_0)
-	{
-		// There was a Timeout.
-		bytes_written = (DWORD) -1;
-		register_error(dev, "WriteFile/WaitForSingleObject Timeout");
-		goto end_of_function;
-	}
+	if (overlapped) {
+		/* Wait for the transaction to complete. This makes
+		   hid_write() synchronous. */
+		res = WaitForSingleObject(dev->write_ol.hEvent, 1000);
+		if (res != WAIT_OBJECT_0) {
+			/* There was a Timeout. */
+			register_winapi_error(dev, L"hid_write/WaitForSingleObject");
+			goto end_of_function;
+		}
 
-	res = GetOverlappedResult(dev->device_handle, &dev->write_ol, &bytes_written, FALSE/*F=don't_wait*/);
-	if (!res) {
-		/* The Write operation failed. */
-		register_error(dev, "WriteFile");
-		bytes_written = (DWORD) -1;
-		goto end_of_function;
+		/* Get the result. */
+		res = GetOverlappedResult(dev->device_handle, &dev->write_ol, &bytes_written, FALSE/*wait*/);
+		if (res) {
+			function_result = bytes_written;
+		}
+		else {
+			/* The Write operation failed. */
+			register_winapi_error(dev, L"hid_write/GetOverlappedResult");
+			goto end_of_function;
+		}
 	}
 
 end_of_function:
-	if (buf != data)
-		free(buf);
-
-	return bytes_written;
+	return function_result;
 }
 
-int HID_API_EXPORT HID_API_CALL hid_write(hid_device *dev, const unsigned char *data, size_t length)
-{
-	return hid_write_timeout(dev, data, length, HID_WRITE_TIMEOUT_MILLISECONDS);
-}
 
 int HID_API_EXPORT HID_API_CALL hid_read_timeout(hid_device *dev, unsigned char *data, size_t length, int milliseconds)
 {
 	DWORD bytes_read = 0;
 	size_t copy_len = 0;
-	BOOL res;
+	BOOL res = FALSE;
+	BOOL overlapped = FALSE;
+
+	if (!data || !length) {
+		register_string_error(dev, L"Zero buffer/length");
+		return -1;
+	}
+
+	register_string_error(dev, NULL);
 
 	/* Copy the handle for convenience. */
 	HANDLE ev = dev->ol.hEvent;
@@ -1157,33 +1097,40 @@ int HID_API_EXPORT HID_API_CALL hid_read_timeout(hid_device *dev, unsigned char
 		dev->read_pending = TRUE;
 		memset(dev->read_buf, 0, dev->input_report_length);
 		ResetEvent(ev);
-		res = ReadFile(dev->device_handle, dev->read_buf, (DWORD)dev->input_report_length, &bytes_read, &dev->ol);
+		res = ReadFile(dev->device_handle, dev->read_buf, (DWORD) dev->input_report_length, &bytes_read, &dev->ol);
 
 		if (!res) {
 			if (GetLastError() != ERROR_IO_PENDING) {
 				/* ReadFile() has failed.
 				   Clean up and return error. */
+				register_winapi_error(dev, L"ReadFile");
 				CancelIo(dev->device_handle);
 				dev->read_pending = FALSE;
 				goto end_of_function;
 			}
+			overlapped = TRUE;
 		}
 	}
-
-	/* See if there is any data yet. */
-	res = WaitForSingleObject(ev, milliseconds >= 0 ? milliseconds : INFINITE);
-	if (res != WAIT_OBJECT_0) {
-		/* There was no data this time. Return zero bytes available,
-			but leave the Overlapped I/O running. */
-		return 0;
+	else {
+		overlapped = TRUE;
 	}
 
-	/* Get the number of bytes read. The actual data has been copied to the data[]
-	   array which was passed to ReadFile(). We must not wait here because we've
-	   already waited on our event above, and since it's auto-reset, it will have
-	   been reset back to unsignalled by now. */
-	res = GetOverlappedResult(dev->device_handle, &dev->ol, &bytes_read, FALSE/*don't wait*/);
+	if (overlapped) {
+		if (milliseconds >= 0) {
+			/* See if there is any data yet. */
+			res = WaitForSingleObject(ev, milliseconds);
+			if (res != WAIT_OBJECT_0) {
+				/* There was no data this time. Return zero bytes available,
+				   but leave the Overlapped I/O running. */
+				return 0;
+			}
+		}
 
+		/* Either WaitForSingleObject() told us that ReadFile has completed, or
+		   we are in non-blocking mode. Get the number of bytes read. The actual
+		   data has been copied to the data[] array which was passed to ReadFile(). */
+		res = GetOverlappedResult(dev->device_handle, &dev->ol, &bytes_read, TRUE/*wait*/);
+	}
 	/* Set pending back to false, even if GetOverlappedResult() returned error. */
 	dev->read_pending = FALSE;
 
@@ -1203,14 +1150,16 @@ int HID_API_EXPORT HID_API_CALL hid_read_timeout(hid_device *dev, unsigned char
 			memcpy(data, dev->read_buf, copy_len);
 		}
 	}
+	if (!res) {
+		register_winapi_error(dev, L"hid_read_timeout/GetOverlappedResult");
+	}
 
 end_of_function:
 	if (!res) {
-		register_error(dev, "GetOverlappedResult");
 		return -1;
 	}
 
-	return (int)copy_len;
+	return (int) copy_len;
 }
 
 int HID_API_EXPORT HID_API_CALL hid_read(hid_device *dev, unsigned char *data, size_t length)
@@ -1226,42 +1175,69 @@ int HID_API_EXPORT HID_API_CALL hid_set_nonblocking(hid_device *dev, int nonbloc
 
 int HID_API_EXPORT HID_API_CALL hid_send_feature_report(hid_device *dev, const unsigned char *data, size_t length)
 {
-	BOOL res = HidD_SetFeature(dev->device_handle, (PVOID)data, (ULONG)length);
-	if (!res) {
-		register_error(dev, "HidD_SetFeature");
+	BOOL res = FALSE;
+	unsigned char *buf;
+	size_t length_to_send;
+
+	if (!data || !length) {
+		register_string_error(dev, L"Zero buffer/length");
 		return -1;
 	}
 
-	return (int)length;
-}
+	register_string_error(dev, NULL);
+
+	/* Windows expects at least caps.FeatureReportByteLength bytes passed
+	   to HidD_SetFeature(), even if the report is shorter. Any less sent and
+	   the function fails with error ERROR_INVALID_PARAMETER set. Any more
+	   and HidD_SetFeature() silently truncates the data sent in the report
+	   to caps.FeatureReportByteLength. */
+	if (length >= dev->feature_report_length) {
+		buf = (unsigned char *) data;
+		length_to_send = length;
+	} else {
+		if (dev->feature_buf == NULL)
+			dev->feature_buf = (unsigned char *) malloc(dev->feature_report_length);
+		buf = dev->feature_buf;
+		memcpy(buf, data, length);
+		memset(buf + length, 0, dev->feature_report_length - length);
+		length_to_send = dev->feature_report_length;
+	}
 
+	res = HidD_SetFeature(dev->device_handle, (PVOID)buf, (DWORD) length_to_send);
 
-int HID_API_EXPORT HID_API_CALL hid_get_feature_report(hid_device *dev, unsigned char *data, size_t length)
-{
-	BOOL res;
-#if 0
-	res = HidD_GetFeature(dev->device_handle, (PVOID)data, (ULONG)length);
 	if (!res) {
-		register_error(dev, "HidD_GetFeature");
+		register_winapi_error(dev, L"HidD_SetFeature");
 		return -1;
 	}
-	return 0; /* HidD_GetFeature() doesn't give us an actual length, unfortunately */
-#else
-	DWORD bytes_returned;
+
+	return (int) length;
+}
+
+static int hid_get_report(hid_device *dev, DWORD report_type, unsigned char *data, size_t length)
+{
+	BOOL res;
+	DWORD bytes_returned = 0;
 
 	OVERLAPPED ol;
 	memset(&ol, 0, sizeof(ol));
 
+	if (!data || !length) {
+		register_string_error(dev, L"Zero buffer/length");
+		return -1;
+	}
+
+	register_string_error(dev, NULL);
+
 	res = DeviceIoControl(dev->device_handle,
-		IOCTL_HID_GET_FEATURE,
-		data, (DWORD)length,
-		data, (DWORD)length,
+		report_type,
+		data, (DWORD) length,
+		data, (DWORD) length,
 		&bytes_returned, &ol);
 
 	if (!res) {
 		if (GetLastError() != ERROR_IO_PENDING) {
 			/* DeviceIoControl() failed. Return error. */
-			register_error(dev, "Send Feature Report DeviceIoControl");
+			register_winapi_error(dev, L"Get Input/Feature Report DeviceIoControl");
 			return -1;
 		}
 	}
@@ -1271,157 +1247,209 @@ int HID_API_EXPORT HID_API_CALL hid_get_feature_report(hid_device *dev, unsigned
 	res = GetOverlappedResult(dev->device_handle, &ol, &bytes_returned, TRUE/*wait*/);
 	if (!res) {
 		/* The operation failed. */
-		register_error(dev, "Send Feature Report GetOverLappedResult");
+		register_winapi_error(dev, L"Get Input/Feature Report GetOverLappedResult");
 		return -1;
 	}
 
+	/* When numbered reports aren't used,
+	   bytes_returned seem to include only what is actually received from the device
+	   (not including the first byte with 0, as an indication "no numbered reports"). */
+	if (data[0] == 0x0) {
+		bytes_returned++;
+	}
+
 	return bytes_returned;
-#endif
 }
 
-void HID_API_EXPORT HID_API_CALL hid_close(hid_device *dev)
+int HID_API_EXPORT HID_API_CALL hid_get_feature_report(hid_device *dev, unsigned char *data, size_t length)
+{
+	/* We could use HidD_GetFeature() instead, but it doesn't give us an actual length, unfortunately */
+	return hid_get_report(dev, IOCTL_HID_GET_FEATURE, data, length);
+}
+
+int HID_API_EXPORT HID_API_CALL hid_get_input_report(hid_device *dev, unsigned char *data, size_t length)
 {
-	typedef BOOL (WINAPI *CancelIoEx_t)(HANDLE hFile, LPOVERLAPPED lpOverlapped);
-	CancelIoEx_t CancelIoExFunc = (CancelIoEx_t)GetProcAddress(GetModuleHandle(TEXT("kernel32.dll")), "CancelIoEx");
+	/* We could use HidD_GetInputReport() instead, but it doesn't give us an actual length, unfortunately */
+	return hid_get_report(dev, IOCTL_HID_GET_INPUT_REPORT, data, length);
+}
 
+void HID_API_EXPORT HID_API_CALL hid_close(hid_device *dev)
+{
 	if (!dev)
 		return;
 
-	if (CancelIoExFunc) {
-		CancelIoExFunc(dev->device_handle, NULL);
-	} else {
-		/* Windows XP, this will only cancel I/O on the current thread */
-		CancelIo(dev->device_handle);
-	}
-	if (dev->read_pending) {
-		DWORD bytes_read = 0;
-
-		GetOverlappedResult(dev->device_handle, &dev->ol, &bytes_read, TRUE/*wait*/);
-	}
+	CancelIo(dev->device_handle);
 	free_hid_device(dev);
 }
 
 int HID_API_EXPORT_CALL HID_API_CALL hid_get_manufacturer_string(hid_device *dev, wchar_t *string, size_t maxlen)
 {
-	BOOL res;
+	if (!string || !maxlen) {
+		register_string_error(dev, L"Zero buffer/length");
+		return -1;
+	}
 
-	res = HidD_GetManufacturerString(dev->device_handle, string, (ULONG)(sizeof(wchar_t) * MIN(maxlen, MAX_STRING_WCHARS)));
-	if (!res) {
-		register_error(dev, "HidD_GetManufacturerString");
+	if (!dev->device_info) {
+		register_string_error(dev, L"NULL device info");
 		return -1;
 	}
 
+	wcsncpy(string, dev->device_info->manufacturer_string, maxlen);
+	string[maxlen - 1] = L'\0';
+
+	register_string_error(dev, NULL);
+
 	return 0;
 }
 
 int HID_API_EXPORT_CALL HID_API_CALL hid_get_product_string(hid_device *dev, wchar_t *string, size_t maxlen)
 {
-	BOOL res;
+	if (!string || !maxlen) {
+		register_string_error(dev, L"Zero buffer/length");
+		return -1;
+	}
 
-	res = HidD_GetProductString(dev->device_handle, string, (ULONG)(sizeof(wchar_t) * MIN(maxlen, MAX_STRING_WCHARS)));
-	if (!res) {
-		register_error(dev, "HidD_GetProductString");
+	if (!dev->device_info) {
+		register_string_error(dev, L"NULL device info");
 		return -1;
 	}
 
+	wcsncpy(string, dev->device_info->product_string, maxlen);
+	string[maxlen - 1] = L'\0';
+
+	register_string_error(dev, NULL);
+
 	return 0;
 }
 
 int HID_API_EXPORT_CALL HID_API_CALL hid_get_serial_number_string(hid_device *dev, wchar_t *string, size_t maxlen)
 {
-	BOOL res;
+	if (!string || !maxlen) {
+		register_string_error(dev, L"Zero buffer/length");
+		return -1;
+	}
 
-	res = HidD_GetSerialNumberString(dev->device_handle, string, (ULONG)(sizeof(wchar_t) * MIN(maxlen, MAX_STRING_WCHARS)));
-	if (!res) {
-		register_error(dev, "HidD_GetSerialNumberString");
+	if (!dev->device_info) {
+		register_string_error(dev, L"NULL device info");
 		return -1;
 	}
 
+	wcsncpy(string, dev->device_info->serial_number, maxlen);
+	string[maxlen - 1] = L'\0';
+
+	register_string_error(dev, NULL);
+
 	return 0;
 }
 
+HID_API_EXPORT struct hid_device_info * HID_API_CALL hid_get_device_info(hid_device *dev) {
+	if (!dev->device_info)
+	{
+		register_string_error(dev, L"NULL device info");
+		return NULL;
+	}
+
+	return dev->device_info;
+}
+
 int HID_API_EXPORT_CALL HID_API_CALL hid_get_indexed_string(hid_device *dev, int string_index, wchar_t *string, size_t maxlen)
 {
 	BOOL res;
 
-	res = HidD_GetIndexedString(dev->device_handle, string_index, string, (ULONG)(sizeof(wchar_t) * MIN(maxlen, MAX_STRING_WCHARS)));
+	res = HidD_GetIndexedString(dev->device_handle, string_index, string, sizeof(wchar_t) * (DWORD) MIN(maxlen, MAX_STRING_WCHARS));
 	if (!res) {
-		register_error(dev, "HidD_GetIndexedString");
+		register_winapi_error(dev, L"HidD_GetIndexedString");
 		return -1;
 	}
 
+	register_string_error(dev, NULL);
+
 	return 0;
 }
 
-HID_API_EXPORT const wchar_t * HID_API_CALL  hid_error(hid_device *dev)
+int HID_API_EXPORT_CALL hid_winapi_get_container_id(hid_device *dev, GUID *container_id)
 {
-	return (wchar_t*)dev->last_error_str;
-}
+	wchar_t *interface_path = NULL, *device_id = NULL;
+	CONFIGRET cr = CR_FAILURE;
+	DEVINST dev_node;
+	DEVPROPTYPE property_type;
+	ULONG len;
 
+	if (!container_id) {
+		register_string_error(dev, L"Invalid Container ID");
+		return -1;
+	}
 
-#if 0
+	register_string_error(dev, NULL);
 
-/*#define PICPGM*/
-/*#define S11*/
-#define P32
-#ifdef S11
-	unsigned short VendorID = 0xa0a0;
-	unsigned short ProductID = 0x0001;
-#endif
+	interface_path = hid_internal_UTF8toUTF16(dev->device_info->path);
+	if (!interface_path) {
+		register_string_error(dev, L"Path conversion failure");
+		goto end;
+	}
 
-#ifdef P32
-	unsigned short VendorID = 0x04d8;
-	unsigned short ProductID = 0x3f;
-#endif
+	/* Get the device id from interface path */
+	device_id = hid_internal_get_device_interface_property(interface_path, &DEVPKEY_Device_InstanceId, DEVPROP_TYPE_STRING);
+	if (!device_id) {
+		register_string_error(dev, L"Failed to get device interface property InstanceId");
+		goto end;
+	}
 
-#ifdef PICPGM
-	unsigned short VendorID = 0x04d8;
-	unsigned short ProductID = 0x0033;
-#endif
+	/* Open devnode from device id */
+	cr = CM_Locate_DevNodeW(&dev_node, (DEVINSTID_W)device_id, CM_LOCATE_DEVNODE_NORMAL);
+	if (cr != CR_SUCCESS) {
+		register_string_error(dev, L"Failed to locate device node");
+		goto end;
+	}
 
-int __cdecl main(int argc, char* argv[])
-{
-	int i, res;
-	unsigned char buf[65];
+	/* Get the container id from devnode */
+	len = sizeof(*container_id);
+	cr = CM_Get_DevNode_PropertyW(dev_node, &DEVPKEY_Device_ContainerId, &property_type, (PBYTE)container_id, &len, 0);
+	if (cr == CR_SUCCESS && property_type != DEVPROP_TYPE_GUID)
+		cr = CR_FAILURE;
 
-	UNREFERENCED_PARAMETER(argc);
-	UNREFERENCED_PARAMETER(argv);
+	if (cr != CR_SUCCESS)
+		register_string_error(dev, L"Failed to read ContainerId property from device node");
+
+end:
+	free(interface_path);
+	free(device_id);
 
-	/* Set up the command buffer. */
-	memset(buf,0x00,sizeof(buf));
-	buf[0] = 0;
-	buf[1] = 0x81;
+	return cr == CR_SUCCESS ? 0 : -1;
+}
 
 
-	/* Open the device. */
-	int handle = open(VendorID, ProductID, L"12345");
-	if (handle < 0)
-		printf("unable to open device\n");
+int HID_API_EXPORT_CALL hid_get_report_descriptor(hid_device *dev, unsigned char *buf, size_t buf_size)
+{
+	PHIDP_PREPARSED_DATA pp_data = NULL;
 
+	if (!HidD_GetPreparsedData(dev->device_handle, &pp_data) || pp_data == NULL) {
+		register_string_error(dev, L"HidD_GetPreparsedData");
+		return -1;
+	}
 
-	/* Toggle LED (cmd 0x80) */
-	buf[1] = 0x80;
-	res = write(handle, buf, 65);
-	if (res < 0)
-		printf("Unable to write()\n");
+	int res = hid_winapi_descriptor_reconstruct_pp_data(pp_data, buf, buf_size);
 
-	/* Request state (cmd 0x81) */
-	buf[1] = 0x81;
-	write(handle, buf, 65);
-	if (res < 0)
-		printf("Unable to write() (2)\n");
+	HidD_FreePreparsedData(pp_data);
 
-	/* Read requested state */
-	read(handle, buf, 65);
-	if (res < 0)
-		printf("Unable to read()\n");
+	return res;
+}
 
-	/* Print out the returned buffer. */
-	for (i = 0; i < 4; i++)
-		printf("buf[%d]: %d\n", i, buf[i]);
+HID_API_EXPORT const wchar_t * HID_API_CALL  hid_error(hid_device *dev)
+{
+	if (dev) {
+		if (dev->last_error_str == NULL)
+			return L"Success";
+		return (wchar_t*)dev->last_error_str;
+	}
 
-	return 0;
+	if (last_global_error_str == NULL)
+		return L"Success";
+	return last_global_error_str;
 }
+
+#ifndef hidapi_winapi_EXPORTS
+#include "hidapi_descriptor_reconstruct.c"
 #endif
 
 #ifdef __cplusplus

+ 35 - 0
src/hidapi/windows/hidapi.rc

@@ -0,0 +1,35 @@
+#include "winresrc.h"
+
+#include "hidapi.h"
+
+// English
+LANGUAGE LANG_ENGLISH, SUBLANG_DEFAULT
+VS_VERSION_INFO VERSIONINFO
+ FILEVERSION HID_API_VERSION_MAJOR,HID_API_VERSION_MINOR,HID_API_VERSION_PATCH,0
+ PRODUCTVERSION HID_API_VERSION_MAJOR,HID_API_VERSION_MINOR,HID_API_VERSION_PATCH,0
+ FILEFLAGSMASK VS_FFI_FILEFLAGSMASK
+ FILEFLAGS 0
+#ifdef _DEBUG
+  | VS_FF_DEBUG
+#endif
+ FILEOS VOS_NT_WINDOWS32
+ FILETYPE VFT_DLL
+BEGIN
+	BLOCK "StringFileInfo"
+	BEGIN
+		BLOCK "04090000"
+		BEGIN
+			VALUE "CompanyName", "libusb/hidapi Team"
+			VALUE "FileDescription", "A multi-platform library to interface with HID devices (USB, Bluetooth, etc.)"
+			VALUE "FileVersion", HID_API_VERSION_STR
+			VALUE "ProductName", "HIDAPI"
+			VALUE "ProductVersion", HID_API_VERSION_STR
+			VALUE "Licence", "https://github.com/libusb/hidapi/blob/master/LICENSE.txt"
+			VALUE "Comments", "https://github.com/libusb/hidapi"
+		END
+	END
+	BLOCK "VarFileInfo"
+	BEGIN
+		VALUE "Translation", 0x409, 0
+	END
+END

+ 41 - 29
src/hidapi/windows/hidapi.sln

@@ -1,29 +1,41 @@
-
-Microsoft Visual Studio Solution File, Format Version 10.00
-# Visual C++ Express 2008
-Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "hidapi", "hidapi.vcproj", "{A107C21C-418A-4697-BB10-20C3AA60E2E4}"
-EndProject
-Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "hidtest", "hidtest.vcproj", "{23E9FF6A-49D1-4993-B2B5-BBB992C6C712}"
-	ProjectSection(ProjectDependencies) = postProject
-		{A107C21C-418A-4697-BB10-20C3AA60E2E4} = {A107C21C-418A-4697-BB10-20C3AA60E2E4}
-	EndProjectSection
-EndProject
-Global
-	GlobalSection(SolutionConfigurationPlatforms) = preSolution
-		Debug|Win32 = Debug|Win32
-		Release|Win32 = Release|Win32
-	EndGlobalSection
-	GlobalSection(ProjectConfigurationPlatforms) = postSolution
-		{A107C21C-418A-4697-BB10-20C3AA60E2E4}.Debug|Win32.ActiveCfg = Debug|Win32
-		{A107C21C-418A-4697-BB10-20C3AA60E2E4}.Debug|Win32.Build.0 = Debug|Win32
-		{A107C21C-418A-4697-BB10-20C3AA60E2E4}.Release|Win32.ActiveCfg = Release|Win32
-		{A107C21C-418A-4697-BB10-20C3AA60E2E4}.Release|Win32.Build.0 = Release|Win32
-		{23E9FF6A-49D1-4993-B2B5-BBB992C6C712}.Debug|Win32.ActiveCfg = Debug|Win32
-		{23E9FF6A-49D1-4993-B2B5-BBB992C6C712}.Debug|Win32.Build.0 = Debug|Win32
-		{23E9FF6A-49D1-4993-B2B5-BBB992C6C712}.Release|Win32.ActiveCfg = Release|Win32
-		{23E9FF6A-49D1-4993-B2B5-BBB992C6C712}.Release|Win32.Build.0 = Release|Win32
-	EndGlobalSection
-	GlobalSection(SolutionProperties) = preSolution
-		HideSolutionNode = FALSE
-	EndGlobalSection
-EndGlobal
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio 15
+VisualStudioVersion = 15.0.28307.136
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "hidapi", "hidapi.vcxproj", "{A107C21C-418A-4697-BB10-20C3AA60E2E4}"
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "hidtest", "hidtest.vcxproj", "{23E9FF6A-49D1-4993-B2B5-BBB992C6C712}"
+EndProject
+Global
+	GlobalSection(SolutionConfigurationPlatforms) = preSolution
+		Debug|Win32 = Debug|Win32
+		Debug|x64 = Debug|x64
+		Release|Win32 = Release|Win32
+		Release|x64 = Release|x64
+	EndGlobalSection
+	GlobalSection(ProjectConfigurationPlatforms) = postSolution
+		{A107C21C-418A-4697-BB10-20C3AA60E2E4}.Debug|Win32.ActiveCfg = Debug|Win32
+		{A107C21C-418A-4697-BB10-20C3AA60E2E4}.Debug|Win32.Build.0 = Debug|Win32
+		{A107C21C-418A-4697-BB10-20C3AA60E2E4}.Debug|x64.ActiveCfg = Debug|x64
+		{A107C21C-418A-4697-BB10-20C3AA60E2E4}.Debug|x64.Build.0 = Debug|x64
+		{A107C21C-418A-4697-BB10-20C3AA60E2E4}.Release|Win32.ActiveCfg = Release|Win32
+		{A107C21C-418A-4697-BB10-20C3AA60E2E4}.Release|Win32.Build.0 = Release|Win32
+		{A107C21C-418A-4697-BB10-20C3AA60E2E4}.Release|x64.ActiveCfg = Release|x64
+		{A107C21C-418A-4697-BB10-20C3AA60E2E4}.Release|x64.Build.0 = Release|x64
+		{23E9FF6A-49D1-4993-B2B5-BBB992C6C712}.Debug|Win32.ActiveCfg = Debug|Win32
+		{23E9FF6A-49D1-4993-B2B5-BBB992C6C712}.Debug|Win32.Build.0 = Debug|Win32
+		{23E9FF6A-49D1-4993-B2B5-BBB992C6C712}.Debug|x64.ActiveCfg = Debug|x64
+		{23E9FF6A-49D1-4993-B2B5-BBB992C6C712}.Debug|x64.Build.0 = Debug|x64
+		{23E9FF6A-49D1-4993-B2B5-BBB992C6C712}.Release|Win32.ActiveCfg = Release|Win32
+		{23E9FF6A-49D1-4993-B2B5-BBB992C6C712}.Release|Win32.Build.0 = Release|Win32
+		{23E9FF6A-49D1-4993-B2B5-BBB992C6C712}.Release|x64.ActiveCfg = Release|x64
+		{23E9FF6A-49D1-4993-B2B5-BBB992C6C712}.Release|x64.Build.0 = Release|x64
+	EndGlobalSection
+	GlobalSection(SolutionProperties) = preSolution
+		HideSolutionNode = FALSE
+	EndGlobalSection
+	GlobalSection(ExtensibilityGlobals) = postSolution
+		SolutionGuid = {8749E535-9C65-4A89-840E-78D7578C7866}
+	EndGlobalSection
+EndGlobal

+ 200 - 201
src/hidapi/windows/hidapi.vcproj

@@ -1,201 +1,200 @@
-<?xml version="1.0" encoding="Windows-1252"?>
-<VisualStudioProject
-	ProjectType="Visual C++"
-	Version="9.00"
-	Name="hidapi"
-	ProjectGUID="{A107C21C-418A-4697-BB10-20C3AA60E2E4}"
-	RootNamespace="hidapi"
-	Keyword="Win32Proj"
-	TargetFrameworkVersion="196613"
-	>
-	<Platforms>
-		<Platform
-			Name="Win32"
-		/>
-	</Platforms>
-	<ToolFiles>
-	</ToolFiles>
-	<Configurations>
-		<Configuration
-			Name="Debug|Win32"
-			OutputDirectory="$(SolutionDir)$(ConfigurationName)"
-			IntermediateDirectory="$(ConfigurationName)"
-			ConfigurationType="2"
-			CharacterSet="1"
-			>
-			<Tool
-				Name="VCPreBuildEventTool"
-			/>
-			<Tool
-				Name="VCCustomBuildTool"
-			/>
-			<Tool
-				Name="VCXMLDataGeneratorTool"
-			/>
-			<Tool
-				Name="VCWebServiceProxyGeneratorTool"
-			/>
-			<Tool
-				Name="VCMIDLTool"
-			/>
-			<Tool
-				Name="VCCLCompilerTool"
-				Optimization="0"
-				AdditionalIncludeDirectories="..\hidapi"
-				PreprocessorDefinitions="WIN32;_DEBUG;_WINDOWS;_USRDLL;HIDAPI_EXPORTS"
-				MinimalRebuild="true"
-				BasicRuntimeChecks="3"
-				RuntimeLibrary="3"
-				UsePrecompiledHeader="0"
-				WarningLevel="3"
-				DebugInformationFormat="4"
-			/>
-			<Tool
-				Name="VCManagedResourceCompilerTool"
-			/>
-			<Tool
-				Name="VCResourceCompilerTool"
-			/>
-			<Tool
-				Name="VCPreLinkEventTool"
-			/>
-			<Tool
-				Name="VCLinkerTool"
-				AdditionalDependencies="setupapi.lib"
-				LinkIncremental="2"
-				GenerateDebugInformation="true"
-				SubSystem="2"
-				TargetMachine="1"
-			/>
-			<Tool
-				Name="VCALinkTool"
-			/>
-			<Tool
-				Name="VCManifestTool"
-			/>
-			<Tool
-				Name="VCXDCMakeTool"
-			/>
-			<Tool
-				Name="VCBscMakeTool"
-			/>
-			<Tool
-				Name="VCFxCopTool"
-			/>
-			<Tool
-				Name="VCAppVerifierTool"
-			/>
-			<Tool
-				Name="VCPostBuildEventTool"
-			/>
-		</Configuration>
-		<Configuration
-			Name="Release|Win32"
-			OutputDirectory="$(SolutionDir)$(ConfigurationName)"
-			IntermediateDirectory="$(ConfigurationName)"
-			ConfigurationType="2"
-			CharacterSet="1"
-			WholeProgramOptimization="1"
-			>
-			<Tool
-				Name="VCPreBuildEventTool"
-			/>
-			<Tool
-				Name="VCCustomBuildTool"
-			/>
-			<Tool
-				Name="VCXMLDataGeneratorTool"
-			/>
-			<Tool
-				Name="VCWebServiceProxyGeneratorTool"
-			/>
-			<Tool
-				Name="VCMIDLTool"
-			/>
-			<Tool
-				Name="VCCLCompilerTool"
-				Optimization="2"
-				EnableIntrinsicFunctions="true"
-				AdditionalIncludeDirectories="..\hidapi"
-				PreprocessorDefinitions="WIN32;NDEBUG;_WINDOWS;_USRDLL;HIDAPI_EXPORTS"
-				RuntimeLibrary="2"
-				EnableFunctionLevelLinking="true"
-				UsePrecompiledHeader="0"
-				WarningLevel="3"
-				DebugInformationFormat="3"
-			/>
-			<Tool
-				Name="VCManagedResourceCompilerTool"
-			/>
-			<Tool
-				Name="VCResourceCompilerTool"
-			/>
-			<Tool
-				Name="VCPreLinkEventTool"
-			/>
-			<Tool
-				Name="VCLinkerTool"
-				AdditionalDependencies="setupapi.lib"
-				LinkIncremental="1"
-				GenerateDebugInformation="true"
-				SubSystem="2"
-				OptimizeReferences="2"
-				EnableCOMDATFolding="2"
-				TargetMachine="1"
-			/>
-			<Tool
-				Name="VCALinkTool"
-			/>
-			<Tool
-				Name="VCManifestTool"
-			/>
-			<Tool
-				Name="VCXDCMakeTool"
-			/>
-			<Tool
-				Name="VCBscMakeTool"
-			/>
-			<Tool
-				Name="VCFxCopTool"
-			/>
-			<Tool
-				Name="VCAppVerifierTool"
-			/>
-			<Tool
-				Name="VCPostBuildEventTool"
-			/>
-		</Configuration>
-	</Configurations>
-	<References>
-	</References>
-	<Files>
-		<Filter
-			Name="Source Files"
-			Filter="cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx"
-			UniqueIdentifier="{4FC737F1-C7A5-4376-A066-2A32D752A2FF}"
-			>
-			<File
-				RelativePath=".\hid.c"
-				>
-			</File>
-		</Filter>
-		<Filter
-			Name="Header Files"
-			Filter="h;hpp;hxx;hm;inl;inc;xsd"
-			UniqueIdentifier="{93995380-89BD-4b04-88EB-625FBE52EBFB}"
-			>
-			<File
-				RelativePath="..\hidapi\hidapi.h"
-				>
-			</File>
-		</Filter>
-		<Filter
-			Name="Resource Files"
-			Filter="rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav"
-			UniqueIdentifier="{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}"
-			>
-		</Filter>
-	</Files>
-	<Globals>
-	</Globals>
-</VisualStudioProject>
+<?xml version="1.0" encoding="Windows-1252"?>
+<VisualStudioProject
+	ProjectType="Visual C++"
+	Version="9.00"
+	Name="hidapi"
+	ProjectGUID="{A107C21C-418A-4697-BB10-20C3AA60E2E4}"
+	RootNamespace="hidapi"
+	Keyword="Win32Proj"
+	TargetFrameworkVersion="196613"
+	>
+	<Platforms>
+		<Platform
+			Name="Win32"
+		/>
+	</Platforms>
+	<ToolFiles>
+	</ToolFiles>
+	<Configurations>
+		<Configuration
+			Name="Debug|Win32"
+			OutputDirectory="$(SolutionDir)$(ConfigurationName)"
+			IntermediateDirectory="$(ConfigurationName)"
+			ConfigurationType="2"
+			CharacterSet="1"
+			>
+			<Tool
+				Name="VCPreBuildEventTool"
+			/>
+			<Tool
+				Name="VCCustomBuildTool"
+			/>
+			<Tool
+				Name="VCXMLDataGeneratorTool"
+			/>
+			<Tool
+				Name="VCWebServiceProxyGeneratorTool"
+			/>
+			<Tool
+				Name="VCMIDLTool"
+			/>
+			<Tool
+				Name="VCCLCompilerTool"
+				Optimization="0"
+				AdditionalIncludeDirectories="..\hidapi"
+				PreprocessorDefinitions="WIN32;_DEBUG;_WINDOWS;_USRDLL;HIDAPI_EXPORTS"
+				MinimalRebuild="true"
+				BasicRuntimeChecks="3"
+				RuntimeLibrary="3"
+				UsePrecompiledHeader="0"
+				WarningLevel="3"
+				DebugInformationFormat="4"
+			/>
+			<Tool
+				Name="VCManagedResourceCompilerTool"
+			/>
+			<Tool
+				Name="VCResourceCompilerTool"
+			/>
+			<Tool
+				Name="VCPreLinkEventTool"
+			/>
+			<Tool
+				Name="VCLinkerTool"
+				LinkIncremental="2"
+				GenerateDebugInformation="true"
+				SubSystem="2"
+				TargetMachine="1"
+			/>
+			<Tool
+				Name="VCALinkTool"
+			/>
+			<Tool
+				Name="VCManifestTool"
+			/>
+			<Tool
+				Name="VCXDCMakeTool"
+			/>
+			<Tool
+				Name="VCBscMakeTool"
+			/>
+			<Tool
+				Name="VCFxCopTool"
+			/>
+			<Tool
+				Name="VCAppVerifierTool"
+			/>
+			<Tool
+				Name="VCPostBuildEventTool"
+			/>
+		</Configuration>
+		<Configuration
+			Name="Release|Win32"
+			OutputDirectory="$(SolutionDir)$(ConfigurationName)"
+			IntermediateDirectory="$(ConfigurationName)"
+			ConfigurationType="2"
+			CharacterSet="1"
+			WholeProgramOptimization="1"
+			>
+			<Tool
+				Name="VCPreBuildEventTool"
+			/>
+			<Tool
+				Name="VCCustomBuildTool"
+			/>
+			<Tool
+				Name="VCXMLDataGeneratorTool"
+			/>
+			<Tool
+				Name="VCWebServiceProxyGeneratorTool"
+			/>
+			<Tool
+				Name="VCMIDLTool"
+			/>
+			<Tool
+				Name="VCCLCompilerTool"
+				Optimization="2"
+				EnableIntrinsicFunctions="true"
+				AdditionalIncludeDirectories="..\hidapi"
+				PreprocessorDefinitions="WIN32;NDEBUG;_WINDOWS;_USRDLL;HIDAPI_EXPORTS"
+				RuntimeLibrary="2"
+				EnableFunctionLevelLinking="true"
+				UsePrecompiledHeader="0"
+				WarningLevel="3"
+				DebugInformationFormat="3"
+			/>
+			<Tool
+				Name="VCManagedResourceCompilerTool"
+			/>
+			<Tool
+				Name="VCResourceCompilerTool"
+			/>
+			<Tool
+				Name="VCPreLinkEventTool"
+			/>
+			<Tool
+				Name="VCLinkerTool"
+				AdditionalDependencies="setupapi.lib"
+				LinkIncremental="1"
+				GenerateDebugInformation="true"
+				SubSystem="2"
+				OptimizeReferences="2"
+				EnableCOMDATFolding="2"
+				TargetMachine="1"
+			/>
+			<Tool
+				Name="VCALinkTool"
+			/>
+			<Tool
+				Name="VCManifestTool"
+			/>
+			<Tool
+				Name="VCXDCMakeTool"
+			/>
+			<Tool
+				Name="VCBscMakeTool"
+			/>
+			<Tool
+				Name="VCFxCopTool"
+			/>
+			<Tool
+				Name="VCAppVerifierTool"
+			/>
+			<Tool
+				Name="VCPostBuildEventTool"
+			/>
+		</Configuration>
+	</Configurations>
+	<References>
+	</References>
+	<Files>
+		<Filter
+			Name="Source Files"
+			Filter="cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx"
+			UniqueIdentifier="{4FC737F1-C7A5-4376-A066-2A32D752A2FF}"
+			>
+			<File
+				RelativePath=".\hid.c"
+				>
+			</File>
+		</Filter>
+		<Filter
+			Name="Header Files"
+			Filter="h;hpp;hxx;hm;inl;inc;xsd"
+			UniqueIdentifier="{93995380-89BD-4b04-88EB-625FBE52EBFB}"
+			>
+			<File
+				RelativePath="..\hidapi\hidapi.h"
+				>
+			</File>
+		</Filter>
+		<Filter
+			Name="Resource Files"
+			Filter="rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav"
+			UniqueIdentifier="{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}"
+			>
+		</Filter>
+	</Files>
+	<Globals>
+	</Globals>
+</VisualStudioProject>

+ 200 - 0
src/hidapi/windows/hidapi.vcxproj

@@ -0,0 +1,200 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project DefaultTargets="Build" ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <ItemGroup Label="ProjectConfigurations">
+    <ProjectConfiguration Include="Debug|Win32">
+      <Configuration>Debug</Configuration>
+      <Platform>Win32</Platform>
+    </ProjectConfiguration>
+    <ProjectConfiguration Include="Debug|x64">
+      <Configuration>Debug</Configuration>
+      <Platform>x64</Platform>
+    </ProjectConfiguration>
+    <ProjectConfiguration Include="Release|Win32">
+      <Configuration>Release</Configuration>
+      <Platform>Win32</Platform>
+    </ProjectConfiguration>
+    <ProjectConfiguration Include="Release|x64">
+      <Configuration>Release</Configuration>
+      <Platform>x64</Platform>
+    </ProjectConfiguration>
+  </ItemGroup>
+  <PropertyGroup Label="Globals">
+    <ProjectGuid>{A107C21C-418A-4697-BB10-20C3AA60E2E4}</ProjectGuid>
+    <RootNamespace>hidapi</RootNamespace>
+    <Keyword>Win32Proj</Keyword>
+  </PropertyGroup>
+  <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
+    <ConfigurationType>DynamicLibrary</ConfigurationType>
+    <PlatformToolset Condition="'$(VisualStudioVersion)'=='11'">v110</PlatformToolset>
+    <PlatformToolset Condition="'$(VisualStudioVersion)'=='12'">v120</PlatformToolset>
+    <PlatformToolset Condition="'$(VisualStudioVersion)'=='14'">v140</PlatformToolset>
+    <PlatformToolset Condition="'$(VisualStudioVersion)'=='15'">v141</PlatformToolset>
+    <PlatformToolset Condition="'$(VisualStudioVersion)'=='16'">v142</PlatformToolset>
+    <PlatformToolset Condition="'$(VisualStudioVersion)'=='17'">v143</PlatformToolset>
+    <CharacterSet>Unicode</CharacterSet>
+    <WholeProgramOptimization>true</WholeProgramOptimization>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
+    <ConfigurationType>DynamicLibrary</ConfigurationType>
+    <PlatformToolset Condition="'$(VisualStudioVersion)'=='11'">v110</PlatformToolset>
+    <PlatformToolset Condition="'$(VisualStudioVersion)'=='12'">v120</PlatformToolset>
+    <PlatformToolset Condition="'$(VisualStudioVersion)'=='14'">v140</PlatformToolset>
+    <PlatformToolset Condition="'$(VisualStudioVersion)'=='15'">v141</PlatformToolset>
+    <PlatformToolset Condition="'$(VisualStudioVersion)'=='16'">v142</PlatformToolset>
+    <PlatformToolset Condition="'$(VisualStudioVersion)'=='17'">v143</PlatformToolset>
+    <CharacterSet>Unicode</CharacterSet>
+    <WholeProgramOptimization>true</WholeProgramOptimization>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
+    <ConfigurationType>DynamicLibrary</ConfigurationType>
+    <PlatformToolset Condition="'$(VisualStudioVersion)'=='11'">v110</PlatformToolset>
+    <PlatformToolset Condition="'$(VisualStudioVersion)'=='12'">v120</PlatformToolset>
+    <PlatformToolset Condition="'$(VisualStudioVersion)'=='14'">v140</PlatformToolset>
+    <PlatformToolset Condition="'$(VisualStudioVersion)'=='15'">v141</PlatformToolset>
+    <PlatformToolset Condition="'$(VisualStudioVersion)'=='16'">v142</PlatformToolset>
+    <PlatformToolset Condition="'$(VisualStudioVersion)'=='17'">v143</PlatformToolset>
+    <CharacterSet>Unicode</CharacterSet>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
+    <ConfigurationType>DynamicLibrary</ConfigurationType>
+    <PlatformToolset Condition="'$(VisualStudioVersion)'=='11'">v110</PlatformToolset>
+    <PlatformToolset Condition="'$(VisualStudioVersion)'=='12'">v120</PlatformToolset>
+    <PlatformToolset Condition="'$(VisualStudioVersion)'=='14'">v140</PlatformToolset>
+    <PlatformToolset Condition="'$(VisualStudioVersion)'=='15'">v141</PlatformToolset>
+    <PlatformToolset Condition="'$(VisualStudioVersion)'=='16'">v142</PlatformToolset>
+    <PlatformToolset Condition="'$(VisualStudioVersion)'=='17'">v143</PlatformToolset>
+    <CharacterSet>Unicode</CharacterSet>
+  </PropertyGroup>
+  <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
+  <ImportGroup Label="ExtensionSettings">
+  </ImportGroup>
+  <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="PropertySheets">
+    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+  </ImportGroup>
+  <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="PropertySheets">
+    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+  </ImportGroup>
+  <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="PropertySheets">
+    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+  </ImportGroup>
+  <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="PropertySheets">
+    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+  </ImportGroup>
+  <PropertyGroup Label="UserMacros" />
+  <PropertyGroup>
+    <_ProjectFileVersion>14.0.25431.1</_ProjectFileVersion>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" />
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" />
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" />
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" />
+  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+    <ClCompile>
+      <Optimization>Disabled</Optimization>
+      <AdditionalIncludeDirectories>..\hidapi;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+      <PreprocessorDefinitions>WIN32;_DEBUG;_WINDOWS;_USRDLL;HIDAPI_EXPORTS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <MinimalRebuild>true</MinimalRebuild>
+      <BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>
+      <RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>
+      <PrecompiledHeader />
+      <WarningLevel>Level3</WarningLevel>
+      <DebugInformationFormat>EditAndContinue</DebugInformationFormat>
+    </ClCompile>
+    <Link>
+      <AdditionalDependencies>%(AdditionalDependencies)</AdditionalDependencies>
+      <GenerateDebugInformation>true</GenerateDebugInformation>
+      <SubSystem>Windows</SubSystem>
+      <TargetMachine>MachineX86</TargetMachine>
+    </Link>
+    <ResourceCompile>
+      <AdditionalIncludeDirectories>..\hidapi</AdditionalIncludeDirectories>
+      <PreprocessorDefinitions>_DEBUG</PreprocessorDefinitions>
+    </ResourceCompile>
+  </ItemDefinitionGroup>
+  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
+    <ClCompile>
+      <Optimization>Disabled</Optimization>
+      <AdditionalIncludeDirectories>..\hidapi;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+      <PreprocessorDefinitions>WIN32;_DEBUG;_WINDOWS;_USRDLL;HIDAPI_EXPORTS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>
+      <RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>
+      <PrecompiledHeader>
+      </PrecompiledHeader>
+      <WarningLevel>Level3</WarningLevel>
+      <DebugInformationFormat>EditAndContinue</DebugInformationFormat>
+    </ClCompile>
+    <Link>
+      <AdditionalDependencies>%(AdditionalDependencies)</AdditionalDependencies>
+      <GenerateDebugInformation>true</GenerateDebugInformation>
+      <SubSystem>Windows</SubSystem>
+    </Link>
+    <ResourceCompile>
+      <AdditionalIncludeDirectories>..\hidapi</AdditionalIncludeDirectories>
+      <PreprocessorDefinitions>_DEBUG</PreprocessorDefinitions>
+    </ResourceCompile>
+  </ItemDefinitionGroup>
+  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+    <ClCompile>
+      <Optimization>MaxSpeed</Optimization>
+      <IntrinsicFunctions>true</IntrinsicFunctions>
+      <AdditionalIncludeDirectories>..\hidapi;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+      <PreprocessorDefinitions>WIN32;NDEBUG;_WINDOWS;_USRDLL;HIDAPI_EXPORTS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <RuntimeLibrary>MultiThreaded</RuntimeLibrary>
+      <FunctionLevelLinking>true</FunctionLevelLinking>
+      <PrecompiledHeader />
+      <WarningLevel>Level3</WarningLevel>
+      <DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
+    </ClCompile>
+    <Link>
+      <AdditionalDependencies>%(AdditionalDependencies)</AdditionalDependencies>
+      <GenerateDebugInformation>true</GenerateDebugInformation>
+      <SubSystem>Windows</SubSystem>
+      <OptimizeReferences>true</OptimizeReferences>
+      <EnableCOMDATFolding>true</EnableCOMDATFolding>
+      <TargetMachine>MachineX86</TargetMachine>
+    </Link>
+    <ResourceCompile>
+      <AdditionalIncludeDirectories>..\hidapi</AdditionalIncludeDirectories>
+      <PreprocessorDefinitions>NDEBUG</PreprocessorDefinitions>
+    </ResourceCompile>
+  </ItemDefinitionGroup>
+  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
+    <ClCompile>
+      <Optimization>MaxSpeed</Optimization>
+      <IntrinsicFunctions>true</IntrinsicFunctions>
+      <AdditionalIncludeDirectories>..\hidapi;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+      <PreprocessorDefinitions>WIN32;NDEBUG;_WINDOWS;_USRDLL;HIDAPI_EXPORTS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <RuntimeLibrary>MultiThreaded</RuntimeLibrary>
+      <FunctionLevelLinking>true</FunctionLevelLinking>
+      <PrecompiledHeader>
+      </PrecompiledHeader>
+      <WarningLevel>Level3</WarningLevel>
+      <DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
+    </ClCompile>
+    <Link>
+      <AdditionalDependencies>%(AdditionalDependencies)</AdditionalDependencies>
+      <GenerateDebugInformation>true</GenerateDebugInformation>
+      <SubSystem>Windows</SubSystem>
+      <OptimizeReferences>true</OptimizeReferences>
+      <EnableCOMDATFolding>true</EnableCOMDATFolding>
+    </Link>
+    <ResourceCompile>
+      <AdditionalIncludeDirectories>..\hidapi</AdditionalIncludeDirectories>
+      <PreprocessorDefinitions>NDEBUG</PreprocessorDefinitions>
+    </ResourceCompile>
+  </ItemDefinitionGroup>
+  <ItemGroup>
+    <ClCompile Include="hid.c" />
+  </ItemGroup>
+  <ItemGroup>
+    <ClInclude Include="..\hidapi\hidapi.h" />
+    <ClInclude Include="hidapi_winapi.h" />
+  </ItemGroup>
+  <ItemGroup>
+    <ResourceCompile Include="hidapi.rc" />
+  </ItemGroup>
+  <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
+  <ImportGroup Label="ExtensionTargets">
+  </ImportGroup>
+</Project>

+ 14 - 16
src/hidapi/windows/hidapi_cfgmgr32.h

@@ -28,18 +28,14 @@
 #include <propkey.h>
 
 #else
-#ifndef PROPERTYKEY_DEFINED
-#define PROPERTYKEY_DEFINED
-typedef struct
-{
-    GUID fmtid;
-    DWORD pid;
-} PROPERTYKEY;
-#endif /* PROPERTYKEY_DEFINED */
 
 /* This part of the header mimics cfgmgr32.h,
     but only what is used by HIDAPI */
 
+#include <initguid.h>
+#include <devpropdef.h>
+#include <propkeydef.h>
+
 typedef DWORD RETURN_TYPE;
 typedef RETURN_TYPE CONFIGRET;
 typedef DWORD DEVNODE, DEVINST;
@@ -59,18 +55,20 @@ typedef CONFIGRET(__stdcall* CM_Get_Parent_)(PDEVINST pdnDevInst, DEVINST dnDevI
 typedef CONFIGRET(__stdcall* CM_Get_DevNode_PropertyW_)(DEVINST dnDevInst, CONST DEVPROPKEY* PropertyKey, DEVPROPTYPE* PropertyType, PBYTE PropertyBuffer, PULONG PropertyBufferSize, ULONG ulFlags);
 typedef CONFIGRET(__stdcall* CM_Get_Device_Interface_PropertyW_)(LPCWSTR pszDeviceInterface, CONST DEVPROPKEY* PropertyKey, DEVPROPTYPE* PropertyType, PBYTE PropertyBuffer, PULONG PropertyBufferSize, ULONG ulFlags);
 typedef CONFIGRET(__stdcall* CM_Get_Device_Interface_List_SizeW_)(PULONG pulLen, LPGUID InterfaceClassGuid, DEVINSTID_W pDeviceID, ULONG ulFlags);
-typedef CONFIGRET(__stdcall* CM_Get_Device_Interface_ListW_)(LPGUID InterfaceClassGuid, DEVINSTID_W pDeviceID, WCHAR* Buffer, ULONG BufferLen, ULONG ulFlags);
+typedef CONFIGRET(__stdcall* CM_Get_Device_Interface_ListW_)(LPGUID InterfaceClassGuid, DEVINSTID_W pDeviceID, PZZWSTR Buffer, ULONG BufferLen, ULONG ulFlags);
 
 // from devpkey.h
-static DEVPROPKEY DEVPKEY_NAME = { { 0xb725f130, 0x47ef, 0x101a, {0xa5, 0xf1, 0x02, 0x60, 0x8c, 0x9e, 0xeb, 0xac} }, 10 }; // DEVPROP_TYPE_STRING
-static DEVPROPKEY DEVPKEY_Device_InstanceId = { { 0x78c34fc8, 0x104a, 0x4aca, {0x9e, 0xa4, 0x52, 0x4d, 0x52, 0x99, 0x6e, 0x57} }, 256 }; // DEVPROP_TYPE_STRING
-//static DEVPROPKEY DEVPKEY_Device_HardwareIds = { { 0xa45c254e, 0xdf1c, 0x4efd, {0x80, 0x20, 0x67, 0xd1, 0x46, 0xa8, 0x50, 0xe0} }, 3 }; // DEVPROP_TYPE_STRING_LIST
-static DEVPROPKEY DEVPKEY_Device_CompatibleIds = { { 0xa45c254e, 0xdf1c, 0x4efd, {0x80, 0x20, 0x67, 0xd1, 0x46, 0xa8, 0x50, 0xe0} }, 4 }; // DEVPROP_TYPE_STRING_LIST
-//static DEVPROPKEY DEVPKEY_Device_ContainerId = { { 0x8c7ed206, 0x3f8a, 0x4827, {0xb3, 0xab, 0xae, 0x9e, 0x1f, 0xae, 0xfc, 0x6c} }, 2 }; // DEVPROP_TYPE_GUID
+DEFINE_DEVPROPKEY(DEVPKEY_NAME, 0xb725f130, 0x47ef, 0x101a, 0xa5, 0xf1, 0x02, 0x60, 0x8c, 0x9e, 0xeb, 0xac, 10); // DEVPROP_TYPE_STRING
+DEFINE_DEVPROPKEY(DEVPKEY_Device_Manufacturer, 0xa45c254e, 0xdf1c, 0x4efd, 0x80, 0x20, 0x67, 0xd1, 0x46, 0xa8, 0x50, 0xe0, 13); // DEVPROP_TYPE_STRING
+DEFINE_DEVPROPKEY(DEVPKEY_Device_InstanceId, 0x78c34fc8, 0x104a, 0x4aca, 0x9e, 0xa4, 0x52, 0x4d, 0x52, 0x99, 0x6e, 0x57, 256); // DEVPROP_TYPE_STRING
+DEFINE_DEVPROPKEY(DEVPKEY_Device_HardwareIds, 0xa45c254e, 0xdf1c, 0x4efd, 0x80, 0x20, 0x67, 0xd1, 0x46, 0xa8, 0x50, 0xe0, 3); // DEVPROP_TYPE_STRING_LIST
+DEFINE_DEVPROPKEY(DEVPKEY_Device_CompatibleIds, 0xa45c254e, 0xdf1c, 0x4efd, 0x80, 0x20, 0x67, 0xd1, 0x46, 0xa8, 0x50, 0xe0, 4); // DEVPROP_TYPE_STRING_LIST
+DEFINE_DEVPROPKEY(DEVPKEY_Device_ContainerId, 0x8c7ed206, 0x3f8a, 0x4827, 0xb3, 0xab, 0xae, 0x9e, 0x1f, 0xae, 0xfc, 0x6c, 2); // DEVPROP_TYPE_GUID
 
 // from propkey.h
-static PROPERTYKEY PKEY_DeviceInterface_Bluetooth_DeviceAddress = { { 0x2bd67d8b, 0x8beb, 0x48d5, {0x87, 0xe0, 0x6c, 0xda, 0x34, 0x28, 0x04, 0x0a} }, 1 }; // DEVPROP_TYPE_STRING
-static PROPERTYKEY PKEY_DeviceInterface_Bluetooth_Manufacturer = { { 0x2bd67d8b, 0x8beb, 0x48d5, {0x87, 0xe0, 0x6c, 0xda, 0x34, 0x28, 0x04, 0x0a} }, 4 }; // DEVPROP_TYPE_STRING
+DEFINE_PROPERTYKEY(PKEY_DeviceInterface_Bluetooth_DeviceAddress, 0x2BD67D8B, 0x8BEB, 0x48D5, 0x87, 0xE0, 0x6C, 0xDA, 0x34, 0x28, 0x04, 0x0A, 1); // DEVPROP_TYPE_STRING
+DEFINE_PROPERTYKEY(PKEY_DeviceInterface_Bluetooth_Manufacturer, 0x2BD67D8B, 0x8BEB, 0x48D5, 0x87, 0xE0, 0x6C, 0xDA, 0x34, 0x28, 0x04, 0x0A, 4); // DEVPROP_TYPE_STRING
+DEFINE_PROPERTYKEY(PKEY_DeviceInterface_Bluetooth_ModelNumber, 0x2BD67D8B, 0x8BEB, 0x48D5, 0x87, 0xE0, 0x6C, 0xDA, 0x34, 0x28, 0x04, 0x0A, 5); // DEVPROP_TYPE_STRING
 
 #endif
 

+ 987 - 0
src/hidapi/windows/hidapi_descriptor_reconstruct.c

@@ -0,0 +1,987 @@
+/*******************************************************
+ HIDAPI - Multi-Platform library for
+ communication with HID devices.
+
+ libusb/hidapi Team
+
+ Copyright 2022, All Rights Reserved.
+
+ At the discretion of the user of this library,
+ this software may be licensed under the terms of the
+ GNU General Public License v3, a BSD-Style license, or the
+ original HIDAPI license as outlined in the LICENSE.txt,
+ LICENSE-gpl3.txt, LICENSE-bsd.txt, and LICENSE-orig.txt
+ files located at the root of the source distribution.
+ These files may also be found in the public source
+ code repository located at:
+        https://github.com/libusb/hidapi .
+********************************************************/
+#include "hidapi_descriptor_reconstruct.h"
+
+/**
+ * @brief References to report descriptor buffer.
+ * 
+ */
+struct rd_buffer {
+	unsigned char* buf; /* Pointer to the array which stores the reconstructed descriptor */
+	size_t buf_size; /* Size of the buffer in bytes */
+	size_t byte_idx; /* Index of the next report byte to write to buf array */
+};
+
+/**
+ * @brief Function that appends a byte to encoded report descriptor buffer.
+ *
+ * @param[in]  byte     Single byte to append.
+ * @param      rpt_desc Pointer to report descriptor buffer struct.
+ */
+static void rd_append_byte(unsigned char byte, struct rd_buffer* rpt_desc) {
+	if (rpt_desc->byte_idx < rpt_desc->buf_size) {
+		rpt_desc->buf[rpt_desc->byte_idx] = byte;
+		rpt_desc->byte_idx++;
+	}
+}
+
+/**
+ * @brief Writes a short report descriptor item according USB HID spec 1.11 chapter 6.2.2.2.
+ *
+ * @param[in]  rd_item  Enumeration identifying type (Main, Global, Local) and function (e.g Usage or Report Count) of the item.
+ * @param[in]  data     Data (Size depends on rd_item 0,1,2 or 4bytes).
+ * @param      list     Chained list of report descriptor bytes.
+ *
+ * @return Returns 0 if successful, -1 for error.
+ */
+static int rd_write_short_item(rd_items rd_item, LONG64 data, struct rd_buffer* rpt_desc) {
+	if (rd_item & 0x03) {
+		// Invalid input data, last to bits are reserved for data size
+		return -1;
+	}
+
+	if (rd_item == rd_main_collection_end) {
+		// Item without data (1Byte prefix only)
+		unsigned char oneBytePrefix = (unsigned char) rd_item + 0x00;
+		rd_append_byte(oneBytePrefix, rpt_desc);
+	}
+	else if ((rd_item == rd_global_logical_minimum) ||
+		(rd_item == rd_global_logical_maximum) ||
+		(rd_item == rd_global_physical_minimum) ||
+		(rd_item == rd_global_physical_maximum)) {
+		// Item with signed integer data
+		if ((data >= -128) && (data <= 127)) {
+			// 1Byte prefix + 1Byte data
+			unsigned char oneBytePrefix = (unsigned char) rd_item + 0x01;
+			char localData = (char)data;
+			rd_append_byte(oneBytePrefix, rpt_desc);
+			rd_append_byte(localData & 0xFF, rpt_desc);
+		}
+		else if ((data >= -32768) && (data <= 32767)) {
+			// 1Byte prefix + 2Byte data
+			unsigned char oneBytePrefix = (unsigned char) rd_item + 0x02;
+			INT16 localData = (INT16)data;
+			rd_append_byte(oneBytePrefix, rpt_desc);
+			rd_append_byte(localData & 0xFF, rpt_desc);
+			rd_append_byte(localData >> 8 & 0xFF, rpt_desc);
+		}
+		else if ((data >= -2147483648LL) && (data <= 2147483647)) {
+			// 1Byte prefix + 4Byte data
+			unsigned char oneBytePrefix = (unsigned char) rd_item + 0x03;
+			INT32 localData = (INT32)data;
+			rd_append_byte(oneBytePrefix, rpt_desc);
+			rd_append_byte(localData & 0xFF, rpt_desc);
+			rd_append_byte(localData >> 8 & 0xFF, rpt_desc);
+			rd_append_byte(localData >> 16 & 0xFF, rpt_desc);
+			rd_append_byte(localData >> 24 & 0xFF, rpt_desc);
+		}
+		else {
+			// Data out of 32 bit signed integer range
+			return -1;
+		}
+	}
+	else {
+		// Item with unsigned integer data
+		if ((data >= 0) && (data <= 0xFF)) {
+			// 1Byte prefix + 1Byte data
+			unsigned char oneBytePrefix = (unsigned char) rd_item + 0x01;
+			unsigned char localData = (unsigned char)data;
+			rd_append_byte(oneBytePrefix, rpt_desc);
+			rd_append_byte(localData & 0xFF, rpt_desc);
+		}
+		else if ((data >= 0) && (data <= 0xFFFF)) {
+			// 1Byte prefix + 2Byte data
+			unsigned char oneBytePrefix = (unsigned char) rd_item + 0x02;
+			UINT16 localData = (UINT16)data;
+			rd_append_byte(oneBytePrefix, rpt_desc);
+			rd_append_byte(localData & 0xFF, rpt_desc);
+			rd_append_byte(localData >> 8 & 0xFF, rpt_desc);
+		}
+		else if ((data >= 0) && (data <= 0xFFFFFFFF)) {
+			// 1Byte prefix + 4Byte data
+			unsigned char oneBytePrefix = (unsigned char) rd_item + 0x03;
+			UINT32 localData = (UINT32)data;
+			rd_append_byte(oneBytePrefix, rpt_desc);
+			rd_append_byte(localData & 0xFF, rpt_desc);
+			rd_append_byte(localData >> 8 & 0xFF, rpt_desc);
+			rd_append_byte(localData >> 16 & 0xFF, rpt_desc);
+			rd_append_byte(localData >> 24 & 0xFF, rpt_desc);
+		}
+		else {
+			// Data out of 32 bit unsigned integer range
+			return -1;
+		}
+	}
+	return 0;
+}
+
+static struct rd_main_item_node * rd_append_main_item_node(int first_bit, int last_bit, rd_node_type type_of_node, int caps_index, int collection_index, rd_main_items main_item_type, unsigned char report_id, struct rd_main_item_node **list) {
+	struct rd_main_item_node *new_list_node;
+
+	// Determine last node in the list
+	while (*list != NULL)
+	{
+		list = &(*list)->next;
+	}
+
+	new_list_node = malloc(sizeof(*new_list_node)); // Create new list entry
+	new_list_node->FirstBit = first_bit;
+	new_list_node->LastBit = last_bit;
+	new_list_node->TypeOfNode = type_of_node;
+	new_list_node->CapsIndex = caps_index;
+	new_list_node->CollectionIndex = collection_index;
+	new_list_node->MainItemType = main_item_type;
+	new_list_node->ReportID = report_id;
+	new_list_node->next = NULL; // NULL marks last node in the list
+
+	*list = new_list_node;
+	return new_list_node;
+}
+
+static struct  rd_main_item_node * rd_insert_main_item_node(int first_bit, int last_bit, rd_node_type type_of_node, int caps_index, int collection_index, rd_main_items main_item_type, unsigned char report_id, struct rd_main_item_node **list) {
+	// Insert item after the main item node referenced by list
+	struct rd_main_item_node *next_item = (*list)->next;
+	(*list)->next = NULL;
+	rd_append_main_item_node(first_bit, last_bit, type_of_node, caps_index, collection_index, main_item_type, report_id, list);
+	(*list)->next->next = next_item;
+	return (*list)->next;
+}
+
+static struct rd_main_item_node * rd_search_main_item_list_for_bit_position(int search_bit, rd_main_items main_item_type, unsigned char report_id, struct rd_main_item_node **list) {
+	// Determine first INPUT/OUTPUT/FEATURE main item, where the last bit position is equal or greater than the search bit position
+
+	while (((*list)->next->MainItemType != rd_collection) &&
+		((*list)->next->MainItemType != rd_collection_end) &&
+		!(((*list)->next->LastBit >= search_bit) &&
+			((*list)->next->ReportID == report_id) &&
+			((*list)->next->MainItemType == main_item_type))
+		)
+	{
+		list = &(*list)->next;
+	}
+	return *list;
+}
+
+int hid_winapi_descriptor_reconstruct_pp_data(void *preparsed_data, unsigned char *buf, size_t buf_size)
+{
+	hidp_preparsed_data *pp_data = (hidp_preparsed_data *) preparsed_data;
+
+	// Check if MagicKey is correct, to ensure that pp_data points to an valid preparse data structure
+	if (memcmp(pp_data->MagicKey, "HidP KDR", 8) != 0) {
+		return -1;
+	}
+
+	struct rd_buffer rpt_desc = {
+		.buf = buf,
+		.buf_size = buf_size,
+		.byte_idx = 0
+	};
+
+	// Set pointer to the first node of link_collection_nodes
+	phid_pp_link_collection_node link_collection_nodes = (phid_pp_link_collection_node)(((unsigned char*)&pp_data->caps[0]) + pp_data->FirstByteOfLinkCollectionArray);
+
+	// ****************************************************************************************************************************
+	// Create lookup tables for the bit range of each report per collection (position of first bit and last bit in each collection)
+	// coll_bit_range[COLLECTION_INDEX][REPORT_ID][INPUT/OUTPUT/FEATURE]
+	// ****************************************************************************************************************************
+	
+	// Allocate memory and initialize lookup table
+	rd_bit_range ****coll_bit_range;
+	coll_bit_range = malloc(pp_data->NumberLinkCollectionNodes * sizeof(*coll_bit_range));
+	for (USHORT collection_node_idx = 0; collection_node_idx < pp_data->NumberLinkCollectionNodes; collection_node_idx++) {
+		coll_bit_range[collection_node_idx] = malloc(256 * sizeof(*coll_bit_range[0])); // 256 possible report IDs (incl. 0x00)
+		for (int reportid_idx = 0; reportid_idx < 256; reportid_idx++) {
+			coll_bit_range[collection_node_idx][reportid_idx] = malloc(NUM_OF_HIDP_REPORT_TYPES * sizeof(*coll_bit_range[0][0]));
+			for (HIDP_REPORT_TYPE rt_idx = 0; rt_idx < NUM_OF_HIDP_REPORT_TYPES; rt_idx++) {
+				coll_bit_range[collection_node_idx][reportid_idx][rt_idx] = malloc(sizeof(rd_bit_range));
+				coll_bit_range[collection_node_idx][reportid_idx][rt_idx]->FirstBit = -1;
+				coll_bit_range[collection_node_idx][reportid_idx][rt_idx]->LastBit = -1;
+			}
+		}
+	}
+
+	// Fill the lookup table where caps exist
+	for (HIDP_REPORT_TYPE rt_idx = 0; rt_idx < NUM_OF_HIDP_REPORT_TYPES; rt_idx++) {
+		for (USHORT caps_idx = pp_data->caps_info[rt_idx].FirstCap; caps_idx < pp_data->caps_info[rt_idx].LastCap; caps_idx++) {
+			int first_bit, last_bit;
+			first_bit = (pp_data->caps[caps_idx].BytePosition - 1) * 8
+			           + pp_data->caps[caps_idx].BitPosition;
+			last_bit = first_bit + pp_data->caps[caps_idx].ReportSize
+			                     * pp_data->caps[caps_idx].ReportCount - 1;
+			if (coll_bit_range[pp_data->caps[caps_idx].LinkCollection][pp_data->caps[caps_idx].ReportID][rt_idx]->FirstBit == -1 ||
+				coll_bit_range[pp_data->caps[caps_idx].LinkCollection][pp_data->caps[caps_idx].ReportID][rt_idx]->FirstBit > first_bit) {
+				coll_bit_range[pp_data->caps[caps_idx].LinkCollection][pp_data->caps[caps_idx].ReportID][rt_idx]->FirstBit = first_bit;
+			}
+			if (coll_bit_range[pp_data->caps[caps_idx].LinkCollection][pp_data->caps[caps_idx].ReportID][rt_idx]->LastBit < last_bit) {
+				coll_bit_range[pp_data->caps[caps_idx].LinkCollection][pp_data->caps[caps_idx].ReportID][rt_idx]->LastBit = last_bit;
+			}
+		}
+	}
+
+	// *************************************************************************
+	// -Determine hierachy levels of each collections and store it in:
+	//  coll_levels[COLLECTION_INDEX]
+	// -Determine number of direct childs of each collections and store it in:
+	//  coll_number_of_direct_childs[COLLECTION_INDEX]
+	// *************************************************************************
+	int max_coll_level = 0;
+	int *coll_levels = malloc(pp_data->NumberLinkCollectionNodes * sizeof(coll_levels[0]));
+	int *coll_number_of_direct_childs = malloc(pp_data->NumberLinkCollectionNodes * sizeof(coll_number_of_direct_childs[0]));
+	for (USHORT collection_node_idx = 0; collection_node_idx < pp_data->NumberLinkCollectionNodes; collection_node_idx++) {
+		coll_levels[collection_node_idx] = -1;
+		coll_number_of_direct_childs[collection_node_idx] = 0;
+	}
+
+	{
+		int actual_coll_level = 0;
+		USHORT collection_node_idx = 0;
+		while (actual_coll_level >= 0) {
+			coll_levels[collection_node_idx] = actual_coll_level;
+			if ((link_collection_nodes[collection_node_idx].NumberOfChildren > 0) &&
+				(coll_levels[link_collection_nodes[collection_node_idx].FirstChild] == -1)) {
+				actual_coll_level++;
+				coll_levels[collection_node_idx] = actual_coll_level;
+				if (max_coll_level < actual_coll_level) {
+					max_coll_level = actual_coll_level;
+				}
+				coll_number_of_direct_childs[collection_node_idx]++;
+				collection_node_idx = link_collection_nodes[collection_node_idx].FirstChild;
+			}
+			else if (link_collection_nodes[collection_node_idx].NextSibling != 0) {
+				coll_number_of_direct_childs[link_collection_nodes[collection_node_idx].Parent]++;
+				collection_node_idx = link_collection_nodes[collection_node_idx].NextSibling;
+			}
+			else {
+				actual_coll_level--;
+				if (actual_coll_level >= 0) {
+					collection_node_idx = link_collection_nodes[collection_node_idx].Parent;
+				}
+			}
+		}
+	}
+
+	// *********************************************************************************
+	// Propagate the bit range of each report from the child collections to their parent
+	// and store the merged result for the parent
+	// *********************************************************************************
+	for (int actual_coll_level = max_coll_level - 1; actual_coll_level >= 0; actual_coll_level--) {
+		for (USHORT collection_node_idx = 0; collection_node_idx < pp_data->NumberLinkCollectionNodes; collection_node_idx++) {
+			if (coll_levels[collection_node_idx] == actual_coll_level) {
+				USHORT child_idx = link_collection_nodes[collection_node_idx].FirstChild;
+				while (child_idx) {
+					for (int reportid_idx = 0; reportid_idx < 256; reportid_idx++) {
+						for (HIDP_REPORT_TYPE rt_idx = 0; rt_idx < NUM_OF_HIDP_REPORT_TYPES; rt_idx++) {
+							// Merge bit range from childs
+							if ((coll_bit_range[child_idx][reportid_idx][rt_idx]->FirstBit != -1) &&
+								(coll_bit_range[collection_node_idx][reportid_idx][rt_idx]->FirstBit > coll_bit_range[child_idx][reportid_idx][rt_idx]->FirstBit)) {
+								coll_bit_range[collection_node_idx][reportid_idx][rt_idx]->FirstBit = coll_bit_range[child_idx][reportid_idx][rt_idx]->FirstBit;
+							}
+							if (coll_bit_range[collection_node_idx][reportid_idx][rt_idx]->LastBit < coll_bit_range[child_idx][reportid_idx][rt_idx]->LastBit) {
+								coll_bit_range[collection_node_idx][reportid_idx][rt_idx]->LastBit = coll_bit_range[child_idx][reportid_idx][rt_idx]->LastBit;
+							}
+							child_idx = link_collection_nodes[child_idx].NextSibling;
+						}
+					}
+				}
+			}
+		}
+	}
+
+	// *************************************************************************************************
+	// Determine child collection order of the whole hierachy, based on previously determined bit ranges
+	// and store it this index coll_child_order[COLLECTION_INDEX][DIRECT_CHILD_INDEX]
+	// *************************************************************************************************
+	USHORT **coll_child_order;
+	coll_child_order = malloc(pp_data->NumberLinkCollectionNodes * sizeof(*coll_child_order));
+	{
+		BOOLEAN *coll_parsed_flag;
+		coll_parsed_flag = malloc(pp_data->NumberLinkCollectionNodes * sizeof(coll_parsed_flag[0]));
+		for (USHORT collection_node_idx = 0; collection_node_idx < pp_data->NumberLinkCollectionNodes; collection_node_idx++) {
+			coll_parsed_flag[collection_node_idx] = FALSE;
+		}
+		int actual_coll_level = 0;
+		USHORT collection_node_idx = 0;
+		while (actual_coll_level >= 0) {
+			if ((coll_number_of_direct_childs[collection_node_idx] != 0) &&
+				(coll_parsed_flag[link_collection_nodes[collection_node_idx].FirstChild] == FALSE)) {
+				coll_parsed_flag[link_collection_nodes[collection_node_idx].FirstChild] = TRUE;
+				coll_child_order[collection_node_idx] = malloc((coll_number_of_direct_childs[collection_node_idx]) * sizeof(*coll_child_order[0]));
+
+				{
+					// Create list of child collection indices
+					// sorted reverse to the order returned to HidP_GetLinkCollectionNodeschild
+					// which seems to match teh original order, as long as no bit position needs to be considered
+					USHORT child_idx = link_collection_nodes[collection_node_idx].FirstChild;
+					int child_count = coll_number_of_direct_childs[collection_node_idx] - 1;
+					coll_child_order[collection_node_idx][child_count] = child_idx;
+					while (link_collection_nodes[child_idx].NextSibling) {
+						child_count--;
+						child_idx = link_collection_nodes[child_idx].NextSibling;
+						coll_child_order[collection_node_idx][child_count] = child_idx;
+					}
+				}
+
+				if (coll_number_of_direct_childs[collection_node_idx] > 1) {
+					// Sort child collections indices by bit positions
+					for (HIDP_REPORT_TYPE rt_idx = 0; rt_idx < NUM_OF_HIDP_REPORT_TYPES; rt_idx++) {
+						for (int reportid_idx = 0; reportid_idx < 256; reportid_idx++) {
+							for (int child_idx = 1; child_idx < coll_number_of_direct_childs[collection_node_idx]; child_idx++) {
+								// since the coll_bit_range array is not sorted, we need to reference the collection index in 
+								// our sorted coll_child_order array, and look up the corresponding bit ranges for comparing values to sort
+								int prev_coll_idx = coll_child_order[collection_node_idx][child_idx - 1];
+								int cur_coll_idx = coll_child_order[collection_node_idx][child_idx];
+								if ((coll_bit_range[prev_coll_idx][reportid_idx][rt_idx]->FirstBit != -1) &&
+									(coll_bit_range[cur_coll_idx][reportid_idx][rt_idx]->FirstBit != -1) &&
+									(coll_bit_range[prev_coll_idx][reportid_idx][rt_idx]->FirstBit > coll_bit_range[cur_coll_idx][reportid_idx][rt_idx]->FirstBit)) {
+									// Swap position indices of the two compared child collections
+									USHORT idx_latch = coll_child_order[collection_node_idx][child_idx - 1];
+									coll_child_order[collection_node_idx][child_idx - 1] = coll_child_order[collection_node_idx][child_idx];
+									coll_child_order[collection_node_idx][child_idx] = idx_latch;
+								}
+							}
+						}
+					}
+				}
+				actual_coll_level++;
+				collection_node_idx = link_collection_nodes[collection_node_idx].FirstChild;
+			}
+			else if (link_collection_nodes[collection_node_idx].NextSibling != 0) {
+				collection_node_idx = link_collection_nodes[collection_node_idx].NextSibling;
+			}
+			else {
+				actual_coll_level--;
+				if (actual_coll_level >= 0) {
+					collection_node_idx = link_collection_nodes[collection_node_idx].Parent;
+				}
+			}
+		}
+		free(coll_parsed_flag);
+	}
+
+
+	// ***************************************************************************************
+	// Create sorted main_item_list containing all the Collection and CollectionEnd main items
+	// ***************************************************************************************
+	struct rd_main_item_node *main_item_list = NULL; // List root
+	// Lookup table to find the Collection items in the list by index
+	struct rd_main_item_node **coll_begin_lookup = malloc(pp_data->NumberLinkCollectionNodes * sizeof(*coll_begin_lookup));
+	struct rd_main_item_node **coll_end_lookup = malloc(pp_data->NumberLinkCollectionNodes * sizeof(*coll_end_lookup));
+	{
+		int *coll_last_written_child = malloc(pp_data->NumberLinkCollectionNodes * sizeof(coll_last_written_child[0]));
+		for (USHORT collection_node_idx = 0; collection_node_idx < pp_data->NumberLinkCollectionNodes; collection_node_idx++) {
+			coll_last_written_child[collection_node_idx] = -1;
+		}
+
+		int actual_coll_level = 0;
+		USHORT collection_node_idx = 0;
+		struct rd_main_item_node *firstDelimiterNode = NULL;
+		struct rd_main_item_node *delimiterCloseNode = NULL;
+		coll_begin_lookup[0] = rd_append_main_item_node(0, 0, rd_item_node_collection, 0, collection_node_idx, rd_collection, 0, &main_item_list);
+		while (actual_coll_level >= 0) {
+			if ((coll_number_of_direct_childs[collection_node_idx] != 0) &&
+				(coll_last_written_child[collection_node_idx] == -1)) {
+				// Collection has child collections, but none is written to the list yet
+
+				coll_last_written_child[collection_node_idx] = coll_child_order[collection_node_idx][0];
+				collection_node_idx = coll_child_order[collection_node_idx][0];
+
+				// In a HID Report Descriptor, the first usage declared is the most preferred usage for the control.
+				// While the order in the WIN32 capabiliy strutures is the opposite:
+				// Here the preferred usage is the last aliased usage in the sequence.
+
+				if (link_collection_nodes[collection_node_idx].IsAlias && (firstDelimiterNode == NULL)) {
+					// Alliased Collection (First node in link_collection_nodes -> Last entry in report descriptor output)
+					firstDelimiterNode = main_item_list;
+					coll_begin_lookup[collection_node_idx] = rd_append_main_item_node(0, 0, rd_item_node_collection, 0, collection_node_idx, rd_delimiter_usage, 0, &main_item_list);
+					coll_begin_lookup[collection_node_idx] = rd_append_main_item_node(0, 0, rd_item_node_collection, 0, collection_node_idx, rd_delimiter_close, 0, &main_item_list);
+					delimiterCloseNode = main_item_list;
+				}
+				else {
+					// Normal not aliased collection
+					coll_begin_lookup[collection_node_idx] = rd_append_main_item_node(0, 0, rd_item_node_collection, 0, collection_node_idx, rd_collection, 0, &main_item_list);
+					actual_coll_level++;
+				}
+
+
+			}
+			else if ((coll_number_of_direct_childs[collection_node_idx] > 1) &&
+				(coll_last_written_child[collection_node_idx] != coll_child_order[collection_node_idx][coll_number_of_direct_childs[collection_node_idx] - 1])) {
+				// Collection has child collections, and this is not the first child
+
+				int nextChild = 1;
+				while (coll_last_written_child[collection_node_idx] != coll_child_order[collection_node_idx][nextChild - 1]) {
+					nextChild++;
+				}
+				coll_last_written_child[collection_node_idx] = coll_child_order[collection_node_idx][nextChild];
+				collection_node_idx = coll_child_order[collection_node_idx][nextChild];
+												
+				if (link_collection_nodes[collection_node_idx].IsAlias && (firstDelimiterNode == NULL)) {
+					// Alliased Collection (First node in link_collection_nodes -> Last entry in report descriptor output)
+					firstDelimiterNode = main_item_list;
+					coll_begin_lookup[collection_node_idx] = rd_append_main_item_node(0, 0, rd_item_node_collection, 0, collection_node_idx, rd_delimiter_usage, 0, &main_item_list);
+					coll_begin_lookup[collection_node_idx] = rd_append_main_item_node(0, 0, rd_item_node_collection, 0, collection_node_idx, rd_delimiter_close, 0, &main_item_list);
+					delimiterCloseNode = main_item_list;
+				}
+				else if (link_collection_nodes[collection_node_idx].IsAlias && (firstDelimiterNode != NULL)) {
+					coll_begin_lookup[collection_node_idx] = rd_insert_main_item_node(0, 0, rd_item_node_collection, 0, collection_node_idx, rd_delimiter_usage, 0, &firstDelimiterNode);
+				}
+				else if (!link_collection_nodes[collection_node_idx].IsAlias && (firstDelimiterNode != NULL)) {
+					coll_begin_lookup[collection_node_idx] = rd_insert_main_item_node(0, 0, rd_item_node_collection, 0, collection_node_idx, rd_delimiter_usage, 0, &firstDelimiterNode);
+					coll_begin_lookup[collection_node_idx] = rd_insert_main_item_node(0, 0, rd_item_node_collection, 0, collection_node_idx, rd_delimiter_open, 0, &firstDelimiterNode);
+					firstDelimiterNode = NULL;
+					main_item_list = delimiterCloseNode;
+					delimiterCloseNode = NULL; // Last entry of alias has .IsAlias == FALSE
+				}
+				if (!link_collection_nodes[collection_node_idx].IsAlias) {
+					coll_begin_lookup[collection_node_idx] = rd_append_main_item_node(0, 0, rd_item_node_collection, 0, collection_node_idx, rd_collection, 0, &main_item_list);
+					actual_coll_level++;
+				}
+			}
+			else {
+				actual_coll_level--;
+				coll_end_lookup[collection_node_idx] = rd_append_main_item_node(0, 0, rd_item_node_collection, 0, collection_node_idx, rd_collection_end, 0, &main_item_list);
+				collection_node_idx = link_collection_nodes[collection_node_idx].Parent;
+			}
+		}
+		free(coll_last_written_child);
+	}
+
+
+	// ****************************************************************
+	// Inserted Input/Output/Feature main items into the main_item_list
+	// in order of reconstructed bit positions
+	// ****************************************************************
+	for (HIDP_REPORT_TYPE rt_idx = 0; rt_idx < NUM_OF_HIDP_REPORT_TYPES; rt_idx++) {
+		// Add all value caps to node list
+		struct rd_main_item_node *firstDelimiterNode = NULL;
+		struct rd_main_item_node *delimiterCloseNode = NULL;
+		for (USHORT caps_idx = pp_data->caps_info[rt_idx].FirstCap; caps_idx < pp_data->caps_info[rt_idx].LastCap; caps_idx++) {
+			struct rd_main_item_node *coll_begin = coll_begin_lookup[pp_data->caps[caps_idx].LinkCollection];
+			int first_bit, last_bit;
+			first_bit = (pp_data->caps[caps_idx].BytePosition - 1) * 8 +
+				pp_data->caps[caps_idx].BitPosition;
+			last_bit = first_bit + pp_data->caps[caps_idx].ReportSize *
+				pp_data->caps[caps_idx].ReportCount - 1;
+
+			for (int child_idx = 0; child_idx < coll_number_of_direct_childs[pp_data->caps[caps_idx].LinkCollection]; child_idx++) {
+				// Determine in which section before/between/after child collection the item should be inserted
+				if (first_bit < coll_bit_range[coll_child_order[pp_data->caps[caps_idx].LinkCollection][child_idx]][pp_data->caps[caps_idx].ReportID][rt_idx]->FirstBit)
+				{
+					// Note, that the default value for undefined coll_bit_range is -1, which can't be greater than the bit position
+					break;
+				}
+				coll_begin = coll_end_lookup[coll_child_order[pp_data->caps[caps_idx].LinkCollection][child_idx]];
+			}
+			struct rd_main_item_node *list_node;
+			list_node = rd_search_main_item_list_for_bit_position(first_bit, (rd_main_items) rt_idx, pp_data->caps[caps_idx].ReportID, &coll_begin);
+
+			// In a HID Report Descriptor, the first usage declared is the most preferred usage for the control.
+			// While the order in the WIN32 capabiliy strutures is the opposite:
+			// Here the preferred usage is the last aliased usage in the sequence.
+
+			if (pp_data->caps[caps_idx].IsAlias && (firstDelimiterNode == NULL)) {
+				// Alliased Usage (First node in pp_data->caps -> Last entry in report descriptor output)
+				firstDelimiterNode = list_node;
+				rd_insert_main_item_node(first_bit, last_bit, rd_item_node_cap, caps_idx, pp_data->caps[caps_idx].LinkCollection, rd_delimiter_usage, pp_data->caps[caps_idx].ReportID, &list_node);
+				rd_insert_main_item_node(first_bit, last_bit, rd_item_node_cap, caps_idx, pp_data->caps[caps_idx].LinkCollection, rd_delimiter_close, pp_data->caps[caps_idx].ReportID, &list_node);
+				delimiterCloseNode = list_node;
+			} else if (pp_data->caps[caps_idx].IsAlias && (firstDelimiterNode != NULL)) {
+				rd_insert_main_item_node(first_bit, last_bit, rd_item_node_cap, caps_idx, pp_data->caps[caps_idx].LinkCollection, rd_delimiter_usage, pp_data->caps[caps_idx].ReportID, &list_node);
+			}
+			else if (!pp_data->caps[caps_idx].IsAlias && (firstDelimiterNode != NULL)) {
+				// Alliased Collection (Last node in pp_data->caps -> First entry in report descriptor output)
+				rd_insert_main_item_node(first_bit, last_bit, rd_item_node_cap, caps_idx, pp_data->caps[caps_idx].LinkCollection, rd_delimiter_usage, pp_data->caps[caps_idx].ReportID, &list_node);
+				rd_insert_main_item_node(first_bit, last_bit, rd_item_node_cap, caps_idx, pp_data->caps[caps_idx].LinkCollection, rd_delimiter_open, pp_data->caps[caps_idx].ReportID, &list_node);
+				firstDelimiterNode = NULL;
+				list_node = delimiterCloseNode;
+				delimiterCloseNode = NULL; // Last entry of alias has .IsAlias == FALSE
+			}
+			if (!pp_data->caps[caps_idx].IsAlias) {
+				rd_insert_main_item_node(first_bit, last_bit, rd_item_node_cap, caps_idx, pp_data->caps[caps_idx].LinkCollection, (rd_main_items) rt_idx, pp_data->caps[caps_idx].ReportID, &list_node);
+			}
+		}
+	}
+
+
+	// ***********************************************************
+	// Add const main items for padding to main_item_list
+	// -To fill all bit gaps
+	// -At each report end for 8bit padding
+	//  Note that information about the padding at the report end,
+	//  is not stored in the preparsed data, but in practice all
+	//  report descriptors seem to have it, as assumed here.
+	// ***********************************************************
+	{
+		int last_bit_position[NUM_OF_HIDP_REPORT_TYPES][256];
+		struct rd_main_item_node *last_report_item_lookup[NUM_OF_HIDP_REPORT_TYPES][256];
+		for (HIDP_REPORT_TYPE rt_idx = 0; rt_idx < NUM_OF_HIDP_REPORT_TYPES; rt_idx++) {
+			for (int reportid_idx = 0; reportid_idx < 256; reportid_idx++) {
+				last_bit_position[rt_idx][reportid_idx] = -1;
+				last_report_item_lookup[rt_idx][reportid_idx] = NULL;
+			}
+		}
+
+		struct rd_main_item_node *list = main_item_list; // List root;
+
+		while (list->next != NULL)
+		{
+			if ((list->MainItemType >= rd_input) &&
+				(list->MainItemType <= rd_feature)) {
+				// INPUT, OUTPUT or FEATURE
+				if (list->FirstBit != -1) {
+					if ((last_bit_position[list->MainItemType][list->ReportID] + 1 != list->FirstBit) &&
+						(last_report_item_lookup[list->MainItemType][list->ReportID] != NULL) &&
+						(last_report_item_lookup[list->MainItemType][list->ReportID]->FirstBit != list->FirstBit) // Happens in case of IsMultipleItemsForArray for multiple dedicated usages for a multi-button array
+						) {
+						struct rd_main_item_node *list_node = rd_search_main_item_list_for_bit_position(last_bit_position[list->MainItemType][list->ReportID], list->MainItemType, list->ReportID, &last_report_item_lookup[list->MainItemType][list->ReportID]);
+						rd_insert_main_item_node(last_bit_position[list->MainItemType][list->ReportID] + 1, list->FirstBit - 1, rd_item_node_padding, -1, 0, list->MainItemType, list->ReportID, &list_node);
+					}
+					last_bit_position[list->MainItemType][list->ReportID] = list->LastBit;
+					last_report_item_lookup[list->MainItemType][list->ReportID] = list;
+				}
+			}
+			list = list->next;
+		}
+		// Add 8 bit padding at each report end
+		for (HIDP_REPORT_TYPE rt_idx = 0; rt_idx < NUM_OF_HIDP_REPORT_TYPES; rt_idx++) {
+			for (int reportid_idx = 0; reportid_idx < 256; reportid_idx++) {
+				if (last_bit_position[rt_idx][reportid_idx] != -1) {
+					int padding = 8 - ((last_bit_position[rt_idx][reportid_idx] + 1) % 8);
+					if (padding < 8) {
+						// Insert padding item after item referenced in last_report_item_lookup
+						rd_insert_main_item_node(last_bit_position[rt_idx][reportid_idx] + 1, last_bit_position[rt_idx][reportid_idx] + padding, rd_item_node_padding, -1, 0, (rd_main_items) rt_idx, (unsigned char) reportid_idx, &last_report_item_lookup[rt_idx][reportid_idx]);
+					}
+				}
+			}
+		}
+	}
+
+
+	// ***********************************
+	// Encode the report descriptor output
+	// ***********************************
+	UCHAR last_report_id = 0;
+	USAGE last_usage_page = 0;
+	LONG last_physical_min = 0;// If both, Physical Minimum and Physical Maximum are 0, the logical limits should be taken as physical limits according USB HID spec 1.11 chapter 6.2.2.7
+	LONG last_physical_max = 0;
+	ULONG last_unit_exponent = 0; // If Unit Exponent is Undefined it should be considered as 0 according USB HID spec 1.11 chapter 6.2.2.7
+	ULONG last_unit = 0; // If the first nibble is 7, or second nibble of Unit is 0, the unit is None according USB HID spec 1.11 chapter 6.2.2.7
+	BOOLEAN inhibit_write_of_usage = FALSE; // Needed in case of delimited usage print, before the normal collection or cap
+	int report_count = 0;
+	while (main_item_list != NULL)
+	{
+		int rt_idx = main_item_list->MainItemType;
+		int	caps_idx = main_item_list->CapsIndex;
+		if (main_item_list->MainItemType == rd_collection) {
+			if (last_usage_page != link_collection_nodes[main_item_list->CollectionIndex].LinkUsagePage) {
+				// Write "Usage Page" at the begin of a collection - except it refers the same table as wrote last 
+				rd_write_short_item(rd_global_usage_page, link_collection_nodes[main_item_list->CollectionIndex].LinkUsagePage, &rpt_desc);
+				last_usage_page = link_collection_nodes[main_item_list->CollectionIndex].LinkUsagePage;
+			}
+			if (inhibit_write_of_usage) {
+				// Inhibit only once after DELIMITER statement
+				inhibit_write_of_usage = FALSE;
+			}
+			else {
+				// Write "Usage" of collection
+				rd_write_short_item(rd_local_usage, link_collection_nodes[main_item_list->CollectionIndex].LinkUsage, &rpt_desc);
+			}
+			// Write begin of "Collection" 
+			rd_write_short_item(rd_main_collection, link_collection_nodes[main_item_list->CollectionIndex].CollectionType, &rpt_desc);
+		}
+		else if (main_item_list->MainItemType == rd_collection_end) {
+			// Write "End Collection"
+			rd_write_short_item(rd_main_collection_end, 0, &rpt_desc);
+		}
+		else if (main_item_list->MainItemType == rd_delimiter_open) {
+			if (main_item_list->CollectionIndex != -1) {
+				// Write "Usage Page" inside of a collection delmiter section
+				if (last_usage_page != link_collection_nodes[main_item_list->CollectionIndex].LinkUsagePage) {
+					rd_write_short_item(rd_global_usage_page, link_collection_nodes[main_item_list->CollectionIndex].LinkUsagePage, &rpt_desc);
+					last_usage_page = link_collection_nodes[main_item_list->CollectionIndex].LinkUsagePage;
+				}
+			}
+			else if (main_item_list->CapsIndex != 0) {
+				// Write "Usage Page" inside of a main item delmiter section
+				if (pp_data->caps[caps_idx].UsagePage != last_usage_page) {
+					rd_write_short_item(rd_global_usage_page, pp_data->caps[caps_idx].UsagePage, &rpt_desc);
+					last_usage_page = pp_data->caps[caps_idx].UsagePage;
+				}
+			}
+			// Write "Delimiter Open"
+			rd_write_short_item(rd_local_delimiter, 1, &rpt_desc); // 1 = open set of aliased usages
+		}
+		else if (main_item_list->MainItemType == rd_delimiter_usage) {
+			if (main_item_list->CollectionIndex != -1) {
+				// Write aliased collection "Usage"
+				rd_write_short_item(rd_local_usage, link_collection_nodes[main_item_list->CollectionIndex].LinkUsage, &rpt_desc);
+			}  if (main_item_list->CapsIndex != 0) {
+				// Write aliased main item range from "Usage Minimum" to "Usage Maximum"
+				if (pp_data->caps[caps_idx].IsRange) {
+					rd_write_short_item(rd_local_usage_minimum, pp_data->caps[caps_idx].Range.UsageMin, &rpt_desc);
+					rd_write_short_item(rd_local_usage_maximum, pp_data->caps[caps_idx].Range.UsageMax, &rpt_desc);
+				}
+				else {
+					// Write single aliased main item "Usage"
+					rd_write_short_item(rd_local_usage, pp_data->caps[caps_idx].NotRange.Usage, &rpt_desc);
+				}
+			}
+		}
+		else if (main_item_list->MainItemType == rd_delimiter_close) {
+			// Write "Delimiter Close"
+			rd_write_short_item(rd_local_delimiter, 0, &rpt_desc); // 0 = close set of aliased usages
+			// Inhibit next usage write
+			inhibit_write_of_usage = TRUE;
+		}
+		else if (main_item_list->TypeOfNode == rd_item_node_padding) {
+			// Padding
+			// The preparsed data doesn't contain any information about padding. Therefore all undefined gaps
+			// in the reports are filled with the same style of constant padding. 
+
+			// Write "Report Size" with number of padding bits
+			rd_write_short_item(rd_global_report_size, (main_item_list->LastBit - main_item_list->FirstBit + 1), &rpt_desc);
+
+			// Write "Report Count" for padding always as 1
+			rd_write_short_item(rd_global_report_count, 1, &rpt_desc);
+
+			if (rt_idx == HidP_Input) {
+				// Write "Input" main item - We know it's Constant - We can only guess the other bits, but they don't matter in case of const
+				rd_write_short_item(rd_main_input, 0x03, &rpt_desc); // Const / Abs
+			}
+			else if (rt_idx == HidP_Output) {
+				// Write "Output" main item - We know it's Constant - We can only guess the other bits, but they don't matter in case of const
+				rd_write_short_item(rd_main_output, 0x03, &rpt_desc); // Const / Abs
+			}
+			else if (rt_idx == HidP_Feature) {
+				// Write "Feature" main item - We know it's Constant - We can only guess the other bits, but they don't matter in case of const
+				rd_write_short_item(rd_main_feature, 0x03, &rpt_desc); // Const / Abs
+			}
+			report_count = 0;
+		}
+		else if (pp_data->caps[caps_idx].IsButtonCap) {
+			// Button
+			// (The preparsed data contain different data for 1 bit Button caps, than for parametric Value caps)
+
+			if (last_report_id != pp_data->caps[caps_idx].ReportID) {
+				// Write "Report ID" if changed
+				rd_write_short_item(rd_global_report_id, pp_data->caps[caps_idx].ReportID, &rpt_desc);
+				last_report_id = pp_data->caps[caps_idx].ReportID;
+			}
+
+			// Write "Usage Page" when changed
+			if (pp_data->caps[caps_idx].UsagePage != last_usage_page) {
+				rd_write_short_item(rd_global_usage_page, pp_data->caps[caps_idx].UsagePage, &rpt_desc);
+				last_usage_page = pp_data->caps[caps_idx].UsagePage;
+			}
+
+			// Write only local report items for each cap, if ReportCount > 1
+			if (pp_data->caps[caps_idx].IsRange) {
+				report_count += (pp_data->caps[caps_idx].Range.DataIndexMax - pp_data->caps[caps_idx].Range.DataIndexMin);
+			}
+
+			if (inhibit_write_of_usage) {
+				// Inhibit only once after Delimiter - Reset flag
+				inhibit_write_of_usage = FALSE;
+			}
+			else {
+				if (pp_data->caps[caps_idx].IsRange) {
+					// Write range from "Usage Minimum" to "Usage Maximum"
+					rd_write_short_item(rd_local_usage_minimum, pp_data->caps[caps_idx].Range.UsageMin, &rpt_desc);
+					rd_write_short_item(rd_local_usage_maximum, pp_data->caps[caps_idx].Range.UsageMax, &rpt_desc);
+				}
+				else {
+					// Write single "Usage"
+					rd_write_short_item(rd_local_usage, pp_data->caps[caps_idx].NotRange.Usage, &rpt_desc);
+				}
+			}
+
+			if (pp_data->caps[caps_idx].IsDesignatorRange) {
+				// Write physical descriptor indices range from "Designator Minimum" to "Designator Maximum"
+				rd_write_short_item(rd_local_designator_minimum, pp_data->caps[caps_idx].Range.DesignatorMin, &rpt_desc);
+				rd_write_short_item(rd_local_designator_maximum, pp_data->caps[caps_idx].Range.DesignatorMax, &rpt_desc);
+			}
+			else if (pp_data->caps[caps_idx].NotRange.DesignatorIndex != 0) {
+				// Designator set 0 is a special descriptor set (of the HID Physical Descriptor),
+				// that specifies the number of additional descriptor sets.
+				// Therefore Designator Index 0 can never be a useful reference for a control and we can inhibit it.
+				// Write single "Designator Index"
+				rd_write_short_item(rd_local_designator_index, pp_data->caps[caps_idx].NotRange.DesignatorIndex, &rpt_desc);
+			}
+
+			if (pp_data->caps[caps_idx].IsStringRange) {
+				// Write range of indices of the USB string descriptor, from "String Minimum" to "String Maximum"
+				rd_write_short_item(rd_local_string_minimum, pp_data->caps[caps_idx].Range.StringMin, &rpt_desc);
+				rd_write_short_item(rd_local_string_maximum, pp_data->caps[caps_idx].Range.StringMax, &rpt_desc);
+			}
+			else if (pp_data->caps[caps_idx].NotRange.StringIndex != 0) {
+				// String Index 0 is a special entry of the USB string descriptor, that contains a list of supported languages,
+				// therefore Designator Index 0 can never be a useful reference for a control and we can inhibit it.
+				// Write single "String Index"
+				rd_write_short_item(rd_local_string, pp_data->caps[caps_idx].NotRange.StringIndex, &rpt_desc);
+			}
+
+			if ((main_item_list->next != NULL) &&
+				((int)main_item_list->next->MainItemType == rt_idx) &&
+				(main_item_list->next->TypeOfNode == rd_item_node_cap) &&
+				(pp_data->caps[main_item_list->next->CapsIndex].IsButtonCap) &&
+				(!pp_data->caps[caps_idx].IsRange) && // This node in list is no array
+				(!pp_data->caps[main_item_list->next->CapsIndex].IsRange) && // Next node in list is no array
+				(pp_data->caps[main_item_list->next->CapsIndex].UsagePage == pp_data->caps[caps_idx].UsagePage) &&
+				(pp_data->caps[main_item_list->next->CapsIndex].ReportID == pp_data->caps[caps_idx].ReportID) &&
+				(pp_data->caps[main_item_list->next->CapsIndex].BitField == pp_data->caps[caps_idx].BitField)
+				) {
+				if (main_item_list->next->FirstBit != main_item_list->FirstBit) {
+					// In case of IsMultipleItemsForArray for multiple dedicated usages for a multi-button array, the report count should be incremented 
+							
+					// Skip global items until any of them changes, than use ReportCount item to write the count of identical report fields
+					report_count++;
+				}
+			}
+			else {
+
+				if ((pp_data->caps[caps_idx].Button.LogicalMin == 0) &&
+					(pp_data->caps[caps_idx].Button.LogicalMax == 0)) {
+					// While a HID report descriptor must always contain LogicalMinimum and LogicalMaximum,
+					// the preparsed data contain both fields set to zero, for the case of simple buttons
+					// Write "Logical Minimum" set to 0 and "Logical Maximum" set to 1
+					rd_write_short_item(rd_global_logical_minimum, 0, &rpt_desc);
+					rd_write_short_item(rd_global_logical_maximum, 1, &rpt_desc);
+				}
+				else {
+					// Write logical range from "Logical Minimum" to "Logical Maximum"
+					rd_write_short_item(rd_global_logical_minimum, pp_data->caps[caps_idx].Button.LogicalMin, &rpt_desc);
+					rd_write_short_item(rd_global_logical_maximum, pp_data->caps[caps_idx].Button.LogicalMax, &rpt_desc);
+				}
+
+				// Write "Report Size"
+				rd_write_short_item(rd_global_report_size, pp_data->caps[caps_idx].ReportSize, &rpt_desc);
+
+				// Write "Report Count"
+				if (!pp_data->caps[caps_idx].IsRange) {
+					// Variable bit field with one bit per button
+					// In case of multiple usages with the same items, only "Usage" is written per cap, and "Report Count" is incremented
+					rd_write_short_item(rd_global_report_count, pp_data->caps[caps_idx].ReportCount + report_count, &rpt_desc);
+				}
+				else {
+					// Button array of "Report Size" x "Report Count
+					rd_write_short_item(rd_global_report_count, pp_data->caps[caps_idx].ReportCount, &rpt_desc);
+				}
+
+
+				// Buttons have only 1 bit and therefore no physical limits/units -> Set to undefined state
+				if (last_physical_min != 0) {
+					// Write "Physical Minimum", but only if changed
+					last_physical_min = 0;
+					rd_write_short_item(rd_global_physical_minimum, last_physical_min, &rpt_desc);
+				}
+				if (last_physical_max != 0) {
+					// Write "Physical Maximum", but only if changed
+					last_physical_max = 0;
+					rd_write_short_item(rd_global_physical_maximum, last_physical_max, &rpt_desc);
+				}
+				if (last_unit_exponent != 0) {
+					// Write "Unit Exponent", but only if changed
+					last_unit_exponent = 0;
+					rd_write_short_item(rd_global_unit_exponent, last_unit_exponent, &rpt_desc);
+				}
+				if (last_unit != 0) {
+					// Write "Unit",but only if changed
+					last_unit = 0;
+					rd_write_short_item(rd_global_unit, last_unit, &rpt_desc);
+				}
+
+				// Write "Input" main item
+				if (rt_idx == HidP_Input) {
+					rd_write_short_item(rd_main_input, pp_data->caps[caps_idx].BitField, &rpt_desc);
+				}
+				// Write "Output" main item
+				else if (rt_idx == HidP_Output) {
+					rd_write_short_item(rd_main_output, pp_data->caps[caps_idx].BitField, &rpt_desc);
+				}
+				// Write "Feature" main item
+				else if (rt_idx == HidP_Feature) {
+					rd_write_short_item(rd_main_feature, pp_data->caps[caps_idx].BitField, &rpt_desc);
+				}
+				report_count = 0;
+			}
+		}
+		else {
+
+			if (last_report_id != pp_data->caps[caps_idx].ReportID) {
+				// Write "Report ID" if changed
+				rd_write_short_item(rd_global_report_id, pp_data->caps[caps_idx].ReportID, &rpt_desc);
+				last_report_id = pp_data->caps[caps_idx].ReportID;
+			}
+
+			// Write "Usage Page" if changed
+			if (pp_data->caps[caps_idx].UsagePage != last_usage_page) {
+				rd_write_short_item(rd_global_usage_page, pp_data->caps[caps_idx].UsagePage, &rpt_desc);
+				last_usage_page = pp_data->caps[caps_idx].UsagePage;
+			}
+
+			if (inhibit_write_of_usage) {
+				// Inhibit only once after Delimiter - Reset flag
+				inhibit_write_of_usage = FALSE;
+			}
+			else {
+				if (pp_data->caps[caps_idx].IsRange) {
+					// Write usage range from "Usage Minimum" to "Usage Maximum"
+					rd_write_short_item(rd_local_usage_minimum, pp_data->caps[caps_idx].Range.UsageMin, &rpt_desc);
+					rd_write_short_item(rd_local_usage_maximum, pp_data->caps[caps_idx].Range.UsageMax, &rpt_desc);
+				}
+				else {
+					// Write single "Usage"
+					rd_write_short_item(rd_local_usage, pp_data->caps[caps_idx].NotRange.Usage, &rpt_desc);
+				}
+			}
+
+			if (pp_data->caps[caps_idx].IsDesignatorRange) {
+				// Write physical descriptor indices range from "Designator Minimum" to "Designator Maximum"
+				rd_write_short_item(rd_local_designator_minimum, pp_data->caps[caps_idx].Range.DesignatorMin, &rpt_desc);
+				rd_write_short_item(rd_local_designator_maximum, pp_data->caps[caps_idx].Range.DesignatorMax, &rpt_desc);
+			}
+			else if (pp_data->caps[caps_idx].NotRange.DesignatorIndex != 0) {
+				// Designator set 0 is a special descriptor set (of the HID Physical Descriptor),
+				// that specifies the number of additional descriptor sets.
+				// Therefore Designator Index 0 can never be a useful reference for a control and we can inhibit it.
+				// Write single "Designator Index"
+				rd_write_short_item(rd_local_designator_index, pp_data->caps[caps_idx].NotRange.DesignatorIndex, &rpt_desc);
+			}
+
+			if (pp_data->caps[caps_idx].IsStringRange) {
+				// Write range of indices of the USB string descriptor, from "String Minimum" to "String Maximum"
+				rd_write_short_item(rd_local_string_minimum, pp_data->caps[caps_idx].Range.StringMin, &rpt_desc);
+				rd_write_short_item(rd_local_string_maximum, pp_data->caps[caps_idx].Range.StringMax, &rpt_desc);
+			}
+			else if (pp_data->caps[caps_idx].NotRange.StringIndex != 0) {
+				// String Index 0 is a special entry of the USB string descriptor, that contains a list of supported languages,
+				// therefore Designator Index 0 can never be a useful reference for a control and we can inhibit it.
+				// Write single "String Index"
+				rd_write_short_item(rd_local_string, pp_data->caps[caps_idx].NotRange.StringIndex, &rpt_desc);
+			}
+
+			if ((pp_data->caps[caps_idx].BitField & 0x02) != 0x02) {
+				// In case of an value array overwrite "Report Count"
+				pp_data->caps[caps_idx].ReportCount = pp_data->caps[caps_idx].Range.DataIndexMax - pp_data->caps[caps_idx].Range.DataIndexMin + 1;
+			}
+
+
+			// Print only local report items for each cap, if ReportCount > 1
+			if ((main_item_list->next != NULL) &&
+				((int) main_item_list->next->MainItemType == rt_idx) &&
+				(main_item_list->next->TypeOfNode == rd_item_node_cap) &&
+				(!pp_data->caps[main_item_list->next->CapsIndex].IsButtonCap) &&
+				(!pp_data->caps[caps_idx].IsRange) && // This node in list is no array
+				(!pp_data->caps[main_item_list->next->CapsIndex].IsRange) && // Next node in list is no array
+				(pp_data->caps[main_item_list->next->CapsIndex].UsagePage == pp_data->caps[caps_idx].UsagePage) &&
+				(pp_data->caps[main_item_list->next->CapsIndex].NotButton.LogicalMin == pp_data->caps[caps_idx].NotButton.LogicalMin) &&
+				(pp_data->caps[main_item_list->next->CapsIndex].NotButton.LogicalMax == pp_data->caps[caps_idx].NotButton.LogicalMax) &&
+				(pp_data->caps[main_item_list->next->CapsIndex].NotButton.PhysicalMin == pp_data->caps[caps_idx].NotButton.PhysicalMin) &&
+				(pp_data->caps[main_item_list->next->CapsIndex].NotButton.PhysicalMax == pp_data->caps[caps_idx].NotButton.PhysicalMax) &&
+				(pp_data->caps[main_item_list->next->CapsIndex].UnitsExp == pp_data->caps[caps_idx].UnitsExp) &&
+				(pp_data->caps[main_item_list->next->CapsIndex].Units == pp_data->caps[caps_idx].Units) &&
+				(pp_data->caps[main_item_list->next->CapsIndex].ReportSize == pp_data->caps[caps_idx].ReportSize) &&
+				(pp_data->caps[main_item_list->next->CapsIndex].ReportID == pp_data->caps[caps_idx].ReportID) &&
+				(pp_data->caps[main_item_list->next->CapsIndex].BitField == pp_data->caps[caps_idx].BitField) &&
+				(pp_data->caps[main_item_list->next->CapsIndex].ReportCount == 1) &&
+				(pp_data->caps[caps_idx].ReportCount == 1)
+				) {
+				// Skip global items until any of them changes, than use ReportCount item to write the count of identical report fields
+				report_count++;
+			}
+			else {
+				// Value
+
+				// Write logical range from "Logical Minimum" to "Logical Maximum"
+				rd_write_short_item(rd_global_logical_minimum, pp_data->caps[caps_idx].NotButton.LogicalMin, &rpt_desc);
+				rd_write_short_item(rd_global_logical_maximum, pp_data->caps[caps_idx].NotButton.LogicalMax, &rpt_desc);
+
+				if ((last_physical_min != pp_data->caps[caps_idx].NotButton.PhysicalMin) ||
+					(last_physical_max != pp_data->caps[caps_idx].NotButton.PhysicalMax)) {
+					// Write range from "Physical Minimum" to " Physical Maximum", but only if one of them changed
+					rd_write_short_item(rd_global_physical_minimum, pp_data->caps[caps_idx].NotButton.PhysicalMin, &rpt_desc);
+					last_physical_min = pp_data->caps[caps_idx].NotButton.PhysicalMin;
+					rd_write_short_item(rd_global_physical_maximum, pp_data->caps[caps_idx].NotButton.PhysicalMax, &rpt_desc);
+					last_physical_max = pp_data->caps[caps_idx].NotButton.PhysicalMax;
+				}
+
+
+				if (last_unit_exponent != pp_data->caps[caps_idx].UnitsExp) {
+					// Write "Unit Exponent", but only if changed
+					rd_write_short_item(rd_global_unit_exponent, pp_data->caps[caps_idx].UnitsExp, &rpt_desc);
+					last_unit_exponent = pp_data->caps[caps_idx].UnitsExp;
+				}
+
+				if (last_unit != pp_data->caps[caps_idx].Units) {
+					// Write physical "Unit", but only if changed
+					rd_write_short_item(rd_global_unit, pp_data->caps[caps_idx].Units, &rpt_desc);
+					last_unit = pp_data->caps[caps_idx].Units;
+				}
+
+				// Write "Report Size"
+				rd_write_short_item(rd_global_report_size, pp_data->caps[caps_idx].ReportSize, &rpt_desc);
+
+				// Write "Report Count"
+				rd_write_short_item(rd_global_report_count, pp_data->caps[caps_idx].ReportCount + report_count, &rpt_desc);
+
+				if (rt_idx == HidP_Input) {
+					// Write "Input" main item
+					rd_write_short_item(rd_main_input, pp_data->caps[caps_idx].BitField, &rpt_desc);
+				}
+				else if (rt_idx == HidP_Output) {
+					// Write "Output" main item
+					rd_write_short_item(rd_main_output, pp_data->caps[caps_idx].BitField, &rpt_desc);
+				}
+				else if (rt_idx == HidP_Feature) {
+					// Write "Feature" main item
+					rd_write_short_item(rd_main_feature, pp_data->caps[caps_idx].BitField, &rpt_desc);
+				}
+				report_count = 0;
+			}
+		}
+
+		// Go to next item in main_item_list and free the memory of the actual item
+		struct rd_main_item_node *main_item_list_prev = main_item_list;
+		main_item_list = main_item_list->next;
+		free(main_item_list_prev);
+	}
+
+	// Free multidimensionable array: coll_bit_range[COLLECTION_INDEX][REPORT_ID][INPUT/OUTPUT/FEATURE]
+	// Free multidimensionable array: coll_child_order[COLLECTION_INDEX][DIRECT_CHILD_INDEX]
+	for (USHORT collection_node_idx = 0; collection_node_idx < pp_data->NumberLinkCollectionNodes; collection_node_idx++) {
+		for (int reportid_idx = 0; reportid_idx < 256; reportid_idx++) {
+			for (HIDP_REPORT_TYPE rt_idx = 0; rt_idx < NUM_OF_HIDP_REPORT_TYPES; rt_idx++) {
+				free(coll_bit_range[collection_node_idx][reportid_idx][rt_idx]);
+			}
+			free(coll_bit_range[collection_node_idx][reportid_idx]);
+		}
+		free(coll_bit_range[collection_node_idx]);
+		if (coll_number_of_direct_childs[collection_node_idx] != 0) free(coll_child_order[collection_node_idx]);
+	}
+	free(coll_bit_range);
+	free(coll_child_order);
+
+	// Free one dimensional arrays
+	free(coll_begin_lookup);
+	free(coll_end_lookup);
+	free(coll_levels);
+	free(coll_number_of_direct_childs);
+
+	return (int) rpt_desc.byte_idx;
+}

+ 238 - 0
src/hidapi/windows/hidapi_descriptor_reconstruct.h

@@ -0,0 +1,238 @@
+/*******************************************************
+ HIDAPI - Multi-Platform library for
+ communication with HID devices.
+
+ libusb/hidapi Team
+
+ Copyright 2022, All Rights Reserved.
+
+ At the discretion of the user of this library,
+ this software may be licensed under the terms of the
+ GNU General Public License v3, a BSD-Style license, or the
+ original HIDAPI license as outlined in the LICENSE.txt,
+ LICENSE-gpl3.txt, LICENSE-bsd.txt, and LICENSE-orig.txt
+ files located at the root of the source distribution.
+ These files may also be found in the public source
+ code repository located at:
+        https://github.com/libusb/hidapi .
+********************************************************/
+#ifndef HIDAPI_DESCRIPTOR_RECONSTRUCT_H__
+#define HIDAPI_DESCRIPTOR_RECONSTRUCT_H__
+
+#if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS)
+/* Do not warn about wcsncpy usage.
+   https://docs.microsoft.com/cpp/c-runtime-library/security-features-in-the-crt */
+#define _CRT_SECURE_NO_WARNINGS
+#endif
+
+#include "hidapi_winapi.h"
+
+#if _MSC_VER
+#pragma warning(push)
+#pragma warning(disable: 4200)
+#pragma warning(disable: 4201)
+#endif
+
+#include <windows.h>
+
+#include "hidapi_hidsdi.h"
+
+#define NUM_OF_HIDP_REPORT_TYPES 3
+
+typedef enum rd_items_ {
+	rd_main_input               = 0x80, /* 1000 00 nn */
+	rd_main_output              = 0x90, /* 1001 00 nn */
+	rd_main_feature             = 0xB0, /* 1011 00 nn */
+	rd_main_collection          = 0xA0, /* 1010 00 nn */
+	rd_main_collection_end      = 0xC0, /* 1100 00 nn */
+	rd_global_usage_page        = 0x04, /* 0000 01 nn */
+	rd_global_logical_minimum   = 0x14, /* 0001 01 nn */
+	rd_global_logical_maximum   = 0x24, /* 0010 01 nn */
+	rd_global_physical_minimum  = 0x34, /* 0011 01 nn */
+	rd_global_physical_maximum  = 0x44, /* 0100 01 nn */
+	rd_global_unit_exponent     = 0x54, /* 0101 01 nn */
+	rd_global_unit              = 0x64, /* 0110 01 nn */
+	rd_global_report_size       = 0x74, /* 0111 01 nn */
+	rd_global_report_id         = 0x84, /* 1000 01 nn */
+	rd_global_report_count      = 0x94, /* 1001 01 nn */
+	rd_global_push              = 0xA4, /* 1010 01 nn */
+	rd_global_pop               = 0xB4, /* 1011 01 nn */
+	rd_local_usage              = 0x08, /* 0000 10 nn */
+	rd_local_usage_minimum      = 0x18, /* 0001 10 nn */
+	rd_local_usage_maximum      = 0x28, /* 0010 10 nn */
+	rd_local_designator_index   = 0x38, /* 0011 10 nn */
+	rd_local_designator_minimum = 0x48, /* 0100 10 nn */
+	rd_local_designator_maximum = 0x58, /* 0101 10 nn */
+	rd_local_string             = 0x78, /* 0111 10 nn */
+	rd_local_string_minimum     = 0x88, /* 1000 10 nn */
+	rd_local_string_maximum     = 0x98, /* 1001 10 nn */
+	rd_local_delimiter          = 0xA8  /* 1010 10 nn */
+} rd_items;
+
+typedef enum rd_main_items_ {
+	rd_input = HidP_Input,
+	rd_output = HidP_Output,
+	rd_feature = HidP_Feature,
+	rd_collection,
+	rd_collection_end,
+	rd_delimiter_open,
+	rd_delimiter_usage,
+	rd_delimiter_close,
+} rd_main_items;
+
+typedef struct rd_bit_range_ {
+	int FirstBit;
+	int LastBit;
+} rd_bit_range;
+
+typedef enum rd_item_node_type_ {
+	rd_item_node_cap,
+	rd_item_node_padding,
+	rd_item_node_collection,
+} rd_node_type;
+
+struct rd_main_item_node {
+	int FirstBit; /* Position of first bit in report (counting from 0) */
+	int LastBit; /* Position of last bit in report (counting from 0) */
+	rd_node_type TypeOfNode; /* Information if caps index refers to the array of button caps, value caps,
+	                            or if the node is just a padding element to fill unused bit positions.
+	                            The node can also be a collection node without any bits in the report. */
+	int CapsIndex; /* Index in the array of caps */
+	int CollectionIndex; /* Index in the array of link collections */
+	rd_main_items MainItemType; /* Input, Output, Feature, Collection or Collection End */
+	unsigned char ReportID;
+	struct rd_main_item_node* next;
+};
+
+typedef struct hid_pp_caps_info_ {
+	USHORT FirstCap;
+	USHORT NumberOfCaps; // Includes empty caps after LastCap 
+	USHORT LastCap;
+	USHORT ReportByteLength;
+} hid_pp_caps_info, *phid_pp_caps_info;
+
+typedef struct hid_pp_link_collection_node_ {
+	USAGE  LinkUsage;
+	USAGE  LinkUsagePage;
+	USHORT Parent;
+	USHORT NumberOfChildren;
+	USHORT NextSibling;
+	USHORT FirstChild;
+	ULONG  CollectionType : 8;
+	ULONG  IsAlias : 1;
+	ULONG  Reserved : 23;
+	// Same as the public API structure HIDP_LINK_COLLECTION_NODE, but without PVOID UserContext at the end
+} hid_pp_link_collection_node, *phid_pp_link_collection_node;
+
+typedef struct hidp_unknown_token_ {
+	UCHAR Token; /* Specifies the one-byte prefix of a global item. */
+	UCHAR Reserved[3];
+	ULONG BitField; /* Specifies the data part of the global item. */
+} hidp_unknown_token, * phidp_unknown_token;
+
+typedef struct hid_pp_cap_ {
+	USAGE   UsagePage;
+	UCHAR   ReportID;
+	UCHAR   BitPosition;
+	USHORT  ReportSize; // WIN32 term for this is BitSize
+	USHORT  ReportCount;
+	USHORT  BytePosition;
+	USHORT  BitCount;
+	ULONG   BitField;
+	USHORT  NextBytePosition;
+	USHORT  LinkCollection;
+	USAGE   LinkUsagePage;
+	USAGE   LinkUsage;
+
+	// Start of 8 Flags in one byte
+	BOOLEAN IsMultipleItemsForArray:1;
+
+	BOOLEAN IsPadding:1;
+	BOOLEAN IsButtonCap:1;
+	BOOLEAN IsAbsolute:1;
+	BOOLEAN IsRange:1;
+	BOOLEAN IsAlias:1; // IsAlias is set to TRUE in the first n-1 capability structures added to the capability array. IsAlias set to FALSE in the nth capability structure.
+	BOOLEAN IsStringRange:1;
+	BOOLEAN IsDesignatorRange:1;
+	// End of 8 Flags in one byte
+	BOOLEAN Reserved1[3];
+
+	hidp_unknown_token UnknownTokens[4]; // 4 x 8 Byte
+
+	union {
+		struct {
+			USAGE  UsageMin;
+			USAGE  UsageMax;
+			USHORT StringMin;
+			USHORT StringMax;
+			USHORT DesignatorMin;
+			USHORT DesignatorMax;
+			USHORT DataIndexMin;
+			USHORT DataIndexMax;
+		} Range;
+		struct {
+			USAGE  Usage;
+			USAGE  Reserved1;
+			USHORT StringIndex;
+			USHORT Reserved2;
+			USHORT DesignatorIndex;
+			USHORT Reserved3;
+			USHORT DataIndex;
+			USHORT Reserved4;
+		} NotRange;
+	};
+	union {
+		struct {
+			LONG    LogicalMin;
+			LONG    LogicalMax;
+		} Button;
+		struct {
+			BOOLEAN HasNull;
+			UCHAR   Reserved4[3];
+			LONG    LogicalMin;
+			LONG    LogicalMax;
+			LONG    PhysicalMin;
+			LONG    PhysicalMax;
+		} NotButton;
+	};
+	ULONG   Units;
+	ULONG   UnitsExp;
+
+} hid_pp_cap, *phid_pp_cap;
+
+typedef struct hidp_preparsed_data_ {
+	UCHAR MagicKey[8];
+	USAGE Usage;
+	USAGE UsagePage;
+	USHORT Reserved[2];
+
+	// CAPS structure for Input, Output and Feature
+	hid_pp_caps_info caps_info[3];
+
+	USHORT FirstByteOfLinkCollectionArray;
+	USHORT NumberLinkCollectionNodes;
+
+#if defined(__MINGW32__) || defined(__CYGWIN__)
+	// MINGW fails with: Flexible array member in union not supported
+	// Solution: https://gcc.gnu.org/onlinedocs/gcc/Zero-Length.html
+	union {
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wpedantic"
+		hid_pp_cap caps[0];
+		hid_pp_link_collection_node LinkCollectionArray[0];
+#pragma GCC diagnostic pop
+	};
+#else
+	union {
+		hid_pp_cap caps[];
+		hid_pp_link_collection_node LinkCollectionArray[];
+	};
+#endif
+
+} hidp_preparsed_data;
+
+#ifdef _MSC_VER
+#pragma warning(pop)
+#endif
+
+#endif

+ 7 - 0
src/hidapi/windows/hidapi_hidpi.h

@@ -29,6 +29,13 @@
 /* This part of the header mimics hidpi.h,
     but only what is used by HIDAPI */
 
+typedef enum _HIDP_REPORT_TYPE
+{
+    HidP_Input,
+    HidP_Output,
+    HidP_Feature
+} HIDP_REPORT_TYPE;
+
 typedef struct _HIDP_PREPARSED_DATA * PHIDP_PREPARSED_DATA;
 
 typedef struct _HIDP_CAPS

+ 2 - 1
src/hidapi/windows/hidapi_hidsdi.h

@@ -40,6 +40,8 @@ typedef struct _HIDD_ATTRIBUTES{
 	USHORT VersionNumber;
 } HIDD_ATTRIBUTES, *PHIDD_ATTRIBUTES;
 
+typedef struct _HIDP_PREPARSED_DATA * PHIDP_PREPARSED_DATA;
+
 typedef void (__stdcall *HidD_GetHidGuid_)(LPGUID hid_guid);
 typedef BOOLEAN (__stdcall *HidD_GetAttributes_)(HANDLE device, PHIDD_ATTRIBUTES attrib);
 typedef BOOLEAN (__stdcall *HidD_GetSerialNumberString_)(HANDLE device, PVOID buffer, ULONG buffer_len);
@@ -52,7 +54,6 @@ typedef BOOLEAN (__stdcall *HidD_GetIndexedString_)(HANDLE handle, ULONG string_
 typedef BOOLEAN (__stdcall *HidD_GetPreparsedData_)(HANDLE handle, PHIDP_PREPARSED_DATA *preparsed_data);
 typedef BOOLEAN (__stdcall *HidD_FreePreparsedData_)(PHIDP_PREPARSED_DATA preparsed_data);
 typedef BOOLEAN (__stdcall *HidD_SetNumInputBuffers_)(HANDLE handle, ULONG number_buffers);
-typedef BOOLEAN (__stdcall *HidD_SetOutputReport_)(HANDLE handle, PVOID buffer, ULONG buffer_len);
 
 #endif
 

+ 74 - 0
src/hidapi/windows/hidapi_winapi.h

@@ -0,0 +1,74 @@
+/*******************************************************
+ HIDAPI - Multi-Platform library for
+ communication with HID devices.
+
+ libusb/hidapi Team
+
+ Copyright 2022, All Rights Reserved.
+
+ At the discretion of the user of this library,
+ this software may be licensed under the terms of the
+ GNU General Public License v3, a BSD-Style license, or the
+ original HIDAPI license as outlined in the LICENSE.txt,
+ LICENSE-gpl3.txt, LICENSE-bsd.txt, and LICENSE-orig.txt
+ files located at the root of the source distribution.
+ These files may also be found in the public source
+ code repository located at:
+        https://github.com/libusb/hidapi .
+********************************************************/
+
+/** @file
+ * @defgroup API hidapi API
+ *
+ * Since version 0.12.0, @ref HID_API_VERSION >= HID_API_MAKE_VERSION(0, 12, 0)
+ */
+
+#ifndef HIDAPI_WINAPI_H__
+#define HIDAPI_WINAPI_H__
+
+#include <stdint.h>
+
+#include <guiddef.h>
+
+#include "hidapi.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+		/** @brief Get the container ID for a HID device.
+
+			Since version 0.12.0, @ref HID_API_VERSION >= HID_API_MAKE_VERSION(0, 12, 0)
+
+			This function returns the `DEVPKEY_Device_ContainerId` property of
+			the given device. This can be used to correlate different
+			interfaces/ports on the same hardware device.
+
+			@ingroup API
+			@param dev A device handle returned from hid_open().
+			@param guid The device's container ID on return.
+
+			@returns
+				This function returns 0 on success and -1 on error.
+		*/
+		int HID_API_EXPORT_CALL hid_winapi_get_container_id(hid_device *dev, GUID *container_id);
+
+		/**
+		 * @brief Reconstructs a HID Report Descriptor from a Win32 HIDP_PREPARSED_DATA structure.
+		 *  This reconstructed report descriptor is logical identical to the real report descriptor,
+		 *  but not byte wise identical.
+		 *
+		 * @param[in]  hidp_preparsed_data Pointer to the HIDP_PREPARSED_DATA to read, i.e.: the value of PHIDP_PREPARSED_DATA,
+		 *   as returned by HidD_GetPreparsedData WinAPI function.
+		 * @param      buf       Pointer to the buffer where the report descriptor should be stored.
+		 * @param[in]  buf_size  Size of the buffer. The recommended size for the buffer is @ref HID_API_MAX_REPORT_DESCRIPTOR_SIZE bytes.
+		 *
+		 * @return Returns size of reconstructed report descriptor if successful, -1 for error.
+		 */
+		int HID_API_EXPORT_CALL hid_winapi_descriptor_reconstruct_pp_data(void *hidp_preparsed_data, unsigned char *buf, size_t buf_size);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif

+ 196 - 196
src/hidapi/windows/hidtest.vcproj

@@ -1,196 +1,196 @@
-<?xml version="1.0" encoding="Windows-1252"?>
-<VisualStudioProject
-	ProjectType="Visual C++"
-	Version="9.00"
-	Name="hidtest"
-	ProjectGUID="{23E9FF6A-49D1-4993-B2B5-BBB992C6C712}"
-	RootNamespace="hidtest"
-	TargetFrameworkVersion="196613"
-	>
-	<Platforms>
-		<Platform
-			Name="Win32"
-		/>
-	</Platforms>
-	<ToolFiles>
-	</ToolFiles>
-	<Configurations>
-		<Configuration
-			Name="Debug|Win32"
-			OutputDirectory="$(SolutionDir)$(ConfigurationName)"
-			IntermediateDirectory="$(ConfigurationName)"
-			ConfigurationType="1"
-			CharacterSet="2"
-			>
-			<Tool
-				Name="VCPreBuildEventTool"
-			/>
-			<Tool
-				Name="VCCustomBuildTool"
-			/>
-			<Tool
-				Name="VCXMLDataGeneratorTool"
-			/>
-			<Tool
-				Name="VCWebServiceProxyGeneratorTool"
-			/>
-			<Tool
-				Name="VCMIDLTool"
-			/>
-			<Tool
-				Name="VCCLCompilerTool"
-				Optimization="0"
-				AdditionalIncludeDirectories="..\hidapi"
-				MinimalRebuild="true"
-				BasicRuntimeChecks="3"
-				RuntimeLibrary="3"
-				WarningLevel="3"
-				DebugInformationFormat="4"
-			/>
-			<Tool
-				Name="VCManagedResourceCompilerTool"
-			/>
-			<Tool
-				Name="VCResourceCompilerTool"
-			/>
-			<Tool
-				Name="VCPreLinkEventTool"
-			/>
-			<Tool
-				Name="VCLinkerTool"
-				AdditionalDependencies="hidapi.lib"
-				AdditionalLibraryDirectories="..\windows\Debug"
-				GenerateDebugInformation="true"
-				SubSystem="1"
-				TargetMachine="1"
-			/>
-			<Tool
-				Name="VCALinkTool"
-			/>
-			<Tool
-				Name="VCManifestTool"
-			/>
-			<Tool
-				Name="VCXDCMakeTool"
-			/>
-			<Tool
-				Name="VCBscMakeTool"
-			/>
-			<Tool
-				Name="VCFxCopTool"
-			/>
-			<Tool
-				Name="VCAppVerifierTool"
-			/>
-			<Tool
-				Name="VCPostBuildEventTool"
-				Description="Copying hidapi.dll to the local direcotry."
-				CommandLine=""
-			/>
-		</Configuration>
-		<Configuration
-			Name="Release|Win32"
-			OutputDirectory="$(SolutionDir)$(ConfigurationName)"
-			IntermediateDirectory="$(ConfigurationName)"
-			ConfigurationType="1"
-			CharacterSet="2"
-			WholeProgramOptimization="1"
-			>
-			<Tool
-				Name="VCPreBuildEventTool"
-			/>
-			<Tool
-				Name="VCCustomBuildTool"
-			/>
-			<Tool
-				Name="VCXMLDataGeneratorTool"
-			/>
-			<Tool
-				Name="VCWebServiceProxyGeneratorTool"
-			/>
-			<Tool
-				Name="VCMIDLTool"
-			/>
-			<Tool
-				Name="VCCLCompilerTool"
-				Optimization="2"
-				EnableIntrinsicFunctions="true"
-				AdditionalIncludeDirectories="..\hidapi"
-				RuntimeLibrary="2"
-				EnableFunctionLevelLinking="true"
-				WarningLevel="3"
-				DebugInformationFormat="3"
-			/>
-			<Tool
-				Name="VCManagedResourceCompilerTool"
-			/>
-			<Tool
-				Name="VCResourceCompilerTool"
-			/>
-			<Tool
-				Name="VCPreLinkEventTool"
-			/>
-			<Tool
-				Name="VCLinkerTool"
-				AdditionalDependencies="hidapi.lib"
-				AdditionalLibraryDirectories="..\windows\Release"
-				GenerateDebugInformation="true"
-				SubSystem="1"
-				OptimizeReferences="2"
-				EnableCOMDATFolding="2"
-				TargetMachine="1"
-			/>
-			<Tool
-				Name="VCALinkTool"
-			/>
-			<Tool
-				Name="VCManifestTool"
-			/>
-			<Tool
-				Name="VCXDCMakeTool"
-			/>
-			<Tool
-				Name="VCBscMakeTool"
-			/>
-			<Tool
-				Name="VCFxCopTool"
-			/>
-			<Tool
-				Name="VCAppVerifierTool"
-			/>
-			<Tool
-				Name="VCPostBuildEventTool"
-				Description="Copying hidapi.dll to the local direcotry."
-				CommandLine=""
-			/>
-		</Configuration>
-	</Configurations>
-	<References>
-	</References>
-	<Files>
-		<Filter
-			Name="Source Files"
-			Filter="cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx"
-			UniqueIdentifier="{4FC737F1-C7A5-4376-A066-2A32D752A2FF}"
-			>
-			<File
-				RelativePath="..\hidtest\hidtest.cpp"
-				>
-			</File>
-		</Filter>
-		<Filter
-			Name="Header Files"
-			Filter="h;hpp;hxx;hm;inl;inc;xsd"
-			UniqueIdentifier="{93995380-89BD-4b04-88EB-625FBE52EBFB}"
-			>
-		</Filter>
-		<Filter
-			Name="Resource Files"
-			Filter="rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav"
-			UniqueIdentifier="{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}"
-			>
-		</Filter>
-	</Files>
-	<Globals>
-	</Globals>
-</VisualStudioProject>
+<?xml version="1.0" encoding="Windows-1252"?>
+<VisualStudioProject
+	ProjectType="Visual C++"
+	Version="9.00"
+	Name="hidtest"
+	ProjectGUID="{23E9FF6A-49D1-4993-B2B5-BBB992C6C712}"
+	RootNamespace="hidtest"
+	TargetFrameworkVersion="196613"
+	>
+	<Platforms>
+		<Platform
+			Name="Win32"
+		/>
+	</Platforms>
+	<ToolFiles>
+	</ToolFiles>
+	<Configurations>
+		<Configuration
+			Name="Debug|Win32"
+			OutputDirectory="$(SolutionDir)$(ConfigurationName)"
+			IntermediateDirectory="$(ConfigurationName)"
+			ConfigurationType="1"
+			CharacterSet="2"
+			>
+			<Tool
+				Name="VCPreBuildEventTool"
+			/>
+			<Tool
+				Name="VCCustomBuildTool"
+			/>
+			<Tool
+				Name="VCXMLDataGeneratorTool"
+			/>
+			<Tool
+				Name="VCWebServiceProxyGeneratorTool"
+			/>
+			<Tool
+				Name="VCMIDLTool"
+			/>
+			<Tool
+				Name="VCCLCompilerTool"
+				Optimization="0"
+				AdditionalIncludeDirectories="..\hidapi;."
+				MinimalRebuild="true"
+				BasicRuntimeChecks="3"
+				RuntimeLibrary="3"
+				WarningLevel="3"
+				DebugInformationFormat="4"
+			/>
+			<Tool
+				Name="VCManagedResourceCompilerTool"
+			/>
+			<Tool
+				Name="VCResourceCompilerTool"
+			/>
+			<Tool
+				Name="VCPreLinkEventTool"
+			/>
+			<Tool
+				Name="VCLinkerTool"
+				AdditionalDependencies="hidapi.lib"
+				AdditionalLibraryDirectories="..\windows\Debug"
+				GenerateDebugInformation="true"
+				SubSystem="1"
+				TargetMachine="1"
+			/>
+			<Tool
+				Name="VCALinkTool"
+			/>
+			<Tool
+				Name="VCManifestTool"
+			/>
+			<Tool
+				Name="VCXDCMakeTool"
+			/>
+			<Tool
+				Name="VCBscMakeTool"
+			/>
+			<Tool
+				Name="VCFxCopTool"
+			/>
+			<Tool
+				Name="VCAppVerifierTool"
+			/>
+			<Tool
+				Name="VCPostBuildEventTool"
+				Description="Copying hidapi.dll to the local directory."
+				CommandLine=""
+			/>
+		</Configuration>
+		<Configuration
+			Name="Release|Win32"
+			OutputDirectory="$(SolutionDir)$(ConfigurationName)"
+			IntermediateDirectory="$(ConfigurationName)"
+			ConfigurationType="1"
+			CharacterSet="2"
+			WholeProgramOptimization="1"
+			>
+			<Tool
+				Name="VCPreBuildEventTool"
+			/>
+			<Tool
+				Name="VCCustomBuildTool"
+			/>
+			<Tool
+				Name="VCXMLDataGeneratorTool"
+			/>
+			<Tool
+				Name="VCWebServiceProxyGeneratorTool"
+			/>
+			<Tool
+				Name="VCMIDLTool"
+			/>
+			<Tool
+				Name="VCCLCompilerTool"
+				Optimization="2"
+				EnableIntrinsicFunctions="true"
+				AdditionalIncludeDirectories="..\hidapi;."
+				RuntimeLibrary="2"
+				EnableFunctionLevelLinking="true"
+				WarningLevel="3"
+				DebugInformationFormat="3"
+			/>
+			<Tool
+				Name="VCManagedResourceCompilerTool"
+			/>
+			<Tool
+				Name="VCResourceCompilerTool"
+			/>
+			<Tool
+				Name="VCPreLinkEventTool"
+			/>
+			<Tool
+				Name="VCLinkerTool"
+				AdditionalDependencies="hidapi.lib"
+				AdditionalLibraryDirectories="..\windows\Release"
+				GenerateDebugInformation="true"
+				SubSystem="1"
+				OptimizeReferences="2"
+				EnableCOMDATFolding="2"
+				TargetMachine="1"
+			/>
+			<Tool
+				Name="VCALinkTool"
+			/>
+			<Tool
+				Name="VCManifestTool"
+			/>
+			<Tool
+				Name="VCXDCMakeTool"
+			/>
+			<Tool
+				Name="VCBscMakeTool"
+			/>
+			<Tool
+				Name="VCFxCopTool"
+			/>
+			<Tool
+				Name="VCAppVerifierTool"
+			/>
+			<Tool
+				Name="VCPostBuildEventTool"
+				Description="Copying hidapi.dll to the local directory."
+				CommandLine=""
+			/>
+		</Configuration>
+	</Configurations>
+	<References>
+	</References>
+	<Files>
+		<Filter
+			Name="Source Files"
+			Filter="cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx"
+			UniqueIdentifier="{4FC737F1-C7A5-4376-A066-2A32D752A2FF}"
+			>
+			<File
+				RelativePath="..\hidtest\test.c"
+				>
+			</File>
+		</Filter>
+		<Filter
+			Name="Header Files"
+			Filter="h;hpp;hxx;hm;inl;inc;xsd"
+			UniqueIdentifier="{93995380-89BD-4b04-88EB-625FBE52EBFB}"
+			>
+		</Filter>
+		<Filter
+			Name="Resource Files"
+			Filter="rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav"
+			UniqueIdentifier="{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}"
+			>
+		</Filter>
+	</Files>
+	<Globals>
+	</Globals>
+</VisualStudioProject>

Some files were not shown because too many files changed in this diff