@iiwate/pi-silent-gui
v0.2.0
Published
Pi extension: private-desktop GUI smoke with Windows Job Object lifecycle and strict audio failure handling
Readme
pi-silent-gui
Windows-only Pi 扩展:在隔离的 Win32 私有桌面中启动和操作 GUI 程序,并输出无损窗口截图。整个过程不切换用户桌面、不使用剪贴板,也不调用 SendInput。
安装
要求:
- Windows 10 或更高版本;
- Node.js >= 20;
- Python >= 3.10,且
python在 PATH; - 提权 Pi 的默认非提权路径需要当前用户的 Explorer shell。
pip install pycaw
pi install npm:@iiwate/pi-silent-gui安装后执行 /reload。可用 PI_SILENT_GUI_PYTHON 指定 Python 解释器。
工具
| Tool | 作用 |
|---|---|
| silent_spawn | 在私有 Desktop 和 Job Object 中启动进程;默认非提权,音频 guard 就绪后才恢复目标线程 |
| silent_message | 按完整窗口 PNG 坐标投递 click/key,自动分流客户区 child 与非客户区,支持批量 steps |
| silent_capture | 捕获完整窗口,返回绝对 PNG 路径、原始尺寸、客户区/DPI 元数据和 all_black |
| silent_kill | 终止 Job、停止 broker,并清理受约束的 session 临时目录 |
启动
{
"exe": "app.exe",
"cwd": "C:/path/to/app",
"args": ["--example"]
}目标默认非提权。只有 Pi 已经提权时,才可显式允许管理员目标:
{
"exe": "admin-tool.exe",
"allow_elevated": true
}扩展不会调用 runas,也不会触发 UAC。
消息
单次 click/key 或批量步骤都在同一个私有桌面和初始顶层 hwnd 上执行。单步 repeat 为 1..100,delay_ms 为 0..10000,总动作数不超过 1000。
click 的 x/y 与 silent_capture 完整 PNG 共用同一窗口坐标系,(0,0) 是 PNG 左上角,不需要也不允许调用方估算标题栏高度。运行时使用 Window/Client Rect、坐标转换和 WM_NCHITTEST:HTCLIENT 会下降到最深可见、非透明 child 并投递客户区鼠标消息;其他命中向顶层窗口异步投递对应非客户区消息,避免标题栏或菜单模态循环阻塞 down/up。
{
"session_id": "...",
"steps": [
{ "action": "click", "x": 50, "y": 50 },
{ "action": "key", "key": "return", "repeat": 2, "delay_ms": 50 }
]
}响应包含 coordinate_space: "window" 和 window.client {x,y,width,height}、window.dpi。click 还返回 hit_test、实际 target_hwnd、dispatch,以及 point.window/screen/target;HTCLOSE 会附带 system_command: "close"。成功只表示消息已完成有界投递,界面效果应由后续截图验证。批量响应在 clicks 中按执行顺序返回每次 click 的投递元数据。
截图
{
"session_id": "...",
"window_class": "ExampleWindow",
"out_path": "C:/evidence/window.png"
}out_path 可省略;默认文件写入当前 session 临时目录。截图始终保留完整 Window Rect,不裁剪客户区。返回值包含 coordinate_space: "window"、相对完整 PNG 的 window.client {x,y,width,height} 和 window.dpi,不内联图像。
清理
{
"session_id": "..."
}Pi session_shutdown 会对仍登记的 session 执行两次 best-effort kill。
隔离与生命周期
每个 session 使用一个持久 Python broker:
- 生成调用方不可指定的唯一私有 Desktop;
- 创建带
KILL_ON_JOB_CLOSE的 Windows Job Object; - 使用
STARTUPINFOEX + PROC_THREAD_ATTRIBUTE_JOB_LIST,在进程创建时原子加入 Job; - 目标保持 suspended,直到 WASAPI 音频通知和首轮静音枚举完成;
- broker 持续跟踪 Job 内进程、维持静音并处理 stop;
- broker 异常退出时,Job handle 关闭并终止整棵进程树。
默认路径不会因 Pi 已提权而让目标继承管理员 token;拿不到可信的非提权 Explorer token 时会直接拒绝启动。
安全边界
- 永不调用
SwitchDesktop、SendInput或剪贴板 API; - Desktop、Job 和 session 状态均绑定 12 位 session ID namespace;
- kill 只操作登记的 Job 和 PID+创建时间身份,不按 exe 名或 PPID 全局扫描;
- PID 身份核验、终止和等待在同一个进程 handle 上完成;
- 客户区和 hit-test 使用
SendMessageTimeoutW限时;非客户区 down/up 使用PostMessageW异步有界入队; - 几何、message、capture 只临时切换对应 helper 操作线程的 per-monitor DPI awareness,并恢复原 context;
- 临时目录只允许位于
%LOCALAPPDATA%\Temp\pi-silent-gui\<session_id>; - 删除前逐级持有目录 handle,拒绝 junction、符号链接和其他 reparse point;
- 异常 spawn 仅在 Job 和 broker 身份完整时登记恢复 session;
- Python stdout/stderr 持续排空,各自只保留最后 1 MiB,不因输出超限强杀 launcher。
清理失败会保留 session 登记和结构化错误,供后续重试。
截图保真
- 捕获完整 Window Rect,保持原始宽高,不缩放、不裁剪;
- 上限为
32 * 1024 * 1024像素,超限明确失败,不降质输出; - 使用 24-bit RGB 无损 PNG;
GetDIBits未返回全部扫描行时立即失败;- 只重试等待 Job/窗口出现,找到窗口后
PrintWindow只执行一次; - 全黑画面仍作为有效证据写入,并以
all_black: boolean明示; - TS 返回前验证绝对路径、文件身份、PNG/IHDR 和窗口尺寸。
静音边界
broker 在恢复目标线程前注册 WASAPI session-created 通知,并辅以启动期 50ms、稳定期 200ms 轮询。SetMute 失败或连续音频 sweep 失败会终止整个 Job。
Windows 用户态 per-process mute 无法形式化证明“绝不产生任何采样”。本扩展保证的是:音频 guard 未建立时不恢复目标,guard 失效时 fail closed。
临时文件
%LOCALAPPDATA%\Temp\pi-silent-gui\<session_id>\
session.json
cap_*.pngsilent_kill 全成功后永久删除该 session 目录;显式指定到其他位置的证据截图不会随 session 删除。
测试
npm run test:unit
npm run test:integration
npm run test:smoke
npm testunit:capture/window/process 单元测试和 TS 伪造响应 fail-closed;integration:后端 CLI、故障注入、扩展 schema、PNG、有界输出、恢复登记和 abort 清理;smoke:真实 broker/Job 生命周期,以及 WinVer 的GetDlgItem(IDOK)child 点击和HTCLOSE非客户区关闭。
发布通过 prepublishOnly 强制运行全量测试;内部测试源码不进入 npm 包。
已知限制
- 部分 DirectX、OpenGL、Vulkan、Chromium GPU 或受保护窗口不支持
PrintWindow; - DirectInput /
GetAsyncKeyState程序可能不响应窗口消息; - 目标显式使用 Job breakaway 或系统级服务代理时可能超出当前 Job;
- 高完整性或反作弊程序可能拒绝窗口消息;
- 本扩展不是通用 computer-use,也不提供 OCR 或 UIA。
