diff --git a/server/src/main/java/com/genymobile/scrcpy/Controller.java b/server/src/main/java/com/genymobile/scrcpy/Controller.java index e52a86dc..31672023 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Controller.java +++ b/server/src/main/java/com/genymobile/scrcpy/Controller.java @@ -13,6 +13,8 @@ import java.io.IOException; public class Controller { + private static final int MAX_FINGERS = 10; + private final Device device; private final DesktopConnection connection; private final DeviceMessageSender sender; @@ -23,10 +25,16 @@ public class Controller { private final MotionEvent.PointerProperties[] mousePointerProperties = {new MotionEvent.PointerProperties()}; private final MotionEvent.PointerCoords[] mousePointerCoords = {new MotionEvent.PointerCoords()}; + private long lastTouchDown; + private final FingersState fingersState = new FingersState(MAX_FINGERS); + private final MotionEvent.PointerProperties[] touchPointerProperties = new MotionEvent.PointerProperties[MAX_FINGERS]; + private final MotionEvent.PointerCoords[] touchPointerCoords = new MotionEvent.PointerCoords[MAX_FINGERS]; + 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 < MAX_FINGERS; ++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.getFingerId(), msg.getPosition(), msg.getPressure()); + break; case ControlMessage.TYPE_INJECT_SCROLL_EVENT: injectScroll(msg.getPosition(), msg.getHScroll(), msg.getVScroll()); break; @@ -164,6 +189,30 @@ public class Controller { return injectEvent(event); } + private boolean injectTouch(int action, long fingerId, Position position, float pressure) { + long now = SystemClock.uptimeMillis(); + if (action == MotionEvent.ACTION_DOWN) { + lastTouchDown = now; + } + Point point = device.getPhysicalPoint(position); + if (point == null) { + // ignore event + return false; + } + boolean ok = fingersState.set(fingerId, point, pressure, action == MotionEvent.ACTION_UP); + if (!ok) { + Ln.w("Too many fingers for touch event"); + return false; + } + // FAIL: action_up will always remove the finger, and the event will not be written! + int pointerCount = fingersState.update(touchPointerProperties, touchPointerCoords); + fingersState.cleanUp(); + Ln.w("pointerCount = " + pointerCount); + 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/Finger.java b/server/src/main/java/com/genymobile/scrcpy/Finger.java new file mode 100644 index 00000000..90e25f0b --- /dev/null +++ b/server/src/main/java/com/genymobile/scrcpy/Finger.java @@ -0,0 +1,41 @@ +package com.genymobile.scrcpy; + +public class Finger { + + private final long id; + private Point point; + private float pressure; + private boolean up; + + public Finger(long id) { + this.id = id; + } + + public long getId() { + return id; + } + + 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/FingersState.java b/server/src/main/java/com/genymobile/scrcpy/FingersState.java new file mode 100644 index 00000000..71a15784 --- /dev/null +++ b/server/src/main/java/com/genymobile/scrcpy/FingersState.java @@ -0,0 +1,108 @@ +package com.genymobile.scrcpy; + +import android.view.MotionEvent; + +public class FingersState { + + /** + * Array of enabled fingers (can contain "null" holes). + *

+ * Once a Finger (identified by its id received from the client) is enabled, it is never moved. + *

+ * Its index is its local identifier injected into MotionEvents. + */ + private final Finger[] fingers; + + public FingersState(int maxFingers) { + fingers = new Finger[maxFingers]; + } + + private int indexOf(long id) { + for (int i = 0; i < fingers.length; ++i) { + Finger finger = fingers[i]; + if (finger != null && finger.getId() == id) { + return i; + } + } + return -1; + } + + private int indexOfFirstEmpty() { + for (int i = 0; i < fingers.length; ++i) { + if (fingers[i] == null) { + return i; + } + } + return -1; + } + + private Finger create(long id) { + int index = indexOf(id); + if (index != -1) { + // already exists, return it + return fingers[index]; + } + int firstEmpty = indexOfFirstEmpty(); + if (firstEmpty == -1) { + // it's full + return null; + } + Finger finger = new Finger(id); + fingers[firstEmpty] = finger; + return finger; + } + + public boolean unset(long id) { + int index = indexOf(id); + if (index == -1) { + return false; + } + fingers[index] = null; + return true; + } + + public boolean set(long id, Point point, float pressure, boolean up) { + Finger finger = create(id); + if (finger == null) { + return false; + } + finger.setPoint(point); + finger.setPressure(pressure); + finger.setUp(up); + return true; + } + + public void cleanUp() { + for (int i = 0; i < fingers.length; ++i) { + if (fingers[i] != null && fingers[i].isUp()) { + fingers[i] = null; + } + } + } + + /** + * 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) { + int count = 0; + for (int i = 0; i < fingers.length; ++i) { + Finger finger = fingers[i]; + if (finger != null) { + // id 0 is reserved for mouse events + props[count].id = i + 1; + + Point point = finger.getPoint(); + coords[i].x = point.getX(); + coords[i].y = point.getY(); + coords[i].pressure = finger.getPressure(); + + ++count; + } + } + return count; + } +}