news 2026/9/13 12:22:27

cua 多光标后台计算机使用演示实战:用 cua-driver 并发驱动五种 UI 框架的“国家档案系统“

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
cua 多光标后台计算机使用演示实战:用 cua-driver 并发驱动五种 UI 框架的“国家档案系统“

cua 多光标后台计算机使用演示实战:用 cua-driver 并发驱动五种 UI 框架的"国家档案系统"

【免费下载链接】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

本篇技术指南围绕仓库 samples/driver/multi-cursor 中的Multi-cursor background computer-use demo("National Records System")展开:一个前台主终端的人机操作,会被实时转发到四个后台终端,由四个独立的 cua-driver 会话(即四个不同颜色的 Agent 光标)并发执行,全程不抬升任何窗口、不移动用户鼠标。读完本文,你将掌握 cua-driver 在"无自动化集成的遗留系统"上做后台并发驱动的完整方法,理解它如何在不依赖辅助功能树(Accessibility Tree)的情况下回退到像素级注入,并学会构建、运行和扩展这样一个跨框架的演示编排器。

演示要回答的核心问题

在企业内网里,大量政府、银行、制造类业务终端是"纯手绘"的遗留应用:没有 API、没有插件接口、没有命令行,甚至在 AI 时代完全没有自动化集成通道。这个 demo 想证明的是——这类应用 cua-driver 照样能自动化

它选择了一组刻意做旧的"政府记录终端"界面:海军蓝横幅、UNCLASSIFIED // FOR OFFICIAL USE ONLY保密条、功能键栏、绿屏记录网格、状态栏。整个演示围绕三个能力点设计:

  1. 一对多的并发重放:用户在中心主终端的一次操作(输入账户名 + 点击 Add Record),被同步扇出到四个角落的后台终端,每个终端由独立的 cua-driver 会话驱动;
  2. 零干扰:任何窗口都不会被抬到前台,用户自己的鼠标指针从未被移动;
  3. 有无辅助功能树都能工作:五个窗口横跨五种 UI 框架,cua-driver 的默认分发逻辑在有 a11y 树的地方自动选择 UIA-Invoke,在没有 a11y 树的地方回退到像素/指针注入。

这正是 cua-driver 后台驱动能力的浓缩验证:把"每次操作都走无障碍接口"与"不得不走像素坐标"的两类窗口,放在同一张屏幕上同时驱动。

窗口布局:2×2 四角后台 + 居中主控

每个窗口都是工作区的一半宽 × 一半高(½ work-width × ½ work-height)。四个角落窗口把任务栏安全的工作区(taskbar-safe work area)平铺成四个象限,主控窗口居中并同时压住四个角落

┌────────────────────────┬────────────────────────┐ │ Win32 GDI (NO a11y) │ WinForms (.NET) │ │ crimson ● │ amber ● │ │ ┌────────────────────────┐ │ │ │ MASTER — Win32 controls │ ← you │ ├───────────│ (foreground, overlaps) │─────────────┤ │ WPF (XAML)│ │ Electron │ │ └────────────────────────┘ mint_lime ● │ │ aqua ● │ (Chromium) │ └────────────────────────┴────────────────────────┘

运行后,在中心主控上点击SUBMIT(或先输入一个 subject 名称再提交),四个彩色光标会同时滑向四个角落终端,并发地在后台提交同一条记录。观察每个角落的绿屏记录网格不断增长、RECORDS:计数器逐条跳动,而没有任何角落窗口被带到前台。

布局由编排器源码精确实现(见 orchestrator/src/main.rs):

  • 通过SystemParametersInfoW(SPI_GETWORKAREA)取工作区矩形(失败时回退到GetSystemMetrics(SM_CXSCREEN/SM_CYSCREEN)),计算每个象限的宽高;
  • 四个角落窗口用SetWindowPosSWP_NOACTIVATE | SWP_SHOWWINDOW | SWP_NOZORDER放置——不激活、不改变 Z 序
  • 主控窗口尺寸只有象限的一半,放在工作区正中央,恰好压住四角的中心接缝,但不会遮住位于象限中心位置的表单;
  • 主控窗口通过SetForegroundWindow置前,交给人类操作。

五种框架各测什么:一张框架能力表

窗口框架无障碍能力cua-driver 驱动路径
TLWin32 + GDI(自绘)像素命中测试 → PostMessage / 指针注入
TR.NET WinFormsMSAA/UIAUIA Invoke
BL.NET WPFUIA (XAML)UIA Invoke(通过WS_EX_NOACTIVATE避免抢占前台)
BRElectronUIA (Chromium)UIA Invoke
CenterWin32 标准控件MSAA(前台;由人驱动)

