- 桌面应用
- 开发者工具
- 人工智能
- AI 应用
- AI Agent
- 代码智能体
【免费下载链接】warp
Warp is an agentic development environment, born out of the terminal.
本文基于仓库内设计文档 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正是利用了这条规则:
- 打开一条专用 X 连接(
RustConnection::connect); - 用
XIChangeHierarchy(AddMaster)创建一对私有主设备,命名为warp-agent-cu-<pid>-<seq>(前缀常量见 seat.rs#L47,服务器会派生出 "... pointer"、"... keyboard"、"… XTEST pointer" 等设备名); - 通过
XISetClientPointer把这条连接的 ClientPointer 指向新建的主指针; - 之后,这条连接上既有的 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环境下通过):
- 枚举:窗口按前到后列出并带 id/pid;空显示返回空列表。
- 后台点击:投递
ButtonPress/Release且坐标精确为窗口局部坐标(用xev验证),期间用户核心指针位置不变。 - 后台键入:投递
KeyPress/Release;向被遮挡的 xterm键入能落盘(在 xterm 里用cat > file验证)。 - 遮挡点点击:自动置顶目标并成功,堆叠顺序确认。
- 被遮挡窗口截图:尺寸与内容正确(无遮挡窗口内容"渗入"),区域捕获裁剪正确。
- 设备卫生:
xinput list只在运行期间显示 Seat(主设备对 + XTEST 从设备),结束后与基线字节级一致。 - 遗留回归:屏幕目标动作仍然移动核心指针。
静态检查与剩余缺口
- 静态检查:
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 条)。
- 加固提交(错误路径清理 + CLI 校验 + 捕获上限)在 macOS 上可编译,但其 Linux 专属文件只经过人工审查,建议补一次 Linux 的
风险与缓解
文档列出六条风险与相应缓解:
- click-to-focus WM 下点击的副作用:Agent 点击可能触发 WM 自身的 core passive grab,从而置顶窗口并把用户的焦点移过去(键入没有此副作用)。已在
background_supported文档注释中说明;无 WM 的云环境不受影响。 - WM 防焦点窃取可能拒绝自动置顶:动作在 500 ms 后以"指名遮挡点"的错误失败。后续方案:用
_NET_RESTACK_WINDOW配合 pager source,这是规范认可的机制。 - 键位图分歧:keycode 按核心键盘映射解析(主流工具包也用它解释事件),但 Agent Seat 的 XTEST 从设备携带的是服务器默认键位图,XI2-native 客户端可能据此解读;叠加既有的仅 Shift 解析器,非美式布局(AltGr/levels/groups/死键)不能完全支持——与遗留全屏路径一致。
- 重定向前被遮挡区域的 Composite 捕获可能陈旧:直到应用重绘;Agent 交互会触发重绘,且直接捕获回退能优雅降级。
- 跨目标跨批次按住输入:一个批次在目标 A 按下、在目标 B 释放,会把按住输入滞留在第一个 Seat 上。macOS 实现有同样语义;实践中模型每个调用只对一个目标发出 down/up 对。
- 命中测试与点击非原子:另一个客户端可能在检查与按下之间重排堆叠。接受此竞态——真人点击同样存在;用
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.
相关推荐
Warp macOS 后台 Computer Use 焦点抑制修复:基于窗口标识的聚焦事件精确拦截(APP-4902)
Warp macOS 后台 Computer Use 焦点抑制修复:基于窗口标识的聚焦事件精确拦截(APP 4902) 导读 Warp 在 macOS 上提供后
桌面应用开发者工具人工智能AI 应用AI Agent代码智能体Penrose:Rust 实现的 X11 窗口管理器库
Penrose:Rust 实现的 X11 窗口管理器库 项目介绍 Penrose 是一个用于配置您自己的 X11 窗口管理器的模块化库,采用 Rust 编写。不
ClawX Computer Use 实战指南:借助内置 CUA CLI 无终端驱动桌面应用
ClawX Computer Use 实战指南:借助内置 CUA CLI 无终端驱动桌面应用 ClawX 将 OpenClaw 的 CLI 式 AI 编排变成了
人工智能AI 应用桌面应用交互助手
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考