关于 macOS Accessibility API,是否可以创建对应于 NSView 或 NSWindow 的 AXUIElementRef?
在 Carbon 时代似乎有一种方法可以使用 AXUIElementCreateWithHIObjectAndIdentifier 来执行此操作,但该功能不再可用。
我知道的唯一方法是使用 Accessibility API 递归搜索应用程序的 UI 元素的整个层次结构,以查找与 NSView 或 NSWindow 匹配的元素。但除了是一个繁重的解决方案之外,它甚至不能保证成功,因为可能没有办法仅使用 AXUIElementRef 的可用属性来积极对应 AXUIElementRef 和 Cocoa 对象。
我愿意考虑未记录的 API 来帮助实现这一目标。
我找到了在 iOS 中做同样事情的方法。
我知道这不是对你的问题的直接答案,但我会尝试解释我在 iOS 中找到它的方法,并希望你能够在 macOS 中做同样的事情。另外,这可能对其他读者有用......
我首先猜测进程本身正在创建
AXUIElementRef
,因此当我请求具有 AXUIElementRef
值的可访问性属性(例如 kAXUIElementAttributeChildren
)时,它必须创建它们。
然后我创建了一个应用程序,并 dlsym'ed
_AXUIElementCreateAppElementWithPid(int pid)
,用 [[NSProcessInfo processInfo] processIdentifier]
调用它。
我收到了根 AXUIElementRef
,然后将其传递给 AXError AXUIElementCopyMultipleAttributeValues(AXUIElementRef element, CFArrayRef attributes, AXCopyMultipleAttributeOptions options, CFArrayRef _Nullable *values)
,请求 kAXUIElementAttributeChildren
,并且它起作用了(应该在主线程上运行)!
我开始仔细调试
AXUIElementCopyMultipleAttributeValues
调用到汇编代码中,它非常像那样(当然,这是非常伪代码......):
// serialize the arguments for MIG call
if (axUIElementRef->pid == self pid) {
// that's good that we are calling our own process, we can easily keep debugging!
_MIGXAAXUIElementCopyMultipleAttributeValues(serialized arguments) {
// Get the original element from the AXUIElementRef:
UIView* view = _AXElementForAXUIElementUniqueId(id);
[view accessibilityAttributeValue:kAXUIElementAttributeChildren] {
[view _accessibilityUserTestingChildren] {
// since this is the UIApplication element, it just gets the windows:
NSArray*<UIWindow*> winArr = [(UIApplication*)view _accessibilityWindows];
// for each value in result, convert to AX value:
...
AXConvertOutgoingValue(winArr) {
// For each UIView, convert to AXUIElementRef:
AXUIElementRef e = _AXCreateAXUIElementWithElement(winArr[i]);
}
}
}
}
} else {
// Do the same only if we are entitled, and outside our process
}
因此,在 iOS 中,您只需调用
AXUIElementRef _AXCreateAXUIElementWithElement(UIView*);
即可从 UI 转换为辅助功能元素,然后按相反方向调用 UIView* _AXElementForAXUIElementUniqueId(AXUIElementRefGetUniqueID(AXUIElementRef));
。
所有符号均来自
AXRuntime.framework
。
在 Mac 中,您需要链接到
ApplicationServices.framework
并尝试类似的操作。
希望这有帮助...
八年多过去了,我想我终于找到了解决方案。诀窍是使用这个未记录的函数,幸运的是它仍然可以在现代 macOS 中工作:
extern "C" AXError _AXUIElementGetWindow(AXUIElementRef, CGWindowID *out);
这样我们就可以使用 Accessibility API 来搜索应用程序的窗口列表,并将它们的窗口 ID 与 NSWindow 的 ID 进行比较以找到匹配项。
这是一个演示如何执行此操作的函数:
AXUIElementRef copyWindowWithPIDAndWindowID(pid_t pid, CGWindowID targetWindowId)
{
AXUIElementRef result = nullptr;
AXUIElementRef app = AXUIElementCreateApplication(pid);
if (!app) {
return nullptr;
}
CFArrayRef windows;
AXError error = AXUIElementCopyAttributeValue(app, kAXWindowsAttribute, (const void **)&windows);
if (error != kAXErrorSuccess || !windows) {
CFRelease(app);
return nullptr;
}
for(long i = 0; i < CFArrayGetCount(windows); ++i) {
CGWindowID windowId;
AXUIElementRef window = (AXUIElementRef)CFArrayGetValueAtIndex(windows, i);
error = _AXUIElementGetWindow(window, &windowId);
if (error != kAXErrorSuccess || targetWindowId == 0) {
qDebug() << "Error: couldn't get CGWindowID from AXUIElementRef. Error code:" << error;
continue;
}
if (windowId == targetWindowId) {
CFRetain(window);
result = window;
break;
}
}
CFRelease(app);
CFRelease(windows);
return result;
}
你可以将它与 NSWindow 一起使用,如下所示:
NSWindow *window = ...;
copyWindowWithPIDAndWindowID(getpid(), window.windowNumber);
此函数还允许从另一个应用程序获取到窗口的 AXUIElementRef。