☰
Warp 的 Linux X11 后台 Computer Use:用 MPX“Agent Seat“实现窗口级无感驱动
2026/10/7 9:57:53 网站建设 项目流程
  • 桌面应用
  • 开发者工具
  • 人工智能
  • AI 应用
  • AI Agent
  • 代码智能体

【免费下载链接】warp

Warp is an agentic development environment, born out of the terminal.

项目地址:https://gitcode.com/GitHub_Trending/wa/warp
点击查看免费下载

本文基于仓库内设计文档 specs/x11-background-computer-use/TECH.md 及其对应实现源码,系统讲解 Warp(一个源自终端的智能开发环境)如何在 Linux X11 上让 Agent 驱动指定窗口——点击、键入、滚动、窗口截图——却完全不移动用户的鼠标、不抢占用户键盘焦点、不扰动用户的修饰键状态。读完本文,你将理解 X11 上"后台驱动"为什么不能照搬 macOS 的做法、MPX(Multi-Pointer X)"Agent Seat" 的完整工作原理、会话级 Seat 生命周期与防泄漏设计、按目标(Target)的动作路由,以及这一设计在当前仓库中的验证矩阵与已知风险。

背景:为什么 X11 需要一个与 macOS 完全不同的方案

跨平台的"后台 computer use"契约

"后台 computer use"指的是:Agent 驱动屏幕上某一个具体的窗口——点击、键入、滚动、按窗口截图——但整个过程不移动用户的鼠标、不窃取用户的键盘焦点、不扰动用户的修饰键状态(比如 Shift/Ctrl 是否处于按下状态)。

在 Warp 的computer_usecrate 中,这个跨平台契约早于本设计存在,并且是平台中立的,核心包括:

  • Target:每个动作都携带一个目标,要么是Screen(整屏/前台应用,即遗留行为),要么是Window { window_id, pid }(驱动某个进程的某个具体窗口)。Options.background_enabled为false时会强制走字节级一致的遗留全屏路径——即使请求里带了窗口目标也会被忽略。
  • ActionResult:每次执行后返回一份刷新的WindowInfo列表(供模型挑选下一个目标),以及CapturedWindow元数据,把"窗口局部坐标"映射到"窗口截图像素"上。
  • 能力门控:background_supported()(其文档注释逐平台说明了能力差异)输出到 Agent 请求设置中的supports_background_computer_use字段,实现在 app/src/ai/agent/api/impl.rs#L111;执行层还要再经过FeatureFlag::BackgroundComputerUse的开关,见 request_computer_use.rs#L102 与 use_computer.rs#L86。

macOS 的对照:进程寻址的事件投递

macOS 实现"后台驱动"的方式是把 Quartz 事件直接投递给目标窗口的所属进程(CGEventPostToPid,见 crates/computer_use/src/mac/post.rs,外加私有窗口寻址字段),所以它能驱动完全被遮挡的窗口,且屏幕上几乎没有任何可见副作用。

X11 的约束:XSendEvent为什么不可行

X11 没有任何"进程寻址"或"窗口寻址"且被应用所接受的事件投递原语。唯一窗口寻址的原语是XSendEvent,但它产生的事件带send_event=true标记,GTK、Qt、Chromium、WINE 都会出于安全原因刻意忽略这类合成事件。

因此 X11 的设计必须建立在完全不同的原语之上,其取舍也和 macOS 不同。这正是本文接下来要展开的核心。

核心机制:一个 MPX "Agent Seat"

Multi-Pointer X 与 ClientPointer 路由

X11(XInput2 ≥ 2.0,2009 年之后的任意 X Server,包括 Xvfb)支持多对相互独立的主指针/主键盘(Multi-Pointer X,简称 MPX)。每对 Master 设备拥有:

  • 自己的屏幕光标;
  • 自己的键盘焦点;

并且服务器会为每一对 Master 设备创建配套的XTEST 从设备。

关键路由规则是:X Server 会把某个客户端的核心输入请求——XTEST 伪输入、WarpPointer、QueryPointer、SetInputFocus——全部路由到该客户端的 "ClientPointer" 主设备对上(对应服务器端Xext/xtest.c里的PickPointer/PickKeyboard)。

AgentSeat 的创建流程

