如何知道CGWindow在哪个屏幕上?

问题描述 投票:4回答:2

我使用CGWindow来获得CGWindowListCreate的列表,如下所示:

CFArrayRef windows = CGWindowListCreate(kCGWindowListOptionOnScreenOnly, 0);

是否有办法判断每个窗口在哪个NSScreen上?我正在尝试查看窗口边界和屏幕框架的交集,但是如果一个窗口部分不在屏幕上,则它可以与另一个屏幕的框架相交。

是否有更可靠的方法来做到这一点?

macos window core-graphics screen
2个回答
0
投票

我之前已经给here一个类似的答案,但是我需要提出一个比您建议的解决方案更好的解决方案。如果要检索当前窗口列表,最好的方法不是使用CGWindowListCreate,而是使用CGWindowListCopyWindowInfo

您需要做的第一件事是检索此question中所述的窗口的windowID。

一旦获得windowID,您就可以使用我建议的改编自先前答案的代码:

NSWindow *window = [NSApplication windowWithWindowNumber: [windowID integerValue]];

if ([[NSScreen screens] count] > 1)
{
    // you have more than one screen attached
    NSScreen *currentScreen = [window screen];

    // you can then test if the window is on the main display
    if (currentScreen == [NSScreen mainScreen])
    {
        // your window is on the main screen
    }
    else
    {
       // your window is not on the main screen
    }
}
else
{
    // your window is on the main screen
}

您可以更改此代码以轻松支持两个以上的屏幕。


0
投票

如果您只对应用程序的窗口感兴趣,则可以使用NSWindow的'screen'方法,如其他答案所示。这不适用于来自其他应用程序的Windows。另外,并非用户会话中的所有CGWindows都不一定也是NSWindows。

如果您对当前用户会话中所有应用程序的所有窗口都感兴趣,则必须使用Core Graphics函数CGWindowListCopyWindowInfo,其中

生成并返回有关当前用户会话中选定窗口的信息。

请参见https://developer.apple.com/documentation/coregraphics/1455137-cgwindowlistcopywindowinfo

[结果CFArray中的每个窗口都被描述为CFDictionary,具有必需的键,如kCGWindowNumber,kCGWindowBounds,kCGWindowOwnerPID等,以及一些可选的键,如kCGWindowOwnerName和kCGWindowName等。

[通过将NSScreens框架与screenRect相交,screenRect本身是CFDictionary,可以使用kCGWindowBounds键从窗体信息字典中检索它,可以确定CGWindow放置在哪个NSScreen上。

正如问题本身已经提到的,在特殊情况下,窗口部分位于屏幕的外部,即它可以与另一个屏幕的框架重叠。

在这种情况下,该窗口不能在macOS下一次显示在多个屏幕上,而只能在一个屏幕上显示较小的可见区域。

例如,在macOS下用鼠标移动窗口时,这取决于您实际释放鼠标按钮的屏幕。在当前版本的macOS中,即使窗口的最大区域不在屏幕上,它也可以确定窗口在哪个屏幕上。

在这种特定情况下,因此只能可靠地确定该窗口可能已放置在哪些屏幕上。

如果窗口完全位于一个屏幕上,则可以明确确定相应的屏幕。

因此,Swift中的代码将如下所示:

private func screensForWindow(window: [String : Any]) -> [NSScreen] {
    let boundsDict = window[kCGWindowBounds as String] as! CFDictionary
    guard let screenRect = CGRect(dictionaryRepresentation: boundsDict) else { return [] }
    return NSScreen.screens.filter { $0.frame.intersects(screenRect) }
}

此函数返回可以放置窗口的屏幕列表。在大多数情况下,这只是一个屏幕。

例如,可用于如下输出当前用户会话的窗口列表:

private func outputWindows() {
    let windows = CGWindowListCopyWindowInfo([.optionOnScreenOnly, .excludeDesktopElements], kCGNullWindowID);
    if let windowsArray = windows as? [[ String : Any]] {
        for w in windowsArray {
            let windowNumber = w[kCGWindowNumber as String] as! Int
            let ownerPID = w[kCGWindowOwnerPID as String] as! Int
            let windowOwner = w[kCGWindowOwnerName as String] as? String ?? ""
            let screens = self.screensForWindow(window: w)
            print("window #\(windowNumber) owned by '\(windowOwner)' with PID '\(ownerPID)' is on one of the following screen(s): ")
            for screen in screens {
                print("  - \(screen.localizedName)")
            }
        }
    }
}

例如,如果您的笔记本电脑内部屏幕上具有日历应用程序,戴尔外部屏幕上具有Xcode和Dock,并且两个屏幕之间的区域中都有一个Finder窗口,则将获得以下输出

window #2808 owned by 'Calendar' with PID '2043' is on one of the following screen(s): 
  - Built-in Display

window #32 owned by 'Dock' with PID '313' is on one of the following screen(s): 
  - DELL U2412M
window #125 owned by 'Xcode' with PID '301' is on one of the following screen(s): 
  - DELL U2412M


window #72 owned by 'Finder' with PID '315' is on one of the following screen(s): 
  - Built-in Display
  - DELL U2412M
© www.soinside.com 2019 - 2024. All rights reserved.