MacDeviceLabel.swift 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111
  1. //
  2. // Copyright © 2024 osy. All rights reserved.
  3. //
  4. // Licensed under the Apache License, Version 2.0 (the "License");
  5. // you may not use this file except in compliance with the License.
  6. // You may obtain a copy of the License at
  7. //
  8. // http://www.apache.org/licenses/LICENSE-2.0
  9. //
  10. // Unless required by applicable law or agreed to in writing, software
  11. // distributed under the License is distributed on an "AS IS" BASIS,
  12. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. // See the License for the specific language governing permissions and
  14. // limitations under the License.
  15. //
  16. import SwiftUI
  17. import UniformTypeIdentifiers
  18. struct MacDeviceLabel<Title>: View where Title : StringProtocol {
  19. let title: Title
  20. let device: MacDevice
  21. init(_ title: Title, device macDevice: MacDevice) {
  22. self.title = title
  23. self.device = macDevice
  24. }
  25. var body: some View {
  26. Label(title, systemImage: device.symbolName)
  27. }
  28. }
  29. // credits: https://adamdemasi.com/2023/04/15/mac-device-icon-by-device-class.html
  30. private extension UTTagClass {
  31. static let deviceModelCode = UTTagClass(rawValue: "com.apple.device-model-code")
  32. }
  33. private extension UTType {
  34. static let macBook = UTType("com.apple.mac.laptop")
  35. static let macBookWithNotch = UTType("com.apple.mac.notched-laptop")
  36. static let macMini = UTType("com.apple.macmini")
  37. static let macStudio = UTType("com.apple.macstudio")
  38. static let iMac = UTType("com.apple.imac")
  39. static let macPro = UTType("com.apple.macpro")
  40. static let macPro2013 = UTType("com.apple.macpro-cylinder")
  41. static let macPro2019 = UTType("com.apple.macpro-2019")
  42. }
  43. struct MacDevice {
  44. let model: String
  45. let symbolName: String
  46. #if os(macOS)
  47. static let current: Self = {
  48. let key = "hw.model"
  49. var size = size_t()
  50. sysctlbyname(key, nil, &size, nil, 0)
  51. let value = malloc(size)
  52. defer {
  53. value?.deallocate()
  54. }
  55. sysctlbyname(key, value, &size, nil, 0)
  56. guard let cChar = value?.bindMemory(to: CChar.self, capacity: size) else {
  57. return Self(model: "Unknown")
  58. }
  59. return Self(model: String(cString: cChar))
  60. }()
  61. #endif
  62. init(model: String?) {
  63. self.model = model ?? "Unknown"
  64. self.symbolName = Self.symbolName(from: self.model)
  65. }
  66. private static func checkModel(_ model: String, conformsTo type: UTType?) -> Bool {
  67. guard let type else {
  68. return false
  69. }
  70. return UTType(tag: model, tagClass: .deviceModelCode, conformingTo: nil)?.conforms(to: type) ?? false
  71. }
  72. private static func symbolName(from model: String) -> String {
  73. if checkModel(model, conformsTo: .macBookWithNotch),
  74. #available(macOS 14, iOS 17, macCatalyst 17, tvOS 17, watchOS 10, *) {
  75. // macbook.gen2 was added with SF Symbols 5.0 (macOS Sonoma, 2023), but MacBooks with a notch
  76. // were released in 2021!
  77. return "macbook.gen2"
  78. } else if checkModel(model, conformsTo: .macBook) {
  79. return "laptopcomputer"
  80. } else if checkModel(model, conformsTo: .macMini) {
  81. return "macmini"
  82. } else if checkModel(model, conformsTo: .macStudio) {
  83. return "macstudio"
  84. } else if checkModel(model, conformsTo: .iMac) {
  85. return "desktopcomputer"
  86. } else if checkModel(model, conformsTo: .macPro2019) {
  87. return "macpro.gen3"
  88. } else if checkModel(model, conformsTo: .macPro2013) {
  89. return "macpro.gen2"
  90. } else if checkModel(model, conformsTo: .macPro) {
  91. return "macpro"
  92. }
  93. return "display"
  94. }
  95. }
  96. #Preview {
  97. MacDeviceLabel("MacBook", device: MacDevice(model: "Mac14,6"))
  98. }