Files

163 lines
5.9 KiB
Bash

#!/usr/bin/env bash
# Drive the dsh-sync app inside MuMu Player 12 from the command line.
#
# bash android/emulator.sh start # boot the VM, wait for adb
# bash android/emulator.sh install # build + install + launch
# bash android/emulator.sh shot [out.png] # capture just the app's window
# bash android/emulator.sh tap X Y # tap in APP coordinates (0..1080 x 0..1920)
# bash android/emulator.sh text "..." # type into the focused field
# bash android/emulator.sh key <keyevent> # e.g. 66 = Enter, 4 = Back
# bash android/emulator.sh deeplink <url> # http://ip:8020/app#invite=<code>
# bash android/emulator.sh ui # dump visible text from the WebView
# bash android/emulator.sh log [n]
#
# Two MuMu behaviours drive the design here, both learned the hard way:
#
# 1. Every app gets its OWN virtual display, and the logical display id is
# re-assigned on every boot (0, then 3, then 6, then 8, then 11 across one
# session). `adb shell input tap` without `-d` goes to display 0, i.e. the
# launcher — taps appear to do nothing while the app is visibly on screen.
# Every input call below resolves the app's current display and passes -d.
#
# 2. `screencap` cannot capture the app's display: the compositor refuses with
# "Status: -2" for MuMu's app displays. Screenshots therefore come from the
# host window (see the CUA path) or from `screencap -a`; this script shells
# out to the window capture for `shot`.
set -uo pipefail
MUMU="${MUMU_HOME:-/c/Program Files/Netease/MuMu}"
ADB="$MUMU/nx_main/adb.exe"
MANAGER="$MUMU/nx_main/MuMuManager.exe"
SERIAL="${MUMU_SERIAL:-127.0.0.1:16384}"
PKG="com.dsh.sync"
APK="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)/data/dist/dsh-sync.apk"
# Git Bash rewrites /sdcard/... into a Windows path; this disables that.
export MSYS_NO_PATHCONV=1
adb_() { "$ADB" -s "$SERIAL" "$@"; }
ensure_serial() {
if ! adb_ get-state >/dev/null 2>&1; then
"$ADB" connect "$SERIAL" >/dev/null 2>&1
fi
}
# Logical display currently hosting our activity, or empty.
#
# The imeInputTarget line names whichever window holds the input focus, so it
# must be filtered down to our own package first — otherwise the launcher's
# line matches and every tap lands on display 0.
app_display() {
adb_ shell dumpsys window windows 2>/dev/null | tr -d '\r' |
grep -E "imeInputTarget in display#" |
grep -F "$PKG" |
grep -oE "display# ?[0-9]+" | grep -oE "[0-9]+$" | head -1
}
require_app() {
local d; d="$(app_display)"
if [ -z "$d" ]; then
echo "app is not on screen — run: $0 start" >&2
exit 1
fi
printf '%s' "$d"
}
case "${1:-}" in
start)
"$MANAGER" control -v 0 launch >/dev/null 2>&1
for i in $(seq 1 40); do
sleep 5
ensure_serial
if [ "$(adb_ shell getprop sys.boot_completed 2>/dev/null | tr -d '\r')" = "1" ]; then
echo "booted after $((i * 5))s"
# A stale proxy silently blackholes every WebView request (Chromium
# honours the system proxy; netcat does not, so the network looks fine).
adb_ shell settings delete global http_proxy >/dev/null 2>&1
adb_ shell settings delete global global_http_proxy_host >/dev/null 2>&1
adb_ shell settings delete global global_http_proxy_port >/dev/null 2>&1
echo "proxy cleared"
exit 0
fi
done
echo "boot timed out" >&2; exit 1
;;
install)
[ -f "$APK" ] || { echo "missing $APK — run android/build.sh first" >&2; exit 1; }
adb_ install -r "$APK" | tail -1
adb_ shell am start -n "$PKG/.MainActivity" >/dev/null
echo launched
;;
deeplink)
# Explicit component: Android only offers a bare http URL to browsers, so an
# implicit VIEW intent never reaches this app.
adb_ shell am start -n "$PKG/.MainActivity" \
-a android.intent.action.VIEW -d "$2" >/dev/null
echo "sent $2"
;;
tap)
d="$(require_app)"
adb_ shell input -d "$d" tap "$2" "$3"
;;
text)
d="$(require_app)"
adb_ shell input -d "$d" text "$2"
;;
key)
d="$(require_app)"
adb_ shell input -d "$d" keyevent "$2"
;;
swipe)
d="$(require_app)"
adb_ shell input -d "$d" swipe "$2" "$3" "$4" "$5" "${6:-300}"
;;
ui)
# dumpsys window gives the WebView's text without needing a screenshot.
d="$(require_app)"
adb_ shell uiautomator dump //sdcard/_ui.xml >/dev/null 2>&1
adb_ shell cat //sdcard/_ui.xml 2>/dev/null |
tr '>' '\n' | grep -oE 'text="[^"]+"' | sed 's/text=//' | tr -d '"'
;;
shot)
# screencap refuses MuMu's app displays, so capture through the host window.
out="${2:-$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)/_mumu_shot.png}"
"$MUMU/nx_main/MuMuManager.exe" >/dev/null 2>&1
powershell -NoProfile -Command "
Add-Type -AssemblyName System.Drawing,System.Windows.Forms
\$b = [System.Windows.Forms.Screen]::PrimaryScreen.Bounds
\$bmp = New-Object System.Drawing.Bitmap \$b.Width, \$b.Height
\$g = [System.Drawing.Graphics]::FromImage(\$bmp)
\$g.CopyFromScreen(\$b.Location, [System.Drawing.Point]::Empty, \$b.Size)
\$bmp.Save('$out')
" >/dev/null 2>&1
[ -s "$out" ] && echo "$out" || { echo "capture failed" >&2; exit 1; }
;;
status)
ensure_serial
echo "serial : $SERIAL"
echo "boot : $(adb_ shell getprop sys.boot_completed 2>/dev/null | tr -d '\r')"
echo "app display : $(app_display)"
echo "app pid : $(adb_ shell pidof $PKG 2>/dev/null | tr -d '\r')"
echo "proxy : $(adb_ shell settings get global http_proxy 2>/dev/null | tr -d '\r')"
echo "foreground : $(adb_ shell dumpsys activity activities 2>/dev/null | tr -d '\r' | grep -m1 topResumedActivity | sed 's/.*u0 //')"
;;
log)
adb_ logcat -d -t "${2:-80}" 2>/dev/null | grep -iE "dsh|chromium|AndroidRuntime|FATAL" | tail -25
;;
*)
sed -n '2,22p' "$0"
;;
esac