Как разрешить проблемы CGDirectDisplayID, связанные с изменениями на более новых ноутбуках Apple с несколькими GPU в Core Foundation/IO Kit?
В Mac OS X каждый дисплей получает уникальный CGDirectDisplayID
номер, присвоенный ему. Вы можете использовать CGGetActiveDisplayList(
) или [NSScreen screens]
для доступа к ним, среди прочих. Per Документы Apple:
Идентификатор дисплея может сохраняться процессы и перезагрузка системы; обычно остается постоянной, пока определенные параметры отображения не изменение.
В новом MacBook Pro середины 2010 года Apple начала использовать графику Intel/nVidia с автоматическим переключением. Ноутбуки имеют два GPU, маломощную Intel и мощную nVidia. Предыдущие двухпроцессорные ноутбуки (модели 2009 года) не имели автоматического переключения на GPU и требовали от пользователя изменения настроек, выхода из системы и повторного входа в систему, чтобы сделать коммутатор GPU. Даже более старые системы имели только один графический процессор.
Проблема с моделями середины 2010 года, когда CGDirectDisplayID не остается прежним, когда дисплей переключается с одного GPU на другой. Например:
- Питание от ноутбука.
- Встроенный ЖК-дисплей
Экран управляется чипсетом Intel.
Идентификатор дисплея: 30002
- Внешний
Дисплей подключен.
- Встроенный
ЖК-экран переключается на nVidia
Набор микросхем. Он отображает идентификационные изменения:
30004
- Внешний дисплей
чипсетом nVidia.
- ... в этот момент,
чипсет Intel неактивен...
- Пользователь отключает Внешний дисплей.
- Встроенный ЖК-экран переключается на
Чипсет Intel. Он отображает идентификатор
возвращается к оригиналу: 30002
Мой вопрос: как я могу сопоставить старый идентификатор дисплея с новым идентификатором отображения, когда они изменяются из-за изменения графического процессора?
Мысль о:
Я заметил, что идентификатор дисплея изменяется только на 2, но у меня недостаточно тестового Mac, чтобы определить, является ли это общим для всех новых MacBook Pro, или просто для меня. Во всяком случае, работает "kludge", если "просто проверьте идентификатор дисплея, которые +/- 2 друг от друга".
Пробовал:
CGDisplayRegisterReconfigurationCallback()
, который уведомляет до и после, когда дисплеи будут меняться, не имеет соответствующей логики. Вложение чего-то подобного внутри метода, зарегистрированного в нем, не работает:
// Run before display settings change:
CGDirectDisplayID directDisplayID = ...;
io_service_t servicePort = CGDisplayIOServicePort(directDisplayID);
CFDictionaryRef oldInfoDict = IODisplayCreateInfoDictionary(servicePort, kIODisplayMatchingInfo);
// ...display settings change...
// Run after display settings change:
CGDirectDisplayID directDisplayID = ...;
io_service_t servicePort = CGDisplayIOServicePort(directDisplayID);
CFDictionaryRef newInfoDict = IODisplayCreateInfoDictionary(servicePort, kIODisplayMatchingInfo);
BOOL match = IODisplayMatchDictionaries(oldInfoDict, newInfoDict, 0);
if (match)
NSLog(@"Displays are a match");
else
NSLog(@"Displays are not a match");
Что происходит выше:
- Я делаю кеширование oldInfoDict до изменения настроек отображения.
- Ожидание изменения параметров отображения
- Затем сравнивая oldInfoDict с newInfoDict с помощью
IODisplayMatchDictionaries()
-
IODisplayMatchDictionaries()
возвращает BOOL, либо они одинаковы, либо они не отличаются.
К сожалению, IODisplayMatchDictionaries()
не возвращает YES, если один и тот же дисплей изменил графический процессор. Здесь пример словаря, который он сравнивает (посмотрите на ключ IODisplayLocation
):
// oldInfoDict (Display ID: 30002)
oldInfoDict: {
DisplayProductID = 40144;
DisplayVendorID = 1552;
IODisplayLocation = "IOService:/AppleACPIPlatformExpert/[email protected]/AppleACPIPCI/[email protected]/AppleIntelFramebuffer/display0/AppleBacklightDisplay";
}
// newInfoDict (Display ID: 30004)
newInfoDict: {
DisplayProductID = 40144;
DisplayVendorID = 1552;
IODisplayLocation = "IOService:/AppleACPIPlatformExpert/[email protected]/AppleACPIPCI/[email protected]/IOPCI2PCIBridge/[email protected]/NVDA,[email protected]/NVDA/display0/AppleBacklightDisplay";
}
Как вы можете видеть, клавиша IODisplayLocation
изменяется при переключении GPU, поэтому IODisplayMatchDictionaries()
не работает.
Теоретически я могу сравнить только ключи DisplayProductID
и DisplayVendorID
, но я пишу программное обеспечение конечного пользователя, и меня беспокоит ситуация, когда пользователи имеют два или более одинаковых монитора, подключенных к сети (это означает, оба имеют одинаковый DisplayProductID/DisplayVendorID). Другими словами, это менее совершенное решение, открытое для потенциальных сбоев.
Любая помощь очень ценится!:)
Ответы
Ответ 1
Я не нашел более разумно лучшего способа, чем то, что вы называете "Пробоченным". Но я нашел решение проблемы двусмысленности сравнения только идентификатора поставщика и идентификатора продукта.
oldInfoDict
и newInfoDict
в вашем коде содержится дополнительная запись для ключа kIODisplayEDIDKey
(определенная в IOGraphicsTypes.h), которая содержит EDID каждого подключенного дисплея. Мои наблюдения показывают, что эти данные в целом остаются постоянными между коммутаторами GPU. Например:
CGDirectDisplayID displayId = [[[screen deviceDescription] valueForKey:@"NSScreenNumber"] unsignedIntValue];
io_service_t displayPort = CGDisplayIOServicePort(displayId);
if (displayPort == MACH_PORT_NULL)
return nil; // No physical device to get a name from.
CFDictionaryRef infoDict = IODisplayCreateInfoDictionary(displayPort, kIODisplayOnlyPreferredName);
NSData *displayEdid = (NSData *)CFDictionaryGetValue(infoDict, CFSTR(kIODisplayEDIDKey));
NSLog(@"EDID: %@", displayEdid);
CFRelease(infoDict);
Глядя на описание данных EDID в Википедии, этот блок уже содержит имя производителя, идентификатор продукта и серийный номер. Поэтому достаточно сравнить дисплеи, используя данные EDID (или, например, хэш, если вы хотите сравнить более короткое число).
Ответ 2
Пока я не профессионал, я считаю, что ответ должен позволить Apple уведомить вас, когда пользователь изменяет отображение. Информация в обратном вызове содержит флаги для добавления и удаления CGDirectDisplayID
s.
Пользователь не должен добавлять или удалять видеокарты во время работы, поэтому я бы играл с составлением списка при запуске, и всякий раз, когда вы получаете флаг "удалить", установите следующую операцию "добавить", чтобы заменить этот идентификатор в списке.
Я бы попробовал просто распечатать информацию, которую вы возвращаете каждый раз, когда CGDisplayRegisterReconfigurationCallback
вызывает вашу функцию. Посмотрите, если вы получите один с DeviceUID с флагом 'remove', а затем последующий вызов другого с флагом 'add'. Проверка этого идентификатора на CGGetActiveDisplayList
также поможет понять, что происходит.
Что мой лучший выбор, надеюсь, что это поможет!
Ответ 3
Используйте CFUUIDRef, который можно получить, используя:
CGDisplayCreateUUIDFromDisplayID(CGDirectDisplayID displayID)
и вы можете получить идентификатор дисплея, используя:
CGDisplayGetDisplayIDFromUUID(CFUUIDRef uuid)
Это то, что я использую для уникальной идентификации дисплеев, даже когда их CGDirectDisplayID изменяется, например, был подключен к другому порту. К сожалению, эти функции не были должным образом задокументированы Apple, но мое тестирование на нескольких компьютерах с несколькими дисплеями показало, что полученный CFUUIDRef является уникальным и последовательным -even после reboot-, независимо от того, изменился ли CGDirectDisplayID по какой-либо причине.
Чтобы проверить, является ли дисплей новым/уникальным, возьмите его CGDirectDisplayID и преобразуйте его в CFUUIDRef, а затем сравните UUID, это отношение "много к одному", многие CGDirectDisplayID будут отображаться на одном CFUUIDRef.
Эти вызовы API доступны в ApplicationServices в 10.7-10.12 и ColorSync с 10.13.