'MacOS IOkit get services by property name in Swift
I would like to get all services that contain specific properties.
AppleDeviceManagementHIDEventService <class AppleDeviceManagementHIDEventService, id 0x100000a79, registered, matched, active, busy 0 (0 ms), retain 7>
| | | | | | {
| | | | | | "IOMatchedAtBoot" = Yes
| | | | | | "LowBatteryNotificationPercentage" = 2
| | | | | | "PrimaryUsagePage" = 65280
| | | | | | "BatteryFaultNotificationType" = "TPBatteryFault"
| | | | | | "HasBattery" = Yes
| | | | | | "VendorID" = 76
| | | | | | "VersionNumber" = 0
| | | | | | "Built-In" = No
| | | | | | "DeviceAddress" = "10-94-bb-ab-b9-53"
| | | | | | "WakeReason" = "Host (0x01)"
| | | | | | "Product" = "Magic Trackpad"
| | | | | | "SerialNumber" = "10-94-bb-ab-b9-53"
| | | | | | "Transport" = "Bluetooth"
| | | | | | "BatteryLowNotificationType" = "TPLowBattery"
| | | | | | "ProductID" = 613
| | | | | | "DeviceUsagePairs" = ({"DeviceUsagePage"=65280,"DeviceUsage"=11},{"DeviceUsagePage"=65280,"DeviceUsage"=20})
| | | | | | "IOPersonalityPublisher" = "com.apple.driver.AppleTopCaseHIDEventDriver"
| | | | | | "MTFW Version" = 920
| | | | | | "BD_ADDR" = <1094bbabb953>
| | | | | | "BatteryPercent" = 42
| | | | | | "BatteryStatusNotificationType" = "BatteryStatusChanged"
| | | | | | "CriticallyLowBatteryNotificationPercentage" = 1
| | | | | | "ReportInterval" = 11250
| | | | | | "RadioFW Version" = 368
| | | | | | "VendorIDSource" = 1
| | | | | | "STFW Version" = 2144
| | | | | | "CFBundleIdentifier" = "com.apple.driver.AppleTopCaseHIDEventDriver"
| | | | | | "IOProviderClass" = "IOHIDInterface"
| | | | | | "LocationID" = 1001109843
| | | | | | "BluetoothDevice" = Yes
| | | | | | "IOClass" = "AppleDeviceManagementHIDEventService"
| | | | | | "HIDServiceSupport" = No
| | | | | | "CFBundleIdentifierKernel" = "com.apple.driver.AppleTopCaseHIDEventDriver"
| | | | | | "ProductIDArray" = (613)
| | | | | | "BatteryStatusFlags" = 0
| | | | | | "ColorID" = 5
| | | | | | "IOMatchCategory" = "IODefaultMatchCategory"
| | | | | | "CountryCode" = 0
| | | | | | "IOProbeScore" = 7175
| | | | | | "PrimaryUsage" = 11
| | | | | | "IOGeneralInterest" = "IOCommand is not serializable"
| | | | | | "BTFW Version" = 368
| | | | | | }
Eg. every service that contains the child property "BatteryPercent"
.
I know that I can get specific service by using IOServiceNameMatching
and then IOServiceGetMatchingServices
, but this seems to not be working on the properties inside the service. Is it possible to that? I want to match by BatteryPercent and then also get ProductName.
EDIT: This is the code I'm currently using to get the trackpad battery.
func getTrackpadBattery() -> (String, Int) {
var serialPortIterator = io_iterator_t()
var object : io_object_t
var percent: Int = 0
var productName: String = ""
let masterPort: mach_port_t = kIOMainPortDefault
let matchingDict : CFDictionary = IOServiceMatching("AppleDeviceManagementHIDEventService")
let kernResult = IOServiceGetMatchingServices(masterPort, matchingDict, &serialPortIterator)
if KERN_SUCCESS == kernResult {
repeat {
object = IOIteratorNext(serialPortIterator)
if object != 0 {
let percentProperty = IORegistryEntryCreateCFProperty(object, "BatteryPercent" as CFString, kCFAllocatorDefault, 0)
if (percentProperty != nil) {
percent = (percentProperty?.takeRetainedValue() as? Int)!
productName = (IORegistryEntryCreateCFProperty(object, "Product" as CFString, kCFAllocatorDefault, 0).takeRetainedValue() as? String)!
}
}
} while percent == 0
IOObjectRelease(object)
}
IOObjectRelease(serialPortIterator)
return (productName, percent)
}
Solution 1:[1]
(Sorry, I don't really know Swift, so my example is in Objective-C and I've attempted to provide it in Swift too but it might not be right.)
The kIOPropertyExistsMatchKey
matching key should be able to do what you're after. Use it in your matching dictionary to constrain the match results to IOKit services which have the property given. For example:
CFDictionaryRef match_dict =
(__bridge_retained CFDictionaryRef)
@{ @kIOPropertyExistsMatchKey : @"BatteryPercent" };
io_service_t service = IOServiceGetMatchingService(
kIOMasterPortDefault, match_dict);
Note that IOKit properties are for the most part not standardised, so unless you know that a specific service (super-)class publishes the property in a way that your code can process, services could export just about any data via properties with arbitrary names, which does not necessarily map to the meaning you were expecting. So you might want to constrain the registry entries you process in this way to a specific allow-list of IOService classes. (By adding the "IOProviderClass"
/kIOProviderClassKey
matching key to your match dictionary, which is also what IOServiceMatching()
spits out.) At minimum you might want to warn the user when interpreting data from an unknown service class.
In Swift (may be wrong):
For returning all I/O registry entries with the "BatteryPercent"
property, try something like:
[…]
let matchingDict = [ kIOPropertyExistsMatchKey: "BatteryPercent" ] as NSDictionary
let kernResult = IOServiceGetMatchingServices(masterPort, matchingDict, &iterator)
[…]
(You'll need to import Foundation
to be able to use NSDictionary
.)
For only returning those objects with a specific class (here: AppleDeviceManagementHIDEventService
) and the "BatteryPercent"
property:
[…]
let matchingDict = IOServiceMatching("AppleDeviceManagementHIDEventService")
CFDictionarySetValue(matchingDict, kIOPropertyExistsMatchKey, "BatteryPercent")
let kernResult = IOServiceGetMatchingServices(masterPort, matchingDict, &iterator)
[…]
Solution 2:[2]
First of all there is a typo, replace
while percent == 0
with
while object != 0
As you have only one percent
and one productName
property I assume that you expect only one match. If so, add a break
statement after extracting both values. My suggestion is this code
func getTrackpadBattery() -> (String, Int) {
var serialPortIterator = io_iterator_t()
var object : io_object_t
var percent = 0
var productName = ""
let masterPort: mach_port_t = kIOMainPortDefault
let matchingDict : CFDictionary = IOServiceMatching("AppleDeviceManagementHIDEventService")
let kernResult = IOServiceGetMatchingServices(masterPort, matchingDict, &serialPortIterator)
if KERN_SUCCESS == kernResult {
repeat {
object = IOIteratorNext(serialPortIterator)
if object != 0 {
if let percentProperty = IORegistryEntryCreateCFProperty(object, "BatteryPercent" as CFString, kCFAllocatorDefault, 0) {
percent = percentProperty.takeRetainedValue() as! Int
}
if let productProperty = IORegistryEntryCreateCFProperty(object, "Product" as CFString, kCFAllocatorDefault, 0) {
productName = productProperty.takeRetainedValue() as! String
}
break
}
} while object != 0
IOObjectRelease(object)
}
IOObjectRelease(serialPortIterator)
return (productName, percent)
}
If you want to retrieve multiple matches remove the break
statement, create an array of a custom struct and append the matches
struct Device {
let name : String
let batteryPercent : Int
}
func getTrackpadBattery() -> [Device] {
var serialPortIterator = io_iterator_t()
var object : io_object_t
var devices = [Device]()
let masterPort: mach_port_t = kIOMainPortDefault
let matchingDict : CFDictionary = IOServiceMatching("AppleDeviceManagementHIDEventService")
let kernResult = IOServiceGetMatchingServices(masterPort, matchingDict, &serialPortIterator)
if KERN_SUCCESS == kernResult {
repeat {
object = IOIteratorNext(serialPortIterator)
if object != 0 {
var percent = 0
var productName = ""
if let percentProperty = IORegistryEntryCreateCFProperty(object, "BatteryPercent" as CFString, kCFAllocatorDefault, 0) {
percent = percentProperty.takeRetainedValue() as! Int
}
if let productProperty = IORegistryEntryCreateCFProperty(object, "Product" as CFString, kCFAllocatorDefault, 0) {
productName = productProperty.takeRetainedValue() as! String
}
devices.append(Device(name: productName, batteryPercent: percent))
}
} while object != 0
IOObjectRelease(object)
}
IOObjectRelease(serialPortIterator)
return devices
}
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | |
Solution 2 |