Compare commits

..
Author SHA1 Message Date
Romain Vimont 185802fbe2 longlong 2019-12-10 09:22:30 +01:00
Romain Vimont 78f566bd6d Move platorm-specific defines to common.h
They could be useful anywhere.
2019-12-10 09:11:09 +01:00
27 changed files with 159 additions and 329 deletions
+3 -3
View File
@@ -233,10 +233,10 @@ You can then [run](README.md#run) _scrcpy_.
## Prebuilt server ## Prebuilt server
- [`scrcpy-server-v1.12.1`][direct-scrcpy-server] - [`scrcpy-server-v1.12`][direct-scrcpy-server]
_(SHA-256: 63e569c8a1d0c1df31d48c4214871c479a601782945fed50c1e61167d78266ea)_ _(SHA-256: b6595262c230e9773fdb817257abcc8c6e6e00f15b1c32b6a850ccfd8176dc10)_
[direct-scrcpy-server]: https://github.com/Genymobile/scrcpy/releases/download/v1.12.1/scrcpy-server-v1.12.1 [direct-scrcpy-server]: https://github.com/Genymobile/scrcpy/releases/download/v1.12/scrcpy-server-v1.12
Download the prebuilt server somewhere, and specify its path during the Meson Download the prebuilt server somewhere, and specify its path during the Meson
configuration: configuration:
+2 -2
View File
@@ -189,7 +189,7 @@ The client uses 4 threads:
recording, recording,
- the **controller** thread, sending _control messages_ to the server, - the **controller** thread, sending _control messages_ to the server,
- the **receiver** thread (managed by the controller), receiving _device - the **receiver** thread (managed by the controller), receiving _device
messages_ from the server. messages_ from the client.
In addition, another thread can be started if necessary to handle APK In addition, another thread can be started if necessary to handle APK
installation or file push requests (via drag&drop on the main window) or to installation or file push requests (via drag&drop on the main window) or to
@@ -214,7 +214,7 @@ When a new decoded frame is available, the decoder _swaps_ the decoding and
rendering frame (with proper synchronization). Thus, it immediatly starts rendering frame (with proper synchronization). Thus, it immediatly starts
to decode a new frame while the main thread renders the last one. to decode a new frame while the main thread renders the last one.
If a [recorder] is present (i.e. `--record` is enabled), then it muxes the raw If a [recorder] is present (i.e. `--record` is enabled), then its muxes the raw
H.264 packet to the output video file. H.264 packet to the output video file.
[stream]: https://github.com/Genymobile/scrcpy/blob/ffe0417228fb78ab45b7ee4e202fc06fc8875bf3/app/src/stream.h [stream]: https://github.com/Genymobile/scrcpy/blob/ffe0417228fb78ab45b7ee4e202fc06fc8875bf3/app/src/stream.h
+1 -1
View File
@@ -188,7 +188,7 @@
identification within third-party archives. identification within third-party archives.
Copyright (C) 2018 Genymobile Copyright (C) 2018 Genymobile
Copyright (C) 2018-2020 Romain Vimont Copyright (C) 2018-2019 Romain Vimont
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
+1 -1
View File
@@ -475,7 +475,7 @@ _²화면이 꺼진 상태에서 우클릭 시 다시 켜지며, 그 외의 상
## 라이선스 ## 라이선스
Copyright (C) 2018 Genymobile Copyright (C) 2018 Genymobile
Copyright (C) 2018-2020 Romain Vimont Copyright (C) 2018-2019 Romain Vimont
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
+10 -53
View File
@@ -1,4 +1,4 @@
# scrcpy (v1.12.1) # scrcpy (v1.12)
This application provides display and control of Android devices connected on This application provides display and control of Android devices connected on
USB (or [over TCP/IP][article-tcpip]). It does not require any _root_ access. USB (or [over TCP/IP][article-tcpip]). It does not require any _root_ access.
@@ -37,11 +37,8 @@ control it using keyboard and mouse.
### Linux ### Linux
In Debian (_testing_ and _sid_ for now): On Linux, you typically need to [build the app manually][BUILD]. Don't worry,
it's not that hard.
```
apt install scrcpy
```
A [Snap] package is available: [`scrcpy`][snap-link]. A [Snap] package is available: [`scrcpy`][snap-link].
@@ -59,37 +56,19 @@ For Gentoo, an [Ebuild] is available: [`scrcpy/`][ebuild-link].
[Ebuild]: https://wiki.gentoo.org/wiki/Ebuild [Ebuild]: https://wiki.gentoo.org/wiki/Ebuild
[ebuild-link]: https://github.com/maggu2810/maggu2810-overlay/tree/master/app-mobilephone/scrcpy [ebuild-link]: https://github.com/maggu2810/maggu2810-overlay/tree/master/app-mobilephone/scrcpy
You could also [build the app manually][BUILD] (don't worry, it's not that
hard).
### Windows ### Windows
For Windows, for simplicity, prebuilt archives with all the dependencies For Windows, for simplicity, prebuilt archives with all the dependencies
(including `adb`) are available: (including `adb`) are available:
- [`scrcpy-win32-v1.12.1.zip`][direct-win32] - [`scrcpy-win32-v1.12.zip`][direct-win32]
_(SHA-256: 0f4b3b063536b50a2df05dc42c760f9cc0093a9a26dbdf02d8232c74dab43480)_ _(SHA-256: b2c8c4a3899c037cf448a2102906775114826ba646ce1b847826925103fa801d)_
- [`scrcpy-win64-v1.12.1.zip`][direct-win64] - [`scrcpy-win64-v1.12.zip`][direct-win64]
_(SHA-256: 57d34b6d16cfd9fe169bc37c4df58ebd256d05c1ea3febc63d9cb0a027ab47c9)_ _(SHA-256: 7d47983b426f7287de0230b88975dc17c1d9c343fa61a93ff2af78b6e9ef5c8c)_
[direct-win32]: https://github.com/Genymobile/scrcpy/releases/download/v1.12.1/scrcpy-win32-v1.12.1.zip [direct-win32]: https://github.com/Genymobile/scrcpy/releases/download/v1.12/scrcpy-win32-v1.12.zip
[direct-win64]: https://github.com/Genymobile/scrcpy/releases/download/v1.12.1/scrcpy-win64-v1.12.1.zip [direct-win64]: https://github.com/Genymobile/scrcpy/releases/download/v1.12/scrcpy-win64-v1.12.zip
It is also available in [Chocolatey]:
[Chocolatey]: https://chocolatey.org/
```bash
choco install scrcpy
```
You need `adb`, accessible from your `PATH`. If you don't have it yet:
```bash
choco install adb
```
You can also [build the app manually][BUILD]. You can also [build the app manually][BUILD].
@@ -177,21 +156,6 @@ scrcpy --crop 1224:1440:0:0 # 1224x1440 at offset (0,0)
If `--max-size` is also specified, resizing is applied after cropping. If `--max-size` is also specified, resizing is applied after cropping.
#### Lock video orientation
To lock the orientation of the mirroring:
```bash
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
```
This affects recording orientation.
### Recording ### Recording
It is possible to record the screen while mirroring: It is possible to record the screen while mirroring:
@@ -250,13 +214,6 @@ scrcpy --serial 0123456789abcdef
scrcpy -s 0123456789abcdef # short version scrcpy -s 0123456789abcdef # short version
``` ```
If the device is connected over TCP/IP:
```bash
scrcpy --serial 192.168.0.1:5555
scrcpy -s 192.168.0.1:5555 # short version
```
You can start several instances of _scrcpy_ for several devices. You can start several instances of _scrcpy_ for several devices.
#### SSH tunnel #### SSH tunnel
@@ -532,7 +489,7 @@ Read the [developers page].
## Licence ## Licence
Copyright (C) 2018 Genymobile Copyright (C) 2018 Genymobile
Copyright (C) 2018-2020 Romain Vimont Copyright (C) 2018-2019 Romain Vimont
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
-6
View File
@@ -106,12 +106,6 @@ conf.set('DEFAULT_LOCAL_PORT', '27183')
# overridden by option --max-size # overridden by option --max-size
conf.set('DEFAULT_MAX_SIZE', '0') # 0: unlimited conf.set('DEFAULT_MAX_SIZE', '0') # 0: unlimited
# the default video orientation
# natural device orientation is 0 and each increment adds 90 degrees
# counterclockwise
# overridden by option --lock-video-orientation
conf.set('DEFAULT_LOCK_VIDEO_ORIENTATION', '-1') # -1: unlocked
# the default video bitrate, in bits/second # the default video bitrate, in bits/second
# overridden by option --bit-rate # overridden by option --bit-rate
conf.set('DEFAULT_BIT_RATE', '8000000') # 8Mbps conf.set('DEFAULT_BIT_RATE', '8000000') # 8Mbps
+1 -7
View File
@@ -41,12 +41,6 @@ Start in fullscreen.
.B \-h, \-\-help .B \-h, \-\-help
Print this help. Print this help.
.TP
.BI "\-\-lock\-video\-orientation " value
Lock video orientation to \fIvalue\fR. Values are integers in the range [-1..3]. Natural device orientation is 0 and each increment adds 90 degrees counterclockwise.
Default is -1 (unlocked).
.TP .TP
.BI "\-\-max\-fps " value .BI "\-\-max\-fps " value
Limit the framerate of screen capture (only supported on devices with Android >= 10). Limit the framerate of screen capture (only supported on devices with Android >= 10).
@@ -267,7 +261,7 @@ Copyright \(co 2018 Genymobile
Genymobile Genymobile
.UE .UE
Copyright \(co 2018\-2020 Copyright \(co 2018\-2019
.MT rom@rom1v.com .MT rom@rom1v.com
Romain Vimont Romain Vimont
.ME .ME
+54 -83
View File
@@ -2,6 +2,7 @@
#include <getopt.h> #include <getopt.h>
#include <stdint.h> #include <stdint.h>
#include <stdio.h>
#include <unistd.h> #include <unistd.h>
#include "config.h" #include "config.h"
@@ -41,12 +42,6 @@ scrcpy_print_usage(const char *arg0) {
" -h, --help\n" " -h, --help\n"
" Print this help.\n" " Print this help.\n"
"\n" "\n"
" --lock-video-orientation value\n"
" Lock video orientation to value. Values are integers in the\n"
" range [-1..3]. Natural device orientation is 0 and each\n"
" increment adds 90 degrees counterclockwise.\n"
" Default is %d%s.\n"
"\n"
" --max-fps value\n" " --max-fps value\n"
" Limit the frame rate of screen capture (only supported on\n" " Limit the frame rate of screen capture (only supported on\n"
" devices with Android >= 10).\n" " devices with Android >= 10).\n"
@@ -199,14 +194,13 @@ scrcpy_print_usage(const char *arg0) {
arg0, arg0,
DEFAULT_BIT_RATE, DEFAULT_BIT_RATE,
DEFAULT_MAX_SIZE, DEFAULT_MAX_SIZE ? "" : " (unlimited)", DEFAULT_MAX_SIZE, DEFAULT_MAX_SIZE ? "" : " (unlimited)",
DEFAULT_LOCK_VIDEO_ORIENTATION, DEFAULT_LOCK_VIDEO_ORIENTATION >= 0 ? "" : " (unlocked)",
DEFAULT_LOCAL_PORT); DEFAULT_LOCAL_PORT);
} }
static bool static bool
parse_integer_arg(const char *s, long *out, bool accept_suffix, long min, parse_integer_arg(const char *s, long long *out, bool accept_suffix,
long max, const char *name) { long long min, long long max, const char *name) {
long value; long long value;
bool ok; bool ok;
if (accept_suffix) { if (accept_suffix) {
ok = parse_integer_with_suffix(s, &value); ok = parse_integer_with_suffix(s, &value);
@@ -219,8 +213,8 @@ parse_integer_arg(const char *s, long *out, bool accept_suffix, long min,
} }
if (value < min || value > max) { if (value < min || value > max) {
LOGE("Could not parse %s: value (%ld) out-of-range (%ld; %ld)", LOGE("Could not parse %s: value (%" PRIlld ") out-of-range (%"
name, value, min, max); PRIlld "; %" PRIlld ")", name, value, min, max);
return false; return false;
} }
@@ -230,10 +224,8 @@ parse_integer_arg(const char *s, long *out, bool accept_suffix, long min,
static bool static bool
parse_bit_rate(const char *s, uint32_t *bit_rate) { parse_bit_rate(const char *s, uint32_t *bit_rate) {
long value; long long value;
// long may be 32 bits (it is the case on mingw), so do not use more than bool ok = parse_integer_arg(s, &value, true, 0, 0xFFFFFFFFLL, "bit-rate");
// 31 bits (long is signed)
bool ok = parse_integer_arg(s, &value, true, 0, 0x7FFFFFFF, "bit-rate");
if (!ok) { if (!ok) {
return false; return false;
} }
@@ -244,7 +236,7 @@ parse_bit_rate(const char *s, uint32_t *bit_rate) {
static bool static bool
parse_max_size(const char *s, uint16_t *max_size) { parse_max_size(const char *s, uint16_t *max_size) {
long value; long long value;
bool ok = parse_integer_arg(s, &value, false, 0, 0xFFFF, "max size"); bool ok = parse_integer_arg(s, &value, false, 0, 0xFFFF, "max size");
if (!ok) { if (!ok) {
return false; return false;
@@ -256,7 +248,7 @@ parse_max_size(const char *s, uint16_t *max_size) {
static bool static bool
parse_max_fps(const char *s, uint16_t *max_fps) { parse_max_fps(const char *s, uint16_t *max_fps) {
long value; long long value;
bool ok = parse_integer_arg(s, &value, false, 0, 1000, "max fps"); bool ok = parse_integer_arg(s, &value, false, 0, 1000, "max fps");
if (!ok) { if (!ok) {
return false; return false;
@@ -266,22 +258,9 @@ parse_max_fps(const char *s, uint16_t *max_fps) {
return true; return true;
} }
static bool
parse_lock_video_orientation(const char *s, int8_t *lock_video_orientation) {
long value;
bool ok = parse_integer_arg(s, &value, false, -1, 3,
"lock video orientation");
if (!ok) {
return false;
}
*lock_video_orientation = (int8_t) value;
return true;
}
static bool static bool
parse_window_position(const char *s, int16_t *position) { parse_window_position(const char *s, int16_t *position) {
long value; long long value;
bool ok = parse_integer_arg(s, &value, false, -1, 0x7FFF, bool ok = parse_integer_arg(s, &value, false, -1, 0x7FFF,
"window position"); "window position");
if (!ok) { if (!ok) {
@@ -294,7 +273,7 @@ parse_window_position(const char *s, int16_t *position) {
static bool static bool
parse_window_dimension(const char *s, uint16_t *dimension) { parse_window_dimension(const char *s, uint16_t *dimension) {
long value; long long value;
bool ok = parse_integer_arg(s, &value, false, 0, 0xFFFF, bool ok = parse_integer_arg(s, &value, false, 0, 0xFFFF,
"window dimension"); "window dimension");
if (!ok) { if (!ok) {
@@ -307,7 +286,7 @@ parse_window_dimension(const char *s, uint16_t *dimension) {
static bool static bool
parse_port(const char *s, uint16_t *port) { parse_port(const char *s, uint16_t *port) {
long value; long long value;
bool ok = parse_integer_arg(s, &value, false, 0, 0xFFFF, "port"); bool ok = parse_integer_arg(s, &value, false, 0, 0xFFFF, "port");
if (!ok) { if (!ok) {
return false; return false;
@@ -347,54 +326,51 @@ guess_record_format(const char *filename) {
return 0; return 0;
} }
#define OPT_RENDER_EXPIRED_FRAMES 1000 #define OPT_RENDER_EXPIRED_FRAMES 1000
#define OPT_WINDOW_TITLE 1001 #define OPT_WINDOW_TITLE 1001
#define OPT_PUSH_TARGET 1002 #define OPT_PUSH_TARGET 1002
#define OPT_ALWAYS_ON_TOP 1003 #define OPT_ALWAYS_ON_TOP 1003
#define OPT_CROP 1004 #define OPT_CROP 1004
#define OPT_RECORD_FORMAT 1005 #define OPT_RECORD_FORMAT 1005
#define OPT_PREFER_TEXT 1006 #define OPT_PREFER_TEXT 1006
#define OPT_WINDOW_X 1007 #define OPT_WINDOW_X 1007
#define OPT_WINDOW_Y 1008 #define OPT_WINDOW_Y 1008
#define OPT_WINDOW_WIDTH 1009 #define OPT_WINDOW_WIDTH 1009
#define OPT_WINDOW_HEIGHT 1010 #define OPT_WINDOW_HEIGHT 1010
#define OPT_WINDOW_BORDERLESS 1011 #define OPT_WINDOW_BORDERLESS 1011
#define OPT_MAX_FPS 1012 #define OPT_MAX_FPS 1012
#define OPT_LOCK_VIDEO_ORIENTATION 1013
bool bool
scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) { scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) {
static const struct option long_options[] = { static const struct option long_options[] = {
{"always-on-top", no_argument, NULL, OPT_ALWAYS_ON_TOP}, {"always-on-top", no_argument, NULL, OPT_ALWAYS_ON_TOP},
{"bit-rate", required_argument, NULL, 'b'}, {"bit-rate", required_argument, NULL, 'b'},
{"crop", required_argument, NULL, OPT_CROP}, {"crop", required_argument, NULL, OPT_CROP},
{"fullscreen", no_argument, NULL, 'f'}, {"fullscreen", no_argument, NULL, 'f'},
{"help", no_argument, NULL, 'h'}, {"help", no_argument, NULL, 'h'},
{"lock-video-orientation", required_argument, NULL, {"max-fps", required_argument, NULL, OPT_MAX_FPS},
OPT_LOCK_VIDEO_ORIENTATION}, {"max-size", required_argument, NULL, 'm'},
{"max-fps", required_argument, NULL, OPT_MAX_FPS}, {"no-control", no_argument, NULL, 'n'},
{"max-size", required_argument, NULL, 'm'}, {"no-display", no_argument, NULL, 'N'},
{"no-control", no_argument, NULL, 'n'}, {"port", required_argument, NULL, 'p'},
{"no-display", no_argument, NULL, 'N'}, {"push-target", required_argument, NULL, OPT_PUSH_TARGET},
{"port", required_argument, NULL, 'p'}, {"record", required_argument, NULL, 'r'},
{"push-target", required_argument, NULL, OPT_PUSH_TARGET}, {"record-format", required_argument, NULL, OPT_RECORD_FORMAT},
{"record", required_argument, NULL, 'r'}, {"render-expired-frames", no_argument, NULL,
{"record-format", required_argument, NULL, OPT_RECORD_FORMAT}, OPT_RENDER_EXPIRED_FRAMES},
{"render-expired-frames", no_argument, NULL, {"serial", required_argument, NULL, 's'},
OPT_RENDER_EXPIRED_FRAMES}, {"show-touches", no_argument, NULL, 't'},
{"serial", required_argument, NULL, 's'}, {"turn-screen-off", no_argument, NULL, 'S'},
{"show-touches", no_argument, NULL, 't'}, {"prefer-text", no_argument, NULL, OPT_PREFER_TEXT},
{"turn-screen-off", no_argument, NULL, 'S'}, {"version", no_argument, NULL, 'v'},
{"prefer-text", no_argument, NULL, OPT_PREFER_TEXT}, {"window-title", required_argument, NULL, OPT_WINDOW_TITLE},
{"version", no_argument, NULL, 'v'}, {"window-x", required_argument, NULL, OPT_WINDOW_X},
{"window-title", required_argument, NULL, OPT_WINDOW_TITLE}, {"window-y", required_argument, NULL, OPT_WINDOW_Y},
{"window-x", required_argument, NULL, OPT_WINDOW_X}, {"window-width", required_argument, NULL, OPT_WINDOW_WIDTH},
{"window-y", required_argument, NULL, OPT_WINDOW_Y}, {"window-height", required_argument, NULL, OPT_WINDOW_HEIGHT},
{"window-width", required_argument, NULL, OPT_WINDOW_WIDTH}, {"window-borderless", no_argument, NULL,
{"window-height", required_argument, NULL, OPT_WINDOW_HEIGHT}, OPT_WINDOW_BORDERLESS},
{"window-borderless", no_argument, NULL, {NULL, 0, NULL, 0 },
OPT_WINDOW_BORDERLESS},
{NULL, 0, NULL, 0 },
}; };
struct scrcpy_options *opts = &args->opts; struct scrcpy_options *opts = &args->opts;
@@ -440,11 +416,6 @@ scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) {
return false; return false;
} }
break; break;
case OPT_LOCK_VIDEO_ORIENTATION:
if (!parse_lock_video_orientation(optarg, &opts->lock_video_orientation)) {
return false;
}
break;
case 'n': case 'n':
opts->control = false; opts->control = false;
break; break;
+1 -31
View File
@@ -4,38 +4,8 @@
#include <stdbool.h> #include <stdbool.h>
#include <inttypes.h> #include <inttypes.h>
#ifdef _WIN32
// not needed here, but winsock2.h must never be included AFTER windows.h
# include <winsock2.h>
# include <windows.h>
# define PATH_SEPARATOR '\\'
# define PRIexitcode "lu"
// <https://stackoverflow.com/a/44383330/1987178>
# ifdef _WIN64
# define PRIsizet PRIu64
# else
# define PRIsizet PRIu32
# endif
# define PROCESS_NONE NULL
# define NO_EXIT_CODE -1u // max value as unsigned
typedef HANDLE process_t;
typedef DWORD exit_code_t;
#else
# include <sys/types.h>
# define PATH_SEPARATOR '/'
# define PRIsizet "zu"
# define PRIexitcode "d"
# define PROCESS_NONE -1
# define NO_EXIT_CODE -1
typedef pid_t process_t;
typedef int exit_code_t;
#endif
#include "config.h" #include "config.h"
#include "common.h"
enum process_result { enum process_result {
PROCESS_SUCCESS, PROCESS_SUCCESS,
+38
View File
@@ -5,6 +5,44 @@
#include "config.h" #include "config.h"
#ifdef _WIN32
// not needed here, but winsock2.h must never be included AFTER windows.h
# include <winsock2.h>
# include <windows.h>
# define PATH_SEPARATOR '\\'
# define PRIexitcode "lu"
// <https://stackoverflow.com/a/44383330/1987178>
# ifdef _WIN64
# define PRIsizet PRIu64
# else
# define PRIsizet PRIu32
# endif
# define PROCESS_NONE NULL
# define NO_EXIT_CODE -1u // max value as unsigned
typedef HANDLE process_t;
typedef DWORD exit_code_t;
#else
# include <sys/types.h>
# define PATH_SEPARATOR '/'
# define PRIsizet "zu"
# define PRIexitcode "d"
# define PROCESS_NONE -1
# define NO_EXIT_CODE -1
typedef pid_t process_t;
typedef int exit_code_t;
#endif
// in MinGW, "%ll" is not supported as printf format for long long
# ifdef __MINGW32__
# define PRIlld "I64d"
# else
# define PRIlld "lld"
# endif
#define ARRAY_LEN(a) (sizeof(a) / sizeof(a[0])) #define ARRAY_LEN(a) (sizeof(a) / sizeof(a[0]))
#define MIN(X,Y) (X) < (Y) ? (X) : (Y) #define MIN(X,Y) (X) < (Y) ? (X) : (Y)
#define MAX(X,Y) (X) > (Y) ? (X) : (Y) #define MAX(X,Y) (X) > (Y) ? (X) : (Y)
-1
View File
@@ -284,7 +284,6 @@ scrcpy(const struct scrcpy_options *options) {
.max_size = options->max_size, .max_size = options->max_size,
.bit_rate = options->bit_rate, .bit_rate = options->bit_rate,
.max_fps = options->max_fps, .max_fps = options->max_fps,
.lock_video_orientation = options->lock_video_orientation,
.control = options->control, .control = options->control,
}; };
if (!server_start(&server, options->serial, &params)) { if (!server_start(&server, options->serial, &params)) {
-2
View File
@@ -19,7 +19,6 @@ struct scrcpy_options {
uint16_t max_size; uint16_t max_size;
uint32_t bit_rate; uint32_t bit_rate;
uint16_t max_fps; uint16_t max_fps;
int8_t lock_video_orientation;
int16_t window_x; int16_t window_x;
int16_t window_y; int16_t window_y;
uint16_t window_width; uint16_t window_width;
@@ -46,7 +45,6 @@ struct scrcpy_options {
.max_size = DEFAULT_MAX_SIZE, \ .max_size = DEFAULT_MAX_SIZE, \
.bit_rate = DEFAULT_BIT_RATE, \ .bit_rate = DEFAULT_BIT_RATE, \
.max_fps = 0, \ .max_fps = 0, \
.lock_video_orientation = DEFAULT_LOCK_VIDEO_ORIENTATION, \
.window_x = -1, \ .window_x = -1, \
.window_y = -1, \ .window_y = -1, \
.window_width = 0, \ .window_width = 0, \
-3
View File
@@ -124,11 +124,9 @@ execute_server(struct server *server, const struct server_params *params) {
char max_size_string[6]; char max_size_string[6];
char bit_rate_string[11]; char bit_rate_string[11];
char max_fps_string[6]; char max_fps_string[6];
char lock_video_orientation_string[3];
sprintf(max_size_string, "%"PRIu16, params->max_size); sprintf(max_size_string, "%"PRIu16, params->max_size);
sprintf(bit_rate_string, "%"PRIu32, params->bit_rate); sprintf(bit_rate_string, "%"PRIu32, params->bit_rate);
sprintf(max_fps_string, "%"PRIu16, params->max_fps); sprintf(max_fps_string, "%"PRIu16, params->max_fps);
sprintf(lock_video_orientation_string, "%"PRIi8, params->lock_video_orientation);
const char *const cmd[] = { const char *const cmd[] = {
"shell", "shell",
"CLASSPATH=" DEVICE_SERVER_PATH, "CLASSPATH=" DEVICE_SERVER_PATH,
@@ -144,7 +142,6 @@ execute_server(struct server *server, const struct server_params *params) {
max_size_string, max_size_string,
bit_rate_string, bit_rate_string,
max_fps_string, max_fps_string,
lock_video_orientation_string,
server->tunnel_forward ? "true" : "false", server->tunnel_forward ? "true" : "false",
params->crop ? params->crop : "-", params->crop ? params->crop : "-",
"true", // always send frame meta (packet boundaries + timestamp) "true", // always send frame meta (packet boundaries + timestamp)
-1
View File
@@ -36,7 +36,6 @@ struct server_params {
uint16_t max_size; uint16_t max_size;
uint32_t bit_rate; uint32_t bit_rate;
uint16_t max_fps; uint16_t max_fps;
int8_t lock_video_orientation;
bool control; bool control;
}; };
+6 -6
View File
@@ -63,13 +63,13 @@ strquote(const char *src) {
} }
bool bool
parse_integer(const char *s, long *out) { parse_integer(const char *s, long long *out) {
char *endptr; char *endptr;
if (*s == '\0') { if (*s == '\0') {
return false; return false;
} }
errno = 0; errno = 0;
long value = strtol(s, &endptr, 0); long long value = strtol(s, &endptr, 0);
if (errno == ERANGE) { if (errno == ERANGE) {
return false; return false;
} }
@@ -82,13 +82,13 @@ parse_integer(const char *s, long *out) {
} }
bool bool
parse_integer_with_suffix(const char *s, long *out) { parse_integer_with_suffix(const char *s, long long *out) {
char *endptr; char *endptr;
if (*s == '\0') { if (*s == '\0') {
return false; return false;
} }
errno = 0; errno = 0;
long value = strtol(s, &endptr, 0); long long value = strtoll(s, &endptr, 0);
if (errno == ERANGE) { if (errno == ERANGE) {
return false; return false;
} }
@@ -106,8 +106,8 @@ parse_integer_with_suffix(const char *s, long *out) {
} }
} }
if ((value < 0 && LONG_MIN / mul > value) || if ((value < 0 && LLONG_MIN / mul > value) ||
(value > 0 && LONG_MAX / mul < value)) { (value > 0 && LLONG_MAX / mul < value)) {
return false; return false;
} }
+2 -2
View File
@@ -29,14 +29,14 @@ strquote(const char *src);
// parse s as an integer into value // parse s as an integer into value
// returns true if the conversion succeeded, false otherwise // returns true if the conversion succeeded, false otherwise
bool bool
parse_integer(const char *s, long *out); parse_integer(const char *s, long long *out);
// parse s as an integer into value // parse s as an integer into value
// like parse_integer(), but accept 'k'/'K' (x1000) and 'm'/'M' (x1000000) as // like parse_integer(), but accept 'k'/'K' (x1000) and 'm'/'M' (x1000000) as
// suffix // suffix
// returns true if the conversion succeeded, false otherwise // returns true if the conversion succeeded, false otherwise
bool bool
parse_integer_with_suffix(const char *s, long *out); parse_integer_with_suffix(const char *s, long long *out);
// return the index to truncate a UTF-8 string at a valid position // return the index to truncate a UTF-8 string at a valid position
size_t size_t
-2
View File
@@ -48,7 +48,6 @@ static void test_options(void) {
"--fullscreen", "--fullscreen",
"--max-fps", "30", "--max-fps", "30",
"--max-size", "1024", "--max-size", "1024",
"--lock-video-orientation", "2",
// "--no-control" is not compatible with "--turn-screen-off" // "--no-control" is not compatible with "--turn-screen-off"
// "--no-display" is not compatible with "--fulscreen" // "--no-display" is not compatible with "--fulscreen"
"--port", "1234", "--port", "1234",
@@ -79,7 +78,6 @@ static void test_options(void) {
assert(opts->fullscreen); assert(opts->fullscreen);
assert(opts->max_fps == 30); assert(opts->max_fps == 30);
assert(opts->max_size == 1024); assert(opts->max_size == 1024);
assert(opts->lock_video_orientation == 2);
assert(opts->port == 1234); assert(opts->port == 1234);
assert(!strcmp(opts->push_target, "/sdcard/Movies")); assert(!strcmp(opts->push_target, "/sdcard/Movies"));
assert(!strcmp(opts->record_filename, "file")); assert(!strcmp(opts->record_filename, "file"));
+2 -2
View File
@@ -171,7 +171,7 @@ static void test_utf8_truncate(void) {
} }
static void test_parse_integer(void) { static void test_parse_integer(void) {
long value; long long value;
bool ok = parse_integer("1234", &value); bool ok = parse_integer("1234", &value);
assert(ok); assert(ok);
assert(value == 1234); assert(value == 1234);
@@ -188,7 +188,7 @@ static void test_parse_integer(void) {
} }
static void test_parse_integer_with_suffix(void) { static void test_parse_integer_with_suffix(void) {
long value; long long value;
bool ok = parse_integer_with_suffix("1234", &value); bool ok = parse_integer_with_suffix("1234", &value);
assert(ok); assert(ok);
assert(value == 1234); assert(value == 1234);
+1 -1
View File
@@ -1,5 +1,5 @@
project('scrcpy', 'c', project('scrcpy', 'c',
version: '1.12.1', version: '1.12',
meson_version: '>= 0.37', meson_version: '>= 0.37',
default_options: [ default_options: [
'c_std=c11', 'c_std=c11',
+2 -2
View File
@@ -6,8 +6,8 @@ android {
applicationId "com.genymobile.scrcpy" applicationId "com.genymobile.scrcpy"
minSdkVersion 21 minSdkVersion 21
targetSdkVersion 29 targetSdkVersion 29
versionCode 14 versionCode 13
versionName "1.12.1" versionName "1.12"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
} }
buildTypes { buildTypes {
+1 -1
View File
@@ -12,7 +12,7 @@
set -e set -e
SCRCPY_DEBUG=false SCRCPY_DEBUG=false
SCRCPY_VERSION_NAME=1.12.1 SCRCPY_VERSION_NAME=1.12
PLATFORM=${ANDROID_PLATFORM:-29} PLATFORM=${ANDROID_PLATFORM:-29}
BUILD_TOOLS=${ANDROID_BUILD_TOOLS:-29.0.2} BUILD_TOOLS=${ANDROID_BUILD_TOOLS:-29.0.2}
@@ -22,13 +22,10 @@ public final class Device {
private final ServiceManager serviceManager = new ServiceManager(); private final ServiceManager serviceManager = new ServiceManager();
private final int lockedVideoOrientation;
private ScreenInfo screenInfo; private ScreenInfo screenInfo;
private RotationListener rotationListener; private RotationListener rotationListener;
public Device(Options options) { public Device(Options options) {
lockedVideoOrientation = options.getLockedVideoOrientation();
screenInfo = computeScreenInfo(options.getCrop(), options.getMaxSize()); screenInfo = computeScreenInfo(options.getCrop(), options.getMaxSize());
registerRotationWatcher(new IRotationWatcher.Stub() { registerRotationWatcher(new IRotationWatcher.Stub() {
@Override @Override
@@ -51,11 +48,11 @@ public final class Device {
private ScreenInfo computeScreenInfo(Rect crop, int maxSize) { private ScreenInfo computeScreenInfo(Rect crop, int maxSize) {
DisplayInfo displayInfo = serviceManager.getDisplayManager().getDisplayInfo(); DisplayInfo displayInfo = serviceManager.getDisplayManager().getDisplayInfo();
int rotation = displayInfo.getRotation(); boolean rotated = (displayInfo.getRotation() & 1) != 0;
Size deviceSize = displayInfo.getSize(); Size deviceSize = displayInfo.getSize();
Rect contentRect = new Rect(0, 0, deviceSize.getWidth(), deviceSize.getHeight()); Rect contentRect = new Rect(0, 0, deviceSize.getWidth(), deviceSize.getHeight());
if (crop != null) { if (crop != null) {
if (rotation % 2 != 0) { // 180s preserve dimensions if (rotated) {
// the crop (provided by the user) is expressed in the natural orientation // the crop (provided by the user) is expressed in the natural orientation
crop = flipRect(crop); crop = flipRect(crop);
} }
@@ -67,7 +64,7 @@ public final class Device {
} }
Size videoSize = computeVideoSize(contentRect.width(), contentRect.height(), maxSize); Size videoSize = computeVideoSize(contentRect.width(), contentRect.height(), maxSize);
return new ScreenInfo(contentRect, videoSize, rotation); return new ScreenInfo(contentRect, videoSize, rotated);
} }
private static String formatCrop(Rect rect) { private static String formatCrop(Rect rect) {
@@ -102,55 +99,22 @@ public final class Device {
return new Size(w, h); return new Size(w, h);
} }
/**
* Return the rotation to apply to the device rotation to get the requested locked video orientation
*
* @param deviceRotation the device rotation
* @return the rotation offset
*/
public int getVideoRotation(int deviceRotation) {
if (lockedVideoOrientation == -1) {
// no offset
return 0;
}
return (deviceRotation + 4 - lockedVideoOrientation) % 4;
}
/**
* Return the rotation to apply to the requested locked video orientation to get the device rotation
* @param deviceRotation the device rotation
* @return the (reverse) rotation offset
*/
private int getReverseVideoRotation(int deviceRotation) {
if (lockedVideoOrientation == -1) {
// no offset
return 0;
}
return (lockedVideoOrientation + 4 - deviceRotation) % 4;
}
public Point getPhysicalPoint(Position position) { public Point getPhysicalPoint(Position position) {
// it hides the field on purpose, to read it with a lock // it hides the field on purpose, to read it with a lock
@SuppressWarnings("checkstyle:HiddenField") @SuppressWarnings("checkstyle:HiddenField")
ScreenInfo screenInfo = getScreenInfo(); // read with synchronization ScreenInfo screenInfo = getScreenInfo(); // read with synchronization
Size videoSize = screenInfo.getVideoSize(); Size videoSize = screenInfo.getVideoSize();
Size clientVideoSize = position.getScreenSize();
int deviceRotation = screenInfo.getRotation();
int reverseVideoRotation = getReverseVideoRotation(deviceRotation);
// reverse the video rotation to apply the events
Position devicePosition = position.rotate(reverseVideoRotation);
Size clientVideoSize = devicePosition.getScreenSize();
if (!videoSize.equals(clientVideoSize)) { if (!videoSize.equals(clientVideoSize)) {
// The client sends a click relative to a video with wrong dimensions, // The client sends a click relative to a video with wrong dimensions,
// the device may have been rotated since the event was generated, so ignore the event // the device may have been rotated since the event was generated, so ignore the event
return null; return null;
} }
Rect contentRect = screenInfo.getContentRect(); Rect contentRect = screenInfo.getContentRect();
Point point = devicePosition.getPoint(); Point point = position.getPoint();
int convertedX = contentRect.left + point.getX() * contentRect.width() / videoSize.getWidth(); int scaledX = contentRect.left + point.getX() * contentRect.width() / videoSize.getWidth();
int convertedY = contentRect.top + point.getY() * contentRect.height() / videoSize.getHeight(); int scaledY = contentRect.top + point.getY() * contentRect.height() / videoSize.getHeight();
return new Point(convertedX, convertedY); return new Point(scaledX, scaledY);
} }
public static String getDeviceName() { public static String getDeviceName() {
@@ -6,7 +6,6 @@ public class Options {
private int maxSize; private int maxSize;
private int bitRate; private int bitRate;
private int maxFps; private int maxFps;
private int lockedVideoOrientation;
private boolean tunnelForward; private boolean tunnelForward;
private Rect crop; private Rect crop;
private boolean sendFrameMeta; // send PTS so that the client may record properly private boolean sendFrameMeta; // send PTS so that the client may record properly
@@ -36,14 +35,6 @@ public class Options {
this.maxFps = maxFps; this.maxFps = maxFps;
} }
public int getLockedVideoOrientation() {
return lockedVideoOrientation;
}
public void setLockedVideoOrientation(int lockedVideoOrientation) {
this.lockedVideoOrientation = lockedVideoOrientation;
}
public boolean isTunnelForward() { public boolean isTunnelForward() {
return tunnelForward; return tunnelForward;
} }
@@ -23,19 +23,6 @@ public class Position {
return screenSize; return screenSize;
} }
public Position rotate(int rotation) {
switch (rotation) {
case 1:
return new Position(new Point(screenSize.getHeight() - point.getY(), point.getX()), screenSize.rotate());
case 2:
return new Position(new Point(screenSize.getWidth() - point.getX(), screenSize.getHeight() - point.getY()), screenSize);
case 3:
return new Position(new Point(point.getY(), screenSize.getWidth() - point.getX()), screenSize.rotate());
default:
return this;
}
}
@Override @Override
public boolean equals(Object o) { public boolean equals(Object o) {
if (this == o) { if (this == o) {
@@ -27,21 +27,19 @@ public class ScreenEncoder implements Device.RotationListener {
private int bitRate; private int bitRate;
private int maxFps; private int maxFps;
private int lockedVideoOrientation;
private int iFrameInterval; private int iFrameInterval;
private boolean sendFrameMeta; private boolean sendFrameMeta;
private long ptsOrigin; private long ptsOrigin;
public ScreenEncoder(boolean sendFrameMeta, int bitRate, int maxFps, int lockedVideoOrientation, int iFrameInterval) { public ScreenEncoder(boolean sendFrameMeta, int bitRate, int maxFps, int iFrameInterval) {
this.sendFrameMeta = sendFrameMeta; this.sendFrameMeta = sendFrameMeta;
this.bitRate = bitRate; this.bitRate = bitRate;
this.maxFps = maxFps; this.maxFps = maxFps;
this.lockedVideoOrientation = lockedVideoOrientation;
this.iFrameInterval = iFrameInterval; this.iFrameInterval = iFrameInterval;
} }
public ScreenEncoder(boolean sendFrameMeta, int bitRate, int maxFps, int lockedVideoOrientation) { public ScreenEncoder(boolean sendFrameMeta, int bitRate, int maxFps) {
this(sendFrameMeta, bitRate, maxFps, lockedVideoOrientation, DEFAULT_I_FRAME_INTERVAL); this(sendFrameMeta, bitRate, maxFps, DEFAULT_I_FRAME_INTERVAL);
} }
@Override @Override
@@ -64,14 +62,12 @@ public class ScreenEncoder implements Device.RotationListener {
do { do {
MediaCodec codec = createCodec(); MediaCodec codec = createCodec();
IBinder display = createDisplay(); IBinder display = createDisplay();
ScreenInfo screenInfo = device.getScreenInfo(); Rect contentRect = device.getScreenInfo().getContentRect();
Rect contentRect = screenInfo.getContentRect(); Rect videoRect = device.getScreenInfo().getVideoSize().toRect();
Rect videoRect = screenInfo.getVideoSize().toRect(); setSize(format, videoRect.width(), videoRect.height());
int videoRotation = device.getVideoRotation(screenInfo.getRotation());
setSize(format, videoRotation, videoRect.width(), videoRect.height());
configure(codec, format); configure(codec, format);
Surface surface = codec.createInputSurface(); Surface surface = codec.createInputSurface();
setDisplaySurface(display, surface, videoRotation, contentRect, videoRect); setDisplaySurface(display, surface, contentRect, videoRect);
codec.start(); codec.start();
try { try {
alive = encode(codec, fd); alive = encode(codec, fd);
@@ -171,21 +167,16 @@ public class ScreenEncoder implements Device.RotationListener {
codec.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE); codec.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
} }
private static void setSize(MediaFormat format, int orientation, int width, int height) { private static void setSize(MediaFormat format, int width, int height) {
if (orientation % 2 == 0) { format.setInteger(MediaFormat.KEY_WIDTH, width);
format.setInteger(MediaFormat.KEY_WIDTH, width); format.setInteger(MediaFormat.KEY_HEIGHT, height);
format.setInteger(MediaFormat.KEY_HEIGHT, height);
return;
}
format.setInteger(MediaFormat.KEY_WIDTH, height);
format.setInteger(MediaFormat.KEY_HEIGHT, width);
} }
private static void setDisplaySurface(IBinder display, Surface surface, int orientation, Rect deviceRect, Rect displayRect) { private static void setDisplaySurface(IBinder display, Surface surface, Rect deviceRect, Rect displayRect) {
SurfaceControl.openTransaction(); SurfaceControl.openTransaction();
try { try {
SurfaceControl.setDisplaySurface(display, surface); SurfaceControl.setDisplaySurface(display, surface);
SurfaceControl.setDisplayProjection(display, orientation, deviceRect, displayRect); SurfaceControl.setDisplayProjection(display, 0, deviceRect, displayRect);
SurfaceControl.setDisplayLayerStack(display, 0); SurfaceControl.setDisplayLayerStack(display, 0);
} finally { } finally {
SurfaceControl.closeTransaction(); SurfaceControl.closeTransaction();
@@ -5,12 +5,12 @@ import android.graphics.Rect;
public final class ScreenInfo { public final class ScreenInfo {
private final Rect contentRect; // device size, possibly cropped private final Rect contentRect; // device size, possibly cropped
private final Size videoSize; private final Size videoSize;
private final int rotation; private final boolean rotated;
public ScreenInfo(Rect contentRect, Size videoSize, int rotation) { public ScreenInfo(Rect contentRect, Size videoSize, boolean rotated) {
this.contentRect = contentRect; this.contentRect = contentRect;
this.videoSize = videoSize; this.videoSize = videoSize;
this.rotation = rotation; this.rotated = rotated;
} }
public Rect getContentRect() { public Rect getContentRect() {
@@ -21,25 +21,11 @@ public final class ScreenInfo {
return videoSize; return videoSize;
} }
public int getRotation() { public ScreenInfo withRotation(int rotation) {
return rotation; boolean newRotated = (rotation & 1) != 0;
} if (rotated == newRotated) {
public ScreenInfo withRotation(int newRotation) {
if (newRotation == rotation) {
return this; return this;
} }
// true if changed between portrait and landscape return new ScreenInfo(Device.flipRect(contentRect), videoSize.rotate(), newRotated);
boolean orientationChanged = (rotation + newRotation) % 2 != 0;
Rect newContentRect;
Size newVideoSize;
if (orientationChanged) {
newContentRect = Device.flipRect(contentRect);
newVideoSize = videoSize.rotate();
} else {
newContentRect = contentRect;
newVideoSize = videoSize;
}
return new ScreenInfo(newContentRect, newVideoSize, newRotation);
} }
} }
@@ -19,8 +19,7 @@ public final class Server {
final Device device = new Device(options); final Device device = new Device(options);
boolean tunnelForward = options.isTunnelForward(); boolean tunnelForward = options.isTunnelForward();
try (DesktopConnection connection = DesktopConnection.open(device, tunnelForward)) { try (DesktopConnection connection = DesktopConnection.open(device, tunnelForward)) {
ScreenEncoder screenEncoder = new ScreenEncoder(options.getSendFrameMeta(), options.getBitRate(), options.getMaxFps(), ScreenEncoder screenEncoder = new ScreenEncoder(options.getSendFrameMeta(), options.getBitRate(), options.getMaxFps());
options.getLockedVideoOrientation());
if (options.getControl()) { if (options.getControl()) {
Controller controller = new Controller(device, connection); Controller controller = new Controller(device, connection);
@@ -80,8 +79,8 @@ public final class Server {
"The server version (" + clientVersion + ") does not match the client " + "(" + BuildConfig.VERSION_NAME + ")"); "The server version (" + clientVersion + ") does not match the client " + "(" + BuildConfig.VERSION_NAME + ")");
} }
if (args.length != 9) { if (args.length != 8) {
throw new IllegalArgumentException("Expecting 9 parameters"); throw new IllegalArgumentException("Expecting 8 parameters");
} }
Options options = new Options(); Options options = new Options();
@@ -95,20 +94,17 @@ public final class Server {
int maxFps = Integer.parseInt(args[3]); int maxFps = Integer.parseInt(args[3]);
options.setMaxFps(maxFps); options.setMaxFps(maxFps);
int lockedVideoOrientation = Integer.parseInt(args[4]);
options.setLockedVideoOrientation(lockedVideoOrientation);
// use "adb forward" instead of "adb tunnel"? (so the server must listen) // use "adb forward" instead of "adb tunnel"? (so the server must listen)
boolean tunnelForward = Boolean.parseBoolean(args[5]); boolean tunnelForward = Boolean.parseBoolean(args[4]);
options.setTunnelForward(tunnelForward); options.setTunnelForward(tunnelForward);
Rect crop = parseCrop(args[6]); Rect crop = parseCrop(args[5]);
options.setCrop(crop); options.setCrop(crop);
boolean sendFrameMeta = Boolean.parseBoolean(args[7]); boolean sendFrameMeta = Boolean.parseBoolean(args[6]);
options.setSendFrameMeta(sendFrameMeta); options.setSendFrameMeta(sendFrameMeta);
boolean control = Boolean.parseBoolean(args[8]); boolean control = Boolean.parseBoolean(args[7]);
options.setControl(control); options.setControl(control);
return options; return options;