项目文件夹

文件
wehub-resource-sync 91e75e620b
CI: cua-driver distro-compat matrix / debian:12 (glibc 2.36) (push) Has been cancelled
CI: SPDX Headers / Check SPDX headers (warn-only) (push) Has been cancelled
CD: Docs MCP Server / build (linux/amd64) (push) Has been cancelled
CD: Docs MCP Server / build (linux/arm64) (push) Has been cancelled
CD: Docs MCP Server / merge (push) Has been cancelled
CI: cua-driver distro-compat matrix / Resolve release version (push) Has been cancelled
CI: cua-driver distro-compat matrix / fedora:41 (glibc 2.40) (push) Has been cancelled
CI: cua-driver distro-compat matrix / rockylinux:9 (glibc 2.34) (push) Has been cancelled
CI: cua-driver distro-compat matrix / ubuntu:22.04 (glibc 2.35) (push) Has been cancelled
CI: cua-driver distro-compat matrix / ubuntu:24.04 (glibc 2.39) (push) Has been cancelled
CI: cua-driver distro-compat matrix / Distro compat summary (push) Has been cancelled
CI: Rust Linux unit / Rust Linux unit and compile (push) Has been cancelled
CI: Rust Windows unit / Rust Windows unit and compile (push) Has been cancelled
CI: Nix Linux Rust source / Nix / compositor build (push) Has been cancelled
CI: Nix Linux Rust source / Nix / driver package (push) Has been cancelled
CI: Nix Linux Rust source / Nix / Rust unit tests (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:03:19 +08:00

192 行
7.4 KiB
Python

此文件含有模棱两可的 Unicode 字符
此文件含有可能会与其他字符混淆的 Unicode 字符。 如果您是想特意这样的,可以安全地忽略该警告。 使用 Escape 按钮显示他们。
"""Mobile interface — touch, gestures, and hardware keys via ADB, backed by a Transport."""
from __future__ import annotations
import asyncio
from cua_sandbox.transport.base import Transport
class Mobile:
"""Mobile (Android) touch and hardware-key control.
All touch coordinates are in screen pixels. Single-touch methods use
``input tap/swipe`` via ``adb shell``. Multi-touch gestures delegate to
the ``multitouch_gesture`` transport action, which uses ``adb root`` +
MT Protocol B ``sendevent`` for reliable injection on both local and cloud
transports.
"""
def __init__(self, transport: Transport):
self._t = transport
async def _shell(self, command: str) -> None:
await self._t.send("shell", command=command)
# ── Touch ─────────────────────────────────────────────────────────────
async def tap(self, x: int, y: int) -> None:
await self._shell(f"input tap {x} {y}")
async def long_press(self, x: int, y: int, duration_ms: int = 1000) -> None:
await self._shell(f"input swipe {x} {y} {x} {y} {duration_ms}")
async def double_tap(self, x: int, y: int, delay: float = 0.1) -> None:
await self._shell(f"input tap {x} {y}")
await asyncio.sleep(delay)
await self._shell(f"input tap {x} {y}")
async def type_text(self, text: str) -> None:
encoded = text.replace(" ", "%s")
await self._shell(f"input text '{encoded}'")
# ── Gestures ──────────────────────────────────────────────────────────
async def swipe(self, x1: int, y1: int, x2: int, y2: int, duration_ms: int = 300) -> None:
await self._shell(f"input swipe {x1} {y1} {x2} {y2} {duration_ms}")
async def scroll_up(self, x: int, y: int, distance: int = 600, duration_ms: int = 400) -> None:
await self._shell(f"input swipe {x} {y + distance} {x} {y} {duration_ms}")
async def scroll_down(
self, x: int, y: int, distance: int = 600, duration_ms: int = 400
) -> None:
await self._shell(f"input swipe {x} {y} {x} {y + distance} {duration_ms}")
async def scroll_left(
self, x: int, y: int, distance: int = 400, duration_ms: int = 300
) -> None:
await self._shell(f"input swipe {x + distance} {y} {x} {y} {duration_ms}")
async def scroll_right(
self, x: int, y: int, distance: int = 400, duration_ms: int = 300
) -> None:
await self._shell(f"input swipe {x} {y} {x + distance} {y} {duration_ms}")
async def fling(self, x1: int, y1: int, x2: int, y2: int) -> None:
await self.swipe(x1, y1, x2, y2, duration_ms=80)
async def _get_screen_size(self) -> tuple[int, int]:
"""Return ``(width, height)`` in screen pixels via ``wm size``."""
result = await self._t.send("shell", command="wm size")
output = result.get("stdout", "").strip()
for line in output.splitlines():
if "size" in line.lower():
dims = line.split(":")[-1].strip()
w, h = dims.split("x")
return int(w), int(h)
return 32768, 32768 # fallback: 1:1 mapping
async def gesture(
self,
*finger_paths: tuple[int, int],
duration_ms: int = 400,
steps: int = 0,
) -> None:
"""Inject an arbitrary N-finger gesture via MT Protocol B ``sendevent``.
Each positional argument is a sequence of ``(x, y)`` waypoints for one
finger. Pass an even number of ``(x, y)`` tuples and they are paired
into start/end positions per finger::
# two-finger pinch-out
await mobile.gesture(
(cx - 20, cy), (cx - 200, cy), # finger 0
(cx + 20, cy), (cx + 200, cy), # finger 1
)
Delegates to the ``multitouch_gesture`` transport action, which uses
``adb root`` + MT Protocol B ``sendevent`` for reliable injection on
both local ADB and cloud HTTP transports.
Args:
*finger_paths: Alternating start/end ``(x, y)`` tuples, two per finger.
Must have an even length >= 4 (at least 2 fingers × 2 points).
duration_ms: Total gesture duration in milliseconds.
steps: Interpolation steps (0 = auto: duration_ms // 20, min 5).
"""
if len(finger_paths) < 4 or len(finger_paths) % 2 != 0:
raise ValueError(
"gesture() requires an even number of (x, y) tuples, two per finger "
f"(start + end). Got {len(finger_paths)} tuples."
)
# Pair waypoints into per-finger (start, end) tuples
fingers_pairs: list[tuple[tuple[int, int], tuple[int, int]]] = []
for i in range(0, len(finger_paths), 2):
fingers_pairs.append((finger_paths[i], finger_paths[i + 1]))
sw, sh = await self._get_screen_size()
n_steps = steps if steps > 0 else max(5, duration_ms // 20)
# Structured params for the server-side multitouch_gesture action
fingers_payload = [{"start": list(start), "end": list(end)} for start, end in fingers_pairs]
await self._t.send(
"multitouch_gesture",
fingers=fingers_payload,
screen_w=sw,
screen_h=sh,
duration_ms=duration_ms,
steps=n_steps,
)
async def pinch_in(self, cx: int, cy: int, spread: int = 300, duration_ms: int = 400) -> None:
"""Pinch-in (zoom out) with two real simultaneous fingers."""
await self.gesture(
(cx - spread, cy),
(cx - 20, cy),
(cx + spread, cy),
(cx + 20, cy),
duration_ms=duration_ms,
)
async def pinch_out(self, cx: int, cy: int, spread: int = 300, duration_ms: int = 400) -> None:
"""Pinch-out (zoom in) with two real simultaneous fingers."""
await self.gesture(
(cx - 20, cy),
(cx - spread, cy),
(cx + 20, cy),
(cx + spread, cy),
duration_ms=duration_ms,
)
# ── Hardware keys ─────────────────────────────────────────────────────
async def key(self, keycode: int) -> None:
await self._shell(f"input keyevent {keycode}")
async def home(self) -> None:
await self.key(3)
async def back(self) -> None:
await self.key(4)
async def recents(self) -> None:
await self.key(187)
async def power(self) -> None:
await self.key(26)
async def volume_up(self) -> None:
await self.key(24)
async def volume_down(self) -> None:
await self.key(25)
async def enter(self) -> None:
await self.key(66)
async def backspace(self) -> None:
await self.key(67)
# ── System ────────────────────────────────────────────────────────────
async def wake(self) -> None:
await self.key(224)
async def notifications(self) -> None:
await self._shell("service call statusbar 1")
async def close_notifications(self) -> None:
await self._shell("service call statusbar 2")