"""FlexUI DevTools watch — DevServer 状态监视 + 调试 URL 提示。
配合 scripts/debug-server.sh 使用:debug-server.sh 启动 DevServer 后前台运行本脚本;
本脚本阻塞运行(轮询 DevServer /json),按 Ctrl+C 退出后由 debug-server.sh 关闭 DevServer。
功能:
1. 设备与 rport 转发检测(hdc):缺失 rport 规则时自动添加
2. 持续监听 DevServer /json,新 target 出现时提示最新调试 URL
3. 周期显示 DevServer / 设备 / target 状态
"""
import argparse
import json
import subprocess
import sys
import time
import urllib.request
try:
sys.stdout.reconfigure(encoding="utf-8")
sys.stderr.reconfigure(encoding="utf-8")
except Exception:
pass
C_RESET = "\033[0m"
C_GREEN = "\033[32m"
C_YELLOW = "\033[33m"
C_CYAN = "\033[36m"
C_RED = "\033[31m"
C_BOLD = "\033[1m"
def color(text, code, enable=True):
return f"{code}{text}{C_RESET}" if enable else text
def run(cmd, timeout=10):
try:
r = subprocess.run(cmd, capture_output=True, text=True, timeout=timeout)
out = (r.stdout or "").strip()
err = (r.stderr or "").strip()
return out + ("\n" + err if err else "")
except Exception as e:
return f"<{e}>"
def fetch_json(url, timeout=3):
try:
with urllib.request.urlopen(url, timeout=timeout) as resp:
return json.loads(resp.read().decode("utf-8"))
except Exception:
return None
def ensure_rport(port, verbose=True):
"""检测 hdc rport 规则(设备 localhost:port → PC port),缺失则添加。
返回 (device, rule_ok)。device 为空表示无设备连接。
"""
targets = run(["hdc", "list", "targets"]).strip()
device = targets.splitlines()[0].strip() if targets.strip() else ""
if not device:
return "", False
fports = run(["hdc", "fport", "ls"])
rule = f"tcp:{port} tcp:{port}"
if "[Reverse]" in fports and rule in fports:
return device, True
out = run(["hdc", "rport", f"tcp:{port}", f"tcp:{port}"])
return device, ("result:OK" in out)
def main():
ap = argparse.ArgumentParser(description="FlexUI DevTools watch")
ap.add_argument("--port", type=int, default=38989)
ap.add_argument("--host", default="localhost")
ap.add_argument("--interval", type=float, default=2.0)
args = ap.parse_args()
base = f"http://{args.host}:{args.port}"
json_url = f"{base}/json"
version_url = f"{base}/json/version"
color_on = sys.stdout.isatty()
def p(text, code=C_RESET):
print(color(text, code, color_on), flush=True)
p(f"===== FlexUI DevTools watch — {base} =====", C_BOLD + C_CYAN)
version = fetch_json(version_url)
if version is None:
p(f"[FAIL] DevServer 未响应 {version_url} —— 请先通过 debug-server.sh 启动 DevServer", C_RED)
sys.exit(1)
browser = version.get("Browser") or version.get("browser") or ""
p(f"[OK] DevServer 就绪" + (f" ({browser})" if browser else ""), C_GREEN)
dev, ok = ensure_rport(args.port)
if not dev:
p("[WARN] 未检测到 hdc 设备(hdc list targets 为空)—— 请连接设备后重试", C_YELLOW)
else:
p(f"[OK] hdc 设备: {dev}", C_GREEN)
if ok:
p(f"[OK] rport 转发就绪: tcp:{args.port} tcp:{args.port} [Reverse]", C_GREEN)
else:
p(f"[WARN] rport 转发添加结果未知,请检查 'hdc fport ls'", C_YELLOW)
print("")
p("等待设备 App 连接…… 请重启 App(force-stop 后重新启动)以触发 DevServer 连接。", C_BOLD)
p("看到新的调试 URL 后,在浏览器打开即可进入 DevTools。按 Ctrl+C 退出(会同时关闭 DevServer)。")
print("")
seen = set()
last_summary = 0.0
last_summary_text = None
try:
while True:
targets = fetch_json(json_url) or []
current = {t.get("clientId") or t.get("id") for t in targets}
new_ids = current - seen
for tid in new_ids:
for t in targets:
if (t.get("clientId") or t.get("id")) == tid:
title = t.get("title", "?")
fe_url = t.get("devtoolsFrontendUrl", "")
ws_url = t.get("webSocketDebuggerUrl", "")
print("")
p(f"*** 新的调试 target: {title} ***", C_BOLD + C_GREEN)
if fe_url:
p(f" DevTools 打开: {fe_url}", C_CYAN)
if ws_url:
p(f" WS 直连: {ws_url}")
print("")
break
seen |= current
now = time.time()
if now - last_summary >= 8.0:
last_summary = now
dev2, ok2 = ensure_rport(args.port, verbose=False)
summary = f"[状态] targets={len(targets)} 设备={dev2 or '无'} rport={'OK' if ok2 else '?'}"
if summary != last_summary_text:
p(summary, C_YELLOW)
last_summary_text = summary
time.sleep(args.interval)
except KeyboardInterrupt:
print("")
p("收到退出信号,停止监听。DevServer 将由 debug-server.sh 关闭。", C_YELLOW)
if __name__ == "__main__":
main()