Skip to content

UI 自动化抽象层设计 ​

版本: v1.0 日期: 2026-05-14 状态: 设计草案


1. 概述 ​

1.1 设计目标 ​

提供跨平台的 UI 自动化接口,统一 Windows UI Automation、macOS Accessibility API 和 Linux AT-SPI。

目标说明
语义统一相同的操作在不同平台有一致的语义
元素定位统一的元素查找和定位方式
属性访问统一的属性名称和类型
事件监听统一的事件模型
易于扩展支持自定义选择器和操作

1.2 平台对应关系 ​

┌─────────────────────────────────────────────────────────────────┐
│                     平台 API 对应关系                            │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│  抽象概念              │  Windows           │  macOS           │
│                       │  (UI Automation)   │  (Accessibility)  │
│  ─────────────────────┼────────────────────┼──────────────────│
│  元素 (Element)        │  IUIAutomation     │  AXUIElement      │
│  树 (Tree)             │  TreeWalker        │  AXUIElement      │
│  模式 (Pattern)        │  Control Pattern   │  AXAttribute      │
│  事件 (Event)          │  Event             │  Notification    │
│                       │                    │                   │
│                       │  Linux             │                   │
│                       │  (AT-SPI)          │                   │
│  ─────────────────────┼────────────────────┴──────────────────│
│  元素 (Element)        │  AtspiAccessible   │                   │
│  树 (Tree)             │  Tree Traversal    │                   │
│  模式 (Pattern)        │  Interface         │                   │
│  事件 (Event)          │  Event             │                   │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

2. 元素模型 ​

2.1 元素标识 ​

cpp
namespace wingman::platform::uia {

/**
 * @brief 元素角色
 *
 * 定义元素的语义角色,与平台具体控件类型解耦
 */
enum class UIAElementRole : uint16_t {
    Unknown,
    // 容器类
    Window,           // 窗口/对话框
    Pane,             // 面板/分组
    Tab,              // 标签页控件
    TabPage,          // 标签页
    Menu,             // 菜单
    MenuBar,          // 菜单栏
    MenuItem,         // 菜单项
    ToolBar,          // 工具栏
    StatusBar,        // 状态栏
    Tree,             // 树控件
    TreeItem,         // 树节点
    Table,            // 表格
    Grid,             // 网格
    Group,            // 分组框
    Splitter,         // 分割器
    // 交互类
    Button,           // 按钮
    CheckBox,         // 复选框
    RadioButton,      // 单选按钮
    ToggleButton,     // 切换按钮
    ComboBox,         // 下拉框
    ListBox,          // 列表框
    ListItem,         // 列表项
    DropDownList,     // 下拉列表
    Slider,           // 滑块
    SpinButton,       // 微调按钮
    ProgressBar,      // 进度条
    ScrollBar,        // 滚动条
    // 输入类
    TextBox,          // 文本框
    RichTextBox,      // 富文本框
    PasswordBox,      // 密码框
    NumericInput,     // 数字输入
    DatePicker,       // 日期选择器
    TimePicker,       // 时间选择器
    // 显示类
    Text,             // 静态文本
    Image,            // 图片
    Icon,             // 图标
    Chart,            // 图表
    Graph,            // 图形
    StatusBar,        // 状态指示器
    Tooltip,          // 工具提示
    // 特殊类
    TitleBar,         // 标题栏
    CloseButton,      // 关闭按钮
    MinimizeButton,   // 最小化按钮
    MaximizeButton,   // 最大化按钮
    HelpButton,       // 帮助按钮
    Scroll,           // 滚动区域
    Header,           // 标题头
    HeaderItem,       // 标题项
    Footer,           // 页脚
    Separator,        // 分隔符
    // 文档类
    Document,         // 文档
    Paragraph,        // 段落
    Heading,          // 标题
    Link,             // 链接
    List,             // 列表
    Comment,          // 注释
    Code,             // 代码块
    Quote,            // 引用
};

/**
 * @brief 元素状态
 */
enum class UIAElementState : uint32_t {
    None = 0,
    Focusable = 1 << 0,
    Focused = 1 << 1,
    Clickable = 1 << 2,
    Checkable = 1 << 3,
    Checked = 1 << 4,
    Editable = 1 << 5,
    Enabled = 1 << 6,
    Visible = 1 << 7,
    Selectable = 1 << 8,
    Selected = 1 << 9,
    Expandable = 1 << 10,
    Expanded = 1 << 11,
    Collapsible = 1 << 12,
    Collapsed = 1 << 13,
    ReadOnly = 1 << 14,
    Required = 1 << 15,
    Invalid = 1 << 16,
    Busy = 1 << 17,
    Loading = 1 << 18,
    Offline = 1 << 19,
};
DEFINE_ENUM_FLAG_OPERATORS(UIAElementState);

/**
 * @brief 元素控制类型(平台特定,用于兼容)
 */
enum class UIAControlType : uint32_t {
    // Windows UIA ControlType
    Button = 50000,
    Calendar = 50001,
    CheckBox = 50002,
    ComboBox = 50003,
    Edit = 50004,
    Hyperlink = 50005,
    Image = 50006,
    ListItem = 50007,
    List = 50008,
    Menu = 50009,
    MenuBar = 50010,
    MenuItem = 50011,
    ProgressBar = 50012,
    RadioButton = 50013,
    ScrollBar = 50014,
    Slider = 50015,
    Spinner = 50016,
    StatusBar = 50017,
    Tab = 50018,
    TabItem = 50019,
    Text = 50020,
    ToolBar = 50021,
    ToolTip = 50022,
    Tree = 50023,
    TreeItem = 50024,
    Custom = 50025,
    Group = 50026,
    Thumb = 50027,
    DataGrid = 50028,
    DataItem = 50029,
    Document = 50030,
    SplitButton = 50031,
    Window = 50032,
    Pane = 50033,
    Header = 50034,
    HeaderItem = 50035,
    Table = 50036,
    TitleBar = 50037,
    Separator = 50038,
};

} // namespace wingman::platform::uia

