--- name: virtuoso-gui-debug description: Replayable Virtuoso GUI debugging via strict JSON DSL with fake, live (vcli), and local (xdotool) executors — unified skill covering remote vcli-driven and direct local X11 GUI automation allowed-tools: Bash(python3 *) Read
Virtuoso GUI Debug Skill
Purpose
This skill provides deterministic, replayable Virtuoso GUI debugging via a strict JSON DSL. It parses and validates scenarios, executes them through one of three executors, and writes machine-readable evidence files.
Three execution engines:
--executor fake— offline-only, deterministic, for regression tests and automation logic verification. No subprocess side effects beyondpython3itself.--executor live— drives the realvcliCLI through a fixed-argv command runner (local or SSH). All GUI input goes throughvcli window action-x11, which re-validates window identity server-side on every action.--executor local— directxdotoolexecution on a local X11 DISPLAY. Binds the target window by PID (or explicit--window-id). SupportsSCROLL(xdotool buttons 4/5/6/7). No vcli binary or session required. Live mode also supportsSCROLLviavcli window action-x11 --operation scroll --text direction[:count].
When to Use
- Replaying a validated GUI-debug scenario for regression testing (fake)
- Verifying GUI automation logic without a live Virtuoso environment (fake)
- Executing an already-validated scenario against a real Virtuoso session via vcli (live)
- Direct local X11 automation when vcli is unavailable or scroll/wheel input is needed (local)
- Generating deterministic audit trails for agentic GUI operations
- Quick manual GUI inspection via
scripts/xdotool_cli.py(env/state/find/shot/click/type/key/drag/scroll/wait/smoke)
Prerequisites
Each scenario requires explicit binding of:
| Parameter | Description |
|---|---|
session_id |
Unique session identifier (non-empty string, e.g. dean-user1-34929) |
pid |
Positive integer process ID |
display |
Valid DISPLAY string (e.g., :0 or :1.0) |
cellview |
Target cellView in lib/cell/view format |
Live mode additionally requires: --session (must equal the scenario's session_id), --vcli PATH (the vcli binary on the Virtuoso host), and --output DIR (a fresh output directory). --ssh-host HOST is optional; when given, vcli runs over SSH with a safely-quoted fixed argv.
Local mode requires: xdotool on PATH, DISPLAY reachable, and --output DIR. --window-id WID optionally overrides PID-based window discovery. ImageMagick import is required for screenshots.
Auto-Discovery (SSH Remote)
自动发现 Virtuoso 的 DISPLAY 和 PID:
# 方法1: 从 daemon log 直接获取
ssh ubuntu-docker "tail /tmp/virtuoso-daemon.log"
# 方法2: 查找 virtuoso 进程并获取 DISPLAY
ssh ubuntu-docker "ps aux | grep virtuoso | grep -v grep | awk '{print \$2}' | head -1"
ssh ubuntu-docker "strings /proc/<PID>/environ | grep DISPLAY"
# 方法3: 从 daemon 获取当前会话端口
ssh ubuntu-docker "cat /tmp/virtuoso-daemon.log | grep PORT"
快速发现脚本 (在 skill-dev 目录执行):
./scripts/vssh.sh --discover
典型结果:
- PID:
12784 - DISPLAY:
:5.0 - Session ID:
dean-user1-<PORT>
SSH Connection Etiquette
Rapid-fire ssh invocations from automation (one connection per command) can trip the remote
sshd's connection protection (MaxStartups / Fail2ban), which shows up as:
ssh_exchange_identification: Connection closed by remote host
This is a client-side connection-frequency issue, NOT a vcli bug. vcli talks to the daemon
over TCP (the session port from vcli session list), not SSH, so vcli never contributes to
connection throttling.
Mitigation:
- Batch commands: pack multiple operations into one ssh call (e.g. one base64-encoded script) instead of one ssh per command.
- Reuse connections: for many sequential calls use SSH ControlMaster, e.g.
ssh -o ControlMaster=auto -o ControlPath=/tmp/vcli-ssh-%r@%h:%p .... - Back off on rejection: after
Connection closed by remote host, wait 60–120s before retrying; the throttle is temporary. - Prefer one round-trip: for complex commands, base64-encode the script to avoid quoting
issues:
echo <b64> | base64 -d | bash. - Don't misread the symptom: the remote session (daemon/vcli) keeps working during the throttle; only the ssh control channel is refused.
vcli GUI Debug 快速指南
一、连接 Session
# 列出所有 session
VCLI_CAPABILITY=admin VB_PORT=XXXXX VB_REMOTE_HOST=localhost vcli session list
# 查看 session 详情
VCLI_CAPABILITY=admin VB_PORT=XXXXX VB_REMOTE_HOST=localhost vcli session show dean-user1-XXXXX
关键字段:
alive: true— session 存活pid: 0— 旧 bridge 元数据,需通过窗口发现回退
二、发现 DISPLAY(云电脑关键!)
⚠️ vcli 的 --display :0 经常不对!云电脑上 Virtuoso 可能运行在其他 DISPLAY:
# 方法1:查看 X11 socket 文件
ssh ubuntu-docker "ls /tmp/.X11-unix/"
# 输出 X99 → DISPLAY=:99
# 方法2:查看 Virtuoso 进程
ssh ubuntu-docker "ps aux | grep virtuoso | grep -v grep"
# 方法3:逐个尝试(常见 :0, :1, :99)
vcli window list-windows-x11 --display :99 --session dean-user1-XXXXX
三、发现窗口
# 列出指定 DISPLAY 上的所有窗口
vcli window list-windows-x11 --display :99 --session dean-user1-XXXXX
输出字段说明:
{
"window_id": "0x3000000", // ← 操作时用这个字段(不是 id)
"pid": 393027, // 进程 ID
"title": "VCLI_XDOTOOL_TEST",
"geometry": {"x":960,"y":446,"w":810,"h":634},
"visible": true
}
快速筛选:
vcli window list-windows-x11 --display :99 --session dean-user1-XXXXX | python3 -c "
import json,sys
for w in json.load(sys.stdin)['windows']:
print(w['window_id'], w['pid'], w['title'][:40])
"
四、执行 GUI 操作
# 通用格式(--direct 跳过 helper 上传,快 5 倍)
vcli window action-x11 \
--window-id 0x3000000 \
--display :99 \
--session dean-user1-XXXXX \
--pid 393027 \
--operation <OP> \
--direct
常用操作:
| 操作 | 额外参数 | 示例 |
|---|---|---|
| activate | 无 | 激活窗口 |
| click-rel | --x --y | 相对坐标点击 |
| click-abs | --x --y | 绝对坐标点击 |
| double-click | --x --y | 双击 |
| key | --text Escape | 发送按键 |
| type | --text "hello" | 输入文本 |
Usage
IMPORTANT: Always validate before running:
python3 scripts/gui_runner.py validate SCENARIO
Run with fake executor (offline):
python3 scripts/gui_runner.py run SCENARIO --output DIR --executor fake
Run with live executor (real vcli):
python3 scripts/gui_runner.py run SCENARIO --output DIR \
--executor live --session dean-user1-34929 \
--vcli /usr/local/bin/vcli [--ssh-host compute-eda-42]
Run with local executor (direct xdotool):
python3 scripts/gui_runner.py run SCENARIO --output DIR \
--executor local [--window-id 0x3000006]
Quick manual GUI inspection (standalone xdotool CLI):
python3 scripts/xdotool_cli.py state
python3 scripts/xdotool_cli.py find --name "Library Manager"
python3 scripts/xdotool_cli.py click --x 100 --y 50
python3 scripts/xdotool_cli.py scroll --direction down --count 5
Live-Mode Contract (fail-closed rules)
Before any GUI input is sent, precheck verifies in order:
- the session exists in
vcli session listand its bridge port matches the session id's trailing number; - the session PID is positive — a zero PID (old bridge metadata) falls back to the scenario PID via window discovery, and is rejected if no unique window binds to it;
- the DISPLAY reported by the X server matches the scenario exactly;
- exactly one window is bound to the PID on that DISPLAY — zero or multiple matches abort;
- an exclusive lock on the DISPLAY (lock file under
~/.cache/virtuoso_bridge/x11-locks/) is acquired and held for the whole run.
Every GUI action (KEY, TYPE, CLICK_REL, CLICK_ABS, DOUBLE_CLICK, DRAG_REL, WINDOW_ACTIVATE, MINIMIZE, MAXIMIZE, CLOSE, SCROLL) maps to a fixed vcli window action-x11 argv carrying the resolved window id, PID, and DISPLAY. --direct is enabled by default (~5x faster, skips helper upload/env resolution/list-windows); use --no-direct for full server-side re-validation. --pid is optional since v1.3.1 (windows without _NET_WM_PID are reachable). verify prefers database-first predicates via vcli; the ciw_eval predicate executes SKILL via vcli skill exec and compares output. recover executes only rollback operations that pass scenario validation.
Typed input text never appears in error payloads or logs — it is replaced by text_length markers.
Failures close the run: there is no fallback to "first title-matched window", root-window coordinates, or unbound xdotool calls.
Local-Mode Contract
Before any GUI input is sent, precheck verifies:
xdotoolis on PATH;- the scenario's
DISPLAYis reachable (xdotool getdisplaygeometry); - a visible window is bound to the scenario PID — or the explicit
--window-idis used.
Actions are sent directly via xdotool with the bound window activated first. SCROLL maps to xdotool mouse buttons 4 (up), 5 (down), 6 (left), 7 (right). Screenshots use ImageMagick import -window <id>.
Output Files
Each run writes to the caller-specified output directory:
| File | Description |
|---|---|
task.json |
Validated scenario snapshot |
agent-actions.jsonl |
Append-only event log |
summary.json |
Final pass/fail with error details |
baseline.png |
Baseline screenshot (local mode) |
window_<id>.png |
Screenshots (live/local mode) |
Allowed Operations
Only these operations are permitted:
VCLI_LOAD— load a SKILL file viavcli skill load(supportsskillpp: truefor SKILL++ mode). Executable by live executor.VCLI_CALL— accepted by the schema; not executable by live or local executors (useCIW_INPUTfor ad-hoc SKILL evaluation).WINDOW_WAIT— poll window state until the requested condition or timeoutWINDOW_ACTIVATE— activate windowWINDOW_DISCOVER— discover/filter windows (title/class/pid filters)DISMISS_DIALOG— dismiss a dialog (vcli dismiss-dialog / xdotool Escape)CLOSE— close a windowKEY— send key eventTYPE— type textCLICK_REL— relative click (window-relative coordinates)CLICK_ABS— absolute click (screen coordinates)DOUBLE_CLICK— double-click (window-relative coordinates)DRAG_REL— relative drag (window-relative vector)SCROLL— scroll wheel at window-relative position (directions: up/down/left/right, optional count 1-100; live mode via vcli scroll, local mode via xdotool buttons 4/5/6/7)MINIMIZE— minimize/iconify the windowMAXIMIZE— maximize the window (requires xdotool ≥ 3.20210804.1; clear error on older versions)CIW_INPUT— type a SKILL expression into the CIW input line and press Return (encapsulates activate→click input line→clear→type→Return)SCREENSHOT— capture screenshotVERIFY— verify state (predicates: window_exists, state_matches, title_matches, geometry_matches, ciw_eval)RECOVER— recovery action (auto-dismiss for KEY/TYPE/CLICK_REL when no rollback)
Constraints
- Unknown fields are REJECTED (strict schema enforcement)
- Timeouts must be 1–300 seconds
- Retries must be 0 or 1
- Every action requires a verifier
- Fake executor performs no shell, vcli, X11, xdotool, or live process execution
- Live executor only runs the fixed vcli argv through the injected command runner — never ssh/xdotool/xprop/shell directly
- Local executor calls xdotool directly but only after precheck binds a specific window
- Live runs require an explicit fresh
--outputdirectory; nothing is written outside it
GUI Operation Playbook (Multi-Method Matrix)
Every GUI operation has at least two stable, independently-verified methods. If one fails or is unreliable, fall through to the next. All methods below were validated on a real Virtuoso IC25.1 session (DISPLAY=:5.0) with the
ui_dynamic_form.ildynamic form.
Critical Environment Constraint
vcli skill exec has NO UI library — hiCreateAppForm, hiDisplayForm, hiGetFieldInfo, hiCreateRadioField are all nil in the daemon exec context. Therefore:
- GUI form creation/display MUST go through the CIW (xdotool type into the CIW input line).
- GUI interaction (clicks, typing) MUST go through xdotool or
vcli window action-x11. - Reading form state / setting field values can go through the CIW (form object access works there).
1. Window Discovery (2+ methods)
| Method | Command | Notes |
|---|---|---|
| A (recommended) | vcli window list-windows-x11 --display :5.0 --format json |
Returns window_id (hex), pid, title, geometry. Server-side validated. |
| B | xdotool search --name "Layer Replace" |
Returns decimal window id (e.g. 39860167 = 0x26037c7). Usable directly with xdotool. |
| C | xwininfo -name "title" |
Returns geometry; useful for cross-checking absolute position. |
2. Coordinate Acquisition (2+ methods)
| Method | How | Precision |
|---|---|---|
| A (recommended): SKILL reverse-engineering | In CIW: hiGetFieldInfo(form (quote fieldName)) → returns ((x y) (w h)) in form-client-relative coordinates. Field center = (x + w/2, y + h/2). |
Exact (±0px) |
| B: pixel-level crop | import -window <wid> out.png then convert out.png -crop WxH+X+Y -resize 200% to visually confirm element position. |
Exact after 2 rounds of cross-checking |
| ❌ OCR percentage boxes | Do NOT rely on OCR's relative-percent bounding boxes — drift of ±40px observed across repeated captures of the same window. | Unreliable |
Coordinate reverse-engineering example (validated on udfLayerReplaceForm):
hiGetFieldInfo(udfLayerReplaceForm (quote oldLayer)) ; → ((5 150) (590 35))
hiGetFieldInfo(udfLayerReplaceForm (quote newLayer)) ; → ((5 187) (590 35))
hiGetFieldInfo(udfLayerReplaceForm (quote layerOp)) ; → ((5 41) (590 33))
hiGetFieldInfo(udfLayerReplaceForm (quote filePath)) ; → ((5 76) (590 35))
Field centers (form-relative): oldLayer=(300,167), newLayer=(300,204), layerOp=(300,57), filePath=(300,93).
Use these directly with xdotool mousemove --window <wid> (method 3B) — no xwininfo needed.
3. Click Operation (3 methods)
| Method | Command | When to use |
|---|---|---|
| A | vcli window action-x11 --window-id <hex> --pid <pid> --display :5.0 --operation click-rel --x <cx> --y <cy> |
Need server-side window re-validation; session-bound. |
| B (recommended, lightweight) | xdotool mousemove --window <wid> <cx> <cy>; sleep 0.3; xdotool click 1 |
Window-relative coords; no xwininfo/absolute math; works with decimal or hex wid. |
| C | xdotool mousemove <abs_x> <abs_y>; xdotool click 1 |
Only when you already have absolute coords from xwininfo. |
cx, cyare form-client-relative coordinates (from method 2A or 2B). For radio buttons inside a field, distribute evenly across the field width.
4. Text Input (3 stable methods, 1 unreliable)
| Method | How | Reliability |
|---|---|---|
| A (recommended) | Click/navigate to field, then xdotool type --clearmodifiers --delay 50 "text" |
✅ High — validated with "TABTEST", "METAL1" |
| B (coordinate-free) | xdotool key Tab (repeat to reach target field), then xdotool type |
✅ High — 4 Tabs reached Target Layer in the test form |
| C (most reliable, bypasses GUI) | In CIW: form->field->value = "text" |
✅ Highest — direct object assignment; no focus needed |
❌ vcli window action-x11 --operation type --text |
— | ❌ Unreliable — injected garbled/clipboard content instead of specified text on IC25.1. Do not use. |
5. Button Submit / Confirm (3 methods)
| Method | How | Notes |
|---|---|---|
| A | Click the button (method 3A or 3B) | Works for OK/Apply when ?buttonLayout callback is correctly bound. |
| B (recommended for dialogs) | xdotool key Return (with dialog focused) |
Equivalent to Open/OK in file dialogs; more reliable than clicking the Open button (which had coordinate-sensitivity issues). |
| C | In CIW: call the callback directly, e.g. udfApplyCB() |
Bypasses GUI entirely; useful for verifying callback logic independent of button wiring. |
6. Close / Cancel (3 methods)
| Method | How | Notes |
|---|---|---|
| A (recommended) | xdotool key Escape (with window focused) |
Dismisses most dialogs; falls back to windowclose if no response. |
| B | In CIW: hiFormCancel(form) |
Clean form dismissal; note: cannot cancel a form that is mapped (returns nil with WARNING). |
| C | Click Cancel button (method 3) | Coordinate-dependent. |
7. Modal Dialog Handling (CRITICAL)
Modal dialogs (e.g. "Choose a File" from hiDisplayFileDialog) intercept ALL input — clicks and typing on the parent form will silently fail or go to the dialog.
Detection: After any Browse/Open action, run vcli window list-windows-x11 and check for unexpected dialog windows (title contains "Choose", "Confirm", "Error", etc.).
Resolution order:
xdotool windowactivate <dialog_wid>; sleep 0.5; xdotool key Return(submit) — orEscape(cancel)- If Return doesn't close it, click the dialog's Open/Cancel button using method 3 with the dialog's window id and geometry
- Only after the dialog is gone should you resume operating the parent form
8. CIW Input (the bootstrap channel)
Since vcli skill exec cannot drive GUI, the CIW is the bootstrap for form creation and state inspection.
DSL operation CIW_INPUT encapsulates the full flow: activate → click input line → clear → type → Return. Use this in scenarios instead of manual xdotool sequences.
{"operation": "CIW_INPUT", "arguments": {"text": "load(\"/tmp/form.il\")"}}
{"operation": "CIW_INPUT", "arguments": {"text": "udfShowForm()", "delay_ms": 10, "clear_first": true}}
Manual CIW input pattern (when not using the DSL):
xdotool windowactivate <ciw_wid>
sleep 0.3
xdotool mousemove --window <ciw_wid> 400 870
xdotool click 1
sleep 0.1
xdotool key Escape # NOT ctrl+a — Virtuoso CIW does not select-all
xdotool type --clearmodifiers --delay 10 'load("/path/to/file.il")'
xdotool key Return
sleep 1
CIW input line coordinates (must be re-verified if the CIW window moves):
- The input line is at the bottom of the CIW window; compute
y = height - 20(approximate), then verify with a screenshot crop. - Always
xwininfo -id <ciw_wid>before typing — the CIW can be moved/resized by the user. - Geometry pitfall:
vcli window list-windows-x11reports window geometry including WM decorations (e.g. 730x743 for a 720x709 CIW). Using this for click-y coordinates lands outside the content area. TheCIW_INPUTDSL operation reads the precise geometry from vcli's/tmp/vcli_geom_<display>_<wid>.jsoncache (written byactivate --direct). For manual operations, usexwininfonotlist-windows.
Recommended Debug Loop
1. debug_wrapper.py validate file.il # syntax layer
2. scp file.il ubuntu-docker:/home/user1/
3. CIW input: load(".../file.il") # deploy
4. CIW input: udfShowForm() # display
5. vcli list-windows-x11 → get form wid # locate
6. CIW: hiGetFieldInfo(form (quote field)) # reverse-engineer coords
7. xdotool mousemove --window + click # interact (method 3B)
8. xdotool type / Tab+type / CIW assign # input (method 4A/B/C)
9. ImageMagick crop screenshot # visual verify
10. CIW screenshot → read callback output # behavioral verify
11. Modal dialog? → handle first (section 7)
12. Bug found → fix SKILL → repeat from 2
Performance Optimization (measured on Virtuoso IC25.1, DISPLAY=:5.0)
Latency baseline
| Operation | Latency | Notes |
|---|---|---|
vcli window action-x11 click-rel |
~1350 ms | Per call — Rust binary startup + X11 reconnect + server-side window re-resolution |
vcli window list-windows-x11 |
~940 ms | Per call — full window tree scan |
xdotool mousemove --window + click |
~10 ms | 135× faster than vcli |
xwininfo -id <wid> |
~3 ms | 313× faster than vcli list-windows |
xdotool type --delay 50 (20 chars) |
~530 ms | Default in earlier scripts |
xdotool type --delay 10 (20 chars) |
~120 ms | 4.4× faster; verified no char loss |
xdotool type --delay 5 (20 chars) |
~70 ms | Reliable for ASCII; use 10 for safety |
import -window <wid> (screenshot) |
~20 ms | Fast; occasional failure on unmapped windows |
| CIW input + exec (click+ctrl+a+type+Return) | ~425 ms | With delay=10; ~800 ms with delay=50 |
P0 — Use xdotool by default, vcli only when server-side validation is required
The vcli window action-x11 path pays a 1.3 second per-call tax because every invocation starts the Rust binary, reconnects to X11, and re-resolves the window. For rapid GUI interaction (clicks, typing, dragging), use direct xdotool with --window <wid>:
# Fast path (10 ms):
xdotool mousemove --window 0x26037c7 300 167
xdotool click 1
# Slow path (1350 ms) — only when you need the Rust side to re-validate window identity:
vcli window action-x11 --window-id 0x26037c7 --pid 114668 --display :5.0 \
--operation click-rel --x 300 --y 167
Use vcli when: (a) the window identity must be server-verified for safety, (b) you are in --executor live mode of the DSL runner, or (c) xdotool is unavailable.
P0 — --direct is now the DEFAULT in live executor (5× faster)
The live executor uses vcli window action-x11 --direct by default, skipping helper upload, env resolution, and list-windows scan. This reduces per-action latency from ~1350ms to ~260ms. Use --no-direct CLI flag only when you need full server-side window re-validation (e.g., untrusted window ids).
# Default (fast, 260ms):
python3 scripts/gui_runner.py run scenario.json --output out --executor live \
--session dean-user1-XXXXX --vcli ~/.cargo/bin/vcli --ssh-host ubuntu-docker
# Full validation (slow, 1350ms, use --no-direct):
python3 scripts/gui_runner.py run scenario.json --output out --executor live \
--session dean-user1-XXXXX --vcli ~/.cargo/bin/vcli --ssh-host ubuntu-docker \
--no-direct
--direct supports: activate, key, type, click-rel, drag-rel, scroll, close. It rejects wait (needs window-list polling) and screenshot (needs artifact fetch) with a clear config error. Verified on IC25.1: click/type/key all succeed with correct field values and callback firing.
P0 — Use action-x11-batch for consecutive operations (6.3× faster)
When you have a sequence of GUI operations (click → type → click → type...), use action-x11-batch with --direct to execute them all in one process invocation and one SSH round-trip. All xdotool commands are merged into a single shell script with per-command exit-code markers.
# batch.jsonl — one JSON action per line:
{"window_id": "0x2603839", "operation": "click-rel", "x": 116, "y": 59}
{"window_id": "0x2603839", "operation": "click-rel", "x": 300, "y": 167}
{"window_id": "0x2603839", "operation": "type", "text": "METAL1"}
{"window_id": "0x2603839", "operation": "click-rel", "x": 300, "y": 204}
{"window_id": "0x2603839", "operation": "type", "text": "METAL2"}
# Execute all 5 in one call (260ms total vs 1300ms for 5 separate --direct calls):
vcli window action-x11-batch --file batch.jsonl --direct --pid 114668 --display :5.0
Result includes per-action status, duration, and error. A single action failure does not abort the batch. Each action may override pid and display; CLI flags are defaults.
Performance comparison (6 actions, IC25.1 remote):
| Mode | Total | Per-action | Speedup |
|---|---|---|---|
6× separate action-x11 (normal) |
~7300 ms | ~1213 ms | 1× |
6× separate action-x11 --direct |
~1650 ms | ~275 ms | 4.4× |
action-x11-batch --direct (merged shell) |
260 ms | ~43 ms | 28× |
P0 — Use xwininfo for geometry, not vcli list-windows
# Fast (3 ms):
xwininfo -id 0x26037c7 | grep -E "Absolute|Width|Height"
# Slow (940 ms) — only when you need to discover windows by title/pid:
vcli window list-windows-x11 --display :5.0 --format json
Reserve list-windows-x11 for window discovery (finding a window you don't have the id for). Once you have the id, all geometry checks use xwininfo.
P1 — Reduce type delay to 10–15 ms
--delay 50 was conservative. --delay 10 is verified reliable for ASCII input into both form fields and the CIW (no dropped characters across 6 repeated rounds). Use --delay 15 for non-ASCII or complex strings.
# Before (530 ms for 20 chars):
xdotool type --clearmodifiers --delay 50 "METAL1"
# After (120 ms for 20 chars):
xdotool type --clearmodifiers --delay 10 "METAL1"
P1 — Eliminate inter-operation sleep for consecutive xdotool calls
Consecutive xdotool mousemove + click calls with zero sleep are reliable (verified: 6 rapid radio clicks all fired callbacks and changed form height correctly). Only sleep when waiting for Virtuoso to respond asynchronously:
- No sleep needed: consecutive clicks, consecutive type, mousemove→click
- Sleep / poll needed: after triggering a form redraw (radio callback changes layout), after opening a modal dialog, after CIW Return (wait for eval result)
- Prefer conditional polling over fixed sleep:
xwininfoloop waiting for height change, orvcli list-windowswaiting for dialog appearance
# Bad: fixed 800ms sleep after every click
xdotool click 1; sleep 0.8
# Good: poll for the expected state change
for i in $(seq 1 20); do
h=$(xwininfo -id $WID 2>/dev/null | grep Height | awk '{print $2}')
[ "$h" = "250" ] && break
sleep 0.05
done
P2 — vcli-side optimizations (Rust changes, all implemented)
- ✅
--directflag (implemented, commit 513f929): skips helper upload, env resolution, and list-windows scan. 4.7× faster (1213ms → 260ms). Use when vcli is required but window identity is already known. - ✅
action-x11-batch(implemented, commit bab1809 + 10c88dc): JSONL batch mode with merged shell execution. 6 actions in 260ms (28× vs normal mode). All xdotool commands merged into one SSH round-trip with per-command exit-code markers. - ✅ Geometry precheck in
--directmode (PR #68): before sending aclick-rel/drag-rel/scrollwith coordinates, runsxwininfoto verify the window is not zero-sized (minimized/unmapped) and the coordinates are within bounds. Out-of-bounds coordinates are rejected with exit code 2 and a clear error message ("coordinates (x, y) out of bounds for window size WxH"). Prevents sending clicks to stale coordinates after a window moves/resizes. - ✅ Batch non-direct shared list-windows (PR #68): in non-direct batch mode, the helper upload, env resolution, and list-windows scan are done once per unique DISPLAY and reused across all actions in the batch. 3 actions in 1527ms (per-action only 134–271ms vs ~940ms each without sharing).
- ✅ Geometry file cache (PR #68): direct-mode writes a
/tmp/vcli_geom_<display>_<wid>.jsoncache (cross-platform viastd::env::temp_dir()) with 500ms TTL. Used for zero-size fast-reject (avoids xwininfo round-trip on repeated calls to a minimized window). Coordinate bounds checking always uses fresh xwininfo. - Daemon mode (deferred): persistent
vcli gui-daemonholding X11 connection over a local socket. Batch mode already covers the main use case (consecutive operations in one process); daemon's marginal gain is small.
P2 — Coordinate caching
hiGetFieldInfo reverse-engineering costs one CIW round-trip (~425ms). The live executor provides a coordinate cache API:
executor.cache_coords("myForm", "oldLayer", x=5, y=150, w=590, h=35)
coords = executor.get_cached_coords("myForm", "oldLayer") # → {"x":5, "y":150, "w":590, "h":35}
executor.invalidate_coords("myForm") # call after window resize/layout change
Cache the resulting ((x y) (w h)) per field for the lifetime of the form; only re-query if xwininfo detects the window was resized or a radio callback changed the layout.
P2 — Modal dialog auto-detection (default ON)
After any GUI action that may spawn a dialog (CLICK_REL, CLICK_ABS, DOUBLE_CLICK, KEY, TYPE, CIW_INPUT), the live executor automatically scans for new dialog windows (titles containing Choose/Confirm/Error/Warning/Dialog/Message/Alert/Question) and dismisses them via vcli dismiss-window-x11. This prevents silent failures when a Browse/Open action spawns a file chooser that intercepts all subsequent input.
Disable with --no-auto-dismiss if you need to interact with dialogs explicitly.
P2 — Verification: prefer CIW state reads over screenshot+OCR
Reading a field value via CIW (form->field->value) costs ~425ms and is deterministic. Screenshot+OCR costs ~20ms but is unreliable (±40px drift, garbled text). Use CIW reads for behavioral verification; use screenshots only for visual evidence in reports.
Virtuoso GUI API Semantics (verified on IC25.1)
Hard-won findings from testing 13 example GUI programs (ui_dynamic_form, ui_callback_patterns, ui_color_picker, ui_listbox_*, ui_multipage_form, ui_progress_*, ui_table_form, ui_toggle_combo_form, menu_demo/*). These are NOT in the Cadence docs — they were discovered by breaking things.
Form Field Access Paths
| Field type | Access pattern | Gotcha |
|---|---|---|
| Top-level field | form->fieldName->value |
Direct access works |
| Field inside tab field | form->tabField->pageName->fieldName->value |
Direct form->fieldName->value returns nil — must go through tab→page |
| ListBox field | form->listbox->value is always a list |
Read with car(), set with (list val) |
| Cyclic field | form->cyclic->value is a single string |
Not a list |
| Toggle field | form->toggle->value is t/nil list |
?choices each item must be (symbol label) list |
Widget Creation Dependencies
| Widget | Requires | Gotcha |
|---|---|---|
hiCreateLayerCyclicField |
Open cellview (geGetEditRep() non-nil) |
techGetTechFile(nil) crashes. Guard: when(rep Tech=techGetTechFile(rep) ...) |
hiCreateReportField |
None | Data is static list of lists; no dynamic update API |
hiCreateTabField |
None | Pages are symbols; fields inside need tab→page access path |
hiCreateSpinBox |
None | Arrows are ~10px, hard to click via xdotool. Prefer CIW: form->spinbox->value = n |
hiCreatePointField / hiCreatePointListField |
None | Values are (x y) lists; render as read-only text |
Modal Form Behavior
hiDisplayFormcreates a modal form that blocksvcli skill exec(30s timeout). While the form is open, allvcli skill execcalls hang until the form is dismissed.- To read form state while a modal form is open: use CIW input (xdotool type into CIW), not
vcli skill exec. Escapedoes NOT always close a form — click Cancel/OK button, or usehiFormCancel(form)via CIW.alt+F4andxdotool windowclosemay not work on Virtuoso modal forms.
Menu System
hiInsertBannerMenu((hiGetCIWindow) menu)inserts a pulldown into the CIW menu bar.hiCreateSliderMenuItemwith?subMenurenders a right-arrow (▶) indicating a submenu.hiCreateSeparatorMenuItemrenders a horizontal divider.- Menu
?callbackis a string that getseval'd on click. If the function is undefined, CIW showsundefined variable - funcName(callback DID fire). - Submenus open on hover in most cases; if not, click the parent item.
CIW Input Reliability
ctrl+adoes NOT select-all in Virtuoso form fields. UseEscapeto clear the CIW input line.ciw_evalverifier format:expectedmust be a dict, not a string. Use{"expression": "varName", "equals": "42"}or{"expression": "func()", "contains": "substring"}. A bare string"42"fails withciw_eval predicate requires expected.expression.- CIW_INPUT geometry fix (v1.3.4+): The operation now reads precise window geometry from vcli's
/tmp/vcli_geom_*.jsoncache afteractivate, instead of usinglist-windowsgeometry which includes WM decorations. Click-y is computed asheight - 20(the CIW input line). vcli skill loadtimes out on large files (>~50 lines). Use CIWload("/path/file.il")instead.- Semicolon-separated multi-expression CIW input: the second assignment may not execute. Run expressions one at a time.
lambda((x) body)fails — must belambda( (x) body)with a space afterlambda(.returnonly works insideprog()blocks, notlet()blocks. Inlet, the last expression is the implicit return.
CIW Input Boundary Conditions (verified 2026-09-11)
Long text input: No practical length limit. Tested up to 511 characters (500-char payload) — xdotool type succeeds and the command executes correctly. Type performance is ~6.7ms/char (209ms for 28 chars, 3.4s for 511 chars). The CIW input line wraps visually but accepts the full string.
SKILL string quoting (critical): Unquoted alphabetic text is parsed as a variable reference, not a string literal. myVar = abcdef fails with *Error* eval: unbound variable - abcdef. Always quote strings: myVar = "abcdef". This is correct SKILL behavior, not a bug. Numeric literals (myVar = 42) do not need quotes.
Input validation boundaries (all return clear errors, no crashes):
| Condition | Behavior |
|---|---|
Empty --text for type |
config_error: operation 'type' requires non-empty --text |
--pid 0 |
config_error: PID must be positive when supplied |
Invalid window ID (not-a-window) |
status: failure |
Wrong DISPLAY (:99.0) |
status: failure |
| Negative coordinates | config_error: coordinates (-10,-10) out of bounds for window size WxH |
Zero coordinates (0,0) |
Success (inside window) |
Boundary coordinates (W-1,H-1) |
Success (inside window) |
| Empty batch file | config_error: batch file contains no actions |
| Invalid JSON in batch | config_error: invalid JSON on line N |
Minimized window: minimize succeeds; subsequent click-rel on the minimized window also returns success (events are delivered to the unmapped window). activate restores the window and input resumes normally.
gui_runner.py scenario validation rejects invalid input at parse time:
- Missing required fields (
session_id,pid,display,cellview,steps) - Version not exactly
"1.0" - Unknown operations (lists all allowed operations in the error)
- Empty
stepsarray
CIW Input Stability Under Load (verified 50 cycles, 2026-09-11)
Critical finding: High-frequency CIW input (< 1s per full cycle) can cause the CIW's X11 event queue to overflow, resulting in complete keyboard input failure while the TCP channel (vcli skill exec) remains fully functional. The CIW window stays active and mapped, but all xdotool type/key and vcli action-x11 type/key operations silently produce no input. This state is not recoverable via X11 operations — requires restarting the Virtuoso process.
Verified stable cycle (50/50 success, 0 failures, ~4 min total):
| Step | Operation | Minimum delay |
|---|---|---|
| 1 | click-rel on input line (y = height - 20) |
0.5s |
| 2 | key Escape to clear |
0.5s |
| 3 | type the command |
1.0s |
| 4 | key Return to execute |
1.5s |
| 5 | skill exec verify |
— |
Total: ~3.5s per cycle. Do not reduce below this for automated loops.
What triggers the failure:
- 20 rapid cycles with delays 0.1s/0.1s/0.3s/0.5s (~1s/cycle) → CIW keyboard input dies at some point during the loop
- The failure is not caused by
MINIMIZE/restore — verified: minimize → activate → type still works perfectly in a fresh session - The failure is not caused by input line pollution — verified: fresh session with clean input line still fails under rapid cycling
Recovery: If CIW keyboard input stops responding but vcli skill exec still works, the CIW X11 event queue has overflowed. Pause for 3-5 seconds with no X11 operations — the queue drains and input recovers automatically (verified: 3s pause → full recovery). Only if recovery fails after 10s should you restart the Virtuoso process. Do not waste time trying X11-based recovery during the overflow window.
Stress Test Findings (verified 2026-09-11)
Concurrency: Multiple vcli processes can run concurrently against the same session without conflicts:
- 5 parallel
skill execcalls: all succeed, daemon processes each in ~10ms - 3 parallel X11 operations + 3 parallel TCP operations: all 6 succeed
- Variable assignments persist correctly across concurrent calls (the earlier "concurrent failure" was a test-script sed parsing bug — output values are quoted strings, not bare numbers)
High-frequency operations (no delay between calls):
- 100 rapid
click-rel: 100/100 success, ~136ms/click - 100 rapid
key Escape: 100/100 success, ~24ms/key - Clicks and keys alone do NOT trigger X11 event queue overflow — only the full type+Return cycle does
- Special keys all work:
ctrl+a,ctrl+c,ctrl+v,alt+Tab,shift+a,F1,F5,Escape,Return,BackSpace
Batch efficiency: action-x11-batch with 50 mixed operations completes in ~5ms (10000 ops/s). Use batch for non-interactive operation sequences.
Resource stability after stress (100 clicks + 100 keys + 20 rapid type cycles + concurrent calls):
- Virtuoso RSS: +1MB (747→748MB)
- No vcli process leaks (0 lingering)
- sshd count stable (5)
- CIW fully responsive after 3s recovery pause
What NOT to do:
- Do not run type+Return cycles faster than ~3.5s/cycle in automated loops
- Do not parse
vcli skill execoutput withsed 's/.*"output": \([0-9]*\).*/\1/'— values are JSON strings ("123"), not bare numbers. Usejq -r .outputor a proper JSON parser.
Screensaver caveat: On Xfce/Xvnc, xfce4-screensaver may cover the full screen (1853x1011), causing xdotool search --onlyvisible to return empty while the CIW window is still mapped underneath. Disable with xset s off && xset -dpms && killall xfce4-screensaver before automated GUI testing.
New session CIW default size: A freshly started Virtuoso CIW is typically 600x200. Resize with xdotool windowsize <wid> 1200 800 before testing.
xdotool search caveat on Xvnc: In some Xvnc configurations, xdotool search --name ".*" returns empty even when windows exist, while xdotool getactivewindow works. Use Python Xlib window-tree traversal as a fallback to discover the CIW window ID.
Remote GUI Program Debugging (verified 2026-09-11)
When debugging external SKILL GUI programs (e.g. from a skill library repo) on a remote Virtuoso session:
Loading and launching:
- Upload the
.ilfile to the remote machine (e.g./tmp/program.il) - Load via CIW input:
load("/tmp/program.il")— do NOT usevcli skill load(times out on files >50 lines) - Launch the form via CIW:
procedureName()— the form appears as a new top-level window hiDisplayFormforms do NOT block CIW input in IC25.1 — you can continue typing while the form is open
Window discovery on Xvnc (xdotool search returns empty):
Use Python Xlib window-tree traversal filtering by WM_CLASS containing "virtuoso", width>50, height>20.
Verified GUI programs (from skill library examples):
| Program | Launch function | Form | Widgets tested | Result |
|---|---|---|---|---|
ui_color_picker.il |
ucpCreateColorPicker("title" "red" nil) |
Pick Color (600x72) | 12 radio buttons, OK/Cancel/Apply | Radio selection changes form field value; Apply triggers callback |
ui_dynamic_form.il |
udfShowLayerReplaceForm() |
…(truncated)