From 7cfefae5e110f32940f6ad35dbd45813d066f735 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sat, 12 Oct 2024 09:23:31 +0200 Subject: [PATCH] Move implicit displayId to Controller Remove from Device the functions using an implicit displayId. Move them to Controller, which knows best which displayId it must use. This will allow to properly dispatch events either to the origin display or to the virtual display created for mirroring. PR #5370 --- .../java/com/genymobile/scrcpy/Server.java | 3 +- .../genymobile/scrcpy/control/Controller.java | 67 ++++++++++++------- .../com/genymobile/scrcpy/device/Device.java | 34 +--------- 3 files changed, 48 insertions(+), 56 deletions(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/Server.java b/server/src/main/java/com/genymobile/scrcpy/Server.java index 9802e0f5..ed3ae669 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Server.java +++ b/server/src/main/java/com/genymobile/scrcpy/Server.java @@ -156,7 +156,8 @@ public final class Server { if (control) { ControlChannel controlChannel = connection.getControlChannel(); - Controller controller = new Controller(device, controlChannel, cleanUp, options.getClipboardAutosync(), options.getPowerOn()); + Controller controller = new Controller( + device, options.getDisplayId(), controlChannel, cleanUp, options.getClipboardAutosync(), options.getPowerOn()); device.setClipboardListener(text -> { DeviceMessage msg = DeviceMessage.createClipboard(text); controller.getSender().send(msg); diff --git a/server/src/main/java/com/genymobile/scrcpy/control/Controller.java b/server/src/main/java/com/genymobile/scrcpy/control/Controller.java index 8fa27e81..ee2e1749 100644 --- a/server/src/main/java/com/genymobile/scrcpy/control/Controller.java +++ b/server/src/main/java/com/genymobile/scrcpy/control/Controller.java @@ -14,6 +14,7 @@ import android.content.Intent; import android.os.Build; import android.os.SystemClock; import android.view.InputDevice; +import android.view.InputEvent; import android.view.KeyCharacterMap; import android.view.KeyEvent; import android.view.MotionEvent; @@ -37,6 +38,8 @@ public class Controller implements AsyncProcessor { private UhidManager uhidManager; private final Device device; + private final int displayId; + private final boolean supportsInputEvents; private final ControlChannel controlChannel; private final CleanUp cleanUp; private final DeviceMessageSender sender; @@ -52,14 +55,20 @@ public class Controller implements AsyncProcessor { private boolean keepPowerModeOff; - public Controller(Device device, ControlChannel controlChannel, CleanUp cleanUp, boolean clipboardAutosync, boolean powerOn) { + public Controller(Device device, int displayId, ControlChannel controlChannel, CleanUp cleanUp, boolean clipboardAutosync, boolean powerOn) { this.device = device; + this.displayId = displayId; this.controlChannel = controlChannel; this.cleanUp = cleanUp; this.clipboardAutosync = clipboardAutosync; this.powerOn = powerOn; initPointers(); sender = new DeviceMessageSender(controlChannel); + + supportsInputEvents = Device.supportsInputEvents(displayId); + if (!supportsInputEvents) { + Ln.w("Input events are not supported for secondary displays before Android 10"); + } } private UhidManager getUhidManager() { @@ -86,7 +95,7 @@ public class Controller implements AsyncProcessor { private void control() throws IOException { // on start, power on the device if (powerOn && !Device.isScreenOn()) { - device.pressReleaseKeycode(KeyEvent.KEYCODE_POWER, Device.INJECT_MODE_ASYNC); + Device.pressReleaseKeycode(KeyEvent.KEYCODE_POWER, displayId, Device.INJECT_MODE_ASYNC); // dirty hack // After POWER is injected, the device is powered on asynchronously. @@ -154,27 +163,27 @@ public class Controller implements AsyncProcessor { switch (msg.getType()) { case ControlMessage.TYPE_INJECT_KEYCODE: - if (device.supportsInputEvents()) { + if (supportsInputEvents) { injectKeycode(msg.getAction(), msg.getKeycode(), msg.getRepeat(), msg.getMetaState()); } break; case ControlMessage.TYPE_INJECT_TEXT: - if (device.supportsInputEvents()) { + if (supportsInputEvents) { injectText(msg.getText()); } break; case ControlMessage.TYPE_INJECT_TOUCH_EVENT: - if (device.supportsInputEvents()) { + if (supportsInputEvents) { injectTouch(msg.getAction(), msg.getPointerId(), msg.getPosition(), msg.getPressure(), msg.getActionButton(), msg.getButtons()); } break; case ControlMessage.TYPE_INJECT_SCROLL_EVENT: - if (device.supportsInputEvents()) { + if (supportsInputEvents) { injectScroll(msg.getPosition(), msg.getHScroll(), msg.getVScroll(), msg.getButtons()); } break; case ControlMessage.TYPE_BACK_OR_SCREEN_ON: - if (device.supportsInputEvents()) { + if (supportsInputEvents) { pressBackOrTurnScreenOn(msg.getAction()); } break; @@ -194,7 +203,7 @@ public class Controller implements AsyncProcessor { setClipboard(msg.getText(), msg.getPaste(), msg.getSequence()); break; case ControlMessage.TYPE_SET_SCREEN_POWER_MODE: - if (device.supportsInputEvents()) { + if (supportsInputEvents) { int mode = msg.getAction(); boolean setPowerModeOk = Device.setScreenPowerMode(mode); if (setPowerModeOk) { @@ -208,7 +217,7 @@ public class Controller implements AsyncProcessor { } break; case ControlMessage.TYPE_ROTATE_DEVICE: - device.rotateDevice(); + Device.rotateDevice(displayId); break; case ControlMessage.TYPE_UHID_CREATE: getUhidManager().open(msg.getId(), msg.getText(), msg.getData()); @@ -233,7 +242,7 @@ public class Controller implements AsyncProcessor { if (keepPowerModeOff && action == KeyEvent.ACTION_UP && (keycode == KeyEvent.KEYCODE_POWER || keycode == KeyEvent.KEYCODE_WAKEUP)) { schedulePowerModeOff(); } - return device.injectKeyEvent(action, keycode, repeat, metaState, Device.INJECT_MODE_ASYNC); + return injectKeyEvent(action, keycode, repeat, metaState, Device.INJECT_MODE_ASYNC); } private boolean injectChar(char c) { @@ -244,7 +253,7 @@ public class Controller implements AsyncProcessor { return false; } for (KeyEvent event : events) { - if (!device.injectEvent(event, Device.INJECT_MODE_ASYNC)) { + if (!injectEvent(event, Device.INJECT_MODE_ASYNC)) { return false; } } @@ -325,7 +334,7 @@ public class Controller implements AsyncProcessor { // First button pressed: ACTION_DOWN MotionEvent downEvent = MotionEvent.obtain(lastTouchDown, now, MotionEvent.ACTION_DOWN, pointerCount, pointerProperties, pointerCoords, 0, buttons, 1f, 1f, DEFAULT_DEVICE_ID, 0, source, 0); - if (!device.injectEvent(downEvent, Device.INJECT_MODE_ASYNC)) { + if (!injectEvent(downEvent, Device.INJECT_MODE_ASYNC)) { return false; } } @@ -336,7 +345,7 @@ public class Controller implements AsyncProcessor { if (!InputManager.setActionButton(pressEvent, actionButton)) { return false; } - if (!device.injectEvent(pressEvent, Device.INJECT_MODE_ASYNC)) { + if (!injectEvent(pressEvent, Device.INJECT_MODE_ASYNC)) { return false; } @@ -350,7 +359,7 @@ public class Controller implements AsyncProcessor { if (!InputManager.setActionButton(releaseEvent, actionButton)) { return false; } - if (!device.injectEvent(releaseEvent, Device.INJECT_MODE_ASYNC)) { + if (!injectEvent(releaseEvent, Device.INJECT_MODE_ASYNC)) { return false; } @@ -358,7 +367,7 @@ public class Controller implements AsyncProcessor { // Last button released: ACTION_UP MotionEvent upEvent = MotionEvent.obtain(lastTouchDown, now, MotionEvent.ACTION_UP, pointerCount, pointerProperties, pointerCoords, 0, buttons, 1f, 1f, DEFAULT_DEVICE_ID, 0, source, 0); - if (!device.injectEvent(upEvent, Device.INJECT_MODE_ASYNC)) { + if (!injectEvent(upEvent, Device.INJECT_MODE_ASYNC)) { return false; } } @@ -369,7 +378,7 @@ public class Controller implements AsyncProcessor { MotionEvent event = MotionEvent.obtain(lastTouchDown, now, action, pointerCount, pointerProperties, pointerCoords, 0, buttons, 1f, 1f, DEFAULT_DEVICE_ID, 0, source, 0); - return device.injectEvent(event, Device.INJECT_MODE_ASYNC); + return injectEvent(event, Device.INJECT_MODE_ASYNC); } private boolean injectScroll(Position position, float hScroll, float vScroll, int buttons) { @@ -391,7 +400,7 @@ public class Controller implements AsyncProcessor { MotionEvent event = MotionEvent.obtain(lastTouchDown, now, MotionEvent.ACTION_SCROLL, 1, pointerProperties, pointerCoords, 0, buttons, 1f, 1f, DEFAULT_DEVICE_ID, 0, InputDevice.SOURCE_MOUSE, 0); - return device.injectEvent(event, Device.INJECT_MODE_ASYNC); + return injectEvent(event, Device.INJECT_MODE_ASYNC); } /** @@ -406,7 +415,7 @@ public class Controller implements AsyncProcessor { private boolean pressBackOrTurnScreenOn(int action) { if (Device.isScreenOn()) { - return device.injectKeyEvent(action, KeyEvent.KEYCODE_BACK, 0, 0, Device.INJECT_MODE_ASYNC); + return injectKeyEvent(action, KeyEvent.KEYCODE_BACK, 0, 0, Device.INJECT_MODE_ASYNC); } // Screen is off @@ -419,15 +428,15 @@ public class Controller implements AsyncProcessor { if (keepPowerModeOff) { schedulePowerModeOff(); } - return device.pressReleaseKeycode(KeyEvent.KEYCODE_POWER, Device.INJECT_MODE_ASYNC); + return pressReleaseKeycode(KeyEvent.KEYCODE_POWER, Device.INJECT_MODE_ASYNC); } private void getClipboard(int copyKey) { // On Android >= 7, press the COPY or CUT key if requested - if (copyKey != ControlMessage.COPY_KEY_NONE && Build.VERSION.SDK_INT >= AndroidVersions.API_24_ANDROID_7_0 && device.supportsInputEvents()) { + if (copyKey != ControlMessage.COPY_KEY_NONE && Build.VERSION.SDK_INT >= AndroidVersions.API_24_ANDROID_7_0 && supportsInputEvents) { int key = copyKey == ControlMessage.COPY_KEY_COPY ? KeyEvent.KEYCODE_COPY : KeyEvent.KEYCODE_CUT; // Wait until the event is finished, to ensure that the clipboard text we read just after is the correct one - device.pressReleaseKeycode(key, Device.INJECT_MODE_WAIT_FOR_FINISH); + pressReleaseKeycode(key, Device.INJECT_MODE_WAIT_FOR_FINISH); } // If clipboard autosync is enabled, then the device clipboard is synchronized to the computer clipboard whenever it changes, in @@ -449,8 +458,8 @@ public class Controller implements AsyncProcessor { } // On Android >= 7, also press the PASTE key if requested - if (paste && Build.VERSION.SDK_INT >= AndroidVersions.API_24_ANDROID_7_0 && device.supportsInputEvents()) { - device.pressReleaseKeycode(KeyEvent.KEYCODE_PASTE, Device.INJECT_MODE_ASYNC); + if (paste && Build.VERSION.SDK_INT >= AndroidVersions.API_24_ANDROID_7_0 && supportsInputEvents) { + pressReleaseKeycode(KeyEvent.KEYCODE_PASTE, Device.INJECT_MODE_ASYNC); } if (sequence != ControlMessage.SEQUENCE_INVALID) { @@ -466,4 +475,16 @@ public class Controller implements AsyncProcessor { Intent intent = new Intent("android.settings.HARD_KEYBOARD_SETTINGS"); ServiceManager.getActivityManager().startActivity(intent); } + + private boolean injectEvent(InputEvent event, int injectMode) { + return Device.injectEvent(event, displayId, injectMode); + } + + private boolean injectKeyEvent(int action, int keyCode, int repeat, int metaState, int injectMode) { + return Device.injectKeyEvent(action, keyCode, repeat, metaState, displayId, injectMode); + } + + private boolean pressReleaseKeycode(int keyCode, int injectMode) { + return Device.pressReleaseKeycode(keyCode, displayId, injectMode); + } } 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 63e33988..7972d740 100644 --- a/server/src/main/java/com/genymobile/scrcpy/device/Device.java +++ b/server/src/main/java/com/genymobile/scrcpy/device/Device.java @@ -43,18 +43,9 @@ public final class Device { private ClipboardListener clipboardListener; private final AtomicBoolean isSettingClipboard = new AtomicBoolean(); - /** - * Logical display identifier - */ - private final int displayId; - - private final boolean supportsInputEvents; - private final AtomicReference screenInfo = new AtomicReference<>(); // set by the ScreenCapture instance public Device(Options options) { - displayId = options.getDisplayId(); - if (options.getControl() && options.getClipboardAutosync()) { // If control and autosync are enabled, synchronize Android clipboard to the computer automatically ClipboardManager clipboardManager = ServiceManager.getClipboardManager(); @@ -80,12 +71,6 @@ public final class Device { Ln.w("No clipboard manager, copy-paste between device and computer will not work"); } } - - // main display or any display on Android >= 10 - supportsInputEvents = options.getDisplayId() == 0 || Build.VERSION.SDK_INT >= AndroidVersions.API_29_ANDROID_10; - if (!supportsInputEvents) { - Ln.w("Input events are not supported for secondary displays before Android 10"); - } } public Point getPhysicalPoint(Position position) { @@ -121,13 +106,10 @@ public final class Device { } public static boolean supportsInputEvents(int displayId) { + // main display or any display on Android >= 10 return displayId == 0 || Build.VERSION.SDK_INT >= AndroidVersions.API_29_ANDROID_10; } - public boolean supportsInputEvents() { - return supportsInputEvents; - } - public void setScreenInfo(ScreenInfo screenInfo) { this.screenInfo.set(screenInfo); } @@ -144,10 +126,6 @@ public final class Device { return ServiceManager.getInputManager().injectInputEvent(inputEvent, injectMode); } - public boolean injectEvent(InputEvent event, int injectMode) { - return injectEvent(event, displayId, injectMode); - } - public static boolean injectKeyEvent(int action, int keyCode, int repeat, int metaState, int displayId, int injectMode) { long now = SystemClock.uptimeMillis(); KeyEvent event = new KeyEvent(now, now, action, keyCode, repeat, metaState, KeyCharacterMap.VIRTUAL_KEYBOARD, 0, 0, @@ -155,19 +133,11 @@ public final class Device { return injectEvent(event, displayId, injectMode); } - public boolean injectKeyEvent(int action, int keyCode, int repeat, int metaState, int injectMode) { - return injectKeyEvent(action, keyCode, repeat, metaState, displayId, injectMode); - } - public static boolean pressReleaseKeycode(int keyCode, int displayId, int injectMode) { return injectKeyEvent(KeyEvent.ACTION_DOWN, keyCode, 0, 0, displayId, injectMode) && injectKeyEvent(KeyEvent.ACTION_UP, keyCode, 0, 0, displayId, injectMode); } - public boolean pressReleaseKeycode(int keyCode, int injectMode) { - return pressReleaseKeycode(keyCode, displayId, injectMode); - } - public static boolean isScreenOn() { return ServiceManager.getPowerManager().isScreenOn(); } @@ -277,7 +247,7 @@ public final class Device { /** * Disable auto-rotation (if enabled), set the screen rotation and re-enable auto-rotation (if it was enabled). */ - public void rotateDevice() { + public static void rotateDevice(int displayId) { WindowManager wm = ServiceManager.getWindowManager(); boolean accelerometerRotation = !wm.isRotationFrozen(displayId);