Compare commits

...

5 Commits

Author SHA1 Message Date
330264cdc6 issue5542 2024-11-27 18:37:50 +01:00
3e689020ba Fix null return value in DisplayManager.toString()
Ensure DisplayListener.toString() returns a non-null value to prevent a
NullPointerException on certain devices.

Fixes #5537 <https://github.com/Genymobile/scrcpy/issues/5537>
2024-11-27 07:45:35 +01:00
3d1f036c04 Rollback to old --turn-screen-off for Android 15
When the screen is turned off with the new display power method
introduced in Android 15, video mirroring freezes.

Use the Android 14 method for Android 15.

Refs 58ba00fa06
Refs #5418 <https://github.com/Genymobile/scrcpy/pull/5418>
Fixes #5530 <https://github.com/Genymobile/scrcpy/issues/5530>
2024-11-26 15:55:16 +01:00
3d5294c1e5 Set main display power for virtual display
Change the display power of the main display when mirroring a virtual
display, to make it possible to turn off the screen.

Fixes #5522 <https://github.com/Genymobile/scrcpy/issues/5522>
Refs #5530 <https://github.com/Genymobile/scrcpy/issues/5530>
2024-11-26 15:43:41 +01:00
1d2f16dbb5 Fix documentation about default mouse mode
When video playback is turned off, the default mouse mode has changed
from "uhid" to "disabled" in 2c25fd7a80.

Update the documentation accordingly.

Refs #5410 <https://github.com/Genymobile/scrcpy/issues/5410>
Refs #5542 <https://github.com/Genymobile/scrcpy/issues/5542>
2024-11-26 14:10:11 +01:00
5 changed files with 53 additions and 24 deletions

View File

@ -23,14 +23,20 @@ To control the device without mirroring:
scrcpy --no-video --no-audio scrcpy --no-video --no-audio
``` ```
By default, mouse mode is switched to UHID if video mirroring is disabled (a By default, the mouse is disabled when video playback is turned off.
relative mouse mode is required).
To control the device using a relative mouse, enable UHID mouse mode:
```bash
scrcpy --no-video --no-audio --mouse=uhid
scrcpy --no-video --no-audio -M # short version
```
To also use a UHID keyboard, set it explicitly: To also use a UHID keyboard, set it explicitly:
```bash ```bash
scrcpy --no-video --no-audio --keyboard=uhid scrcpy --no-video --no-audio --mouse=uhid --keyboard=uhid
scrcpy --no-video --no-audio -K # short version scrcpy --no-video --no-audio -MK # short version
``` ```
To use AOA instead (over USB only): To use AOA instead (over USB only):

View File

@ -207,13 +207,15 @@ public final class CleanUp {
} }
} }
if (displayId != Device.DISPLAY_ID_NONE && Device.isScreenOn(displayId)) { // Change the power of the main display when mirroring a virtual display
int targetDisplayId = displayId != Device.DISPLAY_ID_NONE ? displayId : 0;
if (Device.isScreenOn(targetDisplayId)) {
if (powerOffScreen) { if (powerOffScreen) {
Ln.i("Power off screen"); Ln.i("Power off screen");
Device.powerOffScreen(displayId); Device.powerOffScreen(targetDisplayId);
} else if (restoreDisplayPower) { } else if (restoreDisplayPower) {
Ln.i("Restoring display power"); Ln.i("Restoring display power");
Device.setDisplayPower(displayId, true); Device.setDisplayPower(targetDisplayId, true);
} }
} }

View File

