@@ -13,7 +13,8 @@ class Arm64DDCUtils: NSObject {
1313 public struct DisplayService {
1414 var displayID : CGDirectDisplayID = 0
1515 var service : IOAVService ?
16- var serviceIoregPosition : Int64 = 0
16+ var serviceLocation : Int = 0
17+ var isDiscouraged : Bool = false
1718 }
1819
1920 #if arch(arm64)
@@ -30,21 +31,22 @@ class Arm64DDCUtils: NSObject {
3031 for displayID in displayIDs {
3132 for ioregServiceForMatching in ioregServicesForMatching {
3233 let score = self . ioregMatchScore ( displayID: displayID, ioregEdidUUID: ioregServiceForMatching. edidUUID, ioregProductName: ioregServiceForMatching. productName, ioregSerialNumber: ioregServiceForMatching. serialNumber)
33- let displayService = DisplayService ( displayID: displayID, service: ioregServiceForMatching. service, serviceIoregPosition: ioregServiceForMatching. serviceIoregPosition)
34+ let isDiscouraged = self . checkIfDiscouraged ( ioregService: ioregServiceForMatching)
35+ let displayService = DisplayService ( displayID: displayID, service: ioregServiceForMatching. service, serviceLocation: ioregServiceForMatching. serviceLocation, isDiscouraged: isDiscouraged)
3436 if scoredCandidateDisplayServices [ score] == nil {
3537 scoredCandidateDisplayServices [ score] = [ ]
3638 }
3739 scoredCandidateDisplayServices [ score] ? . append ( displayService)
3840 }
3941 }
40- var takenServiceIoregPositions : [ Int64 ] = [ ]
42+ var takenServiceLocations : [ Int ] = [ ]
4143 var takenDisplayIDs : [ CGDirectDisplayID ] = [ ]
4244 for score in stride ( from: self . MAX_MATCH_SCORE, to: 0 , by: - 1 ) {
4345 if let scoredCandidateDisplayService = scoredCandidateDisplayServices [ score] {
4446 for candidateDisplayService in scoredCandidateDisplayService {
45- if !( takenDisplayIDs. contains ( candidateDisplayService. displayID) || takenServiceIoregPositions . contains ( candidateDisplayService. serviceIoregPosition ) ) {
47+ if !( takenDisplayIDs. contains ( candidateDisplayService. displayID) || takenServiceLocations . contains ( candidateDisplayService. serviceLocation ) ) {
4648 takenDisplayIDs. append ( candidateDisplayService. displayID)
47- takenServiceIoregPositions . append ( candidateDisplayService. serviceIoregPosition )
49+ takenServiceLocations . append ( candidateDisplayService. serviceLocation )
4850 matchedDisplayServices. append ( candidateDisplayService)
4951 }
5052 }
@@ -112,11 +114,14 @@ class Arm64DDCUtils: NSObject {
112114
113115 private struct IOregService {
114116 var edidUUID : String = " "
117+ var manufacturerID : String = " "
115118 var productName : String = " "
116119 var serialNumber : Int64 = 0
120+ var location : String = " "
121+ var transportUpstream : String = " "
122+ var transportDownstream : String = " "
117123 var service : IOAVService ?
118- var displayAttributesIoregPosition : Int64 = 0
119- var serviceIoregPosition : Int64 = 0
124+ var serviceLocation : Int = 0
120125 }
121126
122127 private static let MAX_MATCH_SCORE : Int = 6
@@ -132,16 +137,16 @@ class Arm64DDCUtils: NSObject {
132137 }
133138 let edidUUIDSearchKeys : [ KeyLoc ] = [
134139 // Vendor ID
135- KeyLoc ( key: String ( format: " %04x " , UInt16 ( kDisplayVendorID) ) . uppercased ( ) , loc: 0 ) ,
140+ KeyLoc ( key: String ( format: " %04x " , UInt16 ( max ( 0 , min ( kDisplayVendorID, 256 ^ 2 - 1 ) ) ) ) . uppercased ( ) , loc: 0 ) ,
136141 // Product ID
137- KeyLoc ( key: String ( format: " %02x " , UInt8 ( ( UInt16 ( kDisplayProductID) >> ( 0 * 8 ) ) & 0xFF ) ) . uppercased ( )
138- + String( format: " %02x " , UInt8 ( ( UInt16 ( kDisplayProductID) >> ( 1 * 8 ) ) & 0xFF ) ) . uppercased ( ) , loc: 4 ) ,
142+ KeyLoc ( key: String ( format: " %02x " , UInt8 ( ( UInt16 ( max ( 0 , min ( kDisplayProductID, 256 ^ 2 - 1 ) ) ) >> ( 0 * 8 ) ) & 0xFF ) ) . uppercased ( )
143+ + String( format: " %02x " , UInt8 ( ( UInt16 ( max ( 0 , min ( kDisplayProductID, 256 ^ 2 - 1 ) ) ) >> ( 1 * 8 ) ) & 0xFF ) ) . uppercased ( ) , loc: 4 ) ,
139144 // Manufacture date
140- KeyLoc ( key: String ( format: " %02x " , UInt8 ( kDisplayWeekOfManufacture) ) . uppercased ( )
141- + String( format: " %02x " , UInt8 ( kDisplayYearOfManufacture - 1990 ) ) . uppercased ( ) , loc: 19 ) ,
145+ KeyLoc ( key: String ( format: " %02x " , UInt8 ( max ( 0 , min ( kDisplayWeekOfManufacture, 256 - 1 ) ) ) ) . uppercased ( )
146+ + String( format: " %02x " , UInt8 ( max ( 0 , min ( kDisplayYearOfManufacture - 1990 , 256 - 1 ) ) ) ) . uppercased ( ) , loc: 19 ) ,
142147 // Image size
143- KeyLoc ( key: String ( format: " %02x " , UInt8 ( kDisplayHorizontalImageSize / 10 ) ) . uppercased ( )
144- + String( format: " %02x " , UInt8 ( kDisplayVerticalImageSize / 10 ) ) . uppercased ( ) , loc: 30 ) ,
148+ KeyLoc ( key: String ( format: " %02x " , UInt8 ( max ( 0 , min ( kDisplayHorizontalImageSize / 10 , 256 - 1 ) ) ) ) . uppercased ( )
149+ + String( format: " %02x " , UInt8 ( max ( 0 , min ( kDisplayVerticalImageSize / 10 , 256 - 1 ) ) ) ) . uppercased ( ) , loc: 30 ) ,
145150 ]
146151 for searchKey in edidUUIDSearchKeys where searchKey. key != " 0000 " && searchKey. key == ioregEdidUUID. prefix ( searchKey. loc + 4 ) . suffix ( 4 ) {
147152 matchScore += 1
@@ -157,16 +162,16 @@ class Arm64DDCUtils: NSObject {
157162 return matchScore
158163 }
159164
160- // Iterate to the next requested item in the ioreg tree
161- private static func ioregIterateToNext( ioregObjectName: String , iterator: inout io_iterator_t , position: inout Int64 ) -> io_service_t {
165+ // Iterate to the next AppleCLCD2 or DCPAVServiceProxy item in the ioreg tree and return the name and corresponding service
166+ private static func ioregIterateToNextObjectOfInterest( interests _: [ String ] , iterator: inout io_iterator_t ) -> ( name: String , service: io_service_t ) ? {
167+ var objectName : String = " "
162168 var service : io_service_t = IO_OBJECT_NULL
163169 let name = UnsafeMutablePointer< CChar> . allocate( capacity: MemoryLayout< io_name_t> . size)
164170 defer {
165171 name. deallocate ( )
166172 }
167173 while true {
168174 service = IOIteratorNext ( iterator)
169- position += 1
170175 guard service != MACH_PORT_NULL else {
171176 service = IO_OBJECT_NULL
172177 break
@@ -175,71 +180,95 @@ class Arm64DDCUtils: NSObject {
175180 service = IO_OBJECT_NULL
176181 break
177182 }
178- if String ( cString: name) == ioregObjectName {
179- break
183+ if String ( cString: name) == " AppleCLCD2 " || String ( cString: name) == " DCPAVServiceProxy " {
184+ objectName = String ( cString: name)
185+ return ( objectName, service)
180186 }
181187 }
182- return service
188+ return nil
183189 }
184190
185191 // Returns EDID UUDI, Product Name and Serial Number in an IOregService if it is found using the provided io_service_t pointing to a AppleCDC2 item in the ioreg tree
186- private static func getIORegServiceAppleCDC2Properties( service: io_service_t , position: Int64 = 0 ) -> IOregService ? {
192+ private static func getIORegServiceAppleCDC2Properties( service: io_service_t ) -> IOregService {
193+ var ioregService = IOregService ( )
187194 if let unmanagedEdidUUID = IORegistryEntryCreateCFProperty ( service, CFStringCreateWithCString ( kCFAllocatorDefault, " EDID UUID " , kCFStringEncodingASCII) , kCFAllocatorDefault, IOOptionBits ( kIORegistryIterateRecursively) ) , let edidUUID = unmanagedEdidUUID. takeRetainedValue ( ) as? String {
188- var ioregService = IOregService ( )
189- ioregService. displayAttributesIoregPosition = position
190195 ioregService. edidUUID = edidUUID
191- if let unmanagedDisplayAttrs = IORegistryEntryCreateCFProperty ( service, CFStringCreateWithCString ( kCFAllocatorDefault, " DisplayAttributes " , kCFStringEncodingASCII) , kCFAllocatorDefault, IOOptionBits ( kIORegistryIterateRecursively) ) , let displayAttrs = unmanagedDisplayAttrs. takeRetainedValue ( ) as? NSDictionary , let productAttrs = displayAttrs. value ( forKey: " ProductAttributes " ) as? NSDictionary {
192- if let productName = productAttrs. value ( forKey: " ProductName " ) as? String {
193- ioregService. productName = productName
194- }
195- if let serialNumber = productAttrs. value ( forKey: " SerialNumber " ) as? Int64 {
196- ioregService. serialNumber = serialNumber
197- }
196+ }
197+ if let unmanagedDisplayAttrs = IORegistryEntryCreateCFProperty ( service, CFStringCreateWithCString ( kCFAllocatorDefault, " DisplayAttributes " , kCFStringEncodingASCII) , kCFAllocatorDefault, IOOptionBits ( kIORegistryIterateRecursively) ) , let displayAttrs = unmanagedDisplayAttrs. takeRetainedValue ( ) as? NSDictionary , let productAttrs = displayAttrs. value ( forKey: " ProductAttributes " ) as? NSDictionary {
198+ if let manufacturerID = productAttrs. value ( forKey: " ManufacturerID " ) as? String {
199+ ioregService. manufacturerID = manufacturerID
200+ }
201+ if let productName = productAttrs. value ( forKey: " ProductName " ) as? String {
202+ ioregService. productName = productName
203+ }
204+ if let serialNumber = productAttrs. value ( forKey: " SerialNumber " ) as? Int64 {
205+ ioregService. serialNumber = serialNumber
198206 }
199- return ioregService
200207 }
201- return nil
208+ if let unmanagedTransport = IORegistryEntryCreateCFProperty ( service, CFStringCreateWithCString ( kCFAllocatorDefault, " Transport " , kCFStringEncodingASCII) , kCFAllocatorDefault, IOOptionBits ( kIORegistryIterateRecursively) ) , let transport = unmanagedTransport. takeRetainedValue ( ) as? NSDictionary {
209+ if let upstream = transport. value ( forKey: " Upstream " ) as? String {
210+ ioregService. transportUpstream = upstream
211+ }
212+ if let downstream = transport. value ( forKey: " Downstream " ) as? String {
213+ ioregService. transportDownstream = downstream
214+ }
215+ }
216+ return ioregService
202217 }
203218
204219 // Sets up the service in an IOregService if it is found using the provided io_service_t pointing to a DCPAVServiceProxy item in the ioreg tree
205- private static func setIORegServiceDCPAVServiceProxy( service: io_service_t , ioregService: inout IOregService , position : Int64 = 0 ) -> Bool {
220+ private static func setIORegServiceDCPAVServiceProxy( service: io_service_t , ioregService: inout IOregService ) {
206221 if let unmanagedLocation = IORegistryEntryCreateCFProperty ( service, CFStringCreateWithCString ( kCFAllocatorDefault, " Location " , kCFStringEncodingASCII) , kCFAllocatorDefault, IOOptionBits ( kIORegistryIterateRecursively) ) , let location = unmanagedLocation. takeRetainedValue ( ) as? String {
222+ ioregService. location = location
207223 if location == " External " {
208- ioregService. serviceIoregPosition = position
209224 ioregService. service = IOAVServiceCreateWithService ( kCFAllocatorDefault, service) ? . takeRetainedValue ( ) as IOAVService
210- return true
211225 }
212226 }
213- return false
214227 }
215228
216229 // Returns IOAVSerivces with associated display properties for matching logic
217230 private static func getIoregServicesForMatching( ) -> [ IOregService ] {
218- var position : Int64 = 0
231+ var serviceLocation : Int = 0
219232 var ioregServicesForMatching : [ IOregService ] = [ ]
220233 let ioregRoot : io_registry_entry_t = IORegistryGetRootEntry ( kIOMasterPortDefault)
221234 var iterator = io_iterator_t ( )
235+ var ioregService = IOregService ( )
222236 guard IORegistryEntryCreateIterator ( ioregRoot, " IOService " , IOOptionBits ( kIORegistryIterateRecursively) , & iterator) == KERN_SUCCESS else {
223237 return ioregServicesForMatching
224238 }
225239 while true {
226- let serviceAppleCLCD2 = self . ioregIterateToNext ( ioregObjectName: " AppleCLCD2 " , iterator: & iterator, position: & position)
227- guard serviceAppleCLCD2 != IO_OBJECT_NULL else {
228- break
229- }
230- // We will check if it has an EDID UUID. If so, then we take it as an external display
231- if var ioregService = getIORegServiceAppleCDC2Properties ( service: serviceAppleCLCD2, position: position) {
232- // We will now iterate further, looking for the belonging "DCPAVServiceProxy" service (which should follow "AppleCLCD2" somewhat closely)
233- let serviceDCPAVServiceProxy = self . ioregIterateToNext ( ioregObjectName: " DCPAVServiceProxy " , iterator: & iterator, position: & position)
234- guard serviceDCPAVServiceProxy != IO_OBJECT_NULL else {
235- break
240+ if let objectOfInterest = ioregIterateToNextObjectOfInterest ( interests: [ " AppleCLCD2 " , " DCPAVServiceProxy " ] , iterator: & iterator) {
241+ if objectOfInterest. name == " AppleCLCD2 " , objectOfInterest. service != IO_OBJECT_NULL {
242+ ioregService = self . getIORegServiceAppleCDC2Properties ( service: objectOfInterest. service)
243+ serviceLocation += 1
244+ ioregService. serviceLocation = serviceLocation
236245 }
237- // Let's now create an instance of IOAVService with this service and add it to the service store with the "AppleCLCD2" strings
238- if self . setIORegServiceDCPAVServiceProxy ( service: serviceDCPAVServiceProxy , ioregService: & ioregService, position : position ) {
246+ if objectOfInterest . name == " DCPAVServiceProxy " , objectOfInterest . service != IO_OBJECT_NULL {
247+ self . setIORegServiceDCPAVServiceProxy ( service: objectOfInterest . service , ioregService: & ioregService)
239248 ioregServicesForMatching. append ( ioregService)
240249 }
250+ } else {
251+ break
241252 }
242253 }
243254 return ioregServicesForMatching
244255 }
256+
257+ // Check if it is problematic to enable DDC on the display
258+ private static func checkIfDiscouraged( ioregService: IOregService ) -> Bool {
259+ var modelIdentifier : String = " "
260+ let platformExpertDevice = IOServiceGetMatchingService ( kIOMasterPortDefault, IOServiceMatching ( " IOPlatformExpertDevice " ) )
261+ if let modelData = IORegistryEntryCreateCFProperty ( platformExpertDevice, " model " as CFString , kCFAllocatorDefault, 0 ) . takeRetainedValue ( ) as? Data , let modelIdentifierCString = String ( data: modelData, encoding: . utf8) ? . cString ( using: . utf8) {
262+ modelIdentifier = String ( cString: modelIdentifierCString)
263+ }
264+ // This is a well known dummy plug (not a real display) but it breaks DDC communication on M1
265+ if ioregService. manufacturerID == " AOC " , ioregService. productName == " 28E850 " {
266+ return true
267+ }
268+ // First service location of Mac Mini HDMI is broken for DDC communication
269+ if ioregService. transportDownstream == " HDMI " , ioregService. serviceLocation == 1 , modelIdentifier == " Macmini9,1 " {
270+ return true
271+ }
272+ return false
273+ }
245274}
0 commit comments