How to know when a HID USB/Bluetooth device is connected in Cocoa?
Asked Answered
A

3

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.

Acceptation answered 29/3, 2012 at 2:43 Comment(0)
F
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.

Feuilleton answered 29/3, 2012 at 3:6 Comment(7)
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?Acceptation
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.Feuilleton
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?Acceptation
I am checking what is happening. Anyway, thank you so much for pointing me directions.Acceptation
My App: 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!Acceptation
you are leaking items when you run out those lists!! do something like ``` io_object_t device{}; while ( ( device = IOIteratorNext( iterator ) ) ) { IOObjectRelease( device ); }```Marlinemarlinespike
@yano: Yep, you're correct. Thanks for the bugfix here.Feuilleton
C
3

Use IOHIDManager to get the notifications.

Curtcurtail answered 18/10, 2013 at 12:18 Comment(0)
L
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");
}
Loess answered 8/10, 2015 at 6:51 Comment(1)
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 easierMarlinemarlinespike

© 2022 - 2024 — McMap. All rights reserved.