diff --git a/app/meson.build b/app/meson.build index c0948c4b70..175bdf46ba 100644 --- a/app/meson.build +++ b/app/meson.build @@ -106,10 +106,10 @@ conf.set('DEFAULT_LOCAL_PORT', '27183') # overridden by option --max-size conf.set('DEFAULT_MAX_SIZE', '0') # 0: unlimited -# the default client orientation +# the default video orientation # natural device orientation is 0 and each increment adds 90 degrees -# overridden by option --orientation -conf.set('DEFAULT_ORIENTATION', '-1') # -1: unlocked +# overridden by option --lock-video-orientation +conf.set('DEFAULT_LOCK_VIDEO_ORIENTATION', '-1') # -1: unlocked # the default video bitrate, in bits/second # overridden by option --bit-rate diff --git a/app/scrcpy.1 b/app/scrcpy.1 index 86aaf2091a..a9e01773b4 100644 --- a/app/scrcpy.1 +++ b/app/scrcpy.1 @@ -29,9 +29,7 @@ Default is 8000000. .BI "\-\-crop " width\fR:\fIheight\fR:\fIx\fR:\fIy Crop the device screen on the server. -The values are expressed in the device natural clientOrientation (typically, portrait for a phone, landscape for a tablet) unless -.B \-\-orientation -is specified. Any +The values are expressed in the device natural orientation (typically, portrait for a phone, landscape for a tablet). Any .B \-\-max\-size value is computed on the cropped size. @@ -54,8 +52,8 @@ Limit both the width and height of the video to \fIvalue\fR. The other dimension Default is 0 (unlimited). .TP -.BI "\-o, \-\-orientation " value -Lock client orientation to \fIvalue\fR. Values are integers in the range [0..3]. Natural device orientation is 0 and each increment adds 90 degrees. +.BI "\-\-lock\-video\-orientation " value +Lock video orientation to \fIvalue\fR. Values are integers in the range [0..3]. Natural device orientation is 0 and each increment adds 90 degrees. Default is -1 (unlocked). diff --git a/app/src/cli.c b/app/src/cli.c index 3a0123c25b..a07be49d5d 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -51,8 +51,8 @@ scrcpy_print_usage(const char *arg0) { " is preserved.\n" " Default is %d%s.\n" "\n" - " -o, --orientation value\n" - " Lock client orientation to value. Values are integers in the\n" + " --lock-video-orientation value\n" + " Lock video orientation to value. Values are integers in the\n" " range [0..3]. Natural device orientation is 0 and each\n" " increment adds 90 degrees.\n" " Default is %d%s.\n" @@ -199,7 +199,7 @@ scrcpy_print_usage(const char *arg0) { arg0, DEFAULT_BIT_RATE, DEFAULT_MAX_SIZE, DEFAULT_MAX_SIZE ? "" : " (unlimited)", - DEFAULT_ORIENTATION, DEFAULT_ORIENTATION >= 0 ? "" : " (unlocked)", + DEFAULT_LOCK_VIDEO_ORIENTATION, DEFAULT_LOCK_VIDEO_ORIENTATION >= 0 ? "" : " (unlocked)", DEFAULT_LOCAL_PORT); } @@ -267,15 +267,15 @@ parse_max_fps(const char *s, uint16_t *max_fps) { } static bool -parse_orientation(const char *s, int8_t *orientation) { +parse_lock_video_orientation(const char *s, int8_t *lock_video_orientation) { long value; bool ok = parse_integer_arg(s, &value, false, -1, 3, - "orientation"); + "lock_video_orientation"); if (!ok) { return false; } - *orientation = (int8_t) value; + *lock_video_orientation = (int8_t) value; return true; } @@ -347,52 +347,53 @@ guess_record_format(const char *filename) { return 0; } -#define OPT_RENDER_EXPIRED_FRAMES 1000 -#define OPT_WINDOW_TITLE 1001 -#define OPT_PUSH_TARGET 1002 -#define OPT_ALWAYS_ON_TOP 1003 -#define OPT_CROP 1004 -#define OPT_RECORD_FORMAT 1005 -#define OPT_PREFER_TEXT 1006 -#define OPT_WINDOW_X 1007 -#define OPT_WINDOW_Y 1008 -#define OPT_WINDOW_WIDTH 1009 -#define OPT_WINDOW_HEIGHT 1010 -#define OPT_WINDOW_BORDERLESS 1011 -#define OPT_MAX_FPS 1012 +#define OPT_RENDER_EXPIRED_FRAMES 1000 +#define OPT_WINDOW_TITLE 1001 +#define OPT_PUSH_TARGET 1002 +#define OPT_ALWAYS_ON_TOP 1003 +#define OPT_CROP 1004 +#define OPT_RECORD_FORMAT 1005 +#define OPT_PREFER_TEXT 1006 +#define OPT_WINDOW_X 1007 +#define OPT_WINDOW_Y 1008 +#define OPT_WINDOW_WIDTH 1009 +#define OPT_WINDOW_HEIGHT 1010 +#define OPT_WINDOW_BORDERLESS 1011 +#define OPT_MAX_FPS 1012 +#define OPT_LOCK_VIDEO_ORIENTATION 1013 bool scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) { static const struct option long_options[] = { - {"always-on-top", no_argument, NULL, OPT_ALWAYS_ON_TOP}, - {"bit-rate", required_argument, NULL, 'b'}, - {"crop", required_argument, NULL, OPT_CROP}, - {"fullscreen", no_argument, NULL, 'f'}, - {"help", no_argument, NULL, 'h'}, - {"max-fps", required_argument, NULL, OPT_MAX_FPS}, - {"max-size", required_argument, NULL, 'm'}, - {"orientation", required_argument, NULL, 'o'}, - {"no-control", no_argument, NULL, 'n'}, - {"no-display", no_argument, NULL, 'N'}, - {"port", required_argument, NULL, 'p'}, - {"push-target", required_argument, NULL, OPT_PUSH_TARGET}, - {"record", required_argument, NULL, 'r'}, - {"record-format", required_argument, NULL, OPT_RECORD_FORMAT}, - {"render-expired-frames", no_argument, NULL, - OPT_RENDER_EXPIRED_FRAMES}, - {"serial", required_argument, NULL, 's'}, - {"show-touches", no_argument, NULL, 't'}, - {"turn-screen-off", no_argument, NULL, 'S'}, - {"prefer-text", no_argument, NULL, OPT_PREFER_TEXT}, - {"version", no_argument, NULL, 'v'}, - {"window-title", required_argument, NULL, OPT_WINDOW_TITLE}, - {"window-x", required_argument, NULL, OPT_WINDOW_X}, - {"window-y", required_argument, NULL, OPT_WINDOW_Y}, - {"window-width", required_argument, NULL, OPT_WINDOW_WIDTH}, - {"window-height", required_argument, NULL, OPT_WINDOW_HEIGHT}, - {"window-borderless", no_argument, NULL, - OPT_WINDOW_BORDERLESS}, - {NULL, 0, NULL, 0 }, + {"always-on-top", no_argument, NULL, OPT_ALWAYS_ON_TOP}, + {"bit-rate", required_argument, NULL, 'b'}, + {"crop", required_argument, NULL, OPT_CROP}, + {"fullscreen", no_argument, NULL, 'f'}, + {"help", no_argument, NULL, 'h'}, + {"max-fps", required_argument, NULL, OPT_MAX_FPS}, + {"max-size", required_argument, NULL, 'm'}, + {"lock-video-orientation", required_argument, NULL, OPT_LOCK_VIDEO_ORIENTATION}, + {"no-control", no_argument, NULL, 'n'}, + {"no-display", no_argument, NULL, 'N'}, + {"port", required_argument, NULL, 'p'}, + {"push-target", required_argument, NULL, OPT_PUSH_TARGET}, + {"record", required_argument, NULL, 'r'}, + {"record-format", required_argument, NULL, OPT_RECORD_FORMAT}, + {"render-expired-frames", no_argument, NULL, + OPT_RENDER_EXPIRED_FRAMES}, + {"serial", required_argument, NULL, 's'}, + {"show-touches", no_argument, NULL, 't'}, + {"turn-screen-off", no_argument, NULL, 'S'}, + {"prefer-text", no_argument, NULL, OPT_PREFER_TEXT}, + {"version", no_argument, NULL, 'v'}, + {"window-title", required_argument, NULL, OPT_WINDOW_TITLE}, + {"window-x", required_argument, NULL, OPT_WINDOW_X}, + {"window-y", required_argument, NULL, OPT_WINDOW_Y}, + {"window-width", required_argument, NULL, OPT_WINDOW_WIDTH}, + {"window-height", required_argument, NULL, OPT_WINDOW_HEIGHT}, + {"window-borderless", no_argument, NULL, + OPT_WINDOW_BORDERLESS}, + {NULL, 0, NULL, 0 }, }; struct scrcpy_options *opts = &args->opts; @@ -400,7 +401,7 @@ scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) { optind = 0; // reset to start from the first argument in tests int c; - while ((c = getopt_long(argc, argv, "b:c:fF:hm:o:nNp:r:s:StTv", long_options, + while ((c = getopt_long(argc, argv, "b:c:fF:hm:nNp:r:s:StTv", long_options, NULL)) != -1) { switch (c) { case 'b': @@ -438,8 +439,8 @@ scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) { return false; } break; - case 'o': - if(!parse_orientation(optarg, &opts->orientation)) { + case OPT_LOCK_VIDEO_ORIENTATION: + if (!parse_lock_video_orientation(optarg, &opts->lock_video_orientation)) { return false; } break; diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index 37ea238b70..d80f1ef156 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -284,7 +284,7 @@ scrcpy(const struct scrcpy_options *options) { .max_size = options->max_size, .bit_rate = options->bit_rate, .max_fps = options->max_fps, - .orientation = options->orientation, + .lock_video_orientation = options->lock_video_orientation, .control = options->control, }; if (!server_start(&server, options->serial, ¶ms)) { diff --git a/app/src/scrcpy.h b/app/src/scrcpy.h index 0ef25f8c31..53a5b1f41f 100644 --- a/app/src/scrcpy.h +++ b/app/src/scrcpy.h @@ -19,7 +19,7 @@ struct scrcpy_options { uint16_t max_size; uint32_t bit_rate; uint16_t max_fps; - int8_t orientation; + int8_t lock_video_orientation; int16_t window_x; int16_t window_y; uint16_t window_width; @@ -46,7 +46,7 @@ struct scrcpy_options { .max_size = DEFAULT_MAX_SIZE, \ .bit_rate = DEFAULT_BIT_RATE, \ .max_fps = 0, \ - .orientation = DEFAULT_ORIENTATION, \ + .lock_video_orientation = DEFAULT_LOCK_VIDEO_ORIENTATION, \ .window_x = -1, \ .window_y = -1, \ .window_width = 0, \ diff --git a/app/src/server.c b/app/src/server.c index 9dfa678daa..e09295630c 100644 --- a/app/src/server.c +++ b/app/src/server.c @@ -124,11 +124,11 @@ execute_server(struct server *server, const struct server_params *params) { char max_size_string[6]; char bit_rate_string[11]; char max_fps_string[6]; - char orientation_string[4]; + char lock_video_orientation_string[3]; sprintf(max_size_string, "%"PRIu16, params->max_size); sprintf(bit_rate_string, "%"PRIu32, params->bit_rate); sprintf(max_fps_string, "%"PRIu16, params->max_fps); - sprintf(orientation_string, "%"PRIi8, params->orientation); + sprintf(lock_video_orientation_string, "%"PRIi8, params->lock_video_orientation); const char *const cmd[] = { "shell", "CLASSPATH=" DEVICE_SERVER_PATH, @@ -144,7 +144,7 @@ execute_server(struct server *server, const struct server_params *params) { max_size_string, bit_rate_string, max_fps_string, - orientation_string, + lock_video_orientation_string, server->tunnel_forward ? "true" : "false", params->crop ? params->crop : "-", "true", // always send frame meta (packet boundaries + timestamp) diff --git a/app/src/server.h b/app/src/server.h index fd919f922b..83f0ac8d9c 100644 --- a/app/src/server.h +++ b/app/src/server.h @@ -36,7 +36,7 @@ struct server_params { uint16_t max_size; uint32_t bit_rate; uint16_t max_fps; - int8_t orientation; + int8_t lock_video_orientation; bool control; }; diff --git a/app/tests/test_cli.c b/app/tests/test_cli.c index d5c24b4f57..6d824e29db 100644 --- a/app/tests/test_cli.c +++ b/app/tests/test_cli.c @@ -48,7 +48,7 @@ static void test_options(void) { "--fullscreen", "--max-fps", "30", "--max-size", "1024", - "--orientation", "2", + "--lock-video-orientation", "2", // "--no-control" is not compatible with "--turn-screen-off" // "--no-display" is not compatible with "--fulscreen" "--port", "1234", @@ -79,7 +79,7 @@ static void test_options(void) { assert(opts->fullscreen); assert(opts->max_fps == 30); assert(opts->max_size == 1024); - assert(opts->orientation == 2); + assert(opts->lock_video_orientation == 2); assert(opts->port == 1234); assert(!strcmp(opts->push_target, "/sdcard/Movies")); assert(!strcmp(opts->record_filename, "file")); diff --git a/server/src/main/java/com/genymobile/scrcpy/ControlMessageReader.java b/server/src/main/java/com/genymobile/scrcpy/ControlMessageReader.java index 50f9f1ca0f..e278cd8c6e 100644 --- a/server/src/main/java/com/genymobile/scrcpy/ControlMessageReader.java +++ b/server/src/main/java/com/genymobile/scrcpy/ControlMessageReader.java @@ -18,8 +18,6 @@ public class ControlMessageReader { public static final int CLIPBOARD_TEXT_MAX_LENGTH = 4093; private static final int RAW_BUFFER_SIZE = 1024; - private static int rotationOffset = 0; - private final byte[] rawBuffer = new byte[RAW_BUFFER_SIZE]; private final ByteBuffer buffer = ByteBuffer.wrap(rawBuffer); private final byte[] textBuffer = new byte[CLIPBOARD_TEXT_MAX_LENGTH]; @@ -172,30 +170,7 @@ private static Position readPosition(ByteBuffer buffer) { int y = buffer.getInt(); int screenWidth = toUnsigned(buffer.getShort()); int screenHeight = toUnsigned(buffer.getShort()); - return rotatePosition(x, y, screenWidth, screenHeight); - } - - @SuppressWarnings("SuspiciousNameCombination") - private static Position rotatePosition(int x, int y, int screenWidth, int screenHeight) { - Position position; - switch (rotationOffset) { - case 1: - position = new Position(y, screenWidth - x, screenHeight, screenWidth); - break; - case 2: - position = new Position(screenWidth - x, screenHeight - y, screenWidth, screenHeight); - break; - case 3: - position = new Position(screenHeight - y, x, screenHeight, screenWidth); - break; - default: - position = new Position(x, y, screenWidth, screenHeight); - } - return position; - } - - static void setRotationOffset(int newRotationOffset) { - rotationOffset = newRotationOffset; + return new Position(x, y, screenWidth, screenHeight); } @SuppressWarnings("checkstyle:MagicNumber") diff --git a/server/src/main/java/com/genymobile/scrcpy/Device.java b/server/src/main/java/com/genymobile/scrcpy/Device.java index fb1a34fb4d..27de99688e 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Device.java +++ b/server/src/main/java/com/genymobile/scrcpy/Device.java @@ -104,6 +104,7 @@ public Point getPhysicalPoint(Position position) { @SuppressWarnings("checkstyle:HiddenField") ScreenInfo screenInfo = getScreenInfo(); // read with synchronization Size videoSize = screenInfo.getVideoSize(); + position = position.rotate(ScreenEncoder.rotationOffset); Size clientVideoSize = position.getScreenSize(); if (!videoSize.equals(clientVideoSize)) { // The client sends a click relative to a video with wrong dimensions, @@ -112,9 +113,9 @@ public Point getPhysicalPoint(Position position) { } Rect contentRect = screenInfo.getContentRect(); Point point = position.getPoint(); - int scaledX = contentRect.left + point.getX() * contentRect.width() / videoSize.getWidth(); - int scaledY = contentRect.top + point.getY() * contentRect.height() / videoSize.getHeight(); - return new Point(scaledX, scaledY); + int convertedX = contentRect.left + point.getX() * contentRect.width() / videoSize.getWidth(); + int convertedY = contentRect.top + point.getY() * contentRect.height() / videoSize.getHeight(); + return new Point(convertedX, convertedY); } public static String getDeviceName() { @@ -194,7 +195,6 @@ public void rotateDevice() { @SuppressWarnings("SuspiciousNameCombination") static Rect flipRect(Rect crop) { - crop.set(crop.top, crop.left, crop.bottom, crop.right); - return crop; + return new Rect(crop.top, crop.left, crop.bottom, crop.right); } } diff --git a/server/src/main/java/com/genymobile/scrcpy/Options.java b/server/src/main/java/com/genymobile/scrcpy/Options.java index fad9ef9bc4..d9a29452b0 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Options.java +++ b/server/src/main/java/com/genymobile/scrcpy/Options.java @@ -6,7 +6,7 @@ public class Options { private int maxSize; private int bitRate; private int maxFps; - private int clientOrientation; + private int lockedVideoOrientation; private boolean tunnelForward; private Rect crop; private boolean sendFrameMeta; // send PTS so that the client may record properly @@ -36,12 +36,12 @@ public void setMaxFps(int maxFps) { this.maxFps = maxFps; } - public int getClientOrientation() { - return clientOrientation; + public int getLockedVideoOrientation() { + return lockedVideoOrientation; } - public void setClientOrientation(int clientOrientation) { - this.clientOrientation = clientOrientation; + public void setLockedVideoOrientation(int lockedVideoOrientation) { + this.lockedVideoOrientation = lockedVideoOrientation; } public boolean isTunnelForward() { diff --git a/server/src/main/java/com/genymobile/scrcpy/Position.java b/server/src/main/java/com/genymobile/scrcpy/Position.java index b46d2f7364..c164c5515c 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Position.java +++ b/server/src/main/java/com/genymobile/scrcpy/Position.java @@ -23,6 +23,24 @@ public Size getScreenSize() { return screenSize; } + public Position rotate(int rotation) { + Position position; + switch (rotation) { + case 1: + position = new Position(new Point(point.getY(), screenSize.getWidth() - point.getX()), screenSize.rotate()); + break; + case 2: + position = new Position(new Point(screenSize.getWidth() - point.getX(), screenSize.getHeight() - point.getY()), screenSize); + break; + case 3: + position = new Position(new Point(screenSize.getHeight() - point.getY(), point.getX()), screenSize.rotate()); + break; + default: + position = this; + } + return position; + } + @Override public boolean equals(Object o) { if (this == o) { diff --git a/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java b/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java index 888f092d6d..5e6d33aa96 100644 --- a/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java +++ b/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java @@ -27,22 +27,23 @@ public class ScreenEncoder implements Device.RotationListener { private int bitRate; private int maxFps; - private int clientOrientation; - private int rotationOffset = 0; + private int lockedVideoOrientation; private int iFrameInterval; private boolean sendFrameMeta; private long ptsOrigin; - public ScreenEncoder(boolean sendFrameMeta, int bitRate, int maxFps, int clientOrientation, int iFrameInterval) { + static int rotationOffset; + + public ScreenEncoder(boolean sendFrameMeta, int bitRate, int maxFps, int lockedVideoOrientation, int iFrameInterval) { this.sendFrameMeta = sendFrameMeta; this.bitRate = bitRate; this.maxFps = maxFps; - this.clientOrientation = clientOrientation; + this.lockedVideoOrientation = lockedVideoOrientation; this.iFrameInterval = iFrameInterval; } - public ScreenEncoder(boolean sendFrameMeta, int bitRate, int maxFps, int clientOrientation) { - this(sendFrameMeta, bitRate, maxFps, clientOrientation, DEFAULT_I_FRAME_INTERVAL); + public ScreenEncoder(boolean sendFrameMeta, int bitRate, int maxFps, int lockedVideoOrientation) { + this(sendFrameMeta, bitRate, maxFps, lockedVideoOrientation, DEFAULT_I_FRAME_INTERVAL); } @Override @@ -140,9 +141,8 @@ private void writeFrameMeta(FileDescriptor fd, MediaCodec.BufferInfo bufferInfo, } private void setRotationOffset(int rotation) { - if(clientOrientation != -1) {// user has requested orientation - rotationOffset = rotation + clientOrientation % 4; - ControlMessageReader.setRotationOffset(rotationOffset); + if (lockedVideoOrientation != -1) { // user has requested orientation + rotationOffset = (rotation + lockedVideoOrientation) % 4; } } @@ -180,7 +180,7 @@ private static void configure(MediaCodec codec, MediaFormat format) { } private static void setSize(MediaFormat format, int orientation, int width, int height) { - if(orientation % 2 == 0) { + if (orientation % 2 == 0) { format.setInteger(MediaFormat.KEY_WIDTH, width); format.setInteger(MediaFormat.KEY_HEIGHT, height); return; diff --git a/server/src/main/java/com/genymobile/scrcpy/ScreenInfo.java b/server/src/main/java/com/genymobile/scrcpy/ScreenInfo.java index 30932e8b56..fa19ef128b 100644 --- a/server/src/main/java/com/genymobile/scrcpy/ScreenInfo.java +++ b/server/src/main/java/com/genymobile/scrcpy/ScreenInfo.java @@ -26,9 +26,6 @@ public int getRotation() { } public ScreenInfo withRotation(int newRotation) { - if ((rotation + newRotation) % 2 == 0) { // 180s don't need flipping - return this; - } return new ScreenInfo(Device.flipRect(contentRect), videoSize.rotate(), newRotation); } } diff --git a/server/src/main/java/com/genymobile/scrcpy/Server.java b/server/src/main/java/com/genymobile/scrcpy/Server.java index 520b6b0086..785c0e8222 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Server.java +++ b/server/src/main/java/com/genymobile/scrcpy/Server.java @@ -19,7 +19,7 @@ private static void scrcpy(Options options) throws IOException { final Device device = new Device(options); boolean tunnelForward = options.isTunnelForward(); try (DesktopConnection connection = DesktopConnection.open(device, tunnelForward)) { - ScreenEncoder screenEncoder = new ScreenEncoder(options.getSendFrameMeta(), options.getBitRate(), options.getMaxFps(), options.getClientOrientation()); + ScreenEncoder screenEncoder = new ScreenEncoder(options.getSendFrameMeta(), options.getBitRate(), options.getMaxFps(), options.getLockedVideoOrientation()); if (options.getControl()) { Controller controller = new Controller(device, connection); @@ -94,8 +94,8 @@ private static Options createOptions(String... args) { int maxFps = Integer.parseInt(args[3]); options.setMaxFps(maxFps); - int clientOrientation = Integer.parseInt(args[4]); - options.setClientOrientation(clientOrientation); + int lockedVideoOrientation = Integer.parseInt(args[4]); + options.setLockedVideoOrientation(lockedVideoOrientation); // use "adb forward" instead of "adb tunnel"? (so the server must listen) boolean tunnelForward = Boolean.parseBoolean(args[5]);