每个角落节点的实现都放在 demo 目录内:

  • Win32 GDI 节点(左上角,crimson光标):见 legacy-app/src/main.rs。它完全没有注册任何可访问性接口,Account Name 字段和 Add Record 按钮都是WM_PAINT里用 GDI 画笔手绘的——这就是"没有 a11y 树"的极端情形,只能走像素路径;
  • WinForms 节点(右上角,amber光标):见 dotnet/winforms/Program.cs,经典 Win32 控件 + MSAA/UIA,cua-driver 用 UIA Invoke 驱动 Account Name 文本框和 Add Record 按钮;
  • WPF 节点(左下角,aqua光标):见 dotnet/wpf/Program.cs,XAML 元素树天然暴露 UIA,且窗口带WS_EX_NOACTIVATE扩展样式,确保 UIA Invoke 不会偷偷抢占前台;
  • Electron 节点(右下角,mint_lime光标):见 electron/main.js 与 electron/index.html,Chromium 渲染进程暴露 UIA 树,同样走 UIA Invoke;
  • 中心主控:见 legacy-app/src/main.rs,Account Name 是真实 Win32 EDIT 控件、SUBMIT 是真实 BUTTON 控件,两者被埋点:每当用户提交,就把TYPE\t<text>CLICK\t<rx>\t<ry>写到标准输出,供编排器读取并重放。

注意一个细节:五种框架的界面布局是同一套分数化坐标。Win32 GDI 版用常量NAME/SAVE/DOODLE/GRID的客户端分数矩形(见 legacy-app/src/main.rs),WinForms 用X(a) = W*aY(a) = H*a的分数布局函数,Electron 用grid-template-rows/columns百分比。这让编排器可以用"客户端相对分数"(0~1)统一计算像素目标,跨框架复用同一套命中坐标。

构建:五步凑齐全部组件

在 demo 目录(samples/driver/multi-cursor)下,按顺序构建四类产物:

# from this directory cargo build # legacy-app + orchestrator (Rust) dotnet build dotnet/winforms/winforms.csproj # WinForms dotnet build dotnet/wpf/wpf.csproj # WPF npm install --prefix electron # Electron (downloads electron once)

还要在仓库根工作区把驱动本身构建一次:

cargo build -p cua-driver --manifest-path ..\..\libs\cua-driver\rust\Cargo.toml

各产物说明:

  • Rust 侧:demo 目录本身是一个 Cargo workspace(见 Cargo.toml,members = ["legacy-app", "orchestrator"]),一次cargo build同时产出legacy-app.exeorchestrator.exe
  • .NET 侧:两个 csproj 分别产出winforms-legacy.exewpf-legacy.exe,均放在各自bin/Debug/net10.0-windows/输出目录下(编排器正是按这个路径探测可执行文件是否存在,缺失时会打印(skip)并跳过该角落);
  • Electron 侧npm install --prefix electron只安装一次 Electron 运行时(^39.8.5,见 electron/package.json),运行入口是electron .main.js)。

说明:编排器对缺失的角落节点是容忍的。如果某个框架没构建或未安装,它会打印[orch] (skip) ...继续运行——这非常适合先在只有 GDI + 一个 .NET 节点的环境下快速试跑。

运行:人工驱动与自播放两种模式

.\target\debug\orchestrator.exe # human-driven: click/type in the center .\target\debug\orchestrator.exe --auto # self-playing: drives a TYPE+CLICK every few seconds

编排器启动后按这个顺序工作(见 orchestrator/src/main.rs):

  1. cua-driver serve启动守护进程(daemon);
  2. 依次拉起 legacy-app 的 GDI 角落、WinForms、WPF、Electron,以及带 stdout 管道的中心主控;
  3. 通过窗口标题子串(Win32 GDI/WinForms/WPF/Electron/Master (Win32)用EnumWindows找到各窗口句柄,轮询等待最多 8 秒;
  4. 按 2×2 象限布局放置角落,主控居中置前;
  5. 对每个角落调用cua-driver call get_window_statecapture_mode:"ax")抓取辅助功能树,在树中定位] Edit(Account Name 字段)和含ADD RECORD] Button(提交按钮)——每个动作前都会重新抓取,因为 Chromium 的树出现得晚、且随行数增加会重新编号,而 GDI 永远没有树;
  6. 为每个会话预先启用彩色 Agent 光标:cua-driver call set_agent_cursor_enabled {"enabled":true,"session":"<color>"}
  7. 为每个角落开一个独立驱动线程,每个线程通过mpsc::channel接收来自主控的动作并转发到对应cua-driver call会话。

