Как решить проблемы с изменением CGDirectDisplayID на новых ноутбуках Apple с несколькими графическими процессорами в Core Foundation / IO Kit? - PullRequest
10 голосов
/ 19 мая 2010

В Mac OS X каждому дисплею присваивается уникальный номер CGDirectDisplayID. Вы можете использовать CGGetActiveDisplayList() или [NSScreen screens] для доступа к ним, среди прочего. За документы Apple :

Идентификатор дисплея может сохраняться через процессы и перезагрузка системы, и как правило, остается постоянным, пока определенные параметры дисплея не изменить.

На новых MacBook Pro середины 2010 года Apple начала использовать автоматическое переключение графики Intel / nVidia. Ноутбуки имеют два графических процессора, маломощный Intel и мощный nVidia. Предыдущие ноутбуки с двумя графическими процессорами (модели 2009 года) не имели автоматического переключения графических процессоров и требовали от пользователя изменения настроек, выхода из системы и повторного входа в систему для переключения графического процессора. Даже в старых системах был только один графический процессор.

Существует проблема с моделями середины 2010 года, когда CGDirectDisplayID не остаются неизменными, когда дисплей переключается с одного графического процессора на другое. Например:

  1. Ноутбук включается.
  2. Встроенный ЖК Экран управляется чипсетом Intel. Идентификатор дисплея: 30002
  3. Внешний Дисплей подключен.
  4. Встроенный ЖК-экран переключается на nVidia Набор микросхем. Это изменения ID дисплея: 30004
  5. Внешний дисплей управляется от чипсета nVidia.
  6. ... на данный момент, чипсет Intel не работает ...
  7. Пользователь отключает Внешний дисплей .
  8. Встроенный ЖК-экран переключается обратно на Чипсет Intel. Это идентификатор дисплея возвращается к оригиналу: 30002

У меня вопрос: как сопоставить старый идентификатор дисплея новому идентификатору дисплея, если они меняются из-за смены графического процессора?


Мысли о:

Я заметил, что идентификатор дисплея меняется только на 2, но у меня недостаточно тестовых Mac, чтобы определить, является ли это общим для всех новых MacBook Pro или только для меня. В любом случае это своего рода клочок, если «просто проверьте идентификаторы дисплея, которые находятся +/- 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");

То, что происходит выше, это:

  1. Я кеширую oldInfoDict до изменения настроек дисплея.
  2. Ожидание изменения настроек дисплея
  3. Затем сравнение oldInfoDict с newInfoDict с использованием IODisplayMatchDictionaries()
  4. IODisplayMatchDictionaries() возвращает BOOL, либо ДА, они одинаковые, либо НЕТ, они разные.

К сожалению, IODisplayMatchDictionaries() не возвращает YES, если тот же дисплей изменил GPU. Вот пример сравнения словаря (посмотрите на ключ IODisplayLocation):

// oldInfoDict  (Display ID: 30002)
oldInfoDict: {
    DisplayProductID = 40144;
    DisplayVendorID = 1552;
    IODisplayLocation = "IOService:/AppleACPIPlatformExpert/PCI0@0/AppleACPIPCI/IGPU@2/AppleIntelFramebuffer/display0/AppleBacklightDisplay";
}

// newInfoDict  (Display ID: 30004)
newInfoDict: {
    DisplayProductID = 40144;
    DisplayVendorID = 1552;
    IODisplayLocation = "IOService:/AppleACPIPlatformExpert/PCI0@0/AppleACPIPCI/P0P2@1/IOPCI2PCIBridge/GFX0@0/NVDA,Display-A@0/NVDA/display0/AppleBacklightDisplay";
}

Как видите, кнопка IODisplayLocation изменяется при переключении графических процессоров, поэтому IODisplayMatchDictionaries() не работает.

Теоретически я могу сравнить только клавиши DisplayProductID и DisplayVendorID, но я пишу программное обеспечение для конечного пользователя, и меня беспокоит ситуация, когда у пользователей есть два или более одинаковых монитора, то есть будут ли оба одинаковых DisplayProductID / DisplayVendorID). Другими словами, это не идеальное решение, открытое для потенциальных сбоев.


Любая помощь очень ценится! :)

Ответы [ 3 ]

3 голосов
/ 20 ноября 2013

Я не нашел никакого лучшего способа, чем тот, который вы перечислили как «Пробовал». Но я нашел решение проблемы неоднозначности сравнения только идентификатора поставщика и идентификатора продукта.

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 в Википедии, этот BLOB-объект уже содержит производителя, идентификатор продукта и серийный номер. Поэтому достаточно сравнить дисплеи, используя данные EDID (или, например, их хеш, если вы хотите сравнить только более короткое число).

2 голосов
/ 21 сентября 2018

Используйте CFUUIDRef, который можно получить с помощью:

CGDisplayCreateUUIDFromDisplayID(CGDirectDisplayID displayID), и вы можете получить идентификатор дисплея обратно, используя:

CGDisplayGetDisplayIDFromUUID(CFUUIDRef uuid)

Это то, что яm используется для уникальной идентификации дисплеев, даже когда их CGDirectDisplayID изменяется, например, был подключен к другому порту.К сожалению, Apple не документирует эти функции должным образом, но мое тестирование на нескольких компьютерах Mac с несколькими дисплеями показало, что полученный CFUUIDRef уникален и непротиворечив, даже после перезагрузки, независимо от того, изменился ли CGDirectDisplayID по какой-либо причине.

Чтобы проверить, является ли отображение новым / уникальным, возьмите его CGDirectDisplayID и преобразуйте его в CFUUIDRef, а затем сравните UUID, это отношение многие-к-одному, многие CGDirectDisplayID будут отображаться в один CFUUIDRef.

Эти вызовы API доступны в ApplicationServices в 10.7 - 10.12 и в ColorSync с 10.13.

1 голос
/ 21 мая 2010

Хотя я не профессионал, я считаю, что ответ - позволить Apple уведомлять о вас, когда пользователь меняет экран. Информация в обратном вызове содержит флаги для добавления и удаления CGDirectDisplayID s.

Пользователь не должен добавлять или удалять видеокарты во время работы, поэтому я бы поиграл с созданием списка при запуске, и всякий раз, когда вы получаете флаг «удалить», установите следующую операцию «добавления», чтобы заменить этот идентификатор в списке .

Я бы попробовал просто печатать информацию, которую вы получаете каждый раз, когда CGDisplayRegisterReconfigurationCallback вызывает вашу функцию. Посмотрите, получите ли вы один с DeviceUID с флагом «удалить», а затем последующий вызов другого с флагом «добавить». Проверка этих идентификаторов на CGGetActiveDisplayList также поможет понять, что происходит.

Это мой лучший выбор, надеюсь, это поможет!

...