NSScreen在插入新监视器时不更新监视器计数



我发现NSScreen返回相同数量的显示器,即使在插入额外的显示器后。

做了一个简单的测试应用程序,可以复制这个问题。基本上是无限循环并打印NSScreen计数和CGDisplay计数。

  1. 启动应用程序
  2. 打印"NSScreen = 1 CGDisplay = 1">
  3. 不停止应用程序,插入一个额外的监视器
  4. 打印" NSScreen = 1 CGDisplay = 2 "

但是代码应该打印" NSScreen = 2 CGDisplay = 2 "

在OS X 11上,插入额外的显示器后,我们看到同样的问题(NSScreen = 1 CGDisplay = 2)。

测试代码在这里

main.cpp

#include <iostream>
#include "macScreen.h"
int main(int argc, const char * argv[]) {
getNSScreenCoordinates();
return 0;
}

macScreen.h

#ifndef macScreen_h
#define macScreen_h
float getNSScreenCoordinates();
#endif /* macScreen_h */

macScreen.mm

#import <Foundation/Foundation.h>
#include <iostream>
#include <Cocoa/Cocoa.h>
#import <AppKit/NSScreen.h>
#define MAX_NUM_DISPLAYS 255
float getNSScreenCoordinates() {
NSArray<NSScreen *> *arr = [NSScreen screens];
NSUInteger numScreens = [arr count];
CGDirectDisplayID displays[MAX_NUM_DISPLAYS];
CGDisplayCount displayCount;
CGGetOnlineDisplayList(MAX_NUM_DISPLAYS, displays, &displayCount);

while(1) {
std::cout << "cg num displays " << displayCount << "n";
std::cout << "numscreens " << numScreens << "n";
arr = [NSScreen screens];
numScreens = [arr count];
CGGetOnlineDisplayList(MAX_NUM_DISPLAYS, displays, &displayCount);

}
return 1;
}

在我对macOS 11.6 (Big Sur)的测试中,您需要做两件事才能使[NSScreen screens]更新:

  • 您需要确保单例NSApplication.shared对象存在
  • 需要运行主NSRunLoop

下面是一个例子(在Swift中):

import Cocoa
import Combine
// Ensure the singleton NSApplication exists.
_ = NSApplication.shared
// Arrange to print the number of screens once per second,
// while the main RunLoop is running.
let ticket = Timer.publish(every: 1, on: .main, in: .common)
.autoconnect()
.sink { _ in
print("screen count = (NSScreen.screens.count)")
}
// Run the main RunLoop forever.
RunLoop.main.run()
// Ensure that the Timer isn't cancelled prematurely.
ticket.cancel()

但是如果你注释掉NSApplication.shared行,它就不再工作了。如果将RunLoop的使用替换为从不调用RunLoopwhile循环,它也将不再工作。例如,下面的不是工作:

import Cocoa
import Combine
// Ensure the singleton NSApplication exists.
_ = NSApplication.shared
while true {
// NEVER UPDATES
print("screen count = (NSScreen.screens.count)")
sleep(1)
}

所以你真的应该试着安排你的程序,使RunLoop.main在控制中。但是,如果您确实需要拥有自己的主循环,那么在检查NSScreen.screens之前运行一次RunLoop.main就足够了。

import Cocoa
import Combine
// Ensure the singleton NSApplication exists.
_ = NSApplication.shared
while true {
RunLoop.main.acceptInput(forMode: .default, before: .distantPast)
print("screen count = (NSScreen.screens.count)")
sleep(1)
}

根据NSScreen的文档,你需要在NSApplication实例中运行你的代码,以允许NSScreen连接到底层的Display服务。在做任何需要DisplayServer连接的事情之前,在- (void)applicationDidFinishLaunching:(NSNotification *)aNotification内完成工作或调用NSApplicationLoad。

NSScreenAppKit提供这一事实也表明了这样的需求。

你可以考虑注册到通知NSApplicationDidChangeScreenParametersNotification或指定一个CGDisplayRegisterReconfigurationCallback函数。

最新更新