You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于Mac C语言无障碍API为Tcl/Tk非原生控件创建无障碍对象的问询

Tcl/Tk非原生控件的macOS无障碍适配方案

Tk控件基于macOS绘图API实现而非原生NSControl子类,系统无法自动生成对应的AXUIElementRef,需手动创建并管理无障碍对象,建立层级关联并同步控件状态。以下是具体实现方案:

核心实现步骤

1. 为Tk窗口创建自定义无障碍元素

不能直接将Tk_Window转为CFTypeRef,需为每个可访问控件独立创建AXUIElementRef,并通过标识绑定到Tk窗口:

#include <Tk.h>
#include <ApplicationServices/ApplicationServices.h>

AXUIElementRef createTkAccessibilityElement(Tk_Window tkWin, AXUIElementRef parentElement) {
    // 基于当前进程创建应用内无障碍元素
    AXUIElementRef element = AXUIElementCreateApplication(getpid());
    
    // 设置控件角色(根据Tk控件类型调整,如按钮用kAXButtonRole)
    CFStringRef role = kAXGroupRole;
    AXUIElementSetAttributeValue(element, kAXRoleAttribute, role);
    
    // 绑定Tk窗口标识:将指针转为字符串存储
    char winIdBuf[64];
    snprintf(winIdBuf, sizeof(winIdBuf), "%p", tkWin);
    CFStringRef winId = CFStringCreateWithCString(NULL, winIdBuf, kCFStringEncodingUTF8);
    AXUIElementSetAttributeValue(element, kAXIdentifierAttribute, winId);
    CFRelease(winId);
    
    // 同步控件位置与尺寸
    int x, y, w, h;
    Tk_GetRootCoords(tkWin, &x, &y);
    Tk_GetSize(tkWin, &w, &h);
    CGRect bounds = CGRectMake(x, y, w, h);
    CFValueRef boundsVal = CFValueCreate(NULL, kCGRectType, &bounds);
    AXUIElementSetAttributeValue(element, kAXBoundsAttribute, boundsVal);
    CFRelease(boundsVal);
    
    // 将元素添加到父层级的children中
    CFArrayRef currentChildren;
    if (AXUIElementCopyAttributeValue(parentElement, kAXChildrenAttribute, (CFTypeRef*)&currentChildren) == kAXErrorSuccess) {
        NSMutableArray *newChildren = [(__bridge NSArray*)currentChildren mutableCopy];
        [newChildren addObject:(__bridge id)element];
        AXUIElementSetAttributeValue(parentElement, kAXChildrenAttribute, (__bridge CFArrayRef)newChildren);
        CFRelease(currentChildren);
        [(id)newChildren release];
    } else {
        CFArrayRef newChildren = CFArrayCreate(NULL, (const void**)&element, 1, &kCFTypeArrayCallBacks);
        AXUIElementSetAttributeValue(parentElement, kAXChildrenAttribute, newChildren);
        CFRelease(newChildren);
    }
    
    // 将无障碍元素关联到Tk窗口的私有数据
    Tk_SetAssocData(tkWin, "tk_accessibility_element", NULL, (ClientData)element);
    return element;
}

2. 管理无障碍元素生命周期

同步Tk窗口的创建/销毁事件,维护无障碍元素的存在性:

void destroyTkAccessibilityElement(Tk_Window tkWin, AXUIElementRef parentElement) {
    AXUIElementRef element = (AXUIElementRef)Tk_GetAssocData(tkWin, "tk_accessibility_element", NULL);
    if (!element) return;
    
    // 从父层级中移除当前元素
    CFArrayRef currentChildren;
    if (AXUIElementCopyAttributeValue(parentElement, kAXChildrenAttribute, (CFTypeRef*)&currentChildren) == kAXErrorSuccess) {
        NSMutableArray *newChildren = [(__bridge NSArray*)currentChildren mutableCopy];
        [newChildren removeObject:(__bridge id)element];
        AXUIElementSetAttributeValue(parentElement, kAXChildrenAttribute, (__bridge CFArrayRef)newChildren);
        CFRelease(currentChildren);
        [(id)newChildren release];
    }
    
    CFRelease(element);
    Tk_DeleteAssocData(tkWin, "tk_accessibility_element");
}

3. 响应无障碍属性查询

注册回调处理辅助技术(如VoiceOver)的属性请求,返回Tk控件的实时状态:

OSStatus accessibilityAttrCallback(AXUIElementRef element, CFStringRef attribute, void* outValue) {
    // 通过标识获取绑定的Tk窗口
    CFTypeRef idRef;
    if (AXUIElementCopyAttributeValue(element, kAXIdentifierAttribute, &idRef) != kAXErrorSuccess) {
        return kAXErrorNotFound;
    }
    
    const char* winIdStr = CFStringGetCStringPtr((CFStringRef)idRef, kCFStringEncodingUTF8);
    Tk_Window tkWin = (Tk_Window)strtoul(winIdStr, NULL, 16);
    
    // 根据属性类型返回对应值
    if (CFStringCompare(attribute, kAXTitleAttribute, 0) == kCFCompareEqualTo) {
        const char* title = Tk_GetWindowName(tkWin);
        *(CFTypeRef*)outValue = CFStringCreateWithCString(NULL, title, kCFStringEncodingUTF8);
    } else if (CFStringCompare(attribute, kAXEnabledAttribute, 0) == kCFCompareEqualTo) {
        *(CFTypeRef*)outValue = CFBooleanCreate(NULL, Tk_GetState(tkWin) != TK_STATE_DISABLED);
    }
    
    CFRelease(idRef);
    return kAXErrorSuccess;
}

// 注册应用级无障碍回调
void registerTkAccessibilityCallbacks() {
    AXUIElementRef appElement = AXUIElementCreateApplication(getpid());
    AXUIElementRegisterAttributeCallback(appElement, kAXTitleAttribute, accessibilityAttrCallback, NULL);
    AXUIElementRegisterAttributeCallback(appElement, kAXEnabledAttribute, accessibilityAttrCallback, NULL);
    CFRelease(appElement);
}

关键注意事项

  • 状态同步:当Tk控件的标题、位置、启用状态变化时,需调用AXUIElementSetAttributeValue更新属性,并发送AXUIElementPostNotification通知系统(如kAXTitleChangedNotification)。
  • 测试验证:开启系统VoiceOver或使用Accessibility Inspector工具,检查控件是否被正确识别。
  • 内存管理:严格遵循Core Foundation的内存规则,及时释放创建的CF对象。

内容的提问来源于stack exchange,提问作者Kevin Walzer

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.15 10:14:57