Add virtual display feature
Add a feature to create a new (separate) virtual display instead of mirroring the device screen: scrcpy --new-display=1920x1080 scrcpy --new-display=1920x1080/420 # force 420 dpi scrcpy --new-display # use the main display size and density scrcpy --new-display -m1920 # scaled to fit a max size of 1920 scrcpy --new-display=/240 # use the main display size and 240 dpi Fixes #1887 <https://github.com/Genymobile/scrcpy/issues/1887> PR #5370 <https://github.com/Genymobile/scrcpy/pull/5370> Co-authored-by: Simon Chan <1330321+yume-chan@users.noreply.github.com> Co-authored-by: anirudhb <anirudhb@users.noreply.github.com>
This commit is contained in:
parent
5d0e012a4c
commit
98ed5eb643
@ -46,6 +46,8 @@ _scrcpy() {
|
||||
--mouse-bind=
|
||||
-n --no-control
|
||||
-N --no-playback
|
||||
--new-display
|
||||
--new-display=
|
||||
--no-audio
|
||||
--no-audio-playback
|
||||
--no-cleanup
|
||||
|
@ -52,6 +52,7 @@ arguments=(
|
||||
'--mouse-bind=[Configure bindings of secondary clicks]'
|
||||
{-n,--no-control}'[Disable device control \(mirror the device in read only\)]'
|
||||
{-N,--no-playback}'[Disable video and audio playback]'
|
||||
'--new-display=[Create a new display]'
|
||||
'--no-audio[Disable audio forwarding]'
|
||||
'--no-audio-playback[Disable audio playback]'
|
||||
'--no-cleanup[Disable device cleanup actions on exit]'
|
||||
|
12
app/scrcpy.1
12
app/scrcpy.1
@ -314,6 +314,18 @@ Disable device control (mirror the device in read\-only).
|
||||
.B \-N, \-\-no\-playback
|
||||
Disable video and audio playback on the computer (equivalent to \fB\-\-no\-video\-playback \-\-no\-audio\-playback\fR).
|
||||
|
||||
.TP
|
||||
\fB\-\-new\-display\fR[=[\fIwidth\fRx\fIheight\fR][/\fIdpi\fR]]
|
||||
Create a new display with the specified resolution and density. If not provided, they default to the main display dimensions and DPI, and \fB\-\-max\-size\fR is considered.
|
||||
|
||||
Examples:
|
||||
|
||||
\-\-new\-display=1920x1080
|
||||
\-\-new\-display=1920x1080/420
|
||||
\-\-new\-display # main display size and density
|
||||
\-\-new\-display -m1920 # scaled to fit a max size of 1920
|
||||
\-\-new\-display=/240 # main display size and 240 dpi
|
||||
|
||||
.TP
|
||||
.B \-\-no\-audio
|
||||
Disable audio forwarding.
|
||||
|
@ -102,6 +102,7 @@ enum {
|
||||
OPT_NO_MOUSE_HOVER,
|
||||
OPT_AUDIO_DUP,
|
||||
OPT_GAMEPAD,
|
||||
OPT_NEW_DISPLAY,
|
||||
};
|
||||
|
||||
struct sc_option {
|
||||
@ -557,6 +558,21 @@ static const struct sc_option options[] = {
|
||||
.text = "Disable video and audio playback on the computer (equivalent "
|
||||
"to --no-video-playback --no-audio-playback).",
|
||||
},
|
||||
{
|
||||
.longopt_id = OPT_NEW_DISPLAY,
|
||||
.longopt = "new-display",
|
||||
.argdesc = "[<width>x<height>][/<dpi>]",
|
||||
.optional_arg = true,
|
||||
.text = "Create a new display with the specified resolution and "
|
||||
"density. If not provided, they default to the main display "
|
||||
"dimensions and DPI, and --max-size is considered.\n"
|
||||
"Examples:\n"
|
||||
" --new-display=1920x1080\n"
|
||||
" --new-display=1920x1080/420 # force 420 dpi\n"
|
||||
" --new-display # main display size and density\n"
|
||||
" --new-display -m1920 # scaled to fit a max size of 1920\n"
|
||||
" --new-display=/240 # main display size and 240 dpi",
|
||||
},
|
||||
{
|
||||
.longopt_id = OPT_NO_AUDIO,
|
||||
.longopt = "no-audio",
|
||||
@ -2668,6 +2684,9 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case OPT_NEW_DISPLAY:
|
||||
opts->new_display = optarg ? optarg : "";
|
||||
break;
|
||||
default:
|
||||
// getopt prints the error message on stderr
|
||||
return false;
|
||||
@ -2848,6 +2867,25 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
|
||||
}
|
||||
}
|
||||
|
||||
if (opts->new_display) {
|
||||
if (opts->video_source != SC_VIDEO_SOURCE_DISPLAY) {
|
||||
LOGE("--new-display is only available with --video-source=display");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!opts->video) {
|
||||
LOGE("--new-display is incompatible with --no-video");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (opts->max_size && opts->new_display[0] != '\0'
|
||||
&& opts->new_display[0] != '/') {
|
||||
// An explicit size is defined (not "" nor "/<dpi>")
|
||||
LOGE("Cannot specify both --new-display size and -m/--max-size");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (otg) {
|
||||
if (!opts->control) {
|
||||
LOGE("--no-control is not allowed in OTG mode");
|
||||
@ -2954,6 +2992,11 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
|
||||
return false;
|
||||
}
|
||||
|
||||
if (opts->display_id != 0 && opts->new_display) {
|
||||
LOGE("Cannot specify both --display-id and --new-display");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (opts->audio && opts->audio_source == SC_AUDIO_SOURCE_AUTO) {
|
||||
// Select the audio source according to the video source
|
||||
if (opts->video_source == SC_VIDEO_SOURCE_DISPLAY) {
|
||||
|
@ -103,6 +103,7 @@ const struct scrcpy_options scrcpy_options_default = {
|
||||
.window = true,
|
||||
.mouse_hover = true,
|
||||
.audio_dup = false,
|
||||
.new_display = NULL,
|
||||
};
|
||||
|
||||
enum sc_orientation
|
||||
|
@ -308,6 +308,7 @@ struct scrcpy_options {
|
||||
bool window;
|
||||
bool mouse_hover;
|
||||
bool audio_dup;
|
||||
const char *new_display; // [<width>x<height>][/<dpi>] parsed by the server
|
||||
};
|
||||
|
||||
extern const struct scrcpy_options scrcpy_options_default;
|
||||
|
@ -431,6 +431,7 @@ scrcpy(struct scrcpy_options *options) {
|
||||
.lock_video_orientation = options->lock_video_orientation,
|
||||
.control = options->control,
|
||||
.display_id = options->display_id,
|
||||
.new_display = options->new_display,
|
||||
.video = options->video,
|
||||
.audio = options->audio,
|
||||
.audio_dup = options->audio_dup,
|
||||
|
@ -355,6 +355,10 @@ execute_server(struct sc_server *server,
|
||||
// By default, power_on is true
|
||||
ADD_PARAM("power_on=false");
|
||||
}
|
||||
if (params->new_display) {
|
||||
VALIDATE_STRING(params->new_display);
|
||||
ADD_PARAM("new_display=%s", params->new_display);
|
||||
}
|
||||
if (params->list & SC_OPTION_LIST_ENCODERS) {
|
||||
ADD_PARAM("list_encoders=true");
|
||||
}
|
||||
|
@ -48,6 +48,7 @@ struct sc_server_params {
|
||||
int8_t lock_video_orientation;
|
||||
bool control;
|
||||
uint32_t display_id;
|
||||
const char *new_display;
|
||||
bool video;
|
||||
bool audio;
|
||||
bool audio_dup;
|
||||
|
@ -139,8 +139,10 @@ public final class CleanUp {
|
||||
|
||||
if (Device.isScreenOn()) {
|
||||
if (powerOffScreen) {
|
||||
Ln.i("Power off screen");
|
||||
Device.powerOffScreen(displayId);
|
||||
if (displayId != Device.DISPLAY_ID_NONE) {
|
||||
Ln.i("Power off screen");
|
||||
Device.powerOffScreen(displayId);
|
||||
}
|
||||
} else if (restoreNormalPowerMode) {
|
||||
Ln.i("Restoring normal power mode");
|
||||
Device.setScreenPowerMode(Device.POWER_MODE_NORMAL);
|
||||
|
@ -2,6 +2,7 @@ package com.genymobile.scrcpy;
|
||||
|
||||
import com.genymobile.scrcpy.audio.AudioCodec;
|
||||
import com.genymobile.scrcpy.audio.AudioSource;
|
||||
import com.genymobile.scrcpy.device.NewDisplay;
|
||||
import com.genymobile.scrcpy.device.Size;
|
||||
import com.genymobile.scrcpy.util.CodecOption;
|
||||
import com.genymobile.scrcpy.util.Ln;
|
||||
@ -54,6 +55,8 @@ public class Options {
|
||||
private boolean cleanup = true;
|
||||
private boolean powerOn = true;
|
||||
|
||||
private NewDisplay newDisplay;
|
||||
|
||||
private boolean listEncoders;
|
||||
private boolean listDisplays;
|
||||
private boolean listCameras;
|
||||
@ -205,6 +208,10 @@ public class Options {
|
||||
return powerOn;
|
||||
}
|
||||
|
||||
public NewDisplay getNewDisplay() {
|
||||
return newDisplay;
|
||||
}
|
||||
|
||||
public boolean getList() {
|
||||
return listEncoders || listDisplays || listCameras || listCameraSizes;
|
||||
}
|
||||
@ -418,6 +425,9 @@ public class Options {
|
||||
case "camera_high_speed":
|
||||
options.cameraHighSpeed = Boolean.parseBoolean(value);
|
||||
break;
|
||||
case "new_display":
|
||||
options.newDisplay = parseNewDisplay(value);
|
||||
break;
|
||||
case "send_device_meta":
|
||||
options.sendDeviceMeta = Boolean.parseBoolean(value);
|
||||
break;
|
||||
@ -504,4 +514,36 @@ public class Options {
|
||||
throw new IllegalArgumentException("Invalid float value for " + key + ": \"" + value + "\"");
|
||||
}
|
||||
}
|
||||
|
||||
private static NewDisplay parseNewDisplay(String newDisplay) {
|
||||
// Possible inputs:
|
||||
// - "" (empty string)
|
||||
// - "<width>x<height>/<dpi>"
|
||||
// - "<width>x<height>"
|
||||
// - "/<dpi>"
|
||||
if (newDisplay.isEmpty()) {
|
||||
return new NewDisplay();
|
||||
}
|
||||
|
||||
String[] tokens = newDisplay.split("/");
|
||||
|
||||
Size size;
|
||||
if (!tokens[0].isEmpty()) {
|
||||
size = parseSize(tokens[0]);
|
||||
} else {
|
||||
size = null;
|
||||
}
|
||||
|
||||
int dpi;
|
||||
if (tokens.length >= 2) {
|
||||
dpi = Integer.parseInt(tokens[1]);
|
||||
if (dpi <= 0) {
|
||||
throw new IllegalArgumentException("Invalid non-positive dpi: " + tokens[1]);
|
||||
}
|
||||
} else {
|
||||
dpi = 0;
|
||||
}
|
||||
|
||||
return new NewDisplay(size, dpi);
|
||||
}
|
||||
}
|
||||
|
@ -12,12 +12,14 @@ import com.genymobile.scrcpy.control.Controller;
|
||||
import com.genymobile.scrcpy.device.ConfigurationException;
|
||||
import com.genymobile.scrcpy.device.DesktopConnection;
|
||||
import com.genymobile.scrcpy.device.Device;
|
||||
import com.genymobile.scrcpy.device.NewDisplay;
|
||||
import com.genymobile.scrcpy.device.Streamer;
|
||||
import com.genymobile.scrcpy.util.Ln;
|
||||
import com.genymobile.scrcpy.util.LogUtils;
|
||||
import com.genymobile.scrcpy.util.Settings;
|
||||
import com.genymobile.scrcpy.util.SettingsException;
|
||||
import com.genymobile.scrcpy.video.CameraCapture;
|
||||
import com.genymobile.scrcpy.video.NewDisplayCapture;
|
||||
import com.genymobile.scrcpy.video.ScreenCapture;
|
||||
import com.genymobile.scrcpy.video.SurfaceCapture;
|
||||
import com.genymobile.scrcpy.video.SurfaceEncoder;
|
||||
@ -128,8 +130,11 @@ public final class Server {
|
||||
CleanUp cleanUp = null;
|
||||
Thread initThread = null;
|
||||
|
||||
NewDisplay newDisplay = options.getNewDisplay();
|
||||
int displayId = newDisplay == null ? options.getDisplayId() : Device.DISPLAY_ID_NONE;
|
||||
|
||||
if (options.getCleanup()) {
|
||||
cleanUp = CleanUp.configure(options.getDisplayId());
|
||||
cleanUp = CleanUp.configure(displayId);
|
||||
initThread = startInitThread(options, cleanUp);
|
||||
}
|
||||
|
||||
@ -154,7 +159,7 @@ public final class Server {
|
||||
|
||||
if (control) {
|
||||
ControlChannel controlChannel = connection.getControlChannel();
|
||||
controller = new Controller(options.getDisplayId(), controlChannel, cleanUp, options.getClipboardAutosync(), options.getPowerOn());
|
||||
controller = new Controller(displayId, controlChannel, cleanUp, options.getClipboardAutosync(), options.getPowerOn());
|
||||
asyncProcessors.add(controller);
|
||||
}
|
||||
|
||||
@ -184,8 +189,13 @@ public final class Server {
|
||||
options.getSendFrameMeta());
|
||||
SurfaceCapture surfaceCapture;
|
||||
if (options.getVideoSource() == VideoSource.DISPLAY) {
|
||||
surfaceCapture = new ScreenCapture(controller, options.getDisplayId(), options.getMaxSize(), options.getCrop(),
|
||||
options.getLockVideoOrientation());
|
||||
if (newDisplay != null) {
|
||||
surfaceCapture = new NewDisplayCapture(controller, newDisplay, options.getMaxSize());
|
||||
} else {
|
||||
assert displayId != Device.DISPLAY_ID_NONE;
|
||||
surfaceCapture = new ScreenCapture(controller, displayId, options.getMaxSize(), options.getCrop(),
|
||||
options.getLockVideoOrientation());
|
||||
}
|
||||
} else {
|
||||
surfaceCapture = new CameraCapture(options.getCameraId(), options.getCameraFacing(), options.getCameraSize(),
|
||||
options.getMaxSize(), options.getCameraAspectRatio(), options.getCameraFps(), options.getCameraHighSpeed());
|
||||
|
@ -40,6 +40,9 @@ public class Controller implements AsyncProcessor, VirtualDisplayListener {
|
||||
* In order to make events work correctly in all cases:
|
||||
* - virtualDisplayId must be used for events relative to the display (mouse and touch events with coordinates);
|
||||
* - displayId must be used for other events (like key events).
|
||||
*
|
||||
* If a new separate virtual display is created (using --new-display), then displayId == Device.DISPLAY_ID_NONE. In that case, all events are
|
||||
* sent to the virtual display id.
|
||||
*/
|
||||
|
||||
private static final class DisplayData {
|
||||
@ -151,7 +154,7 @@ public class Controller implements AsyncProcessor, VirtualDisplayListener {
|
||||
|
||||
private void control() throws IOException {
|
||||
// on start, power on the device
|
||||
if (powerOn && !Device.isScreenOn()) {
|
||||
if (powerOn && displayId != Device.DISPLAY_ID_NONE && !Device.isScreenOn()) {
|
||||
Device.pressReleaseKeycode(KeyEvent.KEYCODE_POWER, displayId, Device.INJECT_MODE_ASYNC);
|
||||
|
||||
// dirty hack
|
||||
@ -270,7 +273,7 @@ public class Controller implements AsyncProcessor, VirtualDisplayListener {
|
||||
}
|
||||
break;
|
||||
case ControlMessage.TYPE_ROTATE_DEVICE:
|
||||
Device.rotateDevice(displayId);
|
||||
Device.rotateDevice(getActionDisplayId());
|
||||
break;
|
||||
case ControlMessage.TYPE_UHID_CREATE:
|
||||
getUhidManager().open(msg.getId(), msg.getText(), msg.getData());
|
||||
@ -305,8 +308,10 @@ public class Controller implements AsyncProcessor, VirtualDisplayListener {
|
||||
if (events == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int actionDisplayId = getActionDisplayId();
|
||||
for (KeyEvent event : events) {
|
||||
if (!Device.injectEvent(event, displayId, Device.INJECT_MODE_ASYNC)) {
|
||||
if (!Device.injectEvent(event, actionDisplayId, Device.INJECT_MODE_ASYNC)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@ -543,10 +548,26 @@ public class Controller implements AsyncProcessor, VirtualDisplayListener {
|
||||
}
|
||||
|
||||
private boolean injectKeyEvent(int action, int keyCode, int repeat, int metaState, int injectMode) {
|
||||
return Device.injectKeyEvent(action, keyCode, repeat, metaState, displayId, injectMode);
|
||||
return Device.injectKeyEvent(action, keyCode, repeat, metaState, getActionDisplayId(), injectMode);
|
||||
}
|
||||
|
||||
private boolean pressReleaseKeycode(int keyCode, int injectMode) {
|
||||
return Device.pressReleaseKeycode(keyCode, displayId, injectMode);
|
||||
return Device.pressReleaseKeycode(keyCode, getActionDisplayId(), injectMode);
|
||||
}
|
||||
|
||||
private int getActionDisplayId() {
|
||||
if (displayId != Device.DISPLAY_ID_NONE) {
|
||||
// Real screen mirrored, use the source display id
|
||||
return displayId;
|
||||
}
|
||||
|
||||
// Virtual display created by --new-display, use the virtualDisplayId
|
||||
DisplayData data = displayData.get();
|
||||
if (data == null) {
|
||||
// If no virtual display id is initialized yet, use the main display id
|
||||
return 0;
|
||||
}
|
||||
|
||||
return data.virtualDisplayId;
|
||||
}
|
||||
}
|
||||
|
@ -19,6 +19,8 @@ import android.view.KeyEvent;
|
||||
|
||||
public final class Device {
|
||||
|
||||
public static final int DISPLAY_ID_NONE = -1;
|
||||
|
||||
public static final int POWER_MODE_OFF = SurfaceControl.POWER_MODE_OFF;
|
||||
public static final int POWER_MODE_NORMAL = SurfaceControl.POWER_MODE_NORMAL;
|
||||
|
||||
@ -159,6 +161,8 @@ public final class Device {
|
||||
}
|
||||
|
||||
public static boolean powerOffScreen(int displayId) {
|
||||
assert displayId != DISPLAY_ID_NONE;
|
||||
|
||||
if (!isScreenOn()) {
|
||||
return true;
|
||||
}
|
||||
@ -169,6 +173,8 @@ public final class Device {
|
||||
* Disable auto-rotation (if enabled), set the screen rotation and re-enable auto-rotation (if it was enabled).
|
||||
*/
|
||||
public static void rotateDevice(int displayId) {
|
||||
assert displayId != DISPLAY_ID_NONE;
|
||||
|
||||
WindowManager wm = ServiceManager.getWindowManager();
|
||||
|
||||
boolean accelerometerRotation = !wm.isRotationFrozen(displayId);
|
||||
@ -187,6 +193,8 @@ public final class Device {
|
||||
}
|
||||
|
||||
private static int getCurrentRotation(int displayId) {
|
||||
assert displayId != DISPLAY_ID_NONE;
|
||||
|
||||
if (displayId == 0) {
|
||||
return ServiceManager.getWindowManager().getRotation();
|
||||
}
|
||||
|
@ -0,0 +1,31 @@
|
||||
package com.genymobile.scrcpy.device;
|
||||
|
||||
public final class NewDisplay {
|
||||
private Size size;
|
||||
private int dpi;
|
||||
|
||||
public NewDisplay() {
|
||||
// Auto size and dpi
|
||||
}
|
||||
|
||||
public NewDisplay(Size size, int dpi) {
|
||||
this.size = size;
|
||||
this.dpi = dpi;
|
||||
}
|
||||
|
||||
public Size getSize() {
|
||||
return size;
|
||||
}
|
||||
|
||||
public int getDpi() {
|
||||
return dpi;
|
||||
}
|
||||
|
||||
public boolean hasExplicitSize() {
|
||||
return size != null;
|
||||
}
|
||||
|
||||
public boolean hasExplicitDpi() {
|
||||
return dpi != 0;
|
||||
}
|
||||
}
|
@ -21,6 +21,10 @@ public final class Size {
|
||||
return height;
|
||||
}
|
||||
|
||||
public int getMax() {
|
||||
return Math.max(width, height);
|
||||
}
|
||||
|
||||
public Size rotate() {
|
||||
return new Size(height, width);
|
||||
}
|
||||
|
@ -0,0 +1,146 @@
|
||||
package com.genymobile.scrcpy.video;
|
||||
|
||||
import com.genymobile.scrcpy.AndroidVersions;
|
||||
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.util.Ln;
|
||||
import com.genymobile.scrcpy.wrappers.ServiceManager;
|
||||
|
||||
import android.graphics.Rect;
|
||||
import android.hardware.display.DisplayManager;
|
||||
import android.hardware.display.VirtualDisplay;
|
||||
import android.os.Build;
|
||||
import android.view.Surface;
|
||||
|
||||
public class NewDisplayCapture extends SurfaceCapture {
|
||||
|
||||
// Internal fields copied from android.hardware.display.DisplayManager
|
||||
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;
|
||||
private static final int VIRTUAL_DISPLAY_FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS = 1 << 9;
|
||||
private static final int VIRTUAL_DISPLAY_FLAG_TRUSTED = 1 << 10;
|
||||
private static final int VIRTUAL_DISPLAY_FLAG_OWN_DISPLAY_GROUP = 1 << 11;
|
||||
private static final int VIRTUAL_DISPLAY_FLAG_ALWAYS_UNLOCKED = 1 << 12;
|
||||
private static final int VIRTUAL_DISPLAY_FLAG_TOUCH_FEEDBACK_DISABLED = 1 << 13;
|
||||
private static final int VIRTUAL_DISPLAY_FLAG_OWN_FOCUS = 1 << 14;
|
||||
private static final int VIRTUAL_DISPLAY_FLAG_DEVICE_DISPLAY_GROUP = 1 << 15;
|
||||
|
||||
private final VirtualDisplayListener vdListener;
|
||||
private final NewDisplay newDisplay;
|
||||
|
||||
private Size mainDisplaySize;
|
||||
private int mainDisplayDpi;
|
||||
private int maxSize; // only used if newDisplay.getSize() != null
|
||||
|
||||
private VirtualDisplay virtualDisplay;
|
||||
private Size size;
|
||||
private int dpi;
|
||||
|
||||
public NewDisplayCapture(VirtualDisplayListener vdListener, NewDisplay newDisplay, int maxSize) {
|
||||
this.vdListener = vdListener;
|
||||
this.newDisplay = newDisplay;
|
||||
this.maxSize = maxSize;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init() {
|
||||
size = newDisplay.getSize();
|
||||
dpi = newDisplay.getDpi();
|
||||
if (size == null || dpi == 0) {
|
||||
DisplayInfo displayInfo = ServiceManager.getDisplayManager().getDisplayInfo(0);
|
||||
if (displayInfo != null) {
|
||||
mainDisplaySize = displayInfo.getSize();
|
||||
mainDisplayDpi = displayInfo.getDpi();
|
||||
} else {
|
||||
Ln.w("Main display not found, fallback to 1920x1080 240dpi");
|
||||
mainDisplaySize = new Size(1920, 1080);
|
||||
mainDisplayDpi = 240;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void prepare() {
|
||||
if (!newDisplay.hasExplicitSize()) {
|
||||
size = ScreenInfo.computeVideoSize(mainDisplaySize.getWidth(), mainDisplaySize.getHeight(), maxSize);
|
||||
}
|
||||
if (!newDisplay.hasExplicitDpi()) {
|
||||
dpi = scaleDpi(mainDisplaySize, mainDisplayDpi, size);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start(Surface surface) {
|
||||
if (virtualDisplay != null) {
|
||||
virtualDisplay.release();
|
||||
virtualDisplay = null;
|
||||
}
|
||||
|
||||
int virtualDisplayId;
|
||||
try {
|
||||
int flags = DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC
|
||||
| DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY
|
||||
| VIRTUAL_DISPLAY_FLAG_SUPPORTS_TOUCH
|
||||
| VIRTUAL_DISPLAY_FLAG_ROTATES_WITH_CONTENT
|
||||
| VIRTUAL_DISPLAY_FLAG_DESTROY_CONTENT_ON_REMOVAL
|
||||
| VIRTUAL_DISPLAY_FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS;
|
||||
if (Build.VERSION.SDK_INT >= AndroidVersions.API_33_ANDROID_13) {
|
||||
flags |= VIRTUAL_DISPLAY_FLAG_TRUSTED
|
||||
| VIRTUAL_DISPLAY_FLAG_OWN_DISPLAY_GROUP
|
||||
| VIRTUAL_DISPLAY_FLAG_ALWAYS_UNLOCKED
|
||||
| VIRTUAL_DISPLAY_FLAG_TOUCH_FEEDBACK_DISABLED;
|
||||
if (Build.VERSION.SDK_INT >= AndroidVersions.API_34_ANDROID_14) {
|
||||
flags |= VIRTUAL_DISPLAY_FLAG_OWN_FOCUS
|
||||
| VIRTUAL_DISPLAY_FLAG_DEVICE_DISPLAY_GROUP;
|
||||
}
|
||||
}
|
||||
virtualDisplay = ServiceManager.getDisplayManager()
|
||||
.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 + ")");
|
||||
} catch (Exception e) {
|
||||
Ln.e("Could not create display", e);
|
||||
throw new AssertionError("Could not create display");
|
||||
}
|
||||
|
||||
if (vdListener != null) {
|
||||
virtualDisplayId = virtualDisplay.getDisplay().getDisplayId();
|
||||
Rect contentRect = new Rect(0, 0, size.getWidth(), size.getHeight());
|
||||
PositionMapper positionMapper = new PositionMapper(size, contentRect, 0);
|
||||
vdListener.onNewVirtualDisplay(virtualDisplayId, positionMapper);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void release() {
|
||||
if (virtualDisplay != null) {
|
||||
virtualDisplay.release();
|
||||
virtualDisplay = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized Size getSize() {
|
||||
return size;
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized boolean setMaxSize(int newMaxSize) {
|
||||
if (newDisplay.hasExplicitSize()) {
|
||||
// Cannot retry with a different size if the display size was explicitly provided
|
||||
return false;
|
||||
}
|
||||
|
||||
maxSize = newMaxSize;
|
||||
return true;
|
||||
}
|
||||
|
||||
private static int scaleDpi(Size initialSize, int initialDpi, Size size) {
|
||||
int den = initialSize.getMax();
|
||||
int num = size.getMax();
|
||||
return initialDpi * num / den;
|
||||
}
|
||||
}
|
@ -90,7 +90,7 @@ public final class ScreenInfo {
|
||||
return rect.width() + ":" + rect.height() + ":" + rect.left + ":" + rect.top;
|
||||
}
|
||||
|
||||
private static Size computeVideoSize(int w, int h, int maxSize) {
|
||||
public static Size computeVideoSize(int w, int h, int maxSize) {
|
||||
// Compute the video size and the padding of the content inside this video.
|
||||
// Principle:
|
||||
// - scale down the great side of the screen to maxSize (if necessary);
|
||||
|
@ -1,15 +1,18 @@
|
||||
package com.genymobile.scrcpy.wrappers;
|
||||
|
||||
import com.genymobile.scrcpy.FakeContext;
|
||||
import com.genymobile.scrcpy.device.DisplayInfo;
|
||||
import com.genymobile.scrcpy.device.Size;
|
||||
import com.genymobile.scrcpy.util.Command;
|
||||
import com.genymobile.scrcpy.util.Ln;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
import android.hardware.display.VirtualDisplay;
|
||||
import android.view.Display;
|
||||
import android.view.Surface;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.regex.Matcher;
|
||||
@ -126,4 +129,12 @@ public final class DisplayManager {
|
||||
Method method = getCreateVirtualDisplayMethod();
|
||||
return (VirtualDisplay) method.invoke(null, name, width, height, displayIdToMirror, surface);
|
||||
}
|
||||
|
||||
public VirtualDisplay createNewVirtualDisplay(String name, int width, int height, int dpi, Surface surface, int flags) throws Exception {
|
||||
Constructor<android.hardware.display.DisplayManager> ctor = android.hardware.display.DisplayManager.class.getDeclaredConstructor(
|
||||
Context.class);
|
||||
ctor.setAccessible(true);
|
||||
android.hardware.display.DisplayManager dm = ctor.newInstance(FakeContext.get());
|
||||
return dm.createVirtualDisplay(name, width, height, dpi, surface, flags);
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user