2.2 元素选择器 ​

cpp
/**
 * @brief 元素选择器
 *
 * 用于定位和筛选 UI 元素
 */
struct UIASelector {
    // 按属性筛选
    std::string name;                    // 名称/AutomationName
    std::string id;                      // ID/AutomationId
    std::string className;               // 类名
    std::string roleName;                // 角色名
    UIAElementRole role;                  // 角色
    UIAControlType controlType;          // 控件类型
    std::string text;                    // 文本内容
    int index = -1;                      // 同级索引
    int depth = -1;                      // 查找深度

    // 按状态筛选
    UIAElementState stateFlags = UIAElementState::None;
    bool hasState(UIAElementState state) const {
        return (stateFlags & state) == state;
    }

    // 按边界筛选
    Rect bounds;                          // 边界区域
    bool inBounds(const Point& point) const {
        return point.x >= bounds.x && point.x < bounds.x + bounds.width &&
               point.y >= bounds.y && point.y < bounds.y + bounds.height;
    }

    // 组合条件
    std::vector<UIASelector> children;    // 子元素选择器
    std::vector<UIASelector> descendants; // 后代元素选择器

    // 关系选择
    UIASelector* parent = nullptr;        // 父元素
    UIASelector* ancestor = nullptr;      // 祖先元素
    std::string siblingName;              // 兄弟元素名称

    // 构建函数
    UIASelector& withName(const std::string& n) {
        name = n;
        return *this;
    }

    UIASelector& withId(const std::string& i) {
        id = i;
        return *this;
    }

    UIASelector& withRole(UIAElementRole r) {
        role = r;
        return *this;
    }

    UIASelector& withState(UIAElementState s) {
        stateFlags = s;
        return *this;
    }

    UIASelector& atIndex(int i) {
        index = i;
        return *this;
    }

    UIASelector& withText(const std::string& t) {
        text = t;
        return *this;
    }

    // 链式选择器
    UIASelector& child(const UIASelector& sel) {
        children.push_back(sel);
        return *this;
    }

    UIASelector& descendant(const UIASelector& sel) {
        descendants.push_back(sel);
        return *this;
    }
};

/**
 * @brief 查找范围
 */
enum class UIAScope : uint8_t {
    Element,        // 当前元素
    Children,       // 直接子元素
    Descendants,    // 所有后代元素
    Subtree,        // 子树(包含当前元素)
    Parent,         // 父元素
    Ancestors,      // 所有祖先元素
};

3. 元素接口 ​

3.1 IUIAElement 核心接口 ​

