163 lines
5.9 KiB
Bash
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
|