Cua Driver 在 Linux 上的后台计算机使用:AT-SPI 2、XTEST 输入注入与合成光标全解析
【免费下载链接】cuaScale computer-use 2.0 with open-source drivers, cross-OS fleets, and benchmarks for training, evaluation, and data generation.项目地址: https://gitcode.com/GitHub_Trending/cua/cua
本篇文章围绕 inside-linux-computer-use.md 展开,深入拆解 Cua Driver 在 Linux 上实现"后台驱动真实桌面应用"的完整技术方案:如何用 AT-SPI 2 通过 D-Bus 读取可访问性元素树、如何用 XTEST/XSendEvent 实现不抢焦点的输入注入、如何用独立的合成代理光标与物理指针解耦,以及安装、诊断、systemd 常驻与 Wayland 预览的实战细节。读完你将掌握 Linux 桌面后台自动化从架构选型到落地上线的完整路径。
Cua Driver 是 Cua 项目面向跨操作系统后台桌面自动化的驱动层。在 macOS 与 Windows 后台计算机使用功能落地之后,Linux 后端于 release 0.5.7 起提供,任何 Agent(Claude Code、Codex 或你自己的循环)都能通过 MCP 或 CLI 在后台驱动真实 Linux 桌面应用——你继续在前台工作,光标不动、焦点大部分操作不被抢占,Agent 拥有自己的合成光标。
Linux 为什么"难在另一个维度"
Windows 上一篇博客里有一句话:"Windows 内部装着很多个 Windows"。Linux 有同样的问题,只是表现在别处:没有一个统一的 Linux 桌面 API 可供瞄准。真实环境里同时存在会话、合成器、显示服务器、可访问性服务、D-Bus 总线、各工具包自己的焦点行为,以及"click this element"含义各不相同的应用:
- GTK3 与 GTK4 行为不同,Qt5 有自己的一套形态,Tk 更古老更直接;
- Electron、Chrome、VS Code、Slack、Discord、Obsidian 全部基于 Chromium,Chromium 又拖进自己的一整套可访问性模型;
- 有些应用通过 AT-SPI 暴露了足够多的结构、可以按名字寻址;有些暴露了树却拒绝直接激活;有些只接受"从真实指针同一路径"到达的合成输入。
因此 Linux 后端最终同样做成了一个路由器而非单一配方——这正是 Windows 后端被迫形成的形态。流程先问应用暴露了什么结构:元素若可通过 AT-SPI 到达,就按可访问名、角色、索引与边界(而不是像素)寻址;若动作可通过可访问性触发,驱动优先走这条路。应用拒绝该路径时,回退到 X11 输入;键盘输入从XSendEvent迁移到 XTEST 注入,因为更多应用认为 XTEST 足够"真实";XSendEvent 与 X11 回退仍然保留,用于它是最佳路径的场景。
这种设计背后是"诚实"原则:当应用不可见、AT-SPI 被禁用、会话是纯原生 Wayland、或应用绕过 XWayland 时,驱动返回明确的错误而不是假装动作已发生。Agent 可以从它读得懂的错误中恢复,却无法从"静默什么都不做却报成功"的驱动中恢复——后者正是作者花最多精力设计掉的一种失败模式。在源码层面,这对应 delivery.rs 中结构化的background_unavailable错误:携带code、detail、suggestion与escalation字段,调用方可以按code分支处理(与 Windows 的静默丢弃错误保持同一形状)。
先走捷径(走不远):直接驱动 X11
阻力最小的路径是直接驱动 X11:枚举窗口、读取标题、移动鼠标、发送按键事件,没有结构时就回退到图像匹配。Demo 可行,随后迅速崩坏。X11 能告诉你窗口存在,却无法可靠告诉你:侧边栏里有没有一个叫 "Run" 的按钮、文本框里有没有占位符文本、对话框背后有没有一个破坏性动作。大量现代应用通过 X11 hint 根本不暴露有意义的 UI 结构,于是你又退回像素匹配——这正是计算机使用要绕开的脆弱之物。
这就是 AT-SPI 2 登场的地方。AT-SPI 2 是 Linux 上架在 D-Bus 之上的可访问性层,最接近 macOS Accessibility 与 Windows UI Automation,屏幕阅读器等辅助技术已经在使用它。通过它,驱动能看到一棵由可访问对象组成的树:应用、窗口、面板、按钮、输入框、文本区、菜单项、标签页等等。树并不总是完整,但远比把桌面当位图好。这正是"像素自动化"与"计算机使用"的真正区别:
- 像素自动化说:"在坐标 (842, 513) 附近点击";
- 元素自动化说:"点击这个窗口里名为 Save 的按钮"——Agent 可以推理它,并在布局变动后重新找到它。
Linux 仍然需要像素来做边界、截图与拖拽,但 AT-SPI 给了驱动一个语义起点,而不是"一张截图加一句祈祷"。在实现上,驱动通过atspi/zbus crate 原生访问 AT-SPI 2 的 D-Bus 接口,运行时不需要 Python、pyatspi或 GObject-introspection typelib(见 platform-linux/src/atspi/mod.rs)。元素树结果以 Markdown 树形式暴露,每个节点携带element_index、role、name、value、actions、bounds等字段;树的遍历被默认 5000 节点上限与无限深度保护(Issue #22865 说明:防止 Electron 等大 Web 应用生成上万元素树撑爆上下文窗口),并在 Qt6/GTK 冷启动时注册延迟的情况下自动重试(最多 4 次、150ms 退避),避免首次get_window_state拿到空树。
Chromium 可访问性开关:一个下午换来的教训
Chromium 系应用有一个特别尖锐的边,作者为此花掉一个下午。Chrome、VS Code、Slack、Discord、Obsidian 以及大多数 Electron/CEF 应用默认不保持完整的 AT-SPI 树。Chromium 一直等到它检测到辅助技术存在,才开始构建树。这对性能是合理的,对写驱动的人却极其困惑:应用开着、窗口就在那儿,但树就是缺失的——直到会话宣布"有辅助技术在监听"。
在 Linux 上,这个信号位于会话可访问性状态之下。cua-driver serve启动时,守护进程把会话的org.a11y.Status可访问性标志打开——这正是屏幕阅读器会设置的信号。有用之处在于 Chromium 是追溯响应的:已经打开的 Chromium 与 Electron 应用无需重启、无需任何按应用设置的 flag 就会构建 AT-SPI 树;GTK 与 Qt 应用读取同一信号预热自己的可访问性路径。这一个细节就是后端不再脆弱的大部分原因。仍然需要会话总线上可达的 AT-SPI,GNOME 上还可能需要:
gsettings set org.gnome.desktop.interface toolkit-accessibility truecua-driver doctor会检查这条路径并报告所见。
源码层面,这个机制实现在 a11y.rs:驱动通过org.a11y.Bus服务的org.a11y.Status接口写入ScreenReaderEnabled与IsEnabled两个属性(Chromium 看前者,GTK/Qt 看后者)。写入是幂等且 best-effort 的——失败只会记录日志,绝不能导致守护进程启动失败。值得注意的桌面差异:
- GNOME/COSMIC:会话服务会把
ScreenReaderEnabled当作真实用户请求而启动 Orca,因此默认只写IsEnabled; - Cinnamon:设置守护进程从屏幕阅读器设置推导工具包可访问性,写任何信号都可能进入高频设置写循环,因此默认完全不写;
- 其他桌面(含 KDE):保留完整 Chromium 信号以兼容。
上述策略可通过CUA_DRIVER_RS_A11Y_ADVERTISE_MODE(all/is_enabled_only/none)显式覆盖,CUA_DRIVER_RS_DISABLE_A11Y_ADVERTISE可直接关闭。这些行为都有对应单元测试(见同文件 tests 模块)。
不抢焦点的文本输入:每个工具包各有一套
点击只是问题的一半。文本输入通常是后台自动化崩掉的地方:如果 Agent 每次写入前都必须把焦点给目标应用,就会打断你——前台光标跳动、活动窗口切换,后台 Agent 的前提被削弱。因此 Linux 后端为常见工具包家族准备了免焦写入路径,这意味着要分别处理 GTK3、GTK4、Qt5、Tk:
- 部分控件通过可访问性动作接受文本;
- 有些需要
GrabFocus但不提升整个应用; - 有些需要合成焦点事件,有些需要控件点击回退;
- 有些在工具包 IPC 可用时最好走工具包 IPC。
作者坦言这底下没有一个干净的抽象,是一堆按工具包的特例,每个都是慢慢摸索出来的。这正是 Windows 那课在 Linux 的重演:每个应用形状不同,驱动必须为目标控件挑选"干扰最小的合法路径",没有诚实路径时返回清晰错误。目标不是假装焦点从不重要(它仍然重要,有些应用只在工具包相信可编辑控件获得焦点时才接受文本),而是在平台允许时尽量不偷走你的前台焦点。实践中,Agent 可以填表、往对话框打字、操作简单桌面应用,而不用不断接管你的会话。
从源码看(atspi/mod.rs 与 LINUX.md),type_text的分派顺序是:优先走 AT-SPIEditableText(对 Qt6/GTK4 而言免焦即可落入未聚焦窗口的可编辑控件);当焦点落在不可编辑控件上(电子表格单元格、终端、画布)时,通过 XTEST 合成键入到聚焦控件;终端则走免焦的 pty 注入路径。此外用insert_text时,AT-SPI 的EditableText.insertText可以返回effect:"confirmed"并带value_readback证据(可访问性层从控件模型读回插入值),而键盘/XTEST 等路径只能返回effect:"unverifiable",需要通过verify_state或多模态读取确认。
delivery_mode:后台/前台阶梯
所有输入工具(click、type_text、press_key、hotkey、double_click、right_click、scroll)都接受可选的delivery_mode参数——按调用传入(绝不是存储的全局设置)的"尽力后台"阶梯档位,与 macOS、Windows 表面一致:
delivery_mode | 行为 | X11 实现 | 何时使用 |
|---|---|---|---|
background(默认) | 不激活、不提升目标窗口注入 | AT-SPI /XSendEvent/ XInput2 MPX 指针等无抢焦路径 | 默认选择,后台 Agent 的正确起点 |
foreground | 先激活目标,注入,再恢复先前活动窗口 | EWMH_NET_ACTIVE_WINDOW(wmctrl -a等价物)+ 正确处理时间戳以对抗 WM 的防抢焦 | 后台注入落地失败时的显式升级,例如 GTK 对话框按钮或只在聚焦时读取输入的控件 |
foreground是用户可见的接管边界,绝不自动选择,只在你已授权前台控制或征得同意后使用。若后台投递拒绝且无授权,返回拒绝而不是改变用户的焦点、工作区或合成器光标——这与 delivery.rs 的模块注释完全一致。单元测试还验证了一个防呆设计:缺省字段、垃圾值、null以及已移除的历史值"auto"一律解析为background——"默认绝不前台"契约。
合成光标:Agent 光标不是你的物理指针
Linux 后端与 macOS、Windows 使用同一光标模型:Agent 光标不是你的物理指针。驱动为 Agent 绘制一个叠加光标,每个 Agent 有自己的cursor_id,物理指针留在你手放的地方,而 Agent 光标移动到别处点击、拖拽、按住按钮、松开。如果 Agent 共享你的真实指针,它在工作时你就会失去桌面;有了独立的绘制光标,你可以看着它行动而不交出会话,光标所有权保持显式——这正是多 Agent 与后台工作流可推理的前提。
后端还提供后台拖拽与按住按钮工具,足以在桌面上移动条目、拖拽选中文本、测试滑块或驱动不暴露良好可访问性动作的自定义控件。叠加层是驱动行为的可视化轨迹,不是输入路径本身;真实输入仍走受支持后端——目前即 X11 与 XWayland,经 XTEST 及相关 X11 路由。
在源码中,Linux 的 Agent 光标是一个 X11 RGBA override-redirect 窗口(overlay.rs):后台线程以约 60Hz 渲染光标局部瓦片、软件合成时读取每块瓦片下的根窗口、ShapeInput 透传保证光标自身不拦截输入。值得一提的细节:X11 上还有一种更彻底的"并行指针"机制——通过 XInput2XIAddMaster创建独立的 MPX master pointer + evdev uinput 从指针(input/mod.rs),让 Agent 拥有真正独立的指针设备(含 REL_WHEEL/REL_HWHEEL 以驱动 GTK 平滑滚动),且仅在/dev/uinput可访问、非 Xvfb/非 Xtigervnc 且非 KDE Plasma X11 热插拔风险会话时启用;KDE/X11 上为规避会话级崩溃风险而禁用 MPX,保留 XTEST 前台路径。这套机制也被用于parallel_mouse_drag与函数曲线拖拽(如y = f(x)采样为连续弧长插值轨迹)。
Linux 上守护进程问题更小
Windows 因 Session 0 隔离需要 daemon 代理:服务无法触及交互桌面,这一约束塑造了 Windows 驱动的大量设计。Linux 没有同样的问题:如果你 SSH 进一台机器且该会话有显示服务器,sshd可以干净地继承DISPLAY;只要会话总线与显示可达,cua-driver serve就能直接从 SSH 会话连接桌面,没有 Session 0 那一套。这让远程开发比 Windows 简单:SSH 进带桌面会话的 Linux 桌面或 runner,启动驱动,把 MCP 客户端指向它。
持久会话用systemd --userunit;无头或 CI 风格机器启用 lingering,让服务在注销后继续存活:
loginctl enable-linger "$USER"然后在用户会话下运行cua-driver serve。cua-driver autostart命令族目前仅限 Windows;Linux 上 systemd 是受支持路径。
一个与"会话总线"相关的坑在 LINUX.md 有专门章节:AT-SPI 树完全活在桌面会话的 D-Bus上,驱动经DBUS_SESSION_BUS_ADDRESS到达。在容器入口、无头机、runuser/su进桌面用户、systemdsystemunit 或自带临时总线的 VNC 会话里该变量未设置,AT-SPI 注册表遍历会返回空、get_window_state报每个窗口都没有元素。驱动现在启动时自动发现会话总线(与XAUTHORITY恢复对偶):变量未设置时采用/run/user/<uid>/bus,或从运行中的桌面会话进程(xfce4-session、gnome-session…)的/proc/<pid>/environ读取地址。仍需成立的两个条件:
- 该会话中必须运行着可访问性总线,且
toolkit-accessibility已开启(驱动启动时会广播屏幕阅读器信号去翻转它,但完全没有 a11y 总线——/usr/libexec/at-spi-bus-launcher不存在——的会话无法暴露树);cua-driver doctor现在会真实探测org.a11y.Bus(不只是"有没有总线"),并告诉你两者缺哪个; - 守护进程必须以桌面用户身份运行(才能读该用户的会话进程 environ 与
/run/user/<uid>/bussocket)。以 root 身份对用户会话运行守护进程,就是 Windows "Session 0" 隔离问题在 Linux 的对偶。
AT-SPI 遍历为空现在也被诚实呈现:get_window_state设置degraded: true+degraded_reason(而不是光秃秃的elements: []),调用方可以区分"这个窗口真没有控件"与"a11y 桥没起来 / 守护进程不在会话总线上"。
已校验的模态矩阵(X11 / XFCE)
每档输入阶梯及其稳定公开路由(源自 LINUX.md):
| 模态 | delivery_mode | 路由 | 后置条件证明 |
|---|---|---|---|
元素点击(element_index) | background | accessibility | 用verify_state;仅调用不能算确认 |
| 元素像素动作(x,y) | background | AT-SPI-at-point 命中时accessibility,否则global_input | 用verify_state或多模态读取 |
| 像素(px)点击,已升级 | foreground | global_input | 用verify_state或多模态读取 |
对可编辑控件的type_text | background | accessibility | 仅在有value_readback证据时confirmed |
type_text,不可编辑焦点 | background/foreground | synthetic_events或global_input | 用verify_state或多模态读取 |
要点:背景元素像素动作在 X11 上是能落地的——对暴露 AX 的应用走免焦的 AT-SPIdo_action-at-point 路径(x11_atspi),与 macOS/Windows 背景像素点击完全一致;仅对非 AX 表面回退到 MPX 虚拟指针路径(x11_pixel),且该路径需要真实 Xorg +/dev/uinput——在 Xvnc / 无 uinput 的最小容器里请升级delivery_mode:"foreground"。不可编辑控件上的type_text在background档位依赖焦点,是唯一真实的背景局限,foreground是文档化升级。
用它能做什么
首批 demo 刻意做得小,先夯实原语。两个示例与文档结论互相印证:
1. XFCE 上的多光标拖拽。Agent 光标在 XFCE 桌面上拖拽 "Cua" 一词,作者继续工作。重点在于分离:绘制的 Agent 光标自行移动,物理指针纹丝不动。
2. 构建、启动、动作、验证。一个编码 Agent 构建一个计算器桌面应用,启动它,然后用 Cua Driver 通过应用自己的 Linux UI 做 QA。这与 Windows WPF demo 是同一个循环:Agent 不止步于代码生成,而是真的运行它、检查它。对桌面应用来说 diff 不是证据;Agent 必须启动应用并展示发生了什么。
这些 demo 不意味着每个 Linux 应用都被解决了,它们展示的是受支持的形态:真实桌面应用、尽量元素感知、需要时指针感知、以与 macOS/Windows 相同的契约驱动。
还坏着什么:原生 Wayland、AT-SPI 与无头
原生 Wayland 仍在预览。受支持路径今天仍是 X11 或 XWayland;在 Wayland 桌面上驱动与 XWayland 通信并把会话当 X11 处理。这对跑在 XWayland 下的应用有效,但绕过它的纯原生 Wayland 应用——包括某些新版 Firefox 与 GTK4 构建——可能对后端完全不可见。原生 Wayland 路径在CUA_DRIVER_RS_ENABLE_WAYLAND=1之后,默认关闭,屏幕捕获与完整 AT-SPI 对齐仍在落地中。这也是外部帮助最有效的地方:如果你跑纯 Wayland 环境,打开 flag、报告问题、发 PR——issue 与后端都是开放的。
源码显示 Wayland 侧已铺开相当广的探索面(platform-linux/src/wayland/):Sway/wlroots 走 foreign-toplevel 发现 + wlr-screencopy + virtual pointer/keyboard;Hyprland 有独立发现与捕获适配器,可选插件默认仅发现,opt-in 输入 v3 源候选有严格的合格化与验证边界;GNOME/Mutter 用内置 WinRects Shell helper 做目标几何与激活,加 portal/libei 前台原始输入;KDE/KWin 走 AT-SPI 与 portal。标准 Wayland 没有通用的、面向任意被遮挡表面的原始输入客户端协议:背景 AX 动作仍可经 AT-SPI 送达,PX 左键在命中测试解析到可动作 AT-SPI 控件时也可送达,其他受焦点约束的背景指针与键盘形态返回精确的background_unavailable——它们不会在静默丢弃后报成功。
AT-SPI 必须在会话总线上启用且可达。若可访问性总线缺失或工具包可访问性设置关闭,驱动仍能看到一些 X11 级信息,但会失去让后端真正有用的元素树。
无头仍处于早期。更广泛的私有显示故事尚未交付。纯无头Xvfb配方(如xvfb-run -a cua-driver serve)可用于 CI 实验,但它不等于完全受支持的后台桌面产品;探索过私有 Xvfb 与 Hyprland 后台捕获的社区分支被关闭而非合并。glibc 下限已降低、二进制在 Debian 11 容器内构建,但作者仍预期发行版、工具包与合成器缺口会浮现。cua-driver doctor存在的意义就是让你把这些缺口作为事实而非猜测上报。
安装、诊断与运行
macOS 用的同一个安装器现在会检测 Linux 并路由到 Rust 后端:
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/trycua/cua/main/libs/cua-driver/scripts/install.sh)"在 Linux 上,脚本下载x86_64 linux-gnu二进制并符号链接到~/.local/bin/cua-driver。请确保~/.local/bin在PATH中。仓库内对应的安装脚本位于 libs/cua-driver/scripts/install.sh。
前置条件:
# Debian / Ubuntu sudo apt-get install -y at-spi2-core # Fedora / Rocky / RHEL family sudo dnf install -y at-spi2-core按需启用工具包可访问性:
gsettings set org.gnome.desktop.interface toolkit-accessibility true检查会话:
cua-driver doctor cua-driver status cua-driver list_appsdoctor子命令运行一组平台感知探针并输出结构化报告(纯文本默认、--json输出 JSON),每项探针一行[ok]/[warn]/[err]标签,方便 grep;退出码仅在存在[err]时非零(见 cua-driver/src/doctor.rs)。Linux 相关探针覆盖DISPLAY/WAYLAND_DISPLAY存在性、X11 连接可达性与 AT-SPI 总线可用性提示;在 LINUX.md 的快速分诊里,doctor会报告显示服务器(X11/Wayland)、org.a11y.Bus是否真的在会话总线上应答、发现的DBUS_SESSION_BUS_ADDRESS以及ffmpeg可用性(用于录制)。空 AT-SPI 树的排查顺序是:(a) 守护进程不在桌面会话总线(无头/容器/runuser/root 对用户会话,doctor 会显示DBUS_SESSION_BUS_ADDRESS unset);(b) a11y 桥关闭(toolkit-accessibility true);(c) GTK4/Qt6/Chromium 惰性填充——交互或 AX 启用稳定后重新快照。
启动驱动:
cua-driver serve持久 Linux 用户服务(systemd):
mkdir -p ~/.config/systemd/user cat > ~/.config/systemd/user/cua-driver.service <<'EOF' [Unit] Description=Cua Driver [Service] ExecStart=%h/.local/bin/cua-driver serve Restart=on-failure [Install] WantedBy=default.target EOF systemctl --user daemon-reload systemctl --user enable --now cua-driver无头/CI 用户需要服务在注销后继续运行:
loginctl enable-linger "$USER"接入 Claude Code:
claude mcp add --transport stdio cua-driver -- cua-driver mcp其他 MCP 客户端:
cua-driver mcp-config每个 MCP 工具也都暴露为顶层 CLI 子命令,同样操作可直接脚本化:
cua-driver list_apps cua-driver doctor cua-driver status发布二进制面向 glibc 2.31 及更新版本,在debian:11容器内构建,应能在 Debian 11+、Ubuntu 20.04+ 与 RHEL/Rocky/Alma 8+ 上运行;已实测 Debian 12(glibc 2.36)与 Rocky 9(glibc 2.34),并在 Debian 12、Ubuntu 22.04、Ubuntu 24.04、Rocky 9 与 Fedora 41 上完成测试。仓库内完整的 Linux 能力矩阵(X11/Openbox、Sway/wlroots、Hyprland/Omarchy、GNOME/Mutter、KDE/KWin、嵌套cua-compositor各自的基础能力与主要限制)见 LINUX.md。
进一步阅读
- Linux 后端完整技能文档(免焦契约、输入投递、Wayland 边界、快速分诊):libs/cua-driver/rust/Skills/cua-driver/LINUX.md
- 跨平台循环与失败模式(快照前后、像素点击契约):libs/cua-driver/rust/Skills/cua-driver/SKILL.md
- Linux 平台实现源码:
libs/cua-driver/rust/crates/platform-linux/src/(AT-SPI 树、输入投递、叠加层、Wayland 适配器) - Windows 版后台计算机使用(Session 0 与 daemon 代理问题的对照):blog/inside-windows-computer-use.md
- macOS 版后台计算机使用(私有框架与路由技巧的对照):blog/inside-macos-window-internals.md
如果拿它对一个暴露了奇怪树、拒绝输入或跨工具包行为不一致的 Linux 应用,请附上cua-driver doctor输出、发行版、桌面会话与应用名开 issue——这些报告是把早期覆盖变成无聊、可靠的平台支持的最快途径。作者最想听到的,正是尚未碰到的工具包角落。
【免费下载链接】cuaScale computer-use 2.0 with open-source drivers, cross-OS fleets, and benchmarks for training, evaluation, and data generation.项目地址: https://gitcode.com/GitHub_Trending/cua/cua
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考