cpp
/**
 * @brief UI 自动化元素接口
 *
 * 提供跨平台的 UI 元素操作接口
 */
class IUIAElement {
public:
    virtual ~IUIAElement() = default;

    // ========== 基本属性 ==========

    /**
     * @brief 获取元素名称
     */
    virtual std::string getName() const = 0;

    /**
     * @brief 获取元素 ID
     */
    virtual std::string getId() const = 0;

    /**
     * @brief 获取类名
     */
    virtual std::string getClassName() const = 0;

    /**
     * @brief 获取角色
     */
    virtual UIAElementRole getRole() const = 0;

    /**
     * @brief 获取控件类型
     */
    virtual UIAControlType getControlType() const = 0;

    /**
     * @brief 获取边界
     */
    virtual Rect getBounds() const = 0;

    /**
     * @brief 获取可见状态
     */
    virtual bool isVisible() const = 0;

    /**
     * @brief 获取启用状态
     */
    virtual bool isEnabled() const = 0;

    // ========== 文本属性 ==========

    /**
     * @brief 获取文本内容
     */
    virtual std::string getText() const = 0;

    /**
     * @brief 设置文本内容
     */
    virtual bool setText(const std::string& text) = 0;

    /**
     * @brief 获取光标位置
     */
    virtual int getCaretPosition() const = 0;

    /**
     * @brief 设置光标位置
     */
    virtual bool setCaretPosition(int position) = 0;

    /**
     * @brief 获取选中文本
     */
    virtual std::string getSelection() const = 0;

    /**
     * @brief 设置选中文本
     */
    virtual bool setSelection(int start, int end) = 0;

    // ========== 交互操作 ==========

    /**
     * @brief 点击元素
     */
    virtual bool click() = 0;

    /**
     * @brief 右键点击
     */
    virtual bool rightClick() = 0;

    /**
     * @brief 双击
     */
    virtual bool doubleClick() = 0;

    /**
     * @brief 在指定位置点击
     */
    virtual bool clickAt(int x, int y) = 0;

    /**
     * @brief 悬停
     */
    virtual bool hover() = 0;

    /**
     * @brief 按下鼠标
     */
    virtual bool mouseDown() = 0;

    /**
     * @brief 释放鼠标
     */
    virtual bool mouseUp() = 0;

    // ========== 键盘操作 ==========

    /**
     * @brief 设置焦点
     */
    virtual bool setFocus() = 0;

    /**
     * @brief 获取焦点状态
     */
    virtual bool hasFocus() const = 0;

    /**
     * @brief 输入文本
     */
    virtual bool typeText(const std::string& text) = 0;

    /**
     * @brief 按键
     */
    virtual bool typeKey(KeyCode key) = 0;

    // ========== 模式特定操作 ==========

    /**
     * @brief 获取/设置值(适用于 Slider, ProgressBar 等)
     */
    virtual std::string getValue() const = 0;
    virtual bool setValue(const std::string& value) = 0;

    /**
     * @brief 获取/设置范围值
     */
    virtual double getRangeValue() const = 0;
    virtual bool setRangeValue(double value) = 0;
    virtual double getMinimum() const = 0;
    virtual double getMaximum() const = 0;

    /**
     * @brief 获取/设置切换状态(CheckBox, ToggleButton 等)
     */
    virtual bool isChecked() const = 0;
    virtual bool setChecked(bool checked) = 0;

    /**
     * @brief 获取/设置展开状态
     */
    virtual bool isExpanded() const = 0;
    virtual bool setExpanded(bool expanded) = 0;

    /**
     * @brief 获取选项状态(RadioButton, ListItem 等)
     */
    virtual bool isSelected() const = 0;
    virtual bool setSelected(bool selected) = 0;

    // ========== 滚动操作 ==========

    /**
     * @brief 滚动到视图
     */
    virtual bool scrollIntoView() = 0;

    /**
     * @brief 滚动
     */
    virtual bool scroll(double horizontalPercent, double verticalPercent) = 0;

    // ========== 导航操作 ==========

    /**
     * @brief 获取父元素
     */
    virtual std::shared_ptr<IUIAElement> getParent() const = 0;

    /**
     * @brief 获取子元素
     */
    virtual std::vector<std::shared_ptr<IUIAElement>> getChildren() const = 0;

    /**
     * @brief 获取后代元素
     */
    virtual std::vector<std::shared_ptr<IUIAElement>> getDescendants() const = 0;

    /**
     * @brief 查找单个元素
     */
    virtual std::shared_ptr<IUIAElement> findElement(const UIASelector& selector) = 0;

    /**
     * @brief 查找多个元素
     */
    virtual std::vector<std::shared_ptr<IUIAElement>> findElements(const UIASelector& selector) = 0;

    /**
     * @brief 等待元素出现
     */
    virtual bool waitForElement(const UIASelector& selector, int timeoutMs = 5000) = 0;

    // ========== 模式接口 ==========

    /**
     * @brief 获取支持的模式
     */
    virtual std::vector<std::string> getSupportedPatterns() const = 0;

    /**
     * @brief 检查是否支持模式
     */
    virtual bool supportsPattern(const std::string& patternName) const = 0;

    // ========== 截图和调试 ==========

    /**
     * @brief 截取元素图像
     */
    virtual std::unique_ptr<Bitmap> capture() const = 0;

    /**
     * @brief 高亮显示元素(调试用)
     */
    virtual bool highlight(int durationMs = 1000) = 0;

    /**
     * @brief 获取元素调试信息
     */
    virtual std::string getDebugInfo() const = 0;

    // ========== 生命周期 ==========

    /**
     * @brief 检查元素是否有效
     */
    virtual bool isValid() const = 0;

    /**
     * @brief 刷新元素缓存
     */
    virtual bool refresh() = 0;
};

4. UIA 管理器接口 ​

4.1 IUIAManager ​

cpp
/**
 * @brief UI 自动化管理器接口
 *
 * 提供全局 UI 自动化功能入口
 */
class IUIAManager {
public:
    virtual ~IUIAManager() = default;

    /**
     * @brief 初始化管理器
     */
    virtual bool initialize() = 0;

    /**
     * @brief 关闭管理器
     */
    virtual void shutdown() = 0;

    // ========== 根元素访问 ==========

    /**
     * @brief 获取桌面元素
     */
    virtual std::shared_ptr<IUIAElement> getDesktopElement() = 0;

    /**
     * @brief 获取焦点元素
     */
    virtual std::shared_ptr<IUIAElement> getFocusedElement() = 0;

    /**
     * @brief 获取鼠标下的元素
     */
    virtual std::shared_ptr<IUIAElement> getElementFromPoint(const Point& point) = 0;

    // ========== 窗口访问 ==========

    /**
     * @brief 获取所有窗口元素
     */
    virtual std::vector<std::shared_ptr<IUIAElement>> getWindowElements() = 0;

    /**
     * @brief 按标题查找窗口元素
     */
    virtual std::shared_ptr<IUIAElement> findWindow(const std::string& title) = 0;

    // ========== 全局查找 ==========

    /**
     * @brief 全局查找元素
     */
    virtual std::shared_ptr<IUIAElement> findElement(const UIASelector& selector) = 0;

    virtual std::vector<std::shared_ptr<IUIAElement>> findElements(const UIASelector& selector) = 0;

    // ========== 事件监听 ==========

    /**
     * @brief 事件类型
     */
    enum class UIAEventType : uint32_t {
        ElementAdded,
        ElementRemoved,
        FocusChanged,
        PropertyChanged,
        Invoked,
        TextChanged,
        SelectionChanged,
        WindowOpened,
        WindowClosed,
        StructureChanged,
    };

    /**
     * @brief 事件回调
     */
    using UIAEventCallback = std::function<void(
        UIAEventType eventType,
        std::shared_ptr<IUIAElement> element,
        const std::string& eventData
    )>;

    /**
     * @brief 添加事件监听
     */
    virtual uint64_t addEventListener(
        UIAEventType eventType,
        const UIASelector& targetSelector,
        UIAEventCallback callback
    ) = 0;

    /**
     * @brief 移除事件监听
     */
    virtual bool removeEventListener(uint64_t listenerId) = 0;

    /**
     * @brief 移除所有事件监听
     */
    virtual void removeAllEventListeners() = 0;

    // ========== 辅助功能 ==========

    /**
     * @brief 获取所有顶层窗口
     */
    virtual std::vector<std::shared_ptr<IUIAElement>> getTopLevelWindows() = 0;

    /**
     * @brief 获取鼠标位置
     */
    virtual Point getMousePosition() = 0;

