【问题标题】:CGDisplayIOServicePort is deprecated in OS X >= 10.9, how to replace?CGDisplayIOServicePort 在 OS X >= 10.9 中已弃用,如何替换?
【发布时间】:2013-11-17 00:54:05
【问题描述】:

我做了一个小应用程序,以允许在多台显示器上快速更改屏幕分辨率。我想将产品名称显示为显示器的标题,使用此代码很容易找到:

NSDictionary *deviceInfo = (__bridge NSDictionary *)IODisplayCreateInfoDictionary(CGDisplayIOServicePort(dispID), kIODisplayOnlyPreferredName);

NSDictionary *localizedNames = [deviceInfo objectForKey:[NSString stringWithUTF8String:kDisplayProductName]];

if([localizedNames count] > 0) {
    _title = [localizedNames objectForKey:[[localizedNames allKeys] objectAtIndex:0]];
} else {
    _title = @"Unknown display";
}

CGDisplayIOServicePort 在 OS X >= 10.9 中已弃用,Apple 的文档说没有替代品。不使用此方法如何查找服务端口或产品名称?

我尝试遍历 IO-registry 并尝试使用IOServiceGetMatchingServices 方法查找显示服务,但我对 IO-registry 不是很熟悉,因此找不到解决方案。

感谢您的帮助!

【问题讨论】:

  • 我建议针对此问题提交雷达。苹果应该告诉你如何实现你想要做的事情。
  • 你找到解决这个问题的方法了吗?
  • @Frog 不。如果 Apple 没有在 WWDC 中引入任何新的 API 来解决这个问题,也许是时候为此打开雷达票了。
  • 请注意:该方法虽然已被弃用,但这些年来一直运行良好(自 10.6 以来一直在发布。~)并且仍然适用于 macOS 10.15.1

标签: objective-c macos cocoa osx-mavericks iokit


【解决方案1】:

看起来@Eun 的帖子错过了一条信息来结束这个讨论。经过一番搜索,我发现 IOServicePortFromCGDisplayID 不是 Apple 提供的 API。相反,它是在这里找到的一段开源代码: https://github.com/glfw/glfw/blob/e0a6772e5e4c672179fc69a90bcda3369792ed1f/src/cocoa_monitor.m

我从它复制了 IOServicePortFromCGDisplayID 和“getDisplayName”。 我需要进行两项调整才能使其在 OS X 10.10 上运行。

  1. 删除 IOServicePortFromCGDisplayID 中处理序列号的代码。 (CFDictionaryGetValue 为 kDisplaySerialNumber 为我返回 NULL。)
  2. 删除项目特定 getDisplayName 中的错误处理代码。

如果您需要更多信息

  • 问题的问题跟踪器:github.com/glfw/glfw/issues/165
  • 提交 解决方案: github.com/glfw/glfw/commit/e0a6772e5e4c672179fc69a90bcda3369792ed1f

我要感谢在那里提交代码的 Matthew Henry。

【讨论】:

  • 感谢您的解决方案!我刚刚对此进行了测试,它可以工作。
  • 不幸的是,如果您连接了多个相同的显示器,这将中断。如果您只匹配供应商和产品编号,则将始终返回显示在 IOServices 中的第一个。这是上面链接代码所采用的方法的一个普遍问题——它试图匹配供应商 ID、产品 ID 和序列号,但并非所有显示器都返回序列号(例如,投影仪通常不会)。
  • 我在这里使用它stackoverflow.com/questions/17305184/… 来旋转屏幕,但它不起作用。任何关于此的想法都可能是问题所在。谢谢
【解决方案2】:

这是我对这个问题的看法。我还从 GLFW 3.1 中的代码开始,文件 cocoa_monitor.m。
但我不得不以不同于 Hiroshi 所说的方式修改它,所以这里是:

// Get the name of the specified display
- (NSString*) screenNameForDisplay: (NSNumber*) screen_id
{
    CGDirectDisplayID displayID = [screen_id unsignedIntValue];

    io_service_t serv = [self IOServicePortFromCGDisplayID: displayID];
    if (serv == 0)
        return @"unknown";

    CFDictionaryRef info = IODisplayCreateInfoDictionary(serv, kIODisplayOnlyPreferredName);
    IOObjectRelease(serv);

    CFStringRef display_name;
    CFDictionaryRef names = CFDictionaryGetValue(info, CFSTR(kDisplayProductName));

    if ( !names ||
         !CFDictionaryGetValueIfPresent(names, CFSTR("en_US"), (const void**) & display_name)  )
    {
        // This may happen if a desktop Mac is running headless
        CFRelease( info );
        return @"unknown";
    }

    NSString * displayname = [NSString stringWithString: (__bridge NSString *) display_name];
    CFRelease(info);
    return displayname;
}


