CGDisplayIOServicePort在OS X >= 10.9已被弃用,如何替换?

15

我做了一个小应用程序,可以快速更改多个显示器的屏幕分辨率。我希望将产品名称显示为监视器的标题,使用以下代码非常简单:

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";
}

但是在OS X >= 10.9中,CGDisplayIOServicePort已被弃用,苹果的文档中表示没有替代方法。如何在不使用此方法的情况下找到服务端口或产品名称?

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

谢谢帮助!


3
我建议您提交一个问题报告来反映这个问题。苹果应该会告诉您如何实现您想要做的事情。 - pmdj
你找到这个问题的解决方案了吗? - Frog
@Frog 不行。如果苹果在WWDC上没有引入任何新的API来解决这个问题,也许现在是时候为此开放radar票了。 - juniperi
2
只是一点说明:虽然该方法已被弃用,但在这些年里它一直运行得非常完美(自10.6版以来一直在使用),并且仍可在macOS 10.15.1上运行。 - Kentzo
4个回答

3

以下是我的看法。我也从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连接的苹果外置显示器进行了测试。


我认为这段代码存在泄漏问题。在IOServicePortFromCGDisplayID的循环中,为serv变量创建了许多值。最终一个被传递给调用者中的IOObjectRelease,但是之前的不匹配的值没有被释放。 - JWWalker

3
看起来@Eun的帖子漏掉了一些信息,以结束这个讨论。通过简单搜索,我发现IOServicePortFromCGDisplayID不是苹果提供的API。相反,它是在这里找到的开源代码: https://github.com/glfw/glfw/blob/e0a6772e5e4c672179fc69a90bcda3369792ed1f/src/cocoa_monitor.m 我复制了IOServicePortFromCGDisplayID和其中的“getDisplayName”。 我需要进行两个调整才能使其在OS X 10.10上运行。
  1. 从IOServicePortFromCGDisplayID中删除处理序列号的代码。(对于kDisplaySerialNumber,CFDictionaryGetValue返回NULL。)
  2. 从getDisplayName中删除项目特定的错误处理代码。
如果您需要更多信息
  • 问题的问题跟踪器:github.com/glfw/glfw/issues/165
  • 解决方案的提交: github.com/glfw/glfw/commit/e0a6772e5e4c672179fc69a90bcda3369792ed1f
我要感谢Matthew Henry在那里提交的代码。

谢谢您提供的解决方案!我刚刚测试了一下,它可以正常工作。 - juniperi
1
不幸的是,如果您连接了多个相同的显示器,这将会出现问题。如果您只匹配供应商和产品编号,则始终会返回IOServices中出现的第一个。这是上述代码所采用的方法所面临的一般性问题 - 它尝试匹配供应商ID、产品ID和序列号,但并非所有显示器都返回序列号(例如投影仪通常不会返回)。 - Siobhán
我在这里使用它 https://dev59.com/3HPYa4cB1Zd3GeqPmrxr 来旋转屏幕,但是它不起作用。有什么想法可能是问题所在。谢谢。 - last-Programmer

2

从macOS 10.15开始,-[NSScreen localizedName]已经可用:

NSLog(@"Name of main display is %@", NSScreen.mainScreen.localizedName);

-1
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时出现了错误,并且找不到任何相关的文档。 - juniperi
你能具体指出是哪个错误吗?你确定 displayID 是正确的吗?这段代码在这里对我是可行的:[https://github.com/Eun/DisableMonitor/blob/gui/DisableMonitorAppDelegate.m#L393]。 - Eun
警告:函数的隐式声明 + 错误:未定义符号架构x86_64:“_IOServicePortFromCGDisplayID” - juniperi
将原型添加到您的头文件中:extern io_service_t IOServicePortFromCGDisplayID(CGDirectDisplayID displayID); - Eun

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接