diff --git a/server/mobile.py b/server/mobile.py index 478b38b2..68154b45 100644 --- a/server/mobile.py +++ b/server/mobile.py @@ -4,11 +4,8 @@ import os import shutil import subprocess -import base64 import json from typing import Literal, Optional -import asyncio -import socket import xml.etree.ElementTree as ET import zipfile import plistlib @@ -125,26 +122,91 @@ def get_ios_devices(): return [] -@router.get("/inspect") -def inspect(device_serial: str | None = None): - """Get the Mobile DOM and screenshot.""" +def run_adb_command_bytes(cmd: str, timeout: int = 30) -> bytes: + """ + Run an adb command and return stdout as raw bytes. + + - cmd: full command string (example: 'adb -s SERIAL exec-out screencap -p') + - timeout: seconds + + Raises RuntimeError if adb fails. + """ try: - # Capture UI and screenshot - capture_ui_dump(device_serial=device_serial) - capture_screenshot(device_serial=device_serial) + # Use shell=True ONLY if you are passing a full string with quotes + # (like the sh -c command). For normal adb commands, shell=False is better. + # + # Since your combined command uses quotes heavily, we keep shell=True. + p = subprocess.run( + cmd, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + timeout=timeout, + shell=True, + ) - # Read XML file - with open(UI_XML_PATH, "r") as xml_file: - xml_content = xml_file.read() + if p.returncode != 0: + err = p.stderr.decode("utf-8", errors="replace").strip() + raise RuntimeError(f"ADB command failed ({p.returncode}): {err}") - # Read and encode screenshot - with open(SCREENSHOT_PATH, "rb") as img_file: - screenshot_bytes = img_file.read() - screenshot_base64 = base64.b64encode(screenshot_bytes).decode("utf-8") + return p.stdout + + except subprocess.TimeoutExpired: + raise RuntimeError(f"ADB command timed out after {timeout}s: {cmd}") + + +def fetch_xml_and_screenshot(device_serial: str | None = None) -> tuple[str, bytes]: + """ + Single-function fetch. Primary path uses ONE adb exec-out command to capture + UI XML + PNG (base64) in a single stream. Falls back (still inside this function) + if markers or outputs are invalid. + """ + device_flag = f"-s {device_serial}" if device_serial else "" + + SPLIT = "__ZEUZ_SPLIT__" + END = "__ZEUZ_END__" + + cmd = ( + f'{ADB_PATH} {device_flag} exec-out sh -c ' + f'"uiautomator dump /dev/tty; ' + f'echo {SPLIT}; ' + f'screencap -p | base64; ' + f'echo {END}"' + ).strip() + + out = run_adb_command_bytes(cmd) + + text = out.decode("utf-8", errors="replace") + if SPLIT in text and END in text: + xml_part, rest = text.split(SPLIT, 1) + b64_part = rest.split(END, 1)[0] + + # Extract real XML starting from