#!/usr/bin/env python3 """Liten adb-hjälpare för att styra FitnessDroid i Waydroid (eller på en telefon) från skalet. Användning: python3 tools/waydroid-ui.py |shot [skala]|swipe up|down [andel]|text |key |tapxy > Skärmdumpar hamnar i /tmp/shots/. Enhet via env ADB_SERIAL (standard Waydroid 192.168.240.112:5555). Telefonlik yta i Waydroid: adb shell wm size 720x1560 && adb shell wm density 280 (återställ: wm size reset; wm density reset). """ import re, subprocess, sys, time from PIL import Image import os D = ["adb", "-s", os.environ.get("ADB_SERIAL", "192.168.240.112:5555")] def sh(*a, **k): return subprocess.run(D + list(a), capture_output=True, text=True, **k).stdout def nodes(): sh("shell", "uiautomator", "dump", "/sdcard/ui.xml") x = sh("shell", "cat", "/sdcard/ui.xml") out = [] for m in re.finditer(r']*?>', x): n = m.group(0) g = lambda k: (re.search(k + r'="([^"]*)"', n) or [None, ""])[1] b = re.search(r'bounds="\[(\d+),(\d+)\]\[(\d+),(\d+)\]"', n) if not b: continue out.append(dict(cls=g("class").split(".")[-1], text=g("text").replace("&", "&").replace("🏆", "🏆"), desc=g("content-desc"), b=tuple(map(int, b.groups())), enabled=g("enabled"), checked=g("checked"), selected=g("selected"))) return out def win(ns=None): ns = ns or nodes() for n in ns: if n["desc"].startswith("Caption bar of FitnessDroid"): return n["b"] return (0, 0, 2560, 1408) def center(b): return (b[0] + b[2]) // 2, (b[1] + b[3]) // 2 def cmd_dump(): ns = nodes(); w = win(ns) print("WIN", w) for n in ns: if n["text"] or n["desc"] or n["cls"] in ("EditText", "Switch", "CheckBox", "RadioButton"): flags = "".join(f for f, v in (("E", n["enabled"] == "false"), ("✓", n["checked"] == "true"), ("S", n["selected"] == "true")) if v) print(f'{n["cls"]:10} {n["text"][:90]!r:95} {n["desc"][:30]!r:34} {n["b"]} {flags}') def cmd_tap(label, idx=0): ns = nodes(); hits = [n for n in ns if n["text"] == label or n["desc"] == label] if not hits: hits = [n for n in ns if label.lower() in (n["text"] + n["desc"]).lower()] if not hits: print("NOTFOUND", label); return 1 x, y = center(hits[int(idx)]["b"]); sh("shell", "input", "tap", str(x), str(y)); print("TAP", label, x, y) def cmd_shot(name, scale=0.5): scale = float(scale) ns = nodes(); w = win(ns) os.makedirs("/tmp/shots", exist_ok=True) png = subprocess.run(D + ["exec-out", "screencap", "-p"], capture_output=True).stdout open("/tmp/shots/_full.png", "wb").write(png) im = Image.open("/tmp/shots/_full.png").crop(w) if scale != 1: im = im.resize((int(im.width * scale), int(im.height * scale))) p = f"/tmp/shots/{name}.png"; im.save(p); print("SHOT", p, im.size, "win", w) def cmd_swipe(direction, frac=0.6): frac = float(frac) w = win(); x = w[0] + int((w[2] - w[0]) * 0.3); h = w[3] - w[1] y1, y2 = (w[1] + int(h * 0.85), w[1] + int(h * (0.85 - frac))) if direction == "up" else (w[1] + int(h * 0.25), w[1] + int(h * (0.25 + frac))) sh("shell", "input", "swipe", str(x), str(y1), str(x), str(y2), "500"); print("SWIPE", direction) def cmd_text(s): sh("shell", "input", "text", s.replace(" ", "%s")); print("TEXT", s) def cmd_key(k): sh("shell", "input", "keyevent", k); print("KEY", k) def cmd_tapxy(x, y): sh("shell", "input", "tap", x, y); print("TAPXY", x, y) if __name__ == "__main__": a = sys.argv[1:]; c = a[0] {"dump": lambda: cmd_dump(), "tap": lambda: cmd_tap(*a[1:]), "shot": lambda: cmd_shot(*a[1:]), "swipe": lambda: cmd_swipe(*a[1:]), "text": lambda: cmd_text(a[1]), "key": lambda: cmd_key(a[1]), "tapxy": lambda: cmd_tapxy(a[1], a[2])}[c]()