seat.rs::AgentSeat::new正是利用了这条规则:

  1. 打开一条专用 X 连接(RustConnection::connect);
  2. 用XIChangeHierarchy(AddMaster)创建一对私有主设备,命名为warp-agent-cu-<pid>-<seq>(前缀常量见 seat.rs#L47,服务器会派生出 "... pointer"、"... keyboard"、"… XTEST pointer" 等设备名);
  3. 通过XISetClientPointer把这条连接的 ClientPointer 指向新建的主指针;
  4. 之后,这条连接上既有的 XTEST 鼠标/键盘代码原封不动地驱动 Agent Seat。

带来的结果非常关键:

  • 应用收到的是服务器真实生成的设备输入(synthetic标记为 NO)——与每一种工具包完全兼容,不像XSendEvent那样被忽略;
  • 用户的光标位置、键盘焦点、修饰键状态都属于用户自己的主设备对,永远不会被触碰;
  • Agent 键入时跟随的是Agent 键盘自己的焦点(每个动作前用XISetFocus设置),即使目标窗口被遮挡也能照常输入(对应 seat.rs::focus_window 的实现:只影响 Agent 主键盘,目标应用收到普通的FocusIn会认为自己获得了焦点);
  • 运行窗口目标批次时屏幕上会出现第二个可见光标。

被否决的替代方案

文档明确记录了被否决的两个方案:

  • XSendEvent合成事件——被主流工具包静默忽略;
  • 在用户 Seat 上做"焦点/指针保存—恢复"式操作——会与用户的并发输入竞态,而且本质上并非真正的"后台"。

Agent Seat 的生命周期与防泄漏设计

从"每工具调用"到"每会话"

初版设计中,Seat 存活于Actor(一次工具调用)的生命周期内。但X Server 把输入状态保存在 Seat 上,而这些状态必须跨多次工具调用存续:移除一个主设备对会隐式释放其 XTEST 从设备按住的所有按键,从而终结跨调用的拖拽(一次use_computer调用中按下、后续调用中移动/释放)——典型的受害者就是"拖拽选择"。

因此修改后的设计是:

  • 应用内会话(in-app sessions)共享按会话归属者(client conversation id)键控的 Seat,存活到computer_use::end_background_session(owner)在会话完成或取消时被调用为止;
  • 所以第二个光标在整个会话期间持续存在,而不是单次调用期间;
  • 无归属者的use_computerCLI 则保持每 Actor 一个私有 Seat。

会话级共享实现在 seat.rs::shared_for_session(session_seats()是一个进程级OnceLock<Mutex<HashMap<owner, Arc<AgentSeat>>>>),会话结束走 seat.rs::end_session。在应用侧,会话结束(正常完成、取消、清理)都会调用end_background_session,例如 conversation.rs#L3192 与 controller.rs#L3014。

泄漏安全:Drop、live 注册表与 remove_stale_seats

主设备是服务器全局的,会活得比创建它的连接更久,所以 seat.rs 把泄漏当作一等公民来对待:

  • Drop会移除设备对;AddMaster之后的每一个构造失败路径要么移除设备对、要么注销注册表条目,让下一次创建时回收它。
  • 进程本地 live-seat 注册表(live_seats)在AddMaster之前就预留名字,防止并发创建时把"正在构造中的设备对"误回收。
  • remove_stale_seats在每次创建时运行:清理"本进程 pid、但不在注册表中的 Seat"(由失败构造泄漏的),以及"pid 已死的 Seat"(进程崩溃后留下的,pid 从 Seat 名字里解析出来,见seat_pid,存活检查用kill(pid, None)只做错误检查,见process_alive)。外部 pid 复用可能延迟回收直到复用进程退出——这是一个有界、可接受的残余。
  • use_computerCLI返回ExitCode而不是调用process::exit,保证错误退出时Drop总会执行。

按目标(Target)的动作路由

x11/mod.rs::perform_actions是整个执行的主入口,核心逻辑如下。

批次预校验与 Seat 预创建

  • 批次预校验:先扫描整个批次,拒绝window_id: 0这个哨兵值(与 macOS 侧对齐——0是 CLI 默认值和不可解析线上 id 产生的"未知"哨兵,在这里直接给出可操作的报错,而不是在后续窗口解析时抛出不透明的错误)。
  • Seat 前置创建:只要批次里有窗口目标,就在执行任何动作之前把 Agent Seat 创建好,保证失败不会导致批次半执行。
  • Screen动作走遗留路径,驱动用户的核心指针/键盘;background_enabled == false时行为与既有实现完全一致(所有目标被强转为Screen)。

窗口局部坐标 → 根坐标

窗口目标携带的坐标是窗口局部像素,必须换算成根(屏幕)坐标,并做边界校验(windows.rs::window_local_to_root)——X11 指针事件按屏幕位置投递,越界点会落到不相关的窗口上,所以直接拒绝而不是放行。GetGeometry返回的坐标是相对父窗口(如 WM 的装饰框)的,因此换算前先用TranslateCoordinates把内容框原点平移到根坐标(geometry)。

命中测试与"无焦点置顶"

X11 会把指针事件投递给指针下方最顶层的窗口,所以点击/滚轮必须先做命中测试:

  • window_hit_at_point执行与服务器事件选取完全相同的 root 向下TranslateCoordinates遍历,并检查目标窗口是否在这条链上——这同时天然处理了 WM reparenting 产生的装饰框。
  • 如果目标点被遮挡,先通过ConfigureWindow(Above)无焦点置顶(raise;在有 WM 时该请求会被重定向给 WM 异步执行),再以 500 ms 轮询复查(ensure_window_clickable_at,轮询间隔 20 ms、超时 500 ms,常量见 mod.rs#L35-L37);置顶未生效则动作以带可操作信息的错误失败。
  • 键/键入类动作先XISetFocus把 Agent 键盘焦点设到目标窗口;点击类动作同样先设焦点,以仅在 Agent Seat 上复刻真实点击的焦点效果(用户焦点不动)。

窗口枚举与截图

windows.rs::enumerate_windows(前台到后台排序):

  • 首选 EWMH 的_NET_CLIENT_LIST_STACKING(自底向顶,反转即前到后),次选_NET_CLIENT_LIST;
  • 无 WM 的服务器(如云环境里的裸 Xvfb)回退到QueryTree遍历根窗口的直接子窗口,并排除override_redirect的窗口(菜单、tooltip),以贴近 EWMH 列表的内容(top_level_windows);
  • 标题取_NET_WM_NAME/WM_NAME,pid 取_NET_WM_PID,应用名取WM_CLASS(window_title、window_pid、window_class);
  • 在 X11 上pid仅供参考——事件投递按窗口 id 寻址,pid 不会参与投递。CLI 诊断用的list_windows额外输出 bounds、class、title 表格(mod.rs#L132-L144)。

screenshot.rs::take_window:

  • 优先走Composite 扩展捕获(capture_via_composite):对窗口做 per-client 的RedirectWindow(Automatic)(连接断开时自动释放,冗余重定向报错可忽略),再用NameWindowPixmap拿离屏后备 pixmap,注意后备 pixmap 含边框,内容框要从(border_width, border_width)开始取——所以能看见被遮挡窗口的完整内容;
  • 失败则回退到直接对窗口 drawable 做GetImage(被遮挡区域内容未定义,属预期降级);
  • 原生捕获尺寸有上限MAX_WINDOW_CAPTURE_PIXELS(32 Mi 像素,约 3350 万,覆盖完整 8K 屏 ≈ 7680×4320 ≈ 3320 万像素),因为GetImage回复和 RGB 转换缓冲在缩放限制生效前是按原生尺寸分配的,防止巨型(或恶意)窗口导致 GB 级分配;
  • 捕获区域必须落在窗口尺寸内,超出会报错(见 screenshot.rs#L83-L95)。

能力探测 background_supported

linux/mod.rs::background_supported:

  • 要求是X11 而非 Wayland——Wayland 路径走 XDG portal,没有按窗口寻址的能力(is_wayland_available()/is_x11_available()分别检查WAYLAND_DISPLAY/DISPLAY环境变量,见 linux/mod.rs#L15-L26);
  • 再加一个OnceLock缓存的 XI 2.x 探测(probe_background_support:XIQueryVersion(2,2)且major_version >= 2),因为该探测每次 Agent 请求都会查询,缓存避免重复开连接。

验证与测试

Xvfb 验证矩阵

文档记载了在远端 Linux Agent 上、针对加固前代码树的验证(全部在Xvfb 1280×800、无 WM环境下通过):

  1. 枚举:窗口按前到后列出并带 id/pid;空显示返回空列表。
  2. 后台点击:投递ButtonPress/Release且坐标精确为窗口局部坐标(用xev验证),期间用户核心指针位置不变。
  3. 后台键入:投递KeyPress/Release;向被遮挡的 xterm键入能落盘(在 xterm 里用cat > file验证)。
  4. 遮挡点点击:自动置顶目标并成功,堆叠顺序确认。
  5. 被遮挡窗口截图:尺寸与内容正确(无遮挡窗口内容"渗入"),区域捕获裁剪正确。
  6. 设备卫生:xinput list只在运行期间显示 Seat(主设备对 + XTEST 从设备),结束后与基线字节级一致。
  7. 遗留回归:屏幕目标动作仍然移动核心指针。

静态检查与剩余缺口

  • 静态检查:cargo check -p computer_use、cargo clippy --all-targets -- -D warnings、cargo fmt --check在 Linux 上通过;macOS 宿主构建不受影响。
  • 尚未闭合的缺口(文档明确标注):
    • 加固提交(错误路径清理 + CLI 校验 + 捕获上限)在 macOS 上可编译,但其 Linux 专属文件只经过人工审查,建议补一次 Linux 的cargo check/clippy(CI 或一次性远端 Agent)并重跑上面的 Xvfb 矩阵;
    • 真实 WM 桌面(Mutter/KWin/i3 带合成器):置顶重定向与 click-to-focus 副作用已记录但未实测;
    • 非美式键盘布局与 XI2-native 客户端(键位图分歧,见风险第 3 条)。

风险与缓解

文档列出六条风险与相应缓解:

  1. click-to-focus WM 下点击的副作用:Agent 点击可能触发 WM 自身的 core passive grab,从而置顶窗口并把用户的焦点移过去(键入没有此副作用)。已在background_supported文档注释中说明;无 WM 的云环境不受影响。
  2. WM 防焦点窃取可能拒绝自动置顶:动作在 500 ms 后以"指名遮挡点"的错误失败。后续方案:用_NET_RESTACK_WINDOW配合 pager source,这是规范认可的机制。
  3. 键位图分歧:keycode 按核心键盘映射解析(主流工具包也用它解释事件),但 Agent Seat 的 XTEST 从设备携带的是服务器默认键位图,XI2-native 客户端可能据此解读;叠加既有的仅 Shift 解析器,非美式布局(AltGr/levels/groups/死键)不能完全支持——与遗留全屏路径一致。
  4. 重定向前被遮挡区域的 Composite 捕获可能陈旧:直到应用重绘;Agent 交互会触发重绘,且直接捕获回退能优雅降级。
  5. 跨目标跨批次按住输入:一个批次在目标 A 按下、在目标 B 释放,会把按住输入滞留在第一个 Seat 上。macOS 实现有同样语义;实践中模型每个调用只对一个目标发出 down/up 对。
  6. 命中测试与点击非原子:另一个客户端可能在检查与按下之间重排堆叠。接受此竞态——真人点击同样存在;用GrabServer串行化会冻结所有客户端。

后续方向

文档列出的后续项包括:

  • XKB 感知的按键解析(AltGr/levels/groups)并把用户的键位图克隆到 Agent 键盘上;
  • 基于_NET_RESTACK_WINDOW的置顶(面向 WM 桌面);
  • 既有 X11 图像转换器中的扫描线步长/字节序感知转换;
  • 纯键盘窗口批次的光标位置上报目前回退到用户核心指针位置(纯外观问题);
  • 可选:在按键/按钮仍按住时拒绝目标切换(对应风险 5)。

小结

从设计文档 specs/x11-background-computer-use/TECH.md 与实现源码可以看到,Warp 的 Linux X11 后台 computer use 是一套完全围绕 X11 平台约束重构的方案:用 MPX 的"第二个输入 Seat"绕开XSendEvent被工具包忽略的死路,用会话级 Seat 生命周期保住跨工具调用的拖拽状态,用进程级注册表 + 创建时回收把"服务器全局、比连接长寿"的主设备泄漏风险降到可控,用 Composite 扩展拿到被遮挡窗口的完整画面,再以"命中测试 + 无焦点置顶 + 轮询复查"应对 X11 位置路由的事件投递语义。与 macOS 的进程寻址投递相比,它在"零可见副作用"上做出了必要妥协(第二个光标、点击可能触发 WM 焦点移动),换来了对任意工具包的完整输入兼容性——这正是 X11 平台给出的最优可行解。若想深入代码,可以从 crates/computer_use/src/linux/x11/seat.rs(核心 Seat 机制)、crates/computer_use/src/linux/x11/mod.rs(动作路由)与 crates/computer_use/src/linux/x11/windows.rs(窗口枚举与坐标换算)三处入手。

  • 桌面应用
  • 开发者工具
  • 人工智能
  • AI 应用
  • AI Agent
  • 代码智能体

【免费下载链接】warp

Warp is an agentic development environment, born out of the terminal.

项目地址:https://gitcode.com/GitHub_Trending/wa/warp
点击查看免费下载

相关推荐

上一篇:LeetCode 1985 Find the Kth Largest Integer in the Array:字符串大数第 K 大问题全解
下一篇:vllm-omni 中 Higgs-Audio v2 在线 TTS 服务实战:两阶段流水线、部署配置与声音克隆

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询