Compare commits

...

11 Commits

Author SHA1 Message Date
Romain Vimont
c5c2734db9 Keep the display mode daemon process alive
On Android 14, a separate process was spawn on every display mode
request (to turn the screen off or on).

But starting a java process takes time (a few hundred milliseconds),
causing a noticeable latency between the request to turn the screen off
(MOD+o) or on (MOD+Shift+o) and the actual power mode change.

To minimize this latency, keep the process alive between requests, so
that only the first one will have to spawn the process.

It would be possible to spawn the process in advance, so that even the
first request would be immediate, but any problem would impact all
Android 14 users even without using the "turn screen off" feature.

PR #4446 <https://github.com/Genymobile/scrcpy/pull/4446>
2023-11-23 10:02:10 +01:00
Romain Vimont
c1f2932db8 Fix turn screen off on Android 14
On Android 14, execute a separate process with a different classpath and
LD_PRELOAD to execute the methods required to turn the device screen
off.

Fixes #3927 <https://github.com/Genymobile/scrcpy/issues/3927>
Refs #3927 comment <https://github.com/Genymobile/scrcpy/issues/3927#issuecomment-1790031953>
PR #4446 <https://github.com/Genymobile/scrcpy/pull/4446>

Co-authored-by: Simon Chan <1330321+yume-chan@users.noreply.github.com>
2023-11-23 09:27:38 +01:00
Romain Vimont
890ba529c3 Keep server file on the device until clean up
The server was unlinked (removed) just after it started.

In order to execute a new process using the server jarfile at any time
(typically to set the display power mode from another process), keep the
file until the server closes.
2023-11-21 17:01:17 +01:00
Romain Vimont
798727aa58 Use a different server path for each session
When scrcpy is run, a server is pushed to
/data/local/tmp/scrcpy-server.jar.

Running simultaneous scrcpy instances on the same device was not a
problem, because the file was unlinked (removed) almost immediately once
it started, avoiding any conflicts.

In order to support executing new process using the scrcpy-server.jar at
any time (to change the display power mode from a separate process), the
server file must not be unlinked, so using different names are necessary
to avoid conflicts.

Reuse the scid mechanism already used for generating device socket
names.

Refs 4315be164823d2c8fc44b475b52af79bfee98ff1
Refs 439a1fd4ed3d7ada4c5098a6d52893dd4e87082a
2023-11-21 17:01:06 +01:00
sam80180
e76ff25e31 Do not hardcode server path on the device
The path can be retrieved from the classpath.

PR #4416 <https://github.com/Genymobile/scrcpy/pull/4416>

Co-authored-by: Romain Vimont <rom@rom1v.com>
Signed-off-by: Romain Vimont <rom@rom1v.com>
2023-11-21 16:59:37 +01:00
Romain Vimont
ec80be1eda Fix meson deprecated 'pkgconfig' to 'pkg-config'
When running ./release.sh:

> DEPRECATION: "pkgconfig" entry is deprecated and should be replaced by
> "pkg-config"
2023-11-21 16:59:37 +01:00
Romain Vimont
cdd78273dc Update documentation about video orientation 2023-11-21 00:01:28 +01:00
Romain Vimont
c546293f35 Add --orientation
Add a shortcut to set both the display and record orientations.
2023-11-21 00:01:28 +01:00
Romain Vimont
70baf3c384 Add --record-orientation
Add an option to store the orientation to apply in a recorded file.

Only rotations are supported (not flip), and most players correctly
handle it only for MP4 files (not MKV files).
2023-11-21 00:01:28 +01:00
Romain Vimont
dac0d54c5c Pass --lock-video-orientation argument in degrees
For consistency with the new --display-orientation option, express the
--lock-video-orientation in degrees clockwise:

 * --lock-video-orientation=0 -> --lock-video-orientation=0
 * --lock-video-orientation=3 -> --lock-video-orientation=90
 * --lock-video-orientation=2 -> --lock-video-orientation=180
 * --lock-video-orientation=1 -> --lock-video-orientation=270
2023-11-21 00:01:28 +01:00
Romain Vimont
878bfb1d8b Add --display-orientation
Deprecate the option --rotation and introduce a new option
--display-orientation with the 8 possible orientations (0, 90, 180, 270,
flip0, flip90, flip180 and flip270).

New shortcuts MOD+Shift+(arrow) dynamically change the display
(horizontal or vertical) flip.

Fixes #1380 <https://github.com/Genymobile/scrcpy/issues/1380>
Fixes #3819 <https://github.com/Genymobile/scrcpy/issues/3819>
2023-11-21 00:01:13 +01:00
30 changed files with 877 additions and 130 deletions

View File

@ -21,6 +21,7 @@ _scrcpy() {
--disable-screensaver
--display-buffer=
--display-id=
--display-orientation=
-e --select-tcpip
-f --fullscreen
--force-adb-forward
@ -50,6 +51,7 @@ _scrcpy() {
--no-power-on
--no-video
--no-video-playback
--orientation=
--otg
-p --port=
--pause-on-exit
@ -61,6 +63,7 @@ _scrcpy() {
-r --record=
--raw-key-events
--record-format=
--record-orientation=
--render-driver=
--require-audio
--rotation=
@ -112,8 +115,17 @@ _scrcpy() {
COMPREPLY=($(compgen -W 'front back external' -- "$cur"))
return
;;
--orientation
--display-orientation)
COMPREPLY=($(compgen -> '0 90 180 270 flip0 flip90 flip180 flip270' -- "$cur"))
return
;;
--record-orientation)
COMPREPLY=($(compgen -> '0 90 180 270' -- "$cur"))
return
;;
--lock-video-orientation)
COMPREPLY=($(compgen -W 'unlocked initial 0 1 2 3' -- "$cur"))
COMPREPLY=($(compgen -W 'unlocked initial 0 90 180 270' -- "$cur"))
return
;;
--pause-on-exit)
@ -132,10 +144,6 @@ _scrcpy() {
COMPREPLY=($(compgen -W 'direct3d opengl opengles2 opengles metal software' -- "$cur"))
return
;;
--rotation)
COMPREPLY=($(compgen -W '0 1 2 3' -- "$cur"))
return
;;
--shortcut-mod)
# Only auto-complete a single key
COMPREPLY=($(compgen -W 'lctrl rctrl lalt ralt lsuper rsuper' -- "$cur"))

View File

@ -28,6 +28,7 @@ arguments=(
'--disable-screensaver[Disable screensaver while scrcpy is running]'
'--display-buffer=[Add a buffering delay \(in milliseconds\) before displaying]'
'--display-id=[Specify the display id to mirror]'
'--display-orientation=[Set the initial display orientation]:orientation values:(0 90 180 270 flip0 flip90 flip180 flip270)'
{-e,--select-tcpip}'[Use TCP/IP device]'
{-f,--fullscreen}'[Start in fullscreen]'
'--force-adb-forward[Do not attempt to use \"adb reverse\" to connect to the device]'
@ -40,7 +41,7 @@ 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 1 2 3)'
'--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,--hid-mouse}'[Simulate a physical mouse by using HID over AOAv2]'
'--max-fps=[Limit the frame rate of screen capture]'
@ -56,6 +57,7 @@ arguments=(
'--no-power-on[Do not power on the device on start]'
'--no-video[Disable video forwarding]'
'--no-video-playback[Disable video playback]'
'--orientation=[Set the video orientation]:orientation values:(0 90 180 270 flip0 flip90 flip180 flip270)'
'--otg[Run in OTG mode \(simulating physical keyboard and mouse\)]'
{-p,--port=}'[\[port\[\:port\]\] Set the TCP port \(range\) used by the client to listen]'
'--pause-on-exit=[Make scrcpy pause before exiting]:mode:(true false if-error)'
@ -66,9 +68,9 @@ arguments=(
{-r,--record=}'[Record screen to file]:record file:_files'
'--raw-key-events[Inject key events for all input keys, and ignore text events]'
'--record-format=[Force recording format]:format:(mp4 mkv m4a mka opus aac flac wav)'
'--record-orientation=[Set the record orientation]:orientation values:(0 90 180 270)'
'--render-driver=[Request SDL to use the given render driver]:driver name:(direct3d opengl opengles2 opengles metal software)'
'--require-audio=[Make scrcpy fail if audio is enabled but does not work]'
'--rotation=[Set the initial display rotation]:rotation values:(0 1 2 3)'
{-s,--serial=}'[The device serial number \(mandatory for multiple devices only\)]:serial:($("${ADB-adb}" devices | awk '\''$2 == "device" {print $1}'\''))'
{-S,--turn-screen-off}'[Turn the device screen off immediately]'
'--shortcut-mod=[\[key1,key2+key3,...\] Specify the modifiers to use for scrcpy shortcuts]:shortcut mod:(lctrl rctrl lalt ralt lsuper rsuper)'

