Grafik: km/h radbröts i aktivitetslistan, trenddiagrammet startade på tomma framtidsdagar, "1 reps"; tools/waydroid-ui.py
All checks were successful
release / build-release (push) Successful in 4m22s
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
This commit is contained in:
63
tools/waydroid-ui.py
Normal file
63
tools/waydroid-ui.py
Normal file
@@ -0,0 +1,63 @@
|
||||
#!/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]()
|
||||
Reference in New Issue
Block a user