Last active
April 29, 2024 14:42
-
-
Save macshome/294f4ca723c50d2ed94773e5873bce4b to your computer and use it in GitHub Desktop.
This playground shows you a few different ways to get device info via IOKit.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// This playground shows you a few different ways to get device info via IOKit. | |
// If you want to explore IOKit and look for interesting data I would download | |
// the Additional Developer Tools from Apple and use the IORegistryExplorer app. | |
// It makes it super easy to poke around in the IOKit planes. | |
import IOKit | |
import Foundation | |
// For convient access we can make a computed getter for the PlatformExpert. | |
// Traditionally this has been where you go to find all sorts of data about the | |
// Mac. With Apple Silicon though there seems to be a shift to store product | |
// data over in a new service. | |
// | |
// The class inheritance of this object is: IOPlatformExpertDevice : IOService : IORegistryEntry : OSObject | |
// | |
// We will cover that below as well. | |
private var platformExpert: io_service_t { | |
IOServiceGetMatchingService(kIOMainPortDefault, IOServiceMatching("IOPlatformExpertDevice")) | |
} | |
// This domain is just to show you how to find a different one. In this case it's the power management | |
// domain where you can find things like Clamshell State. | |
private var pmRootDomain : io_service_t { | |
IOServiceGetMatchingService(kIOMainPortDefault, IOServiceMatching("IOPMrootDomain")) | |
} | |
// This method will take a IOKit key as a String and then return a string if it finds a value | |
// in the sepcified IOKit domain. | |
// The types in these examples are just CFBoolean, CFString or CFData on my Mac, so that's all I handle | |
// in this example. | |
// | |
// If you are new to working with CFTypes they aren't that scary. A lot of methods | |
// will return a clear type like CFString or CFData and you can just cast those in | |
// most cases. Some methods, like the one we are using here, return an opaque CFRef. | |
// The simplest way to convert them cleanly to Swift is to check and see what the type | |
// of data held in the CFRef is with CFGetTypeID() and then compare that to known | |
// CFTypes. | |
// | |
// For debugging you can just dump a CFRef to the console with CFShow(). | |
// | |
// Make sure you take the retained value of the IOKit container. That way ARC knows to release it. | |
func getIOPlatformString(_ key: String, domain: io_service_t) -> String? { | |
let iokValue = IORegistryEntryCreateCFProperty(domain, | |
(key as CFString), | |
kCFAllocatorDefault, | |
0).takeRetainedValue() | |
let typeID = CFGetTypeID(iokValue) | |
if typeID == CFStringGetTypeID(), | |
let iokString = iokValue as? String { | |
return iokString | |
} | |
if typeID == CFBooleanGetTypeID(), | |
let iokBool = iokValue as? Bool { | |
return String(iokBool) | |
} | |
if typeID == CFDataGetTypeID(), | |
let data = iokValue as? Data, | |
let iokString = String(data: data, encoding: .utf8)?.trimmingCharacters(in: .newlines) { | |
return iokString | |
} | |
return nil | |
} | |
// On Apple Silicon there seems to be a move to use another area of the IOService plane, AppleARMPE. | |
// Inside there is a "product" class that contains the same basic info as the PLatformExpert | |
// but it also has more info as well. Notice that I can also create and release the IOKit Object | |
// right after using it in my method as well. | |
// | |
// The class inheritance of this object is: IOPlatformDevice : IOService : IORegistryEntry : OSObject | |
// | |
// Just to show something different we get our registry entry here in a different way. | |
// If we use a mach_port to search from then we can just sepcify the path to the entry that | |
// we want a snapshot of. After that we can convert the CFType to a String and print it out. | |
// | |
// Note that I'm assuming Data as the returned value type here as that's the only type I've seen in this | |
// registry entry. In real code you should check it. | |
func getProductDescriptionARM() -> String? { | |
let appleSiliconProduct = IORegistryEntryFromPath(kIOMainPortDefault, "IOService:/AppleARMPE/product") | |
let cfKeyValue = IORegistryEntryCreateCFProperty(appleSiliconProduct, | |
"product-description" as CFString, | |
kCFAllocatorDefault, | |
0) | |
IOObjectRelease(appleSiliconProduct) | |
if let data = cfKeyValue?.takeRetainedValue() as? Data { | |
return String(data: data, encoding: .utf8)?.trimmingCharacters(in: .newlines) | |
} | |
return nil | |
} | |
print("This Mac's UUID from PExpert is: \(getIOPlatformString("IOPlatformUUID", domain: platformExpert) ?? "Not found.")") | |
print("This Mac's model from PExpert is: \(getIOPlatformString("model", domain: platformExpert) ?? "Not found.")") | |
print("This Mac's clamshell state from PMRoot is: \(getIOPlatformString("AppleClamshellState", domain: pmRootDomain) ?? "Not found.")") | |
print("This Mac's product description from AppleARMPE is: \(getProductDescriptionARM() ?? "Not found.")") |
Fixed some ugly code for making Strings.
The code has memory leaks. Need to use takeRetainedValue
The code has memory leaks. Need to use takeRetainedValue
I wasn't super worried about it as it's a playground, but you are correct. I've updated it so that it doesn't catch anyone by surprise.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Sample output: