From eb0c8951196c637e44daf3c0617b131b997d5d2c Mon Sep 17 00:00:00 2001 From: Lena Date: Thu, 1 Jan 2026 00:00:00 +0000 Subject: scrcpy-android: mirror an Android device over wireless ADB Native Java app for Android 12+ that mirrors another Android device over wireless ADB, forwarding video, audio, touch input, and clipboard. Bundles a pinned scrcpy-server.jar and the vendored libadb-android stack. Supports h264/h265/av1 video and raw/opus audio with in-app codec selection. No NDK, no Kotlin. Includes JVM unit tests and a Docker-based emulator e2e rig. --- .../java/invalid/lena/scrcpy/ControlMessages.java | 72 ++++++++++++++++++++++ 1 file changed, 72 insertions(+) create mode 100644 app/src/main/java/invalid/lena/scrcpy/ControlMessages.java (limited to 'app/src/main/java/invalid/lena/scrcpy/ControlMessages.java') diff --git a/app/src/main/java/invalid/lena/scrcpy/ControlMessages.java b/app/src/main/java/invalid/lena/scrcpy/ControlMessages.java new file mode 100644 index 0000000..bd983ed --- /dev/null +++ b/app/src/main/java/invalid/lena/scrcpy/ControlMessages.java @@ -0,0 +1,72 @@ +package invalid.lena.scrcpy; + +import java.nio.charset.StandardCharsets; + +// Pure-java encoders for scrcpy control messages. Layouts mirror +// com.genymobile.scrcpy.control.ControlMessageReader in the scrcpy +// server source. Extracted from Controller so the wire format can be +// unit-tested without android.* on the classpath. +public final class ControlMessages { + + public static final int TYPE_INJECT_KEYCODE = 0; + public static final int TYPE_INJECT_TEXT = 1; + public static final int TYPE_INJECT_TOUCH_EVENT = 2; + public static final int TYPE_BACK_OR_SCREEN_ON = 4; + public static final int TYPE_SET_CLIPBOARD = 9; + + // KeyEvent.ACTION_DOWN / ACTION_UP. Mirror the int values rather + // than depend on android.view.KeyEvent so this stays android-free. + public static final int ACTION_DOWN = 0; + public static final int ACTION_UP = 1; + + public static final int TOUCH_MSG_LEN = 32; // 1 + 1 + 8 + 4 + 4 + 2 + 2 + 2 + 4 + 4 + public static final int KEY_MSG_LEN = 14; // 1 + 1 + 4 + 4 + 4 + public static final int BACK_MSG_LEN = 2; // 1 + 1 + + private ControlMessages() {} + + public static byte[] touch(int action, long pointerId, + int x, int y, int targetW, int targetH, + int pressureU16, int actionButton, int buttons) { + byte[] m = new byte[TOUCH_MSG_LEN]; + m[0] = TYPE_INJECT_TOUCH_EVENT; + m[1] = (byte) action; + Wire.writeBe64(m, 2, pointerId); + Wire.writeBe32(m, 10, x); + Wire.writeBe32(m, 14, y); + m[18] = (byte)(targetW >>> 8); m[19] = (byte) targetW; + m[20] = (byte)(targetH >>> 8); m[21] = (byte) targetH; + m[22] = (byte)(pressureU16 >>> 8); m[23] = (byte) pressureU16; + Wire.writeBe32(m, 24, actionButton); + Wire.writeBe32(m, 28, buttons); + return m; + } + + public static byte[] keycode(int action, int keycode, int repeat, int metaState) { + byte[] m = new byte[KEY_MSG_LEN]; + m[0] = TYPE_INJECT_KEYCODE; + m[1] = (byte) action; + Wire.writeBe32(m, 2, keycode); + Wire.writeBe32(m, 6, repeat); + Wire.writeBe32(m, 10, metaState); + return m; + } + + // Single button-press event. scrcpy server interprets this as Back + // when the screen is on, or screen-on when it's off. We send a + // DOWN+UP pair to make a tap; this helper builds one byte pair. + public static byte[] backOrScreenOn(int action) { + return new byte[]{(byte) TYPE_BACK_OR_SCREEN_ON, (byte) action}; + } + + public static byte[] setClipboard(long sequence, boolean paste, String text) { + byte[] data = text.getBytes(StandardCharsets.UTF_8); + byte[] m = new byte[1 + 8 + 1 + 4 + data.length]; + m[0] = TYPE_SET_CLIPBOARD; + Wire.writeBe64(m, 1, sequence); + m[9] = (byte)(paste ? 1 : 0); + Wire.writeBe32(m, 10, data.length); + System.arraycopy(data, 0, m, 14, data.length); + return m; + } +} -- cgit v1.2.3