Skip to content

Commit 6d4df29

Browse files
authored
Merge pull request #274 from ShimmerResearch/DEV-698
DEV-698 Shimmer3R USB C Improvements (Consensys UI/Backend Update to Speedup Detection)
2 parents 97cce69 + efce839 commit 6d4df29

7 files changed

Lines changed: 518 additions & 1 deletion

File tree

ShimmerDriver/build.gradle

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,5 +136,7 @@ dependencies {
136136

137137
// https://mvnrepository.com/artifact/org.bouncycastle/bcprov-jdk15on
138138
implementation("org.bouncycastle:bcprov-jdk15on:1.61")
139+
implementation 'net.java.dev.jna:jna:5.18.1'
140+
implementation 'net.java.dev.jna:jna-platform:5.18.1'
139141
}
140142

ShimmerDriver/src/main/java/com/shimmerresearch/driverUtilities/UtilShimmer.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1408,5 +1408,7 @@ public static boolean isOsMac() {
14081408
return System.getProperty("os.name").toLowerCase().contains("mac");
14091409
}
14101410

1411-
1411+
public static boolean isOsWindows() {
1412+
return System.getProperty("os.name").toLowerCase().contains("win");
1413+
}
14121414
}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
package com.shimmerresearch.usb;
2+
3+
import com.shimmerresearch.driverUtilities.UtilShimmer;
4+
5+
public class PlatformHwManagerUsbExample {
6+
7+
private UsbDebugListener mUSBDebugListener;
8+
9+
public void startWindowsUsbListener() {
10+
if (UtilShimmer.isOsWindows()) {
11+
System.out.println("Running on Windows");
12+
mUSBDebugListener = new UsbDebugListenerWindows(new UsbDockChangeListener() {
13+
@Override
14+
public void onUsbDeviceConnected() {
15+
System.out.println("[PLATFORM] USB connect event received");
16+
17+
}
18+
19+
@Override
20+
public void onUsbDeviceDisconnected() {
21+
System.out.println("[PLATFORM] USB disconnect event received");
22+
23+
}
24+
});
25+
26+
} else if (UtilShimmer.isOsMac()) {
27+
System.out.println("Running on macOS");
28+
mUSBDebugListener = new UsbDebugListenerMacOS(new UsbDockChangeListener() {
29+
@Override
30+
public void onUsbDeviceConnected() {
31+
System.out.println("[PLATFORM] USB connect event received");
32+
33+
}
34+
35+
@Override
36+
public void onUsbDeviceDisconnected() {
37+
System.out.println("[PLATFORM] USB disconnect event received");
38+
39+
}
40+
});
41+
42+
} else {
43+
System.out.println("Other OS: " + System.getProperty("os.name"));
44+
}
45+
46+
47+
mUSBDebugListener.start();
48+
}
49+
50+
public void stopWindowsUsbListener() {
51+
if (mUSBDebugListener != null) {
52+
mUSBDebugListener.stop();
53+
mUSBDebugListener = null;
54+
}
55+
}
56+
57+
public static void main(String[] args) throws Exception {
58+
PlatformHwManagerUsbExample example = new PlatformHwManagerUsbExample();
59+
example.startWindowsUsbListener();
60+
61+
System.out.println("[DEBUG] Press Enter to stop.");
62+
System.in.read();
63+
64+
example.stopWindowsUsbListener();
65+
System.out.println("[DEBUG] Exiting.");
66+
}
67+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package com.shimmerresearch.usb;
2+
3+
public abstract class UsbDebugListener {
4+
5+
public abstract void start();
6+
public abstract void stop();
7+
protected UsbDockChangeListener listener = null;
8+
9+
10+
}
Lines changed: 253 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,253 @@
1+
package com.shimmerresearch.usb;
2+
3+
import com.sun.jna.*;
4+
import com.sun.jna.ptr.IntByReference;
5+
6+
public class UsbDebugListenerMacOS extends UsbDebugListener {
7+
8+
private static final String kIOUSBDeviceClassName = "IOUSBDevice";
9+
private static final String kIOFirstMatchNotification = "IOServiceFirstMatch";
10+
private static final String kIOTerminatedNotification = "IOServiceTerminate";
11+
12+
private volatile boolean running = false;
13+
private Thread listenerThread;
14+
private final UsbDockChangeListener listener;
15+
16+
private IOKit.IONotificationPortRef notificationPort;
17+
private CoreFoundation.CFRunLoopRef runLoop;
18+
19+
private int addedIterator = 0;
20+
private int removedIterator = 0;
21+
private boolean suppressInitialDeviceScan = true;
22+
23+
public UsbDebugListenerMacOS(UsbDockChangeListener listener) {
24+
this.listener = listener;
25+
}
26+
27+
public synchronized void start() {
28+
if (running) {
29+
System.out.println("[DEBUG] macOS USB listener already running.");
30+
return;
31+
}
32+
33+
running = true;
34+
35+
listenerThread = new Thread(() -> {
36+
try {
37+
runNotificationLoop();
38+
} catch (Exception e) {
39+
e.printStackTrace();
40+
} finally {
41+
running = false;
42+
cleanup();
43+
System.out.println("[DEBUG] macOS USB listener thread finished.");
44+
}
45+
}, "UsbDebugListenerMacOS");
46+
47+
listenerThread.setDaemon(true);
48+
listenerThread.start();
49+
}
50+
51+
public synchronized void stop() {
52+
if (!running) {
53+
return;
54+
}
55+
56+
System.out.println("[DEBUG] Stopping macOS USB listener...");
57+
running = false;
58+
59+
if (runLoop != null) {
60+
CoreFoundation.INSTANCE.CFRunLoopStop(runLoop);
61+
}
62+
}
63+
64+
public boolean isRunning() {
65+
return running;
66+
}
67+
68+
private void runNotificationLoop() {
69+
System.out.println("[DEBUG] Starting macOS USB debug listener...");
70+
71+
runLoop = CoreFoundation.INSTANCE.CFRunLoopGetCurrent();
72+
if (runLoop == null) {
73+
throw new RuntimeException("CFRunLoopGetCurrent returned null");
74+
}
75+
76+
int mainPort = IOKit.INSTANCE.IOMainPort(0, new IntByReference());
77+
System.out.println("[DEBUG] IOMainPort result = " + mainPort);
78+
79+
notificationPort = IOKit.INSTANCE.IONotificationPortCreate(mainPort);
80+
if (notificationPort == null) {
81+
throw new RuntimeException("IONotificationPortCreate failed");
82+
}
83+
84+
Pointer runLoopSource = IOKit.INSTANCE.IONotificationPortGetRunLoopSource(notificationPort);
85+
if (runLoopSource == null) {
86+
throw new RuntimeException("IONotificationPortGetRunLoopSource returned null");
87+
}
88+
89+
if (CoreFoundation.kCFRunLoopDefaultMode == null) {
90+
throw new RuntimeException("kCFRunLoopDefaultMode is null");
91+
}
92+
93+
CoreFoundation.INSTANCE.CFRunLoopAddSource(
94+
runLoop,
95+
runLoopSource,
96+
CoreFoundation.kCFRunLoopDefaultMode
97+
);
98+
99+
Pointer matchingDictAdd = IOKit.INSTANCE.IOServiceMatching(kIOUSBDeviceClassName);
100+
if (matchingDictAdd == null) {
101+
throw new RuntimeException("IOServiceMatching(IOUSBDevice) failed for add");
102+
}
103+
104+
Pointer matchingDictRemove = IOKit.INSTANCE.IOServiceMatching(kIOUSBDeviceClassName);
105+
if (matchingDictRemove == null) {
106+
throw new RuntimeException("IOServiceMatching(IOUSBDevice) failed for remove");
107+
}
108+
109+
IOKit.IOServiceMatchingCallback addCallback = (refCon, iterator) -> {
110+
System.out.println("[DEBUG] Device arrival callback triggered");
111+
drainIterator(iterator, true, false);
112+
};
113+
114+
IOKit.IOServiceMatchingCallback removeCallback = (refCon, iterator) -> {
115+
System.out.println("[DEBUG] Device removal callback triggered");
116+
drainIterator(iterator, false, false);
117+
};
118+
119+
IntByReference addIterRef = new IntByReference();
120+
int kr = IOKit.INSTANCE.IOServiceAddMatchingNotification(
121+
notificationPort,
122+
kIOFirstMatchNotification,
123+
matchingDictAdd,
124+
addCallback,
125+
null,
126+
addIterRef
127+
);
128+
System.out.println("[DEBUG] IOServiceAddMatchingNotification(add) = " + kr);
129+
if (kr != 0) {
130+
throw new RuntimeException("IOServiceAddMatchingNotification(add) failed: " + kr);
131+
}
132+
addedIterator = addIterRef.getValue();
133+
134+
IntByReference removeIterRef = new IntByReference();
135+
kr = IOKit.INSTANCE.IOServiceAddMatchingNotification(
136+
notificationPort,
137+
kIOTerminatedNotification,
138+
matchingDictRemove,
139+
removeCallback,
140+
null,
141+
removeIterRef
142+
);
143+
System.out.println("[DEBUG] IOServiceAddMatchingNotification(remove) = " + kr);
144+
if (kr != 0) {
145+
throw new RuntimeException("IOServiceAddMatchingNotification(remove) failed: " + kr);
146+
}
147+
removedIterator = removeIterRef.getValue();
148+
149+
// Drain existing devices once to arm notifications.
150+
// Usually you do not want currently-connected devices to be treated as "new arrivals".
151+
drainIterator(addedIterator, true, suppressInitialDeviceScan);
152+
drainIterator(removedIterator, false, true);
153+
154+
System.out.println("[DEBUG] Entering CFRunLoop...");
155+
CoreFoundation.INSTANCE.CFRunLoopRun();
156+
System.out.println("[DEBUG] CFRunLoop exited.");
157+
}
158+
159+
private void drainIterator(int iterator, boolean connected, boolean suppressEvents) {
160+
while (true) {
161+
int service = IOKit.INSTANCE.IOIteratorNext(iterator);
162+
if (service == 0) {
163+
break;
164+
}
165+
166+
try {
167+
if (!suppressEvents) {
168+
if (connected) {
169+
System.out.println("[EVENT] Device connected");
170+
if (listener != null) {
171+
listener.onUsbDeviceConnected();
172+
}
173+
} else {
174+
System.out.println("[EVENT] Device disconnected");
175+
if (listener != null) {
176+
listener.onUsbDeviceDisconnected();
177+
}
178+
}
179+
} else {
180+
System.out.println("[DEBUG] Suppressed initial device event");
181+
}
182+
} finally {
183+
IOKit.INSTANCE.IOObjectRelease(service);
184+
}
185+
}
186+
}
187+
188+
private void cleanup() {
189+
if (addedIterator != 0) {
190+
IOKit.INSTANCE.IOObjectRelease(addedIterator);
191+
addedIterator = 0;
192+
}
193+
194+
if (removedIterator != 0) {
195+
IOKit.INSTANCE.IOObjectRelease(removedIterator);
196+
removedIterator = 0;
197+
}
198+
199+
if (notificationPort != null) {
200+
IOKit.INSTANCE.IONotificationPortDestroy(notificationPort);
201+
notificationPort = null;
202+
}
203+
204+
runLoop = null;
205+
System.out.println("[DEBUG] macOS USB listener cleaned up.");
206+
}
207+
208+
interface CoreFoundation extends Library {
209+
CoreFoundation INSTANCE = Native.load("CoreFoundation", CoreFoundation.class);
210+
211+
class CFRunLoopRef extends PointerType {}
212+
213+
Pointer kCFRunLoopDefaultMode = NativeLibrary
214+
.getInstance("CoreFoundation")
215+
.getGlobalVariableAddress("kCFRunLoopDefaultMode")
216+
.getPointer(0);
217+
218+
CFRunLoopRef CFRunLoopGetCurrent();
219+
void CFRunLoopRun();
220+
void CFRunLoopStop(CFRunLoopRef rl);
221+
void CFRunLoopAddSource(CFRunLoopRef rl, Pointer source, Pointer mode);
222+
}
223+
224+
interface IOKit extends Library {
225+
IOKit INSTANCE = Native.load("IOKit", IOKit.class);
226+
227+
class IONotificationPortRef extends PointerType {}
228+
229+
interface IOServiceMatchingCallback extends Callback {
230+
void invoke(Pointer refCon, int iterator);
231+
}
232+
233+
int IOMainPort(int bootstrapPort, IntByReference mainPort);
234+
235+
IONotificationPortRef IONotificationPortCreate(int masterPort);
236+
Pointer IONotificationPortGetRunLoopSource(IONotificationPortRef notifyPort);
237+
void IONotificationPortDestroy(IONotificationPortRef notifyPort);
238+
239+
Pointer IOServiceMatching(String name);
240+
241+
int IOServiceAddMatchingNotification(
242+
IONotificationPortRef notifyPort,
243+
String notificationType,
244+
Pointer matchingDictionary,
245+
IOServiceMatchingCallback callback,
246+
Pointer refCon,
247+
IntByReference notificationIterator
248+
);
249+
250+
int IOIteratorNext(int iterator);
251+
int IOObjectRelease(int object);
252+
}
253+
}

0 commit comments

Comments
 (0)