Skip to content

Instantly share code, notes, and snippets.

@tresf
Last active June 6, 2024 18:52
Show Gist options
  • Save tresf/8c1c2ede83a38dff59e35a5b9e163561 to your computer and use it in GitHub Desktop.
Save tresf/8c1c2ede83a38dff59e35a5b9e163561 to your computer and use it in GitHub Desktop.
Thread safe wrapping of hid4java calls
package org.hid4java.jna;
import com.sun.jna.Native;
import com.sun.jna.Pointer;
import com.sun.jna.WString;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class DarwinHidApiLibrary implements HidApiLibrary {
// WARNING: This is NOT a JNA interface, but rather a wrapped instance
public static HidApiLibrary INSTANCE = new DarwinHidApiLibrary();
private static RealDarwinHidApiLibrary REAL_INSTANCE = Native.load("hidapi", RealDarwinHidApiLibrary.class);
/**
* Changes the behavior of all further calls to {@link #hid_open(short, short, WString)} or {@link #hid_open_path(String)}.
* <br>
* All devices opened by HIDAPI with {@link #hid_open(short, short, WString)} or {@link #hid_open_path(String)}
* are opened in exclusive mode per default.
* <br>
* Calling this function before {@link #hid_init()} or after {@link #hid_exit()} has no effect.
*
* @since hidapi 0.12.0
* @param openExclusive When set to 0 - all further devices will be opened in non-exclusive mode.
* Otherwise - all further devices will be opened in exclusive mode.
*/
// Wrap all macOS API calls into a single thread, per https://github.com/libusb/hidapi/issues/666
private static final ExecutorService executor = Executors.newSingleThreadExecutor();
private interface RealDarwinHidApiLibrary extends HidrawHidApiLibrary {
/**
* Changes the behavior of all further calls to {@link #hid_open(short, short, WString)} or {@link #hid_open_path(String)}.
* <p>
* All devices opened by HIDAPI with {@link #hid_open(short, short, WString)} or {@link #hid_open_path(String)}
* are opened in exclusive mode per default.
* <p>
* Calling this function before {@link #hid_init()} or after {@link #hid_exit()} has no effect.
*
* @since hidapi 0.12.0
* @param openExclusive When set to 0 - all further devices will be opened in non-exclusive mode.
* Otherwise - all further devices will be opened in exclusive mode.
*/
void hid_darwin_set_open_exclusive(int openExclusive);
}
@Override
public void hid_init() {
try {
executor.submit(REAL_INSTANCE::hid_init).get();
} catch (InterruptedException | ExecutionException e) {
throw new RuntimeException(e);
}
}
@Override
public void hid_exit() {
try {
executor.submit(REAL_INSTANCE::hid_exit).get();
} catch (InterruptedException | ExecutionException e) {
throw new RuntimeException(e);
}
}
@Override
public Pointer hid_open(short vendor_id, short product_id, WString serial_number) {
try {
return executor.submit(() -> REAL_INSTANCE.hid_open(vendor_id, product_id, serial_number)).get();
} catch (InterruptedException | ExecutionException e) {
throw new RuntimeException(e);
}
}
@Override
public void hid_close(Pointer device) {
try {
executor.submit(() -> REAL_INSTANCE.hid_close(device)).get();
} catch (InterruptedException | ExecutionException e) {
throw new RuntimeException(e);
}
}
@Override
public Pointer hid_error(Pointer device) {
try {
return executor.submit(() -> REAL_INSTANCE.hid_error(device)).get();
} catch (InterruptedException | ExecutionException e) {
throw new RuntimeException(e);
}
}
@Override
public int hid_read(Pointer device, WideStringBuffer.ByReference bytes, int length) {
try {
return executor.submit(() -> REAL_INSTANCE.hid_read(device, bytes, length)).get();
} catch (InterruptedException | ExecutionException e) {
throw new RuntimeException(e);
}
}
@Override
public int hid_read_timeout(Pointer device, WideStringBuffer.ByReference bytes, int length, int timeout) {
try {
return executor.submit(() -> REAL_INSTANCE.hid_read_timeout(device, bytes, length, timeout)).get();
} catch (InterruptedException | ExecutionException e) {
throw new RuntimeException(e);
}
}
@Override
public int hid_write(Pointer device, WideStringBuffer.ByReference data, int len) {
try {
return executor.submit(() -> REAL_INSTANCE.hid_write(device, data, len)).get();
} catch (InterruptedException | ExecutionException e) {
throw new RuntimeException(e);
}
}
@Override
public int hid_get_feature_report(Pointer device, WideStringBuffer.ByReference data, int length) {
try {
return executor.submit(() -> REAL_INSTANCE.hid_get_feature_report(device, data, length)).get();
} catch (InterruptedException | ExecutionException e) {
throw new RuntimeException(e);
}
}
@Override
public int hid_send_feature_report(Pointer device, WideStringBuffer.ByReference data, int length) {
try {
return executor.submit(() -> REAL_INSTANCE.hid_send_feature_report(device, data, length)).get();
} catch (InterruptedException | ExecutionException e) {
throw new RuntimeException(e);
}
}
@Override
public int hid_get_indexed_string(Pointer device, int idx, WideStringBuffer.ByReference string, int len) {
try {
return executor.submit(() -> REAL_INSTANCE.hid_get_indexed_string(device, idx, string, len)).get();
} catch (InterruptedException | ExecutionException e) {
throw new RuntimeException(e);
}
}
@Override
public int hid_get_report_descriptor(Pointer device, byte[] buffer, int size) {
try {
return executor.submit(() -> REAL_INSTANCE.hid_get_report_descriptor(device, buffer, size)).get();
} catch (InterruptedException | ExecutionException e) {
throw new RuntimeException(e);
}
}
@Override
public int hid_get_manufacturer_string(Pointer device, WideStringBuffer.ByReference str, int len) {
try {
return executor.submit(() -> REAL_INSTANCE.hid_get_manufacturer_string(device, str, len)).get();
} catch (InterruptedException | ExecutionException e) {
throw new RuntimeException(e);
}
}
@Override
public int hid_get_product_string(Pointer device, WideStringBuffer.ByReference str, int len) {
try {
return executor.submit(() -> REAL_INSTANCE.hid_get_product_string(device, str, len)).get();
} catch (InterruptedException | ExecutionException e) {
throw new RuntimeException(e);
}
}
@Override
public int hid_get_serial_number_string(Pointer device, WideStringBuffer.ByReference str, int len) {
try {
return executor.submit(() -> REAL_INSTANCE.hid_get_serial_number_string(device, str, len)).get();
} catch (InterruptedException | ExecutionException e) {
throw new RuntimeException(e);
}
}
@Override
public int hid_set_nonblocking(Pointer device, int nonblock) {
try {
return executor.submit(() -> REAL_INSTANCE.hid_set_nonblocking(device, nonblock)).get();
} catch (InterruptedException | ExecutionException e) {
throw new RuntimeException(e);
}
}
@Override
public HidDeviceInfoStructure hid_enumerate(short vendor_id, short product_id) {
try {
return executor.submit(() -> REAL_INSTANCE.hid_enumerate(vendor_id, product_id)).get();
} catch (InterruptedException | ExecutionException e) {
throw new RuntimeException(e);
}
}
@Override
public void hid_free_enumeration(Pointer devs) {
try {
executor.submit(() -> REAL_INSTANCE.hid_free_enumeration(devs)).get();
} catch (InterruptedException | ExecutionException e) {
throw new RuntimeException(e);
}
}
@Override
public Pointer hid_open_path(String path) {
try {
return executor.submit(() -> REAL_INSTANCE.hid_open_path(path)).get();
} catch (InterruptedException | ExecutionException e) {
throw new RuntimeException(e);
}
}
@Override
public String hid_version_str() {
try {
return executor.submit(REAL_INSTANCE::hid_version_str).get();
} catch (InterruptedException | ExecutionException e) {
throw new RuntimeException(e);
}
}
public void hid_darwin_set_open_exclusive(int openExclusive) {
try {
executor.submit(() -> REAL_INSTANCE.hid_darwin_set_open_exclusive(openExclusive)).get();
} catch (InterruptedException | ExecutionException e) {
throw new RuntimeException(e);
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment