Compare commits

...

2 commits
test ... master

Author SHA1 Message Date
54fb05837a
refine 2023-09-10 01:14:00 +08:00
85a9871082
virtual display 2023-09-08 17:51:14 +08:00
9 changed files with 105 additions and 2 deletions

View file

@ -80,6 +80,8 @@ enum {
OPT_KILL_ADB_ON_CLOSE, OPT_KILL_ADB_ON_CLOSE,
OPT_TIME_LIMIT, OPT_TIME_LIMIT,
OPT_ROOT, OPT_ROOT,
OPT_SU_QUIRK,
OPT_VIRTUAL_DISPLAY,
}; };
struct sc_option { struct sc_option {
@ -541,6 +543,22 @@ static const struct sc_option options[] = {
.longopt = "root", .longopt = "root",
.text = "Launch the server as root (disabled by default).", .text = "Launch the server as root (disabled by default).",
}, },
{
.longopt_id = OPT_SU_QUIRK,
.longopt = "su-quirk",
.text = "Enable workaround for quirky su (disabled by default).",
},
{
.longopt_id = OPT_VIRTUAL_DISPLAY,
.longopt = "virtual-display",
.argdesc = "properties",
.optional_arg = true,
.text = "Start a new virtual display for your applications to "
"launch into. Requires Android version >= 10. "
"Display size and DPI may be specified. e.g. 1920x1080dpi300\n"
"Comma separated extra launch arguments may be passed to am start.\n"
"e.g. 1920x1080dpi300,-a,android.intent.action.MAIN,-n,com.android.settings/.applications.ManageApplications"
},
{ {
.shortopt = 's', .shortopt = 's',
.longopt = "serial", .longopt = "serial",
@ -1986,6 +2004,12 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
case OPT_ROOT: case OPT_ROOT:
opts->root = true; opts->root = true;
break; break;
case OPT_SU_QUIRK:
opts->su_quirk = true;
break;
case OPT_VIRTUAL_DISPLAY:
opts->virtual_display_params = optarg ?: "";
break;
default: default:
// getopt prints the error message on stderr // getopt prints the error message on stderr
return false; return false;

View file

@ -83,4 +83,6 @@ const struct scrcpy_options scrcpy_options_default = {
.list_displays = false, .list_displays = false,
.kill_adb_on_close = false, .kill_adb_on_close = false,
.root = false, .root = false,
.su_quirk = false,
.virtual_display_params = NULL,
}; };

View file

@ -183,6 +183,8 @@ struct scrcpy_options {
bool list_displays; bool list_displays;
bool kill_adb_on_close; bool kill_adb_on_close;
bool root; bool root;
bool su_quirk;
const char *virtual_display_params;
}; };
extern const struct scrcpy_options scrcpy_options_default; extern const struct scrcpy_options scrcpy_options_default;

View file

@ -381,6 +381,8 @@ scrcpy(struct scrcpy_options *options) {
.list_displays = options->list_displays, .list_displays = options->list_displays,
.kill_adb_on_close = options->kill_adb_on_close, .kill_adb_on_close = options->kill_adb_on_close,
.root = options->root, .root = options->root,
.su_quirk = options->su_quirk,
.virtual_display_params = options->virtual_display_params,
}; };
static const struct sc_server_callbacks cbs = { static const struct sc_server_callbacks cbs = {

View file

@ -198,7 +198,8 @@ execute_server(struct sc_server *server,
if (params->root) { if (params->root) {
cmd[count++] = "su"; cmd[count++] = "su";
cmd[count++] = "1000"; // AID_SYSTEM, AID_GRAPHICS is also supported for FLAG_SECURE but lacks other perms cmd[count++] = "1000"; // AID_SYSTEM, AID_GRAPHICS is also supported for FLAG_SECURE but lacks other perms
cmd[count++] = "-c"; if (!params->su_quirk) cmd[count++] = "-c";
cmd[count++] = "env";
} }
cmd[count++] = "CLASSPATH=" SC_DEVICE_SERVER_PATH; cmd[count++] = "CLASSPATH=" SC_DEVICE_SERVER_PATH;
@ -323,6 +324,10 @@ execute_server(struct sc_server *server,
if (params->list_displays) { if (params->list_displays) {
ADD_PARAM("list_displays=true"); ADD_PARAM("list_displays=true");
} }
if (params->virtual_display_params) {
ADD_PARAM("virtual_display=%s", params->virtual_display_params);
}
#undef ADD_PARAM #undef ADD_PARAM

View file

@ -60,6 +60,8 @@ struct sc_server_params {
bool list_displays; bool list_displays;
bool kill_adb_on_close; bool kill_adb_on_close;
bool root; bool root;
bool su_quirk;
const char *virtual_display_params;
}; };
struct sc_server { struct sc_server {

View file

@ -68,7 +68,10 @@ public final class Device {
private final boolean supportsInputEvents; private final boolean supportsInputEvents;
public Device(Options options) throws ConfigurationException { public Device(Options options) throws ConfigurationException {
displayId = options.getDisplayId(); String virtualDisplayParams = options.getVirtualDisplayParams();
displayId = virtualDisplayParams == null
? options.getDisplayId()
: ServiceManager.getDisplayManager().createVirtualDisplay(virtualDisplayParams).getDisplay().getDisplayId();
DisplayInfo displayInfo = ServiceManager.getDisplayManager().getDisplayInfo(displayId); DisplayInfo displayInfo = ServiceManager.getDisplayManager().getDisplayInfo(displayId);
if (displayInfo == null) { if (displayInfo == null) {
Ln.e("Display " + displayId + " not found\n" + LogUtils.buildDisplayListMessage()); Ln.e("Display " + displayId + " not found\n" + LogUtils.buildDisplayListMessage());

View file

@ -44,6 +44,7 @@ public class Options {
private boolean sendFrameMeta = true; // send PTS so that the client may record properly private boolean sendFrameMeta = true; // send PTS so that the client may record properly
private boolean sendDummyByte = true; // write a byte on start to detect connection issues private boolean sendDummyByte = true; // write a byte on start to detect connection issues
private boolean sendCodecMeta = true; // write the codec metadata before the stream private boolean sendCodecMeta = true; // write the codec metadata before the stream
private String virtualDisplayParams = null;
public Ln.Level getLogLevel() { public Ln.Level getLogLevel() {
return logLevel; return logLevel;
@ -176,6 +177,9 @@ public class Options {
public boolean getSendCodecMeta() { public boolean getSendCodecMeta() {
return sendCodecMeta; return sendCodecMeta;
} }
public String getVirtualDisplayParams() {
return virtualDisplayParams;
}
@SuppressWarnings("MethodLength") @SuppressWarnings("MethodLength")
public static Options parse(String... args) { public static Options parse(String... args) {
@ -327,6 +331,9 @@ public class Options {
options.sendCodecMeta = false; options.sendCodecMeta = false;
} }
break; break;
case "virtual_display":
options.virtualDisplayParams = value;
break;
default: default:
Ln.w("Unknown server option: " + key); Ln.w("Unknown server option: " + key);
break; break;

View file

@ -2,12 +2,21 @@ package com.genymobile.scrcpy.wrappers;
import com.genymobile.scrcpy.Command; import com.genymobile.scrcpy.Command;
import com.genymobile.scrcpy.DisplayInfo; import com.genymobile.scrcpy.DisplayInfo;
import com.genymobile.scrcpy.FakeContext;
import com.genymobile.scrcpy.IO;
import com.genymobile.scrcpy.Ln; import com.genymobile.scrcpy.Ln;
import com.genymobile.scrcpy.Size; import com.genymobile.scrcpy.Size;
import android.graphics.SurfaceTexture;
import android.hardware.display.VirtualDisplay;
import android.os.Build;
import android.view.Display; import android.view.Display;
import android.view.Surface;
import java.io.IOException;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
@ -18,6 +27,53 @@ public final class DisplayManager {
this.manager = manager; this.manager = manager;
} }
public VirtualDisplay createVirtualDisplay(String displayParams) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) throw new RuntimeException("requires android 10+");
final int VIRTUAL_DISPLAY_FLAG_TRUSTED = 1 << 10;
final int VIRTUAL_DISPLAY_FLAG_ALWAYS_UNLOCKED = 1 << 12;
try {
if (displayParams == null || displayParams.isEmpty()) displayParams = "1920x1080dpi300";
String[] displayParamsArray = displayParams.split(",");
String resolution = displayParamsArray[0].toLowerCase().split("dpi")[0];
int width = Integer.parseInt(resolution.split("x")[0]);
int height = Integer.parseInt(resolution.split("x")[1]);
int dpi = Integer.parseInt(displayParamsArray[0].toLowerCase().split("dpi")[1]);
android.hardware.display.DisplayManager displayManager = (android.hardware.display.DisplayManager)Class.forName("android.hardware.display.DisplayManager").getConstructor(Class.forName("android.content.Context")).newInstance(FakeContext.get());
SurfaceTexture surfaceTexture = new SurfaceTexture(false);
Surface surface = new Surface(surfaceTexture);
VirtualDisplay[] display = new VirtualDisplay[1];
display[0] = displayManager.createVirtualDisplay("scrcpy", width, height, dpi, surface,
android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC
| android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR
| android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_SECURE
| VIRTUAL_DISPLAY_FLAG_TRUSTED
| VIRTUAL_DISPLAY_FLAG_ALWAYS_UNLOCKED);
Ln.i(String.format("Display: %d", display[0].getDisplay().getDisplayId()));
// start launcher
// TODO: replace shell commands with proper android framework api call
String[] cmd;
if (displayParamsArray.length == 1) {
cmd = new String[]{"am", "start", "--user", "0", "--display", Integer.toString(display[0].getDisplay().getDisplayId()), "-a", "android.intent.action.MAIN", "-c", "android.intent.category.HOME", "-c", "android.intent.category.DEFAULT"};
}
else {
ArrayList<String> command = new ArrayList<>(Arrays.asList("am", "start", "--display", Integer.toString(display[0].getDisplay().getDisplayId())));
command.addAll(Arrays.asList(Arrays.copyOfRange(displayParamsArray, 1, displayParamsArray.length)));
cmd = command.toArray(new String[0]);
}
Process process = Runtime.getRuntime().exec(cmd);
String output = IO.toString(process.getInputStream());
String error = IO.toString(process.getErrorStream());
int exitCode = process.waitFor();
if (exitCode != 0) {
throw new IOException("Command " + Arrays.toString(cmd) + " returned with value " + exitCode);
}
Ln.i(String.format("stdout: %s\nstderr: %s", output.trim(), error.trim()));
return display[0];
} catch (Exception e) {
throw new AssertionError(e);
}
}
// public to call it from unit tests // public to call it from unit tests
public static DisplayInfo parseDisplayInfo(String dumpsysDisplayOutput, int displayId) { public static DisplayInfo parseDisplayInfo(String dumpsysDisplayOutput, int displayId) {
Pattern regex = Pattern.compile( Pattern regex = Pattern.compile(