├── .github ├── images │ └── gopher.png └── workflows │ ├── ci.yml │ └── release.yml ├── CHANGELOG.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── bustype_string.go ├── calloc.go ├── cmd └── lshid │ ├── .gitignore │ ├── doc.go │ └── main.go ├── example_test.go ├── go.mod ├── go.sum ├── hid.go ├── hid_darwin.c ├── hid_darwin.go ├── hid_libusb.c ├── hid_libusb.go ├── hid_linux.c ├── hid_windows.c ├── hid_windows.go ├── hidapi.h ├── hidapi_cfgmgr32.h ├── hidapi_darwin.h ├── hidapi_descriptor_reconstruct.c ├── hidapi_descriptor_reconstruct.h ├── hidapi_hidclass.h ├── hidapi_hidpi.h ├── hidapi_hidsdi.h ├── hidapi_libusb.h ├── hidapi_thread_pthread.h ├── hidapi_winapi.h ├── wchar.go └── wchar_windows.go /.github/images/gopher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sstallion/go-hid/3879eca0960538df22249770709b4ac27c8550d2/.github/images/gopher.png -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: 3 | pull_request: 4 | push: 5 | branches: 6 | - master 7 | jobs: 8 | test: 9 | runs-on: ${{ matrix.os }} 10 | strategy: 11 | fail-fast: false 12 | matrix: 13 | go-version: ['1.20', '1.21'] 14 | os: [ubuntu-latest, macos-latest, windows-latest] 15 | include: 16 | - os: ubuntu-latest 17 | prepare: | 18 | sudo apt-get update 19 | sudo apt-get install libudev-dev libusb-1.0-0-dev 20 | tags: libusb 21 | steps: 22 | - uses: actions/checkout@v4 23 | - name: Install dependencies 24 | if: ${{ matrix.prepare }} 25 | run: ${{ matrix.prepare }} 26 | - uses: actions/setup-go@v5 27 | with: 28 | go-version: ${{ matrix.go-version }} 29 | - name: Run tests 30 | run: go test ./... 31 | - name: Run tagged tests 32 | if: ${{ matrix.tags }} 33 | run: go test -tags ${{ matrix.tags }} ./... 34 | 35 | test-freebsd: 36 | runs-on: ubuntu-latest 37 | strategy: 38 | fail-fast: false 39 | matrix: 40 | go-version: ['1.20', '1.21'] 41 | include: 42 | - go-version: '1.20' 43 | go-pkg: go120 44 | - go-version: '1.21' 45 | go-pkg: go121 46 | steps: 47 | - uses: actions/checkout@v4 48 | - uses: vmactions/freebsd-vm@v1 49 | with: 50 | copyback: false 51 | release: '13.5' 52 | prepare: | 53 | pkg install -y ${{ matrix.go-pkg }} libiconv 54 | run: | 55 | ${{ matrix.go-pkg }} test ./... 56 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | on: 3 | push: 4 | tags: 5 | - v*.*.* 6 | jobs: 7 | release: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/checkout@v4 11 | - uses: ffurrer2/extract-release-notes@v2 12 | id: extract-release-notes 13 | - name: Generate release name 14 | run: | 15 | REF_NAME="${{ github.ref_name }}" 16 | echo "RELEASE_NAME=${REF_NAME#v}" >>$GITHUB_ENV 17 | - uses: softprops/action-gh-release@v2 18 | with: 19 | body: ${{ steps.extract-release-notes.outputs.release_notes }} 20 | name: ${{ env.RELEASE_NAME }} 21 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 6 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 7 | 8 | ## [Unreleased] 9 | 10 | ## [0.15.0] - 2025-05-23 11 | 12 | ### Changed 13 | 14 | - Imported 0.15.0 sources from [libusb/hidapi](https://github.com/libusb/hidapi) 15 | 16 | ### Added 17 | 18 | - Added support for `SendOutputReport` (`hid_send_output_report`) 19 | - Added support for `ReadError` (`hid_read_error`) 20 | - Added support for `SetWriteTimeout` (`hid_winapi_set_write_timeout`) for `windows` 21 | 22 | ## [0.14.1] - 2023-07-15 23 | 24 | ### Fixed 25 | 26 | - Fixed MBCS string support for `windows` ([MURAOKA Taro](https://github.com/koron)) 27 | 28 | ## [0.14.0] - 2023-06-05 29 | 30 | ### Added 31 | 32 | - Added missing optional APIs for `windows` 33 | 34 | ### Changed 35 | 36 | - Imported 0.14.0 sources from [libusb/hidapi](https://github.com/libusb/hidapi) 37 | 38 | ## [0.13.3] - 2023-02-27 39 | 40 | ### Added 41 | 42 | - Added libusb backend support for `linux` 43 | 44 | ## [0.13.2] - 2023-01-31 45 | 46 | ### Changed 47 | 48 | - Improved `lshid` device information formatting 49 | 50 | ### Fixed 51 | 52 | - `go.mod` now correctly references `go1.17` 53 | 54 | ## [0.13.1] - 2023-01-09 55 | 56 | ### Changed 57 | 58 | - Updated `lshid` flags; `-verbose` has been renamed to `-V` 59 | - Imported 0.13.1 sources from [libusb/hidapi](https://github.com/libusb/hidapi) 60 | 61 | ## [0.13.0] - 2023-01-06 62 | 63 | ### Changed 64 | 65 | - Imported 0.13.0 sources from [libusb/hidapi](https://github.com/libusb/hidapi) 66 | 67 | ## [0.12.4] - 2022-12-23 68 | 69 | ### Fixed 70 | 71 | - Guaranteed `hid.Device` satisfies `io.ReadWriteCloser` 72 | 73 | ## [0.12.3] - 2022-12-20 74 | 75 | ### Added 76 | 77 | - Added `/usr/local/lib` to library search path on FreeBSD 78 | 79 | ## [0.12.2] - 2022-12-20 80 | 81 | ### Changed 82 | 83 | - Renamed `hid.InterfaceAny` to `hid.InterfaceNbrAny` for consistency 84 | 85 | ## [0.12.1] - 2022-12-19 86 | 87 | ### Added 88 | 89 | - Added missing optional APIs for `darwin` and `freebsd` 90 | - Improved error reporting 91 | 92 | ## [0.12.0] - 2022-12-18 93 | 94 | ### Changed 95 | 96 | - Imported 0.12.0 sources from [libusb/hidapi](https://github.com/libusb/hidapi) 97 | 98 | ## [0.8.0] - 2022-12-18 99 | 100 | Historical release based on the original HIDAPI, updated to support Go Modules. 101 | See https://github.com/signal11/hidapi for details. 102 | 103 | [Unreleased]: https://github.com/sstallion/go-hid/compare/v0.15.0...HEAD 104 | [0.15.0]: https://github.com/sstallion/go-hid/releases/tag/v0.15.0 105 | [0.14.1]: https://github.com/sstallion/go-hid/releases/tag/v0.14.1 106 | [0.14.0]: https://github.com/sstallion/go-hid/releases/tag/v0.14.0 107 | [0.13.3]: https://github.com/sstallion/go-hid/releases/tag/v0.13.3 108 | [0.13.2]: https://github.com/sstallion/go-hid/releases/tag/v0.13.2 109 | [0.13.1]: https://github.com/sstallion/go-hid/releases/tag/v0.13.1 110 | [0.13.0]: https://github.com/sstallion/go-hid/releases/tag/v0.13.0 111 | [0.12.4]: https://github.com/sstallion/go-hid/releases/tag/v0.12.4 112 | [0.12.3]: https://github.com/sstallion/go-hid/releases/tag/v0.12.3 113 | [0.12.2]: https://github.com/sstallion/go-hid/releases/tag/v0.12.2 114 | [0.12.1]: https://github.com/sstallion/go-hid/releases/tag/v0.12.1 115 | [0.12.0]: https://github.com/sstallion/go-hid/releases/tag/v0.12.0 116 | [0.8.0]: https://github.com/sstallion/go-hid/releases/tag/v0.8.0 117 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | If you have an idea or feature request please open an [issue][1], even if you 4 | don't have time to contribute! 5 | 6 | ## Making Changes 7 | 8 | > [!NOTE] 9 | > This guide assumes you have a functioning Go 1.17 (or later) installation. 10 | 11 | To get started, [fork][2] this repository on GitHub and clone a working copy for 12 | development: 13 | 14 | ``` 15 | $ git clone git@github.com:YOUR-USERNAME/go-hid.git 16 | ``` 17 | 18 | Once you have finished making changes, run tests by issuing: 19 | 20 | ``` 21 | $ go test ./... 22 | ``` 23 | 24 | Finally, commit your changes and create a [pull request][3] against the default 25 | branch for review. At a minimum, there should be no test regressions and 26 | additional tests should be added for new functionality. 27 | 28 | ## Making Releases 29 | 30 | Making releases is automated by [GitHub Actions][4]. Releases are created from 31 | the default branch; as such, tests should be passing at all times. 32 | 33 | To make a release, perform the following: 34 | 35 | 1. Create a new section in [CHANGELOG.md][5] for the release, and move items 36 | from Unreleased to this section. Links should also be updated to point to the 37 | correct tags for comparison. 38 | 39 | 2. Commit outstanding changes by issuing: 40 | 41 | ``` 42 | $ git commit -a -m "Release v" 43 | ``` 44 | 45 | 3. Push changes and verify the results of the [CI][6] workflow. 46 | 47 | 4. Create a release tag from the default branch by issuing: 48 | 49 | ``` 50 | $ git tag -a -m "Release v" v 51 | ``` 52 | 53 | 5. Push the release tag to the remote repository and verify the results of the 54 | [Release][7] workflow: 55 | 56 | ``` 57 | $ git push origin --tags 58 | ``` 59 | 60 | ## License 61 | 62 | By contributing to this repository, you agree that your contributions will be 63 | licensed under its Simplified BSD License. 64 | 65 | [1]: https://github.com/sstallion/go-hid/issues 66 | [2]: https://docs.github.com/en/github/getting-started-with-github/fork-a-repo 67 | [3]: https://docs.github.com/en/github/collaborating-with-issues-and-pull-requests/creating-a-pull-request 68 | [4]: https://docs.github.com/en/actions 69 | [5]: https://github.com/sstallion/go-hid/blob/master/CHANGELOG.md 70 | [6]: https://github.com/sstallion/go-hid/actions/workflows/ci.yml 71 | [7]: https://github.com/sstallion/go-hid/actions/workflows/release.yml 72 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2025 Steven Stallion 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions 5 | are met: 6 | 1. Redistributions of source code must retain the above copyright 7 | notice, this list of conditions and the following disclaimer. 8 | 2. Redistributions in binary form must reproduce the above copyright 9 | notice, this list of conditions and the following disclaimer in the 10 | documentation and/or other materials provided with the distribution. 11 | 12 | THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS "AS IS" AND 13 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 14 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 15 | ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 16 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 17 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 18 | OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 19 | HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 20 | LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 21 | OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 22 | SUCH DAMAGE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # HIDAPI Bindings for Go 2 | 3 | ![](.github/images/gopher.png) 4 | 5 | [![](https://github.com/sstallion/go-hid/actions/workflows/ci.yml/badge.svg?branch=master)][1] 6 | [![](https://pkg.go.dev/badge/github.com/sstallion/go-hid)][2] 7 | [![](https://goreportcard.com/badge/github.com/sstallion/go-hid)][3] 8 | [![](https://img.shields.io/github/v/release/sstallion/go-hid)][4] 9 | [![](https://img.shields.io/github/license/sstallion/go-hid.svg)][5] 10 | 11 | Package `hid` provides an idiomatic interface to HIDAPI, a simple library for 12 | communicating with USB and Bluetooth HID devices on FreeBSD, Linux, macOS, and 13 | Windows. 14 | 15 | See https://github.com/libusb/hidapi for details. 16 | 17 | ## Installation 18 | 19 | To add package `hid` as a dependency or upgrade to its latest version, issue: 20 | 21 | ``` 22 | $ go get github.com/sstallion/go-hid@latest 23 | ``` 24 | 25 | > [!NOTE] 26 | > Prerequisites for building HIDAPI from source must be installed prior to 27 | > issuing `go get`. See [Prerequisites][6] for details. 28 | 29 | > [!IMPORTANT] 30 | > This package requires cgo. GCC must be installed and available on the system 31 | > PATH before compilation. See the [cgo documentation][7] for details. 32 | 33 | ### libusb Backend Support 34 | 35 | On Linux, the hidraw backend is enabled by default. If the libusb backend is 36 | desired, the `libusb` build constraint must be specified: 37 | 38 | ``` 39 | $ go build -tags libusb ./... 40 | ``` 41 | 42 | ### lshid 43 | 44 | A command named `lshid` is provided, which lists HID devices attached to the 45 | system. `lshid` may be installed by issuing: 46 | 47 | ``` 48 | $ go install github.com/sstallion/go-hid/cmd/lshid@latest 49 | ``` 50 | 51 | Once installed, issue `lshid -h` to show usage. 52 | 53 | ## Documentation 54 | 55 | Up-to-date documentation can be found on [pkg.go.dev][2] or by issuing the `go 56 | doc` command after installation: 57 | 58 | ``` 59 | $ go doc -all github.com/sstallion/go-hid 60 | ``` 61 | 62 | ## Contributing 63 | 64 | Pull requests are welcome! See [CONTRIBUTING.md][8] for details. 65 | 66 | ## License 67 | 68 | Source code in this repository is licensed under a Simplified BSD License. See 69 | [LICENSE][5] for details. 70 | 71 | [1]: https://github.com/sstallion/go-hid/actions/workflows/ci.yml 72 | [2]: https://pkg.go.dev/github.com/sstallion/go-hid 73 | [3]: https://goreportcard.com/report/github.com/sstallion/go-hid 74 | [4]: https://github.com/sstallion/go-hid/releases/latest 75 | [5]: https://github.com/sstallion/go-hid/blob/master/LICENSE 76 | [6]: https://github.com/libusb/hidapi/blob/master/BUILD.md#prerequisites 77 | [7]: https://pkg.go.dev/cmd/cgo 78 | [8]: https://github.com/sstallion/go-hid/blob/master/CONTRIBUTING.md 79 | -------------------------------------------------------------------------------- /bustype_string.go: -------------------------------------------------------------------------------- 1 | // Code generated by "stringer -type BusType -trimprefix=Bus"; DO NOT EDIT. 2 | 3 | package hid 4 | 5 | import "strconv" 6 | 7 | func _() { 8 | // An "invalid array index" compiler error signifies that the constant values have changed. 9 | // Re-run the stringer command to generate them again. 10 | var x [1]struct{} 11 | _ = x[BusUnknown-0] 12 | _ = x[BusUSB-1] 13 | _ = x[BusBluetooth-2] 14 | _ = x[BusI2C-3] 15 | _ = x[BusSPI-4] 16 | } 17 | 18 | const _BusType_name = "UnknownUSBBluetoothI2CSPI" 19 | 20 | var _BusType_index = [...]uint8{0, 7, 10, 19, 22, 25} 21 | 22 | func (i BusType) String() string { 23 | if i < 0 || i >= BusType(len(_BusType_index)-1) { 24 | return "BusType(" + strconv.FormatInt(int64(i), 10) + ")" 25 | } 26 | return _BusType_name[_BusType_index[i]:_BusType_index[i+1]] 27 | } 28 | -------------------------------------------------------------------------------- /calloc.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019 Steven Stallion 2 | // 3 | // Redistribution and use in source and binary forms, with or without 4 | // modification, are permitted provided that the following conditions 5 | // are met: 6 | // 1. Redistributions of source code must retain the above copyright 7 | // notice, this list of conditions and the following disclaimer. 8 | // 2. Redistributions in binary form must reproduce the above copyright 9 | // notice, this list of conditions and the following disclaimer in the 10 | // documentation and/or other materials provided with the distribution. 11 | // 12 | // THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS "AS IS" AND 13 | // ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 14 | // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 15 | // ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 16 | // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 17 | // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 18 | // OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 19 | // HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 20 | // LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 21 | // OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 22 | // SUCH DAMAGE. 23 | 24 | package hid 25 | 26 | /* 27 | #include 28 | */ 29 | import "C" 30 | 31 | import ( 32 | "unsafe" 33 | ) 34 | 35 | // calloc allocates memory for an array of n elements of size bytes each. It 36 | // provides a wrapper around C.calloc that guarantees to never return nil. The 37 | // returned pointer must be freed by the caller by calling C.free. 38 | func calloc(n, size C.size_t) unsafe.Pointer { 39 | p := C.calloc(n, size) 40 | if p == nil { 41 | panic("out of memory") 42 | } 43 | return p 44 | } 45 | -------------------------------------------------------------------------------- /cmd/lshid/.gitignore: -------------------------------------------------------------------------------- 1 | /lshid 2 | -------------------------------------------------------------------------------- /cmd/lshid/doc.go: -------------------------------------------------------------------------------- 1 | // Code generated by "doxxer . -h"; DO NOT EDIT. 2 | 3 | /* 4 | Lshid lists HID devices attached to the system. 5 | 6 | Usage: 7 | 8 | lshid [-V] [-v] [-vid vendor] [-pid product] 9 | 10 | Flags: 11 | 12 | -V Print HIDAPI version and exit 13 | -pid product 14 | Show devices with matching product ID 15 | -v Increase verbosity (show device information) 16 | -vid vendor 17 | Show devices with matching vendor ID 18 | 19 | Report issues to https://github.com/sstallion/go-hid/issues. 20 | */ 21 | package main 22 | -------------------------------------------------------------------------------- /cmd/lshid/main.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 Steven Stallion 2 | // 3 | // Redistribution and use in source and binary forms, with or without 4 | // modification, are permitted provided that the following conditions 5 | // are met: 6 | // 1. Redistributions of source code must retain the above copyright 7 | // notice, this list of conditions and the following disclaimer. 8 | // 2. Redistributions in binary form must reproduce the above copyright 9 | // notice, this list of conditions and the following disclaimer in the 10 | // documentation and/or other materials provided with the distribution. 11 | // 12 | // THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS "AS IS" AND 13 | // ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 14 | // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 15 | // ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 16 | // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 17 | // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 18 | // OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 19 | // HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 20 | // LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 21 | // OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 22 | // SUCH DAMAGE. 23 | 24 | //go:generate doxxer . -h 25 | package main 26 | 27 | import ( 28 | "flag" 29 | "fmt" 30 | "os" 31 | "strings" 32 | 33 | "github.com/sstallion/go-hid" 34 | "github.com/sstallion/go-tools/util" 35 | ) 36 | 37 | type versionFlag struct{} 38 | 39 | func (versionFlag) IsBoolFlag() bool { return true } 40 | func (versionFlag) String() string { return "" } 41 | func (versionFlag) Set(s string) error { 42 | fmt.Println("HIDAPI version", hid.GetVersionStr()) 43 | os.Exit(0) 44 | return nil 45 | } 46 | 47 | var ( 48 | verboseFlag bool 49 | vidFlag uint 50 | pidFlag uint 51 | ) 52 | 53 | func fmtRelease(n uint16) string { 54 | if n == 0 { 55 | return "(empty)" 56 | } 57 | return fmt.Sprintf("%#04x (%x.%x)", n, n>>8, n&0xff) 58 | } 59 | 60 | func fmtString(s string) string { 61 | s = strings.TrimSpace(s) 62 | if s == "" { 63 | return "(empty)" 64 | } 65 | return s 66 | } 67 | 68 | func usage() { 69 | util.PrintGlobalUsage(` 70 | Lshid lists HID devices attached to the system. 71 | 72 | Usage: 73 | 74 | {{ .Program }} [-V] [-v] [-vid vendor] [-pid product] 75 | 76 | Flags: 77 | 78 | {{ call .PrintDefaults }} 79 | 80 | Report issues to https://github.com/sstallion/go-hid/issues. 81 | `) 82 | } 83 | 84 | func main() { 85 | flag.Usage = usage 86 | flag.Var(versionFlag{}, "V", "Print HIDAPI version and exit") 87 | flag.BoolVar(&verboseFlag, "v", false, "Increase verbosity (show device information)") 88 | flag.UintVar(&vidFlag, "vid", hid.VendorIDAny, "Show devices with matching `vendor` ID") 89 | flag.UintVar(&pidFlag, "pid", hid.ProductIDAny, "Show devices with matching `product` ID") 90 | flag.Parse() 91 | 92 | vid, pid := uint16(vidFlag), uint16(pidFlag) 93 | hid.Enumerate(vid, pid, func(info *hid.DeviceInfo) error { 94 | fmt.Printf("%s: ID %04x:%04x %s %s\n", 95 | info.Path, info.VendorID, info.ProductID, info.MfrStr, info.ProductStr) 96 | if verboseFlag { 97 | fmt.Println("Device Information:") 98 | fmt.Printf("\tPath %s\n", info.Path) 99 | fmt.Printf("\tVendorID %#04x\n", info.VendorID) 100 | fmt.Printf("\tProductID %#04x\n", info.ProductID) 101 | fmt.Printf("\tSerialNbr %s\n", fmtString(info.SerialNbr)) 102 | fmt.Printf("\tReleaseNbr %s\n", fmtRelease(info.ReleaseNbr)) 103 | fmt.Printf("\tMfrStr %s\n", fmtString(info.MfrStr)) 104 | fmt.Printf("\tProductStr %s\n", fmtString(info.ProductStr)) 105 | fmt.Printf("\tUsagePage %#04x\n", info.UsagePage) 106 | fmt.Printf("\tUsage %#04x\n", info.Usage) 107 | fmt.Printf("\tInterfaceNbr %d\n", info.InterfaceNbr) 108 | fmt.Printf("\tBusType %s\n", info.BusType) 109 | fmt.Println() 110 | } 111 | return nil 112 | }) 113 | } 114 | -------------------------------------------------------------------------------- /example_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019 Steven Stallion 2 | // 3 | // Redistribution and use in source and binary forms, with or without 4 | // modification, are permitted provided that the following conditions 5 | // are met: 6 | // 1. Redistributions of source code must retain the above copyright 7 | // notice, this list of conditions and the following disclaimer. 8 | // 2. Redistributions in binary form must reproduce the above copyright 9 | // notice, this list of conditions and the following disclaimer in the 10 | // documentation and/or other materials provided with the distribution. 11 | // 12 | // THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS "AS IS" AND 13 | // ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 14 | // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 15 | // ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 16 | // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 17 | // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 18 | // OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 19 | // HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 20 | // LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 21 | // OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 22 | // SUCH DAMAGE. 23 | 24 | package hid_test 25 | 26 | import ( 27 | "fmt" 28 | "log" 29 | 30 | "github.com/sstallion/go-hid" 31 | ) 32 | 33 | // The following example was adapted from the HIDAPI documentation to 34 | // demonstrate use of the hid package to communicate with a simple device. 35 | func Example() { 36 | b := make([]byte, 65) 37 | 38 | // Initialize the hid package. 39 | if err := hid.Init(); err != nil { 40 | log.Fatal(err) 41 | } 42 | 43 | // Open the device using the VID and PID. 44 | d, err := hid.OpenFirst(0x4d8, 0x3f) 45 | if err != nil { 46 | log.Fatal(err) 47 | } 48 | 49 | // Read the Manufacturer String. 50 | s, err := d.GetMfrStr() 51 | if err != nil { 52 | log.Fatal(err) 53 | } 54 | fmt.Printf("Manufacturer String: %s\n", s) 55 | 56 | // Read the Product String. 57 | s, err = d.GetProductStr() 58 | if err != nil { 59 | log.Fatal(err) 60 | } 61 | fmt.Printf("Product String: %s\n", s) 62 | 63 | // Read the Serial Number String. 64 | s, err = d.GetSerialNbr() 65 | if err != nil { 66 | log.Fatal(err) 67 | } 68 | fmt.Printf("Serial Number String: %s\n", s) 69 | 70 | // Read Indexed String 1. 71 | s, err = d.GetIndexedStr(1) 72 | if err != nil { 73 | log.Fatal(err) 74 | } 75 | fmt.Printf("Indexed String 1: %s\n", s) 76 | 77 | // Toggle LED (cmd 0x80). The first byte is the report number (0x0). 78 | b[0] = 0x0 79 | b[1] = 0x80 80 | if _, err := d.Write(b); err != nil { 81 | log.Fatal(err) 82 | } 83 | 84 | // Request state (cmd 0x81). The first byte is the report number (0x0). 85 | b[0] = 0x0 86 | b[1] = 0x81 87 | if _, err := d.Write(b); err != nil { 88 | log.Fatal(err) 89 | } 90 | 91 | // Read requested state. 92 | if _, err := d.Read(b); err != nil { 93 | log.Fatal(err) 94 | } 95 | 96 | // Print out the returned buffer. 97 | for i, value := range b[0:3] { 98 | fmt.Printf("b[%d]: %d\n", i, value) 99 | } 100 | 101 | // Finalize the hid package. 102 | if err := hid.Exit(); err != nil { 103 | log.Fatal(err) 104 | } 105 | } 106 | 107 | // The following example demonstrates use of the Enumerate function to display 108 | // device information for all HID devices attached to the system. 109 | func ExampleEnumerate() { 110 | hid.Enumerate(hid.VendorIDAny, hid.ProductIDAny, func(info *hid.DeviceInfo) error { 111 | fmt.Printf("%s: ID %04x:%04x %s %s\n", 112 | info.Path, 113 | info.VendorID, 114 | info.ProductID, 115 | info.MfrStr, 116 | info.ProductStr) 117 | return nil 118 | }) 119 | } 120 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/sstallion/go-hid 2 | 3 | go 1.17 4 | 5 | require ( 6 | github.com/sstallion/go-tools v1.0.1 7 | golang.org/x/sys v0.8.0 8 | ) 9 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/sstallion/go-tools v1.0.1 h1:lPKy89IXk8/a8CXflTktpKJohY5oGSQu5J8vCM6spdA= 2 | github.com/sstallion/go-tools v1.0.1/go.mod h1:y3Rklut4T6cPLmNkaU0obckQpnVSSvAZlB2N87qgUtg= 3 | golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU= 4 | golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 5 | -------------------------------------------------------------------------------- /hid.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2025 Steven Stallion 2 | // 3 | // Redistribution and use in source and binary forms, with or without 4 | // modification, are permitted provided that the following conditions 5 | // are met: 6 | // 1. Redistributions of source code must retain the above copyright 7 | // notice, this list of conditions and the following disclaimer. 8 | // 2. Redistributions in binary form must reproduce the above copyright 9 | // notice, this list of conditions and the following disclaimer in the 10 | // documentation and/or other materials provided with the distribution. 11 | // 12 | // THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS "AS IS" AND 13 | // ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 14 | // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 15 | // ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 16 | // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 17 | // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 18 | // OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 19 | // HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 20 | // LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 21 | // OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 22 | // SUCH DAMAGE. 23 | 24 | // Package hid provides an idiomatic interface to HIDAPI, a simple library for 25 | // communicating with USB and Bluetooth HID devices on FreeBSD, Linux, macOS, 26 | // and Windows. 27 | // 28 | // See https://github.com/libusb/hidapi for details. 29 | package hid 30 | 31 | /* 32 | #cgo darwin LDFLAGS: -framework IOKit -framework CoreFoundation 33 | #cgo freebsd CFLAGS: -I/usr/local/include 34 | #cgo freebsd LDFLAGS: -L/usr/local/lib -lusb -liconv -pthread 35 | #cgo linux LDFLAGS: -ludev -lrt 36 | #cgo linux,libusb pkg-config: libusb-1.0 37 | #cgo linux,libusb LDFLAGS: -lpthread 38 | 39 | #include 40 | #include 41 | #include "hidapi.h" 42 | */ 43 | import "C" 44 | 45 | import ( 46 | "errors" 47 | "io" 48 | "math" 49 | "time" 50 | "unsafe" 51 | ) 52 | 53 | // VendorIDAny and ProductIDAny can be passed to the Enumerate function to 54 | // match any vendor or product ID, respectively. 55 | const ( 56 | VendorIDAny = 0 57 | ProductIDAny = 0 58 | ) 59 | 60 | // maxStrLen is the maximum length of a string descriptor (bLength). 61 | const maxStrLen = math.MaxUint8 62 | 63 | // ErrTimeout is returned if a blocking operation times out before completing. 64 | var ErrTimeout = errors.New("timeout") 65 | 66 | func wrapErr(err error) error { 67 | if err == nil { 68 | return errors.New("unspecified error") 69 | } 70 | return err 71 | } 72 | 73 | // Init initializes the hid package. Calling this function is not strictly 74 | // necessary, however it is recommended for concurrent programs. 75 | func Init() error { 76 | if res := C.hid_init(); res == -1 { 77 | return wrapErr(Error()) 78 | } 79 | return nil 80 | } 81 | 82 | // Exit finalizes the hid package. This function should be called after all 83 | // device handles have been closed to avoid memory leaks. 84 | func Exit() error { 85 | if res := C.hid_exit(); res == -1 { 86 | return wrapErr(Error()) 87 | } 88 | return nil 89 | } 90 | 91 | // BusType describes the underlying bus type. 92 | type BusType int 93 | 94 | //go:generate stringer -type BusType -trimprefix=Bus 95 | const ( 96 | BusUnknown BusType = iota 97 | BusUSB 98 | BusBluetooth 99 | BusI2C 100 | BusSPI 101 | ) 102 | 103 | // DeviceInfo describes a HID device attached to the system. 104 | type DeviceInfo struct { 105 | Path string // Platform-Specific Device Path 106 | VendorID uint16 // Device Vendor ID 107 | ProductID uint16 // Device Product ID 108 | SerialNbr string // Serial Number 109 | ReleaseNbr uint16 // Device Version Number 110 | MfrStr string // Manufacturer String 111 | ProductStr string // Product String 112 | UsagePage uint16 // Usage Page for Device/Interface 113 | Usage uint16 // Usage for Device/Interface 114 | InterfaceNbr int // USB Interface Number 115 | BusType BusType // Underlying Bus Type 116 | } 117 | 118 | func newDeviceInfo(p *C.struct_hid_device_info) *DeviceInfo { 119 | return &DeviceInfo{ 120 | Path: C.GoString(p.path), 121 | VendorID: uint16(p.vendor_id), 122 | ProductID: uint16(p.product_id), 123 | SerialNbr: wcstogo(p.serial_number), 124 | ReleaseNbr: uint16(p.release_number), 125 | MfrStr: wcstogo(p.manufacturer_string), 126 | ProductStr: wcstogo(p.product_string), 127 | UsagePage: uint16(p.usage_page), 128 | Usage: uint16(p.usage), 129 | InterfaceNbr: int(p.interface_number), 130 | BusType: BusType(p.bus_type), 131 | } 132 | } 133 | 134 | // EnumFunc is the type of the function called for each HID device attached to 135 | // the system visited by Enumerate. The information provided by the DeviceInfo 136 | // type can be passed to Open to open the device. 137 | type EnumFunc func(info *DeviceInfo) error 138 | 139 | // Enumerate visits each HID device attached to the system with a matching 140 | // vendor and product ID. To match multiple devices, VendorIDAny and 141 | // ProductIDAny can be passed to this function. If an error is returned by 142 | // EnumFunc, Enumerate will return immediately with the original error. 143 | func Enumerate(vid, pid uint16, enumFn EnumFunc) error { 144 | p := C.hid_enumerate(C.uint16_t(vid), C.uint16_t(pid)) 145 | defer C.hid_free_enumeration(p) 146 | 147 | for p != nil { 148 | if err := enumFn(newDeviceInfo(p)); err != nil { 149 | return err 150 | } 151 | p = p.next 152 | } 153 | return nil 154 | } 155 | 156 | // Device is a HID device attached to the system. 157 | type Device struct { 158 | handle *C.hid_device 159 | } 160 | 161 | // Open opens a HID device attached to the system with a matching vendor ID, 162 | // product ID, and serial number. It returns an open device handle and an 163 | // error, if any. 164 | func Open(vid, pid uint16, serial string) (*Device, error) { 165 | wcs := gotowcs(serial) 166 | defer C.free(unsafe.Pointer(wcs)) 167 | 168 | handle := C.hid_open(C.uint16_t(vid), C.uint16_t(pid), wcs) 169 | if handle == nil { 170 | return nil, wrapErr(Error()) 171 | } 172 | return &Device{handle}, nil 173 | } 174 | 175 | // OpenFirst opens the first HID device attached to the system with a matching 176 | // vendor ID, and product ID. It returns an open device handle and an error, 177 | // if any. 178 | func OpenFirst(vid, pid uint16) (*Device, error) { 179 | handle := C.hid_open(C.uint16_t(vid), C.uint16_t(pid), nil) 180 | if handle == nil { 181 | return nil, wrapErr(Error()) 182 | } 183 | return &Device{handle}, nil 184 | } 185 | 186 | // OpenPath opens the HID device attached to the system with the given path. 187 | // It returns an open device handle and an error, if any. 188 | func OpenPath(path string) (*Device, error) { 189 | cs := C.CString(path) 190 | defer C.free(unsafe.Pointer(cs)) 191 | 192 | handle := C.hid_open_path(cs) 193 | if handle == nil { 194 | return nil, wrapErr(Error()) 195 | } 196 | return &Device{handle}, nil 197 | } 198 | 199 | // Write sends an output report with len(p) bytes to the Device. It returns 200 | // the number of bytes written and an error, if any. 201 | // 202 | // The first byte must contain the report ID; 0 should be used for devices 203 | // which only support a single report. Data will be sent over the first OUT 204 | // endpoint if it exists, otherwise the control endpoint will be used. 205 | func (d *Device) Write(p []byte) (int, error) { 206 | data := (*C.uchar)(&p[0]) 207 | length := C.size_t(len(p)) 208 | 209 | res := C.hid_write(d.handle, data, length) 210 | if res == -1 { 211 | return int(res), wrapErr(d.Error()) 212 | } 213 | return int(res), nil 214 | } 215 | 216 | // ReadWithTimeout receives an input report with len(p) bytes from the Device 217 | // with the specified timeout. It returns the number of bytes read and an 218 | // error, if any. ReadWithTimeout returns ErrTimeout if the operation timed 219 | // out before completing. 220 | // 221 | // If the device supports multiple reports, the first byte will contain the 222 | // report ID. 223 | func (d *Device) ReadWithTimeout(p []byte, timeout time.Duration) (int, error) { 224 | data := (*C.uchar)(&p[0]) 225 | length := C.size_t(len(p)) 226 | milliseconds := C.int(timeout / time.Millisecond) 227 | 228 | res := C.hid_read_timeout(d.handle, data, length, milliseconds) 229 | switch res { 230 | case -1: 231 | return int(res), wrapErr(d.ReadError()) 232 | case 0: 233 | return int(res), ErrTimeout 234 | } 235 | return int(res), nil 236 | } 237 | 238 | // Read receives an input report with len(p) bytes from the Device. It returns 239 | // the number of bytes read and an error, if any. Read returns ErrTimeout if 240 | // the operation timed out before completing. 241 | // 242 | // If the device supports multiple reports, the first byte will contain the 243 | // report ID. 244 | func (d *Device) Read(p []byte) (int, error) { 245 | data := (*C.uchar)(&p[0]) 246 | length := C.size_t(len(p)) 247 | 248 | res := C.hid_read(d.handle, data, length) 249 | switch res { 250 | case -1: 251 | return int(res), wrapErr(d.ReadError()) 252 | case 0: 253 | return int(res), ErrTimeout 254 | } 255 | return int(res), nil 256 | } 257 | 258 | // SetNonblock changes the default behavior for Read. If nonblocking is true, 259 | // Read will return immediately with ErrTimeout if data is not available to be 260 | // read from the Device. 261 | func (d *Device) SetNonblock(nonblocking bool) error { 262 | var nonblock C.int 263 | if nonblocking { 264 | nonblock = 1 265 | } 266 | 267 | res := C.hid_set_nonblocking(d.handle, nonblock) 268 | if res == -1 { 269 | return wrapErr(d.Error()) 270 | } 271 | return nil 272 | } 273 | 274 | // SendFeatureReport sends a feature report with len(p) bytes to the Device. 275 | // It returns the number of bytes written and an error, if any. 276 | // 277 | // The first byte must contain the report ID to send. Data will be sent over 278 | // the control endpoint as a Set_Report transfer. 279 | func (d *Device) SendFeatureReport(p []byte) (int, error) { 280 | data := (*C.uchar)(&p[0]) 281 | length := C.size_t(len(p)) 282 | 283 | res := C.hid_send_feature_report(d.handle, data, length) 284 | if res == -1 { 285 | return int(res), wrapErr(d.Error()) 286 | } 287 | return int(res), nil 288 | } 289 | 290 | // GetFeatureReport receives a feature report with len(p) bytes from the 291 | // Device. It returns the number of bytes read and an error, if any. 292 | // 293 | // The first byte must contain the report ID to receive. 294 | func (d *Device) GetFeatureReport(p []byte) (int, error) { 295 | data := (*C.uchar)(&p[0]) 296 | length := C.size_t(len(p)) 297 | 298 | res := C.hid_get_feature_report(d.handle, data, length) 299 | if res == -1 { 300 | return int(res), wrapErr(d.Error()) 301 | } 302 | return int(res), nil 303 | } 304 | 305 | // GetInputReport receives an input report with len(p) bytes from the Device. 306 | // It returns the number of bytes read and an error, if any. 307 | func (d *Device) GetInputReport(p []byte) (int, error) { 308 | data := (*C.uchar)(&p[0]) 309 | length := C.size_t(len(p)) 310 | 311 | res := C.hid_get_input_report(d.handle, data, length) 312 | if res == -1 { 313 | return int(res), wrapErr(d.Error()) 314 | } 315 | return int(res), nil 316 | } 317 | 318 | // SendOutputReport sends an output report with len(p) bytes to the Device. It 319 | // returns the number of bytes written and an error, if any. 320 | func (d *Device) SendOutputReport(p []byte) (int, error) { 321 | data := (*C.uchar)(&p[0]) 322 | length := C.size_t(len(p)) 323 | 324 | res := C.hid_send_output_report(d.handle, data, length) 325 | if res == -1 { 326 | return int(res), wrapErr(d.Error()) 327 | } 328 | return int(res), nil 329 | } 330 | 331 | // Close closes the Device. 332 | func (d *Device) Close() error { 333 | C.hid_close(d.handle) 334 | return nil 335 | } 336 | 337 | // GetMfrStr returns the manufacturer string descriptor and an error, if any. 338 | func (d *Device) GetMfrStr() (string, error) { 339 | wcs := (*C.wchar_t)(calloc(maxStrLen+1, C.sizeof_wchar_t)) 340 | defer C.free(unsafe.Pointer(wcs)) 341 | 342 | res := C.hid_get_manufacturer_string(d.handle, wcs, maxStrLen) 343 | if res == -1 { 344 | return "", wrapErr(d.Error()) 345 | } 346 | return wcstogo(wcs), nil 347 | } 348 | 349 | // GetProductStr returns the product string descriptor and an error, if any. 350 | func (d *Device) GetProductStr() (string, error) { 351 | wcs := (*C.wchar_t)(calloc(maxStrLen+1, C.sizeof_wchar_t)) 352 | defer C.free(unsafe.Pointer(wcs)) 353 | 354 | res := C.hid_get_product_string(d.handle, wcs, maxStrLen) 355 | if res == -1 { 356 | return "", wrapErr(d.Error()) 357 | } 358 | return wcstogo(wcs), nil 359 | } 360 | 361 | // GetSerialNbr returns the serial number string descriptor and an error, if any. 362 | func (d *Device) GetSerialNbr() (string, error) { 363 | wcs := (*C.wchar_t)(calloc(maxStrLen+1, C.sizeof_wchar_t)) 364 | defer C.free(unsafe.Pointer(wcs)) 365 | 366 | res := C.hid_get_serial_number_string(d.handle, wcs, maxStrLen) 367 | if res == -1 { 368 | return "", wrapErr(d.Error()) 369 | } 370 | return wcstogo(wcs), nil 371 | } 372 | 373 | // GetDeviceInfo returns device information and an error, if any. 374 | func (d *Device) GetDeviceInfo() (*DeviceInfo, error) { 375 | p := C.hid_get_device_info(d.handle) 376 | if p == nil { 377 | return nil, wrapErr(Error()) 378 | } 379 | return newDeviceInfo(p), nil 380 | } 381 | 382 | // GetIndexedStr returns a string descriptor by index and an error, if any. 383 | func (d *Device) GetIndexedStr(index int) (string, error) { 384 | wcs := (*C.wchar_t)(calloc(maxStrLen+1, C.sizeof_wchar_t)) 385 | defer C.free(unsafe.Pointer(wcs)) 386 | 387 | res := C.hid_get_indexed_string(d.handle, C.int(index), wcs, maxStrLen) 388 | if res == -1 { 389 | return "", wrapErr(d.Error()) 390 | } 391 | return wcstogo(wcs), nil 392 | } 393 | 394 | // GetReportDescriptor receives a report descriptor with len(p) bytes from the 395 | // Device. It returns the number of bytes read and an error, if any. 396 | func (d *Device) GetReportDescriptor(p []byte) (int, error) { 397 | data := (*C.uchar)(&p[0]) 398 | length := C.size_t(len(p)) 399 | 400 | res := C.hid_get_report_descriptor(d.handle, data, length) 401 | if res == -1 { 402 | return int(res), wrapErr(d.Error()) 403 | } 404 | return int(res), nil 405 | } 406 | 407 | // Error returns the last error that occurred on the Device. If no error 408 | // occurred, nil is returned. 409 | func (d *Device) Error() error { 410 | wcs := C.hid_error(d.handle) 411 | if wcs == nil { 412 | return nil // no error 413 | } 414 | return errors.New(wcstogo(wcs)) 415 | } 416 | 417 | // ReadError returns the last error that occurred when reading from the Device. 418 | // If no error occurred, nil is returned. 419 | func (d *Device) ReadError() error { 420 | wcs := C.hid_read_error(d.handle) 421 | if wcs == nil { 422 | return nil // no error 423 | } 424 | return errors.New(wcstogo(wcs)) 425 | } 426 | 427 | var _ io.ReadWriteCloser = (*Device)(nil) 428 | 429 | // Error returns the last non-device-specific error that occurred. If no error 430 | // occurred, nil is returned. 431 | func Error() error { 432 | wcs := C.hid_error(nil) 433 | if wcs == nil { 434 | return nil // no error 435 | } 436 | return errors.New(wcstogo(wcs)) 437 | } 438 | 439 | // APIVersion describes the HIDAPI version. 440 | type APIVersion struct { 441 | Major int // Major version number 442 | Minor int // Minor version number 443 | Patch int // Patch version number 444 | } 445 | 446 | // GetVersion returns the HIDAPI version. 447 | func GetVersion() APIVersion { 448 | v := C.hid_version() 449 | return APIVersion{ 450 | Major: int(v.major), 451 | Minor: int(v.minor), 452 | Patch: int(v.patch), 453 | } 454 | } 455 | 456 | // GetVersion returns the HIDAPI version as a string. 457 | func GetVersionStr() string { 458 | return C.GoString(C.hid_version_str()) 459 | } 460 | -------------------------------------------------------------------------------- /hid_darwin.c: -------------------------------------------------------------------------------- 1 | /******************************************************* 2 | HIDAPI - Multi-Platform library for 3 | communication with HID devices. 4 | 5 | Alan Ott 6 | Signal 11 Software 7 | 8 | libusb/hidapi Team 9 | 10 | Copyright 2022, All Rights Reserved. 11 | 12 | At the discretion of the user of this library, 13 | this software may be licensed under the terms of the 14 | GNU General Public License v3, a BSD-Style license, or the 15 | original HIDAPI license as outlined in the LICENSE.txt, 16 | LICENSE-gpl3.txt, LICENSE-bsd.txt, and LICENSE-orig.txt 17 | files located at the root of the source distribution. 18 | These files may also be found in the public source 19 | code repository located at: 20 | https://github.com/libusb/hidapi . 21 | ********************************************************/ 22 | 23 | /* See Apple Technical Note TN2187 for details on IOHidManager. */ 24 | 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | 39 | #include "hidapi_darwin.h" 40 | 41 | /* Barrier implementation because Mac OSX doesn't have pthread_barrier. 42 | It also doesn't have clock_gettime(). So much for POSIX and SUSv2. 43 | This implementation came from Brent Priddy and was posted on 44 | StackOverflow. It is used with his permission. */ 45 | typedef int pthread_barrierattr_t; 46 | typedef struct pthread_barrier { 47 | pthread_mutex_t mutex; 48 | pthread_cond_t cond; 49 | int count; 50 | int trip_count; 51 | } pthread_barrier_t; 52 | 53 | static int pthread_barrier_init(pthread_barrier_t *barrier, const pthread_barrierattr_t *attr, unsigned int count) 54 | { 55 | (void) attr; 56 | 57 | if (count == 0) { 58 | errno = EINVAL; 59 | return -1; 60 | } 61 | 62 | if (pthread_mutex_init(&barrier->mutex, 0) < 0) { 63 | return -1; 64 | } 65 | if (pthread_cond_init(&barrier->cond, 0) < 0) { 66 | pthread_mutex_destroy(&barrier->mutex); 67 | return -1; 68 | } 69 | barrier->trip_count = count; 70 | barrier->count = 0; 71 | 72 | return 0; 73 | } 74 | 75 | static int pthread_barrier_destroy(pthread_barrier_t *barrier) 76 | { 77 | pthread_cond_destroy(&barrier->cond); 78 | pthread_mutex_destroy(&barrier->mutex); 79 | return 0; 80 | } 81 | 82 | static int pthread_barrier_wait(pthread_barrier_t *barrier) 83 | { 84 | pthread_mutex_lock(&barrier->mutex); 85 | ++(barrier->count); 86 | if (barrier->count >= barrier->trip_count) { 87 | barrier->count = 0; 88 | pthread_mutex_unlock(&barrier->mutex); 89 | pthread_cond_broadcast(&barrier->cond); 90 | return 1; 91 | } 92 | else { 93 | do { 94 | pthread_cond_wait(&barrier->cond, &(barrier->mutex)); 95 | } 96 | while (barrier->count != 0); 97 | 98 | pthread_mutex_unlock(&barrier->mutex); 99 | return 0; 100 | } 101 | } 102 | 103 | static int return_data(hid_device *dev, unsigned char *data, size_t length); 104 | 105 | /* Linked List of input reports received from the device. */ 106 | struct input_report { 107 | uint8_t *data; 108 | size_t len; 109 | struct input_report *next; 110 | }; 111 | 112 | static struct hid_api_version api_version = { 113 | .major = HID_API_VERSION_MAJOR, 114 | .minor = HID_API_VERSION_MINOR, 115 | .patch = HID_API_VERSION_PATCH 116 | }; 117 | 118 | /* - Run context - */ 119 | static IOHIDManagerRef hid_mgr = 0x0; 120 | static int is_macos_10_10_or_greater = 0; 121 | static IOOptionBits device_open_options = 0; 122 | static wchar_t *last_global_error_str = NULL; 123 | /* --- */ 124 | 125 | struct hid_device_ { 126 | IOHIDDeviceRef device_handle; 127 | IOOptionBits open_options; 128 | int blocking; 129 | int disconnected; 130 | CFStringRef run_loop_mode; 131 | CFRunLoopRef run_loop; 132 | CFRunLoopSourceRef source; 133 | uint8_t *input_report_buf; 134 | CFIndex max_input_report_len; 135 | struct input_report *input_reports; 136 | struct hid_device_info* device_info; 137 | 138 | pthread_t thread; 139 | pthread_mutex_t mutex; /* Protects input_reports */ 140 | pthread_cond_t condition; 141 | pthread_barrier_t barrier; /* Ensures correct startup sequence */ 142 | pthread_barrier_t shutdown_barrier; /* Ensures correct shutdown sequence */ 143 | int shutdown_thread; 144 | wchar_t *last_error_str; 145 | wchar_t *last_read_error_str; 146 | }; 147 | 148 | static hid_device *new_hid_device(void) 149 | { 150 | hid_device *dev = (hid_device*) calloc(1, sizeof(hid_device)); 151 | if (dev == NULL) { 152 | return NULL; 153 | } 154 | 155 | dev->device_handle = NULL; 156 | dev->open_options = device_open_options; 157 | dev->blocking = 1; 158 | dev->disconnected = 0; 159 | dev->run_loop_mode = NULL; 160 | dev->run_loop = NULL; 161 | dev->source = NULL; 162 | dev->input_report_buf = NULL; 163 | dev->input_reports = NULL; 164 | dev->device_info = NULL; 165 | dev->shutdown_thread = 0; 166 | dev->last_error_str = NULL; 167 | dev->last_read_error_str = NULL; 168 | 169 | /* Thread objects */ 170 | pthread_mutex_init(&dev->mutex, NULL); 171 | pthread_cond_init(&dev->condition, NULL); 172 | pthread_barrier_init(&dev->barrier, NULL, 2); 173 | pthread_barrier_init(&dev->shutdown_barrier, NULL, 2); 174 | 175 | return dev; 176 | } 177 | 178 | static void free_hid_device(hid_device *dev) 179 | { 180 | if (!dev) 181 | return; 182 | 183 | /* Delete any input reports still left over. */ 184 | struct input_report *rpt = dev->input_reports; 185 | while (rpt) { 186 | struct input_report *next = rpt->next; 187 | free(rpt->data); 188 | free(rpt); 189 | rpt = next; 190 | } 191 | 192 | /* Free the string and the report buffer. The check for NULL 193 | is necessary here as CFRelease() doesn't handle NULL like 194 | free() and others do. */ 195 | if (dev->run_loop_mode) 196 | CFRelease(dev->run_loop_mode); 197 | if (dev->source) 198 | CFRelease(dev->source); 199 | free(dev->input_report_buf); 200 | free(dev->last_error_str); 201 | free(dev->last_read_error_str); 202 | hid_free_enumeration(dev->device_info); 203 | 204 | /* Clean up the thread objects */ 205 | pthread_barrier_destroy(&dev->shutdown_barrier); 206 | pthread_barrier_destroy(&dev->barrier); 207 | pthread_cond_destroy(&dev->condition); 208 | pthread_mutex_destroy(&dev->mutex); 209 | 210 | /* Free the structure itself. */ 211 | free(dev); 212 | } 213 | 214 | 215 | /* The caller must free the returned string with free(). */ 216 | static wchar_t *utf8_to_wchar_t(const char *utf8) 217 | { 218 | wchar_t *ret = NULL; 219 | 220 | if (utf8) { 221 | size_t wlen = mbstowcs(NULL, utf8, 0); 222 | if ((size_t) -1 == wlen) { 223 | return wcsdup(L""); 224 | } 225 | ret = (wchar_t*) calloc(wlen+1, sizeof(wchar_t)); 226 | if (ret == NULL) { 227 | /* as much as we can do at this point */ 228 | return NULL; 229 | } 230 | mbstowcs(ret, utf8, wlen+1); 231 | ret[wlen] = 0x0000; 232 | } 233 | 234 | return ret; 235 | } 236 | 237 | 238 | /* Makes a copy of the given error message (and decoded according to the 239 | * currently locale) into the wide string pointer pointed by error_str. 240 | * The last stored error string is freed. 241 | * Use register_error_str(NULL) to free the error message completely. */ 242 | static void register_error_str(wchar_t **error_str, const char *msg) 243 | { 244 | free(*error_str); 245 | *error_str = utf8_to_wchar_t(msg); 246 | } 247 | 248 | /* Similar to register_error_str, but allows passing a format string with va_list args into this function. */ 249 | static void register_error_str_vformat(wchar_t **error_str, const char *format, va_list args) 250 | { 251 | char msg[1024]; 252 | vsnprintf(msg, sizeof(msg), format, args); 253 | 254 | register_error_str(error_str, msg); 255 | } 256 | 257 | /* Set the last global error to be reported by hid_error(NULL). 258 | * The given error message will be copied (and decoded according to the 259 | * currently locale, so do not pass in string constants). 260 | * The last stored global error message is freed. 261 | * Use register_global_error(NULL) to indicate "no error". */ 262 | static void register_global_error(const char *msg) 263 | { 264 | register_error_str(&last_global_error_str, msg); 265 | } 266 | 267 | /* Similar to register_global_error, but allows passing a format string into this function. */ 268 | static void register_global_error_format(const char *format, ...) 269 | { 270 | va_list args; 271 | va_start(args, format); 272 | register_error_str_vformat(&last_global_error_str, format, args); 273 | va_end(args); 274 | } 275 | 276 | /* Set the last error for a device to be reported by hid_error(dev). 277 | * The given error message will be copied (and decoded according to the 278 | * currently locale, so do not pass in string constants). 279 | * The last stored device error message is freed. 280 | * Use register_device_error(dev, NULL) to indicate "no error". */ 281 | static void register_device_error(hid_device *dev, const char *msg) 282 | { 283 | register_error_str(&dev->last_error_str, msg); 284 | } 285 | 286 | /* Similar to register_device_error, but you can pass a format string into this function. */ 287 | static void register_device_error_format(hid_device *dev, const char *format, ...) 288 | { 289 | va_list args; 290 | va_start(args, format); 291 | register_error_str_vformat(&dev->last_error_str, format, args); 292 | va_end(args); 293 | } 294 | 295 | 296 | static CFArrayRef get_array_property(IOHIDDeviceRef device, CFStringRef key) 297 | { 298 | CFTypeRef ref = IOHIDDeviceGetProperty(device, key); 299 | if (ref != NULL && CFGetTypeID(ref) == CFArrayGetTypeID()) { 300 | return (CFArrayRef)ref; 301 | } else { 302 | return NULL; 303 | } 304 | } 305 | 306 | static int32_t get_int_property(IOHIDDeviceRef device, CFStringRef key) 307 | { 308 | CFTypeRef ref; 309 | int32_t value = 0; 310 | 311 | ref = IOHIDDeviceGetProperty(device, key); 312 | if (ref) { 313 | if (CFGetTypeID(ref) == CFNumberGetTypeID()) { 314 | CFNumberGetValue((CFNumberRef) ref, kCFNumberSInt32Type, &value); 315 | return value; 316 | } 317 | } 318 | return 0; 319 | } 320 | 321 | static bool try_get_int_property(IOHIDDeviceRef device, CFStringRef key, int32_t *out_val) 322 | { 323 | bool result = false; 324 | CFTypeRef ref; 325 | 326 | ref = IOHIDDeviceGetProperty(device, key); 327 | if (ref) { 328 | if (CFGetTypeID(ref) == CFNumberGetTypeID()) { 329 | result = CFNumberGetValue((CFNumberRef) ref, kCFNumberSInt32Type, out_val); 330 | } 331 | } 332 | return result; 333 | } 334 | 335 | static bool try_get_ioregistry_int_property(io_service_t service, CFStringRef property, int32_t *out_val) 336 | { 337 | bool result = false; 338 | CFTypeRef ref = IORegistryEntryCreateCFProperty(service, property, kCFAllocatorDefault, 0); 339 | 340 | if (ref) { 341 | if (CFGetTypeID(ref) == CFNumberGetTypeID()) { 342 | result = CFNumberGetValue(ref, kCFNumberSInt32Type, out_val); 343 | } 344 | 345 | CFRelease(ref); 346 | } 347 | 348 | return result; 349 | } 350 | 351 | static CFArrayRef get_usage_pairs(IOHIDDeviceRef device) 352 | { 353 | return get_array_property(device, CFSTR(kIOHIDDeviceUsagePairsKey)); 354 | } 355 | 356 | static unsigned short get_vendor_id(IOHIDDeviceRef device) 357 | { 358 | return get_int_property(device, CFSTR(kIOHIDVendorIDKey)); 359 | } 360 | 361 | static unsigned short get_product_id(IOHIDDeviceRef device) 362 | { 363 | return get_int_property(device, CFSTR(kIOHIDProductIDKey)); 364 | } 365 | 366 | static int32_t get_max_report_length(IOHIDDeviceRef device) 367 | { 368 | return get_int_property(device, CFSTR(kIOHIDMaxInputReportSizeKey)); 369 | } 370 | 371 | static int get_string_property(IOHIDDeviceRef device, CFStringRef prop, wchar_t *buf, size_t len) 372 | { 373 | CFStringRef str; 374 | 375 | if (!len) 376 | return 0; 377 | 378 | str = (CFStringRef) IOHIDDeviceGetProperty(device, prop); 379 | 380 | buf[0] = 0; 381 | 382 | if (str && CFGetTypeID(str) == CFStringGetTypeID()) { 383 | CFIndex str_len = CFStringGetLength(str); 384 | CFRange range; 385 | CFIndex used_buf_len; 386 | CFIndex chars_copied; 387 | 388 | len --; 389 | 390 | range.location = 0; 391 | range.length = ((size_t) str_len > len)? len: (size_t) str_len; 392 | chars_copied = CFStringGetBytes(str, 393 | range, 394 | kCFStringEncodingUTF32LE, 395 | (char) '?', 396 | FALSE, 397 | (UInt8*)buf, 398 | len * sizeof(wchar_t), 399 | &used_buf_len); 400 | 401 | if (chars_copied <= 0) 402 | buf[0] = 0; 403 | else 404 | buf[chars_copied] = 0; 405 | 406 | return 0; 407 | } 408 | else 409 | return -1; 410 | 411 | } 412 | 413 | static int get_serial_number(IOHIDDeviceRef device, wchar_t *buf, size_t len) 414 | { 415 | return get_string_property(device, CFSTR(kIOHIDSerialNumberKey), buf, len); 416 | } 417 | 418 | static int get_manufacturer_string(IOHIDDeviceRef device, wchar_t *buf, size_t len) 419 | { 420 | return get_string_property(device, CFSTR(kIOHIDManufacturerKey), buf, len); 421 | } 422 | 423 | static int get_product_string(IOHIDDeviceRef device, wchar_t *buf, size_t len) 424 | { 425 | return get_string_property(device, CFSTR(kIOHIDProductKey), buf, len); 426 | } 427 | 428 | 429 | /* Implementation of wcsdup() for Mac. */ 430 | static wchar_t *dup_wcs(const wchar_t *s) 431 | { 432 | size_t len = wcslen(s); 433 | wchar_t *ret = (wchar_t*) malloc((len+1)*sizeof(wchar_t)); 434 | wcscpy(ret, s); 435 | 436 | return ret; 437 | } 438 | 439 | /* Initialize the IOHIDManager. Return 0 for success and -1 for failure. */ 440 | static int init_hid_manager(void) 441 | { 442 | /* Initialize all the HID Manager Objects */ 443 | hid_mgr = IOHIDManagerCreate(kCFAllocatorDefault, kIOHIDOptionsTypeNone); 444 | if (hid_mgr) { 445 | IOHIDManagerSetDeviceMatching(hid_mgr, NULL); 446 | IOHIDManagerScheduleWithRunLoop(hid_mgr, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode); 447 | return 0; 448 | } 449 | 450 | register_global_error("Failed to create IOHIDManager"); 451 | return -1; 452 | } 453 | 454 | HID_API_EXPORT const struct hid_api_version* HID_API_CALL hid_version(void) 455 | { 456 | return &api_version; 457 | } 458 | 459 | HID_API_EXPORT const char* HID_API_CALL hid_version_str(void) 460 | { 461 | return HID_API_VERSION_STR; 462 | } 463 | 464 | /* Initialize the IOHIDManager if necessary. This is the public function, and 465 | it is safe to call this function repeatedly. Return 0 for success and -1 466 | for failure. */ 467 | int HID_API_EXPORT hid_init(void) 468 | { 469 | register_global_error(NULL); 470 | 471 | if (!hid_mgr) { 472 | is_macos_10_10_or_greater = (kCFCoreFoundationVersionNumber >= 1151.16); /* kCFCoreFoundationVersionNumber10_10 */ 473 | hid_darwin_set_open_exclusive(1); /* Backward compatibility */ 474 | return init_hid_manager(); 475 | } 476 | 477 | /* Already initialized. */ 478 | return 0; 479 | } 480 | 481 | int HID_API_EXPORT hid_exit(void) 482 | { 483 | if (hid_mgr) { 484 | /* Close the HID manager. */ 485 | IOHIDManagerClose(hid_mgr, kIOHIDOptionsTypeNone); 486 | CFRelease(hid_mgr); 487 | hid_mgr = NULL; 488 | } 489 | 490 | /* Free global error message */ 491 | register_global_error(NULL); 492 | 493 | return 0; 494 | } 495 | 496 | static void process_pending_events(void) { 497 | SInt32 res; 498 | do { 499 | res = CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.001, FALSE); 500 | } while(res != kCFRunLoopRunFinished && res != kCFRunLoopRunTimedOut); 501 | } 502 | 503 | static int read_usb_interface_from_hid_service_parent(io_service_t hid_service) 504 | { 505 | int32_t result = -1; 506 | bool success = false; 507 | io_registry_entry_t current = IO_OBJECT_NULL; 508 | kern_return_t res; 509 | int parent_number = 0; 510 | 511 | res = IORegistryEntryGetParentEntry(hid_service, kIOServicePlane, ¤t); 512 | while (KERN_SUCCESS == res 513 | /* Only search up to 3 parent entries. 514 | * With the default driver - the parent-of-interest supposed to be the first one, 515 | * but lets assume some custom drivers or so, with deeper tree. */ 516 | && parent_number < 3) { 517 | io_registry_entry_t parent = IO_OBJECT_NULL; 518 | int32_t interface_number = -1; 519 | parent_number++; 520 | 521 | success = try_get_ioregistry_int_property(current, CFSTR(kUSBInterfaceNumber), &interface_number); 522 | if (success) { 523 | result = interface_number; 524 | break; 525 | } 526 | 527 | res = IORegistryEntryGetParentEntry(current, kIOServicePlane, &parent); 528 | if (parent) { 529 | IOObjectRelease(current); 530 | current = parent; 531 | } 532 | 533 | } 534 | 535 | if (current) { 536 | IOObjectRelease(current); 537 | current = IO_OBJECT_NULL; 538 | } 539 | 540 | return result; 541 | } 542 | 543 | static struct hid_device_info *create_device_info_with_usage(IOHIDDeviceRef dev, int32_t usage_page, int32_t usage) 544 | { 545 | unsigned short dev_vid; 546 | unsigned short dev_pid; 547 | int BUF_LEN = 256; 548 | wchar_t buf[BUF_LEN]; 549 | CFTypeRef transport_prop; 550 | 551 | struct hid_device_info *cur_dev; 552 | io_service_t hid_service; 553 | kern_return_t res; 554 | uint64_t entry_id = 0; 555 | 556 | if (dev == NULL) { 557 | return NULL; 558 | } 559 | 560 | cur_dev = (struct hid_device_info *)calloc(1, sizeof(struct hid_device_info)); 561 | if (cur_dev == NULL) { 562 | return NULL; 563 | } 564 | 565 | dev_vid = get_vendor_id(dev); 566 | dev_pid = get_product_id(dev); 567 | 568 | cur_dev->usage_page = usage_page; 569 | cur_dev->usage = usage; 570 | 571 | /* Fill out the record */ 572 | cur_dev->next = NULL; 573 | 574 | /* Fill in the path (as a unique ID of the service entry) */ 575 | cur_dev->path = NULL; 576 | hid_service = IOHIDDeviceGetService(dev); 577 | if (hid_service != MACH_PORT_NULL) { 578 | res = IORegistryEntryGetRegistryEntryID(hid_service, &entry_id); 579 | } 580 | else { 581 | res = KERN_INVALID_ARGUMENT; 582 | } 583 | 584 | if (res == KERN_SUCCESS) { 585 | /* max value of entry_id(uint64_t) is 18446744073709551615 which is 20 characters long, 586 | so for (max) "path" string 'DevSrvsID:18446744073709551615' we would need 587 | 9+1+20+1=31 bytes buffer, but allocate 32 for simple alignment */ 588 | const size_t path_len = 32; 589 | cur_dev->path = calloc(1, path_len); 590 | if (cur_dev->path != NULL) { 591 | snprintf(cur_dev->path, path_len, "DevSrvsID:%llu", entry_id); 592 | } 593 | } 594 | 595 | if (cur_dev->path == NULL) { 596 | /* for whatever reason, trying to keep it a non-NULL string */ 597 | cur_dev->path = strdup(""); 598 | } 599 | 600 | /* Serial Number */ 601 | get_serial_number(dev, buf, BUF_LEN); 602 | cur_dev->serial_number = dup_wcs(buf); 603 | 604 | /* Manufacturer and Product strings */ 605 | get_manufacturer_string(dev, buf, BUF_LEN); 606 | cur_dev->manufacturer_string = dup_wcs(buf); 607 | get_product_string(dev, buf, BUF_LEN); 608 | cur_dev->product_string = dup_wcs(buf); 609 | 610 | /* VID/PID */ 611 | cur_dev->vendor_id = dev_vid; 612 | cur_dev->product_id = dev_pid; 613 | 614 | /* Release Number */ 615 | cur_dev->release_number = get_int_property(dev, CFSTR(kIOHIDVersionNumberKey)); 616 | 617 | /* Interface Number. 618 | * We can only retrieve the interface number for USB HID devices. 619 | * See below */ 620 | cur_dev->interface_number = -1; 621 | 622 | /* Bus Type */ 623 | transport_prop = IOHIDDeviceGetProperty(dev, CFSTR(kIOHIDTransportKey)); 624 | 625 | if (transport_prop != NULL && CFGetTypeID(transport_prop) == CFStringGetTypeID()) { 626 | if (CFStringCompare((CFStringRef)transport_prop, CFSTR(kIOHIDTransportUSBValue), 0) == kCFCompareEqualTo) { 627 | int32_t interface_number = -1; 628 | cur_dev->bus_type = HID_API_BUS_USB; 629 | 630 | /* A IOHIDDeviceRef used to have this simple property, 631 | * until macOS 13.3 - we will try to use it. */ 632 | if (try_get_int_property(dev, CFSTR(kUSBInterfaceNumber), &interface_number)) { 633 | cur_dev->interface_number = interface_number; 634 | } else { 635 | /* Otherwise fallback to io_service_t property. 636 | * (of one of the parent services). */ 637 | cur_dev->interface_number = read_usb_interface_from_hid_service_parent(hid_service); 638 | 639 | /* If the above doesn't work - 640 | * no (known) fallback exists at this point. */ 641 | } 642 | 643 | /* Match "Bluetooth", "BluetoothLowEnergy" and "Bluetooth Low Energy" strings */ 644 | } else if (CFStringHasPrefix((CFStringRef)transport_prop, CFSTR(kIOHIDTransportBluetoothValue))) { 645 | cur_dev->bus_type = HID_API_BUS_BLUETOOTH; 646 | } else if (CFStringCompare((CFStringRef)transport_prop, CFSTR(kIOHIDTransportI2CValue), 0) == kCFCompareEqualTo) { 647 | cur_dev->bus_type = HID_API_BUS_I2C; 648 | } else if (CFStringCompare((CFStringRef)transport_prop, CFSTR(kIOHIDTransportSPIValue), 0) == kCFCompareEqualTo) { 649 | cur_dev->bus_type = HID_API_BUS_SPI; 650 | } 651 | } 652 | 653 | return cur_dev; 654 | } 655 | 656 | static struct hid_device_info *create_device_info(IOHIDDeviceRef device) 657 | { 658 | const int32_t primary_usage_page = get_int_property(device, CFSTR(kIOHIDPrimaryUsagePageKey)); 659 | const int32_t primary_usage = get_int_property(device, CFSTR(kIOHIDPrimaryUsageKey)); 660 | 661 | /* Primary should always be first, to match previous behavior. */ 662 | struct hid_device_info *root = create_device_info_with_usage(device, primary_usage_page, primary_usage); 663 | struct hid_device_info *cur = root; 664 | 665 | if (!root) 666 | return NULL; 667 | 668 | CFArrayRef usage_pairs = get_usage_pairs(device); 669 | 670 | if (usage_pairs != NULL) { 671 | struct hid_device_info *next = NULL; 672 | for (CFIndex i = 0; i < CFArrayGetCount(usage_pairs); i++) { 673 | CFTypeRef dict = CFArrayGetValueAtIndex(usage_pairs, i); 674 | if (CFGetTypeID(dict) != CFDictionaryGetTypeID()) { 675 | continue; 676 | } 677 | 678 | CFTypeRef usage_page_ref, usage_ref; 679 | int32_t usage_page, usage; 680 | 681 | if (!CFDictionaryGetValueIfPresent((CFDictionaryRef)dict, CFSTR(kIOHIDDeviceUsagePageKey), &usage_page_ref) || 682 | !CFDictionaryGetValueIfPresent((CFDictionaryRef)dict, CFSTR(kIOHIDDeviceUsageKey), &usage_ref) || 683 | CFGetTypeID(usage_page_ref) != CFNumberGetTypeID() || 684 | CFGetTypeID(usage_ref) != CFNumberGetTypeID() || 685 | !CFNumberGetValue((CFNumberRef)usage_page_ref, kCFNumberSInt32Type, &usage_page) || 686 | !CFNumberGetValue((CFNumberRef)usage_ref, kCFNumberSInt32Type, &usage)) { 687 | continue; 688 | } 689 | if (usage_page == primary_usage_page && usage == primary_usage) 690 | continue; /* Already added. */ 691 | 692 | next = create_device_info_with_usage(device, usage_page, usage); 693 | cur->next = next; 694 | if (next != NULL) { 695 | cur = next; 696 | } 697 | } 698 | } 699 | 700 | return root; 701 | } 702 | 703 | struct hid_device_info HID_API_EXPORT *hid_enumerate(unsigned short vendor_id, unsigned short product_id) 704 | { 705 | struct hid_device_info *root = NULL; /* return object */ 706 | struct hid_device_info *cur_dev = NULL; 707 | CFIndex num_devices; 708 | int i; 709 | 710 | /* Set up the HID Manager if it hasn't been done */ 711 | if (hid_init() < 0) { 712 | return NULL; 713 | } 714 | /* register_global_error: global error is set/reset by hid_init */ 715 | 716 | /* give the IOHIDManager a chance to update itself */ 717 | process_pending_events(); 718 | 719 | /* Get a list of the Devices */ 720 | CFMutableDictionaryRef matching = NULL; 721 | if (vendor_id != 0 || product_id != 0) { 722 | matching = CFDictionaryCreateMutable(kCFAllocatorDefault, kIOHIDOptionsTypeNone, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); 723 | 724 | if (matching && vendor_id != 0) { 725 | CFNumberRef v = CFNumberCreate(kCFAllocatorDefault, kCFNumberShortType, &vendor_id); 726 | CFDictionarySetValue(matching, CFSTR(kIOHIDVendorIDKey), v); 727 | CFRelease(v); 728 | } 729 | 730 | if (matching && product_id != 0) { 731 | CFNumberRef p = CFNumberCreate(kCFAllocatorDefault, kCFNumberShortType, &product_id); 732 | CFDictionarySetValue(matching, CFSTR(kIOHIDProductIDKey), p); 733 | CFRelease(p); 734 | } 735 | } 736 | IOHIDManagerSetDeviceMatching(hid_mgr, matching); 737 | if (matching != NULL) { 738 | CFRelease(matching); 739 | } 740 | 741 | CFSetRef device_set = IOHIDManagerCopyDevices(hid_mgr); 742 | 743 | IOHIDDeviceRef *device_array = NULL; 744 | 745 | if (device_set != NULL) { 746 | /* Convert the list into a C array so we can iterate easily. */ 747 | num_devices = CFSetGetCount(device_set); 748 | device_array = (IOHIDDeviceRef*) calloc(num_devices, sizeof(IOHIDDeviceRef)); 749 | CFSetGetValues(device_set, (const void **) device_array); 750 | } else { 751 | num_devices = 0; 752 | } 753 | 754 | /* Iterate over each device, making an entry for it. */ 755 | for (i = 0; i < num_devices; i++) { 756 | 757 | IOHIDDeviceRef dev = device_array[i]; 758 | if (!dev) { 759 | continue; 760 | } 761 | 762 | struct hid_device_info *tmp = create_device_info(dev); 763 | if (tmp == NULL) { 764 | continue; 765 | } 766 | 767 | if (cur_dev) { 768 | cur_dev->next = tmp; 769 | } 770 | else { 771 | root = tmp; 772 | } 773 | cur_dev = tmp; 774 | 775 | /* move the pointer to the tail of returned list */ 776 | while (cur_dev->next != NULL) { 777 | cur_dev = cur_dev->next; 778 | } 779 | } 780 | 781 | free(device_array); 782 | if (device_set != NULL) 783 | CFRelease(device_set); 784 | 785 | if (root == NULL) { 786 | if (vendor_id == 0 && product_id == 0) { 787 | register_global_error("No HID devices found in the system."); 788 | } else { 789 | register_global_error("No HID devices with requested VID/PID found in the system."); 790 | } 791 | } 792 | 793 | return root; 794 | } 795 | 796 | void HID_API_EXPORT hid_free_enumeration(struct hid_device_info *devs) 797 | { 798 | /* This function is identical to the Linux version. Platform independent. */ 799 | struct hid_device_info *d = devs; 800 | while (d) { 801 | struct hid_device_info *next = d->next; 802 | free(d->path); 803 | free(d->serial_number); 804 | free(d->manufacturer_string); 805 | free(d->product_string); 806 | free(d); 807 | d = next; 808 | } 809 | } 810 | 811 | hid_device * HID_API_EXPORT hid_open(unsigned short vendor_id, unsigned short product_id, const wchar_t *serial_number) 812 | { 813 | /* This function is identical to the Linux version. Platform independent. */ 814 | 815 | struct hid_device_info *devs, *cur_dev; 816 | const char *path_to_open = NULL; 817 | hid_device * handle = NULL; 818 | 819 | /* register_global_error: global error is reset by hid_enumerate/hid_init */ 820 | devs = hid_enumerate(vendor_id, product_id); 821 | if (devs == NULL) { 822 | /* register_global_error: global error is already set by hid_enumerate */ 823 | return NULL; 824 | } 825 | 826 | cur_dev = devs; 827 | while (cur_dev) { 828 | if (cur_dev->vendor_id == vendor_id && 829 | cur_dev->product_id == product_id) { 830 | if (serial_number) { 831 | if (wcscmp(serial_number, cur_dev->serial_number) == 0) { 832 | path_to_open = cur_dev->path; 833 | break; 834 | } 835 | } 836 | else { 837 | path_to_open = cur_dev->path; 838 | break; 839 | } 840 | } 841 | cur_dev = cur_dev->next; 842 | } 843 | 844 | if (path_to_open) { 845 | handle = hid_open_path(path_to_open); 846 | } else { 847 | register_global_error("Device with requested VID/PID/(SerialNumber) not found"); 848 | } 849 | 850 | hid_free_enumeration(devs); 851 | 852 | return handle; 853 | } 854 | 855 | static void hid_device_removal_callback(void *context, IOReturn result, 856 | void *sender) 857 | { 858 | (void) result; 859 | (void) sender; 860 | 861 | /* Stop the Run Loop for this device. */ 862 | hid_device *d = (hid_device*) context; 863 | 864 | d->disconnected = 1; 865 | CFRunLoopStop(d->run_loop); 866 | } 867 | 868 | /* The Run Loop calls this function for each input report received. 869 | This function puts the data into a linked list to be picked up by 870 | hid_read(). */ 871 | static void hid_report_callback(void *context, IOReturn result, void *sender, 872 | IOHIDReportType report_type, uint32_t report_id, 873 | uint8_t *report, CFIndex report_length) 874 | { 875 | (void) result; 876 | (void) sender; 877 | (void) report_type; 878 | (void) report_id; 879 | 880 | struct input_report *rpt; 881 | hid_device *dev = (hid_device*) context; 882 | 883 | /* Make a new Input Report object */ 884 | rpt = (struct input_report*) calloc(1, sizeof(struct input_report)); 885 | rpt->data = (uint8_t*) calloc(1, report_length); 886 | memcpy(rpt->data, report, report_length); 887 | rpt->len = report_length; 888 | rpt->next = NULL; 889 | 890 | /* Lock this section */ 891 | pthread_mutex_lock(&dev->mutex); 892 | 893 | /* Attach the new report object to the end of the list. */ 894 | if (dev->input_reports == NULL) { 895 | /* The list is empty. Put it at the root. */ 896 | dev->input_reports = rpt; 897 | } 898 | else { 899 | /* Find the end of the list and attach. */ 900 | struct input_report *cur = dev->input_reports; 901 | int num_queued = 0; 902 | while (cur->next != NULL) { 903 | cur = cur->next; 904 | num_queued++; 905 | } 906 | cur->next = rpt; 907 | 908 | /* Pop one off if we've reached 30 in the queue. This 909 | way we don't grow forever if the user never reads 910 | anything from the device. */ 911 | if (num_queued > 30) { 912 | return_data(dev, NULL, 0); 913 | } 914 | } 915 | 916 | /* Signal a waiting thread that there is data. */ 917 | pthread_cond_signal(&dev->condition); 918 | 919 | /* Unlock */ 920 | pthread_mutex_unlock(&dev->mutex); 921 | 922 | } 923 | 924 | /* This gets called when the read_thread's run loop gets signaled by 925 | hid_close(), and serves to stop the read_thread's run loop. */ 926 | static void perform_signal_callback(void *context) 927 | { 928 | hid_device *dev = (hid_device*) context; 929 | CFRunLoopStop(dev->run_loop); /*TODO: CFRunLoopGetCurrent()*/ 930 | } 931 | 932 | static void *read_thread(void *param) 933 | { 934 | hid_device *dev = (hid_device*) param; 935 | SInt32 code; 936 | 937 | /* Move the device's run loop to this thread. */ 938 | IOHIDDeviceScheduleWithRunLoop(dev->device_handle, CFRunLoopGetCurrent(), dev->run_loop_mode); 939 | 940 | /* Create the RunLoopSource which is used to signal the 941 | event loop to stop when hid_close() is called. */ 942 | CFRunLoopSourceContext ctx; 943 | memset(&ctx, 0, sizeof(ctx)); 944 | ctx.version = 0; 945 | ctx.info = dev; 946 | ctx.perform = &perform_signal_callback; 947 | dev->source = CFRunLoopSourceCreate(kCFAllocatorDefault, 0/*order*/, &ctx); 948 | CFRunLoopAddSource(CFRunLoopGetCurrent(), dev->source, dev->run_loop_mode); 949 | 950 | /* Store off the Run Loop so it can be stopped from hid_close() 951 | and on device disconnection. */ 952 | dev->run_loop = CFRunLoopGetCurrent(); 953 | 954 | /* Notify the main thread that the read thread is up and running. */ 955 | pthread_barrier_wait(&dev->barrier); 956 | 957 | /* Run the Event Loop. CFRunLoopRunInMode() will dispatch HID input 958 | reports into the hid_report_callback(). */ 959 | while (!dev->shutdown_thread && !dev->disconnected) { 960 | code = CFRunLoopRunInMode(dev->run_loop_mode, 1000/*sec*/, FALSE); 961 | /* Return if the device has been disconnected */ 962 | if (code == kCFRunLoopRunFinished || code == kCFRunLoopRunStopped) { 963 | dev->disconnected = 1; 964 | break; 965 | } 966 | 967 | 968 | /* Break if The Run Loop returns Finished or Stopped. */ 969 | if (code != kCFRunLoopRunTimedOut && 970 | code != kCFRunLoopRunHandledSource) { 971 | /* There was some kind of error. Setting 972 | shutdown seems to make sense, but 973 | there may be something else more appropriate */ 974 | dev->shutdown_thread = 1; 975 | break; 976 | } 977 | } 978 | 979 | /* Now that the read thread is stopping, Wake any threads which are 980 | waiting on data (in hid_read_timeout()). Do this under a mutex to 981 | make sure that a thread which is about to go to sleep waiting on 982 | the condition actually will go to sleep before the condition is 983 | signaled. */ 984 | pthread_mutex_lock(&dev->mutex); 985 | pthread_cond_broadcast(&dev->condition); 986 | pthread_mutex_unlock(&dev->mutex); 987 | 988 | /* Wait here until hid_close() is called and makes it past 989 | the call to CFRunLoopWakeUp(). This thread still needs to 990 | be valid when that function is called on the other thread. */ 991 | pthread_barrier_wait(&dev->shutdown_barrier); 992 | 993 | return NULL; 994 | } 995 | 996 | /* \p path must be one of: 997 | - in format 'DevSrvsID:' (as returned by hid_enumerate); 998 | - a valid path to an IOHIDDevice in the IOService plane (as returned by IORegistryEntryGetPath, 999 | e.g.: "IOService:/AppleACPIPlatformExpert/PCI0@0/AppleACPIPCI/EHC1@1D,7/AppleUSBEHCI/PLAYSTATION(R)3 Controller@fd120000/IOUSBInterface@0/IOUSBHIDDriver"); 1000 | Second format is for compatibility with paths accepted by older versions of HIDAPI. 1001 | */ 1002 | static io_registry_entry_t hid_open_service_registry_from_path(const char *path) 1003 | { 1004 | if (path == NULL) 1005 | return MACH_PORT_NULL; 1006 | 1007 | /* Get the IORegistry entry for the given path */ 1008 | if (strncmp("DevSrvsID:", path, 10) == 0) { 1009 | char *endptr; 1010 | uint64_t entry_id = strtoull(path + 10, &endptr, 10); 1011 | if (*endptr == '\0') { 1012 | return IOServiceGetMatchingService((mach_port_t) 0, IORegistryEntryIDMatching(entry_id)); 1013 | } 1014 | } 1015 | else { 1016 | /* Fallback to older format of the path */ 1017 | return IORegistryEntryFromPath((mach_port_t) 0, path); 1018 | } 1019 | 1020 | return MACH_PORT_NULL; 1021 | } 1022 | 1023 | hid_device * HID_API_EXPORT hid_open_path(const char *path) 1024 | { 1025 | hid_device *dev = NULL; 1026 | io_registry_entry_t entry = MACH_PORT_NULL; 1027 | IOReturn ret = kIOReturnInvalid; 1028 | char str[32]; 1029 | 1030 | /* Set up the HID Manager if it hasn't been done */ 1031 | if (hid_init() < 0) { 1032 | return NULL; 1033 | } 1034 | /* register_global_error: global error is set/reset by hid_init */ 1035 | 1036 | dev = new_hid_device(); 1037 | if (!dev) { 1038 | register_global_error("Couldn't allocate memory"); 1039 | return NULL; 1040 | } 1041 | 1042 | /* Get the IORegistry entry for the given path */ 1043 | entry = hid_open_service_registry_from_path(path); 1044 | if (entry == MACH_PORT_NULL) { 1045 | /* Path wasn't valid (maybe device was removed?) */ 1046 | register_global_error("hid_open_path: device mach entry not found with the given path"); 1047 | goto return_error; 1048 | } 1049 | 1050 | /* Create an IOHIDDevice for the entry */ 1051 | dev->device_handle = IOHIDDeviceCreate(kCFAllocatorDefault, entry); 1052 | if (dev->device_handle == NULL) { 1053 | /* Error creating the HID device */ 1054 | register_global_error("hid_open_path: failed to create IOHIDDevice from the mach entry"); 1055 | goto return_error; 1056 | } 1057 | 1058 | /* Open the IOHIDDevice */ 1059 | ret = IOHIDDeviceOpen(dev->device_handle, dev->open_options); 1060 | if (ret != kIOReturnSuccess) { 1061 | register_global_error_format("hid_open_path: failed to open IOHIDDevice from mach entry: (0x%08X) %s", ret, mach_error_string(ret)); 1062 | goto return_error; 1063 | } 1064 | 1065 | /* Create the buffers for receiving data */ 1066 | dev->max_input_report_len = (CFIndex) get_max_report_length(dev->device_handle); 1067 | dev->input_report_buf = (uint8_t*) calloc(dev->max_input_report_len, sizeof(uint8_t)); 1068 | 1069 | /* Create the Run Loop Mode for this device. 1070 | printing the reference seems to work. */ 1071 | snprintf(str, sizeof(str), "HIDAPI_%p", (void*) dev->device_handle); 1072 | dev->run_loop_mode = 1073 | CFStringCreateWithCString(NULL, str, kCFStringEncodingASCII); 1074 | 1075 | /* Attach the device to a Run Loop */ 1076 | IOHIDDeviceRegisterInputReportCallback( 1077 | dev->device_handle, dev->input_report_buf, dev->max_input_report_len, 1078 | &hid_report_callback, dev); 1079 | IOHIDDeviceRegisterRemovalCallback(dev->device_handle, hid_device_removal_callback, dev); 1080 | 1081 | /* Start the read thread */ 1082 | pthread_create(&dev->thread, NULL, read_thread, dev); 1083 | 1084 | /* Wait here for the read thread to be initialized. */ 1085 | pthread_barrier_wait(&dev->barrier); 1086 | 1087 | IOObjectRelease(entry); 1088 | return dev; 1089 | 1090 | return_error: 1091 | if (dev->device_handle != NULL) 1092 | CFRelease(dev->device_handle); 1093 | 1094 | if (entry != MACH_PORT_NULL) 1095 | IOObjectRelease(entry); 1096 | 1097 | free_hid_device(dev); 1098 | return NULL; 1099 | } 1100 | 1101 | static int set_report(hid_device *dev, IOHIDReportType type, const unsigned char *data, size_t length) 1102 | { 1103 | const unsigned char *data_to_send = data; 1104 | CFIndex length_to_send = length; 1105 | IOReturn res; 1106 | unsigned char report_id; 1107 | 1108 | if (!data || (length == 0)) { 1109 | register_device_error(dev, "Zero buffer/length"); 1110 | return -1; 1111 | } 1112 | 1113 | register_device_error(dev, NULL); 1114 | 1115 | report_id = data[0]; 1116 | 1117 | if (report_id == 0x0) { 1118 | /* Not using numbered Reports. 1119 | Don't send the report number. */ 1120 | data_to_send = data+1; 1121 | length_to_send = length-1; 1122 | } 1123 | 1124 | /* Avoid crash if the device has been unplugged. */ 1125 | if (dev->disconnected) { 1126 | register_device_error(dev, "Device is disconnected"); 1127 | return -1; 1128 | } 1129 | 1130 | res = IOHIDDeviceSetReport(dev->device_handle, 1131 | type, 1132 | report_id, 1133 | data_to_send, length_to_send); 1134 | 1135 | if (res != kIOReturnSuccess) { 1136 | register_device_error_format(dev, "IOHIDDeviceSetReport failed: (0x%08X) %s", res, mach_error_string(res)); 1137 | return -1; 1138 | } 1139 | 1140 | return (int) length; 1141 | } 1142 | 1143 | static int get_report(hid_device *dev, IOHIDReportType type, unsigned char *data, size_t length) 1144 | { 1145 | unsigned char *report = data; 1146 | CFIndex report_length = length; 1147 | IOReturn res = kIOReturnSuccess; 1148 | unsigned char report_id; 1149 | 1150 | if (!data || (length == 0)) { 1151 | register_device_error(dev, "Zero buffer/length"); 1152 | return -1; 1153 | } 1154 | 1155 | register_device_error(dev, NULL); 1156 | 1157 | report_id = data[0]; 1158 | 1159 | if (report_id == 0x0) { 1160 | /* Not using numbered Reports. 1161 | Don't send the report number. */ 1162 | report = data+1; 1163 | report_length = length-1; 1164 | } 1165 | 1166 | /* Avoid crash if the device has been unplugged. */ 1167 | if (dev->disconnected) { 1168 | register_device_error(dev, "Device is disconnected"); 1169 | return -1; 1170 | } 1171 | 1172 | res = IOHIDDeviceGetReport(dev->device_handle, 1173 | type, 1174 | report_id, 1175 | report, &report_length); 1176 | 1177 | if (res != kIOReturnSuccess) { 1178 | register_device_error_format(dev, "IOHIDDeviceGetReport failed: (0x%08X) %s", res, mach_error_string(res)); 1179 | return -1; 1180 | } 1181 | 1182 | if (report_id == 0x0) { /* 0 report number still present at the beginning */ 1183 | report_length++; 1184 | } 1185 | 1186 | return (int) report_length; 1187 | } 1188 | 1189 | int HID_API_EXPORT hid_write(hid_device *dev, const unsigned char *data, size_t length) 1190 | { 1191 | return set_report(dev, kIOHIDReportTypeOutput, data, length); 1192 | } 1193 | 1194 | /* Helper function, so that this isn't duplicated in hid_read(). */ 1195 | static int return_data(hid_device *dev, unsigned char *data, size_t length) 1196 | { 1197 | /* Copy the data out of the linked list item (rpt) into the 1198 | return buffer (data), and delete the liked list item. */ 1199 | struct input_report *rpt = dev->input_reports; 1200 | size_t len = (length < rpt->len)? length: rpt->len; 1201 | if (data != NULL) { 1202 | memcpy(data, rpt->data, len); 1203 | } 1204 | dev->input_reports = rpt->next; 1205 | free(rpt->data); 1206 | free(rpt); 1207 | return (int) len; 1208 | } 1209 | 1210 | static int cond_wait(hid_device *dev, pthread_cond_t *cond, pthread_mutex_t *mutex) 1211 | { 1212 | while (!dev->input_reports) { 1213 | int res = pthread_cond_wait(cond, mutex); 1214 | if (res != 0) 1215 | return res; 1216 | 1217 | /* A res of 0 means we may have been signaled or it may 1218 | be a spurious wakeup. Check to see that there's actually 1219 | data in the queue before returning, and if not, go back 1220 | to sleep. See the pthread_cond_timedwait() man page for 1221 | details. */ 1222 | 1223 | if (dev->shutdown_thread || dev->disconnected) { 1224 | return -1; 1225 | } 1226 | } 1227 | 1228 | return 0; 1229 | } 1230 | 1231 | static int cond_timedwait(hid_device *dev, pthread_cond_t *cond, pthread_mutex_t *mutex, const struct timespec *abstime) 1232 | { 1233 | while (!dev->input_reports) { 1234 | int res = pthread_cond_timedwait(cond, mutex, abstime); 1235 | if (res != 0) 1236 | return res; 1237 | 1238 | /* A res of 0 means we may have been signaled or it may 1239 | be a spurious wakeup. Check to see that there's actually 1240 | data in the queue before returning, and if not, go back 1241 | to sleep. See the pthread_cond_timedwait() man page for 1242 | details. */ 1243 | 1244 | if (dev->shutdown_thread || dev->disconnected) { 1245 | return -1; 1246 | } 1247 | } 1248 | 1249 | return 0; 1250 | } 1251 | 1252 | int HID_API_EXPORT hid_read_timeout(hid_device *dev, unsigned char *data, size_t length, int milliseconds) 1253 | { 1254 | int bytes_read = -1; 1255 | 1256 | if (!data || (length == 0)) { 1257 | register_error_str(&dev->last_read_error_str, "Zero buffer/length"); 1258 | return -1; 1259 | } 1260 | 1261 | register_error_str(&dev->last_read_error_str, NULL); 1262 | 1263 | /* Lock the access to the report list. */ 1264 | pthread_mutex_lock(&dev->mutex); 1265 | 1266 | /* There's an input report queued up. Return it. */ 1267 | if (dev->input_reports) { 1268 | /* Return the first one */ 1269 | bytes_read = return_data(dev, data, length); 1270 | goto ret; 1271 | } 1272 | 1273 | /* Return if the device has been disconnected. */ 1274 | if (dev->disconnected) { 1275 | bytes_read = -1; 1276 | register_error_str(&dev->last_read_error_str, "hid_read_timeout: device disconnected"); 1277 | goto ret; 1278 | } 1279 | 1280 | if (dev->shutdown_thread) { 1281 | /* This means the device has been closed (or there 1282 | has been an error. An error code of -1 should 1283 | be returned. */ 1284 | bytes_read = -1; 1285 | register_error_str(&dev->last_read_error_str, "hid_read_timeout: thread shutdown"); 1286 | goto ret; 1287 | } 1288 | 1289 | /* There is no data. Go to sleep and wait for data. */ 1290 | 1291 | if (milliseconds == -1) { 1292 | /* Blocking */ 1293 | int res; 1294 | res = cond_wait(dev, &dev->condition, &dev->mutex); 1295 | if (res == 0) 1296 | bytes_read = return_data(dev, data, length); 1297 | else { 1298 | /* There was an error, or a device disconnection. */ 1299 | register_error_str(&dev->last_read_error_str, "hid_read_timeout: error waiting for more data"); 1300 | bytes_read = -1; 1301 | } 1302 | } 1303 | else if (milliseconds > 0) { 1304 | /* Non-blocking, but called with timeout. */ 1305 | int res; 1306 | struct timespec ts; 1307 | struct timeval tv; 1308 | gettimeofday(&tv, NULL); 1309 | TIMEVAL_TO_TIMESPEC(&tv, &ts); 1310 | ts.tv_sec += milliseconds / 1000; 1311 | ts.tv_nsec += (milliseconds % 1000) * 1000000; 1312 | if (ts.tv_nsec >= 1000000000L) { 1313 | ts.tv_sec++; 1314 | ts.tv_nsec -= 1000000000L; 1315 | } 1316 | 1317 | res = cond_timedwait(dev, &dev->condition, &dev->mutex, &ts); 1318 | if (res == 0) { 1319 | bytes_read = return_data(dev, data, length); 1320 | } else if (res == ETIMEDOUT) { 1321 | bytes_read = 0; 1322 | } else { 1323 | register_error_str(&dev->last_read_error_str, "hid_read_timeout: error waiting for more data"); 1324 | bytes_read = -1; 1325 | } 1326 | } 1327 | else { 1328 | /* Purely non-blocking */ 1329 | bytes_read = 0; 1330 | } 1331 | 1332 | ret: 1333 | /* Unlock */ 1334 | pthread_mutex_unlock(&dev->mutex); 1335 | return bytes_read; 1336 | } 1337 | 1338 | int HID_API_EXPORT hid_read(hid_device *dev, unsigned char *data, size_t length) 1339 | { 1340 | return hid_read_timeout(dev, data, length, (dev->blocking)? -1: 0); 1341 | } 1342 | 1343 | HID_API_EXPORT const wchar_t * HID_API_CALL hid_read_error(hid_device *dev) 1344 | { 1345 | if (dev->last_read_error_str == NULL) 1346 | return L"Success"; 1347 | return dev->last_read_error_str; 1348 | } 1349 | 1350 | int HID_API_EXPORT hid_set_nonblocking(hid_device *dev, int nonblock) 1351 | { 1352 | /* All Nonblocking operation is handled by the library. */ 1353 | dev->blocking = !nonblock; 1354 | 1355 | return 0; 1356 | } 1357 | 1358 | int HID_API_EXPORT hid_send_feature_report(hid_device *dev, const unsigned char *data, size_t length) 1359 | { 1360 | return set_report(dev, kIOHIDReportTypeFeature, data, length); 1361 | } 1362 | 1363 | int HID_API_EXPORT hid_get_feature_report(hid_device *dev, unsigned char *data, size_t length) 1364 | { 1365 | return get_report(dev, kIOHIDReportTypeFeature, data, length); 1366 | } 1367 | 1368 | int HID_API_EXPORT hid_send_output_report(hid_device *dev, const unsigned char *data, size_t length) 1369 | { 1370 | return set_report(dev, kIOHIDReportTypeOutput, data, length); 1371 | } 1372 | 1373 | int HID_API_EXPORT HID_API_CALL hid_get_input_report(hid_device *dev, unsigned char *data, size_t length) 1374 | { 1375 | return get_report(dev, kIOHIDReportTypeInput, data, length); 1376 | } 1377 | 1378 | void HID_API_EXPORT hid_close(hid_device *dev) 1379 | { 1380 | if (!dev) 1381 | return; 1382 | 1383 | /* Disconnect the report callback before close. 1384 | See comment below. 1385 | */ 1386 | if (is_macos_10_10_or_greater || !dev->disconnected) { 1387 | IOHIDDeviceRegisterInputReportCallback( 1388 | dev->device_handle, dev->input_report_buf, dev->max_input_report_len, 1389 | NULL, dev); 1390 | IOHIDDeviceRegisterRemovalCallback(dev->device_handle, NULL, dev); 1391 | IOHIDDeviceUnscheduleFromRunLoop(dev->device_handle, dev->run_loop, dev->run_loop_mode); 1392 | IOHIDDeviceScheduleWithRunLoop(dev->device_handle, CFRunLoopGetMain(), kCFRunLoopDefaultMode); 1393 | } 1394 | 1395 | /* Cause read_thread() to stop. */ 1396 | dev->shutdown_thread = 1; 1397 | 1398 | /* Wake up the run thread's event loop so that the thread can exit. */ 1399 | CFRunLoopSourceSignal(dev->source); 1400 | CFRunLoopWakeUp(dev->run_loop); 1401 | 1402 | /* Notify the read thread that it can shut down now. */ 1403 | pthread_barrier_wait(&dev->shutdown_barrier); 1404 | 1405 | /* Wait for read_thread() to end. */ 1406 | pthread_join(dev->thread, NULL); 1407 | 1408 | /* Close the OS handle to the device, but only if it's not 1409 | been unplugged. If it's been unplugged, then calling 1410 | IOHIDDeviceClose() will crash. 1411 | 1412 | UPD: The crash part was true in/until some version of macOS. 1413 | Starting with macOS 10.15, there is an opposite effect in some environments: 1414 | crash happenes if IOHIDDeviceClose() is not called. 1415 | Not leaking a resource in all tested environments. 1416 | */ 1417 | if (is_macos_10_10_or_greater || !dev->disconnected) { 1418 | IOHIDDeviceClose(dev->device_handle, dev->open_options); 1419 | } 1420 | 1421 | /* Clear out the queue of received reports. */ 1422 | pthread_mutex_lock(&dev->mutex); 1423 | while (dev->input_reports) { 1424 | return_data(dev, NULL, 0); 1425 | } 1426 | pthread_mutex_unlock(&dev->mutex); 1427 | CFRelease(dev->device_handle); 1428 | 1429 | free_hid_device(dev); 1430 | } 1431 | 1432 | int HID_API_EXPORT_CALL hid_get_manufacturer_string(hid_device *dev, wchar_t *string, size_t maxlen) 1433 | { 1434 | if (!string || !maxlen) 1435 | { 1436 | register_device_error(dev, "Zero buffer/length"); 1437 | return -1; 1438 | } 1439 | 1440 | struct hid_device_info *info = hid_get_device_info(dev); 1441 | if (!info) 1442 | { 1443 | // hid_get_device_info will have set an error already 1444 | return -1; 1445 | } 1446 | 1447 | wcsncpy(string, info->manufacturer_string, maxlen); 1448 | string[maxlen - 1] = L'\0'; 1449 | 1450 | return 0; 1451 | } 1452 | 1453 | int HID_API_EXPORT_CALL hid_get_product_string(hid_device *dev, wchar_t *string, size_t maxlen) 1454 | { 1455 | if (!string || !maxlen) { 1456 | register_device_error(dev, "Zero buffer/length"); 1457 | return -1; 1458 | } 1459 | 1460 | struct hid_device_info *info = hid_get_device_info(dev); 1461 | if (!info) { 1462 | // hid_get_device_info will have set an error already 1463 | return -1; 1464 | } 1465 | 1466 | wcsncpy(string, info->product_string, maxlen); 1467 | string[maxlen - 1] = L'\0'; 1468 | 1469 | return 0; 1470 | } 1471 | 1472 | int HID_API_EXPORT_CALL hid_get_serial_number_string(hid_device *dev, wchar_t *string, size_t maxlen) 1473 | { 1474 | if (!string || !maxlen) { 1475 | register_device_error(dev, "Zero buffer/length"); 1476 | return -1; 1477 | } 1478 | 1479 | struct hid_device_info *info = hid_get_device_info(dev); 1480 | if (!info) { 1481 | // hid_get_device_info will have set an error already 1482 | return -1; 1483 | } 1484 | 1485 | wcsncpy(string, info->serial_number, maxlen); 1486 | string[maxlen - 1] = L'\0'; 1487 | 1488 | return 0; 1489 | } 1490 | 1491 | HID_API_EXPORT struct hid_device_info *HID_API_CALL hid_get_device_info(hid_device *dev) { 1492 | if (dev->device_info) { 1493 | register_device_error(dev, NULL); 1494 | } 1495 | else { 1496 | dev->device_info = create_device_info(dev->device_handle); 1497 | if (!dev->device_info) { 1498 | register_device_error(dev, "Failed to create hid_device_info"); 1499 | } 1500 | } 1501 | 1502 | return dev->device_info; 1503 | } 1504 | 1505 | int HID_API_EXPORT_CALL hid_get_indexed_string(hid_device *dev, int string_index, wchar_t *string, size_t maxlen) 1506 | { 1507 | (void) dev; 1508 | (void) string_index; 1509 | (void) string; 1510 | (void) maxlen; 1511 | 1512 | register_device_error(dev, "hid_get_indexed_string: not available on this platform"); 1513 | return -1; 1514 | } 1515 | 1516 | int HID_API_EXPORT_CALL hid_darwin_get_location_id(hid_device *dev, uint32_t *location_id) 1517 | { 1518 | if (!location_id) { 1519 | register_device_error(dev, "Location ID is NULL"); 1520 | return -1; 1521 | } 1522 | 1523 | register_device_error(dev, NULL); 1524 | 1525 | int res = get_int_property(dev->device_handle, CFSTR(kIOHIDLocationIDKey)); 1526 | if (res != 0) { 1527 | *location_id = (uint32_t) res; 1528 | return 0; 1529 | } else { 1530 | register_device_error(dev, "Failed to get IOHIDLocationID property"); 1531 | return -1; 1532 | } 1533 | } 1534 | 1535 | void HID_API_EXPORT_CALL hid_darwin_set_open_exclusive(int open_exclusive) 1536 | { 1537 | device_open_options = (open_exclusive == 0) ? kIOHIDOptionsTypeNone : kIOHIDOptionsTypeSeizeDevice; 1538 | } 1539 | 1540 | int HID_API_EXPORT_CALL hid_darwin_get_open_exclusive(void) 1541 | { 1542 | return (device_open_options == kIOHIDOptionsTypeSeizeDevice) ? 1 : 0; 1543 | } 1544 | 1545 | int HID_API_EXPORT_CALL hid_darwin_is_device_open_exclusive(hid_device *dev) 1546 | { 1547 | return (dev->open_options == kIOHIDOptionsTypeSeizeDevice) ? 1 : 0; 1548 | } 1549 | 1550 | int HID_API_EXPORT_CALL hid_get_report_descriptor(hid_device *dev, unsigned char *buf, size_t buf_size) 1551 | { 1552 | if (!buf || !buf_size) { 1553 | register_device_error(dev, "Zero buffer/length"); 1554 | return -1; 1555 | } 1556 | 1557 | register_device_error(dev, NULL); 1558 | 1559 | CFTypeRef ref = IOHIDDeviceGetProperty(dev->device_handle, CFSTR(kIOHIDReportDescriptorKey)); 1560 | if (ref != NULL && CFGetTypeID(ref) == CFDataGetTypeID()) { 1561 | CFDataRef report_descriptor = (CFDataRef) ref; 1562 | const UInt8 *descriptor_buf = CFDataGetBytePtr(report_descriptor); 1563 | const CFIndex descriptor_buf_len = CFDataGetLength(report_descriptor); 1564 | size_t copy_len = (size_t) descriptor_buf_len; 1565 | 1566 | if (descriptor_buf == NULL || descriptor_buf_len < 0) { 1567 | register_device_error(dev, "Zero descriptor from device"); 1568 | return -1; 1569 | } 1570 | 1571 | if (buf_size < copy_len) { 1572 | copy_len = buf_size; 1573 | } 1574 | 1575 | memcpy(buf, descriptor_buf, copy_len); 1576 | return copy_len; 1577 | } 1578 | else { 1579 | register_device_error(dev, "Failed to get kIOHIDReportDescriptorKey property"); 1580 | return -1; 1581 | } 1582 | } 1583 | 1584 | HID_API_EXPORT const wchar_t * HID_API_CALL hid_error(hid_device *dev) 1585 | { 1586 | if (dev) { 1587 | if (dev->last_error_str == NULL) 1588 | return L"Success"; 1589 | return dev->last_error_str; 1590 | } 1591 | 1592 | if (last_global_error_str == NULL) 1593 | return L"Success"; 1594 | return last_global_error_str; 1595 | } 1596 | -------------------------------------------------------------------------------- /hid_darwin.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 Steven Stallion 2 | // 3 | // Redistribution and use in source and binary forms, with or without 4 | // modification, are permitted provided that the following conditions 5 | // are met: 6 | // 1. Redistributions of source code must retain the above copyright 7 | // notice, this list of conditions and the following disclaimer. 8 | // 2. Redistributions in binary form must reproduce the above copyright 9 | // notice, this list of conditions and the following disclaimer in the 10 | // documentation and/or other materials provided with the distribution. 11 | // 12 | // THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS "AS IS" AND 13 | // ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 14 | // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 15 | // ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 16 | // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 17 | // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 18 | // OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 19 | // HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 20 | // LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 21 | // OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 22 | // SUCH DAMAGE. 23 | 24 | package hid 25 | 26 | /* 27 | #include 28 | #include "hidapi_darwin.h" 29 | */ 30 | import "C" 31 | 32 | // GetLocationID returns the location ID and an error, if any. 33 | func (d *Device) GetLocationID() (uint32, error) { 34 | var id C.uint32_t 35 | 36 | res := C.hid_darwin_get_location_id(d.handle, &id) 37 | if res == -1 { 38 | return uint32(res), wrapErr(d.Error()) 39 | } 40 | return uint32(id), nil 41 | } 42 | 43 | // SetOpenExclusive changes the default behavior for Open. If exclusive is 44 | // false, devices will be opened in non-exclusive mode. 45 | func SetOpenExclusive(exclusive bool) { 46 | var open_exclusive C.int 47 | if exclusive { 48 | open_exclusive = 1 49 | } 50 | C.hid_darwin_set_open_exclusive(open_exclusive) 51 | } 52 | 53 | // GetOpenExclusive returns if exclusive mode is enabled. 54 | func GetOpenExclusive() bool { 55 | open_exclusive := C.hid_darwin_get_open_exclusive() 56 | if open_exclusive == 0 { 57 | return false 58 | } 59 | return true 60 | } 61 | 62 | // IsOpenExclusive returns if the device is in exclusive mode and an error, if 63 | // any. 64 | func (d *Device) IsOpenExclusive() (bool, error) { 65 | res := C.hid_darwin_is_device_open_exclusive(d.handle) 66 | switch res { 67 | case -1: 68 | return false, wrapErr(d.Error()) 69 | case 0: 70 | return false, nil 71 | } 72 | return true, nil 73 | } 74 | -------------------------------------------------------------------------------- /hid_libusb.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022 Steven Stallion 2 | // 3 | // Redistribution and use in source and binary forms, with or without 4 | // modification, are permitted provided that the following conditions 5 | // are met: 6 | // 1. Redistributions of source code must retain the above copyright 7 | // notice, this list of conditions and the following disclaimer. 8 | // 2. Redistributions in binary form must reproduce the above copyright 9 | // notice, this list of conditions and the following disclaimer in the 10 | // documentation and/or other materials provided with the distribution. 11 | // 12 | // THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS "AS IS" AND 13 | // ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 14 | // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 15 | // ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 16 | // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 17 | // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 18 | // OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 19 | // HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 20 | // LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 21 | // OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 22 | // SUCH DAMAGE. 23 | 24 | //go:build freebsd || (linux && libusb) 25 | 26 | package hid 27 | 28 | /* 29 | #include 30 | #include "hidapi_libusb.h" 31 | */ 32 | import "C" 33 | 34 | // InterfaceNbrAny can be passed to the OpenSysDevice function to match any 35 | // USB interface number. 36 | const InterfaceNbrAny = -1 37 | 38 | // OpenSysDevice opens the HID device attached to the system using 39 | // libusb_wrap_sys_device. This function wraps a platform-specific file 40 | // descriptor known to libusb and the USB interface number specified by fd and 41 | // ifnum, respectively. 42 | func OpenSysDevice(fd uintptr, ifnum int) (*Device, error) { 43 | handle := C.hid_libusb_wrap_sys_device(C.intptr_t(fd), C.int(ifnum)) 44 | if handle == nil { 45 | return nil, wrapErr(Error()) 46 | } 47 | return &Device{handle}, nil 48 | } 49 | -------------------------------------------------------------------------------- /hid_linux.c: -------------------------------------------------------------------------------- 1 | /******************************************************* 2 | HIDAPI - Multi-Platform library for 3 | communication with HID devices. 4 | 5 | Alan Ott 6 | Signal 11 Software 7 | 8 | libusb/hidapi Team 9 | 10 | Copyright 2022, All Rights Reserved. 11 | 12 | At the discretion of the user of this library, 13 | this software may be licensed under the terms of the 14 | GNU General Public License v3, a BSD-Style license, or the 15 | original HIDAPI license as outlined in the LICENSE.txt, 16 | LICENSE-gpl3.txt, LICENSE-bsd.txt, and LICENSE-orig.txt 17 | files located at the root of the source distribution. 18 | These files may also be found in the public source 19 | code repository located at: 20 | https://github.com/libusb/hidapi . 21 | ********************************************************/ 22 | 23 | //go:build !libusb 24 | 25 | /* C */ 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | 32 | /* Unix */ 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | #include 40 | 41 | /* Linux */ 42 | #include 43 | #include 44 | #include 45 | #include 46 | 47 | #include "hidapi.h" 48 | 49 | #ifdef HIDAPI_ALLOW_BUILD_WORKAROUND_KERNEL_2_6_39 50 | /* This definitions first appeared in Linux Kernel 2.6.39 in linux/hidraw.h. 51 | hidapi doesn't support kernels older than that, 52 | so we don't define macros below explicitly, to fail builds on old kernels. 53 | For those who really need this as a workaround (e.g. to be able to build on old build machines), 54 | can workaround by defining the macro above. 55 | */ 56 | #ifndef HIDIOCSFEATURE 57 | #define HIDIOCSFEATURE(len) _IOC(_IOC_WRITE|_IOC_READ, 'H', 0x06, len) 58 | #endif 59 | #ifndef HIDIOCGFEATURE 60 | #define HIDIOCGFEATURE(len) _IOC(_IOC_WRITE|_IOC_READ, 'H', 0x07, len) 61 | #endif 62 | 63 | #endif 64 | 65 | 66 | // HIDIOCGINPUT and HIDIOCSOUTPUT are not defined in Linux kernel headers < 5.11. 67 | // These definitions are from hidraw.h in Linux >= 5.11. 68 | // https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=f43d3870cafa2a0f3854c1819c8385733db8f9ae 69 | #ifndef HIDIOCGINPUT 70 | #define HIDIOCGINPUT(len) _IOC(_IOC_WRITE|_IOC_READ, 'H', 0x0A, len) 71 | #endif 72 | #ifndef HIDIOCSOUTPUT 73 | #define HIDIOCSOUTPUT(len) _IOC(_IOC_WRITE|_IOC_READ, 'H', 0x0B, len) 74 | #endif 75 | 76 | struct hid_device_ { 77 | int device_handle; 78 | int blocking; 79 | wchar_t *last_error_str; 80 | wchar_t *last_read_error_str; 81 | struct hid_device_info* device_info; 82 | }; 83 | 84 | static struct hid_api_version api_version = { 85 | .major = HID_API_VERSION_MAJOR, 86 | .minor = HID_API_VERSION_MINOR, 87 | .patch = HID_API_VERSION_PATCH 88 | }; 89 | 90 | static wchar_t *last_global_error_str = NULL; 91 | 92 | 93 | static hid_device *new_hid_device(void) 94 | { 95 | hid_device *dev = (hid_device*) calloc(1, sizeof(hid_device)); 96 | if (dev == NULL) { 97 | return NULL; 98 | } 99 | 100 | dev->device_handle = -1; 101 | dev->blocking = 1; 102 | dev->last_error_str = NULL; 103 | dev->last_read_error_str = NULL; 104 | dev->device_info = NULL; 105 | 106 | return dev; 107 | } 108 | 109 | 110 | /* The caller must free the returned string with free(). */ 111 | static wchar_t *utf8_to_wchar_t(const char *utf8) 112 | { 113 | wchar_t *ret = NULL; 114 | 115 | if (utf8) { 116 | size_t wlen = mbstowcs(NULL, utf8, 0); 117 | if ((size_t) -1 == wlen) { 118 | return wcsdup(L""); 119 | } 120 | ret = (wchar_t*) calloc(wlen+1, sizeof(wchar_t)); 121 | if (ret == NULL) { 122 | /* as much as we can do at this point */ 123 | return NULL; 124 | } 125 | mbstowcs(ret, utf8, wlen+1); 126 | ret[wlen] = 0x0000; 127 | } 128 | 129 | return ret; 130 | } 131 | 132 | 133 | /* Makes a copy of the given error message (and decoded according to the 134 | * currently locale) into the wide string pointer pointed by error_str. 135 | * The last stored error string is freed. 136 | * Use register_error_str(NULL) to free the error message completely. */ 137 | static void register_error_str(wchar_t **error_str, const char *msg) 138 | { 139 | free(*error_str); 140 | *error_str = utf8_to_wchar_t(msg); 141 | } 142 | 143 | /* Semilar to register_error_str, but allows passing a format string with va_list args into this function. */ 144 | static void register_error_str_vformat(wchar_t **error_str, const char *format, va_list args) 145 | { 146 | char msg[256]; 147 | vsnprintf(msg, sizeof(msg), format, args); 148 | 149 | register_error_str(error_str, msg); 150 | } 151 | 152 | /* Set the last global error to be reported by hid_error(NULL). 153 | * The given error message will be copied (and decoded according to the 154 | * currently locale, so do not pass in string constants). 155 | * The last stored global error message is freed. 156 | * Use register_global_error(NULL) to indicate "no error". */ 157 | static void register_global_error(const char *msg) 158 | { 159 | register_error_str(&last_global_error_str, msg); 160 | } 161 | 162 | /* Similar to register_global_error, but allows passing a format string into this function. */ 163 | static void register_global_error_format(const char *format, ...) 164 | { 165 | va_list args; 166 | va_start(args, format); 167 | register_error_str_vformat(&last_global_error_str, format, args); 168 | va_end(args); 169 | } 170 | 171 | /* Set the last error for a device to be reported by hid_error(dev). 172 | * The given error message will be copied (and decoded according to the 173 | * currently locale, so do not pass in string constants). 174 | * The last stored device error message is freed. 175 | * Use register_device_error(dev, NULL) to indicate "no error". */ 176 | static void register_device_error(hid_device *dev, const char *msg) 177 | { 178 | register_error_str(&dev->last_error_str, msg); 179 | } 180 | 181 | /* Similar to register_device_error, but you can pass a format string into this function. */ 182 | static void register_device_error_format(hid_device *dev, const char *format, ...) 183 | { 184 | va_list args; 185 | va_start(args, format); 186 | register_error_str_vformat(&dev->last_error_str, format, args); 187 | va_end(args); 188 | } 189 | 190 | /* Get an attribute value from a udev_device and return it as a whar_t 191 | string. The returned string must be freed with free() when done.*/ 192 | static wchar_t *copy_udev_string(struct udev_device *dev, const char *udev_name) 193 | { 194 | return utf8_to_wchar_t(udev_device_get_sysattr_value(dev, udev_name)); 195 | } 196 | 197 | /* 198 | * Gets the size of the HID item at the given position 199 | * Returns 1 if successful, 0 if an invalid key 200 | * Sets data_len and key_size when successful 201 | */ 202 | static int get_hid_item_size(const __u8 *report_descriptor, __u32 size, unsigned int pos, int *data_len, int *key_size) 203 | { 204 | int key = report_descriptor[pos]; 205 | int size_code; 206 | 207 | /* 208 | * This is a Long Item. The next byte contains the 209 | * length of the data section (value) for this key. 210 | * See the HID specification, version 1.11, section 211 | * 6.2.2.3, titled "Long Items." 212 | */ 213 | if ((key & 0xf0) == 0xf0) { 214 | if (pos + 1 < size) 215 | { 216 | *data_len = report_descriptor[pos + 1]; 217 | *key_size = 3; 218 | return 1; 219 | } 220 | *data_len = 0; /* malformed report */ 221 | *key_size = 0; 222 | } 223 | 224 | /* 225 | * This is a Short Item. The bottom two bits of the 226 | * key contain the size code for the data section 227 | * (value) for this key. Refer to the HID 228 | * specification, version 1.11, section 6.2.2.2, 229 | * titled "Short Items." 230 | */ 231 | size_code = key & 0x3; 232 | switch (size_code) { 233 | case 0: 234 | case 1: 235 | case 2: 236 | *data_len = size_code; 237 | *key_size = 1; 238 | return 1; 239 | case 3: 240 | *data_len = 4; 241 | *key_size = 1; 242 | return 1; 243 | default: 244 | /* Can't ever happen since size_code is & 0x3 */ 245 | *data_len = 0; 246 | *key_size = 0; 247 | break; 248 | }; 249 | 250 | /* malformed report */ 251 | return 0; 252 | } 253 | 254 | /* 255 | * Get bytes from a HID Report Descriptor. 256 | * Only call with a num_bytes of 0, 1, 2, or 4. 257 | */ 258 | static __u32 get_hid_report_bytes(const __u8 *rpt, size_t len, size_t num_bytes, size_t cur) 259 | { 260 | /* Return if there aren't enough bytes. */ 261 | if (cur + num_bytes >= len) 262 | return 0; 263 | 264 | if (num_bytes == 0) 265 | return 0; 266 | else if (num_bytes == 1) 267 | return rpt[cur + 1]; 268 | else if (num_bytes == 2) 269 | return (rpt[cur + 2] * 256 + rpt[cur + 1]); 270 | else if (num_bytes == 4) 271 | return ( 272 | rpt[cur + 4] * 0x01000000 + 273 | rpt[cur + 3] * 0x00010000 + 274 | rpt[cur + 2] * 0x00000100 + 275 | rpt[cur + 1] * 0x00000001 276 | ); 277 | else 278 | return 0; 279 | } 280 | 281 | /* 282 | * Iterates until the end of a Collection. 283 | * Assumes that *pos is exactly at the beginning of a Collection. 284 | * Skips all nested Collection, i.e. iterates until the end of current level Collection. 285 | * 286 | * The return value is non-0 when an end of current Collection is found, 287 | * 0 when error is occured (broken Descriptor, end of a Collection is found before its begin, 288 | * or no Collection is found at all). 289 | */ 290 | static int hid_iterate_over_collection(const __u8 *report_descriptor, __u32 size, unsigned int *pos, int *data_len, int *key_size) 291 | { 292 | int collection_level = 0; 293 | 294 | while (*pos < size) { 295 | int key = report_descriptor[*pos]; 296 | int key_cmd = key & 0xfc; 297 | 298 | /* Determine data_len and key_size */ 299 | if (!get_hid_item_size(report_descriptor, size, *pos, data_len, key_size)) 300 | return 0; /* malformed report */ 301 | 302 | switch (key_cmd) { 303 | case 0xa0: /* Collection 6.2.2.4 (Main) */ 304 | collection_level++; 305 | break; 306 | case 0xc0: /* End Collection 6.2.2.4 (Main) */ 307 | collection_level--; 308 | break; 309 | } 310 | 311 | if (collection_level < 0) { 312 | /* Broken descriptor or someone is using this function wrong, 313 | * i.e. should be called exactly at the collection start */ 314 | return 0; 315 | } 316 | 317 | if (collection_level == 0) { 318 | /* Found it! 319 | * Also possible when called not at the collection start, but should not happen if used correctly */ 320 | return 1; 321 | } 322 | 323 | *pos += *data_len + *key_size; 324 | } 325 | 326 | return 0; /* Did not find the end of a Collection */ 327 | } 328 | 329 | struct hid_usage_iterator { 330 | unsigned int pos; 331 | int usage_page_found; 332 | unsigned short usage_page; 333 | }; 334 | 335 | /* 336 | * Retrieves the device's Usage Page and Usage from the report descriptor. 337 | * The algorithm returns the current Usage Page/Usage pair whenever a new 338 | * Collection is found and a Usage Local Item is currently in scope. 339 | * Usage Local Items are consumed by each Main Item (See. 6.2.2.8). 340 | * The algorithm should give similar results as Apple's: 341 | * https://developer.apple.com/documentation/iokit/kiohiddeviceusagepairskey?language=objc 342 | * Physical Collections are also matched (macOS does the same). 343 | * 344 | * This function can be called repeatedly until it returns non-0 345 | * Usage is found. pos is the starting point (initially 0) and will be updated 346 | * to the next search position. 347 | * 348 | * The return value is 0 when a pair is found. 349 | * 1 when finished processing descriptor. 350 | * -1 on a malformed report. 351 | */ 352 | static int get_next_hid_usage(const __u8 *report_descriptor, __u32 size, struct hid_usage_iterator *ctx, unsigned short *usage_page, unsigned short *usage) 353 | { 354 | int data_len, key_size; 355 | int initial = ctx->pos == 0; /* Used to handle case where no top-level application collection is defined */ 356 | 357 | int usage_found = 0; 358 | 359 | while (ctx->pos < size) { 360 | int key = report_descriptor[ctx->pos]; 361 | int key_cmd = key & 0xfc; 362 | 363 | /* Determine data_len and key_size */ 364 | if (!get_hid_item_size(report_descriptor, size, ctx->pos, &data_len, &key_size)) 365 | return -1; /* malformed report */ 366 | 367 | switch (key_cmd) { 368 | case 0x4: /* Usage Page 6.2.2.7 (Global) */ 369 | ctx->usage_page = get_hid_report_bytes(report_descriptor, size, data_len, ctx->pos); 370 | ctx->usage_page_found = 1; 371 | break; 372 | 373 | case 0x8: /* Usage 6.2.2.8 (Local) */ 374 | if (data_len == 4) { /* Usages 5.5 / Usage Page 6.2.2.7 */ 375 | ctx->usage_page = get_hid_report_bytes(report_descriptor, size, 2, ctx->pos + 2); 376 | ctx->usage_page_found = 1; 377 | *usage = get_hid_report_bytes(report_descriptor, size, 2, ctx->pos); 378 | usage_found = 1; 379 | } 380 | else { 381 | *usage = get_hid_report_bytes(report_descriptor, size, data_len, ctx->pos); 382 | usage_found = 1; 383 | } 384 | break; 385 | 386 | case 0xa0: /* Collection 6.2.2.4 (Main) */ 387 | if (!hid_iterate_over_collection(report_descriptor, size, &ctx->pos, &data_len, &key_size)) { 388 | return -1; 389 | } 390 | 391 | /* A pair is valid - to be reported when Collection is found */ 392 | if (usage_found && ctx->usage_page_found) { 393 | *usage_page = ctx->usage_page; 394 | return 0; 395 | } 396 | 397 | break; 398 | } 399 | 400 | /* Skip over this key and its associated data */ 401 | ctx->pos += data_len + key_size; 402 | } 403 | 404 | /* If no top-level application collection is found and usage page/usage pair is found, pair is valid 405 | https://docs.microsoft.com/en-us/windows-hardware/drivers/hid/top-level-collections */ 406 | if (initial && usage_found && ctx->usage_page_found) { 407 | *usage_page = ctx->usage_page; 408 | return 0; /* success */ 409 | } 410 | 411 | return 1; /* finished processing */ 412 | } 413 | 414 | /* 415 | * Retrieves the hidraw report descriptor from a file. 416 | * When using this form, /device/report_descriptor, elevated privileges are not required. 417 | */ 418 | static int get_hid_report_descriptor(const char *rpt_path, struct hidraw_report_descriptor *rpt_desc) 419 | { 420 | int rpt_handle; 421 | ssize_t res; 422 | 423 | rpt_handle = open(rpt_path, O_RDONLY | O_CLOEXEC); 424 | if (rpt_handle < 0) { 425 | register_global_error_format("open failed (%s): %s", rpt_path, strerror(errno)); 426 | return -1; 427 | } 428 | 429 | /* 430 | * Read in the Report Descriptor 431 | * The sysfs file has a maximum size of 4096 (which is the same as HID_MAX_DESCRIPTOR_SIZE) so we should always 432 | * be ok when reading the descriptor. 433 | * In practice if the HID descriptor is any larger I suspect many other things will break. 434 | */ 435 | memset(rpt_desc, 0x0, sizeof(*rpt_desc)); 436 | res = read(rpt_handle, rpt_desc->value, HID_MAX_DESCRIPTOR_SIZE); 437 | if (res < 0) { 438 | register_global_error_format("read failed (%s): %s", rpt_path, strerror(errno)); 439 | } 440 | rpt_desc->size = (__u32) res; 441 | 442 | close(rpt_handle); 443 | return (int) res; 444 | } 445 | 446 | /* return size of the descriptor, or -1 on failure */ 447 | static int get_hid_report_descriptor_from_sysfs(const char *sysfs_path, struct hidraw_report_descriptor *rpt_desc) 448 | { 449 | int res = -1; 450 | /* Construct /device/report_descriptor */ 451 | size_t rpt_path_len = strlen(sysfs_path) + 25 + 1; 452 | char* rpt_path = (char*) calloc(1, rpt_path_len); 453 | if (!rpt_path) 454 | return -1; 455 | snprintf(rpt_path, rpt_path_len, "%s/device/report_descriptor", sysfs_path); 456 | 457 | res = get_hid_report_descriptor(rpt_path, rpt_desc); 458 | free(rpt_path); 459 | 460 | return res; 461 | } 462 | 463 | /* return non-zero if successfully parsed */ 464 | static int parse_hid_vid_pid_from_uevent(const char *uevent, unsigned *bus_type, unsigned short *vendor_id, unsigned short *product_id) 465 | { 466 | char tmp[1024]; 467 | size_t uevent_len = strlen(uevent); 468 | if (uevent_len > sizeof(tmp) - 1) 469 | uevent_len = sizeof(tmp) - 1; 470 | memcpy(tmp, uevent, uevent_len); 471 | tmp[uevent_len] = '\0'; 472 | 473 | char *saveptr = NULL; 474 | char *line; 475 | char *key; 476 | char *value; 477 | 478 | line = strtok_r(tmp, "\n", &saveptr); 479 | while (line != NULL) { 480 | /* line: "KEY=value" */ 481 | key = line; 482 | value = strchr(line, '='); 483 | if (!value) { 484 | goto next_line; 485 | } 486 | *value = '\0'; 487 | value++; 488 | 489 | if (strcmp(key, "HID_ID") == 0) { 490 | /** 491 | * type vendor product 492 | * HID_ID=0003:000005AC:00008242 493 | **/ 494 | int ret = sscanf(value, "%x:%hx:%hx", bus_type, vendor_id, product_id); 495 | if (ret == 3) { 496 | return 1; 497 | } 498 | } 499 | 500 | next_line: 501 | line = strtok_r(NULL, "\n", &saveptr); 502 | } 503 | 504 | register_global_error("Couldn't find/parse HID_ID"); 505 | return 0; 506 | } 507 | 508 | /* return non-zero if successfully parsed */ 509 | static int parse_hid_vid_pid_from_uevent_path(const char *uevent_path, unsigned *bus_type, unsigned short *vendor_id, unsigned short *product_id) 510 | { 511 | int handle; 512 | ssize_t res; 513 | 514 | handle = open(uevent_path, O_RDONLY | O_CLOEXEC); 515 | if (handle < 0) { 516 | register_global_error_format("open failed (%s): %s", uevent_path, strerror(errno)); 517 | return 0; 518 | } 519 | 520 | char buf[1024]; 521 | res = read(handle, buf, sizeof(buf) - 1); /* -1 for '\0' at the end */ 522 | close(handle); 523 | 524 | if (res < 0) { 525 | register_global_error_format("read failed (%s): %s", uevent_path, strerror(errno)); 526 | return 0; 527 | } 528 | 529 | buf[res] = '\0'; 530 | return parse_hid_vid_pid_from_uevent(buf, bus_type, vendor_id, product_id); 531 | } 532 | 533 | /* return non-zero if successfully read/parsed */ 534 | static int parse_hid_vid_pid_from_sysfs(const char *sysfs_path, unsigned *bus_type, unsigned short *vendor_id, unsigned short *product_id) 535 | { 536 | int res = 0; 537 | /* Construct /device/uevent */ 538 | size_t uevent_path_len = strlen(sysfs_path) + 14 + 1; 539 | char* uevent_path = (char*) calloc(1, uevent_path_len); 540 | snprintf(uevent_path, uevent_path_len, "%s/device/uevent", sysfs_path); 541 | 542 | res = parse_hid_vid_pid_from_uevent_path(uevent_path, bus_type, vendor_id, product_id); 543 | free(uevent_path); 544 | 545 | return res; 546 | } 547 | 548 | static int get_hid_report_descriptor_from_hidraw(hid_device *dev, struct hidraw_report_descriptor *rpt_desc) 549 | { 550 | int desc_size = 0; 551 | 552 | /* Get Report Descriptor Size */ 553 | int res = ioctl(dev->device_handle, HIDIOCGRDESCSIZE, &desc_size); 554 | if (res < 0) { 555 | register_device_error_format(dev, "ioctl(GRDESCSIZE): %s", strerror(errno)); 556 | return res; 557 | } 558 | 559 | /* Get Report Descriptor */ 560 | memset(rpt_desc, 0x0, sizeof(*rpt_desc)); 561 | rpt_desc->size = desc_size; 562 | res = ioctl(dev->device_handle, HIDIOCGRDESC, rpt_desc); 563 | if (res < 0) { 564 | register_device_error_format(dev, "ioctl(GRDESC): %s", strerror(errno)); 565 | } 566 | 567 | return res; 568 | } 569 | 570 | /* 571 | * The caller is responsible for free()ing the (newly-allocated) character 572 | * strings pointed to by serial_number_utf8 and product_name_utf8 after use. 573 | */ 574 | static int parse_uevent_info(const char *uevent, unsigned *bus_type, 575 | unsigned short *vendor_id, unsigned short *product_id, 576 | char **serial_number_utf8, char **product_name_utf8) 577 | { 578 | char tmp[1024]; 579 | 580 | if (!uevent) { 581 | return 0; 582 | } 583 | 584 | size_t uevent_len = strlen(uevent); 585 | if (uevent_len > sizeof(tmp) - 1) 586 | uevent_len = sizeof(tmp) - 1; 587 | memcpy(tmp, uevent, uevent_len); 588 | tmp[uevent_len] = '\0'; 589 | 590 | char *saveptr = NULL; 591 | char *line; 592 | char *key; 593 | char *value; 594 | 595 | int found_id = 0; 596 | int found_serial = 0; 597 | int found_name = 0; 598 | 599 | line = strtok_r(tmp, "\n", &saveptr); 600 | while (line != NULL) { 601 | /* line: "KEY=value" */ 602 | key = line; 603 | value = strchr(line, '='); 604 | if (!value) { 605 | goto next_line; 606 | } 607 | *value = '\0'; 608 | value++; 609 | 610 | if (strcmp(key, "HID_ID") == 0) { 611 | /** 612 | * type vendor product 613 | * HID_ID=0003:000005AC:00008242 614 | **/ 615 | int ret = sscanf(value, "%x:%hx:%hx", bus_type, vendor_id, product_id); 616 | if (ret == 3) { 617 | found_id = 1; 618 | } 619 | } else if (strcmp(key, "HID_NAME") == 0) { 620 | /* The caller has to free the product name */ 621 | *product_name_utf8 = strdup(value); 622 | found_name = 1; 623 | } else if (strcmp(key, "HID_UNIQ") == 0) { 624 | /* The caller has to free the serial number */ 625 | *serial_number_utf8 = strdup(value); 626 | found_serial = 1; 627 | } 628 | 629 | next_line: 630 | line = strtok_r(NULL, "\n", &saveptr); 631 | } 632 | 633 | return (found_id && found_name && found_serial); 634 | } 635 | 636 | 637 | static struct hid_device_info * create_device_info_for_device(struct udev_device *raw_dev) 638 | { 639 | struct hid_device_info *root = NULL; 640 | struct hid_device_info *cur_dev = NULL; 641 | 642 | const char *sysfs_path; 643 | const char *dev_path; 644 | const char *str; 645 | struct udev_device *hid_dev; /* The device's HID udev node. */ 646 | struct udev_device *usb_dev; /* The device's USB udev node. */ 647 | struct udev_device *intf_dev; /* The device's interface (in the USB sense). */ 648 | unsigned short dev_vid; 649 | unsigned short dev_pid; 650 | char *serial_number_utf8 = NULL; 651 | char *product_name_utf8 = NULL; 652 | unsigned bus_type; 653 | int result; 654 | struct hidraw_report_descriptor report_desc; 655 | 656 | sysfs_path = udev_device_get_syspath(raw_dev); 657 | dev_path = udev_device_get_devnode(raw_dev); 658 | 659 | hid_dev = udev_device_get_parent_with_subsystem_devtype( 660 | raw_dev, 661 | "hid", 662 | NULL); 663 | 664 | if (!hid_dev) { 665 | /* Unable to find parent hid device. */ 666 | goto end; 667 | } 668 | 669 | result = parse_uevent_info( 670 | udev_device_get_sysattr_value(hid_dev, "uevent"), 671 | &bus_type, 672 | &dev_vid, 673 | &dev_pid, 674 | &serial_number_utf8, 675 | &product_name_utf8); 676 | 677 | if (!result) { 678 | /* parse_uevent_info() failed for at least one field. */ 679 | goto end; 680 | } 681 | 682 | /* Filter out unhandled devices right away */ 683 | switch (bus_type) { 684 | case BUS_BLUETOOTH: 685 | case BUS_I2C: 686 | case BUS_USB: 687 | case BUS_SPI: 688 | break; 689 | 690 | default: 691 | goto end; 692 | } 693 | 694 | /* Create the record. */ 695 | root = (struct hid_device_info*) calloc(1, sizeof(struct hid_device_info)); 696 | if (!root) 697 | goto end; 698 | 699 | cur_dev = root; 700 | 701 | /* Fill out the record */ 702 | cur_dev->next = NULL; 703 | cur_dev->path = dev_path? strdup(dev_path): NULL; 704 | 705 | /* VID/PID */ 706 | cur_dev->vendor_id = dev_vid; 707 | cur_dev->product_id = dev_pid; 708 | 709 | /* Serial Number */ 710 | cur_dev->serial_number = utf8_to_wchar_t(serial_number_utf8); 711 | 712 | /* Release Number */ 713 | cur_dev->release_number = 0x0; 714 | 715 | /* Interface Number */ 716 | cur_dev->interface_number = -1; 717 | 718 | switch (bus_type) { 719 | case BUS_USB: 720 | /* The device pointed to by raw_dev contains information about 721 | the hidraw device. In order to get information about the 722 | USB device, get the parent device with the 723 | subsystem/devtype pair of "usb"/"usb_device". This will 724 | be several levels up the tree, but the function will find 725 | it. */ 726 | usb_dev = udev_device_get_parent_with_subsystem_devtype( 727 | raw_dev, 728 | "usb", 729 | "usb_device"); 730 | 731 | /* uhid USB devices 732 | * Since this is a virtual hid interface, no USB information will 733 | * be available. */ 734 | if (!usb_dev) { 735 | /* Manufacturer and Product strings */ 736 | cur_dev->manufacturer_string = wcsdup(L""); 737 | cur_dev->product_string = utf8_to_wchar_t(product_name_utf8); 738 | break; 739 | } 740 | 741 | cur_dev->manufacturer_string = copy_udev_string(usb_dev, "manufacturer"); 742 | cur_dev->product_string = copy_udev_string(usb_dev, "product"); 743 | 744 | cur_dev->bus_type = HID_API_BUS_USB; 745 | 746 | str = udev_device_get_sysattr_value(usb_dev, "bcdDevice"); 747 | cur_dev->release_number = (str)? strtol(str, NULL, 16): 0x0; 748 | 749 | /* Get a handle to the interface's udev node. */ 750 | intf_dev = udev_device_get_parent_with_subsystem_devtype( 751 | raw_dev, 752 | "usb", 753 | "usb_interface"); 754 | if (intf_dev) { 755 | str = udev_device_get_sysattr_value(intf_dev, "bInterfaceNumber"); 756 | cur_dev->interface_number = (str)? strtol(str, NULL, 16): -1; 757 | } 758 | 759 | break; 760 | 761 | case BUS_BLUETOOTH: 762 | cur_dev->manufacturer_string = wcsdup(L""); 763 | cur_dev->product_string = utf8_to_wchar_t(product_name_utf8); 764 | 765 | cur_dev->bus_type = HID_API_BUS_BLUETOOTH; 766 | 767 | break; 768 | case BUS_I2C: 769 | cur_dev->manufacturer_string = wcsdup(L""); 770 | cur_dev->product_string = utf8_to_wchar_t(product_name_utf8); 771 | 772 | cur_dev->bus_type = HID_API_BUS_I2C; 773 | 774 | break; 775 | 776 | case BUS_SPI: 777 | cur_dev->manufacturer_string = wcsdup(L""); 778 | cur_dev->product_string = utf8_to_wchar_t(product_name_utf8); 779 | 780 | cur_dev->bus_type = HID_API_BUS_SPI; 781 | 782 | break; 783 | 784 | default: 785 | /* Unknown device type - this should never happen, as we 786 | * check for USB and Bluetooth devices above */ 787 | break; 788 | } 789 | 790 | /* Usage Page and Usage */ 791 | 792 | if (sysfs_path) { 793 | result = get_hid_report_descriptor_from_sysfs(sysfs_path, &report_desc); 794 | } 795 | else { 796 | result = -1; 797 | } 798 | 799 | if (result >= 0) { 800 | unsigned short page = 0, usage = 0; 801 | struct hid_usage_iterator usage_iterator; 802 | memset(&usage_iterator, 0, sizeof(usage_iterator)); 803 | 804 | /* 805 | * Parse the first usage and usage page 806 | * out of the report descriptor. 807 | */ 808 | if (!get_next_hid_usage(report_desc.value, report_desc.size, &usage_iterator, &page, &usage)) { 809 | cur_dev->usage_page = page; 810 | cur_dev->usage = usage; 811 | } 812 | 813 | /* 814 | * Parse any additional usage and usage pages 815 | * out of the report descriptor. 816 | */ 817 | while (!get_next_hid_usage(report_desc.value, report_desc.size, &usage_iterator, &page, &usage)) { 818 | /* Create new record for additional usage pairs */ 819 | struct hid_device_info *tmp = (struct hid_device_info*) calloc(1, sizeof(struct hid_device_info)); 820 | struct hid_device_info *prev_dev = cur_dev; 821 | 822 | if (!tmp) 823 | break; 824 | cur_dev->next = tmp; 825 | cur_dev = tmp; 826 | 827 | /* Update fields */ 828 | cur_dev->path = dev_path? strdup(dev_path): NULL; 829 | cur_dev->vendor_id = dev_vid; 830 | cur_dev->product_id = dev_pid; 831 | cur_dev->serial_number = prev_dev->serial_number? wcsdup(prev_dev->serial_number): NULL; 832 | cur_dev->release_number = prev_dev->release_number; 833 | cur_dev->interface_number = prev_dev->interface_number; 834 | cur_dev->manufacturer_string = prev_dev->manufacturer_string? wcsdup(prev_dev->manufacturer_string): NULL; 835 | cur_dev->product_string = prev_dev->product_string? wcsdup(prev_dev->product_string): NULL; 836 | cur_dev->usage_page = page; 837 | cur_dev->usage = usage; 838 | cur_dev->bus_type = prev_dev->bus_type; 839 | } 840 | } 841 | 842 | end: 843 | free(serial_number_utf8); 844 | free(product_name_utf8); 845 | 846 | return root; 847 | } 848 | 849 | static struct hid_device_info * create_device_info_for_hid_device(hid_device *dev) { 850 | struct udev *udev; 851 | struct udev_device *udev_dev; 852 | struct stat s; 853 | int ret = -1; 854 | struct hid_device_info *root = NULL; 855 | 856 | register_device_error(dev, NULL); 857 | 858 | /* Get the dev_t (major/minor numbers) from the file handle. */ 859 | ret = fstat(dev->device_handle, &s); 860 | if (-1 == ret) { 861 | register_device_error(dev, "Failed to stat device handle"); 862 | return NULL; 863 | } 864 | 865 | /* Create the udev object */ 866 | udev = udev_new(); 867 | if (!udev) { 868 | errno = ENOMEM; 869 | register_device_error(dev, "Couldn't create udev context"); 870 | return NULL; 871 | } 872 | 873 | /* Open a udev device from the dev_t. 'c' means character device. */ 874 | udev_dev = udev_device_new_from_devnum(udev, 'c', s.st_rdev); 875 | if (udev_dev) { 876 | root = create_device_info_for_device(udev_dev); 877 | } 878 | 879 | if (!root) { 880 | /* TODO: have a better error reporting via create_device_info_for_device */ 881 | errno = EIO; 882 | register_device_error(dev, "Couldn't create hid_device_info"); 883 | } 884 | 885 | udev_device_unref(udev_dev); 886 | udev_unref(udev); 887 | 888 | return root; 889 | } 890 | 891 | HID_API_EXPORT const struct hid_api_version* HID_API_CALL hid_version(void) 892 | { 893 | return &api_version; 894 | } 895 | 896 | HID_API_EXPORT const char* HID_API_CALL hid_version_str(void) 897 | { 898 | return HID_API_VERSION_STR; 899 | } 900 | 901 | int HID_API_EXPORT hid_init(void) 902 | { 903 | const char *locale; 904 | 905 | /* indicate no error */ 906 | register_global_error(NULL); 907 | 908 | /* Set the locale if it's not set. */ 909 | locale = setlocale(LC_CTYPE, NULL); 910 | if (!locale) 911 | setlocale(LC_CTYPE, ""); 912 | 913 | return 0; 914 | } 915 | 916 | int HID_API_EXPORT hid_exit(void) 917 | { 918 | /* Free global error message */ 919 | register_global_error(NULL); 920 | 921 | return 0; 922 | } 923 | 924 | struct hid_device_info HID_API_EXPORT *hid_enumerate(unsigned short vendor_id, unsigned short product_id) 925 | { 926 | struct udev *udev; 927 | struct udev_enumerate *enumerate; 928 | struct udev_list_entry *devices, *dev_list_entry; 929 | 930 | struct hid_device_info *root = NULL; /* return object */ 931 | struct hid_device_info *cur_dev = NULL; 932 | 933 | hid_init(); 934 | /* register_global_error: global error is reset by hid_init */ 935 | 936 | /* Create the udev object */ 937 | udev = udev_new(); 938 | if (!udev) { 939 | register_global_error("Couldn't create udev context"); 940 | return NULL; 941 | } 942 | 943 | /* Create a list of the devices in the 'hidraw' subsystem. */ 944 | enumerate = udev_enumerate_new(udev); 945 | udev_enumerate_add_match_subsystem(enumerate, "hidraw"); 946 | udev_enumerate_scan_devices(enumerate); 947 | devices = udev_enumerate_get_list_entry(enumerate); 948 | /* For each item, see if it matches the vid/pid, and if so 949 | create a udev_device record for it */ 950 | udev_list_entry_foreach(dev_list_entry, devices) { 951 | const char *sysfs_path; 952 | unsigned short dev_vid = 0; 953 | unsigned short dev_pid = 0; 954 | unsigned bus_type = 0; 955 | struct udev_device *raw_dev; /* The device's hidraw udev node. */ 956 | struct hid_device_info * tmp; 957 | 958 | /* Get the filename of the /sys entry for the device 959 | and create a udev_device object (dev) representing it */ 960 | sysfs_path = udev_list_entry_get_name(dev_list_entry); 961 | if (!sysfs_path) 962 | continue; 963 | 964 | if (vendor_id != 0 || product_id != 0) { 965 | if (!parse_hid_vid_pid_from_sysfs(sysfs_path, &bus_type, &dev_vid, &dev_pid)) 966 | continue; 967 | 968 | if (vendor_id != 0 && vendor_id != dev_vid) 969 | continue; 970 | if (product_id != 0 && product_id != dev_pid) 971 | continue; 972 | } 973 | 974 | raw_dev = udev_device_new_from_syspath(udev, sysfs_path); 975 | if (!raw_dev) 976 | continue; 977 | 978 | tmp = create_device_info_for_device(raw_dev); 979 | if (tmp) { 980 | if (cur_dev) { 981 | cur_dev->next = tmp; 982 | } 983 | else { 984 | root = tmp; 985 | } 986 | cur_dev = tmp; 987 | 988 | /* move the pointer to the tail of returned list */ 989 | while (cur_dev->next != NULL) { 990 | cur_dev = cur_dev->next; 991 | } 992 | } 993 | 994 | udev_device_unref(raw_dev); 995 | } 996 | /* Free the enumerator and udev objects. */ 997 | udev_enumerate_unref(enumerate); 998 | udev_unref(udev); 999 | 1000 | if (root == NULL) { 1001 | if (vendor_id == 0 && product_id == 0) { 1002 | register_global_error("No HID devices found in the system."); 1003 | } else { 1004 | register_global_error("No HID devices with requested VID/PID found in the system."); 1005 | } 1006 | } 1007 | 1008 | return root; 1009 | } 1010 | 1011 | void HID_API_EXPORT hid_free_enumeration(struct hid_device_info *devs) 1012 | { 1013 | struct hid_device_info *d = devs; 1014 | while (d) { 1015 | struct hid_device_info *next = d->next; 1016 | free(d->path); 1017 | free(d->serial_number); 1018 | free(d->manufacturer_string); 1019 | free(d->product_string); 1020 | free(d); 1021 | d = next; 1022 | } 1023 | } 1024 | 1025 | hid_device * hid_open(unsigned short vendor_id, unsigned short product_id, const wchar_t *serial_number) 1026 | { 1027 | struct hid_device_info *devs, *cur_dev; 1028 | const char *path_to_open = NULL; 1029 | hid_device *handle = NULL; 1030 | 1031 | /* register_global_error: global error is reset by hid_enumerate/hid_init */ 1032 | devs = hid_enumerate(vendor_id, product_id); 1033 | if (devs == NULL) { 1034 | /* register_global_error: global error is already set by hid_enumerate */ 1035 | return NULL; 1036 | } 1037 | 1038 | cur_dev = devs; 1039 | while (cur_dev) { 1040 | if (cur_dev->vendor_id == vendor_id && 1041 | cur_dev->product_id == product_id) { 1042 | if (serial_number) { 1043 | if (wcscmp(serial_number, cur_dev->serial_number) == 0) { 1044 | path_to_open = cur_dev->path; 1045 | break; 1046 | } 1047 | } 1048 | else { 1049 | path_to_open = cur_dev->path; 1050 | break; 1051 | } 1052 | } 1053 | cur_dev = cur_dev->next; 1054 | } 1055 | 1056 | if (path_to_open) { 1057 | /* Open the device */ 1058 | handle = hid_open_path(path_to_open); 1059 | } else { 1060 | register_global_error("Device with requested VID/PID/(SerialNumber) not found"); 1061 | } 1062 | 1063 | hid_free_enumeration(devs); 1064 | 1065 | return handle; 1066 | } 1067 | 1068 | hid_device * HID_API_EXPORT hid_open_path(const char *path) 1069 | { 1070 | hid_device *dev = NULL; 1071 | 1072 | hid_init(); 1073 | /* register_global_error: global error is reset by hid_init */ 1074 | 1075 | dev = new_hid_device(); 1076 | if (!dev) { 1077 | errno = ENOMEM; 1078 | register_global_error("Couldn't allocate memory"); 1079 | return NULL; 1080 | } 1081 | 1082 | dev->device_handle = open(path, O_RDWR | O_CLOEXEC); 1083 | 1084 | if (dev->device_handle >= 0) { 1085 | int res, desc_size = 0; 1086 | 1087 | /* Make sure this is a HIDRAW device - responds to HIDIOCGRDESCSIZE */ 1088 | res = ioctl(dev->device_handle, HIDIOCGRDESCSIZE, &desc_size); 1089 | if (res < 0) { 1090 | register_global_error_format("ioctl(GRDESCSIZE) error for '%s', not a HIDRAW device?: %s", path, strerror(errno)); 1091 | hid_close(dev); 1092 | return NULL; 1093 | } 1094 | 1095 | return dev; 1096 | } 1097 | else { 1098 | /* Unable to open a device. */ 1099 | free(dev); 1100 | register_global_error_format("Failed to open a device with path '%s': %s", path, strerror(errno)); 1101 | return NULL; 1102 | } 1103 | } 1104 | 1105 | 1106 | int HID_API_EXPORT hid_write(hid_device *dev, const unsigned char *data, size_t length) 1107 | { 1108 | int bytes_written; 1109 | 1110 | if (!data || (length == 0)) { 1111 | errno = EINVAL; 1112 | register_device_error(dev, "Zero buffer/length"); 1113 | return -1; 1114 | } 1115 | 1116 | bytes_written = write(dev->device_handle, data, length); 1117 | 1118 | register_device_error(dev, (bytes_written == -1)? strerror(errno): NULL); 1119 | 1120 | return bytes_written; 1121 | } 1122 | 1123 | 1124 | int HID_API_EXPORT hid_read_timeout(hid_device *dev, unsigned char *data, size_t length, int milliseconds) 1125 | { 1126 | if (!data || (length == 0)) { 1127 | errno = EINVAL; 1128 | register_error_str(&dev->last_read_error_str, "Zero buffer/length"); 1129 | return -1; 1130 | } 1131 | 1132 | /* Set device error to none */ 1133 | register_error_str(&dev->last_read_error_str, NULL); 1134 | 1135 | int bytes_read; 1136 | 1137 | if (milliseconds >= 0) { 1138 | /* Milliseconds is either 0 (non-blocking) or > 0 (contains 1139 | a valid timeout). In both cases we want to call poll() 1140 | and wait for data to arrive. Don't rely on non-blocking 1141 | operation (O_NONBLOCK) since some kernels don't seem to 1142 | properly report device disconnection through read() when 1143 | in non-blocking mode. */ 1144 | int ret; 1145 | struct pollfd fds; 1146 | 1147 | fds.fd = dev->device_handle; 1148 | fds.events = POLLIN; 1149 | fds.revents = 0; 1150 | ret = poll(&fds, 1, milliseconds); 1151 | if (ret == 0) { 1152 | /* Timeout */ 1153 | return ret; 1154 | } 1155 | if (ret == -1) { 1156 | /* Error */ 1157 | register_error_str(&dev->last_read_error_str, strerror(errno)); 1158 | return ret; 1159 | } 1160 | else { 1161 | /* Check for errors on the file descriptor. This will 1162 | indicate a device disconnection. */ 1163 | if (fds.revents & (POLLERR | POLLHUP | POLLNVAL)) { 1164 | // We cannot use strerror() here as no -1 was returned from poll(). 1165 | errno = EIO; 1166 | register_error_str(&dev->last_read_error_str, "hid_read_timeout: unexpected poll error (device disconnected)"); 1167 | return -1; 1168 | } 1169 | } 1170 | } 1171 | 1172 | bytes_read = read(dev->device_handle, data, length); 1173 | if (bytes_read < 0) { 1174 | if (errno == EAGAIN || errno == EINPROGRESS) 1175 | bytes_read = 0; 1176 | else 1177 | register_error_str(&dev->last_read_error_str, strerror(errno)); 1178 | } 1179 | 1180 | return bytes_read; 1181 | } 1182 | 1183 | int HID_API_EXPORT hid_read(hid_device *dev, unsigned char *data, size_t length) 1184 | { 1185 | return hid_read_timeout(dev, data, length, (dev->blocking)? -1: 0); 1186 | } 1187 | 1188 | HID_API_EXPORT const wchar_t * HID_API_CALL hid_read_error(hid_device *dev) 1189 | { 1190 | if (dev->last_read_error_str == NULL) 1191 | return L"Success"; 1192 | return dev->last_read_error_str; 1193 | } 1194 | 1195 | int HID_API_EXPORT hid_set_nonblocking(hid_device *dev, int nonblock) 1196 | { 1197 | /* Do all non-blocking in userspace using poll(), since it looks 1198 | like there's a bug in the kernel in some versions where 1199 | read() will not return -1 on disconnection of the USB device */ 1200 | 1201 | dev->blocking = !nonblock; 1202 | return 0; /* Success */ 1203 | } 1204 | 1205 | 1206 | int HID_API_EXPORT hid_send_feature_report(hid_device *dev, const unsigned char *data, size_t length) 1207 | { 1208 | int res; 1209 | 1210 | if (!data || (length == 0)) { 1211 | errno = EINVAL; 1212 | register_device_error(dev, "Zero buffer/length"); 1213 | return -1; 1214 | } 1215 | 1216 | register_device_error(dev, NULL); 1217 | 1218 | res = ioctl(dev->device_handle, HIDIOCSFEATURE(length), data); 1219 | if (res < 0) 1220 | register_device_error_format(dev, "ioctl (SFEATURE): %s", strerror(errno)); 1221 | 1222 | return res; 1223 | } 1224 | 1225 | int HID_API_EXPORT hid_get_feature_report(hid_device *dev, unsigned char *data, size_t length) 1226 | { 1227 | int res; 1228 | 1229 | if (!data || (length == 0)) { 1230 | errno = EINVAL; 1231 | register_device_error(dev, "Zero buffer/length"); 1232 | return -1; 1233 | } 1234 | 1235 | register_device_error(dev, NULL); 1236 | 1237 | res = ioctl(dev->device_handle, HIDIOCGFEATURE(length), data); 1238 | if (res < 0) 1239 | register_device_error_format(dev, "ioctl (GFEATURE): %s", strerror(errno)); 1240 | 1241 | return res; 1242 | } 1243 | 1244 | int HID_API_EXPORT HID_API_CALL hid_send_output_report(hid_device *dev, const unsigned char *data, size_t length) 1245 | { 1246 | int res; 1247 | 1248 | if (!data || (length == 0)) { 1249 | errno = EINVAL; 1250 | register_device_error(dev, "Zero buffer/length"); 1251 | return -1; 1252 | } 1253 | 1254 | register_device_error(dev, NULL); 1255 | 1256 | res = ioctl(dev->device_handle, HIDIOCSOUTPUT(length), data); 1257 | if (res < 0) 1258 | register_device_error_format(dev, "ioctl (SOUTPUT): %s", strerror(errno)); 1259 | 1260 | return res; 1261 | } 1262 | 1263 | int HID_API_EXPORT HID_API_CALL hid_get_input_report(hid_device *dev, unsigned char *data, size_t length) 1264 | { 1265 | int res; 1266 | 1267 | if (!data || (length == 0)) { 1268 | errno = EINVAL; 1269 | register_device_error(dev, "Zero buffer/length"); 1270 | return -1; 1271 | } 1272 | 1273 | register_device_error(dev, NULL); 1274 | 1275 | res = ioctl(dev->device_handle, HIDIOCGINPUT(length), data); 1276 | if (res < 0) 1277 | register_device_error_format(dev, "ioctl (GINPUT): %s", strerror(errno)); 1278 | 1279 | return res; 1280 | } 1281 | 1282 | void HID_API_EXPORT hid_close(hid_device *dev) 1283 | { 1284 | if (!dev) 1285 | return; 1286 | 1287 | close(dev->device_handle); 1288 | 1289 | free(dev->last_error_str); 1290 | free(dev->last_read_error_str); 1291 | 1292 | hid_free_enumeration(dev->device_info); 1293 | 1294 | free(dev); 1295 | } 1296 | 1297 | 1298 | int HID_API_EXPORT_CALL hid_get_manufacturer_string(hid_device *dev, wchar_t *string, size_t maxlen) 1299 | { 1300 | if (!string || !maxlen) { 1301 | errno = EINVAL; 1302 | register_device_error(dev, "Zero buffer/length"); 1303 | return -1; 1304 | } 1305 | 1306 | struct hid_device_info *info = hid_get_device_info(dev); 1307 | if (!info) { 1308 | // hid_get_device_info will have set an error already 1309 | return -1; 1310 | } 1311 | 1312 | if (info->manufacturer_string) { 1313 | wcsncpy(string, info->manufacturer_string, maxlen); 1314 | string[maxlen - 1] = L'\0'; 1315 | } 1316 | else { 1317 | string[0] = L'\0'; 1318 | } 1319 | 1320 | return 0; 1321 | } 1322 | 1323 | int HID_API_EXPORT_CALL hid_get_product_string(hid_device *dev, wchar_t *string, size_t maxlen) 1324 | { 1325 | if (!string || !maxlen) { 1326 | errno = EINVAL; 1327 | register_device_error(dev, "Zero buffer/length"); 1328 | return -1; 1329 | } 1330 | 1331 | struct hid_device_info *info = hid_get_device_info(dev); 1332 | if (!info) { 1333 | // hid_get_device_info will have set an error already 1334 | return -1; 1335 | } 1336 | 1337 | if (info->product_string) { 1338 | wcsncpy(string, info->product_string, maxlen); 1339 | string[maxlen - 1] = L'\0'; 1340 | } 1341 | else { 1342 | string[0] = L'\0'; 1343 | } 1344 | 1345 | return 0; 1346 | } 1347 | 1348 | int HID_API_EXPORT_CALL hid_get_serial_number_string(hid_device *dev, wchar_t *string, size_t maxlen) 1349 | { 1350 | if (!string || !maxlen) { 1351 | errno = EINVAL; 1352 | register_device_error(dev, "Zero buffer/length"); 1353 | return -1; 1354 | } 1355 | 1356 | struct hid_device_info *info = hid_get_device_info(dev); 1357 | if (!info) { 1358 | // hid_get_device_info will have set an error already 1359 | return -1; 1360 | } 1361 | 1362 | if (info->serial_number) { 1363 | wcsncpy(string, info->serial_number, maxlen); 1364 | string[maxlen - 1] = L'\0'; 1365 | } 1366 | else { 1367 | string[0] = L'\0'; 1368 | } 1369 | 1370 | return 0; 1371 | } 1372 | 1373 | 1374 | HID_API_EXPORT struct hid_device_info *HID_API_CALL hid_get_device_info(hid_device *dev) { 1375 | if (dev->device_info) { 1376 | register_device_error(dev, NULL); 1377 | } 1378 | else { 1379 | // Lazy initialize device_info 1380 | dev->device_info = create_device_info_for_hid_device(dev); 1381 | } 1382 | 1383 | // create_device_info_for_hid_device will set an error if needed 1384 | return dev->device_info; 1385 | } 1386 | 1387 | int HID_API_EXPORT_CALL hid_get_indexed_string(hid_device *dev, int string_index, wchar_t *string, size_t maxlen) 1388 | { 1389 | (void)string_index; 1390 | (void)string; 1391 | (void)maxlen; 1392 | 1393 | errno = ENOSYS; 1394 | register_device_error(dev, "hid_get_indexed_string: not supported by hidraw"); 1395 | 1396 | return -1; 1397 | } 1398 | 1399 | 1400 | int HID_API_EXPORT_CALL hid_get_report_descriptor(hid_device *dev, unsigned char *buf, size_t buf_size) 1401 | { 1402 | struct hidraw_report_descriptor rpt_desc; 1403 | 1404 | if (!buf || !buf_size) { 1405 | errno = EINVAL; 1406 | register_device_error(dev, "Zero buffer/length"); 1407 | return -1; 1408 | } 1409 | 1410 | register_device_error(dev, NULL); 1411 | 1412 | int res = get_hid_report_descriptor_from_hidraw(dev, &rpt_desc); 1413 | if (res < 0) { 1414 | /* error already registered */ 1415 | return res; 1416 | } 1417 | 1418 | if (rpt_desc.size < buf_size) { 1419 | buf_size = (size_t) rpt_desc.size; 1420 | } 1421 | 1422 | memcpy(buf, rpt_desc.value, buf_size); 1423 | 1424 | return (int) buf_size; 1425 | } 1426 | 1427 | 1428 | /* Passing in NULL means asking for the last global error message. */ 1429 | HID_API_EXPORT const wchar_t * HID_API_CALL hid_error(hid_device *dev) 1430 | { 1431 | if (dev) { 1432 | if (dev->last_error_str == NULL) 1433 | return L"Success"; 1434 | return dev->last_error_str; 1435 | } 1436 | 1437 | if (last_global_error_str == NULL) 1438 | return L"Success"; 1439 | return last_global_error_str; 1440 | } 1441 | -------------------------------------------------------------------------------- /hid_windows.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2025 Steven Stallion 2 | // 3 | // Redistribution and use in source and binary forms, with or without 4 | // modification, are permitted provided that the following conditions 5 | // are met: 6 | // 1. Redistributions of source code must retain the above copyright 7 | // notice, this list of conditions and the following disclaimer. 8 | // 2. Redistributions in binary form must reproduce the above copyright 9 | // notice, this list of conditions and the following disclaimer in the 10 | // documentation and/or other materials provided with the distribution. 11 | // 12 | // THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS "AS IS" AND 13 | // ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 14 | // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 15 | // ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 16 | // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 17 | // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 18 | // OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 19 | // HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 20 | // LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 21 | // OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 22 | // SUCH DAMAGE. 23 | 24 | package hid 25 | 26 | /* 27 | #include 28 | #include "hidapi_winapi.h" 29 | */ 30 | import "C" 31 | 32 | import ( 33 | "unsafe" 34 | 35 | "golang.org/x/sys/windows" 36 | ) 37 | 38 | // GetContainerID returns the container ID pointed to by guid and an error, if 39 | // any. 40 | func (d *Device) GetContainerID(guid *windows.GUID) error { 41 | container_id := (*C.GUID)(unsafe.Pointer(guid)) 42 | if res := C.hid_winapi_get_container_id(d.handle, container_id); res == -1 { 43 | return wrapErr(d.Error()) 44 | } 45 | return nil 46 | } 47 | 48 | // SetWriteTimeout sets the timeout when sending output reports on Win32. The 49 | // default timeout is 1 second. Setting the timeout to 0 enables non-blocking 50 | // behavior while -1 blocks until the write completes or returns an error. 51 | func (d *Device) SetWriteTimeout(timeout int) { 52 | C.hid_winapi_set_write_timeout(d.handle, C.ulong(timeout)); 53 | } 54 | 55 | // ReconstructDescriptorData reconstructs a HID Report Descriptor from a Win32 56 | // HIDP_PREPARSED_DATA structure pointed to by data. It returns the number of 57 | // bytes reconstructed and an error, if any. 58 | func ReconstructDescriptorData(data interface{}, p []byte) (int, error) { 59 | hidp_preparsed_data := unsafe.Pointer(&data) 60 | buf := (*C.uchar)(&p[0]) 61 | buf_size := C.size_t(len(p)) 62 | 63 | res := C.hid_winapi_descriptor_reconstruct_pp_data(hidp_preparsed_data, buf, buf_size) 64 | if res == -1 { 65 | return int(res), wrapErr(Error()) 66 | } 67 | return int(res), nil 68 | } 69 | -------------------------------------------------------------------------------- /hidapi.h: -------------------------------------------------------------------------------- 1 | /******************************************************* 2 | HIDAPI - Multi-Platform library for 3 | communication with HID devices. 4 | 5 | Alan Ott 6 | Signal 11 Software 7 | 8 | libusb/hidapi Team 9 | 10 | Copyright 2023, All Rights Reserved. 11 | 12 | At the discretion of the user of this library, 13 | this software may be licensed under the terms of the 14 | GNU General Public License v3, a BSD-Style license, or the 15 | original HIDAPI license as outlined in the LICENSE.txt, 16 | LICENSE-gpl3.txt, LICENSE-bsd.txt, and LICENSE-orig.txt 17 | files located at the root of the source distribution. 18 | These files may also be found in the public source 19 | code repository located at: 20 | https://github.com/libusb/hidapi . 21 | ********************************************************/ 22 | 23 | /** @file 24 | * @defgroup API hidapi API 25 | */ 26 | 27 | #ifndef HIDAPI_H__ 28 | #define HIDAPI_H__ 29 | 30 | #include 31 | 32 | /* #480: this is to be refactored properly for v1.0 */ 33 | #ifdef _WIN32 34 | #ifndef HID_API_NO_EXPORT_DEFINE 35 | #define HID_API_EXPORT __declspec(dllexport) 36 | #endif 37 | #endif 38 | #ifndef HID_API_EXPORT 39 | #define HID_API_EXPORT /**< API export macro */ 40 | #endif 41 | /* To be removed in v1.0 */ 42 | #define HID_API_CALL /**< API call macro */ 43 | 44 | #define HID_API_EXPORT_CALL HID_API_EXPORT HID_API_CALL /**< API export and call macro*/ 45 | 46 | /** @brief Static/compile-time major version of the library. 47 | 48 | @ingroup API 49 | */ 50 | #define HID_API_VERSION_MAJOR 0 51 | /** @brief Static/compile-time minor version of the library. 52 | 53 | @ingroup API 54 | */ 55 | #define HID_API_VERSION_MINOR 15 56 | /** @brief Static/compile-time patch version of the library. 57 | 58 | @ingroup API 59 | */ 60 | #define HID_API_VERSION_PATCH 0 61 | 62 | /* Helper macros */ 63 | #define HID_API_AS_STR_IMPL(x) #x 64 | #define HID_API_AS_STR(x) HID_API_AS_STR_IMPL(x) 65 | #define HID_API_TO_VERSION_STR(v1, v2, v3) HID_API_AS_STR(v1.v2.v3) 66 | 67 | /** @brief Coverts a version as Major/Minor/Patch into a number: 68 | <8 bit major><16 bit minor><8 bit patch>. 69 | 70 | This macro was added in version 0.12.0. 71 | 72 | Convenient function to be used for compile-time checks, like: 73 | @code{.c} 74 | #if HID_API_VERSION >= HID_API_MAKE_VERSION(0, 12, 0) 75 | @endcode 76 | 77 | @ingroup API 78 | */ 79 | #define HID_API_MAKE_VERSION(mj, mn, p) (((mj) << 24) | ((mn) << 8) | (p)) 80 | 81 | /** @brief Static/compile-time version of the library. 82 | 83 | This macro was added in version 0.12.0. 84 | 85 | @see @ref HID_API_MAKE_VERSION. 86 | 87 | @ingroup API 88 | */ 89 | #define HID_API_VERSION HID_API_MAKE_VERSION(HID_API_VERSION_MAJOR, HID_API_VERSION_MINOR, HID_API_VERSION_PATCH) 90 | 91 | /** @brief Static/compile-time string version of the library. 92 | 93 | @ingroup API 94 | */ 95 | #define HID_API_VERSION_STR HID_API_TO_VERSION_STR(HID_API_VERSION_MAJOR, HID_API_VERSION_MINOR, HID_API_VERSION_PATCH) 96 | 97 | /** @brief Maximum expected HID Report descriptor size in bytes. 98 | 99 | Since version 0.13.0, @ref HID_API_VERSION >= HID_API_MAKE_VERSION(0, 13, 0) 100 | 101 | @ingroup API 102 | */ 103 | #define HID_API_MAX_REPORT_DESCRIPTOR_SIZE 4096 104 | 105 | #ifdef __cplusplus 106 | extern "C" { 107 | #endif 108 | /** A structure to hold the version numbers. */ 109 | struct hid_api_version { 110 | int major; /**< major version number */ 111 | int minor; /**< minor version number */ 112 | int patch; /**< patch version number */ 113 | }; 114 | 115 | struct hid_device_; 116 | typedef struct hid_device_ hid_device; /**< opaque hidapi structure */ 117 | 118 | /** @brief HID underlying bus types. 119 | 120 | @ingroup API 121 | */ 122 | typedef enum { 123 | /** Unknown bus type */ 124 | HID_API_BUS_UNKNOWN = 0x00, 125 | 126 | /** USB bus 127 | Specifications: 128 | https://usb.org/hid */ 129 | HID_API_BUS_USB = 0x01, 130 | 131 | /** Bluetooth or Bluetooth LE bus 132 | Specifications: 133 | https://www.bluetooth.com/specifications/specs/human-interface-device-profile-1-1-1/ 134 | https://www.bluetooth.com/specifications/specs/hid-service-1-0/ 135 | https://www.bluetooth.com/specifications/specs/hid-over-gatt-profile-1-0/ */ 136 | HID_API_BUS_BLUETOOTH = 0x02, 137 | 138 | /** I2C bus 139 | Specifications: 140 | https://docs.microsoft.com/previous-versions/windows/hardware/design/dn642101(v=vs.85) */ 141 | HID_API_BUS_I2C = 0x03, 142 | 143 | /** SPI bus 144 | Specifications: 145 | https://www.microsoft.com/download/details.aspx?id=103325 */ 146 | HID_API_BUS_SPI = 0x04, 147 | } hid_bus_type; 148 | 149 | /** hidapi info structure */ 150 | struct hid_device_info { 151 | /** Platform-specific device path */ 152 | char *path; 153 | /** Device Vendor ID */ 154 | unsigned short vendor_id; 155 | /** Device Product ID */ 156 | unsigned short product_id; 157 | /** Serial Number */ 158 | wchar_t *serial_number; 159 | /** Device Release Number in binary-coded decimal, 160 | also known as Device Version Number */ 161 | unsigned short release_number; 162 | /** Manufacturer String */ 163 | wchar_t *manufacturer_string; 164 | /** Product string */ 165 | wchar_t *product_string; 166 | /** Usage Page for this Device/Interface 167 | (Windows/Mac/hidraw only) */ 168 | unsigned short usage_page; 169 | /** Usage for this Device/Interface 170 | (Windows/Mac/hidraw only) */ 171 | unsigned short usage; 172 | /** The USB interface which this logical device 173 | represents. 174 | 175 | Valid only if the device is a USB HID device. 176 | Set to -1 in all other cases. 177 | */ 178 | int interface_number; 179 | 180 | /** Pointer to the next device */ 181 | struct hid_device_info *next; 182 | 183 | /** Underlying bus type 184 | Since version 0.13.0, @ref HID_API_VERSION >= HID_API_MAKE_VERSION(0, 13, 0) 185 | */ 186 | hid_bus_type bus_type; 187 | }; 188 | 189 | 190 | /** @brief Initialize the HIDAPI library. 191 | 192 | This function initializes the HIDAPI library. Calling it is not 193 | strictly necessary, as it will be called automatically by 194 | hid_enumerate() and any of the hid_open_*() functions if it is 195 | needed. This function should be called at the beginning of 196 | execution however, if there is a chance of HIDAPI handles 197 | being opened by different threads simultaneously. 198 | 199 | @ingroup API 200 | 201 | @returns 202 | This function returns 0 on success and -1 on error. 203 | Call hid_error(NULL) to get the failure reason. 204 | */ 205 | int HID_API_EXPORT HID_API_CALL hid_init(void); 206 | 207 | /** @brief Finalize the HIDAPI library. 208 | 209 | This function frees all of the static data associated with 210 | HIDAPI. It should be called at the end of execution to avoid 211 | memory leaks. 212 | 213 | @ingroup API 214 | 215 | @returns 216 | This function returns 0 on success and -1 on error. 217 | */ 218 | int HID_API_EXPORT HID_API_CALL hid_exit(void); 219 | 220 | /** @brief Enumerate the HID Devices. 221 | 222 | This function returns a linked list of all the HID devices 223 | attached to the system which match vendor_id and product_id. 224 | If @p vendor_id is set to 0 then any vendor matches. 225 | If @p product_id is set to 0 then any product matches. 226 | If @p vendor_id and @p product_id are both set to 0, then 227 | all HID devices will be returned. 228 | 229 | @ingroup API 230 | @param vendor_id The Vendor ID (VID) of the types of device 231 | to open. 232 | @param product_id The Product ID (PID) of the types of 233 | device to open. 234 | 235 | @returns 236 | This function returns a pointer to a linked list of type 237 | struct #hid_device_info, containing information about the HID devices 238 | attached to the system, 239 | or NULL in the case of failure or if no HID devices present in the system. 240 | Call hid_error(NULL) to get the failure reason. 241 | 242 | @note The returned value by this function must to be freed by calling hid_free_enumeration(), 243 | when not needed anymore. 244 | */ 245 | struct hid_device_info HID_API_EXPORT * HID_API_CALL hid_enumerate(unsigned short vendor_id, unsigned short product_id); 246 | 247 | /** @brief Free an enumeration Linked List 248 | 249 | This function frees a linked list created by hid_enumerate(). 250 | 251 | @ingroup API 252 | @param devs Pointer to a list of struct_device returned from 253 | hid_enumerate(). 254 | */ 255 | void HID_API_EXPORT HID_API_CALL hid_free_enumeration(struct hid_device_info *devs); 256 | 257 | /** @brief Open a HID device using a Vendor ID (VID), Product ID 258 | (PID) and optionally a serial number. 259 | 260 | If @p serial_number is NULL, the first device with the 261 | specified VID and PID is opened. 262 | 263 | @ingroup API 264 | @param vendor_id The Vendor ID (VID) of the device to open. 265 | @param product_id The Product ID (PID) of the device to open. 266 | @param serial_number The Serial Number of the device to open 267 | (Optionally NULL). 268 | 269 | @returns 270 | This function returns a pointer to a #hid_device object on 271 | success or NULL on failure. 272 | Call hid_error(NULL) to get the failure reason. 273 | 274 | @note The returned object must be freed by calling hid_close(), 275 | when not needed anymore. 276 | */ 277 | HID_API_EXPORT hid_device * HID_API_CALL hid_open(unsigned short vendor_id, unsigned short product_id, const wchar_t *serial_number); 278 | 279 | /** @brief Open a HID device by its path name. 280 | 281 | The path name be determined by calling hid_enumerate(), or a 282 | platform-specific path name can be used (eg: /dev/hidraw0 on 283 | Linux). 284 | 285 | @ingroup API 286 | @param path The path name of the device to open 287 | 288 | @returns 289 | This function returns a pointer to a #hid_device object on 290 | success or NULL on failure. 291 | Call hid_error(NULL) to get the failure reason. 292 | 293 | @note The returned object must be freed by calling hid_close(), 294 | when not needed anymore. 295 | */ 296 | HID_API_EXPORT hid_device * HID_API_CALL hid_open_path(const char *path); 297 | 298 | /** @brief Write an Output report to a HID device. 299 | 300 | The first byte of @p data[] must contain the Report ID. For 301 | devices which only support a single report, this must be set 302 | to 0x0. The remaining bytes contain the report data. Since 303 | the Report ID is mandatory, calls to hid_write() will always 304 | contain one more byte than the report contains. For example, 305 | if a hid report is 16 bytes long, 17 bytes must be passed to 306 | hid_write(), the Report ID (or 0x0, for devices with a 307 | single report), followed by the report data (16 bytes). In 308 | this example, the length passed in would be 17. 309 | 310 | hid_write() will send the data on the first interrupt OUT 311 | endpoint, if one exists. If it does not the behaviour is as 312 | @ref hid_send_output_report 313 | 314 | @ingroup API 315 | @param dev A device handle returned from hid_open(). 316 | @param data The data to send, including the report number as 317 | the first byte. 318 | @param length The length in bytes of the data to send. 319 | 320 | @returns 321 | This function returns the actual number of bytes written and 322 | -1 on error. 323 | Call hid_error(dev) to get the failure reason. 324 | */ 325 | int HID_API_EXPORT HID_API_CALL hid_write(hid_device *dev, const unsigned char *data, size_t length); 326 | 327 | /** @brief Read an Input report from a HID device with timeout. 328 | 329 | Input reports are returned 330 | to the host through the INTERRUPT IN endpoint. The first byte will 331 | contain the Report number if the device uses numbered reports. 332 | 333 | @ingroup API 334 | @param dev A device handle returned from hid_open(). 335 | @param data A buffer to put the read data into. 336 | @param length The number of bytes to read. For devices with 337 | multiple reports, make sure to read an extra byte for 338 | the report number. 339 | @param milliseconds timeout in milliseconds or -1 for blocking wait. 340 | 341 | @returns 342 | This function returns the actual number of bytes read and 343 | -1 on error. 344 | Call hid_read_error(dev) to get the failure reason. 345 | If no packet was available to be read within 346 | the timeout period, this function returns 0. 347 | 348 | @note This function doesn't change the buffer returned by the hid_error(dev). 349 | */ 350 | int HID_API_EXPORT HID_API_CALL hid_read_timeout(hid_device *dev, unsigned char *data, size_t length, int milliseconds); 351 | 352 | /** @brief Read an Input report from a HID device. 353 | 354 | Input reports are returned 355 | to the host through the INTERRUPT IN endpoint. The first byte will 356 | contain the Report number if the device uses numbered reports. 357 | 358 | @ingroup API 359 | @param dev A device handle returned from hid_open(). 360 | @param data A buffer to put the read data into. 361 | @param length The number of bytes to read. For devices with 362 | multiple reports, make sure to read an extra byte for 363 | the report number. 364 | 365 | @returns 366 | This function returns the actual number of bytes read and 367 | -1 on error. 368 | Call hid_read_error(dev) to get the failure reason. 369 | If no packet was available to be read and 370 | the handle is in non-blocking mode, this function returns 0. 371 | 372 | @note This function doesn't change the buffer returned by the hid_error(dev). 373 | */ 374 | int HID_API_EXPORT HID_API_CALL hid_read(hid_device *dev, unsigned char *data, size_t length); 375 | 376 | /** @brief Get a string describing the last error which occurred during hid_read/hid_read_timeout. 377 | 378 | Since version 0.15.0, @ref HID_API_VERSION >= HID_API_MAKE_VERSION(0, 15, 0) 379 | 380 | This function is intended for logging/debugging purposes. 381 | 382 | This function guarantees to never return NULL for a valid @ref dev. 383 | If there was no error in the last call to hid_read/hid_read_error - 384 | the returned string clearly indicates that. 385 | 386 | Strings returned from hid_read_error() must not be freed by the user, 387 | i.e. owned by HIDAPI library. 388 | Device-specific error string may remain allocated at most until hid_close() is called. 389 | 390 | @ingroup API 391 | @param dev A device handle. Shall never be NULL. 392 | 393 | @returns 394 | A string describing the hid_read/hid_read_timeout error (if any). 395 | */ 396 | HID_API_EXPORT const wchar_t* HID_API_CALL hid_read_error(hid_device *dev); 397 | 398 | /** @brief Set the device handle to be non-blocking. 399 | 400 | In non-blocking mode calls to hid_read() will return 401 | immediately with a value of 0 if there is no data to be 402 | read. In blocking mode, hid_read() will wait (block) until 403 | there is data to read before returning. 404 | 405 | Nonblocking can be turned on and off at any time. 406 | 407 | @ingroup API 408 | @param dev A device handle returned from hid_open(). 409 | @param nonblock enable or not the nonblocking reads 410 | - 1 to enable nonblocking 411 | - 0 to disable nonblocking. 412 | 413 | @returns 414 | This function returns 0 on success and -1 on error. 415 | Call hid_error(dev) to get the failure reason. 416 | */ 417 | int HID_API_EXPORT HID_API_CALL hid_set_nonblocking(hid_device *dev, int nonblock); 418 | 419 | /** @brief Send a Feature report to the device. 420 | 421 | Feature reports are sent over the Control endpoint as a 422 | Set_Report transfer. The first byte of @p data[] must 423 | contain the Report ID. For devices which only support a 424 | single report, this must be set to 0x0. The remaining bytes 425 | contain the report data. Since the Report ID is mandatory, 426 | calls to hid_send_feature_report() will always contain one 427 | more byte than the report contains. For example, if a hid 428 | report is 16 bytes long, 17 bytes must be passed to 429 | hid_send_feature_report(): the Report ID (or 0x0, for 430 | devices which do not use numbered reports), followed by the 431 | report data (16 bytes). In this example, the length passed 432 | in would be 17. 433 | 434 | @ingroup API 435 | @param dev A device handle returned from hid_open(). 436 | @param data The data to send, including the report number as 437 | the first byte. 438 | @param length The length in bytes of the data to send, including 439 | the report number. 440 | 441 | @returns 442 | This function returns the actual number of bytes written and 443 | -1 on error. 444 | Call hid_error(dev) to get the failure reason. 445 | */ 446 | int HID_API_EXPORT HID_API_CALL hid_send_feature_report(hid_device *dev, const unsigned char *data, size_t length); 447 | 448 | /** @brief Get a feature report from a HID device. 449 | 450 | Set the first byte of @p data[] to the Report ID of the 451 | report to be read. Make sure to allow space for this 452 | extra byte in @p data[]. Upon return, the first byte will 453 | still contain the Report ID, and the report data will 454 | start in data[1]. 455 | 456 | @ingroup API 457 | @param dev A device handle returned from hid_open(). 458 | @param data A buffer to put the read data into, including 459 | the Report ID. Set the first byte of @p data[] to the 460 | Report ID of the report to be read, or set it to zero 461 | if your device does not use numbered reports. 462 | @param length The number of bytes to read, including an 463 | extra byte for the report ID. The buffer can be longer 464 | than the actual report. 465 | 466 | @returns 467 | This function returns the number of bytes read plus 468 | one for the report ID (which is still in the first 469 | byte), or -1 on error. 470 | Call hid_error(dev) to get the failure reason. 471 | */ 472 | int HID_API_EXPORT HID_API_CALL hid_get_feature_report(hid_device *dev, unsigned char *data, size_t length); 473 | 474 | /** @brief Send a Output report to the device. 475 | 476 | Since version 0.15.0, @ref HID_API_VERSION >= HID_API_MAKE_VERSION(0, 15, 0) 477 | 478 | Output reports are sent over the Control endpoint as a 479 | Set_Report transfer. The first byte of @p data[] must 480 | contain the Report ID. For devices which only support a 481 | single report, this must be set to 0x0. The remaining bytes 482 | contain the report data. Since the Report ID is mandatory, 483 | calls to hid_send_output_report() will always contain one 484 | more byte than the report contains. For example, if a hid 485 | report is 16 bytes long, 17 bytes must be passed to 486 | hid_send_output_report(): the Report ID (or 0x0, for 487 | devices which do not use numbered reports), followed by the 488 | report data (16 bytes). In this example, the length passed 489 | in would be 17. 490 | 491 | This function sets the return value of hid_error(). 492 | 493 | @ingroup API 494 | @param dev A device handle returned from hid_open(). 495 | @param data The data to send, including the report number as 496 | the first byte. 497 | @param length The length in bytes of the data to send, including 498 | the report number. 499 | 500 | @returns 501 | This function returns the actual number of bytes written and 502 | -1 on error. 503 | 504 | @see @ref hid_write 505 | */ 506 | int HID_API_EXPORT HID_API_CALL hid_send_output_report(hid_device* dev, const unsigned char* data, size_t length); 507 | 508 | /** @brief Get a input report from a HID device. 509 | 510 | Since version 0.10.0, @ref HID_API_VERSION >= HID_API_MAKE_VERSION(0, 10, 0) 511 | 512 | Set the first byte of @p data[] to the Report ID of the 513 | report to be read. Make sure to allow space for this 514 | extra byte in @p data[]. Upon return, the first byte will 515 | still contain the Report ID, and the report data will 516 | start in data[1]. 517 | 518 | @ingroup API 519 | @param dev A device handle returned from hid_open(). 520 | @param data A buffer to put the read data into, including 521 | the Report ID. Set the first byte of @p data[] to the 522 | Report ID of the report to be read, or set it to zero 523 | if your device does not use numbered reports. 524 | @param length The number of bytes to read, including an 525 | extra byte for the report ID. The buffer can be longer 526 | than the actual report. 527 | 528 | @returns 529 | This function returns the number of bytes read plus 530 | one for the report ID (which is still in the first 531 | byte), or -1 on error. 532 | Call hid_error(dev) to get the failure reason. 533 | */ 534 | int HID_API_EXPORT HID_API_CALL hid_get_input_report(hid_device *dev, unsigned char *data, size_t length); 535 | 536 | /** @brief Close a HID device. 537 | 538 | @ingroup API 539 | @param dev A device handle returned from hid_open(). 540 | */ 541 | void HID_API_EXPORT HID_API_CALL hid_close(hid_device *dev); 542 | 543 | /** @brief Get The Manufacturer String from a HID device. 544 | 545 | @ingroup API 546 | @param dev A device handle returned from hid_open(). 547 | @param string A wide string buffer to put the data into. 548 | @param maxlen The length of the buffer in multiples of wchar_t. 549 | 550 | @returns 551 | This function returns 0 on success and -1 on error. 552 | Call hid_error(dev) to get the failure reason. 553 | */ 554 | int HID_API_EXPORT_CALL hid_get_manufacturer_string(hid_device *dev, wchar_t *string, size_t maxlen); 555 | 556 | /** @brief Get The Product String from a HID device. 557 | 558 | @ingroup API 559 | @param dev A device handle returned from hid_open(). 560 | @param string A wide string buffer to put the data into. 561 | @param maxlen The length of the buffer in multiples of wchar_t. 562 | 563 | @returns 564 | This function returns 0 on success and -1 on error. 565 | Call hid_error(dev) to get the failure reason. 566 | */ 567 | int HID_API_EXPORT_CALL hid_get_product_string(hid_device *dev, wchar_t *string, size_t maxlen); 568 | 569 | /** @brief Get The Serial Number String from a HID device. 570 | 571 | @ingroup API 572 | @param dev A device handle returned from hid_open(). 573 | @param string A wide string buffer to put the data into. 574 | @param maxlen The length of the buffer in multiples of wchar_t. 575 | 576 | @returns 577 | This function returns 0 on success and -1 on error. 578 | Call hid_error(dev) to get the failure reason. 579 | */ 580 | int HID_API_EXPORT_CALL hid_get_serial_number_string(hid_device *dev, wchar_t *string, size_t maxlen); 581 | 582 | /** @brief Get The struct #hid_device_info from a HID device. 583 | 584 | Since version 0.13.0, @ref HID_API_VERSION >= HID_API_MAKE_VERSION(0, 13, 0) 585 | 586 | @ingroup API 587 | @param dev A device handle returned from hid_open(). 588 | 589 | @returns 590 | This function returns a pointer to the struct #hid_device_info 591 | for this hid_device, or NULL in the case of failure. 592 | Call hid_error(dev) to get the failure reason. 593 | This struct is valid until the device is closed with hid_close(). 594 | 595 | @note The returned object is owned by the @p dev, and SHOULD NOT be freed by the user. 596 | */ 597 | struct hid_device_info HID_API_EXPORT * HID_API_CALL hid_get_device_info(hid_device *dev); 598 | 599 | /** @brief Get a string from a HID device, based on its string index. 600 | 601 | @ingroup API 602 | @param dev A device handle returned from hid_open(). 603 | @param string_index The index of the string to get. 604 | @param string A wide string buffer to put the data into. 605 | @param maxlen The length of the buffer in multiples of wchar_t. 606 | 607 | @returns 608 | This function returns 0 on success and -1 on error. 609 | Call hid_error(dev) to get the failure reason. 610 | */ 611 | int HID_API_EXPORT_CALL hid_get_indexed_string(hid_device *dev, int string_index, wchar_t *string, size_t maxlen); 612 | 613 | /** @brief Get a report descriptor from a HID device. 614 | 615 | Since version 0.14.0, @ref HID_API_VERSION >= HID_API_MAKE_VERSION(0, 14, 0) 616 | 617 | User has to provide a preallocated buffer where descriptor will be copied to. 618 | The recommended size for preallocated buffer is @ref HID_API_MAX_REPORT_DESCRIPTOR_SIZE bytes. 619 | 620 | @ingroup API 621 | @param dev A device handle returned from hid_open(). 622 | @param buf The buffer to copy descriptor into. 623 | @param buf_size The size of the buffer in bytes. 624 | 625 | @returns 626 | This function returns non-negative number of bytes actually copied, or -1 on error. 627 | */ 628 | int HID_API_EXPORT_CALL hid_get_report_descriptor(hid_device *dev, unsigned char *buf, size_t buf_size); 629 | 630 | /** @brief Get a string describing the last error which occurred. 631 | 632 | This function is intended for logging/debugging purposes. 633 | 634 | This function guarantees to never return NULL. 635 | If there was no error in the last function call - 636 | the returned string clearly indicates that. 637 | 638 | Any HIDAPI function that can explicitly indicate an execution failure 639 | (e.g. by an error code, or by returning NULL) - may set the error string, 640 | to be returned by this function. 641 | 642 | Strings returned from hid_error() must not be freed by the user, 643 | i.e. owned by HIDAPI library. 644 | Device-specific error string may remain allocated at most until hid_close() is called. 645 | Global error string may remain allocated at most until hid_exit() is called. 646 | 647 | @ingroup API 648 | @param dev A device handle returned from hid_open(), 649 | or NULL to get the last non-device-specific error 650 | (e.g. for errors in hid_open() or hid_enumerate()). 651 | 652 | @returns 653 | A string describing the last error (if any). 654 | */ 655 | HID_API_EXPORT const wchar_t* HID_API_CALL hid_error(hid_device *dev); 656 | 657 | /** @brief Get a runtime version of the library. 658 | 659 | This function is thread-safe. 660 | 661 | @ingroup API 662 | 663 | @returns 664 | Pointer to statically allocated struct, that contains version. 665 | */ 666 | HID_API_EXPORT const struct hid_api_version* HID_API_CALL hid_version(void); 667 | 668 | 669 | /** @brief Get a runtime version string of the library. 670 | 671 | This function is thread-safe. 672 | 673 | @ingroup API 674 | 675 | @returns 676 | Pointer to statically allocated string, that contains version string. 677 | */ 678 | HID_API_EXPORT const char* HID_API_CALL hid_version_str(void); 679 | 680 | #ifdef __cplusplus 681 | } 682 | #endif 683 | 684 | #endif 685 | -------------------------------------------------------------------------------- /hidapi_cfgmgr32.h: -------------------------------------------------------------------------------- 1 | /******************************************************* 2 | HIDAPI - Multi-Platform library for 3 | communication with HID devices. 4 | 5 | libusb/hidapi Team 6 | 7 | Copyright 2022, All Rights Reserved. 8 | 9 | At the discretion of the user of this library, 10 | this software may be licensed under the terms of the 11 | GNU General Public License v3, a BSD-Style license, or the 12 | original HIDAPI license as outlined in the LICENSE.txt, 13 | LICENSE-gpl3.txt, LICENSE-bsd.txt, and LICENSE-orig.txt 14 | files located at the root of the source distribution. 15 | These files may also be found in the public source 16 | code repository located at: 17 | https://github.com/libusb/hidapi . 18 | ********************************************************/ 19 | 20 | #ifndef HIDAPI_CFGMGR32_H 21 | #define HIDAPI_CFGMGR32_H 22 | 23 | #ifdef HIDAPI_USE_DDK 24 | 25 | #include 26 | #include 27 | #include 28 | #include 29 | 30 | #else 31 | 32 | /* This part of the header mimics cfgmgr32.h, 33 | but only what is used by HIDAPI */ 34 | 35 | #include 36 | #include 37 | #include 38 | 39 | typedef DWORD RETURN_TYPE; 40 | typedef RETURN_TYPE CONFIGRET; 41 | typedef DWORD DEVNODE, DEVINST; 42 | typedef DEVNODE* PDEVNODE, * PDEVINST; 43 | typedef WCHAR* DEVNODEID_W, * DEVINSTID_W; 44 | 45 | #define CR_SUCCESS (0x00000000) 46 | #define CR_BUFFER_SMALL (0x0000001A) 47 | #define CR_FAILURE (0x00000013) 48 | 49 | #define CM_LOCATE_DEVNODE_NORMAL 0x00000000 50 | 51 | #define CM_GET_DEVICE_INTERFACE_LIST_PRESENT (0x00000000) 52 | 53 | typedef CONFIGRET(__stdcall* CM_Locate_DevNodeW_)(PDEVINST pdnDevInst, DEVINSTID_W pDeviceID, ULONG ulFlags); 54 | typedef CONFIGRET(__stdcall* CM_Get_Parent_)(PDEVINST pdnDevInst, DEVINST dnDevInst, ULONG ulFlags); 55 | typedef CONFIGRET(__stdcall* CM_Get_DevNode_PropertyW_)(DEVINST dnDevInst, CONST DEVPROPKEY* PropertyKey, DEVPROPTYPE* PropertyType, PBYTE PropertyBuffer, PULONG PropertyBufferSize, ULONG ulFlags); 56 | typedef CONFIGRET(__stdcall* CM_Get_Device_Interface_PropertyW_)(LPCWSTR pszDeviceInterface, CONST DEVPROPKEY* PropertyKey, DEVPROPTYPE* PropertyType, PBYTE PropertyBuffer, PULONG PropertyBufferSize, ULONG ulFlags); 57 | typedef CONFIGRET(__stdcall* CM_Get_Device_Interface_List_SizeW_)(PULONG pulLen, LPGUID InterfaceClassGuid, DEVINSTID_W pDeviceID, ULONG ulFlags); 58 | typedef CONFIGRET(__stdcall* CM_Get_Device_Interface_ListW_)(LPGUID InterfaceClassGuid, DEVINSTID_W pDeviceID, PZZWSTR Buffer, ULONG BufferLen, ULONG ulFlags); 59 | 60 | // from devpkey.h 61 | DEFINE_DEVPROPKEY(DEVPKEY_NAME, 0xb725f130, 0x47ef, 0x101a, 0xa5, 0xf1, 0x02, 0x60, 0x8c, 0x9e, 0xeb, 0xac, 10); // DEVPROP_TYPE_STRING 62 | DEFINE_DEVPROPKEY(DEVPKEY_Device_Manufacturer, 0xa45c254e, 0xdf1c, 0x4efd, 0x80, 0x20, 0x67, 0xd1, 0x46, 0xa8, 0x50, 0xe0, 13); // DEVPROP_TYPE_STRING 63 | DEFINE_DEVPROPKEY(DEVPKEY_Device_InstanceId, 0x78c34fc8, 0x104a, 0x4aca, 0x9e, 0xa4, 0x52, 0x4d, 0x52, 0x99, 0x6e, 0x57, 256); // DEVPROP_TYPE_STRING 64 | DEFINE_DEVPROPKEY(DEVPKEY_Device_HardwareIds, 0xa45c254e, 0xdf1c, 0x4efd, 0x80, 0x20, 0x67, 0xd1, 0x46, 0xa8, 0x50, 0xe0, 3); // DEVPROP_TYPE_STRING_LIST 65 | DEFINE_DEVPROPKEY(DEVPKEY_Device_CompatibleIds, 0xa45c254e, 0xdf1c, 0x4efd, 0x80, 0x20, 0x67, 0xd1, 0x46, 0xa8, 0x50, 0xe0, 4); // DEVPROP_TYPE_STRING_LIST 66 | DEFINE_DEVPROPKEY(DEVPKEY_Device_ContainerId, 0x8c7ed206, 0x3f8a, 0x4827, 0xb3, 0xab, 0xae, 0x9e, 0x1f, 0xae, 0xfc, 0x6c, 2); // DEVPROP_TYPE_GUID 67 | 68 | // from propkey.h 69 | DEFINE_PROPERTYKEY(PKEY_DeviceInterface_Bluetooth_DeviceAddress, 0x2BD67D8B, 0x8BEB, 0x48D5, 0x87, 0xE0, 0x6C, 0xDA, 0x34, 0x28, 0x04, 0x0A, 1); // DEVPROP_TYPE_STRING 70 | DEFINE_PROPERTYKEY(PKEY_DeviceInterface_Bluetooth_Manufacturer, 0x2BD67D8B, 0x8BEB, 0x48D5, 0x87, 0xE0, 0x6C, 0xDA, 0x34, 0x28, 0x04, 0x0A, 4); // DEVPROP_TYPE_STRING 71 | DEFINE_PROPERTYKEY(PKEY_DeviceInterface_Bluetooth_ModelNumber, 0x2BD67D8B, 0x8BEB, 0x48D5, 0x87, 0xE0, 0x6C, 0xDA, 0x34, 0x28, 0x04, 0x0A, 5); // DEVPROP_TYPE_STRING 72 | 73 | #endif 74 | 75 | #endif /* HIDAPI_CFGMGR32_H */ 76 | -------------------------------------------------------------------------------- /hidapi_darwin.h: -------------------------------------------------------------------------------- 1 | /******************************************************* 2 | HIDAPI - Multi-Platform library for 3 | communication with HID devices. 4 | 5 | libusb/hidapi Team 6 | 7 | Copyright 2022, All Rights Reserved. 8 | 9 | At the discretion of the user of this library, 10 | this software may be licensed under the terms of the 11 | GNU General Public License v3, a BSD-Style license, or the 12 | original HIDAPI license as outlined in the LICENSE.txt, 13 | LICENSE-gpl3.txt, LICENSE-bsd.txt, and LICENSE-orig.txt 14 | files located at the root of the source distribution. 15 | These files may also be found in the public source 16 | code repository located at: 17 | https://github.com/libusb/hidapi . 18 | ********************************************************/ 19 | 20 | /** @file 21 | * @defgroup API hidapi API 22 | 23 | * Since version 0.12.0, @ref HID_API_VERSION >= HID_API_MAKE_VERSION(0, 12, 0) 24 | */ 25 | 26 | #ifndef HIDAPI_DARWIN_H__ 27 | #define HIDAPI_DARWIN_H__ 28 | 29 | #include 30 | 31 | #include "hidapi.h" 32 | 33 | #ifdef __cplusplus 34 | extern "C" { 35 | #endif 36 | 37 | /** @brief Get the location ID for a HID device. 38 | 39 | Since version 0.12.0, @ref HID_API_VERSION >= HID_API_MAKE_VERSION(0, 12, 0) 40 | 41 | @ingroup API 42 | @param dev A device handle returned from hid_open(). 43 | @param location_id The device's location ID on return. 44 | 45 | @returns 46 | This function returns 0 on success and -1 on error. 47 | */ 48 | int HID_API_EXPORT_CALL hid_darwin_get_location_id(hid_device *dev, uint32_t *location_id); 49 | 50 | 51 | /** @brief Changes the behavior of all further calls to @ref hid_open or @ref hid_open_path. 52 | 53 | By default on Darwin platform all devices opened by HIDAPI with @ref hid_open or @ref hid_open_path 54 | are opened in exclusive mode (see kIOHIDOptionsTypeSeizeDevice). 55 | 56 | Since version 0.12.0, @ref HID_API_VERSION >= HID_API_MAKE_VERSION(0, 12, 0) 57 | 58 | @ingroup API 59 | @param open_exclusive When set to 0 - all further devices will be opened 60 | in non-exclusive mode. Otherwise - all further devices will be opened 61 | in exclusive mode. 62 | 63 | @note During the initialisation by @ref hid_init - this property is set to 1 (TRUE). 64 | This is done to preserve full backward compatibility with previous behavior. 65 | 66 | @note Calling this function before @ref hid_init or after @ref hid_exit has no effect. 67 | */ 68 | void HID_API_EXPORT_CALL hid_darwin_set_open_exclusive(int open_exclusive); 69 | 70 | /** @brief Getter for option set by @ref hid_darwin_set_open_exclusive. 71 | 72 | Since version 0.12.0, @ref HID_API_VERSION >= HID_API_MAKE_VERSION(0, 12, 0) 73 | 74 | @ingroup API 75 | @return 1 if all further devices will be opened in exclusive mode. 76 | 77 | @note Value returned by this function before calling to @ref hid_init or after @ref hid_exit 78 | is not reliable. 79 | */ 80 | int HID_API_EXPORT_CALL hid_darwin_get_open_exclusive(void); 81 | 82 | /** @brief Check how the device was opened. 83 | 84 | Since version 0.12.0, @ref HID_API_VERSION >= HID_API_MAKE_VERSION(0, 12, 0) 85 | 86 | @ingroup API 87 | @param dev A device to get property from. 88 | 89 | @return 1 if the device is opened in exclusive mode, 0 - opened in non-exclusive, 90 | -1 - if dev is invalid. 91 | */ 92 | int HID_API_EXPORT_CALL hid_darwin_is_device_open_exclusive(hid_device *dev); 93 | 94 | #ifdef __cplusplus 95 | } 96 | #endif 97 | 98 | #endif 99 | -------------------------------------------------------------------------------- /hidapi_descriptor_reconstruct.h: -------------------------------------------------------------------------------- 1 | /******************************************************* 2 | HIDAPI - Multi-Platform library for 3 | communication with HID devices. 4 | 5 | libusb/hidapi Team 6 | 7 | Copyright 2022, All Rights Reserved. 8 | 9 | At the discretion of the user of this library, 10 | this software may be licensed under the terms of the 11 | GNU General Public License v3, a BSD-Style license, or the 12 | original HIDAPI license as outlined in the LICENSE.txt, 13 | LICENSE-gpl3.txt, LICENSE-bsd.txt, and LICENSE-orig.txt 14 | files located at the root of the source distribution. 15 | These files may also be found in the public source 16 | code repository located at: 17 | https://github.com/libusb/hidapi . 18 | ********************************************************/ 19 | #ifndef HIDAPI_DESCRIPTOR_RECONSTRUCT_H__ 20 | #define HIDAPI_DESCRIPTOR_RECONSTRUCT_H__ 21 | 22 | #if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS) 23 | /* Do not warn about wcsncpy usage. 24 | https://docs.microsoft.com/cpp/c-runtime-library/security-features-in-the-crt */ 25 | #define _CRT_SECURE_NO_WARNINGS 26 | #endif 27 | 28 | #include "hidapi_winapi.h" 29 | 30 | #ifdef _MSC_VER 31 | #pragma warning(push) 32 | #pragma warning(disable: 4200) 33 | #pragma warning(disable: 4201) 34 | #pragma warning(disable: 4214) 35 | #endif 36 | 37 | #include 38 | 39 | #include "hidapi_hidsdi.h" 40 | #include 41 | 42 | #define NUM_OF_HIDP_REPORT_TYPES 3 43 | 44 | typedef enum rd_items_ { 45 | rd_main_input = 0x80, /* 1000 00 nn */ 46 | rd_main_output = 0x90, /* 1001 00 nn */ 47 | rd_main_feature = 0xB0, /* 1011 00 nn */ 48 | rd_main_collection = 0xA0, /* 1010 00 nn */ 49 | rd_main_collection_end = 0xC0, /* 1100 00 nn */ 50 | rd_global_usage_page = 0x04, /* 0000 01 nn */ 51 | rd_global_logical_minimum = 0x14, /* 0001 01 nn */ 52 | rd_global_logical_maximum = 0x24, /* 0010 01 nn */ 53 | rd_global_physical_minimum = 0x34, /* 0011 01 nn */ 54 | rd_global_physical_maximum = 0x44, /* 0100 01 nn */ 55 | rd_global_unit_exponent = 0x54, /* 0101 01 nn */ 56 | rd_global_unit = 0x64, /* 0110 01 nn */ 57 | rd_global_report_size = 0x74, /* 0111 01 nn */ 58 | rd_global_report_id = 0x84, /* 1000 01 nn */ 59 | rd_global_report_count = 0x94, /* 1001 01 nn */ 60 | rd_global_push = 0xA4, /* 1010 01 nn */ 61 | rd_global_pop = 0xB4, /* 1011 01 nn */ 62 | rd_local_usage = 0x08, /* 0000 10 nn */ 63 | rd_local_usage_minimum = 0x18, /* 0001 10 nn */ 64 | rd_local_usage_maximum = 0x28, /* 0010 10 nn */ 65 | rd_local_designator_index = 0x38, /* 0011 10 nn */ 66 | rd_local_designator_minimum = 0x48, /* 0100 10 nn */ 67 | rd_local_designator_maximum = 0x58, /* 0101 10 nn */ 68 | rd_local_string = 0x78, /* 0111 10 nn */ 69 | rd_local_string_minimum = 0x88, /* 1000 10 nn */ 70 | rd_local_string_maximum = 0x98, /* 1001 10 nn */ 71 | rd_local_delimiter = 0xA8 /* 1010 10 nn */ 72 | } rd_items; 73 | 74 | typedef enum rd_main_items_ { 75 | rd_input = HidP_Input, 76 | rd_output = HidP_Output, 77 | rd_feature = HidP_Feature, 78 | rd_collection, 79 | rd_collection_end, 80 | rd_delimiter_open, 81 | rd_delimiter_usage, 82 | rd_delimiter_close, 83 | } rd_main_items; 84 | 85 | typedef struct rd_bit_range_ { 86 | int FirstBit; 87 | int LastBit; 88 | } rd_bit_range; 89 | 90 | typedef enum rd_item_node_type_ { 91 | rd_item_node_cap, 92 | rd_item_node_padding, 93 | rd_item_node_collection, 94 | } rd_node_type; 95 | 96 | struct rd_main_item_node { 97 | int FirstBit; /* Position of first bit in report (counting from 0) */ 98 | int LastBit; /* Position of last bit in report (counting from 0) */ 99 | rd_node_type TypeOfNode; /* Information if caps index refers to the array of button caps, value caps, 100 | or if the node is just a padding element to fill unused bit positions. 101 | The node can also be a collection node without any bits in the report. */ 102 | int CapsIndex; /* Index in the array of caps */ 103 | int CollectionIndex; /* Index in the array of link collections */ 104 | rd_main_items MainItemType; /* Input, Output, Feature, Collection or Collection End */ 105 | unsigned char ReportID; 106 | struct rd_main_item_node* next; 107 | }; 108 | 109 | typedef struct hid_pp_caps_info_ { 110 | USHORT FirstCap; 111 | USHORT NumberOfCaps; // Includes empty caps after LastCap 112 | USHORT LastCap; 113 | USHORT ReportByteLength; 114 | } hid_pp_caps_info, *phid_pp_caps_info; 115 | 116 | typedef struct hid_pp_link_collection_node_ { 117 | USAGE LinkUsage; 118 | USAGE LinkUsagePage; 119 | USHORT Parent; 120 | USHORT NumberOfChildren; 121 | USHORT NextSibling; 122 | USHORT FirstChild; 123 | ULONG CollectionType : 8; 124 | ULONG IsAlias : 1; 125 | ULONG Reserved : 23; 126 | // Same as the public API structure HIDP_LINK_COLLECTION_NODE, but without PVOID UserContext at the end 127 | } hid_pp_link_collection_node, *phid_pp_link_collection_node; 128 | 129 | // Note: This is risk-reduction-measure for this specific struct, as it has ULONG bit-field. 130 | // Although very unlikely, it might still be possible that the compiler creates a memory layout that is 131 | // not binary compatile. 132 | // Other structs are not checked at the time of writing. 133 | static_assert(sizeof(struct hid_pp_link_collection_node_) == 16, 134 | "Size of struct hid_pp_link_collection_node_ not as expected. This might break binary compatibility"); 135 | 136 | typedef struct hidp_unknown_token_ { 137 | UCHAR Token; /* Specifies the one-byte prefix of a global item. */ 138 | UCHAR Reserved[3]; 139 | ULONG BitField; /* Specifies the data part of the global item. */ 140 | } hidp_unknown_token, * phidp_unknown_token; 141 | 142 | typedef struct hid_pp_cap_ { 143 | USAGE UsagePage; 144 | UCHAR ReportID; 145 | UCHAR BitPosition; 146 | USHORT ReportSize; // WIN32 term for this is BitSize 147 | USHORT ReportCount; 148 | USHORT BytePosition; 149 | USHORT BitCount; 150 | ULONG BitField; 151 | USHORT NextBytePosition; 152 | USHORT LinkCollection; 153 | USAGE LinkUsagePage; 154 | USAGE LinkUsage; 155 | 156 | // Start of 8 Flags in one byte 157 | BOOLEAN IsMultipleItemsForArray:1; 158 | 159 | BOOLEAN IsPadding:1; 160 | BOOLEAN IsButtonCap:1; 161 | BOOLEAN IsAbsolute:1; 162 | BOOLEAN IsRange:1; 163 | 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. 164 | BOOLEAN IsStringRange:1; 165 | BOOLEAN IsDesignatorRange:1; 166 | // End of 8 Flags in one byte 167 | BOOLEAN Reserved1[3]; 168 | 169 | hidp_unknown_token UnknownTokens[4]; // 4 x 8 Byte 170 | 171 | union { 172 | struct { 173 | USAGE UsageMin; 174 | USAGE UsageMax; 175 | USHORT StringMin; 176 | USHORT StringMax; 177 | USHORT DesignatorMin; 178 | USHORT DesignatorMax; 179 | USHORT DataIndexMin; 180 | USHORT DataIndexMax; 181 | } Range; 182 | struct { 183 | USAGE Usage; 184 | USAGE Reserved1; 185 | USHORT StringIndex; 186 | USHORT Reserved2; 187 | USHORT DesignatorIndex; 188 | USHORT Reserved3; 189 | USHORT DataIndex; 190 | USHORT Reserved4; 191 | } NotRange; 192 | }; 193 | union { 194 | struct { 195 | LONG LogicalMin; 196 | LONG LogicalMax; 197 | } Button; 198 | struct { 199 | BOOLEAN HasNull; 200 | UCHAR Reserved4[3]; 201 | LONG LogicalMin; 202 | LONG LogicalMax; 203 | LONG PhysicalMin; 204 | LONG PhysicalMax; 205 | } NotButton; 206 | }; 207 | ULONG Units; 208 | ULONG UnitsExp; 209 | 210 | } hid_pp_cap, *phid_pp_cap; 211 | 212 | typedef struct hidp_preparsed_data_ { 213 | UCHAR MagicKey[8]; 214 | USAGE Usage; 215 | USAGE UsagePage; 216 | USHORT Reserved[2]; 217 | 218 | // CAPS structure for Input, Output and Feature 219 | hid_pp_caps_info caps_info[3]; 220 | 221 | USHORT FirstByteOfLinkCollectionArray; 222 | USHORT NumberLinkCollectionNodes; 223 | 224 | #ifndef _MSC_VER 225 | // MINGW fails with: Flexible array member in union not supported 226 | // Solution: https://gcc.gnu.org/onlinedocs/gcc/Zero-Length.html 227 | union { 228 | #pragma GCC diagnostic push 229 | #pragma GCC diagnostic ignored "-Wpedantic" 230 | hid_pp_cap caps[0]; 231 | hid_pp_link_collection_node LinkCollectionArray[0]; 232 | #pragma GCC diagnostic pop 233 | }; 234 | #else 235 | union { 236 | hid_pp_cap caps[]; 237 | hid_pp_link_collection_node LinkCollectionArray[]; 238 | }; 239 | #endif 240 | 241 | } hidp_preparsed_data; 242 | 243 | #ifdef _MSC_VER 244 | #pragma warning(pop) 245 | #endif 246 | 247 | #endif 248 | -------------------------------------------------------------------------------- /hidapi_hidclass.h: -------------------------------------------------------------------------------- 1 | /******************************************************* 2 | HIDAPI - Multi-Platform library for 3 | communication with HID devices. 4 | 5 | libusb/hidapi Team 6 | 7 | Copyright 2022, All Rights Reserved. 8 | 9 | At the discretion of the user of this library, 10 | this software may be licensed under the terms of the 11 | GNU General Public License v3, a BSD-Style license, or the 12 | original HIDAPI license as outlined in the LICENSE.txt, 13 | LICENSE-gpl3.txt, LICENSE-bsd.txt, and LICENSE-orig.txt 14 | files located at the root of the source distribution. 15 | These files may also be found in the public source 16 | code repository located at: 17 | https://github.com/libusb/hidapi . 18 | ********************************************************/ 19 | 20 | #ifndef HIDAPI_HIDCLASS_H 21 | #define HIDAPI_HIDCLASS_H 22 | 23 | #ifdef HIDAPI_USE_DDK 24 | 25 | #include 26 | 27 | #else 28 | 29 | /* This part of the header mimics hidclass.h, 30 | but only what is used by HIDAPI */ 31 | 32 | #ifndef FILE_DEVICE_KEYBOARD 33 | #define FILE_DEVICE_KEYBOARD 0x0000000b 34 | #endif 35 | 36 | #ifndef METHOD_OUT_DIRECT 37 | #define METHOD_OUT_DIRECT 2 38 | #endif 39 | 40 | #ifndef FILE_ANY_ACCESS 41 | #define FILE_ANY_ACCESS 0 42 | #endif 43 | 44 | #ifndef CTL_CODE 45 | #define CTL_CODE( DeviceType, Function, Method, Access ) ( \ 46 | ((DeviceType) << 16) | ((Access) << 14) | ((Function) << 2) | (Method) \ 47 | ) 48 | #endif 49 | 50 | #define HID_OUT_CTL_CODE(id) CTL_CODE(FILE_DEVICE_KEYBOARD, (id), METHOD_OUT_DIRECT, FILE_ANY_ACCESS) 51 | #define IOCTL_HID_GET_FEATURE HID_OUT_CTL_CODE(100) 52 | #define IOCTL_HID_GET_INPUT_REPORT HID_OUT_CTL_CODE(104) 53 | 54 | #endif 55 | 56 | #endif /* HIDAPI_HIDCLASS_H */ 57 | -------------------------------------------------------------------------------- /hidapi_hidpi.h: -------------------------------------------------------------------------------- 1 | /******************************************************* 2 | HIDAPI - Multi-Platform library for 3 | communication with HID devices. 4 | 5 | libusb/hidapi Team 6 | 7 | Copyright 2022, All Rights Reserved. 8 | 9 | At the discretion of the user of this library, 10 | this software may be licensed under the terms of the 11 | GNU General Public License v3, a BSD-Style license, or the 12 | original HIDAPI license as outlined in the LICENSE.txt, 13 | LICENSE-gpl3.txt, LICENSE-bsd.txt, and LICENSE-orig.txt 14 | files located at the root of the source distribution. 15 | These files may also be found in the public source 16 | code repository located at: 17 | https://github.com/libusb/hidapi . 18 | ********************************************************/ 19 | 20 | #ifndef HIDAPI_HIDPI_H 21 | #define HIDAPI_HIDPI_H 22 | 23 | #ifdef HIDAPI_USE_DDK 24 | 25 | #include 26 | 27 | #else 28 | 29 | /* This part of the header mimics hidpi.h, 30 | but only what is used by HIDAPI */ 31 | 32 | typedef enum _HIDP_REPORT_TYPE 33 | { 34 | HidP_Input, 35 | HidP_Output, 36 | HidP_Feature 37 | } HIDP_REPORT_TYPE; 38 | 39 | typedef struct _HIDP_PREPARSED_DATA * PHIDP_PREPARSED_DATA; 40 | 41 | typedef struct _HIDP_CAPS 42 | { 43 | USAGE Usage; 44 | USAGE UsagePage; 45 | USHORT InputReportByteLength; 46 | USHORT OutputReportByteLength; 47 | USHORT FeatureReportByteLength; 48 | USHORT Reserved[17]; 49 | 50 | USHORT NumberLinkCollectionNodes; 51 | 52 | USHORT NumberInputButtonCaps; 53 | USHORT NumberInputValueCaps; 54 | USHORT NumberInputDataIndices; 55 | 56 | USHORT NumberOutputButtonCaps; 57 | USHORT NumberOutputValueCaps; 58 | USHORT NumberOutputDataIndices; 59 | 60 | USHORT NumberFeatureButtonCaps; 61 | USHORT NumberFeatureValueCaps; 62 | USHORT NumberFeatureDataIndices; 63 | } HIDP_CAPS, *PHIDP_CAPS; 64 | 65 | #define HIDP_STATUS_SUCCESS 0x00110000 66 | #define HIDP_STATUS_INVALID_PREPARSED_DATA 0xc0110001 67 | 68 | typedef NTSTATUS (__stdcall *HidP_GetCaps_)(PHIDP_PREPARSED_DATA preparsed_data, PHIDP_CAPS caps); 69 | 70 | #endif 71 | 72 | #endif /* HIDAPI_HIDPI_H */ 73 | -------------------------------------------------------------------------------- /hidapi_hidsdi.h: -------------------------------------------------------------------------------- 1 | /******************************************************* 2 | HIDAPI - Multi-Platform library for 3 | communication with HID devices. 4 | 5 | libusb/hidapi Team 6 | 7 | Copyright 2022, All Rights Reserved. 8 | 9 | At the discretion of the user of this library, 10 | this software may be licensed under the terms of the 11 | GNU General Public License v3, a BSD-Style license, or the 12 | original HIDAPI license as outlined in the LICENSE.txt, 13 | LICENSE-gpl3.txt, LICENSE-bsd.txt, and LICENSE-orig.txt 14 | files located at the root of the source distribution. 15 | These files may also be found in the public source 16 | code repository located at: 17 | https://github.com/libusb/hidapi . 18 | ********************************************************/ 19 | 20 | #ifndef HIDAPI_HIDSDI_H 21 | #define HIDAPI_HIDSDI_H 22 | 23 | #ifdef HIDAPI_USE_DDK 24 | 25 | #include 26 | 27 | #else 28 | 29 | /* This part of the header mimics hidsdi.h, 30 | but only what is used by HIDAPI */ 31 | 32 | typedef USHORT USAGE; 33 | 34 | #include "hidapi_hidpi.h" 35 | 36 | typedef struct _HIDD_ATTRIBUTES{ 37 | ULONG Size; 38 | USHORT VendorID; 39 | USHORT ProductID; 40 | USHORT VersionNumber; 41 | } HIDD_ATTRIBUTES, *PHIDD_ATTRIBUTES; 42 | 43 | typedef void (__stdcall *HidD_GetHidGuid_)(LPGUID hid_guid); 44 | typedef BOOLEAN (__stdcall *HidD_GetAttributes_)(HANDLE device, PHIDD_ATTRIBUTES attrib); 45 | typedef BOOLEAN (__stdcall *HidD_GetSerialNumberString_)(HANDLE device, PVOID buffer, ULONG buffer_len); 46 | typedef BOOLEAN (__stdcall *HidD_GetManufacturerString_)(HANDLE handle, PVOID buffer, ULONG buffer_len); 47 | typedef BOOLEAN (__stdcall *HidD_GetProductString_)(HANDLE handle, PVOID buffer, ULONG buffer_len); 48 | typedef BOOLEAN (__stdcall *HidD_SetFeature_)(HANDLE handle, PVOID data, ULONG length); 49 | typedef BOOLEAN (__stdcall *HidD_GetFeature_)(HANDLE handle, PVOID data, ULONG length); 50 | typedef BOOLEAN (__stdcall* HidD_SetOutputReport_)(HANDLE handle, PVOID data, ULONG length); 51 | typedef BOOLEAN (__stdcall *HidD_GetInputReport_)(HANDLE handle, PVOID data, ULONG length); 52 | typedef BOOLEAN (__stdcall *HidD_GetIndexedString_)(HANDLE handle, ULONG string_index, PVOID buffer, ULONG buffer_len); 53 | typedef BOOLEAN (__stdcall *HidD_GetPreparsedData_)(HANDLE handle, PHIDP_PREPARSED_DATA *preparsed_data); 54 | typedef BOOLEAN (__stdcall *HidD_FreePreparsedData_)(PHIDP_PREPARSED_DATA preparsed_data); 55 | typedef BOOLEAN (__stdcall *HidD_SetNumInputBuffers_)(HANDLE handle, ULONG number_buffers); 56 | 57 | #endif 58 | 59 | #endif /* HIDAPI_HIDSDI_H */ 60 | -------------------------------------------------------------------------------- /hidapi_libusb.h: -------------------------------------------------------------------------------- 1 | /******************************************************* 2 | HIDAPI - Multi-Platform library for 3 | communication with HID devices. 4 | 5 | libusb/hidapi Team 6 | 7 | Copyright 2021, All Rights Reserved. 8 | 9 | At the discretion of the user of this library, 10 | this software may be licensed under the terms of the 11 | GNU General Public License v3, a BSD-Style license, or the 12 | original HIDAPI license as outlined in the LICENSE.txt, 13 | LICENSE-gpl3.txt, LICENSE-bsd.txt, and LICENSE-orig.txt 14 | files located at the root of the source distribution. 15 | These files may also be found in the public source 16 | code repository located at: 17 | https://github.com/libusb/hidapi . 18 | ********************************************************/ 19 | 20 | /** @file 21 | * @defgroup API hidapi API 22 | 23 | * Since version 0.11.0, @ref HID_API_VERSION >= HID_API_MAKE_VERSION(0, 11, 0). 24 | */ 25 | 26 | #ifndef HIDAPI_LIBUSB_H__ 27 | #define HIDAPI_LIBUSB_H__ 28 | 29 | #include 30 | 31 | #include "hidapi.h" 32 | 33 | #ifdef __cplusplus 34 | extern "C" { 35 | #endif 36 | 37 | /** @brief Open a HID device using libusb_wrap_sys_device. 38 | See https://libusb.sourceforge.io/api-1.0/group__libusb__dev.html#ga98f783e115ceff4eaf88a60e6439563c, 39 | for details on libusb_wrap_sys_device. 40 | 41 | @ingroup API 42 | @param sys_dev Platform-specific file descriptor that can be recognised by libusb. 43 | @param interface_num USB interface number of the device to be used as HID interface. 44 | Pass -1 to select first HID interface of the device. 45 | 46 | @returns 47 | This function returns a pointer to a #hid_device object on 48 | success or NULL on failure. 49 | */ 50 | HID_API_EXPORT hid_device * HID_API_CALL hid_libusb_wrap_sys_device(intptr_t sys_dev, int interface_num); 51 | 52 | #ifdef __cplusplus 53 | } 54 | #endif 55 | 56 | #endif 57 | -------------------------------------------------------------------------------- /hidapi_thread_pthread.h: -------------------------------------------------------------------------------- 1 | /******************************************************* 2 | HIDAPI - Multi-Platform library for 3 | communication with HID devices. 4 | 5 | Alan Ott 6 | Signal 11 Software 7 | 8 | libusb/hidapi Team 9 | 10 | Sam Lantinga 11 | 12 | Copyright 2023, All Rights Reserved. 13 | 14 | At the discretion of the user of this library, 15 | this software may be licensed under the terms of the 16 | GNU General Public License v3, a BSD-Style license, or the 17 | original HIDAPI license as outlined in the LICENSE.txt, 18 | LICENSE-gpl3.txt, LICENSE-bsd.txt, and LICENSE-orig.txt 19 | files located at the root of the source distribution. 20 | These files may also be found in the public source 21 | code repository located at: 22 | https://github.com/libusb/hidapi . 23 | ********************************************************/ 24 | 25 | #include 26 | 27 | #if defined(__ANDROID__) && __ANDROID_API__ < __ANDROID_API_N__ 28 | 29 | /* Barrier implementation because Android/Bionic don't have pthread_barrier. 30 | This implementation came from Brent Priddy and was posted on 31 | StackOverflow. It is used with his permission. */ 32 | typedef int pthread_barrierattr_t; 33 | typedef struct pthread_barrier { 34 | pthread_mutex_t mutex; 35 | pthread_cond_t cond; 36 | int count; 37 | int trip_count; 38 | } pthread_barrier_t; 39 | 40 | static int pthread_barrier_init(pthread_barrier_t *barrier, const pthread_barrierattr_t *attr, unsigned int count) 41 | { 42 | if(count == 0) { 43 | errno = EINVAL; 44 | return -1; 45 | } 46 | 47 | if(pthread_mutex_init(&barrier->mutex, 0) < 0) { 48 | return -1; 49 | } 50 | if(pthread_cond_init(&barrier->cond, 0) < 0) { 51 | pthread_mutex_destroy(&barrier->mutex); 52 | return -1; 53 | } 54 | barrier->trip_count = count; 55 | barrier->count = 0; 56 | 57 | return 0; 58 | } 59 | 60 | static int pthread_barrier_destroy(pthread_barrier_t *barrier) 61 | { 62 | pthread_cond_destroy(&barrier->cond); 63 | pthread_mutex_destroy(&barrier->mutex); 64 | return 0; 65 | } 66 | 67 | static int pthread_barrier_wait(pthread_barrier_t *barrier) 68 | { 69 | pthread_mutex_lock(&barrier->mutex); 70 | ++(barrier->count); 71 | if(barrier->count >= barrier->trip_count) { 72 | barrier->count = 0; 73 | pthread_cond_broadcast(&barrier->cond); 74 | pthread_mutex_unlock(&barrier->mutex); 75 | return 1; 76 | } 77 | else { 78 | pthread_cond_wait(&barrier->cond, &(barrier->mutex)); 79 | pthread_mutex_unlock(&barrier->mutex); 80 | return 0; 81 | } 82 | } 83 | 84 | #endif 85 | 86 | #define HIDAPI_THREAD_TIMED_OUT ETIMEDOUT 87 | 88 | typedef struct timespec hidapi_timespec; 89 | 90 | typedef struct 91 | { 92 | pthread_t thread; 93 | pthread_mutex_t mutex; /* Protects input_reports */ 94 | pthread_cond_t condition; 95 | pthread_barrier_t barrier; /* Ensures correct startup sequence */ 96 | 97 | } hidapi_thread_state; 98 | 99 | static void hidapi_thread_state_init(hidapi_thread_state *state) 100 | { 101 | pthread_mutex_init(&state->mutex, NULL); 102 | pthread_cond_init(&state->condition, NULL); 103 | pthread_barrier_init(&state->barrier, NULL, 2); 104 | } 105 | 106 | static void hidapi_thread_state_destroy(hidapi_thread_state *state) 107 | { 108 | pthread_barrier_destroy(&state->barrier); 109 | pthread_cond_destroy(&state->condition); 110 | pthread_mutex_destroy(&state->mutex); 111 | } 112 | 113 | #define hidapi_thread_cleanup_push pthread_cleanup_push 114 | #define hidapi_thread_cleanup_pop pthread_cleanup_pop 115 | 116 | static void hidapi_thread_mutex_lock(hidapi_thread_state *state) 117 | { 118 | pthread_mutex_lock(&state->mutex); 119 | } 120 | 121 | static void hidapi_thread_mutex_unlock(hidapi_thread_state *state) 122 | { 123 | pthread_mutex_unlock(&state->mutex); 124 | } 125 | 126 | static void hidapi_thread_cond_wait(hidapi_thread_state *state) 127 | { 128 | pthread_cond_wait(&state->condition, &state->mutex); 129 | } 130 | 131 | static int hidapi_thread_cond_timedwait(hidapi_thread_state *state, hidapi_timespec *ts) 132 | { 133 | return pthread_cond_timedwait(&state->condition, &state->mutex, ts); 134 | } 135 | 136 | static void hidapi_thread_cond_signal(hidapi_thread_state *state) 137 | { 138 | pthread_cond_signal(&state->condition); 139 | } 140 | 141 | static void hidapi_thread_cond_broadcast(hidapi_thread_state *state) 142 | { 143 | pthread_cond_broadcast(&state->condition); 144 | } 145 | 146 | static void hidapi_thread_barrier_wait(hidapi_thread_state *state) 147 | { 148 | pthread_barrier_wait(&state->barrier); 149 | } 150 | 151 | static void hidapi_thread_create(hidapi_thread_state *state, void *(*func)(void*), void *func_arg) 152 | { 153 | pthread_create(&state->thread, NULL, func, func_arg); 154 | } 155 | 156 | static void hidapi_thread_join(hidapi_thread_state *state) 157 | { 158 | pthread_join(state->thread, NULL); 159 | } 160 | 161 | static void hidapi_thread_gettime(hidapi_timespec *ts) 162 | { 163 | clock_gettime(CLOCK_REALTIME, ts); 164 | } 165 | 166 | static void hidapi_thread_addtime(hidapi_timespec *ts, int milliseconds) 167 | { 168 | ts->tv_sec += milliseconds / 1000; 169 | ts->tv_nsec += (milliseconds % 1000) * 1000000; 170 | if (ts->tv_nsec >= 1000000000L) { 171 | ts->tv_sec++; 172 | ts->tv_nsec -= 1000000000L; 173 | } 174 | } 175 | -------------------------------------------------------------------------------- /hidapi_winapi.h: -------------------------------------------------------------------------------- 1 | /******************************************************* 2 | HIDAPI - Multi-Platform library for 3 | communication with HID devices. 4 | 5 | libusb/hidapi Team 6 | 7 | Copyright 2022, All Rights Reserved. 8 | 9 | At the discretion of the user of this library, 10 | this software may be licensed under the terms of the 11 | GNU General Public License v3, a BSD-Style license, or the 12 | original HIDAPI license as outlined in the LICENSE.txt, 13 | LICENSE-gpl3.txt, LICENSE-bsd.txt, and LICENSE-orig.txt 14 | files located at the root of the source distribution. 15 | These files may also be found in the public source 16 | code repository located at: 17 | https://github.com/libusb/hidapi . 18 | ********************************************************/ 19 | 20 | /** @file 21 | * @defgroup API hidapi API 22 | * 23 | * Since version 0.12.0, @ref HID_API_VERSION >= HID_API_MAKE_VERSION(0, 12, 0) 24 | */ 25 | 26 | #ifndef HIDAPI_WINAPI_H__ 27 | #define HIDAPI_WINAPI_H__ 28 | 29 | #include 30 | 31 | #include 32 | 33 | #include "hidapi.h" 34 | 35 | #ifdef __cplusplus 36 | extern "C" { 37 | #endif 38 | 39 | /** @brief Get the container ID for a HID device. 40 | 41 | Since version 0.12.0, @ref HID_API_VERSION >= HID_API_MAKE_VERSION(0, 12, 0) 42 | 43 | This function returns the `DEVPKEY_Device_ContainerId` property of 44 | the given device. This can be used to correlate different 45 | interfaces/ports on the same hardware device. 46 | 47 | @ingroup API 48 | @param dev A device handle returned from hid_open(). 49 | @param container_id The device's container ID on return. 50 | 51 | @returns 52 | This function returns 0 on success and -1 on error. 53 | */ 54 | int HID_API_EXPORT_CALL hid_winapi_get_container_id(hid_device *dev, GUID *container_id); 55 | 56 | /** 57 | * @brief Reconstructs a HID Report Descriptor from a Win32 HIDP_PREPARSED_DATA structure. 58 | * This reconstructed report descriptor is logical identical to the real report descriptor, 59 | * but not byte wise identical. 60 | * 61 | * @param[in] hidp_preparsed_data Pointer to the HIDP_PREPARSED_DATA to read, i.e.: the value of PHIDP_PREPARSED_DATA, 62 | * as returned by HidD_GetPreparsedData WinAPI function. 63 | * @param buf Pointer to the buffer where the report descriptor should be stored. 64 | * @param[in] buf_size Size of the buffer. The recommended size for the buffer is @ref HID_API_MAX_REPORT_DESCRIPTOR_SIZE bytes. 65 | * 66 | * @return Returns size of reconstructed report descriptor if successful, -1 for error. 67 | */ 68 | int HID_API_EXPORT_CALL hid_winapi_descriptor_reconstruct_pp_data(void *hidp_preparsed_data, unsigned char *buf, size_t buf_size); 69 | 70 | /** 71 | * @brief Sets the timeout for hid_write operation. 72 | * 73 | * Since version 0.15.0, @ref HID_API_VERSION >= HID_API_MAKE_VERSION(0, 15, 0) 74 | * 75 | * The default timeout is 1sec for winapi backend. 76 | * 77 | * In case if 1sec is not enough, on in case of multi-platform development, 78 | * the recommended value is 5sec, e.g. to match (unconfigurable) 5sec timeout 79 | * set for hidraw (linux kernel) implementation. 80 | * 81 | * When the timeout is set to 0, hid_write function becomes non-blocking and would exit immediately. 82 | * When the timeout is set to INFINITE ((DWORD)-1), the function will not exit, 83 | * until the write operation is performed or an error occured. 84 | * See dwMilliseconds parameter documentation of WaitForSingleObject function. 85 | * 86 | * @param timeout New timeout value in milliseconds. 87 | */ 88 | void HID_API_EXPORT_CALL hid_winapi_set_write_timeout(hid_device *dev, unsigned long timeout); 89 | 90 | #ifdef __cplusplus 91 | } 92 | #endif 93 | 94 | #endif 95 | -------------------------------------------------------------------------------- /wchar.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 Steven Stallion 2 | // 3 | // Redistribution and use in source and binary forms, with or without 4 | // modification, are permitted provided that the following conditions 5 | // are met: 6 | // 1. Redistributions of source code must retain the above copyright 7 | // notice, this list of conditions and the following disclaimer. 8 | // 2. Redistributions in binary form must reproduce the above copyright 9 | // notice, this list of conditions and the following disclaimer in the 10 | // documentation and/or other materials provided with the distribution. 11 | // 12 | // THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS "AS IS" AND 13 | // ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 14 | // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 15 | // ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 16 | // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 17 | // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 18 | // OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 19 | // HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 20 | // LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 21 | // OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 22 | // SUCH DAMAGE. 23 | 24 | //go:build !windows 25 | 26 | package hid 27 | 28 | /* 29 | #include 30 | #include 31 | #include 32 | 33 | // Several functions declared in wchar.h return errors that are fundamentally 34 | // incompatible with cgo's type system. The following function may be used to 35 | // determine if an error occurred. 36 | static bool 37 | iswerr(size_t n) 38 | { 39 | return n == (size_t)-1; 40 | } 41 | */ 42 | import "C" 43 | 44 | import ( 45 | "unsafe" 46 | ) 47 | 48 | // gotowcs converts a Go string to a C wide string. The returned string must 49 | // be freed by the caller by calling C.free. 50 | func gotowcs(s string) *C.wchar_t { 51 | cs := C.CString(s) 52 | defer C.free(unsafe.Pointer(cs)) 53 | 54 | n := C.size_t(len(s)) + 1 55 | wcs := (*C.wchar_t)(calloc(n, C.sizeof_wchar_t)) 56 | 57 | if n, err := C.mbstowcs(wcs, cs, n); C.iswerr(n) { 58 | C.free(unsafe.Pointer(wcs)) 59 | panic(err) 60 | } 61 | return wcs 62 | } 63 | 64 | // wcstogo converts a C wide string to a Go string. 65 | func wcstogo(wcs *C.wchar_t) string { 66 | if wcs == nil { 67 | return "" 68 | } 69 | 70 | n := C.wcslen(wcs) + 1 71 | cs := (*C.char)(calloc(n, C.size_t(C.MB_CUR_MAX))) 72 | defer C.free(unsafe.Pointer(cs)) 73 | 74 | if n, err := C.wcstombs(cs, wcs, n); C.iswerr(n) { 75 | panic(err) 76 | } 77 | return C.GoString(cs) 78 | } 79 | -------------------------------------------------------------------------------- /wchar_windows.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 Steven Stallion 2 | // 3 | // Redistribution and use in source and binary forms, with or without 4 | // modification, are permitted provided that the following conditions 5 | // are met: 6 | // 1. Redistributions of source code must retain the above copyright 7 | // notice, this list of conditions and the following disclaimer. 8 | // 2. Redistributions in binary form must reproduce the above copyright 9 | // notice, this list of conditions and the following disclaimer in the 10 | // documentation and/or other materials provided with the distribution. 11 | // 12 | // THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS "AS IS" AND 13 | // ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 14 | // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 15 | // ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 16 | // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 17 | // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 18 | // OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 19 | // HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 20 | // LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 21 | // OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 22 | // SUCH DAMAGE. 23 | 24 | package hid 25 | 26 | // C.mbstowcs() and C.wcstombs() behave poorly on Windows when used with MBCS 27 | // strings (eg. Chinese, Korean, and Japanese). This is due to several related 28 | // issues working with code pages, encoding, the C runtime, and the compiler. 29 | // See #6 and #7 for details. 30 | 31 | /* 32 | #include 33 | */ 34 | import "C" 35 | 36 | import ( 37 | "golang.org/x/sys/windows" 38 | ) 39 | 40 | // gotowcs converts a Go string to a C wide string. The returned string must 41 | // be freed by the caller by calling C.free. 42 | func gotowcs(s string) *C.wchar_t { 43 | u16s, err := windows.UTF16FromString(s) 44 | if err != nil { 45 | panic(err) 46 | } 47 | n := C.size_t(len(u16s)) 48 | wcs := (*C.wchar_t)(calloc(n+1, C.sizeof_wchar_t)) 49 | return C.wmemcpy(wcs, (*C.wchar_t)(&u16s[0]), n) 50 | } 51 | 52 | // wcstogo converts a C wide string to a Go string. 53 | func wcstogo(wcs *C.wchar_t) string { 54 | return windows.UTF16PtrToString((*uint16)(wcs)) 55 | } 56 | --------------------------------------------------------------------------------