Collect the emulator tooling into one repo
ui-trace, the adb wrapper and its device recorder were loose files under ~/.local, and each Android checkout carried its own copy of the same emulator boot sequence. This puts them together, with an install script that symlinks them back so editing the repo is editing what runs. Two things are new rather than moved. `emu` is the emulator lifecycle -- name, serial, list, up, down -- keyed on the AVD named after the enclosing checkout, which is the rule that lets several sessions work here at once; and `adb` now fills in `-s` from that same rule, because with two emulators attached a bare `adb shell pm list packages` comes back empty rather than failing, which reads as the app being uninstalled rather than the question being ambiguous. `emu up` refuses when the machine has no room. On 2026-08-30 an emulator started with 2.8 GB available invoked the OOM killer, and what it took was not the emulator that had just started: it walked the user slice and killed pipewire, dbus-broker and another session's emulator first. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
commit
5d77599f3f
8 files changed
+1187
No files matched your search
@@ -0,0 +1,119 @@
|
||||
#!/bin/bash
|
||||
# adb, aimed at this checkout's own emulator, with screenshots scaled down
|
||||
# before anything reads them.
|
||||
#
|
||||
# This is a wrapper, not a separate screenshot command, because a separate
|
||||
# command is a rule to remember and this has to hold for every agent on
|
||||
# this machine without anybody opting in. `adb exec-out screencap -p` is
|
||||
# the normal way to take a screenshot here, so that is the thing that has
|
||||
# to come back small.
|
||||
#
|
||||
# What it costs to read one is set by pixels, not bytes: an image is
|
||||
# charged at about (width x height) / 750 tokens, after being scaled to fit
|
||||
# 1568px on its long edge. So a 1080x2424 phone screenshot is ~1460 tokens
|
||||
# however well the PNG is compressed, and the only lever that moves is the
|
||||
# size. At 800px on the long edge it is ~380 -- and a 356x800 frame is
|
||||
# still legible enough to check layout, contrast, alignment and small
|
||||
# labels, which is what these are looked at for. Raise it for one call with
|
||||
# ADB_SCREENCAP_MAX_EDGE if something genuinely needs the detail.
|
||||
#
|
||||
# The other half is which device a call means. Several agent sessions share
|
||||
# this machine, each with an emulator named after its checkout, and a bare
|
||||
# `adb shell` with two of them attached does not fail cleanly: `adb shell pm
|
||||
# list packages` comes back *empty*, which reads as the app having been
|
||||
# uninstalled rather than as the question being ambiguous. So when a call
|
||||
# does not name a device and this checkout's own emulator is running, this
|
||||
# aims at it. Nothing is guessed when it is not running: adb's own error is
|
||||
# better than a wrapper picking somebody else's phone.
|
||||
#
|
||||
# Everything that is not a screencap is handed straight through, unchanged,
|
||||
# including its exit status -- a wrapper that breaks `adb install` to save
|
||||
# tokens on screenshots would not survive the week.
|
||||
set -euo pipefail
|
||||
|
||||
. "$(dirname "$(readlink -f "$0")")/../lib/project-avd.sh"
|
||||
|
||||
# See `android_adb`: found without consulting PATH, which is how this script
|
||||
# was called and would be how it called itself for ever.
|
||||
real=$(android_adb) || {
|
||||
echo "adb: no platform-tools under any known SDK; set ANDROID_HOME" >&2
|
||||
exit 127
|
||||
}
|
||||
|
||||
# Aim at this checkout's emulator, unless the caller has already said which
|
||||
# device they mean or is asking something that is not about one.
|
||||
#
|
||||
# `-s` and friends are only valid before the subcommand, so the scan stops at
|
||||
# the first word that is not one of adb's own options -- otherwise
|
||||
# `adb shell echo -s` would look like a device selection.
|
||||
target=()
|
||||
if [ -z "${ANDROID_SERIAL:-}" ]; then
|
||||
subcommand=""
|
||||
named=false
|
||||
for arg in "$@"; do
|
||||
case "$arg" in
|
||||
-s | -d | -e | -t | -H | -P | -L)
|
||||
named=true
|
||||
break
|
||||
;;
|
||||
-*) ;;
|
||||
*)
|
||||
subcommand="$arg"
|
||||
break
|
||||
;;
|
||||
esac
|
||||
done
|
||||
case "$subcommand" in
|
||||
# Not about one device: `devices` lists them all, and the rest talk to
|
||||
# the adb server itself. Passing -s to these is at best ignored.
|
||||
devices | start-server | kill-server | version | help | connect | disconnect) ;;
|
||||
*)
|
||||
if [ "$named" = false ]; then
|
||||
serial=$(avd_serial "$real" "$(project_avd)" || true)
|
||||
[ -n "$serial" ] && target=(-s "$serial")
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
|
||||
screencap=false
|
||||
for arg in "$@"; do
|
||||
if [ "$arg" = screencap ]; then
|
||||
screencap=true
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
# ffmpeg missing is a reason to hand over the full-size image, not to fail:
|
||||
# the caller asked for a screenshot and is entitled to one.
|
||||
if [ "$screencap" != true ] || ! command -v ffmpeg >/dev/null 2>&1; then
|
||||
exec "$real" "${target[@]}" "$@"
|
||||
fi
|
||||
|
||||
max=${ADB_SCREENCAP_MAX_EDGE:-800}
|
||||
tmp=$(mktemp -t adb-screencap.XXXXXX.png)
|
||||
trap 'rm -f "$tmp"' EXIT
|
||||
status=0
|
||||
"$real" "${target[@]}" "$@" >"$tmp" || status=$?
|
||||
|
||||
# Only a PNG that actually arrived on stdout is ours to shrink. `adb shell
|
||||
# screencap -p /sdcard/x.png` writes to the device and produces nothing
|
||||
# here, and feeding that to ffmpeg would turn a working command into a
|
||||
# failing one; so anything that is not a PNG goes back out byte for byte,
|
||||
# with the real adb's exit status.
|
||||
if [ "$status" -ne 0 ] || [ ! -s "$tmp" ] ||
|
||||
[ "$(head -c 8 "$tmp" | od -An -tx1 | tr -d ' \n')" != "89504e470d0a1a0a" ]; then
|
||||
cat "$tmp"
|
||||
exit "$status"
|
||||
fi
|
||||
|
||||
# Never enlarge (min(1,...)), keep the aspect ratio, and keep both edges
|
||||
# even, which some encoders insist on. If ffmpeg cannot make sense of it,
|
||||
# the original still goes out: a smaller screenshot is worth having, a
|
||||
# missing one is not.
|
||||
scale="min(1,$max/max(iw,ih))"
|
||||
if ! ffmpeg -y -loglevel error -i "$tmp" \
|
||||
-vf "scale='trunc(iw*$scale/2)*2':'trunc(ih*$scale/2)*2':flags=area" \
|
||||
-f image2pipe -c:v png -; then
|
||||
cat "$tmp"
|
||||
fi
|
||||
Reference in new issue
Block a user