From 9b03bfc3ae881f639f9c4bb381eef7365b785437 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 10 Nov 2024 13:47:02 +0100 Subject: [PATCH] Handle virtual display rotation Listen to display size changes and rotate the virtual display accordingly. Note: use `git show -b` to Show this commit ignoring whitespace changes. Fixes #5428 Refs #5370 PR #5455 --- .../scrcpy/video/NewDisplayCapture.java | 73 +++++++++++++++---- 1 file changed, 59 insertions(+), 14 deletions(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/video/NewDisplayCapture.java b/server/src/main/java/com/genymobile/scrcpy/video/NewDisplayCapture.java index cc54876a..6ce50521 100644 --- a/server/src/main/java/com/genymobile/scrcpy/video/NewDisplayCapture.java +++ b/server/src/main/java/com/genymobile/scrcpy/video/NewDisplayCapture.java @@ -6,10 +6,13 @@ import com.genymobile.scrcpy.control.PositionMapper; import com.genymobile.scrcpy.device.DisplayInfo; import com.genymobile.scrcpy.device.NewDisplay; import com.genymobile.scrcpy.device.Size; +import com.genymobile.scrcpy.opengl.AffineOpenGLFilter; +import com.genymobile.scrcpy.opengl.OpenGLFilter; +import com.genymobile.scrcpy.opengl.OpenGLRunner; +import com.genymobile.scrcpy.util.AffineMatrix; import com.genymobile.scrcpy.util.Ln; import com.genymobile.scrcpy.wrappers.ServiceManager; -import android.hardware.display.DisplayManager; import android.hardware.display.VirtualDisplay; import android.os.Build; import android.view.Surface; @@ -19,8 +22,8 @@ import java.io.IOException; public class NewDisplayCapture extends SurfaceCapture { // Internal fields copied from android.hardware.display.DisplayManager - private static final int VIRTUAL_DISPLAY_FLAG_PUBLIC = DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC; - private static final int VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY = DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY; + private static final int VIRTUAL_DISPLAY_FLAG_PUBLIC = android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC; + private static final int VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY = android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY; private static final int VIRTUAL_DISPLAY_FLAG_SUPPORTS_TOUCH = 1 << 6; private static final int VIRTUAL_DISPLAY_FLAG_ROTATES_WITH_CONTENT = 1 << 7; private static final int VIRTUAL_DISPLAY_FLAG_DESTROY_CONTENT_ON_REMOVAL = 1 << 8; @@ -35,12 +38,18 @@ public class NewDisplayCapture extends SurfaceCapture { private final VirtualDisplayListener vdListener; private final NewDisplay newDisplay; + private final DisplaySizeMonitor displaySizeMonitor = new DisplaySizeMonitor(); + + private AffineMatrix displayTransform; + private OpenGLRunner glRunner; + private Size mainDisplaySize; private int mainDisplayDpi; private int maxSize; // only used if newDisplay.getSize() != null private VirtualDisplay virtualDisplay; - private Size size; + private Size size; // the logical size of the display (including rotation) + private Size physicalSize; // the physical size of the display (without rotation) private int dpi; public NewDisplayCapture(VirtualDisplayListener vdListener, Options options) { @@ -69,11 +78,27 @@ public class NewDisplayCapture extends SurfaceCapture { @Override public void prepare() { - if (!newDisplay.hasExplicitSize()) { - size = mainDisplaySize.limit(maxSize).round8(); - } - if (!newDisplay.hasExplicitDpi()) { - dpi = scaleDpi(mainDisplaySize, mainDisplayDpi, size); + if (virtualDisplay == null) { + if (!newDisplay.hasExplicitSize()) { + size = mainDisplaySize.limit(maxSize).round8(); + } + if (!newDisplay.hasExplicitDpi()) { + dpi = scaleDpi(mainDisplaySize, mainDisplayDpi, size); + } + + physicalSize = size; + // Set the current display size to avoid an unnecessary call to invalidate() + displaySizeMonitor.setSessionDisplaySize(size); + } else { + DisplayInfo displayInfo = ServiceManager.getDisplayManager().getDisplayInfo(virtualDisplay.getDisplay().getDisplayId()); + size = displayInfo.getSize(); + dpi = displayInfo.getDpi(); + + VideoFilter displayFilter = new VideoFilter(size); + displayFilter.addRotation(displayInfo.getRotation()); + // The display info gives the oriented size, but the virtual display video always remains in the origin orientation + displayTransform = displayFilter.getInverseTransform(); + physicalSize = displayFilter.getOutputSize(); } } @@ -100,28 +125,48 @@ public class NewDisplayCapture extends SurfaceCapture { .createNewVirtualDisplay("scrcpy", size.getWidth(), size.getHeight(), dpi, surface, flags); virtualDisplayId = virtualDisplay.getDisplay().getDisplayId(); Ln.i("New display: " + size.getWidth() + "x" + size.getHeight() + "/" + dpi + " (id=" + virtualDisplayId + ")"); + + displaySizeMonitor.start(virtualDisplayId, this::invalidate); } catch (Exception e) { Ln.e("Could not create display", e); throw new AssertionError("Could not create display"); } - - if (vdListener != null) { - PositionMapper positionMapper = new PositionMapper(size, null); - vdListener.onNewVirtualDisplay(virtualDisplayId, positionMapper); - } } @Override public void start(Surface surface) throws IOException { + if (displayTransform != null) { + assert glRunner == null; + OpenGLFilter glFilter = new AffineOpenGLFilter(displayTransform); + glRunner = new OpenGLRunner(glFilter); + surface = glRunner.start(physicalSize, size, surface); + } + if (virtualDisplay == null) { startNew(surface); } else { virtualDisplay.setSurface(surface); } + + if (vdListener != null) { + // The virtual display rotation must only be applied to video, it is already taken into account when injecting events! + PositionMapper positionMapper = PositionMapper.create(size, null, size); + vdListener.onNewVirtualDisplay(virtualDisplay.getDisplay().getDisplayId(), positionMapper); + } + } + + @Override + public void stop() { + if (glRunner != null) { + glRunner.stopAndRelease(); + glRunner = null; + } } @Override public void release() { + displaySizeMonitor.stopAndRelease(); + if (virtualDisplay != null) { virtualDisplay.release(); virtualDisplay = null;