All checks were successful
release / build-release (push) Successful in 4m22s
Klickrunda i Waydroid (telefonlayout 411×891 dp) genom alla flikar, inställningar, mål, aktiviteter, passdetalj, PB-sök, detaljark, våg och om-sidan. - Aktiviteter: "km/h" bröts till "km/" + "h" när kcal-kolumnen trängde ihop raden – word joiner (U+2060) efter snedstrecket håller enheten ihop. - Statistik → Volym per dag/vecka: LazyRow scrollade till sista stapeln, dvs. månadens tomma framtidsdagar. Nu: senaste stapel med volym, annars dagens stapel, annars längst till höger. - LiftStatusSheet: "Topplista · 1 reps" → "1 rep". - tools/waydroid-ui.py: adb-hjälpare (dump/tap/shot/swipe/text/key) för klickrundor i Waydroid; README beskriver flödet + wm size/density-tricket. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NGT3xpdm51McmSrJcHnrzq
64 lines
3.7 KiB
Python
64 lines
3.7 KiB
Python
#!/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 <dump|tap <text>|shot <namn> [skala]|swipe up|down [andel]|text <str>|key <KEYCODE>|tapxy <x> <y>>
|
|
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'<node[^>]*?>', 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]()
|