From f21e17276780ca0ec042ab8bbdfd3309ac2d3db9 Mon Sep 17 00:00:00 2001 From: Cameron Gutman Date: Fri, 20 Dec 2019 22:25:49 -0800 Subject: [PATCH] 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 --- src/hidapi/mac/hid.c | 48 ++++++++++++++++++++------------------------ 1 file changed, 22 insertions(+), 26 deletions(-) diff --git a/src/hidapi/mac/hid.c b/src/hidapi/mac/hid.c index 55daf5782..41c262fd1 100644 --- a/src/hidapi/mac/hid.c +++ b/src/hidapi/mac/hid.c @@ -372,6 +372,27 @@ static int make_path(IOHIDDeviceRef device, char *buf, size_t len) 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 @@ static int init_hid_manager(void) 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 @@ hid_device * HID_API_EXPORT hid_open(unsigned short vendor_id, unsigned short pr 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 @@ hid_device * HID_API_EXPORT hid_open_path(const char *path, int bExclusive) 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 @@ void HID_API_EXPORT hid_close(hid_device *dev) 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); }