MacOS IOkit 在 Swift 中按 属性 名称获取服务

MacOS IOkit get services by property name in Swift

我想获取所有包含特定属性的服务。

 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
| | | |   |   |     }

例如。每个包含子项的服务 属性 "BatteryPercent".

我知道我可以通过使用 IOServiceNameMatching 然后 IOServiceGetMatchingServices 获得特定服务,但这似乎不适用于服务内的属性。有可能吗?我想通过 BatteryPercent 进行匹配,然后还获取 ProductName。

编辑:这是我目前用来获取触控板电池的代码。

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)
}

(抱歉,我真的不知道 Swift,所以我的示例在 Objective-C 中,我也尝试在 Swift 中提供它,但它可能不是对。)

kIOPropertyExistsMatchKey 匹配键应该可以完成您想要的操作。在您的匹配字典中使用它来将匹配结果限制为具有给定 属性 的 IOKit 服务。例如:

    CFDictionaryRef match_dict =
        (__bridge_retained CFDictionaryRef)
        @{ @kIOPropertyExistsMatchKey : @"BatteryPercent" };

    io_service_t service = IOServiceGetMatchingService(
        kIOMasterPortDefault, match_dict);

请注意,IOKit 属性大部分未标准化,因此除非您知道特定服务(超级)class 以您的代码可以处理的方式发布 属性,服务可以通过具有任意名称的属性导出几乎任何数据,这不一定映射到您期望的含义。因此,您可能希望将以这种方式处理的注册表项限制为特定的 allow-list of IOService classes。 (通过将 "IOProviderClass"/kIOProviderClassKey 匹配键添加到你的匹配字典中,这也是 IOServiceMatching() 吐出的内容。)至少你可能希望在解释来自未知的数据时警告用户服务 class.

在Swift(可能有误):

要返回带有 "BatteryPercent" 属性 的 all I/O 注册表项,请尝试以下内容:

    […]
    let matchingDict = [ kIOPropertyExistsMatchKey: "BatteryPercent" ] as NSDictionary
    let kernResult = IOServiceGetMatchingServices(masterPort, matchingDict, &iterator)
    […]

(您需要 import Foundation 才能使用 NSDictionary。)

仅返回具有特定 class(此处:AppleDeviceManagementHIDEventService "BatteryPercent" 属性 的对象:

    […]
    let matchingDict = IOServiceMatching("AppleDeviceManagementHIDEventService")
    CFDictionarySetValue(matchingDict, kIOPropertyExistsMatchKey, "BatteryPercent")
    let kernResult = IOServiceGetMatchingServices(masterPort, matchingDict, &iterator)
    […]

首先有错字,替换

while percent == 0

while object != 0

因为你只有一个 percent 和一个 productName 属性 我假设你只期望一场比赛。如果是这样,请在提取两个值后添加 break 语句。我的建议是这段代码

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)
}

如果要检索多个匹配项,请删除 break 语句,创建自定义结构数组并附加匹配项

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
}