Compare commits

..

9 Commits

Author SHA1 Message Date
Romain Vimont
6220456def Merge mouse and touch events
Both are handled the very same way on the device.
2019-10-03 20:37:49 +02:00
Romain Vimont
7e1d52c119 Rename "touch pointer" to "pointer"
There are only touch pointers now, mouse pointers have been removed.
2019-10-03 20:27:28 +02:00
Romain Vimont
280d5b718c Use common pointers for mouse and touch
The mouse is a pointer like any other.
2019-10-03 20:05:29 +02:00
Romain Vimont
30168f0428 Ignore duplicate mouse events
In SDL, a touch event may simulate an identical mouse event. Since we
already handle touch event, ignore these duplicates.
2019-10-03 20:05:29 +02:00
Romain Vimont
b5a2d99bc2 Send touch events from the client
On SDL touch events, send control messages to the server.
2019-10-03 20:05:29 +02:00
Romain Vimont
f765aae352 Inject touch events on the server
On receiving an "inject touch" control message, update the local
pointers state and inject touches.
2019-10-03 20:05:29 +02:00
Romain Vimont
77f876e29c Add "inject touch" control message
Add a control message type in the protocol to forward touch events to
the device.
2019-10-03 20:05:27 +02:00
Romain Vimont
d90549d1e6 Rename "pointer" to "mouse pointer"
This will help to distinguish them from "touch pointers".
2019-10-02 21:40:26 +02:00
Romain Vimont
810ff80ba7 Add buffer_write64be()
Add a function to write 64 bits in big-endian from a uint64_t.
2019-10-02 21:40:26 +02:00
12 changed files with 225 additions and 336 deletions

View File

