From 7d5845196e6a7e919d0ec3b5fc53fc7dab8d3222 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sat, 14 Dec 2019 18:13:56 +0100 Subject: [PATCH 01/11] Fix memory leak on portable builds The function get_server_path() sometimes returned an owned string, sometimes a non-owned string. Always return an allocated (owned) string, and free it after usage. --- app/src/server.c | 26 +++++++++++++++++++++----- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/app/src/server.c b/app/src/server.c index ff167aebcc..31e09ae3cc 100644 --- a/app/src/server.c +++ b/app/src/server.c @@ -18,20 +18,31 @@ #define DEFAULT_SERVER_PATH PREFIX "/share/scrcpy/" SERVER_FILENAME #define DEVICE_SERVER_PATH "/data/local/tmp/scrcpy-server.jar" -static const char * +static char * get_server_path(void) { const char *server_path_env = getenv("SCRCPY_SERVER_PATH"); if (server_path_env) { - LOGD("Using SCRCPY_SERVER_PATH: %s", server_path_env); // if the envvar is set, use it - return server_path_env; + char *server_path = SDL_strdup(server_path_env); + if (!server_path) { + LOGE("Could not allocate memory"); + return NULL; + } + LOGD("Using SCRCPY_SERVER_PATH: %s", server_path); + return server_path; } #ifndef PORTABLE LOGD("Using server: " DEFAULT_SERVER_PATH); + char *server_path = SDL_strdup(DEFAULT_SERVER_PATH); + if (!server_path) { + LOGE("Could not allocate memory"); + return NULL; + } // the absolute path is hardcoded - return DEFAULT_SERVER_PATH; + return server_path; #else + // use scrcpy-server in the same directory as the executable char *executable_path = get_executable_path(); if (!executable_path) { @@ -67,12 +78,17 @@ get_server_path(void) { static bool push_server(const char *serial) { - const char *server_path = get_server_path(); + char *server_path = get_server_path(); + if (!server_path) { + return false; + } if (!is_regular_file(server_path)) { LOGE("'%s' does not exist or is not a regular file\n", server_path); + SDL_free(server_path); return false; } process_t process = adb_push(serial, server_path, DEVICE_SERVER_PATH); + SDL_free(server_path); return process_check_success(process, "adb push"); } From 78a320a763d747b04dcb0c6fed41a1edcc90101c Mon Sep 17 00:00:00 2001 From: Yu-Chen Lin Date: Thu, 12 Dec 2019 20:26:58 +0800 Subject: [PATCH 02/11] Fix utf-8 char path in windows MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The file 'E:\安安\scrcpy-win64-v1.12.1-1-g31bd950\scrcpy-server' exists, however, it will show msg as follow: INFO: scrcpy 1.12.1 stat: No such file or directory ERROR: 'E:\安安\scrcpy-win64-v1.12.1-1-g31bd950\scrcpy-server' does not exist or is not a regular file Press any key to continue... This patch fixes it. Signed-off-by: Yu-Chen Lin Signed-off-by: Romain Vimont --- app/src/command.c | 14 -------------- app/src/sys/unix/command.c | 12 ++++++++++++ app/src/sys/win/command.c | 21 +++++++++++++++++++++ 3 files changed, 33 insertions(+), 14 deletions(-) diff --git a/app/src/command.c b/app/src/command.c index 63afccb41c..abaa223d65 100644 --- a/app/src/command.c +++ b/app/src/command.c @@ -4,9 +4,6 @@ #include #include #include -#include -#include -#include #include "config.h" #include "common.h" @@ -205,14 +202,3 @@ process_check_success(process_t proc, const char *name) { } return true; } - -bool -is_regular_file(const char *path) { - struct stat path_stat; - int r = stat(path, &path_stat); - if (r) { - perror("stat"); - return false; - } - return S_ISREG(path_stat.st_mode); -} diff --git a/app/src/sys/unix/command.c b/app/src/sys/unix/command.c index fbcf2355cd..af5d4b2f28 100644 --- a/app/src/sys/unix/command.c +++ b/app/src/sys/unix/command.c @@ -15,6 +15,7 @@ #include #include #include +#include #include #include @@ -127,3 +128,14 @@ get_executable_path(void) { return NULL; #endif } + +bool +is_regular_file(const char *path) { + struct stat path_stat; + + if (stat(path, &path_stat)) { + perror("stat"); + return false; + } + return S_ISREG(path_stat.st_mode); +} diff --git a/app/src/sys/win/command.c b/app/src/sys/win/command.c index 55edaf8f6e..3846847eba 100644 --- a/app/src/sys/win/command.c +++ b/app/src/sys/win/command.c @@ -4,6 +4,8 @@ #include "util/log.h" #include "util/str_util.h" +#include + static int build_cmd(char *cmd, size_t len, const char *const argv[]) { // Windows command-line parsing is WTF: @@ -90,3 +92,22 @@ get_executable_path(void) { buf[len] = '\0'; return utf8_from_wide_char(buf); } + +bool +is_regular_file(const char *path) { + wchar_t *wide_path = utf8_to_wide_char(path); + if (!wide_path) { + LOGC("Could not allocate wide char string"); + return false; + } + + struct _stat path_stat; + int r = _wstat(wide_path, &path_stat); + SDL_free(wide_path); + + if (r) { + perror("stat"); + return false; + } + return S_ISREG(path_stat.st_mode); +} From f9786e50346246139b67ab70d040443e3a80c16b Mon Sep 17 00:00:00 2001 From: Yu-Chen Lin Date: Sat, 14 Dec 2019 14:34:49 +0800 Subject: [PATCH 03/11] Get env in windows correctly Signed-off-by: Yu-Chen Lin Signed-off-by: Romain Vimont --- app/src/server.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/app/src/server.c b/app/src/server.c index 31e09ae3cc..54813ab8fb 100644 --- a/app/src/server.c +++ b/app/src/server.c @@ -6,11 +6,13 @@ #include #include #include +#include #include "config.h" #include "command.h" #include "util/log.h" #include "util/net.h" +#include "util/str_util.h" #define SOCKET_NAME "scrcpy" #define SERVER_FILENAME "scrcpy-server" @@ -20,10 +22,18 @@ static char * get_server_path(void) { +#ifdef __WINDOWS__ + const wchar_t *server_path_env = _wgetenv(L"SCRCPY_SERVER_PATH"); +#else const char *server_path_env = getenv("SCRCPY_SERVER_PATH"); +#endif if (server_path_env) { // if the envvar is set, use it +#ifdef __WINDOWS__ + char *server_path = utf8_from_wide_char(server_path_env); +#else char *server_path = SDL_strdup(server_path_env); +#endif if (!server_path) { LOGE("Could not allocate memory"); return NULL; From db6252e52b95af16976f6afa38028aca8c221036 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 15 Dec 2019 21:55:43 +0100 Subject: [PATCH 04/11] Simplify net.c The platform-specific code for net.c was implemented in sys/*/net.c. But the differences are quite limited, so use ifdef-blocks in the single net.c instead. --- app/meson.build | 2 -- app/src/sys/unix/net.c | 21 --------------------- app/src/sys/win/net.c | 25 ------------------------- app/src/util/net.c | 30 ++++++++++++++++++++++++++++++ 4 files changed, 30 insertions(+), 48 deletions(-) delete mode 100644 app/src/sys/unix/net.c delete mode 100644 app/src/sys/win/net.c diff --git a/app/meson.build b/app/meson.build index 3bcb9bc10b..62006ca110 100644 --- a/app/meson.build +++ b/app/meson.build @@ -76,11 +76,9 @@ cc = meson.get_compiler('c') if host_machine.system() == 'windows' src += [ 'src/sys/win/command.c' ] - src += [ 'src/sys/win/net.c' ] dependencies += cc.find_library('ws2_32') else src += [ 'src/sys/unix/command.c' ] - src += [ 'src/sys/unix/net.c' ] endif conf = configuration_data() diff --git a/app/src/sys/unix/net.c b/app/src/sys/unix/net.c deleted file mode 100644 index d67a660f3e..0000000000 --- a/app/src/sys/unix/net.c +++ /dev/null @@ -1,21 +0,0 @@ -#include "util/net.h" - -#include - -#include "config.h" - -bool -net_init(void) { - // do nothing - return true; -} - -void -net_cleanup(void) { - // do nothing -} - -bool -net_close(socket_t socket) { - return !close(socket); -} diff --git a/app/src/sys/win/net.c b/app/src/sys/win/net.c deleted file mode 100644 index aebce7fc3e..0000000000 --- a/app/src/sys/win/net.c +++ /dev/null @@ -1,25 +0,0 @@ -#include "util/net.h" - -#include "config.h" -#include "util/log.h" - -bool -net_init(void) { - WSADATA wsa; - int res = WSAStartup(MAKEWORD(2, 2), &wsa) < 0; - if (res < 0) { - LOGC("WSAStartup failed with error %d", res); - return false; - } - return true; -} - -void -net_cleanup(void) { - WSACleanup(); -} - -bool -net_close(socket_t socket) { - return !closesocket(socket); -} diff --git a/app/src/util/net.c b/app/src/util/net.c index bf4389dd4e..efce6fa9b9 100644 --- a/app/src/util/net.c +++ b/app/src/util/net.c @@ -1,6 +1,7 @@ #include "net.h" #include +#include #include "config.h" #include "log.h" @@ -115,3 +116,32 @@ bool net_shutdown(socket_t socket, int how) { return !shutdown(socket, how); } + +bool +net_init(void) { +#ifdef __WINDOWS__ + WSADATA wsa; + int res = WSAStartup(MAKEWORD(2, 2), &wsa) < 0; + if (res < 0) { + LOGC("WSAStartup failed with error %d", res); + return false; + } +#endif + return true; +} + +void +net_cleanup(void) { +#ifdef __WINDOWS__ + WSACleanup(); +#endif +} + +bool +net_close(socket_t socket) { +#ifdef __WINDOWS__ + return !closesocket(socket); +#else + return !close(socket); +#endif +} From 83d48267a734e999fac0eccd93173b93019bf006 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 19 Dec 2019 11:49:50 +0100 Subject: [PATCH 05/11] Accept --max-fps before Android 10 KEY_MAX_FPS_TO_ENCODER existed privately before Android 10: --- README.md | 4 +++- app/scrcpy.1 | 2 +- app/src/cli.c | 4 ++-- .../main/java/com/genymobile/scrcpy/ScreenEncoder.java | 10 +++++----- 4 files changed, 11 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 4d6d29fd93..10e61bcaaf 100644 --- a/README.md +++ b/README.md @@ -137,12 +137,14 @@ scrcpy -b 2M # short version #### Limit frame rate -On devices with Android >= 10, the capture frame rate can be limited: +The capture frame rate can be limited: ```bash scrcpy --max-fps 15 ``` +This is officially supported since Android 10, but may work on earlier versions. + #### Crop The device screen may be cropped to mirror only part of the screen. diff --git a/app/scrcpy.1 b/app/scrcpy.1 index 23b168ca8f..2600734b25 100644 --- a/app/scrcpy.1 +++ b/app/scrcpy.1 @@ -43,7 +43,7 @@ Print this help. .TP .BI "\-\-max\-fps " value -Limit the framerate of screen capture (only supported on devices with Android >= 10). +Limit the framerate of screen capture (officially supported since Android 10, but may work on earlier versions). .TP .BI "\-m, \-\-max\-size " value diff --git a/app/src/cli.c b/app/src/cli.c index d9e1013a0a..7502556336 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -42,8 +42,8 @@ scrcpy_print_usage(const char *arg0) { " Print this help.\n" "\n" " --max-fps value\n" - " Limit the frame rate of screen capture (only supported on\n" - " devices with Android >= 10).\n" + " Limit the frame rate of screen capture (officially supported\n" + " since Android 10, but may work on earlier versions).\n" "\n" " -m, --max-size value\n" " Limit both the width and height of the video to value. The\n" diff --git a/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java b/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java index c9a37f847b..1c71eabd36 100644 --- a/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java +++ b/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java @@ -19,6 +19,7 @@ public class ScreenEncoder implements Device.RotationListener { private static final int DEFAULT_I_FRAME_INTERVAL = 10; // seconds private static final int REPEAT_FRAME_DELAY_US = 100_000; // repeat after 100ms + private static final String KEY_MAX_FPS_TO_ENCODER = "max-fps-to-encoder"; private static final int NO_PTS = -1; @@ -150,11 +151,10 @@ private static MediaFormat createFormat(int bitRate, int maxFps, int iFrameInter // display the very first frame, and recover from bad quality when no new frames format.setLong(MediaFormat.KEY_REPEAT_PREVIOUS_FRAME_AFTER, REPEAT_FRAME_DELAY_US); // µs if (maxFps > 0) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { - format.setFloat(MediaFormat.KEY_MAX_FPS_TO_ENCODER, maxFps); - } else { - Ln.w("Max FPS is only supported since Android 10, the option has been ignored"); - } + // The key existed privately before Android 10: + // + // + format.setFloat(KEY_MAX_FPS_TO_ENCODER, maxFps); } return format; } From a8ceaf52846cddf61ea7da939db4d35dff969eb4 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 17 Jan 2020 21:00:14 +0100 Subject: [PATCH 06/11] Fix include order --- app/src/sys/win/command.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/sys/win/command.c b/app/src/sys/win/command.c index 3846847eba..105234b034 100644 --- a/app/src/sys/win/command.c +++ b/app/src/sys/win/command.c @@ -1,11 +1,11 @@ #include "command.h" +#include + #include "config.h" #include "util/log.h" #include "util/str_util.h" -#include - static int build_cmd(char *cmd, size_t len, const char *const argv[]) { // Windows command-line parsing is WTF: From d1a9a76cc6d738c455942d37138a3c365a423e88 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Mon, 9 Dec 2019 22:14:43 +0100 Subject: [PATCH 07/11] Reorder functions Move functions so that they can be called from enable_tunnel() (in the following commit). --- app/src/server.c | 29 ++++++++++++++--------------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/app/src/server.c b/app/src/server.c index 54813ab8fb..019b460a8d 100644 --- a/app/src/server.c +++ b/app/src/server.c @@ -126,6 +126,20 @@ disable_tunnel_forward(const char *serial, uint16_t local_port) { return process_check_success(process, "adb forward --remove"); } +static bool +disable_tunnel(struct server *server) { + if (server->tunnel_forward) { + return disable_tunnel_forward(server->serial, server->local_port); + } + return disable_tunnel_reverse(server->serial); +} + +static socket_t +listen_on_port(uint16_t port) { +#define IPV4_LOCALHOST 0x7F000001 + return net_listen(IPV4_LOCALHOST, port, 1); +} + static bool enable_tunnel(struct server *server) { if (enable_tunnel_reverse(server->serial, server->local_port)) { @@ -137,14 +151,6 @@ enable_tunnel(struct server *server) { return enable_tunnel_forward(server->serial, server->local_port); } -static bool -disable_tunnel(struct server *server) { - if (server->tunnel_forward) { - return disable_tunnel_forward(server->serial, server->local_port); - } - return disable_tunnel_reverse(server->serial); -} - static process_t execute_server(struct server *server, const struct server_params *params) { char max_size_string[6]; @@ -187,13 +193,6 @@ execute_server(struct server *server, const struct server_params *params) { return adb_execute(server->serial, cmd, sizeof(cmd) / sizeof(cmd[0])); } -#define IPV4_LOCALHOST 0x7F000001 - -static socket_t -listen_on_port(uint16_t port) { - return net_listen(IPV4_LOCALHOST, port, 1); -} - static socket_t connect_and_read_byte(uint16_t port) { socket_t socket = net_connect(IPV4_LOCALHOST, port); From ca0031cbde8cc4aaed07ddb7523841754c0423d5 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Mon, 9 Dec 2019 22:10:42 +0100 Subject: [PATCH 08/11] Refactor server tunnel initialization Start the server socket in enable_tunnel() directly. For the caller point of view, enabling the tunnel opens a port (either the server socket locally or the "adb forward" process). --- app/src/server.c | 34 +++++++++++++++------------------- 1 file changed, 15 insertions(+), 19 deletions(-) diff --git a/app/src/server.c b/app/src/server.c index 019b460a8d..0579a4b034 100644 --- a/app/src/server.c +++ b/app/src/server.c @@ -143,9 +143,24 @@ listen_on_port(uint16_t port) { static bool enable_tunnel(struct server *server) { if (enable_tunnel_reverse(server->serial, server->local_port)) { + // At the application level, the device part is "the server" because it + // serves video stream and control. However, at the network level, the + // client listens and the server connects to the client. That way, the + // client can listen before starting the server app, so there is no + // need to try to connect until the server socket is listening on the + // device. + server->server_socket = listen_on_port(server->local_port); + if (server->server_socket == INVALID_SOCKET) { + LOGE("Could not listen on port %" PRIu16, server->local_port); + disable_tunnel(server); + return false; + } + return true; } + // if "adb reverse" does not work (e.g. over "adb connect"), it fallbacks to + // "adb forward", so the app socket is the client LOGW("'adb reverse' failed, fallback to 'adb forward'"); server->tunnel_forward = true; return enable_tunnel_forward(server->serial, server->local_port); @@ -265,25 +280,6 @@ server_start(struct server *server, const char *serial, return false; } - // if "adb reverse" does not work (e.g. over "adb connect"), it fallbacks to - // "adb forward", so the app socket is the client - if (!server->tunnel_forward) { - // At the application level, the device part is "the server" because it - // serves video stream and control. However, at the network level, the - // client listens and the server connects to the client. That way, the - // client can listen before starting the server app, so there is no - // need to try to connect until the server socket is listening on the - // device. - - server->server_socket = listen_on_port(params->local_port); - if (server->server_socket == INVALID_SOCKET) { - LOGE("Could not listen on port %" PRIu16, params->local_port); - disable_tunnel(server); - SDL_free(server->serial); - return false; - } - } - // server will connect to our server socket server->process = execute_server(server, params); From 2a3a9d4ea91f9b79b87387a4883af484e702c53a Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Mon, 9 Dec 2019 14:32:59 +0100 Subject: [PATCH 09/11] Add util function to parse a list of integers This will help parsing arguments like '1234:5678' into a list of integers. --- app/src/util/str_util.c | 29 +++++++++++++++++++++++ app/src/util/str_util.h | 5 ++++ app/tests/test_strutil.c | 50 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 84 insertions(+) diff --git a/app/src/util/str_util.c b/app/src/util/str_util.c index 4d17540718..ce0498a537 100644 --- a/app/src/util/str_util.c +++ b/app/src/util/str_util.c @@ -81,6 +81,35 @@ parse_integer(const char *s, long *out) { return true; } +size_t +parse_integers(const char *s, const char sep, size_t max_items, long *out) { + size_t count = 0; + char *endptr; + do { + errno = 0; + long value = strtol(s, &endptr, 0); + if (errno == ERANGE) { + return 0; + } + + if (endptr == s || (*endptr != sep && *endptr != '\0')) { + return 0; + } + + out[count++] = value; + if (*endptr == sep) { + if (count >= max_items) { + // max items already reached, could not accept a new item + return 0; + } + // parse the next token during the next iteration + s = endptr + 1; + } + } while (*endptr != '\0'); + + return count; +} + bool parse_integer_with_suffix(const char *s, long *out) { char *endptr; diff --git a/app/src/util/str_util.h b/app/src/util/str_util.h index 8d9b990ced..c7f26cdb65 100644 --- a/app/src/util/str_util.h +++ b/app/src/util/str_util.h @@ -31,6 +31,11 @@ strquote(const char *src); bool parse_integer(const char *s, long *out); +// parse s as integers separated by sep (for example '1234:2000') +// returns the number of integers on success, 0 on failure +size_t +parse_integers(const char *s, const char sep, size_t max_items, long *out); + // parse s as an integer into value // like parse_integer(), but accept 'k'/'K' (x1000) and 'm'/'M' (x1000000) as // suffix diff --git a/app/tests/test_strutil.c b/app/tests/test_strutil.c index 200e0f6306..a88bca0eec 100644 --- a/app/tests/test_strutil.c +++ b/app/tests/test_strutil.c @@ -187,6 +187,55 @@ static void test_parse_integer(void) { assert(!ok); // out-of-range } +static void test_parse_integers(void) { + long values[5]; + + size_t count = parse_integers("1234", ':', 5, values); + assert(count == 1); + assert(values[0] == 1234); + + count = parse_integers("1234:5678", ':', 5, values); + assert(count == 2); + assert(values[0] == 1234); + assert(values[1] == 5678); + + count = parse_integers("1234:5678", ':', 2, values); + assert(count == 2); + assert(values[0] == 1234); + assert(values[1] == 5678); + + count = parse_integers("1234:-5678", ':', 2, values); + assert(count == 2); + assert(values[0] == 1234); + assert(values[1] == -5678); + + count = parse_integers("1:2:3:4:5", ':', 5, values); + assert(count == 5); + assert(values[0] == 1); + assert(values[1] == 2); + assert(values[2] == 3); + assert(values[3] == 4); + assert(values[4] == 5); + + count = parse_integers("1234:5678", ':', 1, values); + assert(count == 0); // max_items == 1 + + count = parse_integers("1:2:3:4:5", ':', 3, values); + assert(count == 0); // max_items == 3 + + count = parse_integers(":1234", ':', 5, values); + assert(count == 0); // invalid + + count = parse_integers("1234:", ':', 5, values); + assert(count == 0); // invalid + + count = parse_integers("1234:", ':', 1, values); + assert(count == 0); // invalid, even when max_items == 1 + + count = parse_integers("1234::5678", ':', 5, values); + assert(count == 0); // invalid +} + static void test_parse_integer_with_suffix(void) { long value; bool ok = parse_integer_with_suffix("1234", &value); @@ -249,6 +298,7 @@ int main(void) { test_strquote(); test_utf8_truncate(); test_parse_integer(); + test_parse_integers(); test_parse_integer_with_suffix(); return 0; } From dc7fcf3c7a7a0e26c2447990206b798742e062c7 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Mon, 9 Dec 2019 21:16:09 +0100 Subject: [PATCH 10/11] Accept port range Accept a range of ports to listen to, so that it does not fail if another instance of scrcpy is currently starting. The range can be passed via the command line: scrcpy -p 27183:27186 scrcpy -p 27183 # implicitly 27183:27183, as before The default is 27183:27199. Closes #951 --- app/meson.build | 5 +-- app/scrcpy.1 | 6 ++-- app/src/cli.c | 57 ++++++++++++++++++++++++------ app/src/common.h | 5 +++ app/src/scrcpy.c | 2 +- app/src/scrcpy.h | 8 +++-- app/src/server.c | 84 ++++++++++++++++++++++++++++++++++++++------ app/src/server.h | 26 ++++++++------ app/tests/test_cli.c | 5 +-- 9 files changed, 157 insertions(+), 41 deletions(-) diff --git a/app/meson.build b/app/meson.build index 62006ca110..171d6e359f 100644 --- a/app/meson.build +++ b/app/meson.build @@ -96,9 +96,10 @@ conf.set_quoted('PREFIX', get_option('prefix')) # directory as the executable) conf.set('PORTABLE', get_option('portable')) -# the default client TCP port for the "adb reverse" tunnel +# the default client TCP port range for the "adb reverse" tunnel # overridden by option --port -conf.set('DEFAULT_LOCAL_PORT', '27183') +conf.set('DEFAULT_LOCAL_PORT_RANGE_FIRST', '27183') +conf.set('DEFAULT_LOCAL_PORT_RANGE_LAST', '27199') # the default max video size for both dimensions, in pixels # overridden by option --max-size diff --git a/app/scrcpy.1 b/app/scrcpy.1 index 2600734b25..0c1bf5e467 100644 --- a/app/scrcpy.1 +++ b/app/scrcpy.1 @@ -60,10 +60,10 @@ Disable device control (mirror the device in read\-only). Do not display device (only when screen recording is enabled). .TP -.BI "\-p, \-\-port " port -Set the TCP port the client listens on. +.BI "\-p, \-\-port " port[:port] +Set the TCP port (range) used by the client to listen. -Default is 27183. +Default is 27183:27199. .TP .B \-\-prefer\-text diff --git a/app/src/cli.c b/app/src/cli.c index 7502556336..135dd5dfe0 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -58,9 +58,9 @@ scrcpy_print_usage(const char *arg0) { " Do not display device (only when screen recording is\n" " enabled).\n" "\n" - " -p, --port port\n" - " Set the TCP port the client listens on.\n" - " Default is %d.\n" + " -p, --port port[:port]\n" + " Set the TCP port (range) used by the client to listen.\n" + " Default is %d:%d.\n" "\n" " --prefer-text\n" " Inject alpha characters and space as text events instead of\n" @@ -193,7 +193,7 @@ scrcpy_print_usage(const char *arg0) { arg0, DEFAULT_BIT_RATE, DEFAULT_MAX_SIZE, DEFAULT_MAX_SIZE ? "" : " (unlimited)", - DEFAULT_LOCAL_PORT); + DEFAULT_LOCAL_PORT_RANGE_FIRST, DEFAULT_LOCAL_PORT_RANGE_LAST); } static bool @@ -221,6 +221,27 @@ parse_integer_arg(const char *s, long *out, bool accept_suffix, long min, return true; } +static size_t +parse_integers_arg(const char *s, size_t max_items, long *out, long min, + long max, const char *name) { + size_t count = parse_integers(s, ':', max_items, out); + if (!count) { + LOGE("Could not parse %s: %s", name, s); + return 0; + } + + for (size_t i = 0; i < count; ++i) { + long value = out[i]; + if (value < min || value > max) { + LOGE("Could not parse %s: value (%ld) out-of-range (%ld; %ld)", + name, value, min, max); + return 0; + } + } + + return count; +} + static bool parse_bit_rate(const char *s, uint32_t *bit_rate) { long value; @@ -286,14 +307,30 @@ parse_window_dimension(const char *s, uint16_t *dimension) { } static bool -parse_port(const char *s, uint16_t *port) { - long value; - bool ok = parse_integer_arg(s, &value, false, 0, 0xFFFF, "port"); - if (!ok) { +parse_port_range(const char *s, struct port_range *port_range) { + long values[2]; + size_t count = parse_integers_arg(s, 2, values, 0, 0xFFFF, "port"); + if (!count) { return false; } - *port = (uint16_t) value; + uint16_t v0 = (uint16_t) values[0]; + if (count == 1) { + port_range->first = v0; + port_range->last = v0; + return true; + } + + assert(count == 2); + uint16_t v1 = (uint16_t) values[1]; + if (v0 < v1) { + port_range->first = v0; + port_range->last = v1; + } else { + port_range->first = v1; + port_range->last = v0; + } + return true; } @@ -424,7 +461,7 @@ scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) { opts->display = false; break; case 'p': - if (!parse_port(optarg, &opts->port)) { + if (!parse_port_range(optarg, &opts->port_range)) { return false; } break; diff --git a/app/src/common.h b/app/src/common.h index e5cbe95326..4cbf1d7494 100644 --- a/app/src/common.h +++ b/app/src/common.h @@ -27,4 +27,9 @@ struct position { struct point point; }; +struct port_range { + uint16_t first; + uint16_t last; +}; + #endif diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index 17be1ed415..f315ca2042 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -280,7 +280,7 @@ scrcpy(const struct scrcpy_options *options) { bool record = !!options->record_filename; struct server_params params = { .crop = options->crop, - .local_port = options->port, + .port_range = options->port_range, .max_size = options->max_size, .bit_rate = options->bit_rate, .max_fps = options->max_fps, diff --git a/app/src/scrcpy.h b/app/src/scrcpy.h index 75de8717f3..9303a30c45 100644 --- a/app/src/scrcpy.h +++ b/app/src/scrcpy.h @@ -5,6 +5,7 @@ #include #include "config.h" +#include "common.h" #include "input_manager.h" #include "recorder.h" @@ -15,7 +16,7 @@ struct scrcpy_options { const char *window_title; const char *push_target; enum recorder_format record_format; - uint16_t port; + struct port_range port_range; uint16_t max_size; uint32_t bit_rate; uint16_t max_fps; @@ -41,7 +42,10 @@ struct scrcpy_options { .window_title = NULL, \ .push_target = NULL, \ .record_format = RECORDER_FORMAT_AUTO, \ - .port = DEFAULT_LOCAL_PORT, \ + .port_range = { \ + .first = DEFAULT_LOCAL_PORT_RANGE_FIRST, \ + .last = DEFAULT_LOCAL_PORT_RANGE_LAST, \ + }, \ .max_size = DEFAULT_MAX_SIZE, \ .bit_rate = DEFAULT_BIT_RATE, \ .max_fps = 0, \ diff --git a/app/src/server.c b/app/src/server.c index 0579a4b034..8392bd528d 100644 --- a/app/src/server.c +++ b/app/src/server.c @@ -141,29 +141,91 @@ listen_on_port(uint16_t port) { } static bool -enable_tunnel(struct server *server) { - if (enable_tunnel_reverse(server->serial, server->local_port)) { +enable_tunnel_reverse_any_port(struct server *server, + struct port_range port_range) { + uint16_t port = port_range.first; + for (;;) { + if (!enable_tunnel_reverse(server->serial, port)) { + // the command itself failed, it will fail on any port + return false; + } + // At the application level, the device part is "the server" because it // serves video stream and control. However, at the network level, the // client listens and the server connects to the client. That way, the // client can listen before starting the server app, so there is no // need to try to connect until the server socket is listening on the // device. - server->server_socket = listen_on_port(server->local_port); - if (server->server_socket == INVALID_SOCKET) { - LOGE("Could not listen on port %" PRIu16, server->local_port); - disable_tunnel(server); - return false; + server->server_socket = listen_on_port(port); + if (server->server_socket != INVALID_SOCKET) { + // success + server->local_port = port; + return true; + } + + // failure, disable tunnel and try another port + if (!disable_tunnel_reverse(server->serial)) { + LOGW("Could not remove reverse tunnel on port %" PRIu16, port); + } + + // check before incrementing to avoid overflow on port 65535 + if (port < port_range.last) { + LOGW("Could not listen on port %" PRIu16", retrying on %" PRIu16, + port, port + 1); + port++; + continue; } + if (port_range.first == port_range.last) { + LOGE("Could not listen on port %" PRIu16, port_range.first); + } else { + LOGE("Could not listen on any port in range %" PRIu16 ":%" PRIu16, + port_range.first, port_range.last); + } + return false; + } +} + +static bool +enable_tunnel_forward_any_port(struct server *server, + struct port_range port_range) { + server->tunnel_forward = true; + uint16_t port = port_range.first; + for (;;) { + if (enable_tunnel_forward(server->serial, port)) { + // success + server->local_port = port; + return true; + } + + if (port < port_range.last) { + LOGW("Could not forward port %" PRIu16", retrying on %" PRIu16, + port, port + 1); + port++; + continue; + } + + if (port_range.first == port_range.last) { + LOGE("Could not forward port %" PRIu16, port_range.first); + } else { + LOGE("Could not forward any port in range %" PRIu16 ":%" PRIu16, + port_range.first, port_range.last); + } + return false; + } +} + +static bool +enable_tunnel_any_port(struct server *server, struct port_range port_range) { + if (enable_tunnel_reverse_any_port(server, port_range)) { return true; } // if "adb reverse" does not work (e.g. over "adb connect"), it fallbacks to // "adb forward", so the app socket is the client + LOGW("'adb reverse' failed, fallback to 'adb forward'"); - server->tunnel_forward = true; - return enable_tunnel_forward(server->serial, server->local_port); + return enable_tunnel_forward_any_port(server, port_range); } static process_t @@ -261,7 +323,7 @@ server_init(struct server *server) { bool server_start(struct server *server, const char *serial, const struct server_params *params) { - server->local_port = params->local_port; + server->port_range = params->port_range; if (serial) { server->serial = SDL_strdup(serial); @@ -275,7 +337,7 @@ server_start(struct server *server, const char *serial, return false; } - if (!enable_tunnel(server)) { + if (!enable_tunnel_any_port(server, params->port_range)) { SDL_free(server->serial); return false; } diff --git a/app/src/server.h b/app/src/server.h index 0cb1ab3a6d..8e3be81fb1 100644 --- a/app/src/server.h +++ b/app/src/server.h @@ -6,6 +6,7 @@ #include "config.h" #include "command.h" +#include "common.h" #include "util/net.h" struct server { @@ -14,25 +15,30 @@ struct server { socket_t server_socket; // only used if !tunnel_forward socket_t video_socket; socket_t control_socket; - uint16_t local_port; + struct port_range port_range; + uint16_t local_port; // selected from port_range bool tunnel_enabled; bool tunnel_forward; // use "adb forward" instead of "adb reverse" }; -#define SERVER_INITIALIZER { \ - .serial = NULL, \ - .process = PROCESS_NONE, \ - .server_socket = INVALID_SOCKET, \ - .video_socket = INVALID_SOCKET, \ +#define SERVER_INITIALIZER { \ + .serial = NULL, \ + .process = PROCESS_NONE, \ + .server_socket = INVALID_SOCKET, \ + .video_socket = INVALID_SOCKET, \ .control_socket = INVALID_SOCKET, \ - .local_port = 0, \ - .tunnel_enabled = false, \ - .tunnel_forward = false, \ + .port_range = { \ + .first = 0, \ + .last = 0, \ + }, \ + .local_port = 0, \ + .tunnel_enabled = false, \ + .tunnel_forward = false, \ } struct server_params { const char *crop; - uint16_t local_port; + struct port_range port_range; uint16_t max_size; uint32_t bit_rate; uint16_t max_fps; diff --git a/app/tests/test_cli.c b/app/tests/test_cli.c index 539c3c94cf..dfe95dba04 100644 --- a/app/tests/test_cli.c +++ b/app/tests/test_cli.c @@ -50,7 +50,7 @@ static void test_options(void) { "--max-size", "1024", // "--no-control" is not compatible with "--turn-screen-off" // "--no-display" is not compatible with "--fulscreen" - "--port", "1234", + "--port", "1234:1236", "--push-target", "/sdcard/Movies", "--record", "file", "--record-format", "mkv", @@ -78,7 +78,8 @@ static void test_options(void) { assert(opts->fullscreen); assert(opts->max_fps == 30); assert(opts->max_size == 1024); - assert(opts->port == 1234); + assert(opts->port_range.first == 1234); + assert(opts->port_range.last == 1236); assert(!strcmp(opts->push_target, "/sdcard/Movies")); assert(!strcmp(opts->record_filename, "file")); assert(opts->record_format == RECORDER_FORMAT_MKV); From 96bd2c974d0fb9765dc4d2978688bb0bbd65cc9f Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 19 Jan 2020 15:49:25 +0100 Subject: [PATCH 11/11] Do not report workarounds errors Some workarounds are needed on some devices. But applying them may cause exceptions on other devices, where they are not necessary anyway. Do not report these errors in release builds. Closes #994 --- server/src/main/java/com/genymobile/scrcpy/Workarounds.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/Workarounds.java b/server/src/main/java/com/genymobile/scrcpy/Workarounds.java index b1b8190326..fe5d80356a 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Workarounds.java +++ b/server/src/main/java/com/genymobile/scrcpy/Workarounds.java @@ -73,7 +73,7 @@ public static void fillAppInfo() { mInitialApplicationField.set(activityThread, app); } catch (Throwable throwable) { // this is a workaround, so failing is not an error - Ln.w("Could not fill app info: " + throwable.getMessage()); + Ln.d("Could not fill app info: " + throwable.getMessage()); } } }