Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions ShimmerDriver/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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'
}

Original file line number Diff line number Diff line change
@@ -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.");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.shimmerresearch.usb;

public abstract class UsbDebugListener {

public abstract void start();
public abstract void stop();
protected UsbDockChangeListener listener = null;


}
Original file line number Diff line number Diff line change
@@ -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);
}
}
Loading
Loading