diff --git a/app/data/bash-completion/scrcpy b/app/data/bash-completion/scrcpy
index d9ad4c8d..a954fd6c 100644
--- a/app/data/bash-completion/scrcpy
+++ b/app/data/bash-completion/scrcpy
@@ -17,6 +17,7 @@ _scrcpy() {
--camera-fps=
--camera-high-speed
--camera-size=
+ --capture-orientation=
--crop=
-d --select-usb
--disable-screensaver
@@ -37,8 +38,6 @@ _scrcpy() {
--list-cameras
--list-displays
--list-encoders
- --lock-video-orientation
- --lock-video-orientation=
-m --max-size=
-M
--max-fps=
diff --git a/app/data/zsh-completion/_scrcpy b/app/data/zsh-completion/_scrcpy
index 430e8000..154ddef0 100644
--- a/app/data/zsh-completion/_scrcpy
+++ b/app/data/zsh-completion/_scrcpy
@@ -24,6 +24,7 @@ arguments=(
'--camera-facing=[Select the device camera by its facing direction]:facing:(front back external)'
'--camera-fps=[Specify the camera capture frame rate]'
'--camera-size=[Specify an explicit camera capture size]'
+ '--capture-orientation=[Set the capture video orientation]:orientation:(0 90 180 270 flip0 flip90 flip180 flip270 #0 #90 #180 #270 #flip0 #flip90 #flip180 #flip270)'
'--crop=[\[width\:height\:x\:y\] Crop the device screen on the server]'
{-d,--select-usb}'[Use USB device]'
'--disable-screensaver[Disable screensaver while scrcpy is running]'
@@ -44,7 +45,6 @@ arguments=(
'--list-cameras[List cameras available on the device]'
'--list-displays[List displays available on the device]'
'--list-encoders[List video and audio encoders available on the device]'
- '--lock-video-orientation=[Lock video orientation]:orientation:(unlocked initial 0 90 180 270)'
{-m,--max-size=}'[Limit both the width and height of the video to value]'
'-M[Use UHID/AOA mouse (same as --mouse=uhid or --mouse=aoa, depending on OTG mode)]'
'--max-fps=[Limit the frame rate of screen capture]'
diff --git a/app/scrcpy.1 b/app/scrcpy.1
index 76e36dcb..29b53d98 100644
--- a/app/scrcpy.1
+++ b/app/scrcpy.1
@@ -121,6 +121,18 @@ If not specified, Android's default frame rate (30 fps) is used.
.BI "\-\-camera\-size " width\fRx\fIheight
Specify an explicit camera capture size.
+.TP
+.BI "\-\-capture\-orientation " value
+Possible values are 0, 90, 180, 270, flip0, flip90, flip180 and flip270, possibly prefixed by '@'.
+
+The number represents the clockwise rotation in degrees; the "flip" keyword applies a horizontal flip before the rotation.
+
+If a leading '@' is passed (@90) for display capture, then the rotation is locked, and is relative to the natural device orientation.
+
+If '@' is passed alone, then the rotation is locked to the initial device orientation.
+
+Default is 0.
+
.TP
.BI "\-\-crop " width\fR:\fIheight\fR:\fIx\fR:\fIy
Crop the device screen on the server.
@@ -241,16 +253,6 @@ List video and audio encoders available on the device.
.B \-\-list\-displays
List displays available on the device.
-.TP
-\fB\-\-lock\-video\-orientation\fR[=\fIvalue\fR]
-Lock capture video orientation to \fIvalue\fR.
-
-Possible values are "unlocked", "initial" (locked to the initial orientation), 0, 90, 180, and 270. The values represent the clockwise rotation from the natural device orientation, in degrees.
-
-Default is "unlocked".
-
-Passing the option without argument is equivalent to passing "initial".
-
.TP
.BI "\-m, \-\-max\-size " value
Limit both the width and height of the video to \fIvalue\fR. The other dimension is computed so that the device aspect\-ratio is preserved.
diff --git a/app/src/cli.c b/app/src/cli.c
index e67192bf..1a5e30fd 100644
--- a/app/src/cli.c
+++ b/app/src/cli.c
@@ -107,6 +107,7 @@ enum {
OPT_LIST_APPS,
OPT_START_APP,
OPT_SCREEN_OFF_TIMEOUT,
+ OPT_CAPTURE_ORIENTATION,
};
struct sc_option {
@@ -471,18 +472,27 @@ static const struct sc_option options[] = {
.text = "List video and audio encoders available on the device.",
},
{
+ .longopt_id = OPT_CAPTURE_ORIENTATION,
+ .longopt = "capture-orientation",
+ .argdesc = "value",
+ .text = "Set the capture video orientation.\n"
+ "Possible values are 0, 90, 180, 270, flip0, flip90, flip180 "
+ "and flip270, possibly prefixed by '@'.\n"
+ "The number represents the clockwise rotation in degrees; the "
+ "flip\" keyword applies a horizontal flip before the "
+ "rotation.\n"
+ "If a leading '@' is passed (@90) for display capture, then "
+ "the rotation is locked, and is relative to the natural device "
+ "orientation.\n"
+ "If '@' is passed alone, then the rotation is locked to the "
+ "initial device orientation.\n"
+ "Default is 0.",
+ },
+ {
+ // deprecated
.longopt_id = OPT_LOCK_VIDEO_ORIENTATION,
.longopt = "lock-video-orientation",
.argdesc = "value",
- .optional_arg = true,
- .text = "Lock capture video orientation to value.\n"
- "Possible values are \"unlocked\", \"initial\" (locked to the "
- "initial orientation), 0, 90, 180 and 270. The values "
- "represent the clockwise rotation from the natural device "
- "orientation, in degrees.\n"
- "Default is \"unlocked\".\n"
- "Passing the option without argument is equivalent to passing "
- "\"initial\".",
},
{
.shortopt = 'm',
@@ -1582,66 +1592,6 @@ parse_audio_output_buffer(const char *s, sc_tick *tick) {
return true;
}
-static bool
-parse_lock_video_orientation(const char *s,
- enum sc_lock_video_orientation *lock_mode) {
- if (!s || !strcmp(s, "initial")) {
- // Without argument, lock the initial orientation
- *lock_mode = SC_LOCK_VIDEO_ORIENTATION_INITIAL;
- return true;
- }
-
- if (!strcmp(s, "unlocked")) {
- *lock_mode = SC_LOCK_VIDEO_ORIENTATION_UNLOCKED;
- return true;
- }
-
- if (!strcmp(s, "0")) {
- *lock_mode = SC_LOCK_VIDEO_ORIENTATION_0;
- return true;
- }
-
- if (!strcmp(s, "90")) {
- *lock_mode = SC_LOCK_VIDEO_ORIENTATION_90;
- return true;
- }
-
- if (!strcmp(s, "180")) {
- *lock_mode = SC_LOCK_VIDEO_ORIENTATION_180;
- return true;
- }
-
- if (!strcmp(s, "270")) {
- *lock_mode = SC_LOCK_VIDEO_ORIENTATION_270;
- return true;
- }
-
- if (!strcmp(s, "1")) {
- LOGW("--lock-video-orientation=1 is deprecated, use "
- "--lock-video-orientation=270 instead.");
- *lock_mode = SC_LOCK_VIDEO_ORIENTATION_270;
- return true;
- }
-
- if (!strcmp(s, "2")) {
- LOGW("--lock-video-orientation=2 is deprecated, use "
- "--lock-video-orientation=180 instead.");
- *lock_mode = SC_LOCK_VIDEO_ORIENTATION_180;
- return true;
- }
-
- if (!strcmp(s, "3")) {
- LOGW("--lock-video-orientation=3 is deprecated, use "
- "--lock-video-orientation=90 instead.");
- *lock_mode = SC_LOCK_VIDEO_ORIENTATION_90;
- return true;
- }
-
- LOGE("Unsupported --lock-video-orientation value: %s (expected initial, "
- "unlocked, 0, 90, 180 or 270).", s);
- return false;
-}
-
static bool
parse_rotation(const char *s, uint8_t *rotation) {
long value;
@@ -1693,6 +1643,32 @@ parse_orientation(const char *s, enum sc_orientation *orientation) {
return false;
}
+static bool
+parse_capture_orientation(const char *s, enum sc_orientation *orientation,
+ enum sc_orientation_lock *lock) {
+ if (*s == '\0') {
+ LOGE("Capture orientation may not be empty (expected 0, 90, 180, 270, "
+ "flip0, flip90, flip180 or flip270, possibly prefixed by '@')");
+ return false;
+ }
+
+ // Lock the orientation by a leading '@'
+ if (s[0] == '@') {
+ // Consume '@'
+ ++s;
+ if (*s == '\0') {
+ // Only '@': lock to the initial orientation (orientation is unused)
+ *lock = SC_ORIENTATION_LOCKED_INITIAL;
+ return true;
+ }
+ *lock = SC_ORIENTATION_LOCKED_VALUE;
+ } else {
+ *lock = SC_ORIENTATION_UNLOCKED;
+ }
+
+ return parse_orientation(s, orientation);
+}
+
static bool
parse_window_position(const char *s, int16_t *position) {
// special value for "auto"
@@ -2367,8 +2343,13 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
"--mouse=uhid instead.");
return false;
case OPT_LOCK_VIDEO_ORIENTATION:
- if (!parse_lock_video_orientation(optarg,
- &opts->lock_video_orientation)) {
+ LOGE("--lock-video-orientation has been removed, use "
+ "--capture-orientation instead.");
+ return false;
+ case OPT_CAPTURE_ORIENTATION:
+ if (!parse_capture_orientation(optarg,
+ &opts->capture_orientation,
+ &opts->capture_orientation_lock)) {
return false;
}
break;
@@ -2852,13 +2833,6 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
return false;
}
- if (opts->lock_video_orientation ==
- SC_LOCK_VIDEO_ORIENTATION_UNLOCKED) {
- LOGI("Video orientation is locked for v4l2 sink. "
- "See --lock-video-orientation.");
- opts->lock_video_orientation = SC_LOCK_VIDEO_ORIENTATION_INITIAL;
- }
-
// V4L2 could not handle size change.
// Do not log because downsizing on error is the default behavior,
// not an explicit request from the user.
diff --git a/app/src/options.c b/app/src/options.c
index 3cad9d9f..69f8f64d 100644
--- a/app/src/options.c
+++ b/app/src/options.c
@@ -50,7 +50,8 @@ const struct scrcpy_options scrcpy_options_default = {
.video_bit_rate = 0,
.audio_bit_rate = 0,
.max_fps = NULL,
- .lock_video_orientation = SC_LOCK_VIDEO_ORIENTATION_UNLOCKED,
+ .capture_orientation = SC_ORIENTATION_0,
+ .capture_orientation_lock = SC_ORIENTATION_UNLOCKED,
.display_orientation = SC_ORIENTATION_0,
.record_orientation = SC_ORIENTATION_0,
.window_x = SC_WINDOW_POSITION_UNDEFINED,
diff --git a/app/src/options.h b/app/src/options.h
index 9236c3f8..945fcdf7 100644
--- a/app/src/options.h
+++ b/app/src/options.h
@@ -84,6 +84,12 @@ enum sc_orientation { // v v v
SC_ORIENTATION_FLIP_270, // 1 1 1
};
+enum sc_orientation_lock {
+ SC_ORIENTATION_UNLOCKED,
+ SC_ORIENTATION_LOCKED_VALUE, // lock to specified orientation
+ SC_ORIENTATION_LOCKED_INITIAL, // lock to initial device orientation
+};
+
static inline bool
sc_orientation_is_mirror(enum sc_orientation orientation) {
assert(!(orientation & ~7));
@@ -130,16 +136,6 @@ sc_orientation_get_name(enum sc_orientation orientation) {
}
}
-enum sc_lock_video_orientation {
- SC_LOCK_VIDEO_ORIENTATION_UNLOCKED = -1,
- // lock the current orientation when scrcpy starts
- SC_LOCK_VIDEO_ORIENTATION_INITIAL = -2,
- SC_LOCK_VIDEO_ORIENTATION_0 = 0,
- SC_LOCK_VIDEO_ORIENTATION_90 = 3,
- SC_LOCK_VIDEO_ORIENTATION_180 = 2,
- SC_LOCK_VIDEO_ORIENTATION_270 = 1,
-};
-
enum sc_keyboard_input_mode {
SC_KEYBOARD_INPUT_MODE_AUTO,
SC_KEYBOARD_INPUT_MODE_UHID_OR_AOA, // normal vs otg mode
@@ -251,7 +247,8 @@ struct scrcpy_options {
uint32_t video_bit_rate;
uint32_t audio_bit_rate;
const char *max_fps; // float to be parsed by the server
- enum sc_lock_video_orientation lock_video_orientation;
+ enum sc_orientation capture_orientation;
+ enum sc_orientation_lock capture_orientation_lock;
enum sc_orientation display_orientation;
enum sc_orientation record_orientation;
int16_t window_x; // SC_WINDOW_POSITION_UNDEFINED for "auto"
diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c
index 2721c0d8..5528910a 100644
--- a/app/src/scrcpy.c
+++ b/app/src/scrcpy.c
@@ -429,7 +429,8 @@ scrcpy(struct scrcpy_options *options) {
.audio_bit_rate = options->audio_bit_rate,
.max_fps = options->max_fps,
.screen_off_timeout = options->screen_off_timeout,
- .lock_video_orientation = options->lock_video_orientation,
+ .capture_orientation = options->capture_orientation,
+ .capture_orientation_lock = options->capture_orientation_lock,
.control = options->control,
.display_id = options->display_id,
.new_display = options->new_display,
diff --git a/app/src/server.c b/app/src/server.c
index 41f0bf27..9c12500e 100644
--- a/app/src/server.c
+++ b/app/src/server.c
@@ -274,9 +274,17 @@ execute_server(struct sc_server *server,
VALIDATE_STRING(params->max_fps);
ADD_PARAM("max_fps=%s", params->max_fps);
}
- if (params->lock_video_orientation != SC_LOCK_VIDEO_ORIENTATION_UNLOCKED) {
- ADD_PARAM("lock_video_orientation=%" PRIi8,
- params->lock_video_orientation);
+ if (params->capture_orientation_lock != SC_ORIENTATION_UNLOCKED
+ || params->capture_orientation != SC_ORIENTATION_0) {
+ if (params->capture_orientation_lock == SC_ORIENTATION_LOCKED_INITIAL) {
+ ADD_PARAM("capture_orientation=@");
+ } else {
+ const char *orient =
+ sc_orientation_get_name(params->capture_orientation);
+ bool locked =
+ params->capture_orientation_lock != SC_ORIENTATION_UNLOCKED;
+ ADD_PARAM("capture_orientation=%s%s", locked ? "@" : "", orient);
+ }
}
if (server->tunnel.forward) {
ADD_PARAM("tunnel_forward=true");
diff --git a/app/src/server.h b/app/src/server.h
index 7059be7f..20d998e9 100644
--- a/app/src/server.h
+++ b/app/src/server.h
@@ -46,7 +46,8 @@ struct sc_server_params {
uint32_t audio_bit_rate;
const char *max_fps; // float to be parsed by the server
sc_tick screen_off_timeout;
- int8_t lock_video_orientation;
+ enum sc_orientation capture_orientation;
+ enum sc_orientation_lock capture_orientation_lock;
bool control;
uint32_t display_id;
const char *new_display;
diff --git a/app/tests/test_cli.c b/app/tests/test_cli.c
index 14765792..de605cb9 100644
--- a/app/tests/test_cli.c
+++ b/app/tests/test_cli.c
@@ -51,7 +51,6 @@ static void test_options(void) {
"--fullscreen",
"--max-fps", "30",
"--max-size", "1024",
- "--lock-video-orientation=2", // optional arguments require '='
// "--no-control" is not compatible with "--turn-screen-off"
// "--no-playback" is not compatible with "--fulscreen"
"--port", "1234:1236",
@@ -80,7 +79,6 @@ static void test_options(void) {
assert(opts->fullscreen);
assert(!strcmp(opts->max_fps, "30"));
assert(opts->max_size == 1024);
- assert(opts->lock_video_orientation == 2);
assert(opts->port_range.first == 1234);
assert(opts->port_range.last == 1236);
assert(!strcmp(opts->push_target, "/sdcard/Movies"));
diff --git a/doc/video.md b/doc/video.md
index 74ec74dd..c00b6602 100644
--- a/doc/video.md
+++ b/doc/video.md
@@ -103,21 +103,39 @@ The orientation may be applied at 3 different levels:
- The [shortcut](shortcuts.md) MOD+r requests the
device to switch between portrait and landscape (the current running app may
refuse, if it does not support the requested orientation).
- - `--lock-video-orientation` changes the mirroring orientation (the orientation
+ - `--capture-orientation` changes the mirroring orientation (the orientation
of the video sent from the device to the computer). This affects the
recording.
- `--orientation` is applied on the client side, and affects display and
recording. For the display, it can be changed dynamically using
[shortcuts](shortcuts.md).
-To lock the mirroring orientation (on the capture side):
+To capture the video with a specific orientation:
```bash
-scrcpy --lock-video-orientation # initial (current) orientation
-scrcpy --lock-video-orientation=0 # natural orientation
-scrcpy --lock-video-orientation=90 # 90° clockwise
-scrcpy --lock-video-orientation=180 # 180°
-scrcpy --lock-video-orientation=270 # 270° clockwise
+scrcpy --capture-orientation=0
+scrcpy --capture-orientation=90 # 90° clockwise
+scrcpy --capture-orientation=180 # 180°
+scrcpy --capture-orientation=270 # 270° clockwise
+scrcpy --capture-orientation=flip0 # hflip
+scrcpy --capture-orientation=flip90 # hflip + 90° clockwise
+scrcpy --capture-orientation=flip180 # hflip + 180°
+scrcpy --capture-orientation=flip270 # hflip + 270° clockwise
+```
+
+The capture orientation can be locked by using `@`, so that a physical device
+rotation does not change the captured video orientation:
+
+```bash
+scrcpy --capture-orientation=@ # locked to the initial orientation
+scrcpy --capture-orientation=@0 # locked to 0°
+scrcpy --capture-orientation=@90 # locked to 90° clockwise
+scrcpy --capture-orientation=@180 # locked to 180°
+scrcpy --capture-orientation=@270 # locked to 270° clockwise
+scrcpy --capture-orientation=@flip0 # locked to hflip
+scrcpy --capture-orientation=@flip90 # locked to hflip + 90° clockwise
+scrcpy --capture-orientation=@flip180 # locked to hflip + 180°
+scrcpy --capture-orientation=@flip270 # locked to hflip + 270° clockwise
```
To orient the video (on the rendering side):
diff --git a/server/src/main/java/com/genymobile/scrcpy/Options.java b/server/src/main/java/com/genymobile/scrcpy/Options.java
index c1620432..e1b3b9af 100644
--- a/server/src/main/java/com/genymobile/scrcpy/Options.java
+++ b/server/src/main/java/com/genymobile/scrcpy/Options.java
@@ -4,6 +4,7 @@ import com.genymobile.scrcpy.audio.AudioCodec;
import com.genymobile.scrcpy.audio.AudioSource;
import com.genymobile.scrcpy.device.Device;
import com.genymobile.scrcpy.device.NewDisplay;
+import com.genymobile.scrcpy.device.Orientation;
import com.genymobile.scrcpy.device.Size;
import com.genymobile.scrcpy.util.CodecOption;
import com.genymobile.scrcpy.util.Ln;
@@ -13,6 +14,7 @@ import com.genymobile.scrcpy.video.VideoCodec;
import com.genymobile.scrcpy.video.VideoSource;
import android.graphics.Rect;
+import android.util.Pair;
import java.util.List;
import java.util.Locale;
@@ -32,7 +34,6 @@ public class Options {
private int videoBitRate = 8000000;
private int audioBitRate = 128000;
private float maxFps;
- private int lockVideoOrientation = -1;
private boolean tunnelForward;
private Rect crop;
private boolean control = true;
@@ -59,6 +60,9 @@ public class Options {
private NewDisplay newDisplay;
+ private Orientation.Lock captureOrientationLock = Orientation.Lock.Unlocked;
+ private Orientation captureOrientation = Orientation.Orient0;
+
private boolean listEncoders;
private boolean listDisplays;
private boolean listCameras;
@@ -123,10 +127,6 @@ public class Options {
return maxFps;
}
- public int getLockVideoOrientation() {
- return lockVideoOrientation;
- }
-
public boolean isTunnelForward() {
return tunnelForward;
}
@@ -219,6 +219,14 @@ public class Options {
return newDisplay;
}
+ public Orientation getCaptureOrientation() {
+ return captureOrientation;
+ }
+
+ public Orientation.Lock getCaptureOrientationLock() {
+ return captureOrientationLock;
+ }
+
public boolean getList() {
return listEncoders || listDisplays || listCameras || listCameraSizes || listApps;
}
@@ -341,9 +349,6 @@ public class Options {
case "max_fps":
options.maxFps = parseFloat("max_fps", value);
break;
- case "lock_video_orientation":
- options.lockVideoOrientation = Integer.parseInt(value);
- break;
case "tunnel_forward":
options.tunnelForward = Boolean.parseBoolean(value);
break;
@@ -448,6 +453,11 @@ public class Options {
case "new_display":
options.newDisplay = parseNewDisplay(value);
break;
+ case "capture_orientation":
+ Pair pair = parseCaptureOrientation(value);
+ options.captureOrientationLock = pair.first;
+ options.captureOrientation = pair.second;
+ break;
case "send_device_meta":
options.sendDeviceMeta = Boolean.parseBoolean(value);
break;
@@ -571,4 +581,25 @@ public class Options {
return new NewDisplay(size, dpi);
}
+
+ private static Pair parseCaptureOrientation(String value) {
+ if (value.isEmpty()) {
+ throw new IllegalArgumentException("Empty capture orientation string");
+ }
+
+ Orientation.Lock lock;
+ if (value.charAt(0) == '@') {
+ // Consume '@'
+ value = value.substring(1);
+ if (value.isEmpty()) {
+ // Only '@': lock to the initial orientation (orientation is unused)
+ return Pair.create(Orientation.Lock.LockedInitial, Orientation.Orient0);
+ }
+ lock = Orientation.Lock.LockedValue;
+ } else {
+ lock = Orientation.Lock.Unlocked;
+ }
+
+ return Pair.create(lock, Orientation.getByName(value));
+ }
}
diff --git a/server/src/main/java/com/genymobile/scrcpy/device/Device.java b/server/src/main/java/com/genymobile/scrcpy/device/Device.java
index 09c7d2b6..cd713499 100644
--- a/server/src/main/java/com/genymobile/scrcpy/device/Device.java
+++ b/server/src/main/java/com/genymobile/scrcpy/device/Device.java
@@ -40,9 +40,6 @@ 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_FINISH = InputManager.INJECT_INPUT_EVENT_MODE_WAIT_FOR_FINISH;
- public static final int LOCK_VIDEO_ORIENTATION_UNLOCKED = -1;
- public static final int LOCK_VIDEO_ORIENTATION_INITIAL = -2;
-
private Device() {
// not instantiable
}
diff --git a/server/src/main/java/com/genymobile/scrcpy/device/Orientation.java b/server/src/main/java/com/genymobile/scrcpy/device/Orientation.java
new file mode 100644
index 00000000..78296a36
--- /dev/null
+++ b/server/src/main/java/com/genymobile/scrcpy/device/Orientation.java
@@ -0,0 +1,47 @@
+package com.genymobile.scrcpy.device;
+
+public enum Orientation {
+
+ // @formatter:off
+ Orient0("0"),
+ Orient90("90"),
+ Orient180("180"),
+ Orient270("270"),
+ Flip0("flip0"),
+ Flip90("flip90"),
+ Flip180("flip180"),
+ Flip270("flip270");
+
+ public enum Lock {
+ Unlocked, LockedInitial, LockedValue,
+ }
+
+ private final String name;
+
+ Orientation(String name) {
+ this.name = name;
+ }
+
+ public static Orientation getByName(String name) {
+ for (Orientation orientation : values()) {
+ if (orientation.name.equals(name)) {
+ return orientation;
+ }
+ }
+
+ throw new IllegalArgumentException("Unknown orientation: " + name);
+ }
+
+ public static Orientation fromRotation(int rotation) {
+ assert rotation >= 0 && rotation < 4;
+ return values()[rotation];
+ }
+
+ public boolean isFlipped() {
+ return (ordinal() & 4) != 0;
+ }
+
+ public int getRotation() {
+ return this.ordinal() & 3;
+ }
+}
diff --git a/server/src/main/java/com/genymobile/scrcpy/video/ScreenCapture.java b/server/src/main/java/com/genymobile/scrcpy/video/ScreenCapture.java
index d8c1d3cc..8cbb00d0 100644
--- a/server/src/main/java/com/genymobile/scrcpy/video/ScreenCapture.java
+++ b/server/src/main/java/com/genymobile/scrcpy/video/ScreenCapture.java
@@ -6,6 +6,7 @@ import com.genymobile.scrcpy.control.PositionMapper;
import com.genymobile.scrcpy.device.ConfigurationException;
import com.genymobile.scrcpy.device.Device;
import com.genymobile.scrcpy.device.DisplayInfo;
+import com.genymobile.scrcpy.device.Orientation;
import com.genymobile.scrcpy.device.Size;
import com.genymobile.scrcpy.opengl.AffineOpenGLFilter;
import com.genymobile.scrcpy.opengl.OpenGLFilter;
@@ -35,7 +36,8 @@ public class ScreenCapture extends DisplayCapture {
private final int displayId;
private int maxSize;
private final Rect crop;
- private int lockVideoOrientation;
+ private Orientation.Lock captureOrientationLock;
+ private Orientation captureOrientation;
private DisplayInfo displayInfo;
private Size videoSize;
@@ -62,7 +64,10 @@ public class ScreenCapture extends DisplayCapture {
assert displayId != Device.DISPLAY_ID_NONE;
this.maxSize = options.getMaxSize();
this.crop = options.getCrop();
- this.lockVideoOrientation = options.getLockVideoOrientation();
+ this.captureOrientationLock = options.getCaptureOrientationLock();
+ this.captureOrientation = options.getCaptureOrientation();
+ assert captureOrientationLock != null;
+ assert captureOrientation != null;
}
@Override
@@ -106,9 +111,10 @@ public class ScreenCapture extends DisplayCapture {
Size displaySize = displayInfo.getSize();
setSessionDisplaySize(displaySize);
- if (lockVideoOrientation == Device.LOCK_VIDEO_ORIENTATION_INITIAL) {
+ if (captureOrientationLock == Orientation.Lock.LockedInitial) {
// The user requested to lock the video orientation to the current orientation
- lockVideoOrientation = displayInfo.getRotation();
+ captureOrientationLock = Orientation.Lock.LockedValue;
+ captureOrientation = Orientation.fromRotation(displayInfo.getRotation());
}
VideoFilter filter = new VideoFilter(displaySize);
@@ -118,9 +124,8 @@ public class ScreenCapture extends DisplayCapture {
filter.addCrop(crop, transposed);
}
- if (lockVideoOrientation != Device.LOCK_VIDEO_ORIENTATION_UNLOCKED) {
- filter.addLockVideoOrientation(lockVideoOrientation, displayInfo.getRotation());
- }
+ boolean locked = captureOrientationLock != Orientation.Lock.Unlocked;
+ filter.addOrientation(displayInfo.getRotation(), locked, captureOrientation);
transform = filter.getInverseTransform();
videoSize = filter.getOutputSize().limit(maxSize).round8();
diff --git a/server/src/main/java/com/genymobile/scrcpy/video/VideoFilter.java b/server/src/main/java/com/genymobile/scrcpy/video/VideoFilter.java
index 2bcfc895..05170930 100644
--- a/server/src/main/java/com/genymobile/scrcpy/video/VideoFilter.java
+++ b/server/src/main/java/com/genymobile/scrcpy/video/VideoFilter.java
@@ -1,5 +1,6 @@
package com.genymobile.scrcpy.video;
+import com.genymobile.scrcpy.device.Orientation;
import com.genymobile.scrcpy.device.Size;
import com.genymobile.scrcpy.util.AffineMatrix;
@@ -72,9 +73,20 @@ public class VideoFilter {
size = size.rotate();
}
}
-
- public void addLockVideoOrientation(int lockVideoOrientation, int displayRotation) {
- int ccwRotation = (4 + lockVideoOrientation - displayRotation) % 4;
+ public void addOrientation(Orientation captureOrientation) {
+ if (captureOrientation.isFlipped()) {
+ transform = AffineMatrix.hflip().multiply(transform);
+ }
+ int ccwRotation = (4 - captureOrientation.getRotation()) % 4;
addRotation(ccwRotation);
}
+
+ public void addOrientation(int displayRotation, boolean locked, Orientation captureOrientation) {
+ if (locked) {
+ // flip/rotate the current display from the natural device orientation (i.e. where display rotation is 0)
+ int reverseDisplayRotation = (4 - displayRotation) % 4;
+ addRotation(reverseDisplayRotation);
+ }
+ addOrientation(captureOrientation);
+ }
}