首页 > 解决方案 > 如何在 MacOS 上获取 Java(使用 JNA)中所有窗口句柄的列表?

问题描述

我一直在使用 JNA 库来获取 Windows 中所有可见的窗口句柄。我需要在 macOS 中使用 JNA 做类似的事情。

这是获取 Windows 中所有窗口句柄的代码:

 public static List<HWND> findAll() {
    final List<HWND> windows = new LinkedList<>();
    User32.INSTANCE.EnumWindows(new User32.WNDENUMPROC() {
        @Override
        public boolean callback(HWND hWnd, Pointer arg) {
            if (User32.INSTANCE.IsWindowVisible(hWnd)) {
                windows.add(hWnd);
            }
            return true;
        } 
    }, null);
    return windows;
}

macOS 中的等效代码是什么?

标签: javamacosjna

解决方案


您需要映射部分核心图形框架。您可以使用CGWindowListCopyWindowInfo()函数列出窗口。

要加载框架,您需要映射一个CoreGraphics扩展 JNALibrary类的接口,并映射您需要的函数:

public interface CoreGraphics extends Library {
    CoreGraphics INSTANCE = Native.load("CoreGraphics", CoreGraphics.class);

    CFArrayRef CGWindowListCopyWindowInfo(int option, int relativeToWindow);
}

CFArrayRef类型已映射到CoreFoundation类中的 JNA 中。选择适当的窗口列表选项(可能kCGWindowListOptionAll= 0)。如果您已经有一个窗口编号,您可以使用相对重新引用,否则您将使用kCGNullWindowID(0) 作为第二个参数。从您的代码中调用它应该很简单:

CFArrayRef windowInfo = CoreGraphics.INSTANCE.CGWindowListCopyWindowInfo(0, 0);

CFDictionaryRef这将为您提供代表窗口的对象数组。您可以迭代数组,然后使用CFDictionaryRef类中的其他方法来探索这些字典对象:您将为CFString键创建一个。此处记录了所需密钥的列表,此处提供了可选密钥。常量字符串与变量名匹配

这应该CFNumberRef为每个窗口编号(“句柄”等效项)提供一个:

// Set up keys for dictionary lookup
CFStringRef kCGWindowNumber = CFStringRef.createCFString("kCGWindowNumber");
CFStringRef kCGWindowOwnerPID = CFStringRef.createCFString("kCGWindowOwnerPID");
// Note: the Quartz name is rarely used
CFStringRef kCGWindowName = CFStringRef.createCFString("kCGWindowName");
CFStringRef kCGWindowOwnerName = CFStringRef.createCFString("kCGWindowOwnerName");

// Iterate the array
int numWindows = windowInfo.getCount();
for (int i = 0; i < numWindows; i++) {
    // For each array element, get the dictionary
    Pointer result = windowInfo.getValueAtIndex(i);
    CFDictionaryRef windowRef = new CFDictionaryRef(result);

    // Now get information from the dictionary.

    // Get a pointer to the result, in this case a CFNumber
    result = windowRef.getValue(kCGWindowNumber);
    // "Cast" the pointer to the appropriate type
    CFNumberRef windowNumber = new CFNumberRef(result);
    // CoreFoundation.INSTANCE.CFNumberGetType(windowNumber)
    // --> 4 = kCFNumberSInt64Type, signed 64 bit so use getLong()
    
    // Get a pointer to the result, in this case a CFNumber
    result = windowRef.getValue(kCGWindowOwnerPID);
    // "Cast" the pointer to the appropriate type
    CFNumberRef windowOwnerPID = new CFNumberRef(result);
    // CoreFoundation.INSTANCE.CFNumberGetType(windowOwnerPID)
    // --> 4 = kCFNumberSInt64Type, signed 64 bit so use getLong()

    // Get a pointer to the result, in this case a CFString
    result = windowRef.getValue(kCGWindowName);
    // "Cast" the pointer to the appropriate type
    // Optional key, check for null
    String windowName = result == null ? "" : new CFStringRef(result).stringValue();

    // Get a pointer to the result, in this case a CFString
    result = windowRef.getValue(kCGWindowOwnerName);
    // "Cast" the pointer to the appropriate type
    // Optional key, check for null
    String windowOwnerName = result == null ? "" : new CFStringRef(result).stringValue();

    // ... look up other keys if needed ...
    // use ProcessHandle with the PID to get start time

    // Output or add to List, etc.
    System.out.println(windowNumber.longValue() 
        + " (" + windowOwnerName + ", pid=" 
        + windowOwnerPID.longValue()
        + "): " + windowName);
}

// CF references from "Copy" or "Create" must be released
// release the created key references
kCGWindowNumber.release();
kCGWindowOwnerPID.release();
kCGWindowName.release();
kCGWindowOwnerName.release();
// release the array
windowInfo.release();

推荐阅读