@@ -49,19 +49,15 @@ control_msg_serialize(const struct control_msg *msg, unsigned char *buf) {
CONTROL_MSG_TEXT_MAX_LENGTH, &buf[1]); CONTROL_MSG_TEXT_MAX_LENGTH, &buf[1]);
return 1 + len; return 1 + len;
} }
case CONTROL_MSG_TYPE_INJECT_MOUSE_EVENT:
buf[1] = msg->inject_mouse_event.action;
buffer_write32be(&buf[2], msg->inject_mouse_event.buttons);
write_position(&buf[6], &msg->inject_mouse_event.position);
return 18;
case CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT: case CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT:
buf[1] = msg->inject_touch_event.action; buf[1] = msg->inject_touch_event.action;
buffer_write64be(&buf[2], msg->inject_touch_event.finger_id); buffer_write64be(&buf[2], msg->inject_touch_event.pointer_id);
write_position(&buf[10], &msg->inject_touch_event.position); write_position(&buf[10], &msg->inject_touch_event.position);
uint16_t pressure = uint16_t pressure =
to_fixed_point_16(msg->inject_touch_event.pressure); to_fixed_point_16(msg->inject_touch_event.pressure);
buffer_write16be(&buf[22], pressure); buffer_write16be(&buf[22], pressure);
return 24; buffer_write32be(&buf[24], msg->inject_touch_event.buttons);
return 28;
case CONTROL_MSG_TYPE_INJECT_SCROLL_EVENT: case CONTROL_MSG_TYPE_INJECT_SCROLL_EVENT:
write_position(&buf[1], &msg->inject_scroll_event.position); write_position(&buf[1], &msg->inject_scroll_event.position);
buffer_write32be(&buf[13], buffer_write32be(&buf[13],

View File

@@ -15,10 +15,11 @@
#define CONTROL_MSG_SERIALIZED_MAX_SIZE \ #define CONTROL_MSG_SERIALIZED_MAX_SIZE \
(3 + CONTROL_MSG_CLIPBOARD_TEXT_MAX_LENGTH) (3 + CONTROL_MSG_CLIPBOARD_TEXT_MAX_LENGTH)
#define POINTER_ID_MOUSE UINT64_C(-1);
enum control_msg_type { enum control_msg_type {
CONTROL_MSG_TYPE_INJECT_KEYCODE, CONTROL_MSG_TYPE_INJECT_KEYCODE,
CONTROL_MSG_TYPE_INJECT_TEXT, CONTROL_MSG_TYPE_INJECT_TEXT,
CONTROL_MSG_TYPE_INJECT_MOUSE_EVENT,
CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT, CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT,
CONTROL_MSG_TYPE_INJECT_SCROLL_EVENT, CONTROL_MSG_TYPE_INJECT_SCROLL_EVENT,
CONTROL_MSG_TYPE_BACK_OR_SCREEN_ON, CONTROL_MSG_TYPE_BACK_OR_SCREEN_ON,
@@ -49,11 +50,7 @@ struct control_msg {
struct { struct {
enum android_motionevent_action action; enum android_motionevent_action action;
enum android_motionevent_buttons buttons; enum android_motionevent_buttons buttons;
struct position position; uint64_t pointer_id;
} inject_mouse_event;
struct {
enum android_motionevent_action action;
uint64_t finger_id;
struct position position; struct position position;
float pressure; float pressure;
} inject_touch_event; } inject_touch_event;

View File

@@ -128,15 +128,6 @@ convert_keycode(SDL_Keycode from, enum android_keycode *to, uint16_t mod) {
} }
} }
static bool
convert_mouse_action(SDL_EventType from, enum android_motionevent_action *to) {
switch (from) {
MAP(SDL_MOUSEBUTTONDOWN, AMOTION_EVENT_ACTION_DOWN);
MAP(SDL_MOUSEBUTTONUP, AMOTION_EVENT_ACTION_UP);
FAIL;
}
}
static enum android_motionevent_buttons static enum android_motionevent_buttons
convert_mouse_buttons(uint32_t state) { convert_mouse_buttons(uint32_t state) {
enum android_motionevent_buttons buttons = 0; enum android_motionevent_buttons buttons = 0;
@@ -176,20 +167,31 @@ convert_input_key(const SDL_KeyboardEvent *from, struct control_msg *to) {
return true; return true;
} }
static bool
convert_mouse_action(SDL_EventType from, enum android_motionevent_action *to) {
switch (from) {
MAP(SDL_MOUSEBUTTONDOWN, AMOTION_EVENT_ACTION_DOWN);
MAP(SDL_MOUSEBUTTONUP, AMOTION_EVENT_ACTION_UP);
FAIL;
}
}
bool bool
convert_mouse_button(const SDL_MouseButtonEvent *from, struct size screen_size, convert_mouse_button(const SDL_MouseButtonEvent *from, struct size screen_size,
struct control_msg *to) { struct control_msg *to) {
to->type = CONTROL_MSG_TYPE_INJECT_MOUSE_EVENT; to->type = CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT;
if (!convert_mouse_action(from->type, &to->inject_mouse_event.action)) { if (!convert_mouse_action(from->type, &to->inject_touch_event.action)) {
return false; return false;
} }
to->inject_mouse_event.buttons = to->inject_touch_event.pointer_id = POINTER_ID_MOUSE;
to->inject_touch_event.position.screen_size = screen_size;
to->inject_touch_event.position.point.x = from->x;
to->inject_touch_event.position.point.y = from->y;
to->inject_touch_event.pressure = 1.f;
to->inject_touch_event.buttons =
convert_mouse_buttons(SDL_BUTTON(from->button)); convert_mouse_buttons(SDL_BUTTON(from->button));
to->inject_mouse_event.position.screen_size = screen_size;
to->inject_mouse_event.position.point.x = from->x;
to->inject_mouse_event.position.point.y = from->y;
return true; return true;
} }
@@ -197,12 +199,14 @@ convert_mouse_button(const SDL_MouseButtonEvent *from, struct size screen_size,
bool bool
convert_mouse_motion(const SDL_MouseMotionEvent *from, struct size screen_size, convert_mouse_motion(const SDL_MouseMotionEvent *from, struct size screen_size,
struct control_msg *to) { struct control_msg *to) {
to->type = CONTROL_MSG_TYPE_INJECT_MOUSE_EVENT; to->type = CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT;
to->inject_mouse_event.action = AMOTION_EVENT_ACTION_MOVE; to->inject_touch_event.action = AMOTION_EVENT_ACTION_MOVE;
to->inject_mouse_event.buttons = convert_mouse_buttons(from->state); to->inject_touch_event.pointer_id = POINTER_ID_MOUSE;
to->inject_mouse_event.position.screen_size = screen_size; to->inject_touch_event.position.screen_size = screen_size;
to->inject_mouse_event.position.point.x = from->x; to->inject_touch_event.position.point.x = from->x;
to->inject_mouse_event.position.point.y = from->y; to->inject_touch_event.position.point.y = from->y;
to->inject_touch_event.pressure = 1.f;
to->inject_touch_event.buttons = convert_mouse_buttons(from->state);
return true; return true;
} }
@@ -226,12 +230,13 @@ convert_touch(const SDL_TouchFingerEvent *from, struct size screen_size,
return false; return false;
} }
to->inject_touch_event.finger_id = from->fingerId; to->inject_touch_event.pointer_id = from->fingerId;
to->inject_touch_event.position.screen_size = screen_size; to->inject_touch_event.position.screen_size = screen_size;
// SDL touch event coordinates are normalized in the range [0; 1] // SDL touch event coordinates are normalized in the range [0; 1]
to->inject_touch_event.position.point.x = from->x * screen_size.width; to->inject_touch_event.position.point.x = from->x * screen_size.width;
to->inject_touch_event.position.point.y = from->y * screen_size.height; to->inject_touch_event.position.point.y = from->y * screen_size.height;
to->inject_touch_event.pressure = from->pressure; to->inject_touch_event.pressure = from->pressure;
to->inject_touch_event.buttons = 0;
return true; return true;
} }

View File

@@ -423,6 +423,10 @@ void
input_manager_process_mouse_button(struct input_manager *input_manager, input_manager_process_mouse_button(struct input_manager *input_manager,
const SDL_MouseButtonEvent *event, const SDL_MouseButtonEvent *event,
bool control) { bool control) {
if (event->which == SDL_TOUCH_MOUSEID) {
// simulated from touch events, so it's a duplicate
return;
}
if (event->type == SDL_MOUSEBUTTONDOWN) { if (event->type == SDL_MOUSEBUTTONDOWN) {
if (control && event->button == SDL_BUTTON_RIGHT) { if (control && event->button == SDL_BUTTON_RIGHT) {
press_back_or_turn_screen_on(input_manager->controller); press_back_or_turn_screen_on(input_manager->controller);

View File

@@ -67,45 +67,12 @@ static void test_serialize_inject_text_long(void) {
assert(!memcmp(buf, expected, sizeof(expected))); assert(!memcmp(buf, expected, sizeof(expected)));
} }
static void test_serialize_inject_mouse_event(void) {
struct control_msg msg = {
.type = CONTROL_MSG_TYPE_INJECT_MOUSE_EVENT,
.inject_mouse_event = {
.action = AMOTION_EVENT_ACTION_DOWN,
.buttons = AMOTION_EVENT_BUTTON_PRIMARY,
.position = {
.point = {
.x = 260,
.y = 1026,
},
.screen_size = {
.width = 1080,
.height = 1920,
},
},
},
};
unsigned char buf[CONTROL_MSG_SERIALIZED_MAX_SIZE];
int size = control_msg_serialize(&msg, buf);
assert(size == 18);
const unsigned char expected[] = {
CONTROL_MSG_TYPE_INJECT_MOUSE_EVENT,
0x00, // AKEY_EVENT_ACTION_DOWN
0x00, 0x00, 0x00, 0x01, // AMOTION_EVENT_BUTTON_PRIMARY
0x00, 0x00, 0x01, 0x04, 0x00, 0x00, 0x04, 0x02, // 260 1026
0x04, 0x38, 0x07, 0x80, // 1080 1920
};
assert(!memcmp(buf, expected, sizeof(expected)));
}
#include <stdio.h>
static void test_serialize_inject_touch_event(void) { static void test_serialize_inject_touch_event(void) {
struct control_msg msg = { struct control_msg msg = {
.type = CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT, .type = CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT,
.inject_touch_event = { .inject_touch_event = {
.action = AMOTION_EVENT_ACTION_DOWN, .action = AMOTION_EVENT_ACTION_DOWN,
.finger_id = 0x1234567887654321L, .pointer_id = 0x1234567887654321L,
.position = { .position = {
.point = { .point = {
.x = 100, .x = 100,
@@ -117,20 +84,22 @@ static void test_serialize_inject_touch_event(void) {
}, },
}, },
.pressure = 1.0f, .pressure = 1.0f,
.buttons = AMOTION_EVENT_BUTTON_PRIMARY,
}, },
}; };
unsigned char buf[CONTROL_MSG_SERIALIZED_MAX_SIZE]; unsigned char buf[CONTROL_MSG_SERIALIZED_MAX_SIZE];
int size = control_msg_serialize(&msg, buf); int size = control_msg_serialize(&msg, buf);
assert(size == 24); assert(size == 28);
const unsigned char expected[] = { const unsigned char expected[] = {
CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT, CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT,
0x00, // AKEY_EVENT_ACTION_DOWN 0x00, // AKEY_EVENT_ACTION_DOWN
0x12, 0x34, 0x56, 0x78, 0x87, 0x65, 0x43, 0x21, // finger id 0x12, 0x34, 0x56, 0x78, 0x87, 0x65, 0x43, 0x21, // pointer id
0x00, 0x00, 0x00, 0x64, 0x00, 0x00, 0x00, 0xc8, // 100 200 0x00, 0x00, 0x00, 0x64, 0x00, 0x00, 0x00, 0xc8, // 100 200
0x04, 0x38, 0x07, 0x80, // 1080 1920 0x04, 0x38, 0x07, 0x80, // 1080 1920
0xff, 0xff, // pressure 0xff, 0xff, // pressure
0x00, 0x00, 0x00, 0x01 // AMOTION_EVENT_BUTTON_PRIMARY
}; };
assert(!memcmp(buf, expected, sizeof(expected))); assert(!memcmp(buf, expected, sizeof(expected)));
} }
@@ -271,7 +240,6 @@ int main(void) {
test_serialize_inject_keycode(); test_serialize_inject_keycode();
test_serialize_inject_text(); test_serialize_inject_text();
test_serialize_inject_text_long(); test_serialize_inject_text_long();
test_serialize_inject_mouse_event();
test_serialize_inject_touch_event(); test_serialize_inject_touch_event();
test_serialize_inject_scroll_event(); test_serialize_inject_scroll_event();
test_serialize_back_or_screen_on(); test_serialize_back_or_screen_on();

View File

@@ -7,15 +7,14 @@ public final class ControlMessage {
public static final int TYPE_INJECT_KEYCODE = 0; public static final int TYPE_INJECT_KEYCODE = 0;
public static final int TYPE_INJECT_TEXT = 1; public static final int TYPE_INJECT_TEXT = 1;
public static final int TYPE_INJECT_MOUSE_EVENT = 2; public static final int TYPE_INJECT_TOUCH_EVENT = 2;
public static final int TYPE_INJECT_TOUCH_EVENT = 3; public static final int TYPE_INJECT_SCROLL_EVENT = 3;
public static final int TYPE_INJECT_SCROLL_EVENT = 4; public static final int TYPE_BACK_OR_SCREEN_ON = 4;
public static final int TYPE_BACK_OR_SCREEN_ON = 5; public static final int TYPE_EXPAND_NOTIFICATION_PANEL = 5;
public static final int TYPE_EXPAND_NOTIFICATION_PANEL = 6; public static final int TYPE_COLLAPSE_NOTIFICATION_PANEL = 6;
public static final int TYPE_COLLAPSE_NOTIFICATION_PANEL = 7; public static final int TYPE_GET_CLIPBOARD = 7;
public static final int TYPE_GET_CLIPBOARD = 8; public static final int TYPE_SET_CLIPBOARD = 8;
public static final int TYPE_SET_CLIPBOARD = 9; public static final int TYPE_SET_SCREEN_POWER_MODE = 9;
public static final int TYPE_SET_SCREEN_POWER_MODE = 10;
private int type; private int type;
private String text; private String text;
@@ -23,7 +22,7 @@ public final class ControlMessage {
private int action; // KeyEvent.ACTION_* or MotionEvent.ACTION_* or POWER_MODE_* private int action; // KeyEvent.ACTION_* or MotionEvent.ACTION_* or POWER_MODE_*
private int keycode; // KeyEvent.KEYCODE_* private int keycode; // KeyEvent.KEYCODE_*
private int buttons; // MotionEvent.BUTTON_* private int buttons; // MotionEvent.BUTTON_*
private long fingerId; private long pointerId;
private float pressure; private float pressure;
private Position position; private Position position;
private int hScroll; private int hScroll;
@@ -48,22 +47,15 @@ public final class ControlMessage {
return msg; return msg;
} }
public static ControlMessage createInjectMouseEvent(int action, int buttons, Position position) { public static ControlMessage createInjectTouchEvent(int action, long pointerId, Position position, float pressure,
ControlMessage msg = new ControlMessage(); int buttons) {
msg.type = TYPE_INJECT_MOUSE_EVENT;
msg.action = action;
msg.buttons = buttons;
msg.position = position;
return msg;
}
public static ControlMessage createInjectTouchEvent(int action, long fingerId, Position position, float pressure) {
ControlMessage msg = new ControlMessage(); ControlMessage msg = new ControlMessage();
msg.type = TYPE_INJECT_TOUCH_EVENT; msg.type = TYPE_INJECT_TOUCH_EVENT;
msg.action = action; msg.action = action;
msg.fingerId = fingerId; msg.pointerId = pointerId;
msg.pressure = pressure; msg.pressure = pressure;
msg.position = position; msg.position = position;
msg.buttons = buttons;
return msg; return msg;
} }
@@ -123,8 +115,8 @@ public final class ControlMessage {
return buttons; return buttons;
} }
public long getFingerId() { public long getPointerId() {
return fingerId; return pointerId;
} }
public float getPressure() { public float getPressure() {

View File

@@ -60,9 +60,6 @@ public class ControlMessageReader {
case ControlMessage.TYPE_INJECT_TEXT: case ControlMessage.TYPE_INJECT_TEXT:
msg = parseInjectText(); msg = parseInjectText();
break; break;
case ControlMessage.TYPE_INJECT_MOUSE_EVENT:
msg = parseInjectMouseEvent();
break;
case ControlMessage.TYPE_INJECT_TOUCH_EVENT: case ControlMessage.TYPE_INJECT_TOUCH_EVENT:
msg = parseInjectTouchEvent(); msg = parseInjectTouchEvent();
break; break;
@@ -124,28 +121,20 @@ public class ControlMessageReader {
return ControlMessage.createInjectText(text); return ControlMessage.createInjectText(text);
} }
private ControlMessage parseInjectMouseEvent() { @SuppressWarnings("checkstyle:MagicNumber")
if (buffer.remaining() < INJECT_MOUSE_EVENT_PAYLOAD_LENGTH) {
return null;
}
int action = toUnsigned(buffer.get());
int buttons = buffer.getInt();
Position position = readPosition(buffer);
return ControlMessage.createInjectMouseEvent(action, buttons, position);
}
private ControlMessage parseInjectTouchEvent() { private ControlMessage parseInjectTouchEvent() {
if (buffer.remaining() < INJECT_TOUCH_EVENT_PAYLOAD_LENGTH) { if (buffer.remaining() < INJECT_TOUCH_EVENT_PAYLOAD_LENGTH) {
return null; return null;
} }
int action = toUnsigned(buffer.get()); int action = toUnsigned(buffer.get());
long fingerId = buffer.getLong(); long pointerId = buffer.getLong();
Position position = readPosition(buffer); Position position = readPosition(buffer);
// 16 bits fixed-point // 16 bits fixed-point
int pressureInt = toUnsigned(buffer.getShort()); int pressureInt = toUnsigned(buffer.getShort());
// convert it to a float between 0 and 1 (0x1p16f is 2^16 as float) // convert it to a float between 0 and 1 (0x1p16f is 2^16 as float)
float pressure = pressureInt == 0xffff ? 1f : (pressureInt / 0x1p16f); float pressure = pressureInt == 0xffff ? 1f : (pressureInt / 0x1p16f);
return ControlMessage.createInjectTouchEvent(action, fingerId, position, pressure); int buttons = buffer.getInt();
return ControlMessage.createInjectTouchEvent(action, pointerId, position, pressure, buttons);
} }
private ControlMessage parseInjectScrollEvent() { private ControlMessage parseInjectScrollEvent() {

View File

@@ -13,44 +13,28 @@ import java.io.IOException;
public class Controller { public class Controller {
private static final int MAX_FINGERS = 10;
private final Device device; private final Device device;
private final DesktopConnection connection; private final DesktopConnection connection;
private final DeviceMessageSender sender; private final DeviceMessageSender sender;
private final KeyCharacterMap charMap = KeyCharacterMap.load(KeyCharacterMap.VIRTUAL_KEYBOARD); private final KeyCharacterMap charMap = KeyCharacterMap.load(KeyCharacterMap.VIRTUAL_KEYBOARD);
private long lastMouseDown;
private final MotionEvent.PointerProperties[] mousePointerProperties = {new MotionEvent.PointerProperties()};
private final MotionEvent.PointerCoords[] mousePointerCoords = {new MotionEvent.PointerCoords()};
private long lastTouchDown; private long lastTouchDown;
private final FingersState fingersState = new FingersState(MAX_FINGERS); private final PointersState pointersState = new PointersState();
private final MotionEvent.PointerProperties[] touchPointerProperties = new MotionEvent.PointerProperties[MAX_FINGERS]; private final MotionEvent.PointerProperties[] pointerProperties =
private final MotionEvent.PointerCoords[] touchPointerCoords = new MotionEvent.PointerCoords[MAX_FINGERS]; new MotionEvent.PointerProperties[PointersState.MAX_POINTERS];
private final MotionEvent.PointerCoords[] pointerCoords =
new MotionEvent.PointerCoords[PointersState.MAX_POINTERS];
public Controller(Device device, DesktopConnection connection) { public Controller(Device device, DesktopConnection connection) {
this.device = device; this.device = device;
this.connection = connection; this.connection = connection;
initMousePointer(); initPointers();
initTouchPointers();
sender = new DeviceMessageSender(connection); sender = new DeviceMessageSender(connection);
} }
private void initMousePointer() { private void initPointers() {
MotionEvent.PointerProperties props = mousePointerProperties[0]; for (int i = 0; i < PointersState.MAX_POINTERS; ++i) {
props.id = 0;
props.toolType = MotionEvent.TOOL_TYPE_FINGER;
MotionEvent.PointerCoords coords = mousePointerCoords[0];
coords.orientation = 0;
coords.pressure = 1;
coords.size = 1;
}
private void initTouchPointers() {
for (int i = 0; i < MAX_FINGERS; ++i) {
MotionEvent.PointerProperties props = new MotionEvent.PointerProperties(); MotionEvent.PointerProperties props = new MotionEvent.PointerProperties();
props.toolType = MotionEvent.TOOL_TYPE_FINGER; props.toolType = MotionEvent.TOOL_TYPE_FINGER;
@@ -58,23 +42,11 @@ public class Controller {
coords.orientation = 0; coords.orientation = 0;
coords.size = 1; coords.size = 1;
touchPointerProperties[i] = props; pointerProperties[i] = props;
touchPointerCoords[i] = coords; pointerCoords[i] = coords;
} }
} }
private void setMousePointerCoords(Point point) {
MotionEvent.PointerCoords coords = mousePointerCoords[0];
coords.x = point.getX();
coords.y = point.getY();
}
private void setScroll(int hScroll, int vScroll) {
MotionEvent.PointerCoords coords = mousePointerCoords[0];
coords.setAxisValue(MotionEvent.AXIS_HSCROLL, hScroll);
coords.setAxisValue(MotionEvent.AXIS_VSCROLL, vScroll);
}
@SuppressWarnings("checkstyle:MagicNumber") @SuppressWarnings("checkstyle:MagicNumber")
public void control() throws IOException { public void control() throws IOException {
// on start, power on the device // on start, power on the device
@@ -109,11 +81,8 @@ public class Controller {
case ControlMessage.TYPE_INJECT_TEXT: case ControlMessage.TYPE_INJECT_TEXT:
injectText(msg.getText()); injectText(msg.getText());
break; break;
case ControlMessage.TYPE_INJECT_MOUSE_EVENT:
injectMouse(msg.getAction(), msg.getButtons(), msg.getPosition());
break;
case ControlMessage.TYPE_INJECT_TOUCH_EVENT: case ControlMessage.TYPE_INJECT_TOUCH_EVENT:
injectTouch(msg.getAction(), msg.getFingerId(), msg.getPosition(), msg.getPressure()); injectTouch(msg.getAction(), msg.getPointerId(), msg.getPosition(), msg.getPressure(), 0);
break; break;
case ControlMessage.TYPE_INJECT_SCROLL_EVENT: case ControlMessage.TYPE_INJECT_SCROLL_EVENT:
injectScroll(msg.getPosition(), msg.getHScroll(), msg.getVScroll()); injectScroll(msg.getPosition(), msg.getHScroll(), msg.getVScroll());
@@ -173,63 +142,42 @@ public class Controller {
return successCount; return successCount;
} }
private boolean injectMouse(int action, int buttons, Position position) { private boolean injectTouch(int action, long pointerId, Position position, float pressure, int buttons) {
long now = SystemClock.uptimeMillis(); long now = SystemClock.uptimeMillis();
Point point = device.getPhysicalPoint(position);
if (point == null) {
// ignore event
return false;
}
int pointerIndex = pointersState.getPointerIndex(pointerId);
if (pointerIndex == -1) {
Ln.w("Too many pointers for touch event");
return false;
}
Pointer pointer = pointersState.get(pointerIndex);
pointer.setPoint(point);
pointer.setPressure(pressure);
pointer.setUp(action == MotionEvent.ACTION_UP);
int pointerCount = pointersState.update(pointerProperties, pointerCoords);
if (pointerCount == 1) {
if (action == MotionEvent.ACTION_DOWN) { if (action == MotionEvent.ACTION_DOWN) {
lastMouseDown = now;
}
Point point = device.getPhysicalPoint(position);
if (point == null) {
// ignore event
return false;
}
setMousePointerCoords(point);
MotionEvent event = MotionEvent.obtain(lastMouseDown, now, action, 1, mousePointerProperties, mousePointerCoords, 0, buttons, 1f, 1f, 0, 0,
InputDevice.SOURCE_TOUCHSCREEN, 0);
return injectEvent(event);
}
private boolean injectTouch(int action, long fingerId, Position position, float pressure) {
long now = SystemClock.uptimeMillis();
Point point = device.getPhysicalPoint(position);
if (point == null) {
// ignore event
return false;
}
int fingerIndex = fingersState.getFingerIndex(fingerId);
if (fingerIndex == -1) {
Ln.w("Too many fingers for touch event");
return false;
}
Finger finger = fingersState.get(fingerIndex);
finger.setPoint(point);
finger.setPressure(pressure);
finger.setUp(action == MotionEvent.ACTION_UP);
// FAIL: action_up will always remove the finger, and the event will not be written!
int pointerCount = fingersState.update(touchPointerProperties, touchPointerCoords);
fingersState.cleanUp();
Ln.d("pointerCount = " + pointerCount);
for (int i = 0; i < pointerCount; ++i) {
Ln.d("props = " + touchPointerProperties[i].id);
Ln.d("coords = " + touchPointerCoords[i].x + "," + touchPointerCoords[i].y);
}
if (pointerCount > 1) {
if (action == MotionEvent.ACTION_UP) {
action = MotionEvent.ACTION_POINTER_UP | (fingerIndex << MotionEvent.ACTION_POINTER_INDEX_SHIFT);
} else if (action == MotionEvent.ACTION_DOWN) {
action = MotionEvent.ACTION_POINTER_DOWN | (fingerIndex << MotionEvent.ACTION_POINTER_INDEX_SHIFT);
}
} else if (action == MotionEvent.ACTION_DOWN) {
lastTouchDown = now; lastTouchDown = now;
} }
} else {
// secondary pointers must use ACTION_POINTER_* ORed with the pointerIndex
if (action == MotionEvent.ACTION_UP) {
action = MotionEvent.ACTION_POINTER_UP | (pointerIndex << MotionEvent.ACTION_POINTER_INDEX_SHIFT);
} else if (action == MotionEvent.ACTION_DOWN) {
action = MotionEvent.ACTION_POINTER_DOWN | (pointerIndex << MotionEvent.ACTION_POINTER_INDEX_SHIFT);
}
}
MotionEvent event = MotionEvent.obtain(lastTouchDown, now, action, pointerCount, touchPointerProperties, touchPointerCoords, 0, 0, 1f, 1f, 0, 0, MotionEvent event = MotionEvent.obtain(lastTouchDown, now, action, pointerCount, pointerProperties,
InputDevice.SOURCE_TOUCHSCREEN, 0); pointerCoords, 0, 0, 1f, 1f, 0, 0, InputDevice.SOURCE_TOUCHSCREEN, 0);
return injectEvent(event); return injectEvent(event);
} }
@@ -240,23 +188,30 @@ public class Controller {
// ignore event // ignore event
return false; return false;
} }
setMousePointerCoords(point);
setScroll(hScroll, vScroll); MotionEvent.PointerProperties props = pointerProperties[0];
MotionEvent event = MotionEvent.obtain(lastMouseDown, now, MotionEvent.ACTION_SCROLL, 1, mousePointerProperties, mousePointerCoords, 0, 0, 1f, 1f, 0, props.id = 0;
0, InputDevice.SOURCE_MOUSE, 0);
MotionEvent.PointerCoords coords = pointerCoords[0];
coords.x = point.getX();
coords.y = point.getY();
coords.setAxisValue(MotionEvent.AXIS_HSCROLL, hScroll);
coords.setAxisValue(MotionEvent.AXIS_VSCROLL, vScroll);
MotionEvent event = MotionEvent.obtain(lastTouchDown, now, MotionEvent.ACTION_SCROLL, 1, pointerProperties,
pointerCoords, 0, 0, 1f, 1f, 0, 0, InputDevice.SOURCE_MOUSE, 0);
return injectEvent(event); return injectEvent(event);
} }
private boolean injectKeyEvent(int action, int keyCode, int repeat, int metaState) { private boolean injectKeyEvent(int action, int keyCode, int repeat, int metaState) {
long now = SystemClock.uptimeMillis(); long now = SystemClock.uptimeMillis();
KeyEvent event = new KeyEvent(now, now, action, keyCode, repeat, metaState, KeyCharacterMap.VIRTUAL_KEYBOARD, 0, 0, KeyEvent event = new KeyEvent(now, now, action, keyCode, repeat, metaState, KeyCharacterMap.VIRTUAL_KEYBOARD,
InputDevice.SOURCE_KEYBOARD); 0, 0, InputDevice.SOURCE_KEYBOARD);
return injectEvent(event); return injectEvent(event);
} }
private boolean injectKeycode(int keyCode) { private boolean injectKeycode(int keyCode) {
return injectKeyEvent(KeyEvent.ACTION_DOWN, keyCode, 0, 0) return injectKeyEvent(KeyEvent.ACTION_DOWN, keyCode, 0, 0) && injectKeyEvent(KeyEvent.ACTION_UP, keyCode, 0, 0);
&& injectKeyEvent(KeyEvent.ACTION_UP, keyCode, 0, 0);
} }
private boolean injectEvent(InputEvent event) { private boolean injectEvent(InputEvent event) {

View File

@@ -1,101 +0,0 @@
package com.genymobile.scrcpy;
import android.view.MotionEvent;
import java.util.ArrayList;
import java.util.List;
public class FingersState {
private final List<Finger> fingers = new ArrayList<>();
private int maxFingers;
public FingersState(int maxFingers) {
this.maxFingers = maxFingers;
}
private int indexOf(long id) {
for (int i = 0; i < fingers.size(); ++i) {
Finger finger = fingers.get(i);
if (finger.getId() == id) {
return i;
}
}
return -1;
}
private boolean isLocalIdAvailable(int localId) {
for (int i = 0; i < fingers.size(); ++i) {
Finger finger = fingers.get(i);
if (finger.getLocalId() == localId) {
return false;
}
}
return true;
}
private int nextUnusedLocalId() {
for (int localId = 0; localId < maxFingers; ++localId) {
if (isLocalIdAvailable(localId)) {
return localId;
}
}
return -1;
}
public Finger get(int index) {
return fingers.get(index);
}
public int getFingerIndex(long id) {
int index = indexOf(id);
if (index != -1) {
// already exists, return it
return index;
}
if (fingers.size() >= maxFingers) {
// it's full
return -1;
}
// id 0 is reserved for mouse events
int localId = nextUnusedLocalId();
if (localId == -1) {
throw new AssertionError("fingers.size() < maxFingers implies that a local id is available");
}
Finger finger = new Finger(id, localId);
fingers.add(finger);
// return the index of the finger
return fingers.size() - 1;
}
/**
* Initialize the motion event parameters.
*
* @param props the pointer properties
* @param coords the pointer coordinates
* @return The number of items initialized (the number of fingers).
*/
public int update(MotionEvent.PointerProperties[] props, MotionEvent.PointerCoords[] coords) {
for (int i = 0; i < fingers.size(); ++i) {
Finger finger = fingers.get(i);
// id 0 is reserved for mouse events
props[i].id = finger.getLocalId();
Point point = finger.getPoint();
coords[i].x = point.getX();
coords[i].y = point.getY();
coords[i].pressure = finger.getPressure();
}
return fingers.size();
}
public void cleanUp() {
for (int i = fingers.size() - 1; i >= 0; --i) {
Finger finger = fingers.get(i);
if (finger.isUp()) {
fingers.remove(i);
}
}
}
}

View File

@@ -1,14 +1,22 @@
package com.genymobile.scrcpy; package com.genymobile.scrcpy;
public class Finger { public class Pointer {
/**
* Pointer id as received from the client.
*/
private final long id; private final long id;
/**
* Local pointer id, using the lowest possible values to fill the {@link android.view.MotionEvent.PointerProperties PointerProperties}.
*/
private final int localId; private final int localId;
private Point point; private Point point;
private float pressure; private float pressure;
private boolean up; private boolean up;
public Finger(long id, int localId) { public Pointer(long id, int localId) {
this.id = id; this.id = id;
this.localId = localId; this.localId = localId;
} }

View File

@@ -0,0 +1,103 @@
package com.genymobile.scrcpy;
import android.view.MotionEvent;
import java.util.ArrayList;
import java.util.List;
public class PointersState {
public static final int MAX_POINTERS = 10;
private final List<Pointer> pointers = new ArrayList<>();
private int indexOf(long id) {
for (int i = 0; i < pointers.size(); ++i) {
Pointer pointer = pointers.get(i);
if (pointer.getId() == id) {
return i;
}
}
return -1;
}
private boolean isLocalIdAvailable(int localId) {
for (int i = 0; i < pointers.size(); ++i) {
Pointer pointer = pointers.get(i);
if (pointer.getLocalId() == localId) {
return false;
}
}
return true;
}
private int nextUnusedLocalId() {
for (int localId = 0; localId < MAX_POINTERS; ++localId) {
if (isLocalIdAvailable(localId)) {
return localId;
}
}
return -1;
}
public Pointer get(int index) {
return pointers.get(index);
}
public int getPointerIndex(long id) {
int index = indexOf(id);
if (index != -1) {
// already exists, return it
return index;
}
if (pointers.size() >= MAX_POINTERS) {
// it's full
return -1;
}
// id 0 is reserved for mouse events
int localId = nextUnusedLocalId();
if (localId == -1) {
throw new AssertionError("pointers.size() < maxFingers implies that a local id is available");
}
Pointer pointer = new Pointer(id, localId);
pointers.add(pointer);
// return the index of the pointer
return pointers.size() - 1;
}
/**
* Initialize the motion event parameters.
*
* @param props the pointer properties
* @param coords the pointer coordinates
* @return The number of items initialized (the number of pointers).
*/
public int update(MotionEvent.PointerProperties[] props, MotionEvent.PointerCoords[] coords) {
int count = pointers.size();
for (int i = 0; i < count; ++i) {
Pointer pointer = pointers.get(i);
// id 0 is reserved for mouse events
props[i].id = pointer.getLocalId();
Point point = pointer.getPoint();
coords[i].x = point.getX();
coords[i].y = point.getY();
coords[i].pressure = pointer.getPressure();
}
cleanUp();
return count;
}
/**
* Remove all pointers which are UP.
*/
private void cleanUp() {
for (int i = pointers.size() - 1; i >= 0; --i) {
Pointer pointer = pointers.get(i);
if (pointer.isUp()) {
pointers.remove(i);
}
}
}
}

View File

@@ -76,35 +76,6 @@ public class ControlMessageReaderTest {
Assert.assertEquals(new String(text, StandardCharsets.US_ASCII), event.getText()); Assert.assertEquals(new String(text, StandardCharsets.US_ASCII), event.getText());
} }
@Test
@SuppressWarnings("checkstyle:MagicNumber")
public void testParseMouseEvent() throws IOException {
ControlMessageReader reader = new ControlMessageReader();
ByteArrayOutputStream bos = new ByteArrayOutputStream();
DataOutputStream dos = new DataOutputStream(bos);
dos.writeByte(ControlMessage.TYPE_INJECT_MOUSE_EVENT);
dos.writeByte(MotionEvent.ACTION_DOWN);
dos.writeInt(MotionEvent.BUTTON_PRIMARY);
dos.writeInt(100);
dos.writeInt(200);
dos.writeShort(1080);
dos.writeShort(1920);
byte[] packet = bos.toByteArray();
reader.readFrom(new ByteArrayInputStream(packet));
ControlMessage event = reader.next();
Assert.assertEquals(ControlMessage.TYPE_INJECT_MOUSE_EVENT, event.getType());
Assert.assertEquals(MotionEvent.ACTION_DOWN, event.getAction());
Assert.assertEquals(MotionEvent.BUTTON_PRIMARY, event.getButtons());
Assert.assertEquals(100, event.getPosition().getPoint().getX());
Assert.assertEquals(200, event.getPosition().getPoint().getY());
Assert.assertEquals(1080, event.getPosition().getScreenSize().getWidth());
Assert.assertEquals(1920, event.getPosition().getScreenSize().getHeight());
}
@Test @Test
@SuppressWarnings("checkstyle:MagicNumber") @SuppressWarnings("checkstyle:MagicNumber")
public void testParseTouchEvent() throws IOException { public void testParseTouchEvent() throws IOException {
@@ -114,12 +85,13 @@ public class ControlMessageReaderTest {
DataOutputStream dos = new DataOutputStream(bos); DataOutputStream dos = new DataOutputStream(bos);
dos.writeByte(ControlMessage.TYPE_INJECT_TOUCH_EVENT); dos.writeByte(ControlMessage.TYPE_INJECT_TOUCH_EVENT);
dos.writeByte(MotionEvent.ACTION_DOWN); dos.writeByte(MotionEvent.ACTION_DOWN);
dos.writeLong(-42); // fingerId dos.writeLong(-42); // pointerId
dos.writeInt(100); dos.writeInt(100);
dos.writeInt(200); dos.writeInt(200);
dos.writeShort(1080); dos.writeShort(1080);
dos.writeShort(1920); dos.writeShort(1920);
dos.writeShort(0xffff); // pressure dos.writeShort(0xffff); // pressure
dos.writeInt(MotionEvent.BUTTON_PRIMARY);
byte[] packet = bos.toByteArray(); byte[] packet = bos.toByteArray();
@@ -128,12 +100,13 @@ public class ControlMessageReaderTest {
Assert.assertEquals(ControlMessage.TYPE_INJECT_TOUCH_EVENT, event.getType()); Assert.assertEquals(ControlMessage.TYPE_INJECT_TOUCH_EVENT, event.getType());
Assert.assertEquals(MotionEvent.ACTION_DOWN, event.getAction()); Assert.assertEquals(MotionEvent.ACTION_DOWN, event.getAction());
Assert.assertEquals(-42, event.getFingerId()); Assert.assertEquals(-42, event.getPointerId());
Assert.assertEquals(100, event.getPosition().getPoint().getX()); Assert.assertEquals(100, event.getPosition().getPoint().getX());
Assert.assertEquals(200, event.getPosition().getPoint().getY()); Assert.assertEquals(200, event.getPosition().getPoint().getY());
Assert.assertEquals(1080, event.getPosition().getScreenSize().getWidth()); Assert.assertEquals(1080, event.getPosition().getScreenSize().getWidth());
Assert.assertEquals(1920, event.getPosition().getScreenSize().getHeight()); Assert.assertEquals(1920, event.getPosition().getScreenSize().getHeight());
Assert.assertEquals(1f, event.getPressure(), 0f); // must be exact Assert.assertEquals(1f, event.getPressure(), 0f); // must be exact
Assert.assertEquals(MotionEvent.BUTTON_PRIMARY, event.getButtons());
} }
@Test @Test