在人工模式下,主控的 stdout 管道被BufReader逐行读取:CLICK <rx> <ry>解析为点击动作、TYPE <text>解析为输入动作(见 orchestrator/src/main.rs),随后广播给所有角落线程。在--auto模式下,编排器自己按3 秒 → TYPE SUBJECT-001..003 → 1.8 秒 → CLICK的节奏发送动作,最后还会追加一轮画迷宫(见下文)。

环境变量覆盖(Env overrides)

默认路径写死在编排器里,但以下变量可以覆盖:

  • CUA_DRIVER_EXE:cua-driver 可执行文件路径,默认libs/cua-driver/rust/target/debug/cua-driver.exe(相对仓库根);
  • LEGACY_APP_EXE:legacy-app 可执行文件路径,默认target/debug/legacy-app.exe(相对 demo 目录);
  • WINFORMS_EXEWPF_EXE:对应 .NET 产物路径;
  • ELECTRON_DIR:Electron 应用目录。

CUA_DRIVER_EXELEGACY_APP_EXE不存在,编排器直接报错退出(cua-driver.exe not found at ...)。

进程清理:Windows Job Object 兜底

编排器启动的每个子进程(守护进程、五个窗口)都会通过AssignProcessToJobObject挂进一个设置了JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE的 Job Object(见 orchestrator/src/main.rs)。只要关掉中心窗口或杀掉编排器进程,整个进程树会被 Windows 一次性回收,不会有孤儿进程残留。

动作分发:有树走 UIA,无树走像素

每个角落线程对两类动作采用不同的路径(见 orchestrator/src/main.rs):

输入(Type)

  • 有 a11y 树:直接cua-driver call type_text并携带element_index,cua-driver 会自动路由到 UIAValuePattern.SetValue——不抢焦点、不投 SendInput;
  • 无 a11y(GDI 角落):先把 Account Name 字段的分数位置(FIELD_FRAC = (0.28, 0.145))换算成窗口本地像素,click一次聚焦,再type_text发送WM_CHAR

点击(Click)

  • 有 a11y 树:click携带 SUBMIT 按钮的element_index,走 UIA Invoke;
  • 无 a11y:把主控发出的按钮相对中心(rx/ry分数)映射到 GDI 窗口本地像素再click

每次调用都以 JSON 传入pidwindow_id(HWND 地址)和session,例如:

{"pid":1234,"window_id":-1043813,"element_index":7,"text":"SUBJECT-001","session":"amber"} {"pid":1234,"window_id":-1043813,"x":420,"y":180,"session":"crimson"}

校验(Verify):点击后线程会再抓一次get_window_state,解析状态栏里的Records: N计数器,只有计数器确实递增才判定COMMITTED ✓,否则记录FAIL (no new record)。这是刻意设计的验证手段——只检查输入框里有文本很容易被骗,必须证明记录真正落进了网格。GDI 角落的计数器是手绘像素、无法从树中读出,因此它靠"网格在屏幕上真实增长"做可视化验证。

画迷宫:纯坐标拖拽 + 截图比对的自校验

