USB Connection Delegate on Swift

后端 未结 2 1845
太阳男子
太阳男子 2020-12-03 06:03

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 t

相关标签:
2条回答
  • 2020-12-03 06:44

    Eric Aya's answer is already quite good, but here's a Swift 3 adaptation. I wrapped most of the ugly stuff in a USBWatcher class; set yourself as the delegate of this object to receive notifications.

    You can copy/paste the following into a playground to see it work — the example just logs a message to the console when devices are connected/disconnected.

    It's unfortunate that the IOKit APIs haven't gotten the same Swift-ifying treatment that some other C APIs have been (e.g. CoreGraphics). io_name_t is a clunky tuple instead of a proper struct, the way C structs are usually imported to Swift; io_object_t isn't a real reference type, so it can't take advantage of ARC. Perhaps in the future this will change — if you'd like to see a better Swift API, you should file an enhancement request.

    import Foundation
    import IOKit
    import IOKit.usb
    
    public protocol USBWatcherDelegate: class {
        /// Called on the main thread when a device is connected.
        func deviceAdded(_ device: io_object_t)
    
        /// Called on the main thread when a device is disconnected.
        func deviceRemoved(_ device: io_object_t)
    }
    
    /// An object which observes USB devices added and removed from the system.
    /// Abstracts away most of the ugliness of IOKit APIs.
    public class USBWatcher {
        private weak var delegate: USBWatcherDelegate?
        private let notificationPort = IONotificationPortCreate(kIOMasterPortDefault)
        private var addedIterator: io_iterator_t = 0
        private var removedIterator: io_iterator_t = 0
    
        public init(delegate: USBWatcherDelegate) {
            self.delegate = delegate
    
            func handleNotification(instance: UnsafeMutableRawPointer?, _ iterator: io_iterator_t) {
                let watcher = Unmanaged<USBWatcher>.fromOpaque(instance!).takeUnretainedValue()
                let handler: ((io_iterator_t) -> Void)?
                switch iterator {
                case watcher.addedIterator: handler = watcher.delegate?.deviceAdded
                case watcher.removedIterator: handler = watcher.delegate?.deviceRemoved
                default: assertionFailure("received unexpected IOIterator"); return
                }
                while case let device = IOIteratorNext(iterator), device != IO_OBJECT_NULL {
                    handler?(device)
                    IOObjectRelease(device)
                }
            }
    
            let query = IOServiceMatching(kIOUSBDeviceClassName)
            let opaqueSelf = Unmanaged.passUnretained(self).toOpaque()
    
            // Watch for connected devices.
            IOServiceAddMatchingNotification(
                notificationPort, kIOMatchedNotification, query,
                handleNotification, opaqueSelf, &addedIterator)
    
            handleNotification(instance: opaqueSelf, addedIterator)
    
            // Watch for disconnected devices.
            IOServiceAddMatchingNotification(
                notificationPort, kIOTerminatedNotification, query,
                handleNotification, opaqueSelf, &removedIterator)
    
            handleNotification(instance: opaqueSelf, removedIterator)
    
            // Add the notification to the main run loop to receive future updates.
            CFRunLoopAddSource(
                CFRunLoopGetMain(),
                IONotificationPortGetRunLoopSource(notificationPort).takeUnretainedValue(),
                .commonModes)
        }
    
        deinit {
            IOObjectRelease(addedIterator)
            IOObjectRelease(removedIterator)
            IONotificationPortDestroy(notificationPort)
        }
    }
    
    extension io_object_t {
        /// - Returns: The device's name.
        func name() -> String? {
            let buf = UnsafeMutablePointer<io_name_t>.allocate(capacity: 1)
            defer { buf.deallocate(capacity: 1) }
            return buf.withMemoryRebound(to: CChar.self, capacity: MemoryLayout<io_name_t>.size) {
                if IORegistryEntryGetName(self, $0) == KERN_SUCCESS {
                    return String(cString: $0)
                }
                return nil
            }
        }
    }
    
    
    import PlaygroundSupport
    PlaygroundPage.current.needsIndefiniteExecution = true
    
    class Example: USBWatcherDelegate {
        private var usbWatcher: USBWatcher!
        init() {
            usbWatcher = USBWatcher(delegate: self)
        }
    
        func deviceAdded(_ device: io_object_t) {
            print("device added: \(device.name() ?? "<unknown>")")
        }
    
        func deviceRemoved(_ device: io_object_t) {
            print("device removed: \(device.name() ?? "<unknown>")")
        }
    }
    
    let example = Example()
    
    0 讨论(0)
  • 2020-12-03 06:46

    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.

    0 讨论(0)
提交回复
热议问题