From f765aae352ecc0404ab483ec3255e7e1608ee128 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 15 Sep 2019 22:28:59 +0200 Subject: [PATCH] Inject touch events on the server On receiving an "inject touch" control message, update the local pointers state and inject touches. --- .../com/genymobile/scrcpy/Controller.java | 64 +++++++++++ .../java/com/genymobile/scrcpy/Pointer.java | 55 ++++++++++ .../com/genymobile/scrcpy/PointersState.java | 103 ++++++++++++++++++ 3 files changed, 222 insertions(+) create mode 100644 server/src/main/java/com/genymobile/scrcpy/Pointer.java create mode 100644 server/src/main/java/com/genymobile/scrcpy/PointersState.java diff --git a/server/src/main/java/com/genymobile/scrcpy/Controller.java b/server/src/main/java/com/genymobile/scrcpy/Controller.java index cbc4aec4..5ea712d4 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Controller.java +++ b/server/src/main/java/com/genymobile/scrcpy/Controller.java @@ -23,10 +23,18 @@ public class Controller { private final MotionEvent.PointerProperties[] mousePointerProperties = {new MotionEvent.PointerProperties()}; private final MotionEvent.PointerCoords[] mousePointerCoords = {new MotionEvent.PointerCoords()}; + private long lastTouchDown; + private final PointersState pointersState = new PointersState(); + private final MotionEvent.PointerProperties[] touchPointerProperties = + new MotionEvent.PointerProperties[PointersState.MAX_POINTERS]; + private final MotionEvent.PointerCoords[] touchPointerCoords = + new MotionEvent.PointerCoords[PointersState.MAX_POINTERS]; + public Controller(Device device, DesktopConnection connection) { this.device = device; this.connection = connection; initMousePointer(); + initTouchPointers(); sender = new DeviceMessageSender(connection); } @@ -41,6 +49,20 @@ public class Controller { coords.size = 1; } + private void initTouchPointers() { + for (int i = 0; i < PointersState.MAX_POINTERS; ++i) { + MotionEvent.PointerProperties props = new MotionEvent.PointerProperties(); + props.toolType = MotionEvent.TOOL_TYPE_FINGER; + + MotionEvent.PointerCoords coords = new MotionEvent.PointerCoords(); + coords.orientation = 0; + coords.size = 1; + + touchPointerProperties[i] = props; + touchPointerCoords[i] = coords; + } + } + private void setMousePointerCoords(Point point) { MotionEvent.PointerCoords coords = mousePointerCoords[0]; coords.x = point.getX(); @@ -90,6 +112,9 @@ public class Controller { case ControlMessage.TYPE_INJECT_MOUSE_EVENT: injectMouse(msg.getAction(), msg.getButtons(), msg.getPosition()); break; + case ControlMessage.TYPE_INJECT_TOUCH_EVENT: + injectTouch(msg.getAction(), msg.getPointerId(), msg.getPosition(), msg.getPressure()); + break; case ControlMessage.TYPE_INJECT_SCROLL_EVENT: injectScroll(msg.getPosition(), msg.getHScroll(), msg.getVScroll()); break; @@ -164,6 +189,45 @@ public class Controller { return injectEvent(event); } + private boolean injectTouch(int action, long pointerId, Position position, float pressure) { + 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(touchPointerProperties, touchPointerCoords); + + if (pointerCount == 1) { + if (action == MotionEvent.ACTION_DOWN) { + 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, InputDevice.SOURCE_TOUCHSCREEN, 0); + return injectEvent(event); + } + private boolean injectScroll(Position position, int hScroll, int vScroll) { long now = SystemClock.uptimeMillis(); Point point = device.getPhysicalPoint(position); diff --git a/server/src/main/java/com/genymobile/scrcpy/Pointer.java b/server/src/main/java/com/genymobile/scrcpy/Pointer.java new file mode 100644 index 00000000..b89cc256 --- /dev/null +++ b/server/src/main/java/com/genymobile/scrcpy/Pointer.java @@ -0,0 +1,55 @@ +package com.genymobile.scrcpy; + +public class Pointer { + + /** + * Pointer id as received from the client. + */ + 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 Point point; + private float pressure; + private boolean up; + + public Pointer(long id, int localId) { + this.id = id; + this.localId = localId; + } + + public long getId() { + return id; + } + + public int getLocalId() { + return localId; + } + + public Point getPoint() { + return point; + } + + public void setPoint(Point point) { + this.point = point; + } + + public float getPressure() { + return pressure; + } + + public void setPressure(float pressure) { + this.pressure = pressure; + } + + public boolean isUp() { + return up; + } + + public void setUp(boolean up) { + this.up = up; + } +} diff --git a/server/src/main/java/com/genymobile/scrcpy/PointersState.java b/server/src/main/java/com/genymobile/scrcpy/PointersState.java new file mode 100644 index 00000000..d8daaff2 --- /dev/null +++ b/server/src/main/java/com/genymobile/scrcpy/PointersState.java @@ -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 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); + } + } + } +}