// Returns the io_service_t (an int) corresponding to a CG display ID, or 0 on failure.
// The io_service_t should be released with IOObjectRelease when not needed.

- (io_service_t) IOServicePortFromCGDisplayID: (CGDirectDisplayID) displayID
{
    io_iterator_t iter;
    io_service_t serv, servicePort = 0;

    CFMutableDictionaryRef matching = IOServiceMatching("IODisplayConnect");

    // releases matching for us
    kern_return_t err = IOServiceGetMatchingServices( kIOMasterPortDefault, matching, & iter );
    if ( err )
        return 0;

    while ( (serv = IOIteratorNext(iter)) != 0 )
    {
        CFDictionaryRef displayInfo;
        CFNumberRef vendorIDRef;
        CFNumberRef productIDRef;
        CFNumberRef serialNumberRef;

        displayInfo = IODisplayCreateInfoDictionary( serv, kIODisplayOnlyPreferredName );

        Boolean success;
        success =  CFDictionaryGetValueIfPresent( displayInfo, CFSTR(kDisplayVendorID),  (const void**) & vendorIDRef );
        success &= CFDictionaryGetValueIfPresent( displayInfo, CFSTR(kDisplayProductID), (const void**) & productIDRef );

        if ( !success )
        {
            CFRelease(displayInfo);
            continue;
        }

        SInt32 vendorID;
        CFNumberGetValue( vendorIDRef, kCFNumberSInt32Type, &vendorID );
        SInt32 productID;
        CFNumberGetValue( productIDRef, kCFNumberSInt32Type, &productID );

        // If a serial number is found, use it.
        // Otherwise serial number will be nil (= 0) which will match with the output of 'CGDisplaySerialNumber'
        SInt32 serialNumber = 0;
        if ( CFDictionaryGetValueIfPresent(displayInfo, CFSTR(kDisplaySerialNumber), (const void**) & serialNumberRef) )
        {
            CFNumberGetValue( serialNumberRef, kCFNumberSInt32Type, &serialNumber );
        }

        // If the vendor and product id along with the serial don't match
        // then we are not looking at the correct monitor.
        // NOTE: The serial number is important in cases where two monitors
        //       are the exact same.
        if( CGDisplayVendorNumber(displayID) != vendorID ||
            CGDisplayModelNumber(displayID)  != productID ||
            CGDisplaySerialNumber(displayID) != serialNumber )
        {
            CFRelease(displayInfo);
            continue;
        }

        servicePort = serv;
        CFRelease(displayInfo);
        break;
    }

    IOObjectRelease(iter);
    return servicePort;
}

这在我在 macOS 10.11 (El Capitan) 下编写的屏幕保护程序中效果很好。 我用我的 MacBookPro 的内置显示器和通过 Thunderbolt 连接的 Apple 显示器对其进行了测试。

【讨论】:

  • 我认为这段代码有漏洞。 IOServicePortFromCGDisplayID 中的循环为serv 变量创建了许多值。最后一个被传递给调用者中的IOObjectRelease,但任何更早的不匹配的都不会被释放。
【解决方案3】:
NSString* screenNameForDisplay(CGDirectDisplayID displayID)
{
    NSString *screenName = nil;
    io_service_t service = IOServicePortFromCGDisplayID(displayID);
    if (service)
    {
        NSDictionary *deviceInfo = (NSDictionary *)IODisplayCreateInfoDictionary(service, kIODisplayOnlyPreferredName);
        NSDictionary *localizedNames = [deviceInfo objectForKey:[NSString stringWithUTF8String:kDisplayProductName]];

        if ([localizedNames count] > 0) {
            screenName = [[localizedNames objectForKey:[[localizedNames allKeys] objectAtIndex:0]] retain];
        }

        [deviceInfo release];
    }
    return [screenName autorelease];
}

【讨论】:

  • 我试过了,但是在尝试使用 IOServicePortFromCGDisplayID 时出现错误,并且找不到任何文档。
  • 你能指定哪个错误吗?你确定displayID正确吗?它对我有用here
  • 警告:函数隐式声明 + 错误:架构 x86_64 的未定义符号:“_IOServicePortFromCGDisplayID”
  • 将原型添加到您的标题中:extern io_service_t IOServicePortFromCGDisplayID(CGDirectDisplayID displayID);
猜你喜欢
  • 1970-01-01
  • 2013-11-19
  • 2013-11-01
  • 1970-01-01
  • 2014-12-15
  • 1970-01-01
  • 1970-01-01
  • 2021-11-25
  • 2020-02-27
相关资源
最近更新 更多