macOS: load device model names from dataset

This commit is contained in:
Peter Steinberger
2025-12-17 22:55:50 +01:00
parent 76ddfc4a9e
commit 888dbd7d11
8 changed files with 517 additions and 20 deletions

View File

@@ -101,6 +101,7 @@ pnpm clawdis gateway --force
### macOS Companion (Clawdis.app)
- A menu bar app that can start/stop the Gateway, show health/presence, and provide a local ops UI.
- Instances UI shows friendly hardware model names (from the vendored MIT dataset under `apps/macos/Sources/Clawdis/Resources/DeviceModels/`).
- **Voice Wake** (on-device speech recognition) and Push-to-talk overlay.
- **WebChat** embed + debug tooling (logs, status, heartbeats, sessions).
- Hosts **PeekabooBridge** for UI automation brokering (for clawd workflows).

View File

@@ -50,8 +50,9 @@ let package = Package(
],
resources: [
.copy("Resources/Clawdis.icns"),
.copy("Resources/WebChat"),
.copy("Resources/CanvasA2UI"),
.copy("Resources/WebChat"),
.copy("Resources/DeviceModels"),
],
swiftSettings: [
.enableUpcomingFeature("StrictConcurrency"),

View File

@@ -6,15 +6,18 @@ struct DevicePresentation: Sendable {
}
enum DeviceModelCatalog {
private static let modelIdentifierToName: [String: String] = loadModelIdentifierToName()
static func presentation(deviceFamily: String?, modelIdentifier: String?) -> DevicePresentation? {
let family = (deviceFamily ?? "").trimmingCharacters(in: .whitespacesAndNewlines)
let model = (modelIdentifier ?? "").trimmingCharacters(in: .whitespacesAndNewlines)
let modelEntry = model.isEmpty ? nil : modelIdentifierTable[model]
let symbol = modelEntry?.symbol ?? fallbackSymbol(for: family, modelIdentifier: model)
let friendlyName = model.isEmpty ? nil : modelIdentifierToName[model]
let symbol = symbolFor(modelIdentifier: model, friendlyName: friendlyName)
?? fallbackSymbol(for: family, modelIdentifier: model)
let title = if let name = modelEntry?.name, !name.isEmpty {
name
let title = if let friendlyName, !friendlyName.isEmpty {
friendlyName
} else if !family.isEmpty, !model.isEmpty {
"\(family) (\(model))"
} else if !family.isEmpty {
@@ -29,6 +32,36 @@ enum DeviceModelCatalog {
return DevicePresentation(title: title, symbol: symbol)
}
private static func symbolFor(modelIdentifier rawModelIdentifier: String, friendlyName: String?) -> String? {
let modelIdentifier = rawModelIdentifier.trimmingCharacters(in: .whitespacesAndNewlines)
guard !modelIdentifier.isEmpty else { return nil }
let lower = modelIdentifier.lowercased()
if lower.hasPrefix("ipad") { return "ipad" }
if lower.hasPrefix("iphone") { return "iphone" }
if lower.hasPrefix("ipod") { return "iphone" }
if lower.hasPrefix("watch") { return "applewatch" }
if lower.hasPrefix("appletv") { return "appletv" }
if lower.hasPrefix("audio") || lower.hasPrefix("homepod") { return "speaker" }
if lower.hasPrefix("macbook") || lower.hasPrefix("macbookpro") || lower.hasPrefix("macbookair") {
return "laptopcomputer"
}
if lower.hasPrefix("imac") || lower.hasPrefix("macmini") || lower.hasPrefix("macpro") || lower.hasPrefix("macstudio") {
return "desktopcomputer"
}
if lower.hasPrefix("mac"), let friendlyNameLower = friendlyName?.lowercased() {
if friendlyNameLower.contains("macbook") { return "laptopcomputer" }
if friendlyNameLower.contains("imac") { return "desktopcomputer" }
if friendlyNameLower.contains("mac mini") { return "desktopcomputer" }
if friendlyNameLower.contains("mac studio") { return "desktopcomputer" }
if friendlyNameLower.contains("mac pro") { return "desktopcomputer" }
}
return nil
}
private static func fallbackSymbol(for familyRaw: String, modelIdentifier: String) -> String? {
let family = familyRaw.trimmingCharacters(in: .whitespacesAndNewlines)
if family.isEmpty { return nil }
@@ -49,21 +82,62 @@ enum DeviceModelCatalog {
}
}
private struct ModelEntry: Sendable {
let name: String
let symbol: String?
private static func loadModelIdentifierToName() -> [String: String] {
var combined: [String: String] = [:]
combined.merge(loadMapping(resourceName: "ios-device-identifiers"), uniquingKeysWith: { current, _ in current })
combined.merge(loadMapping(resourceName: "mac-device-identifiers"), uniquingKeysWith: { current, _ in current })
return combined
}
// Friendly model names for a small set of known identifiers.
// Extend this table as needed; unknown identifiers fall back to the raw value.
private static let modelIdentifierTable: [String: ModelEntry] = [
// iPad
"iPad16,5": .init(name: "iPad Pro 11-inch (M4)", symbol: "ipad"),
"iPad16,6": .init(name: "iPad Pro 13-inch (M4)", symbol: "ipad"),
private static func loadMapping(resourceName: String) -> [String: String] {
guard let url = Bundle.module.url(
forResource: resourceName,
withExtension: "json",
subdirectory: "DeviceModels")
else {
return [:]
}
// Mac
"Mac16,6": .init(name: "MacBook Pro (14-inch, 2024)", symbol: "laptopcomputer"),
"Mac16,8": .init(name: "MacBook Pro (16-inch, 2024)", symbol: "laptopcomputer"),
]
do {
let data = try Data(contentsOf: url)
let decoded = try JSONDecoder().decode([String: NameValue].self, from: data)
return decoded.compactMapValues { $0.normalizedName }
} catch {
return [:]
}
}
private enum NameValue: Decodable {
case string(String)
case stringArray([String])
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
if let s = try? container.decode(String.self) {
self = .string(s)
return
}
if let arr = try? container.decode([String].self) {
self = .stringArray(arr)
return
}
throw DecodingError.typeMismatch(
String.self,
.init(codingPath: decoder.codingPath, debugDescription: "Expected string or string array"))
}
var normalizedName: String? {
switch self {
case .string(let s):
let trimmed = s.trimmingCharacters(in: .whitespacesAndNewlines)
return trimmed.isEmpty ? nil : trimmed
case .stringArray(let arr):
let values = arr
.map { $0.trimmingCharacters(in: .whitespacesAndNewlines) }
.filter { !$0.isEmpty }
guard !values.isEmpty else { return nil }
return values.joined(separator: " / ")
}
}
}
}

View File

@@ -39,7 +39,8 @@ enum InstanceIdentity {
var buffer = [CChar](repeating: 0, count: size)
guard sysctlbyname("hw.model", &buffer, &size, nil, 0) == 0 else { return nil }
let s = String(cString: buffer).trimmingCharacters(in: .whitespacesAndNewlines)
let bytes = buffer.prefix { $0 != 0 }.map { UInt8(bitPattern: $0) }
let s = String(decoding: bytes, as: UTF8.self).trimmingCharacters(in: .whitespacesAndNewlines)
return s.isEmpty ? nil : s
}()
}

View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2021 Kyle Seongwoo Jun
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -0,0 +1,9 @@
# Apple device identifier mappings
This directory includes model identifier → human-readable name mappings derived from the open-source project:
- `kyle-seongwoo-jun/apple-device-identifiers`
- iOS mapping pinned to commit `8e7388b29da046183f5d976eb74dbb2f2acda955`
- macOS mapping pinned to commit `98ca75324f7a88c1649eb5edfc266ef47b7b8193`
See `LICENSE.apple-device-identifiers.txt` for license terms.

View File

@@ -0,0 +1,176 @@
{
"i386": "iPhone Simulator",
"x86_64": "iPhone Simulator",
"arm64": "iPhone Simulator",
"iPhone1,1": "iPhone",
"iPhone1,2": "iPhone 3G",
"iPhone2,1": "iPhone 3GS",
"iPhone3,1": "iPhone 4",
"iPhone3,2": "iPhone 4",
"iPhone3,3": "iPhone 4",
"iPhone4,1": "iPhone 4s",
"iPhone5,1": "iPhone 5",
"iPhone5,2": "iPhone 5",
"iPhone5,3": "iPhone 5c",
"iPhone5,4": "iPhone 5c",
"iPhone6,1": "iPhone 5s",
"iPhone6,2": "iPhone 5s",
"iPhone7,1": "iPhone 6 Plus",
"iPhone7,2": "iPhone 6",
"iPhone8,1": "iPhone 6s",
"iPhone8,2": "iPhone 6s Plus",
"iPhone8,4": "iPhone SE (1st generation)",
"iPhone9,1": "iPhone 7",
"iPhone9,2": "iPhone 7 Plus",
"iPhone9,3": "iPhone 7",
"iPhone9,4": "iPhone 7 Plus",
"iPhone10,1": "iPhone 8",
"iPhone10,2": "iPhone 8 Plus",
"iPhone10,3": "iPhone X",
"iPhone10,4": "iPhone 8",
"iPhone10,5": "iPhone 8 Plus",
"iPhone10,6": "iPhone X",
"iPhone11,2": "iPhone XS",
"iPhone11,4": "iPhone XS Max",
"iPhone11,6": "iPhone XS Max",
"iPhone11,8": "iPhone XR",
"iPhone12,1": "iPhone 11",
"iPhone12,3": "iPhone 11 Pro",
"iPhone12,5": "iPhone 11 Pro Max",
"iPhone12,8": "iPhone SE (2nd generation)",
"iPhone13,1": "iPhone 12 mini",
"iPhone13,2": "iPhone 12",
"iPhone13,3": "iPhone 12 Pro",
"iPhone13,4": "iPhone 12 Pro Max",
"iPhone14,2": "iPhone 13 Pro",
"iPhone14,3": "iPhone 13 Pro Max",
"iPhone14,4": "iPhone 13 mini",
"iPhone14,5": "iPhone 13",
"iPhone14,6": "iPhone SE (3rd generation)",
"iPhone14,7": "iPhone 14",
"iPhone14,8": "iPhone 14 Plus",
"iPhone15,2": "iPhone 14 Pro",
"iPhone15,3": "iPhone 14 Pro Max",
"iPhone15,4": "iPhone 15",
"iPhone15,5": "iPhone 15 Plus",
"iPhone16,1": "iPhone 15 Pro",
"iPhone16,2": "iPhone 15 Pro Max",
"iPhone17,1": "iPhone 16 Pro",
"iPhone17,2": "iPhone 16 Pro Max",
"iPhone17,3": "iPhone 16",
"iPhone17,4": "iPhone 16 Plus",
"iPhone17,5": "iPhone 16e",
"iPhone18,1": "iPhone 17 Pro",
"iPhone18,2": "iPhone 17 Pro Max",
"iPhone18,3": "iPhone 17",
"iPhone18,4": "iPhone Air",
"iPad1,1": "iPad",
"iPad1,2": "iPad",
"iPad2,1": "iPad 2",
"iPad2,2": "iPad 2",
"iPad2,3": "iPad 2",
"iPad2,4": "iPad 2",
"iPad2,5": "iPad mini",
"iPad2,6": "iPad mini",
"iPad2,7": "iPad mini",
"iPad3,1": "iPad (3rd generation)",
"iPad3,2": "iPad (3rd generation)",
"iPad3,3": "iPad (3rd generation)",
"iPad3,4": "iPad (4th generation)",
"iPad3,5": "iPad (4th generation)",
"iPad3,6": "iPad (4th generation)",
"iPad4,1": "iPad Air",
"iPad4,2": "iPad Air",
"iPad4,3": "iPad Air",
"iPad4,4": "iPad mini 2",
"iPad4,5": "iPad mini 2",
"iPad4,6": "iPad mini 2",
"iPad4,7": "iPad mini 3",
"iPad4,8": "iPad mini 3",
"iPad4,9": "iPad mini 3",
"iPad5,1": "iPad mini 4",
"iPad5,2": "iPad mini 4",
"iPad5,3": "iPad Air 2",
"iPad5,4": "iPad Air 2",
"iPad6,3": "iPad Pro (9.7-inch)",
"iPad6,4": "iPad Pro (9.7-inch)",
"iPad6,7": "iPad Pro (12.9-inch)",
"iPad6,8": "iPad Pro (12.9-inch)",
"iPad6,11": "iPad (5th generation)",
"iPad6,12": "iPad (5th generation)",
"iPad7,1": "iPad Pro (12.9-inch) (2nd generation)",
"iPad7,2": "iPad Pro (12.9-inch) (2nd generation)",
"iPad7,3": "iPad Pro (10.5-inch)",
"iPad7,4": "iPad Pro (10.5-inch)",
"iPad7,5": "iPad (6th generation)",
"iPad7,6": "iPad (6th generation)",
"iPad7,11": "iPad (7th generation)",
"iPad7,12": "iPad (7th generation)",
"iPad8,1": "iPad Pro (11-inch)",
"iPad8,2": "iPad Pro (11-inch)",
"iPad8,3": "iPad Pro (11-inch)",
"iPad8,4": "iPad Pro (11-inch)",
"iPad8,5": "iPad Pro (12.9-inch) (3rd generation)",
"iPad8,6": "iPad Pro (12.9-inch) (3rd generation)",
"iPad8,7": "iPad Pro (12.9-inch) (3rd generation)",
"iPad8,8": "iPad Pro (12.9-inch) (3rd generation)",
"iPad8,9": "iPad Pro (11-inch) (2nd generation)",
"iPad8,10": "iPad Pro (11-inch) (2nd generation)",
"iPad8,11": "iPad Pro (12.9-inch) (4th generation)",
"iPad8,12": "iPad Pro (12.9-inch) (4th generation)",
"iPad11,1": "iPad mini (5th generation)",
"iPad11,2": "iPad mini (5th generation)",
"iPad11,3": "iPad Air (3rd generation)",
"iPad11,4": "iPad Air (3rd generation)",
"iPad11,6": "iPad (8th generation)",
"iPad11,7": "iPad (8th generation)",
"iPad12,1": "iPad (9th generation)",
"iPad12,2": "iPad (9th generation)",
"iPad13,1": "iPad Air (4th generation)",
"iPad13,2": "iPad Air (4th generation)",
"iPad13,4": "iPad Pro (11-inch) (3rd generation)",
"iPad13,5": "iPad Pro (11-inch) (3rd generation)",
"iPad13,6": "iPad Pro (11-inch) (3rd generation)",
"iPad13,7": "iPad Pro (11-inch) (3rd generation)",
"iPad13,8": "iPad Pro (12.9-inch) (5th generation)",
"iPad13,9": "iPad Pro (12.9-inch) (5th generation)",
"iPad13,10": "iPad Pro (12.9-inch) (5th generation)",
"iPad13,11": "iPad Pro (12.9-inch) (5th generation)",
"iPad13,16": "iPad Air (5th generation)",
"iPad13,17": "iPad Air (5th generation)",
"iPad13,18": "iPad (10th generation)",
"iPad13,19": "iPad (10th generation)",
"iPad14,1": "iPad mini (6th generation)",
"iPad14,2": "iPad mini (6th generation)",
"iPad14,3": "iPad Pro (11-inch) (4th generation)",
"iPad14,4": "iPad Pro (11-inch) (4th generation)",
"iPad14,5": "iPad Pro (12.9-inch) (6th generation)",
"iPad14,6": "iPad Pro (12.9-inch) (6th generation)",
"iPad14,8": "iPad Air 11-inch (M2)",
"iPad14,9": "iPad Air 11-inch (M2)",
"iPad14,10": "iPad Air 13-inch (M2)",
"iPad14,11": "iPad Air 13-inch (M2)",
"iPad15,3": "iPad Air 11-inch (M3)",
"iPad15,4": "iPad Air 11-inch (M3)",
"iPad15,5": "iPad Air 13-inch (M3)",
"iPad15,6": "iPad Air 13-inch (M3)",
"iPad15,7": "iPad (A16)",
"iPad15,8": "iPad (A16)",
"iPad16,1": "iPad mini (A17 Pro)",
"iPad16,2": "iPad mini (A17 Pro)",
"iPad16,3": "iPad Pro 11-inch (M4)",
"iPad16,4": "iPad Pro 11-inch (M4)",
"iPad16,5": "iPad Pro 13-inch (M4)",
"iPad16,6": "iPad Pro 13-inch (M4)",
"iPad17,1": "iPad Pro 11-inch (M5)",
"iPad17,2": "iPad Pro 11-inch (M5)",
"iPad17,3": "iPad Pro 13-inch (M5)",
"iPad17,4": "iPad Pro 13-inch (M5)",
"iPod1,1": "iPod touch",
"iPod2,1": "iPod touch (2nd generation)",
"iPod3,1": "iPod touch (3rd generation)",
"iPod4,1": "iPod touch (4th generation)",
"iPod5,1": "iPod touch (5th generation)",
"iPod7,1": "iPod touch (6th generation)",
"iPod9,1": "iPod touch (7th generation)"
}

View File

@@ -0,0 +1,214 @@
{
"iMac9,1": [
"iMac (20-inch, Early 2009)",
"iMac (24-inch, Early 2009)"
],
"iMac10,1": [
"iMac (21.5-inch, Late 2009)",
"iMac (27-inch, Late 2009)"
],
"iMac11,2": "iMac (21.5-inch, Mid 2010)",
"iMac11,3": "iMac (27-inch, Mid 2010)",
"iMac12,1": "iMac (21.5-inch, Mid 2011)",
"iMac12,2": "iMac (27-inch, Mid 2011)",
"iMac13,1": "iMac (21.5-inch, Late 2012)",
"iMac13,2": "iMac (27-inch, Late 2012)",
"iMac14,1": "iMac (21.5-inch, Late 2013)",
"iMac14,2": "iMac (27-inch, Late 2013)",
"iMac14,4": "iMac (21.5-inch, Mid 2014)",
"iMac15,1": [
"iMac (Retina 5K, 27-inch, Late 2014)",
"iMac (Retina 5K, 27-inch, Mid 2015)"
],
"iMac16,1": "iMac (21.5-inch, Late 2015)",
"iMac16,2": "iMac (Retina 4K, 21.5-inch, Late 2015)",
"iMac17,1": "iMac (Retina 5K, 27-inch, Late 2015)",
"iMac18,1": "iMac (21.5-inch, 2017)",
"iMac18,2": "iMac (Retina 4K, 21.5-inch, 2017)",
"iMac18,3": "iMac (Retina 5K, 27-inch, 2017)",
"iMac19,1": "iMac (Retina 5K, 27-inch, 2019)",
"iMac19,2": "iMac (Retina 4K, 21.5-inch, 2019)",
"iMac20,1": "iMac (Retina 5K, 27-inch, 2020)",
"iMac20,2": "iMac (Retina 5K, 27-inch, 2020)",
"iMac21,1": "iMac (24-inch, M1, 2021)",
"iMac21,2": "iMac (24-inch, M1, 2021)",
"iMacPro1,1": "iMac Pro (2017)",
"Mac13,1": "Mac Studio (2022)",
"Mac13,2": "Mac Studio (2022)",
"Mac14,2": "MacBook Air (M2, 2022)",
"Mac14,3": "Mac mini (2023)",
"Mac14,5": "MacBook Pro (14-inch, 2023)",
"Mac14,6": "MacBook Pro (16-inch, 2023)",
"Mac14,7": "MacBook Pro (13-inch, M2, 2022)",
"Mac14,8": [
"Mac Pro (2023)",
"Mac Pro (Rack, 2023)"
],
"Mac14,9": "MacBook Pro (14-inch, 2023)",
"Mac14,10": "MacBook Pro (16-inch, 2023)",
"Mac14,12": "Mac mini (2023)",
"Mac14,13": "Mac Studio (2023)",
"Mac14,14": "Mac Studio (2023)",
"Mac14,15": "MacBook Air (15-inch, M2, 2023)",
"Mac15,3": "MacBook Pro (14-inch, Nov 2023)",
"Mac15,4": "iMac (24-inch, 2023, Two ports)",
"Mac15,5": "iMac (24-inch, 2023, Four ports)",
"Mac15,6": "MacBook Pro (14-inch, Nov 2023)",
"Mac15,7": "MacBook Pro (16-inch, Nov 2023)",
"Mac15,8": "MacBook Pro (14-inch, Nov 2023)",
"Mac15,9": "MacBook Pro (16-inch, Nov 2023)",
"Mac15,10": "MacBook Pro (14-inch, Nov 2023)",
"Mac15,11": "MacBook Pro (16-inch, Nov 2023)",
"Mac15,12": "MacBook Air (13-inch, M3, 2024)",
"Mac15,13": "MacBook Air (15-inch, M3, 2024)",
"Mac15,14": "Mac Studio (2025)",
"Mac16,1": "MacBook Pro (14-inch, 2024)",
"Mac16,2": "iMac (24-inch, 2024, Two ports)",
"Mac16,3": "iMac (24-inch, 2024, Four ports)",
"Mac16,5": "MacBook Pro (16-inch, 2024)",
"Mac16,6": "MacBook Pro (14-inch, 2024)",
"Mac16,7": "MacBook Pro (16-inch, 2024)",
"Mac16,8": "MacBook Pro (14-inch, 2024)",
"Mac16,9": "Mac Studio (2025)",
"Mac16,10": "Mac mini (2024)",
"Mac16,11": "Mac mini (2024)",
"Mac16,12": "MacBook Air (13-inch, M4, 2025)",
"Mac16,13": "MacBook Air (15-inch, M4, 2025)",
"Mac17,2": "MacBook Pro (14-inch, M5)",
"MacBook5,2": [
"MacBook (13-inch, Early 2009)",
"MacBook (13-inch, Mid 2009)"
],
"MacBook6,1": "MacBook (13-inch, Late 2009)",
"MacBook7,1": "MacBook (13-inch, Mid 2010)",
"MacBook8,1": "MacBook (Retina, 12-inch, Early 2015)",
"MacBook9,1": "MacBook (Retina, 12-inch, Early 2016)",
"MacBook10,1": "MacBook (Retina, 12-inch, 2017)",
"MacBookAir2,1": "MacBook Air (Mid 2009)",
"MacBookAir3,1": "MacBook Air (11-inch, Late 2010)",
"MacBookAir3,2": "MacBook Air (13-inch, Late 2010)",
"MacBookAir4,1": "MacBook Air (11-inch, Mid 2011)",
"MacBookAir4,2": "MacBook Air (13-inch, Mid 2011)",
"MacBookAir5,1": "MacBook Air (11-inch, Mid 2012)",
"MacBookAir5,2": "MacBook Air (13-inch, Mid 2012)",
"MacBookAir6,1": [
"MacBook Air (11-inch, Early 2014)",
"MacBook Air (11-inch, Mid 2013)"
],
"MacBookAir6,2": [
"MacBook Air (13-inch, Early 2014)",
"MacBook Air (13-inch, Mid 2013)"
],
"MacBookAir7,1": "MacBook Air (11-inch, Early 2015)",
"MacBookAir7,2": [
"MacBook Air (13-inch, 2017)",
"MacBook Air (13-inch, Early 2015)"
],
"MacBookAir8,1": "MacBook Air (Retina, 13-inch, 2018)",
"MacBookAir8,2": "MacBook Air (Retina, 13-inch, 2019)",
"MacBookAir9,1": "MacBook Air (Retina, 13-inch, 2020)",
"MacBookAir10,1": "MacBook Air (M1, 2020)",
"MacBookPro4,1": [
"MacBook Pro (15-inch, Early 2008)",
"MacBook Pro (17-inch, Early 2008)"
],
"MacBookPro5,1": "MacBook Pro (15-inch, Late 2008)",
"MacBookPro5,2": [
"MacBook Pro (17-inch, Early 2009)",
"MacBook Pro (17-inch, Mid 2009)"
],
"MacBookPro5,3": [
"MacBook Pro (15-inch, 2.53GHz, Mid 2009)",
"MacBook Pro (15-inch, Mid 2009)"
],
"MacBookPro5,5": "MacBook Pro (13-inch, Mid 2009)",
"MacBookPro6,1": "MacBook Pro (17-inch, Mid 2010)",
"MacBookPro6,2": "MacBook Pro (15-inch, Mid 2010)",
"MacBookPro7,1": "MacBook Pro (13-inch, Mid 2010)",
"MacBookPro8,1": [
"MacBook Pro (13-inch, Early 2011)",
"MacBook Pro (13-inch, Late 2011)"
],
"MacBookPro8,2": [
"MacBook Pro (15-inch, Early 2011)",
"MacBook Pro (15-inch, Late 2011)"
],
"MacBookPro8,3": [
"MacBook Pro (17-inch, Early 2011)",
"MacBook Pro (17-inch, Late 2011)"
],
"MacBookPro9,1": "MacBook Pro (15-inch, Mid 2012)",
"MacBookPro9,2": "MacBook Pro (13-inch, Mid 2012)",
"MacBookPro10,1": [
"MacBook Pro (Retina, 15-inch, Early 2013)",
"MacBook Pro (Retina, 15-inch, Mid 2012)"
],
"MacBookPro10,2": [
"MacBook Pro (Retina, 13-inch, Early 2013)",
"MacBook Pro (Retina, 13-inch, Late 2012)"
],
"MacBookPro11,1": [
"MacBook Pro (Retina, 13-inch, Late 2013)",
"MacBook Pro (Retina, 13-inch, Mid 2014)"
],
"MacBookPro11,2": [
"MacBook Pro (Retina, 15-inch, Late 2013)",
"MacBook Pro (Retina, 15-inch, Mid 2014)"
],
"MacBookPro11,3": [
"MacBook Pro (Retina, 15-inch, Late 2013)",
"MacBook Pro (Retina, 15-inch, Mid 2014)"
],
"MacBookPro11,4": "MacBook Pro (Retina, 15-inch, Mid 2015)",
"MacBookPro11,5": "MacBook Pro (Retina, 15-inch, Mid 2015)",
"MacBookPro12,1": "MacBook Pro (Retina, 13-inch, Early 2015)",
"MacBookPro13,1": "MacBook Pro (13-inch, 2016, Two Thunderbolt 3 ports)",
"MacBookPro13,2": "MacBook Pro (13-inch, 2016, Four Thunderbolt 3 ports)",
"MacBookPro13,3": "MacBook Pro (15-inch, 2016)",
"MacBookPro14,1": "MacBook Pro (13-inch, 2017, Two Thunderbolt 3 ports)",
"MacBookPro14,2": "MacBook Pro (13-inch, 2017, Four Thunderbolt 3 ports)",
"MacBookPro14,3": "MacBook Pro (15-inch, 2017)",
"MacBookPro15,1": [
"MacBook Pro (15-inch, 2018)",
"MacBook Pro (15-inch, 2019)"
],
"MacBookPro15,2": [
"MacBook Pro (13-inch, 2018, Four Thunderbolt 3 ports)",
"MacBook Pro (13-inch, 2019, Four Thunderbolt 3 ports)"
],
"MacBookPro15,3": "MacBook Pro (15-inch, 2019)",
"MacBookPro15,4": "MacBook Pro (13-inch, 2019, Two Thunderbolt 3 ports)",
"MacBookPro16,1": "MacBook Pro (16-inch, 2019)",
"MacBookPro16,2": "MacBook Pro (13-inch, 2020, Four Thunderbolt 3 ports)",
"MacBookPro16,3": "MacBook Pro (13-inch, 2020, Two Thunderbolt 3 ports)",
"MacBookPro16,4": "MacBook Pro (16-inch, 2019)",
"MacBookPro17,1": "MacBook Pro (13-inch, M1, 2020)",
"MacBookPro18,1": "MacBook Pro (16-inch, 2021)",
"MacBookPro18,2": "MacBook Pro (16-inch, 2021)",
"MacBookPro18,3": "MacBook Pro (14-inch, 2021)",
"MacBookPro18,4": "MacBook Pro (14-inch, 2021)",
"Macmini3,1": [
"Mac mini (Early 2009)",
"Mac mini (Late 2009)"
],
"Macmini4,1": "Mac mini (Mid 2010)",
"Macmini5,1": "Mac mini (Mid 2011)",
"Macmini5,2": "Mac mini (Mid 2011)",
"Macmini6,1": "Mac mini (Late 2012)",
"Macmini6,2": "Mac mini (Late 2012)",
"Macmini7,1": "Mac mini (Late 2014)",
"Macmini8,1": "Mac mini (2018)",
"Macmini9,1": "Mac mini (M1, 2020)",
"MacPro4,1": "Mac Pro (Early 2009)",
"MacPro5,1": [
"Mac Pro (Mid 2010)",
"Mac Pro (Mid 2012)",
"Mac Pro Server (Mid 2010)",
"Mac Pro Server (Mid 2012)"
],
"MacPro6,1": "Mac Pro (Late 2013)",
"MacPro7,1": [
"Mac Pro (2019)",
"Mac Pro (Rack, 2019)"
]
}