--auto模式的最后一幕是画迷宫,用来证明 cua-driver 能在完全没有元素目标、没有应用配合的情况下,仅靠坐标完成画布绘制(见 orchestrator/src/main.rs):

  • 迷宫是 7 段直线段组成的螺旋,坐标定义在公共的REGION = [0.470, 0.180, 0.900, 0.295](客户端分数矩形,位于每个框架画板右上角);
  • 每段调用一次cua-driver call drag,参数带"dispatch":"background":cua-driver 能 PostMessage 的地方就 PostMessage 拖拽,对会丢弃 posted mouse 的画布(Chromium/WPF)则回退到 pen 注入(pointer*事件统一覆盖 mouse/pen/touch,这正是 Electron 版用pointerdown/pointermove/pointerup而非mousedown的原因,见 electron/index.html);
  • 画完后通过get_window_statecapture_mode:"vision",WGC 采集,连完全被遮挡的窗口也能截图)把结果存成 PNG;
  • 校验算法把"绘制的墨迹"和"参考迷宫"分别归一化到 96×96 网格,用膨胀(Chebyshev dilation 半径 2)后的 F1 分数打分,MAZE_PASS = 0.45为及格线,低于即判定FAIL (lines don't match)——且这个分数只评价线条形状,不惩罚落点偏移和缩放。

这里用到了两个值得注意的底层能力:一是 WGC(Windows Graphics Capture)采集对遮挡窗口依然有效;二是DwmGetWindowAttribute(DWMWA_EXTENDED_FRAME_BOUNDS)拿到的窗口外框尺寸,用来把"原始位图像素"与"截图缩放后像素"对齐裁剪。

彩色光标:一个会话一种颜色

cua-driver 会按会话名给每个 session 分配光标颜色:调色板命名的会话(如crimson)直接选中对应颜色。每次click/type_text调用里带上"session":"<color>",动作就被路由到该会话的叠加光标,光标会滑行到目标位置。四个会话 → 四个光标同时动画。

本 demo 用到的会话/颜色映射:

角落会话(光标颜色)
Win32 GDIcrimson
WinFormsamber
WPFaqua
Electronmint_lime

关于"不抬升窗口"(no-z-raise)的机制细节,可进一步阅读仓库中的后台输入契约文档 the-no-foreground-contract.mdx,以及 Windows 平台 MCP 工具清单 mcp-tools-windows.mdx。WPF 角落通过WS_EX_NOACTIVATE扩展样式从窗口层面保证 UIA Invoke 不会触发前台抢占,这与 demo 的整体"无前台契约"一脉相承。

从演示到生产:可以复用的工程模式

把 orchestrator/src/main.rs 当作模板,可以提炼出几条可迁移的后台并发驱动模式:

  1. 会话隔离:每个目标窗口一个独立 cua-driver 会话,天然获得独立的彩色光标、独立的状态与校验上下文,互不干扰;
  2. 每动作重发现get_window_state在每次动作前重新抓取并解析element_index,规避了动态应用(如 Chromium)树编号漂移的问题——静态缓存索引在真实生产环境中几乎必坏;
  3. 可回退的分发策略:把"有树 → UIA,无树 → 像素"的决策放在动作执行层而不是启动层,同一套编排代码可以同时服务两类窗口;
  4. 结果级校验:用应用状态(Records:计数器、截图比对)而不是"调用是否成功"来判定动作是否真正生效;
  5. 进程树治理:Job Object +KILL_ON_JOB_CLOSE保证大规模多进程演示/测试不会留下孤儿进程。

这套模式同样适用于仓库中的其他场景:批量跑跨 OS 舰队(cross-OS fleets)评估、给 cua-bench 制造多窗口测试环境,或把同一套"人类一次操作 → 多 Agent 并发重放"的范式用于数据生成与训练轨迹采集。演示的价值正在于:它把 cua-driver 最容易被低估的"后台、并发、无 a11y 也能干"三件事,压缩进了一个肉眼可见、可运行、可自校验的最小系统。

【免费下载链接】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),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/13 12:22:15

家具行业获客服务商实测推荐与避坑指南

1. 项目概述最近三年在家具行业摸爬滚打&#xff0c;发现获客渠道的选择直接决定了生意成败。2023-2026年期间&#xff0c;我亲自测试了市面上12家主流家具获客服务商&#xff0c;今天就把实测有效的5家靠谱公司分享给大家&#xff0c;附带详细合作体验和避坑指南。这个分享特别…

作者头像 李华
网站建设 2026/9/13 12:21:14

视觉小说翻译器:HOOK、OCR与剪贴板的完整上手指南

视觉小说翻译器&#xff1a;HOOK、OCR与剪贴板的完整上手指南 【免费下载链接】LunaTranslator 视觉小说翻译器 / Visual Novel Translator 项目地址: https://gitcode.com/GitHub_Trending/lu/LunaTranslator 游戏文本刚在屏幕上显示&#xff0c;中文翻译已经跟在后面—…

作者头像 李华
网站建设 2026/9/13 12:20:50

什么是连接池?在PHP应用中如何实现数据库连接池?

连接池是什么&#xff1f;连接池就像是一个“蓄水池”&#xff0c;里面存放着许多已经建立好的连接。当需要连接数据库或其他服务时&#xff0c;不是每次都新建一个连接&#xff0c;而是从连接池里取出一个已经存在的连接来使用。这样做的好处是可以提高性能和效率&#xff0c;…

作者头像 李华
网站建设 2026/9/13 12:17:46

Sa-Token SaStrategy 全局策略:核心逻辑代理封装与自定义扩展指南

Sa-Token SaStrategy 全局策略&#xff1a;核心逻辑代理封装与自定义扩展指南 【免费下载链接】Sa-Token ✨ 开源、免费、一站式 Java 权限认证框架&#xff0c;让鉴权变得简单、优雅&#xff01;—— 登录认证、权限认证、分布式 Session 会话、微服务网关鉴权、SSO 单点登录、…

作者头像 李华
网站建设 2026/9/13 12:16:51

Tasmota 如何用 QEMU 在本地模拟 ESP32 固件启动并打开 WebUI

Tasmota 如何用 QEMU 在本地模拟 ESP32 固件启动并打开 WebUI 【免费下载链接】Tasmota Alternative firmware for ESP8266 and ESP32 based devices with easy configuration using webUI, OTA updates, automation using timers or rules, expandability and entirely local …

作者头像 李华