1 package com.genymobile.scrcpy;
2 
3 import java.io.EOFException;
4 import java.io.IOException;
5 import java.io.InputStream;
6 import java.nio.ByteBuffer;
7 import java.nio.charset.StandardCharsets;
8 
9 public class ControlMessageReader {
10 
11     private static final int INJECT_KEYCODE_PAYLOAD_LENGTH = 9;
12     private static final int INJECT_MOUSE_EVENT_PAYLOAD_LENGTH = 17;
13     private static final int INJECT_SCROLL_EVENT_PAYLOAD_LENGTH = 20;
14     private static final int SET_SCREEN_POWER_MODE_PAYLOAD_LENGTH = 1;
15 
16     public static final int TEXT_MAX_LENGTH = 300;
17     public static final int CLIPBOARD_TEXT_MAX_LENGTH = 4093;
18     private static final int RAW_BUFFER_SIZE = 1024;
19 
20     private final byte[] rawBuffer = new byte[RAW_BUFFER_SIZE];
21     private final ByteBuffer buffer = ByteBuffer.wrap(rawBuffer);
22     private final byte[] textBuffer = new byte[CLIPBOARD_TEXT_MAX_LENGTH];
23 
ControlMessageReader()24     public ControlMessageReader() {
25         // invariant: the buffer is always in "get" mode
26         buffer.limit(0);
27     }
28 
isFull()29     public boolean isFull() {
30         return buffer.remaining() == rawBuffer.length;
31     }
32 
readFrom(InputStream input)33     public void readFrom(InputStream input) throws IOException {
34         if (isFull()) {
35             throw new IllegalStateException("Buffer full, call next() to consume");
36         }
37         buffer.compact();
38         int head = buffer.position();
39         int r = input.read(rawBuffer, head, rawBuffer.length - head);
40         if (r == -1) {
41             throw new EOFException("Controller socket closed");
42         }
43         buffer.position(head + r);
44         buffer.flip();
45     }
46 
next()47     public ControlMessage next() {
48         if (!buffer.hasRemaining()) {
49             return null;
50         }
51         int savedPosition = buffer.position();
52 
53         int type = buffer.get();
54         ControlMessage msg;
55         switch (type) {
56             case ControlMessage.TYPE_INJECT_KEYCODE:
57                 msg = parseInjectKeycode();
58                 break;
59             case ControlMessage.TYPE_INJECT_TEXT:
60                 msg = parseInjectText();
61                 break;
62             case ControlMessage.TYPE_INJECT_MOUSE_EVENT:
63                 msg = parseInjectMouseEvent();
64                 break;
65             case ControlMessage.TYPE_INJECT_SCROLL_EVENT:
66                 msg = parseInjectScrollEvent();
67                 break;
68             case ControlMessage.TYPE_SET_CLIPBOARD:
69                 msg = parseSetClipboard();
70                 break;
71             case ControlMessage.TYPE_SET_SCREEN_POWER_MODE:
72                 msg = parseSetScreenPowerMode();
73                 break;
74             case ControlMessage.TYPE_BACK_OR_SCREEN_ON:
75             case ControlMessage.TYPE_EXPAND_NOTIFICATION_PANEL:
76             case ControlMessage.TYPE_COLLAPSE_NOTIFICATION_PANEL:
77             case ControlMessage.TYPE_GET_CLIPBOARD:
78                 msg = ControlMessage.createEmpty(type);
79                 break;
80             default:
81                 Ln.w("Unknown event type: " + type);
82                 msg = null;
83                 break;
84         }
85 
86         if (msg == null) {
87             // failure, reset savedPosition
88             buffer.position(savedPosition);
89         }
90         return msg;
91     }
92 
parseInjectKeycode()93     private ControlMessage parseInjectKeycode() {
94         if (buffer.remaining() < INJECT_KEYCODE_PAYLOAD_LENGTH) {
95             return null;
96         }
97         int action = toUnsigned(buffer.get());
98         int keycode = buffer.getInt();
99         int metaState = buffer.getInt();
100         return ControlMessage.createInjectKeycode(action, keycode, metaState);
101     }
102 
parseString()103     private String parseString() {
104         if (buffer.remaining() < 2) {
105             return null;
106         }
107         int len = toUnsigned(buffer.getShort());
108         if (buffer.remaining() < len) {
109             return null;
110         }
111         buffer.get(textBuffer, 0, len);
112         return new String(textBuffer, 0, len, StandardCharsets.UTF_8);
113     }
114 
parseInjectText()115     private ControlMessage parseInjectText() {
116         String text = parseString();
117         if (text == null) {
118             return null;
119         }
120         return ControlMessage.createInjectText(text);
121     }
122 
parseInjectMouseEvent()123     private ControlMessage parseInjectMouseEvent() {
124         if (buffer.remaining() < INJECT_MOUSE_EVENT_PAYLOAD_LENGTH) {
125             return null;
126         }
127         int action = toUnsigned(buffer.get());
128         int buttons = buffer.getInt();
129         Position position = readPosition(buffer);
130         return ControlMessage.createInjectMouseEvent(action, buttons, position);
131     }
132 
parseInjectScrollEvent()133     private ControlMessage parseInjectScrollEvent() {
134         if (buffer.remaining() < INJECT_SCROLL_EVENT_PAYLOAD_LENGTH) {
135             return null;
136         }
137         Position position = readPosition(buffer);
138         int hScroll = buffer.getInt();
139         int vScroll = buffer.getInt();
140         return ControlMessage.createInjectScrollEvent(position, hScroll, vScroll);
141     }
142 
parseSetClipboard()143     private ControlMessage parseSetClipboard() {
144         String text = parseString();
145         if (text == null) {
146             return null;
147         }
148         return ControlMessage.createSetClipboard(text);
149     }
150 
parseSetScreenPowerMode()151     private ControlMessage parseSetScreenPowerMode() {
152         if (buffer.remaining() < SET_SCREEN_POWER_MODE_PAYLOAD_LENGTH) {
153             return null;
154         }
155         int mode = buffer.get();
156         return ControlMessage.createSetScreenPowerMode(mode);
157     }
158 
readPosition(ByteBuffer buffer)159     private static Position readPosition(ByteBuffer buffer) {
160         int x = buffer.getInt();
161         int y = buffer.getInt();
162         int screenWidth = toUnsigned(buffer.getShort());
163         int screenHeight = toUnsigned(buffer.getShort());
164         return new Position(x, y, screenWidth, screenHeight);
165     }
166 
167     @SuppressWarnings("checkstyle:MagicNumber")
toUnsigned(short value)168     private static int toUnsigned(short value) {
169         return value & 0xffff;
170     }
171 
172     @SuppressWarnings("checkstyle:MagicNumber")
toUnsigned(byte value)173     private static int toUnsigned(byte value) {
174         return value & 0xff;
175     }
176 }
177