Как разрешить проблемы 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.