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
This commit is contained in:
Cameron Gutman 2019-12-20 22:25:49 -08:00
parent dd5987e80a
commit f21e172767

View File

@ -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);
}