通过 OSX Accessibility API 获取窗口号

发布于 2024-11-11 17:46:44 字数 1558 浏览 4 评论 0原文

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

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

CGWindowListCopyWindowInfo(kCGWindowListOptionOnScreenOnly | kCGWindowListExcludeDesktopElements, kCGNullWindowID);

This 返回定义每个打开的窗口的字典数组。 下面是返回的示例字典:

{
    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 返回的可访问性对象明确匹配?

I am working on an application that moves windows of third party applications around on the screen.

To get an overview of all currently open windows, I use

CGWindowListCopyWindowInfo(kCGWindowListOptionOnScreenOnly | kCGWindowListExcludeDesktopElements, kCGNullWindowID);

This returns an array of dictionaries defining every open window.
Here's an exemplary dictionary returned:

{
    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;
},

The dictionary is full of good information used elsewhere but lacks an accessibility object that could be used to modify the windows' positions. Windows are clearly identified by the Window Number.

I am now using the PID (kCGWindowOwnerPID) to create an accessibility object for the window's application:

AXUIElementRef app = AXUIElementCreateApplication(pid);

Followed by retrieving a list of all windows the application has opened using AXUIElementCopyAttributeValues:

NSArray *result;

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

This works and returns an array of AXUIElements.
This is where I am stuck. There seems to be no API call to retrieve the Window Number of an accessibility object. Is there any way to either

a) Find the accessibility object's Window Number (to ultimately iterate over the array and find the right window)

or

b) Otherwise clearly match a window described in the array returned by CGWindowListCopyWindowInfo to the Accessibility Objects returned by AXUIElementCopyAttributeValues?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

白况 2024-11-18 17:46:44

我们最终聘请了一名专门的辅助功能开发人员来完成这项任务。

事实证明,如果不使用未记录的 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% 准确(两个窗口可能具有相同的位置、大小和标题),但到目前为止我们在实际使用中还没有遇到过这种情况。

We ended up hiring a dedicated Accessibility Developer for this task.

It turns out there is no way to do this without using undocumented APIs (a no go in our case).

Luckily, there is a practical workaround:

Loop over all open windows of the app. Get their position, size and title:

AXUIElementCopyAttributeValue(target, kAXPositionAttribute, (CFTypeRef*)&posValue);
AXUIElementCopyAttributeValue(target, kAXSizeAttribute, (CFTypeRef*)&sizeValue);
AXUIElementCopyAttributeValue(target, kAXTitleAttribute, (CFTypeRef*)&titleValue);

Next, convert the position and size into actual CGPoint and CGSize values:

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

Compare the size, position and title against the values returned by the object in CGWindowListCopyWindowInfo().
If they match, you can safely assume it's the window you were looking for and use the already open AXUIElement (target in our case) to work it.

The overhead for looping through all open windows turns out to be negligible on OSX. There is a pretty low cap on how many windows are open at the same time.

Also, while this is not 100% accurate (it is possible that 2 windows have the same position, size and title), we haven't encountered any situation in real usage where this happens so far.

小嗷兮 2024-11-18 17:46:44

有一个私有函数用于获取窗口给定 AX 对象的 CG 窗口号: _AXUIElementGetWindow
SO 讨论中的更多详细信息 唯一标识 OS X 上的活动窗口
看起来没有公共 API 可以 100% 完成该任务。通过标题和框架识别窗口(如上面的答案所述)在 99.9% 的情况下都有效。

There is a private function for obtaining CG window number for a given AX object for window: _AXUIElementGetWindow .
More details in SO discussion Uniquely identify active window on OS X
It looks like there is no public API to do the task with 100% probability. Identifying windows by title and frame (as described in answer above) will works in 99.9% of cases.

话少情深 2024-11-18 17:46:44

澄清其他建议未记录的调用 _AXUIElementGetWindow 的答案。

在 Swift 中,执行以下操作:

1.使用以下内容创建 Bridged-Header.h

#import <AppKit/AppKit.h>

AXError _AXUIElementGetWindow(AXUIElementRef element, uint32_t *identifier);

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

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 函数!

Clarifying other answers that suggest the undocumented call of _AXUIElementGetWindow.

In Swift, do this:

1. Create Bridged-Header.h with the following contents:

#import <AppKit/AppKit.h>

AXError _AXUIElementGetWindow(AXUIElementRef element, uint32_t *identifier);

2. Reference this file in your build settings: (your path may vary, it is relative to project root)

build settings in xcode

3. Call like this:

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

Congrats, you succesfully called objective C functions via a bridge!

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文