swiftmacosusb

USB Connection Delegate on Swift


Is there a delegate in Swift that would let my class know when new devices are plugged in via the computer's USB? I would like to know when a new device becomes available to my program.


Solution

  • This answer worked for me https://stackoverflow.com/a/35788694 but it needed some adaptation, like creating a bridging header to import some specific IOKit parts.

    First, add IOKit.framework to your project (click "+" in "Linked Frameworks and Libraries").

    Then create a new empty ".m" file, whatever its name. Xcode will then ask if it should make a "bridging header". Say YES.

    Ignore the ".m" file. In the new "YOURAPPNAME-Bridging-Header.h" file that Xcode just created, add the following lines:

    #include <IOKit/IOKitLib.h>
    #include <IOKit/usb/IOUSBLib.h>
    #include <IOKit/hid/IOHIDKeys.h>
    

    Now you can use the code in the linked answer. Here's a simplified version:

    class USBDetector {
        class func monitorUSBEvent() {
            var portIterator: io_iterator_t = 0
            let matchingDict = IOServiceMatching(kIOUSBDeviceClassName)
            let gNotifyPort: IONotificationPortRef = IONotificationPortCreate(kIOMasterPortDefault)
            let runLoopSource: Unmanaged<CFRunLoopSource>! = IONotificationPortGetRunLoopSource(gNotifyPort)
            let gRunLoop: CFRunLoop! = CFRunLoopGetCurrent()
            CFRunLoopAddSource(gRunLoop, runLoopSource.takeRetainedValue(), kCFRunLoopDefaultMode)
            let observer = UnsafeMutablePointer<Void>(unsafeAddressOf(self))
            _ = IOServiceAddMatchingNotification(gNotifyPort,
                                                  kIOMatchedNotification,
                                                  matchingDict,
                                                  deviceAdded,
                                                  observer,
                                                  &portIterator)
            deviceAdded(nil, iterator: portIterator)
            _ = IOServiceAddMatchingNotification(gNotifyPort,
                                                  kIOTerminatedNotification,
                                                  matchingDict,
                                                  deviceRemoved,
                                                  observer,
                                                  &portIterator)
            deviceRemoved(nil, iterator: portIterator)
        }
    }
    
    func deviceAdded(refCon: UnsafeMutablePointer<Void>, iterator: io_iterator_t) {
        var kr: kern_return_t = KERN_FAILURE
        while case let usbDevice = IOIteratorNext(iterator) where usbDevice != 0 {
            let deviceNameAsCFString = UnsafeMutablePointer<io_name_t>.alloc(1)
            defer {deviceNameAsCFString.dealloc(1)}
            kr = IORegistryEntryGetName(usbDevice, UnsafeMutablePointer(deviceNameAsCFString))
            if kr != KERN_SUCCESS {
                deviceNameAsCFString.memory.0 = 0
            }
            let deviceName = String.fromCString(UnsafePointer(deviceNameAsCFString))
            print("Active device: \(deviceName!)")
            IOObjectRelease(usbDevice)
        }
    }
    
    func deviceRemoved(refCon: UnsafeMutablePointer<Void>, iterator: io_iterator_t) {
        // ...
    }
    

    Note: deviceAdded and deviceRemoved need to be functions (not methods).

    To use this code, just launch the observer:

    USBDetector.monitorUSBEvent()
    

    This will list the currently plugged devices, and on every new USB device plug/unplug event it will print the device name.