12

在 Mac OS X 中,每个显示器都CGDirectDisplayID分配有一个唯一的编号。您可以使用CGGetActiveDisplayList() 或[NSScreen screens]访问它们等。根据Apple 的文档

显示 ID 可以跨进程和系统重新启动持续存在,并且只要某些显示参数不更改,通常保持不变。

在 2010 年中期更新的 MacBook Pro 上,Apple 开始使用自动切换 Intel/nVidia 显卡。笔记本电脑有两个 GPU,一个低功耗的 Intel 和一个高性能的 nVidia。以前的双 GPU 笔记本电脑(2009 年型号)没有自动 GPU 切换功能,并且需要用户进行设置更改、注销,然后再次登录才能进行 GPU 切换。即使是较旧的系统也只有一个 GPU。

2010 年中期的型号存在一个问题,当显示器从一个 GPU 切换到下一个 GPU 时,CGDirectDisplayID 不会保持不变。例如:

  1. 笔记本电脑开机。
  2. 内置LCD 屏幕由 Intel 芯片组驱动。显示 ID: 30002
  3. 外接显示器已插入。
  4. 内置 LCD 屏幕切换到 nVidia 芯片组。它的显示ID更改: 30004
  5. 外接显示器由 nVidia 芯片组驱动。
  6. ...此时,英特尔芯片组处于休眠状态...
  7. 用户拔下外接显示器
  8. 内置LCD 屏幕切换回 Intel 芯片组。它的显示ID变回原来的:30002

我的问题是,当旧的显示 ID 由于 GPU 更改而改变时,如何将旧显示 ID 与新显示 ID 匹配?


想过:

我注意到显示 ID 仅更改 2,但我没有足够的测试 Mac 可用来确定这是否适用于所有新的 MacBook Pro,或者只是我的。无论如何,如果“只检查彼此相差 +/-2 的显示 ID”,那是一种混搭。


试过:

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. 然后使用比较oldInfoDictnewInfoDictIODisplayMatchDictionaries()
  4. IODisplayMatchDictionaries()返回一个 BOOL,要么是它们相同,要么不它们不同。

不幸的是,IODisplayMatchDictionaries()如果相同的显示器更改了 GPU,则不会返回 YES。这是它正在比较的字典的示例(查看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切换 GPU 时键会发生变化,因此IODisplayMatchDictionaries()不起作用。

从理论上讲,我可以只比较DisplayProductIDDisplayVendorID键,但我正在编写最终用户软件,并且担心用户插入两个或多个相同显示器的情况(这意味着它们都将具有相同的 DisplayProductID/DisplayVendorID) . 换句话说,这是一个不太完美的解决方案,可能会出现潜在故障。


任何帮助是极大的赞赏!:)

4

4 回答 4

7

使用 CFUUIDRef 可以通过以下方式获得:

CGDisplayCreateUUIDFromDisplayID(CGDirectDisplayID displayID) 您可以使用以下方法取回显示 ID:

CGDisplayGetDisplayIDFromUUID(CFUUIDRef uuid)

这就是我用来唯一标识显示器的方法,即使它们的 CGDirectDisplayID 发生变化,例如插入到不同的端口。不幸的是,Apple 没有正确记录这些功能,但我在具有多个显示器的多台 Mac 上进行的测试表明,获得的 CFUUIDRef 是唯一且一致的——即使在重新启动后也是如此——无论 CGDirectDisplayID 是否因任何原因发生更改。

要检查一个显示器是否是新的/唯一的,将其 CGDirectDisplayID 转换为 CFUUIDRef,然后比较 UUID,它是多对一的关系,许多 CGDirectDisplayID 将映射到单个 CFUUIDRef。

这些 API 调用在 10.7 - 10.12 中的 ApplicationServices 和自 10.13 以来的 ColorSync 中可用。

于 2018-09-21T17:32:01.300 回答
4

我发现没有比您列出的“尝试过”更好的方法了。但我找到了解决仅比较供应商 ID 和产品 ID 的歧义问题的解决方案。

oldInfoDictnewInfoDict在您的代码中包含一个额外的键条目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);

查看 Wikipedia 中EDID的数据描述,这个 blob 已经包含制造商、产品 id 和序列号。因此,使用 EDID 数据(或者例如,如果您只想比较较短的数字,则使用它的哈希值)来比较显示就足够了。

于 2013-11-20T16:34:25.397 回答
1

虽然我不是专业人士,但我相信答案是让 Apple在用户更改显示时通知您。CGDirectDisplayID回调的 info inf 包含用于添加和删除s 的标志。

用户不应该在操作过程中添加或删除显卡,所以我会在启动时制作列表,每当你得到“删除”标志时,设置下一个“添加”操作来替换列表中的那个 ID。

我会尝试打印每次CGDisplayRegisterReconfigurationCallback调用函数时返回的信息。看看你是否得到一个带有“删除”标志的 DeviceUID,然后是一个带有“添加”标志的后续调用。检查这些 idCGGetActiveDisplayList也有助于了解发生了什么。

这是我最好的选择,希望对你有帮助!

于 2010-05-21T13:50:35.737 回答
0

我一直在使用:

-(NSString *)uuidForDisplayID:(CGDirectDisplayID)displayID
{
    CFUUIDRef uuidRef = CGDisplayCreateUUIDFromDisplayID (displayID);
    CFStringRef string = CFUUIDCreateString (kCFAllocatorDefault, uuidRef);
    CFRelease (uuidRef);
    
    return ([(NSString *)string autorelease]);
}

但是,在回调中的显示删除事件期间,即使在初始期间,调用也会kCGDisplayBeginConfigurationFlag失败CGDisplayCreateUUIDFromDisplayID并出现“无效的 displayID”错误,因此我认为在删除显示时无法从模棱两可的 CGDirectDisplayID 回到稳定的 UUID .

有趣的是,仍然可以获取 NSScreens 数组并搜索匹配 CGDirectDisplayID 的数组:

[[[screen deviceDescription] objectForKey:@"NSScreenNumber"] unsignedLongValue]

在 Xcode 中NSScreen有一个名为的属性_UUIDString,它包含相同的 UUID,但我看不出有办法从 NSScreen 中获取这个 UUID。

于 2021-07-26T19:27:16.407 回答