diff --git a/server/src/main/java/com/genymobile/scrcpy/control/PositionMapper.java b/server/src/main/java/com/genymobile/scrcpy/control/PositionMapper.java new file mode 100644 index 00000000..2ebb5961 --- /dev/null +++ b/server/src/main/java/com/genymobile/scrcpy/control/PositionMapper.java @@ -0,0 +1,48 @@ +package com.genymobile.scrcpy.control; + +import com.genymobile.scrcpy.device.Point; +import com.genymobile.scrcpy.device.Position; +import com.genymobile.scrcpy.device.Size; +import com.genymobile.scrcpy.video.ScreenInfo; + +import android.graphics.Rect; + +public final class PositionMapper { + + private final Size videoSize; + private final Rect contentRect; + private final int coordsRotation; + + public PositionMapper(Size videoSize, Rect contentRect, int videoRotation) { + this.videoSize = videoSize; + this.contentRect = contentRect; + this.coordsRotation = reverseRotation(videoRotation); + } + + public static PositionMapper from(ScreenInfo screenInfo) { + // ignore the locked video orientation, the events will apply in coordinates considered in the physical device orientation + Size videoSize = screenInfo.getUnlockedVideoSize(); + return new PositionMapper(videoSize, screenInfo.getContentRect(), screenInfo.getVideoRotation()); + } + + private static int reverseRotation(int rotation) { + return (4 - rotation) % 4; + } + + public Point map(Position position) { + // reverse the video rotation to apply the events + Position devicePosition = position.rotate(coordsRotation); + + Size clientVideoSize = devicePosition.getScreenSize(); + if (!videoSize.equals(clientVideoSize)) { + // The client sends a click relative to a video with wrong dimensions, + // the device may have been rotated since the event was generated, so ignore the event + return null; + } + + Point point = devicePosition.getPoint(); + int convertedX = contentRect.left + point.getX() * contentRect.width() / videoSize.getWidth(); + int convertedY = contentRect.top + point.getY() * contentRect.height() / videoSize.getHeight(); + return new Point(convertedX, convertedY); + } +} diff --git a/server/src/main/java/com/genymobile/scrcpy/device/Device.java b/server/src/main/java/com/genymobile/scrcpy/device/Device.java index 1765ccf2..0977a2b7 100644 --- a/server/src/main/java/com/genymobile/scrcpy/device/Device.java +++ b/server/src/main/java/com/genymobile/scrcpy/device/Device.java @@ -1,8 +1,8 @@ package com.genymobile.scrcpy.device; import com.genymobile.scrcpy.AndroidVersions; +import com.genymobile.scrcpy.control.PositionMapper; import com.genymobile.scrcpy.util.Ln; -import com.genymobile.scrcpy.video.ScreenInfo; import com.genymobile.scrcpy.wrappers.ClipboardManager; import com.genymobile.scrcpy.wrappers.DisplayControl; import com.genymobile.scrcpy.wrappers.InputManager; @@ -10,7 +10,6 @@ import com.genymobile.scrcpy.wrappers.ServiceManager; import com.genymobile.scrcpy.wrappers.SurfaceControl; import com.genymobile.scrcpy.wrappers.WindowManager; -import android.graphics.Rect; import android.os.Build; import android.os.IBinder; import android.os.SystemClock; @@ -33,34 +32,17 @@ public final class Device { public static final int LOCK_VIDEO_ORIENTATION_UNLOCKED = -1; public static final int LOCK_VIDEO_ORIENTATION_INITIAL = -2; - private final AtomicReference screenInfo = new AtomicReference<>(); // set by the ScreenCapture instance + private final AtomicReference positionMapper = new AtomicReference<>(); // set by the ScreenCapture instance public Point getPhysicalPoint(Position position) { // it hides the field on purpose, to read it with atomic access @SuppressWarnings("checkstyle:HiddenField") - ScreenInfo screenInfo = this.screenInfo.get(); - if (screenInfo == null) { + PositionMapper positionMapper = this.positionMapper.get(); + if (positionMapper == null) { return null; } - // ignore the locked video orientation, the events will apply in coordinates considered in the physical device orientation - Size unlockedVideoSize = screenInfo.getUnlockedVideoSize(); - - int reverseVideoRotation = screenInfo.getReverseVideoRotation(); - // reverse the video rotation to apply the events - Position devicePosition = position.rotate(reverseVideoRotation); - - Size clientVideoSize = devicePosition.getScreenSize(); - if (!unlockedVideoSize.equals(clientVideoSize)) { - // The client sends a click relative to a video with wrong dimensions, - // the device may have been rotated since the event was generated, so ignore the event - return null; - } - Rect contentRect = screenInfo.getContentRect(); - Point point = devicePosition.getPoint(); - int convertedX = contentRect.left + point.getX() * contentRect.width() / unlockedVideoSize.getWidth(); - int convertedY = contentRect.top + point.getY() * contentRect.height() / unlockedVideoSize.getHeight(); - return new Point(convertedX, convertedY); + return positionMapper.map(position); } public static String getDeviceName() { @@ -72,8 +54,8 @@ public final class Device { return displayId == 0 || Build.VERSION.SDK_INT >= AndroidVersions.API_29_ANDROID_10; } - public void setScreenInfo(ScreenInfo screenInfo) { - this.screenInfo.set(screenInfo); + public void setPositionMapper(PositionMapper positionMapper) { + this.positionMapper.set(positionMapper); } public static boolean injectEvent(InputEvent inputEvent, int displayId, int injectMode) { diff --git a/server/src/main/java/com/genymobile/scrcpy/video/ScreenCapture.java b/server/src/main/java/com/genymobile/scrcpy/video/ScreenCapture.java index f71ff020..066c9ae4 100644 --- a/server/src/main/java/com/genymobile/scrcpy/video/ScreenCapture.java +++ b/server/src/main/java/com/genymobile/scrcpy/video/ScreenCapture.java @@ -1,6 +1,7 @@ package com.genymobile.scrcpy.video; import com.genymobile.scrcpy.AndroidVersions; +import com.genymobile.scrcpy.control.PositionMapper; import com.genymobile.scrcpy.device.ConfigurationException; import com.genymobile.scrcpy.device.Device; import com.genymobile.scrcpy.device.DisplayInfo; @@ -132,7 +133,8 @@ public class ScreenCapture extends SurfaceCapture { } } - device.setScreenInfo(screenInfo); + PositionMapper positionMapper = PositionMapper.from(screenInfo); + device.setPositionMapper(positionMapper); } @Override