Use IOHIDManagerRegisterDeviceRemovalCallback() to monitor for HID removal
The function we currently use, IOHIDDeviceRegisterRemovalCallback(), often
fails on Catalina with a "__CFRunLoopModeFindSourceForMachPort returned NULL"
error message. Once a removal callback is missed, we will eventually crash when
the joystick is closed attempting to use the invalid IOHIDDeviceRef.
https://forums.developer.apple.com/thread/124444
diff --git a/src/hidapi/mac/hid.c b/src/hidapi/mac/hid.c
index 55daf57..41c262f 100644
--- a/src/hidapi/mac/hid.c
+++ b/src/hidapi/mac/hid.c
@@ -372,6 +372,27 @@
return res+1;
}
+static void hid_device_removal_callback(void *context, IOReturn result,
+ void *sender, IOHIDDeviceRef hid_ref)
+{
+ // The device removal callback is sometimes called even after being
+ // unregistered, leading to a crash when trying to access fields in
+ // the already freed hid_device. We keep a linked list of all created
+ // hid_device's so that the one being removed can be checked against
+ // the list to see if it really hasn't been closed yet and needs to
+ // be dealt with here.
+ struct hid_device_list_node *node = device_list;
+ while (node) {
+ if (node->dev->device_handle == hid_ref) {
+ node->dev->disconnected = 1;
+ CFRunLoopStop(node->dev->run_loop);
+ break;
+ }
+
+ node = node->next;
+ }
+}
+
/* Initialize the IOHIDManager. Return 0 for success and -1 for failure. */
static int init_hid_manager(void)
{
@@ -381,6 +402,7 @@
if (hid_mgr) {
IOHIDManagerSetDeviceMatching(hid_mgr, NULL);
IOHIDManagerScheduleWithRunLoop(hid_mgr, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);
+ IOHIDManagerRegisterDeviceRemovalCallback(hid_mgr, hid_device_removal_callback, NULL);
return 0;
}
@@ -569,30 +591,6 @@
return handle;
}
-static void hid_device_removal_callback(void *context, IOReturn result,
- void *sender)
-{
- /* Stop the Run Loop for this device. */
- hid_device *dev = (hid_device *)context;
-
- // The device removal callback is sometimes called even after being
- // unregistered, leading to a crash when trying to access fields in
- // the already freed hid_device. We keep a linked list of all created
- // hid_device's so that the one being removed can be checked against
- // the list to see if it really hasn't been closed yet and needs to
- // be dealt with here.
- struct hid_device_list_node *node = device_list;
- while (node) {
- if (node->dev == dev) {
- dev->disconnected = 1;
- CFRunLoopStop(dev->run_loop);
- break;
- }
-
- node = node->next;
- }
-}
-
/* The Run Loop calls this function for each input report received.
This function puts the data into a linked list to be picked up by
hid_read(). */
@@ -766,7 +764,6 @@
IOHIDDeviceRegisterInputReportCallback(
os_dev, dev->input_report_buf, dev->max_input_report_len,
&hid_report_callback, dev);
- IOHIDDeviceRegisterRemovalCallback(dev->device_handle, hid_device_removal_callback, dev);
struct hid_device_list_node *node = (struct hid_device_list_node *)calloc(1, sizeof(struct hid_device_list_node));
node->dev = dev;
@@ -1042,7 +1039,6 @@
IOHIDDeviceRegisterInputReportCallback(
dev->device_handle, dev->input_report_buf, dev->max_input_report_len,
NULL, dev);
- IOHIDDeviceRegisterRemovalCallback(dev->device_handle, NULL, dev);
IOHIDDeviceUnscheduleFromRunLoop(dev->device_handle, dev->run_loop, dev->run_loop_mode);
IOHIDDeviceScheduleWithRunLoop(dev->device_handle, CFRunLoopGetMain(), kCFRunLoopDefaultMode);
}