5

How can I get a simple call back when a HID device, or at last, any USB/Bluetooth device gets connected/disconnected?

I made a simple app that shows the connected joysticks and the pressed buttons/axis for mac in a pretty way. Since I am not very familiar with cocoa yet, I made the UI using a webview, and used the SDL Joystick library. Everything is working nice, the only problem is that the user needs to scan for new joysticks manually if he/she connects/disconnects something while the program is running.

With a callback, I I can just call the Scan function. I don't want to handle the device or do something fancy, just know when there is something new happening...

Thanks.

Ernest
  • 889
  • 8
  • 11
Rodrigo
  • 674
  • 8
  • 19

3 Answers3

9

Take a look at IOServiceAddMatchingNotification() and related functions. I've only worked with it in the context of serial ports (which are in fact USB to serial adapters, though that doesn't matter), but it should be applicable to any IOKit accessible device. I'm not sure about Bluetooth, but it should at least work for USB devices. Here's a snippet of code I use:

IONotificationPortRef notificationPort = IONotificationPortCreate(kIOMasterPortDefault);
CFRunLoopAddSource(CFRunLoopGetCurrent(), 
               IONotificationPortGetRunLoopSource(notificationPort), 
               kCFRunLoopDefaultMode);

CFMutableDictionaryRef matchingDict = IOServiceMatching(kIOSerialBSDServiceValue);
CFRetain(matchingDict); // Need to use it twice and IOServiceAddMatchingNotification() consumes a reference

CFDictionaryAddValue(matchingDict, CFSTR(kIOSerialBSDTypeKey), CFSTR(kIOSerialBSDRS232Type));

io_iterator_t portIterator = 0;
// Register for notifications when a serial port is added to the system
kern_return_t result = IOServiceAddMatchingNotification(notificationPort,
                                                        kIOPublishNotification,
                                                        matchingDictort,
                                                        SerialDeviceWasAddedFunction,
                                                        self,           
                                                        &portIterator);
io_object_t d;
// Run out the iterator or notifications won't start (you can also use it to iterate the available devices).
while ((d = IOIteratorNext(iterator))) { IOObjectRelease(d); }

// Also register for removal notifications
IONotificationPortRef terminationNotificationPort = IONotificationPortCreate(kIOMasterPortDefault);
CFRunLoopAddSource(CFRunLoopGetCurrent(),
                   IONotificationPortGetRunLoopSource(terminationNotificationPort),
                   kCFRunLoopDefaultMode);
result = IOServiceAddMatchingNotification(terminationNotificationPort,
                                          kIOTerminatedNotification,
                                          matchingDict,
                                          SerialPortWasRemovedFunction,
                                          self,         // refCon/contextInfo
                                          &portIterator);

io_object_t d;
// Run out the iterator or notifications won't start (you can also use it to iterate the available devices).
while ((d = IOIteratorNext(iterator))) { IOObjectRelease(d); }

My SerialPortDeviceWasAddedFunction() and SerialPortWasRemovedFunction() are called when a serial port becomes available on the system or is removed, respectively.

Relevant documentation is here, particularly under the heading Getting Notifications of Device Arrival and Departure.

yano
  • 4,095
  • 3
  • 35
  • 68
Andrew Madsen
  • 21,309
  • 5
  • 56
  • 97
  • Hmm... I tried to use your code, created both callback functions, added some IOkit headers... But it got a undefined reference to "notificationPort", am I missing something? – Rodrigo Apr 06 '12 at 01:37
  • Sorry about that. I copy/pasted this code directly out of an existing project. I didn't mean for it to be a complete solution, just an example of how to get notifications. Anyway, I've updated the code in my answer to include the creation of notificationPort along with scheduling it on the runloop. Keep in mind that this code is specific to notifications for RS-232 ports. It's meant to be an example, but will need to be modified for your application. – Andrew Madsen Apr 06 '12 at 01:44
  • Thanks Andrew. I changed IOServiceMatching to kIOHIDDeviceKey and removed CFDictionaryAddValue line and it worked. But only once. After that I need to register for the notification again, that's correct? – Rodrigo Apr 06 '12 at 03:46
  • I am checking what is happening. Anyway, thank you so much for pointing me directions. – Rodrigo Apr 14 '12 at 21:14
  • My App: http://itunes.apple.com/us/app/joystick-show/id515886877 a future update will feature automatic HID joystick detection. If you are interested in a free copy, just ask me. Thank you! – Rodrigo Apr 14 '12 at 21:17
  • you are leaking items when you run out those lists!! do something like ``` io_object_t device{}; while ( ( device = IOIteratorNext( iterator ) ) ) { IOObjectRelease( device ); }``` – yano Nov 21 '19 at 01:36
  • @yano: Yep, you're correct. Thanks for the bugfix here. – Andrew Madsen Nov 21 '19 at 05:18
3

Use IOHIDManager to get the notifications.

Arjuna
  • 697
  • 4
  • 17
2

Based on the earlier answers from Andrew and Arjuna, I ended up with the following snippet using IOHIDManager that should work with an Apple HID device (e.g. a bluetooth trackpad was tested). This appears to also send notifications more than once without needing to clear/decrement anything.

- (void) startHIDNotification
{
ioHIDManager = IOHIDManagerCreate ( kCFAllocatorDefault, kIOHIDManagerOptionNone  );

CFMutableDictionaryRef matchingDict = IOServiceMatching(kIOHIDDeviceKey);
CFDictionaryAddValue(matchingDict, CFSTR(kIOHIDManufacturerKey), CFSTR("Apple"));

IOHIDManagerSetDeviceMatching (ioHIDManager, matchingDict);

IOHIDManagerRegisterDeviceMatchingCallback( ioHIDManager, AppleHIDDeviceWasAddedFunction, (__bridge void *)(self) );
IOHIDManagerRegisterDeviceRemovalCallback( ioHIDManager, AppleHIDDeviceWasRemovedFunction, (__bridge void *)(self) );

hidNotificationRunLoop = CFRunLoopGetCurrent();

IOHIDManagerScheduleWithRunLoop(ioHIDManager,
                                hidNotificationRunLoop,
                                kCFRunLoopDefaultMode);
}

and the callback methods

void AppleHIDDeviceWasAddedFunction( void *                  context,
                             IOReturn                result,
                             void *                  sender,
                             IOHIDDeviceRef          device)
{
     NSLog(@"added");
}

void AppleHIDDeviceWasRemovedFunction( void *                  context,
                             IOReturn                result,
                             void *                  sender,
                             IOHIDDeviceRef          device)
{
     NSLog(@"removed");
}
Vivek Gani
  • 1,283
  • 14
  • 28
  • this is great for HID. I was using the other solution at first, but I get called before the HID device is actually present/ready. Also much easier – yano Nov 21 '19 at 01:56