如何从 NSView 或 NSWindow 创建 AXUIElementRef?

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

关于 macOS Accessibility API,是否可以创建对应于 NSView 或 NSWindow 的 AXUIElementRef?

在 Carbon 时代似乎有一种方法可以使用 AXUIElementCreateWithHIObjectAndIdentifier 来执行此操作,但该功能不再可用。

我知道的唯一方法是使用 Accessibility API 递归搜索应用程序的 UI 元素的整个层次结构,以查找与 NSView 或 NSWindow 匹配的元素。但除了是一个繁重的解决方案之外,它甚至不能保证成功,因为可能没有办法仅使用 AXUIElementRef 的可用属性来积极对应 AXUIElementRef 和 Cocoa 对象。

我愿意考虑未记录的 API 来帮助实现这一目标。

macos cocoa accessibility
2个回答
3
投票

我找到了在 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
并尝试类似的操作。

希望这有帮助...


0
投票

八年多过去了,我想我终于找到了解决方案。诀窍是使用这个未记录的函数,幸运的是它仍然可以在现代 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。

© www.soinside.com 2019 - 2024. All rights reserved.