通过OSX辅助功能API获取窗口编号

30

我正在开发一个应用程序,可以移动屏幕上第三方应用程序的窗口。

为了获取所有当前打开的窗口的概述,我使用

CGWindowListCopyWindowInfo(kCGWindowListOptionOnScreenOnly | kCGWindowListExcludeDesktopElements, kCGNullWindowID);

这将返回一个包含每个打开窗口定义的字典数组。 以下是一个示例字典:

{
    kCGWindowAlpha = 1;
    kCGWindowBounds =         {
        Height = 442;
        Width = 475;
        X = 3123;
        Y = "-118";
    };
    kCGWindowIsOnscreen = 1;
    kCGWindowLayer = 0;
    kCGWindowMemoryUsage = 907184;
    kCGWindowName = Untitled;
    kCGWindowNumber = 7328;
    kCGWindowOwnerName = TextEdit;
    kCGWindowOwnerPID = 20706;
    kCGWindowSharingState = 1;
    kCGWindowStoreType = 2;
    kCGWindowWorkspace = 3;
},

这个字典里有很多好的信息,但缺少一个可用于修改窗口位置的可访问对象。窗口通过窗口编号明确定位。

我现在正在使用PID(kCGWindowOwnerPID)为窗口的应用程序创建可访问对象:

AXUIElementRef app = AXUIElementCreateApplication(pid);

接下来,使用AXUIElementCopyAttributeValues检索应用程序打开的所有窗口列表:

NSArray *result;

AXUIElementCopyAttributeValues(
                               (AXUIElementRef) app, 
                               kAXWindowsAttribute,
                               0,
                               99999,
                               (CFArrayRef *) &result
                               );

这段代码可以正常运行并返回一个AXUIElements数组。 然而,现在的问题是,貌似没有 API 能够获取无障碍对象的窗口编号。有没有任何方法能够实现:

a) 获取无障碍对象的窗口编号(最终目的是迭代遍历数组,并找到正确的窗口)

b) 否则,在 CGWindowListCopyWindowInfo 返回的数组中明确匹配一个窗口与 AXUIElementCopyAttributeValues 返回的无障碍对象?

3个回答

33
我们最终聘请了一位专门的无障碍开发人员来完成这项任务。 结果发现没有办法不使用未记录的API(在我们的情况下是不可接受的)。 幸运的是,有一个实用的解决方法: 遍历应用程序的所有打开窗口。获取它们的位置、大小和标题:
AXUIElementCopyAttributeValue(target, kAXPositionAttribute, (CFTypeRef*)&posValue);
AXUIElementCopyAttributeValue(target, kAXSizeAttribute, (CFTypeRef*)&sizeValue);
AXUIElementCopyAttributeValue(target, kAXTitleAttribute, (CFTypeRef*)&titleValue);

接下来,将位置和大小转换为实际的 CGPointCGSize 值:

AXValueGetValue(posValue, kAXValueCGPointType, &point);
AXValueGetValue(sizeValue, kAXValueCGSizeType, &size);

将大小、位置和标题与使用CGWindowListCopyWindowInfo()返回的对象的值进行比较。如果它们匹配,您可以安全地假设它就是您正在寻找的窗口,并使用已经打开的AXUIElement(target在我们的情况下)来操作它。

在OSX上循环遍历所有打开的窗口的开销不重要。同一时间打开的窗口数量有一个相当低的上限。

此外,虽然这不是100%准确的(有可能2个窗口具有相同的位置、大小和标题),但我们在实际使用中还没有遇到过这种情况。


17
对于那些能够使用未记录的 API 的人,如何实现? - Samuel
这是我见过的最佳解决方案。但我仍然认为它存在风险。不管怎样,感谢您的回答! - Vannes Yang

18

有一个用于获取给定AX对象的CG窗口编号的私有函数:_AXUIElementGetWindow。更多细节请参见在OS X上唯一标识活动窗口的SO讨论。看起来没有公共API可以以100%的准确率执行此任务。按标题和框架(如上面答案中所述)标识窗口将在99.9%的情况下有效。


我在macOS 10.15 SDK中找不到_AXUIElementGetWindow。 - Vannes Yang

1

澄清其他答案所建议的调用未文档化的_AXUIElementGetWindow

在Swift中,按照以下步骤进行:

1. 创建包含以下内容的Bridged-Header.h文件:

#import <AppKit/AppKit.h>

AXError _AXUIElementGetWindow(AXUIElementRef element, uint32_t *identifier);

2. 在你的构建设置中引用该文件:(路径可能有所不同,它是相对于项目根目录的)

build settings in xcode

3. 这样调用:

// variable 'window' is your AXUIElement window
var cgWindowId = CGWindowID()
if (_AXUIElementGetWindow(window, &gcWindowId) != .success) {. 
  print("cannot get CGWindow id (objc bridged call)")
}

恭喜,您已成功通过桥接调用了Objective C函数!

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