aboutsummaryrefslogtreecommitdiff
path: root/app/src/main/java/invalid/lena/scrcpy/ControlMessages.java
blob: aba2ca8ea437c0e9102bacdd3a4a57cbab66b9ad (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
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 {

    static final int MAX_CLIPBOARD_BYTES = 1 << 20;

    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) {
        if (text.length() > MAX_CLIPBOARD_BYTES) {
            throw new IllegalArgumentException("clipboard text is too large");
        }
        byte[] data = text.getBytes(StandardCharsets.UTF_8);
        if (data.length > MAX_CLIPBOARD_BYTES) {
            throw new IllegalArgumentException("clipboard UTF-8 data is too large");
        }
        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;
    }
}