From 98225af2a51a9846194f5e67642d08eaf704037d Mon Sep 17 00:00:00 2001 From: Vzor- Date: Thu, 6 Jun 2024 20:32:16 -0400 Subject: [PATCH] MacOS thread-crash fix --- .../org/hid4java/jna/DarwinHidApiLibrary.java | 174 ++++++++++++++++-- 1 file changed, 159 insertions(+), 15 deletions(-) diff --git a/src/main/java/org/hid4java/jna/DarwinHidApiLibrary.java b/src/main/java/org/hid4java/jna/DarwinHidApiLibrary.java index 7998fc1..9c0d078 100644 --- a/src/main/java/org/hid4java/jna/DarwinHidApiLibrary.java +++ b/src/main/java/org/hid4java/jna/DarwinHidApiLibrary.java @@ -1,24 +1,168 @@ package org.hid4java.jna; import com.sun.jna.Native; +import com.sun.jna.Pointer; import com.sun.jna.WString; -public interface DarwinHidApiLibrary extends HidrawHidApiLibrary { +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; - DarwinHidApiLibrary INSTANCE = Native.load("hidapi", DarwinHidApiLibrary.class); +/** + * This class provides a management layer for thread-sensitive operations within the HIDAPI library on macOS. Due to + * threading constraints in the underlying native library, certain functions must be called from the same thread. This + * includes {@code hid_init()} and {@code hid_exit()}, which must be invoked on a consistent thread to avoid runtime + * failures. See hidapi #666 + *
+ * Additionally, certain functions such as {@code hid_open()}, {@code hid_enumerate()}, and {@code hid_open_path()} may + * implicitly call {@code hid_init()} if it has not been previously called. To prevent potential crashes due to these + * implicit calls, these functions are also managed in the same way. + *
+ * This class utilizes a single-thread executor to wrap the thread-sensitive calls onto a single thread. Despite using + * an executor, the operations are executed in a blocking manner, ensuring they complete before returning. + */ +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 final RealDarwinHidApiLibrary REAL_INSTANCE = Native.load("hidapi", RealDarwinHidApiLibrary.class); + private static final ExecutorService executor = Executors.newSingleThreadExecutor(); - /** - * Changes the behavior of all further calls to {@link #hid_open(short, short, WString)} or {@link #hid_open_path(String)}. - *
- * 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. - *
- * 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); + 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)}. + *
+ * 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. + *
+ * 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) { + REAL_INSTANCE.hid_close(device); + } + + @Override + public Pointer hid_error(Pointer device) { + return REAL_INSTANCE.hid_error(device); + } + + @Override + public int hid_read(Pointer device, WideStringBuffer.ByReference bytes, int length) { + return REAL_INSTANCE.hid_read(device, bytes, length); + } + + @Override + public int hid_read_timeout(Pointer device, WideStringBuffer.ByReference bytes, int length, int timeout) { + return REAL_INSTANCE.hid_read_timeout(device, bytes, length, timeout); + } + + @Override + public int hid_write(Pointer device, WideStringBuffer.ByReference data, int len) { + return REAL_INSTANCE.hid_write(device, data, len); + } + + @Override + public int hid_get_feature_report(Pointer device, WideStringBuffer.ByReference data, int length) { + return REAL_INSTANCE.hid_get_feature_report(device, data, length); + } + + @Override + public int hid_send_feature_report(Pointer device, WideStringBuffer.ByReference data, int length) { + return REAL_INSTANCE.hid_send_feature_report(device, data, length); + } + + @Override + public int hid_get_indexed_string(Pointer device, int idx, WideStringBuffer.ByReference string, int len) { + return REAL_INSTANCE.hid_get_indexed_string(device, idx, string, len); + } + + @Override + public int hid_get_report_descriptor(Pointer device, byte[] buffer, int size) { + return REAL_INSTANCE.hid_get_report_descriptor(device, buffer, size); + } + + @Override + public int hid_get_manufacturer_string(Pointer device, WideStringBuffer.ByReference str, int len) { + return REAL_INSTANCE.hid_get_manufacturer_string(device, str, len); + } + + @Override + public int hid_get_product_string(Pointer device, WideStringBuffer.ByReference str, int len) { + return REAL_INSTANCE.hid_get_product_string(device, str, len); + } + + @Override + public int hid_get_serial_number_string(Pointer device, WideStringBuffer.ByReference str, int len) { + return REAL_INSTANCE.hid_get_serial_number_string(device, str, len); + } + + @Override + public int hid_set_nonblocking(Pointer device, int nonblock) { + return REAL_INSTANCE.hid_set_nonblocking(device, nonblock); + } + + @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) { + REAL_INSTANCE.hid_free_enumeration(devs); + } + + @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() { + return REAL_INSTANCE.hid_version_str(); + } + + public void hid_darwin_set_open_exclusive(int openExclusive) { + REAL_INSTANCE.hid_darwin_set_open_exclusive(openExclusive); + } }