mirror of
https://github.com/kuhyx/testsAndMisc.git
synced 2026-11-10 18:47:04 +01:00
A raw `adb shell input tap` took no lease, so one session's tap landed in another session's app (2026-09-25). python_pkg.phone_guard classifies every adb call in a Bash command and, as the Claude Code PreToolUse hook, takes the matching phone_lease (one app / real screen / whole phone) or exits 2: key/text input without -d, input on another session's virtual display, or a lease still held elsewhere after 40 s. android_ui gains --display self|<id>: it drives only this session's phone_vd display under that app's lease, and every input carries -d. uiautomator sees display 0 only, so virtual-display trees come from a tiny instrumentation APK (A11yDump.java) built from source without Gradle. Prunes the oldest evidence artifact to stay under the 60-file cap. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WpA6wW2DKiovQADTdjWJoH
84 lines
2.5 KiB
Python
84 lines
2.5 KiB
Python
"""Which session owns which virtual display, read from ``phone_vd.sh`` state.
|
|
|
|
``phone_vd.sh`` keeps one directory per session under
|
|
``$PHONE_VD_STATE_DIR/<serial>/<owner>/`` holding ``display`` (the logical id
|
|
``input -d`` takes), ``sf`` (the SurfaceFlinger id ``screencap -d`` takes) and
|
|
``package``. The owner directory name is the phone_lease owner id with
|
|
anything outside ``[A-Za-z0-9_.-]`` replaced by ``_``, exactly as the script
|
|
writes it.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from dataclasses import dataclass
|
|
import os
|
|
from pathlib import Path
|
|
import re
|
|
import tempfile
|
|
|
|
_UNSAFE = re.compile(r"[^A-Za-z0-9_.-]")
|
|
|
|
|
|
def state_root() -> Path:
|
|
"""The directory ``phone_vd.sh`` keeps its per-phone state in.
|
|
|
|
``$PHONE_VD_STATE_DIR``, else ``phone_vd`` under the temp dir -- the same
|
|
``${TMPDIR:-/tmp}`` rule the script applies, so both sides agree.
|
|
"""
|
|
default = Path(tempfile.gettempdir()) / "phone_vd"
|
|
return Path(os.environ.get("PHONE_VD_STATE_DIR", str(default)))
|
|
|
|
|
|
def owner_dir_name(owner: str) -> str:
|
|
"""``owner`` as ``phone_vd.sh`` names its state directory."""
|
|
return _UNSAFE.sub("_", owner)
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class SessionDisplay:
|
|
"""One running virtual display and whose it is."""
|
|
|
|
owner_dir: str
|
|
display: int
|
|
sf: str
|
|
package: str
|
|
|
|
|
|
def _read(path: Path) -> str:
|
|
try:
|
|
return path.read_text(encoding="utf-8").strip()
|
|
except OSError:
|
|
return ""
|
|
|
|
|
|
def displays(serial: str) -> list[SessionDisplay]:
|
|
"""Every session's virtual display on ``serial``."""
|
|
root = state_root() / serial
|
|
found: list[SessionDisplay] = []
|
|
if not root.is_dir():
|
|
return found
|
|
for entry in sorted(root.iterdir()):
|
|
display = _read(entry / "display")
|
|
if not display.isdigit():
|
|
continue
|
|
found.append(
|
|
SessionDisplay(
|
|
owner_dir=entry.name,
|
|
display=int(display),
|
|
sf=_read(entry / "sf"),
|
|
package=_read(entry / "package"),
|
|
)
|
|
)
|
|
return found
|
|
|
|
|
|
def own_display(serial: str, owner: str) -> SessionDisplay | None:
|
|
"""``owner``'s virtual display on ``serial``, if it has one running."""
|
|
mine = owner_dir_name(owner)
|
|
return next((d for d in displays(serial) if d.owner_dir == mine), None)
|
|
|
|
|
|
def display_owner(serial: str, display: int) -> SessionDisplay | None:
|
|
"""The session whose virtual display has logical id ``display``."""
|
|
return next((d for d in displays(serial) if d.display == display), None)
|