@ -281,7 +281,7 @@ public class Controller implements AsyncProcessor, VirtualDisplayListener {
setClipboard(msg.getText(), msg.getPaste(), msg.getSequence()); setClipboard(msg.getText(), msg.getPaste(), msg.getSequence());
break; break;
case ControlMessage.TYPE_SET_DISPLAY_POWER: case ControlMessage.TYPE_SET_DISPLAY_POWER:
if (supportsInputEvents && displayId != Device.DISPLAY_ID_NONE) { if (supportsInputEvents) {
setDisplayPower(msg.getOn()); setDisplayPower(msg.getOn());
} }
break; break;
@ -356,16 +356,27 @@ public class Controller implements AsyncProcessor, VirtualDisplayListener {
// it hides the field on purpose, to read it with atomic access // it hides the field on purpose, to read it with atomic access
@SuppressWarnings("checkstyle:HiddenField") @SuppressWarnings("checkstyle:HiddenField")
DisplayData displayData = this.displayData.get(); DisplayData displayData = this.displayData.get();
assert displayData != null : "Cannot receive a touch event without a display"; // In scrcpy, displayData should never be null (a touch event can only be generated from the client on a video frame), but it is possible
// to send events without video playback using scrcpy-server alone (except for virtual displays).
assert displayData != null || displayId != Device.DISPLAY_ID_NONE : "Cannot receive a touch event without a display";
Point point = displayData.positionMapper.map(position); Point point;
if (point == null) { int targetDisplayId;
if (Ln.isEnabled(Ln.Level.VERBOSE)) { if (displayData != null) {
Size eventSize = position.getScreenSize(); point = displayData.positionMapper.map(position);
Size currentSize = displayData.positionMapper.getVideoSize(); if (point == null) {
Ln.v("Ignore touch event generated for size " + eventSize + " (current size is " + currentSize + ")"); if (Ln.isEnabled(Ln.Level.VERBOSE)) {
Size eventSize = position.getScreenSize();
Size currentSize = displayData.positionMapper.getVideoSize();
Ln.v("Ignore touch event generated for size " + eventSize + " (current size is " + currentSize + ")");
}
return false;
} }
return false; targetDisplayId = displayData.virtualDisplayId;
} else {
// No display, use the raw coordinates
point = position.getPoint();
targetDisplayId = displayId;
} }
int pointerIndex = pointersState.getPointerIndex(pointerId); int pointerIndex = pointersState.getPointerIndex(pointerId);
@ -421,7 +432,7 @@ public class Controller implements AsyncProcessor, VirtualDisplayListener {
// First button pressed: ACTION_DOWN // First button pressed: ACTION_DOWN
MotionEvent downEvent = MotionEvent.obtain(lastTouchDown, now, MotionEvent.ACTION_DOWN, pointerCount, pointerProperties, MotionEvent downEvent = MotionEvent.obtain(lastTouchDown, now, MotionEvent.ACTION_DOWN, pointerCount, pointerProperties,
pointerCoords, 0, buttons, 1f, 1f, DEFAULT_DEVICE_ID, 0, source, 0); pointerCoords, 0, buttons, 1f, 1f, DEFAULT_DEVICE_ID, 0, source, 0);
if (!Device.injectEvent(downEvent, displayData.virtualDisplayId, Device.INJECT_MODE_ASYNC)) { if (!Device.injectEvent(downEvent, targetDisplayId, Device.INJECT_MODE_ASYNC)) {
return false; return false;
} }
} }
@ -432,7 +443,7 @@ public class Controller implements AsyncProcessor, VirtualDisplayListener {
if (!InputManager.setActionButton(pressEvent, actionButton)) { if (!InputManager.setActionButton(pressEvent, actionButton)) {
return false; return false;
} }
if (!Device.injectEvent(pressEvent, displayData.virtualDisplayId, Device.INJECT_MODE_ASYNC)) { if (!Device.injectEvent(pressEvent, targetDisplayId, Device.INJECT_MODE_ASYNC)) {
return false; return false;
} }
@ -446,7 +457,7 @@ public class Controller implements AsyncProcessor, VirtualDisplayListener {
if (!InputManager.setActionButton(releaseEvent, actionButton)) { if (!InputManager.setActionButton(releaseEvent, actionButton)) {
return false; return false;
} }
if (!Device.injectEvent(releaseEvent, displayData.virtualDisplayId, Device.INJECT_MODE_ASYNC)) { if (!Device.injectEvent(releaseEvent, targetDisplayId, Device.INJECT_MODE_ASYNC)) {
return false; return false;
} }
@ -454,7 +465,7 @@ public class Controller implements AsyncProcessor, VirtualDisplayListener {
// Last button released: ACTION_UP // Last button released: ACTION_UP
MotionEvent upEvent = MotionEvent.obtain(lastTouchDown, now, MotionEvent.ACTION_UP, pointerCount, pointerProperties, MotionEvent upEvent = MotionEvent.obtain(lastTouchDown, now, MotionEvent.ACTION_UP, pointerCount, pointerProperties,
pointerCoords, 0, buttons, 1f, 1f, DEFAULT_DEVICE_ID, 0, source, 0); pointerCoords, 0, buttons, 1f, 1f, DEFAULT_DEVICE_ID, 0, source, 0);
if (!Device.injectEvent(upEvent, displayData.virtualDisplayId, Device.INJECT_MODE_ASYNC)) { if (!Device.injectEvent(upEvent, targetDisplayId, Device.INJECT_MODE_ASYNC)) {
return false; return false;
} }
} }
@ -465,7 +476,7 @@ public class Controller implements AsyncProcessor, VirtualDisplayListener {
MotionEvent event = MotionEvent.obtain(lastTouchDown, now, action, pointerCount, pointerProperties, pointerCoords, 0, buttons, 1f, 1f, MotionEvent event = MotionEvent.obtain(lastTouchDown, now, action, pointerCount, pointerProperties, pointerCoords, 0, buttons, 1f, 1f,
DEFAULT_DEVICE_ID, 0, source, 0); DEFAULT_DEVICE_ID, 0, source, 0);
return Device.injectEvent(event, displayData.virtualDisplayId, Device.INJECT_MODE_ASYNC); return Device.injectEvent(event, targetDisplayId, Device.INJECT_MODE_ASYNC);
} }
private boolean injectScroll(Position position, float hScroll, float vScroll, int buttons) { private boolean injectScroll(Position position, float hScroll, float vScroll, int buttons) {
@ -691,9 +702,12 @@ public class Controller implements AsyncProcessor, VirtualDisplayListener {
} }
private void setDisplayPower(boolean on) { private void setDisplayPower(boolean on) {
boolean setDisplayPowerOk = Device.setDisplayPower(displayId, on); // Change the power of the main display when mirroring a virtual display
int targetDisplayId = displayId != Device.DISPLAY_ID_NONE ? displayId : 0;
boolean setDisplayPowerOk = Device.setDisplayPower(targetDisplayId, on);
if (setDisplayPowerOk) { if (setDisplayPowerOk) {
keepDisplayPowerOff = !on; // Do not keep display power off for virtual displays: MOD+p must wake up the physical device
keepDisplayPowerOff = displayId != Device.DISPLAY_ID_NONE && !on;
Ln.i("Device display turned " + (on ? "on" : "off")); Ln.i("Device display turned " + (on ? "on" : "off"));
if (cleanUp != null) { if (cleanUp != null) {
boolean mustRestoreOnExit = !on; boolean mustRestoreOnExit = !on;

View File

@ -40,6 +40,10 @@ public final class Device {
public static final int INJECT_MODE_WAIT_FOR_RESULT = InputManager.INJECT_INPUT_EVENT_MODE_WAIT_FOR_RESULT; public static final int INJECT_MODE_WAIT_FOR_RESULT = InputManager.INJECT_INPUT_EVENT_MODE_WAIT_FOR_RESULT;
public static final int INJECT_MODE_WAIT_FOR_FINISH = InputManager.INJECT_INPUT_EVENT_MODE_WAIT_FOR_FINISH; public static final int INJECT_MODE_WAIT_FOR_FINISH = InputManager.INJECT_INPUT_EVENT_MODE_WAIT_FOR_FINISH;
// The new display power method introduced in Android 15 does not work as expected:
// <https://github.com/Genymobile/scrcpy/issues/5530>
private static final boolean USE_ANDROID_15_DISPLAY_POWER = false;
private Device() { private Device() {
// not instantiable // not instantiable
} }
@ -127,7 +131,7 @@ public final class Device {
public static boolean setDisplayPower(int displayId, boolean on) { public static boolean setDisplayPower(int displayId, boolean on) {
assert displayId != Device.DISPLAY_ID_NONE; assert displayId != Device.DISPLAY_ID_NONE;
if (Build.VERSION.SDK_INT >= AndroidVersions.API_35_ANDROID_15) { if (USE_ANDROID_15_DISPLAY_POWER && Build.VERSION.SDK_INT >= AndroidVersions.API_35_ANDROID_15) {
return ServiceManager.getDisplayManager().requestDisplayPower(displayId, on); return ServiceManager.getDisplayManager().requestDisplayPower(displayId, on);
} }

View File

@ -192,6 +192,9 @@ public final class DisplayManager {
if ("onDisplayChanged".equals(method.getName())) { if ("onDisplayChanged".equals(method.getName())) {
listener.onDisplayChanged((int) args[0]); listener.onDisplayChanged((int) args[0]);
} }
if ("toString".equals(method.getName())) {
return "DisplayListener";
}
return null; return null;
}); });
try { try {