mirror of
https://github.com/Genymobile/scrcpy.git
synced 2025-08-03 06:39:39 +00:00
Add support for multi-display Android system
Add -d, --display option to work with non-primary display in multi- display Android configurations Signed-off-by: Kostiantyn Luzan <vblack2006@gmail.com>
This commit is contained in:
parent
2afbfc2c75
commit
3ecb89b469
16 changed files with 111 additions and 23 deletions
|
@ -110,6 +110,10 @@ conf.set('DEFAULT_MAX_SIZE', '0') # 0: unlimited
|
||||||
# overridden by option --bit-rate
|
# overridden by option --bit-rate
|
||||||
conf.set('DEFAULT_BIT_RATE', '8000000') # 8Mbps
|
conf.set('DEFAULT_BIT_RATE', '8000000') # 8Mbps
|
||||||
|
|
||||||
|
# the default display number
|
||||||
|
# overridden by option --display
|
||||||
|
conf.set('DEFAULT_DISPLAY_ID', '0') # Primary display
|
||||||
|
|
||||||
# enable High DPI support
|
# enable High DPI support
|
||||||
conf.set('HIDPI_SUPPORT', get_option('hidpi_support'))
|
conf.set('HIDPI_SUPPORT', get_option('hidpi_support'))
|
||||||
|
|
||||||
|
|
|
@ -35,6 +35,10 @@ scrcpy_print_usage(const char *arg0) {
|
||||||
" (typically, portrait for a phone, landscape for a tablet).\n"
|
" (typically, portrait for a phone, landscape for a tablet).\n"
|
||||||
" Any --max-size value is computed on the cropped size.\n"
|
" Any --max-size value is computed on the cropped size.\n"
|
||||||
"\n"
|
"\n"
|
||||||
|
" -d, --display value\n"
|
||||||
|
" Use specified display in multi-display Android systems.\n"
|
||||||
|
" Default is %d.\n"
|
||||||
|
"\n"
|
||||||
" -f, --fullscreen\n"
|
" -f, --fullscreen\n"
|
||||||
" Start in fullscreen.\n"
|
" Start in fullscreen.\n"
|
||||||
"\n"
|
"\n"
|
||||||
|
@ -192,6 +196,7 @@ scrcpy_print_usage(const char *arg0) {
|
||||||
"\n",
|
"\n",
|
||||||
arg0,
|
arg0,
|
||||||
DEFAULT_BIT_RATE,
|
DEFAULT_BIT_RATE,
|
||||||
|
DEFAULT_DISPLAY_ID,
|
||||||
DEFAULT_MAX_SIZE, DEFAULT_MAX_SIZE ? "" : " (unlimited)",
|
DEFAULT_MAX_SIZE, DEFAULT_MAX_SIZE ? "" : " (unlimited)",
|
||||||
DEFAULT_LOCAL_PORT);
|
DEFAULT_LOCAL_PORT);
|
||||||
}
|
}
|
||||||
|
@ -235,6 +240,19 @@ parse_bit_rate(const char *s, uint32_t *bit_rate) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool
|
||||||
|
parse_display_id(const char *s, uint16_t *display_id) {
|
||||||
|
long value;
|
||||||
|
bool ok = parse_integer_arg(s, &value, false, 0, 0xFFFF, "display");
|
||||||
|
if (!ok) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
*display_id = (uint16_t) value;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
parse_max_size(const char *s, uint16_t *max_size) {
|
parse_max_size(const char *s, uint16_t *max_size) {
|
||||||
long value;
|
long value;
|
||||||
|
@ -347,6 +365,7 @@ scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) {
|
||||||
{"always-on-top", no_argument, NULL, OPT_ALWAYS_ON_TOP},
|
{"always-on-top", no_argument, NULL, OPT_ALWAYS_ON_TOP},
|
||||||
{"bit-rate", required_argument, NULL, 'b'},
|
{"bit-rate", required_argument, NULL, 'b'},
|
||||||
{"crop", required_argument, NULL, OPT_CROP},
|
{"crop", required_argument, NULL, OPT_CROP},
|
||||||
|
{"display", required_argument, NULL, 'd'},
|
||||||
{"fullscreen", no_argument, NULL, 'f'},
|
{"fullscreen", no_argument, NULL, 'f'},
|
||||||
{"help", no_argument, NULL, 'h'},
|
{"help", no_argument, NULL, 'h'},
|
||||||
{"max-fps", required_argument, NULL, OPT_MAX_FPS},
|
{"max-fps", required_argument, NULL, OPT_MAX_FPS},
|
||||||
|
@ -379,7 +398,7 @@ scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) {
|
||||||
optind = 0; // reset to start from the first argument in tests
|
optind = 0; // reset to start from the first argument in tests
|
||||||
|
|
||||||
int c;
|
int c;
|
||||||
while ((c = getopt_long(argc, argv, "b:c:fF:hm:nNp:r:s:StTv", long_options,
|
while ((c = getopt_long(argc, argv, "b:c:d:fF:hm:nNp:r:s:StTv", long_options,
|
||||||
NULL)) != -1) {
|
NULL)) != -1) {
|
||||||
switch (c) {
|
switch (c) {
|
||||||
case 'b':
|
case 'b':
|
||||||
|
@ -390,6 +409,11 @@ scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) {
|
||||||
case 'c':
|
case 'c':
|
||||||
LOGW("Deprecated option -c. Use --crop instead.");
|
LOGW("Deprecated option -c. Use --crop instead.");
|
||||||
// fall through
|
// fall through
|
||||||
|
case 'd':
|
||||||
|
if (!parse_display_id(optarg, &opts->display_id)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
break;
|
||||||
case OPT_CROP:
|
case OPT_CROP:
|
||||||
opts->crop = optarg;
|
opts->crop = optarg;
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -285,6 +285,7 @@ scrcpy(const struct scrcpy_options *options) {
|
||||||
.bit_rate = options->bit_rate,
|
.bit_rate = options->bit_rate,
|
||||||
.max_fps = options->max_fps,
|
.max_fps = options->max_fps,
|
||||||
.control = options->control,
|
.control = options->control,
|
||||||
|
.display_id = options->display_id,
|
||||||
};
|
};
|
||||||
if (!server_start(&server, options->serial, ¶ms)) {
|
if (!server_start(&server, options->serial, ¶ms)) {
|
||||||
return false;
|
return false;
|
||||||
|
|
|
@ -18,6 +18,7 @@ struct scrcpy_options {
|
||||||
uint16_t port;
|
uint16_t port;
|
||||||
uint16_t max_size;
|
uint16_t max_size;
|
||||||
uint32_t bit_rate;
|
uint32_t bit_rate;
|
||||||
|
uint16_t display_id;
|
||||||
uint16_t max_fps;
|
uint16_t max_fps;
|
||||||
int16_t window_x;
|
int16_t window_x;
|
||||||
int16_t window_y;
|
int16_t window_y;
|
||||||
|
@ -44,6 +45,7 @@ struct scrcpy_options {
|
||||||
.port = DEFAULT_LOCAL_PORT, \
|
.port = DEFAULT_LOCAL_PORT, \
|
||||||
.max_size = DEFAULT_MAX_SIZE, \
|
.max_size = DEFAULT_MAX_SIZE, \
|
||||||
.bit_rate = DEFAULT_BIT_RATE, \
|
.bit_rate = DEFAULT_BIT_RATE, \
|
||||||
|
.display_id = DEFAULT_DISPLAY_ID, \
|
||||||
.max_fps = 0, \
|
.max_fps = 0, \
|
||||||
.window_x = -1, \
|
.window_x = -1, \
|
||||||
.window_y = -1, \
|
.window_y = -1, \
|
||||||
|
|
|
@ -124,9 +124,11 @@ execute_server(struct server *server, const struct server_params *params) {
|
||||||
char max_size_string[6];
|
char max_size_string[6];
|
||||||
char bit_rate_string[11];
|
char bit_rate_string[11];
|
||||||
char max_fps_string[6];
|
char max_fps_string[6];
|
||||||
|
char display_id_string[6];
|
||||||
sprintf(max_size_string, "%"PRIu16, params->max_size);
|
sprintf(max_size_string, "%"PRIu16, params->max_size);
|
||||||
sprintf(bit_rate_string, "%"PRIu32, params->bit_rate);
|
sprintf(bit_rate_string, "%"PRIu32, params->bit_rate);
|
||||||
sprintf(max_fps_string, "%"PRIu16, params->max_fps);
|
sprintf(max_fps_string, "%"PRIu16, params->max_fps);
|
||||||
|
sprintf(display_id_string, "%"PRIu16, params->display_id);
|
||||||
const char *const cmd[] = {
|
const char *const cmd[] = {
|
||||||
"shell",
|
"shell",
|
||||||
"CLASSPATH=" DEVICE_SERVER_PATH,
|
"CLASSPATH=" DEVICE_SERVER_PATH,
|
||||||
|
@ -146,6 +148,7 @@ execute_server(struct server *server, const struct server_params *params) {
|
||||||
params->crop ? params->crop : "-",
|
params->crop ? params->crop : "-",
|
||||||
"true", // always send frame meta (packet boundaries + timestamp)
|
"true", // always send frame meta (packet boundaries + timestamp)
|
||||||
params->control ? "true" : "false",
|
params->control ? "true" : "false",
|
||||||
|
display_id_string,
|
||||||
};
|
};
|
||||||
#ifdef SERVER_DEBUGGER
|
#ifdef SERVER_DEBUGGER
|
||||||
LOGI("Server debugger waiting for a client on device port "
|
LOGI("Server debugger waiting for a client on device port "
|
||||||
|
|
|
@ -37,6 +37,7 @@ struct server_params {
|
||||||
uint32_t bit_rate;
|
uint32_t bit_rate;
|
||||||
uint16_t max_fps;
|
uint16_t max_fps;
|
||||||
bool control;
|
bool control;
|
||||||
|
uint16_t display_id;
|
||||||
};
|
};
|
||||||
|
|
||||||
// init default values
|
// init default values
|
||||||
|
|
|
@ -48,6 +48,7 @@ static void test_options(void) {
|
||||||
"--fullscreen",
|
"--fullscreen",
|
||||||
"--max-fps", "30",
|
"--max-fps", "30",
|
||||||
"--max-size", "1024",
|
"--max-size", "1024",
|
||||||
|
"--display", "1",
|
||||||
// "--no-control" is not compatible with "--turn-screen-off"
|
// "--no-control" is not compatible with "--turn-screen-off"
|
||||||
// "--no-display" is not compatible with "--fulscreen"
|
// "--no-display" is not compatible with "--fulscreen"
|
||||||
"--port", "1234",
|
"--port", "1234",
|
||||||
|
@ -78,6 +79,7 @@ static void test_options(void) {
|
||||||
assert(opts->fullscreen);
|
assert(opts->fullscreen);
|
||||||
assert(opts->max_fps == 30);
|
assert(opts->max_fps == 30);
|
||||||
assert(opts->max_size == 1024);
|
assert(opts->max_size == 1024);
|
||||||
|
assert(opts->display_id == 1);
|
||||||
assert(opts->port == 1234);
|
assert(opts->port == 1234);
|
||||||
assert(!strcmp(opts->push_target, "/sdcard/Movies"));
|
assert(!strcmp(opts->push_target, "/sdcard/Movies"));
|
||||||
assert(!strcmp(opts->record_filename, "file"));
|
assert(!strcmp(opts->record_filename, "file"));
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package com.genymobile.scrcpy;
|
package com.genymobile.scrcpy;
|
||||||
|
|
||||||
import com.genymobile.scrcpy.wrappers.InputManager;
|
import com.genymobile.scrcpy.wrappers.InputManager;
|
||||||
|
import com.genymobile.scrcpy.wrappers.MotionEventWrapper;
|
||||||
|
|
||||||
import android.os.SystemClock;
|
import android.os.SystemClock;
|
||||||
import android.view.InputDevice;
|
import android.view.InputDevice;
|
||||||
|
@ -179,9 +180,10 @@ public class Controller {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
MotionEvent event = MotionEvent
|
MotionEvent event = MotionEventWrapper
|
||||||
.obtain(lastTouchDown, now, action, pointerCount, pointerProperties, pointerCoords, 0, buttons, 1f, 1f, DEVICE_ID_VIRTUAL, 0,
|
.obtain(lastTouchDown, now, action, pointerCount, pointerProperties, pointerCoords, 0, buttons, 1f, 1f, DEVICE_ID_VIRTUAL, 0,
|
||||||
InputDevice.SOURCE_TOUCHSCREEN, 0);
|
InputDevice.SOURCE_TOUCHSCREEN, device.getScreenInfo().getDisplayId(), 0);
|
||||||
|
|
||||||
return injectEvent(event);
|
return injectEvent(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -202,9 +204,9 @@ public class Controller {
|
||||||
coords.setAxisValue(MotionEvent.AXIS_HSCROLL, hScroll);
|
coords.setAxisValue(MotionEvent.AXIS_HSCROLL, hScroll);
|
||||||
coords.setAxisValue(MotionEvent.AXIS_VSCROLL, vScroll);
|
coords.setAxisValue(MotionEvent.AXIS_VSCROLL, vScroll);
|
||||||
|
|
||||||
MotionEvent event = MotionEvent
|
MotionEvent event = MotionEventWrapper
|
||||||
.obtain(lastTouchDown, now, MotionEvent.ACTION_SCROLL, 1, pointerProperties, pointerCoords, 0, 0, 1f, 1f, DEVICE_ID_VIRTUAL, 0,
|
.obtain(lastTouchDown, now, MotionEvent.ACTION_SCROLL, 1, pointerProperties, pointerCoords, 0, 0, 1f, 1f, DEVICE_ID_VIRTUAL, 0,
|
||||||
InputDevice.SOURCE_MOUSE, 0);
|
InputDevice.SOURCE_MOUSE, device.getScreenInfo().getDisplayId(), 0);
|
||||||
return injectEvent(event);
|
return injectEvent(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -26,7 +26,8 @@ public final class Device {
|
||||||
private RotationListener rotationListener;
|
private RotationListener rotationListener;
|
||||||
|
|
||||||
public Device(Options options) {
|
public Device(Options options) {
|
||||||
screenInfo = computeScreenInfo(options.getCrop(), options.getMaxSize());
|
int displayId = options.getDisplayId();
|
||||||
|
screenInfo = computeScreenInfo(displayId, options.getCrop(), options.getMaxSize());
|
||||||
registerRotationWatcher(new IRotationWatcher.Stub() {
|
registerRotationWatcher(new IRotationWatcher.Stub() {
|
||||||
@Override
|
@Override
|
||||||
public void onRotationChanged(int rotation) throws RemoteException {
|
public void onRotationChanged(int rotation) throws RemoteException {
|
||||||
|
@ -39,15 +40,15 @@ public final class Device {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
}, displayId);
|
||||||
}
|
}
|
||||||
|
|
||||||
public synchronized ScreenInfo getScreenInfo() {
|
public synchronized ScreenInfo getScreenInfo() {
|
||||||
return screenInfo;
|
return screenInfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
private ScreenInfo computeScreenInfo(Rect crop, int maxSize) {
|
private ScreenInfo computeScreenInfo(int displayId, Rect crop, int maxSize) {
|
||||||
DisplayInfo displayInfo = serviceManager.getDisplayManager().getDisplayInfo();
|
DisplayInfo displayInfo = serviceManager.getDisplayManager().getDisplayInfo(displayId);
|
||||||
boolean rotated = (displayInfo.getRotation() & 1) != 0;
|
boolean rotated = (displayInfo.getRotation() & 1) != 0;
|
||||||
Size deviceSize = displayInfo.getSize();
|
Size deviceSize = displayInfo.getSize();
|
||||||
Rect contentRect = new Rect(0, 0, deviceSize.getWidth(), deviceSize.getHeight());
|
Rect contentRect = new Rect(0, 0, deviceSize.getWidth(), deviceSize.getHeight());
|
||||||
|
@ -64,7 +65,7 @@ public final class Device {
|
||||||
}
|
}
|
||||||
|
|
||||||
Size videoSize = computeVideoSize(contentRect.width(), contentRect.height(), maxSize);
|
Size videoSize = computeVideoSize(contentRect.width(), contentRect.height(), maxSize);
|
||||||
return new ScreenInfo(contentRect, videoSize, rotated);
|
return new ScreenInfo(displayId, contentRect, videoSize, rotated);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String formatCrop(Rect rect) {
|
private static String formatCrop(Rect rect) {
|
||||||
|
@ -129,8 +130,8 @@ public final class Device {
|
||||||
return serviceManager.getPowerManager().isScreenOn();
|
return serviceManager.getPowerManager().isScreenOn();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void registerRotationWatcher(IRotationWatcher rotationWatcher) {
|
public void registerRotationWatcher(IRotationWatcher rotationWatcher, int displayId) {
|
||||||
serviceManager.getWindowManager().registerRotationWatcher(rotationWatcher);
|
serviceManager.getWindowManager().registerRotationWatcher(rotationWatcher, displayId);
|
||||||
}
|
}
|
||||||
|
|
||||||
public synchronized void setRotationListener(RotationListener rotationListener) {
|
public synchronized void setRotationListener(RotationListener rotationListener) {
|
||||||
|
|
|
@ -3,6 +3,7 @@ package com.genymobile.scrcpy;
|
||||||
import android.graphics.Rect;
|
import android.graphics.Rect;
|
||||||
|
|
||||||
public class Options {
|
public class Options {
|
||||||
|
private int displayId;
|
||||||
private int maxSize;
|
private int maxSize;
|
||||||
private int bitRate;
|
private int bitRate;
|
||||||
private int maxFps;
|
private int maxFps;
|
||||||
|
@ -11,6 +12,14 @@ public class Options {
|
||||||
private boolean sendFrameMeta; // send PTS so that the client may record properly
|
private boolean sendFrameMeta; // send PTS so that the client may record properly
|
||||||
private boolean control;
|
private boolean control;
|
||||||
|
|
||||||
|
public int getDisplayId() {
|
||||||
|
return displayId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDisplayId(int displayId) {
|
||||||
|
this.displayId = displayId;
|
||||||
|
}
|
||||||
|
|
||||||
public int getMaxSize() {
|
public int getMaxSize() {
|
||||||
return maxSize;
|
return maxSize;
|
||||||
}
|
}
|
||||||
|
|
|
@ -64,10 +64,11 @@ public class ScreenEncoder implements Device.RotationListener {
|
||||||
IBinder display = createDisplay();
|
IBinder display = createDisplay();
|
||||||
Rect contentRect = device.getScreenInfo().getContentRect();
|
Rect contentRect = device.getScreenInfo().getContentRect();
|
||||||
Rect videoRect = device.getScreenInfo().getVideoSize().toRect();
|
Rect videoRect = device.getScreenInfo().getVideoSize().toRect();
|
||||||
|
int displayId = device.getScreenInfo().getDisplayId();
|
||||||
setSize(format, videoRect.width(), videoRect.height());
|
setSize(format, videoRect.width(), videoRect.height());
|
||||||
configure(codec, format);
|
configure(codec, format);
|
||||||
Surface surface = codec.createInputSurface();
|
Surface surface = codec.createInputSurface();
|
||||||
setDisplaySurface(display, surface, contentRect, videoRect);
|
setDisplaySurface(display, surface, displayId, contentRect, videoRect);
|
||||||
codec.start();
|
codec.start();
|
||||||
try {
|
try {
|
||||||
alive = encode(codec, fd);
|
alive = encode(codec, fd);
|
||||||
|
@ -172,12 +173,12 @@ public class ScreenEncoder implements Device.RotationListener {
|
||||||
format.setInteger(MediaFormat.KEY_HEIGHT, height);
|
format.setInteger(MediaFormat.KEY_HEIGHT, height);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void setDisplaySurface(IBinder display, Surface surface, Rect deviceRect, Rect displayRect) {
|
private static void setDisplaySurface(IBinder display, Surface surface, int displayId, Rect deviceRect, Rect displayRect) {
|
||||||
SurfaceControl.openTransaction();
|
SurfaceControl.openTransaction();
|
||||||
try {
|
try {
|
||||||
SurfaceControl.setDisplaySurface(display, surface);
|
SurfaceControl.setDisplaySurface(display, surface);
|
||||||
SurfaceControl.setDisplayProjection(display, 0, deviceRect, displayRect);
|
SurfaceControl.setDisplayProjection(display, 0, deviceRect, displayRect);
|
||||||
SurfaceControl.setDisplayLayerStack(display, 0);
|
SurfaceControl.setDisplayLayerStack(display, displayId);
|
||||||
} finally {
|
} finally {
|
||||||
SurfaceControl.closeTransaction();
|
SurfaceControl.closeTransaction();
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,13 +6,19 @@ public final class ScreenInfo {
|
||||||
private final Rect contentRect; // device size, possibly cropped
|
private final Rect contentRect; // device size, possibly cropped
|
||||||
private final Size videoSize;
|
private final Size videoSize;
|
||||||
private final boolean rotated;
|
private final boolean rotated;
|
||||||
|
private final int displayId;
|
||||||
|
|
||||||
public ScreenInfo(Rect contentRect, Size videoSize, boolean rotated) {
|
public ScreenInfo(int displayId, Rect contentRect, Size videoSize, boolean rotated) {
|
||||||
|
this.displayId = displayId;
|
||||||
this.contentRect = contentRect;
|
this.contentRect = contentRect;
|
||||||
this.videoSize = videoSize;
|
this.videoSize = videoSize;
|
||||||
this.rotated = rotated;
|
this.rotated = rotated;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int getDisplayId() {
|
||||||
|
return displayId;
|
||||||
|
}
|
||||||
|
|
||||||
public Rect getContentRect() {
|
public Rect getContentRect() {
|
||||||
return contentRect;
|
return contentRect;
|
||||||
}
|
}
|
||||||
|
@ -26,6 +32,6 @@ public final class ScreenInfo {
|
||||||
if (rotated == newRotated) {
|
if (rotated == newRotated) {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
return new ScreenInfo(Device.flipRect(contentRect), videoSize.rotate(), newRotated);
|
return new ScreenInfo(displayId, Device.flipRect(contentRect), videoSize.rotate(), newRotated);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -79,8 +79,8 @@ public final class Server {
|
||||||
"The server version (" + clientVersion + ") does not match the client " + "(" + BuildConfig.VERSION_NAME + ")");
|
"The server version (" + clientVersion + ") does not match the client " + "(" + BuildConfig.VERSION_NAME + ")");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (args.length != 8) {
|
if (args.length != 9) {
|
||||||
throw new IllegalArgumentException("Expecting 8 parameters");
|
throw new IllegalArgumentException("Expecting 9 parameters");
|
||||||
}
|
}
|
||||||
|
|
||||||
Options options = new Options();
|
Options options = new Options();
|
||||||
|
@ -107,6 +107,9 @@ public final class Server {
|
||||||
boolean control = Boolean.parseBoolean(args[7]);
|
boolean control = Boolean.parseBoolean(args[7]);
|
||||||
options.setControl(control);
|
options.setControl(control);
|
||||||
|
|
||||||
|
int displayId = Integer.parseInt(args[8]);
|
||||||
|
options.setDisplayId(displayId);
|
||||||
|
|
||||||
return options;
|
return options;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -12,9 +12,9 @@ public final class DisplayManager {
|
||||||
this.manager = manager;
|
this.manager = manager;
|
||||||
}
|
}
|
||||||
|
|
||||||
public DisplayInfo getDisplayInfo() {
|
public DisplayInfo getDisplayInfo(int display) {
|
||||||
try {
|
try {
|
||||||
Object displayInfo = manager.getClass().getMethod("getDisplayInfo", int.class).invoke(manager, 0);
|
Object displayInfo = manager.getClass().getMethod("getDisplayInfo", int.class).invoke(manager, display);
|
||||||
Class<?> cls = displayInfo.getClass();
|
Class<?> cls = displayInfo.getClass();
|
||||||
// width and height already take the rotation into account
|
// width and height already take the rotation into account
|
||||||
int width = cls.getDeclaredField("logicalWidth").getInt(displayInfo);
|
int width = cls.getDeclaredField("logicalWidth").getInt(displayInfo);
|
||||||
|
|
|
@ -0,0 +1,29 @@
|
||||||
|
package com.genymobile.scrcpy.wrappers;
|
||||||
|
|
||||||
|
import java.lang.reflect.InvocationTargetException;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
|
||||||
|
import android.view.MotionEvent;
|
||||||
|
|
||||||
|
public final class MotionEventWrapper {
|
||||||
|
static public MotionEvent obtain(long downTime, long eventTime,
|
||||||
|
int action, int pointerCount, MotionEvent.PointerProperties[] pointerProperties,
|
||||||
|
MotionEvent.PointerCoords[] pointerCoords, int metaState, int buttonState,
|
||||||
|
float xPrecision, float yPrecision, int deviceId,
|
||||||
|
int edgeFlags, int source, int displayId, int flags) {
|
||||||
|
MotionEvent motionEvent;
|
||||||
|
try {
|
||||||
|
// Modern Android 10 (29+ API), Android R has new displayId parameter, to support multi-display touch
|
||||||
|
motionEvent = (MotionEvent)MotionEvent.class.getMethod("obtain",
|
||||||
|
long.class, long.class,
|
||||||
|
int.class, int.class, MotionEvent.PointerProperties[].class,
|
||||||
|
MotionEvent.PointerCoords[].class, int.class, int.class,
|
||||||
|
float.class, float.class, int.class,
|
||||||
|
int.class, int.class, int.class, int.class)
|
||||||
|
.invoke(null, downTime, eventTime, action, pointerCount, pointerProperties, pointerCoords, metaState, buttonState, xPrecision, yPrecision, deviceId, edgeFlags, source, displayId, flags);
|
||||||
|
} catch (InvocationTargetException | IllegalAccessException | NoSuchMethodException e) {
|
||||||
|
motionEvent = MotionEvent.obtain(downTime, eventTime, action, pointerCount, pointerProperties, pointerCoords, metaState, buttonState, xPrecision, yPrecision, deviceId, edgeFlags, source, flags);
|
||||||
|
}
|
||||||
|
return motionEvent;
|
||||||
|
}
|
||||||
|
}
|
|
@ -93,13 +93,13 @@ public final class WindowManager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void registerRotationWatcher(IRotationWatcher rotationWatcher) {
|
public void registerRotationWatcher(IRotationWatcher rotationWatcher, int displayId) {
|
||||||
try {
|
try {
|
||||||
Class<?> cls = manager.getClass();
|
Class<?> cls = manager.getClass();
|
||||||
try {
|
try {
|
||||||
// display parameter added since this commit:
|
// display parameter added since this commit:
|
||||||
// https://android.googlesource.com/platform/frameworks/base/+/35fa3c26adcb5f6577849fd0df5228b1f67cf2c6%5E%21/#F1
|
// https://android.googlesource.com/platform/frameworks/base/+/35fa3c26adcb5f6577849fd0df5228b1f67cf2c6%5E%21/#F1
|
||||||
cls.getMethod("watchRotation", IRotationWatcher.class, int.class).invoke(manager, rotationWatcher, 0);
|
cls.getMethod("watchRotation", IRotationWatcher.class, int.class).invoke(manager, rotationWatcher, displayId);
|
||||||
} catch (NoSuchMethodException e) {
|
} catch (NoSuchMethodException e) {
|
||||||
// old version
|
// old version
|
||||||
cls.getMethod("watchRotation", IRotationWatcher.class).invoke(manager, rotationWatcher);
|
cls.getMethod("watchRotation", IRotationWatcher.class).invoke(manager, rotationWatcher);
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue