Monitor for new devices connected - swift

I would like my macOS application to be constantly notified whenever a new device is connected to the computer such as mobile devices or USBs.
I have the following code that I am trying to get working below.
The problem with it is that after adding let example = Example() to my viewDidLoad, it only calls the functions once when the program first starts. How can I make it still check for devices after the program has already launched?
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>")")
}
}

Related

How can I update the view when the app is done scanning for NFC?

I have a Swift application that reads from an NFC card. I want it to show the data it read on the screen, which I am able to do if I have a button that checks for updated data using the getDetected() function. I want to, however, update the view when it is done reading the NFC tag so I can immediately display the data. How can I do this?
NFC Reader class:
import Foundation
import CoreNFC
class NFCReader: NSObject, NFCNDEFReaderSessionDelegate {
var detected = [NFCNDEFMessage]()
var session: NFCNDEFReaderSession?
func beginScanning() {
guard NFCNDEFReaderSession.readingAvailable else { return }
session = NFCNDEFReaderSession(delegate: self, queue: nil, invalidateAfterFirstRead: true)
session?.alertMessage = "Hold your iPhone near the reader to unlock."
session?.begin()
}
func readerSession(_ session: NFCNDEFReaderSession, didDetectNDEFs messages: [NFCNDEFMessage]) {
self.detected = messages
self.session = nil
}
func readerSession(_ session: NFCNDEFReaderSession, didInvalidateWithError error: Error) {
self.session = nil
}
func getDetected() -> [NFCNDEFMessage] {
return detected
}
}
you could try the following approach, using a ObservableObject.
Whenever the #Published var detected is changed, the UI will be updated.
class NFCReader: NSObject, NFCNDEFReaderSessionDelegate, ObservableObject { // <--- here
#Published var detected = [NFCNDEFMessage]() // <--- here
var session: NFCNDEFReaderSession?
func beginScanning() {
guard NFCNDEFReaderSession.readingAvailable else { return }
session = NFCNDEFReaderSession(delegate: self, queue: nil, invalidateAfterFirstRead: true)
session?.alertMessage = "Hold your iPhone near the reader to unlock."
session?.begin()
}
func readerSession(_ session: NFCNDEFReaderSession, didDetectNDEFs messages: [NFCNDEFMessage]) {
self.detected = messages
self.session = nil
}
func readerSession(_ session: NFCNDEFReaderSession, didInvalidateWithError error: Error) {
self.session = nil
}
// no real need for this
func getDetected() -> [NFCNDEFMessage] {
return detected
}
}
struct ContentView: View {
#StateObject var readerNFC = NFCReader() // <-- here
var body: some View {
ForEach(readerNFC.detected, id: \.self) { msg in
// .....
}
}
}

Detect USB devices with Swift 4 on macOS

I found the code bellow (here), but I am a newbie with Swift so I can't make it work as it is needed.
The code works fine, I had only to make very small changes on it, and once it runs it shows the devices added. Now I cant figure out how, to once into a loop, keep it active so watching for when a device is connected/disconnected because my lack of understanding about how do delegates works with that.
import IOKit
import IOKit.usb
import IOKit.usb.IOUSBLib
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() }
return buf.withMemoryRebound(to: CChar.self, capacity: MemoryLayout<io_name_t>.size) {
if IORegistryEntryGetName(self, $0) == KERN_SUCCESS {
return String(cString: $0)
}
return nil
}
}
}
class usbDelegate: 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>")")
}
}
var example = usbDelegate()
I've copied your code into my main.swift file and all I did to make it work was adding a call to CFRunLoopRun() at the end.

Is there a battery level did change notification equivalent for kIOPSCurrentCapacityKey on macOS?

I am building a Swift app that monitors the battery percentage, as well as the charging state, of a Mac laptop's battery. On iOS, there is a batteryLevelDidChange notification that is sent when the device's battery percentage changes, as well as a batteryStateDidChange notification that is sent when the device is plugged in, unplugged, and fully charged.
What is the macOS equivalent of those two notifications in Swift, or more specifically, for kIOPSCurrentCapacityKey and kIOPSIsChargingKey? I read through the notification documentation and didn't see any notifications for either. Here is the code I have for fetching the current battery charge level and charging status:
import Cocoa
import IOKit.ps
class MainViewController: NSViewController {
enum BatteryError: Error { case error }
func getMacBatteryPercent() {
do {
guard let snapshot = IOPSCopyPowerSourcesInfo()?.takeRetainedValue()
else { throw BatteryError.error }
guard let sources: NSArray = IOPSCopyPowerSourcesList(snapshot)?.takeRetainedValue()
else { throw BatteryError.error }
for powerSource in sources {
guard let info: NSDictionary = IOPSGetPowerSourceDescription(snapshot, ps as CFTypeRef)?.takeUnretainedValue()
else { throw BatteryError.error }
if let name = info[kIOPSNameKey] as? String,
let state = info[kIOPSIsChargingKey] as? Bool,
let capacity = info[kIOPSCurrentCapacityKey] as? Int,
let max = info[kIOPSMaxCapacityKey] as? Int {
print("\(name): \(capacity) of \(max), \(state)")
}
}
} catch {
print("Unable to get mac battery percent.")
}
}
override func viewDidLoad() {
super.viewDidLoad()
getMacBatteryPercent()
}
}
(I'm replying to this almost 3-year-old question as it is the third result that comes up on the Google search "swift iokit notification".)
The functions you're looking for are IOPSNotificationCreateRunLoopSource and IOPSCreateLimitedPowerNotification.
Simplest usage of IOPSNotificationCreateRunLoopSource:
import IOKit
let loop = IOPSNotificationCreateRunLoopSource({ _ in
// Perform usual battery status fetching
}, nil).takeRetainedValue() as CFRunLoopSource
CFRunLoopAddSource(CFRunLoopGetCurrent(), loop, .defaultMode)
Note that the second parameter context is passed as the only parameter in the callback function, which can be used to pass the instance as a pointer to the closure since C functions do not capture context. (See the link below for actual implementation.)
Here is my code that converts the C-style API into a more Swift-friendly one using the observer pattern: (don't know how much performance benefit it will has for removing run loops)
import Cocoa
import IOKit
// Swift doesn't support nested protocol(?!)
protocol BatteryInfoObserverProtocol: AnyObject {
func batteryInfo(didChange info: BatteryInfo)
}
class BatteryInfo {
typealias ObserverProtocol = BatteryInfoObserverProtocol
struct Observation {
weak var observer: ObserverProtocol?
}
static let shared = BatteryInfo()
private init() {}
private var notificationSource: CFRunLoopSource?
var observers = [ObjectIdentifier: Observation]()
private func startNotificationSource() {
if notificationSource != nil {
stopNotificationSource()
}
notificationSource = IOPSNotificationCreateRunLoopSource({ _ in
BatteryInfo.shared.observers.forEach { (_, value) in
value.observer?.batteryInfo(didChange: BatteryInfo.shared)
}
}, nil).takeRetainedValue() as CFRunLoopSource
CFRunLoopAddSource(CFRunLoopGetCurrent(), notificationSource, .defaultMode)
}
private func stopNotificationSource() {
guard let loop = notificationSource else { return }
CFRunLoopRemoveSource(CFRunLoopGetCurrent(), loop, .defaultMode)
}
func addObserver(_ observer: ObserverProtocol) {
if observers.count == 0 {
startNotificationSource()
}
observers[ObjectIdentifier(observer)] = Observation(observer: observer)
}
func removeObserver(_ observer: ObserverProtocol) {
observers.removeValue(forKey: ObjectIdentifier(observer))
if observers.count == 0 {
stopNotificationSource()
}
}
// Functions for retrieving different properties in the battery description...
}
Usage:
class MyBatteryObserver: BatteryInfo.ObserverProtocol {
init() {
BatteryInfo.shared.addObserver(self)
}
deinit {
BatteryInfo.shared.removeObserver(self)
}
func batteryInfo(didChange info: BatteryInfo) {
print("Changed")
}
}
Credits to this post and Koen.'s answer.
I'd Use this link to get the percentage (looks cleaner)
Fetch the battery status of my MacBook with Swift
And to find changes in the state, use a timer to re-declare your battery state every 5 seconds and then set it as a new variable var OldBattery:Int re-declare it once again and set it as NewBattery, then, write this code:
if (OldBattery =! NewBattery) {
print("battery changed!")
// write the function you want to happen here
}

Swift completion handler in class and function

I have a Class with a function that connect to a firestoreDB and get some data:
import UIKit
import CoreLocation
import Firebase
private let _singletonInstance = GetBottlesFromDB()
class GetBottlesFromDB: NSObject {
class var sharedInstance: GetBottlesFromDB { return _singletonInstance }
var Pins = [LayoutBottlesFromDB]()
// MARK: - init
override init() {
super.init()
populatePinList(completion: { pin in self.Pins } )
//print("GET ALL PINS: \(Pins)")
}
func populatePinList(completion: #escaping ([LayoutBottlesFromDB]) -> ()) {
Pins = []
AppDelegate.ADglobalVar.db.collection("Bottles").whereField("pickupuser", isEqualTo: NSNull()).getDocuments { (querySnapshot, err) in
if let err = err {
print("Error getting documents: \(err)")
} else {
print("start getting documents:")
for document in querySnapshot!.documents {
//print("\(document.documentID) => \(document.data())")
//print("\(document.documentID)")
let bottleID:String = document.documentID
let bottlekind:Int = document.data()["bottle"] as! Int
var bottletitel:String
var bottlesub:String
var bottleurl:String = (document.data()["pic"] as? String)!
let pin = LayoutBottlesFromDB(document.data()["lat"] as! CLLocationDegrees, document.data()["long"] as! CLLocationDegrees, ID: bottleID, title: bottletitel, subtitle: bottlesub, type: bottlekind, url:bottleurl)
//print("GET DAATA from DB: \(pin)")
self.Pins.append(pin)
} //for
completion(self.Pins)
} //else
} //querysnap
}//function
}//class
in my ViewController I call this function.
for pin in GetBottlesFromDB.sharedInstance.Pins{
print("Add Pin : \(pin)")
}
My pProblem is that the function will called but the print is empty.
The function doesn't wait for a completion. What did I do wrong?
You are calling directly GetBottlesFromDB.sharedInstance.Pins and this will not wait for completion of populatePinList method so that's why you are getting blank So You need to wait for completion or you can check if data is not available in pins variable the you need to call completion method like this way:
GetBottlesFromDB.sharedInstance.populatePinList { (pins) in
for pin in pins{
print("Add Pin : \(pin)")
}
}
Nothing in your code waits for the execution of the asynchronous method, so that's no surprise. Also, it would be a terrible design because it would block your app. In addition, your singleton implementaion is overly verbose and doesn't guarantee that it stays a singleton, so I'd recommend to change it to
class GetBottlesFromDB {
private(set) var Pins = [LayoutBottlesFromDB]()
static let shared = GetBottlesFromDB()
private init() {}
// populatePinList as before
}
and in your view controller, e.g. in viewDidLoad do:
override func viewDidLoad() {
GetBottlesFromDB.shared.populatePinList { pins in
pins.forEach { print("Add Pin: \(pin)") }
}
}

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.
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()
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.