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]段)。
Metadata
Release files for uictl 0.1.4
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| uictl-0.1.4.tar.gz | 45.7 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| uictl-0.1.4-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 94.3 kB
Release files / uictl-0.1.4.tar.gz
| Download URL | uictl-0.1.4.tar.gz |
|---|---|
| Size | 45.7 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
b9a2414c422f2a792042fef385af88ecd1c870b17970944935a838c7eb8ce80c
|
|
BLAKE2b-256 checksum How to use checksums |
b888a4856e1316f8f7740feb80aa67a2cc69197f744524b3fe7f0b6606537edf
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.6.6
|
Release files / uictl-0.1.4-py3-none-any.whl
| Download URL | uictl-0.1.4-py3-none-any.whl |
|---|---|
| Size | 48.5 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
de41d97d0d3b81027d3570c851a43c87343c1a9c4712603ecc671735235d8755
|
|
BLAKE2b-256 checksum How to use checksums |
073a18b2b319ec2e7247850980876cf6bd63ec8d17e8c25d3d363c47b0753b4d
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.6.6
|