├── .travis.yml ├── src ├── main │ ├── resources │ │ ├── win32-x86 │ │ │ └── hidapi.dll │ │ ├── linux-arm │ │ │ ├── libhidapi.so │ │ │ └── libhidapi-libusb.so │ │ ├── linux-x86 │ │ │ ├── libhidapi.so │ │ │ └── libhidapi-libusb.so │ │ ├── win32-x86-64 │ │ │ └── hidapi.dll │ │ ├── linux-amd64 │ │ │ ├── libhidapi.so │ │ │ └── libhidapi-libusb.so │ │ ├── linux-armel │ │ │ ├── libhidapi.so │ │ │ └── libhidapi-libusb.so │ │ ├── linux-x86-64 │ │ │ ├── libhidapi.so │ │ │ └── libhidapi-libusb.so │ │ ├── win32-aarch64 │ │ │ └── hidapi.dll │ │ ├── linux-aarch64 │ │ │ ├── libhidapi.so │ │ │ └── libhidapi-libusb.so │ │ ├── linux-riscv64 │ │ │ ├── libhidapi.so │ │ │ └── libhidapi-libusb.so │ │ ├── darwin-aarch64 │ │ │ └── libhidapi.dylib │ │ └── darwin-x86-64 │ │ │ └── libhidapi.dylib │ └── java │ │ └── org │ │ └── hid4java │ │ ├── jna │ │ ├── DarwinHidApiLibrary.java │ │ ├── HidrawHidApiLibrary.java │ │ ├── LibusbHidApiLibrary.java │ │ ├── HidDeviceStructure.java │ │ ├── WideStringBuffer.java │ │ ├── HidDeviceInfoStructure.java │ │ ├── HidApiLibrary.java │ │ └── HidApi.java │ │ ├── HidException.java │ │ ├── ScanMode.java │ │ ├── HidServicesListener.java │ │ ├── event │ │ ├── HidServicesEvent.java │ │ └── HidServicesListenerList.java │ │ ├── HidManager.java │ │ ├── HidServicesSpecification.java │ │ ├── HidServices.java │ │ ├── HidDeviceManager.java │ │ └── HidDevice.java └── test │ └── java │ └── org │ └── hid4java │ ├── jna │ ├── WideStringBufferTest.java │ └── HidDeviceInfoStructureTest.java │ ├── HidDeviceTest.java │ └── examples │ ├── LibusbEnumerationExample.java │ ├── UsbHidEnumerationExample.java │ ├── BaseExample.java │ └── Fido2AuthenticationExample.java ├── AUTHORS ├── .gitignore ├── .github └── ISSUE_TEMPLATE │ ├── feature_request.md │ ├── release.md │ └── bug_report.md ├── LICENSE ├── CODE_OF_CONDUCT.md ├── README.md ├── release.sh └── pom.xml /.travis.yml: -------------------------------------------------------------------------------- 1 | language: java 2 | 3 | jdk: 4 | # Test on Open JDK 11 on Travis due to EOL issues 5 | - openjdk11 -------------------------------------------------------------------------------- /src/main/resources/win32-x86/hidapi.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gary-rowe/hid4java/HEAD/src/main/resources/win32-x86/hidapi.dll -------------------------------------------------------------------------------- /src/main/resources/linux-arm/libhidapi.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gary-rowe/hid4java/HEAD/src/main/resources/linux-arm/libhidapi.so -------------------------------------------------------------------------------- /src/main/resources/linux-x86/libhidapi.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gary-rowe/hid4java/HEAD/src/main/resources/linux-x86/libhidapi.so -------------------------------------------------------------------------------- /src/main/resources/win32-x86-64/hidapi.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gary-rowe/hid4java/HEAD/src/main/resources/win32-x86-64/hidapi.dll -------------------------------------------------------------------------------- /src/main/resources/linux-amd64/libhidapi.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gary-rowe/hid4java/HEAD/src/main/resources/linux-amd64/libhidapi.so -------------------------------------------------------------------------------- /src/main/resources/linux-armel/libhidapi.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gary-rowe/hid4java/HEAD/src/main/resources/linux-armel/libhidapi.so -------------------------------------------------------------------------------- /src/main/resources/linux-x86-64/libhidapi.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gary-rowe/hid4java/HEAD/src/main/resources/linux-x86-64/libhidapi.so -------------------------------------------------------------------------------- /src/main/resources/win32-aarch64/hidapi.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gary-rowe/hid4java/HEAD/src/main/resources/win32-aarch64/hidapi.dll -------------------------------------------------------------------------------- /src/main/resources/linux-aarch64/libhidapi.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gary-rowe/hid4java/HEAD/src/main/resources/linux-aarch64/libhidapi.so -------------------------------------------------------------------------------- /src/main/resources/linux-riscv64/libhidapi.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gary-rowe/hid4java/HEAD/src/main/resources/linux-riscv64/libhidapi.so -------------------------------------------------------------------------------- /src/main/resources/darwin-aarch64/libhidapi.dylib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gary-rowe/hid4java/HEAD/src/main/resources/darwin-aarch64/libhidapi.dylib -------------------------------------------------------------------------------- /src/main/resources/darwin-x86-64/libhidapi.dylib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gary-rowe/hid4java/HEAD/src/main/resources/darwin-x86-64/libhidapi.dylib -------------------------------------------------------------------------------- /src/main/resources/linux-arm/libhidapi-libusb.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gary-rowe/hid4java/HEAD/src/main/resources/linux-arm/libhidapi-libusb.so -------------------------------------------------------------------------------- /src/main/resources/linux-x86/libhidapi-libusb.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gary-rowe/hid4java/HEAD/src/main/resources/linux-x86/libhidapi-libusb.so -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | The following developers made contributions to this project: 2 | 3 | Gary Rowe (Lead developer) 4 | jbliesener 5 | Ktar5 6 | tresf 7 | DaPutzy 8 | -------------------------------------------------------------------------------- /src/main/resources/linux-aarch64/libhidapi-libusb.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gary-rowe/hid4java/HEAD/src/main/resources/linux-aarch64/libhidapi-libusb.so -------------------------------------------------------------------------------- /src/main/resources/linux-amd64/libhidapi-libusb.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gary-rowe/hid4java/HEAD/src/main/resources/linux-amd64/libhidapi-libusb.so -------------------------------------------------------------------------------- /src/main/resources/linux-armel/libhidapi-libusb.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gary-rowe/hid4java/HEAD/src/main/resources/linux-armel/libhidapi-libusb.so -------------------------------------------------------------------------------- /src/main/resources/linux-riscv64/libhidapi-libusb.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gary-rowe/hid4java/HEAD/src/main/resources/linux-riscv64/libhidapi-libusb.so -------------------------------------------------------------------------------- /src/main/resources/linux-x86-64/libhidapi-libusb.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gary-rowe/hid4java/HEAD/src/main/resources/linux-x86-64/libhidapi-libusb.so -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Eclipse 2 | .classpath 3 | .project 4 | .settings/ 5 | 6 | # Intellij 7 | .idea/ 8 | *.iml 9 | *.iws 10 | 11 | # Mac 12 | .DS_Store 13 | 14 | # Maven 15 | log/ 16 | target/ 17 | /release.properties 18 | /pom.xml.releaseBackup 19 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for hid4java 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. 12 | 13 | * I'm always frustrated when [...], or 14 | * If only you supported [...] 15 | * Why do I always have to [...] 16 | 17 | **Describe the solution you'd like** 18 | A clear and concise description of what you want to happen. 19 | 20 | * After a bit of research, I think this could be solved by [...] 21 | * You support platform [...] 22 | 23 | **Describe alternatives you've considered** 24 | A clear and concise description of any alternative solutions or features you've 25 | considered. 26 | 27 | **Additional context** 28 | Add any other context or screenshots about the feature request here. 29 | -------------------------------------------------------------------------------- /src/test/java/org/hid4java/jna/WideStringBufferTest.java: -------------------------------------------------------------------------------- 1 | package org.hid4java.jna; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | import java.util.List; 6 | 7 | import static org.junit.jupiter.api.Assertions.assertEquals; 8 | 9 | class WideStringBufferTest { 10 | 11 | @Test 12 | void getFieldOrder() { 13 | 14 | // Arrange 15 | WideStringBuffer testObject = new WideStringBuffer(new byte[] {0x61, 0x00, 0x62, 0x00, 0x63, 0x00}); 16 | 17 | // Act 18 | List fieldOrder = testObject.getFieldOrder(); 19 | 20 | // Assert 21 | assertEquals(1, fieldOrder.size()); 22 | assertEquals("buffer", fieldOrder.get(0)); 23 | 24 | } 25 | 26 | @Test 27 | void testToString() { 28 | 29 | // Arrange 30 | WideStringBuffer testObject = new WideStringBuffer(new byte[] {0x61, 0x00, 0x62, 0x00, 0x63, 0x00}); 31 | 32 | // Act 33 | String wchar_t = testObject.toString(); 34 | 35 | // Assert 36 | assertEquals("abc", wchar_t); 37 | 38 | } 39 | } -------------------------------------------------------------------------------- /src/main/java/org/hid4java/jna/DarwinHidApiLibrary.java: -------------------------------------------------------------------------------- 1 | package org.hid4java.jna; 2 | 3 | import com.sun.jna.Native; 4 | import com.sun.jna.WString; 5 | 6 | public interface DarwinHidApiLibrary extends HidrawHidApiLibrary { 7 | 8 | DarwinHidApiLibrary INSTANCE = Native.load("hidapi", DarwinHidApiLibrary.class); 9 | 10 | /** 11 | * Changes the behavior of all further calls to {@link #hid_open(short, short, WString)} or {@link #hid_open_path(String)}. 12 | *
13 | * All devices opened by HIDAPI with {@link #hid_open(short, short, WString)} or {@link #hid_open_path(String)} 14 | * are opened in exclusive mode per default. 15 | *
16 | * Calling this function before {@link #hid_init()} or after {@link #hid_exit()} has no effect. 17 | * 18 | * @since hidapi 0.12.0 19 | * @param openExclusive When set to 0 - all further devices will be opened in non-exclusive mode. 20 | * Otherwise - all further devices will be opened in exclusive mode. 21 | */ 22 | void hid_darwin_set_open_exclusive(int openExclusive); 23 | 24 | } 25 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/release.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Release 3 | about: Create a release checklist 4 | title: 'Release checklist for 0.x.0' 5 | labels: '' 6 | assignees: 'gary-rowe' 7 | --- 8 | Here's what's outstanding before `0.x.0` can be released. 9 | 10 | **Pre-release checklist** 11 | 12 | - [ ] FIDO2 device verified working in all test environments 13 | - [ ] `develop-SNAPSHOT` deployed to Maven Central (snapshots) 14 | - [ ] Issues for release marked as "Awaiting merge" 15 | 16 | Feedback received from community verifying the following libraries for `develop-SNAPSHOT`: 17 | 18 | - [ ] `darwin-x86-64` - macOS 64-bit 19 | - [ ] `darwin-aarch64` - macOS ARM64 20 | - [ ] `linux-aarch64` - Linux ARMv8 64-bit only 21 | - [ ] `linux-amd64` - Linux AMD 64-bit only 22 | - [ ] `linux-arm` - Linux ARMv7 hard float 32-bit only 23 | - [ ] `linux-armel` - Linux ARMv5 soft float 32-bit 24 | - [ ] `linux-riscv64` - Linux RISC-V 64-bit 25 | - [ ] `linux-x86-64` - Linux x86 64-bit only 26 | - [ ] `linux-x86` - Linux x86 32-bit only 27 | - [ ] `win32-x86` - Windows 32-bit only 28 | - [ ] `win32-x86-64` - Windows 64-bit only 29 | - [ ] `win32-aarch64` - Windows 64-bit ARM64 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Gary Rowe 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help the project improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | --- 8 | **Describe the problem** 9 | 10 | You may find that this issue has arisen before, so here is a checklist of useful links to provide assistance and potentially avoid duplicate issues: 11 | - [ ] I have read the [Troubleshooting Guide](https://github.com/gary-rowe/hid4java/wiki/Troubleshooting) and this is not covered 12 | - [ ] I have done a [quick search of previous issues](https://github.com/gary-rowe/hid4java/issues?q=is%3Aissue+hidraw) and this needs addressing 13 | 14 | Please provide a clear and concise description of what the problem is below: 15 | 16 | **Platform** 17 | 18 | Knowing the platform greatly narrows down the potential causes of the problem. 19 | 20 | - Platform (e.g. `linux-x86-64`): 21 | - OS version (e.g. `Ubuntu 22.04`): 22 | - `hid4java` version (e.g. `0.8.0`): 23 | 24 | **To Reproduce** 25 | 26 | Steps to reproduce the behavior: 27 | 28 | 1. Attempting to read from device '...' 29 | 2. Code sample looks like this '...' 30 | 3. Error happens when '...' 31 | 32 | **Expected behavior** 33 | 34 | A clear and concise description of what you expected to happen. 35 | 36 | **Screenshots and logs** 37 | 38 | If applicable, add screenshots and redacted logs to help explain your problem. 39 | 40 | Screenshots: 41 | Drag and drop image here if useful 42 | 43 | Logs: 44 | ``` 45 | 46 | ``` 47 | 48 | **Additional information** 49 | Add any other context about the problem here, including any investigative work 50 | done and thoughts on solution. 51 | -------------------------------------------------------------------------------- /src/main/java/org/hid4java/jna/HidrawHidApiLibrary.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2014-2015 Gary Rowe 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | * 24 | */ 25 | 26 | package org.hid4java.jna; 27 | 28 | import com.sun.jna.Native; 29 | 30 | /** 31 | * JNA library interface to act as the proxy for the underlying native library 32 | * This approach removes the need for any JNI or native code 33 | * @since 0.7.0 34 | */ 35 | public interface HidrawHidApiLibrary extends HidApiLibrary { 36 | 37 | HidApiLibrary INSTANCE = Native.load("hidapi", HidApiLibrary.class); 38 | 39 | } -------------------------------------------------------------------------------- /src/main/java/org/hid4java/jna/LibusbHidApiLibrary.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2014-2015 Gary Rowe 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | * 24 | */ 25 | 26 | package org.hid4java.jna; 27 | 28 | import com.sun.jna.Native; 29 | 30 | /** 31 | * JNA library interface to act as the proxy for the underlying native library 32 | * This approach removes the need for any JNI or native code 33 | * @since 0.7.0 34 | */ 35 | public interface LibusbHidApiLibrary extends HidrawHidApiLibrary { 36 | 37 | LibusbHidApiLibrary INSTANCE = Native.load("hidapi-libusb", LibusbHidApiLibrary.class); 38 | 39 | } -------------------------------------------------------------------------------- /src/main/java/org/hid4java/HidException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2014-2015 Gary Rowe 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | * 24 | */ 25 | 26 | package org.hid4java; 27 | 28 | /** 29 | * Caught exception to provide the following to API consumers: 30 | * 33 | * 34 | * @since 0.0.1 35 | */ 36 | public class HidException extends RuntimeException { 37 | 38 | public HidException(String message) { 39 | super(message); 40 | } 41 | 42 | public HidException(String message, Throwable cause) { 43 | super(message, cause); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/main/java/org/hid4java/jna/HidDeviceStructure.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2014-2015 Gary Rowe 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | * 24 | */ 25 | 26 | package org.hid4java.jna; 27 | 28 | import com.sun.jna.Pointer; 29 | import com.sun.jna.Structure; 30 | 31 | import java.util.Arrays; 32 | import java.util.List; 33 | 34 | /** 35 | * Low level JNA value object to provide a HID device pointer 36 | * @since 0.1.0 37 | */ 38 | public class HidDeviceStructure extends Structure implements Structure.ByReference { 39 | 40 | public Pointer ptr; 41 | 42 | public HidDeviceStructure(Pointer p) { 43 | ptr = p; 44 | } 45 | 46 | public Pointer ptr() { 47 | return ptr; 48 | } 49 | 50 | @Override 51 | protected List getFieldOrder() { 52 | return Arrays.asList("ptr"); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/main/java/org/hid4java/ScanMode.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2014-2016 Gary Rowe 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | * 24 | */ 25 | 26 | package org.hid4java; 27 | 28 | /** 29 | * Provide a collection of different device enumeration scanning modes so that 30 | * device attach/detach events can be generated. 31 | */ 32 | public enum ScanMode { 33 | 34 | /** 35 | * Equivalent to scan interval of zero. 36 | */ 37 | NO_SCAN, 38 | /** 39 | * Trigger continuous scan at given interval. 40 | */ 41 | SCAN_AT_FIXED_INTERVAL, 42 | /** 43 | * Trigger continuous scan at given interval but introduce a pause after a write 44 | * operation to allow the device time to process data without having to respond 45 | * to further enumeration requests. 46 | * 47 | * This can be a useful strategy for handling devices with constrained processing 48 | * power and/or limited USB stacks. 49 | * 50 | * Note this will affect the time to generate a device attach/detach event since 51 | * scanning will be paused. 52 | */ 53 | SCAN_AT_FIXED_INTERVAL_WITH_PAUSE_AFTER_WRITE, 54 | 55 | } 56 | -------------------------------------------------------------------------------- /src/test/java/org/hid4java/jna/HidDeviceInfoStructureTest.java: -------------------------------------------------------------------------------- 1 | package org.hid4java.jna; 2 | 3 | import com.sun.jna.WString; 4 | import org.junit.jupiter.api.Test; 5 | 6 | import java.util.Arrays; 7 | import java.util.List; 8 | 9 | import static org.junit.jupiter.api.Assertions.*; 10 | 11 | class HidDeviceInfoStructureTest { 12 | 13 | @Test 14 | void getFieldOrder() { 15 | 16 | // Arrange 17 | HidDeviceInfoStructure testObject = new HidDeviceInfoStructure(); 18 | List expectedFieldOrder = Arrays.asList( 19 | "path", 20 | "vendor_id", 21 | "product_id", 22 | "serial_number", 23 | "release_number", 24 | "manufacturer_string", 25 | "product_string", 26 | "usage_page", 27 | "usage", 28 | "interface_number", 29 | "next" 30 | ); 31 | 32 | // Act 33 | List actualFieldOrder = testObject.getFieldOrder(); 34 | 35 | // Assert 36 | assertEquals(11, actualFieldOrder.size()); 37 | assertEquals(expectedFieldOrder, actualFieldOrder); 38 | 39 | } 40 | 41 | @Test 42 | void show() { 43 | 44 | // Arrange 45 | HidDeviceInfoStructure testObject = new HidDeviceInfoStructure(); 46 | testObject.path = "path"; 47 | testObject.vendor_id = 0x01; 48 | testObject.product_id = 0x02; 49 | testObject.serial_number = new WString("serial"); 50 | testObject.release_number = 0x03; 51 | testObject.manufacturer_string = new WString("manufacturer"); 52 | testObject.product_string = new WString("product"); 53 | testObject.usage_page = 0x04; 54 | testObject.usage = 0x05; 55 | testObject.interface_number = 0x06; 56 | testObject.next = null; 57 | 58 | String expectedShow = "HidDevice\n" + 59 | "\tpath:path>\n" + 60 | "\tvendor_id: 1\n" + 61 | "\tproduct_id: 2\n" + 62 | "\tserial_number: serial>\n" + 63 | "\trelease_number: 3\n" + 64 | "\tmanufacturer_string: manufacturer>\n" + 65 | "\tproduct_string: product>\n" + 66 | "\tusage_page: 4\n" + 67 | "\tusage: 5\n" + 68 | "\tinterface_number: 6\n"; 69 | 70 | // Act 71 | String actualShow = testObject.show(); 72 | 73 | // Assert 74 | assertEquals(expectedShow, actualShow); 75 | 76 | } 77 | } -------------------------------------------------------------------------------- /src/main/java/org/hid4java/HidServicesListener.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2014-2015 Gary Rowe 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | * 24 | */ 25 | 26 | package org.hid4java; 27 | 28 | import org.hid4java.event.HidServicesEvent; 29 | 30 | import java.util.EventListener; 31 | 32 | /** 33 | * Interface to provide the following to API consumers: 34 | *
    35 | *
  • Notification of a HID event
  • 36 | *
37 | * 38 | * @since 0.0.1 39 | */ 40 | public interface HidServicesListener extends EventListener { 41 | /** 42 | * A HID was attached 43 | * 44 | * @param event The event 45 | */ 46 | void hidDeviceAttached(HidServicesEvent event); 47 | 48 | /** 49 | * A HID was detached 50 | * 51 | * @param event The event 52 | */ 53 | void hidDeviceDetached(HidServicesEvent event); 54 | 55 | /** 56 | * A HID failure occurred (enumeration, data transfer etc) 57 | * 58 | * @param event The event 59 | */ 60 | void hidFailure(HidServicesEvent event); 61 | 62 | /** 63 | * A HID input data buffer was populated 64 | * 65 | * @param event The event 66 | */ 67 | void hidDataReceived(HidServicesEvent event); 68 | 69 | } -------------------------------------------------------------------------------- /src/main/java/org/hid4java/jna/WideStringBuffer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2014-2015 Gary Rowe 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | * 24 | */ 25 | 26 | package org.hid4java.jna; 27 | 28 | import com.sun.jna.Structure; 29 | 30 | import java.util.Collections; 31 | import java.util.List; 32 | 33 | /** 34 | * Wrapper for a wide character (WCHAR) structure 35 | * @since 0.1.0 36 | */ 37 | public class WideStringBuffer extends Structure implements Structure.ByReference { 38 | 39 | public byte[] buffer; 40 | 41 | public WideStringBuffer(int len) { 42 | buffer = new byte[len]; 43 | } 44 | 45 | public WideStringBuffer(byte[] bytes) { 46 | buffer = bytes; 47 | } 48 | 49 | @Override 50 | protected List getFieldOrder() { 51 | return Collections.singletonList("buffer"); 52 | } 53 | 54 | /** 55 | * HIDAPI uses wchar_t which is written l i k e t h i s (with '\0' in between) 56 | */ 57 | public String toString() { 58 | StringBuilder str = new StringBuilder(); 59 | for (int i = 0; i < buffer.length && buffer[i] != 0; i += 2) 60 | str.append((char) (buffer[i] | buffer[i + 1] << 8)); 61 | return str.toString(); 62 | } 63 | 64 | } 65 | -------------------------------------------------------------------------------- /src/test/java/org/hid4java/HidDeviceTest.java: -------------------------------------------------------------------------------- 1 | package org.hid4java; 2 | 3 | import com.sun.jna.WString; 4 | import org.hid4java.jna.HidDeviceInfoStructure; 5 | import org.junit.jupiter.api.Test; 6 | 7 | import static org.junit.jupiter.api.Assertions.assertEquals; 8 | import static org.junit.jupiter.api.Assertions.assertTrue; 9 | 10 | class HidDeviceTest { 11 | 12 | HidDeviceInfoStructure mockStructure = new HidDeviceInfoStructure(); 13 | 14 | @Test 15 | void isVidPidSerial_UnsignedShort_Simple() { 16 | 17 | // Arrange 18 | mockStructure.vendor_id = 0x01; 19 | mockStructure.product_id = 0x02; 20 | mockStructure.serial_number = new WString("1234"); 21 | 22 | // Act 23 | HidDevice testObject = new HidDevice(mockStructure, null, new HidServicesSpecification()); 24 | 25 | // Assert 26 | assertTrue(testObject.isVidPidSerial(0x01, 0x02, "1234")); 27 | 28 | } 29 | 30 | @Test 31 | void isVidPidSerial_UnsignedShort_Overflow() { 32 | 33 | // Arrange 34 | mockStructure.vendor_id = 0xffff8001; 35 | mockStructure.product_id = 0xffff8002; 36 | mockStructure.serial_number = new WString("1234"); 37 | 38 | // Act 39 | HidDevice testObject = new HidDevice(mockStructure, null, new HidServicesSpecification()); 40 | 41 | // Assert 42 | assertTrue(testObject.isVidPidSerial(0x8001, 0x8002, "1234")); 43 | 44 | } 45 | 46 | @Test 47 | void verifyFields() { 48 | 49 | // Arrange 50 | mockStructure.path="path"; 51 | mockStructure.vendor_id=1; 52 | mockStructure.product_id=2; 53 | mockStructure.serial_number=new WString("serial"); 54 | mockStructure.release_number=3; 55 | mockStructure.manufacturer_string=new WString("manufacturer"); 56 | mockStructure.product_string = new WString("product"); 57 | mockStructure.usage_page=4; 58 | mockStructure.usage=5; 59 | mockStructure.interface_number=6; 60 | mockStructure.next=null; 61 | 62 | // Act 63 | HidDevice testObject = new HidDevice(mockStructure, null, new HidServicesSpecification()); 64 | 65 | // Assert 66 | assertEquals("path", testObject.getPath()); 67 | assertEquals(1, testObject.getVendorId()); 68 | assertEquals(2, testObject.getProductId()); 69 | assertEquals("serial", testObject.getSerialNumber()); 70 | assertEquals(3, testObject.getReleaseNumber()); 71 | assertEquals("manufacturer", testObject.getManufacturer()); 72 | assertEquals("product", testObject.getProduct()); 73 | assertEquals(4,testObject.getUsagePage()); 74 | assertEquals(5, testObject.getUsage()); 75 | assertEquals(6, testObject.getInterfaceNumber()); 76 | 77 | } 78 | 79 | } -------------------------------------------------------------------------------- /src/test/java/org/hid4java/examples/LibusbEnumerationExample.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2014-2020 Gary Rowe 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | * 24 | */ 25 | 26 | package org.hid4java.examples; 27 | 28 | import org.hid4java.*; 29 | import org.hid4java.jna.HidApi; 30 | 31 | /** 32 | * Demonstrate the USB HID interface with older libusb Linux library variant 33 | * 34 | * @since 0.7.0 35 | */ 36 | public class LibusbEnumerationExample extends BaseExample { 37 | 38 | public static void main(String[] args) throws HidException { 39 | 40 | LibusbEnumerationExample example = new LibusbEnumerationExample(); 41 | example.executeExample(); 42 | 43 | } 44 | 45 | private void executeExample() throws HidException { 46 | 47 | printPlatform(); 48 | 49 | // Configure to use default specification 50 | HidServicesSpecification hidServicesSpecification = new HidServicesSpecification(); 51 | 52 | // Set the libusb variant (only needed for older Linux platforms) 53 | HidApi.useLibUsbVariant = true; 54 | 55 | // Get HID services using custom specification 56 | HidServices hidServices = HidManager.getHidServices(hidServicesSpecification); 57 | hidServices.addHidServicesListener(this); 58 | 59 | System.out.println(ANSI_GREEN + "Enumerating attached devices..." + ANSI_RESET); 60 | 61 | // Provide a list of attached devices 62 | for (HidDevice hidDevice : hidServices.getAttachedHidDevices()) { 63 | System.out.println(hidDevice); 64 | } 65 | 66 | waitAndShutdown(hidServices); 67 | 68 | } 69 | 70 | } 71 | -------------------------------------------------------------------------------- /src/main/java/org/hid4java/event/HidServicesEvent.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2014-2015 Gary Rowe 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | * 24 | */ 25 | 26 | package org.hid4java.event; 27 | 28 | import org.hid4java.HidDevice; 29 | 30 | import java.util.Arrays; 31 | 32 | 33 | /** 34 | * Event to provide the following to API consumers: 35 | *
    36 | *
  • Provision of HID device information
  • 37 | *
38 | * 39 | * @since 0.0.1 40 | */ 41 | public class HidServicesEvent { 42 | 43 | private final HidDevice hidDevice; 44 | private final byte[] dataReceived; 45 | 46 | /** 47 | * @param device The HidDevice involved in the event 48 | */ 49 | public HidServicesEvent(HidDevice device) { 50 | hidDevice = device; 51 | dataReceived = null; 52 | } 53 | 54 | /** 55 | * @param device The HidDevice involved in the event 56 | * @param dataReceived The contents of all data read 57 | * @since 0.8.0 58 | */ 59 | public HidServicesEvent(HidDevice device, byte[] dataReceived) { 60 | hidDevice = device; 61 | this.dataReceived = Arrays.copyOf(dataReceived, dataReceived.length); 62 | } 63 | 64 | /** 65 | * @return The associated HidDevice 66 | */ 67 | public HidDevice getHidDevice() { 68 | return hidDevice; 69 | } 70 | 71 | /** 72 | * @return The data received (might be multiple packets of data) 73 | */ 74 | public byte[] getDataReceived() { 75 | return dataReceived; 76 | } 77 | 78 | @Override 79 | public String toString() { 80 | return "HidServicesEvent{" + 81 | "hidDevice=" + hidDevice + 82 | '}'; 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/test/java/org/hid4java/examples/UsbHidEnumerationExample.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2014-2020 Gary Rowe 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | * 24 | */ 25 | 26 | package org.hid4java.examples; 27 | 28 | import org.hid4java.*; 29 | 30 | /** 31 | * Demonstrate the USB HID interface using a Satoshi Labs Trezor 32 | * 33 | * @since 0.0.1 34 | */ 35 | public class UsbHidEnumerationExample extends BaseExample { 36 | 37 | public static void main(String[] args) throws HidException { 38 | 39 | UsbHidEnumerationExample example = new UsbHidEnumerationExample(); 40 | example.executeExample(); 41 | 42 | } 43 | 44 | private void executeExample() throws HidException { 45 | 46 | printPlatform(); 47 | 48 | // Configure to use custom specification 49 | HidServicesSpecification hidServicesSpecification = new HidServicesSpecification(); 50 | // Use the v0.7.0 manual start feature to get immediate attach events 51 | hidServicesSpecification.setAutoStart(false); 52 | 53 | // Get HID services using custom specification 54 | HidServices hidServices = HidManager.getHidServices(hidServicesSpecification); 55 | hidServices.addHidServicesListener(this); 56 | 57 | // Manually start the services to get attachment event 58 | System.out.println(ANSI_GREEN + "Manually starting HID services." + ANSI_RESET); 59 | hidServices.start(); 60 | 61 | System.out.println(ANSI_GREEN + "Enumerating attached devices..." + ANSI_RESET); 62 | 63 | // Provide a list of attached devices 64 | for (HidDevice hidDevice : hidServices.getAttachedHidDevices()) { 65 | System.out.println(hidDevice); 66 | } 67 | 68 | waitAndShutdown(hidServices); 69 | 70 | } 71 | 72 | } 73 | -------------------------------------------------------------------------------- /src/main/java/org/hid4java/HidManager.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2014-2015 Gary Rowe 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | * 24 | */ 25 | 26 | package org.hid4java; 27 | 28 | /** 29 | * Factory to provide the following to API consumers: 30 | *
    31 | *
  • Access to configured HID services
  • 32 | *
33 | * 34 | * @since 0.0.1 35 | */ 36 | public class HidManager { 37 | 38 | private static final Object servicesLock = new Object(); 39 | 40 | private static HidServices hidServices = null; 41 | 42 | /** 43 | * Simple service provider providing generally safe defaults. If you find you are experiencing problems, particularly 44 | * with constrained devices, consider exploring the {@link HidServicesSpecification} options. 45 | * 46 | * @return A single instance of the HID services using the default specification 47 | */ 48 | public static HidServices getHidServices() throws HidException { 49 | 50 | synchronized (servicesLock) { 51 | if (null == hidServices) { 52 | // Use defaults 53 | hidServices = getHidServices(new HidServicesSpecification()); 54 | } 55 | } 56 | 57 | return hidServices; 58 | 59 | } 60 | 61 | /** 62 | * Fully configurable service provider 63 | * 64 | * @param hidServicesSpecification Provides various parameters for configuring HID services 65 | * @return A single instance of the HID services using specified parameters 66 | * @since 0.5.0 67 | */ 68 | public static HidServices getHidServices(HidServicesSpecification hidServicesSpecification) throws HidException { 69 | 70 | synchronized (servicesLock) { 71 | if (null == hidServices) { 72 | hidServices = new HidServices(hidServicesSpecification); 73 | } 74 | } 75 | 76 | return hidServices; 77 | 78 | } 79 | 80 | } 81 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, sex characteristics, gender identity and expression, 9 | level of experience, education, socio-economic status, nationality, personal 10 | appearance, race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at g.rowe@froot.co.uk. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | 75 | For answers to common questions about this code of conduct, see 76 | https://www.contributor-covenant.org/faq 77 | -------------------------------------------------------------------------------- /src/main/java/org/hid4java/jna/HidDeviceInfoStructure.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2014-2015 Gary Rowe 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | * 24 | */ 25 | 26 | package org.hid4java.jna; 27 | 28 | import com.sun.jna.Structure; 29 | import com.sun.jna.WString; 30 | 31 | import java.util.Arrays; 32 | import java.util.List; 33 | 34 | /** 35 | * Value object to provide HID device information 36 | * @since 0.1.0 37 | */ 38 | public class HidDeviceInfoStructure extends Structure implements Structure.ByReference { 39 | 40 | /** 41 | * USB path 42 | */ 43 | public String path; 44 | 45 | /** 46 | * Vendor ID 47 | */ 48 | public short vendor_id; 49 | /** 50 | * Produce ID 51 | */ 52 | public short product_id; 53 | /** 54 | * Serial number 55 | */ 56 | public WString serial_number; 57 | 58 | /** 59 | * Release number 60 | */ 61 | public short release_number; 62 | /** 63 | * Manufacturer string 64 | */ 65 | public WString manufacturer_string; 66 | 67 | /** 68 | * Usage Page for this Device/Interface (Windows/Mac only) 69 | */ 70 | public WString product_string; 71 | /** 72 | * Usage for this Device/Interface (Windows/Mac only) 73 | */ 74 | public short usage_page; 75 | 76 | /** 77 | * Usage number 78 | */ 79 | public short usage; 80 | /** 81 | * Interface number 82 | */ 83 | public int interface_number; 84 | 85 | /** 86 | * Reference to next device 87 | */ 88 | // Consider public HidDeviceInfo.ByReference next; 89 | public HidDeviceInfoStructure next; 90 | 91 | public HidDeviceInfoStructure next() { 92 | return next; 93 | } 94 | 95 | public boolean hasNext() { 96 | return next != null; 97 | } 98 | 99 | @Override 100 | protected List getFieldOrder() { 101 | 102 | // If this precise order is not specified you get "SIGSEGV (0xb)" 103 | return Arrays.asList( 104 | "path", 105 | "vendor_id", 106 | "product_id", 107 | "serial_number", 108 | "release_number", 109 | "manufacturer_string", 110 | "product_string", 111 | "usage_page", 112 | "usage", 113 | "interface_number", 114 | "next" 115 | ); 116 | 117 | } 118 | 119 | /** 120 | * @return A string representation of the attached device 121 | */ 122 | public String show() { 123 | HidDeviceInfoStructure u = this; 124 | String str = "HidDevice\n"; 125 | str += "\tpath:" + u.path + ">\n"; 126 | str += "\tvendor_id: " + Integer.toHexString(u.vendor_id) + "\n"; 127 | str += "\tproduct_id: " + Integer.toHexString(u.product_id) + "\n"; 128 | str += "\tserial_number: " + u.serial_number + ">\n"; 129 | str += "\trelease_number: " + u.release_number + "\n"; 130 | str += "\tmanufacturer_string: " + u.manufacturer_string + ">\n"; 131 | str += "\tproduct_string: " + u.product_string + ">\n"; 132 | str += "\tusage_page: " + u.usage_page + "\n"; 133 | str += "\tusage: " + u.usage + "\n"; 134 | str += "\tinterface_number: " + u.interface_number + "\n"; 135 | return str; 136 | } 137 | } 138 | 139 | -------------------------------------------------------------------------------- /src/main/java/org/hid4java/HidServicesSpecification.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2014-2016 Gary Rowe 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | * 24 | */ 25 | 26 | package org.hid4java; 27 | 28 | /** 29 | * Specification to provide the following to API consumers: 30 | *
    31 | *
  • Flexible configuration of HID services parameters
  • 32 | *
33 | * 34 | * @since 0.5.0 35 | */ 36 | public class HidServicesSpecification { 37 | 38 | private ScanMode scanMode = ScanMode.SCAN_AT_FIXED_INTERVAL; 39 | private boolean autoShutdown = true; 40 | private int scanInterval = 500; 41 | private int pauseInterval = 5000; 42 | private boolean autoStart = true; 43 | private boolean autoDataRead = false; 44 | private int dataReadInterval = 500; 45 | 46 | public ScanMode getScanMode() { 47 | return scanMode; 48 | } 49 | 50 | /** 51 | * @param scanMode The scan mode to use to facilitate attach/detach events 52 | */ 53 | public void setScanMode(ScanMode scanMode) { 54 | this.scanMode = scanMode; 55 | } 56 | 57 | public int getScanInterval() { 58 | return scanInterval; 59 | } 60 | 61 | /** 62 | * @param scanInterval The interval in milliseconds between device enumeration scans 63 | */ 64 | public void setScanInterval(int scanInterval) { 65 | // Verify parameters 66 | if (scanInterval < 0) { 67 | throw new IllegalArgumentException("'scanInterval' must be greater than or equal to zero."); 68 | } 69 | this.scanInterval = scanInterval; 70 | } 71 | 72 | public int getPauseInterval() { 73 | return pauseInterval; 74 | } 75 | 76 | /** 77 | * @param pauseInterval The interval in milliseconds where device enumeration is paused (if scan mode supports pausing) 78 | * 79 | */ 80 | public void setPauseInterval(int pauseInterval) { 81 | if (pauseInterval < 0) { 82 | throw new IllegalArgumentException("'pauseInterval' must be greater than or equal to zero."); 83 | } 84 | this.pauseInterval = pauseInterval; 85 | } 86 | 87 | public boolean isAutoShutdown() { 88 | 89 | return autoShutdown; 90 | } 91 | 92 | /** 93 | * @param autoShutdown True if a shutdown hook should be set to close the API automatically (recommended) 94 | * 95 | */ 96 | public void setAutoShutdown(boolean autoShutdown) { 97 | this.autoShutdown = autoShutdown; 98 | } 99 | 100 | 101 | public boolean isAutoStart() { 102 | return autoStart; 103 | } 104 | 105 | /** 106 | * @param autoStart True if the HidServices should start before any listeners are registered (default is backwards compatible to 0.6.0 and below) 107 | */ 108 | public void setAutoStart(boolean autoStart) { 109 | this.autoStart = autoStart; 110 | } 111 | 112 | public boolean isAutoDataRead() { 113 | return autoDataRead; 114 | } 115 | 116 | /** 117 | * @param autoDataRead True if device input buffer should be automatically checked and read 118 | * @since 0.8.0 119 | */ 120 | public void setAutoDataRead(boolean autoDataRead) { 121 | this.autoDataRead = autoDataRead; 122 | } 123 | 124 | public int getDataReadInterval() { 125 | return dataReadInterval; 126 | } 127 | 128 | /** 129 | * 130 | * @param dataReadInterval The interval in milliseconds between attempts to read device input buffer 131 | */ 132 | public void setDataReadInterval(int dataReadInterval) { 133 | this.dataReadInterval = dataReadInterval; 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /src/test/java/org/hid4java/examples/BaseExample.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2014-2020 Gary Rowe 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | * 24 | */ 25 | 26 | package org.hid4java.examples; 27 | 28 | import com.sun.jna.Platform; 29 | import org.hid4java.HidServices; 30 | import org.hid4java.HidServicesListener; 31 | import org.hid4java.event.HidServicesEvent; 32 | 33 | import java.util.concurrent.TimeUnit; 34 | 35 | import static java.util.concurrent.TimeUnit.NANOSECONDS; 36 | 37 | public abstract class BaseExample implements HidServicesListener { 38 | 39 | public static final String ANSI_RESET = "\u001B[0m"; 40 | public static final String ANSI_BLACK = "\u001B[30m"; 41 | public static final String ANSI_RED = "\u001B[31m"; 42 | public static final String ANSI_GREEN = "\u001B[32m"; 43 | public static final String ANSI_YELLOW = "\u001B[33m"; 44 | public static final String ANSI_BLUE = "\u001B[34m"; 45 | public static final String ANSI_PURPLE = "\u001B[35m"; 46 | public static final String ANSI_CYAN = "\u001B[36m"; 47 | public static final String ANSI_WHITE = "\u001B[37m"; 48 | 49 | public void printPlatform() { 50 | 51 | // System info to assist with library detection 52 | System.out.println("Platform architecture: " + Platform.ARCH); 53 | System.out.println("Resource prefix: " + Platform.RESOURCE_PREFIX); 54 | System.out.println("Libusb activation: " + Platform.isLinux()); 55 | 56 | } 57 | 58 | public static void printAsHex(byte[] dataReceived) { 59 | System.out.printf("< [%02x]:", dataReceived.length); 60 | for (byte b : dataReceived) { 61 | System.out.printf(" %02x", b); 62 | } 63 | System.out.println(ANSI_RESET); 64 | } 65 | 66 | public void waitAndShutdown(HidServices hidServices) { 67 | 68 | System.out.printf(ANSI_YELLOW + "Waiting 30s to demonstrate attach/detach handling. Watch for slow response after write if configured.%n" + ANSI_RESET); 69 | 70 | // Stop the main thread to demonstrate attach and detach events 71 | sleepNoInterruption(); 72 | 73 | // Shut down and rely on auto-shutdown hook to clear HidApi resources 74 | System.out.printf(ANSI_YELLOW + "Triggering shutdown...%n" + ANSI_RESET); 75 | hidServices.shutdown(); 76 | } 77 | 78 | /** 79 | * Invokes {@code unit.}{@link TimeUnit#sleep(long) sleep(sleepFor)} 80 | * uninterruptibly. 81 | */ 82 | public static void sleepNoInterruption() { 83 | boolean interrupted = false; 84 | try { 85 | long remainingNanos = TimeUnit.SECONDS.toNanos(30); 86 | long end = System.nanoTime() + remainingNanos; 87 | while (true) { 88 | try { 89 | // TimeUnit.sleep() treats negative timeouts just like zero. 90 | NANOSECONDS.sleep(remainingNanos); 91 | return; 92 | } catch (InterruptedException e) { 93 | interrupted = true; 94 | remainingNanos = end - System.nanoTime(); 95 | } 96 | } 97 | } finally { 98 | if (interrupted) { 99 | Thread.currentThread().interrupt(); 100 | } 101 | } 102 | } 103 | 104 | @Override 105 | public void hidDeviceAttached(HidServicesEvent event) { 106 | 107 | System.out.println(ANSI_BLUE + "Device attached: " + event + ANSI_RESET); 108 | 109 | } 110 | 111 | @Override 112 | public void hidDeviceDetached(HidServicesEvent event) { 113 | 114 | System.out.println(ANSI_YELLOW + "Device detached: " + event + ANSI_RESET); 115 | 116 | } 117 | 118 | @Override 119 | public void hidFailure(HidServicesEvent event) { 120 | 121 | System.out.println(ANSI_RED + "HID failure: " + event + ANSI_RESET); 122 | 123 | } 124 | 125 | @Override 126 | public void hidDataReceived(HidServicesEvent event) { 127 | 128 | System.out.printf(ANSI_PURPLE + "Data received:%n"); 129 | byte[] dataReceived = event.getDataReceived(); 130 | 131 | printAsHex(dataReceived); 132 | 133 | } 134 | 135 | } 136 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Project status 2 | 3 | [![Maven Central](https://maven-badges.herokuapp.com/maven-central/org.hid4java/hid4java/badge.svg)](https://maven-badges.herokuapp.com/maven-central/org.hid4java/hid4java) [![Javadoc](https://javadoc.io/badge2/org.hid4java/hid4java/javadoc.svg)](https://javadoc.io/doc/org.hid4java/hid4java) 4 | 5 | # 🌟 Summary 6 | 7 | The `hid4java` project supports USB HID devices through a common API which is provided here under the MIT license. The API is very simple but provides great flexibility such as support for feature reports and blocking reads with timeouts. Attach/detach events are provided to allow applications to respond instantly to device availability. 8 | 9 | ## Telegram group 10 | 11 | If you want to discuss `hid4java` in general please use [the Telegram chat](https://t.me/joinchat/CtU4ZBltWCAFBAjwM5KLLw). I can't guarantee 12 | an instant response but I'm usually active on Telegram during office hours in the GMT timezone. 13 | 14 | 👀 Remember to [check the Wiki first](https://github.com/gary-rowe/hid4java/wiki/Home) before asking questions to avoid causing frustration! 15 | 16 | ## Technologies 17 | 18 | * [hidapi](https://github.com/libusb/hidapi) - Native USB HID library for multiple platforms 19 | * [JNA](https://github.com/twall/jna) - Removes the need for Java Native Interface (JNI) and greatly simplify the project 20 | * [dockcross](https://github.com/dockcross/dockcross) - Cross-compilation environments for multiple platforms to create hidapi libraries 21 | * [Maven](https://maven.apache.org/) - Build environment for Java projects if you need to customise the library 22 | * Java 8+ - to remove dependencies on JVMs that have reached end of life 23 | 24 | ## Maven dependency 25 | 26 | ```xml 27 | 28 | 29 | 30 | 31 | 32 | org.hid4java 33 | hid4java 34 | 0.8.0 35 | 36 | 37 | 38 | 39 | ``` 40 | If you are developing and want the latest code, you'll need to reference release candidate artifacts deployed to the Maven Central snapshot repository. These are updated more frequently as part of wider user acceptance testing, but they are not considered stable enough for production use. 41 | 42 | Add the following `repositories` section to your project's `pom.xml` if you're using Maven for your build process. 43 | 44 | ```xml 45 | 46 | 47 | 48 | oss.sonatype.org-snapshot 49 | https://oss.sonatype.org/content/repositories/snapshots 50 | 51 | false 52 | 53 | 54 | true 55 | 56 | 57 | 58 | ``` 59 | 60 | ## Gradle dependency 61 | 62 | ```gradle 63 | 64 | repositories { 65 | mavenCentral() 66 | } 67 | 68 | dependencies { 69 | implementation('org.hid4java:hid4java') 70 | } 71 | 72 | ``` 73 | 74 | ## 🚀 Code example 75 | 76 | Taken from [UsbHidEnumerationExample](https://github.com/gary-rowe/hid4java/blob/develop/src/test/java/org/hid4java/examples/UsbHidEnumerationExample.java) which 77 | provides more details. 78 | 79 | ```java 80 | // Configure to use custom specification 81 | HidServicesSpecification hidServicesSpecification = new HidServicesSpecification(); 82 | 83 | // Use the v0.7.0 manual start feature to get immediate attach events 84 | hidServicesSpecification.setAutoStart(false); 85 | 86 | // Get HID services using custom specification 87 | HidServices hidServices = HidManager.getHidServices(hidServicesSpecification); 88 | hidServices.addHidServicesListener(this); 89 | 90 | // Manually start the services to get attachment event 91 | hidServices.start(); 92 | 93 | // Provide a list of attached devices 94 | for (HidDevice hidDevice : hidServices.getAttachedHidDevices()) { 95 | System.out.println(hidDevice); 96 | } 97 | 98 | ``` 99 | 100 | # ⚙ Local build 101 | 102 | If you're unfamiliar with Maven and git the wiki provides [an easy guide to creating a development environment](https://github.com/gary-rowe/hid4java/wiki/How-to-set-up-a-build-environment-from-scratch). 103 | 104 | The project uses the standard Maven build process and can be used without having external hardware attached. Just do the usual 105 | 106 | ```shell 107 | cd 108 | git clone https://github.com/gary-rowe/hid4java.git 109 | cd hid4java 110 | 111 | # Maven build 112 | mvn clean install 113 | ``` 114 | 115 | and you're good to go. 116 | 117 | Maven will place the built JAR into the `target` directory. 118 | The Maven `install` process will also place copies of the built JARs into `~/.m2/repository/org/hid4java/hid4java//` so that other local projects can find and share them. 119 | 120 | # 🤔 More information 121 | 122 | Much of the information previously in this README has been migrated to the [project Wiki](https://github.com/gary-rowe/hid4java/wiki/Home) as it was getting rather long. Here are some useful jumping off points that should help: 123 | 124 | * [Home](https://github.com/gary-rowe/hid4java/wiki/Home) - The wiki Home page with lots of useful launch points 125 | * [FAQ](https://github.com/gary-rowe/hid4java/wiki/FAQ) - Frequently asked questions 126 | * [Examples](https://github.com/gary-rowe/hid4java/wiki/Examples) - Using the examples to kickstart your own project 127 | * [Troubleshooting](https://github.com/gary-rowe/hid4java/wiki/Troubleshooting) - A comprehensive troubleshooting guide 128 | 129 | # 📕 Closing notes 130 | 131 | All trademarks and copyrights are acknowledged. 132 | 133 | Many thanks to `victorix` who provided the basis for this library. Please [see the inspiration on the mbed site](https://os.mbed.com/cookbook/USBHID-bindings-). 134 | 135 | Thanks also go to everyone who has contributed their knowledge and advice during the creation and subsequent improvement of this library. 136 | -------------------------------------------------------------------------------- /release.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Work out the appropriate service name from the repo directory 4 | PROJECT_NAME=$(basename `pwd`) 5 | 6 | # Work out the local branch for version tagging 7 | if BRANCH=$(git symbolic-ref --short -q HEAD) 8 | then 9 | echo -e "Releasing \033[32m${PROJECT_NAME}-${BRANCH}\033[0m" 10 | else 11 | echo -e "\033[31mFailed\033[0m - not on any branch.\n" 12 | exit -1 13 | fi 14 | 15 | # Check local branch for staged files 16 | if git diff-index --quiet HEAD --; then 17 | echo -e "\033[32mOK\033[0m - No staged files awaiting commit." 18 | else 19 | echo -e "\033[31mFailed\033[0m - Your issue branch contains files that are staged and not committed.\n" 20 | exit -1 21 | fi 22 | 23 | # Check local branch relative to remote 24 | UPSTREAM=${1:-'@{u}'} 25 | LOCAL=$(git rev-parse "@{0}") 26 | REMOTE=$(git rev-parse "$UPSTREAM") 27 | BASE=$(git merge-base "@{0}" "$UPSTREAM") 28 | 29 | if [ ${LOCAL} = ${REMOTE} ]; then 30 | echo -e "\033[32mOK\033[0m - Your branch can be released." 31 | elif [ ${LOCAL} = ${BASE} ]; then 32 | echo -e "\033[31mFailed\033[0m - You need to pull from upstream.\n" 33 | exit -1 34 | elif [ ${REMOTE} = ${BASE} ]; then 35 | echo -e "\033[31mFailed\033[0m - You need to push to upstream.\n" 36 | exit -1 37 | else 38 | echo -e "\033[31mFailed\033[0m - Your local branch has diverged from the remote.\n" 39 | exit -1 40 | fi 41 | 42 | BEHIND_MASTER=$(git rev-list --left-right --count master...@) 43 | BEHIND_DEVELOP=$(git rev-list --left-right --count develop...@) 44 | 45 | # Check issue branch rules 46 | if [[ "$BRANCH" =~ ^issue.* ]]; then 47 | echo -e "\nApplying release rules for 'issue' to 'develop'." 48 | 49 | if [[ "$BEHIND_DEVELOP" =~ ^0 ]]; then 50 | echo -e "\033[32mOK\033[0m - This branch is ahead of 'develop'." 51 | else 52 | echo -e "\033[31mFailed\033[0m - Your issue branch is behind develop. You should rebase and resolve conflicts.\n" 53 | exit -1 54 | fi 55 | 56 | if grep -s -q ":issue-" "./pom.xml"; then 57 | echo -e "\033[31mFailed\033[0m - The pom.xml contains ':issue-' implying an issue dependency which is not allowed in the develop branch.\n" 58 | exit -1 59 | else 60 | echo -e "\033[32mOK\033[0m - No 'issue' dependencies in './pom.xml'" 61 | fi 62 | 63 | # Force user to be sure about releasing 64 | read -p "Please enter the project name to confirm the release ($PROJECT_NAME): " userInput 65 | if [[ "$userInput" == "$PROJECT_NAME" ]]; then 66 | echo -e "\033[32mOK\033[0m - Releasing to '$userInput'" 67 | git checkout develop 68 | git merge ${BRANCH} 69 | git push 70 | else 71 | echo -e "\033[31mFailed\033[0m - Project name did not match. Aborting with no changes.\n" 72 | exit -1 73 | fi 74 | fi 75 | 76 | # Check develop branch rules 77 | if [[ "$BRANCH" =~ ^develop ]]; then 78 | echo -e "\nApplying release rules for 'develop' to a release branch." 79 | 80 | if [[ "$BEHIND_MASTER" =~ ^0 ]]; then 81 | echo -e "\033[32mOK\033[0m - This branch is ahead of 'master'." 82 | else 83 | echo -e "\033[31mFailed\033[0m - Your release branch is behind 'master'. You should rebase and resolve conflicts.\n" 84 | exit -1 85 | fi 86 | 87 | if grep -s ":.*\-SNAPSHOT" "./pom.xml"; then 88 | echo -e "\033[31mFailed\033[0m - The pom.xml contains '-SNAPSHOT' implying a SNAPSHOT dependency which is not allowed in a release branch.\n" 89 | exit -1 90 | else 91 | echo -e "\033[32mOK\033[0m - No SNAPSHOT dependencies in './pom.xml'" 92 | fi 93 | 94 | echo -e "Most recent tagged version:\n$(git describe --abbrev=0 --tags)" 95 | 96 | # Force user to be sure about releasing 97 | read -p "Please enter the agreed version number (e.g. 1.2.3) to confirm the release: " userInput 98 | echo -e "\033[32mOK\033[0m - Releasing to '$userInput'" 99 | git branch ${userInput} 100 | git checkout ${userInput} 101 | git merge develop 102 | git push --set-upstream origin ${userInput} 103 | 104 | fi 105 | 106 | # Check release branch rules (0.0.0 format) 107 | if [[ "$BRANCH" =~ ^[0-9] ]]; then 108 | echo -e "\nApplying release rules for release branch to master and develop." 109 | 110 | if [[ "$BEHIND_MASTER" =~ ^0 ]]; then 111 | echo -e "\033[32mOK\033[0m - This branch is ahead of 'master'." 112 | else 113 | echo -e "\033[31mFailed\033[0m - Your release branch is behind master. You should rebase and resolve conflicts.\n" 114 | exit -1 115 | fi 116 | 117 | if grep -s ":.*\-SNAPSHOT" "./pom.xml"; then 118 | echo -e "\033[31mFailed\033[0m - The pom.xml contains '-SNAPSHOT' implying a SNAPSHOT dependency which is not allowed in a release branch.\n" 119 | exit -1 120 | else 121 | echo -e "\033[32mOK\033[0m - No SNAPSHOT dependencies in './build.gradle'" 122 | fi 123 | 124 | # Force user to be sure about releasing 125 | read -p "Please enter the project name to confirm the release ($PROJECT_NAME): " userInput 126 | if [[ "$userInput" == "$PROJECT_NAME" ]]; then 127 | echo -e "\033[32mOK\033[0m - Releasing to '$userInput' on 'master'" 128 | git checkout master 129 | git merge ${BRANCH} 130 | # Use prefix to avoid branch naming problems when rebuilding release branches 131 | git tag -a version-${BRANCH} -m "version-${BRANCH}" 132 | git push --follow-tags 133 | fi 134 | 135 | # Check if merge to develop to retrofit fixes can be applied safely 136 | if [[ "$BEHIND_DEVELOP" =~ ^0 ]]; then 137 | echo -e "\033[32mOK\033[0m - This branch is ahead of 'develop'." 138 | echo -e "\033[32mOK\033[0m - Merging ${BRANCH} into 'develop'" 139 | git checkout develop 140 | git merge ${BRANCH} 141 | git push 142 | else 143 | echo -e "\033[33mWarning\033[0m - Your release branch is behind 'develop'. You should resolve conflicts.\n" 144 | git checkout develop 145 | git merge ${BRANCH} 146 | git push 147 | fi 148 | 149 | fi 150 | 151 | echo -e "Done." -------------------------------------------------------------------------------- /src/main/java/org/hid4java/event/HidServicesListenerList.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2014-2015 Gary Rowe 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | * 24 | */ 25 | 26 | package org.hid4java.event; 27 | 28 | import org.hid4java.HidDevice; 29 | import org.hid4java.HidServicesListener; 30 | 31 | import java.util.ArrayList; 32 | import java.util.Collections; 33 | import java.util.List; 34 | import java.util.concurrent.ExecutorService; 35 | import java.util.concurrent.Executors; 36 | import java.util.concurrent.ThreadFactory; 37 | 38 | /** 39 | * HID services listener list 40 | * 41 | * @since 0.0.1 42 | */ 43 | public class HidServicesListenerList { 44 | 45 | /** 46 | * The list with registered listeners 47 | */ 48 | private final List listeners = Collections.synchronizedList(new ArrayList()); 49 | 50 | private final ExecutorService executorService = Executors.newFixedThreadPool( 51 | 3, new ThreadFactory() { 52 | @Override 53 | public Thread newThread(Runnable runnable) { 54 | Thread thread = Executors.defaultThreadFactory().newThread(runnable); 55 | thread.setName("hid4java event worker"); 56 | thread.setDaemon(true); 57 | return thread; 58 | } 59 | }); 60 | 61 | /** 62 | * @param listener The listener to add 63 | */ 64 | public final void add(final HidServicesListener listener) { 65 | if (this.listeners.contains(listener)) { 66 | return; 67 | } 68 | this.listeners.add(listener); 69 | } 70 | 71 | /** 72 | * @param listener The listener to remove 73 | */ 74 | public final void remove(final HidServicesListener listener) { 75 | this.listeners.remove(listener); 76 | } 77 | 78 | /** 79 | * Removes all listeners 80 | */ 81 | public final void clear() { 82 | this.listeners.clear(); 83 | } 84 | 85 | /** 86 | * @return The listeners list 87 | */ 88 | protected final List getListeners() { 89 | return this.listeners; 90 | } 91 | 92 | /** 93 | * Returns an array with the currently registered listeners. 94 | * The returned array is detached from the internal list of registered listeners. 95 | * 96 | * @return Array with registered listeners. 97 | */ 98 | public HidServicesListener[] toArray() { 99 | return getListeners().toArray(new HidServicesListener[0]); 100 | } 101 | 102 | /** 103 | * Fire the HID device attached event 104 | * 105 | * @param hidDevice The device that was attached 106 | */ 107 | public void fireHidDeviceAttached(final HidDevice hidDevice) { 108 | 109 | // Broadcast on a different thread 110 | executorService.submit( 111 | new Runnable() { 112 | @Override 113 | public void run() { 114 | 115 | HidServicesEvent event = new HidServicesEvent(hidDevice); 116 | 117 | for (final HidServicesListener listener : toArray()) { 118 | listener.hidDeviceAttached(event); 119 | } 120 | 121 | } 122 | }); 123 | 124 | } 125 | 126 | /** 127 | * Fire the HID device detached event 128 | * 129 | * @param hidDevice The device that was detached 130 | */ 131 | public void fireHidDeviceDetached(final HidDevice hidDevice) { 132 | 133 | // Broadcast on a different thread 134 | executorService.submit( 135 | new Runnable() { 136 | @Override 137 | public void run() { 138 | 139 | HidServicesEvent event = new HidServicesEvent(hidDevice); 140 | 141 | for (final HidServicesListener listener : toArray()) { 142 | listener.hidDeviceDetached(event); 143 | } 144 | 145 | } 146 | }); 147 | 148 | } 149 | 150 | /** 151 | * Fire the HID failure event 152 | * 153 | * @param hidDevice The device that caused the error if known 154 | */ 155 | public void fireHidFailure(final HidDevice hidDevice) { 156 | 157 | // Broadcast on a different thread 158 | executorService.submit( 159 | new Runnable() { 160 | @Override 161 | public void run() { 162 | 163 | HidServicesEvent event = new HidServicesEvent(hidDevice); 164 | 165 | for (final HidServicesListener listener : toArray()) { 166 | listener.hidFailure(event); 167 | } 168 | 169 | } 170 | }); 171 | 172 | } 173 | 174 | /** 175 | * Fire the HID data received event 176 | * 177 | * @param hidDevice The device that triggered the data input 178 | * @param dataReceived The buffer with the data received 179 | */ 180 | public void fireHidDataReceived(final HidDevice hidDevice, final byte[] dataReceived) { 181 | 182 | // Broadcast on a different thread 183 | executorService.submit( 184 | new Runnable() { 185 | @Override 186 | public void run() { 187 | 188 | HidServicesEvent event = new HidServicesEvent(hidDevice, dataReceived); 189 | 190 | for (final HidServicesListener listener : toArray()) { 191 | listener.hidDataReceived(event); 192 | } 193 | 194 | } 195 | }); 196 | 197 | } 198 | 199 | } 200 | 201 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | org.hid4java 6 | hid4java 7 | develop-SNAPSHOT 8 | 9 | hid4java 10 | A cross-platform Java Native Access (JNA) wrapper for the libusb/hidapi library 11 | 12 | https://github.com/gary-rowe/hid4java 13 | 2014 14 | 15 | 16 | 17 | Gary Rowe 18 | g.rowe@froot.co.uk 19 | 20 | 21 | 22 | 23 | 24 | MIT 25 | https://www.opensource.org/licenses/mit-license.php 26 | repo 27 | All source code is under the MIT license. 28 | 29 | 30 | 31 | 32 | GitHub 33 | https://github.com/gary-rowe/hid4java/issues 34 | 35 | 36 | 37 | 38 | sonatype-nexus-snapshots 39 | Sonatype Nexus Snapshots 40 | https://oss.sonatype.org/content/repositories/snapshots 41 | 42 | 43 | sonatype-nexus-staging 44 | Nexus Release Repository 45 | https://oss.sonatype.org/service/local/staging/deploy/maven2/ 46 | 47 | https://oss.sonatype.org/content/groups/public/org/hid4java 48 | 49 | 50 | 51 | scm:git:git@github.com:gary-rowe/hid4java.git 52 | scm:git:git@github.com:gary-rowe/hid4java.git 53 | git@github.com:gary-rowe/hid4java.git 54 | HEAD 55 | 56 | 57 | 58 | 59 | UTF-8 60 | 61 | 1.8 62 | 63 | 1.8 64 | 65 | 66 | 67 | 68 | 69 | 70 | release-sign-artifacts 71 | 72 | 73 | 74 | org.apache.maven.plugins 75 | maven-gpg-plugin 76 | 3.2.3 77 | 78 | 79 | sign-artifacts 80 | verify 81 | 82 | sign 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | org.apache.maven.plugins 99 | maven-surefire-plugin 100 | 3.1.2 101 | 102 | 103 | 104 | 105 | org.apache.maven.plugins 106 | maven-source-plugin 107 | 3.3.0 108 | 109 | 110 | attach-sources 111 | 112 | jar 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | org.apache.maven.plugins 121 | maven-javadoc-plugin 122 | 3.6.3 123 | 124 | 8 125 | 126 | 127 | 128 | attach-javadocs 129 | 130 | jar 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | org.apache.maven.plugins 139 | maven-release-plugin 140 | 3.0.1 141 | 142 | true 143 | forked-path 144 | release-sign-artifacts 145 | 146 | 147 | 148 | 149 | 150 | org.apache.maven.plugins 151 | maven-jar-plugin 152 | 3.3.0 153 | 154 | 155 | 156 | true 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | net.java.dev.jna 169 | jna 170 | 171 | 172 | 173 | [5.8.0,) 174 | 175 | 176 | 177 | 178 | org.junit.jupiter 179 | junit-jupiter 180 | 5.10.2 181 | test 182 | 183 | 184 | 185 | 186 | 187 | -------------------------------------------------------------------------------- /src/main/java/org/hid4java/HidServices.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2014-2015 Gary Rowe 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | * 24 | */ 25 | 26 | package org.hid4java; 27 | 28 | import org.hid4java.event.HidServicesListenerList; 29 | import org.hid4java.jna.HidApi; 30 | 31 | import java.io.IOException; 32 | import java.net.URL; 33 | import java.util.List; 34 | import java.util.jar.Attributes; 35 | import java.util.jar.Manifest; 36 | 37 | /** 38 | * JNA bridge class to provide the following to USB HID: 39 | *
    40 | *
  • Access to the signal11/hidapi via JNA
  • 41 | *
42 | * Requires the hidapi to be present on the classpath or the system library search path. 43 | * 44 | * @since 0.0.1 45 | */ 46 | public class HidServices { 47 | 48 | /** 49 | * The HID services listeners for receiving attach/detach events etc 50 | */ 51 | private final HidServicesListenerList listeners = new HidServicesListenerList(); 52 | 53 | /** 54 | * The HID device manager handles scanning operations 55 | */ 56 | private final HidDeviceManager hidDeviceManager; 57 | 58 | /** 59 | * Jar entry point to allow for version interrogation 60 | * 61 | * @param args Nothing required 62 | */ 63 | public static void main(String[] args) { 64 | System.out.println("Version: " + getVersion()); 65 | } 66 | 67 | /** 68 | * Initialise with a default HID specification 69 | * 70 | * @throws HidException If something goes wrong (see {@link HidDeviceManager#HidDeviceManager(HidServicesListenerList, HidServicesSpecification)} 71 | */ 72 | public HidServices() throws HidException { 73 | this(new HidServicesSpecification()); 74 | } 75 | 76 | /** 77 | * @param hidServicesSpecification Provides various parameters for configuring HID services 78 | * @throws HidException If something goes wrong (see {@link HidDeviceManager#HidDeviceManager(HidServicesListenerList, HidServicesSpecification)} 79 | */ 80 | public HidServices(HidServicesSpecification hidServicesSpecification) { 81 | hidDeviceManager = new HidDeviceManager(listeners, hidServicesSpecification); 82 | 83 | // Check for automatic start (default behaviour for 0.6.0 and below) 84 | // which will prevent an attachment event firing if the device is already 85 | // attached since listeners will not have been registered at this point 86 | if (hidServicesSpecification.isAutoStart()) { 87 | hidDeviceManager.start(); 88 | } 89 | 90 | if (hidServicesSpecification.isAutoShutdown()) { 91 | // Ensure we release resources during shutdown 92 | Runtime.getRuntime().addShutdownHook( 93 | new Thread() { 94 | @Override 95 | public void run() { 96 | shutdown(); 97 | } 98 | }); 99 | } 100 | 101 | } 102 | 103 | /** 104 | * Stop all device threads and shut down the {@link HidApi} 105 | */ 106 | public void shutdown() { 107 | stop(); 108 | try { 109 | HidApi.exit(); 110 | } catch (Throwable e) { 111 | // Silently fail (user will already have been given an exception) 112 | } 113 | } 114 | 115 | /** 116 | * Stop all threads (enumeration, data read etc), close all devices 117 | * and clear all listeners 118 | * 119 | * Normally part of an application shutdown 120 | */ 121 | public void stop() { 122 | hidDeviceManager.stop(); 123 | this.listeners.clear(); 124 | } 125 | 126 | /** 127 | * Start all threads (enumeration, data read etc) as configured 128 | */ 129 | public void start() { 130 | hidDeviceManager.start(); 131 | } 132 | 133 | /** 134 | * @param listener The listener to add 135 | */ 136 | public void addHidServicesListener(final HidServicesListener listener) { 137 | this.listeners.add(listener); 138 | } 139 | 140 | /** 141 | * @param listener The listener to remove 142 | */ 143 | public void removeHidServicesListener(final HidServicesListener listener) { 144 | this.listeners.remove(listener); 145 | } 146 | 147 | /** 148 | * Manually scans for HID device connection changes and triggers listener events as required 149 | */ 150 | public void scan() { 151 | this.hidDeviceManager.scan(); 152 | } 153 | 154 | /** 155 | * @return A list of all attached HID devices 156 | */ 157 | public List getAttachedHidDevices() { 158 | return hidDeviceManager.getAttachedHidDevices(); 159 | } 160 | 161 | /** 162 | * @param vendorId The vendor ID 163 | * @param productId The product ID 164 | * @param serialNumber The serial number (use null for wildcard) 165 | * @return The device if attached, null if detached 166 | */ 167 | public HidDevice getHidDevice(int vendorId, int productId, String serialNumber) { 168 | 169 | List devices = hidDeviceManager.getAttachedHidDevices(); 170 | for (HidDevice device : devices) { 171 | if (device.isVidPidSerial(vendorId, productId, serialNumber)) { 172 | device.open(); 173 | return device; 174 | } 175 | } 176 | 177 | return null; 178 | } 179 | 180 | /** 181 | * @return The current library version from the manifest or 0.0.x if an error occurs 182 | */ 183 | public static String getVersion() { 184 | 185 | Class clazz = HidServices.class; 186 | String className = clazz.getSimpleName() + ".class"; 187 | String classPath = clazz.getResource(className).toString(); 188 | if (!classPath.startsWith("jar")) { 189 | // Class not from JAR 190 | return "0.0.1"; 191 | } 192 | String manifestPath = classPath.substring(0, classPath.lastIndexOf("!") + 1) + 193 | "/META-INF/MANIFEST.MF"; 194 | Manifest manifest; 195 | try { 196 | manifest = new Manifest(new URL(manifestPath).openStream()); 197 | } catch (IOException e) { 198 | return "0.0.2"; 199 | } 200 | Attributes attr = manifest.getMainAttributes(); 201 | String value = attr.getValue("Implementation-Version"); 202 | if (null == value) { 203 | return "0.0.3"; 204 | } else { 205 | return value; 206 | } 207 | } 208 | 209 | /** 210 | * Returns the full version of the underlying hidapi library 211 | * 212 | * @return The version in major.minor.patch format 213 | */ 214 | public static String getNativeVersion() { 215 | return HidApi.getVersion(); 216 | } 217 | } 218 | -------------------------------------------------------------------------------- /src/main/java/org/hid4java/HidDeviceManager.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2014-2015 Gary Rowe 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | * 24 | */ 25 | 26 | package org.hid4java; 27 | 28 | import org.hid4java.event.HidServicesListenerList; 29 | import org.hid4java.jna.HidApi; 30 | import org.hid4java.jna.HidDeviceInfoStructure; 31 | 32 | import java.util.*; 33 | 34 | /** 35 | * Manager to provide the following to HID services: 36 | *
    37 | *
  • Access to the underlying JNA and hidapi library
  • 38 | *
  • Device attach/detach detection (if configured)
  • 39 | *
  • Device data read (if configured)
  • 40 | *
41 | * 42 | * @since 0.0.1 43 | */ 44 | class HidDeviceManager { 45 | 46 | /** 47 | * The HID services specification providing configuration parameters 48 | */ 49 | private final HidServicesSpecification hidServicesSpecification; 50 | 51 | /** 52 | * The currently attached devices keyed on ID 53 | */ 54 | private final Map attachedDevices = Collections.synchronizedMap(new HashMap()); 55 | 56 | /** 57 | * HID services listener list 58 | */ 59 | private final HidServicesListenerList listenerList; 60 | 61 | /** 62 | * The device enumeration thread 63 | *
64 | * We use a Thread instead of Executor since it may be stopped/paused/restarted frequently 65 | * and executors are more heavyweight in this regard 66 | */ 67 | private Thread scanThread = null; 68 | 69 | /** 70 | * Constructs a new device manager 71 | * 72 | * @param listenerList The HID services providing access to the event model 73 | * @param hidServicesSpecification Provides various parameters for configuring HID services 74 | * 75 | * @throws HidException If USB HID initialization fails 76 | */ 77 | HidDeviceManager(HidServicesListenerList listenerList, HidServicesSpecification hidServicesSpecification) throws HidException { 78 | 79 | this.listenerList = listenerList; 80 | this.hidServicesSpecification = hidServicesSpecification; 81 | 82 | // Attempt to initialise and fail fast 83 | try { 84 | HidApi.init(); 85 | } catch (Throwable t) { 86 | // Typically this is a linking issue with the native library 87 | throw new HidException("Hidapi did not initialise: " + t.getMessage(), t); 88 | } 89 | 90 | } 91 | 92 | /** 93 | * Starts the manager 94 | *
95 | * If already started (scanning) it will immediately return without doing anything 96 | *
97 | * Otherwise, this will perform a one-off scan of all devices then if the scan interval 98 | * is zero will stop there or will start the scanning daemon thread at the required interval. 99 | * 100 | * @throws HidException If something goes wrong (such as Hidapi not initialising correctly) 101 | */ 102 | public void start() { 103 | 104 | // Check for previous start 105 | if (this.isScanning()) { 106 | return; 107 | } 108 | 109 | // Perform a one-off scan to populate attached devices 110 | scan(); 111 | 112 | // Ensure we have a scan thread available 113 | configureScanThread(getScanRunnable()); 114 | 115 | } 116 | 117 | /** 118 | * Stop the scan thread and close all attached devices 119 | *
120 | * This is normally part of a general application shutdown and will 121 | * also clear the attached devices map 122 | */ 123 | public synchronized void stop() { 124 | 125 | stopScanThread(); 126 | 127 | // Close all attached devices 128 | for (HidDevice hidDevice: attachedDevices.values()) { 129 | hidDevice.close(); 130 | } 131 | 132 | // Remove all entries from the attached devices 133 | attachedDevices.clear(); 134 | 135 | } 136 | 137 | /** 138 | * Updates the device list by adding newly connected devices to it and by 139 | * removing no longer connected devices. 140 | *
141 | * Will fire attach/detach events as appropriate. 142 | */ 143 | public synchronized void scan() { 144 | 145 | List removeList = new ArrayList<>(); 146 | 147 | List attachedHidDeviceList = getAttachedHidDevices(); 148 | 149 | for (HidDevice attachedDevice : attachedHidDeviceList) { 150 | 151 | if (!this.attachedDevices.containsKey(attachedDevice.getId())) { 152 | 153 | // Device has become attached so add it but do not open 154 | attachedDevices.put(attachedDevice.getId(), attachedDevice); 155 | 156 | // Fire the event on a separate thread 157 | listenerList.fireHidDeviceAttached(attachedDevice); 158 | 159 | } 160 | } 161 | 162 | for (Map.Entry entry : attachedDevices.entrySet()) { 163 | 164 | String deviceId = entry.getKey(); 165 | HidDevice hidDevice = entry.getValue(); 166 | 167 | if (!attachedHidDeviceList.contains(hidDevice)) { 168 | 169 | // Keep track of removals 170 | removeList.add(deviceId); 171 | 172 | // Fire the event on a separate thread 173 | listenerList.fireHidDeviceDetached(this.attachedDevices.get(deviceId)); 174 | 175 | } 176 | } 177 | 178 | if (!removeList.isEmpty()) { 179 | // Update the attached devices map 180 | this.attachedDevices.keySet().removeAll(removeList); 181 | } 182 | 183 | } 184 | 185 | /** 186 | * @return True if the scan thread is running, false otherwise. 187 | */ 188 | public boolean isScanning() { 189 | return scanThread != null && scanThread.isAlive(); 190 | } 191 | 192 | /** 193 | * @return A list of all attached HID devices 194 | */ 195 | public List getAttachedHidDevices() { 196 | 197 | List hidDeviceList = new ArrayList<>(); 198 | 199 | final HidDeviceInfoStructure root; 200 | try { 201 | // Use 0,0 to list all attached devices 202 | // This comes back as a linked list from hidapi 203 | root = HidApi.enumerateDevices(0, 0); 204 | } catch (Throwable e) { 205 | // Could not initialise hidapi (possibly an unknown platform) 206 | // Trigger a general stop as something serious has happened 207 | stop(); 208 | // Inform the caller that something serious has gone wrong 209 | throw new HidException("Unable to start HidApi: " + e.getMessage()); 210 | } 211 | 212 | if (root != null) { 213 | 214 | HidDeviceInfoStructure hidDeviceInfoStructure = root; 215 | do { 216 | // Wrap in HidDevice 217 | hidDeviceList.add(new HidDevice( 218 | hidDeviceInfoStructure, 219 | this, 220 | hidServicesSpecification)); 221 | // Move to the next in the linked list 222 | hidDeviceInfoStructure = hidDeviceInfoStructure.next(); 223 | } while (hidDeviceInfoStructure != null); 224 | 225 | // Dispose of the device list to free memory 226 | HidApi.freeEnumeration(root); 227 | } 228 | 229 | return hidDeviceList; 230 | } 231 | 232 | /** 233 | * Indicate that a device write has occurred which may require a change in scanning frequency 234 | */ 235 | public void afterDeviceWrite() { 236 | 237 | if (ScanMode.SCAN_AT_FIXED_INTERVAL_WITH_PAUSE_AFTER_WRITE == hidServicesSpecification.getScanMode() && isScanning()) { 238 | stopScanThread(); 239 | // Ensure we have a new scan executor service available 240 | configureScanThread(getScanRunnable()); 241 | 242 | } 243 | 244 | } 245 | 246 | /** 247 | * Indicate that an automatic data read has occurred which may require an event to be fired 248 | * 249 | * @param hidDevice The device that has received data 250 | * @param dataReceived The data received 251 | * @since 0.8.0 252 | */ 253 | public void afterDeviceDataRead(HidDevice hidDevice, byte[] dataReceived) { 254 | 255 | if (dataReceived != null && dataReceived.length > 0) { 256 | this.listenerList.fireHidDataReceived(hidDevice, dataReceived); 257 | } 258 | 259 | } 260 | 261 | /** 262 | * Stop the scan thread 263 | */ 264 | private synchronized void stopScanThread() { 265 | 266 | if (isScanning()) { 267 | scanThread.interrupt(); 268 | // Wait up to 50ms for scanThread to terminate to avoid 269 | // spurious return values from isScanning() 270 | // See hid4java issue #125 271 | try { 272 | scanThread.join(50); 273 | } catch (InterruptedException e) { 274 | // Ignore and continue 275 | } 276 | } 277 | 278 | } 279 | 280 | /** 281 | * Configures the scan thread to allow recovery from stop or pause 282 | */ 283 | private synchronized void configureScanThread(Runnable scanRunnable) { 284 | 285 | if (isScanning()) { 286 | stopScanThread(); 287 | } 288 | 289 | // Require a new one 290 | scanThread = new Thread(scanRunnable); 291 | scanThread.setDaemon(true); 292 | scanThread.setName("hid4java device scanner"); 293 | scanThread.start(); 294 | 295 | } 296 | 297 | private synchronized Runnable getScanRunnable() { 298 | 299 | final int scanInterval = hidServicesSpecification.getScanInterval(); 300 | final int pauseInterval = hidServicesSpecification.getPauseInterval(); 301 | 302 | switch (hidServicesSpecification.getScanMode()) { 303 | case NO_SCAN: 304 | return new Runnable() { 305 | @Override 306 | public void run() { 307 | // Do nothing 308 | } 309 | }; 310 | case SCAN_AT_FIXED_INTERVAL: 311 | return new Runnable() { 312 | @Override 313 | public void run() { 314 | 315 | while (true) { 316 | try { 317 | //noinspection BusyWait 318 | Thread.sleep(scanInterval); 319 | } catch (final InterruptedException e) { 320 | Thread.currentThread().interrupt(); 321 | break; 322 | } 323 | scan(); 324 | } 325 | } 326 | }; 327 | case SCAN_AT_FIXED_INTERVAL_WITH_PAUSE_AFTER_WRITE: 328 | return new Runnable() { 329 | @Override 330 | public void run() { 331 | // Provide an initial pause 332 | try { 333 | Thread.sleep(pauseInterval); 334 | } catch (final InterruptedException e) { 335 | Thread.currentThread().interrupt(); 336 | } 337 | 338 | // Switch to continuous running 339 | while (true) { 340 | try { 341 | //noinspection BusyWait 342 | Thread.sleep(scanInterval); 343 | } catch (final InterruptedException e) { 344 | Thread.currentThread().interrupt(); 345 | break; 346 | } 347 | scan(); 348 | } 349 | } 350 | }; 351 | default: 352 | return null; 353 | } 354 | 355 | 356 | } 357 | 358 | } 359 | -------------------------------------------------------------------------------- /src/test/java/org/hid4java/examples/Fido2AuthenticationExample.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2014-2020 Gary Rowe 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | * 24 | */ 25 | 26 | package org.hid4java.examples; 27 | 28 | import org.hid4java.*; 29 | import org.hid4java.event.HidServicesEvent; 30 | import org.hid4java.jna.HidApi; 31 | 32 | import java.security.SecureRandom; 33 | 34 | /** 35 | * Demonstrate the USB HID interface using a FIDO2 USB device 36 | *
37 | * If you have a FIDO2 U2F authentication device (e.g. a HyperFIDO, Yubikey, Solokey, Trezor or Ledger) 38 | * you may wish to explore its capabilities using this example. Simply plug it in and run the example to 39 | * see the initial handshake to select a channel and basic device information. 40 | *
41 | * You can see some of hid4java features in use such as: 42 | *
    43 | *
      44 | *
    • device enumeration and selection
    • 45 | *
    • automatic data read events (new for 0.8.0)
    • 46 | *
    • low level HID traffic to System.out to assist early stage debugging
    • 47 | *
    • manual start to enable attach/detach events
    • 48 | *
    49 | *
    50 | * Use the following command to try it out: 51 | *
    52 | * 53 | * mvn clean test exec:java -Dexec.classpathScope="test" -Dexec.mainClass="org.hid4java.examples.Fido2AuthenticationExample" 54 | * 55 | * 56 | * @since 0.8.0 57 | */ 58 | public class Fido2AuthenticationExample extends BaseExample { 59 | 60 | // The FIDO2 scaffolding code will get included in later versions of this example 61 | 62 | // Authentication data flags 63 | // private static byte CTAP_AUTHDATA_USER_PRESENT = 0x01; 64 | // private static byte CTAP_AUTHDATA_USER_VERIFIED = 0x04; 65 | // private static byte CTAP_AUTHDATA_ATT_CRED = 0x40; 66 | // private static byte CTAP_AUTHDATA_EXT_DATA = (byte) 0x80; 67 | // 68 | // // CTAPHID command opcodes 69 | // private static byte CTAP_CMD_PING = 0x01; 70 | // private static byte CTAP_CMD_MSG = 0x03; 71 | // private static byte CTAP_CMD_LOCK = 0x04; 72 | private static final byte CTAP_CMD_INIT = 0x06; 73 | // private static byte CTAP_CMD_WINK = 0x08; 74 | // private static byte CTAP_CMD_CBOR = 0x10; 75 | // private static byte CTAP_CMD_CANCEL = 0x11; 76 | // private static byte CTAP_KEEPALIVE = 0x3b; 77 | // private static byte CTAP_FRAME_INIT = (byte) 0x80; 78 | // 79 | // // CTAPHID CBOR command opcodes 80 | // private static byte CTAP_CBOR_MAKECRED = 0x01; 81 | // private static byte CTAP_CBOR_ASSERT = 0x02; 82 | // private static byte CTAP_CBOR_GETINFO = 0x04; 83 | // private static byte CTAP_CBOR_CLIENT_PIN = 0x06; 84 | // private static byte CTAP_CBOR_RESET = 0x07; 85 | // private static byte CTAP_CBOR_NEXT_ASSERT = 0x08; 86 | // private static byte CTAP_CBOR_BIO_ENROLL_PRE = 0x40; 87 | // private static byte CTAP_CBOR_CRED_MGMT_PRE = 0x41; 88 | // 89 | // // U2F command opcodes 90 | // private static byte U2F_CMD_REGISTER = 0x01; 91 | // private static byte U2F_CMD_AUTH = 0x02; 92 | // 93 | // // U2F command flags 94 | // private static byte U2F_AUTH_SIGN = 0x03; 95 | // private static byte U2F_AUTH_CHECK = 0x07; 96 | // 97 | // // ISO7816-4 status words 98 | // private static int SW_CONDITIONS_NOT_SATISFIED = 0x6985; 99 | // private static int SW_WRONG_DATA = 0x6a80; 100 | // private static int SW_NO_ERROR = 0x9000; 101 | // 102 | // // HID Broadcast channel ID 103 | // private static int CTAP_CID_BROADCAST = 0xffffffff; 104 | // 105 | // private static int CTAP_INIT_HEADER_LEN = 7; 106 | // private static int CTAP_CONT_HEADER_LEN = 5; 107 | // 108 | // // Maximum length of a CTAP HID report in bytes 109 | private static final int CTAP_MAX_REPORT_LEN = 64; 110 | // 111 | // // Minimum length of a CTAP HID report in bytes 112 | // private static int CTAP_MIN_REPORT_LEN = CTAP_INIT_HEADER_LEN + 1; 113 | // 114 | // // Maximum message size in bytes 115 | // private static int FIDO_MAXMSG = 2048; 116 | // 117 | // // CTAP capability bits (set means supported) 118 | // private static byte FIDO_CAP_WINK = 0x01; 119 | // private static byte FIDO_CAP_CBOR = 0x04; 120 | // private static byte FIDO_CAP_NMSG = 0x08; 121 | // 122 | // // Supported COSE algorithms 123 | // private static int COSE_ES256 = -7; 124 | // private static int COSE_EDDSA = -8; 125 | // private static int COSE_ECDH_ES256 = -25; 126 | // private static int COSE_RS256 = -257; 127 | // 128 | // // Supported COSE types 129 | // private static int COSE_KTY_OKP = 1; 130 | // private static int COSE_KTY_EC2 = 2; 131 | // private static int COSE_KTY_RSA = 3; 132 | // 133 | // // Supported curves 134 | // private static int COSE_P256 = 1; 135 | // private static int COSE_ED25519 = 6; 136 | // 137 | // // Supported extensions 138 | // private static byte FIDO_EXT_HMAC_SECRET = 0x01; 139 | // private static byte FIDO_EXT_CRED_PROTECT = 0x02; 140 | // 141 | // // Supported credential protection policies 142 | // private static int FIDO_CRED_PROT_UV_OPTIONAL = 0x01; 143 | // private static int FIDO_CRED_PROT_UV_OPTIONAL_WITH_ID = 0x02; 144 | // private static int FIDO_CRED_PROT_UV_REQUIRED = 0x03; 145 | 146 | // Secure random source for nonce 147 | private static final SecureRandom secureRandom = new SecureRandom(); 148 | 149 | // Instance variables 150 | private final byte[] nonce = new byte[8]; 151 | //private final byte[] fidoChannel = new byte[4]; 152 | 153 | public static void main(String[] args) throws HidException { 154 | 155 | Fido2AuthenticationExample example = new Fido2AuthenticationExample(); 156 | example.executeExample(); 157 | 158 | } 159 | 160 | private void executeExample() throws HidException { 161 | 162 | printPlatform(); 163 | 164 | // Demonstrate low level traffic logging 165 | HidApi.logTraffic = true; 166 | 167 | // Configure to use custom specification 168 | HidServicesSpecification hidServicesSpecification = new HidServicesSpecification(); 169 | 170 | // Use manual start 171 | hidServicesSpecification.setAutoStart(false); 172 | 173 | // Use data received events 174 | hidServicesSpecification.setAutoDataRead(true); 175 | hidServicesSpecification.setDataReadInterval(500); 176 | 177 | // Get HID services using custom specification 178 | HidServices hidServices = HidManager.getHidServices(hidServicesSpecification); 179 | 180 | // Register for service events 181 | hidServices.addHidServicesListener(this); 182 | 183 | // Manually start HID services 184 | hidServices.start(); 185 | 186 | // Enumerate devices looking for usage page = 0xf1d0 (FIDO...) 187 | HidDevice fidoDevice = null; 188 | for (HidDevice hidDevice : hidServices.getAttachedHidDevices()) { 189 | if (hidDevice.getUsage() == 0x01 && hidDevice.getUsagePage() == 0xfffff1d0) { 190 | System.out.println(ANSI_GREEN + "Using FIDO2 device: " + hidDevice.getPath() + ANSI_RESET); 191 | fidoDevice = hidDevice; 192 | break; 193 | } 194 | } 195 | 196 | if (fidoDevice == null) { 197 | // Shut down and rely on auto-shutdown hook to clear HidApi resources 198 | System.out.println(ANSI_YELLOW + "No FIDO2 devices attached." + ANSI_RESET); 199 | } else { 200 | 201 | // Open the device 202 | if (fidoDevice.isClosed()) { 203 | if (!fidoDevice.open()) { 204 | throw new IllegalStateException("Unable to open device"); 205 | } 206 | } 207 | 208 | // Perform a USB ReportDescriptor operation to determine general device capabilities 209 | // This requires complex decoding defined in the referenced documents 210 | // Reports can be up to 4096 bytes for complex devices so 64 is quite low 211 | byte[] reportDescriptor = new byte[64]; 212 | if (fidoDevice.getReportDescriptor(reportDescriptor) > 0) { 213 | System.out.println(ANSI_GREEN + "FIDO2 device report descriptor (first 64 bytes): " + fidoDevice.getPath() + ANSI_RESET); 214 | printAsHex(reportDescriptor); 215 | } 216 | 217 | // Perform a FIDO Initialise operation 218 | handleInitialise(fidoDevice); 219 | 220 | // TODO Consider further operations such as Registration to illustrate state machine 221 | 222 | } 223 | 224 | waitAndShutdown(hidServices); 225 | 226 | } 227 | 228 | /** 229 | * Generate a random set of 8 bytes for use as a nonce 230 | */ 231 | private void generateNonce() { 232 | 233 | secureRandom.nextBytes(nonce); 234 | 235 | } 236 | 237 | /** 238 | * Initialise the FIDO2 device and set a communications channel 239 | * 240 | * @param hidDevice The device to use 241 | * @return True if the device is now initialised for use 242 | */ 243 | private boolean handleInitialise(HidDevice hidDevice) { 244 | 245 | generateNonce(); 246 | 247 | // Initialise 248 | byte[] initialiseRequest = new byte[]{ 249 | (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, // Broadcast channel 250 | (byte) ((byte) 0x80 + CTAP_CMD_INIT), // Initialise command 251 | 0x00, 0x08, // Payload byte count (BCNT) 252 | nonce[0], nonce[1], nonce[2], nonce[3], nonce[4], nonce[5], nonce[6], nonce[7] 253 | }; 254 | 255 | // Write message to device with zero byte padding 256 | System.out.println(ANSI_GREEN + "Sending CTAPHID_INIT..." + ANSI_RESET); 257 | int bytesWritten = hidDevice.write(initialiseRequest, CTAP_MAX_REPORT_LEN, (byte) 0x00, true); 258 | if (bytesWritten < 0) { 259 | System.out.println(ANSI_RED + hidDevice.getLastErrorMessage() + ANSI_RESET); 260 | return false; 261 | } 262 | 263 | return true; 264 | 265 | } 266 | 267 | @Override 268 | public void hidDataReceived(HidServicesEvent event) { 269 | super.hidDataReceived(event); 270 | 271 | // Analyse the response 272 | byte[] initialiseResponse = event.getDataReceived(); 273 | 274 | // Decode the response 275 | System.out.println(ANSI_GREEN + "Response is:" + ANSI_RESET); 276 | System.out.printf("Channel id: %02x %02x %02x %02x%n", 277 | initialiseResponse[0], initialiseResponse[1], initialiseResponse[2], initialiseResponse[3] 278 | ); 279 | System.out.printf("Command (0x86): %02x%n", initialiseResponse[4]); 280 | System.out.printf("Byte count (0x00 0x11): %02x %02x%n", initialiseResponse[5], initialiseResponse[6]); 281 | System.out.printf("Nonce (%02x %02x %02x %02x %02x %02x %02x %02x): %02x %02x %02x %02x %02x %02x %02x %02x%n", 282 | nonce[0], nonce[1], nonce[2], nonce[3], nonce[4], nonce[5], nonce[6], nonce[7], 283 | initialiseResponse[7], initialiseResponse[8], initialiseResponse[9], initialiseResponse[10], 284 | initialiseResponse[11], initialiseResponse[12], initialiseResponse[13], initialiseResponse[14] 285 | ); 286 | System.out.printf("New channel id: %02x %02x %02x %02x%n", 287 | initialiseResponse[15], initialiseResponse[16], initialiseResponse[17], initialiseResponse[18] 288 | ); 289 | System.out.printf("Protocol (0x02): %02x%n", initialiseResponse[19]); 290 | System.out.printf("Major: %02x%n", initialiseResponse[20]); 291 | System.out.printf("Minor: %02x%n", initialiseResponse[21]); 292 | System.out.printf("Build: %02x%n", initialiseResponse[22]); 293 | System.out.printf("Capabilities: %02x%n", initialiseResponse[23]); 294 | 295 | // TODO Copy the channel for ongoing communications 296 | // fidoChannel[0] = initialiseResponse[15]; 297 | // fidoChannel[1] = initialiseResponse[16]; 298 | // fidoChannel[2] = initialiseResponse[17]; 299 | // fidoChannel[3] = initialiseResponse[18]; 300 | 301 | } 302 | } 303 | -------------------------------------------------------------------------------- /src/main/java/org/hid4java/jna/HidApiLibrary.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2014-2015 Gary Rowe 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | * 24 | */ 25 | 26 | package org.hid4java.jna; 27 | 28 | import com.sun.jna.Library; 29 | import com.sun.jna.Pointer; 30 | import com.sun.jna.WString; 31 | 32 | /** 33 | * JNA library interface to act as the proxy for the underlying native library 34 | * This approach removes the need for any JNI or native code 35 | * 36 | * @since 0.1.0 37 | */ 38 | public interface HidApiLibrary extends Library { 39 | 40 | /** 41 | * Initialize the HIDAPI library. 42 | * This function initializes the HIDAPI library. Calling it is not strictly necessary, 43 | * as it will be called automatically by hid_enumerate() and any of the hid_open_*() functions 44 | * if it is needed. This function should be called at the beginning of execution however, 45 | * if there is a chance of HIDAPI handles being opened by different threads simultaneously. 46 | */ 47 | void hid_init(); 48 | 49 | /** 50 | * Finalize the HIDAPI library. 51 | *
    52 | * This function frees all the static data associated with HIDAPI. It should be called 53 | * at the end of execution to avoid memory leaks. 54 | */ 55 | void hid_exit(); 56 | 57 | /** 58 | * Open a HID device using a Vendor ID (VID), Product ID (PID) and optionally a serial number. 59 | *
    60 | * If serial_number is NULL, the first device with the specified VID and PID is opened. 61 | * 62 | * @param vendor_id The vendor ID 63 | * @param product_id The product ID 64 | * @param serial_number The serial number (or null for wildcard) 65 | * @return A pointer to a HidDevice on success or null on failure 66 | */ 67 | Pointer hid_open(short vendor_id, short product_id, WString serial_number); 68 | 69 | /** 70 | * Close a HID device 71 | * 72 | * @param device A device handle 73 | */ 74 | void hid_close(Pointer device); 75 | 76 | /** 77 | * Get a string describing the last error which occurred. 78 | * 79 | * @param device A device handle 80 | * @return A string containing the last error which occurred or null if none has occurred. 81 | */ 82 | Pointer hid_error(Pointer device); 83 | 84 | /** 85 | * Read an Input report from a HID device. 86 | *
    87 | * Input reports are returned to the host through the INTERRUPT IN endpoint. The first byte will contain the Report number 88 | * if the device uses numbered reports. 89 | * 90 | * @param device A device handle returned from hid_open(). 91 | * @param bytes A buffer to put the read data into. 92 | * @param length The number of bytes to read. For devices with multiple reports, make sure to read an extra byte for the report number. 93 | * @return This function returns the actual number of bytes read and -1 on error. If no packet was available to be read 94 | * and the handle is in non-blocking mode this function returns 0. 95 | */ 96 | int hid_read(Pointer device, WideStringBuffer.ByReference bytes, int length); 97 | 98 | /** 99 | * Read an Input report from a HID device with timeout. 100 | *
    101 | * Input reports are returned to the host through the INTERRUPT IN endpoint. The first byte will contain the Report number 102 | * if the device uses numbered reports. 103 | * 104 | * @param device A device handle 105 | * @param bytes A buffer to put the read data into. 106 | * @param length The number of bytes to read. For devices with multiple reports, make sure to read an extra byte for the report number. 107 | * @param timeout The timeout in milliseconds or -1 for blocking wait. 108 | * @return This function returns the actual number of bytes read and -1 on error. If no packet was available to be read within 109 | * the timeout period, this function returns 0. 110 | */ 111 | int hid_read_timeout(Pointer device, WideStringBuffer.ByReference bytes, int length, int timeout); 112 | 113 | /** 114 | * Write an Output report to a HID device. 115 | *
    116 | * The first byte of data[] must contain the Report ID. For devices which only support a single report, this must be set to 0x0. 117 | * The remaining bytes contain the report data. 118 | *
    119 | * Since the Report ID is mandatory, calls to hid_write() will always contain one more byte than the report contains. 120 | *
    121 | * For example, if a hid report is 16 bytes long, 17 bytes must be passed to hid_write(), the Report ID (or 0x0, for devices with 122 | * a single report), followed by the report data (16 bytes). In this example, the length passed in would be 17. 123 | *
    124 | * hid_write() will send the data on the first OUT endpoint, if one exists. If it does not, it will send the data through the 125 | * Control Endpoint (Endpoint 0). 126 | * 127 | * @param device A device handle 128 | * @param data the data to send, including the report number as the first byte 129 | * @param len The length in bytes of the data to send 130 | * @return The actual number of bytes written, -1 on error 131 | */ 132 | int hid_write(Pointer device, WideStringBuffer.ByReference data, int len); 133 | 134 | /** 135 | * Get a feature report from a HID device. 136 | *
    137 | * Set the first byte of data[] to the Report ID of the report to be read. Make sure to allow space for this extra byte in data[]. 138 | * Upon return, the first byte will still contain the Report ID, and the report data will start in data[1]. 139 | * 140 | * @param device A device handle 141 | * @param data A buffer to put the read data into, including the Report ID. Set the first byte of data[] to the Report ID of the report to be read, or set it to zero if your device does not use numbered reports. 142 | * @param length The number of bytes to read, including an extra byte for the report ID. The buffer can be longer than the actual report. 143 | * @return The number of bytes read plus one for the report ID (which is still in the first byte), or -1 on error 144 | */ 145 | int hid_get_feature_report(Pointer device, WideStringBuffer.ByReference data, int length); 146 | 147 | /** 148 | * Send a Feature report to the device. 149 | *
    150 | * Feature reports are sent over the Control endpoint as a Set_Report transfer. 151 | *
    152 | * The first byte of data[] must contain the Report ID. For devices which only support a single report, this must be set to 0x0. 153 | *
    154 | * The remaining bytes contain the report data. 155 | *
    156 | * Since the Report ID is mandatory, calls to hid_send_feature_report() will always contain one more byte than the report contains. 157 | *
    158 | * For example, if a hid report is 16 bytes long, 17 bytes must be passed to hid_send_feature_report(): 159 | * the Report ID (or 0x0, for devices which do not use numbered reports), followed by the report data (16 bytes). 160 | * In this example, the length passed in would be 17. 161 | * 162 | * @param device The device handle 163 | * @param data The data to send, including the report number as the first byte 164 | * @param length The length in bytes of the data to send, including the report number 165 | * @return The actual number of bytes written, -1 on error 166 | */ 167 | int hid_send_feature_report(Pointer device, WideStringBuffer.ByReference data, int length); 168 | 169 | /** 170 | * Get a string from a HID device, based on its string index. 171 | * 172 | * @param device the device handle 173 | * @param idx The index of the string to get 174 | * @param string A wide string buffer to put the data into 175 | * @param len The length of the buffer in multiples of wchar_t 176 | * @return 0 on success, -1 on failure 177 | */ 178 | int hid_get_indexed_string(Pointer device, int idx, WideStringBuffer.ByReference string, int len); 179 | 180 | /** 181 | * Get the manufacturer string from a HID device 182 | * 183 | * @param device the device handle 184 | * @param buffer A byte buffer to put the data into 185 | * @param size The length of the buffer in multiple of wchar_t 186 | * @return 0 on success, -1 on failure 187 | */ 188 | @SuppressWarnings("UnusedReturnValue") 189 | int hid_get_report_descriptor(Pointer device, byte[] buffer, int size); 190 | 191 | /** 192 | * Get the manufacturer string from a HID device 193 | * 194 | * @param device the device handle 195 | * @param str A wide string buffer to put the data into 196 | * @param len The length of the buffer in multiple of wchar_t 197 | * @return 0 on success, -1 on failure 198 | */ 199 | @SuppressWarnings("UnusedReturnValue") 200 | int hid_get_manufacturer_string(Pointer device, WideStringBuffer.ByReference str, int len); 201 | 202 | /** 203 | * Get the product number string from a HID device 204 | * 205 | * @param device the device handle 206 | * @param str A wide string buffer to put the data into 207 | * @param len The length of the buffer in multiple of wchar_t 208 | * @return 0 on success, -1 on failure 209 | */ 210 | @SuppressWarnings("UnusedReturnValue") 211 | int hid_get_product_string(Pointer device, WideStringBuffer.ByReference str, int len); 212 | 213 | /** 214 | * Get the serial number string from a HID device 215 | * 216 | * @param device the device handle 217 | * @param str A wide string buffer to put the data into 218 | * @param len The length of the buffer in multiple of wchar_t 219 | * @return 0 on success, -1 on failure 220 | */ 221 | @SuppressWarnings("UnusedReturnValue") 222 | int hid_get_serial_number_string(Pointer device, WideStringBuffer.ByReference str, int len); 223 | 224 | /** 225 | * Set the device handle to be non-blocking. 226 | *
    227 | * In non-blocking mode calls to hid_read() will return immediately with a value of 0 if there is no data to be read. 228 | *
    229 | * In blocking mode, hid_read() will wait (block) until there is data to read before returning. 230 | *
    231 | * Nonblocking can be turned on and off at any time. 232 | * 233 | * @param device The device handle 234 | * @param nonblock 0 disables non-blocking, 1 enables non-blocking 235 | * @return 0 on success, -1 on error 236 | */ 237 | int hid_set_nonblocking(Pointer device, int nonblock); 238 | 239 | /** 240 | * Enumerate the HID Devices. 241 | *
    242 | * This function returns a linked list of all the HID devices attached to the system which match vendor_id and product_id. 243 | *
    244 | * If vendor_id is set to 0 then any vendor matches. If product_id is set to 0 then any product matches. 245 | *
    246 | * If vendor_id and product_id are both set to 0, then all HID devices will be returned. 247 | * 248 | * @param vendor_id The vendor ID 249 | * @param product_id The product ID 250 | * @return A linked list of all discovered matching devices 251 | */ 252 | HidDeviceInfoStructure hid_enumerate(short vendor_id, short product_id); 253 | 254 | /** 255 | * Free an enumeration linked list 256 | * 257 | * @param devs The device information pointer 258 | */ 259 | void hid_free_enumeration(Pointer devs); 260 | 261 | /** 262 | * Open a HID device by its path name. 263 | *
    264 | * The path name be determined by calling hid_enumerate(), or a platform-specific path name can be used (eg: "/dev/hidraw0" on Linux). 265 | * 266 | * @param path The path name 267 | * @return The pointer if successful or null 268 | */ 269 | Pointer hid_open_path(String path); 270 | 271 | /** 272 | * Get version of hidapi library 273 | * 274 | * @return Version in major.minor.patch format 275 | */ 276 | String hid_version_str(); 277 | } 278 | -------------------------------------------------------------------------------- /src/main/java/org/hid4java/jna/HidApi.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2014-2015 Gary Rowe 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | * 24 | */ 25 | 26 | package org.hid4java.jna; 27 | 28 | import com.sun.jna.Platform; 29 | import com.sun.jna.Pointer; 30 | import com.sun.jna.WString; 31 | 32 | /** 33 | * JNA utility class to provide the following to low level operations: 34 | *
      35 | *
    • Direct access to the HID API library through JNA
    • 36 | *
    37 | * 38 | * @since 0.0.1 39 | */ 40 | public class HidApi { 41 | 42 | /** 43 | * Default length for wide string buffer 44 | */ 45 | private static final int WSTR_LEN = 512; 46 | 47 | /** 48 | * Error message if device is not initialised 49 | */ 50 | private static final String DEVICE_NULL = "Device not initialised"; 51 | 52 | /** 53 | * Device error code 54 | */ 55 | private static final int DEVICE_ERROR = -2; 56 | 57 | /** 58 | * Enables use of the libusb variant of the hidapi native library when running on a Linux platform. 59 | *
    60 | * The default is hidraw which enables Bluetooth devices but requires udev rules. 61 | */ 62 | public static boolean useLibUsbVariant = false; 63 | 64 | /** 65 | * When false - all devices will be opened in exclusive mode. (Default) 66 | * When true - all devices will be opened in non-exclusive mode. 67 | *
    68 | * See {@link DarwinHidApiLibrary#hid_darwin_set_open_exclusive(int)} for more information. 69 | */ 70 | public static boolean darwinOpenDevicesNonExclusive = false; 71 | 72 | /** 73 | * Enables HID traffic logging to stdout to assist debugging. This will show all bytes (including the extra report ID) 74 | * that were sent or received via HIDAPI buffers. It does not log direct string calls (e.g. getEnumeratedString()). 75 | *
    76 | * Format is '>' for host to device then '[count]' then hex bytes. 77 | *
    78 | * This may present a security issue if left enabled in production, although a packet sniffer would see the same data. 79 | */ 80 | public static boolean logTraffic = false; 81 | 82 | /** 83 | * The HID API library 84 | */ 85 | private static HidApiLibrary hidApiLibrary; 86 | 87 | /** 88 | * Open a HID device using a Vendor ID (VID), Product ID (PID) and optionally a serial number 89 | * 90 | * @param vendor The vendor ID 91 | * @param product The product ID 92 | * @param serialNumber The serial number 93 | * @return The device or null if not found 94 | */ 95 | public static HidDeviceStructure open(int vendor, int product, String serialNumber) { 96 | 97 | // Attempt to open the device 98 | Pointer p = hidApiLibrary.hid_open( 99 | (short) vendor, 100 | (short) product, 101 | serialNumber == null ? null : new WString(serialNumber) 102 | ); 103 | 104 | if (p != null) { 105 | // Wrap the structure 106 | return new HidDeviceStructure(p); 107 | } 108 | 109 | return null; 110 | 111 | } 112 | 113 | /** 114 | * Initialise the HID API library. Should always be called before using any other API calls. 115 | */ 116 | public static void init() { 117 | 118 | if (useLibUsbVariant && Platform.isLinux()) { 119 | hidApiLibrary = LibusbHidApiLibrary.INSTANCE; 120 | } else if (Platform.isMac()) { 121 | hidApiLibrary = DarwinHidApiLibrary.INSTANCE; 122 | } else { 123 | hidApiLibrary = HidrawHidApiLibrary.INSTANCE; 124 | } 125 | 126 | hidApiLibrary.hid_init(); 127 | 128 | if (hidApiLibrary instanceof DarwinHidApiLibrary) { 129 | ((DarwinHidApiLibrary) hidApiLibrary).hid_darwin_set_open_exclusive(darwinOpenDevicesNonExclusive ? 0 : 1); 130 | } 131 | } 132 | 133 | /** 134 | * Finalise the HID API library 135 | */ 136 | public static void exit() { 137 | hidApiLibrary.hid_exit(); 138 | } 139 | 140 | /** 141 | * Open a HID device by its path name 142 | * 143 | * @param path The device path (e.g. "0003:0002:00") 144 | * @return The device or null if not found 145 | */ 146 | public static HidDeviceStructure open(String path) { 147 | Pointer p = hidApiLibrary.hid_open_path(path); 148 | return (p == null ? null : new HidDeviceStructure(p)); 149 | } 150 | 151 | /** 152 | * Close a HID device 153 | * 154 | * @param device The HID device structure 155 | */ 156 | public static void close(HidDeviceStructure device) { 157 | 158 | if (device != null) { 159 | hidApiLibrary.hid_close(device.ptr()); 160 | } 161 | 162 | } 163 | 164 | /** 165 | * Enumerate the attached HID devices 166 | * 167 | * @param vendor The vendor ID 168 | * @param product The product ID 169 | * @return The device info of the matching device 170 | */ 171 | public static HidDeviceInfoStructure enumerateDevices(int vendor, int product) { 172 | 173 | return hidApiLibrary.hid_enumerate((short) vendor, (short) product); 174 | 175 | } 176 | 177 | /** 178 | * Free an enumeration linked list 179 | * 180 | * @param list The list to free 181 | */ 182 | public static void freeEnumeration(HidDeviceInfoStructure list) { 183 | 184 | hidApiLibrary.hid_free_enumeration(list.getPointer()); 185 | 186 | } 187 | 188 | /** 189 | * @param device The HID device structure 190 | * @return A string describing the last error which occurred 191 | */ 192 | public static String getLastErrorMessage(HidDeviceStructure device) { 193 | 194 | if (device == null) { 195 | return DEVICE_NULL; 196 | } 197 | 198 | Pointer p = hidApiLibrary.hid_error(device.ptr()); 199 | 200 | return p == null ? null : new WideStringBuffer(p.getByteArray(0, WSTR_LEN)).toString(); 201 | } 202 | 203 | /** 204 | * @param device The HID device 205 | * @return The device manufacturer string 206 | */ 207 | public static String getManufacturer(HidDeviceStructure device) { 208 | 209 | if (device == null) { 210 | return DEVICE_NULL; 211 | } 212 | 213 | WideStringBuffer wStr = new WideStringBuffer(WSTR_LEN); 214 | hidApiLibrary.hid_get_manufacturer_string(device.ptr(), wStr, WSTR_LEN); 215 | 216 | return wStr.toString(); 217 | } 218 | 219 | /** 220 | * @param device The HID device 221 | * @return The device product ID 222 | */ 223 | public static String getProductId(HidDeviceStructure device) { 224 | 225 | if (device == null) { 226 | return DEVICE_NULL; 227 | } 228 | 229 | WideStringBuffer wBuffer = new WideStringBuffer(WSTR_LEN); 230 | hidApiLibrary.hid_get_product_string(device.ptr(), wBuffer, WSTR_LEN); 231 | 232 | return wBuffer.toString(); 233 | } 234 | 235 | /** 236 | * @param device The HID device 237 | * @return The device serial number 238 | */ 239 | public static String getSerialNumber(HidDeviceStructure device) { 240 | 241 | if (device == null) { 242 | return DEVICE_NULL; 243 | } 244 | 245 | WideStringBuffer wBuffer = new WideStringBuffer(WSTR_LEN); 246 | 247 | hidApiLibrary.hid_get_serial_number_string(device.ptr(), wBuffer, WSTR_LEN); 248 | 249 | return wBuffer.toString(); 250 | } 251 | 252 | /** 253 | * Set the device handle to be non-blocking 254 | *
    255 | * In non-blocking mode calls to hid_read() will return immediately with a value of 0 if there is no data to be read. 256 | * In blocking mode, hid_read() will wait (block) until there is data to read before returning 257 | *
    258 | * Non-blocking can be turned on and off at any time 259 | * 260 | * @param device The HID device 261 | * @param nonBlocking True if non-blocking mode is required 262 | * @return True if successful 263 | */ 264 | public static boolean setNonBlocking(HidDeviceStructure device, boolean nonBlocking) { 265 | 266 | return device != null && 0 == hidApiLibrary.hid_set_nonblocking(device.ptr(), nonBlocking ? 1 : 0); 267 | 268 | } 269 | 270 | /** 271 | * Read an Input report from a HID device 272 | * Input reports are returned to the host through the INTERRUPT IN endpoint. The first byte 273 | * will contain the Report ID if the device uses numbered reports. 274 | * 275 | * @param device The HID device 276 | * @param buffer The buffer to read into (allow an extra byte if device supports multiple report IDs) 277 | * @return The actual number of bytes read and -1 on error. If no packet was available to be read 278 | * and the handle is in non-blocking mode, this function returns 0. 279 | */ 280 | public static int read(HidDeviceStructure device, byte[] buffer) { 281 | 282 | if (device == null || buffer == null) { 283 | return DEVICE_ERROR; 284 | } 285 | 286 | WideStringBuffer wBuffer = new WideStringBuffer(buffer); 287 | 288 | int result = hidApiLibrary.hid_read(device.ptr(), wBuffer, wBuffer.buffer.length); 289 | 290 | if (result > 0) { 291 | logTraffic(wBuffer, false); 292 | } 293 | 294 | return result; 295 | 296 | } 297 | 298 | /** 299 | * Read an Input report from a HID device with timeout 300 | * 301 | * @param device The HID device 302 | * @param buffer The buffer to read into 303 | * @param timeoutMillis The number of milliseconds to wait before giving up 304 | * @return The actual number of bytes read and -1 on error. If no packet was available to be read within 305 | * the timeout period returns 0. 306 | */ 307 | public static int read(HidDeviceStructure device, byte[] buffer, int timeoutMillis) { 308 | 309 | if (device == null || buffer == null) { 310 | return DEVICE_ERROR; 311 | } 312 | 313 | WideStringBuffer wBuffer = new WideStringBuffer(buffer); 314 | 315 | int result = hidApiLibrary.hid_read_timeout(device.ptr(), wBuffer, buffer.length, timeoutMillis); 316 | 317 | if (result > 0) { 318 | logTraffic(wBuffer, false); 319 | } 320 | 321 | return result; 322 | 323 | } 324 | 325 | /** 326 | * Get a feature report from a HID device 327 | * HID API notes 328 | *
    329 | * Under the covers the HID library will set the first byte of data[] to the Report ID of the report to be read. 330 | * Upon return, the first byte will still contain the Report ID, and the report data will start in data[1] 331 | * This method handles all the wide string and array manipulation for you 332 | * 333 | * @param device The HID device 334 | * @param data The buffer to contain the report 335 | * @param reportId The report ID (or (byte) 0x00) 336 | * @return The number of bytes read plus one for the report ID (which has been removed from the first byte), or -1 on error. 337 | */ 338 | public static int getFeatureReport(HidDeviceStructure device, byte[] data, byte reportId) { 339 | 340 | if (device == null || data == null) { 341 | return DEVICE_ERROR; 342 | } 343 | 344 | // Create a large buffer 345 | WideStringBuffer report = new WideStringBuffer(WSTR_LEN); 346 | report.buffer[0] = reportId; 347 | int res = hidApiLibrary.hid_get_feature_report(device.ptr(), report, data.length + 1); 348 | 349 | if (res == -1) { 350 | return res; 351 | } 352 | 353 | // Avoid index out of bounds exception 354 | System.arraycopy(report.buffer, 1, data, 0, Math.min(res, data.length)); 355 | 356 | logTraffic(report, false); 357 | 358 | return res; 359 | 360 | } 361 | 362 | /** 363 | * Send a Feature report to the device using a simplified interface 364 | * HID API notes 365 | *
    366 | * Under the covers, feature reports are sent over the Control endpoint as a Set_Report transfer. 367 | * The first byte of data[] must contain the Report ID. For devices which only support a single report, 368 | * this must be set to 0x0. The remaining bytes contain the report data 369 | * Since the Report ID is mandatory, calls to hid_send_feature_report() will always contain one more byte than 370 | * the report contains. 371 | *
    372 | * For example, if a hid report is 16 bytes long, 17 bytes must be passed to 373 | * hid_send_feature_report(): the Report ID (or 0x00, for devices which do not use numbered reports), followed by 374 | * the report data (16 bytes). In this example, the bytes written would be 17. 375 | *
    376 | * This method handles all the array manipulation for you 377 | * 378 | * @param device The HID device 379 | * @param data The feature report data (will be widened and have the report ID pre-pended) 380 | * @param reportId The report ID (or (byte) 0x00) 381 | * @return This function returns the actual number of bytes written and -1 on error. 382 | */ 383 | public static int sendFeatureReport(HidDeviceStructure device, byte[] data, byte reportId) { 384 | 385 | if (device == null || data == null) { 386 | return DEVICE_ERROR; 387 | } 388 | 389 | WideStringBuffer report = new WideStringBuffer(data.length + 1); 390 | report.buffer[0] = reportId; 391 | 392 | System.arraycopy(data, 0, report.buffer, 1, data.length); 393 | 394 | logTraffic(report, true); 395 | 396 | return hidApiLibrary.hid_send_feature_report(device.ptr(), report, report.buffer.length); 397 | 398 | } 399 | 400 | /** 401 | * Write an Output report to a HID device using a simplified interface 402 | * HID API notes 403 | *
    404 | * In USB HID the first byte of the data packet must contain the Report ID. 405 | * For devices which only support a single report, this must be set to 0x00. 406 | * The remaining bytes contain the report data. Since the Report ID is mandatory, 407 | * calls to hid_write() will always contain one more byte than the report 408 | * contains. 409 | *
    410 | * For example, if a HID report is 16 bytes long, 17 bytes must be passed to hid_write(), 411 | * the Report ID (or 0x00, for devices with a single report), followed by the report data (16 bytes). 412 | * In this example, the length passed in would be 17. 413 | * hid_write() will send the data on the first OUT endpoint, if one exists. 414 | * If it does not, it will send the data through the Control Endpoint (Endpoint 0) 415 | * 416 | * @param device The device 417 | * @param data The report data to write (should not include the Report ID) 418 | * @param len The length of the report data (should not include the Report ID) 419 | * @param reportId The report ID (or (byte) 0x00) 420 | * @return The number of bytes written, or -1 if an error occurs 421 | */ 422 | public static int write(HidDeviceStructure device, byte[] data, int len, byte reportId) { 423 | 424 | // Fail fast 425 | if (device == null || data == null) { 426 | return DEVICE_ERROR; 427 | } 428 | 429 | // Precondition checks 430 | if (data.length < len) { 431 | len = data.length; 432 | } 433 | 434 | final WideStringBuffer report; 435 | 436 | // Put report ID into position 0 and fill out buffer 437 | report = new WideStringBuffer(len + 1); 438 | report.buffer[0] = reportId; 439 | if (len >= 1) { 440 | System.arraycopy(data, 0, report.buffer, 1, len); 441 | } 442 | 443 | logTraffic(report, true); 444 | 445 | return hidApiLibrary.hid_write(device.ptr(), report, report.buffer.length); 446 | 447 | } 448 | 449 | /** 450 | * Get a string from a HID device, based on its string index 451 | * 452 | * @param device The HID device 453 | * @param idx The index 454 | * @return The string 455 | */ 456 | public static String getIndexedString(HidDeviceStructure device, int idx) { 457 | 458 | if (device == null) { 459 | return DEVICE_NULL; 460 | } 461 | WideStringBuffer wStr = new WideStringBuffer(WSTR_LEN); 462 | int res = hidApiLibrary.hid_get_indexed_string(device.ptr(), idx, wStr, WSTR_LEN); 463 | 464 | return res == -1 ? null : wStr.toString(); 465 | } 466 | 467 | /** 468 | * Get a report descriptor from a HID device 469 | *
    470 | * User has to provide a preallocated buffer (4096 bytes recommended) where descriptor will be copied to 471 | *
    472 | * @param device The HID device 473 | * @param buffer The buffer to copy descriptor into. 474 | * @param size The size of the buffer in bytes. 475 | * @return A non-negative number of bytes actually copied, or -1 on error. 476 | * @since 0.14.0 hidapi 477 | */ 478 | public static int getReportDescriptor(HidDeviceStructure device, byte[] buffer, int size) { 479 | 480 | if (device == null) { 481 | return -1; 482 | } 483 | return hidApiLibrary.hid_get_report_descriptor(device.ptr(), buffer, size); 484 | } 485 | 486 | /** 487 | * @param buffer The buffer to serialise for traffic 488 | * @param isWrite True if writing (from host to device) 489 | */ 490 | private static void logTraffic(WideStringBuffer buffer, boolean isWrite) { 491 | if (HidApi.logTraffic && buffer != null && buffer.size() > 0) { 492 | if (isWrite) { 493 | System.out.print("> "); 494 | } else { 495 | System.out.print("< "); 496 | } 497 | System.out.printf("[%02x]:", buffer.buffer.length); 498 | for (byte b : buffer.buffer) { 499 | System.out.printf(" %02x", b); 500 | } 501 | System.out.println(); 502 | } 503 | } 504 | 505 | /** 506 | * Returns the full version of the underlying hidapi library 507 | * 508 | * @return The version in major.minor.patch format 509 | * @see org.hid4java.HidServices#getNativeVersion 510 | */ 511 | public static String getVersion() { 512 | if (hidApiLibrary == null) { 513 | init(); 514 | } 515 | return hidApiLibrary.hid_version_str(); 516 | } 517 | 518 | } 519 | -------------------------------------------------------------------------------- /src/main/java/org/hid4java/HidDevice.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2014-2015 Gary Rowe 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | * 24 | */ 25 | 26 | package org.hid4java; 27 | 28 | import org.hid4java.jna.HidApi; 29 | import org.hid4java.jna.HidDeviceInfoStructure; 30 | import org.hid4java.jna.HidDeviceStructure; 31 | 32 | import java.io.ByteArrayOutputStream; 33 | import java.io.IOException; 34 | import java.util.Arrays; 35 | 36 | /** 37 | * High level wrapper to provide the following to API consumers: 38 | * 39 | *
      40 | *
    • Simplified access to the underlying JNA HidDeviceStructure
    • 41 | *
    42 | * 43 | * @since 0.0.1 44 | */ 45 | public class HidDevice { 46 | 47 | private final HidDeviceManager hidDeviceManager; 48 | private HidDeviceStructure hidDeviceStructure; 49 | 50 | private final String path; 51 | private final int vendorId; 52 | private final int productId; 53 | private String serialNumber; 54 | private final int releaseNumber; 55 | private String manufacturer; 56 | private String product; 57 | private final int usagePage; 58 | private final int usage; 59 | private final int interfaceNumber; 60 | 61 | private final boolean autoDataRead; 62 | private final int dataReadInterval; 63 | 64 | /** 65 | * The data read thread 66 | * We use a Thread instead of Executor since it may be stopped/paused/restarted frequently 67 | * and executors are more heavyweight in this regard 68 | */ 69 | private Thread dataReadThread = null; 70 | 71 | /** 72 | * @param infoStructure The HID device info structure providing details 73 | * @param hidDeviceManager The HID device manager providing access to device enumeration for post IO scanning 74 | * @param hidServicesSpecification The HID services specification providing configuration details 75 | * @since 0.1.0 76 | */ 77 | public HidDevice(HidDeviceInfoStructure infoStructure, HidDeviceManager hidDeviceManager, HidServicesSpecification hidServicesSpecification) { 78 | 79 | this.hidDeviceManager = hidDeviceManager; 80 | 81 | this.dataReadInterval = hidServicesSpecification.getDataReadInterval(); 82 | this.autoDataRead = hidServicesSpecification.isAutoDataRead(); 83 | 84 | this.hidDeviceStructure = null; 85 | 86 | this.path = infoStructure.path; 87 | 88 | // Note that the low-level HidDeviceInfoStructure is directly written to by 89 | // the JNA library and implies an unsigned short which is not available in Java. 90 | // The bitmask converts from [-32768, 32767] to [0,65535] 91 | // In Java 8 Short.toUnsignedInt() is available. 92 | this.vendorId = infoStructure.vendor_id & 0xffff; 93 | this.productId = infoStructure.product_id & 0xffff; 94 | 95 | this.releaseNumber = infoStructure.release_number; 96 | if (infoStructure.serial_number != null) { 97 | this.serialNumber = infoStructure.serial_number.toString(); 98 | } 99 | if (infoStructure.manufacturer_string != null) { 100 | this.manufacturer = infoStructure.manufacturer_string.toString(); 101 | } 102 | if (infoStructure.product_string != null) { 103 | this.product = infoStructure.product_string.toString(); 104 | } 105 | this.usagePage = infoStructure.usage_page; 106 | this.usage = infoStructure.usage; 107 | this.interfaceNumber = infoStructure.interface_number; 108 | } 109 | 110 | /** 111 | * Handles the process of starting the data read thread 112 | */ 113 | private void startDataReadThread() { 114 | 115 | // Check for previous start 116 | if (this.isDataRead()) { 117 | return; 118 | } 119 | 120 | // Perform an immediate data read 121 | dataRead(); 122 | 123 | // Ensure we have a scan thread available 124 | configureDataReadThread(getDataReadRunnable()); 125 | 126 | } 127 | 128 | /** 129 | * Stop the data read thread 130 | */ 131 | private synchronized void stopDataReadThread() { 132 | 133 | if (isDataRead()) { 134 | dataReadThread.interrupt(); 135 | } 136 | 137 | } 138 | 139 | /** 140 | * Configures the data read thread to allow recovery from stop or pause 141 | */ 142 | private synchronized void configureDataReadThread(Runnable dataReadRunnable) { 143 | 144 | if (autoDataRead) { 145 | stopDataReadThread(); 146 | } 147 | 148 | // Require a new one 149 | dataReadThread = new Thread(dataReadRunnable); 150 | dataReadThread.setDaemon(true); 151 | dataReadThread.setName("hid4java data reader"); 152 | dataReadThread.start(); 153 | 154 | } 155 | 156 | private synchronized Runnable getDataReadRunnable() { 157 | 158 | return new Runnable() { 159 | @Override 160 | public void run() { 161 | 162 | while (true) { 163 | try { 164 | //noinspection BusyWait 165 | Thread.sleep(dataReadInterval); 166 | } catch (final InterruptedException e) { 167 | Thread.currentThread().interrupt(); 168 | break; 169 | } 170 | dataRead(); 171 | } 172 | } 173 | }; 174 | 175 | } 176 | 177 | /** 178 | * @return True if the data read thread is running 179 | */ 180 | private boolean isDataRead() { 181 | return dataReadThread != null && dataReadThread.isAlive(); 182 | } 183 | 184 | 185 | /** 186 | * Attempt to read all data from the device input buffer as part 187 | * of the automatic data read process 188 | *
    189 | * Will fire attach/detach events as appropriate. 190 | */ 191 | private synchronized void dataRead() { 192 | 193 | byte[] dataRead = readAll(100); 194 | 195 | // Fire the event on a separate thread 196 | hidDeviceManager.afterDeviceDataRead(this, dataRead); 197 | 198 | } 199 | 200 | /** 201 | * The "path" is well-supported across Windows, Mac and Linux so makes a 202 | * better choice for a unique ID 203 | *
    204 | * See hid4java issue #8 for details 205 | * 206 | * @return A unique device ID made up from vendor ID, product ID and serial number 207 | * @since 0.1.0 208 | */ 209 | public String getId() { 210 | return path; 211 | } 212 | 213 | /** 214 | * @return The device path 215 | * @since 0.1.0 216 | */ 217 | public String getPath() { 218 | return path; 219 | } 220 | 221 | /** 222 | * @return Int version of vendor ID 223 | * @since 0.1.0 224 | */ 225 | public int getVendorId() { 226 | return vendorId; 227 | } 228 | 229 | /** 230 | * @return Int version of product ID 231 | * @since 0.1.0 232 | */ 233 | public int getProductId() { 234 | return productId; 235 | } 236 | 237 | /** 238 | * @return The device serial number 239 | * @since 0.1.0 240 | */ 241 | public String getSerialNumber() { 242 | return serialNumber; 243 | } 244 | 245 | /** 246 | * @return The release number 247 | * @since 0.1.0 248 | */ 249 | public int getReleaseNumber() { 250 | return releaseNumber; 251 | } 252 | 253 | /** 254 | * @return The manufacturer 255 | * @since 0.1.0 256 | */ 257 | public String getManufacturer() { 258 | return manufacturer; 259 | } 260 | 261 | /** 262 | * @return The product 263 | * @since 0.1.0 264 | */ 265 | public String getProduct() { 266 | return product; 267 | } 268 | 269 | /** 270 | * @return The usage page 271 | * @since 0.1.0 272 | */ 273 | public int getUsagePage() { 274 | return usagePage; 275 | } 276 | 277 | /** 278 | * @return The usage information 279 | * @since 0.1.0 280 | */ 281 | public int getUsage() { 282 | return usage; 283 | } 284 | 285 | public int getInterfaceNumber() { 286 | return interfaceNumber; 287 | } 288 | 289 | /** 290 | * Open this device and obtain a device structure 291 | * 292 | * @return True if the device was successfully opened 293 | * @since 0.1.0 294 | */ 295 | public boolean open() { 296 | hidDeviceStructure = HidApi.open(path); 297 | 298 | // Configure automatic data read 299 | if (autoDataRead) { 300 | startDataReadThread(); 301 | } 302 | 303 | return hidDeviceStructure != null; 304 | } 305 | 306 | /** 307 | * @return True if the device structure is present 308 | * @since 0.1.0 309 | * @deprecated Use isClosed() instead of !isOpen() to improve code clarity 310 | */ 311 | @Deprecated 312 | public boolean isOpen() { 313 | return !isClosed(); 314 | } 315 | 316 | /** 317 | * @return True if the device structure is not present (device closed) 318 | * @since 0.8.0 319 | */ 320 | public boolean isClosed() { 321 | return hidDeviceStructure == null; 322 | } 323 | 324 | /** 325 | * Close this device freeing the HidApi resources 326 | * 327 | * @since 0.1.0 328 | */ 329 | public void close() { 330 | if (isClosed()) { 331 | return; 332 | } 333 | 334 | // Prevent further automatic data read attempts 335 | stopDataReadThread(); 336 | 337 | // Close the Hidapi reference 338 | HidApi.close(hidDeviceStructure); 339 | 340 | // Ensure structure is removed from memory and prevent further interaction 341 | hidDeviceStructure = null; 342 | } 343 | 344 | /** 345 | * Set the device handle to be non-blocking 346 | *
    347 | * In non-blocking mode calls to hid_read() will return immediately with a 348 | * value of 0 if there is no data to be read. In blocking mode, hid_read() 349 | * will wait (block) until there is data to read before returning 350 | *
    351 | * Non-blocking can be turned on and off at any time 352 | * 353 | * @param nonBlocking True if non-blocking mode is required 354 | * @since 0.1.0 355 | */ 356 | public void setNonBlocking(boolean nonBlocking) { 357 | if (isClosed()) { 358 | throw new IllegalStateException("Device has not been opened"); 359 | } 360 | HidApi.setNonBlocking(hidDeviceStructure, nonBlocking); 361 | } 362 | 363 | /** 364 | * Read an Input report from a HID device 365 | *
    366 | * Input reports are returned to the host through the INTERRUPT IN endpoint. 367 | * The first byte will contain the Report number if the device uses numbered 368 | * reports 369 | * 370 | * @param data The buffer to read into 371 | * @return The actual number of bytes read and -1 on error. If no packet was 372 | * available to be read and the handle is in non-blocking mode, this 373 | * function returns 0. 374 | * @since 0.1.0 375 | */ 376 | public int read(byte[] data) { 377 | if (isClosed()) { 378 | throw new IllegalStateException("Device has not been opened"); 379 | } 380 | return HidApi.read(hidDeviceStructure, data); 381 | } 382 | 383 | /** 384 | * Read an Input report from a HID device 385 | *
    386 | * Input reports are returned to the host through the INTERRUPT IN endpoint. 387 | * The first byte will contain the Report number if the device uses numbered 388 | * reports 389 | * 390 | * @param amountToRead the number of bytes to read 391 | * @param timeoutMillis The number of milliseconds to wait before giving up 392 | * @return a Byte array of the read data 393 | * @since 0.1.0 394 | */ 395 | public Byte[] read(int amountToRead, int timeoutMillis) { 396 | if (isClosed()) { 397 | throw new IllegalStateException("Device has not been opened"); 398 | } 399 | 400 | byte[] bytes = new byte[amountToRead]; 401 | int read = HidApi.read(hidDeviceStructure, bytes, timeoutMillis); 402 | Byte[] retData = new Byte[read]; 403 | for (int i = 0; i < read; i++) { 404 | retData[i] = bytes[i]; 405 | } 406 | return retData; 407 | } 408 | 409 | /** 410 | * Read an Input report from a HID device 411 | * Input reports are returned to the host through the INTERRUPT IN endpoint. 412 | * The first byte will contain the Report number if the device uses numbered 413 | * reports 414 | * 415 | * @param amountToRead the number of bytes to read. 416 | * @return a Byte array of the read data 417 | * @since 0.1.0 418 | */ 419 | public Byte[] read(int amountToRead) { 420 | if (isClosed()) { 421 | throw new IllegalStateException("Device has not been opened"); 422 | } 423 | 424 | byte[] bytes = new byte[amountToRead]; 425 | int read = HidApi.read(hidDeviceStructure, bytes); 426 | Byte[] retData = new Byte[read]; 427 | for (int i = 0; i < read; i++) { 428 | retData[i] = bytes[i]; 429 | } 430 | return retData; 431 | } 432 | 433 | /** 434 | * Read an Input report (64 bytes) from a HID device with a 1000ms timeout. 435 | *
    436 | * Input reports are returned to the host through the INTERRUPT IN endpoint. 437 | * The first byte will contain the Report number if the device uses numbered 438 | * reports. 439 | * 440 | * @return A Byte array of the read data 441 | * @since 0.1.0 442 | */ 443 | public Byte[] read() { 444 | return read(64, 1000); 445 | } 446 | 447 | /** 448 | * Read an Input report from a HID device with timeout 449 | * 450 | * @param bytes The buffer to read into 451 | * @param timeoutMillis The number of milliseconds to wait before giving up 452 | * @return The actual number of bytes read and -1 on error. If no packet was 453 | * available to be read within the timeout period returns 0. 454 | * @since 0.1.0 455 | */ 456 | public int read(byte[] bytes, int timeoutMillis) { 457 | if (isClosed()) { 458 | throw new IllegalStateException("Device has not been opened"); 459 | } 460 | return HidApi.read(hidDeviceStructure, bytes, timeoutMillis); 461 | 462 | } 463 | 464 | /** 465 | * Read an Input report from a HID device with timeout 466 | * 467 | * @param timeoutMillis The number of milliseconds to wait before giving up 468 | * @return A byte[] of the read data 469 | * @since 0.8.0 470 | */ 471 | public byte[] readAll(int timeoutMillis) { 472 | if (isClosed()) { 473 | throw new IllegalStateException("Device has not been opened"); 474 | } 475 | 476 | // Overall data storage 477 | ByteArrayOutputStream output = new ByteArrayOutputStream(); 478 | 479 | // Prepare to read a single HID packet 480 | boolean morePackets = true; 481 | while (morePackets) { 482 | byte[] packet = new byte[64]; 483 | 484 | // This method will block while awaiting data 485 | int bytesRead = read(packet, timeoutMillis); 486 | 487 | if (bytesRead > 0) { 488 | try { 489 | output.write(packet); 490 | } catch (IOException e) { 491 | morePackets = false; 492 | } 493 | } else { 494 | morePackets = false; 495 | } 496 | } 497 | 498 | return output.toByteArray(); 499 | } 500 | 501 | 502 | /** 503 | * Get a feature report from a HID device 504 | * Under the covers the HID library will set the first byte of data[] to the 505 | * Report ID of the report to be read. Upon return, the first byte will 506 | * still contain the Report ID, and the report data will start in data[1] 507 | *
    508 | * This method handles all the wide string and array manipulation for you 509 | * 510 | * @param data The buffer to contain the report 511 | * @param reportId The report ID (or (byte) 0x00) 512 | * @return The number of bytes read plus one for the report ID (which has 513 | * been removed from the first byte), or -1 on error. 514 | * @since 0.1.0 515 | */ 516 | public int getFeatureReport(byte[] data, byte reportId) { 517 | if (isClosed()) { 518 | throw new IllegalStateException("Device has not been opened"); 519 | } 520 | return HidApi.getFeatureReport(hidDeviceStructure, data, reportId); 521 | } 522 | 523 | /** 524 | * Send a Feature report to the device 525 | *
    526 | * Under the covers, feature reports are sent over the Control endpoint as a 527 | * Set_Report transfer. The first byte of data[] must contain the Report ID. 528 | * For devices which only support a single report, this must be set to 0x0. 529 | * The remaining bytes contain the report data 530 | *
    531 | * Since the Report ID is mandatory, calls to hid_send_feature_report() will 532 | * always contain one more byte than the report contains. For example, if a 533 | * hid report is 16 bytes long, 17 bytes must be passed to 534 | * hid_send_feature_report(): the Report ID (or 0x0, for devices which do 535 | * not use numbered reports), followed by the report data (16 bytes). In 536 | * this example, the length passed in would be 17 537 | *
    538 | * This method handles all the array manipulation for you 539 | * 540 | * @param data The feature report data (will be widened and have the report 541 | * ID pre-pended) 542 | * @param reportId The report ID (or (byte) 0x00) 543 | * @return This function returns the actual number of bytes written and -1 544 | * on error. 545 | * @since 0.1.0 546 | */ 547 | public int sendFeatureReport(byte[] data, byte reportId) { 548 | if (isClosed()) { 549 | throw new IllegalStateException("Device has not been opened"); 550 | } 551 | return HidApi.sendFeatureReport(hidDeviceStructure, data, reportId); 552 | } 553 | 554 | /** 555 | * Get a string from a HID device, based on its string index 556 | * 557 | * @param index The index 558 | * @return The string 559 | * @since 0.1.0 560 | */ 561 | public String getIndexedString(int index) { 562 | if (isClosed()) { 563 | throw new IllegalStateException("Device has not been opened"); 564 | } 565 | return HidApi.getIndexedString(hidDeviceStructure, index); 566 | } 567 | 568 | /** 569 | * Get a report descriptor from a HID device 570 | *
    571 | * A report descriptor provides significant detail on the characteristics of 572 | * an attached device using a defined format. 573 | *
    574 | * You may find these supporting documents helpful: 575 | * HID Usage Tables 1.5 (PDF) 576 | *
    577 | * Device Class Definition HID 1.11 (PDF) 578 | * 579 | * @param buffer The buffer for descriptor (4096 bytes recommended). 580 | * @return A non-negative number of bytes actually copied, or -1 on error. 581 | * @since 0.14.0 hidapi 582 | */ 583 | public int getReportDescriptor(byte[] buffer) { 584 | if (isClosed()) { 585 | throw new IllegalStateException("Device has not been opened"); 586 | } 587 | return HidApi.getReportDescriptor(hidDeviceStructure, buffer, buffer.length); 588 | } 589 | 590 | /** 591 | * Write the message to the HID API without zero byte padding. 592 | *
    593 | * Note that the report ID will be prefixed to the HID packet as per HID rules. 594 | * 595 | * @param message The message 596 | * @param packetLength The packet length 597 | * @param reportId The report ID (will be prefixed to the HID packet) 598 | * @return The number of bytes written (including report ID), or -1 if an error occurs 599 | * @since 0.1.0 600 | */ 601 | public int write(byte[] message, int packetLength, byte reportId) { 602 | if (isClosed()) { 603 | throw new IllegalStateException("Device has not been opened"); 604 | } 605 | return write(message, packetLength, reportId, false); 606 | } 607 | 608 | /** 609 | * Write the message to the HID API with optional zero byte padding to packet length. 610 | *
    611 | * Note that the report ID will be prefixed to the HID packet as per HID rules. 612 | * 613 | * @param message The message 614 | * @param packetLength The packet length 615 | * @param reportId The report ID 616 | * @param applyPadding True if the message should be filled with zero bytes to the packet length 617 | * @return The number of bytes written (including report ID), or -1 if an error occurs 618 | * @since 0.8.0 619 | */ 620 | public int write(byte[] message, int packetLength, byte reportId, boolean applyPadding) { 621 | if (isClosed()) { 622 | throw new IllegalStateException("Device has not been opened"); 623 | } 624 | 625 | if (applyPadding) { 626 | message = Arrays.copyOf(message, packetLength + 1); 627 | } 628 | 629 | int result = HidApi.write(hidDeviceStructure, message, packetLength, reportId); 630 | // Update HID manager 631 | hidDeviceManager.afterDeviceWrite(); 632 | return result; 633 | 634 | } 635 | 636 | /** 637 | * @return The last error message from HID API 638 | * @since 0.1.0 639 | */ 640 | public String getLastErrorMessage() { 641 | return HidApi.getLastErrorMessage(hidDeviceStructure); 642 | } 643 | 644 | /** 645 | * @param vendorId The vendor ID 646 | * @param productId The product ID 647 | * @param serialNumber The serial number 648 | * @return True if the device matches the given the combination with vendorId, productId being zero acting as a wildcard 649 | * @since 0.1.0 650 | */ 651 | public boolean isVidPidSerial(int vendorId, int productId, String serialNumber) { 652 | if (serialNumber == null) 653 | return (vendorId == 0 || this.vendorId == vendorId) 654 | && (productId == 0 || this.productId == productId); 655 | else 656 | return (vendorId == 0 || this.vendorId == vendorId) 657 | && (productId == 0 || this.productId == productId) 658 | && (this.serialNumber.equals(serialNumber)); 659 | } 660 | 661 | @Override 662 | public boolean equals(Object o) { 663 | if (this == o) return true; 664 | if (o == null || getClass() != o.getClass()) return false; 665 | 666 | HidDevice hidDevice = (HidDevice) o; 667 | 668 | return path.equals(hidDevice.path); 669 | 670 | } 671 | 672 | @Override 673 | public int hashCode() { 674 | return path.hashCode(); 675 | } 676 | 677 | @Override 678 | public String toString() { 679 | return "HidDevice [path=" + path 680 | + ", vendorId=0x" + Integer.toHexString(vendorId) 681 | + ", productId=0x" + Integer.toHexString(productId) 682 | + ", serialNumber=" + serialNumber 683 | + ", releaseNumber=0x" + Integer.toHexString(releaseNumber) 684 | + ", manufacturer=" + manufacturer 685 | + ", product=" + product 686 | + ", usagePage=0x" + Integer.toHexString(usagePage) 687 | + ", usage=0x" + Integer.toHexString(usage) 688 | + ", interfaceNumber=" + interfaceNumber 689 | + "]"; 690 | } 691 | 692 | } 693 | --------------------------------------------------------------------------------