chore: import upstream snapshot with attribution

This commit is contained in:
wehub-resource-sync
2026-07-13 13:04:19 +08:00
commit 46c3330e28
212 changed files with 65282 additions and 0 deletions
+86
View File
@@ -0,0 +1,86 @@
# adb_ui.py - 一键dump+解析Android UI (u2优先,原生fallback)
# 要先看 computer_use.mddump配合ui_detect,微信/支付宝小程序常需detect补盲
# 搜索框一般直接输入拼音/首字母即可,别硬啃 adb 中文输入
# u2 (uiautomator2) 不受idle限制,适合动画密集app(美团等)
# 弹窗检测: ui(clickable_only=True, raw=True) 找全屏FrameLayout+底部小ImageView(关闭X)
# 已知包名: 美团外卖=com.sankuai.meituan.takeoutnew 淘宝=com.taobao.taobao
import subprocess, xml.etree.ElementTree as ET, os, re, shutil
ADB = shutil.which("adb") or "adb"
LOCAL_XML = os.path.join(os.path.dirname(os.path.abspath(__file__)), "ui_mt.xml")
def _dump_u2():
"""用uiautomator2 dump,不受idle限制"""
try:
import uiautomator2 as u2
d = u2.connect()
xml_str = d.dump_hierarchy()
if xml_str and len(xml_str) > 100: return xml_str
except Exception as e:
print(f"[u2 fallback] {e}")
return None
def _dump_native():
"""原生uiautomator dump(需idle状态)"""
subprocess.run([ADB, "shell", "rm", "-f", "/sdcard/ui.xml"], capture_output=True)
r = subprocess.run([ADB, "shell", "uiautomator", "dump", "--compressed", "/sdcard/ui.xml"],
capture_output=True, text=True, timeout=15)
if "dumped" not in r.stdout.lower() and "dumped" not in r.stderr.lower(): print(f"dump failed: {r.stdout}{r.stderr}"); return None
subprocess.run([ADB, "pull", "/sdcard/ui.xml", LOCAL_XML], capture_output=True, timeout=10)
with open(LOCAL_XML, "r", encoding="utf-8") as f:
return f.read()
def _parse_xml(xml_str, keyword=None, clickable_only=False, raw=False):
"""解析XML字符串为节点列表"""
root = ET.fromstring(xml_str)
nodes = []
for n in root.iter("node"):
pkg = n.get("package", "")
if "termux" in pkg.lower(): continue
text = n.get("text", "")
desc = n.get("content-desc", "")
bounds = n.get("bounds", "")
click = n.get("clickable") == "true"
cls = n.get("class", "").split(".")[-1]
rid = n.get("resource-id", "")
label = text or desc
if not label and not click and not raw: continue
if clickable_only and not click: continue
if keyword and keyword.lower() not in label.lower(): continue
cx, cy = 0, 0
if bounds:
m = re.findall(r'\[(\d+),(\d+)\]', bounds)
if len(m) == 2:
cx = (int(m[0][0]) + int(m[1][0])) // 2
cy = (int(m[0][1]) + int(m[1][1])) // 2
edit = cls == "EditText"
nodes.append({"text": text or desc, "click": click, "edit": edit, "cx": cx, "cy": cy, "cls": cls, "rid": rid})
return nodes
def ui(keyword=None, clickable_only=False, raw=False):
"""一键dump+解析Android UI (u2优先)
keyword: 过滤含关键词的节点
clickable_only: 只显示可点击节点
raw: 返回原始节点列表而非打印
"""
xml_str = _dump_u2() or _dump_native()
if not xml_str: print("dump failed (both u2 and native)"); return []
nodes = _parse_xml(xml_str, keyword, clickable_only, raw)
if not raw:
for n in nodes:
flag = "E" if n.get("edit") else ("Y" if n["click"] else " ")
coord = f"({n['cx']},{n['cy']})" if n['cx'] else ""
display_text = n['text']
if not display_text:
hint = n.get('rid', '').split('/')[-1] or n.get('cls', 'icon')
display_text = f"<{hint}>"
print(f"[{flag}] {display_text} {coord}")
print(f"\ntotal: {len(nodes)} nodes")
return nodes
def tap(x, y):
subprocess.run([ADB, "shell", "input", "tap", str(x), str(y)], capture_output=True)
print(f"tap({x},{y}) ok")
if __name__ == "__main__":
ui()