소스 검색

device: Fix 10.15 segfault on USB hotplug

For some reason, IOHIDDeviceRegisterRemovalCallback() no longer works on
10.15+, so an app will crash once trying to poll a device that doesn't
exist anymore.  Thankfully, there is the alternative solution of using
IOHIDManagerRegisterDeviceRemovalCallback(). This just required a little
rearranging of the callback code, as well as keeping track of the
connection between IOHIDDeviceRefs and IOKitInputDevices so we actually
know which device to remove.

Closes #847
Donny Lawrence 6 년 전
부모
커밋
17dddeedc4

+ 0 - 35
panda/src/device/ioKitInputDevice.cxx

@@ -21,15 +21,6 @@
 #include "gamepadButton.h"
 #include "mouseButton.h"
 
-static void removal_callback(void *ctx, IOReturn result, void *sender) {
-  // We need to hold a reference to this because it may otherwise be destroyed
-  // during the call to on_remove().
-  PT(IOKitInputDevice) input_device = (IOKitInputDevice *)ctx;
-  nassertv(input_device != nullptr);
-  nassertv(input_device->test_ref_count_integrity());
-  input_device->on_remove();
-}
-
 /**
  * Protected constructor.
  */
@@ -137,7 +128,6 @@ IOKitInputDevice(IOHIDDeviceRef device) :
   }
 
   _is_connected = true;
-  IOHIDDeviceRegisterRemovalCallback(device, removal_callback, this);
 }
 
 /**
@@ -147,31 +137,6 @@ IOKitInputDevice::
 ~IOKitInputDevice() {
 }
 
-/**
- * The nonstatic version of on_remove_device.
- */
-void IOKitInputDevice::
-on_remove() {
-  {
-    LightMutexHolder holder(_lock);
-    if (!_is_connected) {
-      return;
-    }
-    _is_connected = false;
-  }
-
-  if (device_cat.is_debug()) {
-    device_cat.debug()
-      << "Removed input device " << *this << "\n";
-  }
-
-  IOHIDDeviceClose(_device, kIOHIDOptionsTypeNone);
-
-  InputDeviceManager *mgr = InputDeviceManager::get_global_ptr();
-  nassertv(mgr != nullptr);
-  mgr->remove_device(this);
-}
-
 /**
  *
  */

+ 0 - 2
panda/src/device/ioKitInputDevice.h

@@ -30,8 +30,6 @@ public:
   IOKitInputDevice(IOHIDDeviceRef device);
   ~IOKitInputDevice();
 
-  void on_remove();
-
 private:
   void parse_element(IOHIDElementRef element);
 

+ 30 - 2
panda/src/device/ioKitInputDeviceManager.cxx

@@ -59,6 +59,7 @@ IOKitInputDeviceManager() {
   CFRelease(match);
 
   IOHIDManagerRegisterDeviceMatchingCallback(_hid_manager, on_match_device, this);
+  IOHIDManagerRegisterDeviceRemovalCallback(_hid_manager, on_remove_device, this);
   IOHIDManagerScheduleWithRunLoop(_hid_manager, CFRunLoopGetMain(), kCFRunLoopCommonModes);
   IOHIDManagerOpen(_hid_manager, kIOHIDOptionsTypeNone);
 }
@@ -78,16 +79,43 @@ IOKitInputDeviceManager::
  */
 void IOKitInputDeviceManager::
 on_match_device(void *ctx, IOReturn result, void *sender, IOHIDDeviceRef device) {
-  InputDeviceManager *mgr = (InputDeviceManager *)ctx;
+  IOKitInputDeviceManager *mgr = (IOKitInputDeviceManager *)ctx;
   nassertv(mgr != nullptr);
   nassertv(device);
 
-  PT(InputDevice) input_device = new IOKitInputDevice(device);
+  IOKitInputDevice *input_device = new IOKitInputDevice(device);
   if (device_cat.is_debug()) {
     device_cat.debug()
       << "Discovered input device " << *input_device << "\n";
   }
   mgr->add_device(input_device);
+  mgr->_devices_by_hidref[device] = input_device;
 }
 
+/**
+ * Called by IOKit when an input device has disappeared.
+ */
+void IOKitInputDeviceManager::
+on_remove_device(void *ctx, IOReturn result, void *sender, IOHIDDeviceRef device) {
+  IOKitInputDeviceManager *mgr = (IOKitInputDeviceManager *)ctx;
+  nassertv(mgr != nullptr);
+  nassertv(device);
+
+  auto it = mgr->_devices_by_hidref.find(device);
+  nassertv(it != mgr->_devices_by_hidref.end());
+  IOKitInputDevice *input_device = it->second;
+
+  input_device->set_connected(false);
+
+  mgr->_devices_by_hidref.erase(device);
+
+  IOHIDDeviceClose(device, kIOHIDOptionsTypeNone);
+
+  if (device_cat.is_debug()) {
+    device_cat.debug()
+      << "Removed input device " << *input_device << "\n";
+  }
+
+  mgr->remove_device(input_device);
+}
 #endif

+ 11 - 0
panda/src/device/ioKitInputDeviceManager.h

@@ -19,6 +19,8 @@
 #if defined(__APPLE__) && !defined(CPPPARSER)
 #include <IOKit/hid/IOHIDManager.h>
 
+class IOKitInputDevice;
+
 /**
  * The macOS implementation of InputDeviceManager.
  */
@@ -30,7 +32,16 @@ protected:
 protected:
   IOHIDManagerRef _hid_manager;
 
+  // The device removal callback method we need to use requires us to remember
+  // which IOKitInputDevice corresponds to which IOHIDDeviceRef. This is the
+  // same strategy used by winInputDevice and friends.
+  //
+  // We can make this a mapping to raw pointers since we know _devices will be
+  // holding a reference until remove_device is called.
+  pmap<IOHIDDeviceRef, IOKitInputDevice *> _devices_by_hidref;
+
   static void on_match_device(void *ctx, IOReturn result, void *sender, IOHIDDeviceRef device);
+  static void on_remove_device(void *ctx, IOReturn result, void *sender, IOHIDDeviceRef device);
 
   friend class InputDeviceManager;
 };