    // ========== 后端信息 ==========

    /**
     * @brief 获取后端名称
     */
    virtual std::string getBackendName() const = 0;

    /**
     * @brief 获取后端版本
     */
    virtual std::string getBackendVersion() const = 0;
};

5. 使用示例 ​

5.1 基本操作 ​

cpp
using namespace wingman::platform::uia;

auto& factory = getPlatformFactory();
auto uiaManager = factory.createUIA();

// 查找窗口
auto window = uiaManager->findWindow("Notepad");

// 查找菜单项
auto fileMenu = window->findElement(
    UIASelector().withRole(UIAElementRole::MenuBar)
                 .child(UIASelector().withName("File"))
);

// 点击菜单
fileMenu->click();

// 等待对话框出现
auto dialog = window->waitForElement(
    UIASelector().withRole(UIAElementRole::Window)
                 .child(UIASelector().withName("Save As"))
);

// 查找文本框并输入
auto textBox = dialog->findElement(
    UIASelector().withRole(UIAElementRole::TextBox).withId("FileNameBox")
);
textBox->setText("document.txt");

// 查找按钮并点击
auto saveButton = dialog->findElement(
    UIASelector().withRole(UIAElementRole::Button).withName("Save")
);
saveButton->click();

5.2 事件监听 ​

cpp
// 监听焦点变化
uint64_t listenerId = uiaManager->addEventListener(
    UIAEventType::FocusChanged,
    UIASelector(),  // 所有元素
    [](UIAEventType type, auto element, const auto& data) {
        spdlog::info("Focus changed to: {}", element->getName());
    }
);

// 监听窗口打开
uiaManager->addEventListener(
    UIAEventType::WindowOpened,
    UIASelector().withRole(UIAElementRole::Window),
    [](UIAEventType type, auto element, const auto& data) {
        spdlog::info("Window opened: {}", element->getName());
    }
);

5.3 表格操作 ​

cpp
// 查找表格
auto table = window->findElement(
    UIASelector().withRole(UIAElementRole::Table).withId("DataTable")
);

// 获取所有行
auto rows = table->findElements(
    UIASelector().withRole(UIAElementRole::ListItem)
);

// 遍历行
for (auto& row : rows) {
    auto cells = row->findElements(
        UIASelector().withRole(UIAElementRole::Text)
    );
    for (auto& cell : cells) {
        std::cout << cell->getText() << "\t";
    }
    std::cout << std::endl;
}

6. 平台实现映射 ​

6.1 Windows UI Automation ​

抽象接口Windows UIA
IUIAElementIUIAutomationElement
getName()get_CurrentName()
getId()get_CurrentAutomationId()
getRole()get_CurrentControlType() → 转换为 UIAElementRole
getText()TextPattern / get_CurrentName()
click()InvokePattern / LegacyIAccessiblePattern
setValue()ValuePattern
setChecked()TogglePattern / SelectionItemPattern

6.2 macOS Accessibility ​

抽象接口macOS Accessibility
IUIAElementAXUIElementRef
getName()AXRoleDescription / AXTitle
getId()自定义属性
getRole()AXRole → 转换为 UIAElementRole
getText()AXValue / AXTitle
click()AXPress
setValue()AXSetValue
setChecked()AXSetValue(1)

6.3 Linux AT-SPI ​

抽象接口AT-SPI
IUIAElementAtspiAccessible
getName()get_name()
getId()get_accessible_id()
getRole()get_role() → 转换为 UIAElementRole
getText()get_text() / Text interface
click()get_action() → click
setValue()set_current_value()
setChecked()set_current_value(true)

7. 目录结构 ​

lib/wingman/
├── include/wingman/platform/uia/
│   ├── iuia_element.hpp         # 元素接口
│   ├── iuia_manager.hpp         # 管理器接口
│   ├── uia_types.hpp            # 类型定义
│   ├── uia_selector.hpp         # 选择器定义
│   └── uia_event.hpp            # 事件定义
│
└── src/platform/uia/
    ├── win/
    │   └── uiautomation_manager.cpp    # Windows UIA 实现
    ├── mac/
    │   └── accessibility_manager.cpp   # macOS AX 实现
    ├── linux/
    │   └── atspi_manager.cpp           # Linux AT-SPI 实现
    └── mock/
        └── mock_uia_manager.cpp         # Mock 实现