diff --git a/ShimmerDriver/build.gradle b/ShimmerDriver/build.gradle index 0e6cd25f..0498cc18 100644 --- a/ShimmerDriver/build.gradle +++ b/ShimmerDriver/build.gradle @@ -136,5 +136,7 @@ dependencies { // https://mvnrepository.com/artifact/org.bouncycastle/bcprov-jdk15on implementation("org.bouncycastle:bcprov-jdk15on:1.61") + implementation 'net.java.dev.jna:jna:5.18.1' + implementation 'net.java.dev.jna:jna-platform:5.18.1' } diff --git a/ShimmerDriver/src/main/java/com/shimmerresearch/usb/PlatformHwManagerUsbExample.java b/ShimmerDriver/src/main/java/com/shimmerresearch/usb/PlatformHwManagerUsbExample.java new file mode 100644 index 00000000..f455166a --- /dev/null +++ b/ShimmerDriver/src/main/java/com/shimmerresearch/usb/PlatformHwManagerUsbExample.java @@ -0,0 +1,68 @@ +package com.shimmerresearch.usb; + +public class PlatformHwManagerUsbExample { + + private UsbDebugListener mUSBDebugListener; + + public void startWindowsUsbListener() { + + String os = System.getProperty("os.name").toLowerCase(); + + if (os.contains("win")) { + System.out.println("Running on Windows"); + mUSBDebugListener = new UsbDebugListenerWindows(new UsbDockChangeListener() { + @Override + public void onUsbDeviceConnected() { + System.out.println("[PLATFORM] USB connect event received"); + + } + + @Override + public void onUsbDeviceDisconnected() { + System.out.println("[PLATFORM] USB disconnect event received"); + + } + }); + + } else if (os.contains("mac")) { + System.out.println("Running on macOS"); + mUSBDebugListener = new UsbDebugListenerMacOS(new UsbDockChangeListener() { + @Override + public void onUsbDeviceConnected() { + System.out.println("[PLATFORM] USB connect event received"); + + } + + @Override + public void onUsbDeviceDisconnected() { + System.out.println("[PLATFORM] USB disconnect event received"); + + } + }); + + } else { + System.out.println("Other OS: " + os); + } + + + mUSBDebugListener.start(); + } + + public void stopWindowsUsbListener() { + if (mUSBDebugListener != null) { + mUSBDebugListener.stop(); + mUSBDebugListener = null; + } + } + + public static void main(String[] args) throws Exception { + PlatformHwManagerUsbExample example = new PlatformHwManagerUsbExample(); + example.startWindowsUsbListener(); + + System.out.println("[DEBUG] Press Enter to stop."); + System.in.read(); + + example.stopWindowsUsbListener(); + System.out.println("[DEBUG] Exiting."); + } +} \ No newline at end of file diff --git a/ShimmerDriver/src/main/java/com/shimmerresearch/usb/UsbDebugListener.java b/ShimmerDriver/src/main/java/com/shimmerresearch/usb/UsbDebugListener.java new file mode 100644 index 00000000..2e5bc4a6 --- /dev/null +++ b/ShimmerDriver/src/main/java/com/shimmerresearch/usb/UsbDebugListener.java @@ -0,0 +1,10 @@ +package com.shimmerresearch.usb; + +public abstract class UsbDebugListener { + + public abstract void start(); + public abstract void stop(); + protected UsbDockChangeListener listener = null; + + +} diff --git a/ShimmerDriver/src/main/java/com/shimmerresearch/usb/UsbDebugListenerMacOS.java b/ShimmerDriver/src/main/java/com/shimmerresearch/usb/UsbDebugListenerMacOS.java new file mode 100644 index 00000000..70736eb4 --- /dev/null +++ b/ShimmerDriver/src/main/java/com/shimmerresearch/usb/UsbDebugListenerMacOS.java @@ -0,0 +1,253 @@ +package com.shimmerresearch.usb; + +import com.sun.jna.*; +import com.sun.jna.ptr.IntByReference; + +public class UsbDebugListenerMacOS extends UsbDebugListener { + + private static final String kIOUSBDeviceClassName = "IOUSBDevice"; + private static final String kIOFirstMatchNotification = "IOServiceFirstMatch"; + private static final String kIOTerminatedNotification = "IOServiceTerminate"; + + private volatile boolean running = false; + private Thread listenerThread; + private final UsbDockChangeListener listener; + + private IOKit.IONotificationPortRef notificationPort; + private CoreFoundation.CFRunLoopRef runLoop; + + private int addedIterator = 0; + private int removedIterator = 0; + private boolean suppressInitialDeviceScan = true; + + public UsbDebugListenerMacOS(UsbDockChangeListener listener) { + this.listener = listener; + } + + public synchronized void start() { + if (running) { + System.out.println("[DEBUG] macOS USB listener already running."); + return; + } + + running = true; + + listenerThread = new Thread(() -> { + try { + runNotificationLoop(); + } catch (Exception e) { + e.printStackTrace(); + } finally { + running = false; + cleanup(); + System.out.println("[DEBUG] macOS USB listener thread finished."); + } + }, "UsbDebugListenerMacOS"); + + listenerThread.setDaemon(true); + listenerThread.start(); + } + + public synchronized void stop() { + if (!running) { + return; + } + + System.out.println("[DEBUG] Stopping macOS USB listener..."); + running = false; + + if (runLoop != null) { + CoreFoundation.INSTANCE.CFRunLoopStop(runLoop); + } + } + + public boolean isRunning() { + return running; + } + + private void runNotificationLoop() { + System.out.println("[DEBUG] Starting macOS USB debug listener..."); + + runLoop = CoreFoundation.INSTANCE.CFRunLoopGetCurrent(); + if (runLoop == null) { + throw new RuntimeException("CFRunLoopGetCurrent returned null"); + } + + int mainPort = IOKit.INSTANCE.IOMainPort(0, new IntByReference()); + System.out.println("[DEBUG] IOMainPort result = " + mainPort); + + notificationPort = IOKit.INSTANCE.IONotificationPortCreate(mainPort); + if (notificationPort == null) { + throw new RuntimeException("IONotificationPortCreate failed"); + } + + Pointer runLoopSource = IOKit.INSTANCE.IONotificationPortGetRunLoopSource(notificationPort); + if (runLoopSource == null) { + throw new RuntimeException("IONotificationPortGetRunLoopSource returned null"); + } + + if (CoreFoundation.kCFRunLoopDefaultMode == null) { + throw new RuntimeException("kCFRunLoopDefaultMode is null"); + } + + CoreFoundation.INSTANCE.CFRunLoopAddSource( + runLoop, + runLoopSource, + CoreFoundation.kCFRunLoopDefaultMode + ); + + Pointer matchingDictAdd = IOKit.INSTANCE.IOServiceMatching(kIOUSBDeviceClassName); + if (matchingDictAdd == null) { + throw new RuntimeException("IOServiceMatching(IOUSBDevice) failed for add"); + } + + Pointer matchingDictRemove = IOKit.INSTANCE.IOServiceMatching(kIOUSBDeviceClassName); + if (matchingDictRemove == null) { + throw new RuntimeException("IOServiceMatching(IOUSBDevice) failed for remove"); + } + + IOKit.IOServiceMatchingCallback addCallback = (refCon, iterator) -> { + System.out.println("[DEBUG] Device arrival callback triggered"); + drainIterator(iterator, true, false); + }; + + IOKit.IOServiceMatchingCallback removeCallback = (refCon, iterator) -> { + System.out.println("[DEBUG] Device removal callback triggered"); + drainIterator(iterator, false, false); + }; + + IntByReference addIterRef = new IntByReference(); + int kr = IOKit.INSTANCE.IOServiceAddMatchingNotification( + notificationPort, + kIOFirstMatchNotification, + matchingDictAdd, + addCallback, + null, + addIterRef + ); + System.out.println("[DEBUG] IOServiceAddMatchingNotification(add) = " + kr); + if (kr != 0) { + throw new RuntimeException("IOServiceAddMatchingNotification(add) failed: " + kr); + } + addedIterator = addIterRef.getValue(); + + IntByReference removeIterRef = new IntByReference(); + kr = IOKit.INSTANCE.IOServiceAddMatchingNotification( + notificationPort, + kIOTerminatedNotification, + matchingDictRemove, + removeCallback, + null, + removeIterRef + ); + System.out.println("[DEBUG] IOServiceAddMatchingNotification(remove) = " + kr); + if (kr != 0) { + throw new RuntimeException("IOServiceAddMatchingNotification(remove) failed: " + kr); + } + removedIterator = removeIterRef.getValue(); + + // Drain existing devices once to arm notifications. + // Usually you do not want currently-connected devices to be treated as "new arrivals". + drainIterator(addedIterator, true, suppressInitialDeviceScan); + drainIterator(removedIterator, false, true); + + System.out.println("[DEBUG] Entering CFRunLoop..."); + CoreFoundation.INSTANCE.CFRunLoopRun(); + System.out.println("[DEBUG] CFRunLoop exited."); + } + + private void drainIterator(int iterator, boolean connected, boolean suppressEvents) { + while (true) { + int service = IOKit.INSTANCE.IOIteratorNext(iterator); + if (service == 0) { + break; + } + + try { + if (!suppressEvents) { + if (connected) { + System.out.println("[EVENT] Device connected"); + if (listener != null) { + listener.onUsbDeviceConnected(); + } + } else { + System.out.println("[EVENT] Device disconnected"); + if (listener != null) { + listener.onUsbDeviceDisconnected(); + } + } + } else { + System.out.println("[DEBUG] Suppressed initial device event"); + } + } finally { + IOKit.INSTANCE.IOObjectRelease(service); + } + } + } + + private void cleanup() { + if (addedIterator != 0) { + IOKit.INSTANCE.IOObjectRelease(addedIterator); + addedIterator = 0; + } + + if (removedIterator != 0) { + IOKit.INSTANCE.IOObjectRelease(removedIterator); + removedIterator = 0; + } + + if (notificationPort != null) { + IOKit.INSTANCE.IONotificationPortDestroy(notificationPort); + notificationPort = null; + } + + runLoop = null; + System.out.println("[DEBUG] macOS USB listener cleaned up."); + } + + interface CoreFoundation extends Library { + CoreFoundation INSTANCE = Native.load("CoreFoundation", CoreFoundation.class); + + class CFRunLoopRef extends PointerType {} + + Pointer kCFRunLoopDefaultMode = NativeLibrary + .getInstance("CoreFoundation") + .getGlobalVariableAddress("kCFRunLoopDefaultMode") + .getPointer(0); + + CFRunLoopRef CFRunLoopGetCurrent(); + void CFRunLoopRun(); + void CFRunLoopStop(CFRunLoopRef rl); + void CFRunLoopAddSource(CFRunLoopRef rl, Pointer source, Pointer mode); + } + + interface IOKit extends Library { + IOKit INSTANCE = Native.load("IOKit", IOKit.class); + + class IONotificationPortRef extends PointerType {} + + interface IOServiceMatchingCallback extends Callback { + void invoke(Pointer refCon, int iterator); + } + + int IOMainPort(int bootstrapPort, IntByReference mainPort); + + IONotificationPortRef IONotificationPortCreate(int masterPort); + Pointer IONotificationPortGetRunLoopSource(IONotificationPortRef notifyPort); + void IONotificationPortDestroy(IONotificationPortRef notifyPort); + + Pointer IOServiceMatching(String name); + + int IOServiceAddMatchingNotification( + IONotificationPortRef notifyPort, + String notificationType, + Pointer matchingDictionary, + IOServiceMatchingCallback callback, + Pointer refCon, + IntByReference notificationIterator + ); + + int IOIteratorNext(int iterator); + int IOObjectRelease(int object); + } +} \ No newline at end of file diff --git a/ShimmerDriver/src/main/java/com/shimmerresearch/usb/UsbDebugListenerWindows.java b/ShimmerDriver/src/main/java/com/shimmerresearch/usb/UsbDebugListenerWindows.java new file mode 100644 index 00000000..86703222 --- /dev/null +++ b/ShimmerDriver/src/main/java/com/shimmerresearch/usb/UsbDebugListenerWindows.java @@ -0,0 +1,177 @@ +package com.shimmerresearch.usb; + +import com.sun.jna.platform.win32.DBT; +import com.sun.jna.platform.win32.Kernel32; +import com.sun.jna.platform.win32.User32; +import com.sun.jna.platform.win32.WinDef.ATOM; +import com.sun.jna.platform.win32.WinDef.HMODULE; +import com.sun.jna.platform.win32.WinDef.HWND; +import com.sun.jna.platform.win32.WinDef.LPARAM; +import com.sun.jna.platform.win32.WinDef.LRESULT; +import com.sun.jna.platform.win32.WinDef.WPARAM; +import com.sun.jna.platform.win32.WinUser; +import com.sun.jna.platform.win32.WinUser.MSG; +import com.sun.jna.platform.win32.WinUser.WNDCLASSEX; +import com.sun.jna.platform.win32.WinUser.WindowProc; + +public class UsbDebugListenerWindows extends UsbDebugListener { + + private volatile boolean running = false; + private HWND hwnd; + private Thread listenerThread; + private final UsbDockChangeListener listener; + + public UsbDebugListenerWindows(UsbDockChangeListener listener) { + this.listener = listener; + } + + public synchronized void start() { + if (running) { + System.out.println("[DEBUG] USB listener already running."); + return; + } + + running = true; + + listenerThread = new Thread(() -> { + try { + runMessageLoop(); + } catch (Exception e) { + e.printStackTrace(); + } finally { + running = false; + System.out.println("[DEBUG] USB listener thread finished."); + } + }, "UsbDebugListenerWindows"); + + listenerThread.setDaemon(true); + listenerThread.start(); + } + + public synchronized void stop() { + if (!running) { + return; + } + + running = false; + + if (hwnd != null) { + System.out.println("[DEBUG] Posting WM_CLOSE..."); + User32.INSTANCE.PostMessage(hwnd, WinUser.WM_CLOSE, null, null); + } + } + + public boolean isRunning() { + return running; + } + + private void runMessageLoop() { + System.out.println("[DEBUG] Starting USB debug listener..."); + + HMODULE hInstance = Kernel32.INSTANCE.GetModuleHandle(""); + System.out.println("[DEBUG] hInstance = " + hInstance); + + String className = "UsbDebugListenerWindow_" + System.currentTimeMillis(); + + WindowProc wndProc = new WindowProc() { + @Override + public LRESULT callback(HWND hWnd, int uMsg, WPARAM wParam, LPARAM lParam) { + if (uMsg == WinUser.WM_CREATE) { + System.out.println("[DEBUG] WM_CREATE received"); + } + else if (uMsg == WinUser.WM_DEVICECHANGE) { + int eventType = wParam.intValue(); + System.out.println("[DEBUG] WM_DEVICECHANGE received, wParam=" + eventType + ", lParam=" + lParam); + + if (eventType == DBT.DBT_DEVICEARRIVAL) { + System.out.println("[EVENT] Device connected"); + if (listener != null) { + listener.onUsbDeviceConnected(); + } + } + else if (eventType == DBT.DBT_DEVICEREMOVECOMPLETE) { + System.out.println("[EVENT] Device disconnected"); + if (listener != null) { + listener.onUsbDeviceDisconnected(); + } + } + else { + System.out.println("[DEBUG] Other device event type: " + eventType); + } + return new LRESULT(1); + } + else if (uMsg == WinUser.WM_CLOSE) { + System.out.println("[DEBUG] WM_CLOSE received"); + User32.INSTANCE.DestroyWindow(hWnd); + return new LRESULT(0); + } + else if (uMsg == WinUser.WM_DESTROY) { + System.out.println("[DEBUG] WM_DESTROY received"); + User32.INSTANCE.PostQuitMessage(0); + return new LRESULT(0); + } + + return User32.INSTANCE.DefWindowProc(hWnd, uMsg, wParam, lParam); + } + }; + + WNDCLASSEX wc = new WNDCLASSEX(); + wc.cbSize = wc.size(); + wc.hInstance = hInstance; + wc.lpfnWndProc = wndProc; + wc.lpszClassName = className; + + System.out.println("[DEBUG] Registering window class..."); + ATOM atom = User32.INSTANCE.RegisterClassEx(wc); + int registerErr = Kernel32.INSTANCE.GetLastError(); + System.out.println("[DEBUG] RegisterClassEx atom = " + atom + ", lastError = " + registerErr); + + if (atom == null || atom.intValue() == 0) { + throw new RuntimeException("RegisterClassEx failed, GetLastError=" + registerErr); + } + + System.out.println("[DEBUG] Creating hidden window..."); + hwnd = User32.INSTANCE.CreateWindowEx( + 0, + className, + "USB Debug Hidden Window", + 0, + 0, 0, 0, 0, + null, + null, + hInstance, + null + ); + + int createErr = Kernel32.INSTANCE.GetLastError(); + System.out.println("[DEBUG] hwnd = " + hwnd + ", lastError = " + createErr); + + if (hwnd == null) { + throw new RuntimeException("CreateWindowEx failed, GetLastError=" + createErr); + } + + System.out.println("[DEBUG] Entering message loop..."); + MSG msg = new MSG(); + + while (running) { + int result = User32.INSTANCE.GetMessage(msg, null, 0, 0); + System.out.println("[DEBUG] GetMessage returned: " + result); + + if (result == -1) { + int err = Kernel32.INSTANCE.GetLastError(); + throw new RuntimeException("GetMessage failed, GetLastError=" + err); + } + else if (result == 0) { + System.out.println("[DEBUG] WM_QUIT received, exiting loop."); + break; + } + else { + System.out.println("[DEBUG] Dispatching message: " + msg.message); + User32.INSTANCE.TranslateMessage(msg); + User32.INSTANCE.DispatchMessage(msg); + } + } + + System.out.println("[DEBUG] Listener stopped."); + } +} \ No newline at end of file diff --git a/ShimmerDriver/src/main/java/com/shimmerresearch/usb/UsbDockChangeListener.java b/ShimmerDriver/src/main/java/com/shimmerresearch/usb/UsbDockChangeListener.java new file mode 100644 index 00000000..b2f1e56a --- /dev/null +++ b/ShimmerDriver/src/main/java/com/shimmerresearch/usb/UsbDockChangeListener.java @@ -0,0 +1,6 @@ +package com.shimmerresearch.usb; + +public interface UsbDockChangeListener { + void onUsbDeviceConnected(); + void onUsbDeviceDisconnected(); +} \ No newline at end of file