View File

@ -289,6 +289,10 @@ if get_option('buildtype') == 'debug'
'tests/test_device_msg_deserialize.c',
'src/device_msg.c',
]],
['test_orientation', [
'tests/test_orientation.c',
'src/options.c',
]],
['test_strbuf', [
'tests/test_strbuf.c',
'src/util/strbuf.c',

View File

@ -141,6 +141,14 @@ The available display ids can be listed by \fB\-\-list\-displays\fR.
Default is 0.
.TP
.BI "\-\-display\-orientation " value
Set the initial display orientation.
Possible values are 0, 90, 180, 270, flip0, flip90, flip180 and flip270. The number represents the clockwise rotation in degrees; the "flip" keyword applies a horizontal flip before the rotation.
Default is 0.
.TP
.B \-e, \-\-select\-tcpip
Use TCP/IP device (if there is exactly one, like adb -e).
@ -207,7 +215,9 @@ List displays available on the device.
.TP
\fB\-\-lock\-video\-orientation\fR[=\fIvalue\fR]
Lock video orientation to \fIvalue\fR. Possible values are "unlocked", "initial" (locked to the initial orientation), 0, 1, 2 and 3. Natural device orientation is 0, and each increment adds a 90 degrees rotation counterclockwise.
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".
@ -289,6 +299,10 @@ Disable video forwarding.
.B \-\-no\-video\-playback
Disable video playback on the computer.
.TP
.BI "\-\-orientation " value
Same as --display-orientation=value --record-orientation=value.
.TP
.B \-\-otg
Run in OTG mode: simulate physical keyboard and mouse, as if the computer keyboard and mouse were plugged directly to the device via an OTG cable.
@ -357,6 +371,14 @@ Inject key events for all input keys, and ignore text events.
.BI "\-\-record\-format " format
Force recording format (mp4, mkv, m4a, mka, opus, aac, flac or wav).
.TP
.BI "\-\-record\-orientation " value
Set the record orientation.
Possible values are 0, 90, 180 and 270. The number represents the clockwise rotation in degrees.
Default is 0.
.TP
.BI "\-\-render\-driver " name
Request SDL to use the given render driver (this is just a hint).
@ -369,10 +391,6 @@ Supported names are currently "direct3d", "opengl", "opengles2", "opengles", "me
.B \-\-require\-audio
By default, scrcpy mirrors only the video if audio capture fails on the device. This option makes scrcpy fail if audio is enabled but does not work.
.TP
.BI "\-\-rotation " value
Set the initial display rotation. Possibles values are 0, 1, 2 and 3. Each increment adds a 90 degrees rotation counterclockwise.
.TP
.BI "\-s, \-\-serial " number
The device serial number. Mandatory only if several devices are connected to adb.
@ -534,6 +552,14 @@ Rotate display left
.B MOD+Right
Rotate display right
.TP
.B MOD+Shift+Left, MOD+Shift+Right
Flip display horizontally
.TP
.B MOD+Shift+Up, MOD+Shift+Down
Flip display vertically
.TP
.B MOD+g
Resize window to 1:1 (pixel\-perfect)

View File

@ -90,6 +90,9 @@ enum {
OPT_CAMERA_AR,
OPT_CAMERA_FPS,
OPT_CAMERA_HIGH_SPEED,
OPT_DISPLAY_ORIENTATION,
OPT_RECORD_ORIENTATION,
OPT_ORIENTATION,
};
struct sc_option {
@ -309,6 +312,17 @@ static const struct sc_option options[] = {
" scrcpy --list-displays\n"
"Default is 0.",
},
{
.longopt_id = OPT_DISPLAY_ORIENTATION,
.longopt = "display-orientation",
.argdesc = "value",
.text = "Set the initial display orientation.\n"
"Possible values are 0, 90, 180, 270, flip0, flip90, flip180 "
"and flip270. The number represents the clockwise rotation "
"in degrees; the \"flip\" keyword applies a horizontal flip "
"before the rotation.\n"
"Default is 0.",
},
{
.shortopt = 'e',
.longopt = "select-tcpip",
@ -399,11 +413,11 @@ static const struct sc_option options[] = {
.longopt = "lock-video-orientation",
.argdesc = "value",
.optional_arg = true,
.text = "Lock video orientation to value.\n"
.text = "Lock capture video orientation to value.\n"
"Possible values are \"unlocked\", \"initial\" (locked to the "
"initial orientation), 0, 1, 2 and 3. Natural device "
"orientation is 0, and each increment adds a 90 degrees "
"rotation counterclockwise.\n"
"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\".",
@ -512,6 +526,13 @@ static const struct sc_option options[] = {
.longopt = "no-video-playback",
.text = "Disable video playback on the computer.",
},
{
.longopt_id = OPT_ORIENTATION,
.longopt = "orientation",
.argdesc = "value",
.text = "Same as --display-orientation=value "
"--record-orientation=value.",
},
{
.longopt_id = OPT_OTG,
.longopt = "otg",
@ -597,6 +618,15 @@ static const struct sc_option options[] = {
.text = "Force recording format (mp4, mkv, m4a, mka, opus, aac, flac "
"or wav).",
},
{
.longopt_id = OPT_RECORD_ORIENTATION,
.longopt = "record-orientation",
.argdesc = "value",
.text = "Set the record orientation.\n"
"Possible values are 0, 90, 180 and 270. The number represents "
"the clockwise rotation in degrees.\n"
"Default is 0.",
},
{
.longopt_id = OPT_RENDER_DRIVER,
.longopt = "render-driver",
@ -615,12 +645,10 @@ static const struct sc_option options[] = {
"is enabled but does not work."
},
{
// deprecated
.longopt_id = OPT_ROTATION,
.longopt = "rotation",
.argdesc = "value",
.text = "Set the initial display rotation.\n"
"Possible values are 0, 1, 2 and 3. Each increment adds a 90 "
"degrees rotation counterclockwise.",
},
{
.shortopt = 's',
@ -824,6 +852,14 @@ static const struct sc_shortcut shortcuts[] = {
.shortcuts = { "MOD+Right" },
.text = "Rotate display right",
},
{
.shortcuts = { "MOD+Shift+Left", "MOD+Shift+Right" },
.text = "Flip display horizontally",
},
{
.shortcuts = { "MOD+Shift+Up", "MOD+Shift+Down" },
.text = "Flip display vertically",
},
{
.shortcuts = { "MOD+g" },
.text = "Resize window to 1:1 (pixel-perfect)",
@ -1382,15 +1418,50 @@ parse_lock_video_orientation(const char *s,
return true;
}
long value;
bool ok = parse_integer_arg(s, &value, false, 0, 3,
"lock video orientation");
if (!ok) {
return false;
if (!strcmp(s, "0")) {
*lock_mode = SC_LOCK_VIDEO_ORIENTATION_0;
return true;
}
*lock_mode = (enum sc_lock_video_orientation) value;
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
@ -1405,6 +1476,45 @@ parse_rotation(const char *s, uint8_t *rotation) {
return true;
}
static bool
parse_orientation(const char *s, enum sc_orientation *orientation) {
if (!strcmp(s, "0")) {
*orientation = SC_ORIENTATION_0;
return true;
}
if (!strcmp(s, "90")) {
*orientation = SC_ORIENTATION_90;
return true;
}
if (!strcmp(s, "180")) {
*orientation = SC_ORIENTATION_180;
return true;
}
if (!strcmp(s, "270")) {
*orientation = SC_ORIENTATION_270;
return true;
}
if (!strcmp(s, "flip0")) {
*orientation = SC_ORIENTATION_FLIP_0;
return true;
}
if (!strcmp(s, "flip90")) {
*orientation = SC_ORIENTATION_FLIP_90;
return true;
}
if (!strcmp(s, "flip180")) {
*orientation = SC_ORIENTATION_FLIP_180;
return true;
}
if (!strcmp(s, "flip270")) {
*orientation = SC_ORIENTATION_FLIP_270;
return true;
}
LOGE("Unsupported orientation: %s (expected 0, 90, 180, 270, flip0, "
"flip90, flip180 or flip270)", optarg);
return false;
}
static bool
parse_window_position(const char *s, int16_t *position) {
// special value for "auto"
@ -2008,9 +2118,49 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
opts->key_inject_mode = SC_KEY_INJECT_MODE_RAW;
break;
case OPT_ROTATION:
if (!parse_rotation(optarg, &opts->rotation)) {
LOGW("--rotation is deprecated, use --display-orientation "
"instead.");
uint8_t rotation;
if (!parse_rotation(optarg, &rotation)) {
return false;
}
assert(rotation <= 3);
switch (rotation) {
case 0:
opts->display_orientation = SC_ORIENTATION_0;
break;
case 1:
// rotation 1 was 90° counterclockwise, but orientation
// is expressed clockwise
opts->display_orientation = SC_ORIENTATION_270;
break;
case 2:
opts->display_orientation = SC_ORIENTATION_180;
break;
case 3:
// rotation 1 was 270° counterclockwise, but orientation
// is expressed clockwise
opts->display_orientation = SC_ORIENTATION_90;
break;
}
break;
case OPT_DISPLAY_ORIENTATION:
if (!parse_orientation(optarg, &opts->display_orientation)) {
return false;
}
break;
case OPT_RECORD_ORIENTATION:
if (!parse_orientation(optarg, &opts->record_orientation)) {
return false;
}
break;
case OPT_ORIENTATION:
enum sc_orientation orientation;
if (!parse_orientation(optarg, &orientation)) {
return false;
}
opts->display_orientation = orientation;
opts->record_orientation = orientation;
break;
case OPT_RENDER_DRIVER:
opts->render_driver = optarg;
@ -2378,6 +2528,14 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
}
}
if (opts->record_orientation != SC_ORIENTATION_0) {
if (sc_orientation_is_mirror(opts->record_orientation)) {
LOGE("Record orientation only supports rotation, not "
"flipping: %s", optarg);
return false;
}
}
if (opts->video
&& sc_record_format_is_audio_only(opts->record_format)) {
LOGE("Audio container does not support video stream");

View File

@ -3,7 +3,9 @@
#include "config.h"
#include <libavcodec/version.h>
#include <libavformat/version.h>
#include <libavutil/version.h>
#include <SDL2/SDL_version.h>
#ifndef __WIN32
@ -50,6 +52,15 @@
# define SCRCPY_LAVU_HAS_CHLAYOUT
#endif
// In ffmpeg/doc/APIchanges:
// 2023-10-06 - 5432d2aacad - lavc 60.15.100 - avformat.h
// Deprecate AVFormatContext.{nb_,}side_data, av_stream_add_side_data(),
// av_stream_new_side_data(), and av_stream_get_side_data(). Side data fields
// from AVFormatContext.codecpar should be used from now on.
#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(60, 15, 100)
# define SCRCPY_LAVC_HAS_CODECPAR_CODEC_SIDEDATA
#endif
#if SDL_VERSION_ATLEAST(2, 0, 6)
// <https://github.com/libsdl-org/SDL/commit/d7a318de563125e5bb465b1000d6bc9576fbc6fc>
# define SCRCPY_SDL_HAS_HINT_TOUCH_MOUSE_EVENTS

View File

@ -234,7 +234,7 @@ sc_display_update_texture(struct sc_display *display, const AVFrame *frame) {
enum sc_display_result
sc_display_render(struct sc_display *display, const SDL_Rect *geometry,
unsigned rotation) {
enum sc_orientation orientation) {
SDL_RenderClear(display->renderer);
if (display->pending.flags) {
@ -247,33 +247,33 @@ sc_display_render(struct sc_display *display, const SDL_Rect *geometry,
SDL_Renderer *renderer = display->renderer;
SDL_Texture *texture = display->texture;
if (rotation == 0) {
if (orientation == SC_ORIENTATION_0) {
int ret = SDL_RenderCopy(renderer, texture, NULL, geometry);
if (ret) {
LOGE("Could not render texture: %s", SDL_GetError());
return SC_DISPLAY_RESULT_ERROR;
}
} else {
// rotation in RenderCopyEx() is clockwise, while screen->rotation is
// counterclockwise (to be consistent with --lock-video-orientation)
int cw_rotation = (4 - rotation) % 4;
unsigned cw_rotation = sc_orientation_get_rotation(orientation);
double angle = 90 * cw_rotation;
const SDL_Rect *dstrect = NULL;
SDL_Rect rect;
if (rotation & 1) {
if (sc_orientation_is_swap(orientation)) {
rect.x = geometry->x + (geometry->w - geometry->h) / 2;
rect.y = geometry->y + (geometry->h - geometry->w) / 2;
rect.w = geometry->h;
rect.h = geometry->w;
dstrect = &rect;
} else {
assert(rotation == 2);
dstrect = geometry;
}
SDL_RendererFlip flip = sc_orientation_is_mirror(orientation)
? SDL_FLIP_HORIZONTAL : 0;
int ret = SDL_RenderCopyEx(renderer, texture, NULL, dstrect, angle,
NULL, 0);
NULL, flip);
if (ret) {
LOGE("Could not render texture: %s", SDL_GetError());
return SC_DISPLAY_RESULT_ERROR;

View File

@ -9,6 +9,7 @@
#include "coords.h"
#include "opengl.h"
#include "options.h"
#ifdef __APPLE__
# define SC_DISPLAY_FORCE_OPENGL_CORE_PROFILE
@ -54,6 +55,6 @@ sc_display_update_texture(struct sc_display *display, const AVFrame *frame);
enum sc_display_result
sc_display_render(struct sc_display *display, const SDL_Rect *geometry,
unsigned rotation);
enum sc_orientation orientation);
#endif

View File

@ -293,15 +293,11 @@ rotate_device(struct sc_controller *controller) {
}
static void
rotate_client_left(struct sc_screen *screen) {
unsigned new_rotation = (screen->rotation + 1) % 4;
sc_screen_set_rotation(screen, new_rotation);
}
static void
rotate_client_right(struct sc_screen *screen) {
unsigned new_rotation = (screen->rotation + 3) % 4;
sc_screen_set_rotation(screen, new_rotation);
apply_orientation_transform(struct sc_screen *screen,
enum sc_orientation transform) {
enum sc_orientation new_orientation =
sc_orientation_apply(screen->orientation, transform);
sc_screen_set_orientation(screen, new_orientation);
}
static void
@ -421,25 +417,47 @@ sc_input_manager_process_key(struct sc_input_manager *im,
}
return;
case SDLK_DOWN:
if (controller && !shift) {
if (shift) {
if (!repeat & down) {
apply_orientation_transform(im->screen,
SC_ORIENTATION_FLIP_180);
}
} else if (controller) {
// forward repeated events
action_volume_down(controller, action);
}
return;
case SDLK_UP:
if (controller && !shift) {
if (shift) {
if (!repeat & down) {
apply_orientation_transform(im->screen,
SC_ORIENTATION_FLIP_180);
}
} else if (controller) {
// forward repeated events
action_volume_up(controller, action);
}
return;
case SDLK_LEFT:
if (!shift && !repeat && down) {
rotate_client_left(im->screen);
if (!repeat && down) {
if (shift) {
apply_orientation_transform(im->screen,
SC_ORIENTATION_FLIP_0);
} else {
apply_orientation_transform(im->screen,
SC_ORIENTATION_270);
}
}
return;
case SDLK_RIGHT:
if (!shift && !repeat && down) {
rotate_client_right(im->screen);
if (!repeat && down) {
if (shift) {
apply_orientation_transform(im->screen,
SC_ORIENTATION_FLIP_0);
} else {
apply_orientation_transform(im->screen,
SC_ORIENTATION_90);
}
}
return;
case SDLK_c:

View File

@ -39,7 +39,8 @@ const struct scrcpy_options scrcpy_options_default = {
.audio_bit_rate = 0,
.max_fps = 0,
.lock_video_orientation = SC_LOCK_VIDEO_ORIENTATION_UNLOCKED,
.rotation = 0,
.display_orientation = SC_ORIENTATION_0,
.record_orientation = SC_ORIENTATION_0,
.window_x = SC_WINDOW_POSITION_UNDEFINED,
.window_y = SC_WINDOW_POSITION_UNDEFINED,
.window_width = 0,
@ -89,3 +90,39 @@ const struct scrcpy_options scrcpy_options_default = {
.camera_high_speed = false,
.list = 0,
};
enum sc_orientation
sc_orientation_apply(enum sc_orientation src, enum sc_orientation transform) {
assert(!(src & ~7));
assert(!(transform & ~7));
unsigned transform_hflip = transform & 4;
unsigned transform_rotation = transform & 3;
unsigned src_hflip = src & 4;
unsigned src_rotation = src & 3;
unsigned src_swap = src & 1;
if (src_swap && transform_hflip) {
// If the src is rotated by 90 or 270 degrees, applying a flipped
// transformation requires an additional 180 degrees rotation to
// compensate for the inversion of the order of multiplication:
//
// hflip1 × rotate1 × hflip2 × rotate2
// `--------------' `--------------'
// src transform
//
// In the final result, we want all the hflips then all the rotations,
// so we must move hflip2 to the left:
//
// hflip1 × hflip2 × f(rotate1) × rotate2
//
// with f(rotate1) = | rotate1 if src is 0 or 180
// | rotate1 + 180 if src is 90 or 270
src_rotation += 2;
}
unsigned result_hflip = src_hflip ^ transform_hflip;
unsigned result_rotation = (transform_rotation + src_rotation) % 4;
enum sc_orientation result = result_hflip | result_rotation;
return result;
}

View File

@ -3,6 +3,7 @@
#include "common.h"
#include <assert.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
@ -67,14 +68,75 @@ enum sc_camera_facing {
SC_CAMERA_FACING_EXTERNAL,
};
// ,----- hflip (applied before the rotation)
// | ,--- 180°
// | | ,- 90° clockwise
// | | |
enum sc_orientation { // v v v
SC_ORIENTATION_0, // 0 0 0
SC_ORIENTATION_90, // 0 0 1
SC_ORIENTATION_180, // 0 1 0
SC_ORIENTATION_270, // 0 1 1
SC_ORIENTATION_FLIP_0, // 1 0 0
SC_ORIENTATION_FLIP_90, // 1 0 1
SC_ORIENTATION_FLIP_180, // 1 1 0
SC_ORIENTATION_FLIP_270, // 1 1 1
};
static inline bool
sc_orientation_is_mirror(enum sc_orientation orientation) {
assert(!(orientation & ~7));
return orientation & 4;
}
// Does the orientation swap width and height?
static inline bool
sc_orientation_is_swap(enum sc_orientation orientation) {
assert(!(orientation & ~7));
return orientation & 1;
}
static inline enum sc_orientation
sc_orientation_get_rotation(enum sc_orientation orientation) {
assert(!(orientation & ~7));
return orientation & 3;
}
enum sc_orientation
sc_orientation_apply(enum sc_orientation src, enum sc_orientation transform);
static inline const char *
sc_orientation_get_name(enum sc_orientation orientation) {
switch (orientation) {
case SC_ORIENTATION_0:
return "0";
case SC_ORIENTATION_90:
return "90";
case SC_ORIENTATION_180:
return "180";
case SC_ORIENTATION_270:
return "270";
case SC_ORIENTATION_FLIP_0:
return "flip0";
case SC_ORIENTATION_FLIP_90:
return "flip90";
case SC_ORIENTATION_FLIP_180:
return "flip180";
case SC_ORIENTATION_FLIP_270:
return "flip270";
default:
return "(unknown)";
}
}
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_1,
SC_LOCK_VIDEO_ORIENTATION_2,
SC_LOCK_VIDEO_ORIENTATION_3,
SC_LOCK_VIDEO_ORIENTATION_90 = 3,
SC_LOCK_VIDEO_ORIENTATION_180 = 2,
SC_LOCK_VIDEO_ORIENTATION_270 = 1,
};
enum sc_keyboard_input_mode {
@ -157,7 +219,8 @@ struct scrcpy_options {
uint32_t audio_bit_rate;
uint16_t max_fps;
enum sc_lock_video_orientation lock_video_orientation;
uint8_t rotation;
enum sc_orientation display_orientation;
enum sc_orientation record_orientation;
int16_t window_x; // SC_WINDOW_POSITION_UNDEFINED for "auto"
int16_t window_y; // SC_WINDOW_POSITION_UNDEFINED for "auto"
uint16_t window_width;

View File

@ -4,6 +4,7 @@
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libavutil/time.h>
#include <libavutil/display.h>
#include "util/log.h"
#include "util/str.h"
@ -493,6 +494,42 @@ run_recorder(void *data) {
return 0;
}
static bool
sc_recorder_set_orientation(AVStream *stream, enum sc_orientation orientation) {
assert(!sc_orientation_is_mirror(orientation));
uint8_t *raw_data;
#ifdef SCRCPY_LAVC_HAS_CODECPAR_CODEC_SIDEDATA
AVPacketSideData *sd =
av_packet_side_data_new(&stream->codecpar->coded_side_data,
&stream->codecpar->nb_coded_side_data,
AV_PKT_DATA_DISPLAYMATRIX,
sizeof(int32_t) * 9, 0);
if (!sd) {
LOG_OOM();
return false;
}
raw_data = sd->data;
#else
raw_data = av_stream_new_side_data(stream, AV_PKT_DATA_DISPLAYMATRIX,
sizeof(int32_t) * 9);
if (!raw_data) {
LOG_OOM();
return false;
}
#endif
int32_t *matrix = (int32_t *) raw_data;
unsigned rotation = orientation;
unsigned angle = rotation * 90;
av_display_rotation_set(matrix, angle);
return true;
}
static bool
sc_recorder_video_packet_sink_open(struct sc_packet_sink *sink,
AVCodecContext *ctx) {
@ -520,6 +557,16 @@ sc_recorder_video_packet_sink_open(struct sc_packet_sink *sink,
recorder->video_stream.index = stream->index;
if (recorder->orientation != SC_ORIENTATION_0) {
if (!sc_recorder_set_orientation(stream, recorder->orientation)) {
sc_mutex_unlock(&recorder->mutex);
return false;
}
LOGI("Record orientation set to %s",
sc_orientation_get_name(recorder->orientation));
}
recorder->video_init = true;
sc_cond_signal(&recorder->cond);
sc_mutex_unlock(&recorder->mutex);
@ -689,7 +736,10 @@ sc_recorder_stream_init(struct sc_recorder_stream *stream) {
bool
sc_recorder_init(struct sc_recorder *recorder, const char *filename,
enum sc_record_format format, bool video, bool audio,
enum sc_orientation orientation,
const struct sc_recorder_callbacks *cbs, void *cbs_userdata) {
assert(!sc_orientation_is_mirror(orientation));
recorder->filename = strdup(filename);
if (!recorder->filename) {
LOG_OOM();
@ -710,6 +760,8 @@ sc_recorder_init(struct sc_recorder *recorder, const char *filename,
recorder->video = video;
recorder->audio = audio;
recorder->orientation = orientation;
sc_vecdeque_init(&recorder->video_queue);
sc_vecdeque_init(&recorder->audio_queue);
recorder->stopped = false;

View File

@ -34,6 +34,8 @@ struct sc_recorder {
bool audio;
bool video;
enum sc_orientation orientation;
char *filename;
enum sc_record_format format;
AVFormatContext *ctx;
@ -67,6 +69,7 @@ struct sc_recorder_callbacks {
bool
sc_recorder_init(struct sc_recorder *recorder, const char *filename,
enum sc_record_format format, bool video, bool audio,
enum sc_orientation orientation,
const struct sc_recorder_callbacks *cbs, void *cbs_userdata);
bool

View File

@ -507,7 +507,8 @@ scrcpy(struct scrcpy_options *options) {
};
if (!sc_recorder_init(&s->recorder, options->record_filename,
options->record_format, options->video,
options->audio, &recorder_cbs, NULL)) {
options->audio, options->record_orientation,
&recorder_cbs, NULL)) {
goto end;
}
recorder_initialized = true;
@ -691,7 +692,7 @@ aoa_hid_end:
.window_width = options->window_width,
.window_height = options->window_height,
.window_borderless = options->window_borderless,
.rotation = options->rotation,
.orientation = options->display_orientation,
.mipmaps = options->mipmaps,
.fullscreen = options->fullscreen,
.start_fps_counter = options->start_fps_counter,

View File

@ -14,16 +14,16 @@
#define DOWNCAST(SINK) container_of(SINK, struct sc_screen, frame_sink)
static inline struct sc_size
get_rotated_size(struct sc_size size, int rotation) {
struct sc_size rotated_size;
if (rotation & 1) {
rotated_size.width = size.height;
rotated_size.height = size.width;
get_oriented_size(struct sc_size size, enum sc_orientation orientation) {
struct sc_size oriented_size;
if (sc_orientation_is_swap(orientation)) {
oriented_size.width = size.height;
oriented_size.height = size.width;
} else {
rotated_size.width = size.width;
rotated_size.height = size.height;
oriented_size.width = size.width;
oriented_size.height = size.height;
}
return rotated_size;
return oriented_size;
}
// get the window size in a struct sc_size
@ -251,7 +251,7 @@ sc_screen_render(struct sc_screen *screen, bool update_content_rect) {
}
enum sc_display_result res =
sc_display_render(&screen->display, &screen->rect, screen->rotation);
sc_display_render(&screen->display, &screen->rect, screen->orientation);
(void) res; // any error already logged
}
@ -379,9 +379,10 @@ sc_screen_init(struct sc_screen *screen,
goto error_destroy_frame_buffer;
}
screen->rotation = params->rotation;
if (screen->rotation) {
LOGI("Initial display rotation set to %u", screen->rotation);
screen->orientation = params->orientation;
if (screen->orientation != SC_ORIENTATION_0) {
LOGI("Initial display orientation set to %s",
sc_orientation_get_name(screen->orientation));
}
uint32_t window_flags = SDL_WINDOW_HIDDEN
@ -559,19 +560,19 @@ apply_pending_resize(struct sc_screen *screen) {
}
void
sc_screen_set_rotation(struct sc_screen *screen, unsigned rotation) {
assert(rotation < 4);
if (rotation == screen->rotation) {
sc_screen_set_orientation(struct sc_screen *screen,
enum sc_orientation orientation) {
if (orientation == screen->orientation) {
return;
}
struct sc_size new_content_size =
get_rotated_size(screen->frame_size, rotation);
get_oriented_size(screen->frame_size, orientation);
set_content_size(screen, new_content_size);
screen->rotation = rotation;
LOGI("Display rotation set to %u", rotation);
screen->orientation = orientation;
LOGI("Display orientation set to %s", sc_orientation_get_name(orientation));
sc_screen_render(screen, true);
}
@ -584,7 +585,7 @@ sc_screen_init_size(struct sc_screen *screen) {
// The requested size is passed via screen->frame_size
struct sc_size content_size =
get_rotated_size(screen->frame_size, screen->rotation);
get_oriented_size(screen->frame_size, screen->orientation);
screen->content_size = content_size;
enum sc_display_result res =
@ -604,7 +605,7 @@ prepare_for_frame(struct sc_screen *screen, struct sc_size new_frame_size) {
screen->frame_size = new_frame_size;
struct sc_size new_content_size =
get_rotated_size(new_frame_size, screen->rotation);
get_oriented_size(new_frame_size, screen->orientation);
set_content_size(screen, new_content_size);
sc_screen_update_content_rect(screen);
@ -843,8 +844,7 @@ sc_screen_handle_event(struct sc_screen *screen, const SDL_Event *event) {
struct sc_point
sc_screen_convert_drawable_to_frame_coords(struct sc_screen *screen,
int32_t x, int32_t y) {
unsigned rotation = screen->rotation;
assert(rotation < 4);
enum sc_orientation orientation = screen->orientation;
int32_t w = screen->content_size.width;
int32_t h = screen->content_size.height;
@ -855,27 +855,43 @@ sc_screen_convert_drawable_to_frame_coords(struct sc_screen *screen,
x = (int64_t) (x - screen->rect.x) * w / screen->rect.w;
y = (int64_t) (y - screen->rect.y) * h / screen->rect.h;
// rotate
struct sc_point result;
switch (rotation) {
case 0:
switch (orientation) {
case SC_ORIENTATION_0:
result.x = x;
result.y = y;
break;
case 1:
result.x = h - y;
result.y = x;
break;
case 2:
result.x = w - x;
result.y = h - y;
break;
default:
assert(rotation == 3);
case SC_ORIENTATION_90:
result.x = y;
result.y = w - x;
break;
case SC_ORIENTATION_180:
result.x = w - x;
result.y = h - y;
break;
case SC_ORIENTATION_270:
result.x = h - y;
result.y = x;
break;
case SC_ORIENTATION_FLIP_0:
result.x = w - x;
result.y = y;
break;
case SC_ORIENTATION_FLIP_90:
result.x = h - y;
result.y = w - x;
break;
case SC_ORIENTATION_FLIP_180:
result.x = x;
result.y = h - y;
break;
default:
assert(orientation == SC_ORIENTATION_FLIP_270);
result.x = y;
result.y = x;
break;
}
return result;
}

View File

@ -14,6 +14,7 @@
#include "frame_buffer.h"
#include "input_manager.h"
#include "opengl.h"
#include "options.h"
#include "trait/key_processor.h"
#include "trait/frame_sink.h"
#include "trait/mouse_processor.h"
@ -49,8 +50,8 @@ struct sc_screen {
// fullscreen (meaningful only when resize_pending is true)
struct sc_size windowed_content_size;
// client rotation: 0, 1, 2 or 3 (x90 degrees counterclockwise)
unsigned rotation;
// client orientation
enum sc_orientation orientation;
// rectangle of the content (excluding black borders)
struct SDL_Rect rect;
bool has_frame;
@ -86,7 +87,7 @@ struct sc_screen_params {
bool window_borderless;
uint8_t rotation;
enum sc_orientation orientation;
bool mipmaps;
bool fullscreen;
@ -129,9 +130,10 @@ sc_screen_resize_to_fit(struct sc_screen *screen);
void
sc_screen_resize_to_pixel_perfect(struct sc_screen *screen);
// set the display rotation (0, 1, 2 or 3, x90 degrees counterclockwise)
// set the display orientation
void
sc_screen_set_rotation(struct sc_screen *screen, unsigned rotation);
sc_screen_set_orientation(struct sc_screen *screen,
enum sc_orientation orientation);
// react to SDL events
// If this function returns false, scrcpy must exit with an error.

View File

@ -18,7 +18,6 @@
#define SC_SERVER_FILENAME "scrcpy-server"
#define SC_SERVER_PATH_DEFAULT PREFIX "/share/scrcpy/" SC_SERVER_FILENAME
#define SC_DEVICE_SERVER_PATH "/data/local/tmp/scrcpy-server.jar"
#define SC_ADB_PORT_DEFAULT 5555
#define SC_SOCKET_NAME_PREFIX "scrcpy_"
@ -117,7 +116,7 @@ error:
}
static bool
push_server(struct sc_intr *intr, const char *serial) {
push_server(struct sc_intr *intr, uint32_t scid, const char *serial) {
char *server_path = get_server_path();
if (!server_path) {
return false;
@ -127,7 +126,16 @@ push_server(struct sc_intr *intr, const char *serial) {
free(server_path);
return false;
}
bool ok = sc_adb_push(intr, serial, server_path, SC_DEVICE_SERVER_PATH, 0);
char *device_server_path;
if (asprintf(&device_server_path, "/data/local/tmp/scrcpy-server-%08x.jar",
scid) == -1) {
LOG_OOM();
free(server_path);
return false;
}
bool ok = sc_adb_push(intr, serial, server_path, device_server_path, 0);
free(device_server_path);
free(server_path);
return ok;
}
@ -209,13 +217,20 @@ execute_server(struct sc_server *server,
const char *serial = server->serial;
assert(serial);
char *classpath;
if (asprintf(&classpath, "CLASSPATH=/data/local/tmp/scrcpy-server-%08x.jar",
params->scid) == -1) {
LOG_OOM();
return SC_PROCESS_NONE;
}
const char *cmd[128];
unsigned count = 0;
cmd[count++] = sc_adb_get_executable();
cmd[count++] = "-s";
cmd[count++] = serial;
cmd[count++] = "shell";
cmd[count++] = "CLASSPATH=" SC_DEVICE_SERVER_PATH;
cmd[count++] = classpath;
cmd[count++] = "app_process";
#ifdef SERVER_DEBUGGER
@ -388,6 +403,7 @@ end:
for (unsigned i = dyn_idx; i < count; ++i) {
free((char *) cmd[i]);
}
free(classpath);
return pid;
}
@ -937,7 +953,7 @@ run_server(void *data) {
assert(serial);
LOGD("Device serial: %s", serial);
ok = push_server(&server->intr, serial);
ok = push_server(&server->intr, params->scid, serial);
if (!ok) {
goto error_connection_failed;
}

View File

@ -0,0 +1,91 @@
#include "common.h"
#include <assert.h>
#include "options.h"
static void test_transforms(void) {
#define O(X) SC_ORIENTATION_ ## X
#define ASSERT_TRANSFORM(SRC, TR, RES) \
assert(sc_orientation_apply(O(SRC), O(TR)) == O(RES));
ASSERT_TRANSFORM(0, 0, 0);
ASSERT_TRANSFORM(0, 90, 90);
ASSERT_TRANSFORM(0, 180, 180);
ASSERT_TRANSFORM(0, 270, 270);
ASSERT_TRANSFORM(0, FLIP_0, FLIP_0);
ASSERT_TRANSFORM(0, FLIP_90, FLIP_90);
ASSERT_TRANSFORM(0, FLIP_180, FLIP_180);
ASSERT_TRANSFORM(0, FLIP_270, FLIP_270);
ASSERT_TRANSFORM(90, 0, 90);
ASSERT_TRANSFORM(90, 90, 180);
ASSERT_TRANSFORM(90, 180, 270);
ASSERT_TRANSFORM(90, 270, 0);
ASSERT_TRANSFORM(90, FLIP_0, FLIP_270);
ASSERT_TRANSFORM(90, FLIP_90, FLIP_0);
ASSERT_TRANSFORM(90, FLIP_180, FLIP_90);
ASSERT_TRANSFORM(90, FLIP_270, FLIP_180);
ASSERT_TRANSFORM(180, 0, 180);
ASSERT_TRANSFORM(180, 90, 270);
ASSERT_TRANSFORM(180, 180, 0);
ASSERT_TRANSFORM(180, 270, 90);
ASSERT_TRANSFORM(180, FLIP_0, FLIP_180);
ASSERT_TRANSFORM(180, FLIP_90, FLIP_270);
ASSERT_TRANSFORM(180, FLIP_180, FLIP_0);
ASSERT_TRANSFORM(180, FLIP_270, FLIP_90);
ASSERT_TRANSFORM(270, 0, 270);
ASSERT_TRANSFORM(270, 90, 0);
ASSERT_TRANSFORM(270, 180, 90);
ASSERT_TRANSFORM(270, 270, 180);
ASSERT_TRANSFORM(270, FLIP_0, FLIP_90);
ASSERT_TRANSFORM(270, FLIP_90, FLIP_180);
ASSERT_TRANSFORM(270, FLIP_180, FLIP_270);
ASSERT_TRANSFORM(270, FLIP_270, FLIP_0);
ASSERT_TRANSFORM(FLIP_0, 0, FLIP_0);
ASSERT_TRANSFORM(FLIP_0, 90, FLIP_90);
ASSERT_TRANSFORM(FLIP_0, 180, FLIP_180);
ASSERT_TRANSFORM(FLIP_0, 270, FLIP_270);
ASSERT_TRANSFORM(FLIP_0, FLIP_0, 0);
ASSERT_TRANSFORM(FLIP_0, FLIP_90, 90);
ASSERT_TRANSFORM(FLIP_0, FLIP_180, 180);
ASSERT_TRANSFORM(FLIP_0, FLIP_270, 270);
ASSERT_TRANSFORM(FLIP_90, 0, FLIP_90);
ASSERT_TRANSFORM(FLIP_90, 90, FLIP_180);
ASSERT_TRANSFORM(FLIP_90, 180, FLIP_270);
ASSERT_TRANSFORM(FLIP_90, 270, FLIP_0);
ASSERT_TRANSFORM(FLIP_90, FLIP_0, 270);
ASSERT_TRANSFORM(FLIP_90, FLIP_90, 0);
ASSERT_TRANSFORM(FLIP_90, FLIP_180, 90);
ASSERT_TRANSFORM(FLIP_90, FLIP_270, 180);
ASSERT_TRANSFORM(FLIP_180, 0, FLIP_180);
ASSERT_TRANSFORM(FLIP_180, 90, FLIP_270);
ASSERT_TRANSFORM(FLIP_180, 180, FLIP_0);
ASSERT_TRANSFORM(FLIP_180, 270, FLIP_90);
ASSERT_TRANSFORM(FLIP_180, FLIP_0, 180);
ASSERT_TRANSFORM(FLIP_180, FLIP_90, 270);
ASSERT_TRANSFORM(FLIP_180, FLIP_180, 0);
ASSERT_TRANSFORM(FLIP_180, FLIP_270, 90);
ASSERT_TRANSFORM(FLIP_270, 0, FLIP_270);
ASSERT_TRANSFORM(FLIP_270, 90, FLIP_0);
ASSERT_TRANSFORM(FLIP_270, 180, FLIP_90);
ASSERT_TRANSFORM(FLIP_270, 270, FLIP_180);
ASSERT_TRANSFORM(FLIP_270, FLIP_0, 90);
ASSERT_TRANSFORM(FLIP_270, FLIP_90, 180);
ASSERT_TRANSFORM(FLIP_270, FLIP_180, 270);
ASSERT_TRANSFORM(FLIP_270, FLIP_270, 0);
}
int main(int argc, char *argv[]) {
(void) argc;
(void) argv;
test_transforms();
return 0;
}

View File

@ -6,7 +6,7 @@ c = 'i686-w64-mingw32-gcc'
cpp = 'i686-w64-mingw32-g++'
ar = 'i686-w64-mingw32-ar'
strip = 'i686-w64-mingw32-strip'
pkgconfig = 'i686-w64-mingw32-pkg-config'
pkg-config = 'i686-w64-mingw32-pkg-config'
windres = 'i686-w64-mingw32-windres'
[host_machine]

View File

@ -6,7 +6,7 @@ c = 'x86_64-w64-mingw32-gcc'
cpp = 'x86_64-w64-mingw32-g++'
ar = 'x86_64-w64-mingw32-ar'
strip = 'x86_64-w64-mingw32-strip'
pkgconfig = 'x86_64-w64-mingw32-pkg-config'
pkg-config = 'x86_64-w64-mingw32-pkg-config'
windres = 'x86_64-w64-mingw32-windres'
[host_machine]

View File

@ -101,6 +101,16 @@ scrcpy --video-source=camera --camera-size=1920x1080 -m3000 # error
```
## Rotation
To rotate the captured video, use the [video orientation](video.md#orientation)
option:
```
scrcpy --video-source=camera --camera-size=1920x1080 --orientation=90
```
## Frame rate
By default, camera is captured at Android's default frame rate (30 fps).

View File

@ -50,6 +50,12 @@ scrcpy --record=file --record-format=mkv
```
## Rotation
The video can be recorded rotated. See [video
orientation](video.md#orientation).
## No playback
To disable playback while recording:

View File

@ -26,6 +26,8 @@ _<kbd>[Super]</kbd> is typically the <kbd>Windows</kbd> or <kbd>Cmd</kbd> key._
| Switch fullscreen mode | <kbd>MOD</kbd>+<kbd>f</kbd>
| Rotate display left | <kbd>MOD</kbd>+<kbd></kbd> _(left)_
| Rotate display right | <kbd>MOD</kbd>+<kbd></kbd> _(right)_
| Flip display horizontally | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd></kbd> _(left)_ \| <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd></kbd> _(right)_
| Flip display vertically | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd></kbd> _(up)_ \| <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd></kbd> _(down)_
| Resize window to 1:1 (pixel-perfect) | <kbd>MOD</kbd>+<kbd>g</kbd>
| Resize window to remove black borders | <kbd>MOD</kbd>+<kbd>w</kbd> \| _Double-left-click¹_
| Click on `HOME` | <kbd>MOD</kbd>+<kbd>h</kbd> \| _Middle-click_

View File

@ -97,39 +97,50 @@ scrcpy --video-codec=h264 --video-encoder='OMX.qcom.video.encoder.avc'
```
## Rotation
## Orientation
The rotation may be applied at 3 different levels:
The orientation may be applied at 3 different levels:
- The [shortcut](shortcuts.md) <kbd>MOD</kbd>+<kbd>r</kbd> 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
of the video sent from the device to the computer). This affects the
recording.
- `--rotation` rotates only the window content. This only affects the display,
not the recording. It may be changed dynamically at any time using the
[shortcuts](shortcuts.md) <kbd>MOD</kbd>+<kbd></kbd> and
<kbd>MOD</kbd>+<kbd></kbd>.
- `--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:
To lock the mirroring orientation (on the capture side):
```bash
scrcpy --lock-video-orientation # initial (current) orientation
scrcpy --lock-video-orientation=0 # natural orientation
scrcpy --lock-video-orientation=1 # 90° counterclockwise
scrcpy --lock-video-orientation=2 # 180°
scrcpy --lock-video-orientation=3 # 90° clockwise
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
```
To set an initial window rotation:
To orient the video (on the rendering side):
```bash
scrcpy --rotation=0 # no rotation
scrcpy --rotation=1 # 90 degrees counterclockwise
scrcpy --rotation=2 # 180 degrees
scrcpy --rotation=3 # 90 degrees clockwise
scrcpy --orientation=0
scrcpy --orientation=90 # 90° clockwise
scrcpy --orientation=180 # 180°
scrcpy --orientation=270 # 270° clockwise
scrcpy --orientation=flip0 # hflip
scrcpy --orientation=flip90 # hflip + 90° clockwise
scrcpy --orientation=flip180 # vflip (hflip + 180°)
scrcpy --orientation=flip270 # hflip + 270°
```
The orientation can be set separately for display and record if necessary, via
`--display-orientation` and `--record-orientation`.
The rotation is applied to a recorded file by writing a display transformation
to the MP4 or MKV target file. Flipping is not supported, so only the 4 first
values are allowed when recording.
## Crop
The device screen may be cropped to mirror only part of the screen.

View File

@ -14,8 +14,6 @@ import java.io.IOException;
*/
public final class CleanUp {
public static final String SERVER_PATH = "/data/local/tmp/scrcpy-server.jar";
// A simple struct to be passed from the main process to the cleanup process
public static class Config implements Parcelable {
@ -135,21 +133,19 @@ public final class CleanUp {
String[] cmd = {"app_process", "/", CleanUp.class.getName(), config.toBase64()};
ProcessBuilder builder = new ProcessBuilder(cmd);
builder.environment().put("CLASSPATH", SERVER_PATH);
builder.environment().put("CLASSPATH", Server.SERVER_PATH);
builder.start();
}
public static void unlinkSelf() {
try {
new File(SERVER_PATH).delete();
new File(Server.SERVER_PATH).delete();
} catch (Exception e) {
Ln.e("Could not unlink server", e);
}
}
public static void main(String... args) {
unlinkSelf();
try {
// Wait for the server to die
System.in.read();
@ -187,7 +183,11 @@ public final class CleanUp {
} else if (config.restoreNormalPowerMode) {
Ln.i("Restoring normal power mode");
Device.setScreenPowerMode(Device.POWER_MODE_NORMAL);
// Make sure the request is performed before exiting
DisplayPowerMode.stopAndJoin();
}
}
unlinkSelf();
}
}

View File

@ -315,6 +315,14 @@ public final class Device {
*/
public static boolean setScreenPowerMode(int mode) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE && !SurfaceControl.hasPhysicalDisplayIdsMethod()) {
// On Android 14+, these internal methods have been moved to system server classes.
// Run a separate process with the correct classpath and LD_PRELOAD to change the display power mode.
DisplayPowerMode.setRemoteDisplayPowerMode(mode);
// The call is asynchronous (we don't want to block)
return true;
}
// Change the power mode for all physical displays
long[] physicalDisplayIds = SurfaceControl.getPhysicalDisplayIds();
if (physicalDisplayIds == null) {

View File

@ -0,0 +1,184 @@
package com.genymobile.scrcpy;
import android.annotation.SuppressLint;
import android.os.IBinder;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintStream;
import java.lang.reflect.Method;
/**
* On Android 14, the methods used to turn the device screen off have been moved from SurfaceControl (in framework.jar) to DisplayControl (a system
* server class). As a consequence, they could not be called directly. See {@url https://github.com/Genymobile/scrcpy/issues/3927}.
* <p>
* Instead, run a separate process with a different classpath and LD_PRELOAD just to set the display power mode. The scrcpy server can request to
* this process to set the display mode by writing the mode (a single byte, the value of one of the SurfaceControl.POWER_MODE_* constants,
* typically 0=off, 2=on) to the process stdin. In return, it receives the status of the request (0=ok, 1=error) on the process stdout.
* <p>
* This separate process is started on the first display mode request.
* <p>
* Since the client must not block, and calling/joining a process is blocking (this specific one takes a few hundred milliseconds to complete),
* this class uses an internal thread to execute the requests asynchronously, and serialize them (so that two successive requests are guaranteed to
* be executed in order). In addition, it only executes the last pending request (setting power mode to value X then to value Y is equivalent to
* just setting it to value Y).
*/
public final class DisplayPowerMode {
private static final Proxy PROXY = new Proxy();
private static final class Proxy implements Runnable {
private Process process;
private Thread thread;
private int requestedMode = -1;
private boolean stopped;
synchronized boolean requestMode(int mode) {
try {
if (process == null) {
process = executeDisplayPowerModeDaemon();
thread = new Thread(this, "DisplayPowerModeProxy");
thread.setDaemon(true);
thread.start();
}
requestedMode = mode;
notify();
return true;
} catch (IOException e) {
Ln.e("Could not start display power mode daemon", e);
return false;
}
}
void stopAndJoin() {
boolean hasThread;
synchronized (this) {
hasThread = thread != null;
if (thread != null) {
stopped = true;
notify();
}
}
if (hasThread) {
// Join the thread without holding the mutex (that would cause a deadlock)
try {
thread.join();
} catch (InterruptedException e) {
Ln.e("Thread join interrupted", e);
}
}
}
@Override
public void run() {
try {
OutputStream out = process.getOutputStream();
InputStream in = process.getInputStream();
while (true) {
int mode;
synchronized (this) {
while (!stopped && requestedMode == -1) {
wait();
}
mode = requestedMode;
requestedMode = -1;
}
// Even if stopped, the last request must be executed to restore the display power mode to normal
if (mode == -1) {
return;
}
try {
out.write(mode);
out.flush();
int status = in.read();
if (status != 0) {
Ln.e("Set display power mode failed remotely: status=" + status);
}
} catch (IOException e) {
Ln.e("Could not request display power mode", e);
}
}
} catch (InterruptedException e) {
// end of thread
}
}
}
private DisplayPowerMode() {
// not instantiable
}
// Called from the scrcpy process
public static boolean setRemoteDisplayPowerMode(int mode) {
return PROXY.requestMode(mode);
}
public static void stopAndJoin() {
PROXY.stopAndJoin();
}
// Called from the proxy thread in the scrcpy process
private static Process executeDisplayPowerModeDaemon() throws IOException {
String[] ldPreloadLibs = {"/system/lib64/libandroid_servers.so"};
String[] cmd = {"app_process", "/", DisplayPowerMode.class.getName()};
ProcessBuilder builder = new ProcessBuilder(cmd);
builder.environment().put("LD_PRELOAD", String.join(" ", ldPreloadLibs));
builder.environment().put("CLASSPATH", Server.SERVER_PATH + ":/system/framework/services.jar");
return builder.start();
}
// Executed in the DisplayPowerMode-specific process
@SuppressLint({"PrivateApi", "SoonBlockedPrivateApi"})
private static void setDisplayPowerModeUsingDisplayControl(int mode) throws Exception {
System.loadLibrary("android_servers");
@SuppressLint("PrivateApi")
Class<?> displayControlClass = Class.forName("com.android.server.display.DisplayControl");
Method getPhysicalDisplayIdsMethod = displayControlClass.getDeclaredMethod("getPhysicalDisplayIds");
Method getPhysicalDisplayTokenMethod = displayControlClass.getDeclaredMethod("getPhysicalDisplayToken", long.class);
Class<?> surfaceControlClass = Class.forName("android.view.SurfaceControl");
Method setDisplayPowerModeMethod = surfaceControlClass.getDeclaredMethod("setDisplayPowerMode", IBinder.class, int.class);
long[] displayIds = (long[]) getPhysicalDisplayIdsMethod.invoke(null);
for (long displayId : displayIds) {
Object token = getPhysicalDisplayTokenMethod.invoke(null, displayId);
setDisplayPowerModeMethod.invoke(null, token, mode);
}
}
public static void main(String... args) {
// This process uses stdin/stdout to communicate with the caller, make sure nothing else writes to stdout
// (and never use Ln methods other than Ln.w() and Ln.e()).
PrintStream nullStream = new PrintStream(new Ln.NullOutputStream());
System.setOut(nullStream);
PrintStream stdout = Ln.CONSOLE_OUT;
try {
while (true) {
// Wait for requests
int request = System.in.read();
if (request == -1) {
// EOF
return;
}
try {
setDisplayPowerModeUsingDisplayControl(request);
stdout.write(0); // ok
} catch (Throwable e) {
Ln.e("Could not set display power mode", e);
stdout.write(1); // error
}
stdout.flush();
}
} catch (IOException e) {
// Expected when the server is dead
}
}
}

View File

@ -16,8 +16,8 @@ public final class Ln {
private static final String TAG = "scrcpy";
private static final String PREFIX = "[server] ";
private static final PrintStream CONSOLE_OUT = new PrintStream(new FileOutputStream(FileDescriptor.out));
private static final PrintStream CONSOLE_ERR = new PrintStream(new FileOutputStream(FileDescriptor.err));
public static final PrintStream CONSOLE_OUT = new PrintStream(new FileOutputStream(FileDescriptor.out));
public static final PrintStream CONSOLE_ERR = new PrintStream(new FileOutputStream(FileDescriptor.err));
enum Level {
VERBOSE, DEBUG, INFO, WARN, ERROR

View File

@ -3,12 +3,20 @@ package com.genymobile.scrcpy;
import android.os.BatteryManager;
import android.os.Build;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
public final class Server {
public static final String SERVER_PATH;
static {
String[] classPaths = System.getProperty("java.class.path").split(File.pathSeparator);
// By convention, scrcpy is always executed with the absolute path of scrcpy-server.jar as the first item in the classpath
SERVER_PATH = classPaths[0];
}
private static class Completion {
private int running;
private boolean fatalError;

View File

@ -139,6 +139,15 @@ public final class SurfaceControl {
return getPhysicalDisplayIdsMethod;
}
public static boolean hasPhysicalDisplayIdsMethod() {
try {
getGetPhysicalDisplayIdsMethod();
return true;
} catch (NoSuchMethodException e) {
return false;
}
}
public static long[] getPhysicalDisplayIds() {
try {
Method method = getGetPhysicalDisplayIdsMethod();