uictl
Linux 桌面 UI 自动化控制 CLI,基于 dogtail / AT-SPI 无障碍树。搜索窗口、浏览控件树、点击控件、输入文本、等待/断言,一条命令搞定。
uictl apps # 列出所有应用
uictl windows gnome-text-editor # 列出应用的窗口
uictl tree gnome-text-editor --depth 4 # 打印窗口控件树
uictl find gnome-text-editor "role=button"
uictl inspect gnome-text-editor "name=保存"
uictl read Chromium # 提取页面干净文本(自动定位 document)
uictl read Chromium 'role=paragraph' # 只读 selector 子树下所有文本
uictl click gnome-text-editor "role=button[name=保存]"
uictl type gnome-text-editor "role=text" --text "hello"
uictl wait gnome-text-editor "name=保存" --visible
uictl expect gnome-text-editor "name=保存" --visible --sensitive
安装
# 轻量命令(不碰无障碍树):直接跑
uvx uictl --version
uvx uictl install-skill # 注入 agent 技能到 ~/.agents/skills/uictl/
# 完整 UI 命令:用系统 Python 跑(gi 无 PyPI wheel,只能来自系统包;
# uictl 在 gi 缺失时自动注入系统 site-packages 到 sys.path,无需 PYTHONPATH)
uvx --python /usr/bin/python3 uictl apps
# 或持久安装
uv tool install uictl --python /usr/bin/python3
依赖系统包(AT-SPI typelib,无法 pip 安装):
gir1.2-atspi-2.0 python3-gi python3-pyatspi
(Wayland 下如需输入注入:gnome-ponytail-daemon,GNOME 46+ 已封禁注入,见下)。
本地开发(本仓库):
cd ~/projects/uictl
uv venv .venv --python $(which python3) --system-site-packages
uv pip install -e .
子命令
| 子命令 | 作用 | Wayland 可用 |
|---|---|---|
apps |
列出应用(--json) |
✅ |
windows [APP] |
列出窗口 | ✅ |
tree APP |
控件树(--depth --path --json) |
✅ |
find APP SEL [--context N] |
搜索控件,输出 path;--context N 打印目标周边局部树 |
✅ |
inspect APP SEL |
控件详情(状态/文本/坐标/Action 列表) | ✅ |
click APP SEL |
点击:AT-SPI Action 自动链(click→press→activate→toggle→check/uncheck),全无才回退注入(--action NAME) |
✅ Action / ⚠️ 注入 |
dblclick APP SEL |
双击 | ⚠️ 需注入 |
hover APP SEL |
悬停 | ⚠️ 需注入 |
type APP SEL --text X |
输入:fill 后自动验证,失败自动降级 insert_text,仍失败清晰报错(--keystrokes 逐键 ⚠️ 需注入) |
✅ / ⚠️ |
key COMBO |
组合键,如 "<Control>a" |
⚠️ 需注入 |
read APP [SEL] [--win] [--min-length N] [--skip-role a,b] [--max-depth N] |
提取干净文本:默认读窗口内所有 document 节点,无则整窗;传 SEL 则读其子树 | ✅ |
search [APP] KEYWORD [--win] [--scope] [--name-only] [--context N] [--limit N] |
全文发现(name/text/desc 子串,不区分大小写);APP 省略搜所有应用 | ✅ |
snapshot APP [--win] |
捕获 tree-json 精度基线,打印短 id,存 /tmp/uictl-UID/<id>.json |
✅ |
delta ID [--keep] [--json] |
对比快照,输出 +/-/~(树节点级差异),默认 diff 后删除 |
✅ |
snapshots [--clear] |
列出/清理存活快照 | ✅ |
wait APP SEL [--visible] [--sensitive] [--focused] [--gone] |
轮询等待条件成立 | ✅ |
expect APP SEL [--text S] [--role R] ... |
断言,失败 exit 1 | ✅ |
install-skill [--dir DIR] [--force] |
注入 agent 技能到 ~/.agents/skills/uictl/ |
✅ |
⚠️ 需注入 = 需要原始输入注入(X11 会话原生支持;Wayland 需 RemoteDesktop 后端)。在只读环境(GNOME 46+ Wayland)下这些命令会报清晰错误而不是 抛 traceback。
选择器语法(Playwright 风格)
role=button[name=保存] # 引擎 + [属性...]
name=保存 # 单属性
text=请输入 # 匹配 node.text 子串
path=0:2:3 # 从应用根开始的索引路径(find 输出的 path 可直接用)
保存 # 裸字符串 = name
支持属性:name role(role_name) desc(description) label id(accessible_id) text。
值含空格需加引号:name="保存 文档"。
strict 模式:inspect/click 等要求唯一匹配,命中多个会报错并列出全部匹配,
用 --index N 指定;--scope 限定在父节点内搜索;--win 指定多窗口应用的窗口。
read:提取干净的页面文本
uictl read APP 把页面内容抽成干净的纯文本行(每段/每标题一行),适合让 agent
直接"读网页"。行为:
- 默认根:在窗口里递归找所有 document 类角色(
document/document web/document frame/document picture/web area),逐个读取拼接;一个都没有 (如 GTK 文本编辑器)就回退读整个窗口子树。 - 去重:容器节点(heading/paragraph/link)的
node.text已聚合子孙文本, 重复的 static 子节点会被自动丢弃,标题/链接不会出现两遍。 - 内联替换:Chromium 用
\ufffc(U+FFFC) 占位内联子节点,如"You can ."会还原成"You can index it here."。 image无文本时用其 name(alt 文本)兜底。
常用参数:--min-length N 丢弃过短行、--skip-role button,panel 整类跳过、
--max-depth N 限深、--win 指定窗口。传 SEL 时改为 strict 解析单个节点、只读
该子树(如 uictl read Chromium 'role=paragraph')。
search / snapshot / delta:给 LLM Agent 的"发现 + 验证"配套
uictl 面向 LLM Agent 使用做了几处容错设计:
-
type不再静默撒谎:fill(set_text_contents)后自动读回验证,失败自动降级delete_text + insert_text(Chromium 网页输入框吃这一套),仍失败才报清晰错误。 -
click自动选 Action:按click → press → activate → toggle → check/uncheck依次尝试,Wayland 下 radio/checkbox 无需手动--action。 -
search代替tree | head:禁止对tree全量输出用head截断(会漏掉 document 里的内容)。发现用search APP "关键词"(覆盖整页、一次拿全),看结构用find APP SEL --context N(目标周边局部树,命中行标◀ target)。 -
snapshot+delta验证操作生效:$ uictl snapshot Chromium snap: e3f2a91 $ uictl type Chromium "name=令牌名称" --text tea-cli $ uictl delta e3f2a91 ~ [entry] 令牌名称: text '' → 'tea-cli' # 填进去了 [snapshot e3f2a91 removed]snapshot捕获与tree --json同精度的树(role/name/text/path/children), 存/tmp/uictl-<uid>/<id8>.json;delta对树做多重集 diff(稳定键role+name+text,忽略 path/坐标 churn),输出+新增 /-消失 /~文本编辑。- 默认 diff 完即删,强制"基线→操作→验证"原子化,防止拿过期基线对比;
--keep保留、snapshots列出、--clear清空。 - 性能说明:
tree/snapshot/delta都是整树遍历,AT-SPI 每节点一次 DBus(无缓存), 重页面约 5–15s,属正常范围(tree --json用增量 path,比旧版快约 5 倍)。
平台限制说明(为什么有的命令报错)
- 读(apps/windows/tree/find/inspect/read/search/snapshot/delta/wait/expect)走 AT-SPI 总线,Wayland 全可用。
- 控(click/type-fill)走 AT-SPI 接口(Action/EditableText/Component), 应用自己实现,Wayland 全可用——这是 GNOME 给无障碍"控制"设计的官方通道。
- 原始输入注入(key/hover/dblclick/type-keystrokes)在 X11 走 XTEST;
在 Wayland 被设计成受控特权(需 RemoteDesktop portal 授权)。
另外 GNOME 46+ 移除了
org.gnome.Shell.Introspect.GetWindows, gnome-ponytail-daemon 依赖它做坐标换算,因此 dogtail 的注入在 GNOME 46+ Wayland 上整体不可用 → uictl 会报清晰错误。
退出码
0成功1未找到 / 断言失败 / strict 多匹配 / 平台不支持130用户中断
Roadmap
screenshot(GNOME 50 需走 XDG Desktop Portal,core.take_screenshot已预留)- RemoteDesktop portal 输入后端(Wayland 注入的官方通道,需用户授权)
发布到 PyPI
cd ~/projects/uictl
uv build # 构建 sdist + wheel(产出 dist/)
uv publish # 需要 PyPI API token 凭据
凭据(三选一):
~/.pypirc([pypi] username=__token__ password=pypi-xxx)——本机已配置- 环境变量:
UV_PUBLISH_TOKEN=pypi-xxx uv publish - 交互输入:
uv publish --username __token__(会提示输入密码)
⚠️ 版本一旦发布不可覆盖,改代码后记得 bump
pyproject.toml的version。 正式发布前可先试水 Test PyPI:uv publish --publish-url https://test.pypi.org/legacy/ --check-url https://test.pypi.org/simple/(需要 test.pypi.org 的 token,写到~/.pypirc的[testpypi]段)。
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file uictl-0.1.4.tar.gz.
File metadata
- Download URL: uictl-0.1.4.tar.gz
- Upload date:
- Size: 45.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via:
uv/0.6.6
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b9a2414c422f2a792042fef385af88ecd1c870b17970944935a838c7eb8ce80c
|
|
| MD5 |
fd0a86c40fdedf060c2d7cbf5affd613
|
|
| BLAKE2b-256 |
b888a4856e1316f8f7740feb80aa67a2cc69197f744524b3fe7f0b6606537edf
|
File details
Details for the file uictl-0.1.4-py3-none-any.whl.
File metadata
- Download URL: uictl-0.1.4-py3-none-any.whl
- Upload date:
- Size: 48.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via:
uv/0.6.6
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
de41d97d0d3b81027d3570c851a43c87343c1a9c4712603ecc671735235d8755
|
|
| MD5 |
f97432a68be91a987f316af229926357
|
|
| BLAKE2b-256 |
073a18b2b319ec2e7247850980876cf6bd63ec8d17e8c25d3d363c47b0753b4d
|