From 7d5845196e6a7e919d0ec3b5fc53fc7dab8d3222 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sat, 14 Dec 2019 18:13:56 +0100 Subject: [PATCH 001/450] 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 ff167aeb..31e09ae3 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 002/450] 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 63afccb4..abaa223d 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 fbcf2355..af5d4b2f 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 55edaf8f..3846847e 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 003/450] 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 31e09ae3..54813ab8 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 004/450] 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 3bcb9bc1..62006ca1 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 d67a660f..00000000 --- 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 aebce7fc..00000000 --- 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 bf4389dd..efce6fa9 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 005/450] 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 4d6d29fd..10e61bca 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 23b168ca..2600734b 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 d9e1013a..75025563 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 c9a37f84..1c71eabd 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 @@ public class ScreenEncoder implements Device.RotationListener { // 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 006/450] 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 3846847e..105234b0 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 007/450] 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 54813ab8..019b460a 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 008/450] 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 019b460a..0579a4b0 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 009/450] 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 4d175407..ce0498a5 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 8d9b990c..c7f26cdb 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 200e0f63..a88bca0e 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 010/450] 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 62006ca1..171d6e35 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 2600734b..0c1bf5e4 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 75025563..135dd5df 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 e5cbe953..4cbf1d74 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 17be1ed4..f315ca20 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 75de8717..9303a30c 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 0579a4b0..8392bd52 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 0cb1ab3a..8e3be81f 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 539c3c94..dfe95dba 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 011/450] 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 b1b81903..fe5d8035 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 final class Workarounds { 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()); } } } From 4794ca8ae7515c7376b8e19aa8ec9c07546c965c Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Tue, 25 Feb 2020 12:18:49 +0100 Subject: [PATCH 012/450] Use linear filtering Anisotropic filtering makes no sense for scrcpy use case. This (semantically) reverts 9e328ef98b0fc730036aa389f33649a31737d0e3. --- app/src/scrcpy.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index f315ca20..a6c5c26e 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -60,9 +60,9 @@ sdl_init_and_configure(bool display) { return true; } - // Use the best available scale quality - if (!SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "2")) { - LOGW("Could not enable bilinear filtering"); + // Linear filtering + if (!SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "1")) { + LOGW("Could not enable linear filtering"); } #ifdef SCRCPY_SDL_HAS_HINT_MOUSE_FOCUS_CLICKTHROUGH From ef56cc6ff74862e85c0af4746fa26ef391f6f3a9 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Mon, 24 Feb 2020 21:08:32 +0100 Subject: [PATCH 013/450] Retrieve screen info once The method getScreenInfo() is synchronized, and the result may change between calls. Call it once and store the result in a local variable. --- .../src/main/java/com/genymobile/scrcpy/ScreenEncoder.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java b/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java index 1c71eabd..7edfacaf 100644 --- a/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java +++ b/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java @@ -63,8 +63,9 @@ public class ScreenEncoder implements Device.RotationListener { do { MediaCodec codec = createCodec(); IBinder display = createDisplay(); - Rect contentRect = device.getScreenInfo().getContentRect(); - Rect videoRect = device.getScreenInfo().getVideoSize().toRect(); + ScreenInfo screenInfo = device.getScreenInfo(); + Rect contentRect = screenInfo.getContentRect(); + Rect videoRect = screenInfo.getVideoSize().toRect(); setSize(format, videoRect.width(), videoRect.height()); configure(codec, format); Surface surface = codec.createInputSurface(); From 1982bc439b67829ec815b5e1bd13f1bf4e660f4b Mon Sep 17 00:00:00 2001 From: George Stamoulis Date: Sun, 16 Feb 2020 13:30:36 +0200 Subject: [PATCH 014/450] Add option to lock video orientation PR #1151 Signed-off-by: Romain Vimont --- README.md | 15 +++ app/meson.build | 6 + app/scrcpy.1 | 6 + app/src/cli.c | 110 +++++++++++------- app/src/scrcpy.c | 1 + app/src/scrcpy.h | 2 + app/src/server.c | 3 + app/src/server.h | 1 + app/tests/test_cli.c | 2 + .../java/com/genymobile/scrcpy/Device.java | 52 +++++++-- .../java/com/genymobile/scrcpy/Options.java | 9 ++ .../java/com/genymobile/scrcpy/Position.java | 13 +++ .../com/genymobile/scrcpy/ScreenEncoder.java | 28 +++-- .../com/genymobile/scrcpy/ScreenInfo.java | 28 +++-- .../java/com/genymobile/scrcpy/Server.java | 18 +-- 15 files changed, 221 insertions(+), 73 deletions(-) diff --git a/README.md b/README.md index 07fe6cf3..998caae8 100644 --- a/README.md +++ b/README.md @@ -179,6 +179,21 @@ scrcpy --crop 1224:1440:0:0 # 1224x1440 at offset (0,0) 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 It is possible to record the screen while mirroring: diff --git a/app/meson.build b/app/meson.build index 171d6e35..3df2f35c 100644 --- a/app/meson.build +++ b/app/meson.build @@ -105,6 +105,12 @@ conf.set('DEFAULT_LOCAL_PORT_RANGE_LAST', '27199') # overridden by option --max-size 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 # overridden by option --bit-rate conf.set('DEFAULT_BIT_RATE', '8000000') # 8Mbps diff --git a/app/scrcpy.1 b/app/scrcpy.1 index a5d7404c..b3c57064 100644 --- a/app/scrcpy.1 +++ b/app/scrcpy.1 @@ -41,6 +41,12 @@ Start in fullscreen. .B \-h, \-\-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 .BI "\-\-max\-fps " value Limit the framerate of screen capture (officially supported since Android 10, but may work on earlier versions). diff --git a/app/src/cli.c b/app/src/cli.c index 135dd5df..4b093c49 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -41,6 +41,12 @@ scrcpy_print_usage(const char *arg0) { " -h, --help\n" " Print this help.\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" " Limit the frame rate of screen capture (officially supported\n" " since Android 10, but may work on earlier versions).\n" @@ -192,6 +198,7 @@ scrcpy_print_usage(const char *arg0) { "\n", arg0, DEFAULT_BIT_RATE, + DEFAULT_LOCK_VIDEO_ORIENTATION, DEFAULT_LOCK_VIDEO_ORIENTATION >= 0 ? "" : " (unlocked)", DEFAULT_MAX_SIZE, DEFAULT_MAX_SIZE ? "" : " (unlimited)", DEFAULT_LOCAL_PORT_RANGE_FIRST, DEFAULT_LOCAL_PORT_RANGE_LAST); } @@ -280,6 +287,19 @@ parse_max_fps(const char *s, uint16_t *max_fps) { 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 parse_window_position(const char *s, int16_t *position) { long value; @@ -364,51 +384,54 @@ guess_record_format(const char *filename) { return 0; } -#define OPT_RENDER_EXPIRED_FRAMES 1000 -#define OPT_WINDOW_TITLE 1001 -#define OPT_PUSH_TARGET 1002 -#define OPT_ALWAYS_ON_TOP 1003 -#define OPT_CROP 1004 -#define OPT_RECORD_FORMAT 1005 -#define OPT_PREFER_TEXT 1006 -#define OPT_WINDOW_X 1007 -#define OPT_WINDOW_Y 1008 -#define OPT_WINDOW_WIDTH 1009 -#define OPT_WINDOW_HEIGHT 1010 -#define OPT_WINDOW_BORDERLESS 1011 -#define OPT_MAX_FPS 1012 +#define OPT_RENDER_EXPIRED_FRAMES 1000 +#define OPT_WINDOW_TITLE 1001 +#define OPT_PUSH_TARGET 1002 +#define OPT_ALWAYS_ON_TOP 1003 +#define OPT_CROP 1004 +#define OPT_RECORD_FORMAT 1005 +#define OPT_PREFER_TEXT 1006 +#define OPT_WINDOW_X 1007 +#define OPT_WINDOW_Y 1008 +#define OPT_WINDOW_WIDTH 1009 +#define OPT_WINDOW_HEIGHT 1010 +#define OPT_WINDOW_BORDERLESS 1011 +#define OPT_MAX_FPS 1012 +#define OPT_LOCK_VIDEO_ORIENTATION 1013 bool scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) { static const struct option long_options[] = { - {"always-on-top", no_argument, NULL, OPT_ALWAYS_ON_TOP}, - {"bit-rate", required_argument, NULL, 'b'}, - {"crop", required_argument, NULL, OPT_CROP}, - {"fullscreen", no_argument, NULL, 'f'}, - {"help", no_argument, NULL, 'h'}, - {"max-fps", required_argument, NULL, OPT_MAX_FPS}, - {"max-size", required_argument, NULL, 'm'}, - {"no-control", no_argument, NULL, 'n'}, - {"no-display", no_argument, NULL, 'N'}, - {"port", required_argument, NULL, 'p'}, - {"push-target", required_argument, NULL, OPT_PUSH_TARGET}, - {"record", required_argument, NULL, 'r'}, - {"record-format", required_argument, NULL, OPT_RECORD_FORMAT}, - {"render-expired-frames", no_argument, NULL, - OPT_RENDER_EXPIRED_FRAMES}, - {"serial", required_argument, NULL, 's'}, - {"show-touches", no_argument, NULL, 't'}, - {"turn-screen-off", no_argument, NULL, 'S'}, - {"prefer-text", no_argument, NULL, OPT_PREFER_TEXT}, - {"version", no_argument, NULL, 'v'}, - {"window-title", required_argument, NULL, OPT_WINDOW_TITLE}, - {"window-x", required_argument, NULL, OPT_WINDOW_X}, - {"window-y", required_argument, NULL, OPT_WINDOW_Y}, - {"window-width", required_argument, NULL, OPT_WINDOW_WIDTH}, - {"window-height", required_argument, NULL, OPT_WINDOW_HEIGHT}, - {"window-borderless", no_argument, NULL, - OPT_WINDOW_BORDERLESS}, - {NULL, 0, NULL, 0 }, + {"always-on-top", no_argument, NULL, OPT_ALWAYS_ON_TOP}, + {"bit-rate", required_argument, NULL, 'b'}, + {"crop", required_argument, NULL, OPT_CROP}, + {"fullscreen", no_argument, NULL, 'f'}, + {"help", no_argument, NULL, 'h'}, + {"lock-video-orientation", required_argument, NULL, + OPT_LOCK_VIDEO_ORIENTATION}, + {"max-fps", required_argument, NULL, OPT_MAX_FPS}, + {"max-size", required_argument, NULL, 'm'}, + {"no-control", no_argument, NULL, 'n'}, + {"no-display", no_argument, NULL, 'N'}, + {"port", required_argument, NULL, 'p'}, + {"push-target", required_argument, NULL, OPT_PUSH_TARGET}, + {"record", required_argument, NULL, 'r'}, + {"record-format", required_argument, NULL, OPT_RECORD_FORMAT}, + {"render-expired-frames", no_argument, NULL, + OPT_RENDER_EXPIRED_FRAMES}, + {"serial", required_argument, NULL, 's'}, + {"show-touches", no_argument, NULL, 't'}, + {"turn-screen-off", no_argument, NULL, 'S'}, + {"prefer-text", no_argument, NULL, OPT_PREFER_TEXT}, + {"version", no_argument, NULL, 'v'}, + {"window-title", required_argument, NULL, OPT_WINDOW_TITLE}, + {"window-x", required_argument, NULL, OPT_WINDOW_X}, + {"window-y", required_argument, NULL, OPT_WINDOW_Y}, + {"window-width", required_argument, NULL, OPT_WINDOW_WIDTH}, + {"window-height", required_argument, NULL, OPT_WINDOW_HEIGHT}, + {"window-borderless", no_argument, NULL, + OPT_WINDOW_BORDERLESS}, + {NULL, 0, NULL, 0 }, }; struct scrcpy_options *opts = &args->opts; @@ -454,6 +477,11 @@ scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) { return false; } break; + case OPT_LOCK_VIDEO_ORIENTATION: + if (!parse_lock_video_orientation(optarg, &opts->lock_video_orientation)) { + return false; + } + break; case 'n': opts->control = false; break; diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index a6c5c26e..4d9ad88b 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -284,6 +284,7 @@ scrcpy(const struct scrcpy_options *options) { .max_size = options->max_size, .bit_rate = options->bit_rate, .max_fps = options->max_fps, + .lock_video_orientation = options->lock_video_orientation, .control = options->control, }; if (!server_start(&server, options->serial, ¶ms)) { diff --git a/app/src/scrcpy.h b/app/src/scrcpy.h index 9303a30c..e29298f2 100644 --- a/app/src/scrcpy.h +++ b/app/src/scrcpy.h @@ -20,6 +20,7 @@ struct scrcpy_options { uint16_t max_size; uint32_t bit_rate; uint16_t max_fps; + int8_t lock_video_orientation; int16_t window_x; int16_t window_y; uint16_t window_width; @@ -49,6 +50,7 @@ struct scrcpy_options { .max_size = DEFAULT_MAX_SIZE, \ .bit_rate = DEFAULT_BIT_RATE, \ .max_fps = 0, \ + .lock_video_orientation = DEFAULT_LOCK_VIDEO_ORIENTATION, \ .window_x = -1, \ .window_y = -1, \ .window_width = 0, \ diff --git a/app/src/server.c b/app/src/server.c index 8392bd52..4b2c1866 100644 --- a/app/src/server.c +++ b/app/src/server.c @@ -233,9 +233,11 @@ execute_server(struct server *server, const struct server_params *params) { char max_size_string[6]; char bit_rate_string[11]; char max_fps_string[6]; + char lock_video_orientation_string[3]; sprintf(max_size_string, "%"PRIu16, params->max_size); sprintf(bit_rate_string, "%"PRIu32, params->bit_rate); sprintf(max_fps_string, "%"PRIu16, params->max_fps); + sprintf(lock_video_orientation_string, "%"PRIi8, params->lock_video_orientation); const char *const cmd[] = { "shell", "CLASSPATH=" DEVICE_SERVER_PATH, @@ -251,6 +253,7 @@ execute_server(struct server *server, const struct server_params *params) { max_size_string, bit_rate_string, max_fps_string, + lock_video_orientation_string, server->tunnel_forward ? "true" : "false", params->crop ? params->crop : "-", "true", // always send frame meta (packet boundaries + timestamp) diff --git a/app/src/server.h b/app/src/server.h index 8e3be81f..d84a5cc8 100644 --- a/app/src/server.h +++ b/app/src/server.h @@ -42,6 +42,7 @@ struct server_params { uint16_t max_size; uint32_t bit_rate; uint16_t max_fps; + int8_t lock_video_orientation; bool control; }; diff --git a/app/tests/test_cli.c b/app/tests/test_cli.c index dfe95dba..c5d95633 100644 --- a/app/tests/test_cli.c +++ b/app/tests/test_cli.c @@ -48,6 +48,7 @@ static void test_options(void) { "--fullscreen", "--max-fps", "30", "--max-size", "1024", + "--lock-video-orientation", "2", // "--no-control" is not compatible with "--turn-screen-off" // "--no-display" is not compatible with "--fulscreen" "--port", "1234:1236", @@ -78,6 +79,7 @@ static void test_options(void) { assert(opts->fullscreen); assert(opts->max_fps == 30); assert(opts->max_size == 1024); + assert(opts->lock_video_orientation == 2); assert(opts->port_range.first == 1234); assert(opts->port_range.last == 1236); assert(!strcmp(opts->push_target, "/sdcard/Movies")); diff --git a/server/src/main/java/com/genymobile/scrcpy/Device.java b/server/src/main/java/com/genymobile/scrcpy/Device.java index 9448098a..7ccd0403 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Device.java +++ b/server/src/main/java/com/genymobile/scrcpy/Device.java @@ -22,10 +22,12 @@ public final class Device { private final ServiceManager serviceManager = new ServiceManager(); + private final int lockedVideoOrientation; private ScreenInfo screenInfo; private RotationListener rotationListener; public Device(Options options) { + lockedVideoOrientation = options.getLockedVideoOrientation(); screenInfo = computeScreenInfo(options.getCrop(), options.getMaxSize()); registerRotationWatcher(new IRotationWatcher.Stub() { @Override @@ -48,11 +50,11 @@ public final class Device { private ScreenInfo computeScreenInfo(Rect crop, int maxSize) { DisplayInfo displayInfo = serviceManager.getDisplayManager().getDisplayInfo(); - boolean rotated = (displayInfo.getRotation() & 1) != 0; + int rotation = displayInfo.getRotation(); Size deviceSize = displayInfo.getSize(); Rect contentRect = new Rect(0, 0, deviceSize.getWidth(), deviceSize.getHeight()); if (crop != null) { - if (rotated) { + if (rotation % 2 != 0) { // 180s preserve dimensions // the crop (provided by the user) is expressed in the natural orientation crop = flipRect(crop); } @@ -64,7 +66,7 @@ public final class Device { } Size videoSize = computeVideoSize(contentRect.width(), contentRect.height(), maxSize); - return new ScreenInfo(contentRect, videoSize, rotated); + return new ScreenInfo(contentRect, videoSize, rotation); } private static String formatCrop(Rect rect) { @@ -99,22 +101,56 @@ public final class Device { 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) { // it hides the field on purpose, to read it with a lock @SuppressWarnings("checkstyle:HiddenField") ScreenInfo screenInfo = getScreenInfo(); // read with synchronization 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)) { // 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 return null; } Rect contentRect = screenInfo.getContentRect(); - Point point = position.getPoint(); - int scaledX = contentRect.left + point.getX() * contentRect.width() / videoSize.getWidth(); - int scaledY = contentRect.top + point.getY() * contentRect.height() / videoSize.getHeight(); - return new Point(scaledX, scaledY); + Point point = devicePosition.getPoint(); + int convertedX = contentRect.left + point.getX() * contentRect.width() / videoSize.getWidth(); + int convertedY = contentRect.top + point.getY() * contentRect.height() / videoSize.getHeight(); + return new Point(convertedX, convertedY); } public static String getDeviceName() { diff --git a/server/src/main/java/com/genymobile/scrcpy/Options.java b/server/src/main/java/com/genymobile/scrcpy/Options.java index 5b993f30..d9a29452 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Options.java +++ b/server/src/main/java/com/genymobile/scrcpy/Options.java @@ -6,6 +6,7 @@ public class Options { private int maxSize; private int bitRate; private int maxFps; + private int lockedVideoOrientation; private boolean tunnelForward; private Rect crop; private boolean sendFrameMeta; // send PTS so that the client may record properly @@ -35,6 +36,14 @@ public class Options { this.maxFps = maxFps; } + public int getLockedVideoOrientation() { + return lockedVideoOrientation; + } + + public void setLockedVideoOrientation(int lockedVideoOrientation) { + this.lockedVideoOrientation = lockedVideoOrientation; + } + public boolean isTunnelForward() { return tunnelForward; } diff --git a/server/src/main/java/com/genymobile/scrcpy/Position.java b/server/src/main/java/com/genymobile/scrcpy/Position.java index b46d2f73..e9b6d8a2 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Position.java +++ b/server/src/main/java/com/genymobile/scrcpy/Position.java @@ -23,6 +23,19 @@ public class Position { 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 public boolean equals(Object o) { if (this == o) { diff --git a/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java b/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java index 7edfacaf..3e9772ee 100644 --- a/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java +++ b/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java @@ -28,19 +28,21 @@ public class ScreenEncoder implements Device.RotationListener { private int bitRate; private int maxFps; + private int lockedVideoOrientation; private int iFrameInterval; private boolean sendFrameMeta; private long ptsOrigin; - public ScreenEncoder(boolean sendFrameMeta, int bitRate, int maxFps, int iFrameInterval) { + public ScreenEncoder(boolean sendFrameMeta, int bitRate, int maxFps, int lockedVideoOrientation, int iFrameInterval) { this.sendFrameMeta = sendFrameMeta; this.bitRate = bitRate; this.maxFps = maxFps; + this.lockedVideoOrientation = lockedVideoOrientation; this.iFrameInterval = iFrameInterval; } - public ScreenEncoder(boolean sendFrameMeta, int bitRate, int maxFps) { - this(sendFrameMeta, bitRate, maxFps, DEFAULT_I_FRAME_INTERVAL); + public ScreenEncoder(boolean sendFrameMeta, int bitRate, int maxFps, int lockedVideoOrientation) { + this(sendFrameMeta, bitRate, maxFps, lockedVideoOrientation, DEFAULT_I_FRAME_INTERVAL); } @Override @@ -66,10 +68,11 @@ public class ScreenEncoder implements Device.RotationListener { ScreenInfo screenInfo = device.getScreenInfo(); Rect contentRect = screenInfo.getContentRect(); 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); Surface surface = codec.createInputSurface(); - setDisplaySurface(display, surface, contentRect, videoRect); + setDisplaySurface(display, surface, videoRotation, contentRect, videoRect); codec.start(); try { alive = encode(codec, fd); @@ -168,16 +171,21 @@ public class ScreenEncoder implements Device.RotationListener { codec.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE); } - private static void setSize(MediaFormat format, int width, int height) { - format.setInteger(MediaFormat.KEY_WIDTH, width); - format.setInteger(MediaFormat.KEY_HEIGHT, height); + private static void setSize(MediaFormat format, int orientation, int width, int height) { + if (orientation % 2 == 0) { + format.setInteger(MediaFormat.KEY_WIDTH, width); + 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, Rect deviceRect, Rect displayRect) { + private static void setDisplaySurface(IBinder display, Surface surface, int orientation, Rect deviceRect, Rect displayRect) { SurfaceControl.openTransaction(); try { SurfaceControl.setDisplaySurface(display, surface); - SurfaceControl.setDisplayProjection(display, 0, deviceRect, displayRect); + SurfaceControl.setDisplayProjection(display, orientation, deviceRect, displayRect); SurfaceControl.setDisplayLayerStack(display, 0); } finally { SurfaceControl.closeTransaction(); diff --git a/server/src/main/java/com/genymobile/scrcpy/ScreenInfo.java b/server/src/main/java/com/genymobile/scrcpy/ScreenInfo.java index f2fce1d6..0f01615e 100644 --- a/server/src/main/java/com/genymobile/scrcpy/ScreenInfo.java +++ b/server/src/main/java/com/genymobile/scrcpy/ScreenInfo.java @@ -5,12 +5,12 @@ import android.graphics.Rect; public final class ScreenInfo { private final Rect contentRect; // device size, possibly cropped private final Size videoSize; - private final boolean rotated; + private final int rotation; - public ScreenInfo(Rect contentRect, Size videoSize, boolean rotated) { + public ScreenInfo(Rect contentRect, Size videoSize, int rotation) { this.contentRect = contentRect; this.videoSize = videoSize; - this.rotated = rotated; + this.rotation = rotation; } public Rect getContentRect() { @@ -21,11 +21,25 @@ public final class ScreenInfo { return videoSize; } - public ScreenInfo withRotation(int rotation) { - boolean newRotated = (rotation & 1) != 0; - if (rotated == newRotated) { + public int getRotation() { + return rotation; + } + + public ScreenInfo withRotation(int newRotation) { + if (newRotation == rotation) { return this; } - return new ScreenInfo(Device.flipRect(contentRect), videoSize.rotate(), newRotated); + // true if changed between portrait and landscape + 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); } } diff --git a/server/src/main/java/com/genymobile/scrcpy/Server.java b/server/src/main/java/com/genymobile/scrcpy/Server.java index 56b738fb..2b0d32a2 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Server.java +++ b/server/src/main/java/com/genymobile/scrcpy/Server.java @@ -19,7 +19,8 @@ public final class Server { final Device device = new Device(options); boolean tunnelForward = options.isTunnelForward(); try (DesktopConnection connection = DesktopConnection.open(device, tunnelForward)) { - ScreenEncoder screenEncoder = new ScreenEncoder(options.getSendFrameMeta(), options.getBitRate(), options.getMaxFps()); + ScreenEncoder screenEncoder = new ScreenEncoder(options.getSendFrameMeta(), options.getBitRate(), options.getMaxFps(), + options.getLockedVideoOrientation()); if (options.getControl()) { Controller controller = new Controller(device, connection); @@ -79,8 +80,8 @@ public final class Server { "The server version (" + clientVersion + ") does not match the client " + "(" + BuildConfig.VERSION_NAME + ")"); } - if (args.length != 8) { - throw new IllegalArgumentException("Expecting 8 parameters"); + if (args.length != 9) { + throw new IllegalArgumentException("Expecting 9 parameters"); } Options options = new Options(); @@ -94,17 +95,20 @@ public final class Server { int maxFps = Integer.parseInt(args[3]); options.setMaxFps(maxFps); + int lockedVideoOrientation = Integer.parseInt(args[4]); + options.setLockedVideoOrientation(lockedVideoOrientation); + // use "adb forward" instead of "adb tunnel"? (so the server must listen) - boolean tunnelForward = Boolean.parseBoolean(args[4]); + boolean tunnelForward = Boolean.parseBoolean(args[5]); options.setTunnelForward(tunnelForward); - Rect crop = parseCrop(args[5]); + Rect crop = parseCrop(args[6]); options.setCrop(crop); - boolean sendFrameMeta = Boolean.parseBoolean(args[6]); + boolean sendFrameMeta = Boolean.parseBoolean(args[7]); options.setSendFrameMeta(sendFrameMeta); - boolean control = Boolean.parseBoolean(args[7]); + boolean control = Boolean.parseBoolean(args[8]); options.setControl(control); return options; From d3281f4b67c36164955593ec6b7fcf16e2f95436 Mon Sep 17 00:00:00 2001 From: yangfl Date: Thu, 16 Jan 2020 02:47:34 +0800 Subject: [PATCH 015/450] Show a friendly hint for adb installation Signed-off-by: Romain Vimont --- app/src/command.c | 27 +++++++++++++++++++++++++++ app/src/command.h | 5 +++++ app/src/sys/unix/command.c | 37 +++++++++++++++++++++++++++++++++++++ 3 files changed, 69 insertions(+) diff --git a/app/src/command.c b/app/src/command.c index abaa223d..81047b7a 100644 --- a/app/src/command.c +++ b/app/src/command.c @@ -55,6 +55,32 @@ argv_to_string(const char *const *argv, char *buf, size_t bufsize) { return idx; } +static void +show_adb_installation_msg() { +#ifndef __WINDOWS__ + static const struct { + const char *binary; + const char *command; + } pkg_managers[] = { + {"apt", "apt install adb"}, + {"apt-get", "apt-get install adb"}, + {"brew", "brew cask install android-platform-tools"}, + {"dnf", "dnf install android-tools"}, + {"emerge", "emerge dev-util/android-tools"}, + {"pacman", "pacman -S android-tools"}, + }; + for (size_t i = 0; i < ARRAY_LEN(pkg_managers); ++i) { + if (cmd_search(pkg_managers[i].binary)) { + LOGI("You may install 'adb' by \"%s\"", pkg_managers[i].command); + return; + } + } +#endif + + LOGI("You may download and install 'adb' from " + "https://developer.android.com/studio/releases/platform-tools"); +} + static void show_adb_err_msg(enum process_result err, const char *const argv[]) { char buf[512]; @@ -68,6 +94,7 @@ show_adb_err_msg(enum process_result err, const char *const argv[]) { LOGE("Command not found: %s", buf); LOGE("(make 'adb' accessible from your PATH or define its full" "path in the ADB environment variable)"); + show_adb_installation_msg(); break; case PROCESS_SUCCESS: // do nothing diff --git a/app/src/command.h b/app/src/command.h index 9fc81c1c..28f9fbcf 100644 --- a/app/src/command.h +++ b/app/src/command.h @@ -43,6 +43,11 @@ enum process_result { PROCESS_ERROR_MISSING_BINARY, }; +#ifndef __WINDOWS__ +bool +cmd_search(const char *file); +#endif + enum process_result cmd_execute(const char *const argv[], process_t *process); diff --git a/app/src/sys/unix/command.c b/app/src/sys/unix/command.c index af5d4b2f..a60e21bc 100644 --- a/app/src/sys/unix/command.c +++ b/app/src/sys/unix/command.c @@ -14,6 +14,7 @@ #include #include #include +#include #include #include #include @@ -21,6 +22,42 @@ #include "util/log.h" +bool +cmd_search(const char *file) { + char *path = getenv("PATH"); + if (!path) + return false; + path = strdup(path); + if (!path) + return false; + + bool ret = false; + size_t file_len = strlen(file); + char *saveptr; + for (char *dir = strtok_r(path, ":", &saveptr); dir; + dir = strtok_r(NULL, ":", &saveptr)) { + size_t dir_len = strlen(dir); + char *fullpath = malloc(dir_len + file_len + 2); + if (!fullpath) + continue; + memcpy(fullpath, dir, dir_len); + fullpath[dir_len] = '/'; + memcpy(fullpath + dir_len + 1, file, file_len + 1); + + struct stat sb; + bool fullpath_executable = stat(fullpath, &sb) == 0 && + sb.st_mode & S_IXUSR; + free(fullpath); + if (fullpath_executable) { + ret = true; + break; + } + } + + free(path); + return ret; +} + enum process_result cmd_execute(const char *const argv[], pid_t *pid) { int fd[2]; From da18c9cdab1c40c605bb63105e92b40284704867 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Wed, 4 Mar 2020 21:56:48 +0100 Subject: [PATCH 016/450] Remove useles import --- server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java | 1 - 1 file changed, 1 deletion(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java b/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java index 3e9772ee..0ef61e9a 100644 --- a/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java +++ b/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java @@ -6,7 +6,6 @@ import android.graphics.Rect; import android.media.MediaCodec; import android.media.MediaCodecInfo; import android.media.MediaFormat; -import android.os.Build; import android.os.IBinder; import android.view.Surface; From 63286424bb5a21cbdc2e94a4bf493abe07ea1e98 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Wed, 4 Mar 2020 21:44:20 +0100 Subject: [PATCH 017/450] Compute all screen info from ScreenInfo Screen information was partially initialized from Device. Move this initialization to ScreenInfo. --- .../java/com/genymobile/scrcpy/Device.java | 60 +--------------- .../com/genymobile/scrcpy/ScreenInfo.java | 69 ++++++++++++++++++- 2 files changed, 70 insertions(+), 59 deletions(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/Device.java b/server/src/main/java/com/genymobile/scrcpy/Device.java index 7ccd0403..c60761cf 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Device.java +++ b/server/src/main/java/com/genymobile/scrcpy/Device.java @@ -28,7 +28,8 @@ public final class Device { public Device(Options options) { lockedVideoOrientation = options.getLockedVideoOrientation(); - screenInfo = computeScreenInfo(options.getCrop(), options.getMaxSize()); + DisplayInfo displayInfo = serviceManager.getDisplayManager().getDisplayInfo(); + screenInfo = ScreenInfo.computeScreenInfo(displayInfo, options.getCrop(), options.getMaxSize()); registerRotationWatcher(new IRotationWatcher.Stub() { @Override public void onRotationChanged(int rotation) throws RemoteException { @@ -48,59 +49,6 @@ public final class Device { return screenInfo; } - private ScreenInfo computeScreenInfo(Rect crop, int maxSize) { - DisplayInfo displayInfo = serviceManager.getDisplayManager().getDisplayInfo(); - int rotation = displayInfo.getRotation(); - Size deviceSize = displayInfo.getSize(); - Rect contentRect = new Rect(0, 0, deviceSize.getWidth(), deviceSize.getHeight()); - if (crop != null) { - if (rotation % 2 != 0) { // 180s preserve dimensions - // the crop (provided by the user) is expressed in the natural orientation - crop = flipRect(crop); - } - if (!contentRect.intersect(crop)) { - // intersect() changes contentRect so that it is intersected with crop - Ln.w("Crop rectangle (" + formatCrop(crop) + ") does not intersect device screen (" + formatCrop(deviceSize.toRect()) + ")"); - contentRect = new Rect(); // empty - } - } - - Size videoSize = computeVideoSize(contentRect.width(), contentRect.height(), maxSize); - return new ScreenInfo(contentRect, videoSize, rotation); - } - - private static String formatCrop(Rect rect) { - return rect.width() + ":" + rect.height() + ":" + rect.left + ":" + rect.top; - } - - @SuppressWarnings("checkstyle:MagicNumber") - private static Size computeVideoSize(int w, int h, int maxSize) { - // Compute the video size and the padding of the content inside this video. - // Principle: - // - scale down the great side of the screen to maxSize (if necessary); - // - scale down the other side so that the aspect ratio is preserved; - // - round this value to the nearest multiple of 8 (H.264 only accepts multiples of 8) - w &= ~7; // in case it's not a multiple of 8 - h &= ~7; - if (maxSize > 0) { - if (BuildConfig.DEBUG && maxSize % 8 != 0) { - throw new AssertionError("Max size must be a multiple of 8"); - } - boolean portrait = h > w; - int major = portrait ? h : w; - int minor = portrait ? w : h; - if (major > maxSize) { - int minorExact = minor * maxSize / major; - // +4 to round the value to the nearest multiple of 8 - minor = (minorExact + 4) & ~7; - major = maxSize; - } - w = portrait ? minor : major; - h = portrait ? major : minor; - } - return new Size(w, h); - } - /** * Return the rotation to apply to the device rotation to get the requested locked video orientation * @@ -227,8 +175,4 @@ public final class Device { wm.thawRotation(); } } - - static Rect flipRect(Rect crop) { - return new Rect(crop.top, crop.left, crop.bottom, crop.right); - } } diff --git a/server/src/main/java/com/genymobile/scrcpy/ScreenInfo.java b/server/src/main/java/com/genymobile/scrcpy/ScreenInfo.java index 0f01615e..6dd8460c 100644 --- a/server/src/main/java/com/genymobile/scrcpy/ScreenInfo.java +++ b/server/src/main/java/com/genymobile/scrcpy/ScreenInfo.java @@ -3,8 +3,19 @@ package com.genymobile.scrcpy; import android.graphics.Rect; public final class ScreenInfo { + /** + * Device (physical) size, possibly cropped + */ private final Rect contentRect; // device size, possibly cropped + + /** + * Video size, possibly smaller than the device size, already taking the device rotation and crop into account + */ private final Size videoSize; + + /** + * Device rotation, related to the natural device orientation (0, 1, 2 or 3) + */ private final int rotation; public ScreenInfo(Rect contentRect, Size videoSize, int rotation) { @@ -34,7 +45,7 @@ public final class ScreenInfo { Rect newContentRect; Size newVideoSize; if (orientationChanged) { - newContentRect = Device.flipRect(contentRect); + newContentRect = flipRect(contentRect); newVideoSize = videoSize.rotate(); } else { newContentRect = contentRect; @@ -42,4 +53,60 @@ public final class ScreenInfo { } return new ScreenInfo(newContentRect, newVideoSize, newRotation); } + + public static ScreenInfo computeScreenInfo(DisplayInfo displayInfo, Rect crop, int maxSize) { + int rotation = displayInfo.getRotation(); + Size deviceSize = displayInfo.getSize(); + Rect contentRect = new Rect(0, 0, deviceSize.getWidth(), deviceSize.getHeight()); + if (crop != null) { + if (rotation % 2 != 0) { // 180s preserve dimensions + // the crop (provided by the user) is expressed in the natural orientation + crop = flipRect(crop); + } + if (!contentRect.intersect(crop)) { + // intersect() changes contentRect so that it is intersected with crop + Ln.w("Crop rectangle (" + formatCrop(crop) + ") does not intersect device screen (" + formatCrop(deviceSize.toRect()) + ")"); + contentRect = new Rect(); // empty + } + } + + Size videoSize = computeVideoSize(contentRect.width(), contentRect.height(), maxSize); + return new ScreenInfo(contentRect, videoSize, rotation); + } + + private static String formatCrop(Rect rect) { + return rect.width() + ":" + rect.height() + ":" + rect.left + ":" + rect.top; + } + + @SuppressWarnings("checkstyle:MagicNumber") + private static Size computeVideoSize(int w, int h, int maxSize) { + // Compute the video size and the padding of the content inside this video. + // Principle: + // - scale down the great side of the screen to maxSize (if necessary); + // - scale down the other side so that the aspect ratio is preserved; + // - round this value to the nearest multiple of 8 (H.264 only accepts multiples of 8) + w &= ~7; // in case it's not a multiple of 8 + h &= ~7; + if (maxSize > 0) { + if (BuildConfig.DEBUG && maxSize % 8 != 0) { + throw new AssertionError("Max size must be a multiple of 8"); + } + boolean portrait = h > w; + int major = portrait ? h : w; + int minor = portrait ? w : h; + if (major > maxSize) { + int minorExact = minor * maxSize / major; + // +4 to round the value to the nearest multiple of 8 + minor = (minorExact + 4) & ~7; + major = maxSize; + } + w = portrait ? minor : major; + h = portrait ? major : minor; + } + return new Size(w, h); + } + + private static Rect flipRect(Rect crop) { + return new Rect(crop.top, crop.left, crop.bottom, crop.right); + } } From c5f5d1e45669d676f74eaebb532f55aae99c65f4 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Wed, 4 Mar 2020 22:04:56 +0100 Subject: [PATCH 018/450] Rename "rotation" to "device rotation" This paves the way to reduce confusion in ScreenInfo when it will handle locked video orientation. --- .../java/com/genymobile/scrcpy/Device.java | 4 ++-- .../com/genymobile/scrcpy/ScreenEncoder.java | 2 +- .../java/com/genymobile/scrcpy/ScreenInfo.java | 18 +++++++++--------- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/Device.java b/server/src/main/java/com/genymobile/scrcpy/Device.java index c60761cf..1eebc679 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Device.java +++ b/server/src/main/java/com/genymobile/scrcpy/Device.java @@ -34,7 +34,7 @@ public final class Device { @Override public void onRotationChanged(int rotation) throws RemoteException { synchronized (Device.this) { - screenInfo = screenInfo.withRotation(rotation); + screenInfo = screenInfo.withDeviceRotation(rotation); // notify if (rotationListener != null) { @@ -83,7 +83,7 @@ public final class Device { ScreenInfo screenInfo = getScreenInfo(); // read with synchronization Size videoSize = screenInfo.getVideoSize(); - int deviceRotation = screenInfo.getRotation(); + int deviceRotation = screenInfo.getDeviceRotation(); int reverseVideoRotation = getReverseVideoRotation(deviceRotation); // reverse the video rotation to apply the events Position devicePosition = position.rotate(reverseVideoRotation); diff --git a/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java b/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java index 0ef61e9a..61bccc93 100644 --- a/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java +++ b/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java @@ -67,7 +67,7 @@ public class ScreenEncoder implements Device.RotationListener { ScreenInfo screenInfo = device.getScreenInfo(); Rect contentRect = screenInfo.getContentRect(); Rect videoRect = screenInfo.getVideoSize().toRect(); - int videoRotation = device.getVideoRotation(screenInfo.getRotation()); + int videoRotation = device.getVideoRotation(screenInfo.getDeviceRotation()); setSize(format, videoRotation, videoRect.width(), videoRect.height()); configure(codec, format); Surface surface = codec.createInputSurface(); diff --git a/server/src/main/java/com/genymobile/scrcpy/ScreenInfo.java b/server/src/main/java/com/genymobile/scrcpy/ScreenInfo.java index 6dd8460c..3e7cadc3 100644 --- a/server/src/main/java/com/genymobile/scrcpy/ScreenInfo.java +++ b/server/src/main/java/com/genymobile/scrcpy/ScreenInfo.java @@ -16,12 +16,12 @@ public final class ScreenInfo { /** * Device rotation, related to the natural device orientation (0, 1, 2 or 3) */ - private final int rotation; + private final int deviceRotation; - public ScreenInfo(Rect contentRect, Size videoSize, int rotation) { + public ScreenInfo(Rect contentRect, Size videoSize, int deviceRotation) { this.contentRect = contentRect; this.videoSize = videoSize; - this.rotation = rotation; + this.deviceRotation = deviceRotation; } public Rect getContentRect() { @@ -32,16 +32,16 @@ public final class ScreenInfo { return videoSize; } - public int getRotation() { - return rotation; + public int getDeviceRotation() { + return deviceRotation; } - public ScreenInfo withRotation(int newRotation) { - if (newRotation == rotation) { + public ScreenInfo withDeviceRotation(int newDeviceRotation) { + if (newDeviceRotation == deviceRotation) { return this; } // true if changed between portrait and landscape - boolean orientationChanged = (rotation + newRotation) % 2 != 0; + boolean orientationChanged = (deviceRotation + newDeviceRotation) % 2 != 0; Rect newContentRect; Size newVideoSize; if (orientationChanged) { @@ -51,7 +51,7 @@ public final class ScreenInfo { newContentRect = contentRect; newVideoSize = videoSize; } - return new ScreenInfo(newContentRect, newVideoSize, newRotation); + return new ScreenInfo(newContentRect, newVideoSize, newDeviceRotation); } public static ScreenInfo computeScreenInfo(DisplayInfo displayInfo, Rect crop, int maxSize) { From ae2d094362fbdc4d402b0315d981015ffe5c4967 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 5 Mar 2020 22:01:35 +0100 Subject: [PATCH 019/450] Handle locked video orientation from ScreenInfo Centralize video size management in ScreenInfo. This allows to always send the correct initial video size to the client if the video orientation is locked. --- .../java/com/genymobile/scrcpy/Device.java | 45 ++--------- .../com/genymobile/scrcpy/ScreenEncoder.java | 20 +++-- .../com/genymobile/scrcpy/ScreenInfo.java | 74 ++++++++++++++++--- 3 files changed, 80 insertions(+), 59 deletions(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/Device.java b/server/src/main/java/com/genymobile/scrcpy/Device.java index 1eebc679..1b1fbf7d 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Device.java +++ b/server/src/main/java/com/genymobile/scrcpy/Device.java @@ -22,14 +22,12 @@ public final class Device { private final ServiceManager serviceManager = new ServiceManager(); - private final int lockedVideoOrientation; private ScreenInfo screenInfo; private RotationListener rotationListener; public Device(Options options) { - lockedVideoOrientation = options.getLockedVideoOrientation(); DisplayInfo displayInfo = serviceManager.getDisplayManager().getDisplayInfo(); - screenInfo = ScreenInfo.computeScreenInfo(displayInfo, options.getCrop(), options.getMaxSize()); + screenInfo = ScreenInfo.computeScreenInfo(displayInfo, options.getCrop(), options.getMaxSize(), options.getLockedVideoOrientation()); registerRotationWatcher(new IRotationWatcher.Stub() { @Override public void onRotationChanged(int rotation) throws RemoteException { @@ -49,55 +47,28 @@ public final class Device { return screenInfo; } - /** - * 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) { // it hides the field on purpose, to read it with a lock @SuppressWarnings("checkstyle:HiddenField") ScreenInfo screenInfo = getScreenInfo(); // read with synchronization - Size videoSize = screenInfo.getVideoSize(); - int deviceRotation = screenInfo.getDeviceRotation(); - int reverseVideoRotation = getReverseVideoRotation(deviceRotation); + // ignore the locked video orientation, the events will apply in coordinates considered in the physical device orientation + Size unlockedVideoSize = screenInfo.getUnlockedVideoSize(); + + int reverseVideoRotation = screenInfo.getReverseVideoRotation(); // reverse the video rotation to apply the events Position devicePosition = position.rotate(reverseVideoRotation); Size clientVideoSize = devicePosition.getScreenSize(); - if (!videoSize.equals(clientVideoSize)) { + if (!unlockedVideoSize.equals(clientVideoSize)) { // 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 return null; } Rect contentRect = screenInfo.getContentRect(); Point point = devicePosition.getPoint(); - int convertedX = contentRect.left + point.getX() * contentRect.width() / videoSize.getWidth(); - int convertedY = contentRect.top + point.getY() * contentRect.height() / videoSize.getHeight(); + int convertedX = contentRect.left + point.getX() * contentRect.width() / unlockedVideoSize.getWidth(); + int convertedY = contentRect.top + point.getY() * contentRect.height() / unlockedVideoSize.getHeight(); return new Point(convertedX, convertedY); } diff --git a/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java b/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java index 61bccc93..e99084af 100644 --- a/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java +++ b/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java @@ -66,12 +66,15 @@ public class ScreenEncoder implements Device.RotationListener { IBinder display = createDisplay(); ScreenInfo screenInfo = device.getScreenInfo(); Rect contentRect = screenInfo.getContentRect(); + // include the locked video orientation Rect videoRect = screenInfo.getVideoSize().toRect(); - int videoRotation = device.getVideoRotation(screenInfo.getDeviceRotation()); - setSize(format, videoRotation, videoRect.width(), videoRect.height()); + // does not include the locked video orientation + Rect unlockedVideoRect = screenInfo.getUnlockedVideoSize().toRect(); + int videoRotation = screenInfo.getVideoRotation(); + setSize(format, videoRect.width(), videoRect.height()); configure(codec, format); Surface surface = codec.createInputSurface(); - setDisplaySurface(display, surface, videoRotation, contentRect, videoRect); + setDisplaySurface(display, surface, videoRotation, contentRect, unlockedVideoRect); codec.start(); try { alive = encode(codec, fd); @@ -170,14 +173,9 @@ public class ScreenEncoder implements Device.RotationListener { codec.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE); } - private static void setSize(MediaFormat format, int orientation, int width, int height) { - if (orientation % 2 == 0) { - format.setInteger(MediaFormat.KEY_WIDTH, width); - format.setInteger(MediaFormat.KEY_HEIGHT, height); - return; - } - format.setInteger(MediaFormat.KEY_WIDTH, height); - format.setInteger(MediaFormat.KEY_HEIGHT, width); + private static void setSize(MediaFormat format, int width, int height) { + format.setInteger(MediaFormat.KEY_WIDTH, width); + format.setInteger(MediaFormat.KEY_HEIGHT, height); } private static void setDisplaySurface(IBinder display, Surface surface, int orientation, Rect deviceRect, Rect displayRect) { diff --git a/server/src/main/java/com/genymobile/scrcpy/ScreenInfo.java b/server/src/main/java/com/genymobile/scrcpy/ScreenInfo.java index 3e7cadc3..0204de82 100644 --- a/server/src/main/java/com/genymobile/scrcpy/ScreenInfo.java +++ b/server/src/main/java/com/genymobile/scrcpy/ScreenInfo.java @@ -9,27 +9,53 @@ public final class ScreenInfo { private final Rect contentRect; // device size, possibly cropped /** - * Video size, possibly smaller than the device size, already taking the device rotation and crop into account + * Video size, possibly smaller than the device size, already taking the device rotation and crop into account. + *

+ * However, it does not include the locked video orientation. */ - private final Size videoSize; + private final Size unlockedVideoSize; /** * Device rotation, related to the natural device orientation (0, 1, 2 or 3) */ private final int deviceRotation; - public ScreenInfo(Rect contentRect, Size videoSize, int deviceRotation) { + /** + * The locked video orientation (-1: disabled, 0: normal, 1: 90° CCW, 2: 180°, 3: 90° CW) + */ + private final int lockedVideoOrientation; + + public ScreenInfo(Rect contentRect, Size unlockedVideoSize, int deviceRotation, int lockedVideoOrientation) { this.contentRect = contentRect; - this.videoSize = videoSize; + this.unlockedVideoSize = unlockedVideoSize; this.deviceRotation = deviceRotation; + this.lockedVideoOrientation = lockedVideoOrientation; } public Rect getContentRect() { return contentRect; } + /** + * Return the video size as if locked video orientation was not set. + * + * @return the unlocked video size + */ + public Size getUnlockedVideoSize() { + return unlockedVideoSize; + } + + /** + * Return the actual video size if locked video orientation is set. + * + * @return the actual video size + */ public Size getVideoSize() { - return videoSize; + if (getVideoRotation() % 2 == 0) { + return unlockedVideoSize; + } + + return unlockedVideoSize.rotate(); } public int getDeviceRotation() { @@ -43,18 +69,18 @@ public final class ScreenInfo { // true if changed between portrait and landscape boolean orientationChanged = (deviceRotation + newDeviceRotation) % 2 != 0; Rect newContentRect; - Size newVideoSize; + Size newUnlockedVideoSize; if (orientationChanged) { newContentRect = flipRect(contentRect); - newVideoSize = videoSize.rotate(); + newUnlockedVideoSize = unlockedVideoSize.rotate(); } else { newContentRect = contentRect; - newVideoSize = videoSize; + newUnlockedVideoSize = unlockedVideoSize; } - return new ScreenInfo(newContentRect, newVideoSize, newDeviceRotation); + return new ScreenInfo(newContentRect, newUnlockedVideoSize, newDeviceRotation, lockedVideoOrientation); } - public static ScreenInfo computeScreenInfo(DisplayInfo displayInfo, Rect crop, int maxSize) { + public static ScreenInfo computeScreenInfo(DisplayInfo displayInfo, Rect crop, int maxSize, int lockedVideoOrientation) { int rotation = displayInfo.getRotation(); Size deviceSize = displayInfo.getSize(); Rect contentRect = new Rect(0, 0, deviceSize.getWidth(), deviceSize.getHeight()); @@ -71,7 +97,7 @@ public final class ScreenInfo { } Size videoSize = computeVideoSize(contentRect.width(), contentRect.height(), maxSize); - return new ScreenInfo(contentRect, videoSize, rotation); + return new ScreenInfo(contentRect, videoSize, rotation, lockedVideoOrientation); } private static String formatCrop(Rect rect) { @@ -109,4 +135,30 @@ public final class ScreenInfo { private static Rect flipRect(Rect crop) { return new Rect(crop.top, crop.left, crop.bottom, crop.right); } + + /** + * Return the rotation to apply to the device rotation to get the requested locked video orientation + * + * @return the rotation offset + */ + public int getVideoRotation() { + 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 + * + * @return the (reverse) rotation offset + */ + public int getReverseVideoRotation() { + if (lockedVideoOrientation == -1) { + // no offset + return 0; + } + return (lockedVideoOrientation + 4 - deviceRotation) % 4; + } } From cd69eb4a4fecf8167208399def4ef536b59c9d22 Mon Sep 17 00:00:00 2001 From: Jaafar Biyadi Date: Sat, 29 Feb 2020 18:33:17 +0100 Subject: [PATCH 020/450] Handle NumPad events when NumLock is disabled PR #1188 Fixes #1048 Signed-off-by: Romain Vimont --- app/src/event_converter.c | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/app/src/event_converter.c b/app/src/event_converter.c index 80ead615..1054dcf9 100644 --- a/app/src/event_converter.c +++ b/app/src/event_converter.c @@ -94,6 +94,23 @@ convert_keycode(SDL_Keycode from, enum android_keycode *to, uint16_t mod, MAP(SDLK_UP, AKEYCODE_DPAD_UP); } + if (!(mod & (KMOD_NUM | KMOD_SHIFT))) { + // Handle Numpad events when Num Lock is disabled + // If SHIFT is pressed, a text event will be sent instead + switch(from) { + MAP(SDLK_KP_0, AKEYCODE_INSERT); + MAP(SDLK_KP_1, AKEYCODE_MOVE_END); + MAP(SDLK_KP_2, AKEYCODE_DPAD_DOWN); + MAP(SDLK_KP_3, AKEYCODE_PAGE_DOWN); + MAP(SDLK_KP_4, AKEYCODE_DPAD_LEFT); + MAP(SDLK_KP_6, AKEYCODE_DPAD_RIGHT); + MAP(SDLK_KP_7, AKEYCODE_MOVE_HOME); + MAP(SDLK_KP_8, AKEYCODE_DPAD_UP); + MAP(SDLK_KP_9, AKEYCODE_PAGE_UP); + MAP(SDLK_KP_PERIOD, AKEYCODE_FORWARD_DEL); + } + } + if (prefer_text) { // do not forward alpha and space key events return false; From 902b99174df8ffc1fe7548399c19e446aa5488b6 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 19 Mar 2020 19:15:43 +0100 Subject: [PATCH 021/450] Fix server debugger for Android >= 9 Add a compilation flag to select the debugger method to use: - old: Android < 9 - new: Android >= 9 See --- DEVELOP.md | 9 +++++++++ app/meson.build | 3 +++ app/src/server.c | 6 ++++++ meson_options.txt | 1 + 4 files changed, 19 insertions(+) diff --git a/DEVELOP.md b/DEVELOP.md index 0258782f..4d8acc59 100644 --- a/DEVELOP.md +++ b/DEVELOP.md @@ -282,6 +282,15 @@ meson x -Dserver_debugger=true meson configure x -Dserver_debugger=true ``` +If your device runs Android 8 or below, set the `server_debugger_method` to +`old` in addition: + +```bash +meson x -Dserver_debugger=true -Dserver_debugger_method=old +# or, if x is already configured +meson configure x -Dserver_debugger=true -Dserver_debugger_method=old +``` + Then recompile. When you start scrcpy, it will start a debugger on port 5005 on the device. diff --git a/app/meson.build b/app/meson.build index 3df2f35c..49c4683f 100644 --- a/app/meson.build +++ b/app/meson.build @@ -124,6 +124,9 @@ conf.set('WINDOWS_NOCONSOLE', get_option('windows_noconsole')) # run a server debugger and wait for a client to be attached conf.set('SERVER_DEBUGGER', get_option('server_debugger')) +# select the debugger method ('old' for Android < 9, 'new' for Android >= 9) +conf.set('SERVER_DEBUGGER_METHOD_NEW', get_option('server_debugger_method') == 'new') + configure_file(configuration: conf, output: 'config.h') src_dir = include_directories('src') diff --git a/app/src/server.c b/app/src/server.c index 4b2c1866..a8d598e4 100644 --- a/app/src/server.c +++ b/app/src/server.c @@ -244,7 +244,13 @@ execute_server(struct server *server, const struct server_params *params) { "app_process", #ifdef SERVER_DEBUGGER # define SERVER_DEBUGGER_PORT "5005" +# ifdef SERVER_DEBUGGER_METHOD_NEW + /* Android 9 and above */ + "-XjdwpProvider:internal -XjdwpOptions:transport=dt_socket,suspend=y,server=y,address=" +# else + /* Android 8 and below */ "-agentlib:jdwp=transport=dt_socket,suspend=y,server=y,address=" +# endif SERVER_DEBUGGER_PORT, #endif "/", // unused diff --git a/meson_options.txt b/meson_options.txt index 4cf4a8bf..c213e7dd 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -6,3 +6,4 @@ option('prebuilt_server', type: 'string', description: 'Path of the prebuilt ser option('portable', type: 'boolean', value: false, description: 'Use scrcpy-server from the same directory as the scrcpy executable') option('hidpi_support', type: 'boolean', value: true, description: 'Enable High DPI support') option('server_debugger', type: 'boolean', value: false, description: 'Run a server debugger and wait for a client to be attached') +option('server_debugger_method', type: 'combo', choices: ['old', 'new'], value: 'new', description: 'Select the debugger method (Android < 9: "old", Android >= 9: "new")') From 600df37753856122f004b79edff980a375ec6447 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 19 Mar 2020 19:16:08 +0100 Subject: [PATCH 022/450] Mention AutoAdb in README --- README.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/README.md b/README.md index 998caae8..b190406c 100644 --- a/README.md +++ b/README.md @@ -261,6 +261,16 @@ scrcpy -s 192.168.0.1:5555 # short version You can start several instances of _scrcpy_ for several devices. +#### Autostart on device connection + +You could use [AutoAdb]: + +```bash +autoadb scrcpy -s '{}' +``` + +[AutoAdb]: https://github.com/rom1v/usbaudio + #### SSH tunnel To connect to a remote device, it is possible to connect a local `adb` client to From a0af402d96849f1573b1361523ac96772952e480 Mon Sep 17 00:00:00 2001 From: Tzah Mazuz Date: Mon, 16 Mar 2020 11:23:11 +0200 Subject: [PATCH 023/450] Fix the printed versions (were opposite) PR #1224 Signed-off-by: Romain Vimont --- server/src/main/java/com/genymobile/scrcpy/Server.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/Server.java b/server/src/main/java/com/genymobile/scrcpy/Server.java index 2b0d32a2..ef4214c4 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Server.java +++ b/server/src/main/java/com/genymobile/scrcpy/Server.java @@ -77,7 +77,7 @@ public final class Server { String clientVersion = args[0]; if (!clientVersion.equals(BuildConfig.VERSION_NAME)) { throw new IllegalArgumentException( - "The server version (" + clientVersion + ") does not match the client " + "(" + BuildConfig.VERSION_NAME + ")"); + "The server version (" + BuildConfig.VERSION_NAME + ") does not match the client " + "(" + clientVersion + ")"); } if (args.length != 9) { From 566ba766af6f84e04d0c64533c88bbbc5c4ec35a Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 26 Mar 2020 22:43:53 +0100 Subject: [PATCH 024/450] Remove unused constant It has not been removed when mouse and touch events have been merged. --- .../main/java/com/genymobile/scrcpy/ControlMessageReader.java | 1 - 1 file changed, 1 deletion(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/ControlMessageReader.java b/server/src/main/java/com/genymobile/scrcpy/ControlMessageReader.java index 726b5659..70459913 100644 --- a/server/src/main/java/com/genymobile/scrcpy/ControlMessageReader.java +++ b/server/src/main/java/com/genymobile/scrcpy/ControlMessageReader.java @@ -9,7 +9,6 @@ import java.nio.charset.StandardCharsets; public class ControlMessageReader { private static final int INJECT_KEYCODE_PAYLOAD_LENGTH = 9; - private static final int INJECT_MOUSE_EVENT_PAYLOAD_LENGTH = 17; private static final int INJECT_TOUCH_EVENT_PAYLOAD_LENGTH = 21; private static final int INJECT_SCROLL_EVENT_PAYLOAD_LENGTH = 20; private static final int SET_SCREEN_POWER_MODE_PAYLOAD_LENGTH = 1; From 89d1602185066ff540b6525bf99eb894004aad04 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 26 Mar 2020 22:45:43 +0100 Subject: [PATCH 025/450] Fix expected message length for touch events The expected length for a touch event control message was incorrect. As a consequence, a BufferUnderflowException could occur. Fixes #1245 --- .../main/java/com/genymobile/scrcpy/ControlMessageReader.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/ControlMessageReader.java b/server/src/main/java/com/genymobile/scrcpy/ControlMessageReader.java index 70459913..065688a6 100644 --- a/server/src/main/java/com/genymobile/scrcpy/ControlMessageReader.java +++ b/server/src/main/java/com/genymobile/scrcpy/ControlMessageReader.java @@ -9,7 +9,7 @@ import java.nio.charset.StandardCharsets; public class ControlMessageReader { private static final int INJECT_KEYCODE_PAYLOAD_LENGTH = 9; - private static final int INJECT_TOUCH_EVENT_PAYLOAD_LENGTH = 21; + private static final int INJECT_TOUCH_EVENT_PAYLOAD_LENGTH = 27; private static final int INJECT_SCROLL_EVENT_PAYLOAD_LENGTH = 20; private static final int SET_SCREEN_POWER_MODE_PAYLOAD_LENGTH = 1; From 3504c0016b835dab4e74e1a05bfadedcd325c873 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 26 Mar 2020 22:48:01 +0100 Subject: [PATCH 026/450] Add tests for control message length This will avoid regressions for #1245. --- .../com/genymobile/scrcpy/ControlMessageReader.java | 8 ++++---- .../genymobile/scrcpy/ControlMessageReaderTest.java | 12 ++++++++++++ 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/ControlMessageReader.java b/server/src/main/java/com/genymobile/scrcpy/ControlMessageReader.java index 065688a6..e4347ddb 100644 --- a/server/src/main/java/com/genymobile/scrcpy/ControlMessageReader.java +++ b/server/src/main/java/com/genymobile/scrcpy/ControlMessageReader.java @@ -8,10 +8,10 @@ import java.nio.charset.StandardCharsets; public class ControlMessageReader { - private static final int INJECT_KEYCODE_PAYLOAD_LENGTH = 9; - private static final int INJECT_TOUCH_EVENT_PAYLOAD_LENGTH = 27; - private static final int INJECT_SCROLL_EVENT_PAYLOAD_LENGTH = 20; - private static final int SET_SCREEN_POWER_MODE_PAYLOAD_LENGTH = 1; + static final int INJECT_KEYCODE_PAYLOAD_LENGTH = 9; + static final int INJECT_TOUCH_EVENT_PAYLOAD_LENGTH = 27; + static final int INJECT_SCROLL_EVENT_PAYLOAD_LENGTH = 20; + static final int SET_SCREEN_POWER_MODE_PAYLOAD_LENGTH = 1; public static final int TEXT_MAX_LENGTH = 300; public static final int CLIPBOARD_TEXT_MAX_LENGTH = 4093; diff --git a/server/src/test/java/com/genymobile/scrcpy/ControlMessageReaderTest.java b/server/src/test/java/com/genymobile/scrcpy/ControlMessageReaderTest.java index 5e663bb9..2f95dfe1 100644 --- a/server/src/test/java/com/genymobile/scrcpy/ControlMessageReaderTest.java +++ b/server/src/test/java/com/genymobile/scrcpy/ControlMessageReaderTest.java @@ -28,6 +28,9 @@ public class ControlMessageReaderTest { dos.writeInt(KeyEvent.META_CTRL_ON); byte[] packet = bos.toByteArray(); + // The message type (1 byte) does not count + Assert.assertEquals(ControlMessageReader.INJECT_KEYCODE_PAYLOAD_LENGTH, packet.length - 1); + reader.readFrom(new ByteArrayInputStream(packet)); ControlMessage event = reader.next(); @@ -95,6 +98,9 @@ public class ControlMessageReaderTest { byte[] packet = bos.toByteArray(); + // The message type (1 byte) does not count + Assert.assertEquals(ControlMessageReader.INJECT_TOUCH_EVENT_PAYLOAD_LENGTH, packet.length - 1); + reader.readFrom(new ByteArrayInputStream(packet)); ControlMessage event = reader.next(); @@ -126,6 +132,9 @@ public class ControlMessageReaderTest { byte[] packet = bos.toByteArray(); + // The message type (1 byte) does not count + Assert.assertEquals(ControlMessageReader.INJECT_SCROLL_EVENT_PAYLOAD_LENGTH, packet.length - 1); + reader.readFrom(new ByteArrayInputStream(packet)); ControlMessage event = reader.next(); @@ -233,6 +242,9 @@ public class ControlMessageReaderTest { byte[] packet = bos.toByteArray(); + // The message type (1 byte) does not count + Assert.assertEquals(ControlMessageReader.SET_SCREEN_POWER_MODE_PAYLOAD_LENGTH, packet.length - 1); + reader.readFrom(new ByteArrayInputStream(packet)); ControlMessage event = reader.next(); From dc7c677728cdeb7471745a2aff9f63ebe8d14f53 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 26 Mar 2020 17:54:06 +0100 Subject: [PATCH 027/450] Accept negative window position It seems to work on some window managers. Fixes #1242 --- app/scrcpy.1 | 4 ++-- app/src/cli.c | 15 ++++++++++++--- app/src/scrcpy.h | 8 ++++---- app/src/screen.c | 6 ++++-- app/src/screen.h | 3 +++ 5 files changed, 25 insertions(+), 11 deletions(-) diff --git a/app/scrcpy.1 b/app/scrcpy.1 index b3c57064..55b7a631 100644 --- a/app/scrcpy.1 +++ b/app/scrcpy.1 @@ -131,13 +131,13 @@ Set a custom window title. .BI "\-\-window\-x " value Set the initial window horizontal position. -Default is -1 (automatic).\n +Default is "auto".\n .TP .BI "\-\-window\-y " value Set the initial window vertical position. -Default is -1 (automatic).\n +Default is "auto".\n .TP .BI "\-\-window\-width " value diff --git a/app/src/cli.c b/app/src/cli.c index 4b093c49..48876a29 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -1,5 +1,6 @@ #include "cli.h" +#include #include #include #include @@ -116,11 +117,11 @@ scrcpy_print_usage(const char *arg0) { "\n" " --window-x value\n" " Set the initial window horizontal position.\n" - " Default is -1 (automatic).\n" + " Default is \"auto\".\n" "\n" " --window-y value\n" " Set the initial window vertical position.\n" - " Default is -1 (automatic).\n" + " Default is \"auto\".\n" "\n" " --window-width value\n" " Set the initial window width.\n" @@ -302,8 +303,16 @@ parse_lock_video_orientation(const char *s, int8_t *lock_video_orientation) { static bool parse_window_position(const char *s, int16_t *position) { + // special value for "auto" + static_assert(WINDOW_POSITION_UNDEFINED == -0x8000); + + if (!strcmp(s, "auto")) { + *position = WINDOW_POSITION_UNDEFINED; + return true; + } + long value; - bool ok = parse_integer_arg(s, &value, false, -1, 0x7FFF, + bool ok = parse_integer_arg(s, &value, false, -0x7FFF, 0x7FFF, "window position"); if (!ok) { return false; diff --git a/app/src/scrcpy.h b/app/src/scrcpy.h index e29298f2..014f2cfe 100644 --- a/app/src/scrcpy.h +++ b/app/src/scrcpy.h @@ -21,8 +21,8 @@ struct scrcpy_options { uint32_t bit_rate; uint16_t max_fps; int8_t lock_video_orientation; - int16_t window_x; - int16_t window_y; + int16_t window_x; // WINDOW_POSITION_UNDEFINED for "auto" + int16_t window_y; // WINDOW_POSITION_UNDEFINED for "auto" uint16_t window_width; uint16_t window_height; bool show_touches; @@ -51,8 +51,8 @@ struct scrcpy_options { .bit_rate = DEFAULT_BIT_RATE, \ .max_fps = 0, \ .lock_video_orientation = DEFAULT_LOCK_VIDEO_ORIENTATION, \ - .window_x = -1, \ - .window_y = -1, \ + .window_x = WINDOW_POSITION_UNDEFINED, \ + .window_y = WINDOW_POSITION_UNDEFINED, \ .window_width = 0, \ .window_height = 0, \ .show_touches = false, \ diff --git a/app/src/screen.c b/app/src/screen.c index beb10754..03e2c3a1 100644 --- a/app/src/screen.c +++ b/app/src/screen.c @@ -186,8 +186,10 @@ screen_init_rendering(struct screen *screen, const char *window_title, window_flags |= SDL_WINDOW_BORDERLESS; } - int x = window_x != -1 ? window_x : (int) SDL_WINDOWPOS_UNDEFINED; - int y = window_y != -1 ? window_y : (int) SDL_WINDOWPOS_UNDEFINED; + int x = window_x != WINDOW_POSITION_UNDEFINED + ? window_x : (int) SDL_WINDOWPOS_UNDEFINED; + int y = window_y != WINDOW_POSITION_UNDEFINED + ? window_y : (int) SDL_WINDOWPOS_UNDEFINED; screen->window = SDL_CreateWindow(window_title, x, y, window_size.width, window_size.height, window_flags); diff --git a/app/src/screen.h b/app/src/screen.h index 2346ff15..c31f32c5 100644 --- a/app/src/screen.h +++ b/app/src/screen.h @@ -8,6 +8,8 @@ #include "config.h" #include "common.h" +#define WINDOW_POSITION_UNDEFINED (-0x8000) + struct video_buffer; struct screen { @@ -53,6 +55,7 @@ void screen_init(struct screen *screen); // initialize screen, create window, renderer and texture (window is hidden) +// window_x and window_y accept WINDOW_POSITION_UNDEFINED bool screen_init_rendering(struct screen *screen, const char *window_title, struct size frame_size, bool always_on_top, From e050cfdcd6ab5c0133d8b98baaa38c45a5b604aa Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 27 Mar 2020 14:01:56 +0100 Subject: [PATCH 028/450] Fix static_assert() parameters In C11, static_assert() expects a message. --- app/src/cli.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/cli.c b/app/src/cli.c index 48876a29..d2cd93b7 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -304,7 +304,7 @@ parse_lock_video_orientation(const char *s, int8_t *lock_video_orientation) { static bool parse_window_position(const char *s, int16_t *position) { // special value for "auto" - static_assert(WINDOW_POSITION_UNDEFINED == -0x8000); + static_assert(WINDOW_POSITION_UNDEFINED == -0x8000, "unexpected value"); if (!strcmp(s, "auto")) { *position = WINDOW_POSITION_UNDEFINED; From 4adf5fde6dfb91d7af21103a2620f28707bc8334 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sat, 28 Mar 2020 15:52:02 +0100 Subject: [PATCH 029/450] Log device details on server start --- server/src/main/java/com/genymobile/scrcpy/Server.java | 1 + 1 file changed, 1 insertion(+) diff --git a/server/src/main/java/com/genymobile/scrcpy/Server.java b/server/src/main/java/com/genymobile/scrcpy/Server.java index ef4214c4..db093053 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Server.java +++ b/server/src/main/java/com/genymobile/scrcpy/Server.java @@ -16,6 +16,7 @@ public final class Server { } private static void scrcpy(Options options) throws IOException { + Ln.i("Device: " + Build.MANUFACTURER + " " + Build.MODEL + " (Android " + Build.VERSION.RELEASE + ")"); final Device device = new Device(options); boolean tunnelForward = options.isTunnelForward(); try (DesktopConnection connection = DesktopConnection.open(device, tunnelForward)) { From 5031b2c8ff4285a43ca445f53adaaa7fe635d06f Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sat, 28 Mar 2020 22:08:16 +0100 Subject: [PATCH 030/450] Remove MagicNumber checkstyle There are a lot of "magic numbers" that we really don't want to extract as a constant. Until now, many @SuppressWarnings annotations were added, but it makes no sense to check for magic number if we silent the warnings everywhere. --- config/checkstyle/checkstyle.xml | 5 ----- .../java/com/genymobile/scrcpy/ControlMessageReader.java | 3 --- server/src/main/java/com/genymobile/scrcpy/Controller.java | 1 - .../main/java/com/genymobile/scrcpy/DesktopConnection.java | 1 - .../main/java/com/genymobile/scrcpy/DeviceMessageWriter.java | 1 - .../src/main/java/com/genymobile/scrcpy/ScreenEncoder.java | 1 - server/src/main/java/com/genymobile/scrcpy/ScreenInfo.java | 1 - server/src/main/java/com/genymobile/scrcpy/Server.java | 3 --- server/src/main/java/com/genymobile/scrcpy/StringUtils.java | 1 - .../java/com/genymobile/scrcpy/ControlMessageReaderTest.java | 2 -- .../src/test/java/com/genymobile/scrcpy/StringUtilsTest.java | 1 - 11 files changed, 20 deletions(-) diff --git a/config/checkstyle/checkstyle.xml b/config/checkstyle/checkstyle.xml index 798814d9..812d060b 100644 --- a/config/checkstyle/checkstyle.xml +++ b/config/checkstyle/checkstyle.xml @@ -129,11 +129,6 @@ page at http://checkstyle.sourceforge.net/config.html --> - - - - - diff --git a/server/src/main/java/com/genymobile/scrcpy/ControlMessageReader.java b/server/src/main/java/com/genymobile/scrcpy/ControlMessageReader.java index e4347ddb..29172c08 100644 --- a/server/src/main/java/com/genymobile/scrcpy/ControlMessageReader.java +++ b/server/src/main/java/com/genymobile/scrcpy/ControlMessageReader.java @@ -121,7 +121,6 @@ public class ControlMessageReader { return ControlMessage.createInjectText(text); } - @SuppressWarnings("checkstyle:MagicNumber") private ControlMessage parseInjectTouchEvent() { if (buffer.remaining() < INJECT_TOUCH_EVENT_PAYLOAD_LENGTH) { return null; @@ -171,12 +170,10 @@ public class ControlMessageReader { return new Position(x, y, screenWidth, screenHeight); } - @SuppressWarnings("checkstyle:MagicNumber") private static int toUnsigned(short value) { return value & 0xffff; } - @SuppressWarnings("checkstyle:MagicNumber") private static int toUnsigned(byte value) { return value & 0xff; } diff --git a/server/src/main/java/com/genymobile/scrcpy/Controller.java b/server/src/main/java/com/genymobile/scrcpy/Controller.java index dc0fa67b..7595a9d7 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Controller.java +++ b/server/src/main/java/com/genymobile/scrcpy/Controller.java @@ -47,7 +47,6 @@ public class Controller { } } - @SuppressWarnings("checkstyle:MagicNumber") public void control() throws IOException { // on start, power on the device if (!device.isScreenOn()) { diff --git a/server/src/main/java/com/genymobile/scrcpy/DesktopConnection.java b/server/src/main/java/com/genymobile/scrcpy/DesktopConnection.java index a725d83d..0ec43040 100644 --- a/server/src/main/java/com/genymobile/scrcpy/DesktopConnection.java +++ b/server/src/main/java/com/genymobile/scrcpy/DesktopConnection.java @@ -84,7 +84,6 @@ public final class DesktopConnection implements Closeable { controlSocket.close(); } - @SuppressWarnings("checkstyle:MagicNumber") private void send(String deviceName, int width, int height) throws IOException { byte[] buffer = new byte[DEVICE_NAME_FIELD_LENGTH + 4]; diff --git a/server/src/main/java/com/genymobile/scrcpy/DeviceMessageWriter.java b/server/src/main/java/com/genymobile/scrcpy/DeviceMessageWriter.java index e2a3a1a2..6c7f3634 100644 --- a/server/src/main/java/com/genymobile/scrcpy/DeviceMessageWriter.java +++ b/server/src/main/java/com/genymobile/scrcpy/DeviceMessageWriter.java @@ -13,7 +13,6 @@ public class DeviceMessageWriter { private final byte[] rawBuffer = new byte[MAX_EVENT_SIZE]; private final ByteBuffer buffer = ByteBuffer.wrap(rawBuffer); - @SuppressWarnings("checkstyle:MagicNumber") public void writeTo(DeviceMessage msg, OutputStream output) throws IOException { buffer.clear(); buffer.put((byte) DeviceMessage.TYPE_CLIPBOARD); diff --git a/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java b/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java index e99084af..d75833f3 100644 --- a/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java +++ b/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java @@ -145,7 +145,6 @@ public class ScreenEncoder implements Device.RotationListener { return MediaCodec.createEncoderByType("video/avc"); } - @SuppressWarnings("checkstyle:MagicNumber") private static MediaFormat createFormat(int bitRate, int maxFps, int iFrameInterval) { MediaFormat format = new MediaFormat(); format.setString(MediaFormat.KEY_MIME, "video/avc"); diff --git a/server/src/main/java/com/genymobile/scrcpy/ScreenInfo.java b/server/src/main/java/com/genymobile/scrcpy/ScreenInfo.java index 0204de82..10acfb50 100644 --- a/server/src/main/java/com/genymobile/scrcpy/ScreenInfo.java +++ b/server/src/main/java/com/genymobile/scrcpy/ScreenInfo.java @@ -104,7 +104,6 @@ public final class ScreenInfo { return rect.width() + ":" + rect.height() + ":" + rect.left + ":" + rect.top; } - @SuppressWarnings("checkstyle:MagicNumber") private static Size computeVideoSize(int w, int h, int maxSize) { // Compute the video size and the padding of the content inside this video. // Principle: diff --git a/server/src/main/java/com/genymobile/scrcpy/Server.java b/server/src/main/java/com/genymobile/scrcpy/Server.java index db093053..c4954429 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Server.java +++ b/server/src/main/java/com/genymobile/scrcpy/Server.java @@ -69,7 +69,6 @@ public final class Server { }).start(); } - @SuppressWarnings("checkstyle:MagicNumber") private static Options createOptions(String... args) { if (args.length < 1) { throw new IllegalArgumentException("Missing client version"); @@ -115,7 +114,6 @@ public final class Server { return options; } - @SuppressWarnings("checkstyle:MagicNumber") private static Rect parseCrop(String crop) { if ("-".equals(crop)) { return null; @@ -140,7 +138,6 @@ public final class Server { } } - @SuppressWarnings("checkstyle:MagicNumber") private static void suggestFix(Throwable e) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { if (e instanceof MediaCodec.CodecException) { diff --git a/server/src/main/java/com/genymobile/scrcpy/StringUtils.java b/server/src/main/java/com/genymobile/scrcpy/StringUtils.java index 199fc8c1..dac05466 100644 --- a/server/src/main/java/com/genymobile/scrcpy/StringUtils.java +++ b/server/src/main/java/com/genymobile/scrcpy/StringUtils.java @@ -5,7 +5,6 @@ public final class StringUtils { // not instantiable } - @SuppressWarnings("checkstyle:MagicNumber") public static int getUtf8TruncationIndex(byte[] utf8, int maxLength) { int len = utf8.length; if (len <= maxLength) { diff --git a/server/src/test/java/com/genymobile/scrcpy/ControlMessageReaderTest.java b/server/src/test/java/com/genymobile/scrcpy/ControlMessageReaderTest.java index 2f95dfe1..1905942b 100644 --- a/server/src/test/java/com/genymobile/scrcpy/ControlMessageReaderTest.java +++ b/server/src/test/java/com/genymobile/scrcpy/ControlMessageReaderTest.java @@ -80,7 +80,6 @@ public class ControlMessageReaderTest { } @Test - @SuppressWarnings("checkstyle:MagicNumber") public void testParseTouchEvent() throws IOException { ControlMessageReader reader = new ControlMessageReader(); @@ -116,7 +115,6 @@ public class ControlMessageReaderTest { } @Test - @SuppressWarnings("checkstyle:MagicNumber") public void testParseScrollEvent() throws IOException { ControlMessageReader reader = new ControlMessageReader(); diff --git a/server/src/test/java/com/genymobile/scrcpy/StringUtilsTest.java b/server/src/test/java/com/genymobile/scrcpy/StringUtilsTest.java index 7d89ee64..89799c5e 100644 --- a/server/src/test/java/com/genymobile/scrcpy/StringUtilsTest.java +++ b/server/src/test/java/com/genymobile/scrcpy/StringUtilsTest.java @@ -8,7 +8,6 @@ import java.nio.charset.StandardCharsets; public class StringUtilsTest { @Test - @SuppressWarnings("checkstyle:MagicNumber") public void testUtf8Truncate() { String s = "aÉbÔc"; byte[] utf8 = s.getBytes(StandardCharsets.UTF_8); From 4150eedcdf1652fd131480383850882f95a0ea7d Mon Sep 17 00:00:00 2001 From: e_vigurskiy Date: Mon, 24 Feb 2020 14:16:38 +0300 Subject: [PATCH 031/450] Add display id parameter Add --display command line parameter to specify a display id. PR #1238 Signed-off-by: Romain Vimont --- README.md | 15 +++++ app/scrcpy.1 | 9 +++ app/src/cli.c | 28 ++++++++++ app/src/scrcpy.c | 1 + app/src/scrcpy.h | 2 + app/src/server.c | 3 + app/src/server.h | 1 + .../com/genymobile/scrcpy/Controller.java | 24 ++++++-- .../java/com/genymobile/scrcpy/Device.java | 56 ++++++++++++++++++- .../com/genymobile/scrcpy/DisplayInfo.java | 23 +++++++- .../scrcpy/InvalidDisplayIdException.java | 21 +++++++ .../java/com/genymobile/scrcpy/Options.java | 9 +++ .../com/genymobile/scrcpy/ScreenEncoder.java | 8 ++- .../java/com/genymobile/scrcpy/Server.java | 17 +++++- .../scrcpy/wrappers/DisplayManager.java | 19 ++++++- .../scrcpy/wrappers/InputManager.java | 21 +++++++ 16 files changed, 241 insertions(+), 16 deletions(-) create mode 100644 server/src/main/java/com/genymobile/scrcpy/InvalidDisplayIdException.java diff --git a/README.md b/README.md index b190406c..96b2f087 100644 --- a/README.md +++ b/README.md @@ -353,6 +353,21 @@ scrcpy --no-control scrcpy -n ``` +#### Display + +If several displays are available, it is possible to select the display to +mirror: + +```bash +scrcpy --display 1 +``` + +The list of display ids can be retrieved by: + +``` +adb shell dumpsys display # search "mDisplayId=" in the output +``` + #### Turn screen off It is possible to turn the device screen off while mirroring on start with a diff --git a/app/scrcpy.1 b/app/scrcpy.1 index 55b7a631..da6c7f2d 100644 --- a/app/scrcpy.1 +++ b/app/scrcpy.1 @@ -33,6 +33,15 @@ The values are expressed in the device natural orientation (typically, portrait .B \-\-max\-size value is computed on the cropped size. +.TP +.BI "\-\-display " id +Specify the display id to mirror. + +The list of possible display ids can be listed by "adb shell dumpsys display" +(search "mDisplayId=" in the output). + +Default is 0. + .TP .B \-f, \-\-fullscreen Start in fullscreen. diff --git a/app/src/cli.c b/app/src/cli.c index d2cd93b7..bb379f47 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -36,6 +36,15 @@ scrcpy_print_usage(const char *arg0) { " (typically, portrait for a phone, landscape for a tablet).\n" " Any --max-size value is computed on the cropped size.\n" "\n" + " --display id\n" + " Specify the display id to mirror.\n" + "\n" + " The list of possible display ids can be listed by:\n" + " adb shell dumpsys display\n" + " (search \"mDisplayId=\" in the output)\n" + "\n" + " Default is 0.\n" + "\n" " -f, --fullscreen\n" " Start in fullscreen.\n" "\n" @@ -363,6 +372,18 @@ parse_port_range(const char *s, struct port_range *port_range) { return true; } +static bool +parse_display_id(const char *s, uint16_t *display_id) { + long value; + bool ok = parse_integer_arg(s, &value, false, 0, 0xFFFF, "display id"); + if (!ok) { + return false; + } + + *display_id = (uint16_t) value; + return true; +} + static bool parse_record_format(const char *optarg, enum recorder_format *format) { if (!strcmp(optarg, "mp4")) { @@ -407,6 +428,7 @@ guess_record_format(const char *filename) { #define OPT_WINDOW_BORDERLESS 1011 #define OPT_MAX_FPS 1012 #define OPT_LOCK_VIDEO_ORIENTATION 1013 +#define OPT_DISPLAY_ID 1014 bool scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) { @@ -414,6 +436,7 @@ scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) { {"always-on-top", no_argument, NULL, OPT_ALWAYS_ON_TOP}, {"bit-rate", required_argument, NULL, 'b'}, {"crop", required_argument, NULL, OPT_CROP}, + {"display", required_argument, NULL, OPT_DISPLAY_ID}, {"fullscreen", no_argument, NULL, 'f'}, {"help", no_argument, NULL, 'h'}, {"lock-video-orientation", required_argument, NULL, @@ -462,6 +485,11 @@ scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) { case OPT_CROP: opts->crop = optarg; break; + case OPT_DISPLAY_ID: + if (!parse_display_id(optarg, &opts->display_id)) { + return false; + } + break; case 'f': opts->fullscreen = true; break; diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index 4d9ad88b..84c87ad8 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -286,6 +286,7 @@ scrcpy(const struct scrcpy_options *options) { .max_fps = options->max_fps, .lock_video_orientation = options->lock_video_orientation, .control = options->control, + .display_id = options->display_id, }; if (!server_start(&server, options->serial, ¶ms)) { return false; diff --git a/app/src/scrcpy.h b/app/src/scrcpy.h index 014f2cfe..b6c52fd7 100644 --- a/app/src/scrcpy.h +++ b/app/src/scrcpy.h @@ -25,6 +25,7 @@ struct scrcpy_options { int16_t window_y; // WINDOW_POSITION_UNDEFINED for "auto" uint16_t window_width; uint16_t window_height; + uint16_t display_id; bool show_touches; bool fullscreen; bool always_on_top; @@ -55,6 +56,7 @@ struct scrcpy_options { .window_y = WINDOW_POSITION_UNDEFINED, \ .window_width = 0, \ .window_height = 0, \ + .display_id = 0, \ .show_touches = false, \ .fullscreen = false, \ .always_on_top = false, \ diff --git a/app/src/server.c b/app/src/server.c index a8d598e4..89bf9c72 100644 --- a/app/src/server.c +++ b/app/src/server.c @@ -234,10 +234,12 @@ execute_server(struct server *server, const struct server_params *params) { char bit_rate_string[11]; char max_fps_string[6]; char lock_video_orientation_string[3]; + char display_id_string[6]; sprintf(max_size_string, "%"PRIu16, params->max_size); sprintf(bit_rate_string, "%"PRIu32, params->bit_rate); sprintf(max_fps_string, "%"PRIu16, params->max_fps); sprintf(lock_video_orientation_string, "%"PRIi8, params->lock_video_orientation); + sprintf(display_id_string, "%"PRIu16, params->display_id); const char *const cmd[] = { "shell", "CLASSPATH=" DEVICE_SERVER_PATH, @@ -264,6 +266,7 @@ execute_server(struct server *server, const struct server_params *params) { params->crop ? params->crop : "-", "true", // always send frame meta (packet boundaries + timestamp) params->control ? "true" : "false", + display_id_string, }; #ifdef SERVER_DEBUGGER LOGI("Server debugger waiting for a client on device port " diff --git a/app/src/server.h b/app/src/server.h index d84a5cc8..cc712b63 100644 --- a/app/src/server.h +++ b/app/src/server.h @@ -44,6 +44,7 @@ struct server_params { uint16_t max_fps; int8_t lock_video_orientation; bool control; + uint16_t display_id; }; // init default values diff --git a/server/src/main/java/com/genymobile/scrcpy/Controller.java b/server/src/main/java/com/genymobile/scrcpy/Controller.java index 7595a9d7..32a18e16 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Controller.java +++ b/server/src/main/java/com/genymobile/scrcpy/Controller.java @@ -75,19 +75,29 @@ public class Controller { ControlMessage msg = connection.receiveControlMessage(); switch (msg.getType()) { case ControlMessage.TYPE_INJECT_KEYCODE: - injectKeycode(msg.getAction(), msg.getKeycode(), msg.getMetaState()); + if (device.supportsInputEvents()) { + injectKeycode(msg.getAction(), msg.getKeycode(), msg.getMetaState()); + } break; case ControlMessage.TYPE_INJECT_TEXT: - injectText(msg.getText()); + if (device.supportsInputEvents()) { + injectText(msg.getText()); + } break; case ControlMessage.TYPE_INJECT_TOUCH_EVENT: - injectTouch(msg.getAction(), msg.getPointerId(), msg.getPosition(), msg.getPressure(), msg.getButtons()); + if (device.supportsInputEvents()) { + injectTouch(msg.getAction(), msg.getPointerId(), msg.getPosition(), msg.getPressure(), msg.getButtons()); + } break; case ControlMessage.TYPE_INJECT_SCROLL_EVENT: - injectScroll(msg.getPosition(), msg.getHScroll(), msg.getVScroll()); + if (device.supportsInputEvents()) { + injectScroll(msg.getPosition(), msg.getHScroll(), msg.getVScroll()); + } break; case ControlMessage.TYPE_BACK_OR_SCREEN_ON: - pressBackOrTurnScreenOn(); + if (device.supportsInputEvents()) { + pressBackOrTurnScreenOn(); + } break; case ControlMessage.TYPE_EXPAND_NOTIFICATION_PANEL: device.expandNotificationPanel(); @@ -103,7 +113,9 @@ public class Controller { device.setClipboardText(msg.getText()); break; case ControlMessage.TYPE_SET_SCREEN_POWER_MODE: - device.setScreenPowerMode(msg.getAction()); + if (device.supportsInputEvents()) { + device.setScreenPowerMode(msg.getAction()); + } break; case ControlMessage.TYPE_ROTATE_DEVICE: device.rotateDevice(); diff --git a/server/src/main/java/com/genymobile/scrcpy/Device.java b/server/src/main/java/com/genymobile/scrcpy/Device.java index 1b1fbf7d..64d77de3 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Device.java +++ b/server/src/main/java/com/genymobile/scrcpy/Device.java @@ -1,5 +1,6 @@ package com.genymobile.scrcpy; +import com.genymobile.scrcpy.wrappers.InputManager; import com.genymobile.scrcpy.wrappers.ServiceManager; import com.genymobile.scrcpy.wrappers.SurfaceControl; import com.genymobile.scrcpy.wrappers.WindowManager; @@ -25,9 +26,35 @@ public final class Device { private ScreenInfo screenInfo; private RotationListener rotationListener; + /** + * Logical display identifier + */ + private final int displayId; + + /** + * The surface flinger layer stack associated with this logical display + */ + private final int layerStack; + + /** + * The FLAG_PRESENTATION from the DisplayInfo + */ + private final boolean isPresentationDisplay; + public Device(Options options) { - DisplayInfo displayInfo = serviceManager.getDisplayManager().getDisplayInfo(); + displayId = options.getDisplayId(); + DisplayInfo displayInfo = serviceManager.getDisplayManager().getDisplayInfo(displayId); + if (displayInfo == null) { + int[] displayIds = serviceManager.getDisplayManager().getDisplayIds(); + throw new InvalidDisplayIdException(displayId, displayIds); + } + + int displayInfoFlags = displayInfo.getFlags(); + screenInfo = ScreenInfo.computeScreenInfo(displayInfo, options.getCrop(), options.getMaxSize(), options.getLockedVideoOrientation()); + layerStack = displayInfo.getLayerStack(); + isPresentationDisplay = (displayInfoFlags & DisplayInfo.FLAG_PRESENTATION) != 0; + registerRotationWatcher(new IRotationWatcher.Stub() { @Override public void onRotationChanged(int rotation) throws RemoteException { @@ -41,12 +68,24 @@ public final class Device { } } }); + + if ((displayInfoFlags & DisplayInfo.FLAG_SUPPORTS_PROTECTED_BUFFERS) == 0) { + Ln.w("Display doesn't have FLAG_SUPPORTS_PROTECTED_BUFFERS flag, mirroring can be restricted"); + } + + if (!supportsInputEvents()) { + Ln.w("Input events are not supported for displays with FLAG_PRESENTATION enabled for devices with API lower than 29"); + } } public synchronized ScreenInfo getScreenInfo() { return screenInfo; } + public int getLayerStack() { + return layerStack; + } + public Point getPhysicalPoint(Position position) { // it hides the field on purpose, to read it with a lock @SuppressWarnings("checkstyle:HiddenField") @@ -76,7 +115,22 @@ public final class Device { return Build.MODEL; } + public boolean supportsInputEvents() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + return true; + } + return !isPresentationDisplay; + } + public boolean injectInputEvent(InputEvent inputEvent, int mode) { + if (!supportsInputEvents()) { + throw new AssertionError("Could not inject input event if !supportsInputEvents()"); + } + + if (displayId != 0 && !InputManager.setDisplayId(inputEvent, displayId)) { + return false; + } + return serviceManager.getInputManager().injectInputEvent(inputEvent, mode); } diff --git a/server/src/main/java/com/genymobile/scrcpy/DisplayInfo.java b/server/src/main/java/com/genymobile/scrcpy/DisplayInfo.java index 639869b5..50bc94aa 100644 --- a/server/src/main/java/com/genymobile/scrcpy/DisplayInfo.java +++ b/server/src/main/java/com/genymobile/scrcpy/DisplayInfo.java @@ -1,12 +1,25 @@ package com.genymobile.scrcpy; public final class DisplayInfo { + private final int displayId; private final Size size; private final int rotation; + private final int layerStack; + private final int flags; - public DisplayInfo(Size size, int rotation) { + public static final int FLAG_SUPPORTS_PROTECTED_BUFFERS = 0x00000001; + public static final int FLAG_PRESENTATION = 0x00000008; + + public DisplayInfo(int displayId, Size size, int rotation, int layerStack, int flags) { + this.displayId = displayId; this.size = size; this.rotation = rotation; + this.layerStack = layerStack; + this.flags = flags; + } + + public int getDisplayId() { + return displayId; } public Size getSize() { @@ -16,5 +29,13 @@ public final class DisplayInfo { public int getRotation() { return rotation; } + + public int getLayerStack() { + return layerStack; + } + + public int getFlags() { + return flags; + } } diff --git a/server/src/main/java/com/genymobile/scrcpy/InvalidDisplayIdException.java b/server/src/main/java/com/genymobile/scrcpy/InvalidDisplayIdException.java new file mode 100644 index 00000000..81e3b903 --- /dev/null +++ b/server/src/main/java/com/genymobile/scrcpy/InvalidDisplayIdException.java @@ -0,0 +1,21 @@ +package com.genymobile.scrcpy; + +public class InvalidDisplayIdException extends RuntimeException { + + private final int displayId; + private final int[] availableDisplayIds; + + public InvalidDisplayIdException(int displayId, int[] availableDisplayIds) { + super("There is no display having id " + displayId); + this.displayId = displayId; + this.availableDisplayIds = availableDisplayIds; + } + + public int getDisplayId() { + return displayId; + } + + public int[] getAvailableDisplayIds() { + return availableDisplayIds; + } +} diff --git a/server/src/main/java/com/genymobile/scrcpy/Options.java b/server/src/main/java/com/genymobile/scrcpy/Options.java index d9a29452..2a4bba33 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Options.java +++ b/server/src/main/java/com/genymobile/scrcpy/Options.java @@ -11,6 +11,7 @@ public class Options { private Rect crop; private boolean sendFrameMeta; // send PTS so that the client may record properly private boolean control; + private int displayId; public int getMaxSize() { return maxSize; @@ -75,4 +76,12 @@ public class Options { public void setControl(boolean control) { this.control = control; } + + public int getDisplayId() { + return displayId; + } + + public void setDisplayId(int displayId) { + this.displayId = displayId; + } } diff --git a/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java b/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java index d75833f3..40457af8 100644 --- a/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java +++ b/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java @@ -71,10 +71,12 @@ public class ScreenEncoder implements Device.RotationListener { // does not include the locked video orientation Rect unlockedVideoRect = screenInfo.getUnlockedVideoSize().toRect(); int videoRotation = screenInfo.getVideoRotation(); + int layerStack = device.getLayerStack(); + setSize(format, videoRect.width(), videoRect.height()); configure(codec, format); Surface surface = codec.createInputSurface(); - setDisplaySurface(display, surface, videoRotation, contentRect, unlockedVideoRect); + setDisplaySurface(display, surface, videoRotation, contentRect, unlockedVideoRect, layerStack); codec.start(); try { alive = encode(codec, fd); @@ -177,12 +179,12 @@ public class ScreenEncoder implements Device.RotationListener { format.setInteger(MediaFormat.KEY_HEIGHT, height); } - private static void setDisplaySurface(IBinder display, Surface surface, int orientation, Rect deviceRect, Rect displayRect) { + private static void setDisplaySurface(IBinder display, Surface surface, int orientation, Rect deviceRect, Rect displayRect, int layerStack) { SurfaceControl.openTransaction(); try { SurfaceControl.setDisplaySurface(display, surface); SurfaceControl.setDisplayProjection(display, orientation, deviceRect, displayRect); - SurfaceControl.setDisplayLayerStack(display, 0); + SurfaceControl.setDisplayLayerStack(display, layerStack); } finally { SurfaceControl.closeTransaction(); } diff --git a/server/src/main/java/com/genymobile/scrcpy/Server.java b/server/src/main/java/com/genymobile/scrcpy/Server.java index c4954429..bd387285 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Server.java +++ b/server/src/main/java/com/genymobile/scrcpy/Server.java @@ -80,8 +80,8 @@ public final class Server { "The server version (" + BuildConfig.VERSION_NAME + ") does not match the client " + "(" + clientVersion + ")"); } - if (args.length != 9) { - throw new IllegalArgumentException("Expecting 9 parameters"); + if (args.length != 10) { + throw new IllegalArgumentException("Expecting 10 parameters"); } Options options = new Options(); @@ -111,6 +111,9 @@ public final class Server { boolean control = Boolean.parseBoolean(args[8]); options.setControl(control); + int displayId = Integer.parseInt(args[9]); + options.setDisplayId(displayId); + return options; } @@ -149,6 +152,16 @@ public final class Server { } } } + if (e instanceof InvalidDisplayIdException) { + InvalidDisplayIdException idie = (InvalidDisplayIdException) e; + int[] displayIds = idie.getAvailableDisplayIds(); + if (displayIds != null && displayIds.length > 0) { + Ln.e("Try to use one of the available display ids:"); + for (int id : displayIds) { + Ln.e(" scrcpy --display " + id); + } + } + } } public static void main(String... args) throws Exception { diff --git a/server/src/main/java/com/genymobile/scrcpy/wrappers/DisplayManager.java b/server/src/main/java/com/genymobile/scrcpy/wrappers/DisplayManager.java index 568afacd..cedb3f47 100644 --- a/server/src/main/java/com/genymobile/scrcpy/wrappers/DisplayManager.java +++ b/server/src/main/java/com/genymobile/scrcpy/wrappers/DisplayManager.java @@ -12,15 +12,28 @@ public final class DisplayManager { this.manager = manager; } - public DisplayInfo getDisplayInfo() { + public DisplayInfo getDisplayInfo(int displayId) { try { - Object displayInfo = manager.getClass().getMethod("getDisplayInfo", int.class).invoke(manager, 0); + Object displayInfo = manager.getClass().getMethod("getDisplayInfo", int.class).invoke(manager, displayId); + if (displayInfo == null) { + return null; + } Class cls = displayInfo.getClass(); // width and height already take the rotation into account int width = cls.getDeclaredField("logicalWidth").getInt(displayInfo); int height = cls.getDeclaredField("logicalHeight").getInt(displayInfo); int rotation = cls.getDeclaredField("rotation").getInt(displayInfo); - return new DisplayInfo(new Size(width, height), rotation); + int layerStack = cls.getDeclaredField("layerStack").getInt(displayInfo); + int flags = cls.getDeclaredField("flags").getInt(displayInfo); + return new DisplayInfo(displayId, new Size(width, height), rotation, layerStack, flags); + } catch (Exception e) { + throw new AssertionError(e); + } + } + + public int[] getDisplayIds() { + try { + return (int[]) manager.getClass().getMethod("getDisplayIds").invoke(manager); } catch (Exception e) { throw new AssertionError(e); } diff --git a/server/src/main/java/com/genymobile/scrcpy/wrappers/InputManager.java b/server/src/main/java/com/genymobile/scrcpy/wrappers/InputManager.java index 44fa613b..36d07353 100644 --- a/server/src/main/java/com/genymobile/scrcpy/wrappers/InputManager.java +++ b/server/src/main/java/com/genymobile/scrcpy/wrappers/InputManager.java @@ -17,6 +17,8 @@ public final class InputManager { private final IInterface manager; private Method injectInputEventMethod; + private static Method setDisplayIdMethod; + public InputManager(IInterface manager) { this.manager = manager; } @@ -37,4 +39,23 @@ public final class InputManager { return false; } } + + private static Method getSetDisplayIdMethod() throws NoSuchMethodException { + if (setDisplayIdMethod == null) { + setDisplayIdMethod = InputEvent.class.getMethod("setDisplayId", int.class); + } + return setDisplayIdMethod; + } + + public static boolean setDisplayId(InputEvent inputEvent, int displayId) { + try { + Method method = getSetDisplayIdMethod(); + method.invoke(inputEvent, displayId); + return true; + } catch (InvocationTargetException | IllegalAccessException | NoSuchMethodException e) { + // just a warning, it might happen on old devices + Ln.w("Cannot associate a display id to the input event"); + return false; + } + } } From 64d5edce92dc27cfef11594a27876cd378225e53 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sat, 28 Mar 2020 23:25:38 +0100 Subject: [PATCH 032/450] Refactor server_start() error handling This avoids cleanup duplication. --- app/src/server.c | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/app/src/server.c b/app/src/server.c index 89bf9c72..2923c6c7 100644 --- a/app/src/server.c +++ b/app/src/server.c @@ -345,30 +345,31 @@ server_start(struct server *server, const char *serial, } if (!push_server(serial)) { - SDL_free(server->serial); - return false; + goto error1; } if (!enable_tunnel_any_port(server, params->port_range)) { - SDL_free(server->serial); - return false; + goto error1; } // server will connect to our server socket server->process = execute_server(server, params); - if (server->process == PROCESS_NONE) { - if (!server->tunnel_forward) { - close_socket(&server->server_socket); - } - disable_tunnel(server); - SDL_free(server->serial); - return false; + goto error2; } server->tunnel_enabled = true; return true; + +error2: + if (!server->tunnel_forward) { + close_socket(&server->server_socket); + } + disable_tunnel(server); +error1: + SDL_free(server->serial); + return false; } bool From d421741a83a1d7114a85d286f4bb92c52b20f31b Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sat, 28 Mar 2020 23:15:15 +0100 Subject: [PATCH 033/450] Wait server from a separate thread Create a thread just to wait for the server process exit. This paves the way to simply wake up a blocking accept() in a portable way. --- app/src/server.c | 24 +++++++++++++++++++++--- app/src/server.h | 3 +++ 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/app/src/server.c b/app/src/server.c index 2923c6c7..77978d19 100644 --- a/app/src/server.c +++ b/app/src/server.c @@ -5,6 +5,7 @@ #include #include #include +#include #include #include @@ -332,6 +333,14 @@ server_init(struct server *server) { *server = (struct server) SERVER_INITIALIZER; } +static int +run_wait_server(void *data) { + struct server *server = data; + cmd_simple_wait(server->process, NULL); // ignore exit code + LOGD("Server terminated"); + return 0; +} + bool server_start(struct server *server, const char *serial, const struct server_params *params) { @@ -358,6 +367,16 @@ server_start(struct server *server, const char *serial, goto error2; } + server->wait_server_thread = + SDL_CreateThread(run_wait_server, "wait-server", server); + if (!server->wait_server_thread) { + if (!cmd_terminate(server->process)) { + LOGW("Could not terminate server"); + } + cmd_simple_wait(server->process, NULL); // ignore exit code + goto error2; + } + server->tunnel_enabled = true; return true; @@ -430,13 +449,12 @@ server_stop(struct server *server) { LOGW("Could not terminate server"); } - cmd_simple_wait(server->process, NULL); // ignore exit code - LOGD("Server terminated"); - if (server->tunnel_enabled) { // ignore failure disable_tunnel(server); } + + SDL_WaitThread(server->wait_server_thread, NULL); } void diff --git a/app/src/server.h b/app/src/server.h index cc712b63..050f7f01 100644 --- a/app/src/server.h +++ b/app/src/server.h @@ -3,6 +3,7 @@ #include #include +#include #include "config.h" #include "command.h" @@ -12,6 +13,7 @@ struct server { char *serial; process_t process; + SDL_Thread *wait_server_thread; socket_t server_socket; // only used if !tunnel_forward socket_t video_socket; socket_t control_socket; @@ -24,6 +26,7 @@ struct server { #define SERVER_INITIALIZER { \ .serial = NULL, \ .process = PROCESS_NONE, \ + .wait_server_thread = NULL, \ .server_socket = INVALID_SOCKET, \ .video_socket = INVALID_SOCKET, \ .control_socket = INVALID_SOCKET, \ From a346bb80f4ae85abafa2fc5d261d77c433928bea Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sat, 28 Mar 2020 23:56:25 +0100 Subject: [PATCH 034/450] Do not block on accept() if server died The server may die before connecting to the client. In that case, the client was blocked indefinitely (until Ctrl+C) on accept(). To avoid the problem, close the server socket once the server process is dead. --- app/src/server.c | 43 +++++++++++++++++++++++++++++++------------ app/src/server.h | 3 +++ 2 files changed, 34 insertions(+), 12 deletions(-) diff --git a/app/src/server.c b/app/src/server.c index 77978d19..bea6fe98 100644 --- a/app/src/server.c +++ b/app/src/server.c @@ -318,14 +318,12 @@ connect_to_server(uint16_t port, uint32_t attempts, uint32_t delay) { } static void -close_socket(socket_t *socket) { - assert(*socket != INVALID_SOCKET); - net_shutdown(*socket, SHUT_RDWR); - if (!net_close(*socket)) { +close_socket(socket_t socket) { + assert(socket != INVALID_SOCKET); + net_shutdown(socket, SHUT_RDWR); + if (!net_close(socket)) { LOGW("Could not close socket"); - return; } - *socket = INVALID_SOCKET; } void @@ -337,6 +335,14 @@ static int run_wait_server(void *data) { struct server *server = data; cmd_simple_wait(server->process, NULL); // ignore exit code + // no need for synchronization, server_socket is initialized before this + // thread was created + if (server->server_socket != INVALID_SOCKET + && SDL_AtomicCAS(&server->server_socket_closed, 0, 1)) { + // On Linux, accept() is unblocked by shutdown(), but on Windows, it is + // unblocked by closesocket(). Therefore, call both (close_socket()). + close_socket(server->server_socket); + } LOGD("Server terminated"); return 0; } @@ -367,6 +373,12 @@ server_start(struct server *server, const char *serial, goto error2; } + // If the server process dies before connecting to the server socket, then + // the client will be stuck forever on accept(). To avoid the problem, we + // must be able to wake up the accept() call when the server dies. To keep + // things simple and multiplatform, just spawn a new thread waiting for the + // server process and calling shutdown()/close() on the server socket if + // necessary to wake up any accept() blocking call. server->wait_server_thread = SDL_CreateThread(run_wait_server, "wait-server", server); if (!server->wait_server_thread) { @@ -383,7 +395,9 @@ server_start(struct server *server, const char *serial, error2: if (!server->tunnel_forward) { - close_socket(&server->server_socket); + // the wait server thread is not started, SDL_AtomicSet() is sufficient + SDL_AtomicSet(&server->server_socket_closed, 1); + close_socket(server->server_socket); } disable_tunnel(server); error1: @@ -406,7 +420,11 @@ server_connect_to(struct server *server) { } // we don't need the server socket anymore - close_socket(&server->server_socket); + if (SDL_AtomicCAS(&server->server_socket_closed, 0, 1)) { + // close it from here + close_socket(server->server_socket); + // otherwise, it is closed by run_wait_server() + } } else { uint32_t attempts = 100; uint32_t delay = 100; // ms @@ -433,14 +451,15 @@ server_connect_to(struct server *server) { void server_stop(struct server *server) { - if (server->server_socket != INVALID_SOCKET) { - close_socket(&server->server_socket); + if (server->server_socket != INVALID_SOCKET + && SDL_AtomicCAS(&server->server_socket_closed, 0, 1)) { + close_socket(server->server_socket); } if (server->video_socket != INVALID_SOCKET) { - close_socket(&server->video_socket); + close_socket(server->video_socket); } if (server->control_socket != INVALID_SOCKET) { - close_socket(&server->control_socket); + close_socket(server->control_socket); } assert(server->process != PROCESS_NONE); diff --git a/app/src/server.h b/app/src/server.h index 050f7f01..79db72e5 100644 --- a/app/src/server.h +++ b/app/src/server.h @@ -3,6 +3,7 @@ #include #include +#include #include #include "config.h" @@ -14,6 +15,7 @@ struct server { char *serial; process_t process; SDL_Thread *wait_server_thread; + SDL_atomic_t server_socket_closed; socket_t server_socket; // only used if !tunnel_forward socket_t video_socket; socket_t control_socket; @@ -27,6 +29,7 @@ struct server { .serial = NULL, \ .process = PROCESS_NONE, \ .wait_server_thread = NULL, \ + .server_socket_closed = {0}, \ .server_socket = INVALID_SOCKET, \ .video_socket = INVALID_SOCKET, \ .control_socket = INVALID_SOCKET, \ From 94e16968693bcb326fcbbd85818589c7f4b8d02f Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 29 Mar 2020 00:49:08 +0100 Subject: [PATCH 035/450] Do not warn on terminating the server If the server is already dead, terminating it fails. This is expected. --- app/src/server.c | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/app/src/server.c b/app/src/server.c index bea6fe98..b9bb9537 100644 --- a/app/src/server.c +++ b/app/src/server.c @@ -382,9 +382,7 @@ server_start(struct server *server, const char *serial, server->wait_server_thread = SDL_CreateThread(run_wait_server, "wait-server", server); if (!server->wait_server_thread) { - if (!cmd_terminate(server->process)) { - LOGW("Could not terminate server"); - } + cmd_terminate(server->process); cmd_simple_wait(server->process, NULL); // ignore exit code goto error2; } @@ -464,9 +462,7 @@ server_stop(struct server *server) { assert(server->process != PROCESS_NONE); - if (!cmd_terminate(server->process)) { - LOGW("Could not terminate server"); - } + cmd_terminate(server->process); if (server->tunnel_enabled) { // ignore failure From bea1c11f8ed74854f15b26428f69c7ed78f0de4e Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Wed, 1 Apr 2020 11:28:48 +0200 Subject: [PATCH 036/450] Do not log success on failure If calling the private API does not work, an exception is printed. In that case, do not log that the action succeeded. --- .../src/main/java/com/genymobile/scrcpy/Device.java | 12 ++++++++---- .../genymobile/scrcpy/wrappers/ClipboardManager.java | 4 +++- .../genymobile/scrcpy/wrappers/SurfaceControl.java | 4 +++- 3 files changed, 14 insertions(+), 6 deletions(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/Device.java b/server/src/main/java/com/genymobile/scrcpy/Device.java index 64d77de3..cbe294af 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Device.java +++ b/server/src/main/java/com/genymobile/scrcpy/Device.java @@ -163,8 +163,10 @@ public final class Device { } public void setClipboardText(String text) { - serviceManager.getClipboardManager().setText(text); - Ln.i("Device clipboard set"); + boolean ok = serviceManager.getClipboardManager().setText(text); + if (ok) { + Ln.i("Device clipboard set"); + } } /** @@ -176,8 +178,10 @@ public final class Device { Ln.e("Could not get built-in display"); return; } - SurfaceControl.setDisplayPowerMode(d, mode); - Ln.i("Device screen turned " + (mode == Device.POWER_MODE_OFF ? "off" : "on")); + boolean ok = SurfaceControl.setDisplayPowerMode(d, mode); + if (ok) { + Ln.i("Device screen turned " + (mode == Device.POWER_MODE_OFF ? "off" : "on")); + } } /** diff --git a/server/src/main/java/com/genymobile/scrcpy/wrappers/ClipboardManager.java b/server/src/main/java/com/genymobile/scrcpy/wrappers/ClipboardManager.java index 592bdf6b..ade23032 100644 --- a/server/src/main/java/com/genymobile/scrcpy/wrappers/ClipboardManager.java +++ b/server/src/main/java/com/genymobile/scrcpy/wrappers/ClipboardManager.java @@ -74,13 +74,15 @@ public class ClipboardManager { } } - public void setText(CharSequence text) { + public boolean setText(CharSequence text) { try { Method method = getSetPrimaryClipMethod(); ClipData clipData = ClipData.newPlainText(null, text); setPrimaryClip(method, manager, clipData); + return true; } catch (InvocationTargetException | IllegalAccessException | NoSuchMethodException e) { Ln.e("Could not invoke method", e); + return false; } } } diff --git a/server/src/main/java/com/genymobile/scrcpy/wrappers/SurfaceControl.java b/server/src/main/java/com/genymobile/scrcpy/wrappers/SurfaceControl.java index 227bbc85..8fbb860b 100644 --- a/server/src/main/java/com/genymobile/scrcpy/wrappers/SurfaceControl.java +++ b/server/src/main/java/com/genymobile/scrcpy/wrappers/SurfaceControl.java @@ -121,12 +121,14 @@ public final class SurfaceControl { return setDisplayPowerModeMethod; } - public static void setDisplayPowerMode(IBinder displayToken, int mode) { + public static boolean setDisplayPowerMode(IBinder displayToken, int mode) { try { Method method = getSetDisplayPowerModeMethod(); method.invoke(null, displayToken, mode); + return true; } catch (InvocationTargetException | IllegalAccessException | NoSuchMethodException e) { Ln.e("Could not invoke method", e); + return false; } } From 54ccccd8839f16cc289f98160882b909ea7aa3c0 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 2 Apr 2020 19:16:33 +0200 Subject: [PATCH 037/450] Replace SDL_Atomic by stdatomic from C11 There is no reason to use SDL atomics. --- app/src/fps_counter.c | 26 ++++++++++++++++++-------- app/src/fps_counter.h | 4 ++-- app/src/server.c | 13 ++++++++----- app/src/server.h | 6 +++--- 4 files changed, 31 insertions(+), 18 deletions(-) diff --git a/app/src/fps_counter.c b/app/src/fps_counter.c index 58c62d55..b4dd8b9b 100644 --- a/app/src/fps_counter.c +++ b/app/src/fps_counter.c @@ -23,7 +23,7 @@ fps_counter_init(struct fps_counter *counter) { } counter->thread = NULL; - SDL_AtomicSet(&counter->started, 0); + atomic_init(&counter->started, 0); // no need to initialize the other fields, they are unused until started return true; @@ -35,6 +35,16 @@ fps_counter_destroy(struct fps_counter *counter) { SDL_DestroyMutex(counter->mutex); } +static inline bool +is_started(struct fps_counter *counter) { + return atomic_load_explicit(&counter->started, memory_order_acquire); +} + +static inline void +set_started(struct fps_counter *counter, bool started) { + atomic_store_explicit(&counter->started, started, memory_order_release); +} + // must be called with mutex locked static void display_fps(struct fps_counter *counter) { @@ -70,10 +80,10 @@ run_fps_counter(void *data) { mutex_lock(counter->mutex); while (!counter->interrupted) { - while (!counter->interrupted && !SDL_AtomicGet(&counter->started)) { + while (!counter->interrupted && !is_started(counter)) { cond_wait(counter->state_cond, counter->mutex); } - while (!counter->interrupted && SDL_AtomicGet(&counter->started)) { + while (!counter->interrupted && is_started(counter)) { uint32_t now = SDL_GetTicks(); check_interval_expired(counter, now); @@ -96,7 +106,7 @@ fps_counter_start(struct fps_counter *counter) { counter->nr_skipped = 0; mutex_unlock(counter->mutex); - SDL_AtomicSet(&counter->started, 1); + set_started(counter, true); cond_signal(counter->state_cond); // counter->thread is always accessed from the same thread, no need to lock @@ -114,13 +124,13 @@ fps_counter_start(struct fps_counter *counter) { void fps_counter_stop(struct fps_counter *counter) { - SDL_AtomicSet(&counter->started, 0); + set_started(counter, false); cond_signal(counter->state_cond); } bool fps_counter_is_started(struct fps_counter *counter) { - return SDL_AtomicGet(&counter->started); + return is_started(counter); } void @@ -145,7 +155,7 @@ fps_counter_join(struct fps_counter *counter) { void fps_counter_add_rendered_frame(struct fps_counter *counter) { - if (!SDL_AtomicGet(&counter->started)) { + if (!is_started(counter)) { return; } @@ -158,7 +168,7 @@ fps_counter_add_rendered_frame(struct fps_counter *counter) { void fps_counter_add_skipped_frame(struct fps_counter *counter) { - if (!SDL_AtomicGet(&counter->started)) { + if (!is_started(counter)) { return; } diff --git a/app/src/fps_counter.h b/app/src/fps_counter.h index 1c56bb01..52157172 100644 --- a/app/src/fps_counter.h +++ b/app/src/fps_counter.h @@ -1,9 +1,9 @@ #ifndef FPSCOUNTER_H #define FPSCOUNTER_H +#include #include #include -#include #include #include @@ -16,7 +16,7 @@ struct fps_counter { // atomic so that we can check without locking the mutex // if the FPS counter is disabled, we don't want to lock unnecessarily - SDL_atomic_t started; + atomic_bool started; // the following fields are protected by the mutex bool interrupted; diff --git a/app/src/server.c b/app/src/server.c index b9bb9537..87997e72 100644 --- a/app/src/server.c +++ b/app/src/server.c @@ -338,7 +338,7 @@ run_wait_server(void *data) { // no need for synchronization, server_socket is initialized before this // thread was created if (server->server_socket != INVALID_SOCKET - && SDL_AtomicCAS(&server->server_socket_closed, 0, 1)) { + && !atomic_flag_test_and_set(&server->server_socket_closed)) { // On Linux, accept() is unblocked by shutdown(), but on Windows, it is // unblocked by closesocket(). Therefore, call both (close_socket()). close_socket(server->server_socket); @@ -393,8 +393,11 @@ server_start(struct server *server, const char *serial, error2: if (!server->tunnel_forward) { - // the wait server thread is not started, SDL_AtomicSet() is sufficient - SDL_AtomicSet(&server->server_socket_closed, 1); + bool was_closed = + atomic_flag_test_and_set(&server->server_socket_closed); + // the thread is not started, the flag could not be already set + assert(!was_closed); + (void) was_closed; close_socket(server->server_socket); } disable_tunnel(server); @@ -418,7 +421,7 @@ server_connect_to(struct server *server) { } // we don't need the server socket anymore - if (SDL_AtomicCAS(&server->server_socket_closed, 0, 1)) { + if (!atomic_flag_test_and_set(&server->server_socket_closed)) { // close it from here close_socket(server->server_socket); // otherwise, it is closed by run_wait_server() @@ -450,7 +453,7 @@ server_connect_to(struct server *server) { void server_stop(struct server *server) { if (server->server_socket != INVALID_SOCKET - && SDL_AtomicCAS(&server->server_socket_closed, 0, 1)) { + && !atomic_flag_test_and_set(&server->server_socket_closed)) { close_socket(server->server_socket); } if (server->video_socket != INVALID_SOCKET) { diff --git a/app/src/server.h b/app/src/server.h index 79db72e5..a2ecdefc 100644 --- a/app/src/server.h +++ b/app/src/server.h @@ -1,9 +1,9 @@ #ifndef SERVER_H #define SERVER_H +#include #include #include -#include #include #include "config.h" @@ -15,7 +15,7 @@ struct server { char *serial; process_t process; SDL_Thread *wait_server_thread; - SDL_atomic_t server_socket_closed; + atomic_flag server_socket_closed; socket_t server_socket; // only used if !tunnel_forward socket_t video_socket; socket_t control_socket; @@ -29,7 +29,7 @@ struct server { .serial = NULL, \ .process = PROCESS_NONE, \ .wait_server_thread = NULL, \ - .server_socket_closed = {0}, \ + .server_socket_closed = ATOMIC_FLAG_INIT, \ .server_socket = INVALID_SOCKET, \ .video_socket = INVALID_SOCKET, \ .control_socket = INVALID_SOCKET, \ From 271de0954a15ec23dca945009eb3f576af6abed8 Mon Sep 17 00:00:00 2001 From: Harsh Shandilya Date: Fri, 3 Apr 2020 20:29:58 +0530 Subject: [PATCH 038/450] Update to AGP 3.6.2 Signed-off-by: Harsh Shandilya --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index b6ec625d..94862d2e 100644 --- a/build.gradle +++ b/build.gradle @@ -7,7 +7,7 @@ buildscript { jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:3.4.2' + classpath 'com.android.tools.build:gradle:3.6.2' // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files From 9e78b765da01843b4986f93179b0c92701269062 Mon Sep 17 00:00:00 2001 From: Harsh Shandilya Date: Fri, 3 Apr 2020 20:30:11 +0530 Subject: [PATCH 039/450] Update to Gradle 6.3 Signed-off-by: Harsh Shandilya Signed-off-by: Romain Vimont -- Note from committer: The binary gradle/wrapper/gradle-wrapper.jar has the expected SHA-256 checksum: $ curl -L https://services.gradle.org/distributions/gradle-6.3-wrapper.jar.sha256 1cef53de8dc192036e7b0cc47584449b0cf570a00d560bfaa6c9eabe06e1fc06 All the changed files match an upgrade executed independently: --- gradle/wrapper/gradle-wrapper.jar | Bin 55616 -> 58694 bytes gradle/wrapper/gradle-wrapper.properties | 2 +- gradlew | 33 ++++++++++------------- gradlew.bat | 3 +++ 4 files changed, 18 insertions(+), 20 deletions(-) diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 5c2d1cf016b3885f6930543d57b744ea8c220a1a..490fda8577df6c95960ba7077c43220e5bb2c0d9 100644 GIT binary patch delta 22806 zcmX@GiTT(m<_+46yi-*#FWbq;z#z%Yz@R^Qg0k3TKSq&y6+^w`oW$bd-k{TYmmNfG zFYhQdT-Nf%wQ`C>yk}&W( z=H!SiF)WrZK6iEJ_jBjt@7FU_bnRvMykm36ZpL>H_x1bA^VgWLip|She7CqUYIUvN z1KH;*g?310FTU5)UT>RvK1Dxync(xzGv}6GO||i@%3HwQ_+hHyDY^7b8X|{}7v}vA zyno6h_@cVDyK?Gc@yd5x#~*}bT`s+3mAZX?iH3t(r<7&m+OQf$`%@mzFM7{ic~0fj zl9%qW>04ix-+HfMd^IfLWxU2z3ClGh|J;Q*cKy7ew#tPoCr0hbijx_fD#F z4&1u$JC*mMknV=^qq}k*Hk)PIi2jH&+H7fj?c19duQn?Se`vq7k)eF&tz9|18HXO% zKl=1z=d%h6dAI8?T(>lp)%}+~GW}J&<%7Kes{93W%Pkl$=v@pleii(Q^WxS`cN~-7 zX=lw(e-Y@K@23!4_42OM;;Qc(d#0{Bl65rZu+`f3+cysIRxt*~9X*&I;LXg!#lXP9 z!N9=a$dJjXvs#{&fkB0ffk77<&ocG5gN4H-{>v8M+@RB8sMXoD(aDoRSbuBShOl0Z z3#__~xm#}Oom5xmo2NG{P>IYZcopoJqOO* z+Bb;z6nB`FQ`~vR6a&>{srRQoI8HTCP7HJ7nVu4JtlH?= zgnH5AdwK-KQeqB<7VeRE@(WnkS`d4PT`0&%X@_F6jJayfjn23l6-VyGz#FH!7c21T zC3+sKV^nBc)RM}Uddg(yT*ECzGp9>7EiVZ!_cQ(>bW)q=Ur*=*p4*{!pKP9bNoo7@ zt**@)iC0f$9}_)$?PzuHi}GounKqE%ZgZ`!+mV` z^*nco{gHg96U|!R7SY^rb~?|z@=J34D&^+tdAUpH?eSQBGx+dw&wbrRQ}^30Ub`bn zsHDhjsYZ1jQaXH$Pn{;(UJEWbf|n9BW%{bMZ`J z5&E?Gxuxn-25Cz!oi!dmA{u#5tz5jI$(rk3S2DwDp=%;FP5z>H4%eR2+gg6=^|BDz z>{HB|LW>?4*Z8f~ifRhF9Tdu1AOFCcHU44tt0h&JjN3w=E!xMm?_h##%vvdv?hqcv zC(*3;A2v3<|JbnXjc9w$gN`Qo56XuxXlogKv0GoC`Dt4O@6$J#bKRm;yr;!p%Gl(u zHgE3LV;{f13d_Ivtn=Pgx9~NM4avG^d?(&_*;JUZ{MhW*R-uza=1&Z1{W$H^6>H`C zpeKH1Yu|hb`dsCq60Kg6Z9Cyq>e**%M`y3e-?VG_^NdalsZVD6bBcR~?o3^O<50j~ z+e=TbE#~@=`Yz>`&c=vGtnaLNc3t0gBVqTIZ8vk2wp4vQzh}vmd48WkZdaZbL;Z;pe!9`$)jQ z+S%ooq!C(@yP&@M%zuvCi~ng=E&BJNPwP-}V}`<>$xD6~} z`Kv4b>wj72-1n>Ad5UAhx7`bZUo0uscf8xq_hsSyI*T6VJ|5TgL4V8(1ysBCI$gZ4 z%qw_jiMWHVMWMAz*^FxM#|ErY2ekSwoOR!J?jrx9i5ik^4NFIf|k9Z z-cyfTd+sq@0Ty#Z zR%%?$n&7aU^P3@)mQ&y4I|{oB)!EWDZHk_=aZQq6#=tKu!Dz$sjpaCp#mSUA`z-ul zGN(LxUmo20pJ8$~+ehB2geN~OSr`~@urn|?f=kePc20Je)U?FXoRZ0}CFJUthTaX9 z4i))-H^+?oG+U5|#*}Rh+e}%!R)uX@vNpFdBST|qfWTU-=}*jZcE5R)#{5_M*Ms2r zUyJ^a9o}F?PS>R1;$p3A2-WCKHgK``grH`4;M8bl=!GUzi0b=-sj(Q`}bCU zIsg1S!=8rkK1V)!*+vyObvbGpsdY{+wWv>hWD=L_z?8n^*_zH`iL3(#ZagWxd(xD; zt}CafEwEE7l9>8vPop8TI8TqUi_H2MF*|4Xd!J9g+;rZ%&qM!bO`_wEmwSxTBwl$P z5=@ojXj^<+R_5EZnB$L^cJB-S=DKv!cJ0Q3sju&`#_QaQ+q$XH?T70_HXa@KAL_Z+ z`TUIQ=lH*KdazQVdYQx_=eW)tW>0g&V^*9y&s9I^yw#G-bJeczd1*JuZD=zbG(MXz8rA0v{GcNzQtl7db^DYSPcu zH4~S|3NF$2ePXnDv5|W7bT1IIcD|1PHy^9@2XDt&ncEfZ)evL4UEJv^9l9-| zG{b9NL2%*9yS$C!1?h9sawb38bxNlq^wY^(lTu4;{r0nL*&r{jyCrFTtJ#+58|{4D z?ic;n{Nmk{n8?J+UEf}KWYPC$uFR9g^b$`U*s^7hXX@QHv%_{vOILh(*||#k#^vyj0%Vs~J zxi8k>(F~beg3EKYx1O}TA05OtGwSiO#Gca&FFgBMYU#Xl&K$+ud&{uL6-c`NVDzaF0uI}y$Sd}jse)1gKVq)*$qqjF@Pxmek6>Z;*RvgVw%u8<8 zdq><4EW0m!iT}mnev8KWBD+Nj)wb}KHCOO?9?cW5P2pV`yWZ-$d$nwo%{8a2s{L70 zS@P}S1A897X2!ob!NRq+x-*&)aU+?mHg8v_-AR5rQZ{& z&YD?5S5GEgI{7>_d6V?Z)X*TGw<$rhyjJnuI$+5h62|=0)!apQJ?AN}s^iJ3Yk#{v z=hg39mMy;~>Acn~`XWqloS10I}`fM?()r{Z5R29W-@moolmUrh%`|1(TI#hPAT}v0Te3 zKWX6cGyInlujxOA`Y9jJO$)muwCeW;Ikw5GUT)XiuDkf~y)<`I*A;)H!Y8I|xZk#L!qkTCx0?)> zW~oTWIe)5B*t71*+G!K+&0ZnRCb)imoQQ_;8MBsKYE|ZwzD}GY-^!z%xA51@`iF5E zQ_ST;%sx&?yg4_bV)2^YW_;fDJl)e7B-hMcp`4v``ug5HP7(-ekq{v^OzHbxD_b zZqAVSu;Jm`FSop8418YiJkc>>)$t9_!rT?SekCrB*&&fsVaWEQ|H_-65;Fq=m!7PD zx@^iEACctnjNtyJlVOPQ=)3Z0&xfR{;*k$%dY)4*mv=m!?b@Z zEp8k)eev@MgT)TdY$3}h?N7=@uI~I}C^Uc06TxM3rXp#+97{Hc z$S{>?*3HRbVPb1hG4XIo=$XGnJu|^X`TQQogyYA`oGcHTIH8?A?<5T}!+#S-h%$uQO}nv98PO8{X}Hf2a6--M{MjKMs}0)$$&w z534-YU;4w;{@2H!TIcpHXp0X&oGhAa3 zACUj`QRbfqZ+w_d{hGG*KQ^+~Cp}or_U}sn)E~z7VGqv#{lW0hN%#L!seh-Aysq2B zU%$3(z2pz+uYaQL@AdEh{P_Jx{;VIzwX%Mz3;%ypfBgDKeYSsBkL>@sk@Nq%)?dN= zA&;eh@Y~NjUO(%HzWqPx`yoR6mTKHL`e1*mFMstzUeg+P-eMV+ECJIHiJ;7m<09+j zWgjh0O`4JP%TOb9*$>~9W=B3P7M^duw9S8Is7`5C*!cw~jqLfn4@`Mlw?|3aCoQ+rh=J)kxDZTO6PWdjovu4RU<0oRyPRs6OovI2pn^81dQ+nzi-&6B$`(~P? zZr1c&W-|LylGkd@+^po?iscecx7QZfyi@sp0fm=$e%{S2Kh(MVqTp@c2@&QOidQWE9&vu5*6J_${{ckNcTwZIH>F;ZK@>||s4^y`L z)o(o8XFkli;JIv3=Z*z+Srd)dKCOSUXXg7UZ+4qY_3zrvGPl<|V#AHEN2i=^W;#Fb zh(_<4*L}z4&inFY!(^K*{^K&DdyaI-N^hAQ_;sFg)tyca^!#S&W^|1_`kFlM=k37 zR(r92#$r>6Z!cCD^_B*nI`vDkG$_wx-sRBfUDH`Fl;$0o8Pt6zX8v`_zTJLCdB-zi ztR$lqw$8qtby-t=XZ4+vp<2TBbL6ChMS}|$P5yS|^5nmLoA%!F-BGsYc4z-?VOOp> z7i~QGj1K-hbbGRg_SrM4&Thrw(_f?(ADJtds;#@iXGwjmqw~S}_qJG1Et4!gKJQ79 z>y+E!Vqu3r{&aPIXOc2ov0QS~-3hX7cYK?Jzs)e7(8G02m*d;BRz^|xs}Y8VMRxCQ zJ?Cn?vvRRpAnzHk<#W6Gbk6*Ewuv+L%~G$m^-DgSTNIGUfBd-6k)?^&Pjl{H6P3Sc zyJ_V{pSM$+XZ$KWezrc(ORnrrCrj~`WgYr2dycMHYrEU6?)1eom2t|yXX&tRxcBh# zoh7fFF1~x^TXyqgL`dOUokv=I6VmPk9(i#&_29`>i)5wBc7J8MT-Ki;dAm+Uc5`g{ z0)fu2PmRM~ZR?APNuFovy6|$R%3jVX9hblAL|lvrYl^?fHuuMhL!V#0@vi5yn0{7Y zG-IEO7Wc-`&Je@G@}ic|_x&oDP71o4Dn{G)8{~TRwI2VlMe1InV((_LWrmp#Wz<-$ zr#mdaz3=ovm(ELx4{hYOeyFWrH#T^yfpYvPp8ZEv@EZ21C_}+r#1U4a?0!dccj;6C>%Y+ zFKbb?MSn@4P6$`>!XuIDlA9iv*goy~P{4gwdFrJp?e)I|%v-;`&S`Or>^b?OAnN0m z)?Z0{hCaF8t~VbpEOAU+(i^+*it40`?!^msW+nQUEn95ZE3t1)CqpXXegE8gd}%5IG`u$gs2 zYRctcp3b&8o1g5lsXrybFLU>L)pphXlefKpe2Shv{h+6#N8tIbb7%K#<#m6&IJY|Y zdf9n@OB)r5+S(Ak*^3y=v!m4ce?6%@c(NvURl~H-S}haVQuS#ka!o|L)b9jn*_uvW zFzcc~=5x--h0V?FGiN(&+kLC(?$TQmj>l?l)NkLUrQ09-WC z_R#R2M_D%hR%=(MB~O?hefRUPme_hDpQuxN{&VlE{iAvOtgPReh>MA_hnA;a(b!rx z=}69s*Pq=#{%O5xUYTYx%jadb@y464)k^+w&G}q@CAGI(|J1=_pTym^hIyAiKH3?* z*&*%ZUt@>$zvjAM+7o-8{na<4?HfMbXW9B?%M!UT(VbB>(P!`7STXBo(dX%DzvP~& zTCS{Lf8*XU;kmE2?pxCGC5X2$($)BXT-DZ-L08k$O+@F}@9%nlww5cJYnqO}^RhZ# zhdZI~xV*w=?{BYKGUdYTD`hT*!26FJ==erCV_ ztJROLM4$Se>^62olkAoSzjQ6a~fB9Rn{K=}{ zTcRz#gp`@P)$_C|%I8Bc8Z7BF$HIVd8hxkvryJgus7 z!2o%!IqX-qch${1ZToZQ!R7P!^gnznBmew1!=4WxBClT2(OqjiVRh6Q;rdw{o07SG zwmf1=_l0JKhy~64(N*9fnu5vm*Ws}$1M=$4e$uH*m8hzT=*LX!nR-NQj-?jRO zFN=KH!;^nmr^flLuCYN}n`hgq%D=B)*j(^iTOROE>`Ib$*aJVdbZN6kdbyXkt$EVq zJDbZa_tLwt7dN+T^E^AHR4(Y}#i^Uu)rUuOui^Nye&H{+tn@_QlRR1L7KYsnTA91@ z>hJeyI`0k^y}7b$?VEjb-gVCkpEmi&-wO56l*Q32&97}u37GEqcca?VSDPy3Hy4!$ zzWY1Je!dsiGuQsm&h{zEbK*~FuU^yc|Nqg&)92Um<$pG1 zF^ahNuzvQ@u7#0nWEMSYlU(zP^OsS?-G`Z3d{t=zDJQO%NI3c@$N>y1O!KhFL&)AwnA67%uBG56j3n0cyaEG?U$TH0~+-dQE5Wy<~6WWu=bJ_Dgyhv$KAFmzw`RNoe~cNwc#mr|r)i`DbzO?5BzuyYC;i zTffLQedj-p+s22^{pb9nvMTeUNL|-vsj5O*``+}>)m`TCPII1mb!^Vz4E3m2)q9*C zpt)+oChI*ZKO*-`4-7o|C-TR0jUC4hSTRTQ*rY$GWPWe_p^jPJz#zlG?9sw`mL_^9 zRcB>8AC}?$s{2gY=R9uHjt9m5oGLz( zi}+r1nrZd(itL$}S0|yL@h_~VrSN=HGk=owo!EtS>5~q(_dmT*9xhyZ=lbPemKE#R z%1kdW<9WBA$NTyZ<~1Lu9e5X6!gn^oKOvtj*4O1#ZB5#S`M%ct!I39wns(_KEb+fI zY5B&k`ULSstltHvKTE6$XN)ToeUK`i{UcJ;hv}|<)`>kU6!$*5y&_F3T;$QtPM<%U z&L0a-3VHSTlxo{s2eyZYbsL){Ww=Vh>!jL^U%brZ{Icr19sm7yEoOHmm9mPc*4KX} zU99#v`d>JG^xwi$&kkJVVLKu4lZS16!SmZ!&Mmw&*Rj8T%Hww*UX;j&?>JTyYVfjl z&Bf$5_tjtg+!)g{OG0u(v(LBFS!KS`tN+XWs%`mu;ry@p3G6WkihIs|wu<97Vp@Ij zV`0p~7PCdiL(kbPS#!+$2aB=AL5cKC|BnGbR4R4^Uh+$^2sSNRDe&6cYTcd^gH6V7 zzH`YwOba;^Si5vxVp;te?Nwj;&u*-;VHM(>bxL=Al3Do4=|7iE+NoO5lz#o;fo7TN zsJsVaOb>6x?EUcM<*N559;9S%i(9v0m)x$P6*Jgs8|peZ{}|-fH@^KQxafgp?ucy#_H_I<8PAl<9+s9&n(5M3<9M!Tfxc0SnS0x-T3wl* zYbQ*5Vj~L*D<+oJ%@K=xc=owgexUwFZo9_YGv%M&Zd~JNdB29|O?pY1j@qNmx3)8! zZ;rpTkF_%D&!&AAEjL{EADTJ6Xxrm(70b^PKet``WBJIcLV9(I?TY^kO?PhVyRrV? z?h4s+2mbE|^`R%fKPk2OhD3`fuVs$Nqc9EzhBhGv2BpcM`Otbjf%=Hb8j*6*|Nk}} z*O|YN^Uwb~SN`|j?|t8Y`=0yzd|^A| znnU)_5}9@KCf(UHL3;LV5%a#Dda)&{G7|+4b+jxtN?B4_)Z@Or{e#lG_%qu251%c) zBlG+jpCsoVA2;*3b7$v%E)b79xOjK!9-;L~lfq9|@V?`GxYg&^E`hl7o9-z*?yV4B zzh|e+_eZ)uKf>niTPqW|XRnOmp61yX1>QY=T7IfR{GM*|gtZO1O1Vck^|#Wix!vc77?_E_^xFM{MgE z=PH}r)jsM0w-axE^tcn2+$JWu@oGxYX64$r`?Ko(z5YB*GPQMnwQB3@s!N=WUj8!| z&id%VFF2zxiNngdD(k3M$ES zZmnkhvo@?}&%O=L=3Cd5Bn52#zM@&C{S+VH>A0)cD(A(1`K-x0rR=iG$F^BsnI?O) zrcGXxvCR3mr%=7?%0tj?hEN}i?qT*8~bW&61XA+ON zFBS%r;GunkJ=ujPX#Em+g|RqRGC6Yf{?F zT#PyTOMYuG0puHV&B7QE1Uo51@&a_+r+_2sKPr@J0nt}|O`!SsrK%=1G( z7^sQX^vyp1`Ih(xSJwDL(?x4o$$I~QxvI^g2hkNF2e%t)KD6fA*ZrQ8 zW!9usZ#90{)j7C5WpLQFI`SdE>K~7T{WD*y|5({FU-O4$-NFYpbt@l)SNI%!{G)zr z)BO$q?koAt$h^#RHnHrGeC3@@{Sz0cf3W7s=Epy*IdAeCuJ5&Ey&w;6%o7^7!^7gqHbD@6o((Y4biBu^_Q$MJ|U{;X?OA_ippsUg-OfdEvGnxl^pK%}OuLsDCL@9`SJp_cf-1 zbp07O56JFYVbIl*cyzgM^yy`}vvy~`wQSGX<1u%3S%ij}>7HFDl3NcvRA|xou6h4^ z#FtGvUZ-n1)>W5n?w*luC+GAlvME`!@0aYJ!)U&NvA5VT zVL{1Hy-?Y`Rg?b9ovOZcw&i+N{kGKasy8O}|M1qVv}BczIC$*;Z^^e^i6rR) zhS|j~dG^Y6pYk5<<=)Z{RYNXM_v1cc$D*zELz5+7zv}JF%_rWz^nLQ}i%wB>mH9>U zMg4WwA8H=uEx*WcN+Ga5HukTCOYC8ZQ?I0sKUmHE`}l+P-0a5$R&cW)7g!_R{)M4F zaN5JPtmY&9YmVq%3A-re>A=2=q5R0K7mf%0nQkfD?u!;ouuc6G+4AePoJ%Cz=W|Z) ztY66`_Ug*Z z5l(+aC0jb{GUuyr`DEwuPx!-cv&(nv8&hLX*Sj0qtmZtu+$ZB>``^gGB&P5*Pwn+e zO~-iFsV(AJwfAgxM9qzQw(}nq-+W&Zdwcrk3MTESJ9nRRZDBdoXuQKOL1cDi$=(Ie zQ&*@)ELqYvrKP#V=lrZSiwX*uw$`fuvz|Fs=EMTM=BjJ%_P26QvyfexZHx%4cjnSJX+pP2$WGdtG1HeWQ(UqipOWg|cpz zZx_mx;NIZlz+sO&=A0xxDT`$ByYm+4djh#J@jah1xxq6@rG|-tL6VJuff2G|r(Tbv-ZQVbBrzw) zIU_YW8#1y#bxN$iu%k%Z{mkvLyY4#a)+sEi@s8Gt=9(pNRKl}I%~jvP_d{Mp+olbF zPvousr<}jzg3F?dN4)j_1RgWq>>cgJYvOzU%+0yqE#KdqdDp(a{tr{YA!X*mC#eyB z0U9ShI4sk)>MDvco#5bkT&lh~@avC#+Hq!CO^I7-j@&Okof)Ao6K=DNoBdAV?RhuE z_MKn7MM-!~LFL}8w_9wM$1M%5+R=5eCiHvsozm@mt+i_|?fS4S=H2f@={L^Ag*-pS zyFQCmZ4JL%zFBK}>y*|wsjC;aN&OB!y{-EXHI0N6I7r zNq;%-W$I_B_kIIs%<`R*YYy=qHAtD<%u#99U{Km8u%q(?oAV*P)Ih1@EB<)Y{S=AX zSJ3rhdOe?-v+99m3pYxt+|Y5mImhW7i;>SGz6EJQ=dSpeNiUSQ{kh>xvDDMOA^)_u z+`q8Z|H2~m62)7OOE(_R)?&H8xSnU?6XR)e2TekGE5c56PkejBGyeQz+c^?xs_jRL zXY5x{Gycn;v!Q~qoB7Da)t&Fl%l2sh>pPM9Xo`@2soCyuy)$~k?7!!P8n$n_Rr@PN zu*K|^JZidqQpQxUScHK=PnUr~0os6OM@&N1gj~I9ufBOtVuem;i$M;bhk|yZJ5$nR z0Tl@*RUO_due?7^>Nz8He~%$&?3T5nTi33Q%DNb~Hugo++7&7pTDNXRMNiMl&0f3v zuK4cSTXl6yeg8k-H}j-QCC887KU#m^+x`7c@%c~o&*xP?Z~wpZy@a6d!~N`;_4?1f zgB8w7UhQGnW?@PFVJ#W?}q-LeB>8<`VJgnK5!pUd9M6*}XHUO6*kh)IGYFDhekYlHvL! z6eqRBDo$7|j1XHR`B={e8$L92IlYDMXU`g^CdmwcYXuzu?EosMYw!NoWM$+}=w$la_h@$eFe>n(2?<%bTkTHy^vr zw8!><^^)ZD30Xg5FKuCYS$S{i$GqPAy6eSRUe?t4PcZw`DitrXw z7fQ94Sz>;KH}=w=gW(x}1bf$~T#|m|dAq~y^{zA9Y+o7YMU~bED$g{T?6a`+>+9@o z=6SmdVx2#4Tg$uk_IA^pyj=y}XSebCu=a{ePnQY}RK6E^H79yv#q+FXoTZ!(#kZGB zT%02Lpw{PK#-h!ax6DX0QCo9vmEY-%{#l{6(=Oi4jSB6}5-?_d+c~eoByo%J*)rvv zi0N|ELwE7+Ud^U-ENbzg%KCM;TXc7t%rLq8CbxUpq>0{VzfN2cdHGtJ+T__iI~Jy_ zE_xhLe~2^TdAx9^`sA_uh+|Jh?k|SM$l}=^5rT)lA(Ur3GtU%Zj?q!_&ns zlF9~qrqtwbU*sb!`>pWux?B6UGOD!hR5=@a zY|>NhS;2-@#+hAEq2Y$yy>~tCjX4inl&d+y;cefTah@obnB0G9@}dU zPdm_gQ}^;Dsd(P)2^(}pTPurM=0zJ#@?IJJrQYDkQ?`WFrdKa2Za8y0_VkmNM%5SP z8}qHNG*ue1oYZM|yp-b|DY$!mmhc76jCI{5vpl8u*vK+9A9}Nj_v5=My;FUZ)h=GU z#r|G;+d&h)H?o{Fmi10t73Qhbm$$8Vo9*nLyjJ7h^%s9Ema2_>+;8Z+_sSL}+1j0A zcV>AoL_Y7Yzf`s6(z*)I$0c!jc}*OyI};+()o!ox*#0;KZl-KI!3 z@xA8ll;ANhO*!zTG`&g9ChO6xiAn2Y*M2bv}a zW${@*$w1brg(qE3Wf_M$^q5!gK5KS6CtSI4MYHD$sj}n2>*`fI>4Z;T74`K)})8uUo}!Zp@X9pM9Z$=jpnePDPe z^;b&I@91~81s`XwE1dS4alYjCwfoixPiZvV$`!dL&P`?QLFSt>iU%hpZmPFj9&faG zLl?)UuA&)Nj|UqX&%R~rrIWu~^LO_)^T}G>$JV`bm}I~3qdMc&Z7${Ue#?@gA|A~; zcqg@Z0o&$Ekr_6DhKUyfmd)LsaZzcR-}SY6oAv+ItF7L1&-Iedw6v2OL{7VKedje- zZhftF(@OJj#!iE%;$J;QuUGJ_7gc{=T7M~L^24n`6`}L^*2f;6e16)=_cpC3m(R}p zTs56<{U)!l$l6;TCwP~C%na)mpSx|>4B4D8$$OohKCJr!PLx#j$X$O~{-t`Z+yeH} zs+n;YUsiwF;1_SLcf*R-Fz{~+J1c=@^Z zpKf#CFA|@rXXSrV`kb!lA?~ip+=t$B8cX)6zcJ7XyWLTL*x-xfZIZ^4?cZsq?!{UpgWhnQEzKZRn?BeUPlt-Z+od874|dS79hg?T +6EP z4m?rkZegffRh@cVr0C|JxrTG=kN9&fUcTh# zM~TWkuJk)Oy^_PSoU5&Lem)nMvS0rB^CJn}i7^uHN0)!(w%UI}Kl9I~ zbL&5>uDAL>slN50+;Y1sKd=9ho?CxqE)9#(UT1dL7i*c(qbv^}P?Q-U1~zUr*J(SGLgXQr7Y{bE;WinqN}iB6WT9v_q_A z;SGZ9=DSz%9pc>K7W2j8X+4vO3_t5swvT2RJ8nGvwcv}&b?dkVYhpia-MuX&uP5m4 z+cw>;;#M*ze%@NtsZ#al?aPi2)25s%4f=ONXXQHk(mBq%gyeEv-6DK-%tGrIWYv#B1AKzBx6_YPZr0qT)RevM& zPtMtW5n11gV~$#V{XExP@+SA|ss|?x#ZLdasrx2dm{VFYp(Ni)@mh}bG+ntFDo^7U zPwZN(vB6C0#mu*L+Qu@fk+sv7oS(Azgnz8~{z}eQf+q`A^|npv=6U|_&`CejwI^Ln zQfCXjU%c$evF@9%Q~nedWp2HiaCu}wPu^DRZY0Yd$N9}X;Eg(^$P-xY44x)2`B7wyz8VU%d<1?bgNggioF7pAZ&S*zzZJs(Z$x#I1*p zU-VPAzqNCQUCP-}>~gNv80ulfJ3nFSzV^5?<|muTE>lx08>1vh07UxBO`~S-Mo`3I9*6 zQ`+n+Hq2jU=iyr3H)*qo)wIT9)|9nNB)14m(hn-iD+oJ%YUi}P1^$jo@@p6Ut^bl) zzi7v1(RO7K%9*TN@j znw|Hrdb9i{yXX3Er@c@7cW||z8~IjtNx^A``m-y)RfhfG{;T+YY%`TJ6>KP}E)U-8a&mcgN^e@*Hqv^^83_~r7?sN%x) zkV<{s1S^MNF0Vb`gEPCAFZ!u5Rd(qoYr~+%GsW6%TJHRn@0PY!vUG6G>JPg5-Em8X z$V48UeZeOc_Ra8re4&n|DxS0VyW)d&Yy#^SUX`%r7rV<-9n&JPE9~WJl{2^2I3JvH z^>*M5GyWL8Mb$6uZoILHsIL=P9QRA?SZQjcI8DU;YN(x$X{spmmL<2MH5GS#QH$c)qIWxTLrgH&H1q8h7dI~2c{Kdy9i}T? zhpz5(jJhfGiTA=yewlNR&R?vwpSFAAG36J_4qQ;&<+(6Tb6z^L@zw(;wrZu+|NX&t zY5T(Y{8zMJT31Z|55B?Y6sbd%U%jV16AkKVfwL+BcJ@a~o7Rl#&%W6c~q%@{q{Qgop6yvvJa^vji^^QnC#Q3G`kZDtX4qTTVBgl6#(lx|Cgam2&*lA({wIIgu{rlZ zL!Ig`=No3{Y#r_$ZVtH;6!)Qzan;QuzRe-GAMKz2D$0iS^@FcXRX=3b#oxDacy3u) z!~Dsjs6OB@W0bvzbTr$`KVm=gWES``UkT~mvrlr{Jtpa2zhBPPz1rBmQe%l|PHB>= zNXEP6&*q-!PkNN=%bvOHa2b2t#$cJHy=N8ui!fH*D&p$OXU{ng1en}UvTz# zeh_W`r@7{u>t=^i$!jrQ(JwOtsyE*I`6c+5WKA(|#{7lz7w6mC{N}M;ec)e!7&HIM z4Q`IyJb#@U_X@12s@LQGR>j5Awjod9aq^$5nIWq$ewNC;G9loF#aXxc3yEtu; zRk2oYIrxpW$}rZY*yQ&Ua9=P`^jgyPco;Dl{E@|m+bgg zJmY)hfxniiJuCUzw@6v}E}0&3rt1WwQ`I~*p(iKbYrhQbKO`{eeb}`6)dtZW?$6cE z--;By!S{TMrbF17C?*%a3E|tW%|7wmvZR$=@rRJdblz^0@T1?oH~cvr@k8{;wbdVv z>Q79oZ(aLNG|H*|gj<5)inyaQ#F@?o-=4x={H*!Yb+5*$k@jnr+}E)0U=lv@Ie*rQ zi}{t;W+zNi-!Hz@yJh)?9WIx?Heab%J?ePG@zc>whu=(;Rco5A!CYpjp7w}+g~ycD z6Z%=Myk4Hya_Dk%3Fo5|S(1ik4ksBm1?sl$xippQC;N+_&Xx0;O%8Uhn>XRxfrAVE zPcGVfNiraRA;YJQ)AbIV%XhTu&iTr7NIl5s-C?~g`q>B68hT&oOZ=7gUH0E`U7hT) zpX~KL-~FxH|4qN=_@8Ncee-nz)rcLuf_%bX>{RBQzTq6cVfwd+mAdn;Z}gX})_!Bu ze~itW0cinpRDUyb3MJc*7mRI-pjH4k}O}YKRmsVTS?(R-^x9w zMA@uxt4$zUQte<@BDeuA8`*b?F0wtxIp4Ti+ zOX@shw@EhX53^dqH^G}(tm}^cyivsL`*g*Nv~6oX-18CE)(YKwy}aOsDMPW@rr`4* z;#PP?@J*P1JlO8F$z=6~Q8Rr$TiBaauMNF*)=r(8oX^h_eywin5gt{i8;Xt1vVtbR z{BKE0$>^rKm!;2@Nocw}mtmb!Scu)p^Y7M_wjW6BJz4ptaONBPttaCgPS1>)>laZM zdS>VOGNDQKtEb+-$W`;_!(MLr_X{sS5q?mZX`J6!s-nx5kTxgTrads$(sKDKhiSnK zbLMPPW3aYP^=)X@y8Jw<#wOwMvvW4zQ}5lHJO6Z{`i4Wo6^mvY-kAHHv&XU0!SqGr z>+jlUstby4c)xdk`hZ+PN^L(*m*F&QsMCfhqk2U z;aZ+YC)}Gl(dV1QwBo48hIjDK8-t?eB(6osbS@%O)tR7FDU{q(h?$d`k+A9n7 zU#f&}I%<%Xr5d$7W$D}#7D*rY7JIBnIuWhwsC)Zq#MS0qkG2M|ly{lWc4Aex*?DXE zFJ9i@Z2PtgymhfE@xjmLT)gw^d-Ucj?&}u_HeTMbcGV*8Kl@apXFXSJcI9U-uwC+S zS42|1#-DgOPD3pxxvR~Oy_`dY*{qkx7d~B|8L`yg=i>RO4_n!~YIHmH@Vfoll)}y# z8_@1sr}Cd?+Wi*?PUV*+*h{TS&)|#n+~&9?n@9PGKx)`7lS9c3onI=_l=fa&bmMqM zzOO^v^`M{X5&ZKHUshXr#$o;K3(_YHK5F0m=eO^Ft5#0pr5l<(lddnfnxPy!lOJ_p zddu^Y3UhV_hKYO(3~JzkX}hz+^;5%M)=OU%wN3VxsK$&b;jOecHOrmrmiky2X}=Y+rJE`#)dZ{r9Ym zr{yS$|5)(-UG=W_cZ#Q%KmYstt@(YX0m+LKdC?@p6{{tnm?g(`;Ixpr@xd|isO1bTc=LD zC%^rpEa&~B-)9_IYp_rL82^tCx`O)L76!>rrq%~VZO#ckZMiM&1=EwBb*rC+cy83W zDpc^}(4n7S&V0G`>Cu-%w~p?7^X1l~OHOIuni@`=34OY2l9pzb;Z_fZs*h_L)@eF# z&vD%zK4sEkt{3y-XH=I|Wv#4RwL3$rb<(b!mCsxkpOKR5Nn0X!(M!v*Ma?93^LfsU z{@!&Q96pk)LG`a^UMwlSrujUi*z@GnkZtFmgo^|}-nw6M!*$anTchL|N2jD~l^zZ; zGYc^`U4P^C^OLKMGH0c{aL8Gox$RnT$lJXiBf7U9s=71#&pF9yp-NYy<5rK1>)2vn(C>&;O`g+9gvcU=7%h8>G*w@y)te><^F+uO!xi@?H z?W0rTO3qwzJsPFnne@W*`_>JYJ{5#VZq-@3V%gS3SvQ&5Y*oF_S}#k>U%F}cf3?2} zd8ePf%GqtB>7XG0yL_Qv{C#0n)~i!K24DLrS?A~IdGZJAdbJPUlKYchhVD^%aHamY ze$}xbTRZNb_@P^;{^+~WGXsBdyV-RCN90rf2#QLsf9P-Yuje}Jmz{4;walD#rAzW| zShvqx-mn=CQ!}r!$m9evGOzvLYQ8!cVS1+!YJZAg&HrK*<`tDWF-r7cY*R7mi(f@r0-*LCu++P!d zndg*buh+k}>A~!&H|v$oFDqHKYUB5kzkh=sr=>|-+sS0VI;-0dwr97?+cS?}=-cpp z-(s_W_vQO{>y^ARyNXT*bnME|48M{ZEL^bU>)Cu4pL=`N?)<;JbI-a-h5IK|E~+W| z&+zv4uS0k2_q-GD=q>usZ7I!qRBcDXn@=jc#k780bK2Om{j&V&Ej|f*uBI>8xOq#S zl=+VMU4pNdm@`h)UopElTw)Vz`KSGdi;Ap$zX#}CzNJ^M^d~8>etzMUNmpB*hkn0# z(Yu-bpMolL)kzPiVwVblDko8>M%iCX>xTbY|kzdTg!s&jD$5ZjrlJ_&@xx2#{XZauLO!%GqCA@G& z+u_Z;@ixDb<^FmFbNNrY7_7H<2Yc}H@~!t4sR)``q8{ z>TT4Kl$ql@Y3uIohFhc*f)1O?ytIuwYCH9fS$*F+H#d#&EHdv-mQqk z$xp>P_Yd2L$oQp`V`^$UIRbZoU$}6(pihXlVRcCYwwicO>^ zAN#y@O4l~jb=a^~N~+kWe%d&FPme&6a&&VkTXV$W%PGnobsBkwCrkFu@ZNoTDf6Lv zLBWa1R$p#>e7VQPKxRjYlF)jkLhgq@UT3UUQSvOmdV0IEQJJgsW78#Cb8PpoQIcO! zRFwVt$+@i-rOP{i2&a7URQf2gG>i9mXV~Mq<*$!i4tU+Eq<8o4m;a*MJ-RC{=m!5f zaH+S7LH0lM=De`=%3LaRv zL&f9DeVT0jKfQEW7V*^I^}Kt+wWw{*S?!5FalI;E*6K!{RtxowoYvcvbmU}J%+-+M zNr~I8-~5x$?L9|#WB2#9hC76-!V)tCo0iU9yCunH^5x1&TU5XH%!!}O8~Uc(Y5RiH z1%nkzh5uAb9*^18LN zHYG0G<{Nt5LWpPWw_9bdUXwS(eNi)ia=l1JKYQniRhcWI^y2hh7N^Cv`O3UXQC8Pm z$J4)WPTo!7m6I+6x!%mYe{Y*0%tV}tyZtB+0m8VbVEsX)8ghAa4C{h9U5cKk0je7JeRqVBpEx?f(W*X_>;-?FONyF~wj|HQK5E$bKG6z8dJ(U#zP zzxbW)+t`fd8zW!b(0O%JW>1cstjcQz*_)4r)-hb`s{h4zQexd?bLp?Ldll}O__Np_ z_{#b2L;987A9&|2{r5h@njxNl;xfe(-!ASbXUP1^aPMotjBWe=IlEq-=Nfh)LpUKv zaBU9fTb)*h=#48_a@E~K)C#!ox;U=MyL`%(v0d4N1m6s_TAC)b6umO24L+aa2D{u}J;h_WOq<4y4XnelpkCcC(ddQS-Ce zd7R~}E&Ch&ju6tf-2R0f)S8`q=c3f+E47cAID5K% zh5s-!FqBR{(BM{IbUQ@gU*Fm%J2qXiTIMOO@PKW(XH92u_Us#(-jm`wrN!^C%_*8J zdUeIdrF}Y=_c8S!h@K)jL&WI9zvhRs6(SPfJo1vR`#mw3`|$kEH#ax`e!JXu_219) z9cR8Ot`itckaU}0y!pGsdx7+pSD`1^O>&i z+PqcU6u;v-TCgZuI`d8}+5jZ)lk38fk3z4c>lzRo32hlW#53KT#3xERq>v z9q`u4FI|U+d0J#i$ZbuVGM>{66<31=8-`;kKD8czBJ(Gl@rg?<7y?4m*5JBi+S`ul$7%_dC%! zM?0Dl=RDkTe1e_W@<*R)S!e&4^4OSFHOKJCpC2I~80H)6{H)rQ|8B-L;mzJ@^K9RE z9JK7=42d^X;V<8i6fR|Yw`86D)h}ssbFydkm=>$l$Exm~F}-19=f_`1Oq`!6L~=iM z-}S7ZX03y3WW^N;y`ODc>O33{-{o(K)e;l`A6@u@b;9$ar=3}^-!|}B&#?ZNz1;oT z{B`bXeanw*xEJjD>TBT1Rm)y}t*R+(le=ES|Gx6n=c@%hS#4d78a%m7tFlDzCR*I* zx_o*{czLudJ4gLAmZpz!0y%{O^BVK0JTRxBU(EJOA%r;VpYyE9r_gZ{X>pN`irVGDTe+t`r&U@!cK0 zh5ynTl}YxB6Fioc3I1vP#<<7%v1j42XBUr)38aaC&^y`ovgeDTSN*n!>sCyg)bo&e zPJYy1Zkbw>)zuTcPKw-iz3=wOon_u)`>)0o4Elnnyp%7!pYipG`abs({vu7!+XYQu zrlwa*Xt?@&?SB-aIYZCRJw?1(|C@mBQuig{QTevT%t8V8m8U0ovlw>=PCG0S?ba3R z))%{UTfx=VeGeLMwXaKJn#@ukxn^00sKbo9AkHNxo@uO8?9I&bGR~XOxPRi~2^LPv zl@*xMXD<}<2wyaF_nnQ*?|!u=nmxDjtI+!0XSwx2ddzW|&wABKTN}9FUElfQ){d6% znQ^Wi`x2%d;#TncyhG*J6P-y{_|9_#PZV&OzlZH2YkDF3qWR|@C2Kb5XZ#DA^q-M+ zGfV4!Cf=GiLFX>8F)*kJGBDUqzF4a=`AfS@{n?0@<-(yN|M!}mU2>Vxd!m+Wq05U& zC5=tiVcD*0wz#ep)7IlTIe~A9acSD6Ch2#(JGwjq3!DqZZGzT3sJg@dzwP-C^9qYs zdyOYA$z{s?`DFU{dw0see^ssj^ZDy`hNzFNJymy97c-W7?{Ih1G@Byq@;Fslzc5Z{ z&x@X2o=){fO^lpvJ+3!&7BfZvn9`N7fk{orO=?nLclFQ9(;l2!ajbwv*(m9TR*F6qQKt~*Wbm6a}s7T5fhh_{bxRNoMLXtK|*DJ{!YZIl;8IIl~b zxp!Ue3{TGUf+=rVIBLsgEz2ukVsiYVY~wV&(|+4ja&;$Os*ir8xp?VeDVZy0T&5?! zGSA$$_;$DYrFcz&Z>k(RR=g|yoo|SKl-a=0sjg($IKktTOn7lA=Xv3+>z(wOS!Q*= zwql$gDtj#`bK9m(*_+MQY+VwoeDzYQh2Gt%-=#c*i!YlQ#@RfrTKa$Pr86ns3FXq8 zqcdl;-Bv4DP}dfx{cdIbqrRClKl0WxE#%X7yf*i!hV9FPy}N2xZ!>hAuu4yR-O^ts zw;#M`kSJp3DGL26by>==cCA|F|4)y&MATHzpPY6xPyKT9u9sXf(evNmYn$0-wkY*y z=+n)@Gh919>fXH-kZ?DruQhGA?XR%uA%YQ6_Q^9hPu^AC{OpO2=Gy!nhmGs|G}?o# zs#l)zwmcPcTK2-*t&S@-oZ~Ev=U2X*EdEwOTV;Ci_b4eSpB(RmZhvnOy|@+Z$xIyUj44@_O)$=ou99I zbLkyTxcA;r0*R+M92a94BJ#bqiSl=->YOasanp;xWn^)-ZubUlIvi?_W(Awxn zS5|U*mxo*~3Jt%#X!Wd>X_L0kj0)DPyqp-tK*H^$pz(o9>2uJ*Ze7 zJB#`*}Fls0szIFiy%4 z3%S^l;lFgv#5YqGOw^J*r?u|>@tE?2MZWF= zo#)gOekS(2ORwctWK7aObwn$;);o<^yS{Nf#dBb4qPx!SV%(DR<`0^J95BZ42! zjkqrK{>ZIqRqEHwUB1ii(7W(XchaE<+l%#vC#p9-kkIG z**(9IQm>k}`RAkJkZ$LIqvG=tj;4!n#JzpUHMQYO3Fnfnm(F=ibhxE{^G1uLUsF+! z5~K1#e%ASM8BcBt9Q50e?;dFG7&i#!?i-JWl3tYS1i*ou8UGV55u#e?-Glx0mp0lwzY?}d=8?s-u6zOE`@tdg2W%g?_jJtP;Qgm3@FVx$Np*({KU&ub z)jtR?JoGtyO3kM3Pupfcis^d%WT)JzkaOpS8~JZMFO`FBFooo$X=lEf*inn+op9y}qBF-4*%#Id5psW6HG)GkcnHM@*!*XWxR1 zlr`L4ZpYrnUHq_8zI;ND(3kFnIk;Kzr~PJ;{uyE{0*h_gH*Z)fCM5b-@0+dM+&y!5^z1*rdH)AOc8=e%GK{*@_riy`qVM${FCrlyoS#gtBo)VA&AMnJ)z~IBez+i%+iGvTSNpbS7 zD}vI<=lYd6<|WhN^Wh)>SBpv5$^2F!hv!9SVzg23d<7o-?}PG-C$JDK~U z8Iyg>i`R>UQ&{Q_Y1ih z7;I4V9qa}xdeI;a*0=GJ6qEP_u+WD~rZOl^M-+W0CQWv{oXPZgCRpwH%dSkH=1g{+ zr8(LDiUZU4`I8&3Br~xu28-}twPs>jI@xiSDcFjMSEZO3R!nxBr9b)3RV$|VtH37s zFA$vEa9Ln-%rz+{=e1y=+1K=BKv`H!h=CzTlYv1E#R0V&CSPn$JwEp7cH^&q~aV4ONVix12 b$#%!p#R9xp*+AlE3}y_kgcumyE`fLe?S@vi delta 19980 zcmX?hiuu4M<_+46T&r%T-C$;AV0gtjd1IR7WIslcdcEYF#Ny)Ku;_5%aFKs~GgA^| zSd$%^yPC~~^g6d*3)`^8Yp(*ME@O_%ZN8I=J$`AIP9E`oFJAqZec!jG{;{iTIk&94 zb@lqc?tk5OncrugbP#GzPKBp?YkGZn$ z-Ld3>(K?M8heGQ&`e<_~E>2+EoV4>=<7=l+W$e#VQc`;~UL4xf!^)bzQRDG3zat;| zdSck~xXQhgcB)RhEn5+Ou(~3??OtQ*#c&Ug>F7e#$aA{pn z;>Vj=nKP|F|Jvj^DdX6=*UuKclX$dOZ_1s}#fK)Gx#4FRTk@spWc}suvU_8m`Tc6j z-7PvjF0JJ7sa?As7p2cpj1i1(OYU5HM)vQ#d1j@nXPn#BmneF?wli_LzNDJ`_fHq+ zD!Jac?Kac#qjTIw?r6srd{r|G+UBI`>+eJT_%P(Egp4zik+=BD*zSg66zZ{BL z_RvFOx<8+7q|+)9O1PKYUe>T9K{l9A((D zIkafn*42LVxGe)Otn<=LT>XfnZzFm6s#LcGfO?R9v(0k#~)2#@{7w%}d;Z zows)go%%nqYyC&wtJTJDVy+hT3f}oF$y)D=&c;5S#qYyilNEmc zd}f?(3!P z(I&=|4xZsq%1y3UWpj7VKC|8`MxCqd>FT}$kv+#}9r_hCv8HwRoJ!1c~d9)dX~TJnoxgyUYX53l?B?{ zoV|1%Zcn(z_QLJSwsYbyC)+sOPx~m&B&v4S~=+=~>XX5j|I{(Ph{Q}iGXX>2! zn?3wKO|owEi~6ncgYi(Iz~L_!?R)ED4khY19L_xywLo~{A5r^*RiBc~dj$Cfj%x(5 zs5tGHzO;pV;!)N)N4!Oznyg*^!KhKp&i}FKC9B}&hwQ7To{LlVw7*fzrFL3k!bRQs z#n*rHpL{Xt<#%p_XQ^im{3gDb9i09*{gjr|`IqW1&hol{Uz=FZ`*G&P<-!Y%gZfKe zxXZ}Boao0nDeFbI+=_<}l3gA>jFaH|kfv&jpwGmW)UIAKWG1Pif*cas8f{{_I}yIotBQXV>4qw`aU_Jegg1 zM(Km3YsX@^KJ(=zDYhOJnRe#ewC2=1WfL0mlaC&gZTpy>DD&z$52yY9qxBPXPR~l% z5y6@Kao676%2%ZK2?rioeL|~$+YYX?JLP>|x!qWFr)^U8-L2d5IwIOb*DXydh(ES5 z;AzM9s3wD>T_Il=tgFx!a&-Tg7QEs?>zgAJpKM*anMt*6<*p0TF7G`;rBAdPrA2)C zJoWn@X8xpw%?}q$>fO!5{qMw~)vA}bZmkbbnPJU-;z7=-ZL7MzUidQ4&QG+5Up&)i z(lN_<5^B;>=G?xQ-`}1VVD@gOhQb2xKeH{K868mRHVccL^^Q6C$--WDp4R`necIl1 zZVc|6InmcRrRqq>@vjM+Z(mB9o_OettEbZ|y~CU0B^xqCk9`gLm(%g^q`(9FAmio9 zDNd)0>YG=aF148H@$2VXBj-Gi&bOOpe)!U1X6V2ESnE%r4QCV_^&9*c*P1Z*td%+N z%S)PD@>l=&Ciz0nG&@@co0z4hojOIepR~JX#k^w|dcWt1;;}u2=j~ixN$bfR_*ta% zT#)yM`c40me?CILnJ?{B&SY9& z`bw4D^oI{-HKqpDGX!`uvv4smaBwg%FgP-pZnK$vfRBM;jw}O%BE#gutIG9rBROkg zjz|8lzw~b3@~f90e%-lmli!<&Ynj%4vZp(Rz4xV@p0sA_lT&4nY&`uh-C7;{^lkKQ zFE3{=-leK7i%rg6T;g!sLG!_uucvx%sj<9rXeBluJw2w zZ<7CPcK);7|BW~Qyt6&-7f-|7PuCQ#J-%Mw^x|Op>KnTk&bl7*`Sq%wyr#b`h4)Jy z$$z+=x5lnRp-#Qy{?YfWA9?EHP3sp_{0lIx7dgWBXJezj#E;1e(;oXbb38oXR3R51 zaydTe?EKk3x)tvA?+-Z}FZpBBuYaWi=O5-b{fMq{HLVYK&8*LVB;WMI{mAnKGFfWX zk)1R%DA{AJ($$$k`KOGS{UwCBP8CHap7PrITFW_1degBf^&2#^c_Y(L{ZeR4Y1WkX z-Rb&PdZF5;lT)I7cb=Vcc3F<$)3V@gvo0+#yOHLxSkaehwf4k}uGPHQNak3bEA`yQRd z>G*fc>^TK$0d}yd8}5ZPS$KKE*osFcy}uPlod!BK`ha+pBG6!XC z`xfJ*Jj?Q|ufA$*l(5!Kzx34!uFf}Qo4U-Ns2mfC3QuRZjEFI_4_&}IX{Am>m}>CR zBh2qKCfi8(F1s7(K2@T)-g^FxwOR7q(?71rlH2@1DfQstO76_7F-KIn@An^nvCs4T z?7X>Q2@6fkpZIx~JyveIrurcB^P}SSqLhBc^2kl$w{&l+mq!=%zuEplZ_nXF{*Tjj zc80%Gc6j=FiL>}dY!<-L;mtd8L2Gisq9ugTy$%aaZwn9_q*wg-f{tn8@hzg zPb!|V`HADz8(!xHuS$oWm5_e-I^sk^Sp9?Wi{6n&dcsZG%lW>1FZn9d$+K)m#>B(E!|4)TbPxTZ94AU@QC`j#qHN4 zK0{|+@1qfc6H6QuwoHv&aYXg*jl-4`qV{fJv%0i#$GQs+VYW}_dUD(M_eTf?hs~SL zJ?%(+>(7Ao*R}=a?Ahvkc#0}#;!3vX2iqO?FF2<=b6R`R&X7H2#fh%DVl`}&B3p}! zx)e_cT%R;$L)YJC=Jx0r*E4rU9GV`#YY&eer&3qd&aaqvrCA^S?juTvop)R?T4LiKR2lce*@CUg@Fq z(lzMi%ax#p* zDns_{u)HbFt9QLA$eNd=61+B2gm+W8(>Bu$Yj01=x;?4OB**jZ<^Q?bucAI}uyNAS z41O5P&Rsr}*IV%hUwzoKZ#8$HerW56eb=-6=hObHNj@GT8^y(o%RQTTe0H7mzUh5^ z*Q2ifb1F8!ivm}KPqW?S^*O(-+}Ue&nx;U1=3Mi|=QpUHy%yeBSuM6lwn_82^^2a{ zFD-=YV=op)99sT;MPO3y^doCtzxv#M_fM<7L207Svt?hlem3Dg>77+yFJkfheZ^@h zv3QfiyifCEver#|rq;#0vFzYHRq-7HYhRVid3`RkdcR_A>O0G)@2_m#vPEUz>TVz1 zeeY7=yUbm>J>b^8qtVM&uV3-6%~&=oB7Tz4&fD3yLhqgF_txi+TvfI0Xi)6ucAt+& zH~TO2be#M&^~99*itVdU#eSM{e*fd*`qqmfhgn?n^yBVWX6_Drdi%(V;G#sU1v^96 z1PM@BWoBkv=bf z-J7OaFU|C2rL4a$N9en%HLF%W^}Tz!*nRD;?NzOZ{cf%dblq8U`%v_!eL2UbAMmR% zjDBb2`+t?k(`~mu{nwZ(z94B*J;$B!w6il^v%AV8zif&4Jonj);}MUNJ~27(-`*pC zeoA5P&f^_L5yyL$*A+#Eo^$%jmm6c{&UIpb?K1NcJyGrYskNWaPdZR>Y3`=ItZGv} zhDZt*#oTE5`l9hbW1~Tm#rc1%45A&$y2~bL^h}oV7w?sK^4KePgMTG^%fDsC@pj5T zcqe@~O1oiq=Ay!%4OTqg0PkMeS79>B`(UO|IZsDelPo-*l6Yrb;ke@c?IX+&ecZ z-kfDybnm<`yON|GwxF@^MjP)HrTCT0y zI_*H$gi6^^^8oAi2OG{T`emiF)O+c*rD5yy7xG7Kl)1e%M|-!P`tP^1!t19^{=rm{VDF%cUl_$-CWtzEc45Jd6A8IDrXSo@_P>4;Tg|B)^Q#$$-z8j~_iB}`X=442GYOAB zyj#`v+^|%3dx}6Mm$rSI@4{%~9l3n7+nuA+3V)woofQ@qyYc&#HLI@Oe-(OFdqe4} zSGMODuKXyxf+zR%f{#3IztACm6 z#_6B6Pj^; zZ~b`+$1huS@-LE?SABlmupzv#Ym@(DZi{C9hdTRu*guHQSzIr+{A1~*dj}!~Z4~O} zOxC)Ql<2bjK!4Ees69>PkKHA%%&idXH!kG$+IMKD`V2k(P1ovs>O-geGW%mu?Rct& z#lCUAi)rbep6?sNowMT(`(F&3@6}V|wQ;()=C@6np^8^4gx)(ZU9sidx)0r*%QGg& ztAAg;V%N#uY@Rjd8x9{Zu4kKP`k{{bykx~ahy9Jh0`gDqr1a_?Rh{)(`o#`UU*>K} z&Oq}_-;+Edu{<>o)VSBxUG(`UFL~q665G`8x>_>9e|Jn=ed+O+MOy29i*Fd-nQ_oB zv6%gT*~j;O|K2{kKQHo5>DGYljC&tA3G(F}I)7>DXC*t2bNmkvTMF)<(R}@HeXsB4 z8@DfilB&^fyER)wySdi?@FmY0^T|6bOU}!D<3IoC-i2C8nG6+?@(;5e)mInG^>4Z{ zV~O15lB>=oDO{^Niv<_mvpur6a?ZW@0~7zU%G~T1mq@VUEPwdi#B5(|a@kSo6=^+T zB0V>J)b_pK9H3dq#ar;KaM$Amwst?0cE0-b0+o_2pDw>R^&rB(_{9#X^4}J6>v-mN zOuF$dTkzcXlnb}t@&7(l=l6{#+^qGNk%ed5z3>b5Q?=QzoH}@EYU2DUkMq8tP0X+; z@nXCY9k;AYt^0vW^DTdG`KRWu7rRxlZMm7!wLWpu_DgH_9yvW(XLX&}6&3H7fnt}F zbyS|N-(3IYfK<^RrqkOe-PvxKCBIbq+oWIKKT_QaCfS|4B$s?;{Ug_Jzc_{Dll>FF zJ!Xx!U-$QvHpkO?nfVL)P964LvBUG@R4emHuInLQ?)jf$K8kv%WN1xKF_>+=WZp9m z)?)XJ8SPg(FJx}BJ!@XTJuxpT;4}84M;@27Nu0I#2n{r00HfY~;#+pgH z_V=fL5lMVEbCJ)@zzcQl=ThSmS9~n3T@=kS$zRN9snjQ5lkD2xmOq7_6;;(ubP7qj zC%^VMVi-WC1|CQxhdUQCp?|M$Y+vWk@q$L91F z=8K7&HLYUgGt!c`xU7#&&e_McIrM|g*FOwb)=yQORx)Fkdqiz?WbLVKiz8c$wy|=Y zn15;FMu`sp?AuN(4d*j%A8PqE{VT8HA+4%bTl?=fd*c)J9nOVseG-4PWuIGULd^eo zU*T8(UHz1P*N_L>)XuyHXp)7^f{KR zySi%L$O&H6uxLdG*QxdYWb5phUVT3ICeLhkZqDDt`=6_A-v6%Mcfb7G*82S)lp1_1 zvL&UR&D>KbPd>XO$LFR@cVfNqGLZeQLp zej>J)H@e5HUX>ypmzA<&zL)lrh+`Z3XYE|H+S{`6+?L1Bf-J+&N4dRfS^fNT+iWTB zU-zN{Umbsam&gCr>}@%+dVbtnSkLL)J-4(?Gok+Z3W2X`XAj-tWm+v=`Eon=yu8Su zyycm*@8vcA7WU@U+tcE<`BbF-&w%d0@;bW}kwUxKF^d5ZlrRAaACG`}UTbGP< z7VpYFRat4Mus~M(Nzl*3aZ`;sYmXf*v@xZXz`UOW$o9h z@HzOHCDzu_@t`nMc<8!={YM)+lil2$BFu80<=oZYRkFQ&qXTa}!zZiYlGwxFg7)r- zc)5;o-i-~xCB|+A;Zc?{cY}H!-b+u{S?(ajBXMuZYCTy<_&B7sfh=C%<|kx&BI_|HTXM zKeDsdU-=;QZ^^^bKaq#yKZJ9cD=5so{90sfPW#&mS=Zlj#`hhcl~nLqU({W|Cs|*0 zaAS4GSKi7+<{!;@>!<(ApIpABYNgJ?x4$NEuZc8zHq~Q_Hsk6CPJKr0&&7Y)HkTeK z)K>re_sX0%v$~s=ry8wWSX~##nUf!}Lu%R_*Ozi$CuUuqWW1+&myNfBU+ZVb+FL99 zdw)LC%DwILah*%E*0zk5XKb}r%@fw)jG3PiQt$fubmHd77po^6Za+Os$dsG$+|wgF z%NIR}a6NCaqI&Wk3ERnrTx)(yNBq1zJ@%rsO!B{VVWtLV@oz*TU%16gzG}YlgX4=e z_1udiPgW<(y>;u{Rw$vJA-!*Eruy{H>q{LaPJPW>X>de8tEW82vgh0>w?^5y?Rz|( z?p6Mr@LSuwe%oWEpXT+8v$k37S@+IMyv^-V(8)NXZxast_gW=+o(f6G|95}G>nY;) zzN;dA?|yT6Tc^5IbFt5>R>#LGn~igR$Fd$cE97wfDTBhV(&;nqJGx!jx6L&FkIjFr zh;1G^(x8h&avrUCNwS9;mrq26L#aZEz`DX-_2UIUA^;@@ma>bN%&I?myk1j5burB*$uRm4S-D}6fBR?+4*WZZ= zzn8&vN6qi?ym=mTU#j@BaV-6~?dw64ka_pE97ybqn(pG#emAtSVo8(ok+ag3uN&7_ zr0{9Zx@#cTKc`V(Z<0sPhxd^;(%XAO|II&N&l0(7u8!~I+3qJ!>sVTPhS_=e*|~(x zQw@$&4Zo-0{?GAj`33cB3G*Y`|0;FfcG1rhcee02ooU#{_>qC_=w=IrdB-PIupWL? zDkGGibS3?vpw8mE(H|Vs9A`|s_PDHX(UslZ|JYC1Z(4umVoL>E^BqwOCBH`ue3O<& zPCPE_B3-XEsXmG0FvsQNTr!JdSd_0^(vW}Cd+D%5cwWOVzDrzJrrADao=yJoBVZUrzaVlFPrmaNlgX2a;UpOD?nM6yaqjeGb$#k4-(S+VaBcgis*^RrKam()+})_$@x z;zn$}Qu6X$n*Gyv{V=K2JN?Wn?%gx3b!Cs&x;+%li|W{GP`1lNd+AD1u5^x8a8hcJ1T^(C9tz=E%Wuv?)X=`W4Juc;1A70VCXWrXoQF%p|z3ki{_U5e;f2I91 zTkoEV{Btj3_lIZmvYzO2W#{Fk%{QqtUprauuCMQ$*Y%IYDz|wVA3vKbQ}D4;@19l; z`)11{iGq5M9+g$j)3=EDfQh1+-o&Kcl-A7HtHUIdkZ8ej*wm8&LSL|}K&%9T&M5n%fbuDk* zy4;xE%gTQ4+PZbMRK3~ybuqgqX)b?flRG<1?#iLHPkY@=1oC3fnC#IK>XVGR5wV{2 z@vd(D#3DB3wQH-UWG2O`+={vE1;-+qkbA-(0O!H#ajSR$-0#5$Cg2C2QLpH#!_y z&U!ujVndC()myh{hH0~>6sB+VHQ;!6ci)P2l2xS&X$HEdG!C-8=4{@^67@LnEQiaX zZf@Ph>`G4;2^+TR*Unk_VTY%ql70QQezsYUKke0%N|8#O$p6~y=PuK&GbYWpTe{JA zwsE~gx`gIXPK{2TbZX!cU(KO?u(x{5f~+^;^DkG;3BKx*zt!PGN1Rxz^!-U#m zi0MR*1E$#%9ymXhYg)OdWO4Xw!+4Vmc{eY;<9AW4f3A_udaU_rDPu6>W42#T;nkPq z_cTA#liYFp(owGXwx_psz6sBZyt?f7wqr-~K6AGj)%Y|%*y43!lfakNKYMnFnFLOd z?LM;D?(UIa275QhewW;JU#5^%cZZ`@e%gvJ-m5=6Rk$jV!JF-7vx@)f#|NU$DQ!!W zcLiSNw2)l1>}plLiAcv$mO}-L4qj34n|2_(X4QSC)_3uXv+J~_j`7s1)BU`R&}2g)Djk@2;yxYuujPsGt9${+(JoSNy^c<)Zt96z_>=zW3sG^GRiuYz|5Lrt`wF z*i1NP+k%XXYiHh?YnOdZXW6cvo!T>B_*r~ODLpqSZJSB)ukEvRqf`ARpImFCwlAb~ zvWkg2hpuIz)61*=ldj!KzHH^D@N&7Sf5y~#9BYeziJzZ-G+p-iBVpb}k#mcGmq*qs z?AEbMEMC6bchZ$Z!qdXU%x7)9-L!AkjBvHfAyu>H1-_K63SSym6|waGW%-b*Ir9Qu z3jLbqC4SjFv}%G}(95EIT+;IcR%&+dTYhQ#OQWe}0-N4_5BiKjlEp$_vHMKHZ z=;qaImp!^1PvYwL3U^xEcsh6Ptm~m|L5u4Dhn{`DF#75I&wm#!XAik|_`5-_m6mYl z-sBrfk9ccM`9kiU=3iB!eRtU>=RN*U^Di%(9MV>eA_-EfKJz$u}}0=KnYSDOno7cEj!=6O6_HBOi7nvf$8gW+~>o$uRR zloi)MGhOs?#*5QhlTNu9&@XKLAd<*)c7VZZK^@TYGo zId1E@C*3{USa9egi=Drswfx#Mk_@|3-oE{?wN**OHHZ88`S73O9|Qs_qn@V!h@4ul z=^-9r)vLDfd4K-HurTQ9%v1Uwww`#u$f@yv_sLA=(>hb*CuIij ztKV}e=tt_*`m^g-{8`1ad(Xm&iVK?-t@5yWVLn%4tHvDV$eya(zgGw5d#gk}oVwzp zbZh~q;B&4pr7ae)SGdg>rw6VdjHv4Yri(f5AOe_ znX?p2XPkQPx#aik2g#QbUayr3SkUXwzvHEI(vI&(`|87A&i|r6)&9Zikbe{M-9Ok= zt#R+1{YG%UTc!E)BmDd-FfZ_gZ0J*Sug%vdFp%e;?h$lcT-&^)o3(E_^!BnAoB9fq-WfpWlUOb99uZ+ z?1{QOHMb{y#$q@0`naNbCfcTIF0Ze%dsLGnF!f1Uk<)g*XVV^snH^U;xMN|rHjmGa z*cC<(=9)cO_eHw(>VswfV{Fu%&97Y4i+9}Fb;+7}nWHZAgNRNl#JrpxN$J*6XZ*gAK4urI1tc{Tr^?WN}_cbpv)7niTgvg~5M zHtTz_A-93#`xQ21sLD5h{~#N~zYQDiWvbY*ODjJ;%XKC7?s7}# z=(W|c{`Ng>G0an!pSF6i?sY~*>5nGCi<1^J2SzWSoMA00G_l#Hgt4}HhTf5Iu~-+& zBB$fkeKOb2)~oZ~dM3O)&*pf-yQg1dw}d}W>!>oZv5+`@0ze#<+x zvfxCrPpk0X!?x$*q;7{iDXfxFJ-XC(%5C4ra*Ow{vwe)@knDEY$+BnKB^NE`=bL@( z!lQOC{So68e%bWF3CHdiQM>p;?wItiYp=_Vh<34GGIv6b(vv+C^A4@aH0iACF;~Ca zbG2;E9@gxFKU<$XZ)?>y5dfL_pW}=^ECHe*)Sz|cGZdKg*U`@6vfItUud0iE#uW^ zi{jW~{X72Zp()jyc4yA|YbNz9(CzH2Ws}V-L|)F?m)PE2{&nwzz+Ia0SCWjcZfdG` zTYQ&g+xevvw@6;N!6ckzGIds_+Rp_V_H&)q2Dmr)E&rUsn|riJVV_fpo$CtjTJgXu zA&J~9Q?71z_Pklbc6Pb-3-u>L3xl)vPI=cd>48*4srX&qr#(w@w{*W_w6_#lT)puS zpOoV-&AaSd^^UIF{qfn6V$Hujb$rHm`K^{dc~O7ZWVzFA(Nn=M7+b$>aH{THR^zz% zBc|7|gy#-no8T<21mQ1;v zkkom0du>2cy;#_~C3&wJi@q%mHx*U4Wxt@Dbz<2XWs}&2&B1IPH@x}PU1~V3MAWjp z<}X{dXp)@P`VG^&?w{E;@flOlX7eX!0)0HYq-Vc*G2`})CmOjqMOANJ z#R*LzL$S9@c2E^EsOB!kd7OG2n&G@6qQg@0ce*b~{m%sFgF5BFeyg28JJlh*TSDss! zy(@i>T6$N%*ix1EVri=HIrkNtPEOk_!*+IMlV#%7SI6X5ZG7~M>xi$!moIa-EUKR# zX7)8GcXG}Ai_Yoa0_M8RsM6T5&`FSYZPuy+Ti;4K)%2~c<^8KS&OUdmp+coG#rLq@ ztdxnd-Qh0J{%+Yf+sxzmP2PqW#iY%bynPo`_?O<9(CnyS7K~98c{MuQz_je4;Fv_0hp1mlocDCt|mnPizqn ze5$zSJ?qlEjMeM~Z&<&)=?s$0I^M%e#8}<)x6n8(_Dt$8X_TIy4_u5u&{vA0h?HSj_KZ!YiY?@ASZ#ddr zuxa{)_Ib-KFU;Fu!GHT{@-_LF{sKuXhgLn2l)iRmf%!|_;D%OBYn>(bv&#e++a|v) zmRfk>w#`ptfs>Qwn+g5=nOdKtmsjemztt-(Z*zj|gf#{8Qd$|}wizi1Z;{d1GWnCs z4xyhM&U^B@4(P|dTv}#v^`|fMu_}#Q&W2O8vzAM)+I~8SD~I6*$FxFc)5Qz8>iMS_ z&b`<6>E6O%#g|4^{8R3+ujQYx%bsN^|AeV_E^fa#zSz0x{Aamh*Y?QH@#|;);G5YC zroSvP``h}Dt2fsv@q^r7UNQOBJ>Ts9%33?8Yo^9CcZIR-W-DR+68`w~tmkdPbuRx| zGmnT!Y0kf_(P|gA;w#5gZIL&NZ!TH#FvhL^!rBJOlyCJlZZ{)qH|$b2WtCt1EI?l8 zxx^NsXcc2uU%_cQ+UfBY95QvKiUX!jHE`O6(R7is!FNq%R$OzqRl`UQq3p9Y^@bd2r9 zvy1;{wVtd0yR2WaUHRX8Tc@3O&VSJmFZ*S3u!J|PbJ^5INlyyG>my&AY@d;_>A}`z z7t%S!M1K8mF%&V-e(IuC-tWuvaJ?C*ENOOn&wozAXa7$VErt+zt5o#Xbro+)cK zot?Vq36tvTf#R{+&zPy!vI><5hcadM~m&@^zEzuWLaZT>%{1ER zi|{zf|6=~N*z3HHlVKP~?F{4R-{!ubefZC>zmL@$YIDvk?hse4h%mnAogDYG;_+gq z54W=>)Q8s|a$2l@&UMYEA0Dco{w^t5VRG>F?yL!)qFccMi7UB*Sfl$wRE`}Irz zxIVf2D?#D>tj;SvpY2wJJeBACdh5-%%u6pbUbj4uOUd<&$XO90$A5lSP`xmZN=xq= z)k(q&r*^7)UguENxp_sG;3mi~KT&*djsm#696T<3F}zv}(I z<-gxqmREj1YybcAF?)tNNAD}k6zO&(+cF7XSDuo0V#4%9=T_C*tdSz2HnR1OES5Jn zx@xsPJQ369aMbtaKGnm^y4G^^JrowK3HsOlc&34!=)vV5XL6L(ge^%At?_$y-n~F5 ze!|0TUC%>o!*9Uc^>TSE>50&Ias^< zb8xZt^fcpO?a$oBFK2dhExLUD>+CA6_0c=8*ziW|3}np}W?lW{#k9>!o}X@OQl9=i zO@8LAh!u0KCT;$jb1yoIPw2ngrkP!D{9w)Ol)Lt^8;UjgwMtK4>vlct7kh2)SHr5U zj=r-U+#mm5$hy`x-QxG-jY9b;uMP8;ZNGjd=H@lm@{$j^f$aB$3OaAziqqzjnw811 zLSfIU={=IMOp@m=tx?~0tCi{QhP_j@>INZq0eF|8i-m zSI1cX7z2;$ur@8hiCM+4-+bSu-{F@=dSQZ~uJ%K6o`v`RdJ*(wIgk z`Q6_l=H7}i?y9SCQWbCF_}sUzOnI^RU#HCNy}gHgld~=xu9+nLt>LMgcVfvDljIlS zP2z{^?hBtT20Ixm@Gy4E=dFwGU0wE3>9oU9ei4_9@;RQx!k6kR8eWMjDOf%` z_|xfToN>zJqx;vdaeLCz7HG5L;bDvQkJ_cwK5S|`{;{R4KRL`<>hhl3)wY3bX_XV z;B@!AnKceaS4-73-_QCXTetop|BuB@e$%hALJ=FSM$Qcn7PzGV8L_z%0bpI*BL*Ft=HZBzcTopI= z-ngmsu+|-=`+d8NH?O|lYnp!l+Mf7F-}fK>efsdngUzf}ZyX*=muIgtzPI55zgG8u z75#v)u!A40cYUkt^b{`U+HE(-?!shgiKtKS1a8kbelfm6=9pC3>%VjN3m&>>?r>XL zidFr8y}$OSTQ0Y1KV3WV^vuI~^0!VOx+JitOss?7%b9y>cjVoLI}`rA+F`(zTDv8{ z=Iz^(qnCM&Cudx_{@R-Rb=kvdCJxsxhS_~mnQv;AzP|G$+nty7-YfU)y6xJ3{Bm#M z(&xWJ6KrCyeO}7^ZOk?7i(Owlc_{RJ=h3RW!G z+tv5^VC>z+ze^VXE{V-tQKzw`Ir((wyM*!;UFY7;vbNqd(zf>VJu+0#ib#TKIli@_Wtt&r7Dnr5jc4YHDn+igz>EIXCCs+6Q}YzsnW$ z3%tJBedgB8MJW=0Bd2?xv5t0rePmPjvY^LY>x51o|0{m$$5z$*!glXA+i03qODz9w z=G= zcwMU4+2XzV^itMS`vpT6a?953{!tRQK$7!Lijh@><0I7;;q{9%ttEYTt>)V)eR1J# ziFHXS(VL!64>dVjRFwU?cK7s0XJ)f^*!x{j_ju&FEUQ(?b;*xZ@8UYT>|g=1^|m>C$JPIhedu8+1zjb0GqxJNmx zW!nXAC4~;LDcvGmj-DUxZ(38j@Xe&RO8c75KjcnW^cZFXH?}$%g1j>h;#J9+i5@TbtWv`akilT)IGQO4Bq6<7?MbYgJdjd^IOWEVQ^| zOS@_8W;Y2ntGieKU)^w1JGII=wwq_4VX|v#hDguNvtLuM*68VrcL<-8>WYb&;_Exd z+h=~k^oL2$rY@cJcvj>VF|+C36L=14#)hBW*c79)YRT32uaci=$aEPmkjapnSU>BO zhr}!1+JlpPwYMi`ww*IPcHzl|K24#hmam(PcWdpRD9jppUR_Kl`0{7xsdMzo&uecu z$-X}4nd>914=+wjZnLut6^mMT#QNoJYbzy(O^eo?Jt6&V`Gbl>8_M)TAIN2&S|@V; z_L7L8Bu`_PRXdL>=t|69wx}ZkNwqimoua7+Q{h~PwcAsJ@=IV%A{YT@@EK%>i zh3|cA!+d@Ej-Hv8v#LGW%5Q>Q?d5>CyTdPbx<1edoO`lsky!V{MwQUae9_*EPq(~W zwp3K;N@)F}!+eG-=6+sv^g^JWVbST;tCHT{)lDxvbMcOq$gSOr)6QCMN&b0Z<+jS@ zt7lbTUG&%L@B_=8hx~)|dk_4dy6Z<~z(u$Hf845MHUxO^m@L}!q2njpuiQ(Slg`E6 zYQA_wy<*YEAAEb{U%YWNUd!urOfBHBnqWe3hGHD|Ne}ty1+4WOG-vr7N#5aiP3Fq? z4~N~W)qV-iJOAP6{sqUH->;ba_Fv-%y&pk6D*rTYJKp`Kw?uW}_U;2gI(};szR%lG zce^ZNhmcr94x4X|*jb%+j@jGqc3QQ4yL;3FUFO&+>f&;DDo4OS9p6|GQ?8U*Z}}DPBBMSq=Y;Fbyo5}J^RVMYw)d$^n6Zv;dY>jgFksUKywk_c^Wvn@PXHvp!71mkN zg#}USTh#WRbSf0hu~a_o8@I6k!gi1JTZ)>Om;AT7y#2-fea_al+Lj4P%|2?ze;41i z{rz%IzWrQVtNMSxUvo1QS12TNbgRFZI8pGRlDl79i>hQ~4BsQJbwxEQVy9)anZDI; zXX~AiM$cyqbzPUMz9UmA~aW=iFuko-1b;t&J*sI5GE%Z{K+dw!1r}yUlVxZMB?PzoKsa zvem01J;ifR8))zJJ3YnxE8qPi2iHf5D#o_k|0*kG_gC5}T;4pbmHBkZ`o`}v{M$T3 zGmctl&5r32=&6s$zPohPu=s$?$Daa)%(3Ori9#5No#-{kjw5DPS;WhgbJ!Iq(Zmu%P-jx|&z3Amy8;k2jlJ&VyJyv<`STVcd zV&}(WM@*bE>?66Kw(m+Su37Ei8tHOHLa$OT^X&v?-tzkbbt@xcYBoQ*z^!Qhm4 z=0FKPi506tOJ+Z;vGz3&j@x^?KJQlg1VNr-)rlKUI##jqulVh39(vE|Wv2d(y;)LK zb2+CLd!3!-wJBxShduRMf~uEVUwRrV`2M}Q#$mbtobTli{a4NVWDzI0^qrQIuOxS? zOXc#J@^*o54}Q>Fn|JA!|6(EkKTPp96Yn(sSC9JrLC)5{%hs&lZd-%G?uPFt&3x*s zTaIh|Y%^Y*?tdV3yMFQdWTwe3(Px%rJaZP9-J$Gh=k7UA zWZBA6Uu*WSf1736j|fU|HM-2nmEGqg6G zvaCmQ7{%x4mt6c<$SHqehf@|`;P#qnI)+lmRWJIcPh7C9(rpJ9tHQ3Ap&fCS-M3r1 zFZ@{fKyi=k0$ zN>(OowouT5(EhD!+v^uU|MJEE#lsc%-=BGtH!1eU5&6GQ&wa0Z|Gjko`Q5*-Ua$Wv z-Qefbet~6c&$PdaErOE;_!SxIdky?-JM6MjK+53MZ9~p>r-)Yd<_fJAF&o)K#ud~2=W!>R*PUvy=YI6Or*fYR{{!(o zdlVkJ+Wk|N|G!FR|FO?PaY@&vtL{;H7|M84rm*Dli*lX29v&5Q4Xo1>Gj3-~)X%y) zIV3lFGwUtoWm7Z!BeHB(ZaqG!s=D9OW{uRxj=YTBA*Zjc*>&?zIro$*DVDsQe5;Q; zUwOJw$B4gswUS-;#0ak(_fiwie1YxhPWH+yx43RwbZa*OrA=n%ykd&syJm^LFy8i5cJP-`_p@XpZ9Db+32)J8R25qiS>B3%}_H zruVj<6qjs0G*AD{i=&pCGaVEzBp*8&eR_tv<@|?aYi>G6A}CzuOrd zxF@vYUgC`8w)CrWGV-)?9O}Y8<(@Fvwbmso!dRxUTDth~)h#zdI9EwlPTl#4Wtu?Z zn}qpRkM8HyYaVynI%kF1y9t_1vc28ccA6}-yd7d)J^9hGW$brP3qJPScF&sUOD9j~ z-IMd=shQq?){ccczRcFtUY2{+^50cUliWF`M!P;ROU^Fjcit~pA2IQ*&fRHCsDQ5)jSUx6Suu%S|J>%YS%oJdoe6_A=(9)s{;E z`Q{ph!J9=7mx#FAuFzbyaEb8V-djm#7t7z-FF1N))BQ)XX8BIX1Z6~T+{x9r^`WC! zDKO_IZdd3ciSs7&f_V2x*r>rDJeZ>S!Cp=r1h|zX}fqy<@~(c zQ$pjOU6}fJTi{gj^Jf3&Epa>hK50cyu!ZhJTgms2dwT32tmxtYu&~FzNM+ZrAW!$@ zRcAW3O*K4wNlfRD&OepM=WBlV7v%L_-+O3c;XK)YhX0fv3NtPZkUTl#$dm)J_czwp zbCu|ASohpO_k449Y)G>}Mx;+E_nPR4=*83KuH6*pzB%OhmJBcPbym^-P*!{DuxVSgsRG)yn;8jbFN6FzE-*A2s%`eYvb@He>@#B`&%hN87@wadK z>$|kq?LYKn@^($`^1wOWh4q~_<@@Ka46l(ddl>ph;%}YUnq_Bpb;$~q z#k8-o>JdciisZX8yx| z%A(FiM2vaO3Z;(FE%i))YHD0-gW4p059TzDFz1J$sLz1Cd*ziV9I8j zTzFM>a?TYAnd6{iRhSqUd{`J5Oc)SgNuwV-Sdq?EO&Qd4G>{eT=A2x3)t70P1X%6e zs}4-7BqtYM6`g!eL1FUAI|7qau1PU%Q~(QoxW+bl_BA`EQe`ODktszN%=EY}FN4^% z#K540u!3Ppqn{pF(Rl~<$@8yE$e^yHMyOoU_|X)qkau#x4K1b^FEDpQD9_}%Hw0u* zr$dnSarlB2LMBHvWKc)dkrj&gLlp{x{g!@HiYYG)EVL_87A&+pO@IfcV@YFO^yG&( zBbh4WCO^C>Gxdd0+Y*bNikhXpUhpXI{EGs839e0dIkoDC50S|7XzOE{$+~x?m{ck!D=bx>T-U-r`NU-b8Ti--Cj)~uiVOIwzzQ>4 zr6%9GtHrdf7R=@C6q;;%Pe2BxQ-`8&Wdlgzq#o(XckT*I-hNMtiKP`JGWpv*QyF*@ zPmqDZ7De0EcCezX`)*7k{b1(R`>ssFlO`)H)tqcTS8VeBM*@?xA4oAtOa%*Vcwoh( zF$2u>Su6;aHG3$6z5J>SgJqSdxgN{0}liyGd+@G5?BB>S#LE& zL((HDrj$isp{BKRATy*vxk*fjfgwi|G?jvI;gZIAlLH?rPv(7W!1QDLq|-{16CWEe zu^yOoT5q!KU4hAB=a3^@^YG-ikJFg`9+~{(y!GS>PqdgW9Gkr7i8qtd@yW86)F&G} T<=``BFlIO`$iR?(9ApasgriHy diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 430dfabc..a4b44297 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-5.5.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-6.3-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew index 8e25e6c1..2fe81a7d 100755 --- a/gradlew +++ b/gradlew @@ -125,8 +125,8 @@ if $darwin; then GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" fi -# For Cygwin, switch paths to Windows format before running java -if $cygwin ; then +# For Cygwin or MSYS, switch paths to Windows format before running java +if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then APP_HOME=`cygpath --path --mixed "$APP_HOME"` CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` JAVACMD=`cygpath --unix "$JAVACMD"` @@ -154,19 +154,19 @@ if $cygwin ; then else eval `echo args$i`="\"$arg\"" fi - i=$((i+1)) + i=`expr $i + 1` done case $i in - (0) set -- ;; - (1) set -- "$args0" ;; - (2) set -- "$args0" "$args1" ;; - (3) set -- "$args0" "$args1" "$args2" ;; - (4) set -- "$args0" "$args1" "$args2" "$args3" ;; - (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; - (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; - (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; - (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; - (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + 0) set -- ;; + 1) set -- "$args0" ;; + 2) set -- "$args0" "$args1" ;; + 3) set -- "$args0" "$args1" "$args2" ;; + 4) set -- "$args0" "$args1" "$args2" "$args3" ;; + 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; esac fi @@ -175,14 +175,9 @@ save () { for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done echo " " } -APP_ARGS=$(save "$@") +APP_ARGS=`save "$@"` # Collect all arguments for the java command, following the shell quoting and substitution rules eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" -# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong -if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then - cd "$(dirname "$0")" -fi - exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat index 24467a14..9109989e 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -29,6 +29,9 @@ if "%DIRNAME%" == "" set DIRNAME=. set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" From d30593e1d53c55cddaf0c597e8827b96c7cd8419 Mon Sep 17 00:00:00 2001 From: Harsh Shandilya Date: Fri, 3 Apr 2020 20:32:13 +0530 Subject: [PATCH 040/450] gitignore: Add x/ directory People following default build instructions can be caught off guard by seeing the build artifacts in the git tree. Signed-off-by: Harsh Shandilya Signed-off-by: Romain Vimont --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 59bc840d..222769b3 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ build/ /dist/ .idea/ .gradle/ +/x/ From fd63e7eb5a60af8e5e52ecfddb9d0f3b7f3740af Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Wed, 8 Apr 2020 12:02:15 +0200 Subject: [PATCH 041/450] Format shortcut documentation For consistency, start the descriptions with a capital letter. --- app/scrcpy.1 | 26 +++++++++++++------------- app/src/cli.c | 40 ++++++++++++++++++++-------------------- 2 files changed, 33 insertions(+), 33 deletions(-) diff --git a/app/scrcpy.1 b/app/scrcpy.1 index da6c7f2d..60ca3dc3 100644 --- a/app/scrcpy.1 +++ b/app/scrcpy.1 @@ -164,15 +164,15 @@ Default is 0 (automatic).\n .TP .B Ctrl+f -switch fullscreen mode +Switch fullscreen mode .TP .B Ctrl+g -resize window to 1:1 (pixel\-perfect) +Resize window to 1:1 (pixel\-perfect) .TP .B Ctrl+x, Double\-click on black borders -resize window to remove black borders +Resize window to remove black borders .TP .B Ctrl+h, Home, Middle\-click @@ -204,43 +204,43 @@ Click on POWER (turn screen on/off) .TP .B Right\-click (when screen is off) -turn screen on +Turn screen on .TP .B Ctrl+o -turn device screen off (keep mirroring) +Turn device screen off (keep mirroring) .TP .B Ctrl+r -rotate device screen +Rotate device screen .TP .B Ctrl+n -expand notification panel +Expand notification panel .TP .B Ctrl+Shift+n -collapse notification panel +Collapse notification panel .TP .B Ctrl+c -copy device clipboard to computer +Copy device clipboard to computer .TP .B Ctrl+v -paste computer clipboard to device +Paste computer clipboard to device .TP .B Ctrl+Shift+v -copy computer clipboard to device +Copy computer clipboard to device .TP .B Ctrl+i -enable/disable FPS counter (print frames/second in logs) +Enable/disable FPS counter (print frames/second in logs) .TP .B Drag & drop APK file -install APK from computer +Install APK from computer .SH Environment variables diff --git a/app/src/cli.c b/app/src/cli.c index bb379f47..142c277f 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -143,68 +143,68 @@ scrcpy_print_usage(const char *arg0) { "Shortcuts:\n" "\n" " " CTRL_OR_CMD "+f\n" - " switch fullscreen mode\n" + " Switch fullscreen mode\n" "\n" " " CTRL_OR_CMD "+g\n" - " resize window to 1:1 (pixel-perfect)\n" + " Resize window to 1:1 (pixel-perfect)\n" "\n" " " CTRL_OR_CMD "+x\n" " Double-click on black borders\n" - " resize window to remove black borders\n" + " Resize window to remove black borders\n" "\n" " Ctrl+h\n" " Middle-click\n" - " click on HOME\n" + " Click on HOME\n" "\n" " " CTRL_OR_CMD "+b\n" " " CTRL_OR_CMD "+Backspace\n" " Right-click (when screen is on)\n" - " click on BACK\n" + " Click on BACK\n" "\n" " " CTRL_OR_CMD "+s\n" - " click on APP_SWITCH\n" + " Click on APP_SWITCH\n" "\n" " Ctrl+m\n" - " click on MENU\n" + " Click on MENU\n" "\n" " " CTRL_OR_CMD "+Up\n" - " click on VOLUME_UP\n" + " Click on VOLUME_UP\n" "\n" " " CTRL_OR_CMD "+Down\n" - " click on VOLUME_DOWN\n" + " Click on VOLUME_DOWN\n" "\n" " " CTRL_OR_CMD "+p\n" - " click on POWER (turn screen on/off)\n" + " Click on POWER (turn screen on/off)\n" "\n" " Right-click (when screen is off)\n" - " power on\n" + " Power on\n" "\n" " " CTRL_OR_CMD "+o\n" - " turn device screen off (keep mirroring)\n" + " Turn device screen off (keep mirroring)\n" "\n" " " CTRL_OR_CMD "+r\n" - " rotate device screen\n" + " Rotate device screen\n" "\n" " " CTRL_OR_CMD "+n\n" - " expand notification panel\n" + " Expand notification panel\n" "\n" " " CTRL_OR_CMD "+Shift+n\n" - " collapse notification panel\n" + " Collapse notification panel\n" "\n" " " CTRL_OR_CMD "+c\n" - " copy device clipboard to computer\n" + " Copy device clipboard to computer\n" "\n" " " CTRL_OR_CMD "+v\n" - " paste computer clipboard to device\n" + " Paste computer clipboard to device\n" "\n" " " CTRL_OR_CMD "+Shift+v\n" - " copy computer clipboard to device\n" + " Copy computer clipboard to device\n" "\n" " " CTRL_OR_CMD "+i\n" - " enable/disable FPS counter (print frames/second in logs)\n" + " Enable/disable FPS counter (print frames/second in logs)\n" "\n" " Drag & drop APK file\n" - " install APK from computer\n" + " Install APK from computer\n" "\n", arg0, DEFAULT_BIT_RATE, From d48b375a1dbc8bed92e3424b5967e59c2d8f6ca1 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Tue, 7 Apr 2020 23:03:23 +0200 Subject: [PATCH 042/450] Add shortcuts to rotate display Add Ctrl+Left and Ctrl+Right shortcuts to rotate the display (the content of the scrcpy window). Contrary to --lock-video-orientation, the rotation has no impact on recording, and can be changed dynamically (and immediately). Fixes #218 --- README.md | 2 + app/scrcpy.1 | 8 +++ app/src/cli.c | 6 ++ app/src/input_manager.c | 22 +++++++ app/src/screen.c | 134 ++++++++++++++++++++++++++++++++-------- app/src/screen.h | 7 +++ 6 files changed, 152 insertions(+), 27 deletions(-) diff --git a/README.md b/README.md index 96b2f087..321acfd6 100644 --- a/README.md +++ b/README.md @@ -491,6 +491,8 @@ Also see [issue #14]. | Action | Shortcut | Shortcut (macOS) | -------------------------------------- |:----------------------------- |:----------------------------- | Switch fullscreen mode | `Ctrl`+`f` | `Cmd`+`f` + | Rotate display left | `Ctrl`+`←` _(left)_ | `Cmd`+`←` _(left)_ + | Rotate display right | `Ctrl`+`→` _(right)_ | `Cmd`+`→` _(right)_ | Resize window to 1:1 (pixel-perfect) | `Ctrl`+`g` | `Cmd`+`g` | Resize window to remove black borders | `Ctrl`+`x` \| _Double-click¹_ | `Cmd`+`x` \| _Double-click¹_ | Click on `HOME` | `Ctrl`+`h` \| _Middle-click_ | `Ctrl`+`h` \| _Middle-click_ diff --git a/app/scrcpy.1 b/app/scrcpy.1 index 60ca3dc3..2bcde227 100644 --- a/app/scrcpy.1 +++ b/app/scrcpy.1 @@ -166,6 +166,14 @@ Default is 0 (automatic).\n .B Ctrl+f Switch fullscreen mode +.TP +.B Ctrl+Left +Rotate display left + +.TP +.B Ctrl+Right +Rotate display right + .TP .B Ctrl+g Resize window to 1:1 (pixel\-perfect) diff --git a/app/src/cli.c b/app/src/cli.c index 142c277f..c4d5585a 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -145,6 +145,12 @@ scrcpy_print_usage(const char *arg0) { " " CTRL_OR_CMD "+f\n" " Switch fullscreen mode\n" "\n" + " " CTRL_OR_CMD "+Left\n" + " Rotate display left\n" + "\n" + " " CTRL_OR_CMD "+Right\n" + " Rotate display right\n" + "\n" " " CTRL_OR_CMD "+g\n" " Resize window to 1:1 (pixel-perfect)\n" "\n" diff --git a/app/src/input_manager.c b/app/src/input_manager.c index 8c4c230a..62fde2fc 100644 --- a/app/src/input_manager.c +++ b/app/src/input_manager.c @@ -221,6 +221,18 @@ rotate_device(struct controller *controller) { } } +static void +rotate_client_left(struct screen *screen) { + unsigned new_rotation = (screen->rotation + 1) % 4; + screen_set_rotation(screen, new_rotation); +} + +static void +rotate_client_right(struct screen *screen) { + unsigned new_rotation = (screen->rotation + 3) % 4; + screen_set_rotation(screen, new_rotation); +} + void input_manager_process_text_input(struct input_manager *im, const SDL_TextInputEvent *event) { @@ -351,6 +363,16 @@ input_manager_process_key(struct input_manager *im, action_volume_up(controller, action); } return; + case SDLK_LEFT: + if (cmd && !shift && down) { + rotate_client_left(im->screen); + } + return; + case SDLK_RIGHT: + if (cmd && !shift && down) { + rotate_client_right(im->screen); + } + return; case SDLK_c: if (control && cmd && !shift && !repeat && down) { request_device_clipboard(controller); diff --git a/app/src/screen.c b/app/src/screen.c index 03e2c3a1..f506e1d6 100644 --- a/app/src/screen.c +++ b/app/src/screen.c @@ -15,6 +15,19 @@ #define DISPLAY_MARGINS 96 +static inline struct size +get_rotated_size(struct size size, int rotation) { + struct size rotated_size; + if (rotation & 1) { + rotated_size.width = size.height; + rotated_size.height = size.width; + } else { + rotated_size.width = size.width; + rotated_size.height = size.height; + } + return rotated_size; +} + // get the window size in a struct size static struct size get_window_size(SDL_Window *window) { @@ -80,8 +93,8 @@ get_preferred_display_bounds(struct size *bounds) { // - it keeps the aspect ratio // - it scales down to make it fit in the display_size static struct size -get_optimal_size(struct size current_size, struct size frame_size) { - if (frame_size.width == 0 || frame_size.height == 0) { +get_optimal_size(struct size current_size, struct size content_size) { + if (content_size.width == 0 || content_size.height == 0) { // avoid division by 0 return current_size; } @@ -100,14 +113,14 @@ get_optimal_size(struct size current_size, struct size frame_size) { h = MIN(current_size.height, display_size.height); } - bool keep_width = frame_size.width * h > frame_size.height * w; + bool keep_width = content_size.width * h > content_size.height * w; if (keep_width) { // remove black borders on top and bottom - h = frame_size.height * w / frame_size.width; + h = content_size.height * w / content_size.width; } else { // remove black borders on left and right (or none at all if it already // fits) - w = frame_size.width * h / frame_size.height; + w = content_size.width * h / content_size.height; } // w and h must fit into 16 bits @@ -117,33 +130,33 @@ get_optimal_size(struct size current_size, struct size frame_size) { // same as get_optimal_size(), but read the current size from the window static inline struct size -get_optimal_window_size(const struct screen *screen, struct size frame_size) { +get_optimal_window_size(const struct screen *screen, struct size content_size) { struct size windowed_size = get_windowed_window_size(screen); - return get_optimal_size(windowed_size, frame_size); + return get_optimal_size(windowed_size, content_size); } // initially, there is no current size, so use the frame size as current size // req_width and req_height, if not 0, are the sizes requested by the user static inline struct size -get_initial_optimal_size(struct size frame_size, uint16_t req_width, +get_initial_optimal_size(struct size content_size, uint16_t req_width, uint16_t req_height) { struct size window_size; if (!req_width && !req_height) { - window_size = get_optimal_size(frame_size, frame_size); + window_size = get_optimal_size(content_size, content_size); } else { if (req_width) { window_size.width = req_width; } else { // compute from the requested height - window_size.width = (uint32_t) req_height * frame_size.width - / frame_size.height; + window_size.width = (uint32_t) req_height * content_size.width + / content_size.height; } if (req_height) { window_size.height = req_height; } else { // compute from the requested width - window_size.height = (uint32_t) req_width * frame_size.height - / frame_size.width; + window_size.height = (uint32_t) req_width * content_size.height + / content_size.width; } } return window_size; @@ -167,9 +180,11 @@ screen_init_rendering(struct screen *screen, const char *window_title, int16_t window_x, int16_t window_y, uint16_t window_width, uint16_t window_height, bool window_borderless) { screen->frame_size = frame_size; + struct size content_size = + get_rotated_size(frame_size, screen->rotation); struct size window_size = - get_initial_optimal_size(frame_size, window_width, window_height); + get_initial_optimal_size(content_size, window_width, window_height); uint32_t window_flags = SDL_WINDOW_HIDDEN | SDL_WINDOW_RESIZABLE; #ifdef HIDPI_SUPPORT window_flags |= SDL_WINDOW_ALLOW_HIGHDPI; @@ -206,8 +221,8 @@ screen_init_rendering(struct screen *screen, const char *window_title, return false; } - if (SDL_RenderSetLogicalSize(screen->renderer, frame_size.width, - frame_size.height)) { + if (SDL_RenderSetLogicalSize(screen->renderer, content_size.width, + content_size.height)) { LOGE("Could not set renderer logical size: %s", SDL_GetError()); screen_destroy(screen); return false; @@ -253,13 +268,51 @@ screen_destroy(struct screen *screen) { } } +void +screen_set_rotation(struct screen *screen, unsigned rotation) { + assert(rotation < 4); + if (rotation == screen->rotation) { + return; + } + + struct size old_content_size = + get_rotated_size(screen->frame_size, screen->rotation); + struct size new_content_size = + get_rotated_size(screen->frame_size, rotation); + + if (SDL_RenderSetLogicalSize(screen->renderer, + new_content_size.width, + new_content_size.height)) { + LOGE("Could not set renderer logical size: %s", SDL_GetError()); + return; + } + + struct size windowed_size = get_windowed_window_size(screen); + struct size target_size = { + .width = (uint32_t) windowed_size.width * new_content_size.width + / old_content_size.width, + .height = (uint32_t) windowed_size.height * new_content_size.height + / old_content_size.height, + }; + target_size = get_optimal_size(target_size, new_content_size); + set_window_size(screen, target_size); + + screen->rotation = rotation; + LOGI("Display rotation set to %u", rotation); + + screen_render(screen); +} + // recreate the texture and resize the window if the frame size has changed static bool prepare_for_frame(struct screen *screen, struct size new_frame_size) { if (screen->frame_size.width != new_frame_size.width || screen->frame_size.height != new_frame_size.height) { - if (SDL_RenderSetLogicalSize(screen->renderer, new_frame_size.width, - new_frame_size.height)) { + struct size new_content_size = + get_rotated_size(new_frame_size, screen->rotation); + if (SDL_RenderSetLogicalSize(screen->renderer, + new_content_size.width, + new_content_size.height)) { LOGE("Could not set renderer logical size: %s", SDL_GetError()); return false; } @@ -267,14 +320,16 @@ prepare_for_frame(struct screen *screen, struct size new_frame_size) { // frame dimension changed, destroy texture SDL_DestroyTexture(screen->texture); + struct size content_size = + get_rotated_size(screen->frame_size, screen->rotation); struct size windowed_size = get_windowed_window_size(screen); struct size target_size = { - (uint32_t) windowed_size.width * new_frame_size.width - / screen->frame_size.width, - (uint32_t) windowed_size.height * new_frame_size.height - / screen->frame_size.height, + (uint32_t) windowed_size.width * new_content_size.width + / content_size.width, + (uint32_t) windowed_size.height * new_content_size.height + / content_size.height, }; - target_size = get_optimal_size(target_size, new_frame_size); + target_size = get_optimal_size(target_size, new_content_size); set_window_size(screen, target_size); screen->frame_size = new_frame_size; @@ -319,7 +374,29 @@ screen_update_frame(struct screen *screen, struct video_buffer *vb) { void screen_render(struct screen *screen) { SDL_RenderClear(screen->renderer); - SDL_RenderCopy(screen->renderer, screen->texture, NULL, NULL); + if (screen->rotation == 0) { + SDL_RenderCopy(screen->renderer, screen->texture, NULL, NULL); + } else { + // rotation in RenderCopyEx() is clockwise, while screen->rotation is + // counterclockwise (to be consistent with --lock-video-orientation) + int cw_rotation = (4 - screen->rotation) % 4; + double angle = 90 * cw_rotation; + + SDL_Rect *dstrect = NULL; + SDL_Rect rect; + if (screen->rotation & 1) { + struct size size = + get_rotated_size(screen->frame_size, screen->rotation); + rect.x = (size.width - size.height) / 2; + rect.y = (size.height - size.width) / 2; + rect.w = size.height; + rect.h = size.width; + dstrect = ▭ + } + + SDL_RenderCopyEx(screen->renderer, screen->texture, NULL, dstrect, + angle, NULL, 0); + } SDL_RenderPresent(screen->renderer); } @@ -349,8 +426,10 @@ screen_resize_to_fit(struct screen *screen) { screen->maximized = false; } + struct size content_size = + get_rotated_size(screen->frame_size, screen->rotation); struct size optimal_size = - get_optimal_window_size(screen, screen->frame_size); + get_optimal_window_size(screen, content_size); SDL_SetWindowSize(screen->window, optimal_size.width, optimal_size.height); LOGD("Resized to optimal size"); } @@ -366,8 +445,9 @@ screen_resize_to_pixel_perfect(struct screen *screen) { screen->maximized = false; } - SDL_SetWindowSize(screen->window, screen->frame_size.width, - screen->frame_size.height); + struct size content_size = + get_rotated_size(screen->frame_size, screen->rotation); + SDL_SetWindowSize(screen->window, content_size.width, content_size.height); LOGD("Resized to pixel-perfect"); } diff --git a/app/src/screen.h b/app/src/screen.h index c31f32c5..4e893811 100644 --- a/app/src/screen.h +++ b/app/src/screen.h @@ -22,6 +22,8 @@ struct screen { // Since we receive the event SIZE_CHANGED before MAXIMIZED, we must be // able to revert the size to its non-maximized value. struct size windowed_window_size_backup; + // client rotation: 0, 1, 2 or 3 (x90 degrees counterclockwise) + unsigned rotation; bool has_frame; bool fullscreen; bool maximized; @@ -44,6 +46,7 @@ struct screen { .width = 0, \ .height = 0, \ }, \ + .rotation = 0, \ .has_frame = false, \ .fullscreen = false, \ .maximized = false, \ @@ -90,6 +93,10 @@ screen_resize_to_fit(struct screen *screen); void screen_resize_to_pixel_perfect(struct screen *screen); +// set the display rotation (0, 1, 2 or 3, x90 degrees counterclockwise) +void +screen_set_rotation(struct screen *screen, unsigned rotation); + // react to window events void screen_handle_window_event(struct screen *screen, const SDL_WindowEvent *event); From 28c71c528f08f961bb0ceacff6d22684635dc3d7 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Wed, 8 Apr 2020 10:17:12 +0200 Subject: [PATCH 043/450] Add --rotation command-line option In addition to Ctrl+Left and Ctrl+Right shortcuts, add a command-line parameter to set the initial rotation. --- README.md | 27 +++++++++++++++++++++++++++ app/scrcpy.1 | 4 ++++ app/src/cli.c | 24 ++++++++++++++++++++++++ app/src/scrcpy.c | 3 ++- app/src/scrcpy.h | 2 ++ app/src/screen.c | 7 ++++++- app/src/screen.h | 3 ++- 7 files changed, 67 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 321acfd6..97be9f71 100644 --- a/README.md +++ b/README.md @@ -340,6 +340,33 @@ scrcpy -f # short version Fullscreen can then be toggled dynamically with `Ctrl`+`f`. +#### Rotation + +The window may be rotated: + +```bash +scrcpy --rotation 1 +``` + +Possibles values are: + - `0`: no rotation + - `1`: 90 degrees counterclockwise + - `2`: 180 degrees + - `3`: 90 degrees clockwise + +The rotation can also be changed dynamically with `Ctrl`+`←` _(left)_ and +`Ctrl`+`→` _(right)_. + +Note that _scrcpy_ manages 3 different rotations: + - `Ctrl`+`r` requests the device to switch between portrait and landscape (the + current running app may refuse, if it does 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` (or `Ctrl`+`←`/`Ctrl`+`→`) rotates only the window content. This + affects only the display, not the recording. + ### Other mirroring options diff --git a/app/scrcpy.1 b/app/scrcpy.1 index 2bcde227..e44bfc7e 100644 --- a/app/scrcpy.1 +++ b/app/scrcpy.1 @@ -110,6 +110,10 @@ Force recording format (either mp4 or mkv). .B \-\-render\-expired\-frames By default, to minimize latency, scrcpy always renders the last available decoded frame, and drops any previous ones. This flag forces to render all frames, at a cost of a possible increased latency. +.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. diff --git a/app/src/cli.c b/app/src/cli.c index c4d5585a..44f06ef2 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -104,6 +104,11 @@ scrcpy_print_usage(const char *arg0) { " This flag forces to render all frames, at a cost of a\n" " possible increased latency.\n" "\n" + " --rotation value\n" + " Set the initial display rotation.\n" + " Possibles values are 0, 1, 2 and 3. Each increment adds a 90\n" + " degrees rotation counterclockwise.\n" + "\n" " -s, --serial serial\n" " The device serial number. Mandatory only if several devices\n" " are connected to adb.\n" @@ -316,6 +321,18 @@ parse_lock_video_orientation(const char *s, int8_t *lock_video_orientation) { return true; } +static bool +parse_rotation(const char *s, uint8_t *rotation) { + long value; + bool ok = parse_integer_arg(s, &value, false, 0, 3, "rotation"); + if (!ok) { + return false; + } + + *rotation = (uint8_t) value; + return true; +} + static bool parse_window_position(const char *s, int16_t *position) { // special value for "auto" @@ -435,6 +452,7 @@ guess_record_format(const char *filename) { #define OPT_MAX_FPS 1012 #define OPT_LOCK_VIDEO_ORIENTATION 1013 #define OPT_DISPLAY_ID 1014 +#define OPT_ROTATION 1015 bool scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) { @@ -455,6 +473,7 @@ scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) { {"push-target", required_argument, NULL, OPT_PUSH_TARGET}, {"record", required_argument, NULL, 'r'}, {"record-format", required_argument, NULL, OPT_RECORD_FORMAT}, + {"rotation", required_argument, NULL, OPT_ROTATION}, {"render-expired-frames", no_argument, NULL, OPT_RENDER_EXPIRED_FRAMES}, {"serial", required_argument, NULL, 's'}, @@ -592,6 +611,11 @@ scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) { case OPT_PREFER_TEXT: opts->prefer_text = true; break; + case OPT_ROTATION: + if (!parse_rotation(optarg, &opts->rotation)) { + return false; + } + break; default: // getopt prints the error message on stderr return false; diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index 84c87ad8..94e8cde5 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -396,7 +396,8 @@ scrcpy(const struct scrcpy_options *options) { options->always_on_top, options->window_x, options->window_y, options->window_width, options->window_height, - options->window_borderless)) { + options->window_borderless, + options->rotation)) { goto end; } diff --git a/app/src/scrcpy.h b/app/src/scrcpy.h index b6c52fd7..93ee6724 100644 --- a/app/src/scrcpy.h +++ b/app/src/scrcpy.h @@ -21,6 +21,7 @@ struct scrcpy_options { uint32_t bit_rate; uint16_t max_fps; int8_t lock_video_orientation; + uint8_t rotation; int16_t window_x; // WINDOW_POSITION_UNDEFINED for "auto" int16_t window_y; // WINDOW_POSITION_UNDEFINED for "auto" uint16_t window_width; @@ -52,6 +53,7 @@ struct scrcpy_options { .bit_rate = DEFAULT_BIT_RATE, \ .max_fps = 0, \ .lock_video_orientation = DEFAULT_LOCK_VIDEO_ORIENTATION, \ + .rotation = 0, \ .window_x = WINDOW_POSITION_UNDEFINED, \ .window_y = WINDOW_POSITION_UNDEFINED, \ .window_width = 0, \ diff --git a/app/src/screen.c b/app/src/screen.c index f506e1d6..ae33cd27 100644 --- a/app/src/screen.c +++ b/app/src/screen.c @@ -178,8 +178,13 @@ bool screen_init_rendering(struct screen *screen, const char *window_title, struct size frame_size, bool always_on_top, int16_t window_x, int16_t window_y, uint16_t window_width, - uint16_t window_height, bool window_borderless) { + uint16_t window_height, bool window_borderless, + uint8_t rotation) { screen->frame_size = frame_size; + screen->rotation = rotation; + if (rotation) { + LOGI("Initial display rotation set to %u", rotation); + } struct size content_size = get_rotated_size(frame_size, screen->rotation); diff --git a/app/src/screen.h b/app/src/screen.h index 4e893811..8afbbf46 100644 --- a/app/src/screen.h +++ b/app/src/screen.h @@ -63,7 +63,8 @@ bool screen_init_rendering(struct screen *screen, const char *window_title, struct size frame_size, bool always_on_top, int16_t window_x, int16_t window_y, uint16_t window_width, - uint16_t window_height, bool window_borderless); + uint16_t window_height, bool window_borderless, + uint8_t rotation); // show the window void From cbde7b964a7573accf96bb76d2355468d1002fab Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Wed, 8 Apr 2020 12:04:57 +0200 Subject: [PATCH 044/450] Improve documentation for consistency Make --lock-video-orientation documentation consistent with that of --rotation. --- app/scrcpy.1 | 2 +- app/src/cli.c | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/app/scrcpy.1 b/app/scrcpy.1 index e44bfc7e..e3b9f4cc 100644 --- a/app/scrcpy.1 +++ b/app/scrcpy.1 @@ -52,7 +52,7 @@ 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. +Lock video orientation to \fIvalue\fR. Possible values are -1 (unlocked), 0, 1, 2 and 3. Natural device orientation is 0, and each increment adds a 90 degrees otation counterclockwise. Default is -1 (unlocked). diff --git a/app/src/cli.c b/app/src/cli.c index 44f06ef2..0d6ced9b 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -52,9 +52,10 @@ scrcpy_print_usage(const char *arg0) { " Print this help.\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" + " Lock video orientation to value.\n" + " Possible values are -1 (unlocked), 0, 1, 2 and 3.\n" + " Natural device orientation is 0, and each increment adds a\n" + " 90 degrees rotation counterclockwise.\n" " Default is %d%s.\n" "\n" " --max-fps value\n" From a8fd4aec9aea94f86203e3123aa22939d38bb927 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Wed, 8 Apr 2020 12:06:24 +0200 Subject: [PATCH 045/450] Remove --fullscreen validation Many options are meaningless if --no-display is set. We don't want to validate all possible combinations, so don't make an exception for --fullscreen. --- app/src/cli.c | 5 ----- 1 file changed, 5 deletions(-) diff --git a/app/src/cli.c b/app/src/cli.c index 0d6ced9b..abb7b5f9 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -628,11 +628,6 @@ scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) { return false; } - if (!opts->display && opts->fullscreen) { - LOGE("-f/--fullscreen-window is incompatible with -N/--no-display"); - return false; - } - int index = optind; if (index < argc) { LOGE("Unexpected additional argument: %s", argv[index]); From f07d21f050f1151d69f64edd9494cced5b3a1649 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Wed, 8 Apr 2020 12:08:27 +0200 Subject: [PATCH 046/450] Suppress DiscouragedPrivateApi lint warning --- server/src/main/java/com/genymobile/scrcpy/Workarounds.java | 2 +- .../java/com/genymobile/scrcpy/wrappers/ServiceManager.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/Workarounds.java b/server/src/main/java/com/genymobile/scrcpy/Workarounds.java index fe5d8035..351cc574 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Workarounds.java +++ b/server/src/main/java/com/genymobile/scrcpy/Workarounds.java @@ -28,7 +28,7 @@ public final class Workarounds { Looper.prepareMainLooper(); } - @SuppressLint("PrivateApi") + @SuppressLint("PrivateApi,DiscouragedPrivateApi") public static void fillAppInfo() { try { // ActivityThread activityThread = new ActivityThread(); diff --git a/server/src/main/java/com/genymobile/scrcpy/wrappers/ServiceManager.java b/server/src/main/java/com/genymobile/scrcpy/wrappers/ServiceManager.java index 0b625c92..cc567837 100644 --- a/server/src/main/java/com/genymobile/scrcpy/wrappers/ServiceManager.java +++ b/server/src/main/java/com/genymobile/scrcpy/wrappers/ServiceManager.java @@ -6,7 +6,7 @@ import android.os.IInterface; import java.lang.reflect.Method; -@SuppressLint("PrivateApi") +@SuppressLint("PrivateApi,DiscouragedPrivateApi") public final class ServiceManager { private final Method getServiceMethod; From c1ebea26e6489be65603c1eae1dae6a455351ecf Mon Sep 17 00:00:00 2001 From: Kostiantyn Luzan Date: Tue, 7 Apr 2020 23:48:05 +0300 Subject: [PATCH 047/450] Register rotation watcher on selected display PR #1275 Signed-off-by: Kostiantyn Luzan Signed-off-by: Romain Vimont --- server/src/main/java/com/genymobile/scrcpy/Device.java | 6 +++--- .../java/com/genymobile/scrcpy/wrappers/WindowManager.java | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/Device.java b/server/src/main/java/com/genymobile/scrcpy/Device.java index cbe294af..afa9f165 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Device.java +++ b/server/src/main/java/com/genymobile/scrcpy/Device.java @@ -67,7 +67,7 @@ public final class Device { } } } - }); + }, displayId); if ((displayInfoFlags & DisplayInfo.FLAG_SUPPORTS_PROTECTED_BUFFERS) == 0) { Ln.w("Display doesn't have FLAG_SUPPORTS_PROTECTED_BUFFERS flag, mirroring can be restricted"); @@ -138,8 +138,8 @@ public final class Device { return serviceManager.getPowerManager().isScreenOn(); } - public void registerRotationWatcher(IRotationWatcher rotationWatcher) { - serviceManager.getWindowManager().registerRotationWatcher(rotationWatcher); + public void registerRotationWatcher(IRotationWatcher rotationWatcher, int displayId) { + serviceManager.getWindowManager().registerRotationWatcher(rotationWatcher, displayId); } public synchronized void setRotationListener(RotationListener rotationListener) { diff --git a/server/src/main/java/com/genymobile/scrcpy/wrappers/WindowManager.java b/server/src/main/java/com/genymobile/scrcpy/wrappers/WindowManager.java index cc687cd5..faa366a5 100644 --- a/server/src/main/java/com/genymobile/scrcpy/wrappers/WindowManager.java +++ b/server/src/main/java/com/genymobile/scrcpy/wrappers/WindowManager.java @@ -93,13 +93,13 @@ public final class WindowManager { } } - public void registerRotationWatcher(IRotationWatcher rotationWatcher) { + public void registerRotationWatcher(IRotationWatcher rotationWatcher, int displayId) { try { Class cls = manager.getClass(); try { // display parameter added since this commit: // https://android.googlesource.com/platform/frameworks/base/+/35fa3c26adcb5f6577849fd0df5228b1f67cf2c6%5E%21/#F1 - cls.getMethod("watchRotation", IRotationWatcher.class, int.class).invoke(manager, rotationWatcher, 0); + cls.getMethod("watchRotation", IRotationWatcher.class, int.class).invoke(manager, rotationWatcher, displayId); } catch (NoSuchMethodException e) { // old version cls.getMethod("watchRotation", IRotationWatcher.class).invoke(manager, rotationWatcher); From f3fba3c4b968f4242fc0f5d092daa63ffa9b8f5e Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Wed, 8 Apr 2020 14:11:23 +0200 Subject: [PATCH 048/450] Store rotated content size This avoids to compute it every time from the frame size. --- app/src/screen.c | 22 +++++++++------------- app/src/screen.h | 7 ++++++- 2 files changed, 15 insertions(+), 14 deletions(-) diff --git a/app/src/screen.c b/app/src/screen.c index ae33cd27..4dacaae2 100644 --- a/app/src/screen.c +++ b/app/src/screen.c @@ -185,8 +185,8 @@ screen_init_rendering(struct screen *screen, const char *window_title, if (rotation) { LOGI("Initial display rotation set to %u", rotation); } - struct size content_size = - get_rotated_size(frame_size, screen->rotation); + struct size content_size = get_rotated_size(frame_size, screen->rotation); + screen->content_size = content_size; struct size window_size = get_initial_optimal_size(content_size, window_width, window_height); @@ -280,8 +280,7 @@ screen_set_rotation(struct screen *screen, unsigned rotation) { return; } - struct size old_content_size = - get_rotated_size(screen->frame_size, screen->rotation); + struct size old_content_size = screen->content_size; struct size new_content_size = get_rotated_size(screen->frame_size, rotation); @@ -302,6 +301,7 @@ screen_set_rotation(struct screen *screen, unsigned rotation) { target_size = get_optimal_size(target_size, new_content_size); set_window_size(screen, target_size); + screen->content_size = new_content_size; screen->rotation = rotation; LOGI("Display rotation set to %u", rotation); @@ -325,8 +325,7 @@ prepare_for_frame(struct screen *screen, struct size new_frame_size) { // frame dimension changed, destroy texture SDL_DestroyTexture(screen->texture); - struct size content_size = - get_rotated_size(screen->frame_size, screen->rotation); + struct size content_size = screen->content_size; struct size windowed_size = get_windowed_window_size(screen); struct size target_size = { (uint32_t) windowed_size.width * new_content_size.width @@ -338,6 +337,7 @@ prepare_for_frame(struct screen *screen, struct size new_frame_size) { set_window_size(screen, target_size); screen->frame_size = new_frame_size; + screen->content_size = new_content_size; LOGI("New texture: %" PRIu16 "x%" PRIu16, screen->frame_size.width, screen->frame_size.height); @@ -390,8 +390,7 @@ screen_render(struct screen *screen) { SDL_Rect *dstrect = NULL; SDL_Rect rect; if (screen->rotation & 1) { - struct size size = - get_rotated_size(screen->frame_size, screen->rotation); + struct size size = screen->content_size; rect.x = (size.width - size.height) / 2; rect.y = (size.height - size.width) / 2; rect.w = size.height; @@ -431,10 +430,8 @@ screen_resize_to_fit(struct screen *screen) { screen->maximized = false; } - struct size content_size = - get_rotated_size(screen->frame_size, screen->rotation); struct size optimal_size = - get_optimal_window_size(screen, content_size); + get_optimal_window_size(screen, screen->content_size); SDL_SetWindowSize(screen->window, optimal_size.width, optimal_size.height); LOGD("Resized to optimal size"); } @@ -450,8 +447,7 @@ screen_resize_to_pixel_perfect(struct screen *screen) { screen->maximized = false; } - struct size content_size = - get_rotated_size(screen->frame_size, screen->rotation); + struct size content_size = screen->content_size; SDL_SetWindowSize(screen->window, content_size.width, content_size.height); LOGD("Resized to pixel-perfect"); } diff --git a/app/src/screen.h b/app/src/screen.h index 8afbbf46..39593e04 100644 --- a/app/src/screen.h +++ b/app/src/screen.h @@ -17,6 +17,7 @@ struct screen { SDL_Renderer *renderer; SDL_Texture *texture; struct size frame_size; + struct size content_size; // rotated frame_size // The window size the last time it was not maximized or fullscreen. struct size windowed_window_size; // Since we receive the event SIZE_CHANGED before MAXIMIZED, we must be @@ -35,7 +36,11 @@ struct screen { .renderer = NULL, \ .texture = NULL, \ .frame_size = { \ - .width = 0, \ + .width = 0, \ + .height = 0, \ + }, \ + .content_size = { \ + .width = 0, \ .height = 0, \ }, \ .windowed_window_size = { \ From 6295c1a1102e22478db58c9119d9590a2cc4c989 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Wed, 8 Apr 2020 14:27:25 +0200 Subject: [PATCH 049/450] Remap event positions on rotated display If the display is rotated, the position of clicks must be adapted. --- app/src/input_manager.c | 44 +++++++++++++++++++++++++++++++++++------ 1 file changed, 38 insertions(+), 6 deletions(-) diff --git a/app/src/input_manager.c b/app/src/input_manager.c index 62fde2fc..71a87b57 100644 --- a/app/src/input_manager.c +++ b/app/src/input_manager.c @@ -442,6 +442,37 @@ input_manager_process_key(struct input_manager *im, } } +static struct point +rotate_position(struct screen *screen, int32_t x, int32_t y) { + unsigned rotation = screen->rotation; + assert(rotation < 4); + + int32_t w = screen->content_size.width; + int32_t h = screen->content_size.height; + struct point result; + switch (rotation) { + case 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; + case 3: + result.x = y; + result.y = w - x; + break; + default: + assert(!"Unreachable"); + } + return result; +} + static bool convert_mouse_motion(const SDL_MouseMotionEvent *from, struct screen *screen, struct control_msg *to) { @@ -449,8 +480,8 @@ convert_mouse_motion(const SDL_MouseMotionEvent *from, struct screen *screen, to->inject_touch_event.action = AMOTION_EVENT_ACTION_MOVE; to->inject_touch_event.pointer_id = POINTER_ID_MOUSE; to->inject_touch_event.position.screen_size = screen->frame_size; - to->inject_touch_event.position.point.x = from->x; - to->inject_touch_event.position.point.y = from->y; + to->inject_touch_event.position.point = + rotate_position(screen, from->x, from->y); to->inject_touch_event.pressure = 1.f; to->inject_touch_event.buttons = convert_mouse_buttons(from->state); @@ -490,8 +521,9 @@ convert_touch(const SDL_TouchFingerEvent *from, struct screen *screen, to->inject_touch_event.pointer_id = from->fingerId; to->inject_touch_event.position.screen_size = frame_size; // SDL touch event coordinates are normalized in the range [0; 1] - to->inject_touch_event.position.point.x = from->x * frame_size.width; - to->inject_touch_event.position.point.y = from->y * frame_size.height; + float x = from->x * frame_size.width; + float y = from->y * frame_size.height; + to->inject_touch_event.position.point = rotate_position(screen, x, y); to->inject_touch_event.pressure = from->pressure; to->inject_touch_event.buttons = 0; return true; @@ -526,8 +558,8 @@ convert_mouse_button(const SDL_MouseButtonEvent *from, struct screen *screen, to->inject_touch_event.pointer_id = POINTER_ID_MOUSE; to->inject_touch_event.position.screen_size = screen->frame_size; - to->inject_touch_event.position.point.x = from->x; - to->inject_touch_event.position.point.y = from->y; + to->inject_touch_event.position.point = + rotate_position(screen, from->x, from->y); to->inject_touch_event.pressure = 1.f; to->inject_touch_event.buttons = convert_mouse_buttons(SDL_BUTTON(from->button)); From 9f4735ede3c541163a530c5c474b39ba4696773b Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Wed, 8 Apr 2020 16:34:46 +0200 Subject: [PATCH 050/450] Fix double click on rotated display A double-click outside the device content (in the black borders) resizes so that black borders are removed. But the display rotation was not taken into account to detect the content. Use the content size instead of the frame size to fix the issue. Ref: --- app/src/input_manager.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/input_manager.c b/app/src/input_manager.c index 71a87b57..39e1252d 100644 --- a/app/src/input_manager.c +++ b/app/src/input_manager.c @@ -543,8 +543,8 @@ input_manager_process_touch(struct input_manager *im, static bool is_outside_device_screen(struct input_manager *im, int x, int y) { - return x < 0 || x >= im->screen->frame_size.width || - y < 0 || y >= im->screen->frame_size.height; + return x < 0 || x >= im->screen->content_size.width || + y < 0 || y >= im->screen->content_size.height; } static bool From ab52b368958e60d30e68d6c65baa40484eaa9cce Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sat, 11 Apr 2020 14:31:14 +0200 Subject: [PATCH 051/450] Reorder options in alphabetical order --- app/src/cli.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/cli.c b/app/src/cli.c index abb7b5f9..488bbc33 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -474,9 +474,9 @@ scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) { {"push-target", required_argument, NULL, OPT_PUSH_TARGET}, {"record", required_argument, NULL, 'r'}, {"record-format", required_argument, NULL, OPT_RECORD_FORMAT}, - {"rotation", required_argument, NULL, OPT_ROTATION}, {"render-expired-frames", no_argument, NULL, OPT_RENDER_EXPIRED_FRAMES}, + {"rotation", required_argument, NULL, OPT_ROTATION}, {"serial", required_argument, NULL, 's'}, {"show-touches", no_argument, NULL, 't'}, {"turn-screen-off", no_argument, NULL, 'S'}, From ee2894779aa85a7a78a3e57aa154bfeb1025e55c Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 12 Apr 2020 02:05:55 +0200 Subject: [PATCH 052/450] Remove unused lockedVideoOrientation field During PR #1151, this field has been moved to ScreenInfo, but has not been removed from ScreenEncoder. --- .../main/java/com/genymobile/scrcpy/ScreenEncoder.java | 8 +++----- server/src/main/java/com/genymobile/scrcpy/Server.java | 3 +-- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java b/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java index 40457af8..69b309c1 100644 --- a/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java +++ b/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java @@ -27,21 +27,19 @@ public class ScreenEncoder implements Device.RotationListener { private int bitRate; private int maxFps; - private int lockedVideoOrientation; private int iFrameInterval; private boolean sendFrameMeta; 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.bitRate = bitRate; this.maxFps = maxFps; - this.lockedVideoOrientation = lockedVideoOrientation; this.iFrameInterval = iFrameInterval; } - public ScreenEncoder(boolean sendFrameMeta, int bitRate, int maxFps, int lockedVideoOrientation) { - this(sendFrameMeta, bitRate, maxFps, lockedVideoOrientation, DEFAULT_I_FRAME_INTERVAL); + public ScreenEncoder(boolean sendFrameMeta, int bitRate, int maxFps) { + this(sendFrameMeta, bitRate, maxFps, DEFAULT_I_FRAME_INTERVAL); } @Override diff --git a/server/src/main/java/com/genymobile/scrcpy/Server.java b/server/src/main/java/com/genymobile/scrcpy/Server.java index bd387285..a6f7a78c 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Server.java +++ b/server/src/main/java/com/genymobile/scrcpy/Server.java @@ -20,8 +20,7 @@ public final class Server { final Device device = new Device(options); boolean tunnelForward = options.isTunnelForward(); try (DesktopConnection connection = DesktopConnection.open(device, tunnelForward)) { - ScreenEncoder screenEncoder = new ScreenEncoder(options.getSendFrameMeta(), options.getBitRate(), options.getMaxFps(), - options.getLockedVideoOrientation()); + ScreenEncoder screenEncoder = new ScreenEncoder(options.getSendFrameMeta(), options.getBitRate(), options.getMaxFps()); if (options.getControl()) { Controller controller = new Controller(device, connection); From 927d655ff66fa439ccd27e054ae6d54a651640cc Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 12 Apr 2020 02:08:56 +0200 Subject: [PATCH 053/450] Simplify ScreenEncoder Do not handle iFrameInterval field and parameter, it is never used dynamically. --- .../main/java/com/genymobile/scrcpy/ScreenEncoder.java | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java b/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java index 69b309c1..08affaf6 100644 --- a/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java +++ b/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java @@ -27,19 +27,13 @@ public class ScreenEncoder implements Device.RotationListener { private int bitRate; private int maxFps; - private int iFrameInterval; private boolean sendFrameMeta; private long ptsOrigin; - public ScreenEncoder(boolean sendFrameMeta, int bitRate, int maxFps, int iFrameInterval) { + public ScreenEncoder(boolean sendFrameMeta, int bitRate, int maxFps) { this.sendFrameMeta = sendFrameMeta; this.bitRate = bitRate; this.maxFps = maxFps; - this.iFrameInterval = iFrameInterval; - } - - public ScreenEncoder(boolean sendFrameMeta, int bitRate, int maxFps) { - this(sendFrameMeta, bitRate, maxFps, DEFAULT_I_FRAME_INTERVAL); } @Override @@ -55,7 +49,7 @@ public class ScreenEncoder implements Device.RotationListener { Workarounds.prepareMainLooper(); Workarounds.fillAppInfo(); - MediaFormat format = createFormat(bitRate, maxFps, iFrameInterval); + MediaFormat format = createFormat(bitRate, maxFps, DEFAULT_I_FRAME_INTERVAL); device.setRotationListener(this); boolean alive; try { From 7eb16ce36458011d87972715f08bd9f03ff39807 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Mon, 13 Apr 2020 16:22:43 +0200 Subject: [PATCH 054/450] Fix log format warning The expression port + 1 is promoted to int, but printed as uint16_t. --- app/src/server.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/server.c b/app/src/server.c index 87997e72..b102f0c2 100644 --- a/app/src/server.c +++ b/app/src/server.c @@ -172,7 +172,7 @@ enable_tunnel_reverse_any_port(struct server *server, // 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, (uint16_t) (port + 1)); port++; continue; } From ea46d3ab689b92a9ee1c9ef53dc3247feee1adc4 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Mon, 13 Apr 2020 16:26:55 +0200 Subject: [PATCH 055/450] Add missing include string.h Include for strdup() and strtok_r(). --- app/src/sys/unix/command.c | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/sys/unix/command.c b/app/src/sys/unix/command.c index a60e21bc..64a54e71 100644 --- a/app/src/sys/unix/command.c +++ b/app/src/sys/unix/command.c @@ -14,6 +14,7 @@ #include #include #include +#include #include #include #include From 95fa1a69e4985e479486d7e730a59d538a6d70a3 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Mon, 13 Apr 2020 16:29:12 +0200 Subject: [PATCH 056/450] Workaround compiler warning Some compilers warns on uninitialized value in impossible case: warning: variable 'result' is used uninitialized whenever switch default is taken [-Wsometimes-uninitialized] --- app/src/input_manager.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/app/src/input_manager.c b/app/src/input_manager.c index 39e1252d..589eb842 100644 --- a/app/src/input_manager.c +++ b/app/src/input_manager.c @@ -463,12 +463,11 @@ rotate_position(struct screen *screen, int32_t x, int32_t y) { result.x = w - x; result.y = h - y; break; - case 3: + default: + assert(rotation == 3); result.x = y; result.y = w - x; break; - default: - assert(!"Unreachable"); } return result; } From 270d0bf639fbcc8e3a88ff29dc097eb6597a0a88 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Mon, 13 Apr 2020 19:37:34 +0200 Subject: [PATCH 057/450] Rename max length constant for text injection To avoid confusion with the max text size for clipboard, rename the constant limiting the text injection length. --- app/src/control_msg.c | 5 +++-- app/src/control_msg.h | 2 +- app/tests/test_control_msg_serialize.c | 10 +++++----- .../com/genymobile/scrcpy/ControlMessageReader.java | 2 +- .../genymobile/scrcpy/ControlMessageReaderTest.java | 2 +- 5 files changed, 11 insertions(+), 10 deletions(-) diff --git a/app/src/control_msg.c b/app/src/control_msg.c index 45113139..252a3425 100644 --- a/app/src/control_msg.c +++ b/app/src/control_msg.c @@ -45,8 +45,9 @@ control_msg_serialize(const struct control_msg *msg, unsigned char *buf) { buffer_write32be(&buf[6], msg->inject_keycode.metastate); return 10; case CONTROL_MSG_TYPE_INJECT_TEXT: { - size_t len = write_string(msg->inject_text.text, - CONTROL_MSG_TEXT_MAX_LENGTH, &buf[1]); + size_t len = + write_string(msg->inject_text.text, + CONTROL_MSG_INJECT_TEXT_MAX_LENGTH, &buf[1]); return 1 + len; } case CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT: diff --git a/app/src/control_msg.h b/app/src/control_msg.h index 49a159a6..e132fc6b 100644 --- a/app/src/control_msg.h +++ b/app/src/control_msg.h @@ -10,7 +10,7 @@ #include "android/keycodes.h" #include "common.h" -#define CONTROL_MSG_TEXT_MAX_LENGTH 300 +#define CONTROL_MSG_INJECT_TEXT_MAX_LENGTH 300 #define CONTROL_MSG_CLIPBOARD_TEXT_MAX_LENGTH 4093 #define CONTROL_MSG_SERIALIZED_MAX_SIZE \ (3 + CONTROL_MSG_CLIPBOARD_TEXT_MAX_LENGTH) diff --git a/app/tests/test_control_msg_serialize.c b/app/tests/test_control_msg_serialize.c index d6f556f3..4dc79018 100644 --- a/app/tests/test_control_msg_serialize.c +++ b/app/tests/test_control_msg_serialize.c @@ -49,20 +49,20 @@ static void test_serialize_inject_text(void) { static void test_serialize_inject_text_long(void) { struct control_msg msg; msg.type = CONTROL_MSG_TYPE_INJECT_TEXT; - char text[CONTROL_MSG_TEXT_MAX_LENGTH + 1]; + char text[CONTROL_MSG_INJECT_TEXT_MAX_LENGTH + 1]; memset(text, 'a', sizeof(text)); - text[CONTROL_MSG_TEXT_MAX_LENGTH] = '\0'; + text[CONTROL_MSG_INJECT_TEXT_MAX_LENGTH] = '\0'; msg.inject_text.text = text; unsigned char buf[CONTROL_MSG_SERIALIZED_MAX_SIZE]; int size = control_msg_serialize(&msg, buf); - assert(size == 3 + CONTROL_MSG_TEXT_MAX_LENGTH); + assert(size == 3 + CONTROL_MSG_INJECT_TEXT_MAX_LENGTH); - unsigned char expected[3 + CONTROL_MSG_TEXT_MAX_LENGTH]; + unsigned char expected[3 + CONTROL_MSG_INJECT_TEXT_MAX_LENGTH]; expected[0] = CONTROL_MSG_TYPE_INJECT_TEXT; expected[1] = 0x01; expected[2] = 0x2c; // text length (16 bits) - memset(&expected[3], 'a', CONTROL_MSG_TEXT_MAX_LENGTH); + memset(&expected[3], 'a', CONTROL_MSG_INJECT_TEXT_MAX_LENGTH); assert(!memcmp(buf, expected, sizeof(expected))); } diff --git a/server/src/main/java/com/genymobile/scrcpy/ControlMessageReader.java b/server/src/main/java/com/genymobile/scrcpy/ControlMessageReader.java index 29172c08..1c081058 100644 --- a/server/src/main/java/com/genymobile/scrcpy/ControlMessageReader.java +++ b/server/src/main/java/com/genymobile/scrcpy/ControlMessageReader.java @@ -13,8 +13,8 @@ public class ControlMessageReader { static final int INJECT_SCROLL_EVENT_PAYLOAD_LENGTH = 20; static final int SET_SCREEN_POWER_MODE_PAYLOAD_LENGTH = 1; - public static final int TEXT_MAX_LENGTH = 300; public static final int CLIPBOARD_TEXT_MAX_LENGTH = 4093; + public static final int INJECT_TEXT_MAX_LENGTH = 300; private static final int RAW_BUFFER_SIZE = 1024; private final byte[] rawBuffer = new byte[RAW_BUFFER_SIZE]; diff --git a/server/src/test/java/com/genymobile/scrcpy/ControlMessageReaderTest.java b/server/src/test/java/com/genymobile/scrcpy/ControlMessageReaderTest.java index 1905942b..202126a5 100644 --- a/server/src/test/java/com/genymobile/scrcpy/ControlMessageReaderTest.java +++ b/server/src/test/java/com/genymobile/scrcpy/ControlMessageReaderTest.java @@ -66,7 +66,7 @@ public class ControlMessageReaderTest { ByteArrayOutputStream bos = new ByteArrayOutputStream(); DataOutputStream dos = new DataOutputStream(bos); dos.writeByte(ControlMessage.TYPE_INJECT_TEXT); - byte[] text = new byte[ControlMessageReader.TEXT_MAX_LENGTH]; + byte[] text = new byte[ControlMessageReader.INJECT_TEXT_MAX_LENGTH]; Arrays.fill(text, (byte) 'a'); dos.writeShort(text.length); dos.write(text); From eb8f7a1f288b43e333db12e0f329e7706da5a164 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Mon, 13 Apr 2020 22:43:28 +0200 Subject: [PATCH 058/450] Require Meson 0.48 to get rid of warnings Debian buster (stable) provides Meson 0.49, which is also available in stretch (oldstable) backports. It's time to abandon Meson 0.37. Ref: 20b3f101a40cd7455cc5b41e381291504deec5ba --- meson.build | 2 +- server/meson.build | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/meson.build b/meson.build index 412c9c51..e958b20e 100644 --- a/meson.build +++ b/meson.build @@ -1,6 +1,6 @@ project('scrcpy', 'c', version: '1.12.1', - meson_version: '>= 0.37', + meson_version: '>= 0.48', default_options: [ 'c_std=c11', 'warning_level=2', diff --git a/server/meson.build b/server/meson.build index 4ba481d5..984daf3b 100644 --- a/server/meson.build +++ b/server/meson.build @@ -3,7 +3,9 @@ prebuilt_server = get_option('prebuilt_server') if prebuilt_server == '' custom_target('scrcpy-server', - build_always: true, # gradle is responsible for tracking source changes + # gradle is responsible for tracking source changes + build_by_default: true, + build_always_stale: true, output: 'scrcpy-server', command: [find_program('./scripts/build-wrapper.sh'), meson.current_source_dir(), '@OUTPUT@', get_option('buildtype')], console: true, From d62eb2b11c4554f3c32a470231355ae0430a956a Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Wed, 15 Apr 2020 09:57:59 +0200 Subject: [PATCH 059/450] Fix typo in README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index ff9a1109..9836d09c 100644 --- a/README.md +++ b/README.md @@ -363,7 +363,7 @@ Note that _scrcpy_ manages 3 different rotations: current running app may refuse, if it does 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 + of the video sent from the device to the computer). This affects the recording. - `--rotation` (or `Ctrl`+`←`/`Ctrl`+`→`) rotates only the window content. This affects only the display, not the recording. From 8a9b20b27e69dc0f77e305e2a0b829d89d264ca7 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sat, 11 Apr 2020 14:34:41 +0200 Subject: [PATCH 060/450] Add --render-driver command-line option Add an option to set a render driver hint (SDL_HINT_RENDER_DRIVER). --- app/scrcpy.1 | 8 ++++++++ app/src/cli.c | 12 ++++++++++++ app/src/scrcpy.c | 8 ++++++-- app/src/scrcpy.h | 2 ++ app/src/screen.c | 5 +++++ 5 files changed, 33 insertions(+), 2 deletions(-) diff --git a/app/scrcpy.1 b/app/scrcpy.1 index e3b9f4cc..99936732 100644 --- a/app/scrcpy.1 +++ b/app/scrcpy.1 @@ -106,6 +106,14 @@ option if set, or by the file extension (.mp4 or .mkv). .BI "\-\-record\-format " format Force recording format (either mp4 or mkv). +.TP +.BI "\-\-render\-driver " name +Request SDL to use the given render driver (this is just a hint). + +Supported names are currently "direct3d", "opengl", "opengles2", "opengles", "metal" and "software". +.UR https://wiki.libsdl.org/SDL_HINT_RENDER_DRIVER +.UE + .TP .B \-\-render\-expired\-frames By default, to minimize latency, scrcpy always renders the last available decoded frame, and drops any previous ones. This flag forces to render all frames, at a cost of a possible increased latency. diff --git a/app/src/cli.c b/app/src/cli.c index 488bbc33..9c79eb02 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -99,6 +99,13 @@ scrcpy_print_usage(const char *arg0) { " --record-format format\n" " Force recording format (either mp4 or mkv).\n" "\n" + " --render-driver name\n" + " Request SDL to use the given render driver (this is just a\n" + " hint).\n" + " Supported names are currently \"direct3d\", \"opengl\",\n" + " \"opengles2\", \"opengles\", \"metal\" and \"software\".\n" + " \n" + "\n" " --render-expired-frames\n" " By default, to minimize latency, scrcpy always renders the\n" " last available decoded frame, and drops any previous ones.\n" @@ -454,6 +461,7 @@ guess_record_format(const char *filename) { #define OPT_LOCK_VIDEO_ORIENTATION 1013 #define OPT_DISPLAY_ID 1014 #define OPT_ROTATION 1015 +#define OPT_RENDER_DRIVER 1016 bool scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) { @@ -474,6 +482,7 @@ scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) { {"push-target", required_argument, NULL, OPT_PUSH_TARGET}, {"record", required_argument, NULL, 'r'}, {"record-format", required_argument, NULL, OPT_RECORD_FORMAT}, + {"render-driver", required_argument, NULL, OPT_RENDER_DRIVER}, {"render-expired-frames", no_argument, NULL, OPT_RENDER_EXPIRED_FRAMES}, {"rotation", required_argument, NULL, OPT_ROTATION}, @@ -617,6 +626,9 @@ scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) { return false; } break; + case OPT_RENDER_DRIVER: + opts->render_driver = optarg; + break; default: // getopt prints the error message on stderr return false; diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index 94e8cde5..0fe3d0f0 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -47,7 +47,7 @@ static struct input_manager input_manager = { // init SDL and set appropriate hints static bool -sdl_init_and_configure(bool display) { +sdl_init_and_configure(bool display, const char *render_driver) { uint32_t flags = display ? SDL_INIT_VIDEO : SDL_INIT_EVENTS; if (SDL_Init(flags)) { LOGC("Could not initialize SDL: %s", SDL_GetError()); @@ -60,6 +60,10 @@ sdl_init_and_configure(bool display) { return true; } + if (render_driver && !SDL_SetHint(SDL_HINT_RENDER_DRIVER, render_driver)) { + LOGW("Could not set render driver"); + } + // Linear filtering if (!SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "1")) { LOGW("Could not enable linear filtering"); @@ -310,7 +314,7 @@ scrcpy(const struct scrcpy_options *options) { bool controller_initialized = false; bool controller_started = false; - if (!sdl_init_and_configure(options->display)) { + if (!sdl_init_and_configure(options->display, options->render_driver)) { goto end; } diff --git a/app/src/scrcpy.h b/app/src/scrcpy.h index 93ee6724..a53aaa21 100644 --- a/app/src/scrcpy.h +++ b/app/src/scrcpy.h @@ -15,6 +15,7 @@ struct scrcpy_options { const char *record_filename; const char *window_title; const char *push_target; + const char *render_driver; enum recorder_format record_format; struct port_range port_range; uint16_t max_size; @@ -44,6 +45,7 @@ struct scrcpy_options { .record_filename = NULL, \ .window_title = NULL, \ .push_target = NULL, \ + .render_driver = NULL, \ .record_format = RECORDER_FORMAT_AUTO, \ .port_range = { \ .first = DEFAULT_LOCAL_PORT_RANGE_FIRST, \ diff --git a/app/src/screen.c b/app/src/screen.c index 4dacaae2..83b07362 100644 --- a/app/src/screen.c +++ b/app/src/screen.c @@ -226,6 +226,11 @@ screen_init_rendering(struct screen *screen, const char *window_title, return false; } + SDL_RendererInfo renderer_info; + int r = SDL_GetRendererInfo(screen->renderer, &renderer_info); + const char *renderer_name = r ? NULL : renderer_info.name; + LOGI("Renderer: %s", renderer_name ? renderer_name : "(unknown)"); + if (SDL_RenderSetLogicalSize(screen->renderer, content_size.width, content_size.height)) { LOGE("Could not set renderer logical size: %s", SDL_GetError()); From bea7658807d276aeab7d18d856a366c83ee05827 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sat, 11 Apr 2020 16:08:23 +0200 Subject: [PATCH 061/450] Enable trilinear filtering for OpenGL MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Improve downscaling quality if mipmapping is available. Suggested-by: Giumo Clanjor (哆啦比猫/兰威举) Fixes #40 Ref: --- app/meson.build | 1 + app/src/opengl.c | 56 ++++++++++++++++++++++++++++++++++++++++++++ app/src/opengl.h | 36 ++++++++++++++++++++++++++++ app/src/screen.c | 61 +++++++++++++++++++++++++++++++++++++++++++----- app/src/screen.h | 7 ++++++ 5 files changed, 155 insertions(+), 6 deletions(-) create mode 100644 app/src/opengl.c create mode 100644 app/src/opengl.h diff --git a/app/meson.build b/app/meson.build index 49c4683f..5d2b4caa 100644 --- a/app/meson.build +++ b/app/meson.build @@ -11,6 +11,7 @@ src = [ 'src/file_handler.c', 'src/fps_counter.c', 'src/input_manager.c', + 'src/opengl.c', 'src/receiver.c', 'src/recorder.c', 'src/scrcpy.c', diff --git a/app/src/opengl.c b/app/src/opengl.c new file mode 100644 index 00000000..da05c082 --- /dev/null +++ b/app/src/opengl.c @@ -0,0 +1,56 @@ +#include "opengl.h" + +#include +#include +#include "SDL2/SDL.h" + +void +sc_opengl_init(struct sc_opengl *gl) { + gl->GetString = SDL_GL_GetProcAddress("glGetString"); + assert(gl->GetString); + + gl->TexParameterf = SDL_GL_GetProcAddress("glTexParameterf"); + assert(gl->TexParameterf); + + gl->TexParameteri = SDL_GL_GetProcAddress("glTexParameteri"); + assert(gl->TexParameteri); + + // optional + gl->GenerateMipmap = SDL_GL_GetProcAddress("glGenerateMipmap"); + + const char *version = (const char *) gl->GetString(GL_VERSION); + assert(version); + gl->version = version; + +#define OPENGL_ES_PREFIX "OpenGL ES " + /* starts with "OpenGL ES " */ + gl->is_opengles = !strncmp(gl->version, OPENGL_ES_PREFIX, + sizeof(OPENGL_ES_PREFIX) - 1); + if (gl->is_opengles) { + /* skip the prefix */ + version += sizeof(PREFIX) - 1; + } + + int r = sscanf(version, "%d.%d", &gl->version_major, &gl->version_minor); + if (r != 2) { + // failed to parse the version + gl->version_major = 0; + gl->version_minor = 0; + } +} + +bool +sc_opengl_version_at_least(struct sc_opengl *gl, + int minver_major, int minver_minor, + int minver_es_major, int minver_es_minor) +{ + if (gl->is_opengles) { + return gl->version_major > minver_es_major + || (gl->version_major == minver_es_major + && gl->version_minor >= minver_es_minor); + } + + return gl->version_major > minver_major + || (gl->version_major == minver_major + && gl->version_minor >= minver_minor); +} diff --git a/app/src/opengl.h b/app/src/opengl.h new file mode 100644 index 00000000..f0a89a14 --- /dev/null +++ b/app/src/opengl.h @@ -0,0 +1,36 @@ +#ifndef SC_OPENGL_H +#define SC_OPENGL_H + +#include +#include + +#include "config.h" + +struct sc_opengl { + const char *version; + bool is_opengles; + int version_major; + int version_minor; + + const GLubyte * + (*GetString)(GLenum name); + + void + (*TexParameterf)(GLenum target, GLenum pname, GLfloat param); + + void + (*TexParameteri)(GLenum target, GLenum pname, GLint param); + + void + (*GenerateMipmap)(GLenum target); +}; + +void +sc_opengl_init(struct sc_opengl *gl); + +bool +sc_opengl_version_at_least(struct sc_opengl *gl, + int minver_major, int minver_minor, + int minver_es_major, int minver_es_minor); + +#endif diff --git a/app/src/screen.c b/app/src/screen.c index 83b07362..c982a5c8 100644 --- a/app/src/screen.c +++ b/app/src/screen.c @@ -168,10 +168,30 @@ screen_init(struct screen *screen) { } static inline SDL_Texture * -create_texture(SDL_Renderer *renderer, struct size frame_size) { - return SDL_CreateTexture(renderer, SDL_PIXELFORMAT_YV12, - SDL_TEXTUREACCESS_STREAMING, - frame_size.width, frame_size.height); +create_texture(struct screen *screen) { + SDL_Renderer *renderer = screen->renderer; + struct size size = screen->frame_size; + SDL_Texture *texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_YV12, + SDL_TEXTUREACCESS_STREAMING, + size.width, size.height); + if (!texture) { + return NULL; + } + + if (screen->mipmaps) { + struct sc_opengl *gl = &screen->gl; + + SDL_GL_BindTexture(texture, NULL, NULL); + + // Enable trilinear filtering for downscaling + gl->TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, + GL_LINEAR_MIPMAP_LINEAR); + gl->TexParameterf(GL_TEXTURE_2D, GL_TEXTURE_LOD_BIAS, -.5f); + + SDL_GL_UnbindTexture(texture); + } + + return texture; } bool @@ -238,6 +258,28 @@ screen_init_rendering(struct screen *screen, const char *window_title, return false; } + // stats with "opengl" + screen->use_opengl = renderer_name && !strncmp(renderer_name, "opengl", 6); + if (screen->use_opengl) { + struct sc_opengl *gl = &screen->gl; + sc_opengl_init(gl); + + LOGI("OpenGL version: %s", gl->version); + + bool supports_mipmaps = + sc_opengl_version_at_least(gl, 3, 0, /* OpenGL 3.0+ */ + 2, 0 /* OpenGL ES 2.0+ */); + if (supports_mipmaps) { + LOGI("Trilinear filtering enabled"); + screen->mipmaps = true; + } else { + LOGW("Trilinear filtering disabled " + "(OpenGL 3.0+ or ES 2.0+ required)"); + } + } else { + LOGW("Trilinear filtering disabled (not an OpenGL renderer)"); + } + SDL_Surface *icon = read_xpm(icon_xpm); if (icon) { SDL_SetWindowIcon(screen->window, icon); @@ -248,7 +290,7 @@ screen_init_rendering(struct screen *screen, const char *window_title, LOGI("Initial texture: %" PRIu16 "x%" PRIu16, frame_size.width, frame_size.height); - screen->texture = create_texture(screen->renderer, frame_size); + screen->texture = create_texture(screen); if (!screen->texture) { LOGC("Could not create texture: %s", SDL_GetError()); screen_destroy(screen); @@ -346,7 +388,7 @@ prepare_for_frame(struct screen *screen, struct size new_frame_size) { LOGI("New texture: %" PRIu16 "x%" PRIu16, screen->frame_size.width, screen->frame_size.height); - screen->texture = create_texture(screen->renderer, new_frame_size); + screen->texture = create_texture(screen); if (!screen->texture) { LOGC("Could not create texture: %s", SDL_GetError()); return false; @@ -363,6 +405,13 @@ update_texture(struct screen *screen, const AVFrame *frame) { frame->data[0], frame->linesize[0], frame->data[1], frame->linesize[1], frame->data[2], frame->linesize[2]); + + if (screen->mipmaps) { + assert(screen->use_opengl); + SDL_GL_BindTexture(screen->texture, NULL, NULL); + screen->gl.GenerateMipmap(GL_TEXTURE_2D); + SDL_GL_UnbindTexture(screen->texture); + } } bool diff --git a/app/src/screen.h b/app/src/screen.h index 39593e04..90537463 100644 --- a/app/src/screen.h +++ b/app/src/screen.h @@ -7,6 +7,7 @@ #include "config.h" #include "common.h" +#include "opengl.h" #define WINDOW_POSITION_UNDEFINED (-0x8000) @@ -16,6 +17,8 @@ struct screen { SDL_Window *window; SDL_Renderer *renderer; SDL_Texture *texture; + bool use_opengl; + struct sc_opengl gl; struct size frame_size; struct size content_size; // rotated frame_size // The window size the last time it was not maximized or fullscreen. @@ -29,12 +32,15 @@ struct screen { bool fullscreen; bool maximized; bool no_window; + bool mipmaps; }; #define SCREEN_INITIALIZER { \ .window = NULL, \ .renderer = NULL, \ .texture = NULL, \ + .use_opengl = false, \ + .gl = {0}, \ .frame_size = { \ .width = 0, \ .height = 0, \ @@ -56,6 +62,7 @@ struct screen { .fullscreen = false, \ .maximized = false, \ .no_window = false, \ + .mipmaps = false, \ } // initialize default values From 11a61b2cb374087884ef1270d2f0b5844d797ffd Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sat, 11 Apr 2020 23:55:29 +0200 Subject: [PATCH 062/450] Add option --no-mipmaps Add an option to disable trilinear filtering even if mipmapping is available. --- app/scrcpy.1 | 4 ++++ app/src/cli.c | 10 ++++++++++ app/src/scrcpy.c | 2 +- app/src/scrcpy.h | 2 ++ app/src/screen.c | 22 +++++++++++++--------- app/src/screen.h | 2 +- 6 files changed, 31 insertions(+), 11 deletions(-) diff --git a/app/scrcpy.1 b/app/scrcpy.1 index 99936732..673cd11d 100644 --- a/app/scrcpy.1 +++ b/app/scrcpy.1 @@ -74,6 +74,10 @@ Disable device control (mirror the device in read\-only). .B \-N, \-\-no\-display Do not display device (only when screen recording is enabled). +.TP +.B \-\-no\-mipmaps +If the renderer is OpenGL 3.0+ or OpenGL ES 2.0+, then mipmaps are automatically generated to improve downscaling quality. This option disables the generation of mipmaps. + .TP .BI "\-p, \-\-port " port[:port] Set the TCP port (range) used by the client to listen. diff --git a/app/src/cli.c b/app/src/cli.c index 9c79eb02..13351dee 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -75,6 +75,11 @@ scrcpy_print_usage(const char *arg0) { " Do not display device (only when screen recording is\n" " enabled).\n" "\n" + " --no-mipmaps\n" + " If the renderer is OpenGL 3.0+ or OpenGL ES 2.0+, then\n" + " mipmaps are automatically generated to improve downscaling\n" + " quality. This option disables the generation of mipmaps.\n" + "\n" " -p, --port port[:port]\n" " Set the TCP port (range) used by the client to listen.\n" " Default is %d:%d.\n" @@ -462,6 +467,7 @@ guess_record_format(const char *filename) { #define OPT_DISPLAY_ID 1014 #define OPT_ROTATION 1015 #define OPT_RENDER_DRIVER 1016 +#define OPT_NO_MIPMAPS 1017 bool scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) { @@ -478,6 +484,7 @@ scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) { {"max-size", required_argument, NULL, 'm'}, {"no-control", no_argument, NULL, 'n'}, {"no-display", no_argument, NULL, 'N'}, + {"no-mipmaps", no_argument, NULL, OPT_NO_MIPMAPS}, {"port", required_argument, NULL, 'p'}, {"push-target", required_argument, NULL, OPT_PUSH_TARGET}, {"record", required_argument, NULL, 'r'}, @@ -629,6 +636,9 @@ scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) { case OPT_RENDER_DRIVER: opts->render_driver = optarg; break; + case OPT_NO_MIPMAPS: + opts->mipmaps = false; + break; default: // getopt prints the error message on stderr return false; diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index 0fe3d0f0..81f8239e 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -401,7 +401,7 @@ scrcpy(const struct scrcpy_options *options) { options->window_y, options->window_width, options->window_height, options->window_borderless, - options->rotation)) { + options->rotation, options-> mipmaps)) { goto end; } diff --git a/app/src/scrcpy.h b/app/src/scrcpy.h index a53aaa21..d6b0a0f6 100644 --- a/app/src/scrcpy.h +++ b/app/src/scrcpy.h @@ -37,6 +37,7 @@ struct scrcpy_options { bool render_expired_frames; bool prefer_text; bool window_borderless; + bool mipmaps; }; #define SCRCPY_OPTIONS_DEFAULT { \ @@ -70,6 +71,7 @@ struct scrcpy_options { .render_expired_frames = false, \ .prefer_text = false, \ .window_borderless = false, \ + .mipmaps = true, \ } bool diff --git a/app/src/screen.c b/app/src/screen.c index c982a5c8..512d7bbd 100644 --- a/app/src/screen.c +++ b/app/src/screen.c @@ -199,7 +199,7 @@ screen_init_rendering(struct screen *screen, const char *window_title, struct size frame_size, bool always_on_top, int16_t window_x, int16_t window_y, uint16_t window_width, uint16_t window_height, bool window_borderless, - uint8_t rotation) { + uint8_t rotation, bool mipmaps) { screen->frame_size = frame_size; screen->rotation = rotation; if (rotation) { @@ -266,15 +266,19 @@ screen_init_rendering(struct screen *screen, const char *window_title, LOGI("OpenGL version: %s", gl->version); - bool supports_mipmaps = - sc_opengl_version_at_least(gl, 3, 0, /* OpenGL 3.0+ */ - 2, 0 /* OpenGL ES 2.0+ */); - if (supports_mipmaps) { - LOGI("Trilinear filtering enabled"); - screen->mipmaps = true; + if (mipmaps) { + bool supports_mipmaps = + sc_opengl_version_at_least(gl, 3, 0, /* OpenGL 3.0+ */ + 2, 0 /* OpenGL ES 2.0+ */); + if (supports_mipmaps) { + LOGI("Trilinear filtering enabled"); + screen->mipmaps = true; + } else { + LOGW("Trilinear filtering disabled " + "(OpenGL 3.0+ or ES 2.0+ required)"); + } } else { - LOGW("Trilinear filtering disabled " - "(OpenGL 3.0+ or ES 2.0+ required)"); + LOGI("Trilinear filtering disabled"); } } else { LOGW("Trilinear filtering disabled (not an OpenGL renderer)"); diff --git a/app/src/screen.h b/app/src/screen.h index 90537463..c9e019a1 100644 --- a/app/src/screen.h +++ b/app/src/screen.h @@ -76,7 +76,7 @@ screen_init_rendering(struct screen *screen, const char *window_title, struct size frame_size, bool always_on_top, int16_t window_x, int16_t window_y, uint16_t window_width, uint16_t window_height, bool window_borderless, - uint8_t rotation); + uint8_t rotation, bool mipmaps); // show the window void From cc22f4622ae9cfe81eb5dba41da266438de232d8 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 12 Apr 2020 00:37:10 +0200 Subject: [PATCH 063/450] Mention mipmapping in FAQ --- FAQ.md | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/FAQ.md b/FAQ.md index d68a8011..871ae8f0 100644 --- a/FAQ.md +++ b/FAQ.md @@ -134,18 +134,27 @@ that's all. See [#37]. ### The quality is low -On Windows, you may need to configure the [scaling behavior]. +If the definition of your client window is smaller than that of your device +screen, then you might get poor quality, especially visible on text (see [#40]). + +[#40]: https://github.com/Genymobile/scrcpy/issues/40 + +To improve downscaling quality, trilinear filtering is enabled automatically +if the renderer is OpenGL and if it supports mipmapping. + +On Windows, you might want to force OpenGL: + +``` +scrcpy --render-driver=opengl +``` + +You may also need to configure the [scaling behavior]: > `scrcpy.exe` > Properties > Compatibility > Change high DPI settings > > Override high DPI scaling behavior > Scaling performed by: _Application_. [scaling behavior]: https://github.com/Genymobile/scrcpy/issues/40#issuecomment-424466723 -If the definition of your client window is far smaller than that of your device -screen, then you'll get poor quality. This is especially visible on text. See -[#40]. - -[#40]: https://github.com/Genymobile/scrcpy/issues/40 ### KWin compositor crashes From 94a7f1a0f84cb4bc998e7eb117517209e3ca5584 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Wed, 15 Apr 2020 21:11:00 +0200 Subject: [PATCH 064/450] Disable input events when necessary Disable input events on secondary displays before Android 10, even if FLAG_PRESENTATION is not set. Refs #1288 --- .../main/java/com/genymobile/scrcpy/Device.java | 17 ++++++----------- .../java/com/genymobile/scrcpy/DisplayInfo.java | 1 - .../scrcpy/wrappers/InputManager.java | 3 +-- 3 files changed, 7 insertions(+), 14 deletions(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/Device.java b/server/src/main/java/com/genymobile/scrcpy/Device.java index afa9f165..0c43dd34 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Device.java +++ b/server/src/main/java/com/genymobile/scrcpy/Device.java @@ -36,10 +36,7 @@ public final class Device { */ private final int layerStack; - /** - * The FLAG_PRESENTATION from the DisplayInfo - */ - private final boolean isPresentationDisplay; + private final boolean supportsInputEvents; public Device(Options options) { displayId = options.getDisplayId(); @@ -53,7 +50,6 @@ public final class Device { screenInfo = ScreenInfo.computeScreenInfo(displayInfo, options.getCrop(), options.getMaxSize(), options.getLockedVideoOrientation()); layerStack = displayInfo.getLayerStack(); - isPresentationDisplay = (displayInfoFlags & DisplayInfo.FLAG_PRESENTATION) != 0; registerRotationWatcher(new IRotationWatcher.Stub() { @Override @@ -73,8 +69,10 @@ public final class Device { Ln.w("Display doesn't have FLAG_SUPPORTS_PROTECTED_BUFFERS flag, mirroring can be restricted"); } - if (!supportsInputEvents()) { - Ln.w("Input events are not supported for displays with FLAG_PRESENTATION enabled for devices with API lower than 29"); + // main display or any display on Android >= Q + supportsInputEvents = displayId == 0 || Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q; + if (!supportsInputEvents) { + Ln.w("Input events are not supported for secondary displays before Android 10"); } } @@ -116,10 +114,7 @@ public final class Device { } public boolean supportsInputEvents() { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { - return true; - } - return !isPresentationDisplay; + return supportsInputEvents; } public boolean injectInputEvent(InputEvent inputEvent, int mode) { diff --git a/server/src/main/java/com/genymobile/scrcpy/DisplayInfo.java b/server/src/main/java/com/genymobile/scrcpy/DisplayInfo.java index 50bc94aa..4b8036f8 100644 --- a/server/src/main/java/com/genymobile/scrcpy/DisplayInfo.java +++ b/server/src/main/java/com/genymobile/scrcpy/DisplayInfo.java @@ -8,7 +8,6 @@ public final class DisplayInfo { private final int flags; public static final int FLAG_SUPPORTS_PROTECTED_BUFFERS = 0x00000001; - public static final int FLAG_PRESENTATION = 0x00000008; public DisplayInfo(int displayId, Size size, int rotation, int layerStack, int flags) { this.displayId = displayId; diff --git a/server/src/main/java/com/genymobile/scrcpy/wrappers/InputManager.java b/server/src/main/java/com/genymobile/scrcpy/wrappers/InputManager.java index 36d07353..e17b5a17 100644 --- a/server/src/main/java/com/genymobile/scrcpy/wrappers/InputManager.java +++ b/server/src/main/java/com/genymobile/scrcpy/wrappers/InputManager.java @@ -53,8 +53,7 @@ public final class InputManager { method.invoke(inputEvent, displayId); return true; } catch (InvocationTargetException | IllegalAccessException | NoSuchMethodException e) { - // just a warning, it might happen on old devices - Ln.w("Cannot associate a display id to the input event"); + Ln.e("Cannot associate a display id to the input event", e); return false; } } From 14ead499fd848504895fee7649b5c7430be148b5 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 17 Apr 2020 18:17:12 +0200 Subject: [PATCH 065/450] Fix touch coordinates on rotated display The touch coordinates were not rotated. --- app/src/input_manager.c | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/app/src/input_manager.c b/app/src/input_manager.c index 589eb842..72f15ee3 100644 --- a/app/src/input_manager.c +++ b/app/src/input_manager.c @@ -515,13 +515,11 @@ convert_touch(const SDL_TouchFingerEvent *from, struct screen *screen, return false; } - struct size frame_size = screen->frame_size; - to->inject_touch_event.pointer_id = from->fingerId; - to->inject_touch_event.position.screen_size = frame_size; + to->inject_touch_event.position.screen_size = screen->frame_size; // SDL touch event coordinates are normalized in the range [0; 1] - float x = from->x * frame_size.width; - float y = from->y * frame_size.height; + float x = from->x * screen->content_size.width; + float y = from->y * screen->content_size.height; to->inject_touch_event.position.point = rotate_position(screen, x, y); to->inject_touch_event.pressure = from->pressure; to->inject_touch_event.buttons = 0; From 125c5561e8f4e169fe52884e47fae1f3b9ffb9f7 Mon Sep 17 00:00:00 2001 From: Tzah Mazuz Date: Sat, 18 Apr 2020 02:13:19 +0200 Subject: [PATCH 066/450] Use MediaFormat constant for MIME type Replace "video/avc" by MIMETYPE_VIDEO_AVC. Signed-off-by: Romain Vimont --- server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java b/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java index 08affaf6..fc1a25b1 100644 --- a/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java +++ b/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java @@ -136,12 +136,12 @@ public class ScreenEncoder implements Device.RotationListener { } private static MediaCodec createCodec() throws IOException { - return MediaCodec.createEncoderByType("video/avc"); + return MediaCodec.createEncoderByType(MediaFormat.MIMETYPE_VIDEO_AVC); } private static MediaFormat createFormat(int bitRate, int maxFps, int iFrameInterval) { MediaFormat format = new MediaFormat(); - format.setString(MediaFormat.KEY_MIME, "video/avc"); + format.setString(MediaFormat.KEY_MIME, MediaFormat.MIMETYPE_VIDEO_AVC); format.setInteger(MediaFormat.KEY_BIT_RATE, bitRate); // must be present to configure the encoder, but does not impact the actual frame rate, which is variable format.setInteger(MediaFormat.KEY_FRAME_RATE, 60); From 44f720e4a4768a11fe2bd9cec8c952341bbbde62 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sat, 18 Apr 2020 00:31:31 +0200 Subject: [PATCH 067/450] Log new size on auto-resize request On "resize to fit" and "resize to pixel-perfect", log the new size. --- app/src/screen.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/app/src/screen.c b/app/src/screen.c index 512d7bbd..ea1b13f2 100644 --- a/app/src/screen.c +++ b/app/src/screen.c @@ -491,7 +491,8 @@ screen_resize_to_fit(struct screen *screen) { struct size optimal_size = get_optimal_window_size(screen, screen->content_size); SDL_SetWindowSize(screen->window, optimal_size.width, optimal_size.height); - LOGD("Resized to optimal size"); + LOGD("Resized to optimal size: %ux%u", optimal_size.width, + optimal_size.height); } void @@ -507,7 +508,8 @@ screen_resize_to_pixel_perfect(struct screen *screen) { struct size content_size = screen->content_size; SDL_SetWindowSize(screen->window, content_size.width, content_size.height); - LOGD("Resized to pixel-perfect"); + LOGD("Resized to pixel-perfect: %ux%u", content_size.width, + content_size.height); } void From 3c9ae99ddad51a5d7acf5867973fc30cef9593c1 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 17 Apr 2020 18:43:29 +0200 Subject: [PATCH 068/450] Move rotation coordinates to screen Move the window-to-frame coordinates conversion from the input manager to the screen. This will allow to apply more screen-related transformations without impacting the input manager. --- app/src/input_manager.c | 37 ++++--------------------------------- app/src/screen.c | 30 ++++++++++++++++++++++++++++++ app/src/screen.h | 5 +++++ 3 files changed, 39 insertions(+), 33 deletions(-) diff --git a/app/src/input_manager.c b/app/src/input_manager.c index 72f15ee3..14ee7e40 100644 --- a/app/src/input_manager.c +++ b/app/src/input_manager.c @@ -442,36 +442,6 @@ input_manager_process_key(struct input_manager *im, } } -static struct point -rotate_position(struct screen *screen, int32_t x, int32_t y) { - unsigned rotation = screen->rotation; - assert(rotation < 4); - - int32_t w = screen->content_size.width; - int32_t h = screen->content_size.height; - struct point result; - switch (rotation) { - case 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); - result.x = y; - result.y = w - x; - break; - } - return result; -} - static bool convert_mouse_motion(const SDL_MouseMotionEvent *from, struct screen *screen, struct control_msg *to) { @@ -480,7 +450,7 @@ convert_mouse_motion(const SDL_MouseMotionEvent *from, struct screen *screen, to->inject_touch_event.pointer_id = POINTER_ID_MOUSE; to->inject_touch_event.position.screen_size = screen->frame_size; to->inject_touch_event.position.point = - rotate_position(screen, from->x, from->y); + screen_convert_to_frame_coords(screen, from->x, from->y); to->inject_touch_event.pressure = 1.f; to->inject_touch_event.buttons = convert_mouse_buttons(from->state); @@ -520,7 +490,8 @@ convert_touch(const SDL_TouchFingerEvent *from, struct screen *screen, // SDL touch event coordinates are normalized in the range [0; 1] float x = from->x * screen->content_size.width; float y = from->y * screen->content_size.height; - to->inject_touch_event.position.point = rotate_position(screen, x, y); + to->inject_touch_event.position.point = + screen_convert_to_frame_coords(screen, x, y); to->inject_touch_event.pressure = from->pressure; to->inject_touch_event.buttons = 0; return true; @@ -556,7 +527,7 @@ convert_mouse_button(const SDL_MouseButtonEvent *from, struct screen *screen, to->inject_touch_event.pointer_id = POINTER_ID_MOUSE; to->inject_touch_event.position.screen_size = screen->frame_size; to->inject_touch_event.position.point = - rotate_position(screen, from->x, from->y); + screen_convert_to_frame_coords(screen, from->x, from->y); to->inject_touch_event.pressure = 1.f; to->inject_touch_event.buttons = convert_mouse_buttons(SDL_BUTTON(from->button)); diff --git a/app/src/screen.c b/app/src/screen.c index ea1b13f2..d4f96a39 100644 --- a/app/src/screen.c +++ b/app/src/screen.c @@ -553,3 +553,33 @@ screen_handle_window_event(struct screen *screen, break; } } + +struct point +screen_convert_to_frame_coords(struct screen *screen, int32_t x, int32_t y) { + unsigned rotation = screen->rotation; + assert(rotation < 4); + + int32_t w = screen->content_size.width; + int32_t h = screen->content_size.height; + struct point result; + switch (rotation) { + case 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); + result.x = y; + result.y = w - x; + break; + } + return result; +} diff --git a/app/src/screen.h b/app/src/screen.h index c9e019a1..85514279 100644 --- a/app/src/screen.h +++ b/app/src/screen.h @@ -114,4 +114,9 @@ screen_set_rotation(struct screen *screen, unsigned rotation); void screen_handle_window_event(struct screen *screen, const SDL_WindowEvent *event); +// convert point from window coordinates to frame coordinates +// x and y are expressed in pixels +struct point +screen_convert_to_frame_coords(struct screen *screen, int32_t x, int32_t y); + #endif From 561ede444e7ed2aa4a18a16988d2c8adc9d36151 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 23 Apr 2020 16:36:48 +0200 Subject: [PATCH 069/450] Mention Ubuntu 20.04 package Ubuntu 20.04 has been released today, and scrcpy is available in their repositories: --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 593dd2a9..5506dc31 100644 --- a/README.md +++ b/README.md @@ -37,7 +37,7 @@ control it using keyboard and mouse. ### Linux -In Debian (_testing_ and _sid_ for now): +On Debian (_testing_ and _sid_ for now) and Ubuntu (20.04): ``` apt install scrcpy From 92cb3a666109b67598cd36b9c0088fb697abbd23 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 24 Apr 2020 21:34:25 +0200 Subject: [PATCH 070/450] Improve resizing workaround Call the same method as when the event is received on the event loop, so that the behavior is the same in both cases. --- app/src/scrcpy.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index 81f8239e..d557b208 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -109,9 +109,10 @@ static int event_watcher(void *data, SDL_Event *event) { (void) data; if (event->type == SDL_WINDOWEVENT - && event->window.event == SDL_WINDOWEVENT_RESIZED) { - // called from another thread, not very safe, but it's a workaround! - screen_render(&screen); + && event->window.event == SDL_WINDOWEVENT_SIZE_CHANGED) { + // In practice, it seems to always be called from the same thread in + // that specific case. Anyway, it's just a workaround. + screen_handle_window_event(&screen, &event->window); } return 0; } From 8581d6850bb4cf9229e99de188b339da50eee777 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 24 Apr 2020 22:52:02 +0200 Subject: [PATCH 071/450] Stabilize auto-resize The window dimensions are integers, so resizing to fit the content may not be exact. When computing the optimal size, it could cause to reduce alternatively the width and height by few pixels, making the "optimal size" unstable. To avoid this problem, check if the optimal size is already correct either by keeping the width or the height. --- app/src/screen.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/app/src/screen.c b/app/src/screen.c index d4f96a39..a9019172 100644 --- a/app/src/screen.c +++ b/app/src/screen.c @@ -113,6 +113,13 @@ get_optimal_size(struct size current_size, struct size content_size) { h = MIN(current_size.height, display_size.height); } + if (h == w * content_size.height / content_size.width + || w == h * content_size.width / content_size.height) { + // The size is already optimal, if we ignore rounding errors due to + // integer window dimensions + return (struct size) {w, h}; + } + bool keep_width = content_size.width * h > content_size.height * w; if (keep_width) { // remove black borders on top and bottom From a14840a5159aa81fb005e41964a2d62ea0d2ae10 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 24 Apr 2020 23:01:58 +0200 Subject: [PATCH 072/450] Fix typo in comments --- app/src/screen.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/screen.c b/app/src/screen.c index a9019172..0af8de83 100644 --- a/app/src/screen.c +++ b/app/src/screen.c @@ -265,7 +265,7 @@ screen_init_rendering(struct screen *screen, const char *window_title, return false; } - // stats with "opengl" + // starts with "opengl" screen->use_opengl = renderer_name && !strncmp(renderer_name, "opengl", 6); if (screen->use_opengl) { struct sc_opengl *gl = &screen->gl; From b55ca127f8903f6b27f770b3ff82be4e8337ca93 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Mon, 13 Apr 2020 22:56:01 +0200 Subject: [PATCH 073/450] Upgrade FFmpeg (4.2.2) for Windows Include the latest version of FFmpeg in Windows releases. --- Makefile.CrossWindows | 20 ++++++++++---------- cross_win32.txt | 4 ++-- cross_win64.txt | 4 ++-- prebuilt-deps/Makefile | 24 ++++++++++++------------ 4 files changed, 26 insertions(+), 26 deletions(-) diff --git a/Makefile.CrossWindows b/Makefile.CrossWindows index 2b30dcb5..413088c7 100644 --- a/Makefile.CrossWindows +++ b/Makefile.CrossWindows @@ -100,11 +100,11 @@ dist-win32: build-server build-win32 build-win32-noconsole cp "$(SERVER_BUILD_DIR)"/server/scrcpy-server "$(DIST)/$(WIN32_TARGET_DIR)/" cp "$(WIN32_BUILD_DIR)"/app/scrcpy.exe "$(DIST)/$(WIN32_TARGET_DIR)/" cp "$(WIN32_NOCONSOLE_BUILD_DIR)"/app/scrcpy.exe "$(DIST)/$(WIN32_TARGET_DIR)/scrcpy-noconsole.exe" - cp prebuilt-deps/ffmpeg-4.2.1-win32-shared/bin/avutil-56.dll "$(DIST)/$(WIN32_TARGET_DIR)/" - cp prebuilt-deps/ffmpeg-4.2.1-win32-shared/bin/avcodec-58.dll "$(DIST)/$(WIN32_TARGET_DIR)/" - cp prebuilt-deps/ffmpeg-4.2.1-win32-shared/bin/avformat-58.dll "$(DIST)/$(WIN32_TARGET_DIR)/" - cp prebuilt-deps/ffmpeg-4.2.1-win32-shared/bin/swresample-3.dll "$(DIST)/$(WIN32_TARGET_DIR)/" - cp prebuilt-deps/ffmpeg-4.2.1-win32-shared/bin/swscale-5.dll "$(DIST)/$(WIN32_TARGET_DIR)/" + cp prebuilt-deps/ffmpeg-4.2.2-win32-shared/bin/avutil-56.dll "$(DIST)/$(WIN32_TARGET_DIR)/" + cp prebuilt-deps/ffmpeg-4.2.2-win32-shared/bin/avcodec-58.dll "$(DIST)/$(WIN32_TARGET_DIR)/" + cp prebuilt-deps/ffmpeg-4.2.2-win32-shared/bin/avformat-58.dll "$(DIST)/$(WIN32_TARGET_DIR)/" + cp prebuilt-deps/ffmpeg-4.2.2-win32-shared/bin/swresample-3.dll "$(DIST)/$(WIN32_TARGET_DIR)/" + cp prebuilt-deps/ffmpeg-4.2.2-win32-shared/bin/swscale-5.dll "$(DIST)/$(WIN32_TARGET_DIR)/" cp prebuilt-deps/platform-tools/adb.exe "$(DIST)/$(WIN32_TARGET_DIR)/" cp prebuilt-deps/platform-tools/AdbWinApi.dll "$(DIST)/$(WIN32_TARGET_DIR)/" cp prebuilt-deps/platform-tools/AdbWinUsbApi.dll "$(DIST)/$(WIN32_TARGET_DIR)/" @@ -115,11 +115,11 @@ dist-win64: build-server build-win64 build-win64-noconsole cp "$(SERVER_BUILD_DIR)"/server/scrcpy-server "$(DIST)/$(WIN64_TARGET_DIR)/" cp "$(WIN64_BUILD_DIR)"/app/scrcpy.exe "$(DIST)/$(WIN64_TARGET_DIR)/" cp "$(WIN64_NOCONSOLE_BUILD_DIR)"/app/scrcpy.exe "$(DIST)/$(WIN64_TARGET_DIR)/scrcpy-noconsole.exe" - cp prebuilt-deps/ffmpeg-4.2.1-win64-shared/bin/avutil-56.dll "$(DIST)/$(WIN64_TARGET_DIR)/" - cp prebuilt-deps/ffmpeg-4.2.1-win64-shared/bin/avcodec-58.dll "$(DIST)/$(WIN64_TARGET_DIR)/" - cp prebuilt-deps/ffmpeg-4.2.1-win64-shared/bin/avformat-58.dll "$(DIST)/$(WIN64_TARGET_DIR)/" - cp prebuilt-deps/ffmpeg-4.2.1-win64-shared/bin/swresample-3.dll "$(DIST)/$(WIN64_TARGET_DIR)/" - cp prebuilt-deps/ffmpeg-4.2.1-win64-shared/bin/swscale-5.dll "$(DIST)/$(WIN64_TARGET_DIR)/" + cp prebuilt-deps/ffmpeg-4.2.2-win64-shared/bin/avutil-56.dll "$(DIST)/$(WIN64_TARGET_DIR)/" + cp prebuilt-deps/ffmpeg-4.2.2-win64-shared/bin/avcodec-58.dll "$(DIST)/$(WIN64_TARGET_DIR)/" + cp prebuilt-deps/ffmpeg-4.2.2-win64-shared/bin/avformat-58.dll "$(DIST)/$(WIN64_TARGET_DIR)/" + cp prebuilt-deps/ffmpeg-4.2.2-win64-shared/bin/swresample-3.dll "$(DIST)/$(WIN64_TARGET_DIR)/" + cp prebuilt-deps/ffmpeg-4.2.2-win64-shared/bin/swscale-5.dll "$(DIST)/$(WIN64_TARGET_DIR)/" cp prebuilt-deps/platform-tools/adb.exe "$(DIST)/$(WIN64_TARGET_DIR)/" cp prebuilt-deps/platform-tools/AdbWinApi.dll "$(DIST)/$(WIN64_TARGET_DIR)/" cp prebuilt-deps/platform-tools/AdbWinUsbApi.dll "$(DIST)/$(WIN64_TARGET_DIR)/" diff --git a/cross_win32.txt b/cross_win32.txt index d13af0e2..17ad376f 100644 --- a/cross_win32.txt +++ b/cross_win32.txt @@ -15,6 +15,6 @@ cpu = 'i686' endian = 'little' [properties] -prebuilt_ffmpeg_shared = 'ffmpeg-4.2.1-win32-shared' -prebuilt_ffmpeg_dev = 'ffmpeg-4.2.1-win32-dev' +prebuilt_ffmpeg_shared = 'ffmpeg-4.2.2-win32-shared' +prebuilt_ffmpeg_dev = 'ffmpeg-4.2.2-win32-dev' prebuilt_sdl2 = 'SDL2-2.0.10/i686-w64-mingw32' diff --git a/cross_win64.txt b/cross_win64.txt index 09f387e1..faa95d28 100644 --- a/cross_win64.txt +++ b/cross_win64.txt @@ -15,6 +15,6 @@ cpu = 'x86_64' endian = 'little' [properties] -prebuilt_ffmpeg_shared = 'ffmpeg-4.2.1-win64-shared' -prebuilt_ffmpeg_dev = 'ffmpeg-4.2.1-win64-dev' +prebuilt_ffmpeg_shared = 'ffmpeg-4.2.2-win64-shared' +prebuilt_ffmpeg_dev = 'ffmpeg-4.2.2-win64-dev' prebuilt_sdl2 = 'SDL2-2.0.10/x86_64-w64-mingw32' diff --git a/prebuilt-deps/Makefile b/prebuilt-deps/Makefile index 892af6c7..0410a787 100644 --- a/prebuilt-deps/Makefile +++ b/prebuilt-deps/Makefile @@ -10,24 +10,24 @@ prepare-win32: prepare-sdl2 prepare-ffmpeg-shared-win32 prepare-ffmpeg-dev-win32 prepare-win64: prepare-sdl2 prepare-ffmpeg-shared-win64 prepare-ffmpeg-dev-win64 prepare-adb prepare-ffmpeg-shared-win32: - @./prepare-dep https://ffmpeg.zeranoe.com/builds/win32/shared/ffmpeg-4.2.1-win32-shared.zip \ - 9208255f409410d95147151d7e829b5699bf8d91bfe1e81c3f470f47c2fa66d2 \ - ffmpeg-4.2.1-win32-shared + @./prepare-dep https://ffmpeg.zeranoe.com/builds/win32/shared/ffmpeg-4.2.2-win32-shared.zip \ + ab5d603aaa54de360db2c2ffe378c82376b9343ea1175421dd644639aa07ee31 \ + ffmpeg-4.2.2-win32-shared prepare-ffmpeg-dev-win32: - @./prepare-dep https://ffmpeg.zeranoe.com/builds/win32/dev/ffmpeg-4.2.1-win32-dev.zip \ - c3469e6c5f031cbcc8cba88dee92d6548c5c6b6ff14f4097f18f72a92d0d70c4 \ - ffmpeg-4.2.1-win32-dev + @./prepare-dep https://ffmpeg.zeranoe.com/builds/win32/dev/ffmpeg-4.2.2-win32-dev.zip \ + 8d224be567a2950cad4be86f4aabdd045bfa52ad758e87c72cedd278613bc6c8 \ + ffmpeg-4.2.2-win32-dev prepare-ffmpeg-shared-win64: - @./prepare-dep https://ffmpeg.zeranoe.com/builds/win64/shared/ffmpeg-4.2.1-win64-shared.zip \ - 55063d3cf750a75485c7bf196031773d81a1b25d0980c7db48ecfc7701a42331 \ - ffmpeg-4.2.1-win64-shared + @./prepare-dep https://ffmpeg.zeranoe.com/builds/win64/shared/ffmpeg-4.2.2-win64-shared.zip \ + 5aedf268952b7d9f6541dbfcb47cd86a7e7881a3b7ba482fd3bc4ca33bda7bf5 \ + ffmpeg-4.2.2-win64-shared prepare-ffmpeg-dev-win64: - @./prepare-dep https://ffmpeg.zeranoe.com/builds/win64/dev/ffmpeg-4.2.1-win64-dev.zip \ - 5af393be5f25c0a71aa29efce768e477c35347f7f8e0d9696767d5b9d405b74e \ - ffmpeg-4.2.1-win64-dev + @./prepare-dep https://ffmpeg.zeranoe.com/builds/win64/dev/ffmpeg-4.2.2-win64-dev.zip \ + f4885f859c5b0d6663c2a0a4c1cf035b1c60b146402790b796bd3ad84f4f3ca2 \ + ffmpeg-4.2.2-win64-dev prepare-sdl2: @./prepare-dep https://libsdl.org/release/SDL2-devel-2.0.10-mingw.tar.gz \ From 76567e684a9ef1ec2b1577f84f15735335646f9f Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Mon, 13 Apr 2020 23:00:22 +0200 Subject: [PATCH 074/450] Upgrade SDL (2.0.12) for Windows Include the latest version of SDL in Windows releases. --- Makefile.CrossWindows | 4 ++-- cross_win32.txt | 2 +- cross_win64.txt | 2 +- prebuilt-deps/Makefile | 6 +++--- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Makefile.CrossWindows b/Makefile.CrossWindows index 413088c7..3aed2019 100644 --- a/Makefile.CrossWindows +++ b/Makefile.CrossWindows @@ -108,7 +108,7 @@ dist-win32: build-server build-win32 build-win32-noconsole cp prebuilt-deps/platform-tools/adb.exe "$(DIST)/$(WIN32_TARGET_DIR)/" cp prebuilt-deps/platform-tools/AdbWinApi.dll "$(DIST)/$(WIN32_TARGET_DIR)/" cp prebuilt-deps/platform-tools/AdbWinUsbApi.dll "$(DIST)/$(WIN32_TARGET_DIR)/" - cp prebuilt-deps/SDL2-2.0.10/i686-w64-mingw32/bin/SDL2.dll "$(DIST)/$(WIN32_TARGET_DIR)/" + cp prebuilt-deps/SDL2-2.0.12/i686-w64-mingw32/bin/SDL2.dll "$(DIST)/$(WIN32_TARGET_DIR)/" dist-win64: build-server build-win64 build-win64-noconsole mkdir -p "$(DIST)/$(WIN64_TARGET_DIR)" @@ -123,7 +123,7 @@ dist-win64: build-server build-win64 build-win64-noconsole cp prebuilt-deps/platform-tools/adb.exe "$(DIST)/$(WIN64_TARGET_DIR)/" cp prebuilt-deps/platform-tools/AdbWinApi.dll "$(DIST)/$(WIN64_TARGET_DIR)/" cp prebuilt-deps/platform-tools/AdbWinUsbApi.dll "$(DIST)/$(WIN64_TARGET_DIR)/" - cp prebuilt-deps/SDL2-2.0.10/x86_64-w64-mingw32/bin/SDL2.dll "$(DIST)/$(WIN64_TARGET_DIR)/" + cp prebuilt-deps/SDL2-2.0.12/x86_64-w64-mingw32/bin/SDL2.dll "$(DIST)/$(WIN64_TARGET_DIR)/" zip-win32: dist-win32 cd "$(DIST)/$(WIN32_TARGET_DIR)"; \ diff --git a/cross_win32.txt b/cross_win32.txt index 17ad376f..7b420690 100644 --- a/cross_win32.txt +++ b/cross_win32.txt @@ -17,4 +17,4 @@ endian = 'little' [properties] prebuilt_ffmpeg_shared = 'ffmpeg-4.2.2-win32-shared' prebuilt_ffmpeg_dev = 'ffmpeg-4.2.2-win32-dev' -prebuilt_sdl2 = 'SDL2-2.0.10/i686-w64-mingw32' +prebuilt_sdl2 = 'SDL2-2.0.12/i686-w64-mingw32' diff --git a/cross_win64.txt b/cross_win64.txt index faa95d28..cb9e0954 100644 --- a/cross_win64.txt +++ b/cross_win64.txt @@ -17,4 +17,4 @@ endian = 'little' [properties] prebuilt_ffmpeg_shared = 'ffmpeg-4.2.2-win64-shared' prebuilt_ffmpeg_dev = 'ffmpeg-4.2.2-win64-dev' -prebuilt_sdl2 = 'SDL2-2.0.10/x86_64-w64-mingw32' +prebuilt_sdl2 = 'SDL2-2.0.12/x86_64-w64-mingw32' diff --git a/prebuilt-deps/Makefile b/prebuilt-deps/Makefile index 0410a787..088cee92 100644 --- a/prebuilt-deps/Makefile +++ b/prebuilt-deps/Makefile @@ -30,9 +30,9 @@ prepare-ffmpeg-dev-win64: ffmpeg-4.2.2-win64-dev prepare-sdl2: - @./prepare-dep https://libsdl.org/release/SDL2-devel-2.0.10-mingw.tar.gz \ - a90a7cddaec4996f4d7be6d80c57ec69b062e132bffc513965f99217f603274a \ - SDL2-2.0.10 + @./prepare-dep https://libsdl.org/release/SDL2-devel-2.0.12-mingw.tar.gz \ + e614a60f797e35ef9f3f96aef3dc6a1d786de3cc7ca6216f97e435c0b6aafc46 \ + SDL2-2.0.12 prepare-adb: @./prepare-dep https://dl.google.com/android/repository/platform-tools_r29.0.5-windows.zip \ From 9babe268050cd8a791364e5edb346c3c15a6e575 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Wed, 29 Apr 2020 22:24:08 +0200 Subject: [PATCH 075/450] Bump version to 1.13 --- meson.build | 2 +- server/build.gradle | 4 ++-- server/build_without_gradle.sh | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/meson.build b/meson.build index e958b20e..49093453 100644 --- a/meson.build +++ b/meson.build @@ -1,5 +1,5 @@ project('scrcpy', 'c', - version: '1.12.1', + version: '1.13', meson_version: '>= 0.48', default_options: [ 'c_std=c11', diff --git a/server/build.gradle b/server/build.gradle index 539a97b8..3fa16519 100644 --- a/server/build.gradle +++ b/server/build.gradle @@ -6,8 +6,8 @@ android { applicationId "com.genymobile.scrcpy" minSdkVersion 21 targetSdkVersion 29 - versionCode 14 - versionName "1.12.1" + versionCode 15 + versionName "1.13" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" } buildTypes { diff --git a/server/build_without_gradle.sh b/server/build_without_gradle.sh index c117d89c..06fc0d75 100755 --- a/server/build_without_gradle.sh +++ b/server/build_without_gradle.sh @@ -12,7 +12,7 @@ set -e SCRCPY_DEBUG=false -SCRCPY_VERSION_NAME=1.12.1 +SCRCPY_VERSION_NAME=1.13 PLATFORM=${ANDROID_PLATFORM:-29} BUILD_TOOLS=${ANDROID_BUILD_TOOLS:-29.0.2} From 4e9e712312c9726824d28bed4fc85721e3a2b203 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Wed, 29 Apr 2020 22:56:26 +0200 Subject: [PATCH 076/450] Update links to v1.13 in README and BUILD --- BUILD.md | 6 +++--- README.md | 8 ++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/BUILD.md b/BUILD.md index 8b845583..e4f175ef 100644 --- a/BUILD.md +++ b/BUILD.md @@ -249,10 +249,10 @@ You can then [run](README.md#run) _scrcpy_. ## Prebuilt server - - [`scrcpy-server-v1.12.1`][direct-scrcpy-server] - _(SHA-256: 63e569c8a1d0c1df31d48c4214871c479a601782945fed50c1e61167d78266ea)_ + - [`scrcpy-server-v1.13`][direct-scrcpy-server] + _(SHA-256: 5fee64ca1ccdc2f38550f31f5353c66de3de30c2e929a964e30fa2d005d5f885)_ -[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.13/scrcpy-server-v1.13 Download the prebuilt server somewhere, and specify its path during the Meson configuration: diff --git a/README.md b/README.md index 55a380c5..a24e06d0 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# scrcpy (v1.12.1) +# scrcpy (v1.13) 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. @@ -69,10 +69,10 @@ hard). For Windows, for simplicity, a prebuilt archive with all the dependencies (including `adb`) is available: - - [`scrcpy-win64-v1.12.1.zip`][direct-win64] - _(SHA-256: 57d34b6d16cfd9fe169bc37c4df58ebd256d05c1ea3febc63d9cb0a027ab47c9)_ + - [`scrcpy-win64-v1.13.zip`][direct-win64] + _(SHA-256: 806aafc00d4db01513193addaa24f47858893ba5efe75770bfef6ae1ea987d27)_ -[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.13/scrcpy-win64-v1.13.zip It is also available in [Chocolatey]: From d4eeb1c84d67ed590189aa9d083ebd65f75fec92 Mon Sep 17 00:00:00 2001 From: antoninsmetana <52748994+antoninsmetana@users.noreply.github.com> Date: Fri, 1 May 2020 19:18:23 +0200 Subject: [PATCH 077/450] Fix AutoAdb url PR #1344 Signed-off-by: Romain Vimont --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index a24e06d0..20b777b0 100644 --- a/README.md +++ b/README.md @@ -270,7 +270,7 @@ You could use [AutoAdb]: autoadb scrcpy -s '{}' ``` -[AutoAdb]: https://github.com/rom1v/usbaudio +[AutoAdb]: https://github.com/rom1v/autoadb #### SSH tunnel From 62c0c1321feec0a673329cb6771efeefb6dc9657 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 30 Apr 2020 18:37:08 +0200 Subject: [PATCH 078/450] Apply workarounds only on error To avoid NullPointerException on some devices, workarounds have been implemented. But these workaround produce (harmless) internal errors causing exceptions to be printed in the console. To avoid this problem, apply the workarounds only if it fails without them. Fixes #994 Refs #365 Refs #940 --- .../java/com/genymobile/scrcpy/ScreenEncoder.java | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java b/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java index fc1a25b1..2377ec02 100644 --- a/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java +++ b/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java @@ -47,8 +47,20 @@ public class ScreenEncoder implements Device.RotationListener { public void streamScreen(Device device, FileDescriptor fd) throws IOException { Workarounds.prepareMainLooper(); - Workarounds.fillAppInfo(); + try { + internalStreamScreen(device, fd); + } catch (NullPointerException e) { + // Retry with workarounds enabled: + // + // + Ln.d("Applying workarounds to avoid NullPointerException"); + Workarounds.fillAppInfo(); + internalStreamScreen(device, fd); + } + } + + private void internalStreamScreen(Device device, FileDescriptor fd) throws IOException { MediaFormat format = createFormat(bitRate, maxFps, DEFAULT_I_FRAME_INTERVAL); device.setRotationListener(this); boolean alive; From 8c6799297b6c84a21509d5847106494b389d0b97 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 17 Nov 2019 15:23:16 +0100 Subject: [PATCH 079/450] Implement access to settings without Context Expose methods to access the Android settings using private APIs. This allows to read and write settings values immediately without starting a new process to call "settings". --- .../java/com/genymobile/scrcpy/Device.java | 5 + .../scrcpy/wrappers/ActivityManager.java | 87 ++++++++++++ .../scrcpy/wrappers/ContentProvider.java | 132 ++++++++++++++++++ .../scrcpy/wrappers/ServiceManager.java | 18 +++ 4 files changed, 242 insertions(+) create mode 100644 server/src/main/java/com/genymobile/scrcpy/wrappers/ActivityManager.java create mode 100644 server/src/main/java/com/genymobile/scrcpy/wrappers/ContentProvider.java diff --git a/server/src/main/java/com/genymobile/scrcpy/Device.java b/server/src/main/java/com/genymobile/scrcpy/Device.java index 0c43dd34..6aa339a1 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Device.java +++ b/server/src/main/java/com/genymobile/scrcpy/Device.java @@ -1,5 +1,6 @@ package com.genymobile.scrcpy; +import com.genymobile.scrcpy.wrappers.ContentProvider; import com.genymobile.scrcpy.wrappers.InputManager; import com.genymobile.scrcpy.wrappers.ServiceManager; import com.genymobile.scrcpy.wrappers.SurfaceControl; @@ -199,4 +200,8 @@ public final class Device { wm.thawRotation(); } } + + public ContentProvider createSettingsProvider() { + return serviceManager.getActivityManager().createSettingsProvider(); + } } diff --git a/server/src/main/java/com/genymobile/scrcpy/wrappers/ActivityManager.java b/server/src/main/java/com/genymobile/scrcpy/wrappers/ActivityManager.java new file mode 100644 index 00000000..71967c50 --- /dev/null +++ b/server/src/main/java/com/genymobile/scrcpy/wrappers/ActivityManager.java @@ -0,0 +1,87 @@ +package com.genymobile.scrcpy.wrappers; + +import com.genymobile.scrcpy.Ln; + +import android.os.Binder; +import android.os.IBinder; +import android.os.IInterface; + +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +public class ActivityManager { + + private final IInterface manager; + private Method getContentProviderExternalMethod; + private boolean getContentProviderExternalMethodLegacy; + private Method removeContentProviderExternalMethod; + + public ActivityManager(IInterface manager) { + this.manager = manager; + } + + private Method getGetContentProviderExternalMethod() throws NoSuchMethodException { + if (getContentProviderExternalMethod == null) { + try { + getContentProviderExternalMethod = manager.getClass() + .getMethod("getContentProviderExternal", String.class, int.class, IBinder.class, String.class); + } catch (NoSuchMethodException e) { + // old version + getContentProviderExternalMethod = manager.getClass().getMethod("getContentProviderExternal", String.class, int.class, IBinder.class); + getContentProviderExternalMethodLegacy = true; + } + } + return getContentProviderExternalMethod; + } + + private Method getRemoveContentProviderExternalMethod() throws NoSuchMethodException { + if (removeContentProviderExternalMethod == null) { + removeContentProviderExternalMethod = manager.getClass().getMethod("removeContentProviderExternal", String.class, IBinder.class); + } + return removeContentProviderExternalMethod; + } + + private ContentProvider getContentProviderExternal(String name, IBinder token) { + try { + Method method = getGetContentProviderExternalMethod(); + Object[] args; + if (!getContentProviderExternalMethodLegacy) { + // new version + args = new Object[]{name, ServiceManager.USER_ID, token, null}; + } else { + // old version + args = new Object[]{name, ServiceManager.USER_ID, token}; + } + // ContentProviderHolder providerHolder = getContentProviderExternal(...); + Object providerHolder = method.invoke(manager, args); + if (providerHolder == null) { + return null; + } + // IContentProvider provider = providerHolder.provider; + Field providerField = providerHolder.getClass().getDeclaredField("provider"); + providerField.setAccessible(true); + Object provider = providerField.get(providerHolder); + if (provider == null) { + return null; + } + return new ContentProvider(this, provider, name, token); + } catch (InvocationTargetException | IllegalAccessException | NoSuchMethodException | NoSuchFieldException e) { + Ln.e("Could not invoke method", e); + return null; + } + } + + void removeContentProviderExternal(String name, IBinder token) { + try { + Method method = getRemoveContentProviderExternalMethod(); + method.invoke(manager, name, token); + } catch (InvocationTargetException | IllegalAccessException | NoSuchMethodException e) { + Ln.e("Could not invoke method", e); + } + } + + public ContentProvider createSettingsProvider() { + return getContentProviderExternal("settings", new Binder()); + } +} diff --git a/server/src/main/java/com/genymobile/scrcpy/wrappers/ContentProvider.java b/server/src/main/java/com/genymobile/scrcpy/wrappers/ContentProvider.java new file mode 100644 index 00000000..b43494c7 --- /dev/null +++ b/server/src/main/java/com/genymobile/scrcpy/wrappers/ContentProvider.java @@ -0,0 +1,132 @@ +package com.genymobile.scrcpy.wrappers; + +import com.genymobile.scrcpy.Ln; + +import android.os.Bundle; +import android.os.IBinder; + +import java.io.Closeable; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +public class ContentProvider implements Closeable { + + public static final String TABLE_SYSTEM = "system"; + public static final String TABLE_SECURE = "secure"; + public static final String TABLE_GLOBAL = "global"; + + // See android/providerHolder/Settings.java + private static final String CALL_METHOD_GET_SYSTEM = "GET_system"; + private static final String CALL_METHOD_GET_SECURE = "GET_secure"; + private static final String CALL_METHOD_GET_GLOBAL = "GET_global"; + + private static final String CALL_METHOD_PUT_SYSTEM = "PUT_system"; + private static final String CALL_METHOD_PUT_SECURE = "PUT_secure"; + private static final String CALL_METHOD_PUT_GLOBAL = "PUT_global"; + + private static final String CALL_METHOD_USER_KEY = "_user"; + + private static final String NAME_VALUE_TABLE_VALUE = "value"; + + private final ActivityManager manager; + // android.content.IContentProvider + private final Object provider; + private final String name; + private final IBinder token; + + private Method callMethod; + private boolean callMethodLegacy; + + ContentProvider(ActivityManager manager, Object provider, String name, IBinder token) { + this.manager = manager; + this.provider = provider; + this.name = name; + this.token = token; + } + + private Method getCallMethod() throws NoSuchMethodException { + if (callMethod == null) { + try { + callMethod = provider.getClass().getMethod("call", String.class, String.class, String.class, String.class, Bundle.class); + } catch (NoSuchMethodException e) { + // old version + callMethod = provider.getClass().getMethod("call", String.class, String.class, String.class, Bundle.class); + callMethodLegacy = true; + } + } + return callMethod; + } + + private Bundle call(String callMethod, String arg, Bundle extras) { + try { + Method method = getCallMethod(); + Object[] args; + if (!callMethodLegacy) { + args = new Object[]{ServiceManager.PACKAGE_NAME, "settings", callMethod, arg, extras}; + } else { + args = new Object[]{ServiceManager.PACKAGE_NAME, callMethod, arg, extras}; + } + return (Bundle) method.invoke(provider, args); + } catch (InvocationTargetException | IllegalAccessException | NoSuchMethodException e) { + Ln.e("Could not invoke method", e); + return null; + } + } + + public void close() { + manager.removeContentProviderExternal(name, token); + } + + private static String getGetMethod(String table) { + switch (table) { + case TABLE_SECURE: + return CALL_METHOD_GET_SECURE; + case TABLE_SYSTEM: + return CALL_METHOD_GET_SYSTEM; + case TABLE_GLOBAL: + return CALL_METHOD_GET_GLOBAL; + default: + throw new IllegalArgumentException("Invalid table: " + table); + } + } + + private static String getPutMethod(String table) { + switch (table) { + case TABLE_SECURE: + return CALL_METHOD_PUT_SECURE; + case TABLE_SYSTEM: + return CALL_METHOD_PUT_SYSTEM; + case TABLE_GLOBAL: + return CALL_METHOD_PUT_GLOBAL; + default: + throw new IllegalArgumentException("Invalid table: " + table); + } + } + + public String getValue(String table, String key) { + String method = getGetMethod(table); + Bundle arg = new Bundle(); + arg.putInt(CALL_METHOD_USER_KEY, ServiceManager.USER_ID); + Bundle bundle = call(method, key, arg); + if (bundle == null) { + return null; + } + return bundle.getString("value"); + } + + public void putValue(String table, String key, String value) { + String method = getPutMethod(table); + Bundle arg = new Bundle(); + arg.putInt(CALL_METHOD_USER_KEY, ServiceManager.USER_ID); + arg.putString(NAME_VALUE_TABLE_VALUE, value); + call(method, key, arg); + } + + public String getAndPutValue(String table, String key, String value) { + String oldValue = getValue(table, key); + if (!value.equals(oldValue)) { + putValue(table, key, value); + } + return oldValue; + } +} diff --git a/server/src/main/java/com/genymobile/scrcpy/wrappers/ServiceManager.java b/server/src/main/java/com/genymobile/scrcpy/wrappers/ServiceManager.java index cc567837..f7985813 100644 --- a/server/src/main/java/com/genymobile/scrcpy/wrappers/ServiceManager.java +++ b/server/src/main/java/com/genymobile/scrcpy/wrappers/ServiceManager.java @@ -16,6 +16,7 @@ public final class ServiceManager { private PowerManager powerManager; private StatusBarManager statusBarManager; private ClipboardManager clipboardManager; + private ActivityManager activityManager; public ServiceManager() { try { @@ -76,4 +77,21 @@ public final class ServiceManager { } return clipboardManager; } + + public ActivityManager getActivityManager() { + if (activityManager == null) { + try { + // On old Android versions, the ActivityManager is not exposed via AIDL, + // so use ActivityManagerNative.getDefault() + Class cls = Class.forName("android.app.ActivityManagerNative"); + Method getDefaultMethod = cls.getDeclaredMethod("getDefault"); + IInterface am = (IInterface) getDefaultMethod.invoke(null); + activityManager = new ActivityManager(am); + } catch (Exception e) { + throw new AssertionError(e); + } + } + + return activityManager; + } } From 2f74ec2518377103e144684a9fb0c2d358168bbb Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sat, 2 May 2020 00:39:40 +0200 Subject: [PATCH 080/450] Add a clean up process on the device In order to clean up on close, use a separate process which is not killed when the device is disconnected (even if the main process itself is killed). --- .../java/com/genymobile/scrcpy/CleanUp.java | 62 +++++++++++++++++++ .../java/com/genymobile/scrcpy/Server.java | 14 +---- 2 files changed, 65 insertions(+), 11 deletions(-) create mode 100644 server/src/main/java/com/genymobile/scrcpy/CleanUp.java diff --git a/server/src/main/java/com/genymobile/scrcpy/CleanUp.java b/server/src/main/java/com/genymobile/scrcpy/CleanUp.java new file mode 100644 index 00000000..ca16cc3d --- /dev/null +++ b/server/src/main/java/com/genymobile/scrcpy/CleanUp.java @@ -0,0 +1,62 @@ +package com.genymobile.scrcpy; + +import com.genymobile.scrcpy.wrappers.ContentProvider; +import com.genymobile.scrcpy.wrappers.ServiceManager; + +import java.io.File; +import java.io.IOException; + +/** + * Handle the cleanup of scrcpy, even if the main process is killed. + *

+ * This is useful to restore some state when scrcpy is closed, even on device disconnection (which kills the scrcpy process). + */ +public final class CleanUp { + + public static final String SERVER_PATH = "/data/local/tmp/scrcpy-server.jar"; + + private CleanUp() { + // not instantiable + } + + public static void configure() throws IOException { + // TODO + boolean needProcess = false; + if (needProcess) { + startProcess(); + } else { + // There is no additional clean up to do when scrcpy dies + unlinkSelf(); + } + } + + private static void startProcess() throws IOException { + String[] cmd = {"app_process", "/", CleanUp.class.getName()}; + + ProcessBuilder builder = new ProcessBuilder(cmd); + builder.environment().put("CLASSPATH", SERVER_PATH); + builder.start(); + } + + private static void unlinkSelf() { + try { + new File(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(); + } catch (IOException e) { + // Expected when the server is dead + } + + Ln.i("Cleaning up"); + // TODO + } +} diff --git a/server/src/main/java/com/genymobile/scrcpy/Server.java b/server/src/main/java/com/genymobile/scrcpy/Server.java index a6f7a78c..4ac961de 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Server.java +++ b/server/src/main/java/com/genymobile/scrcpy/Server.java @@ -4,12 +4,10 @@ import android.graphics.Rect; import android.media.MediaCodec; import android.os.Build; -import java.io.File; import java.io.IOException; public final class Server { - private static final String SERVER_PATH = "/data/local/tmp/scrcpy-server.jar"; private Server() { // not instantiable @@ -18,6 +16,9 @@ public final class Server { private static void scrcpy(Options options) throws IOException { Ln.i("Device: " + Build.MANUFACTURER + " " + Build.MODEL + " (Android " + Build.VERSION.RELEASE + ")"); final Device device = new Device(options); + + CleanUp.configure(); + boolean tunnelForward = options.isTunnelForward(); try (DesktopConnection connection = DesktopConnection.open(device, tunnelForward)) { ScreenEncoder screenEncoder = new ScreenEncoder(options.getSendFrameMeta(), options.getBitRate(), options.getMaxFps()); @@ -132,14 +133,6 @@ public final class Server { return new Rect(x, y, x + width, y + height); } - private static void unlinkSelf() { - try { - new File(SERVER_PATH).delete(); - } catch (Exception e) { - Ln.e("Could not unlink server", e); - } - } - private static void suggestFix(Throwable e) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { if (e instanceof MediaCodec.CodecException) { @@ -172,7 +165,6 @@ public final class Server { } }); - unlinkSelf(); Options options = createOptions(args); scrcpy(options); } From dbb0df607c988c2a94877ebe61590d021d09bc4e Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 1 May 2020 23:15:48 +0200 Subject: [PATCH 081/450] Move constants to ServiceManager PACKAGE_NAME and USER_ID could be use by several "managers", so move them to the service manager. --- .../genymobile/scrcpy/wrappers/ClipboardManager.java | 12 ++++-------- .../genymobile/scrcpy/wrappers/ServiceManager.java | 4 ++++ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/wrappers/ClipboardManager.java b/server/src/main/java/com/genymobile/scrcpy/wrappers/ClipboardManager.java index ade23032..7a3bfafb 100644 --- a/server/src/main/java/com/genymobile/scrcpy/wrappers/ClipboardManager.java +++ b/server/src/main/java/com/genymobile/scrcpy/wrappers/ClipboardManager.java @@ -10,10 +10,6 @@ import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; public class ClipboardManager { - - private static final String PACKAGE_NAME = "com.android.shell"; - private static final int USER_ID = 0; - private final IInterface manager; private Method getPrimaryClipMethod; private Method setPrimaryClipMethod; @@ -46,17 +42,17 @@ public class ClipboardManager { private static ClipData getPrimaryClip(Method method, IInterface manager) throws InvocationTargetException, IllegalAccessException { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) { - return (ClipData) method.invoke(manager, PACKAGE_NAME); + return (ClipData) method.invoke(manager, ServiceManager.PACKAGE_NAME); } - return (ClipData) method.invoke(manager, PACKAGE_NAME, USER_ID); + return (ClipData) method.invoke(manager, ServiceManager.PACKAGE_NAME, ServiceManager.USER_ID); } private static void setPrimaryClip(Method method, IInterface manager, ClipData clipData) throws InvocationTargetException, IllegalAccessException { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) { - method.invoke(manager, clipData, PACKAGE_NAME); + method.invoke(manager, clipData, ServiceManager.PACKAGE_NAME); } else { - method.invoke(manager, clipData, PACKAGE_NAME, USER_ID); + method.invoke(manager, clipData, ServiceManager.PACKAGE_NAME, ServiceManager.USER_ID); } } diff --git a/server/src/main/java/com/genymobile/scrcpy/wrappers/ServiceManager.java b/server/src/main/java/com/genymobile/scrcpy/wrappers/ServiceManager.java index f7985813..c4ce59c2 100644 --- a/server/src/main/java/com/genymobile/scrcpy/wrappers/ServiceManager.java +++ b/server/src/main/java/com/genymobile/scrcpy/wrappers/ServiceManager.java @@ -8,6 +8,10 @@ import java.lang.reflect.Method; @SuppressLint("PrivateApi,DiscouragedPrivateApi") public final class ServiceManager { + + public static final String PACKAGE_NAME = "com.android.shell"; + public static final int USER_ID = 0; + private final Method getServiceMethod; private WindowManager windowManager; From 4668638ee10764e8341c0770d548c0b74de69a8d Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 1 May 2020 23:49:37 +0200 Subject: [PATCH 082/450] Handle "show touches" on the device-side Now that the server can access the Android settings and clean up properly, handle the "show touches" option from the server. The initial state is now correctly restored, even on device disconnection. --- README.md | 3 +- app/scrcpy.1 | 2 +- app/src/cli.c | 3 +- app/src/scrcpy.c | 39 +------------------ app/src/server.c | 1 + app/src/server.h | 1 + .../java/com/genymobile/scrcpy/CleanUp.java | 22 +++++++---- .../java/com/genymobile/scrcpy/Options.java | 9 +++++ .../java/com/genymobile/scrcpy/Server.java | 21 ++++++++-- 9 files changed, 50 insertions(+), 51 deletions(-) diff --git a/README.md b/README.md index 20b777b0..ea5731a7 100644 --- a/README.md +++ b/README.md @@ -429,7 +429,8 @@ device). Android provides this feature in _Developers options_. -_Scrcpy_ provides an option to enable this feature on start and disable on exit: +_Scrcpy_ provides an option to enable this feature on start and restore the +initial value on exit: ```bash scrcpy --show-touches diff --git a/app/scrcpy.1 b/app/scrcpy.1 index 673cd11d..15200460 100644 --- a/app/scrcpy.1 +++ b/app/scrcpy.1 @@ -136,7 +136,7 @@ Turn the device screen off immediately. .TP .B \-t, \-\-show\-touches -Enable "show touches" on start, disable on quit. +Enable "show touches" on start, restore the initial value on exit.. It only shows physical touches (not clicks from scrcpy). diff --git a/app/src/cli.c b/app/src/cli.c index 13351dee..d6d3c63e 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -130,7 +130,8 @@ scrcpy_print_usage(const char *arg0) { " Turn the device screen off immediately.\n" "\n" " -t, --show-touches\n" - " Enable \"show touches\" on start, disable on quit.\n" + " Enable \"show touches\" on start, restore the initial value\n" + " on exit.\n" " It only shows physical touches (not clicks from scrcpy).\n" "\n" " -v, --version\n" diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index d557b208..015dec08 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -229,21 +229,6 @@ event_loop(bool display, bool control) { return false; } -static process_t -set_show_touches_enabled(const char *serial, bool enabled) { - const char *value = enabled ? "1" : "0"; - const char *const adb_cmd[] = { - "shell", "settings", "put", "system", "show_touches", value - }; - return adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd)); -} - -static void -wait_show_touches(process_t process) { - // reap the process, ignore the result - process_check_success(process, "show_touches"); -} - static SDL_LogPriority sdl_priority_from_av_level(int level) { switch (level) { @@ -292,19 +277,12 @@ scrcpy(const struct scrcpy_options *options) { .lock_video_orientation = options->lock_video_orientation, .control = options->control, .display_id = options->display_id, + .show_touches = options->show_touches, }; if (!server_start(&server, options->serial, ¶ms)) { return false; } - process_t proc_show_touches = PROCESS_NONE; - bool show_touches_waited; - if (options->show_touches) { - LOGI("Enable show_touches"); - proc_show_touches = set_show_touches_enabled(options->serial, true); - show_touches_waited = false; - } - bool ret = false; bool fps_counter_initialized = false; @@ -421,11 +399,6 @@ scrcpy(const struct scrcpy_options *options) { } } - if (options->show_touches) { - wait_show_touches(proc_show_touches); - show_touches_waited = true; - } - input_manager.prefer_text = options->prefer_text; ret = event_loop(options->display, options->control); @@ -482,16 +455,6 @@ end: fps_counter_destroy(&fps_counter); } - if (options->show_touches) { - if (!show_touches_waited) { - // wait the process which enabled "show touches" - wait_show_touches(proc_show_touches); - } - LOGI("Disable show_touches"); - proc_show_touches = set_show_touches_enabled(options->serial, false); - wait_show_touches(proc_show_touches); - } - server_destroy(&server); return ret; diff --git a/app/src/server.c b/app/src/server.c index b102f0c2..5399d269 100644 --- a/app/src/server.c +++ b/app/src/server.c @@ -268,6 +268,7 @@ execute_server(struct server *server, const struct server_params *params) { "true", // always send frame meta (packet boundaries + timestamp) params->control ? "true" : "false", display_id_string, + params->show_touches ? "true" : "false", }; #ifdef SERVER_DEBUGGER LOGI("Server debugger waiting for a client on device port " diff --git a/app/src/server.h b/app/src/server.h index a2ecdefc..33e99bea 100644 --- a/app/src/server.h +++ b/app/src/server.h @@ -51,6 +51,7 @@ struct server_params { int8_t lock_video_orientation; bool control; uint16_t display_id; + bool show_touches; }; // init default values diff --git a/server/src/main/java/com/genymobile/scrcpy/CleanUp.java b/server/src/main/java/com/genymobile/scrcpy/CleanUp.java index ca16cc3d..ccbca275 100644 --- a/server/src/main/java/com/genymobile/scrcpy/CleanUp.java +++ b/server/src/main/java/com/genymobile/scrcpy/CleanUp.java @@ -19,19 +19,18 @@ public final class CleanUp { // not instantiable } - public static void configure() throws IOException { - // TODO - boolean needProcess = false; + public static void configure(boolean disableShowTouches) throws IOException { + boolean needProcess = disableShowTouches; if (needProcess) { - startProcess(); + startProcess(disableShowTouches); } else { // There is no additional clean up to do when scrcpy dies unlinkSelf(); } } - private static void startProcess() throws IOException { - String[] cmd = {"app_process", "/", CleanUp.class.getName()}; + private static void startProcess(boolean disableShowTouches) throws IOException { + String[] cmd = {"app_process", "/", CleanUp.class.getName(), String.valueOf(disableShowTouches)}; ProcessBuilder builder = new ProcessBuilder(cmd); builder.environment().put("CLASSPATH", SERVER_PATH); @@ -57,6 +56,15 @@ public final class CleanUp { } Ln.i("Cleaning up"); - // TODO + + boolean disableShowTouches = Boolean.parseBoolean(args[0]); + + if (disableShowTouches) { + ServiceManager serviceManager = new ServiceManager(); + try (ContentProvider settings = serviceManager.getActivityManager().createSettingsProvider()) { + Ln.i("Disabling \"show touches\""); + settings.putValue(ContentProvider.TABLE_SYSTEM, "show_touches", "0"); + } + } } } diff --git a/server/src/main/java/com/genymobile/scrcpy/Options.java b/server/src/main/java/com/genymobile/scrcpy/Options.java index 2a4bba33..838416c8 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Options.java +++ b/server/src/main/java/com/genymobile/scrcpy/Options.java @@ -12,6 +12,7 @@ public class Options { private boolean sendFrameMeta; // send PTS so that the client may record properly private boolean control; private int displayId; + private boolean showTouches; public int getMaxSize() { return maxSize; @@ -84,4 +85,12 @@ public class Options { public void setDisplayId(int displayId) { this.displayId = displayId; } + + public boolean getShowTouches() { + return showTouches; + } + + public void setShowTouches(boolean showTouches) { + this.showTouches = showTouches; + } } diff --git a/server/src/main/java/com/genymobile/scrcpy/Server.java b/server/src/main/java/com/genymobile/scrcpy/Server.java index 4ac961de..43ab0f55 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Server.java +++ b/server/src/main/java/com/genymobile/scrcpy/Server.java @@ -1,5 +1,7 @@ package com.genymobile.scrcpy; +import com.genymobile.scrcpy.wrappers.ContentProvider; + import android.graphics.Rect; import android.media.MediaCodec; import android.os.Build; @@ -17,7 +19,16 @@ public final class Server { Ln.i("Device: " + Build.MANUFACTURER + " " + Build.MODEL + " (Android " + Build.VERSION.RELEASE + ")"); final Device device = new Device(options); - CleanUp.configure(); + boolean mustDisableShowTouchesOnCleanUp = false; + if (options.getShowTouches()) { + try (ContentProvider settings = device.createSettingsProvider()) { + String oldValue = settings.getAndPutValue(ContentProvider.TABLE_SYSTEM, "show_touches", "1"); + // If "show touches" was disabled, it must be disabled back on clean up + mustDisableShowTouchesOnCleanUp = !"1".equals(oldValue); + } + } + + CleanUp.configure(mustDisableShowTouchesOnCleanUp); boolean tunnelForward = options.isTunnelForward(); try (DesktopConnection connection = DesktopConnection.open(device, tunnelForward)) { @@ -80,8 +91,9 @@ public final class Server { "The server version (" + BuildConfig.VERSION_NAME + ") does not match the client " + "(" + clientVersion + ")"); } - if (args.length != 10) { - throw new IllegalArgumentException("Expecting 10 parameters"); + final int expectedParameters = 11; + if (args.length != expectedParameters) { + throw new IllegalArgumentException("Expecting " + expectedParameters + " parameters"); } Options options = new Options(); @@ -114,6 +126,9 @@ public final class Server { int displayId = Integer.parseInt(args[9]); options.setDisplayId(displayId); + boolean showTouches = Boolean.parseBoolean(args[10]); + options.setShowTouches(showTouches); + return options; } From 828327365a51ef918c5b59db3b656feb57c8c9fa Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sat, 2 May 2020 01:51:35 +0200 Subject: [PATCH 083/450] Reorder options in alphabetical order --- app/src/cli.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/cli.c b/app/src/cli.c index d6d3c63e..d49301ac 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -487,6 +487,7 @@ scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) { {"no-display", no_argument, NULL, 'N'}, {"no-mipmaps", no_argument, NULL, OPT_NO_MIPMAPS}, {"port", required_argument, NULL, 'p'}, + {"prefer-text", no_argument, NULL, OPT_PREFER_TEXT}, {"push-target", required_argument, NULL, OPT_PUSH_TARGET}, {"record", required_argument, NULL, 'r'}, {"record-format", required_argument, NULL, OPT_RECORD_FORMAT}, @@ -497,7 +498,6 @@ scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) { {"serial", required_argument, NULL, 's'}, {"show-touches", no_argument, NULL, 't'}, {"turn-screen-off", no_argument, NULL, 'S'}, - {"prefer-text", no_argument, NULL, OPT_PREFER_TEXT}, {"version", no_argument, NULL, 'v'}, {"window-title", required_argument, NULL, OPT_WINDOW_TITLE}, {"window-x", required_argument, NULL, OPT_WINDOW_X}, From c77024314ddc9fa7cde51828eaef455a1ee10344 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sat, 2 May 2020 01:54:48 +0200 Subject: [PATCH 084/450] Add an option to keep the device awake Add an option to prevent the device to sleep: scrcpy --stay-awake scrcpy -w The initial state is restored on exit. Fixes #631 --- README.md | 20 +++++++++++ app/scrcpy.1 | 4 +++ app/src/cli.c | 14 +++++++- app/src/scrcpy.c | 1 + app/src/scrcpy.h | 2 ++ app/src/server.c | 1 + app/src/server.h | 1 + .../java/com/genymobile/scrcpy/CleanUp.java | 23 ++++++++----- .../java/com/genymobile/scrcpy/Options.java | 9 +++++ .../java/com/genymobile/scrcpy/Server.java | 33 +++++++++++++++---- 10 files changed, 93 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index ea5731a7..09cb27b8 100644 --- a/README.md +++ b/README.md @@ -396,6 +396,18 @@ The list of display ids can be retrieved by: adb shell dumpsys display # search "mDisplayId=" in the output ``` +#### Stay awake + +To prevent the device to sleep after some delay: + +```bash +scrcpy --stay-awake +scrcpy -w +``` + +The initial state is restored when scrcpy is closed. + + #### Turn screen off It is possible to turn the device screen off while mirroring on start with a @@ -410,6 +422,14 @@ Or by pressing `Ctrl`+`o` at any time. To turn it back on, press `POWER` (or `Ctrl`+`p`). +It can be useful to also prevent the device to sleep: + +```bash +scrcpy --turn-screen-off --stay-awake +scrcpy -Sw +``` + + #### Render expired frames By default, to minimize latency, _scrcpy_ always renders the last decoded frame diff --git a/app/scrcpy.1 b/app/scrcpy.1 index 15200460..02389159 100644 --- a/app/scrcpy.1 +++ b/app/scrcpy.1 @@ -144,6 +144,10 @@ It only shows physical touches (not clicks from scrcpy). .B \-v, \-\-version Print the version of scrcpy. +.TP +.B \-w, \-\-stay-awake +Keep the device on while scrcpy is running. + .TP .B \-\-window\-borderless Disable window decorations (display borderless window). diff --git a/app/src/cli.c b/app/src/cli.c index d49301ac..0c68279f 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -137,6 +137,9 @@ scrcpy_print_usage(const char *arg0) { " -v, --version\n" " Print the version of scrcpy.\n" "\n" + " -w, --stay-awake\n" + " Keep the device on while scrcpy is running.\n" + "\n" " --window-borderless\n" " Disable window decorations (display borderless window).\n" "\n" @@ -497,6 +500,7 @@ scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) { {"rotation", required_argument, NULL, OPT_ROTATION}, {"serial", required_argument, NULL, 's'}, {"show-touches", no_argument, NULL, 't'}, + {"stay-awake", no_argument, NULL, 'w'}, {"turn-screen-off", no_argument, NULL, 'S'}, {"version", no_argument, NULL, 'v'}, {"window-title", required_argument, NULL, OPT_WINDOW_TITLE}, @@ -514,7 +518,7 @@ scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) { optind = 0; // reset to start from the first argument in tests int c; - while ((c = getopt_long(argc, argv, "b:c:fF:hm:nNp:r:s:StTv", long_options, + while ((c = getopt_long(argc, argv, "b:c:fF:hm:nNp:r:s:StTvw", long_options, NULL)) != -1) { switch (c) { case 'b': @@ -594,6 +598,9 @@ scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) { case 'v': args->version = true; break; + case 'w': + opts->stay_awake = true; + break; case OPT_RENDER_EXPIRED_FRAMES: opts->render_expired_frames = true; break; @@ -676,5 +683,10 @@ scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) { return false; } + if (!opts->control && opts->stay_awake) { + LOGE("Could not request to stay awake if control is disabled"); + return false; + } + return true; } diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index 015dec08..c085e769 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -278,6 +278,7 @@ scrcpy(const struct scrcpy_options *options) { .control = options->control, .display_id = options->display_id, .show_touches = options->show_touches, + .stay_awake = options->stay_awake, }; if (!server_start(&server, options->serial, ¶ms)) { return false; diff --git a/app/src/scrcpy.h b/app/src/scrcpy.h index d6b0a0f6..99e72ef0 100644 --- a/app/src/scrcpy.h +++ b/app/src/scrcpy.h @@ -38,6 +38,7 @@ struct scrcpy_options { bool prefer_text; bool window_borderless; bool mipmaps; + bool stay_awake; }; #define SCRCPY_OPTIONS_DEFAULT { \ @@ -72,6 +73,7 @@ struct scrcpy_options { .prefer_text = false, \ .window_borderless = false, \ .mipmaps = true, \ + .stay_awake = false, \ } bool diff --git a/app/src/server.c b/app/src/server.c index 5399d269..b98cec2f 100644 --- a/app/src/server.c +++ b/app/src/server.c @@ -269,6 +269,7 @@ execute_server(struct server *server, const struct server_params *params) { params->control ? "true" : "false", display_id_string, params->show_touches ? "true" : "false", + params->stay_awake ? "true" : "false", }; #ifdef SERVER_DEBUGGER LOGI("Server debugger waiting for a client on device port " diff --git a/app/src/server.h b/app/src/server.h index 33e99bea..13d0b451 100644 --- a/app/src/server.h +++ b/app/src/server.h @@ -52,6 +52,7 @@ struct server_params { bool control; uint16_t display_id; bool show_touches; + bool stay_awake; }; // init default values diff --git a/server/src/main/java/com/genymobile/scrcpy/CleanUp.java b/server/src/main/java/com/genymobile/scrcpy/CleanUp.java index ccbca275..74555636 100644 --- a/server/src/main/java/com/genymobile/scrcpy/CleanUp.java +++ b/server/src/main/java/com/genymobile/scrcpy/CleanUp.java @@ -19,18 +19,18 @@ public final class CleanUp { // not instantiable } - public static void configure(boolean disableShowTouches) throws IOException { - boolean needProcess = disableShowTouches; + public static void configure(boolean disableShowTouches, int restoreStayOn) throws IOException { + boolean needProcess = disableShowTouches || restoreStayOn != -1; if (needProcess) { - startProcess(disableShowTouches); + startProcess(disableShowTouches, restoreStayOn); } else { // There is no additional clean up to do when scrcpy dies unlinkSelf(); } } - private static void startProcess(boolean disableShowTouches) throws IOException { - String[] cmd = {"app_process", "/", CleanUp.class.getName(), String.valueOf(disableShowTouches)}; + private static void startProcess(boolean disableShowTouches, int restoreStayOn) throws IOException { + String[] cmd = {"app_process", "/", CleanUp.class.getName(), String.valueOf(disableShowTouches), String.valueOf(restoreStayOn)}; ProcessBuilder builder = new ProcessBuilder(cmd); builder.environment().put("CLASSPATH", SERVER_PATH); @@ -58,12 +58,19 @@ public final class CleanUp { Ln.i("Cleaning up"); boolean disableShowTouches = Boolean.parseBoolean(args[0]); + int restoreStayOn = Integer.parseInt(args[1]); - if (disableShowTouches) { + if (disableShowTouches || restoreStayOn != -1) { ServiceManager serviceManager = new ServiceManager(); try (ContentProvider settings = serviceManager.getActivityManager().createSettingsProvider()) { - Ln.i("Disabling \"show touches\""); - settings.putValue(ContentProvider.TABLE_SYSTEM, "show_touches", "0"); + if (disableShowTouches) { + Ln.i("Disabling \"show touches\""); + settings.putValue(ContentProvider.TABLE_SYSTEM, "show_touches", "0"); + } + if (restoreStayOn != -1) { + Ln.i("Restoring \"stay awake\""); + settings.putValue(ContentProvider.TABLE_GLOBAL, "stay_on_while_plugged_in", String.valueOf(restoreStayOn)); + } } } } diff --git a/server/src/main/java/com/genymobile/scrcpy/Options.java b/server/src/main/java/com/genymobile/scrcpy/Options.java index 838416c8..152aa2f7 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Options.java +++ b/server/src/main/java/com/genymobile/scrcpy/Options.java @@ -13,6 +13,7 @@ public class Options { private boolean control; private int displayId; private boolean showTouches; + private boolean stayAwake; public int getMaxSize() { return maxSize; @@ -93,4 +94,12 @@ public class Options { public void setShowTouches(boolean showTouches) { this.showTouches = showTouches; } + + public boolean getStayAwake() { + return stayAwake; + } + + public void setStayAwake(boolean stayAwake) { + this.stayAwake = stayAwake; + } } diff --git a/server/src/main/java/com/genymobile/scrcpy/Server.java b/server/src/main/java/com/genymobile/scrcpy/Server.java index 43ab0f55..73776c3e 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Server.java +++ b/server/src/main/java/com/genymobile/scrcpy/Server.java @@ -4,6 +4,7 @@ import com.genymobile.scrcpy.wrappers.ContentProvider; import android.graphics.Rect; import android.media.MediaCodec; +import android.os.BatteryManager; import android.os.Build; import java.io.IOException; @@ -20,15 +21,32 @@ public final class Server { final Device device = new Device(options); boolean mustDisableShowTouchesOnCleanUp = false; - if (options.getShowTouches()) { + int restoreStayOn = -1; + if (options.getShowTouches() || options.getStayAwake()) { try (ContentProvider settings = device.createSettingsProvider()) { - String oldValue = settings.getAndPutValue(ContentProvider.TABLE_SYSTEM, "show_touches", "1"); - // If "show touches" was disabled, it must be disabled back on clean up - mustDisableShowTouchesOnCleanUp = !"1".equals(oldValue); + if (options.getShowTouches()) { + String oldValue = settings.getAndPutValue(ContentProvider.TABLE_SYSTEM, "show_touches", "1"); + // If "show touches" was disabled, it must be disabled back on clean up + mustDisableShowTouchesOnCleanUp = !"1".equals(oldValue); + } + + if (options.getStayAwake()) { + int stayOn = BatteryManager.BATTERY_PLUGGED_AC | BatteryManager.BATTERY_PLUGGED_USB | BatteryManager.BATTERY_PLUGGED_WIRELESS; + String oldValue = settings.getAndPutValue(ContentProvider.TABLE_GLOBAL, "stay_on_while_plugged_in", String.valueOf(stayOn)); + try { + restoreStayOn = Integer.parseInt(oldValue); + if (restoreStayOn == stayOn) { + // No need to restore + restoreStayOn = -1; + } + } catch (NumberFormatException e) { + restoreStayOn = 0; + } + } } } - CleanUp.configure(mustDisableShowTouchesOnCleanUp); + CleanUp.configure(mustDisableShowTouchesOnCleanUp, restoreStayOn); boolean tunnelForward = options.isTunnelForward(); try (DesktopConnection connection = DesktopConnection.open(device, tunnelForward)) { @@ -91,7 +109,7 @@ public final class Server { "The server version (" + BuildConfig.VERSION_NAME + ") does not match the client " + "(" + clientVersion + ")"); } - final int expectedParameters = 11; + final int expectedParameters = 12; if (args.length != expectedParameters) { throw new IllegalArgumentException("Expecting " + expectedParameters + " parameters"); } @@ -129,6 +147,9 @@ public final class Server { boolean showTouches = Boolean.parseBoolean(args[10]); options.setShowTouches(showTouches); + boolean stayAwake = Boolean.parseBoolean(args[11]); + options.setStayAwake(stayAwake); + return options; } From 74ece9b45b6ee477d786c7a9b6e2f21f90f8b5a2 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Tue, 5 May 2020 19:01:44 +0200 Subject: [PATCH 085/450] Simplify ScreenEncoder more Commit 927d655ff66fa439ccd27e054ae6d54a651640cc removed the iFrameInternal field and constructor argument. Also remove it from createFormat() arguments. --- .../src/main/java/com/genymobile/scrcpy/ScreenEncoder.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java b/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java index 2377ec02..651affde 100644 --- a/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java +++ b/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java @@ -61,7 +61,7 @@ public class ScreenEncoder implements Device.RotationListener { } private void internalStreamScreen(Device device, FileDescriptor fd) throws IOException { - MediaFormat format = createFormat(bitRate, maxFps, DEFAULT_I_FRAME_INTERVAL); + MediaFormat format = createFormat(bitRate, maxFps); device.setRotationListener(this); boolean alive; try { @@ -151,14 +151,14 @@ public class ScreenEncoder implements Device.RotationListener { return MediaCodec.createEncoderByType(MediaFormat.MIMETYPE_VIDEO_AVC); } - private static MediaFormat createFormat(int bitRate, int maxFps, int iFrameInterval) { + private static MediaFormat createFormat(int bitRate, int maxFps) { MediaFormat format = new MediaFormat(); format.setString(MediaFormat.KEY_MIME, MediaFormat.MIMETYPE_VIDEO_AVC); format.setInteger(MediaFormat.KEY_BIT_RATE, bitRate); // must be present to configure the encoder, but does not impact the actual frame rate, which is variable format.setInteger(MediaFormat.KEY_FRAME_RATE, 60); format.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface); - format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, iFrameInterval); + format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, DEFAULT_I_FRAME_INTERVAL); // 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) { From ead7ee4a03c9533ea7bb992a9c2df6ec757fd1f0 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Wed, 6 May 2020 01:05:05 +0200 Subject: [PATCH 086/450] Revert "Improve resizing workaround" This reverts commit 92cb3a666109b67598cd36b9c0088fb697abbd23, which broke the fullscreen/maximized restoration size on Windows. Fixes #1346 --- app/src/scrcpy.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index c085e769..096b0794 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -109,10 +109,10 @@ static int event_watcher(void *data, SDL_Event *event) { (void) data; if (event->type == SDL_WINDOWEVENT - && event->window.event == SDL_WINDOWEVENT_SIZE_CHANGED) { + && event->window.event == SDL_WINDOWEVENT_RESIZED) { // In practice, it seems to always be called from the same thread in // that specific case. Anyway, it's just a workaround. - screen_handle_window_event(&screen, &event->window); + screen_render(&screen); } return 0; } From 0b4e484da014833f90d58b2b336bf9e5204b1f71 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Wed, 6 May 2020 22:21:33 +0200 Subject: [PATCH 087/450] Add a note about multi-display limitation Secondary display mirroring is read-only before Android 10. --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index 20b777b0..04b644a4 100644 --- a/README.md +++ b/README.md @@ -396,6 +396,10 @@ The list of display ids can be retrieved by: adb shell dumpsys display # search "mDisplayId=" in the output ``` +The secondary display may only be controlled if the device runs at least Android +10 (otherwise it is mirrored in read-only). + + #### Turn screen off It is possible to turn the device screen off while mirroring on start with a From 1a9429f3c73e319f108211a66ad57b8c2c9a60aa Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Wed, 6 May 2020 22:26:43 +0200 Subject: [PATCH 088/450] Improve bug report template Insert a code block to (hopefully) increase the chances that users format their terminal output. --- .github/ISSUE_TEMPLATE/bug_report.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 29a2bf01..1c04da7f 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -21,5 +21,9 @@ assignees: '' A clear and concise description of what the bug is. On errors, please provide the output of the console (and `adb logcat` if relevant). -Format them between code blocks (delimited by ```). + +``` +Please paste terminal output in a code block. +``` + Please do not post screenshots of your terminal, just post the content as text instead. From e2d5f0e7fc98af97f3785ce492b7e8f87b109df1 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 7 May 2020 15:08:11 +0200 Subject: [PATCH 089/450] Send scroll events as a touchscreen Scroll events were sent with a mouse input device. When scrolling on a list, this could cause the whole list to be focused, and drawn with the focus color as background. Send scroll events with a touchscreen input device instead (like motion events). Fixes #1362 --- server/src/main/java/com/genymobile/scrcpy/Controller.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/Controller.java b/server/src/main/java/com/genymobile/scrcpy/Controller.java index 32a18e16..eb5a0805 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Controller.java +++ b/server/src/main/java/com/genymobile/scrcpy/Controller.java @@ -215,7 +215,7 @@ public class Controller { MotionEvent event = MotionEvent .obtain(lastTouchDown, now, MotionEvent.ACTION_SCROLL, 1, pointerProperties, pointerCoords, 0, 0, 1f, 1f, DEVICE_ID_VIRTUAL, 0, - InputDevice.SOURCE_MOUSE, 0); + InputDevice.SOURCE_TOUCHSCREEN, 0); return injectEvent(event); } From 28abd98f7fef5161ede934a41287b2c0ec1d7668 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 8 May 2020 14:54:33 +0200 Subject: [PATCH 090/450] Properly handle Ctrl+C on Windows By default, Ctrl+C just kills the process on Windows. This caused corrupted video files on recording. Handle Ctrl+C properly to clean up properly. Fixes #818 --- README.md | 1 - app/src/scrcpy.c | 24 ++++++++++++++++++++++++ 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 09cb27b8..0d0c4c5e 100644 --- a/README.md +++ b/README.md @@ -210,7 +210,6 @@ To disable mirroring while recording: scrcpy --no-display --record file.mp4 scrcpy -Nr file.mkv # interrupt recording with Ctrl+C -# Ctrl+C does not terminate properly on Windows, so disconnect the device ``` "Skipped frames" are recorded, even if they are not displayed in real time (for diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index 096b0794..2a4702e6 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -7,6 +7,10 @@ #include #include +#ifdef _WIN32 +# include +#endif + #include "config.h" #include "command.h" #include "common.h" @@ -45,6 +49,18 @@ static struct input_manager input_manager = { .prefer_text = false, // initialized later }; +#ifdef _WIN32 +BOOL windows_ctrl_handler(DWORD ctrl_type) { + if (ctrl_type == CTRL_C_EVENT) { + SDL_Event event; + event.type = SDL_QUIT; + SDL_PushEvent(&event); + return TRUE; + } + return FALSE; +} +#endif // _WIN32 + // init SDL and set appropriate hints static bool sdl_init_and_configure(bool display, const char *render_driver) { @@ -56,6 +72,14 @@ sdl_init_and_configure(bool display, const char *render_driver) { atexit(SDL_Quit); +#ifdef _WIN32 + // Clean up properly on Ctrl+C on Windows + bool ok = SetConsoleCtrlHandler(windows_ctrl_handler, TRUE); + if (!ok) { + LOGW("Could not set Ctrl+C handler"); + } +#endif // _WIN32 + if (!display) { return true; } From 24c803924449b1f72801e982ba35dfb80d54116d Mon Sep 17 00:00:00 2001 From: latreta Date: Wed, 22 Apr 2020 14:07:47 -0300 Subject: [PATCH 091/450] Add Brazilian Portuguese translation Signed-off-by: Romain Vimont --- README.pt-br.md | 529 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 529 insertions(+) create mode 100644 README.pt-br.md diff --git a/README.pt-br.md b/README.pt-br.md new file mode 100644 index 00000000..8fb0f149 --- /dev/null +++ b/README.pt-br.md @@ -0,0 +1,529 @@ +# scrcpy (v1.12.1) + +Esta aplicação fornece visualização e controle de dispositivos Android conectados via +USB (ou [via TCP/IP][article-tcpip]). Não requer nenhum acesso root. +Funciona em _GNU/Linux_, _Windows_ e _macOS_. + +![screenshot](assets/screenshot-debian-600.jpg) + +Foco em: + + - **leveza** (Nativo, mostra apenas a tela do dispositivo) + - **performance** (30~60fps) + - **qualidade** (1920×1080 ou acima) + - **baixa latência** ([35~70ms][lowlatency]) + - **baixo tempo de inicialização** (~1 segundo para mostrar a primeira imagem) + - **não intrusivo** (nada é deixado instalado no dispositivo) + +[lowlatency]: https://github.com/Genymobile/scrcpy/pull/646 + + +## Requisitos + +O Dispositivo Android requer pelo menos a API 21 (Android 5.0). + + +Tenha certeza de ter [ativado a depuração USB][enable-adb] no(s) seu(s) dispositivo(s). + +[enable-adb]: https://developer.android.com/studio/command-line/adb.html#Enabling + + +Em alguns dispositivos, você também precisará ativar [uma opção adicional][control] para controlá-lo usando o teclado e mouse. + +[control]: https://github.com/Genymobile/scrcpy/issues/70#issuecomment-373286323 + + +## Obtendo o app + + +### Linux + +No Debian (_em testes_ e _sid_ por enquanto): + +``` +apt install scrcpy +``` + +O pacote [Snap] está disponível: [`scrcpy`][snap-link]. + +[snap-link]: https://snapstats.org/snaps/scrcpy + +[snap]: https://en.wikipedia.org/wiki/Snappy_(package_manager) + +Para Arch Linux, um pacote [AUR] está disponível: [`scrcpy`][aur-link]. + +[AUR]: https://wiki.archlinux.org/index.php/Arch_User_Repository +[aur-link]: https://aur.archlinux.org/packages/scrcpy/ + +Para Gentoo, uma [Ebuild] está disponível: [`scrcpy/`][ebuild-link]. + +[Ebuild]: https://wiki.gentoo.org/wiki/Ebuild +[ebuild-link]: https://github.com/maggu2810/maggu2810-overlay/tree/master/app-mobilephone/scrcpy + + +Você também pode [compilar a aplicação manualmente][BUILD] (não se preocupe, não é tão difícil). + + +### Windows + +Para Windows, para simplicidade, um arquivo pré-compilado com todas as dependências +(incluindo `adb`) está disponível: + + - [`scrcpy-win64-v1.12.1.zip`][direct-win64] + _(SHA-256: 57d34b6d16cfd9fe169bc37c4df58ebd256d05c1ea3febc63d9cb0a027ab47c9)_ + +[direct-win64]: https://github.com/Genymobile/scrcpy/releases/download/v1.12.1/scrcpy-win64-v1.12.1.zip + +Também disponível em [Chocolatey]: + +[Chocolatey]: https://chocolatey.org/ + +```bash +choco install scrcpy +choco install adb # se você ainda não o tem +``` + +E no [Scoop]: + +```bash +scoop install scrcpy +scoop install adb # se você ainda não o tem +``` + +[Scoop]: https://scoop.sh + +Você também pode [compilar a aplicação manualmente][BUILD]. + + +### macOS + +A aplicação está disponível em [Homebrew]. Apenas a instale: + +[Homebrew]: https://brew.sh/ + +```bash +brew install scrcpy +``` + +Você precisa do `adb`, acessível através do seu `PATH`. Se você ainda não o tem: + +```bash +brew cask install android-platform-tools +``` + +Você também pode [compilar a aplicação manualmente][BUILD]. + + +## Executar + +Plugue um dispositivo Android e execute: + +```bash +scrcpy +``` + +Também aceita argumentos de linha de comando, listados por: + +```bash +scrcpy --help +``` + +## Funcionalidades + +### Configuração de captura + +#### Redução de tamanho + +Algumas vezes, é útil espelhar um dispositivo Android em uma resolução menor para +aumentar performance. + +Para limitar ambos(largura e altura) para algum valor (ex: 1024): + +```bash +scrcpy --max-size 1024 +scrcpy -m 1024 # versão reduzida +``` + +A outra dimensão é calculada para que a proporção do dispositivo seja preservada. +Dessa forma, um dispositivo em 1920x1080 será espelhado em 1024x576. + + +#### Mudanças no bit-rate + +O Padrão de bit-rate é 8 mbps. Para mudar o bitrate do vídeo (ex: para 2 Mbps): + +```bash +scrcpy --bit-rate 2M +scrcpy -b 2M # versão reduzida +``` + +#### Limitar frame rates + +Em dispositivos com Android >= 10, a captura de frame rate pode ser limitada: + +```bash +scrcpy --max-fps 15 +``` + +#### Cortar + +A tela do dispositivo pode ser cortada para espelhar apenas uma parte da tela. + +Isso é útil por exemplo, ao espelhar apenas um olho do Oculus Go: + +```bash +scrcpy --crop 1224:1440:0:0 # 1224x1440 no deslocamento (0,0) +``` + +Se `--max-size` também for especificado, redimensionar é aplicado após os cortes. + + +### Gravando + +É possível gravar a tela enquanto ocorre o espelhamento: + +```bash +scrcpy --record file.mp4 +scrcpy -r file.mkv +``` + +Para desativar o espelhamento durante a gravação: + +```bash +scrcpy --no-display --record file.mp4 +scrcpy -Nr file.mkv +# interrompe a gravação com Ctrl+C +# Ctrl+C não encerrar propriamente no Windows, então desconecte o dispositivo +``` + +"Frames pulados" são gravados, mesmo que não sejam mostrado em tempo real (por motivos de performance). +Frames tem seu _horário_ _carimbado_ no dispositivo, então [Variação de atraso nos pacotes] não impacta na gravação do arquivo. + +[Variação de atraso de pacote]: https://en.wikipedia.org/wiki/Packet_delay_variation + + +### Conexão + +#### Wireless/Sem fio + +_Scrcpy_ usa `adb` para se comunicar com o dispositivo, e `adb` pode [conectar-se] à um dispositivo via TCP/IP: + +1. Conecte o dispositivo a mesma rede Wi-Fi do seu computador. +2. Pegue o endereço de IP do seu dispositivo (Em Configurações → Sobre o Telefone → Status). +3. Ative o adb via TCP/IP no seu dispositivo: `adb tcpip 5555`. +4. Desplugue seu dispositivo. +5. Conecte-se ao seu dispositivo: `adb connect DEVICE_IP:5555` _(substitua o `DEVICE_IP`)_. +6. Execute `scrcpy` como de costume. + +Pode ser útil diminuir o bit-rate e a resolução: + +```bash +scrcpy --bit-rate 2M --max-size 800 +scrcpy -b2M -m800 # versão reduzida +``` + +[conectar-se]: https://developer.android.com/studio/command-line/adb.html#wireless + + +#### N-dispositivos + +Se alguns dispositivos estão listados em `adb devices`, você precisa especificar o _serial_: + +```bash +scrcpy --serial 0123456789abcdef +scrcpy -s 0123456789abcdef # versão reduzida +``` + +Se o dispositivo está conectado via TCP/IP: + +```bash +scrcpy --serial 192.168.0.1:5555 +scrcpy -s 192.168.0.1:5555 # versão reduzida +``` + +Você pode iniciar algumas instâncias do _scrcpy_ para alguns dispositivos. + +#### Conexão via SSH + +Para conectar-se à um dispositivo remoto, é possível se conectar um cliente local `adb` à um servidor `adb` remoto (contanto que eles usem a mesma versão do protocolo _adb_): + +```bash +adb kill-server # encerra o servidor local na 5037 +ssh -CN -L5037:localhost:5037 -R27183:localhost:27183 your_remote_computer +# mantém isso aberto +``` + +De outro terminal: + +```bash +scrcpy +``` + +Igual para conexões sem fio, pode ser útil reduzir a qualidade: + +``` +scrcpy -b2M -m800 --max-fps 15 +``` + +### Configurações de Janela + +#### Título + +Por padrão, o título da janela é o modelo do dispositivo. Isto pode ser mudado: + +```bash +scrcpy --window-title 'Meu dispositivo' +``` + +#### Posição e tamanho + +A posição e tamanho iniciais da janela podem ser especificados: + +```bash +scrcpy --window-x 100 --window-y 100 --window-width 800 --window-height 600 +``` + +#### Sem bordas + +Para desativar decorações da janela: + +```bash +scrcpy --window-borderless +``` + +#### Sempre visível + +Para manter a janela do scrcpy sempre visível: + +```bash +scrcpy --always-on-top +``` + +#### Tela cheia + +A aplicação pode ser iniciada diretamente em tela cheia: + +```bash +scrcpy --fullscreen +scrcpy -f # versão reduzida +``` + +Tela cheia pode ser alternada dinamicamente com `Ctrl`+`f`. + + +### Outras opções de espelhamento + +#### Apenas leitura + +Para desativar controles (tudo que possa interagir com o dispositivo: teclas de entrada, eventos de mouse, arrastar e soltar arquivos): + +```bash +scrcpy --no-control +scrcpy -n +``` + +#### Desligar a tela + +É possível desligar a tela do dispositivo durante o início do espelhamento com uma opção de linha de comando: + +```bash +scrcpy --turn-screen-off +scrcpy -S +``` + +Ou apertando `Ctrl`+`o` durante qualquer momento. + +Para ligar novamente, pressione `POWER` (ou `Ctrl`+`p`). + +#### Frames expirados de renderização + +Por padrão, para minimizar a latência, _scrcpy_ sempre renderiza o último frame decodificado disponível e descarta o anterior. + +Para forçar a renderização de todos os frames ( com o custo de aumento de latência), use: + +```bash +scrcpy --render-expired-frames +``` + +#### Mostrar toques + +Para apresentações, pode ser útil mostrar toques físicos(dispositivo físico). + +Android fornece esta funcionalidade nas _Opções do Desenvolvedor_. + +_Scrcpy_ fornece esta opção de ativar esta funcionalidade no início e desativar no encerramento: + +```bash +scrcpy --show-touches +scrcpy -t +``` + +Note que isto mostra apenas toques _físicos_ (com o dedo no dispositivo). + + +### Controle de entrada + +#### Rotacionar a tela do dispositivo + +Pressione `Ctrl`+`r` para mudar entre os modos Retrato e Paisagem. + +Note que só será rotacionado se a aplicação em primeiro plano tiver suporte para o modo requisitado. + +#### Copiar-Colar + +É possível sincronizar áreas de transferência entre computador e o dispositivo, +para ambas direções: + + - `Ctrl`+`c` copia a área de transferência do dispositivo para a área de trasferência do computador; + - `Ctrl`+`Shift`+`v` copia a área de transferência do computador para a área de transferência do dispositivo; + - `Ctrl`+`v` _cola_ a área de transferência do computador como uma sequência de eventos de texto (mas + quebra caracteres não-ASCII). + +#### Preferências de injeção de texto + +Existe dois tipos de [eventos][textevents] gerados ao digitar um texto: + - _eventos de teclas_, sinalizando que a tecla foi pressionada ou solta; + - _eventos de texto_, sinalizando que o texto foi inserido. + +Por padrão, letras são injetadas usando eventos de teclas, assim teclados comportam-se +como esperado em jogos (normalmente para tecladas WASD) + +Mas isto pode [causar problemas][prefertext]. Se você encontrar tal problema, +pode evitá-lo usando: + +```bash +scrcpy --prefer-text +``` + +(mas isto vai quebrar o comportamento do teclado em jogos) + +[textevents]: https://blog.rom1v.com/2018/03/introducing-scrcpy/#handle-text-input +[prefertext]: https://github.com/Genymobile/scrcpy/issues/650#issuecomment-512945343 + + +### Transferência de arquivo + +#### Instalar APK + +Para instalar um APK, arraste e solte o arquivo APK(com extensão `.apk`) na janela _scrcpy_. + +Não existe feedback visual, um log é imprimido no console. + + +#### Enviar arquivo para o dispositivo + +Para enviar um arquivo para o diretório `/sdcard/` no dispositivo, arraste e solte um arquivo não APK para a janela do +_scrcpy_. + +Não existe feedback visual, um log é imprimido no console. + +O diretório alvo pode ser mudado ao iniciar: + +```bash +scrcpy --push-target /sdcard/foo/bar/ +``` + + +### Encaminhamento de áudio + +Áudio não é encaminhando pelo _scrcpy_. Use [USBaudio] (Apenas linux). + +Também veja [issue #14]. + +[USBaudio]: https://github.com/rom1v/usbaudio +[issue #14]: https://github.com/Genymobile/scrcpy/issues/14 + + +## Atalhos + + | Ação | Atalho | Atalho (macOS) + | ------------------------------------------------------------- |:------------------------------- |:----------------------------- + | Alternar para modo de tela cheia | `Ctrl`+`f` | `Cmd`+`f` + | Redimensionar janela para pixel-perfect(Escala 1:1) | `Ctrl`+`g` | `Cmd`+`g` + | Redimensionar janela para tirar as bordas pretas | `Ctrl`+`x` \| _Clique-duplo¹_ | `Cmd`+`x` \| _Clique-duplo¹_ + | Clicar em `HOME` | `Ctrl`+`h` \| _Clique-central_ | `Ctrl`+`h` \| _Clique-central_ + | Clicar em `BACK` | `Ctrl`+`b` \| _Clique-direito²_ | `Cmd`+`b` \| _Clique-direito²_ + | Clicar em `APP_SWITCH` | `Ctrl`+`s` | `Cmd`+`s` + | Clicar em `MENU` | `Ctrl`+`m` | `Ctrl`+`m` + | Clicar em `VOLUME_UP` | `Ctrl`+`↑` _(cima)_ | `Cmd`+`↑` _(cima)_ + | Clicar em `VOLUME_DOWN` | `Ctrl`+`↓` _(baixo)_ | `Cmd`+`↓` _(baixo)_ + | Clicar em `POWER` | `Ctrl`+`p` | `Cmd`+`p` + | Ligar | _Clique-direito²_ | _Clique-direito²_ + | Desligar a tela do dispositivo | `Ctrl`+`o` | `Cmd`+`o` + | Rotacionar tela do dispositivo | `Ctrl`+`r` | `Cmd`+`r` + | Expandir painel de notificação | `Ctrl`+`n` | `Cmd`+`n` + | Esconder painel de notificação | `Ctrl`+`Shift`+`n` | `Cmd`+`Shift`+`n` + | Copiar área de transferência do dispositivo para o computador | `Ctrl`+`c` | `Cmd`+`c` + | Colar área de transferência do computador para o dispositivo | `Ctrl`+`v` | `Cmd`+`v` + | Copiar área de transferência do computador para dispositivo | `Ctrl`+`Shift`+`v` | `Cmd`+`Shift`+`v` + | Ativar/desativar contador de FPS(Frames por segundo) | `Ctrl`+`i` | `Cmd`+`i` + +_¹Clique-duplo em bordas pretas para removê-las._ +_²Botão direito liga a tela se ela estiver desligada, clique BACK para o contrário._ + + +## Caminhos personalizados + +Para usar um binário específico _adb_, configure seu caminho na variável de ambiente `ADB`: + + ADB=/caminho/para/adb scrcpy + +Para sobrepor o caminho do arquivo `scrcpy-server`, configure seu caminho em +`SCRCPY_SERVER_PATH`. + +[useful]: https://github.com/Genymobile/scrcpy/issues/278#issuecomment-429330345 + + +## Por quê _scrcpy_? + +Um colega me desafiou a encontrar um nome impronunciável como [gnirehtet]. + +[`strcpy`] copia uma **str**ing; `scrcpy` copia uma **scr**een. + +[gnirehtet]: https://github.com/Genymobile/gnirehtet +[`strcpy`]: http://man7.org/linux/man-pages/man3/strcpy.3.html + + +## Como compilar? + +Veja [BUILD]. + +[BUILD]: BUILD.md + + +## Problemas comuns + +Veja [FAQ](FAQ.md). + + +## Desenvolvedores + +Leia a [developers page]. + +[developers page]: DEVELOP.md + + +## Licença + + Copyright (C) 2018 Genymobile + Copyright (C) 2018-2020 Romain Vimont + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +## Artigos + +- [Introducing scrcpy][article-intro] +- [Scrcpy now works wirelessly][article-tcpip] + +[article-intro]: https://blog.rom1v.com/2018/03/introducing-scrcpy/ +[article-tcpip]: https://www.genymotion.com/blog/open-source-project-scrcpy-now-works-wirelessly/ From 82446b6b0db284e9f9a125cd3e4785586f6845f3 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 10 May 2020 11:47:56 +0200 Subject: [PATCH 092/450] Reference README.md from localized versions Mention that the README.md is the only one being up-to-date. --- README.ko.md | 2 ++ README.pt-br.md | 2 ++ 2 files changed, 4 insertions(+) diff --git a/README.ko.md b/README.ko.md index b232accd..4e6d8fc5 100644 --- a/README.ko.md +++ b/README.ko.md @@ -1,3 +1,5 @@ +_Only the original [README](README.md) is guaranteed to be up-to-date._ + # scrcpy (v1.11) This document will be updated frequently along with the original Readme file diff --git a/README.pt-br.md b/README.pt-br.md index 8fb0f149..654f62cb 100644 --- a/README.pt-br.md +++ b/README.pt-br.md @@ -1,3 +1,5 @@ +_Only the original [README](README.md) is guaranteed to be up-to-date._ + # scrcpy (v1.12.1) Esta aplicação fornece visualização e controle de dispositivos Android conectados via From a85848a5415b05b712f4320d7a73afd229f5d43a Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Mon, 11 May 2020 01:32:54 +0200 Subject: [PATCH 093/450] Fix Windows Ctrl Handler declaration The handler function signature must include the calling convention declaration. Ref: --- app/src/scrcpy.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index 2a4702e6..e56ad84d 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -50,7 +50,7 @@ static struct input_manager input_manager = { }; #ifdef _WIN32 -BOOL windows_ctrl_handler(DWORD ctrl_type) { +BOOL WINAPI windows_ctrl_handler(DWORD ctrl_type) { if (ctrl_type == CTRL_C_EVENT) { SDL_Event event; event.type = SDL_QUIT; From 2608b1dc62d4cce830880bc504d995db96db0406 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Mon, 11 May 2020 02:00:46 +0200 Subject: [PATCH 094/450] Factorize window resize When the content size changes, either on frame size or client rotation changes, the window must be resized. Factorize for both cases. --- app/src/screen.c | 41 +++++++++++++++++------------------------ 1 file changed, 17 insertions(+), 24 deletions(-) diff --git a/app/src/screen.c b/app/src/screen.c index 0af8de83..0ca1343b 100644 --- a/app/src/screen.c +++ b/app/src/screen.c @@ -331,6 +331,21 @@ screen_destroy(struct screen *screen) { } } +static void +set_content_size(struct screen *screen, struct size new_content_size) { + struct size old_content_size = screen->content_size; + struct size windowed_size = get_windowed_window_size(screen); + struct size target_size = { + .width = (uint32_t) windowed_size.width * new_content_size.width + / old_content_size.width, + .height = (uint32_t) windowed_size.height * new_content_size.height + / old_content_size.height, + }; + target_size = get_optimal_size(target_size, new_content_size); + set_window_size(screen, target_size); + screen->content_size = new_content_size; +} + void screen_set_rotation(struct screen *screen, unsigned rotation) { assert(rotation < 4); @@ -338,7 +353,6 @@ screen_set_rotation(struct screen *screen, unsigned rotation) { return; } - struct size old_content_size = screen->content_size; struct size new_content_size = get_rotated_size(screen->frame_size, rotation); @@ -349,17 +363,7 @@ screen_set_rotation(struct screen *screen, unsigned rotation) { return; } - struct size windowed_size = get_windowed_window_size(screen); - struct size target_size = { - .width = (uint32_t) windowed_size.width * new_content_size.width - / old_content_size.width, - .height = (uint32_t) windowed_size.height * new_content_size.height - / old_content_size.height, - }; - target_size = get_optimal_size(target_size, new_content_size); - set_window_size(screen, target_size); - - screen->content_size = new_content_size; + set_content_size(screen, new_content_size); screen->rotation = rotation; LOGI("Display rotation set to %u", rotation); @@ -383,19 +387,8 @@ prepare_for_frame(struct screen *screen, struct size new_frame_size) { // frame dimension changed, destroy texture SDL_DestroyTexture(screen->texture); - struct size content_size = screen->content_size; - struct size windowed_size = get_windowed_window_size(screen); - struct size target_size = { - (uint32_t) windowed_size.width * new_content_size.width - / content_size.width, - (uint32_t) windowed_size.height * new_content_size.height - / content_size.height, - }; - target_size = get_optimal_size(target_size, new_content_size); - set_window_size(screen, target_size); - + set_content_size(screen, new_content_size); screen->frame_size = new_frame_size; - screen->content_size = new_content_size; LOGI("New texture: %" PRIu16 "x%" PRIu16, screen->frame_size.width, screen->frame_size.height); From 6b1da2fcffdc31f83519a81400af548c8d1dd787 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Mon, 11 May 2020 03:11:13 +0200 Subject: [PATCH 095/450] Simplify size changes in fullscreen or maximized If the content size changes (due to rotation for example) while the window is maximized or fullscreen, the resize must be applied once fullscreen and maximized are disabled. The previous strategy consisted in storing the windowed size, computing the target size on rotation, and applying it on window restoration. But tracking the windowed size (while ignoring the non-windowed size) was tricky, due to unspecified order of SDL events (e.g. size changes can be notified before "maximized" events), race conditions when reading window flags, different behaviors on different platforms... To simplify the whole resize management, store the old content size (the frame size, possibly rotated) when it changes while the window is maximized or fullscreen, so that the new optimal size can be computed on window restoration. --- app/src/screen.c | 97 ++++++++++++++++++++---------------------------- app/src/screen.h | 18 ++++----- 2 files changed, 48 insertions(+), 67 deletions(-) diff --git a/app/src/screen.c b/app/src/screen.c index 0ca1343b..5c1c0a93 100644 --- a/app/src/screen.c +++ b/app/src/screen.c @@ -30,10 +30,10 @@ get_rotated_size(struct size size, int rotation) { // get the window size in a struct size static struct size -get_window_size(SDL_Window *window) { +get_window_size(const struct screen *screen) { int width; int height; - SDL_GetWindowSize(window, &width, &height); + SDL_GetWindowSize(screen->window, &width, &height); struct size size; size.width = width; @@ -41,31 +41,12 @@ get_window_size(SDL_Window *window) { return size; } -// get the windowed window size -static struct size -get_windowed_window_size(const struct screen *screen) { - if (screen->fullscreen || screen->maximized) { - return screen->windowed_window_size; - } - return get_window_size(screen->window); -} - -// apply the windowed window size if fullscreen and maximized are disabled -static void -apply_windowed_size(struct screen *screen) { - if (!screen->fullscreen && !screen->maximized) { - SDL_SetWindowSize(screen->window, screen->windowed_window_size.width, - screen->windowed_window_size.height); - } -} - // set the window size to be applied when fullscreen is disabled static void set_window_size(struct screen *screen, struct size new_size) { - // setting the window size during fullscreen is implementation defined, - // so apply the resize only after fullscreen is disabled - screen->windowed_window_size = new_size; - apply_windowed_size(screen); + assert(!screen->fullscreen); + assert(!screen->maximized); + SDL_SetWindowSize(screen->window, new_size.width, new_size.height); } // get the preferred display bounds (i.e. the screen bounds with some margins) @@ -138,8 +119,8 @@ get_optimal_size(struct size current_size, struct size content_size) { // same as get_optimal_size(), but read the current size from the window static inline struct size get_optimal_window_size(const struct screen *screen, struct size content_size) { - struct size windowed_size = get_windowed_window_size(screen); - return get_optimal_size(windowed_size, content_size); + struct size window_size = get_window_size(screen); + return get_optimal_size(window_size, content_size); } // initially, there is no current size, so use the frame size as current size @@ -308,8 +289,6 @@ screen_init_rendering(struct screen *screen, const char *window_title, return false; } - screen->windowed_window_size = window_size; - return true; } @@ -332,20 +311,44 @@ screen_destroy(struct screen *screen) { } static void -set_content_size(struct screen *screen, struct size new_content_size) { - struct size old_content_size = screen->content_size; - struct size windowed_size = get_windowed_window_size(screen); +resize_for_content(struct screen *screen, struct size old_content_size, + struct size new_content_size) { + struct size window_size = get_window_size(screen); struct size target_size = { - .width = (uint32_t) windowed_size.width * new_content_size.width + .width = (uint32_t) window_size.width * new_content_size.width / old_content_size.width, - .height = (uint32_t) windowed_size.height * new_content_size.height + .height = (uint32_t) window_size.height * new_content_size.height / old_content_size.height, }; target_size = get_optimal_size(target_size, new_content_size); set_window_size(screen, target_size); +} + +static void +set_content_size(struct screen *screen, struct size new_content_size) { + if (!screen->fullscreen && !screen->maximized) { + resize_for_content(screen, screen->content_size, new_content_size); + } else if (!screen->resize_pending) { + // Store the windowed size to be able to compute the optimal size once + // fullscreen and maximized are disabled + screen->windowed_content_size = screen->content_size; + screen->resize_pending = true; + } + screen->content_size = new_content_size; } +static void +apply_pending_resize(struct screen *screen) { + assert(!screen->fullscreen); + assert(!screen->maximized); + if (screen->resize_pending) { + resize_for_content(screen, screen->windowed_content_size, + screen->content_size); + screen->resize_pending = false; + } +} + void screen_set_rotation(struct screen *screen, unsigned rotation) { assert(rotation < 4); @@ -471,7 +474,9 @@ screen_switch_fullscreen(struct screen *screen) { } screen->fullscreen = !screen->fullscreen; - apply_windowed_size(screen); + if (!screen->fullscreen && !screen->maximized) { + apply_pending_resize(screen); + } LOGD("Switched to %s mode", screen->fullscreen ? "fullscreen" : "windowed"); screen_render(screen); @@ -520,36 +525,14 @@ screen_handle_window_event(struct screen *screen, screen_render(screen); break; case SDL_WINDOWEVENT_SIZE_CHANGED: - if (!screen->fullscreen && !screen->maximized) { - // Backup the previous size: if we receive the MAXIMIZED event, - // then the new size must be ignored (it's the maximized size). - // We could not rely on the window flags due to race conditions - // (they could be updated asynchronously, at least on X11). - screen->windowed_window_size_backup = - screen->windowed_window_size; - - // Save the windowed size, so that it is available once the - // window is maximized or fullscreen is enabled. - screen->windowed_window_size = get_window_size(screen->window); - } screen_render(screen); break; case SDL_WINDOWEVENT_MAXIMIZED: - // The backup size must be non-nul. - assert(screen->windowed_window_size_backup.width); - assert(screen->windowed_window_size_backup.height); - // Revert the last size, it was updated while screen was maximized. - screen->windowed_window_size = screen->windowed_window_size_backup; -#ifdef DEBUG - // Reset the backup to invalid values to detect unexpected usage - screen->windowed_window_size_backup.width = 0; - screen->windowed_window_size_backup.height = 0; -#endif screen->maximized = true; break; case SDL_WINDOWEVENT_RESTORED: screen->maximized = false; - apply_windowed_size(screen); + apply_pending_resize(screen); break; } } diff --git a/app/src/screen.h b/app/src/screen.h index 85514279..7703b5a2 100644 --- a/app/src/screen.h +++ b/app/src/screen.h @@ -21,11 +21,12 @@ struct screen { struct sc_opengl gl; struct size frame_size; struct size content_size; // rotated frame_size - // The window size the last time it was not maximized or fullscreen. - struct size windowed_window_size; - // Since we receive the event SIZE_CHANGED before MAXIMIZED, we must be - // able to revert the size to its non-maximized value. - struct size windowed_window_size_backup; + + bool resize_pending; // resize requested while fullscreen or maximized + // The content size the last time the window was not maximized or + // fullscreen (meaningful only when resize_pending is true) + struct size windowed_content_size; + // client rotation: 0, 1, 2 or 3 (x90 degrees counterclockwise) unsigned rotation; bool has_frame; @@ -49,11 +50,8 @@ struct screen { .width = 0, \ .height = 0, \ }, \ - .windowed_window_size = { \ - .width = 0, \ - .height = 0, \ - }, \ - .windowed_window_size_backup = { \ + .resize_pending = false, \ + .windowed_content_size = { \ .width = 0, \ .height = 0, \ }, \ From 4c2e10fd743a3e83eacc8828d5fbb89c87e929a8 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Tue, 12 May 2020 22:30:37 +0200 Subject: [PATCH 096/450] Workaround maximized+fullscreen on Windows On Windows, in maximized+fullscreen state, disabling fullscreen mode unexpectedly triggers the "restored" then "maximized" events, leaving the window in a weird state (maximized according to the events, but not maximized visually). Moreover, apply_pending_resize() asserts that fullscreen is disabled. To avoid the issue, if fullscreen is set, just ignore the "restored" event. --- app/src/screen.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/app/src/screen.c b/app/src/screen.c index 5c1c0a93..869ae0e0 100644 --- a/app/src/screen.c +++ b/app/src/screen.c @@ -531,6 +531,13 @@ screen_handle_window_event(struct screen *screen, screen->maximized = true; break; case SDL_WINDOWEVENT_RESTORED: + if (screen->fullscreen) { + // On Windows, in maximized+fullscreen, disabling fullscreen + // mode unexpectedly triggers the "restored" then "maximized" + // events, leaving the window in a weird state (maximized + // according to the events, but not maximized visually). + break; + } screen->maximized = false; apply_pending_resize(screen); break; From ec047b501e830dea136d91dc5e7289077488fe34 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 15 May 2020 18:53:43 +0200 Subject: [PATCH 097/450] Disable "resize to fit" in maximized state In maximized state (but not fullscreen), it was possible to resize to fit the device screen (with Ctrl+x or double-clicking on black borders). This caused problems on macOS with the "expand to fullscreen" feature, which behaves like a fullscreen mode but is seen as maximized by SDL. In that state, resizing to fit causes unexpected results. To keep the behavior consistent on all platforms, just disable "resize to fit" when the window is maximized. --- app/src/screen.c | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/app/src/screen.c b/app/src/screen.c index 869ae0e0..314b47ba 100644 --- a/app/src/screen.c +++ b/app/src/screen.c @@ -484,15 +484,10 @@ screen_switch_fullscreen(struct screen *screen) { void screen_resize_to_fit(struct screen *screen) { - if (screen->fullscreen) { + if (screen->fullscreen || screen->maximized) { return; } - if (screen->maximized) { - SDL_RestoreWindow(screen->window); - screen->maximized = false; - } - struct size optimal_size = get_optimal_window_size(screen, screen->content_size); SDL_SetWindowSize(screen->window, optimal_size.width, optimal_size.height); From d860ad48e6be41ec7c7ccbfca8b305fe126fc340 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sat, 25 Apr 2020 00:19:44 +0200 Subject: [PATCH 098/450] Extract optimal window size detection Extract the computation to detect whether the current size of the window is already optimal. This will allow to reuse it for rendering. --- app/src/screen.c | 44 +++++++++++++++++++++++++------------------- 1 file changed, 25 insertions(+), 19 deletions(-) diff --git a/app/src/screen.c b/app/src/screen.c index 314b47ba..566b2d99 100644 --- a/app/src/screen.c +++ b/app/src/screen.c @@ -68,6 +68,16 @@ get_preferred_display_bounds(struct size *bounds) { return true; } +static bool +is_optimal_size(struct size current_size, struct size content_size) { + // The size is optimal if we can recompute one dimension of the current + // size from the other + return current_size.height == current_size.width * content_size.height + / content_size.width + || current_size.width == current_size.height * content_size.width + / content_size.height; +} + // return the optimal size of the window, with the following constraints: // - it attempts to keep at least one dimension of the current_size (i.e. it // crops the black borders) @@ -80,40 +90,36 @@ get_optimal_size(struct size current_size, struct size content_size) { return current_size; } - struct size display_size; - // 32 bits because we need to multiply two 16 bits values - uint32_t w; - uint32_t h; + struct size window_size; + struct size display_size; if (!get_preferred_display_bounds(&display_size)) { // could not get display bounds, do not constraint the size - w = current_size.width; - h = current_size.height; + window_size.width = current_size.width; + window_size.height = current_size.height; } else { - w = MIN(current_size.width, display_size.width); - h = MIN(current_size.height, display_size.height); + window_size.width = MIN(current_size.width, display_size.width); + window_size.height = MIN(current_size.height, display_size.height); } - if (h == w * content_size.height / content_size.width - || w == h * content_size.width / content_size.height) { - // The size is already optimal, if we ignore rounding errors due to - // integer window dimensions - return (struct size) {w, h}; + if (is_optimal_size(window_size, content_size)) { + return window_size; } - bool keep_width = content_size.width * h > content_size.height * w; + bool keep_width = content_size.width * window_size.height + > content_size.height * window_size.width; if (keep_width) { // remove black borders on top and bottom - h = content_size.height * w / content_size.width; + window_size.height = content_size.height * window_size.width + / content_size.width; } else { // remove black borders on left and right (or none at all if it already // fits) - w = content_size.width * h / content_size.height; + window_size.width = content_size.width * window_size.height + / content_size.height; } - // w and h must fit into 16 bits - assert(w < 0x10000 && h < 0x10000); - return (struct size) {w, h}; + return window_size; } // same as get_optimal_size(), but read the current size from the window From e40532a3761da8b3aef484f1d435ef5f50d94574 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 17 Apr 2020 18:44:24 +0200 Subject: [PATCH 099/450] Manually position and scale the content Position and scale the content "manually" instead of relying on the renderer "logical size". This avoids possible rounding differences between the computed window size and the content size, causing one row or column of black pixels on the bottom or on the right. This also avoids HiDPI scale issues, by computing the scaling manually. This will also enable to draw items at their expected size on the screen (unscaled). Fixes #15 --- app/src/input_manager.c | 61 ++++++++------------ app/src/scrcpy.c | 2 +- app/src/screen.c | 119 ++++++++++++++++++++++++++++------------ app/src/screen.h | 20 ++++++- 4 files changed, 125 insertions(+), 77 deletions(-) diff --git a/app/src/input_manager.c b/app/src/input_manager.c index 14ee7e40..e8c8d68e 100644 --- a/app/src/input_manager.c +++ b/app/src/input_manager.c @@ -7,33 +7,6 @@ #include "util/lock.h" #include "util/log.h" -// Convert window coordinates (as provided by SDL_GetMouseState() to renderer -// coordinates (as provided in SDL mouse events) -// -// See my question: -// -static void -convert_to_renderer_coordinates(SDL_Renderer *renderer, int *x, int *y) { - SDL_Rect viewport; - float scale_x, scale_y; - SDL_RenderGetViewport(renderer, &viewport); - SDL_RenderGetScale(renderer, &scale_x, &scale_y); - *x = (int) (*x / scale_x) - viewport.x; - *y = (int) (*y / scale_y) - viewport.y; -} - -static struct point -get_mouse_point(struct screen *screen) { - int x; - int y; - SDL_GetMouseState(&x, &y); - convert_to_renderer_coordinates(screen->renderer, &x, &y); - return (struct point) { - .x = x, - .y = y, - }; -} - static const int ACTION_DOWN = 1; static const int ACTION_UP = 1 << 1; @@ -487,11 +460,17 @@ convert_touch(const SDL_TouchFingerEvent *from, struct screen *screen, to->inject_touch_event.pointer_id = from->fingerId; to->inject_touch_event.position.screen_size = screen->frame_size; + + int ww; + int wh; + SDL_GL_GetDrawableSize(screen->window, &ww, &wh); + // SDL touch event coordinates are normalized in the range [0; 1] - float x = from->x * screen->content_size.width; - float y = from->y * screen->content_size.height; + int32_t x = from->x * ww; + int32_t y = from->y * wh; to->inject_touch_event.position.point = screen_convert_to_frame_coords(screen, x, y); + to->inject_touch_event.pressure = from->pressure; to->inject_touch_event.buttons = 0; return true; @@ -508,13 +487,6 @@ input_manager_process_touch(struct input_manager *im, } } -static bool -is_outside_device_screen(struct input_manager *im, int x, int y) -{ - return x < 0 || x >= im->screen->content_size.width || - y < 0 || y >= im->screen->content_size.height; -} - static bool convert_mouse_button(const SDL_MouseButtonEvent *from, struct screen *screen, struct control_msg *to) { @@ -552,10 +524,15 @@ input_manager_process_mouse_button(struct input_manager *im, action_home(im->controller, ACTION_DOWN | ACTION_UP); return; } + // double-click on black borders resize to fit the device screen if (event->button == SDL_BUTTON_LEFT && event->clicks == 2) { - bool outside = - is_outside_device_screen(im, event->x, event->y); + int32_t x = event->x; + int32_t y = event->y; + screen_hidpi_scale_coords(im->screen, &x, &y); + SDL_Rect *r = &im->screen->rect; + bool outside = x < r->x || x >= r->x + r->w + || y < r->y || y >= r->y + r->h; if (outside) { screen_resize_to_fit(im->screen); return; @@ -579,9 +556,15 @@ input_manager_process_mouse_button(struct input_manager *im, static bool convert_mouse_wheel(const SDL_MouseWheelEvent *from, struct screen *screen, struct control_msg *to) { + + // mouse_x and mouse_y are expressed in pixels relative to the window + int mouse_x; + int mouse_y; + SDL_GetMouseState(&mouse_x, &mouse_y); + struct position position = { .screen_size = screen->frame_size, - .point = get_mouse_point(screen), + .point = screen_convert_to_frame_coords(screen, mouse_x, mouse_y), }; to->type = CONTROL_MSG_TYPE_INJECT_SCROLL_EVENT; diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index e56ad84d..7f8d3c75 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -136,7 +136,7 @@ event_watcher(void *data, SDL_Event *event) { && event->window.event == SDL_WINDOWEVENT_RESIZED) { // In practice, it seems to always be called from the same thread in // that specific case. Anyway, it's just a workaround. - screen_render(&screen); + screen_render(&screen, true); } return 0; } diff --git a/app/src/screen.c b/app/src/screen.c index 566b2d99..967cf5d7 100644 --- a/app/src/screen.c +++ b/app/src/screen.c @@ -156,6 +156,43 @@ get_initial_optimal_size(struct size content_size, uint16_t req_width, return window_size; } +static void +screen_update_content_rect(struct screen *screen) { + int dw; + int dh; + SDL_GL_GetDrawableSize(screen->window, &dw, &dh); + + struct size content_size = screen->content_size; + // The drawable size is the window size * the HiDPI scale + struct size drawable_size = {dw, dh}; + + SDL_Rect *rect = &screen->rect; + + if (is_optimal_size(drawable_size, content_size)) { + rect->x = 0; + rect->y = 0; + rect->w = drawable_size.width; + rect->h = drawable_size.height; + return; + } + + bool keep_width = content_size.width * drawable_size.height + > content_size.height * drawable_size.width; + if (keep_width) { + rect->x = 0; + rect->w = drawable_size.width; + rect->h = drawable_size.width * content_size.height + / content_size.width; + rect->y = (drawable_size.height - rect->h) / 2; + } else { + rect->y = 0; + rect->h = drawable_size.height; + rect->w = drawable_size.height * content_size.width + / content_size.height; + rect->x = (drawable_size.width - rect->w) / 2; + } +} + void screen_init(struct screen *screen) { *screen = (struct screen) SCREEN_INITIALIZER; @@ -245,13 +282,6 @@ screen_init_rendering(struct screen *screen, const char *window_title, const char *renderer_name = r ? NULL : renderer_info.name; LOGI("Renderer: %s", renderer_name ? renderer_name : "(unknown)"); - if (SDL_RenderSetLogicalSize(screen->renderer, content_size.width, - content_size.height)) { - LOGE("Could not set renderer logical size: %s", SDL_GetError()); - screen_destroy(screen); - return false; - } - // starts with "opengl" screen->use_opengl = renderer_name && !strncmp(renderer_name, "opengl", 6); if (screen->use_opengl) { @@ -295,6 +325,8 @@ screen_init_rendering(struct screen *screen, const char *window_title, return false; } + screen_update_content_rect(screen); + return true; } @@ -365,18 +397,12 @@ screen_set_rotation(struct screen *screen, unsigned rotation) { struct size new_content_size = get_rotated_size(screen->frame_size, rotation); - if (SDL_RenderSetLogicalSize(screen->renderer, - new_content_size.width, - new_content_size.height)) { - LOGE("Could not set renderer logical size: %s", SDL_GetError()); - return; - } - set_content_size(screen, new_content_size); + screen->rotation = rotation; LOGI("Display rotation set to %u", rotation); - screen_render(screen); + screen_render(screen, true); } // recreate the texture and resize the window if the frame size has changed @@ -384,21 +410,17 @@ static bool prepare_for_frame(struct screen *screen, struct size new_frame_size) { if (screen->frame_size.width != new_frame_size.width || screen->frame_size.height != new_frame_size.height) { - struct size new_content_size = - get_rotated_size(new_frame_size, screen->rotation); - if (SDL_RenderSetLogicalSize(screen->renderer, - new_content_size.width, - new_content_size.height)) { - LOGE("Could not set renderer logical size: %s", SDL_GetError()); - return false; - } - // frame dimension changed, destroy texture SDL_DestroyTexture(screen->texture); - set_content_size(screen, new_content_size); screen->frame_size = new_frame_size; + struct size new_content_size = + get_rotated_size(new_frame_size, screen->rotation); + set_content_size(screen, new_content_size); + + screen_update_content_rect(screen); + LOGI("New texture: %" PRIu16 "x%" PRIu16, screen->frame_size.width, screen->frame_size.height); screen->texture = create_texture(screen); @@ -439,15 +461,19 @@ screen_update_frame(struct screen *screen, struct video_buffer *vb) { update_texture(screen, frame); mutex_unlock(vb->mutex); - screen_render(screen); + screen_render(screen, false); return true; } void -screen_render(struct screen *screen) { +screen_render(struct screen *screen, bool update_content_rect) { + if (update_content_rect) { + screen_update_content_rect(screen); + } + SDL_RenderClear(screen->renderer); if (screen->rotation == 0) { - SDL_RenderCopy(screen->renderer, screen->texture, NULL, NULL); + SDL_RenderCopy(screen->renderer, screen->texture, NULL, &screen->rect); } else { // rotation in RenderCopyEx() is clockwise, while screen->rotation is // counterclockwise (to be consistent with --lock-video-orientation) @@ -457,12 +483,14 @@ screen_render(struct screen *screen) { SDL_Rect *dstrect = NULL; SDL_Rect rect; if (screen->rotation & 1) { - struct size size = screen->content_size; - rect.x = (size.width - size.height) / 2; - rect.y = (size.height - size.width) / 2; - rect.w = size.height; - rect.h = size.width; + rect.x = screen->rect.x + (screen->rect.w - screen->rect.h) / 2; + rect.y = screen->rect.y + (screen->rect.h - screen->rect.w) / 2; + rect.w = screen->rect.h; + rect.h = screen->rect.w; dstrect = ▭ + } else { + assert(screen->rotation == 2); + dstrect = &screen->rect; } SDL_RenderCopyEx(screen->renderer, screen->texture, NULL, dstrect, @@ -485,7 +513,7 @@ screen_switch_fullscreen(struct screen *screen) { } LOGD("Switched to %s mode", screen->fullscreen ? "fullscreen" : "windowed"); - screen_render(screen); + screen_render(screen, true); } void @@ -523,10 +551,10 @@ screen_handle_window_event(struct screen *screen, const SDL_WindowEvent *event) { switch (event->event) { case SDL_WINDOWEVENT_EXPOSED: - screen_render(screen); + screen_render(screen, true); break; case SDL_WINDOWEVENT_SIZE_CHANGED: - screen_render(screen); + screen_render(screen, true); break; case SDL_WINDOWEVENT_MAXIMIZED: screen->maximized = true; @@ -552,6 +580,13 @@ screen_convert_to_frame_coords(struct screen *screen, int32_t x, int32_t y) { int32_t w = screen->content_size.width; int32_t h = screen->content_size.height; + + screen_hidpi_scale_coords(screen, &x, &y); + + x = (int64_t) (x - screen->rect.x) * w / screen->rect.w; + y = (int64_t) (y - screen->rect.y) * h / screen->rect.h; + + // rotate struct point result; switch (rotation) { case 0: @@ -574,3 +609,15 @@ screen_convert_to_frame_coords(struct screen *screen, int32_t x, int32_t y) { } return result; } + +void +screen_hidpi_scale_coords(struct screen *screen, int32_t *x, int32_t *y) { + // take the HiDPI scaling (dw/ww and dh/wh) into account + int ww, wh, dw, dh; + SDL_GetWindowSize(screen->window, &ww, &wh); + SDL_GL_GetDrawableSize(screen->window, &dw, &dh); + + // scale for HiDPI (64 bits for intermediate multiplications) + *x = (int64_t) *x * dw / ww; + *y = (int64_t) *y * dh / wh; +} diff --git a/app/src/screen.h b/app/src/screen.h index 7703b5a2..aa6218f7 100644 --- a/app/src/screen.h +++ b/app/src/screen.h @@ -29,6 +29,8 @@ struct screen { // client rotation: 0, 1, 2 or 3 (x90 degrees counterclockwise) unsigned rotation; + // rectangle of the content (excluding black borders) + struct SDL_Rect rect; bool has_frame; bool fullscreen; bool maximized; @@ -56,6 +58,12 @@ struct screen { .height = 0, \ }, \ .rotation = 0, \ + .rect = { \ + .x = 0, \ + .y = 0, \ + .w = 0, \ + .h = 0, \ + }, \ .has_frame = false, \ .fullscreen = false, \ .maximized = false, \ @@ -89,8 +97,11 @@ bool screen_update_frame(struct screen *screen, struct video_buffer *vb); // render the texture to the renderer +// +// Set the update_content_rect flag if the window or content size may have +// changed, so that the content rectangle is recomputed void -screen_render(struct screen *screen); +screen_render(struct screen *screen, bool update_content_rect); // switch the fullscreen mode void @@ -117,4 +128,11 @@ screen_handle_window_event(struct screen *screen, const SDL_WindowEvent *event); struct point screen_convert_to_frame_coords(struct screen *screen, int32_t x, int32_t y); +// Convert coordinates from window to drawable. +// Events are expressed in window coordinates, but content is expressed in +// drawable coordinates. They are the same if HiDPI scaling is 1, but differ +// otherwise. +void +screen_hidpi_scale_coords(struct screen *screen, int32_t *x, int32_t *y); + #endif From f5aeecbc621289fdd92f935858ed2abc4347f2b3 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Mon, 18 May 2020 17:56:22 +0200 Subject: [PATCH 100/450] Reset window size on initialization On macOS with renderer "metal", HiDPI scaling may be incorrect on initialization when several displays are connected. Resetting the window size fixes the problem. Refs #15 --- app/src/screen.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/app/src/screen.c b/app/src/screen.c index 967cf5d7..b92c1119 100644 --- a/app/src/screen.c +++ b/app/src/screen.c @@ -325,6 +325,11 @@ screen_init_rendering(struct screen *screen, const char *window_title, return false; } + // Reset the window size to trigger a SIZE_CHANGED event, to workaround + // HiDPI issues with some SDL renderers when several displays having + // different HiDPI scaling are connected + SDL_SetWindowSize(screen->window, window_size.width, window_size.height); + screen_update_content_rect(screen); return true; From fae3f9eeabbb6586eadde8f720a849befd45b6a0 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sat, 23 May 2020 14:23:30 +0200 Subject: [PATCH 101/450] Remove warning when renderer is not OpenGL Trilinear filtering can currently only be enabled for OpenGL renderers. Do not print a warning if the renderer is not OpenGL, as it can confuses users, while nothing is wrong. --- app/src/screen.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/screen.c b/app/src/screen.c index b92c1119..2b7ff237 100644 --- a/app/src/screen.c +++ b/app/src/screen.c @@ -305,7 +305,7 @@ screen_init_rendering(struct screen *screen, const char *window_title, LOGI("Trilinear filtering disabled"); } } else { - LOGW("Trilinear filtering disabled (not an OpenGL renderer)"); + LOGD("Trilinear filtering disabled (not an OpenGL renderer)"); } SDL_Surface *icon = read_xpm(icon_xpm); From ac4c8b4a3f5c342ff2059fb5ca0720dab8b933f2 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sat, 23 May 2020 14:32:16 +0200 Subject: [PATCH 102/450] Increase LOD bias for mipmapping Mipmapping caused too much blurring. Using a LOD bias of -1 instead of -0.5 seems a better compromise to avoid low-quality downscaling while keeping sharp edges in any case. Refs Refs --- app/src/screen.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/screen.c b/app/src/screen.c index 2b7ff237..66a1163b 100644 --- a/app/src/screen.c +++ b/app/src/screen.c @@ -217,7 +217,7 @@ create_texture(struct screen *screen) { // Enable trilinear filtering for downscaling gl->TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); - gl->TexParameterf(GL_TEXTURE_2D, GL_TEXTURE_LOD_BIAS, -.5f); + gl->TexParameterf(GL_TEXTURE_2D, GL_TEXTURE_LOD_BIAS, -1.f); SDL_GL_UnbindTexture(texture); } From e1cd75792c3b6a014043b82a4089c6d34fcf78fb Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 21 May 2020 00:20:44 +0200 Subject: [PATCH 103/450] Simplify rotation watcher call Remove unnecessary private method (which was wrongly public). --- server/src/main/java/com/genymobile/scrcpy/Device.java | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/Device.java b/server/src/main/java/com/genymobile/scrcpy/Device.java index 6aa339a1..aff04fd0 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Device.java +++ b/server/src/main/java/com/genymobile/scrcpy/Device.java @@ -52,7 +52,7 @@ public final class Device { screenInfo = ScreenInfo.computeScreenInfo(displayInfo, options.getCrop(), options.getMaxSize(), options.getLockedVideoOrientation()); layerStack = displayInfo.getLayerStack(); - registerRotationWatcher(new IRotationWatcher.Stub() { + serviceManager.getWindowManager().registerRotationWatcher(new IRotationWatcher.Stub() { @Override public void onRotationChanged(int rotation) throws RemoteException { synchronized (Device.this) { @@ -134,10 +134,6 @@ public final class Device { return serviceManager.getPowerManager().isScreenOn(); } - public void registerRotationWatcher(IRotationWatcher rotationWatcher, int displayId) { - serviceManager.getWindowManager().registerRotationWatcher(rotationWatcher, displayId); - } - public synchronized void setRotationListener(RotationListener rotationListener) { this.rotationListener = rotationListener; } From 73e722784d610266d84e1bf75d1db16e9ad3c2d6 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sat, 23 May 2020 18:21:54 +0200 Subject: [PATCH 104/450] Remove useless exception declaration The interface declares it can throw a RemoteException, but the implementation never throws such exception. --- server/src/main/java/com/genymobile/scrcpy/Device.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/Device.java b/server/src/main/java/com/genymobile/scrcpy/Device.java index aff04fd0..91705359 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Device.java +++ b/server/src/main/java/com/genymobile/scrcpy/Device.java @@ -54,7 +54,7 @@ public final class Device { serviceManager.getWindowManager().registerRotationWatcher(new IRotationWatcher.Stub() { @Override - public void onRotationChanged(int rotation) throws RemoteException { + public void onRotationChanged(int rotation) { synchronized (Device.this) { screenInfo = screenInfo.withDeviceRotation(rotation); From acc4ef31df8dbdfa9c1ed6170088741a45b1aead Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Wed, 20 May 2020 20:05:29 +0200 Subject: [PATCH 105/450] Synchronize device clipboard to computer Automatically synchronize the device clipboard to the computer any time it changes. This allows seamless copy-paste from Android to the computer. Fixes #1056 PR #1423 --- README.md | 3 ++ .../IOnPrimaryClipChangedListener.aidl | 24 +++++++++++++ .../java/com/genymobile/scrcpy/Device.java | 28 ++++++++++++++- .../java/com/genymobile/scrcpy/Server.java | 9 ++++- .../scrcpy/wrappers/ClipboardManager.java | 35 +++++++++++++++++++ 5 files changed, 97 insertions(+), 2 deletions(-) create mode 100644 server/src/main/aidl/android/content/IOnPrimaryClipChangedListener.aidl diff --git a/README.md b/README.md index 8a1f5523..ab66827f 100644 --- a/README.md +++ b/README.md @@ -482,6 +482,9 @@ both directions: - `Ctrl`+`v` _pastes_ the computer clipboard as a sequence of text events (but breaks non-ASCII characters). +Moreover, any time the Android clipboard changes, it is automatically +synchronized to the computer clipboard. + #### Text injection preference There are two kinds of [events][textevents] generated when typing text: diff --git a/server/src/main/aidl/android/content/IOnPrimaryClipChangedListener.aidl b/server/src/main/aidl/android/content/IOnPrimaryClipChangedListener.aidl new file mode 100644 index 00000000..46d7f7ca --- /dev/null +++ b/server/src/main/aidl/android/content/IOnPrimaryClipChangedListener.aidl @@ -0,0 +1,24 @@ +/** + * Copyright (c) 2008, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.content; + +/** + * {@hide} + */ +oneway interface IOnPrimaryClipChangedListener { + void dispatchPrimaryClipChanged(); +} diff --git a/server/src/main/java/com/genymobile/scrcpy/Device.java b/server/src/main/java/com/genymobile/scrcpy/Device.java index 91705359..6e788928 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Device.java +++ b/server/src/main/java/com/genymobile/scrcpy/Device.java @@ -6,10 +6,10 @@ import com.genymobile.scrcpy.wrappers.ServiceManager; import com.genymobile.scrcpy.wrappers.SurfaceControl; import com.genymobile.scrcpy.wrappers.WindowManager; +import android.content.IOnPrimaryClipChangedListener; import android.graphics.Rect; import android.os.Build; import android.os.IBinder; -import android.os.RemoteException; import android.view.IRotationWatcher; import android.view.InputEvent; @@ -22,10 +22,15 @@ public final class Device { void onRotationChanged(int rotation); } + public interface ClipboardListener { + void onClipboardTextChanged(String text); + } + private final ServiceManager serviceManager = new ServiceManager(); private ScreenInfo screenInfo; private RotationListener rotationListener; + private ClipboardListener clipboardListener; /** * Logical display identifier @@ -66,6 +71,23 @@ public final class Device { } }, displayId); + if (options.getControl()) { + // If control is enabled, synchronize Android clipboard to the computer automatically + serviceManager.getClipboardManager().addPrimaryClipChangedListener(new IOnPrimaryClipChangedListener.Stub() { + @Override + public void dispatchPrimaryClipChanged() { + synchronized (Device.this) { + if (clipboardListener != null) { + String text = getClipboardText(); + if (text != null) { + clipboardListener.onClipboardTextChanged(text); + } + } + } + } + }); + } + if ((displayInfoFlags & DisplayInfo.FLAG_SUPPORTS_PROTECTED_BUFFERS) == 0) { Ln.w("Display doesn't have FLAG_SUPPORTS_PROTECTED_BUFFERS flag, mirroring can be restricted"); } @@ -138,6 +160,10 @@ public final class Device { this.rotationListener = rotationListener; } + public synchronized void setClipboardListener(ClipboardListener clipboardListener) { + this.clipboardListener = clipboardListener; + } + public void expandNotificationPanel() { serviceManager.getStatusBarManager().expandNotificationsPanel(); } diff --git a/server/src/main/java/com/genymobile/scrcpy/Server.java b/server/src/main/java/com/genymobile/scrcpy/Server.java index 73776c3e..e2c702ba 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Server.java +++ b/server/src/main/java/com/genymobile/scrcpy/Server.java @@ -53,11 +53,18 @@ public final class Server { ScreenEncoder screenEncoder = new ScreenEncoder(options.getSendFrameMeta(), options.getBitRate(), options.getMaxFps()); if (options.getControl()) { - Controller controller = new Controller(device, connection); + final Controller controller = new Controller(device, connection); // asynchronous startController(controller); startDeviceMessageSender(controller.getSender()); + + device.setClipboardListener(new Device.ClipboardListener() { + @Override + public void onClipboardTextChanged(String text) { + controller.getSender().pushClipboardText(text); + } + }); } try { diff --git a/server/src/main/java/com/genymobile/scrcpy/wrappers/ClipboardManager.java b/server/src/main/java/com/genymobile/scrcpy/wrappers/ClipboardManager.java index 7a3bfafb..e25b6e99 100644 --- a/server/src/main/java/com/genymobile/scrcpy/wrappers/ClipboardManager.java +++ b/server/src/main/java/com/genymobile/scrcpy/wrappers/ClipboardManager.java @@ -3,6 +3,7 @@ package com.genymobile.scrcpy.wrappers; import com.genymobile.scrcpy.Ln; import android.content.ClipData; +import android.content.IOnPrimaryClipChangedListener; import android.os.Build; import android.os.IInterface; @@ -13,6 +14,7 @@ public class ClipboardManager { private final IInterface manager; private Method getPrimaryClipMethod; private Method setPrimaryClipMethod; + private Method addPrimaryClipChangedListener; public ClipboardManager(IInterface manager) { this.manager = manager; @@ -81,4 +83,37 @@ public class ClipboardManager { return false; } } + + private static void addPrimaryClipChangedListener(Method method, IInterface manager, IOnPrimaryClipChangedListener listener) + throws InvocationTargetException, IllegalAccessException { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) { + method.invoke(manager, listener, ServiceManager.PACKAGE_NAME); + } else { + method.invoke(manager, listener, ServiceManager.PACKAGE_NAME, ServiceManager.USER_ID); + } + } + + private Method getAddPrimaryClipChangedListener() throws NoSuchMethodException { + if (addPrimaryClipChangedListener == null) { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) { + addPrimaryClipChangedListener = manager.getClass() + .getMethod("addPrimaryClipChangedListener", IOnPrimaryClipChangedListener.class, String.class); + } else { + addPrimaryClipChangedListener = manager.getClass() + .getMethod("addPrimaryClipChangedListener", IOnPrimaryClipChangedListener.class, String.class, int.class); + } + } + return addPrimaryClipChangedListener; + } + + public boolean addPrimaryClipChangedListener(IOnPrimaryClipChangedListener listener) { + try { + Method method = getAddPrimaryClipChangedListener(); + addPrimaryClipChangedListener(method, manager, listener); + return true; + } catch (InvocationTargetException | IllegalAccessException | NoSuchMethodException e) { + Ln.e("Could not invoke method", e); + return false; + } + } } From 517dbd9c85842797ec8bf21ced2ddc4040a87230 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 24 May 2020 14:28:58 +0200 Subject: [PATCH 106/450] Increase buffer size to fix "set clipboard" event The buffer size must be greater than any event message. Clipboard events may take up to 4096 bytes, so increase the buffer size. Fixes #1425 --- .../main/java/com/genymobile/scrcpy/ControlMessageReader.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/ControlMessageReader.java b/server/src/main/java/com/genymobile/scrcpy/ControlMessageReader.java index 1c081058..2688641c 100644 --- a/server/src/main/java/com/genymobile/scrcpy/ControlMessageReader.java +++ b/server/src/main/java/com/genymobile/scrcpy/ControlMessageReader.java @@ -15,7 +15,8 @@ public class ControlMessageReader { public static final int CLIPBOARD_TEXT_MAX_LENGTH = 4093; public static final int INJECT_TEXT_MAX_LENGTH = 300; - private static final int RAW_BUFFER_SIZE = 1024; + + private static final int RAW_BUFFER_SIZE = 4096; private final byte[] rawBuffer = new byte[RAW_BUFFER_SIZE]; private final ByteBuffer buffer = ByteBuffer.wrap(rawBuffer); From c7155a1954bd26e09195d8f2c2d582598d5b67dd Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 24 May 2020 14:30:50 +0200 Subject: [PATCH 107/450] Add unit test for big "set clipboard" event Add a unit test to avoid regressions. Refs #1425 --- .../scrcpy/ControlMessageReaderTest.java | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/server/src/test/java/com/genymobile/scrcpy/ControlMessageReaderTest.java b/server/src/test/java/com/genymobile/scrcpy/ControlMessageReaderTest.java index 202126a5..d495b44c 100644 --- a/server/src/test/java/com/genymobile/scrcpy/ControlMessageReaderTest.java +++ b/server/src/test/java/com/genymobile/scrcpy/ControlMessageReaderTest.java @@ -229,6 +229,30 @@ public class ControlMessageReaderTest { Assert.assertEquals("testé", event.getText()); } + @Test + public void testParseBigSetClipboardEvent() throws IOException { + ControlMessageReader reader = new ControlMessageReader(); + + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + DataOutputStream dos = new DataOutputStream(bos); + dos.writeByte(ControlMessage.TYPE_SET_CLIPBOARD); + + byte[] rawText = new byte[ControlMessageReader.CLIPBOARD_TEXT_MAX_LENGTH]; + Arrays.fill(rawText, (byte) 'a'); + String text = new String(rawText, 0, rawText.length); + + dos.writeShort(rawText.length); + dos.write(rawText); + + byte[] packet = bos.toByteArray(); + + reader.readFrom(new ByteArrayInputStream(packet)); + ControlMessage event = reader.next(); + + Assert.assertEquals(ControlMessage.TYPE_SET_CLIPBOARD, event.getType()); + Assert.assertEquals(text, event.getText()); + } + @Test public void testParseSetScreenPowerMode() throws IOException { ControlMessageReader reader = new ControlMessageReader(); From 080a4ee3654a9b7e96c8ffe37474b5c21c02852a Mon Sep 17 00:00:00 2001 From: Tzah Mazuz Date: Sun, 26 Apr 2020 15:22:08 +0300 Subject: [PATCH 108/450] Add --codec-options Add a command-line parameter to pass custom options to the device encoder (as a comma-separated list of "key[:type]=value"). The list of possible codec options is available in the Android documentation: PR #1325 Refs #1226 Co-authored-by: Romain Vimont --- app/scrcpy.1 | 10 ++ app/src/cli.c | 14 +++ app/src/scrcpy.c | 1 + app/src/scrcpy.h | 2 + app/src/server.c | 1 + app/src/server.h | 1 + .../com/genymobile/scrcpy/CodecOption.java | 112 +++++++++++++++++ .../java/com/genymobile/scrcpy/Options.java | 9 ++ .../com/genymobile/scrcpy/ScreenEncoder.java | 33 ++++- .../java/com/genymobile/scrcpy/Server.java | 10 +- .../genymobile/scrcpy/CodecOptionsTest.java | 114 ++++++++++++++++++ 11 files changed, 302 insertions(+), 5 deletions(-) create mode 100644 server/src/main/java/com/genymobile/scrcpy/CodecOption.java create mode 100644 server/src/test/java/com/genymobile/scrcpy/CodecOptionsTest.java diff --git a/app/scrcpy.1 b/app/scrcpy.1 index 02389159..6920e9a4 100644 --- a/app/scrcpy.1 +++ b/app/scrcpy.1 @@ -25,6 +25,16 @@ Encode the video at the given bit\-rate, expressed in bits/s. Unit suffixes are Default is 8000000. +.TP +.BI "\-\-codec\-options " key[:type]=value[,...] +Set a list of comma-separated key:type=value options for the device encoder. + +The possible values for 'type' are 'int' (default), 'long', 'float' and 'string'. + +The list of possible codec options is available in the Android documentation +.UR https://d.android.com/reference/android/media/MediaFormat +.UE . + .TP .BI "\-\-crop " width\fR:\fIheight\fR:\fIx\fR:\fIy Crop the device screen on the server. diff --git a/app/src/cli.c b/app/src/cli.c index 0c68279f..30272828 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -30,6 +30,15 @@ scrcpy_print_usage(const char *arg0) { " Unit suffixes are supported: 'K' (x1000) and 'M' (x1000000).\n" " Default is %d.\n" "\n" + " --codec-options key[:type]=value[,...]\n" + " Set a list of comma-separated key:type=value options for the\n" + " device encoder.\n" + " The possible values for 'type' are 'int' (default), 'long',\n" + " 'float' and 'string'.\n" + " The list of possible codec options is available in the\n" + " Android documentation:\n" + " \n" + "\n" " --crop width:height:x:y\n" " Crop the device screen on the server.\n" " The values are expressed in the device natural orientation\n" @@ -472,12 +481,14 @@ guess_record_format(const char *filename) { #define OPT_ROTATION 1015 #define OPT_RENDER_DRIVER 1016 #define OPT_NO_MIPMAPS 1017 +#define OPT_CODEC_OPTIONS 1018 bool scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) { static const struct option long_options[] = { {"always-on-top", no_argument, NULL, OPT_ALWAYS_ON_TOP}, {"bit-rate", required_argument, NULL, 'b'}, + {"codec-options", required_argument, NULL, OPT_CODEC_OPTIONS}, {"crop", required_argument, NULL, OPT_CROP}, {"display", required_argument, NULL, OPT_DISPLAY_ID}, {"fullscreen", no_argument, NULL, 'f'}, @@ -647,6 +658,9 @@ scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) { case OPT_NO_MIPMAPS: opts->mipmaps = false; break; + case OPT_CODEC_OPTIONS: + opts->codec_options = optarg; + break; default: // getopt prints the error message on stderr return false; diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index 7f8d3c75..05960630 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -303,6 +303,7 @@ scrcpy(const struct scrcpy_options *options) { .display_id = options->display_id, .show_touches = options->show_touches, .stay_awake = options->stay_awake, + .codec_options = options->codec_options, }; if (!server_start(&server, options->serial, ¶ms)) { return false; diff --git a/app/src/scrcpy.h b/app/src/scrcpy.h index 99e72ef0..af2e3bf5 100644 --- a/app/src/scrcpy.h +++ b/app/src/scrcpy.h @@ -16,6 +16,7 @@ struct scrcpy_options { const char *window_title; const char *push_target; const char *render_driver; + const char *codec_options; enum recorder_format record_format; struct port_range port_range; uint16_t max_size; @@ -48,6 +49,7 @@ struct scrcpy_options { .window_title = NULL, \ .push_target = NULL, \ .render_driver = NULL, \ + .codec_options = NULL, \ .record_format = RECORDER_FORMAT_AUTO, \ .port_range = { \ .first = DEFAULT_LOCAL_PORT_RANGE_FIRST, \ diff --git a/app/src/server.c b/app/src/server.c index b98cec2f..94881e51 100644 --- a/app/src/server.c +++ b/app/src/server.c @@ -270,6 +270,7 @@ execute_server(struct server *server, const struct server_params *params) { display_id_string, params->show_touches ? "true" : "false", params->stay_awake ? "true" : "false", + params->codec_options ? params->codec_options : "-", }; #ifdef SERVER_DEBUGGER LOGI("Server debugger waiting for a client on device port " diff --git a/app/src/server.h b/app/src/server.h index 13d0b451..5b51695a 100644 --- a/app/src/server.h +++ b/app/src/server.h @@ -44,6 +44,7 @@ struct server { struct server_params { const char *crop; + const char *codec_options; struct port_range port_range; uint16_t max_size; uint32_t bit_rate; diff --git a/server/src/main/java/com/genymobile/scrcpy/CodecOption.java b/server/src/main/java/com/genymobile/scrcpy/CodecOption.java new file mode 100644 index 00000000..1897bda3 --- /dev/null +++ b/server/src/main/java/com/genymobile/scrcpy/CodecOption.java @@ -0,0 +1,112 @@ +package com.genymobile.scrcpy; + +import java.util.ArrayList; +import java.util.List; + +public class CodecOption { + private String key; + private Object value; + + public CodecOption(String key, Object value) { + this.key = key; + this.value = value; + } + + public String getKey() { + return key; + } + + public Object getValue() { + return value; + } + + public static List parse(String codecOptions) { + if ("-".equals(codecOptions)) { + return null; + } + + List result = new ArrayList<>(); + + boolean escape = false; + StringBuilder buf = new StringBuilder(); + + for (char c : codecOptions.toCharArray()) { + switch (c) { + case '\\': + if (escape) { + buf.append('\\'); + escape = false; + } else { + escape = true; + } + break; + case ',': + if (escape) { + buf.append(','); + escape = false; + } else { + // This comma is a separator between codec options + String codecOption = buf.toString(); + result.add(parseOption(codecOption)); + // Clear buf + buf.setLength(0); + } + break; + default: + buf.append(c); + break; + } + } + + if (buf.length() > 0) { + String codecOption = buf.toString(); + result.add(parseOption(codecOption)); + } + + return result; + } + + private static CodecOption parseOption(String option) { + int equalSignIndex = option.indexOf('='); + if (equalSignIndex == -1) { + throw new IllegalArgumentException("'=' expected"); + } + String keyAndType = option.substring(0, equalSignIndex); + if (keyAndType.length() == 0) { + throw new IllegalArgumentException("Key may not be null"); + } + + String key; + String type; + + int colonIndex = keyAndType.indexOf(':'); + if (colonIndex != -1) { + key = keyAndType.substring(0, colonIndex); + type = keyAndType.substring(colonIndex + 1); + } else { + key = keyAndType; + type = "int"; // assume int by default + } + + Object value; + String valueString = option.substring(equalSignIndex + 1); + switch (type) { + case "int": + value = Integer.parseInt(valueString); + break; + case "long": + value = Long.parseLong(valueString); + break; + case "float": + value = Float.parseFloat(valueString); + break; + case "string": + value = valueString; + break; + default: + throw new IllegalArgumentException("Invalid codec option type (int, long, float, str): " + type); + } + + return new CodecOption(key, value); + } +} diff --git a/server/src/main/java/com/genymobile/scrcpy/Options.java b/server/src/main/java/com/genymobile/scrcpy/Options.java index 152aa2f7..d2cd9a15 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Options.java +++ b/server/src/main/java/com/genymobile/scrcpy/Options.java @@ -14,6 +14,7 @@ public class Options { private int displayId; private boolean showTouches; private boolean stayAwake; + private String codecOptions; public int getMaxSize() { return maxSize; @@ -102,4 +103,12 @@ public class Options { public void setStayAwake(boolean stayAwake) { this.stayAwake = stayAwake; } + + public String getCodecOptions() { + return codecOptions; + } + + public void setCodecOptions(String codecOptions) { + this.codecOptions = codecOptions; + } } diff --git a/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java b/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java index 651affde..d722388c 100644 --- a/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java +++ b/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java @@ -12,6 +12,7 @@ import android.view.Surface; import java.io.FileDescriptor; import java.io.IOException; import java.nio.ByteBuffer; +import java.util.List; import java.util.concurrent.atomic.AtomicBoolean; public class ScreenEncoder implements Device.RotationListener { @@ -25,15 +26,17 @@ public class ScreenEncoder implements Device.RotationListener { private final AtomicBoolean rotationChanged = new AtomicBoolean(); private final ByteBuffer headerBuffer = ByteBuffer.allocate(12); + private List codecOptions; private int bitRate; private int maxFps; private boolean sendFrameMeta; private long ptsOrigin; - public ScreenEncoder(boolean sendFrameMeta, int bitRate, int maxFps) { + public ScreenEncoder(boolean sendFrameMeta, int bitRate, int maxFps, List codecOptions) { this.sendFrameMeta = sendFrameMeta; this.bitRate = bitRate; this.maxFps = maxFps; + this.codecOptions = codecOptions; } @Override @@ -61,7 +64,7 @@ public class ScreenEncoder implements Device.RotationListener { } private void internalStreamScreen(Device device, FileDescriptor fd) throws IOException { - MediaFormat format = createFormat(bitRate, maxFps); + MediaFormat format = createFormat(bitRate, maxFps, codecOptions); device.setRotationListener(this); boolean alive; try { @@ -151,7 +154,24 @@ public class ScreenEncoder implements Device.RotationListener { return MediaCodec.createEncoderByType(MediaFormat.MIMETYPE_VIDEO_AVC); } - private static MediaFormat createFormat(int bitRate, int maxFps) { + private static void setCodecOption(MediaFormat format, CodecOption codecOption) { + String key = codecOption.getKey(); + Object value = codecOption.getValue(); + + if (value instanceof Integer) { + format.setInteger(key, (Integer) value); + } else if (value instanceof Long) { + format.setLong(key, (Long) value); + } else if (value instanceof Float) { + format.setFloat(key, (Float) value); + } else if (value instanceof String) { + format.setString(key, (String) value); + } + + Ln.d("Codec option set: " + key + " (" + value.getClass().getSimpleName() + ") = " + value); + } + + private static MediaFormat createFormat(int bitRate, int maxFps, List codecOptions) { MediaFormat format = new MediaFormat(); format.setString(MediaFormat.KEY_MIME, MediaFormat.MIMETYPE_VIDEO_AVC); format.setInteger(MediaFormat.KEY_BIT_RATE, bitRate); @@ -167,6 +187,13 @@ public class ScreenEncoder implements Device.RotationListener { // format.setFloat(KEY_MAX_FPS_TO_ENCODER, maxFps); } + + if (codecOptions != null) { + for (CodecOption option : codecOptions) { + setCodecOption(format, option); + } + } + return format; } diff --git a/server/src/main/java/com/genymobile/scrcpy/Server.java b/server/src/main/java/com/genymobile/scrcpy/Server.java index e2c702ba..4ada08e6 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Server.java +++ b/server/src/main/java/com/genymobile/scrcpy/Server.java @@ -8,6 +8,7 @@ import android.os.BatteryManager; import android.os.Build; import java.io.IOException; +import java.util.List; public final class Server { @@ -19,6 +20,7 @@ public final class Server { private static void scrcpy(Options options) throws IOException { Ln.i("Device: " + Build.MANUFACTURER + " " + Build.MODEL + " (Android " + Build.VERSION.RELEASE + ")"); final Device device = new Device(options); + List codecOptions = CodecOption.parse(options.getCodecOptions()); boolean mustDisableShowTouchesOnCleanUp = false; int restoreStayOn = -1; @@ -49,8 +51,9 @@ public final class Server { CleanUp.configure(mustDisableShowTouchesOnCleanUp, restoreStayOn); boolean tunnelForward = options.isTunnelForward(); + 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(), codecOptions); if (options.getControl()) { final Controller controller = new Controller(device, connection); @@ -116,7 +119,7 @@ public final class Server { "The server version (" + BuildConfig.VERSION_NAME + ") does not match the client " + "(" + clientVersion + ")"); } - final int expectedParameters = 12; + final int expectedParameters = 13; if (args.length != expectedParameters) { throw new IllegalArgumentException("Expecting " + expectedParameters + " parameters"); } @@ -157,6 +160,9 @@ public final class Server { boolean stayAwake = Boolean.parseBoolean(args[11]); options.setStayAwake(stayAwake); + String codecOptions = args[12]; + options.setCodecOptions(codecOptions); + return options; } diff --git a/server/src/test/java/com/genymobile/scrcpy/CodecOptionsTest.java b/server/src/test/java/com/genymobile/scrcpy/CodecOptionsTest.java new file mode 100644 index 00000000..ad802258 --- /dev/null +++ b/server/src/test/java/com/genymobile/scrcpy/CodecOptionsTest.java @@ -0,0 +1,114 @@ +package com.genymobile.scrcpy; + +import org.junit.Assert; +import org.junit.Test; + +import java.util.List; + +public class CodecOptionsTest { + + @Test + public void testIntegerImplicit() { + List codecOptions = CodecOption.parse("some_key=5"); + + Assert.assertEquals(1, codecOptions.size()); + + CodecOption option = codecOptions.get(0); + Assert.assertEquals("some_key", option.getKey()); + Assert.assertEquals(5, option.getValue()); + } + + @Test + public void testInteger() { + List codecOptions = CodecOption.parse("some_key:int=5"); + + Assert.assertEquals(1, codecOptions.size()); + + CodecOption option = codecOptions.get(0); + Assert.assertEquals("some_key", option.getKey()); + Assert.assertTrue(option.getValue() instanceof Integer); + Assert.assertEquals(5, option.getValue()); + } + + @Test + public void testLong() { + List codecOptions = CodecOption.parse("some_key:long=5"); + + Assert.assertEquals(1, codecOptions.size()); + + CodecOption option = codecOptions.get(0); + Assert.assertEquals("some_key", option.getKey()); + Assert.assertTrue(option.getValue() instanceof Long); + Assert.assertEquals(5L, option.getValue()); + } + + @Test + public void testFloat() { + List codecOptions = CodecOption.parse("some_key:float=4.5"); + + Assert.assertEquals(1, codecOptions.size()); + + CodecOption option = codecOptions.get(0); + Assert.assertEquals("some_key", option.getKey()); + Assert.assertTrue(option.getValue() instanceof Float); + Assert.assertEquals(4.5f, option.getValue()); + } + + @Test + public void testString() { + List codecOptions = CodecOption.parse("some_key:string=some_value"); + + Assert.assertEquals(1, codecOptions.size()); + + CodecOption option = codecOptions.get(0); + Assert.assertEquals("some_key", option.getKey()); + Assert.assertTrue(option.getValue() instanceof String); + Assert.assertEquals("some_value", option.getValue()); + } + + @Test + public void testStringEscaped() { + List codecOptions = CodecOption.parse("some_key:string=warning\\,this_is_not=a_new_key"); + + Assert.assertEquals(1, codecOptions.size()); + + CodecOption option = codecOptions.get(0); + Assert.assertEquals("some_key", option.getKey()); + Assert.assertTrue(option.getValue() instanceof String); + Assert.assertEquals("warning,this_is_not=a_new_key", option.getValue()); + } + + @Test + public void testList() { + List codecOptions = CodecOption.parse("a=1,b:int=2,c:long=3,d:float=4.5,e:string=a\\,b=c"); + + Assert.assertEquals(5, codecOptions.size()); + + CodecOption option; + + option = codecOptions.get(0); + Assert.assertEquals("a", option.getKey()); + Assert.assertTrue(option.getValue() instanceof Integer); + Assert.assertEquals(1, option.getValue()); + + option = codecOptions.get(1); + Assert.assertEquals("b", option.getKey()); + Assert.assertTrue(option.getValue() instanceof Integer); + Assert.assertEquals(2, option.getValue()); + + option = codecOptions.get(2); + Assert.assertEquals("c", option.getKey()); + Assert.assertTrue(option.getValue() instanceof Long); + Assert.assertEquals(3L, option.getValue()); + + option = codecOptions.get(3); + Assert.assertEquals("d", option.getKey()); + Assert.assertTrue(option.getValue() instanceof Float); + Assert.assertEquals(4.5f, option.getValue()); + + option = codecOptions.get(4); + Assert.assertEquals("e", option.getKey()); + Assert.assertTrue(option.getValue() instanceof String); + Assert.assertEquals("a,b=c", option.getValue()); + } +} From 56bff2f7181f1b51132f1f06682010900720cff9 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 24 May 2020 21:11:21 +0200 Subject: [PATCH 109/450] Avoid compiler warning MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The field lock_video_orientation may only take values between -1 and 3 (included). But the compiler may trigger a warning on the sprintf() call, because its type could represent values which could overflow the string (like "-128"): > warning: ‘%i’ directive writing between 1 and 4 bytes into a region of > size 3 [-Wformat-overflow=] Increase the buffer size to remove the warning. --- app/src/server.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/server.c b/app/src/server.c index 94881e51..fa9230e0 100644 --- a/app/src/server.c +++ b/app/src/server.c @@ -234,7 +234,7 @@ execute_server(struct server *server, const struct server_params *params) { char max_size_string[6]; char bit_rate_string[11]; char max_fps_string[6]; - char lock_video_orientation_string[3]; + char lock_video_orientation_string[5]; char display_id_string[6]; sprintf(max_size_string, "%"PRIu16, params->max_size); sprintf(bit_rate_string, "%"PRIu32, params->bit_rate); From 3df63c579da7a35048458c6884f5f386154d2353 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 24 May 2020 21:08:22 +0200 Subject: [PATCH 110/450] Configure server verbosity from the client Send the requested log level from the client. This paves the way to configure it via a command-line argument. --- app/src/server.c | 5 +++ .../main/java/com/genymobile/scrcpy/Ln.java | 13 +++++++- .../java/com/genymobile/scrcpy/Options.java | 9 ++++++ .../java/com/genymobile/scrcpy/Server.java | 32 +++++++++++-------- 4 files changed, 45 insertions(+), 14 deletions(-) diff --git a/app/src/server.c b/app/src/server.c index fa9230e0..7cfbef0d 100644 --- a/app/src/server.c +++ b/app/src/server.c @@ -259,6 +259,11 @@ execute_server(struct server *server, const struct server_params *params) { "/", // unused "com.genymobile.scrcpy.Server", SCRCPY_VERSION, +#ifndef NDEBUG + "debug", +#else + "info", +#endif max_size_string, bit_rate_string, max_fps_string, diff --git a/server/src/main/java/com/genymobile/scrcpy/Ln.java b/server/src/main/java/com/genymobile/scrcpy/Ln.java index 26f13a56..8112bb1c 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Ln.java +++ b/server/src/main/java/com/genymobile/scrcpy/Ln.java @@ -15,12 +15,23 @@ public final class Ln { DEBUG, INFO, WARN, ERROR } - private static final Level THRESHOLD = BuildConfig.DEBUG ? Level.DEBUG : Level.INFO; + private static Level THRESHOLD; private Ln() { // not instantiable } + /** + * Initialize the log level. + *

+ * Must be called before starting any new thread. + * + * @param level the log level + */ + public static void initLogLevel(Level level) { + THRESHOLD = level; + } + public static boolean isEnabled(Level level) { return level.ordinal() >= THRESHOLD.ordinal(); } diff --git a/server/src/main/java/com/genymobile/scrcpy/Options.java b/server/src/main/java/com/genymobile/scrcpy/Options.java index d2cd9a15..06312a37 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Options.java +++ b/server/src/main/java/com/genymobile/scrcpy/Options.java @@ -3,6 +3,7 @@ package com.genymobile.scrcpy; import android.graphics.Rect; public class Options { + private Ln.Level logLevel; private int maxSize; private int bitRate; private int maxFps; @@ -16,6 +17,14 @@ public class Options { private boolean stayAwake; private String codecOptions; + public Ln.Level getLogLevel() { + return logLevel; + } + + public void setLogLevel(Ln.Level logLevel) { + this.logLevel = logLevel; + } + public int getMaxSize() { return maxSize; } diff --git a/server/src/main/java/com/genymobile/scrcpy/Server.java b/server/src/main/java/com/genymobile/scrcpy/Server.java index 4ada08e6..54292868 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Server.java +++ b/server/src/main/java/com/genymobile/scrcpy/Server.java @@ -119,48 +119,51 @@ public final class Server { "The server version (" + BuildConfig.VERSION_NAME + ") does not match the client " + "(" + clientVersion + ")"); } - final int expectedParameters = 13; + final int expectedParameters = 14; if (args.length != expectedParameters) { throw new IllegalArgumentException("Expecting " + expectedParameters + " parameters"); } Options options = new Options(); - int maxSize = Integer.parseInt(args[1]) & ~7; // multiple of 8 + Ln.Level level = Ln.Level.valueOf(args[1].toUpperCase()); + options.setLogLevel(level); + + int maxSize = Integer.parseInt(args[2]) & ~7; // multiple of 8 options.setMaxSize(maxSize); - int bitRate = Integer.parseInt(args[2]); + int bitRate = Integer.parseInt(args[3]); options.setBitRate(bitRate); - int maxFps = Integer.parseInt(args[3]); + int maxFps = Integer.parseInt(args[4]); options.setMaxFps(maxFps); - int lockedVideoOrientation = Integer.parseInt(args[4]); + int lockedVideoOrientation = Integer.parseInt(args[5]); options.setLockedVideoOrientation(lockedVideoOrientation); // use "adb forward" instead of "adb tunnel"? (so the server must listen) - boolean tunnelForward = Boolean.parseBoolean(args[5]); + boolean tunnelForward = Boolean.parseBoolean(args[6]); options.setTunnelForward(tunnelForward); - Rect crop = parseCrop(args[6]); + Rect crop = parseCrop(args[7]); options.setCrop(crop); - boolean sendFrameMeta = Boolean.parseBoolean(args[7]); + boolean sendFrameMeta = Boolean.parseBoolean(args[8]); options.setSendFrameMeta(sendFrameMeta); - boolean control = Boolean.parseBoolean(args[8]); + boolean control = Boolean.parseBoolean(args[9]); options.setControl(control); - int displayId = Integer.parseInt(args[9]); + int displayId = Integer.parseInt(args[10]); options.setDisplayId(displayId); - boolean showTouches = Boolean.parseBoolean(args[10]); + boolean showTouches = Boolean.parseBoolean(args[11]); options.setShowTouches(showTouches); - boolean stayAwake = Boolean.parseBoolean(args[11]); + boolean stayAwake = Boolean.parseBoolean(args[12]); options.setStayAwake(stayAwake); - String codecOptions = args[12]; + String codecOptions = args[13]; options.setCodecOptions(codecOptions); return options; @@ -215,6 +218,9 @@ public final class Server { }); Options options = createOptions(args); + + Ln.initLogLevel(options.getLogLevel()); + scrcpy(options); } } From a3ef461d73c4b010eacfc1f6703d38d0f2e44fad Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 24 May 2020 21:51:40 +0200 Subject: [PATCH 111/450] Add cli option to set the verbosity level The verbosity was set either to info (in release mode) or debug (in debug mode). Add a command-line argument to change it, so that users can enable debug logs using the release: scrcpy -Vdebug --- app/scrcpy.1 | 6 ++++++ app/src/cli.c | 44 ++++++++++++++++++++++++++++++++++++++++++-- app/src/main.c | 29 +++++++++++++++++++++++++---- app/src/scrcpy.c | 1 + app/src/scrcpy.h | 3 +++ app/src/server.c | 23 ++++++++++++++++++----- app/src/server.h | 2 ++ app/src/util/log.h | 7 +++++++ 8 files changed, 104 insertions(+), 11 deletions(-) diff --git a/app/scrcpy.1 b/app/scrcpy.1 index 6920e9a4..0729bc23 100644 --- a/app/scrcpy.1 +++ b/app/scrcpy.1 @@ -154,6 +154,12 @@ It only shows physical touches (not clicks from scrcpy). .B \-v, \-\-version Print the version of scrcpy. +.TP +.BI "\-V, \-\-verbosity " value +Set the log level ("debug", "info", "warn" or "error"). + +Default is "info" for release builds, "debug" for debug builds. + .TP .B \-w, \-\-stay-awake Keep the device on while scrcpy is running. diff --git a/app/src/cli.c b/app/src/cli.c index 30272828..b19d9a58 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -145,6 +145,14 @@ scrcpy_print_usage(const char *arg0) { "\n" " -v, --version\n" " Print the version of scrcpy.\n" + "\n" + " -V, --verbosity value\n" + " Set the log level (debug, info, warn or error).\n" +#ifndef NDEBUG + " Default is debug.\n" +#else + " Default is info.\n" +#endif "\n" " -w, --stay-awake\n" " Keep the device on while scrcpy is running.\n" @@ -433,6 +441,32 @@ parse_display_id(const char *s, uint16_t *display_id) { return true; } +static bool +parse_log_level(const char *s, enum sc_log_level *log_level) { + if (!strcmp(s, "debug")) { + *log_level = SC_LOG_LEVEL_DEBUG; + return true; + } + + if (!strcmp(s, "info")) { + *log_level = SC_LOG_LEVEL_INFO; + return true; + } + + if (!strcmp(s, "warn")) { + *log_level = SC_LOG_LEVEL_WARN; + return true; + } + + if (!strcmp(s, "error")) { + *log_level = SC_LOG_LEVEL_ERROR; + return true; + } + + LOGE("Could not parse log level: %s", s); + return false; +} + static bool parse_record_format(const char *optarg, enum recorder_format *format) { if (!strcmp(optarg, "mp4")) { @@ -513,6 +547,7 @@ scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) { {"show-touches", no_argument, NULL, 't'}, {"stay-awake", no_argument, NULL, 'w'}, {"turn-screen-off", no_argument, NULL, 'S'}, + {"verbosity", required_argument, NULL, 'V'}, {"version", no_argument, NULL, 'v'}, {"window-title", required_argument, NULL, OPT_WINDOW_TITLE}, {"window-x", required_argument, NULL, OPT_WINDOW_X}, @@ -529,8 +564,8 @@ scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) { optind = 0; // reset to start from the first argument in tests int c; - while ((c = getopt_long(argc, argv, "b:c:fF:hm:nNp:r:s:StTvw", long_options, - NULL)) != -1) { + while ((c = getopt_long(argc, argv, "b:c:fF:hm:nNp:r:s:StTvV:w", + long_options, NULL)) != -1) { switch (c) { case 'b': if (!parse_bit_rate(optarg, &opts->bit_rate)) { @@ -609,6 +644,11 @@ scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) { case 'v': args->version = true; break; + case 'V': + if (!parse_log_level(optarg, &opts->log_level)) { + return false; + } + break; case 'w': opts->stay_awake = true; break; diff --git a/app/src/main.c b/app/src/main.c index d683c508..85e578ae 100644 --- a/app/src/main.c +++ b/app/src/main.c @@ -29,6 +29,24 @@ print_version(void) { LIBAVUTIL_VERSION_MICRO); } +static SDL_LogPriority +convert_log_level_to_sdl(enum sc_log_level level) { + switch (level) { + case SC_LOG_LEVEL_DEBUG: + return SDL_LOG_PRIORITY_DEBUG; + case SC_LOG_LEVEL_INFO: + return SDL_LOG_PRIORITY_INFO; + case SC_LOG_LEVEL_WARN: + return SDL_LOG_PRIORITY_WARN; + case SC_LOG_LEVEL_ERROR: + return SDL_LOG_PRIORITY_ERROR; + default: + assert(!"unexpected log level"); + return SC_LOG_LEVEL_INFO; + } +} + + int main(int argc, char *argv[]) { #ifdef __WINDOWS__ @@ -38,20 +56,23 @@ main(int argc, char *argv[]) { setbuf(stderr, NULL); #endif -#ifndef NDEBUG - SDL_LogSetAllPriority(SDL_LOG_PRIORITY_DEBUG); -#endif - struct scrcpy_cli_args args = { .opts = SCRCPY_OPTIONS_DEFAULT, .help = false, .version = false, }; +#ifndef NDEBUG + args.opts.log_level = SC_LOG_LEVEL_DEBUG; +#endif + if (!scrcpy_parse_args(&args, argc, argv)) { return 1; } + SDL_LogPriority sdl_log = convert_log_level_to_sdl(args.opts.log_level); + SDL_LogSetAllPriority(sdl_log); + if (args.help) { scrcpy_print_usage(argv[0]); return 0; diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index 05960630..7a873391 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -293,6 +293,7 @@ bool scrcpy(const struct scrcpy_options *options) { bool record = !!options->record_filename; struct server_params params = { + .log_level = options->log_level, .crop = options->crop, .port_range = options->port_range, .max_size = options->max_size, diff --git a/app/src/scrcpy.h b/app/src/scrcpy.h index af2e3bf5..8d324378 100644 --- a/app/src/scrcpy.h +++ b/app/src/scrcpy.h @@ -8,6 +8,7 @@ #include "common.h" #include "input_manager.h" #include "recorder.h" +#include "util/log.h" struct scrcpy_options { const char *serial; @@ -17,6 +18,7 @@ struct scrcpy_options { const char *push_target; const char *render_driver; const char *codec_options; + enum sc_log_level log_level; enum recorder_format record_format; struct port_range port_range; uint16_t max_size; @@ -50,6 +52,7 @@ struct scrcpy_options { .push_target = NULL, \ .render_driver = NULL, \ .codec_options = NULL, \ + .log_level = SC_LOG_LEVEL_INFO, \ .record_format = RECORDER_FORMAT_AUTO, \ .port_range = { \ .first = DEFAULT_LOCAL_PORT_RANGE_FIRST, \ diff --git a/app/src/server.c b/app/src/server.c index 7cfbef0d..5ec2441c 100644 --- a/app/src/server.c +++ b/app/src/server.c @@ -229,6 +229,23 @@ enable_tunnel_any_port(struct server *server, struct port_range port_range) { return enable_tunnel_forward_any_port(server, port_range); } +static const char * +log_level_to_server_string(enum sc_log_level level) { + switch (level) { + case SC_LOG_LEVEL_DEBUG: + return "debug"; + case SC_LOG_LEVEL_INFO: + return "info"; + case SC_LOG_LEVEL_WARN: + return "warn"; + case SC_LOG_LEVEL_ERROR: + return "error"; + default: + assert(!"unexpected log level"); + return "(unknown)"; + } +} + static process_t execute_server(struct server *server, const struct server_params *params) { char max_size_string[6]; @@ -259,11 +276,7 @@ execute_server(struct server *server, const struct server_params *params) { "/", // unused "com.genymobile.scrcpy.Server", SCRCPY_VERSION, -#ifndef NDEBUG - "debug", -#else - "info", -#endif + log_level_to_server_string(params->log_level), max_size_string, bit_rate_string, max_fps_string, diff --git a/app/src/server.h b/app/src/server.h index 5b51695a..ff7acbdb 100644 --- a/app/src/server.h +++ b/app/src/server.h @@ -9,6 +9,7 @@ #include "config.h" #include "command.h" #include "common.h" +#include "util/log.h" #include "util/net.h" struct server { @@ -43,6 +44,7 @@ struct server { } struct server_params { + enum sc_log_level log_level; const char *crop; const char *codec_options; struct port_range port_range; diff --git a/app/src/util/log.h b/app/src/util/log.h index 5955c7fb..8c5c7dee 100644 --- a/app/src/util/log.h +++ b/app/src/util/log.h @@ -3,6 +3,13 @@ #include +enum sc_log_level { + SC_LOG_LEVEL_DEBUG, + SC_LOG_LEVEL_INFO, + SC_LOG_LEVEL_WARN, + SC_LOG_LEVEL_ERROR, +}; + #define LOGV(...) SDL_LogVerbose(SDL_LOG_CATEGORY_APPLICATION, __VA_ARGS__) #define LOGD(...) SDL_LogDebug(SDL_LOG_CATEGORY_APPLICATION, __VA_ARGS__) #define LOGI(...) SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, __VA_ARGS__) From ee3882f8be22fe092bea0685be1e57e5add3935e Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 24 May 2020 23:14:30 +0200 Subject: [PATCH 112/450] Fix typo in manpage --- app/scrcpy.1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/scrcpy.1 b/app/scrcpy.1 index 0729bc23..3bbdbffe 100644 --- a/app/scrcpy.1 +++ b/app/scrcpy.1 @@ -146,7 +146,7 @@ Turn the device screen off immediately. .TP .B \-t, \-\-show\-touches -Enable "show touches" on start, restore the initial value on exit.. +Enable "show touches" on start, restore the initial value on exit. It only shows physical touches (not clicks from scrcpy). From 8f46e184262aebb3a420571de73520d60ccdfbd0 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 24 May 2020 23:27:34 +0200 Subject: [PATCH 113/450] Add --force-adb-forward Add a command-line option to force "adb forward", without attempting "adb reverse" first. This is especially useful for using SSH tunnels without enabling remote port forwarding. --- README.md | 16 ++++++++++++++++ app/scrcpy.1 | 4 ++++ app/src/cli.c | 10 ++++++++++ app/src/scrcpy.c | 1 + app/src/scrcpy.h | 2 ++ app/src/server.c | 22 ++++++++++++++-------- app/src/server.h | 1 + 7 files changed, 48 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index ab66827f..3f4db9fa 100644 --- a/README.md +++ b/README.md @@ -289,6 +289,22 @@ From another terminal: scrcpy ``` +To avoid enabling remote port forwarding, you could force a forward connection +instead (notice the `-L` instead of `-R`): + +```bash +adb kill-server # kill the local adb server on 5037 +ssh -CN -L5037:localhost:5037 -L27183:localhost:27183 your_remote_computer +# keep this open +``` + +From another terminal: + +```bash +scrcpy --force-adb-forwrad +``` + + Like for wireless connections, it may be useful to reduce quality: ``` diff --git a/app/scrcpy.1 b/app/scrcpy.1 index 3bbdbffe..1020a2fb 100644 --- a/app/scrcpy.1 +++ b/app/scrcpy.1 @@ -52,6 +52,10 @@ The list of possible display ids can be listed by "adb shell dumpsys display" Default is 0. +.TP +.B \-\-force\-adb\-forward +Do not attempt to use "adb reverse" to connect to the device. + .TP .B \-f, \-\-fullscreen Start in fullscreen. diff --git a/app/src/cli.c b/app/src/cli.c index b19d9a58..a0c17c1a 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -54,6 +54,10 @@ scrcpy_print_usage(const char *arg0) { "\n" " Default is 0.\n" "\n" + " --force-adb-forward\n" + " Do not attempt to use \"adb reverse\" to connect to the\n" + " the device.\n" + "\n" " -f, --fullscreen\n" " Start in fullscreen.\n" "\n" @@ -516,6 +520,7 @@ guess_record_format(const char *filename) { #define OPT_RENDER_DRIVER 1016 #define OPT_NO_MIPMAPS 1017 #define OPT_CODEC_OPTIONS 1018 +#define OPT_FORCE_ADB_FORWARD 1019 bool scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) { @@ -525,6 +530,8 @@ scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) { {"codec-options", required_argument, NULL, OPT_CODEC_OPTIONS}, {"crop", required_argument, NULL, OPT_CROP}, {"display", required_argument, NULL, OPT_DISPLAY_ID}, + {"force-adb-forward", no_argument, NULL, + OPT_FORCE_ADB_FORWARD}, {"fullscreen", no_argument, NULL, 'f'}, {"help", no_argument, NULL, 'h'}, {"lock-video-orientation", required_argument, NULL, @@ -701,6 +708,9 @@ scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) { case OPT_CODEC_OPTIONS: opts->codec_options = optarg; break; + case OPT_FORCE_ADB_FORWARD: + opts->force_adb_forward = true; + break; default: // getopt prints the error message on stderr return false; diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index 7a873391..67ebf8c0 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -305,6 +305,7 @@ scrcpy(const struct scrcpy_options *options) { .show_touches = options->show_touches, .stay_awake = options->stay_awake, .codec_options = options->codec_options, + .force_adb_forward = options->force_adb_forward, }; if (!server_start(&server, options->serial, ¶ms)) { return false; diff --git a/app/src/scrcpy.h b/app/src/scrcpy.h index 8d324378..70d99433 100644 --- a/app/src/scrcpy.h +++ b/app/src/scrcpy.h @@ -42,6 +42,7 @@ struct scrcpy_options { bool window_borderless; bool mipmaps; bool stay_awake; + bool force_adb_forward; }; #define SCRCPY_OPTIONS_DEFAULT { \ @@ -79,6 +80,7 @@ struct scrcpy_options { .window_borderless = false, \ .mipmaps = true, \ .stay_awake = false, \ + .force_adb_forward = false, \ } bool diff --git a/app/src/server.c b/app/src/server.c index 5ec2441c..fb498d63 100644 --- a/app/src/server.c +++ b/app/src/server.c @@ -217,15 +217,20 @@ enable_tunnel_forward_any_port(struct server *server, } 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; +enable_tunnel_any_port(struct server *server, struct port_range port_range, + bool force_adb_forward) { + if (!force_adb_forward) { + // Attempt to use "adb reverse" + 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'"); } - // 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'"); return enable_tunnel_forward_any_port(server, port_range); } @@ -384,7 +389,8 @@ server_start(struct server *server, const char *serial, goto error1; } - if (!enable_tunnel_any_port(server, params->port_range)) { + if (!enable_tunnel_any_port(server, params->port_range, + params->force_adb_forward)) { goto error1; } diff --git a/app/src/server.h b/app/src/server.h index ff7acbdb..2215d817 100644 --- a/app/src/server.h +++ b/app/src/server.h @@ -56,6 +56,7 @@ struct server_params { uint16_t display_id; bool show_touches; bool stay_awake; + bool force_adb_forward; }; // init default values From 5c2cf88a1dd80cbf9752bb500c3d1bd426316926 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Mon, 25 May 2020 03:22:07 +0200 Subject: [PATCH 114/450] Rename THRESHOLD to threshold Since the field is not final anymore, lint expects the name not to be capitalized. --- server/src/main/java/com/genymobile/scrcpy/Ln.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/Ln.java b/server/src/main/java/com/genymobile/scrcpy/Ln.java index 8112bb1c..4013315f 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Ln.java +++ b/server/src/main/java/com/genymobile/scrcpy/Ln.java @@ -15,7 +15,7 @@ public final class Ln { DEBUG, INFO, WARN, ERROR } - private static Level THRESHOLD; + private static Level threshold; private Ln() { // not instantiable @@ -29,11 +29,11 @@ public final class Ln { * @param level the log level */ public static void initLogLevel(Level level) { - THRESHOLD = level; + threshold = level; } public static boolean isEnabled(Level level) { - return level.ordinal() >= THRESHOLD.ordinal(); + return level.ordinal() >= threshold.ordinal(); } public static void d(String message) { From 81573d81a078021bf17a7e5b76ecd9633c7d0fad Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Mon, 25 May 2020 03:28:15 +0200 Subject: [PATCH 115/450] Pass a Locale to toUpperCase() Make lint happy. --- server/src/main/java/com/genymobile/scrcpy/Server.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/Server.java b/server/src/main/java/com/genymobile/scrcpy/Server.java index 54292868..44b3afd4 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Server.java +++ b/server/src/main/java/com/genymobile/scrcpy/Server.java @@ -9,6 +9,7 @@ import android.os.Build; import java.io.IOException; import java.util.List; +import java.util.Locale; public final class Server { @@ -126,7 +127,7 @@ public final class Server { Options options = new Options(); - Ln.Level level = Ln.Level.valueOf(args[1].toUpperCase()); + Ln.Level level = Ln.Level.valueOf(args[1].toUpperCase(Locale.ENGLISH)); options.setLogLevel(level); int maxSize = Integer.parseInt(args[2]) & ~7; // multiple of 8 From c7a33fac36576817f092188f54b140e71edd8412 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Mon, 25 May 2020 02:23:32 +0200 Subject: [PATCH 116/450] Log actions on the caller side Some actions are exposed by the Device class, but logging success should be done by the caller. --- .../java/com/genymobile/scrcpy/Controller.java | 11 +++++++++-- .../main/java/com/genymobile/scrcpy/Device.java | 16 +++++----------- 2 files changed, 14 insertions(+), 13 deletions(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/Controller.java b/server/src/main/java/com/genymobile/scrcpy/Controller.java index eb5a0805..4442188c 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Controller.java +++ b/server/src/main/java/com/genymobile/scrcpy/Controller.java @@ -110,11 +110,18 @@ public class Controller { sender.pushClipboardText(clipboardText); break; case ControlMessage.TYPE_SET_CLIPBOARD: - device.setClipboardText(msg.getText()); + boolean setClipboardOk = device.setClipboardText(msg.getText()); + if (setClipboardOk) { + Ln.i("Device clipboard set"); + } break; case ControlMessage.TYPE_SET_SCREEN_POWER_MODE: if (device.supportsInputEvents()) { - device.setScreenPowerMode(msg.getAction()); + int mode = msg.getAction(); + boolean setPowerModeOk = device.setScreenPowerMode(mode); + if (setPowerModeOk) { + Ln.i("Device screen turned " + (mode == Device.POWER_MODE_OFF ? "off" : "on")); + } } break; case ControlMessage.TYPE_ROTATE_DEVICE: diff --git a/server/src/main/java/com/genymobile/scrcpy/Device.java b/server/src/main/java/com/genymobile/scrcpy/Device.java index 6e788928..1a851b15 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Device.java +++ b/server/src/main/java/com/genymobile/scrcpy/Device.java @@ -180,26 +180,20 @@ public final class Device { return s.toString(); } - public void setClipboardText(String text) { - boolean ok = serviceManager.getClipboardManager().setText(text); - if (ok) { - Ln.i("Device clipboard set"); - } + public boolean setClipboardText(String text) { + return serviceManager.getClipboardManager().setText(text); } /** * @param mode one of the {@code SCREEN_POWER_MODE_*} constants */ - public void setScreenPowerMode(int mode) { + public boolean setScreenPowerMode(int mode) { IBinder d = SurfaceControl.getBuiltInDisplay(); if (d == null) { Ln.e("Could not get built-in display"); - return; - } - boolean ok = SurfaceControl.setDisplayPowerMode(d, mode); - if (ok) { - Ln.i("Device screen turned " + (mode == Device.POWER_MODE_OFF ? "off" : "on")); + return false; } + return SurfaceControl.setDisplayPowerMode(d, mode); } /** From ffc57512b3241d28424cd883985d66ac92698cf3 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Mon, 25 May 2020 02:58:06 +0200 Subject: [PATCH 117/450] Avoid clipboard synchronization loop The Android device listens for clipboard changes to synchronize with the computer clipboard. However, if the change comes from scrcpy (for example via Ctrl+Shift+v), do not notify the change. --- .../src/main/java/com/genymobile/scrcpy/Device.java | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/Device.java b/server/src/main/java/com/genymobile/scrcpy/Device.java index 1a851b15..ec28bd7c 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Device.java +++ b/server/src/main/java/com/genymobile/scrcpy/Device.java @@ -13,6 +13,8 @@ import android.os.IBinder; import android.view.IRotationWatcher; import android.view.InputEvent; +import java.util.concurrent.atomic.AtomicBoolean; + public final class Device { public static final int POWER_MODE_OFF = SurfaceControl.POWER_MODE_OFF; @@ -31,6 +33,7 @@ public final class Device { private ScreenInfo screenInfo; private RotationListener rotationListener; private ClipboardListener clipboardListener; + private final AtomicBoolean isSettingClipboard = new AtomicBoolean(); /** * Logical display identifier @@ -76,6 +79,10 @@ public final class Device { serviceManager.getClipboardManager().addPrimaryClipChangedListener(new IOnPrimaryClipChangedListener.Stub() { @Override public void dispatchPrimaryClipChanged() { + if (isSettingClipboard.get()) { + // This is a notification for the change we are currently applying, ignore it + return; + } synchronized (Device.this) { if (clipboardListener != null) { String text = getClipboardText(); @@ -181,7 +188,10 @@ public final class Device { } public boolean setClipboardText(String text) { - return serviceManager.getClipboardManager().setText(text); + isSettingClipboard.set(true); + boolean ok = serviceManager.getClipboardManager().setText(text); + isSettingClipboard.set(false); + return ok; } /** From 4bbabfb4ef5f1105215dc2bb8a282ffce221bbcc Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Mon, 25 May 2020 02:32:37 +0200 Subject: [PATCH 118/450] Move injection methods to Device Only the main injection method was exposed on Device, the convenience methods were implemented in Controller. For consistency, move them all to the Device class. --- .../com/genymobile/scrcpy/Controller.java | 30 ++++--------------- .../java/com/genymobile/scrcpy/Device.java | 21 ++++++++++++- 2 files changed, 26 insertions(+), 25 deletions(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/Controller.java b/server/src/main/java/com/genymobile/scrcpy/Controller.java index 4442188c..ab7b6c40 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Controller.java +++ b/server/src/main/java/com/genymobile/scrcpy/Controller.java @@ -1,10 +1,7 @@ package com.genymobile.scrcpy; -import com.genymobile.scrcpy.wrappers.InputManager; - import android.os.SystemClock; import android.view.InputDevice; -import android.view.InputEvent; import android.view.KeyCharacterMap; import android.view.KeyEvent; import android.view.MotionEvent; @@ -50,7 +47,7 @@ public class Controller { public void control() throws IOException { // on start, power on the device if (!device.isScreenOn()) { - injectKeycode(KeyEvent.KEYCODE_POWER); + device.injectKeycode(KeyEvent.KEYCODE_POWER); // dirty hack // After POWER is injected, the device is powered on asynchronously. @@ -133,7 +130,7 @@ public class Controller { } private boolean injectKeycode(int action, int keycode, int metaState) { - return injectKeyEvent(action, keycode, 0, metaState); + return device.injectKeyEvent(action, keycode, 0, metaState); } private boolean injectChar(char c) { @@ -144,7 +141,7 @@ public class Controller { return false; } for (KeyEvent event : events) { - if (!injectEvent(event)) { + if (!device.injectEvent(event)) { return false; } } @@ -200,7 +197,7 @@ public class Controller { MotionEvent event = MotionEvent .obtain(lastTouchDown, now, action, pointerCount, pointerProperties, pointerCoords, 0, buttons, 1f, 1f, DEVICE_ID_VIRTUAL, 0, InputDevice.SOURCE_TOUCHSCREEN, 0); - return injectEvent(event); + return device.injectEvent(event); } private boolean injectScroll(Position position, int hScroll, int vScroll) { @@ -223,26 +220,11 @@ public class Controller { MotionEvent event = MotionEvent .obtain(lastTouchDown, now, MotionEvent.ACTION_SCROLL, 1, pointerProperties, pointerCoords, 0, 0, 1f, 1f, DEVICE_ID_VIRTUAL, 0, InputDevice.SOURCE_TOUCHSCREEN, 0); - return injectEvent(event); - } - - private boolean injectKeyEvent(int action, int keyCode, int repeat, int metaState) { - long now = SystemClock.uptimeMillis(); - KeyEvent event = new KeyEvent(now, now, action, keyCode, repeat, metaState, KeyCharacterMap.VIRTUAL_KEYBOARD, 0, 0, - InputDevice.SOURCE_KEYBOARD); - return injectEvent(event); - } - - private boolean injectKeycode(int keyCode) { - return injectKeyEvent(KeyEvent.ACTION_DOWN, keyCode, 0, 0) && injectKeyEvent(KeyEvent.ACTION_UP, keyCode, 0, 0); - } - - private boolean injectEvent(InputEvent event) { - return device.injectInputEvent(event, InputManager.INJECT_INPUT_EVENT_MODE_ASYNC); + return device.injectEvent(event); } private boolean pressBackOrTurnScreenOn() { int keycode = device.isScreenOn() ? KeyEvent.KEYCODE_BACK : KeyEvent.KEYCODE_POWER; - return injectKeycode(keycode); + return device.injectKeycode(keycode); } } diff --git a/server/src/main/java/com/genymobile/scrcpy/Device.java b/server/src/main/java/com/genymobile/scrcpy/Device.java index ec28bd7c..349486c3 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Device.java +++ b/server/src/main/java/com/genymobile/scrcpy/Device.java @@ -10,8 +10,12 @@ import android.content.IOnPrimaryClipChangedListener; import android.graphics.Rect; import android.os.Build; import android.os.IBinder; +import android.os.SystemClock; import android.view.IRotationWatcher; +import android.view.InputDevice; import android.view.InputEvent; +import android.view.KeyCharacterMap; +import android.view.KeyEvent; import java.util.concurrent.atomic.AtomicBoolean; @@ -147,7 +151,7 @@ public final class Device { return supportsInputEvents; } - public boolean injectInputEvent(InputEvent inputEvent, int mode) { + public boolean injectEvent(InputEvent inputEvent, int mode) { if (!supportsInputEvents()) { throw new AssertionError("Could not inject input event if !supportsInputEvents()"); } @@ -159,6 +163,21 @@ public final class Device { return serviceManager.getInputManager().injectInputEvent(inputEvent, mode); } + public boolean injectEvent(InputEvent event) { + return injectEvent(event, InputManager.INJECT_INPUT_EVENT_MODE_ASYNC); + } + + public boolean injectKeyEvent(int action, int keyCode, int repeat, int metaState) { + long now = SystemClock.uptimeMillis(); + KeyEvent event = new KeyEvent(now, now, action, keyCode, repeat, metaState, KeyCharacterMap.VIRTUAL_KEYBOARD, 0, 0, + InputDevice.SOURCE_KEYBOARD); + return injectEvent(event); + } + + public boolean injectKeycode(int keyCode) { + return injectKeyEvent(KeyEvent.ACTION_DOWN, keyCode, 0, 0) && injectKeyEvent(KeyEvent.ACTION_UP, keyCode, 0, 0); + } + public boolean isScreenOn() { return serviceManager.getPowerManager().isScreenOn(); } From 274b591d1867eacd9cffd6a1b38c906253023f8e Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Mon, 25 May 2020 18:41:05 +0200 Subject: [PATCH 119/450] Fix union typo The "set clipboard" event used the wrong union type to store its text. In practice, it worked because both are at the same offset. --- app/src/control_msg.c | 2 +- app/tests/test_control_msg_serialize.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/control_msg.c b/app/src/control_msg.c index 252a3425..72504138 100644 --- a/app/src/control_msg.c +++ b/app/src/control_msg.c @@ -67,7 +67,7 @@ control_msg_serialize(const struct control_msg *msg, unsigned char *buf) { (uint32_t) msg->inject_scroll_event.vscroll); return 21; case CONTROL_MSG_TYPE_SET_CLIPBOARD: { - size_t len = write_string(msg->inject_text.text, + size_t len = write_string(msg->set_clipboard.text, CONTROL_MSG_CLIPBOARD_TEXT_MAX_LENGTH, &buf[1]); return 1 + len; diff --git a/app/tests/test_control_msg_serialize.c b/app/tests/test_control_msg_serialize.c index 4dc79018..da243d91 100644 --- a/app/tests/test_control_msg_serialize.c +++ b/app/tests/test_control_msg_serialize.c @@ -200,7 +200,7 @@ static void test_serialize_get_clipboard(void) { static void test_serialize_set_clipboard(void) { struct control_msg msg = { .type = CONTROL_MSG_TYPE_SET_CLIPBOARD, - .inject_text = { + .set_clipboard = { .text = "hello, world!", }, }; From fc1dec027093ee8a2746a212db0c72a9d96d39a4 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Mon, 25 May 2020 20:58:24 +0200 Subject: [PATCH 120/450] Paste on "set clipboard" if possible Ctrl+Shift+v synchronizes the computer clipboard to the Android device clipboard. This feature had been added to provide a way to copy UTF-8 text from the computer to the device. To make such a paste more straightforward, if the device runs at least Android 7, also send a PASTE keycode to paste immediately. Fixes #786 --- README.md | 49 ++++++++++--------- app/scrcpy.1 | 2 +- app/src/cli.c | 3 +- app/src/control_msg.c | 5 +- app/src/control_msg.h | 5 +- app/src/input_manager.c | 7 +-- app/tests/test_control_msg_serialize.c | 4 +- .../com/genymobile/scrcpy/ControlMessage.java | 12 ++++- .../scrcpy/ControlMessageReader.java | 9 +++- .../com/genymobile/scrcpy/Controller.java | 21 ++++++-- .../scrcpy/ControlMessageReaderTest.java | 8 +++ 11 files changed, 84 insertions(+), 41 deletions(-) diff --git a/README.md b/README.md index 3f4db9fa..7cc7e1fd 100644 --- a/README.md +++ b/README.md @@ -494,7 +494,8 @@ It is possible to synchronize clipboards between the computer and the device, in both directions: - `Ctrl`+`c` copies the device clipboard to the computer clipboard; - - `Ctrl`+`Shift`+`v` copies the computer clipboard to the device clipboard; + - `Ctrl`+`Shift`+`v` copies the computer clipboard to the device clipboard (and + pastes if the device runs Android >= 7); - `Ctrl`+`v` _pastes_ the computer clipboard as a sequence of text events (but breaks non-ASCII characters). @@ -559,29 +560,29 @@ Also see [issue #14]. ## Shortcuts - | Action | Shortcut | Shortcut (macOS) - | -------------------------------------- |:----------------------------- |:----------------------------- - | Switch fullscreen mode | `Ctrl`+`f` | `Cmd`+`f` - | Rotate display left | `Ctrl`+`←` _(left)_ | `Cmd`+`←` _(left)_ - | Rotate display right | `Ctrl`+`→` _(right)_ | `Cmd`+`→` _(right)_ - | Resize window to 1:1 (pixel-perfect) | `Ctrl`+`g` | `Cmd`+`g` - | Resize window to remove black borders | `Ctrl`+`x` \| _Double-click¹_ | `Cmd`+`x` \| _Double-click¹_ - | Click on `HOME` | `Ctrl`+`h` \| _Middle-click_ | `Ctrl`+`h` \| _Middle-click_ - | Click on `BACK` | `Ctrl`+`b` \| _Right-click²_ | `Cmd`+`b` \| _Right-click²_ - | Click on `APP_SWITCH` | `Ctrl`+`s` | `Cmd`+`s` - | Click on `MENU` | `Ctrl`+`m` | `Ctrl`+`m` - | Click on `VOLUME_UP` | `Ctrl`+`↑` _(up)_ | `Cmd`+`↑` _(up)_ - | Click on `VOLUME_DOWN` | `Ctrl`+`↓` _(down)_ | `Cmd`+`↓` _(down)_ - | Click on `POWER` | `Ctrl`+`p` | `Cmd`+`p` - | Power on | _Right-click²_ | _Right-click²_ - | Turn device screen off (keep mirroring)| `Ctrl`+`o` | `Cmd`+`o` - | Rotate device screen | `Ctrl`+`r` | `Cmd`+`r` - | Expand notification panel | `Ctrl`+`n` | `Cmd`+`n` - | Collapse notification panel | `Ctrl`+`Shift`+`n` | `Cmd`+`Shift`+`n` - | Copy device clipboard to computer | `Ctrl`+`c` | `Cmd`+`c` - | Paste computer clipboard to device | `Ctrl`+`v` | `Cmd`+`v` - | Copy computer clipboard to device | `Ctrl`+`Shift`+`v` | `Cmd`+`Shift`+`v` - | Enable/disable FPS counter (on stdout) | `Ctrl`+`i` | `Cmd`+`i` + | Action | Shortcut | Shortcut (macOS) + | ------------------------------------------- |:----------------------------- |:----------------------------- + | Switch fullscreen mode | `Ctrl`+`f` | `Cmd`+`f` + | Rotate display left | `Ctrl`+`←` _(left)_ | `Cmd`+`←` _(left)_ + | Rotate display right | `Ctrl`+`→` _(right)_ | `Cmd`+`→` _(right)_ + | Resize window to 1:1 (pixel-perfect) | `Ctrl`+`g` | `Cmd`+`g` + | Resize window to remove black borders | `Ctrl`+`x` \| _Double-click¹_ | `Cmd`+`x` \| _Double-click¹_ + | Click on `HOME` | `Ctrl`+`h` \| _Middle-click_ | `Ctrl`+`h` \| _Middle-click_ + | Click on `BACK` | `Ctrl`+`b` \| _Right-click²_ | `Cmd`+`b` \| _Right-click²_ + | Click on `APP_SWITCH` | `Ctrl`+`s` | `Cmd`+`s` + | Click on `MENU` | `Ctrl`+`m` | `Ctrl`+`m` + | Click on `VOLUME_UP` | `Ctrl`+`↑` _(up)_ | `Cmd`+`↑` _(up)_ + | Click on `VOLUME_DOWN` | `Ctrl`+`↓` _(down)_ | `Cmd`+`↓` _(down)_ + | Click on `POWER` | `Ctrl`+`p` | `Cmd`+`p` + | Power on | _Right-click²_ | _Right-click²_ + | Turn device screen off (keep mirroring) | `Ctrl`+`o` | `Cmd`+`o` + | Rotate device screen | `Ctrl`+`r` | `Cmd`+`r` + | Expand notification panel | `Ctrl`+`n` | `Cmd`+`n` + | Collapse notification panel | `Ctrl`+`Shift`+`n` | `Cmd`+`Shift`+`n` + | Copy device clipboard to computer | `Ctrl`+`c` | `Cmd`+`c` + | Paste computer clipboard to device | `Ctrl`+`v` | `Cmd`+`v` + | Copy computer clipboard to device and paste | `Ctrl`+`Shift`+`v` | `Cmd`+`Shift`+`v` + | Enable/disable FPS counter (on stdout) | `Ctrl`+`i` | `Cmd`+`i` _¹Double-click on black borders to remove them._ _²Right-click turns the screen on if it was off, presses BACK otherwise._ diff --git a/app/scrcpy.1 b/app/scrcpy.1 index 1020a2fb..ed4594ed 100644 --- a/app/scrcpy.1 +++ b/app/scrcpy.1 @@ -280,7 +280,7 @@ Paste computer clipboard to device .TP .B Ctrl+Shift+v -Copy computer clipboard to device +Copy computer clipboard to device (and paste if the device runs Android >= 7) .TP .B Ctrl+i diff --git a/app/src/cli.c b/app/src/cli.c index a0c17c1a..836edc30 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -247,7 +247,8 @@ scrcpy_print_usage(const char *arg0) { " Paste computer clipboard to device\n" "\n" " " CTRL_OR_CMD "+Shift+v\n" - " Copy computer clipboard to device\n" + " Copy computer clipboard to device (and paste if the device\n" + " runs Android >= 7)\n" "\n" " " CTRL_OR_CMD "+i\n" " Enable/disable FPS counter (print frames/second in logs)\n" diff --git a/app/src/control_msg.c b/app/src/control_msg.c index 72504138..c5778c02 100644 --- a/app/src/control_msg.c +++ b/app/src/control_msg.c @@ -67,10 +67,11 @@ control_msg_serialize(const struct control_msg *msg, unsigned char *buf) { (uint32_t) msg->inject_scroll_event.vscroll); return 21; case CONTROL_MSG_TYPE_SET_CLIPBOARD: { + buf[1] = !!msg->set_clipboard.paste; size_t len = write_string(msg->set_clipboard.text, CONTROL_MSG_CLIPBOARD_TEXT_MAX_LENGTH, - &buf[1]); - return 1 + len; + &buf[2]); + return 2 + len; } case CONTROL_MSG_TYPE_SET_SCREEN_POWER_MODE: buf[1] = msg->set_screen_power_mode.mode; diff --git a/app/src/control_msg.h b/app/src/control_msg.h index e132fc6b..0e85c97e 100644 --- a/app/src/control_msg.h +++ b/app/src/control_msg.h @@ -11,9 +11,9 @@ #include "common.h" #define CONTROL_MSG_INJECT_TEXT_MAX_LENGTH 300 -#define CONTROL_MSG_CLIPBOARD_TEXT_MAX_LENGTH 4093 +#define CONTROL_MSG_CLIPBOARD_TEXT_MAX_LENGTH 4092 #define CONTROL_MSG_SERIALIZED_MAX_SIZE \ - (3 + CONTROL_MSG_CLIPBOARD_TEXT_MAX_LENGTH) + (4 + CONTROL_MSG_CLIPBOARD_TEXT_MAX_LENGTH) #define POINTER_ID_MOUSE UINT64_C(-1); @@ -62,6 +62,7 @@ struct control_msg { } inject_scroll_event; struct { char *text; // owned, to be freed by SDL_free() + bool paste; } set_clipboard; struct { enum screen_power_mode mode; diff --git a/app/src/input_manager.c b/app/src/input_manager.c index e8c8d68e..3cb9e3ac 100644 --- a/app/src/input_manager.c +++ b/app/src/input_manager.c @@ -112,7 +112,7 @@ request_device_clipboard(struct controller *controller) { } static void -set_device_clipboard(struct controller *controller) { +set_device_clipboard(struct controller *controller, bool paste) { char *text = SDL_GetClipboardText(); if (!text) { LOGW("Could not get clipboard text: %s", SDL_GetError()); @@ -127,6 +127,7 @@ set_device_clipboard(struct controller *controller) { struct control_msg msg; msg.type = CONTROL_MSG_TYPE_SET_CLIPBOARD; msg.set_clipboard.text = text; + msg.set_clipboard.paste = paste; if (!controller_push_msg(controller, &msg)) { SDL_free(text); @@ -354,8 +355,8 @@ input_manager_process_key(struct input_manager *im, case SDLK_v: if (control && cmd && !repeat && down) { if (shift) { - // store the text in the device clipboard - set_device_clipboard(controller); + // store the text in the device clipboard and paste + set_device_clipboard(controller, true); } else { // inject the text as input events clipboard_paste(controller); diff --git a/app/tests/test_control_msg_serialize.c b/app/tests/test_control_msg_serialize.c index da243d91..c6ff7b2e 100644 --- a/app/tests/test_control_msg_serialize.c +++ b/app/tests/test_control_msg_serialize.c @@ -201,16 +201,18 @@ static void test_serialize_set_clipboard(void) { struct control_msg msg = { .type = CONTROL_MSG_TYPE_SET_CLIPBOARD, .set_clipboard = { + .paste = true, .text = "hello, world!", }, }; unsigned char buf[CONTROL_MSG_SERIALIZED_MAX_SIZE]; int size = control_msg_serialize(&msg, buf); - assert(size == 16); + assert(size == 17); const unsigned char expected[] = { CONTROL_MSG_TYPE_SET_CLIPBOARD, + 1, // paste 0x00, 0x0d, // text length 'h', 'e', 'l', 'l', 'o', ',', ' ', 'w', 'o', 'r', 'l', 'd', '!', // text }; diff --git a/server/src/main/java/com/genymobile/scrcpy/ControlMessage.java b/server/src/main/java/com/genymobile/scrcpy/ControlMessage.java index 195b04bf..7d0ab7a6 100644 --- a/server/src/main/java/com/genymobile/scrcpy/ControlMessage.java +++ b/server/src/main/java/com/genymobile/scrcpy/ControlMessage.java @@ -17,6 +17,8 @@ public final class ControlMessage { public static final int TYPE_SET_SCREEN_POWER_MODE = 9; public static final int TYPE_ROTATE_DEVICE = 10; + public static final int FLAGS_PASTE = 1; + private int type; private String text; private int metaState; // KeyEvent.META_* @@ -28,6 +30,7 @@ public final class ControlMessage { private Position position; private int hScroll; private int vScroll; + private int flags; private ControlMessage() { } @@ -68,10 +71,13 @@ public final class ControlMessage { return msg; } - public static ControlMessage createSetClipboard(String text) { + public static ControlMessage createSetClipboard(String text, boolean paste) { ControlMessage msg = new ControlMessage(); msg.type = TYPE_SET_CLIPBOARD; msg.text = text; + if (paste) { + msg.flags = FLAGS_PASTE; + } return msg; } @@ -134,4 +140,8 @@ public final class ControlMessage { public int getVScroll() { return vScroll; } + + public int getFlags() { + return flags; + } } diff --git a/server/src/main/java/com/genymobile/scrcpy/ControlMessageReader.java b/server/src/main/java/com/genymobile/scrcpy/ControlMessageReader.java index 2688641c..fbf49a61 100644 --- a/server/src/main/java/com/genymobile/scrcpy/ControlMessageReader.java +++ b/server/src/main/java/com/genymobile/scrcpy/ControlMessageReader.java @@ -12,8 +12,9 @@ public class ControlMessageReader { static final int INJECT_TOUCH_EVENT_PAYLOAD_LENGTH = 27; static final int INJECT_SCROLL_EVENT_PAYLOAD_LENGTH = 20; static final int SET_SCREEN_POWER_MODE_PAYLOAD_LENGTH = 1; + static final int SET_CLIPBOARD_FIXED_PAYLOAD_LENGTH = 1; - public static final int CLIPBOARD_TEXT_MAX_LENGTH = 4093; + public static final int CLIPBOARD_TEXT_MAX_LENGTH = 4092; // 4096 - 1 (type) - 1 (parse flag) - 2 (length) public static final int INJECT_TEXT_MAX_LENGTH = 300; private static final int RAW_BUFFER_SIZE = 4096; @@ -148,11 +149,15 @@ public class ControlMessageReader { } private ControlMessage parseSetClipboard() { + if (buffer.remaining() < SET_CLIPBOARD_FIXED_PAYLOAD_LENGTH) { + return null; + } + boolean parse = buffer.get() != 0; String text = parseString(); if (text == null) { return null; } - return ControlMessage.createSetClipboard(text); + return ControlMessage.createSetClipboard(text, parse); } private ControlMessage parseSetScreenPowerMode() { diff --git a/server/src/main/java/com/genymobile/scrcpy/Controller.java b/server/src/main/java/com/genymobile/scrcpy/Controller.java index ab7b6c40..4999f7eb 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Controller.java +++ b/server/src/main/java/com/genymobile/scrcpy/Controller.java @@ -1,5 +1,6 @@ package com.genymobile.scrcpy; +import android.os.Build; import android.os.SystemClock; import android.view.InputDevice; import android.view.KeyCharacterMap; @@ -107,10 +108,8 @@ public class Controller { sender.pushClipboardText(clipboardText); break; case ControlMessage.TYPE_SET_CLIPBOARD: - boolean setClipboardOk = device.setClipboardText(msg.getText()); - if (setClipboardOk) { - Ln.i("Device clipboard set"); - } + boolean paste = (msg.getFlags() & ControlMessage.FLAGS_PASTE) != 0; + setClipboard(msg.getText(), paste); break; case ControlMessage.TYPE_SET_SCREEN_POWER_MODE: if (device.supportsInputEvents()) { @@ -227,4 +226,18 @@ public class Controller { int keycode = device.isScreenOn() ? KeyEvent.KEYCODE_BACK : KeyEvent.KEYCODE_POWER; return device.injectKeycode(keycode); } + + private boolean setClipboard(String text, boolean paste) { + boolean ok = device.setClipboardText(text); + if (ok) { + Ln.i("Device clipboard set"); + } + + // On Android >= 7, also press the PASTE key if requested + if (paste && Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && device.supportsInputEvents()) { + device.injectKeycode(KeyEvent.KEYCODE_PASTE); + } + + return ok; + } } diff --git a/server/src/test/java/com/genymobile/scrcpy/ControlMessageReaderTest.java b/server/src/test/java/com/genymobile/scrcpy/ControlMessageReaderTest.java index d495b44c..f5fa4d09 100644 --- a/server/src/test/java/com/genymobile/scrcpy/ControlMessageReaderTest.java +++ b/server/src/test/java/com/genymobile/scrcpy/ControlMessageReaderTest.java @@ -216,6 +216,7 @@ public class ControlMessageReaderTest { ByteArrayOutputStream bos = new ByteArrayOutputStream(); DataOutputStream dos = new DataOutputStream(bos); dos.writeByte(ControlMessage.TYPE_SET_CLIPBOARD); + dos.writeByte(1); // paste byte[] text = "testé".getBytes(StandardCharsets.UTF_8); dos.writeShort(text.length); dos.write(text); @@ -227,6 +228,9 @@ public class ControlMessageReaderTest { Assert.assertEquals(ControlMessage.TYPE_SET_CLIPBOARD, event.getType()); Assert.assertEquals("testé", event.getText()); + + boolean parse = (event.getFlags() & ControlMessage.FLAGS_PASTE) != 0; + Assert.assertTrue(parse); } @Test @@ -238,6 +242,7 @@ public class ControlMessageReaderTest { dos.writeByte(ControlMessage.TYPE_SET_CLIPBOARD); byte[] rawText = new byte[ControlMessageReader.CLIPBOARD_TEXT_MAX_LENGTH]; + dos.writeByte(1); // paste Arrays.fill(rawText, (byte) 'a'); String text = new String(rawText, 0, rawText.length); @@ -251,6 +256,9 @@ public class ControlMessageReaderTest { Assert.assertEquals(ControlMessage.TYPE_SET_CLIPBOARD, event.getType()); Assert.assertEquals(text, event.getText()); + + boolean parse = (event.getFlags() & ControlMessage.FLAGS_PASTE) != 0; + Assert.assertTrue(parse); } @Test From 8f619f337baec26791514632c64c0385585c2f06 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Tue, 26 May 2020 19:21:05 +0200 Subject: [PATCH 121/450] Upgrade platform-tools (30.0.0) for Windows Include the latest version of adb in Windows releases. --- prebuilt-deps/Makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/prebuilt-deps/Makefile b/prebuilt-deps/Makefile index 088cee92..c6cbcf7b 100644 --- a/prebuilt-deps/Makefile +++ b/prebuilt-deps/Makefile @@ -35,6 +35,6 @@ prepare-sdl2: SDL2-2.0.12 prepare-adb: - @./prepare-dep https://dl.google.com/android/repository/platform-tools_r29.0.5-windows.zip \ - 2df06160056ec9a84c7334af2a1e42740befbb1a2e34370e7af544a2cc78152c \ + @./prepare-dep https://dl.google.com/android/repository/platform-tools_r30.0.0-windows.zip \ + 854305f9a702f5ea2c3de73edde402bd26afa0ee944c9b0c4380420f5a862e0d \ platform-tools From d499ee53c9fa63fb02db00dc09fdd4425f506ef6 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Wed, 27 May 2020 12:05:29 +0200 Subject: [PATCH 122/450] Initialize a default log level Clean up has been broken by 3df63c579da7a35048458c6884f5f386154d2353. The verbosity was correctly initialized for the Server process, but not for the CleanUp process. To avoid the problem, initialize a default log level. --- server/src/main/java/com/genymobile/scrcpy/Ln.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/Ln.java b/server/src/main/java/com/genymobile/scrcpy/Ln.java index 4013315f..c218fa0f 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Ln.java +++ b/server/src/main/java/com/genymobile/scrcpy/Ln.java @@ -15,7 +15,7 @@ public final class Ln { DEBUG, INFO, WARN, ERROR } - private static Level threshold; + private static Level threshold = Level.INFO; private Ln() { // not instantiable From 2ca8318b9df10f24de138ab59ab41d589db26fb3 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Wed, 27 May 2020 12:38:59 +0200 Subject: [PATCH 123/450] Improve manpage formatting Add a new line to avoid unwanted text justification --- app/scrcpy.1 | 1 + 1 file changed, 1 insertion(+) diff --git a/app/scrcpy.1 b/app/scrcpy.1 index ed4594ed..e5568849 100644 --- a/app/scrcpy.1 +++ b/app/scrcpy.1 @@ -129,6 +129,7 @@ Force recording format (either mp4 or mkv). Request SDL to use the given render driver (this is just a hint). Supported names are currently "direct3d", "opengl", "opengles2", "opengles", "metal" and "software". + .UR https://wiki.libsdl.org/SDL_HINT_RENDER_DRIVER .UE From 93a5c5149d6b4d04376f147e6da2f5ca3ced8aea Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Wed, 27 May 2020 18:12:35 +0200 Subject: [PATCH 124/450] Push clipboard text only if not null In practice, it does not change anything (it just avoids a spurious wake-up), but semantically, it makes no sense to call pushClipboardText() with a null value. --- server/src/main/java/com/genymobile/scrcpy/Controller.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/Controller.java b/server/src/main/java/com/genymobile/scrcpy/Controller.java index 4999f7eb..960c6a6e 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Controller.java +++ b/server/src/main/java/com/genymobile/scrcpy/Controller.java @@ -105,7 +105,9 @@ public class Controller { break; case ControlMessage.TYPE_GET_CLIPBOARD: String clipboardText = device.getClipboardText(); - sender.pushClipboardText(clipboardText); + if (clipboardText != null) { + sender.pushClipboardText(clipboardText); + } break; case ControlMessage.TYPE_SET_CLIPBOARD: boolean paste = (msg.getFlags() & ControlMessage.FLAGS_PASTE) != 0; From dcde578a50ffb9d2594c4764a067e9dd328bf55c Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Wed, 27 May 2020 18:18:39 +0200 Subject: [PATCH 125/450] Reactivate "turn device screen on" feature This reverts commit 8c8649cfcd710859ce18eab557ed2af8cedb9a42. I cannot reproduce the issue with Ctrl+Shift+o on any device, so in practice it works, it's too bad to remove the feature for a random bug on some Android versions on some devices. --- README.md | 1 + app/scrcpy.1 | 4 ++++ app/src/cli.c | 3 +++ app/src/input_manager.c | 7 +++++-- 4 files changed, 13 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 7cc7e1fd..fe4c8584 100644 --- a/README.md +++ b/README.md @@ -576,6 +576,7 @@ Also see [issue #14]. | Click on `POWER` | `Ctrl`+`p` | `Cmd`+`p` | Power on | _Right-click²_ | _Right-click²_ | Turn device screen off (keep mirroring) | `Ctrl`+`o` | `Cmd`+`o` + | Turn device screen on | `Ctrl`+`Shift`+`o` | `Cmd`+`Shift`+`o` | Rotate device screen | `Ctrl`+`r` | `Cmd`+`r` | Expand notification panel | `Ctrl`+`n` | `Cmd`+`n` | Collapse notification panel | `Ctrl`+`Shift`+`n` | `Cmd`+`Shift`+`n` diff --git a/app/scrcpy.1 b/app/scrcpy.1 index e5568849..de4a70e4 100644 --- a/app/scrcpy.1 +++ b/app/scrcpy.1 @@ -259,6 +259,10 @@ Turn screen on .B Ctrl+o Turn device screen off (keep mirroring) +.TP +.B Ctrl+Shift+o +Turn device screen on + .TP .B Ctrl+r Rotate device screen diff --git a/app/src/cli.c b/app/src/cli.c index 836edc30..0be1bd75 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -231,6 +231,9 @@ scrcpy_print_usage(const char *arg0) { " " CTRL_OR_CMD "+o\n" " Turn device screen off (keep mirroring)\n" "\n" + " " CTRL_OR_CMD "+Shift+o\n" + " Turn device screen on\n" + "\n" " " CTRL_OR_CMD "+r\n" " Rotate device screen\n" "\n" diff --git a/app/src/input_manager.c b/app/src/input_manager.c index 3cb9e3ac..e8ba9f79 100644 --- a/app/src/input_manager.c +++ b/app/src/input_manager.c @@ -321,8 +321,11 @@ input_manager_process_key(struct input_manager *im, } return; case SDLK_o: - if (control && cmd && !shift && down) { - set_screen_power_mode(controller, SCREEN_POWER_MODE_OFF); + if (control && cmd && down) { + enum screen_power_mode mode = shift + ? SCREEN_POWER_MODE_NORMAL + : SCREEN_POWER_MODE_OFF; + set_screen_power_mode(controller, mode); } return; case SDLK_DOWN: From 44fa4a090eb7c64678a2034535da3ee09884b657 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Wed, 27 May 2020 18:19:33 +0200 Subject: [PATCH 126/450] Bump version to 1.14 --- meson.build | 2 +- server/build.gradle | 4 ++-- server/build_without_gradle.sh | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/meson.build b/meson.build index 49093453..46b9a092 100644 --- a/meson.build +++ b/meson.build @@ -1,5 +1,5 @@ project('scrcpy', 'c', - version: '1.13', + version: '1.14', meson_version: '>= 0.48', default_options: [ 'c_std=c11', diff --git a/server/build.gradle b/server/build.gradle index 3fa16519..c8ff85d6 100644 --- a/server/build.gradle +++ b/server/build.gradle @@ -6,8 +6,8 @@ android { applicationId "com.genymobile.scrcpy" minSdkVersion 21 targetSdkVersion 29 - versionCode 15 - versionName "1.13" + versionCode 16 + versionName "1.14" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" } buildTypes { diff --git a/server/build_without_gradle.sh b/server/build_without_gradle.sh index 06fc0d75..f6889701 100755 --- a/server/build_without_gradle.sh +++ b/server/build_without_gradle.sh @@ -12,7 +12,7 @@ set -e SCRCPY_DEBUG=false -SCRCPY_VERSION_NAME=1.13 +SCRCPY_VERSION_NAME=1.14 PLATFORM=${ANDROID_PLATFORM:-29} BUILD_TOOLS=${ANDROID_BUILD_TOOLS:-29.0.2} From ef91ab2841db16d8ac192813cc8cdca7552f8a12 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Wed, 27 May 2020 19:30:56 +0200 Subject: [PATCH 127/450] Update links to v1.14 in README and BUILD --- BUILD.md | 6 +++--- README.md | 8 ++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/BUILD.md b/BUILD.md index e4f175ef..3696e6b0 100644 --- a/BUILD.md +++ b/BUILD.md @@ -249,10 +249,10 @@ You can then [run](README.md#run) _scrcpy_. ## Prebuilt server - - [`scrcpy-server-v1.13`][direct-scrcpy-server] - _(SHA-256: 5fee64ca1ccdc2f38550f31f5353c66de3de30c2e929a964e30fa2d005d5f885)_ + - [`scrcpy-server-v1.14`][direct-scrcpy-server] + _(SHA-256: 1d1b18a2b80e956771fd63b99b414d2d028713a8f12ddfa5a369709ad4295620)_ -[direct-scrcpy-server]: https://github.com/Genymobile/scrcpy/releases/download/v1.13/scrcpy-server-v1.13 +[direct-scrcpy-server]: https://github.com/Genymobile/scrcpy/releases/download/v1.14/scrcpy-server-v1.14 Download the prebuilt server somewhere, and specify its path during the Meson configuration: diff --git a/README.md b/README.md index fe4c8584..0162de0d 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# scrcpy (v1.13) +# scrcpy (v1.14) 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. @@ -69,10 +69,10 @@ hard). For Windows, for simplicity, a prebuilt archive with all the dependencies (including `adb`) is available: - - [`scrcpy-win64-v1.13.zip`][direct-win64] - _(SHA-256: 806aafc00d4db01513193addaa24f47858893ba5efe75770bfef6ae1ea987d27)_ + - [`scrcpy-win64-v1.14.zip`][direct-win64] + _(SHA-256: 2be9139e46e29cf2f5f695848bb2b75a543b8f38be1133257dc5068252abc25f)_ -[direct-win64]: https://github.com/Genymobile/scrcpy/releases/download/v1.13/scrcpy-win64-v1.13.zip +[direct-win64]: https://github.com/Genymobile/scrcpy/releases/download/v1.14/scrcpy-win64-v1.14.zip It is also available in [Chocolatey]: From 8b73c90427e5f31d031357c2b319512b57ef78f5 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Wed, 27 May 2020 19:32:02 +0200 Subject: [PATCH 128/450] Mention how to turn the screen on in README Now that Ctrl+Shift+o has been reactivated, mention it in the "turn screen off" section. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 0162de0d..6f698292 100644 --- a/README.md +++ b/README.md @@ -439,7 +439,7 @@ scrcpy -S Or by pressing `Ctrl`+`o` at any time. -To turn it back on, press `POWER` (or `Ctrl`+`p`). +To turn it back on, press `Ctrl`+`Shift`+`o` (or `POWER`, `Ctrl`+`p`). It can be useful to also prevent the device to sleep: From 0e4a6f462bcc628af00896eea38aa883d68acc88 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 28 May 2020 23:07:28 +0200 Subject: [PATCH 129/450] Mention stay awake limitation The "stay awake" feature only works when the device is plugged in. Refs #1445 --- README.md | 2 +- app/scrcpy.1 | 2 +- app/src/cli.c | 3 ++- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 6f698292..fe7f0fac 100644 --- a/README.md +++ b/README.md @@ -417,7 +417,7 @@ The secondary display may only be controlled if the device runs at least Android #### Stay awake -To prevent the device to sleep after some delay: +To prevent the device to sleep after some delay when the device is plugged in: ```bash scrcpy --stay-awake diff --git a/app/scrcpy.1 b/app/scrcpy.1 index de4a70e4..776d78ae 100644 --- a/app/scrcpy.1 +++ b/app/scrcpy.1 @@ -167,7 +167,7 @@ Default is "info" for release builds, "debug" for debug builds. .TP .B \-w, \-\-stay-awake -Keep the device on while scrcpy is running. +Keep the device on while scrcpy is running, when the device is plugged in. .TP .B \-\-window\-borderless diff --git a/app/src/cli.c b/app/src/cli.c index 0be1bd75..be0b7c23 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -159,7 +159,8 @@ scrcpy_print_usage(const char *arg0) { #endif "\n" " -w, --stay-awake\n" - " Keep the device on while scrcpy is running.\n" + " Keep the device on while scrcpy is running, when the device\n" + " is plugged in.\n" "\n" " --window-borderless\n" " Disable window decorations (display borderless window).\n" From e4efd757666c0a29876783dd916043ebebbec493 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 29 May 2020 22:02:41 +0200 Subject: [PATCH 130/450] Avoid repetition for some shortcuts Keeping the key pressed generate "repeat" events. It does not make sense to repeat the event for rotation or turn screen off. --- app/src/input_manager.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/src/input_manager.c b/app/src/input_manager.c index e8ba9f79..54e619bf 100644 --- a/app/src/input_manager.c +++ b/app/src/input_manager.c @@ -321,7 +321,7 @@ input_manager_process_key(struct input_manager *im, } return; case SDLK_o: - if (control && cmd && down) { + if (control && cmd && !repeat && down) { enum screen_power_mode mode = shift ? SCREEN_POWER_MODE_NORMAL : SCREEN_POWER_MODE_OFF; @@ -341,12 +341,12 @@ input_manager_process_key(struct input_manager *im, } return; case SDLK_LEFT: - if (cmd && !shift && down) { + if (cmd && !shift && !repeat && down) { rotate_client_left(im->screen); } return; case SDLK_RIGHT: - if (cmd && !shift && down) { + if (cmd && !shift && !repeat && down) { rotate_client_right(im->screen); } return; From 8ff07e0c88c7d267cbf00fd9e63f8c9f9dc3c952 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Tue, 2 Jun 2020 18:17:25 +0200 Subject: [PATCH 131/450] Git-ignore release directories --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index 222769b3..7bc30289 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,8 @@ build/ /dist/ +/build-*/ +/build_*/ +/release-*/ .idea/ .gradle/ /x/ From c4323df97607d345f35d832cd373b4882b73a9ac Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Tue, 2 Jun 2020 18:18:58 +0200 Subject: [PATCH 132/450] Fix incorrect log return value The function must return a SDL_LogPriority, but returned an enum sc_log_level. (It was harmless because this specific return should never happen, as asserted.) --- app/src/main.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main.c b/app/src/main.c index 85e578ae..29c9b423 100644 --- a/app/src/main.c +++ b/app/src/main.c @@ -42,7 +42,7 @@ convert_log_level_to_sdl(enum sc_log_level level) { return SDL_LOG_PRIORITY_ERROR; default: assert(!"unexpected log level"); - return SC_LOG_LEVEL_INFO; + return SDL_LOG_PRIORITY_INFO; } } From 6e1069a8228217d39b87f6a490a4c2c75258c16a Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Tue, 2 Jun 2020 18:21:01 +0200 Subject: [PATCH 133/450] Configure log level for application only Do not expose internal SDL logs to users. Fixes #1441 --- app/src/main.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main.c b/app/src/main.c index 29c9b423..7d7ddb82 100644 --- a/app/src/main.c +++ b/app/src/main.c @@ -71,7 +71,7 @@ main(int argc, char *argv[]) { } SDL_LogPriority sdl_log = convert_log_level_to_sdl(args.opts.log_level); - SDL_LogSetAllPriority(sdl_log); + SDL_LogSetPriority(SDL_LOG_CATEGORY_APPLICATION, sdl_log); if (args.help) { scrcpy_print_usage(argv[0]); From 1b73eff3c95a3e1875f0db51a60179f3d282ea57 Mon Sep 17 00:00:00 2001 From: Louis Leseur Date: Sun, 7 Jun 2020 00:02:21 +0200 Subject: [PATCH 134/450] Add missing file in build_without_gradle.sh Fixes #1481 PR #1482 Signed-off-by: Louis Leseur Signed-off-by: Romain Vimont --- server/build_without_gradle.sh | 3 +++ 1 file changed, 3 insertions(+) diff --git a/server/build_without_gradle.sh b/server/build_without_gradle.sh index f6889701..e3be2faf 100755 --- a/server/build_without_gradle.sh +++ b/server/build_without_gradle.sh @@ -42,6 +42,8 @@ echo "Generating java from aidl..." cd "$SERVER_DIR/src/main/aidl" "$ANDROID_HOME/build-tools/$BUILD_TOOLS/aidl" -o"$CLASSES_DIR" \ android/view/IRotationWatcher.aidl +"$ANDROID_HOME/build-tools/$BUILD_TOOLS/aidl" -o"$CLASSES_DIR" \ + android/content/IOnPrimaryClipChangedListener.aidl echo "Compiling java sources..." cd ../java @@ -55,6 +57,7 @@ cd "$CLASSES_DIR" "$ANDROID_HOME/build-tools/$BUILD_TOOLS/dx" --dex \ --output "$BUILD_DIR/classes.dex" \ android/view/*.class \ + android/content/*.class \ com/genymobile/scrcpy/*.class \ com/genymobile/scrcpy/wrappers/*.class From 3c0fc8f54f42bf6e7eca35b352a7d343749b65c4 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Tue, 9 Jun 2020 22:08:34 +0200 Subject: [PATCH 135/450] Mention sndcpy --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index fe7f0fac..df3e1172 100644 --- a/README.md +++ b/README.md @@ -550,11 +550,11 @@ scrcpy --push-target /sdcard/foo/bar/ ### Audio forwarding -Audio is not forwarded by _scrcpy_. Use [USBaudio] (Linux-only). +Audio is not forwarded by _scrcpy_. Use [sndcpy]. Also see [issue #14]. -[USBaudio]: https://github.com/rom1v/usbaudio +[sndcpy]: https://github.com/rom1v/sndcpy [issue #14]: https://github.com/Genymobile/scrcpy/issues/14 From 8f314c74b073a7f844de16539657a264f05662d6 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 4 Jun 2020 21:09:42 +0200 Subject: [PATCH 136/450] Reorganize message size constants Make the max clipboard text length depend on the max message size. --- app/src/control_msg.h | 8 +++++--- app/src/device_msg.h | 5 +++-- .../java/com/genymobile/scrcpy/ControlMessageReader.java | 8 ++++---- .../java/com/genymobile/scrcpy/DeviceMessageWriter.java | 6 +++--- 4 files changed, 15 insertions(+), 12 deletions(-) diff --git a/app/src/control_msg.h b/app/src/control_msg.h index 0e85c97e..1a5449a0 100644 --- a/app/src/control_msg.h +++ b/app/src/control_msg.h @@ -10,10 +10,12 @@ #include "android/keycodes.h" #include "common.h" +#define CONTROL_MSG_SERIALIZED_MAX_SIZE 4096 + #define CONTROL_MSG_INJECT_TEXT_MAX_LENGTH 300 -#define CONTROL_MSG_CLIPBOARD_TEXT_MAX_LENGTH 4092 -#define CONTROL_MSG_SERIALIZED_MAX_SIZE \ - (4 + CONTROL_MSG_CLIPBOARD_TEXT_MAX_LENGTH) +// type: 1 byte; paste flag: 1 byte; length: 2 bytes +#define CONTROL_MSG_CLIPBOARD_TEXT_MAX_LENGTH \ + (CONTROL_MSG_SERIALIZED_MAX_SIZE - 4) #define POINTER_ID_MOUSE UINT64_C(-1); diff --git a/app/src/device_msg.h b/app/src/device_msg.h index 04723597..f19630bf 100644 --- a/app/src/device_msg.h +++ b/app/src/device_msg.h @@ -7,8 +7,9 @@ #include "config.h" -#define DEVICE_MSG_TEXT_MAX_LENGTH 4093 -#define DEVICE_MSG_SERIALIZED_MAX_SIZE (3 + DEVICE_MSG_TEXT_MAX_LENGTH) +#define DEVICE_MSG_SERIALIZED_MAX_SIZE 4096 +// type: 1 byte; length: 2 bytes +#define DEVICE_MSG_TEXT_MAX_LENGTH (DEVICE_MSG_SERIALIZED_MAX_SIZE - 3) enum device_msg_type { DEVICE_MSG_TYPE_CLIPBOARD, diff --git a/server/src/main/java/com/genymobile/scrcpy/ControlMessageReader.java b/server/src/main/java/com/genymobile/scrcpy/ControlMessageReader.java index fbf49a61..f80ffebb 100644 --- a/server/src/main/java/com/genymobile/scrcpy/ControlMessageReader.java +++ b/server/src/main/java/com/genymobile/scrcpy/ControlMessageReader.java @@ -14,12 +14,12 @@ public class ControlMessageReader { static final int SET_SCREEN_POWER_MODE_PAYLOAD_LENGTH = 1; static final int SET_CLIPBOARD_FIXED_PAYLOAD_LENGTH = 1; - public static final int CLIPBOARD_TEXT_MAX_LENGTH = 4092; // 4096 - 1 (type) - 1 (parse flag) - 2 (length) + private static final int MESSAGE_MAX_SIZE = 4096; + + public static final int CLIPBOARD_TEXT_MAX_LENGTH = MESSAGE_MAX_SIZE - 4; // type: 1 byte; paste flag: 1 byte; length: 2 bytes public static final int INJECT_TEXT_MAX_LENGTH = 300; - private static final int RAW_BUFFER_SIZE = 4096; - - private final byte[] rawBuffer = new byte[RAW_BUFFER_SIZE]; + private final byte[] rawBuffer = new byte[MESSAGE_MAX_SIZE]; private final ByteBuffer buffer = ByteBuffer.wrap(rawBuffer); private final byte[] textBuffer = new byte[CLIPBOARD_TEXT_MAX_LENGTH]; diff --git a/server/src/main/java/com/genymobile/scrcpy/DeviceMessageWriter.java b/server/src/main/java/com/genymobile/scrcpy/DeviceMessageWriter.java index 6c7f3634..ea54d533 100644 --- a/server/src/main/java/com/genymobile/scrcpy/DeviceMessageWriter.java +++ b/server/src/main/java/com/genymobile/scrcpy/DeviceMessageWriter.java @@ -7,10 +7,10 @@ import java.nio.charset.StandardCharsets; public class DeviceMessageWriter { - public static final int CLIPBOARD_TEXT_MAX_LENGTH = 4093; - private static final int MAX_EVENT_SIZE = CLIPBOARD_TEXT_MAX_LENGTH + 3; + private static final int MESSAGE_MAX_SIZE = 4096; + public static final int CLIPBOARD_TEXT_MAX_LENGTH = MESSAGE_MAX_SIZE - 3; // type: 1 byte; length: 2 bytes - private final byte[] rawBuffer = new byte[MAX_EVENT_SIZE]; + private final byte[] rawBuffer = new byte[MESSAGE_MAX_SIZE]; private final ByteBuffer buffer = ByteBuffer.wrap(rawBuffer); public void writeTo(DeviceMessage msg, OutputStream output) throws IOException { From a00a8763d6ebc240ab06380c04a762d10dfc7939 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 4 Jun 2020 18:33:42 +0200 Subject: [PATCH 137/450] Avoid additional copy on Java text parsing Directly pass the buffer range to the String constructor. --- .../java/com/genymobile/scrcpy/ControlMessageReader.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/ControlMessageReader.java b/server/src/main/java/com/genymobile/scrcpy/ControlMessageReader.java index f80ffebb..b8fba824 100644 --- a/server/src/main/java/com/genymobile/scrcpy/ControlMessageReader.java +++ b/server/src/main/java/com/genymobile/scrcpy/ControlMessageReader.java @@ -21,7 +21,6 @@ public class ControlMessageReader { private final byte[] rawBuffer = new byte[MESSAGE_MAX_SIZE]; private final ByteBuffer buffer = ByteBuffer.wrap(rawBuffer); - private final byte[] textBuffer = new byte[CLIPBOARD_TEXT_MAX_LENGTH]; public ControlMessageReader() { // invariant: the buffer is always in "get" mode @@ -111,8 +110,10 @@ public class ControlMessageReader { if (buffer.remaining() < len) { return null; } - buffer.get(textBuffer, 0, len); - return new String(textBuffer, 0, len, StandardCharsets.UTF_8); + int position = buffer.position(); + // Move the buffer position to consume the text + buffer.position(position + len); + return new String(rawBuffer, position, len, StandardCharsets.UTF_8); } private ControlMessage parseInjectText() { From 08c0c64af64cf124c46f0922447f81fc804a74b8 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 4 Jun 2020 21:43:07 +0200 Subject: [PATCH 138/450] Rename test names from "event" to "msg" The meson test names had not been changed when "event" had been renamed to "message". Ref: 28980bbc90ab93cf61bdc4b36d2448b8cebbb7df --- app/meson.build | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/meson.build b/app/meson.build index 5d2b4caa..505f0819 100644 --- a/app/meson.build +++ b/app/meson.build @@ -164,12 +164,12 @@ if get_option('buildtype') == 'debug' 'src/cli.c', 'src/util/str_util.c', ]], - ['test_control_event_serialize', [ + ['test_control_msg_serialize', [ 'tests/test_control_msg_serialize.c', 'src/control_msg.c', 'src/util/str_util.c', ]], - ['test_device_event_deserialize', [ + ['test_device_msg_deserialize', [ 'tests/test_device_msg_deserialize.c', 'src/device_msg.c', ]], From d202d7b2059eb440c044477993a6e0d6aa4d5086 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 4 Jun 2020 21:22:47 +0200 Subject: [PATCH 139/450] Add unit test for big clipboard device message Test clipboard synchronization from the device to the computer. --- app/tests/test_device_msg_deserialize.c | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/app/tests/test_device_msg_deserialize.c b/app/tests/test_device_msg_deserialize.c index e163ad72..2e29d087 100644 --- a/app/tests/test_device_msg_deserialize.c +++ b/app/tests/test_device_msg_deserialize.c @@ -4,6 +4,7 @@ #include "device_msg.h" #include + static void test_deserialize_clipboard(void) { const unsigned char input[] = { DEVICE_MSG_TYPE_CLIPBOARD, @@ -22,7 +23,28 @@ static void test_deserialize_clipboard(void) { device_msg_destroy(&msg); } +static void test_deserialize_clipboard_big(void) { + unsigned char input[DEVICE_MSG_SERIALIZED_MAX_SIZE]; + input[0] = DEVICE_MSG_TYPE_CLIPBOARD; + input[1] = DEVICE_MSG_TEXT_MAX_LENGTH >> 8; // MSB + input[2] = DEVICE_MSG_TEXT_MAX_LENGTH & 0xff; // LSB + + memset(input + 3, 'a', DEVICE_MSG_TEXT_MAX_LENGTH); + + struct device_msg msg; + ssize_t r = device_msg_deserialize(input, sizeof(input), &msg); + assert(r == DEVICE_MSG_SERIALIZED_MAX_SIZE); + + assert(msg.type == DEVICE_MSG_TYPE_CLIPBOARD); + assert(msg.clipboard.text); + assert(strlen(msg.clipboard.text) == DEVICE_MSG_TEXT_MAX_LENGTH); + assert(msg.clipboard.text[0] == 'a'); + + device_msg_destroy(&msg); +} + int main(void) { test_deserialize_clipboard(); + test_deserialize_clipboard_big(); return 0; } From d91c5dcfd54fa98a06be938da83fdad8e85e3724 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 4 Jun 2020 21:26:38 +0200 Subject: [PATCH 140/450] Rename MSG_SERIALIZED_MAX_SIZE to MSG_MAX_SIZE For simplicity and consistency with the server part. --- app/src/control_msg.h | 7 +++---- app/src/controller.c | 2 +- app/src/device_msg.h | 4 ++-- app/src/receiver.c | 6 +++--- app/tests/test_control_msg_serialize.c | 24 ++++++++++++------------ app/tests/test_device_msg_deserialize.c | 4 ++-- 6 files changed, 23 insertions(+), 24 deletions(-) diff --git a/app/src/control_msg.h b/app/src/control_msg.h index 1a5449a0..f5d633a8 100644 --- a/app/src/control_msg.h +++ b/app/src/control_msg.h @@ -10,12 +10,11 @@ #include "android/keycodes.h" #include "common.h" -#define CONTROL_MSG_SERIALIZED_MAX_SIZE 4096 +#define CONTROL_MSG_MAX_SIZE 4096 #define CONTROL_MSG_INJECT_TEXT_MAX_LENGTH 300 // type: 1 byte; paste flag: 1 byte; length: 2 bytes -#define CONTROL_MSG_CLIPBOARD_TEXT_MAX_LENGTH \ - (CONTROL_MSG_SERIALIZED_MAX_SIZE - 4) +#define CONTROL_MSG_CLIPBOARD_TEXT_MAX_LENGTH (CONTROL_MSG_MAX_SIZE - 4) #define POINTER_ID_MOUSE UINT64_C(-1); @@ -72,7 +71,7 @@ struct control_msg { }; }; -// buf size must be at least CONTROL_MSG_SERIALIZED_MAX_SIZE +// buf size must be at least CONTROL_MSG_MAX_SIZE // return the number of bytes written size_t control_msg_serialize(const struct control_msg *msg, unsigned char *buf); diff --git a/app/src/controller.c b/app/src/controller.c index d59a7411..b68b886b 100644 --- a/app/src/controller.c +++ b/app/src/controller.c @@ -60,7 +60,7 @@ controller_push_msg(struct controller *controller, static bool process_msg(struct controller *controller, const struct control_msg *msg) { - unsigned char serialized_msg[CONTROL_MSG_SERIALIZED_MAX_SIZE]; + unsigned char serialized_msg[CONTROL_MSG_MAX_SIZE]; int length = control_msg_serialize(msg, serialized_msg); if (!length) { return false; diff --git a/app/src/device_msg.h b/app/src/device_msg.h index f19630bf..60c35817 100644 --- a/app/src/device_msg.h +++ b/app/src/device_msg.h @@ -7,9 +7,9 @@ #include "config.h" -#define DEVICE_MSG_SERIALIZED_MAX_SIZE 4096 +#define DEVICE_MSG_MAX_SIZE 4096 // type: 1 byte; length: 2 bytes -#define DEVICE_MSG_TEXT_MAX_LENGTH (DEVICE_MSG_SERIALIZED_MAX_SIZE - 3) +#define DEVICE_MSG_TEXT_MAX_LENGTH (DEVICE_MSG_MAX_SIZE - 3) enum device_msg_type { DEVICE_MSG_TYPE_CLIPBOARD, diff --git a/app/src/receiver.c b/app/src/receiver.c index 0474ff55..8c8a7e03 100644 --- a/app/src/receiver.c +++ b/app/src/receiver.c @@ -60,13 +60,13 @@ static int run_receiver(void *data) { struct receiver *receiver = data; - unsigned char buf[DEVICE_MSG_SERIALIZED_MAX_SIZE]; + unsigned char buf[DEVICE_MSG_MAX_SIZE]; size_t head = 0; for (;;) { - assert(head < DEVICE_MSG_SERIALIZED_MAX_SIZE); + assert(head < DEVICE_MSG_MAX_SIZE); ssize_t r = net_recv(receiver->control_socket, buf, - DEVICE_MSG_SERIALIZED_MAX_SIZE - head); + DEVICE_MSG_MAX_SIZE - head); if (r <= 0) { LOGD("Receiver stopped"); break; diff --git a/app/tests/test_control_msg_serialize.c b/app/tests/test_control_msg_serialize.c index c6ff7b2e..a0328060 100644 --- a/app/tests/test_control_msg_serialize.c +++ b/app/tests/test_control_msg_serialize.c @@ -13,7 +13,7 @@ static void test_serialize_inject_keycode(void) { }, }; - unsigned char buf[CONTROL_MSG_SERIALIZED_MAX_SIZE]; + unsigned char buf[CONTROL_MSG_MAX_SIZE]; int size = control_msg_serialize(&msg, buf); assert(size == 10); @@ -34,7 +34,7 @@ static void test_serialize_inject_text(void) { }, }; - unsigned char buf[CONTROL_MSG_SERIALIZED_MAX_SIZE]; + unsigned char buf[CONTROL_MSG_MAX_SIZE]; int size = control_msg_serialize(&msg, buf); assert(size == 16); @@ -54,7 +54,7 @@ static void test_serialize_inject_text_long(void) { text[CONTROL_MSG_INJECT_TEXT_MAX_LENGTH] = '\0'; msg.inject_text.text = text; - unsigned char buf[CONTROL_MSG_SERIALIZED_MAX_SIZE]; + unsigned char buf[CONTROL_MSG_MAX_SIZE]; int size = control_msg_serialize(&msg, buf); assert(size == 3 + CONTROL_MSG_INJECT_TEXT_MAX_LENGTH); @@ -88,7 +88,7 @@ static void test_serialize_inject_touch_event(void) { }, }; - unsigned char buf[CONTROL_MSG_SERIALIZED_MAX_SIZE]; + unsigned char buf[CONTROL_MSG_MAX_SIZE]; int size = control_msg_serialize(&msg, buf); assert(size == 28); @@ -123,7 +123,7 @@ static void test_serialize_inject_scroll_event(void) { }, }; - unsigned char buf[CONTROL_MSG_SERIALIZED_MAX_SIZE]; + unsigned char buf[CONTROL_MSG_MAX_SIZE]; int size = control_msg_serialize(&msg, buf); assert(size == 21); @@ -142,7 +142,7 @@ static void test_serialize_back_or_screen_on(void) { .type = CONTROL_MSG_TYPE_BACK_OR_SCREEN_ON, }; - unsigned char buf[CONTROL_MSG_SERIALIZED_MAX_SIZE]; + unsigned char buf[CONTROL_MSG_MAX_SIZE]; int size = control_msg_serialize(&msg, buf); assert(size == 1); @@ -157,7 +157,7 @@ static void test_serialize_expand_notification_panel(void) { .type = CONTROL_MSG_TYPE_EXPAND_NOTIFICATION_PANEL, }; - unsigned char buf[CONTROL_MSG_SERIALIZED_MAX_SIZE]; + unsigned char buf[CONTROL_MSG_MAX_SIZE]; int size = control_msg_serialize(&msg, buf); assert(size == 1); @@ -172,7 +172,7 @@ static void test_serialize_collapse_notification_panel(void) { .type = CONTROL_MSG_TYPE_COLLAPSE_NOTIFICATION_PANEL, }; - unsigned char buf[CONTROL_MSG_SERIALIZED_MAX_SIZE]; + unsigned char buf[CONTROL_MSG_MAX_SIZE]; int size = control_msg_serialize(&msg, buf); assert(size == 1); @@ -187,7 +187,7 @@ static void test_serialize_get_clipboard(void) { .type = CONTROL_MSG_TYPE_GET_CLIPBOARD, }; - unsigned char buf[CONTROL_MSG_SERIALIZED_MAX_SIZE]; + unsigned char buf[CONTROL_MSG_MAX_SIZE]; int size = control_msg_serialize(&msg, buf); assert(size == 1); @@ -206,7 +206,7 @@ static void test_serialize_set_clipboard(void) { }, }; - unsigned char buf[CONTROL_MSG_SERIALIZED_MAX_SIZE]; + unsigned char buf[CONTROL_MSG_MAX_SIZE]; int size = control_msg_serialize(&msg, buf); assert(size == 17); @@ -227,7 +227,7 @@ static void test_serialize_set_screen_power_mode(void) { }, }; - unsigned char buf[CONTROL_MSG_SERIALIZED_MAX_SIZE]; + unsigned char buf[CONTROL_MSG_MAX_SIZE]; int size = control_msg_serialize(&msg, buf); assert(size == 2); @@ -243,7 +243,7 @@ static void test_serialize_rotate_device(void) { .type = CONTROL_MSG_TYPE_ROTATE_DEVICE, }; - unsigned char buf[CONTROL_MSG_SERIALIZED_MAX_SIZE]; + unsigned char buf[CONTROL_MSG_MAX_SIZE]; int size = control_msg_serialize(&msg, buf); assert(size == 1); diff --git a/app/tests/test_device_msg_deserialize.c b/app/tests/test_device_msg_deserialize.c index 2e29d087..6a38d5e5 100644 --- a/app/tests/test_device_msg_deserialize.c +++ b/app/tests/test_device_msg_deserialize.c @@ -24,7 +24,7 @@ static void test_deserialize_clipboard(void) { } static void test_deserialize_clipboard_big(void) { - unsigned char input[DEVICE_MSG_SERIALIZED_MAX_SIZE]; + unsigned char input[DEVICE_MSG_MAX_SIZE]; input[0] = DEVICE_MSG_TYPE_CLIPBOARD; input[1] = DEVICE_MSG_TEXT_MAX_LENGTH >> 8; // MSB input[2] = DEVICE_MSG_TEXT_MAX_LENGTH & 0xff; // LSB @@ -33,7 +33,7 @@ static void test_deserialize_clipboard_big(void) { struct device_msg msg; ssize_t r = device_msg_deserialize(input, sizeof(input), &msg); - assert(r == DEVICE_MSG_SERIALIZED_MAX_SIZE); + assert(r == DEVICE_MSG_MAX_SIZE); assert(msg.type == DEVICE_MSG_TYPE_CLIPBOARD); assert(msg.clipboard.text); From 245999aec4a4a1454212b38a988aa96fa701bb04 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 4 Jun 2020 21:42:09 +0200 Subject: [PATCH 141/450] Serialize text size on 4 bytes This will allow to send text having a size greater than 65535 bytes. --- app/src/control_msg.c | 6 +++--- app/src/control_msg.h | 4 ++-- app/src/device_msg.c | 10 +++++----- app/src/device_msg.h | 4 ++-- app/tests/test_control_msg_serialize.c | 20 ++++++++++--------- app/tests/test_device_msg_deserialize.c | 12 ++++++----- .../scrcpy/ControlMessageReader.java | 6 +++--- .../scrcpy/DeviceMessageWriter.java | 4 ++-- .../scrcpy/ControlMessageReaderTest.java | 8 ++++---- .../scrcpy/DeviceMessageWriterTest.java | 2 +- 10 files changed, 40 insertions(+), 36 deletions(-) diff --git a/app/src/control_msg.c b/app/src/control_msg.c index c5778c02..2a7f2f34 100644 --- a/app/src/control_msg.c +++ b/app/src/control_msg.c @@ -20,9 +20,9 @@ write_position(uint8_t *buf, const struct position *position) { static size_t write_string(const char *utf8, size_t max_len, unsigned char *buf) { size_t len = utf8_truncation_index(utf8, max_len); - buffer_write16be(buf, (uint16_t) len); - memcpy(&buf[2], utf8, len); - return 2 + len; + buffer_write32be(buf, len); + memcpy(&buf[4], utf8, len); + return 4 + len; } static uint16_t diff --git a/app/src/control_msg.h b/app/src/control_msg.h index f5d633a8..d8898c9c 100644 --- a/app/src/control_msg.h +++ b/app/src/control_msg.h @@ -13,8 +13,8 @@ #define CONTROL_MSG_MAX_SIZE 4096 #define CONTROL_MSG_INJECT_TEXT_MAX_LENGTH 300 -// type: 1 byte; paste flag: 1 byte; length: 2 bytes -#define CONTROL_MSG_CLIPBOARD_TEXT_MAX_LENGTH (CONTROL_MSG_MAX_SIZE - 4) +// type: 1 byte; paste flag: 1 byte; length: 4 bytes +#define CONTROL_MSG_CLIPBOARD_TEXT_MAX_LENGTH (CONTROL_MSG_MAX_SIZE - 6) #define POINTER_ID_MOUSE UINT64_C(-1); diff --git a/app/src/device_msg.c b/app/src/device_msg.c index db176129..09e68936 100644 --- a/app/src/device_msg.c +++ b/app/src/device_msg.c @@ -9,7 +9,7 @@ ssize_t device_msg_deserialize(const unsigned char *buf, size_t len, struct device_msg *msg) { - if (len < 3) { + if (len < 5) { // at least type + empty string length return 0; // not available } @@ -17,8 +17,8 @@ device_msg_deserialize(const unsigned char *buf, size_t len, msg->type = buf[0]; switch (msg->type) { case DEVICE_MSG_TYPE_CLIPBOARD: { - uint16_t clipboard_len = buffer_read16be(&buf[1]); - if (clipboard_len > len - 3) { + size_t clipboard_len = buffer_read32be(&buf[1]); + if (clipboard_len > len - 5) { return 0; // not available } char *text = SDL_malloc(clipboard_len + 1); @@ -27,12 +27,12 @@ device_msg_deserialize(const unsigned char *buf, size_t len, return -1; } if (clipboard_len) { - memcpy(text, &buf[3], clipboard_len); + memcpy(text, &buf[5], clipboard_len); } text[clipboard_len] = '\0'; msg->clipboard.text = text; - return 3 + clipboard_len; + return 5 + clipboard_len; } default: LOGW("Unknown device message type: %d", (int) msg->type); diff --git a/app/src/device_msg.h b/app/src/device_msg.h index 60c35817..0fc7d87c 100644 --- a/app/src/device_msg.h +++ b/app/src/device_msg.h @@ -8,8 +8,8 @@ #include "config.h" #define DEVICE_MSG_MAX_SIZE 4096 -// type: 1 byte; length: 2 bytes -#define DEVICE_MSG_TEXT_MAX_LENGTH (DEVICE_MSG_MAX_SIZE - 3) +// type: 1 byte; length: 4 bytes +#define DEVICE_MSG_TEXT_MAX_LENGTH (DEVICE_MSG_MAX_SIZE - 5) enum device_msg_type { DEVICE_MSG_TYPE_CLIPBOARD, diff --git a/app/tests/test_control_msg_serialize.c b/app/tests/test_control_msg_serialize.c index a0328060..9ba90ae8 100644 --- a/app/tests/test_control_msg_serialize.c +++ b/app/tests/test_control_msg_serialize.c @@ -36,11 +36,11 @@ static void test_serialize_inject_text(void) { unsigned char buf[CONTROL_MSG_MAX_SIZE]; int size = control_msg_serialize(&msg, buf); - assert(size == 16); + assert(size == 18); const unsigned char expected[] = { CONTROL_MSG_TYPE_INJECT_TEXT, - 0x00, 0x0d, // text length + 0x00, 0x00, 0x00, 0x0d, // text length 'h', 'e', 'l', 'l', 'o', ',', ' ', 'w', 'o', 'r', 'l', 'd', '!', // text }; assert(!memcmp(buf, expected, sizeof(expected))); @@ -56,13 +56,15 @@ static void test_serialize_inject_text_long(void) { unsigned char buf[CONTROL_MSG_MAX_SIZE]; int size = control_msg_serialize(&msg, buf); - assert(size == 3 + CONTROL_MSG_INJECT_TEXT_MAX_LENGTH); + assert(size == 5 + CONTROL_MSG_INJECT_TEXT_MAX_LENGTH); - unsigned char expected[3 + CONTROL_MSG_INJECT_TEXT_MAX_LENGTH]; + unsigned char expected[5 + CONTROL_MSG_INJECT_TEXT_MAX_LENGTH]; expected[0] = CONTROL_MSG_TYPE_INJECT_TEXT; - expected[1] = 0x01; - expected[2] = 0x2c; // text length (16 bits) - memset(&expected[3], 'a', CONTROL_MSG_INJECT_TEXT_MAX_LENGTH); + expected[1] = 0x00; + expected[2] = 0x00; + expected[3] = 0x01; + expected[4] = 0x2c; // text length (32 bits) + memset(&expected[5], 'a', CONTROL_MSG_INJECT_TEXT_MAX_LENGTH); assert(!memcmp(buf, expected, sizeof(expected))); } @@ -208,12 +210,12 @@ static void test_serialize_set_clipboard(void) { unsigned char buf[CONTROL_MSG_MAX_SIZE]; int size = control_msg_serialize(&msg, buf); - assert(size == 17); + assert(size == 19); const unsigned char expected[] = { CONTROL_MSG_TYPE_SET_CLIPBOARD, 1, // paste - 0x00, 0x0d, // text length + 0x00, 0x00, 0x00, 0x0d, // text length 'h', 'e', 'l', 'l', 'o', ',', ' ', 'w', 'o', 'r', 'l', 'd', '!', // text }; assert(!memcmp(buf, expected, sizeof(expected))); diff --git a/app/tests/test_device_msg_deserialize.c b/app/tests/test_device_msg_deserialize.c index 6a38d5e5..8fcfc93d 100644 --- a/app/tests/test_device_msg_deserialize.c +++ b/app/tests/test_device_msg_deserialize.c @@ -8,13 +8,13 @@ static void test_deserialize_clipboard(void) { const unsigned char input[] = { DEVICE_MSG_TYPE_CLIPBOARD, - 0x00, 0x03, // text length + 0x00, 0x00, 0x00, 0x03, // text length 0x41, 0x42, 0x43, // "ABC" }; struct device_msg msg; ssize_t r = device_msg_deserialize(input, sizeof(input), &msg); - assert(r == 6); + assert(r == 8); assert(msg.type == DEVICE_MSG_TYPE_CLIPBOARD); assert(msg.clipboard.text); @@ -26,10 +26,12 @@ static void test_deserialize_clipboard(void) { static void test_deserialize_clipboard_big(void) { unsigned char input[DEVICE_MSG_MAX_SIZE]; input[0] = DEVICE_MSG_TYPE_CLIPBOARD; - input[1] = DEVICE_MSG_TEXT_MAX_LENGTH >> 8; // MSB - input[2] = DEVICE_MSG_TEXT_MAX_LENGTH & 0xff; // LSB + input[1] = (DEVICE_MSG_TEXT_MAX_LENGTH & 0xff000000u) >> 24; + input[2] = (DEVICE_MSG_TEXT_MAX_LENGTH & 0x00ff0000u) >> 16; + input[3] = (DEVICE_MSG_TEXT_MAX_LENGTH & 0x0000ff00u) >> 8; + input[4] = DEVICE_MSG_TEXT_MAX_LENGTH & 0x000000ffu; - memset(input + 3, 'a', DEVICE_MSG_TEXT_MAX_LENGTH); + memset(input + 5, 'a', DEVICE_MSG_TEXT_MAX_LENGTH); struct device_msg msg; ssize_t r = device_msg_deserialize(input, sizeof(input), &msg); diff --git a/server/src/main/java/com/genymobile/scrcpy/ControlMessageReader.java b/server/src/main/java/com/genymobile/scrcpy/ControlMessageReader.java index b8fba824..a9905919 100644 --- a/server/src/main/java/com/genymobile/scrcpy/ControlMessageReader.java +++ b/server/src/main/java/com/genymobile/scrcpy/ControlMessageReader.java @@ -16,7 +16,7 @@ public class ControlMessageReader { private static final int MESSAGE_MAX_SIZE = 4096; - public static final int CLIPBOARD_TEXT_MAX_LENGTH = MESSAGE_MAX_SIZE - 4; // type: 1 byte; paste flag: 1 byte; length: 2 bytes + public static final int CLIPBOARD_TEXT_MAX_LENGTH = MESSAGE_MAX_SIZE - 6; // type: 1 byte; paste flag: 1 byte; length: 4 bytes public static final int INJECT_TEXT_MAX_LENGTH = 300; private final byte[] rawBuffer = new byte[MESSAGE_MAX_SIZE]; @@ -103,10 +103,10 @@ public class ControlMessageReader { } private String parseString() { - if (buffer.remaining() < 2) { + if (buffer.remaining() < 4) { return null; } - int len = toUnsigned(buffer.getShort()); + int len = buffer.getInt(); if (buffer.remaining() < len) { return null; } diff --git a/server/src/main/java/com/genymobile/scrcpy/DeviceMessageWriter.java b/server/src/main/java/com/genymobile/scrcpy/DeviceMessageWriter.java index ea54d533..2e12698f 100644 --- a/server/src/main/java/com/genymobile/scrcpy/DeviceMessageWriter.java +++ b/server/src/main/java/com/genymobile/scrcpy/DeviceMessageWriter.java @@ -8,7 +8,7 @@ import java.nio.charset.StandardCharsets; public class DeviceMessageWriter { private static final int MESSAGE_MAX_SIZE = 4096; - public static final int CLIPBOARD_TEXT_MAX_LENGTH = MESSAGE_MAX_SIZE - 3; // type: 1 byte; length: 2 bytes + public static final int CLIPBOARD_TEXT_MAX_LENGTH = MESSAGE_MAX_SIZE - 5; // type: 1 byte; length: 4 bytes private final byte[] rawBuffer = new byte[MESSAGE_MAX_SIZE]; private final ByteBuffer buffer = ByteBuffer.wrap(rawBuffer); @@ -21,7 +21,7 @@ public class DeviceMessageWriter { String text = msg.getText(); byte[] raw = text.getBytes(StandardCharsets.UTF_8); int len = StringUtils.getUtf8TruncationIndex(raw, CLIPBOARD_TEXT_MAX_LENGTH); - buffer.putShort((short) len); + buffer.putInt(len); buffer.put(raw, 0, len); output.write(rawBuffer, 0, buffer.position()); break; diff --git a/server/src/test/java/com/genymobile/scrcpy/ControlMessageReaderTest.java b/server/src/test/java/com/genymobile/scrcpy/ControlMessageReaderTest.java index f5fa4d09..c56bd17e 100644 --- a/server/src/test/java/com/genymobile/scrcpy/ControlMessageReaderTest.java +++ b/server/src/test/java/com/genymobile/scrcpy/ControlMessageReaderTest.java @@ -48,7 +48,7 @@ public class ControlMessageReaderTest { DataOutputStream dos = new DataOutputStream(bos); dos.writeByte(ControlMessage.TYPE_INJECT_TEXT); byte[] text = "testé".getBytes(StandardCharsets.UTF_8); - dos.writeShort(text.length); + dos.writeInt(text.length); dos.write(text); byte[] packet = bos.toByteArray(); @@ -68,7 +68,7 @@ public class ControlMessageReaderTest { dos.writeByte(ControlMessage.TYPE_INJECT_TEXT); byte[] text = new byte[ControlMessageReader.INJECT_TEXT_MAX_LENGTH]; Arrays.fill(text, (byte) 'a'); - dos.writeShort(text.length); + dos.writeInt(text.length); dos.write(text); byte[] packet = bos.toByteArray(); @@ -218,7 +218,7 @@ public class ControlMessageReaderTest { dos.writeByte(ControlMessage.TYPE_SET_CLIPBOARD); dos.writeByte(1); // paste byte[] text = "testé".getBytes(StandardCharsets.UTF_8); - dos.writeShort(text.length); + dos.writeInt(text.length); dos.write(text); byte[] packet = bos.toByteArray(); @@ -246,7 +246,7 @@ public class ControlMessageReaderTest { Arrays.fill(rawText, (byte) 'a'); String text = new String(rawText, 0, rawText.length); - dos.writeShort(rawText.length); + dos.writeInt(rawText.length); dos.write(rawText); byte[] packet = bos.toByteArray(); diff --git a/server/src/test/java/com/genymobile/scrcpy/DeviceMessageWriterTest.java b/server/src/test/java/com/genymobile/scrcpy/DeviceMessageWriterTest.java index df12f647..88bf2af9 100644 --- a/server/src/test/java/com/genymobile/scrcpy/DeviceMessageWriterTest.java +++ b/server/src/test/java/com/genymobile/scrcpy/DeviceMessageWriterTest.java @@ -19,7 +19,7 @@ public class DeviceMessageWriterTest { ByteArrayOutputStream bos = new ByteArrayOutputStream(); DataOutputStream dos = new DataOutputStream(bos); dos.writeByte(DeviceMessage.TYPE_CLIPBOARD); - dos.writeShort(data.length); + dos.writeInt(data.length); dos.write(data); byte[] expected = bos.toByteArray(); From 00d292b2f5833740517cec829cc08531f85155e6 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 4 Jun 2020 22:44:43 +0200 Subject: [PATCH 142/450] Fix receiver on partial device messages If a single device message was read in several chunks from the control socket, the communication was broken. --- app/src/receiver.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/app/src/receiver.c b/app/src/receiver.c index 8c8a7e03..bfe9d6e9 100644 --- a/app/src/receiver.c +++ b/app/src/receiver.c @@ -65,23 +65,24 @@ run_receiver(void *data) { for (;;) { assert(head < DEVICE_MSG_MAX_SIZE); - ssize_t r = net_recv(receiver->control_socket, buf, + ssize_t r = net_recv(receiver->control_socket, buf + head, DEVICE_MSG_MAX_SIZE - head); if (r <= 0) { LOGD("Receiver stopped"); break; } - ssize_t consumed = process_msgs(buf, r); + head += r; + ssize_t consumed = process_msgs(buf, head); if (consumed == -1) { // an error occurred break; } if (consumed) { + head -= consumed; // shift the remaining data in the buffer - memmove(buf, &buf[consumed], r - consumed); - head = r - consumed; + memmove(buf, &buf[consumed], head); } } From 488d22d4e23afe902bbcef0e3c535591f66b6f2c Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 4 Jun 2020 23:23:09 +0200 Subject: [PATCH 143/450] Increase clipboard size from 4k to 256k Beyond 256k, SDL_GetClipboardText() returns an empty string on my computer. Fixes #1117 --- app/src/control_msg.h | 2 +- app/src/device_msg.h | 2 +- .../main/java/com/genymobile/scrcpy/ControlMessageReader.java | 2 +- .../main/java/com/genymobile/scrcpy/DeviceMessageWriter.java | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/src/control_msg.h b/app/src/control_msg.h index d8898c9c..bc4ff9ef 100644 --- a/app/src/control_msg.h +++ b/app/src/control_msg.h @@ -10,7 +10,7 @@ #include "android/keycodes.h" #include "common.h" -#define CONTROL_MSG_MAX_SIZE 4096 +#define CONTROL_MSG_MAX_SIZE (1 << 18) // 256k #define CONTROL_MSG_INJECT_TEXT_MAX_LENGTH 300 // type: 1 byte; paste flag: 1 byte; length: 4 bytes diff --git a/app/src/device_msg.h b/app/src/device_msg.h index 0fc7d87c..4b681e2c 100644 --- a/app/src/device_msg.h +++ b/app/src/device_msg.h @@ -7,7 +7,7 @@ #include "config.h" -#define DEVICE_MSG_MAX_SIZE 4096 +#define DEVICE_MSG_MAX_SIZE (1 << 18) // 256k // type: 1 byte; length: 4 bytes #define DEVICE_MSG_TEXT_MAX_LENGTH (DEVICE_MSG_MAX_SIZE - 5) diff --git a/server/src/main/java/com/genymobile/scrcpy/ControlMessageReader.java b/server/src/main/java/com/genymobile/scrcpy/ControlMessageReader.java index a9905919..3b3e9559 100644 --- a/server/src/main/java/com/genymobile/scrcpy/ControlMessageReader.java +++ b/server/src/main/java/com/genymobile/scrcpy/ControlMessageReader.java @@ -14,7 +14,7 @@ public class ControlMessageReader { static final int SET_SCREEN_POWER_MODE_PAYLOAD_LENGTH = 1; static final int SET_CLIPBOARD_FIXED_PAYLOAD_LENGTH = 1; - private static final int MESSAGE_MAX_SIZE = 4096; + private static final int MESSAGE_MAX_SIZE = 1 << 18; // 256k public static final int CLIPBOARD_TEXT_MAX_LENGTH = MESSAGE_MAX_SIZE - 6; // type: 1 byte; paste flag: 1 byte; length: 4 bytes public static final int INJECT_TEXT_MAX_LENGTH = 300; diff --git a/server/src/main/java/com/genymobile/scrcpy/DeviceMessageWriter.java b/server/src/main/java/com/genymobile/scrcpy/DeviceMessageWriter.java index 2e12698f..15d91a35 100644 --- a/server/src/main/java/com/genymobile/scrcpy/DeviceMessageWriter.java +++ b/server/src/main/java/com/genymobile/scrcpy/DeviceMessageWriter.java @@ -7,7 +7,7 @@ import java.nio.charset.StandardCharsets; public class DeviceMessageWriter { - private static final int MESSAGE_MAX_SIZE = 4096; + private static final int MESSAGE_MAX_SIZE = 1 << 18; // 256k public static final int CLIPBOARD_TEXT_MAX_LENGTH = MESSAGE_MAX_SIZE - 5; // type: 1 byte; length: 4 bytes private final byte[] rawBuffer = new byte[MESSAGE_MAX_SIZE]; From 1e4ee547b560ad3c52ce33fd32cf1b10b3d24d1a Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 4 Jun 2020 23:50:08 +0200 Subject: [PATCH 144/450] Make message buffer static Now that the message max size is 256k, do not put the buffer on the stack. --- app/src/controller.c | 2 +- app/src/receiver.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/controller.c b/app/src/controller.c index b68b886b..c5897e5d 100644 --- a/app/src/controller.c +++ b/app/src/controller.c @@ -60,7 +60,7 @@ controller_push_msg(struct controller *controller, static bool process_msg(struct controller *controller, const struct control_msg *msg) { - unsigned char serialized_msg[CONTROL_MSG_MAX_SIZE]; + static unsigned char serialized_msg[CONTROL_MSG_MAX_SIZE]; int length = control_msg_serialize(msg, serialized_msg); if (!length) { return false; diff --git a/app/src/receiver.c b/app/src/receiver.c index bfe9d6e9..5ecff3b7 100644 --- a/app/src/receiver.c +++ b/app/src/receiver.c @@ -60,7 +60,7 @@ static int run_receiver(void *data) { struct receiver *receiver = data; - unsigned char buf[DEVICE_MSG_MAX_SIZE]; + static unsigned char buf[DEVICE_MSG_MAX_SIZE]; size_t head = 0; for (;;) { From 42641d273772b2dcc55a34f8ae7cd5f6bdea51d5 Mon Sep 17 00:00:00 2001 From: AreYouLoco? Date: Thu, 18 Jun 2020 20:05:42 +0200 Subject: [PATCH 145/450] Fix typo in README.md PR #1523 Signed-off-by: Romain Vimont --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index df3e1172..a894313b 100644 --- a/README.md +++ b/README.md @@ -301,7 +301,7 @@ ssh -CN -L5037:localhost:5037 -L27183:localhost:27183 your_remote_computer From another terminal: ```bash -scrcpy --force-adb-forwrad +scrcpy --force-adb-forward ``` From dc7b60e6199b90a45ea26751988f6f30f8b2efdf Mon Sep 17 00:00:00 2001 From: Ivan Keliukh Date: Sat, 13 Jun 2020 15:10:41 +0300 Subject: [PATCH 146/450] Add option for disabling screensaver PR #1502 Fixes #1370 Signed-off-by: Romain Vimont --- README.md | 11 +++++++++++ app/scrcpy.1 | 4 ++++ app/src/cli.c | 9 +++++++++ app/src/scrcpy.c | 15 +++++++++++---- app/src/scrcpy.h | 2 ++ 5 files changed, 37 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index fe7f0fac..2c01a01e 100644 --- a/README.md +++ b/README.md @@ -479,6 +479,17 @@ scrcpy -t Note that it only shows _physical_ touches (with the finger on the device). +#### Disable screensaver + +By default, scrcpy does not prevent the screensaver to run on the computer. + +To disable it: + +```bash +scrcpy --disable-screensaver +``` + + ### Input control #### Rotate device screen diff --git a/app/scrcpy.1 b/app/scrcpy.1 index 776d78ae..4e3c43f2 100644 --- a/app/scrcpy.1 +++ b/app/scrcpy.1 @@ -43,6 +43,10 @@ The values are expressed in the device natural orientation (typically, portrait .B \-\-max\-size value is computed on the cropped size. +.TP +.BI "\-\-disable-screensaver" +Disable screensaver while scrcpy is running. + .TP .BI "\-\-display " id Specify the display id to mirror. diff --git a/app/src/cli.c b/app/src/cli.c index be0b7c23..6a45d430 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -45,6 +45,9 @@ scrcpy_print_usage(const char *arg0) { " (typically, portrait for a phone, landscape for a tablet).\n" " Any --max-size value is computed on the cropped size.\n" "\n" + " --disable-screensaver\n" + " Disable screensaver while scrcpy is running.\n" + "\n" " --display id\n" " Specify the display id to mirror.\n" "\n" @@ -526,6 +529,7 @@ guess_record_format(const char *filename) { #define OPT_NO_MIPMAPS 1017 #define OPT_CODEC_OPTIONS 1018 #define OPT_FORCE_ADB_FORWARD 1019 +#define OPT_DISABLE_SCREENSAVER 1020 bool scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) { @@ -534,6 +538,8 @@ scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) { {"bit-rate", required_argument, NULL, 'b'}, {"codec-options", required_argument, NULL, OPT_CODEC_OPTIONS}, {"crop", required_argument, NULL, OPT_CROP}, + {"disable-screensaver", no_argument, NULL, + OPT_DISABLE_SCREENSAVER}, {"display", required_argument, NULL, OPT_DISPLAY_ID}, {"force-adb-forward", no_argument, NULL, OPT_FORCE_ADB_FORWARD}, @@ -716,6 +722,9 @@ scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) { case OPT_FORCE_ADB_FORWARD: opts->force_adb_forward = true; break; + case OPT_DISABLE_SCREENSAVER: + opts->disable_screensaver = true; + break; default: // getopt prints the error message on stderr return false; diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index 67ebf8c0..cde6c27f 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -63,7 +63,8 @@ BOOL WINAPI windows_ctrl_handler(DWORD ctrl_type) { // init SDL and set appropriate hints static bool -sdl_init_and_configure(bool display, const char *render_driver) { +sdl_init_and_configure(bool display, const char *render_driver, + bool disable_screensaver) { uint32_t flags = display ? SDL_INIT_VIDEO : SDL_INIT_EVENTS; if (SDL_Init(flags)) { LOGC("Could not initialize SDL: %s", SDL_GetError()); @@ -112,8 +113,13 @@ sdl_init_and_configure(bool display, const char *render_driver) { LOGW("Could not disable minimize on focus loss"); } - // Do not disable the screensaver when scrcpy is running - SDL_EnableScreenSaver(); + if (disable_screensaver) { + LOGD("Screensaver disabled"); + SDL_DisableScreenSaver(); + } else { + LOGD("Screensaver enabled"); + SDL_EnableScreenSaver(); + } return true; } @@ -321,7 +327,8 @@ scrcpy(const struct scrcpy_options *options) { bool controller_initialized = false; bool controller_started = false; - if (!sdl_init_and_configure(options->display, options->render_driver)) { + if (!sdl_init_and_configure(options->display, options->render_driver, + options->disable_screensaver)) { goto end; } diff --git a/app/src/scrcpy.h b/app/src/scrcpy.h index 70d99433..f2760152 100644 --- a/app/src/scrcpy.h +++ b/app/src/scrcpy.h @@ -43,6 +43,7 @@ struct scrcpy_options { bool mipmaps; bool stay_awake; bool force_adb_forward; + bool disable_screensaver; }; #define SCRCPY_OPTIONS_DEFAULT { \ @@ -81,6 +82,7 @@ struct scrcpy_options { .mipmaps = true, \ .stay_awake = false, \ .force_adb_forward = false, \ + .disable_screensaver = false, \ } bool From 29e5af76d4f0a7d5b079c760cc48aa8b26c554e8 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 19 Jun 2020 21:54:24 +0200 Subject: [PATCH 147/450] Remove fprintf() call in tests It should never have been committed. --- app/tests/test_cli.c | 1 - 1 file changed, 1 deletion(-) diff --git a/app/tests/test_cli.c b/app/tests/test_cli.c index c5d95633..2a1e2607 100644 --- a/app/tests/test_cli.c +++ b/app/tests/test_cli.c @@ -73,7 +73,6 @@ static void test_options(void) { const struct scrcpy_options *opts = &args.opts; assert(opts->always_on_top); - fprintf(stderr, "%d\n", (int) opts->bit_rate); assert(opts->bit_rate == 5000000); assert(!strcmp(opts->crop, "100:200:300:400")); assert(opts->fullscreen); From 0ba74fbd9a1c599b7a93d44631e6973eb4940049 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 19 Jun 2020 22:04:06 +0200 Subject: [PATCH 148/450] Make scrcpy.h independant of other headers The header scrcpy.h is intended to be the "public" API. It should not depend on other internal headers. Therefore, declare all required structs in this header and adapt internal code. --- app/src/cli.c | 21 +++++++++++---------- app/src/main.c | 1 + app/src/recorder.c | 8 ++++---- app/src/recorder.h | 11 +++-------- app/src/scrcpy.c | 2 ++ app/src/scrcpy.h | 39 ++++++++++++++++++++++++++++----------- app/src/screen.c | 5 +++-- app/src/screen.h | 4 +--- app/src/server.c | 6 +++--- app/src/server.h | 5 +++-- app/src/util/log.h | 7 ------- app/tests/test_cli.c | 5 +++-- 12 files changed, 62 insertions(+), 52 deletions(-) diff --git a/app/src/cli.c b/app/src/cli.c index 6a45d430..9e07e912 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -3,10 +3,11 @@ #include #include #include +#include #include #include "config.h" -#include "recorder.h" +#include "scrcpy.h" #include "util/log.h" #include "util/str_util.h" @@ -382,10 +383,10 @@ parse_rotation(const char *s, uint8_t *rotation) { static bool parse_window_position(const char *s, int16_t *position) { // special value for "auto" - static_assert(WINDOW_POSITION_UNDEFINED == -0x8000, "unexpected value"); + static_assert(SC_WINDOW_POSITION_UNDEFINED == -0x8000, "unexpected value"); if (!strcmp(s, "auto")) { - *position = WINDOW_POSITION_UNDEFINED; + *position = SC_WINDOW_POSITION_UNDEFINED; return true; } @@ -414,7 +415,7 @@ parse_window_dimension(const char *s, uint16_t *dimension) { } static bool -parse_port_range(const char *s, struct port_range *port_range) { +parse_port_range(const char *s, struct sc_port_range *port_range) { long values[2]; size_t count = parse_integers_arg(s, 2, values, 0, 0xFFFF, "port"); if (!count) { @@ -480,20 +481,20 @@ parse_log_level(const char *s, enum sc_log_level *log_level) { } static bool -parse_record_format(const char *optarg, enum recorder_format *format) { +parse_record_format(const char *optarg, enum sc_record_format *format) { if (!strcmp(optarg, "mp4")) { - *format = RECORDER_FORMAT_MP4; + *format = SC_RECORD_FORMAT_MP4; return true; } if (!strcmp(optarg, "mkv")) { - *format = RECORDER_FORMAT_MKV; + *format = SC_RECORD_FORMAT_MKV; return true; } LOGE("Unsupported format: %s (expected mp4 or mkv)", optarg); return false; } -static enum recorder_format +static enum sc_record_format guess_record_format(const char *filename) { size_t len = strlen(filename); if (len < 4) { @@ -501,10 +502,10 @@ guess_record_format(const char *filename) { } const char *ext = &filename[len - 4]; if (!strcmp(ext, ".mp4")) { - return RECORDER_FORMAT_MP4; + return SC_RECORD_FORMAT_MP4; } if (!strcmp(ext, ".mkv")) { - return RECORDER_FORMAT_MKV; + return SC_RECORD_FORMAT_MKV; } return 0; } diff --git a/app/src/main.c b/app/src/main.c index 7d7ddb82..202c217c 100644 --- a/app/src/main.c +++ b/app/src/main.c @@ -1,5 +1,6 @@ #include "scrcpy.h" +#include #include #include #include diff --git a/app/src/recorder.c b/app/src/recorder.c index 465b24e8..76edbd03 100644 --- a/app/src/recorder.c +++ b/app/src/recorder.c @@ -63,7 +63,7 @@ recorder_queue_clear(struct recorder_queue *queue) { bool recorder_init(struct recorder *recorder, const char *filename, - enum recorder_format format, + enum sc_record_format format, struct size declared_frame_size) { recorder->filename = SDL_strdup(filename); if (!recorder->filename) { @@ -105,10 +105,10 @@ recorder_destroy(struct recorder *recorder) { } static const char * -recorder_get_format_name(enum recorder_format format) { +recorder_get_format_name(enum sc_record_format format) { switch (format) { - case RECORDER_FORMAT_MP4: return "mp4"; - case RECORDER_FORMAT_MKV: return "matroska"; + case SC_RECORD_FORMAT_MP4: return "mp4"; + case SC_RECORD_FORMAT_MKV: return "matroska"; default: return NULL; } } diff --git a/app/src/recorder.h b/app/src/recorder.h index 4f5d526c..bc87a23b 100644 --- a/app/src/recorder.h +++ b/app/src/recorder.h @@ -8,14 +8,9 @@ #include "config.h" #include "common.h" +#include "scrcpy.h" #include "util/queue.h" -enum recorder_format { - RECORDER_FORMAT_AUTO, - RECORDER_FORMAT_MP4, - RECORDER_FORMAT_MKV, -}; - struct record_packet { AVPacket packet; struct record_packet *next; @@ -25,7 +20,7 @@ struct recorder_queue QUEUE(struct record_packet); struct recorder { char *filename; - enum recorder_format format; + enum sc_record_format format; AVFormatContext *ctx; struct size declared_frame_size; bool header_written; @@ -46,7 +41,7 @@ struct recorder { bool recorder_init(struct recorder *recorder, const char *filename, - enum recorder_format format, struct size declared_frame_size); + enum sc_record_format format, struct size declared_frame_size); void recorder_destroy(struct recorder *recorder); diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index cde6c27f..5a88122d 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -8,6 +8,8 @@ #include #ifdef _WIN32 +// not needed here, but winsock2.h must never be included AFTER windows.h +# include # include #endif diff --git a/app/src/scrcpy.h b/app/src/scrcpy.h index f2760152..d7eb2f58 100644 --- a/app/src/scrcpy.h +++ b/app/src/scrcpy.h @@ -2,13 +2,30 @@ #define SCRCPY_H #include +#include #include #include "config.h" -#include "common.h" -#include "input_manager.h" -#include "recorder.h" -#include "util/log.h" + +enum sc_log_level { + SC_LOG_LEVEL_DEBUG, + SC_LOG_LEVEL_INFO, + SC_LOG_LEVEL_WARN, + SC_LOG_LEVEL_ERROR, +}; + +enum sc_record_format { + SC_RECORD_FORMAT_AUTO, + SC_RECORD_FORMAT_MP4, + SC_RECORD_FORMAT_MKV, +}; + +struct sc_port_range { + uint16_t first; + uint16_t last; +}; + +#define SC_WINDOW_POSITION_UNDEFINED (-0x8000) struct scrcpy_options { const char *serial; @@ -19,15 +36,15 @@ struct scrcpy_options { const char *render_driver; const char *codec_options; enum sc_log_level log_level; - enum recorder_format record_format; - struct port_range port_range; + enum sc_record_format record_format; + struct sc_port_range port_range; uint16_t max_size; uint32_t bit_rate; uint16_t max_fps; int8_t lock_video_orientation; uint8_t rotation; - int16_t window_x; // WINDOW_POSITION_UNDEFINED for "auto" - int16_t window_y; // WINDOW_POSITION_UNDEFINED for "auto" + int16_t window_x; // SC_WINDOW_POSITION_UNDEFINED for "auto" + int16_t window_y; // SC_WINDOW_POSITION_UNDEFINED for "auto" uint16_t window_width; uint16_t window_height; uint16_t display_id; @@ -55,7 +72,7 @@ struct scrcpy_options { .render_driver = NULL, \ .codec_options = NULL, \ .log_level = SC_LOG_LEVEL_INFO, \ - .record_format = RECORDER_FORMAT_AUTO, \ + .record_format = SC_RECORD_FORMAT_AUTO, \ .port_range = { \ .first = DEFAULT_LOCAL_PORT_RANGE_FIRST, \ .last = DEFAULT_LOCAL_PORT_RANGE_LAST, \ @@ -65,8 +82,8 @@ struct scrcpy_options { .max_fps = 0, \ .lock_video_orientation = DEFAULT_LOCK_VIDEO_ORIENTATION, \ .rotation = 0, \ - .window_x = WINDOW_POSITION_UNDEFINED, \ - .window_y = WINDOW_POSITION_UNDEFINED, \ + .window_x = SC_WINDOW_POSITION_UNDEFINED, \ + .window_y = SC_WINDOW_POSITION_UNDEFINED, \ .window_width = 0, \ .window_height = 0, \ .display_id = 0, \ diff --git a/app/src/screen.c b/app/src/screen.c index 66a1163b..4cee61b0 100644 --- a/app/src/screen.c +++ b/app/src/screen.c @@ -8,6 +8,7 @@ #include "common.h" #include "compat.h" #include "icon.xpm" +#include "scrcpy.h" #include "tiny_xpm.h" #include "video_buffer.h" #include "util/lock.h" @@ -257,9 +258,9 @@ screen_init_rendering(struct screen *screen, const char *window_title, window_flags |= SDL_WINDOW_BORDERLESS; } - int x = window_x != WINDOW_POSITION_UNDEFINED + int x = window_x != SC_WINDOW_POSITION_UNDEFINED ? window_x : (int) SDL_WINDOWPOS_UNDEFINED; - int y = window_y != WINDOW_POSITION_UNDEFINED + int y = window_y != SC_WINDOW_POSITION_UNDEFINED ? window_y : (int) SDL_WINDOWPOS_UNDEFINED; screen->window = SDL_CreateWindow(window_title, x, y, window_size.width, window_size.height, diff --git a/app/src/screen.h b/app/src/screen.h index aa6218f7..4c3a593e 100644 --- a/app/src/screen.h +++ b/app/src/screen.h @@ -9,8 +9,6 @@ #include "common.h" #include "opengl.h" -#define WINDOW_POSITION_UNDEFINED (-0x8000) - struct video_buffer; struct screen { @@ -76,7 +74,7 @@ void screen_init(struct screen *screen); // initialize screen, create window, renderer and texture (window is hidden) -// window_x and window_y accept WINDOW_POSITION_UNDEFINED +// window_x and window_y accept SC_WINDOW_POSITION_UNDEFINED bool screen_init_rendering(struct screen *screen, const char *window_title, struct size frame_size, bool always_on_top, diff --git a/app/src/server.c b/app/src/server.c index fb498d63..05b2cf91 100644 --- a/app/src/server.c +++ b/app/src/server.c @@ -143,7 +143,7 @@ listen_on_port(uint16_t port) { static bool enable_tunnel_reverse_any_port(struct server *server, - struct port_range port_range) { + struct sc_port_range port_range) { uint16_t port = port_range.first; for (;;) { if (!enable_tunnel_reverse(server->serial, port)) { @@ -189,7 +189,7 @@ enable_tunnel_reverse_any_port(struct server *server, static bool enable_tunnel_forward_any_port(struct server *server, - struct port_range port_range) { + struct sc_port_range port_range) { server->tunnel_forward = true; uint16_t port = port_range.first; for (;;) { @@ -217,7 +217,7 @@ enable_tunnel_forward_any_port(struct server *server, } static bool -enable_tunnel_any_port(struct server *server, struct port_range port_range, +enable_tunnel_any_port(struct server *server, struct sc_port_range port_range, bool force_adb_forward) { if (!force_adb_forward) { // Attempt to use "adb reverse" diff --git a/app/src/server.h b/app/src/server.h index 2215d817..254afe30 100644 --- a/app/src/server.h +++ b/app/src/server.h @@ -9,6 +9,7 @@ #include "config.h" #include "command.h" #include "common.h" +#include "scrcpy.h" #include "util/log.h" #include "util/net.h" @@ -20,7 +21,7 @@ struct server { socket_t server_socket; // only used if !tunnel_forward socket_t video_socket; socket_t control_socket; - struct port_range port_range; + struct sc_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" @@ -47,7 +48,7 @@ struct server_params { enum sc_log_level log_level; const char *crop; const char *codec_options; - struct port_range port_range; + struct sc_port_range port_range; uint16_t max_size; uint32_t bit_rate; uint16_t max_fps; diff --git a/app/src/util/log.h b/app/src/util/log.h index 8c5c7dee..5955c7fb 100644 --- a/app/src/util/log.h +++ b/app/src/util/log.h @@ -3,13 +3,6 @@ #include -enum sc_log_level { - SC_LOG_LEVEL_DEBUG, - SC_LOG_LEVEL_INFO, - SC_LOG_LEVEL_WARN, - SC_LOG_LEVEL_ERROR, -}; - #define LOGV(...) SDL_LogVerbose(SDL_LOG_CATEGORY_APPLICATION, __VA_ARGS__) #define LOGD(...) SDL_LogDebug(SDL_LOG_CATEGORY_APPLICATION, __VA_ARGS__) #define LOGI(...) SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, __VA_ARGS__) diff --git a/app/tests/test_cli.c b/app/tests/test_cli.c index 2a1e2607..07974361 100644 --- a/app/tests/test_cli.c +++ b/app/tests/test_cli.c @@ -1,4 +1,5 @@ #include +#include #include "cli.h" #include "common.h" @@ -83,7 +84,7 @@ static void test_options(void) { 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); + assert(opts->record_format == SC_RECORD_FORMAT_MKV); assert(opts->render_expired_frames); assert(!strcmp(opts->serial, "0123456789abcdef")); assert(opts->show_touches); @@ -118,7 +119,7 @@ static void test_options2(void) { assert(!opts->control); assert(!opts->display); assert(!strcmp(opts->record_filename, "file.mp4")); - assert(opts->record_format == RECORDER_FORMAT_MP4); + assert(opts->record_format == SC_RECORD_FORMAT_MP4); } int main(void) { From 3c1ed5d86c38bcbd5353b0a7e6ef5653d6c44d0d Mon Sep 17 00:00:00 2001 From: xeropresence <3128949+xeropresence@users.noreply.github.com> Date: Thu, 11 Jun 2020 04:40:52 -0400 Subject: [PATCH 149/450] Handle repeating keycodes Initialize "repeat" count on key events. PR #1519 Refs #1013 Signed-off-by: Romain Vimont --- app/src/control_msg.c | 5 +++-- app/src/control_msg.h | 1 + app/src/input_manager.c | 11 +++++++++-- app/src/input_manager.h | 5 +++++ app/src/scrcpy.c | 1 + app/tests/test_control_msg_serialize.c | 4 +++- .../java/com/genymobile/scrcpy/ControlMessage.java | 8 +++++++- .../com/genymobile/scrcpy/ControlMessageReader.java | 5 +++-- .../main/java/com/genymobile/scrcpy/Controller.java | 6 +++--- .../genymobile/scrcpy/ControlMessageReaderTest.java | 10 ++++++++++ 10 files changed, 45 insertions(+), 11 deletions(-) diff --git a/app/src/control_msg.c b/app/src/control_msg.c index 2a7f2f34..27c7903d 100644 --- a/app/src/control_msg.c +++ b/app/src/control_msg.c @@ -42,8 +42,9 @@ control_msg_serialize(const struct control_msg *msg, unsigned char *buf) { case CONTROL_MSG_TYPE_INJECT_KEYCODE: buf[1] = msg->inject_keycode.action; buffer_write32be(&buf[2], msg->inject_keycode.keycode); - buffer_write32be(&buf[6], msg->inject_keycode.metastate); - return 10; + buffer_write32be(&buf[6], msg->inject_keycode.repeat); + buffer_write32be(&buf[10], msg->inject_keycode.metastate); + return 14; case CONTROL_MSG_TYPE_INJECT_TEXT: { size_t len = write_string(msg->inject_text.text, diff --git a/app/src/control_msg.h b/app/src/control_msg.h index bc4ff9ef..e0b480de 100644 --- a/app/src/control_msg.h +++ b/app/src/control_msg.h @@ -44,6 +44,7 @@ struct control_msg { struct { enum android_keyevent_action action; enum android_keycode keycode; + uint32_t repeat; enum android_metastate metastate; } inject_keycode; struct { diff --git a/app/src/input_manager.c b/app/src/input_manager.c index 54e619bf..52f4f9fe 100644 --- a/app/src/input_manager.c +++ b/app/src/input_manager.c @@ -234,7 +234,7 @@ input_manager_process_text_input(struct input_manager *im, static bool convert_input_key(const SDL_KeyboardEvent *from, struct control_msg *to, - bool prefer_text) { + bool prefer_text, uint32_t repeat) { to->type = CONTROL_MSG_TYPE_INJECT_KEYCODE; if (!convert_keycode_action(from->type, &to->inject_keycode.action)) { @@ -247,6 +247,7 @@ convert_input_key(const SDL_KeyboardEvent *from, struct control_msg *to, return false; } + to->inject_keycode.repeat = repeat; to->inject_keycode.metastate = convert_meta_state(mod); return true; @@ -411,8 +412,14 @@ input_manager_process_key(struct input_manager *im, return; } + if (event->repeat) { + ++im->repeat; + } else { + im->repeat = 0; + } + struct control_msg msg; - if (convert_input_key(event, &msg, im->prefer_text)) { + if (convert_input_key(event, &msg, im->prefer_text, im->repeat)) { if (!controller_push_msg(controller, &msg)) { LOGW("Could not request 'inject keycode'"); } diff --git a/app/src/input_manager.h b/app/src/input_manager.h index 43fc0eeb..90b74fec 100644 --- a/app/src/input_manager.h +++ b/app/src/input_manager.h @@ -14,6 +14,11 @@ struct input_manager { struct controller *controller; struct video_buffer *video_buffer; struct screen *screen; + + // SDL reports repeated events as a boolean, but Android expects the actual + // number of repetitions. This variable keeps track of the count. + unsigned repeat; + bool prefer_text; }; diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index 5a88122d..889cbb87 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -48,6 +48,7 @@ static struct input_manager input_manager = { .controller = &controller, .video_buffer = &video_buffer, .screen = &screen, + .repeat = 0, .prefer_text = false, // initialized later }; diff --git a/app/tests/test_control_msg_serialize.c b/app/tests/test_control_msg_serialize.c index 9ba90ae8..592c2628 100644 --- a/app/tests/test_control_msg_serialize.c +++ b/app/tests/test_control_msg_serialize.c @@ -9,18 +9,20 @@ static void test_serialize_inject_keycode(void) { .inject_keycode = { .action = AKEY_EVENT_ACTION_UP, .keycode = AKEYCODE_ENTER, + .repeat = 5, .metastate = AMETA_SHIFT_ON | AMETA_SHIFT_LEFT_ON, }, }; unsigned char buf[CONTROL_MSG_MAX_SIZE]; int size = control_msg_serialize(&msg, buf); - assert(size == 10); + assert(size == 14); const unsigned char expected[] = { CONTROL_MSG_TYPE_INJECT_KEYCODE, 0x01, // AKEY_EVENT_ACTION_UP 0x00, 0x00, 0x00, 0x42, // AKEYCODE_ENTER + 0x00, 0x00, 0x00, 0X05, // repeat 0x00, 0x00, 0x00, 0x41, // AMETA_SHIFT_ON | AMETA_SHIFT_LEFT_ON }; assert(!memcmp(buf, expected, sizeof(expected))); diff --git a/server/src/main/java/com/genymobile/scrcpy/ControlMessage.java b/server/src/main/java/com/genymobile/scrcpy/ControlMessage.java index 7d0ab7a6..dbb8d382 100644 --- a/server/src/main/java/com/genymobile/scrcpy/ControlMessage.java +++ b/server/src/main/java/com/genymobile/scrcpy/ControlMessage.java @@ -31,15 +31,17 @@ public final class ControlMessage { private int hScroll; private int vScroll; private int flags; + private int repeat; private ControlMessage() { } - public static ControlMessage createInjectKeycode(int action, int keycode, int metaState) { + public static ControlMessage createInjectKeycode(int action, int keycode, int repeat, int metaState) { ControlMessage msg = new ControlMessage(); msg.type = TYPE_INJECT_KEYCODE; msg.action = action; msg.keycode = keycode; + msg.repeat = repeat; msg.metaState = metaState; return msg; } @@ -144,4 +146,8 @@ public final class ControlMessage { public int getFlags() { return flags; } + + public int getRepeat() { + return repeat; + } } diff --git a/server/src/main/java/com/genymobile/scrcpy/ControlMessageReader.java b/server/src/main/java/com/genymobile/scrcpy/ControlMessageReader.java index 3b3e9559..132e3f4e 100644 --- a/server/src/main/java/com/genymobile/scrcpy/ControlMessageReader.java +++ b/server/src/main/java/com/genymobile/scrcpy/ControlMessageReader.java @@ -8,7 +8,7 @@ import java.nio.charset.StandardCharsets; public class ControlMessageReader { - static final int INJECT_KEYCODE_PAYLOAD_LENGTH = 9; + static final int INJECT_KEYCODE_PAYLOAD_LENGTH = 13; static final int INJECT_TOUCH_EVENT_PAYLOAD_LENGTH = 27; static final int INJECT_SCROLL_EVENT_PAYLOAD_LENGTH = 20; static final int SET_SCREEN_POWER_MODE_PAYLOAD_LENGTH = 1; @@ -98,8 +98,9 @@ public class ControlMessageReader { } int action = toUnsigned(buffer.get()); int keycode = buffer.getInt(); + int repeat = buffer.getInt(); int metaState = buffer.getInt(); - return ControlMessage.createInjectKeycode(action, keycode, metaState); + return ControlMessage.createInjectKeycode(action, keycode, repeat, metaState); } private String parseString() { diff --git a/server/src/main/java/com/genymobile/scrcpy/Controller.java b/server/src/main/java/com/genymobile/scrcpy/Controller.java index 960c6a6e..bc4a0601 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Controller.java +++ b/server/src/main/java/com/genymobile/scrcpy/Controller.java @@ -74,7 +74,7 @@ public class Controller { switch (msg.getType()) { case ControlMessage.TYPE_INJECT_KEYCODE: if (device.supportsInputEvents()) { - injectKeycode(msg.getAction(), msg.getKeycode(), msg.getMetaState()); + injectKeycode(msg.getAction(), msg.getKeycode(), msg.getRepeat(), msg.getMetaState()); } break; case ControlMessage.TYPE_INJECT_TEXT: @@ -130,8 +130,8 @@ public class Controller { } } - private boolean injectKeycode(int action, int keycode, int metaState) { - return device.injectKeyEvent(action, keycode, 0, metaState); + private boolean injectKeycode(int action, int keycode, int repeat, int metaState) { + return device.injectKeyEvent(action, keycode, repeat, metaState); } private boolean injectChar(char c) { diff --git a/server/src/test/java/com/genymobile/scrcpy/ControlMessageReaderTest.java b/server/src/test/java/com/genymobile/scrcpy/ControlMessageReaderTest.java index c56bd17e..1e2b8266 100644 --- a/server/src/test/java/com/genymobile/scrcpy/ControlMessageReaderTest.java +++ b/server/src/test/java/com/genymobile/scrcpy/ControlMessageReaderTest.java @@ -25,6 +25,7 @@ public class ControlMessageReaderTest { dos.writeByte(ControlMessage.TYPE_INJECT_KEYCODE); dos.writeByte(KeyEvent.ACTION_UP); dos.writeInt(KeyEvent.KEYCODE_ENTER); + dos.writeInt(5); // repeat dos.writeInt(KeyEvent.META_CTRL_ON); byte[] packet = bos.toByteArray(); @@ -37,6 +38,7 @@ public class ControlMessageReaderTest { Assert.assertEquals(ControlMessage.TYPE_INJECT_KEYCODE, event.getType()); Assert.assertEquals(KeyEvent.ACTION_UP, event.getAction()); Assert.assertEquals(KeyEvent.KEYCODE_ENTER, event.getKeycode()); + Assert.assertEquals(5, event.getRepeat()); Assert.assertEquals(KeyEvent.META_CTRL_ON, event.getMetaState()); } @@ -308,11 +310,13 @@ public class ControlMessageReaderTest { dos.writeByte(ControlMessage.TYPE_INJECT_KEYCODE); dos.writeByte(KeyEvent.ACTION_UP); dos.writeInt(KeyEvent.KEYCODE_ENTER); + dos.writeInt(0); // repeat dos.writeInt(KeyEvent.META_CTRL_ON); dos.writeByte(ControlMessage.TYPE_INJECT_KEYCODE); dos.writeByte(MotionEvent.ACTION_DOWN); dos.writeInt(MotionEvent.BUTTON_PRIMARY); + dos.writeInt(1); // repeat dos.writeInt(KeyEvent.META_CTRL_ON); byte[] packet = bos.toByteArray(); @@ -322,12 +326,14 @@ public class ControlMessageReaderTest { Assert.assertEquals(ControlMessage.TYPE_INJECT_KEYCODE, event.getType()); Assert.assertEquals(KeyEvent.ACTION_UP, event.getAction()); Assert.assertEquals(KeyEvent.KEYCODE_ENTER, event.getKeycode()); + Assert.assertEquals(0, event.getRepeat()); Assert.assertEquals(KeyEvent.META_CTRL_ON, event.getMetaState()); event = reader.next(); Assert.assertEquals(ControlMessage.TYPE_INJECT_KEYCODE, event.getType()); Assert.assertEquals(MotionEvent.ACTION_DOWN, event.getAction()); Assert.assertEquals(MotionEvent.BUTTON_PRIMARY, event.getKeycode()); + Assert.assertEquals(1, event.getRepeat()); Assert.assertEquals(KeyEvent.META_CTRL_ON, event.getMetaState()); } @@ -341,6 +347,7 @@ public class ControlMessageReaderTest { dos.writeByte(ControlMessage.TYPE_INJECT_KEYCODE); dos.writeByte(KeyEvent.ACTION_UP); dos.writeInt(KeyEvent.KEYCODE_ENTER); + dos.writeInt(4); // repeat dos.writeInt(KeyEvent.META_CTRL_ON); dos.writeByte(ControlMessage.TYPE_INJECT_KEYCODE); @@ -353,6 +360,7 @@ public class ControlMessageReaderTest { Assert.assertEquals(ControlMessage.TYPE_INJECT_KEYCODE, event.getType()); Assert.assertEquals(KeyEvent.ACTION_UP, event.getAction()); Assert.assertEquals(KeyEvent.KEYCODE_ENTER, event.getKeycode()); + Assert.assertEquals(4, event.getRepeat()); Assert.assertEquals(KeyEvent.META_CTRL_ON, event.getMetaState()); event = reader.next(); @@ -360,6 +368,7 @@ public class ControlMessageReaderTest { bos.reset(); dos.writeInt(MotionEvent.BUTTON_PRIMARY); + dos.writeInt(5); // repeat dos.writeInt(KeyEvent.META_CTRL_ON); packet = bos.toByteArray(); reader.readFrom(new ByteArrayInputStream(packet)); @@ -369,6 +378,7 @@ public class ControlMessageReaderTest { Assert.assertEquals(ControlMessage.TYPE_INJECT_KEYCODE, event.getType()); Assert.assertEquals(MotionEvent.ACTION_DOWN, event.getAction()); Assert.assertEquals(MotionEvent.BUTTON_PRIMARY, event.getKeycode()); + Assert.assertEquals(5, event.getRepeat()); Assert.assertEquals(KeyEvent.META_CTRL_ON, event.getMetaState()); } } From 8c27f59aa571504e2e038dc5386e2be6897cec32 Mon Sep 17 00:00:00 2001 From: Hossam Elbadissi Date: Thu, 25 Jun 2020 19:38:19 +0100 Subject: [PATCH 150/450] Improve linguistic PR #1543 Signed-off-by: Romain Vimont --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index a894313b..847a744d 100644 --- a/README.md +++ b/README.md @@ -441,7 +441,7 @@ Or by pressing `Ctrl`+`o` at any time. To turn it back on, press `Ctrl`+`Shift`+`o` (or `POWER`, `Ctrl`+`p`). -It can be useful to also prevent the device to sleep: +It can also be useful to prevent the device from sleeping: ```bash scrcpy --turn-screen-off --stay-awake From e8a565f9ea91a30cd354de696b3b35ea74cda83b Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 25 Jun 2020 08:54:40 +0200 Subject: [PATCH 151/450] Fix touch events HiDPI-scaling Touch events were HiDPI-scaled twice: - once because the position (provided as floats between 0 and 1) were converted in pixels using the drawable size (not the window size) - once due to screen_convert_to_frame_coords() One possible fix could be to compute the position in pixels from the window size instead, but this would unnecessarily round the event position to the nearest window coordinates (instead of drawable coordinates). Instead, expose two separate functions to convert to frame coordinates from either window or drawable coordinates. Fixes #1536 Refs #15 Refs e40532a3761da8b3aef484f1d435ef5f50d94574 --- app/src/input_manager.c | 19 ++++++++++--------- app/src/screen.c | 11 +++++++++-- app/src/screen.h | 9 ++++++++- 3 files changed, 27 insertions(+), 12 deletions(-) diff --git a/app/src/input_manager.c b/app/src/input_manager.c index 52f4f9fe..9c22ee0a 100644 --- a/app/src/input_manager.c +++ b/app/src/input_manager.c @@ -434,7 +434,7 @@ convert_mouse_motion(const SDL_MouseMotionEvent *from, struct screen *screen, to->inject_touch_event.pointer_id = POINTER_ID_MOUSE; to->inject_touch_event.position.screen_size = screen->frame_size; to->inject_touch_event.position.point = - screen_convert_to_frame_coords(screen, from->x, from->y); + screen_convert_window_to_frame_coords(screen, from->x, from->y); to->inject_touch_event.pressure = 1.f; to->inject_touch_event.buttons = convert_mouse_buttons(from->state); @@ -472,15 +472,15 @@ convert_touch(const SDL_TouchFingerEvent *from, struct screen *screen, to->inject_touch_event.pointer_id = from->fingerId; to->inject_touch_event.position.screen_size = screen->frame_size; - int ww; - int wh; - SDL_GL_GetDrawableSize(screen->window, &ww, &wh); + int dw; + int dh; + SDL_GL_GetDrawableSize(screen->window, &dw, &dh); // SDL touch event coordinates are normalized in the range [0; 1] - int32_t x = from->x * ww; - int32_t y = from->y * wh; + int32_t x = from->x * dw; + int32_t y = from->y * dh; to->inject_touch_event.position.point = - screen_convert_to_frame_coords(screen, x, y); + screen_convert_drawable_to_frame_coords(screen, x, y); to->inject_touch_event.pressure = from->pressure; to->inject_touch_event.buttons = 0; @@ -510,7 +510,7 @@ convert_mouse_button(const SDL_MouseButtonEvent *from, struct screen *screen, to->inject_touch_event.pointer_id = POINTER_ID_MOUSE; to->inject_touch_event.position.screen_size = screen->frame_size; to->inject_touch_event.position.point = - screen_convert_to_frame_coords(screen, from->x, from->y); + screen_convert_window_to_frame_coords(screen, from->x, from->y); to->inject_touch_event.pressure = 1.f; to->inject_touch_event.buttons = convert_mouse_buttons(SDL_BUTTON(from->button)); @@ -575,7 +575,8 @@ convert_mouse_wheel(const SDL_MouseWheelEvent *from, struct screen *screen, struct position position = { .screen_size = screen->frame_size, - .point = screen_convert_to_frame_coords(screen, mouse_x, mouse_y), + .point = screen_convert_window_to_frame_coords(screen, + mouse_x, mouse_y), }; to->type = CONTROL_MSG_TYPE_INJECT_SCROLL_EVENT; diff --git a/app/src/screen.c b/app/src/screen.c index 4cee61b0..fe2bc867 100644 --- a/app/src/screen.c +++ b/app/src/screen.c @@ -580,14 +580,14 @@ screen_handle_window_event(struct screen *screen, } struct point -screen_convert_to_frame_coords(struct screen *screen, int32_t x, int32_t y) { +screen_convert_drawable_to_frame_coords(struct screen *screen, + int32_t x, int32_t y) { unsigned rotation = screen->rotation; assert(rotation < 4); int32_t w = screen->content_size.width; int32_t h = screen->content_size.height; - screen_hidpi_scale_coords(screen, &x, &y); x = (int64_t) (x - screen->rect.x) * w / screen->rect.w; y = (int64_t) (y - screen->rect.y) * h / screen->rect.h; @@ -616,6 +616,13 @@ screen_convert_to_frame_coords(struct screen *screen, int32_t x, int32_t y) { return result; } +struct point +screen_convert_window_to_frame_coords(struct screen *screen, + int32_t x, int32_t y) { + screen_hidpi_scale_coords(screen, &x, &y); + return screen_convert_drawable_to_frame_coords(screen, x, y); +} + void screen_hidpi_scale_coords(struct screen *screen, int32_t *x, int32_t *y) { // take the HiDPI scaling (dw/ww and dh/wh) into account diff --git a/app/src/screen.h b/app/src/screen.h index 4c3a593e..c4fbbf66 100644 --- a/app/src/screen.h +++ b/app/src/screen.h @@ -124,7 +124,14 @@ screen_handle_window_event(struct screen *screen, const SDL_WindowEvent *event); // convert point from window coordinates to frame coordinates // x and y are expressed in pixels struct point -screen_convert_to_frame_coords(struct screen *screen, int32_t x, int32_t y); +screen_convert_window_to_frame_coords(struct screen *screen, + int32_t x, int32_t y); + +// convert point from drawable coordinates to frame coordinates +// x and y are expressed in pixels +struct point +screen_convert_drawable_to_frame_coords(struct screen *screen, + int32_t x, int32_t y); // Convert coordinates from window to drawable. // Events are expressed in window coordinates, but content is expressed in From e99b896ae2b6a59d5f7f2b76a3dd9c29f4838013 Mon Sep 17 00:00:00 2001 From: axxx007xxxz Date: Sat, 27 Jun 2020 12:16:27 +0200 Subject: [PATCH 152/450] README: Add Fedora install instructions PR #1549 Signed-off-by: Romain Vimont --- README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.md b/README.md index 847a744d..5818b95f 100644 --- a/README.md +++ b/README.md @@ -49,6 +49,11 @@ A [Snap] package is available: [`scrcpy`][snap-link]. [snap]: https://en.wikipedia.org/wiki/Snappy_(package_manager) +For Fedora, a [COPR] package is available: [`scrcpy`][copr-link]. + +[COPR]: https://fedoraproject.org/wiki/Category:Copr +[copr-link]: https://copr.fedorainfracloud.org/coprs/zeno/scrcpy/ + For Arch Linux, an [AUR] package is available: [`scrcpy`][aur-link]. [AUR]: https://wiki.archlinux.org/index.php/Arch_User_Repository From f7d4b6d0db0f417ab0c45088710dc174e97a0894 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 3 Jul 2020 08:50:52 +0200 Subject: [PATCH 153/450] Do not crash on missing clipboard manager Some devices have no clipboard manager. In that case, do not try to enable clipboard synchronization to avoid a crash. Fixes #1440 Fixes #1556 --- .../java/com/genymobile/scrcpy/Device.java | 46 ++++++++++++------- .../scrcpy/wrappers/ServiceManager.java | 9 +++- 2 files changed, 38 insertions(+), 17 deletions(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/Device.java b/server/src/main/java/com/genymobile/scrcpy/Device.java index 349486c3..14495736 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Device.java +++ b/server/src/main/java/com/genymobile/scrcpy/Device.java @@ -1,5 +1,6 @@ package com.genymobile.scrcpy; +import com.genymobile.scrcpy.wrappers.ClipboardManager; import com.genymobile.scrcpy.wrappers.ContentProvider; import com.genymobile.scrcpy.wrappers.InputManager; import com.genymobile.scrcpy.wrappers.ServiceManager; @@ -80,23 +81,28 @@ public final class Device { if (options.getControl()) { // If control is enabled, synchronize Android clipboard to the computer automatically - serviceManager.getClipboardManager().addPrimaryClipChangedListener(new IOnPrimaryClipChangedListener.Stub() { - @Override - public void dispatchPrimaryClipChanged() { - if (isSettingClipboard.get()) { - // This is a notification for the change we are currently applying, ignore it - return; - } - synchronized (Device.this) { - if (clipboardListener != null) { - String text = getClipboardText(); - if (text != null) { - clipboardListener.onClipboardTextChanged(text); + ClipboardManager clipboardManager = serviceManager.getClipboardManager(); + if (clipboardManager != null) { + clipboardManager.addPrimaryClipChangedListener(new IOnPrimaryClipChangedListener.Stub() { + @Override + public void dispatchPrimaryClipChanged() { + if (isSettingClipboard.get()) { + // This is a notification for the change we are currently applying, ignore it + return; + } + synchronized (Device.this) { + if (clipboardListener != null) { + String text = getClipboardText(); + if (text != null) { + clipboardListener.onClipboardTextChanged(text); + } } } } - } - }); + }); + } else { + Ln.w("No clipboard manager, copy-paste between device and computer will not work"); + } } if ((displayInfoFlags & DisplayInfo.FLAG_SUPPORTS_PROTECTED_BUFFERS) == 0) { @@ -199,7 +205,11 @@ public final class Device { } public String getClipboardText() { - CharSequence s = serviceManager.getClipboardManager().getText(); + ClipboardManager clipboardManager = serviceManager.getClipboardManager(); + if (clipboardManager == null) { + return null; + } + CharSequence s = clipboardManager.getText(); if (s == null) { return null; } @@ -207,8 +217,12 @@ public final class Device { } public boolean setClipboardText(String text) { + ClipboardManager clipboardManager = serviceManager.getClipboardManager(); + if (clipboardManager == null) { + return false; + } isSettingClipboard.set(true); - boolean ok = serviceManager.getClipboardManager().setText(text); + boolean ok = clipboardManager.setText(text); isSettingClipboard.set(false); return ok; } diff --git a/server/src/main/java/com/genymobile/scrcpy/wrappers/ServiceManager.java b/server/src/main/java/com/genymobile/scrcpy/wrappers/ServiceManager.java index c4ce59c2..6f4b9c04 100644 --- a/server/src/main/java/com/genymobile/scrcpy/wrappers/ServiceManager.java +++ b/server/src/main/java/com/genymobile/scrcpy/wrappers/ServiceManager.java @@ -77,7 +77,14 @@ public final class ServiceManager { public ClipboardManager getClipboardManager() { if (clipboardManager == null) { - clipboardManager = new ClipboardManager(getService("clipboard", "android.content.IClipboard")); + IInterface clipboard = getService("clipboard", "android.content.IClipboard"); + if (clipboard == null) { + // Some devices have no clipboard manager + // + // + return null; + } + clipboardManager = new ClipboardManager(clipboard); } return clipboardManager; } From 334964c380ebc647b9f1e876182372efa51f26dc Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Tue, 7 Jul 2020 15:34:34 +0200 Subject: [PATCH 154/450] Make setScreenPowerMode() method static Its implementation does not use the instance at all. --- server/src/main/java/com/genymobile/scrcpy/Controller.java | 2 +- server/src/main/java/com/genymobile/scrcpy/Device.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/Controller.java b/server/src/main/java/com/genymobile/scrcpy/Controller.java index bc4a0601..71e7ec9c 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Controller.java +++ b/server/src/main/java/com/genymobile/scrcpy/Controller.java @@ -116,7 +116,7 @@ public class Controller { case ControlMessage.TYPE_SET_SCREEN_POWER_MODE: if (device.supportsInputEvents()) { int mode = msg.getAction(); - boolean setPowerModeOk = device.setScreenPowerMode(mode); + boolean setPowerModeOk = Device.setScreenPowerMode(mode); if (setPowerModeOk) { Ln.i("Device screen turned " + (mode == Device.POWER_MODE_OFF ? "off" : "on")); } diff --git a/server/src/main/java/com/genymobile/scrcpy/Device.java b/server/src/main/java/com/genymobile/scrcpy/Device.java index 14495736..6ba8d446 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Device.java +++ b/server/src/main/java/com/genymobile/scrcpy/Device.java @@ -230,7 +230,7 @@ public final class Device { /** * @param mode one of the {@code SCREEN_POWER_MODE_*} constants */ - public boolean setScreenPowerMode(int mode) { + public static boolean setScreenPowerMode(int mode) { IBinder d = SurfaceControl.getBuiltInDisplay(); if (d == null) { Ln.e("Could not get built-in display"); From 5fa46ad0c71169f9369f9f3bde9cce3d29ed4b88 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Tue, 7 Jul 2020 15:46:50 +0200 Subject: [PATCH 155/450] Fix constants name in comment --- server/src/main/java/com/genymobile/scrcpy/Device.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/Device.java b/server/src/main/java/com/genymobile/scrcpy/Device.java index 6ba8d446..375c9ed6 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Device.java +++ b/server/src/main/java/com/genymobile/scrcpy/Device.java @@ -228,7 +228,7 @@ public final class Device { } /** - * @param mode one of the {@code SCREEN_POWER_MODE_*} constants + * @param mode one of the {@code POWER_MODE_*} constants */ public static boolean setScreenPowerMode(int mode) { IBinder d = SurfaceControl.getBuiltInDisplay(); From 5086e7b744e9edfa9d1d56cf5ae2ad3b0ae32ddf Mon Sep 17 00:00:00 2001 From: NGAU Zeonfung Date: Sat, 4 Jul 2020 02:27:21 +0900 Subject: [PATCH 156/450] Update BUILD.md Update the macOS section. PR #1559 Signed-off-by: Romain Vimont --- BUILD.md | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/BUILD.md b/BUILD.md index 3696e6b0..57b2db97 100644 --- a/BUILD.md +++ b/BUILD.md @@ -176,8 +176,8 @@ Additionally, if you want to build the server, install Java 8 from Caskroom, and make it avaliable from the `PATH`: ```bash -brew tap caskroom/versions -brew cask install java8 +brew tap homebrew/cask-versions +brew cask install adoptopenjdk/openjdk/adoptopenjdk8 export JAVA_HOME="$(/usr/libexec/java_home --version 1.8)" export PATH="$JAVA_HOME/bin:$PATH" ``` @@ -190,12 +190,17 @@ See [pierlon/scrcpy-docker](https://github.com/pierlon/scrcpy-docker). ## Common steps If you want to build the server, install the [Android SDK] (_Android Studio_), -and set `ANDROID_HOME` to its directory. For example: +and set `ANDROID_SDK_ROOT` to its directory. For example: [Android SDK]: https://developer.android.com/studio/index.html ```bash -export ANDROID_HOME=~/android/sdk +# Linux +export ANDROID_SDK_ROOT=~/Android/Sdk +# Mac +export ANDROID_SDK_ROOT=~/Library/Android/sdk +# Windows +set ANDROID_SDK_ROOT=%LOCALAPPDATA%\Android\sdk ``` If you don't want to build the server, use the [prebuilt server]. From deea29f52ac742a5090b53bc880e113bfbbcb265 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Tue, 7 Jul 2020 19:37:52 +0200 Subject: [PATCH 157/450] Send touch event without pressure on button up Refs #1518 --- app/src/input_manager.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/src/input_manager.c b/app/src/input_manager.c index 9c22ee0a..7d500efe 100644 --- a/app/src/input_manager.c +++ b/app/src/input_manager.c @@ -511,7 +511,8 @@ convert_mouse_button(const SDL_MouseButtonEvent *from, struct screen *screen, to->inject_touch_event.position.screen_size = screen->frame_size; to->inject_touch_event.position.point = screen_convert_window_to_frame_coords(screen, from->x, from->y); - to->inject_touch_event.pressure = 1.f; + to->inject_touch_event.pressure = + from->type == SDL_MOUSEBUTTONDOWN ? 1.f : 0.f; to->inject_touch_event.buttons = convert_mouse_buttons(SDL_BUTTON(from->button)); From a973757fd141fc07ef81314d350bd4f4de64f59c Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 9 Jul 2020 22:31:05 +0200 Subject: [PATCH 158/450] Warn on ignored touch event In theory, this was expected to only happen when a touch event is sent just before the device is rotated, but some devices do not respect the encoding size, causing an unexpected mismatch. Refs #1518 --- server/src/main/java/com/genymobile/scrcpy/Controller.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/Controller.java b/server/src/main/java/com/genymobile/scrcpy/Controller.java index 71e7ec9c..abfc434a 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Controller.java +++ b/server/src/main/java/com/genymobile/scrcpy/Controller.java @@ -166,7 +166,7 @@ public class Controller { Point point = device.getPhysicalPoint(position); if (point == null) { - // ignore event + Ln.w("Ignore touch event, it was generated for a different device size"); return false; } From 30714aba34e8b447159beaebeaf4eeab7bd33907 Mon Sep 17 00:00:00 2001 From: brunoais Date: Tue, 7 Jul 2020 22:13:15 +0100 Subject: [PATCH 159/450] Restore power mode to normal on cleanup This avoids to let the device screen turned off (as enabled by Ctrl+o or --turn-screen-off). PR #1576 Fixes #1572 --- .../java/com/genymobile/scrcpy/CleanUp.java | 17 ++++++++++++----- .../main/java/com/genymobile/scrcpy/Server.java | 2 +- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/CleanUp.java b/server/src/main/java/com/genymobile/scrcpy/CleanUp.java index 74555636..d0ea141b 100644 --- a/server/src/main/java/com/genymobile/scrcpy/CleanUp.java +++ b/server/src/main/java/com/genymobile/scrcpy/CleanUp.java @@ -19,18 +19,19 @@ public final class CleanUp { // not instantiable } - public static void configure(boolean disableShowTouches, int restoreStayOn) throws IOException { - boolean needProcess = disableShowTouches || restoreStayOn != -1; + public static void configure(boolean disableShowTouches, int restoreStayOn, boolean restoreNormalPowerMode) throws IOException { + boolean needProcess = disableShowTouches || restoreStayOn != -1 || restoreNormalPowerMode; if (needProcess) { - startProcess(disableShowTouches, restoreStayOn); + startProcess(disableShowTouches, restoreStayOn, restoreNormalPowerMode); } else { // There is no additional clean up to do when scrcpy dies unlinkSelf(); } } - private static void startProcess(boolean disableShowTouches, int restoreStayOn) throws IOException { - String[] cmd = {"app_process", "/", CleanUp.class.getName(), String.valueOf(disableShowTouches), String.valueOf(restoreStayOn)}; + private static void startProcess(boolean disableShowTouches, int restoreStayOn, boolean restoreNormalPowerMode) throws IOException { + String[] cmd = {"app_process", "/", CleanUp.class.getName(), String.valueOf(disableShowTouches), String.valueOf( + restoreStayOn), String.valueOf(restoreNormalPowerMode)}; ProcessBuilder builder = new ProcessBuilder(cmd); builder.environment().put("CLASSPATH", SERVER_PATH); @@ -59,6 +60,7 @@ public final class CleanUp { boolean disableShowTouches = Boolean.parseBoolean(args[0]); int restoreStayOn = Integer.parseInt(args[1]); + boolean restoreNormalPowerMode = Boolean.parseBoolean(args[2]); if (disableShowTouches || restoreStayOn != -1) { ServiceManager serviceManager = new ServiceManager(); @@ -73,5 +75,10 @@ public final class CleanUp { } } } + + if (restoreNormalPowerMode) { + Ln.i("Restoring normal power mode"); + Device.setScreenPowerMode(Device.POWER_MODE_NORMAL); + } } } diff --git a/server/src/main/java/com/genymobile/scrcpy/Server.java b/server/src/main/java/com/genymobile/scrcpy/Server.java index 44b3afd4..d257e319 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Server.java +++ b/server/src/main/java/com/genymobile/scrcpy/Server.java @@ -49,7 +49,7 @@ public final class Server { } } - CleanUp.configure(mustDisableShowTouchesOnCleanUp, restoreStayOn); + CleanUp.configure(mustDisableShowTouchesOnCleanUp, restoreStayOn, true); boolean tunnelForward = options.isTunnelForward(); From 322f1512ea806611eb37f508cd952bbbc050f853 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Wed, 15 Jul 2020 10:21:24 +0200 Subject: [PATCH 160/450] Inject WAKEUP instead of POWER To power the device on, inject KEYCODE_WAKEUP to avoid a possible race condition (the device might become off between the test isScreenOn() and the POWER keycode injection). --- server/src/main/java/com/genymobile/scrcpy/Controller.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/Controller.java b/server/src/main/java/com/genymobile/scrcpy/Controller.java index abfc434a..6ff8d208 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Controller.java +++ b/server/src/main/java/com/genymobile/scrcpy/Controller.java @@ -48,10 +48,10 @@ public class Controller { public void control() throws IOException { // on start, power on the device if (!device.isScreenOn()) { - device.injectKeycode(KeyEvent.KEYCODE_POWER); + device.injectKeycode(KeyEvent.KEYCODE_WAKEUP); // dirty hack - // After POWER is injected, the device is powered on asynchronously. + // After the keycode is injected, the device is powered on asynchronously. // To turn the device screen off while mirroring, the client will send a message that // would be handled before the device is actually powered on, so its effect would // be "canceled" once the device is turned back on. @@ -225,7 +225,7 @@ public class Controller { } private boolean pressBackOrTurnScreenOn() { - int keycode = device.isScreenOn() ? KeyEvent.KEYCODE_BACK : KeyEvent.KEYCODE_POWER; + int keycode = device.isScreenOn() ? KeyEvent.KEYCODE_BACK : KeyEvent.KEYCODE_WAKEUP; return device.injectKeycode(keycode); } From 199c74f62f5f78748769e6489122242a3217dc54 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Wed, 15 Jul 2020 12:17:04 +0200 Subject: [PATCH 161/450] Declare main() with argc/argv params in tests Declaring the main method as "int main(void)" causes issues with SDL. Fixes #1209 --- app/tests/test_buffer_util.c | 5 ++++- app/tests/test_cbuf.c | 5 ++++- app/tests/test_cli.c | 5 ++++- app/tests/test_control_msg_serialize.c | 5 ++++- app/tests/test_device_msg_deserialize.c | 5 ++++- app/tests/test_queue.c | 5 ++++- app/tests/test_strutil.c | 5 ++++- 7 files changed, 28 insertions(+), 7 deletions(-) diff --git a/app/tests/test_buffer_util.c b/app/tests/test_buffer_util.c index ba3f9f06..d61e6918 100644 --- a/app/tests/test_buffer_util.c +++ b/app/tests/test_buffer_util.c @@ -65,7 +65,10 @@ static void test_buffer_read64be(void) { assert(val == 0xABCD1234567890EF); } -int main(void) { +int main(int argc, char *argv[]) { + (void) argc; + (void) argv; + test_buffer_write16be(); test_buffer_write32be(); test_buffer_write64be(); diff --git a/app/tests/test_cbuf.c b/app/tests/test_cbuf.c index dbe50aab..f8beb880 100644 --- a/app/tests/test_cbuf.c +++ b/app/tests/test_cbuf.c @@ -65,7 +65,10 @@ static void test_cbuf_push_take(void) { assert(item == 35); } -int main(void) { +int main(int argc, char *argv[]) { + (void) argc; + (void) argv; + test_cbuf_empty(); test_cbuf_full(); test_cbuf_push_take(); diff --git a/app/tests/test_cli.c b/app/tests/test_cli.c index 07974361..7a7d408d 100644 --- a/app/tests/test_cli.c +++ b/app/tests/test_cli.c @@ -122,7 +122,10 @@ static void test_options2(void) { assert(opts->record_format == SC_RECORD_FORMAT_MP4); } -int main(void) { +int main(int argc, char *argv[]) { + (void) argc; + (void) argv; + test_flag_version(); test_flag_help(); test_options(); diff --git a/app/tests/test_control_msg_serialize.c b/app/tests/test_control_msg_serialize.c index 592c2628..b58c8e20 100644 --- a/app/tests/test_control_msg_serialize.c +++ b/app/tests/test_control_msg_serialize.c @@ -257,7 +257,10 @@ static void test_serialize_rotate_device(void) { assert(!memcmp(buf, expected, sizeof(expected))); } -int main(void) { +int main(int argc, char *argv[]) { + (void) argc; + (void) argv; + test_serialize_inject_keycode(); test_serialize_inject_text(); test_serialize_inject_text_long(); diff --git a/app/tests/test_device_msg_deserialize.c b/app/tests/test_device_msg_deserialize.c index 8fcfc93d..3dfd0b0f 100644 --- a/app/tests/test_device_msg_deserialize.c +++ b/app/tests/test_device_msg_deserialize.c @@ -45,7 +45,10 @@ static void test_deserialize_clipboard_big(void) { device_msg_destroy(&msg); } -int main(void) { +int main(int argc, char *argv[]) { + (void) argc; + (void) argv; + test_deserialize_clipboard(); test_deserialize_clipboard_big(); return 0; diff --git a/app/tests/test_queue.c b/app/tests/test_queue.c index b0950bb0..e10821cd 100644 --- a/app/tests/test_queue.c +++ b/app/tests/test_queue.c @@ -32,7 +32,10 @@ static void test_queue(void) { assert(queue_is_empty(&queue)); } -int main(void) { +int main(int argc, char *argv[]) { + (void) argc; + (void) argv; + test_queue(); return 0; } diff --git a/app/tests/test_strutil.c b/app/tests/test_strutil.c index a88bca0e..7b9c61da 100644 --- a/app/tests/test_strutil.c +++ b/app/tests/test_strutil.c @@ -286,7 +286,10 @@ static void test_parse_integer_with_suffix(void) { assert(!ok); } -int main(void) { +int main(int argc, char *argv[]) { + (void) argc; + (void) argv; + test_xstrncpy_simple(); test_xstrncpy_just_fit(); test_xstrncpy_truncated(); From eabaf6f7bd391d0147eaebda51c79bbf9a75807a Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 17 Jul 2020 00:00:42 +0200 Subject: [PATCH 162/450] Simplify PASTE option for "set clipboard" When the client requests to set the clipboard, it may request to press the PASTE key in addition. To be a bit generic, it was stored as a flag in ControlMessage.java. But flags suggest that it represents a bitwise union. Use a simple boolean instead. --- .../java/com/genymobile/scrcpy/ControlMessage.java | 12 ++++-------- .../com/genymobile/scrcpy/ControlMessageReader.java | 4 ++-- .../main/java/com/genymobile/scrcpy/Controller.java | 3 +-- .../genymobile/scrcpy/ControlMessageReaderTest.java | 8 ++------ 4 files changed, 9 insertions(+), 18 deletions(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/ControlMessage.java b/server/src/main/java/com/genymobile/scrcpy/ControlMessage.java index dbb8d382..736acf80 100644 --- a/server/src/main/java/com/genymobile/scrcpy/ControlMessage.java +++ b/server/src/main/java/com/genymobile/scrcpy/ControlMessage.java @@ -17,8 +17,6 @@ public final class ControlMessage { public static final int TYPE_SET_SCREEN_POWER_MODE = 9; public static final int TYPE_ROTATE_DEVICE = 10; - public static final int FLAGS_PASTE = 1; - private int type; private String text; private int metaState; // KeyEvent.META_* @@ -30,7 +28,7 @@ public final class ControlMessage { private Position position; private int hScroll; private int vScroll; - private int flags; + private boolean paste; private int repeat; private ControlMessage() { @@ -77,9 +75,7 @@ public final class ControlMessage { ControlMessage msg = new ControlMessage(); msg.type = TYPE_SET_CLIPBOARD; msg.text = text; - if (paste) { - msg.flags = FLAGS_PASTE; - } + msg.paste = paste; return msg; } @@ -143,8 +139,8 @@ public final class ControlMessage { return vScroll; } - public int getFlags() { - return flags; + public boolean getPaste() { + return paste; } public int getRepeat() { diff --git a/server/src/main/java/com/genymobile/scrcpy/ControlMessageReader.java b/server/src/main/java/com/genymobile/scrcpy/ControlMessageReader.java index 132e3f4e..ce185103 100644 --- a/server/src/main/java/com/genymobile/scrcpy/ControlMessageReader.java +++ b/server/src/main/java/com/genymobile/scrcpy/ControlMessageReader.java @@ -154,12 +154,12 @@ public class ControlMessageReader { if (buffer.remaining() < SET_CLIPBOARD_FIXED_PAYLOAD_LENGTH) { return null; } - boolean parse = buffer.get() != 0; + boolean paste = buffer.get() != 0; String text = parseString(); if (text == null) { return null; } - return ControlMessage.createSetClipboard(text, parse); + return ControlMessage.createSetClipboard(text, paste); } private ControlMessage parseSetScreenPowerMode() { diff --git a/server/src/main/java/com/genymobile/scrcpy/Controller.java b/server/src/main/java/com/genymobile/scrcpy/Controller.java index 6ff8d208..f32e5ec0 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Controller.java +++ b/server/src/main/java/com/genymobile/scrcpy/Controller.java @@ -110,8 +110,7 @@ public class Controller { } break; case ControlMessage.TYPE_SET_CLIPBOARD: - boolean paste = (msg.getFlags() & ControlMessage.FLAGS_PASTE) != 0; - setClipboard(msg.getText(), paste); + setClipboard(msg.getText(), msg.getPaste()); break; case ControlMessage.TYPE_SET_SCREEN_POWER_MODE: if (device.supportsInputEvents()) { diff --git a/server/src/test/java/com/genymobile/scrcpy/ControlMessageReaderTest.java b/server/src/test/java/com/genymobile/scrcpy/ControlMessageReaderTest.java index 1e2b8266..5eb52760 100644 --- a/server/src/test/java/com/genymobile/scrcpy/ControlMessageReaderTest.java +++ b/server/src/test/java/com/genymobile/scrcpy/ControlMessageReaderTest.java @@ -230,9 +230,7 @@ public class ControlMessageReaderTest { Assert.assertEquals(ControlMessage.TYPE_SET_CLIPBOARD, event.getType()); Assert.assertEquals("testé", event.getText()); - - boolean parse = (event.getFlags() & ControlMessage.FLAGS_PASTE) != 0; - Assert.assertTrue(parse); + Assert.assertTrue(event.getPaste()); } @Test @@ -258,9 +256,7 @@ public class ControlMessageReaderTest { Assert.assertEquals(ControlMessage.TYPE_SET_CLIPBOARD, event.getType()); Assert.assertEquals(text, event.getText()); - - boolean parse = (event.getFlags() & ControlMessage.FLAGS_PASTE) != 0; - Assert.assertTrue(parse); + Assert.assertTrue(event.getPaste()); } @Test From 9d9dd1f143e6dae770263432a11946f1601be82e Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 17 Jul 2020 00:00:42 +0200 Subject: [PATCH 163/450] Make expression order consistent The condition "cmd" was always before "shift" in all expressions except 4. --- app/src/input_manager.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/src/input_manager.c b/app/src/input_manager.c index 7d500efe..465ba6e6 100644 --- a/app/src/input_manager.c +++ b/app/src/input_manager.c @@ -368,22 +368,22 @@ input_manager_process_key(struct input_manager *im, } return; case SDLK_f: - if (!shift && cmd && !repeat && down) { + if (cmd && !shift && !repeat && down) { screen_switch_fullscreen(im->screen); } return; case SDLK_x: - if (!shift && cmd && !repeat && down) { + if (cmd && !shift && !repeat && down) { screen_resize_to_fit(im->screen); } return; case SDLK_g: - if (!shift && cmd && !repeat && down) { + if (cmd && !shift && !repeat && down) { screen_resize_to_pixel_perfect(im->screen); } return; case SDLK_i: - if (!shift && cmd && !repeat && down) { + if (cmd && !shift && !repeat && down) { struct fps_counter *fps_counter = im->video_buffer->fps_counter; switch_fps_counter_state(fps_counter); From 63cb93d7d7d90354b834e71200eb43ee9228f2a3 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 17 Jul 2020 00:00:42 +0200 Subject: [PATCH 164/450] Use Ctrl for all shortcuts Remove the Cmd modifier on macOS, which was possible only for some shortcuts but not all. This paves the way to make the shortcut modifier customizable. --- README.md | 48 ++++++++++++++++----------------- app/src/cli.c | 45 ++++++++++++++----------------- app/src/input_manager.c | 59 +++++++++++++++-------------------------- 3 files changed, 66 insertions(+), 86 deletions(-) diff --git a/README.md b/README.md index 2c01a01e..3cb9021c 100644 --- a/README.md +++ b/README.md @@ -571,30 +571,30 @@ Also see [issue #14]. ## Shortcuts - | Action | Shortcut | Shortcut (macOS) - | ------------------------------------------- |:----------------------------- |:----------------------------- - | Switch fullscreen mode | `Ctrl`+`f` | `Cmd`+`f` - | Rotate display left | `Ctrl`+`←` _(left)_ | `Cmd`+`←` _(left)_ - | Rotate display right | `Ctrl`+`→` _(right)_ | `Cmd`+`→` _(right)_ - | Resize window to 1:1 (pixel-perfect) | `Ctrl`+`g` | `Cmd`+`g` - | Resize window to remove black borders | `Ctrl`+`x` \| _Double-click¹_ | `Cmd`+`x` \| _Double-click¹_ - | Click on `HOME` | `Ctrl`+`h` \| _Middle-click_ | `Ctrl`+`h` \| _Middle-click_ - | Click on `BACK` | `Ctrl`+`b` \| _Right-click²_ | `Cmd`+`b` \| _Right-click²_ - | Click on `APP_SWITCH` | `Ctrl`+`s` | `Cmd`+`s` - | Click on `MENU` | `Ctrl`+`m` | `Ctrl`+`m` - | Click on `VOLUME_UP` | `Ctrl`+`↑` _(up)_ | `Cmd`+`↑` _(up)_ - | Click on `VOLUME_DOWN` | `Ctrl`+`↓` _(down)_ | `Cmd`+`↓` _(down)_ - | Click on `POWER` | `Ctrl`+`p` | `Cmd`+`p` - | Power on | _Right-click²_ | _Right-click²_ - | Turn device screen off (keep mirroring) | `Ctrl`+`o` | `Cmd`+`o` - | Turn device screen on | `Ctrl`+`Shift`+`o` | `Cmd`+`Shift`+`o` - | Rotate device screen | `Ctrl`+`r` | `Cmd`+`r` - | Expand notification panel | `Ctrl`+`n` | `Cmd`+`n` - | Collapse notification panel | `Ctrl`+`Shift`+`n` | `Cmd`+`Shift`+`n` - | Copy device clipboard to computer | `Ctrl`+`c` | `Cmd`+`c` - | Paste computer clipboard to device | `Ctrl`+`v` | `Cmd`+`v` - | Copy computer clipboard to device and paste | `Ctrl`+`Shift`+`v` | `Cmd`+`Shift`+`v` - | Enable/disable FPS counter (on stdout) | `Ctrl`+`i` | `Cmd`+`i` + | Action | Shortcut + | ------------------------------------------- |:----------------------------- + | Switch fullscreen mode | `Ctrl`+`f` + | Rotate display left | `Ctrl`+`←` _(left)_ + | Rotate display right | `Ctrl`+`→` _(right)_ + | Resize window to 1:1 (pixel-perfect) | `Ctrl`+`g` + | Resize window to remove black borders | `Ctrl`+`x` \| _Double-click¹_ + | Click on `HOME` | `Ctrl`+`h` \| _Middle-click_ + | Click on `BACK` | `Ctrl`+`b` \| _Right-click²_ + | Click on `APP_SWITCH` | `Ctrl`+`s` + | Click on `MENU` | `Ctrl`+`m` + | Click on `VOLUME_UP` | `Ctrl`+`↑` _(up)_ + | Click on `VOLUME_DOWN` | `Ctrl`+`↓` _(down)_ + | Click on `POWER` | `Ctrl`+`p` + | Power on | _Right-click²_ + | Turn device screen off (keep mirroring) | `Ctrl`+`o` + | Turn device screen on | `Ctrl`+`Shift`+`o` + | Rotate device screen | `Ctrl`+`r` + | Expand notification panel | `Ctrl`+`n` + | Collapse notification panel | `Ctrl`+`Shift`+`n` + | Copy device clipboard to computer | `Ctrl`+`c` + | Paste computer clipboard to device | `Ctrl`+`v` + | Copy computer clipboard to device and paste | `Ctrl`+`Shift`+`v` + | Enable/disable FPS counter (on stdout) | `Ctrl`+`i` _¹Double-click on black borders to remove them._ _²Right-click turns the screen on if it was off, presses BACK otherwise._ diff --git a/app/src/cli.c b/app/src/cli.c index 9e07e912..4b65c98f 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -13,11 +13,6 @@ void scrcpy_print_usage(const char *arg0) { -#ifdef __APPLE__ -# define CTRL_OR_CMD "Cmd" -#else -# define CTRL_OR_CMD "Ctrl" -#endif fprintf(stderr, "Usage: %s [options]\n" "\n" @@ -190,19 +185,19 @@ scrcpy_print_usage(const char *arg0) { "\n" "Shortcuts:\n" "\n" - " " CTRL_OR_CMD "+f\n" + " Ctrl+f\n" " Switch fullscreen mode\n" "\n" - " " CTRL_OR_CMD "+Left\n" + " Ctrl+Left\n" " Rotate display left\n" "\n" - " " CTRL_OR_CMD "+Right\n" + " Ctrl+Right\n" " Rotate display right\n" "\n" - " " CTRL_OR_CMD "+g\n" + " Ctrl+g\n" " Resize window to 1:1 (pixel-perfect)\n" "\n" - " " CTRL_OR_CMD "+x\n" + " Ctrl+x\n" " Double-click on black borders\n" " Resize window to remove black borders\n" "\n" @@ -210,55 +205,55 @@ scrcpy_print_usage(const char *arg0) { " Middle-click\n" " Click on HOME\n" "\n" - " " CTRL_OR_CMD "+b\n" - " " CTRL_OR_CMD "+Backspace\n" + " Ctrl+b\n" + " Ctrl+Backspace\n" " Right-click (when screen is on)\n" " Click on BACK\n" "\n" - " " CTRL_OR_CMD "+s\n" + " Ctrl+s\n" " Click on APP_SWITCH\n" "\n" " Ctrl+m\n" " Click on MENU\n" "\n" - " " CTRL_OR_CMD "+Up\n" + " Ctrl+Up\n" " Click on VOLUME_UP\n" "\n" - " " CTRL_OR_CMD "+Down\n" + " Ctrl+Down\n" " Click on VOLUME_DOWN\n" "\n" - " " CTRL_OR_CMD "+p\n" + " Ctrl+p\n" " Click on POWER (turn screen on/off)\n" "\n" " Right-click (when screen is off)\n" " Power on\n" "\n" - " " CTRL_OR_CMD "+o\n" + " Ctrl+o\n" " Turn device screen off (keep mirroring)\n" "\n" - " " CTRL_OR_CMD "+Shift+o\n" + " Ctrl+Shift+o\n" " Turn device screen on\n" "\n" - " " CTRL_OR_CMD "+r\n" + " Ctrl+r\n" " Rotate device screen\n" "\n" - " " CTRL_OR_CMD "+n\n" + " Ctrl+n\n" " Expand notification panel\n" "\n" - " " CTRL_OR_CMD "+Shift+n\n" + " Ctrl+Shift+n\n" " Collapse notification panel\n" "\n" - " " CTRL_OR_CMD "+c\n" + " Ctrl+c\n" " Copy device clipboard to computer\n" "\n" - " " CTRL_OR_CMD "+v\n" + " Ctrl+v\n" " Paste computer clipboard to device\n" "\n" - " " CTRL_OR_CMD "+Shift+v\n" + " Ctrl+Shift+v\n" " Copy computer clipboard to device (and paste if the device\n" " runs Android >= 7)\n" "\n" - " " CTRL_OR_CMD "+i\n" + " Ctrl+i\n" " Enable/disable FPS counter (print frames/second in logs)\n" "\n" " Drag & drop APK file\n" diff --git a/app/src/input_manager.c b/app/src/input_manager.c index 465ba6e6..e16dee52 100644 --- a/app/src/input_manager.c +++ b/app/src/input_manager.c @@ -264,27 +264,16 @@ input_manager_process_key(struct input_manager *im, bool alt = event->keysym.mod & (KMOD_LALT | KMOD_RALT); bool meta = event->keysym.mod & (KMOD_LGUI | KMOD_RGUI); - // use Cmd on macOS, Ctrl on other platforms -#ifdef __APPLE__ - bool cmd = !ctrl && meta; -#else - if (meta) { - // no shortcuts involve Meta on platforms other than macOS, and it must - // not be forwarded to the device - return; - } - bool cmd = ctrl; // && !meta, already guaranteed -#endif - - if (alt) { - // no shortcuts involve Alt, and it must not be forwarded to the device + if (alt || meta) { + // no shortcuts involve Alt or Meta, and they must not be forwarded to + // the device return; } struct controller *controller = im->controller; // capture all Ctrl events - if (ctrl || cmd) { + if (ctrl) { SDL_Keycode keycode = event->keysym.sym; bool down = event->type == SDL_KEYDOWN; int action = down ? ACTION_DOWN : ACTION_UP; @@ -292,37 +281,33 @@ input_manager_process_key(struct input_manager *im, bool shift = event->keysym.mod & (KMOD_LSHIFT | KMOD_RSHIFT); switch (keycode) { case SDLK_h: - // Ctrl+h on all platform, since Cmd+h is already captured by - // the system on macOS to hide the window - if (control && ctrl && !meta && !shift && !repeat) { + if (control && ctrl && !shift && !repeat) { action_home(controller, action); } return; case SDLK_b: // fall-through case SDLK_BACKSPACE: - if (control && cmd && !shift && !repeat) { + if (control && ctrl && !shift && !repeat) { action_back(controller, action); } return; case SDLK_s: - if (control && cmd && !shift && !repeat) { + if (control && ctrl && !shift && !repeat) { action_app_switch(controller, action); } return; case SDLK_m: - // Ctrl+m on all platform, since Cmd+m is already captured by - // the system on macOS to minimize the window - if (control && ctrl && !meta && !shift && !repeat) { + if (control && ctrl && !shift && !repeat) { action_menu(controller, action); } return; case SDLK_p: - if (control && cmd && !shift && !repeat) { + if (control && ctrl && !shift && !repeat) { action_power(controller, action); } return; case SDLK_o: - if (control && cmd && !repeat && down) { + if (control && ctrl && !repeat && down) { enum screen_power_mode mode = shift ? SCREEN_POWER_MODE_NORMAL : SCREEN_POWER_MODE_OFF; @@ -330,34 +315,34 @@ input_manager_process_key(struct input_manager *im, } return; case SDLK_DOWN: - if (control && cmd && !shift) { + if (control && ctrl && !shift) { // forward repeated events action_volume_down(controller, action); } return; case SDLK_UP: - if (control && cmd && !shift) { + if (control && ctrl && !shift) { // forward repeated events action_volume_up(controller, action); } return; case SDLK_LEFT: - if (cmd && !shift && !repeat && down) { + if (ctrl && !shift && !repeat && down) { rotate_client_left(im->screen); } return; case SDLK_RIGHT: - if (cmd && !shift && !repeat && down) { + if (ctrl && !shift && !repeat && down) { rotate_client_right(im->screen); } return; case SDLK_c: - if (control && cmd && !shift && !repeat && down) { + if (control && ctrl && !shift && !repeat && down) { request_device_clipboard(controller); } return; case SDLK_v: - if (control && cmd && !repeat && down) { + if (control && ctrl && !repeat && down) { if (shift) { // store the text in the device clipboard and paste set_device_clipboard(controller, true); @@ -368,29 +353,29 @@ input_manager_process_key(struct input_manager *im, } return; case SDLK_f: - if (cmd && !shift && !repeat && down) { + if (ctrl && !shift && !repeat && down) { screen_switch_fullscreen(im->screen); } return; case SDLK_x: - if (cmd && !shift && !repeat && down) { + if (ctrl && !shift && !repeat && down) { screen_resize_to_fit(im->screen); } return; case SDLK_g: - if (cmd && !shift && !repeat && down) { + if (ctrl && !shift && !repeat && down) { screen_resize_to_pixel_perfect(im->screen); } return; case SDLK_i: - if (cmd && !shift && !repeat && down) { + if (ctrl && !shift && !repeat && down) { struct fps_counter *fps_counter = im->video_buffer->fps_counter; switch_fps_counter_state(fps_counter); } return; case SDLK_n: - if (control && cmd && !repeat && down) { + if (control && ctrl && !repeat && down) { if (shift) { collapse_notification_panel(controller); } else { @@ -399,7 +384,7 @@ input_manager_process_key(struct input_manager *im, } return; case SDLK_r: - if (control && cmd && !shift && !repeat && down) { + if (control && ctrl && !shift && !repeat && down) { rotate_device(controller); } return; From 1b76d9fd78c3a88a8503a72d4cd2f65bdb836aa4 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 17 Jul 2020 00:00:42 +0200 Subject: [PATCH 165/450] Customize shortcut modifier Add --shortcut-mod, and use Alt as default modifier. This paves the way to forward the Ctrl key to the device. --- README.md | 82 ++++++++++++-------- app/meson.build | 2 +- app/scrcpy.1 | 55 ++++++++------ app/src/cli.c | 162 ++++++++++++++++++++++++++++++++++------ app/src/cli.h | 5 ++ app/src/input_manager.c | 110 +++++++++++++++++++-------- app/src/input_manager.h | 14 +++- app/src/scrcpy.c | 11 ++- app/src/scrcpy.h | 21 ++++++ app/tests/test_cli.c | 39 ++++++++++ 10 files changed, 392 insertions(+), 109 deletions(-) diff --git a/README.md b/README.md index 3cb9021c..7eb5578a 100644 --- a/README.md +++ b/README.md @@ -354,7 +354,7 @@ scrcpy --fullscreen scrcpy -f # short version ``` -Fullscreen can then be toggled dynamically with `Ctrl`+`f`. +Fullscreen can then be toggled dynamically with `MOD`+`f`. #### Rotation @@ -370,17 +370,17 @@ Possibles values are: - `2`: 180 degrees - `3`: 90 degrees clockwise -The rotation can also be changed dynamically with `Ctrl`+`←` _(left)_ and -`Ctrl`+`→` _(right)_. +The rotation can also be changed dynamically with `MOD`+`←` _(left)_ and +`MOD`+`→` _(right)_. Note that _scrcpy_ manages 3 different rotations: - - `Ctrl`+`r` requests the device to switch between portrait and landscape (the + - `MOD`+`r` requests the device to switch between portrait and landscape (the current running app may refuse, if it does 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` (or `Ctrl`+`←`/`Ctrl`+`→`) rotates only the window content. This + - `--rotation` (or `MOD`+`←`/`MOD`+`→`) rotates only the window content. This affects only the display, not the recording. @@ -437,9 +437,9 @@ scrcpy --turn-screen-off scrcpy -S ``` -Or by pressing `Ctrl`+`o` at any time. +Or by pressing `MOD`+`o` at any time. -To turn it back on, press `Ctrl`+`Shift`+`o` (or `POWER`, `Ctrl`+`p`). +To turn it back on, press `MOD`+`Shift`+`o` (or `POWER`, `MOD`+`p`). It can be useful to also prevent the device to sleep: @@ -494,7 +494,7 @@ scrcpy --disable-screensaver #### Rotate device screen -Press `Ctrl`+`r` to switch between portrait and landscape modes. +Press `MOD`+`r` to switch between portrait and landscape modes. Note that it rotates only if the application in foreground supports the requested orientation. @@ -504,10 +504,10 @@ requested orientation. It is possible to synchronize clipboards between the computer and the device, in both directions: - - `Ctrl`+`c` copies the device clipboard to the computer clipboard; - - `Ctrl`+`Shift`+`v` copies the computer clipboard to the device clipboard (and + - `MOD`+`c` copies the device clipboard to the computer clipboard; + - `MOD`+`Shift`+`v` copies the computer clipboard to the device clipboard (and pastes if the device runs Android >= 7); - - `Ctrl`+`v` _pastes_ the computer clipboard as a sequence of text events (but + - `MOD`+`v` _pastes_ the computer clipboard as a sequence of text events (but breaks non-ASCII characters). Moreover, any time the Android clipboard changes, it is automatically @@ -571,30 +571,48 @@ Also see [issue #14]. ## Shortcuts +In the following list, `MOD` is the shortcut modifier. By default, it's (left) +`Alt`. + +It can be changed using `--shortcut-mod`. Possible keys are `lctrl`, `rctrl`, +`lalt`, `ralt`, `lsuper` and `rsuper`. For example: + +```bash +# use RCtrl for shortcuts +scrcpy --shortcut-mod=rctrl + +# use either LCtrl+LAlt or LSuper for shortcuts +scrcpy --shortcut-mod=lctrl+lalt,lsuper +``` + +_[Super] is typically the "Windows" or "Cmd" key._ + +[Super]: https://en.wikipedia.org/wiki/Super_key_(keyboard_button) + | Action | Shortcut | ------------------------------------------- |:----------------------------- - | Switch fullscreen mode | `Ctrl`+`f` - | Rotate display left | `Ctrl`+`←` _(left)_ - | Rotate display right | `Ctrl`+`→` _(right)_ - | Resize window to 1:1 (pixel-perfect) | `Ctrl`+`g` - | Resize window to remove black borders | `Ctrl`+`x` \| _Double-click¹_ - | Click on `HOME` | `Ctrl`+`h` \| _Middle-click_ - | Click on `BACK` | `Ctrl`+`b` \| _Right-click²_ - | Click on `APP_SWITCH` | `Ctrl`+`s` - | Click on `MENU` | `Ctrl`+`m` - | Click on `VOLUME_UP` | `Ctrl`+`↑` _(up)_ - | Click on `VOLUME_DOWN` | `Ctrl`+`↓` _(down)_ - | Click on `POWER` | `Ctrl`+`p` + | Switch fullscreen mode | `MOD`+`f` + | Rotate display left | `MOD`+`←` _(left)_ + | Rotate display right | `MOD`+`→` _(right)_ + | Resize window to 1:1 (pixel-perfect) | `MOD`+`g` + | Resize window to remove black borders | `MOD`+`x` \| _Double-click¹_ + | Click on `HOME` | `MOD`+`h` \| _Middle-click_ + | Click on `BACK` | `MOD`+`b` \| _Right-click²_ + | Click on `APP_SWITCH` | `MOD`+`s` + | Click on `MENU` | `MOD`+`m` + | Click on `VOLUME_UP` | `MOD`+`↑` _(up)_ + | Click on `VOLUME_DOWN` | `MOD`+`↓` _(down)_ + | Click on `POWER` | `MOD`+`p` | Power on | _Right-click²_ - | Turn device screen off (keep mirroring) | `Ctrl`+`o` - | Turn device screen on | `Ctrl`+`Shift`+`o` - | Rotate device screen | `Ctrl`+`r` - | Expand notification panel | `Ctrl`+`n` - | Collapse notification panel | `Ctrl`+`Shift`+`n` - | Copy device clipboard to computer | `Ctrl`+`c` - | Paste computer clipboard to device | `Ctrl`+`v` - | Copy computer clipboard to device and paste | `Ctrl`+`Shift`+`v` - | Enable/disable FPS counter (on stdout) | `Ctrl`+`i` + | Turn device screen off (keep mirroring) | `MOD`+`o` + | Turn device screen on | `MOD`+`Shift`+`o` + | Rotate device screen | `MOD`+`r` + | Expand notification panel | `MOD`+`n` + | Collapse notification panel | `MOD`+`Shift`+`n` + | Copy device clipboard to computer | `MOD`+`c` + | Paste computer clipboard to device | `MOD`+`v` + | Copy computer clipboard to device and paste | `MOD`+`Shift`+`v` + | Enable/disable FPS counter (on stdout) | `MOD`+`i` _¹Double-click on black borders to remove them._ _²Right-click turns the screen on if it was off, presses BACK otherwise._ diff --git a/app/meson.build b/app/meson.build index 505f0819..0163dd7f 100644 --- a/app/meson.build +++ b/app/meson.build @@ -186,7 +186,7 @@ if get_option('buildtype') == 'debug' exe = executable(t[0], t[1], include_directories: src_dir, dependencies: dependencies, - c_args: ['-DSDL_MAIN_HANDLED']) + c_args: ['-DSDL_MAIN_HANDLED', '-DSC_TEST']) test(t[0], exe) endforeach endif diff --git a/app/scrcpy.1 b/app/scrcpy.1 index 4e3c43f2..7ea778f3 100644 --- a/app/scrcpy.1 +++ b/app/scrcpy.1 @@ -149,6 +149,16 @@ Set the initial display rotation. Possibles values are 0, 1, 2 and 3. Each incre .BI "\-s, \-\-serial " number The device serial number. Mandatory only if several devices are connected to adb. +.TP +.BI "\-\-shortcut\-mod " key[+...]][,...] +Specify the modifiers to use for scrcpy shortcuts. Possible keys are "lctrl", "rctrl", "lalt", "ralt", "lsuper" and "rsuper". + +A shortcut can consist in several keys, separated by '+'. Several shortcuts can be specified, separated by ','. + +For example, to use either LCtrl+LAlt or LSuper for scrcpy shortcuts, pass "lctrl+lalt,lsuper". + +Default is "lalt" (left-Alt). + .TP .B \-S, \-\-turn\-screen\-off Turn the device screen off immediately. @@ -207,52 +217,55 @@ Default is 0 (automatic).\n .SH SHORTCUTS +In the following list, MOD is the shortcut modifier. By default, it's (left) +Alt, but it can be configured by \-\-shortcut-mod. + .TP -.B Ctrl+f +.B MOD+f Switch fullscreen mode .TP -.B Ctrl+Left +.B MOD+Left Rotate display left .TP -.B Ctrl+Right +.B MOD+Right Rotate display right .TP -.B Ctrl+g +.B MOD+g Resize window to 1:1 (pixel\-perfect) .TP -.B Ctrl+x, Double\-click on black borders +.B MOD+x, Double\-click on black borders Resize window to remove black borders .TP -.B Ctrl+h, Home, Middle\-click +.B MOD+h, Home, Middle\-click Click on HOME .TP -.B Ctrl+b, Ctrl+Backspace, Right\-click (when screen is on) +.B MOD+b, MOD+Backspace, Right\-click (when screen is on) Click on BACK .TP -.B Ctrl+s +.B MOD+s Click on APP_SWITCH .TP -.B Ctrl+m +.B MOD+m Click on MENU .TP -.B Ctrl+Up +.B MOD+Up Click on VOLUME_UP .TP -.B Ctrl+Down +.B MOD+Down Click on VOLUME_DOWN .TP -.B Ctrl+p +.B MOD+p Click on POWER (turn screen on/off) .TP @@ -260,39 +273,39 @@ Click on POWER (turn screen on/off) Turn screen on .TP -.B Ctrl+o +.B MOD+o Turn device screen off (keep mirroring) .TP -.B Ctrl+Shift+o +.B MOD+Shift+o Turn device screen on .TP -.B Ctrl+r +.B MOD+r Rotate device screen .TP -.B Ctrl+n +.B MOD+n Expand notification panel .TP -.B Ctrl+Shift+n +.B MOD+Shift+n Collapse notification panel .TP -.B Ctrl+c +.B MOD+c Copy device clipboard to computer .TP -.B Ctrl+v +.B MOD+v Paste computer clipboard to device .TP -.B Ctrl+Shift+v +.B MOD+Shift+v Copy computer clipboard to device (and paste if the device runs Android >= 7) .TP -.B Ctrl+i +.B MOD+i Enable/disable FPS counter (print frames/second in logs) .TP diff --git a/app/src/cli.c b/app/src/cli.c index 4b65c98f..de32810e 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -138,6 +138,19 @@ scrcpy_print_usage(const char *arg0) { " The device serial number. Mandatory only if several devices\n" " are connected to adb.\n" "\n" + " --shortcut-mod key[+...]][,...]\n" + " Specify the modifiers to use for scrcpy shortcuts.\n" + " Possible keys are \"lctrl\", \"rctrl\", \"lalt\", \"ralt\",\n" + " \"lsuper\" and \"rsuper\".\n" + "\n" + " A shortcut can consist in several keys, separated by '+'.\n" + " Several shortcuts can be specified, separated by ','.\n" + "\n" + " For example, to use either LCtrl+LAlt or LSuper for scrcpy\n" + " shortcuts, pass \"lctrl+lalt,lsuper\".\n" + "\n" + " Default is \"lalt\" (left-Alt).\n" + "\n" " -S, --turn-screen-off\n" " Turn the device screen off immediately.\n" "\n" @@ -185,75 +198,78 @@ scrcpy_print_usage(const char *arg0) { "\n" "Shortcuts:\n" "\n" - " Ctrl+f\n" + " In the following list, MOD is the shortcut modifier. By default,\n" + " it's (left) Alt, but it can be configured by --shortcut-mod.\n" + "\n" + " MOD+f\n" " Switch fullscreen mode\n" "\n" - " Ctrl+Left\n" + " MOD+Left\n" " Rotate display left\n" "\n" - " Ctrl+Right\n" + " MOD+Right\n" " Rotate display right\n" "\n" - " Ctrl+g\n" + " MOD+g\n" " Resize window to 1:1 (pixel-perfect)\n" "\n" - " Ctrl+x\n" + " MOD+x\n" " Double-click on black borders\n" " Resize window to remove black borders\n" "\n" - " Ctrl+h\n" + " MOD+h\n" " Middle-click\n" " Click on HOME\n" "\n" - " Ctrl+b\n" - " Ctrl+Backspace\n" + " MOD+b\n" + " MOD+Backspace\n" " Right-click (when screen is on)\n" " Click on BACK\n" "\n" - " Ctrl+s\n" + " MOD+s\n" " Click on APP_SWITCH\n" "\n" - " Ctrl+m\n" + " MOD+m\n" " Click on MENU\n" "\n" - " Ctrl+Up\n" + " MOD+Up\n" " Click on VOLUME_UP\n" "\n" - " Ctrl+Down\n" + " MOD+Down\n" " Click on VOLUME_DOWN\n" "\n" - " Ctrl+p\n" + " MOD+p\n" " Click on POWER (turn screen on/off)\n" "\n" " Right-click (when screen is off)\n" " Power on\n" "\n" - " Ctrl+o\n" + " MOD+o\n" " Turn device screen off (keep mirroring)\n" "\n" - " Ctrl+Shift+o\n" + " MOD+Shift+o\n" " Turn device screen on\n" "\n" - " Ctrl+r\n" + " MOD+r\n" " Rotate device screen\n" "\n" - " Ctrl+n\n" + " MOD+n\n" " Expand notification panel\n" "\n" - " Ctrl+Shift+n\n" + " MOD+Shift+n\n" " Collapse notification panel\n" "\n" - " Ctrl+c\n" + " MOD+c\n" " Copy device clipboard to computer\n" "\n" - " Ctrl+v\n" + " MOD+v\n" " Paste computer clipboard to device\n" "\n" - " Ctrl+Shift+v\n" + " MOD+Shift+v\n" " Copy computer clipboard to device (and paste if the device\n" " runs Android >= 7)\n" "\n" - " Ctrl+i\n" + " MOD+i\n" " Enable/disable FPS counter (print frames/second in logs)\n" "\n" " Drag & drop APK file\n" @@ -475,6 +491,101 @@ parse_log_level(const char *s, enum sc_log_level *log_level) { return false; } +// item is a list of mod keys separated by '+' (e.g. "lctrl+lalt") +// returns a bitwise-or of SC_MOD_* constants (or 0 on error) +static unsigned +parse_shortcut_mods_item(const char *item, size_t len) { + unsigned mod = 0; + + for (;;) { + char *plus = strchr(item, '+'); + // strchr() does not consider the "len" parameter, to it could find an + // occurrence too far in the string (there is no strnchr()) + bool has_plus = plus && plus < item + len; + + assert(!has_plus || plus > item); + size_t key_len = has_plus ? (size_t) (plus - item) : len; + +#define STREQ(literal, s, len) \ + ((sizeof(literal)-1 == len) && !memcmp(literal, s, len)) + + if (STREQ("lctrl", item, key_len)) { + mod |= SC_MOD_LCTRL; + } else if (STREQ("rctrl", item, key_len)) { + mod |= SC_MOD_RCTRL; + } else if (STREQ("lalt", item, key_len)) { + mod |= SC_MOD_LALT; + } else if (STREQ("ralt", item, key_len)) { + mod |= SC_MOD_RALT; + } else if (STREQ("lsuper", item, key_len)) { + mod |= SC_MOD_LSUPER; + } else if (STREQ("rsuper", item, key_len)) { + mod |= SC_MOD_RSUPER; + } else { + LOGW("Unknown modifier key: %.*s", (int) key_len, item); + return 0; + } +#undef STREQ + + if (!has_plus) { + break; + } + + item = plus + 1; + assert(len >= key_len + 1); + len -= key_len + 1; + } + + return mod; +} + +static bool +parse_shortcut_mods(const char *s, struct sc_shortcut_mods *mods) { + unsigned count = 0; + unsigned current = 0; + + // LCtrl+LAlt or RCtrl or LCtrl+RSuper: "lctrl+lalt,rctrl,lctrl+rsuper" + + for (;;) { + char *comma = strchr(s, ','); + if (comma && count == SC_MAX_SHORTCUT_MODS - 1) { + assert(count < SC_MAX_SHORTCUT_MODS); + LOGW("Too many shortcut modifiers alternatives"); + return false; + } + + assert(!comma || comma > s); + size_t limit = comma ? (size_t) (comma - s) : strlen(s); + + unsigned mod = parse_shortcut_mods_item(s, limit); + if (!mod) { + LOGE("Invalid modifier keys: %.*s", (int) limit, s); + return false; + } + + mods->data[current++] = mod; + ++count; + + if (!comma) { + break; + } + + s = comma + 1; + } + + mods->count = count; + + return true; +} + +#ifdef SC_TEST +// expose the function to unit-tests +bool +sc_parse_shortcut_mods(const char *s, struct sc_shortcut_mods *mods) { + return parse_shortcut_mods(s, mods); +} +#endif + static bool parse_record_format(const char *optarg, enum sc_record_format *format) { if (!strcmp(optarg, "mp4")) { @@ -526,6 +637,7 @@ guess_record_format(const char *filename) { #define OPT_CODEC_OPTIONS 1018 #define OPT_FORCE_ADB_FORWARD 1019 #define OPT_DISABLE_SCREENSAVER 1020 +#define OPT_SHORTCUT_MOD 1021 bool scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) { @@ -558,6 +670,7 @@ scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) { OPT_RENDER_EXPIRED_FRAMES}, {"rotation", required_argument, NULL, OPT_ROTATION}, {"serial", required_argument, NULL, 's'}, + {"shortcut-mod", required_argument, NULL, OPT_SHORTCUT_MOD}, {"show-touches", no_argument, NULL, 't'}, {"stay-awake", no_argument, NULL, 'w'}, {"turn-screen-off", no_argument, NULL, 'S'}, @@ -721,6 +834,11 @@ scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) { case OPT_DISABLE_SCREENSAVER: opts->disable_screensaver = true; break; + case OPT_SHORTCUT_MOD: + if (!parse_shortcut_mods(optarg, &opts->shortcut_mods)) { + return false; + } + break; default: // getopt prints the error message on stderr return false; diff --git a/app/src/cli.h b/app/src/cli.h index 2e2bfe93..73dfe228 100644 --- a/app/src/cli.h +++ b/app/src/cli.h @@ -18,4 +18,9 @@ scrcpy_print_usage(const char *arg0); bool scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]); +#ifdef SC_TEST +bool +sc_parse_shortcut_mods(const char *s, struct sc_shortcut_mods *mods); +#endif + #endif diff --git a/app/src/input_manager.c b/app/src/input_manager.c index e16dee52..b954fbd9 100644 --- a/app/src/input_manager.c +++ b/app/src/input_manager.c @@ -1,6 +1,7 @@ #include "input_manager.h" #include +#include #include "config.h" #include "event_converter.h" @@ -10,6 +11,64 @@ static const int ACTION_DOWN = 1; static const int ACTION_UP = 1 << 1; +#define SC_SDL_SHORTCUT_MODS_MASK (KMOD_CTRL | KMOD_ALT | KMOD_GUI) + +static inline uint16_t +to_sdl_mod(unsigned mod) { + uint16_t sdl_mod = 0; + if (mod & SC_MOD_LCTRL) { + sdl_mod |= KMOD_LCTRL; + } + if (mod & SC_MOD_RCTRL) { + sdl_mod |= KMOD_RCTRL; + } + if (mod & SC_MOD_LALT) { + sdl_mod |= KMOD_LALT; + } + if (mod & SC_MOD_RALT) { + sdl_mod |= KMOD_RALT; + } + if (mod & SC_MOD_LSUPER) { + sdl_mod |= KMOD_LGUI; + } + if (mod & SC_MOD_RSUPER) { + sdl_mod |= KMOD_RGUI; + } + return sdl_mod; +} + +static bool +is_shortcut_mod(struct input_manager *im, uint16_t sdl_mod) { + // keep only the relevant modifier keys + sdl_mod &= SC_SDL_SHORTCUT_MODS_MASK; + + assert(im->sdl_shortcut_mods.count); + assert(im->sdl_shortcut_mods.count < SC_MAX_SHORTCUT_MODS); + for (unsigned i = 0; i < im->sdl_shortcut_mods.count; ++i) { + if (im->sdl_shortcut_mods.data[i] == sdl_mod) { + return true; + } + } + + return false; +} + +void +input_manager_init(struct input_manager *im, bool prefer_text, + const struct sc_shortcut_mods *shortcut_mods) +{ + im->prefer_text = prefer_text; + + assert(shortcut_mods->count); + assert(shortcut_mods->count < SC_MAX_SHORTCUT_MODS); + for (unsigned i = 0; i < shortcut_mods->count; ++i) { + uint16_t sdl_mod = to_sdl_mod(shortcut_mods->data[i]); + assert(sdl_mod); + im->sdl_shortcut_mods.data[i] = sdl_mod; + } + im->sdl_shortcut_mods.count = shortcut_mods->count; +} + static void send_keycode(struct controller *controller, enum android_keycode keycode, int actions, const char *name) { @@ -258,22 +317,13 @@ input_manager_process_key(struct input_manager *im, const SDL_KeyboardEvent *event, bool control) { // control: indicates the state of the command-line option --no-control - // ctrl: the Ctrl key - bool ctrl = event->keysym.mod & (KMOD_LCTRL | KMOD_RCTRL); - bool alt = event->keysym.mod & (KMOD_LALT | KMOD_RALT); - bool meta = event->keysym.mod & (KMOD_LGUI | KMOD_RGUI); - - if (alt || meta) { - // no shortcuts involve Alt or Meta, and they must not be forwarded to - // the device - return; - } + bool smod = is_shortcut_mod(im, event->keysym.mod); struct controller *controller = im->controller; - // capture all Ctrl events - if (ctrl) { + // The shortcut modifier is pressed + if (smod) { SDL_Keycode keycode = event->keysym.sym; bool down = event->type == SDL_KEYDOWN; int action = down ? ACTION_DOWN : ACTION_UP; @@ -281,33 +331,33 @@ input_manager_process_key(struct input_manager *im, bool shift = event->keysym.mod & (KMOD_LSHIFT | KMOD_RSHIFT); switch (keycode) { case SDLK_h: - if (control && ctrl && !shift && !repeat) { + if (control && !shift && !repeat) { action_home(controller, action); } return; case SDLK_b: // fall-through case SDLK_BACKSPACE: - if (control && ctrl && !shift && !repeat) { + if (control && !shift && !repeat) { action_back(controller, action); } return; case SDLK_s: - if (control && ctrl && !shift && !repeat) { + if (control && !shift && !repeat) { action_app_switch(controller, action); } return; case SDLK_m: - if (control && ctrl && !shift && !repeat) { + if (control && !shift && !repeat) { action_menu(controller, action); } return; case SDLK_p: - if (control && ctrl && !shift && !repeat) { + if (control && !shift && !repeat) { action_power(controller, action); } return; case SDLK_o: - if (control && ctrl && !repeat && down) { + if (control && !repeat && down) { enum screen_power_mode mode = shift ? SCREEN_POWER_MODE_NORMAL : SCREEN_POWER_MODE_OFF; @@ -315,34 +365,34 @@ input_manager_process_key(struct input_manager *im, } return; case SDLK_DOWN: - if (control && ctrl && !shift) { + if (control && !shift) { // forward repeated events action_volume_down(controller, action); } return; case SDLK_UP: - if (control && ctrl && !shift) { + if (control && !shift) { // forward repeated events action_volume_up(controller, action); } return; case SDLK_LEFT: - if (ctrl && !shift && !repeat && down) { + if (!shift && !repeat && down) { rotate_client_left(im->screen); } return; case SDLK_RIGHT: - if (ctrl && !shift && !repeat && down) { + if (!shift && !repeat && down) { rotate_client_right(im->screen); } return; case SDLK_c: - if (control && ctrl && !shift && !repeat && down) { + if (control && !shift && !repeat && down) { request_device_clipboard(controller); } return; case SDLK_v: - if (control && ctrl && !repeat && down) { + if (control && !repeat && down) { if (shift) { // store the text in the device clipboard and paste set_device_clipboard(controller, true); @@ -353,29 +403,29 @@ input_manager_process_key(struct input_manager *im, } return; case SDLK_f: - if (ctrl && !shift && !repeat && down) { + if (!shift && !repeat && down) { screen_switch_fullscreen(im->screen); } return; case SDLK_x: - if (ctrl && !shift && !repeat && down) { + if (!shift && !repeat && down) { screen_resize_to_fit(im->screen); } return; case SDLK_g: - if (ctrl && !shift && !repeat && down) { + if (!shift && !repeat && down) { screen_resize_to_pixel_perfect(im->screen); } return; case SDLK_i: - if (ctrl && !shift && !repeat && down) { + if (!shift && !repeat && down) { struct fps_counter *fps_counter = im->video_buffer->fps_counter; switch_fps_counter_state(fps_counter); } return; case SDLK_n: - if (control && ctrl && !repeat && down) { + if (control && !repeat && down) { if (shift) { collapse_notification_panel(controller); } else { @@ -384,7 +434,7 @@ input_manager_process_key(struct input_manager *im, } return; case SDLK_r: - if (control && ctrl && !shift && !repeat && down) { + if (control && !shift && !repeat && down) { rotate_device(controller); } return; diff --git a/app/src/input_manager.h b/app/src/input_manager.h index 90b74fec..c854664a 100644 --- a/app/src/input_manager.h +++ b/app/src/input_manager.h @@ -3,12 +3,15 @@ #include +#include + #include "config.h" #include "common.h" #include "controller.h" #include "fps_counter.h" -#include "video_buffer.h" +#include "scrcpy.h" #include "screen.h" +#include "video_buffer.h" struct input_manager { struct controller *controller; @@ -20,8 +23,17 @@ struct input_manager { unsigned repeat; bool prefer_text; + + struct { + unsigned data[SC_MAX_SHORTCUT_MODS]; + unsigned count; + } sdl_shortcut_mods; }; +void +input_manager_init(struct input_manager *im, bool prefer_text, + const struct sc_shortcut_mods *shortcut_mods); + void input_manager_process_text_input(struct input_manager *im, const SDL_TextInputEvent *event); diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index 889cbb87..06a3b3d1 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -49,7 +49,13 @@ static struct input_manager input_manager = { .video_buffer = &video_buffer, .screen = &screen, .repeat = 0, - .prefer_text = false, // initialized later + + // initialized later + .prefer_text = false, + .sdl_shortcut_mods = { + .data = {0}, + .count = 0, + }, }; #ifdef _WIN32 @@ -437,7 +443,8 @@ scrcpy(const struct scrcpy_options *options) { } } - input_manager.prefer_text = options->prefer_text; + input_manager_init(&input_manager, options->prefer_text, + &options->shortcut_mods); ret = event_loop(options->display, options->control); LOGD("quit..."); diff --git a/app/src/scrcpy.h b/app/src/scrcpy.h index d7eb2f58..220fcc2c 100644 --- a/app/src/scrcpy.h +++ b/app/src/scrcpy.h @@ -20,6 +20,22 @@ enum sc_record_format { SC_RECORD_FORMAT_MKV, }; +#define SC_MAX_SHORTCUT_MODS 8 + +enum sc_shortcut_mod { + SC_MOD_LCTRL = 1 << 0, + SC_MOD_RCTRL = 1 << 1, + SC_MOD_LALT = 1 << 2, + SC_MOD_RALT = 1 << 3, + SC_MOD_LSUPER = 1 << 4, + SC_MOD_RSUPER = 1 << 5, +}; + +struct sc_shortcut_mods { + unsigned data[SC_MAX_SHORTCUT_MODS]; + unsigned count; +}; + struct sc_port_range { uint16_t first; uint16_t last; @@ -38,6 +54,7 @@ struct scrcpy_options { enum sc_log_level log_level; enum sc_record_format record_format; struct sc_port_range port_range; + struct sc_shortcut_mods shortcut_mods; uint16_t max_size; uint32_t bit_rate; uint16_t max_fps; @@ -77,6 +94,10 @@ struct scrcpy_options { .first = DEFAULT_LOCAL_PORT_RANGE_FIRST, \ .last = DEFAULT_LOCAL_PORT_RANGE_LAST, \ }, \ + .shortcut_mods = { \ + .data = {SC_MOD_LALT}, \ + .count = 1, \ + }, \ .max_size = DEFAULT_MAX_SIZE, \ .bit_rate = DEFAULT_BIT_RATE, \ .max_fps = 0, \ diff --git a/app/tests/test_cli.c b/app/tests/test_cli.c index 7a7d408d..1024dba6 100644 --- a/app/tests/test_cli.c +++ b/app/tests/test_cli.c @@ -3,6 +3,7 @@ #include "cli.h" #include "common.h" +#include "scrcpy.h" static void test_flag_version(void) { struct scrcpy_cli_args args = { @@ -122,6 +123,43 @@ static void test_options2(void) { assert(opts->record_format == SC_RECORD_FORMAT_MP4); } +static void test_parse_shortcut_mods(void) { + struct sc_shortcut_mods mods; + bool ok; + + ok = sc_parse_shortcut_mods("lctrl", &mods); + assert(ok); + assert(mods.count == 1); + assert(mods.data[0] == SC_MOD_LCTRL); + + ok = sc_parse_shortcut_mods("lctrl+lalt", &mods); + assert(ok); + assert(mods.count == 1); + assert(mods.data[0] == (SC_MOD_LCTRL | SC_MOD_LALT)); + + ok = sc_parse_shortcut_mods("rctrl,lalt", &mods); + assert(ok); + assert(mods.count == 2); + assert(mods.data[0] == SC_MOD_RCTRL); + assert(mods.data[1] == SC_MOD_LALT); + + ok = sc_parse_shortcut_mods("lsuper,rsuper+lalt,lctrl+rctrl+ralt", &mods); + assert(ok); + assert(mods.count == 3); + assert(mods.data[0] == SC_MOD_LSUPER); + assert(mods.data[1] == (SC_MOD_RSUPER | SC_MOD_LALT)); + assert(mods.data[2] == (SC_MOD_LCTRL | SC_MOD_RCTRL | SC_MOD_RALT)); + + ok = sc_parse_shortcut_mods("", &mods); + assert(!ok); + + ok = sc_parse_shortcut_mods("lctrl+", &mods); + assert(!ok); + + ok = sc_parse_shortcut_mods("lctrl,", &mods); + assert(!ok); +} + int main(int argc, char *argv[]) { (void) argc; (void) argv; @@ -130,5 +168,6 @@ int main(int argc, char *argv[]) { test_flag_help(); test_options(); test_options2(); + test_parse_shortcut_mods(); return 0; }; From e4bb7c1d1feb51e3277db086207833611fc20603 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 17 Jul 2020 00:00:42 +0200 Subject: [PATCH 166/450] Accept Super as shortcut modifier In addition to (left) Alt, also accept (left) Super. This is especially convenient for macOS users (Super is the Cmd key). --- README.md | 2 +- app/scrcpy.1 | 4 ++-- app/src/cli.c | 5 +++-- app/src/scrcpy.h | 4 ++-- 4 files changed, 8 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 7eb5578a..3aa9180c 100644 --- a/README.md +++ b/README.md @@ -572,7 +572,7 @@ Also see [issue #14]. ## Shortcuts In the following list, `MOD` is the shortcut modifier. By default, it's (left) -`Alt`. +`Alt` or (left) `Super`. It can be changed using `--shortcut-mod`. Possible keys are `lctrl`, `rctrl`, `lalt`, `ralt`, `lsuper` and `rsuper`. For example: diff --git a/app/scrcpy.1 b/app/scrcpy.1 index 7ea778f3..d79b3964 100644 --- a/app/scrcpy.1 +++ b/app/scrcpy.1 @@ -157,7 +157,7 @@ A shortcut can consist in several keys, separated by '+'. Several shortcuts can For example, to use either LCtrl+LAlt or LSuper for scrcpy shortcuts, pass "lctrl+lalt,lsuper". -Default is "lalt" (left-Alt). +Default is "lalt,lsuper" (left-Alt or left-Super). .TP .B \-S, \-\-turn\-screen\-off @@ -218,7 +218,7 @@ Default is 0 (automatic).\n .SH SHORTCUTS In the following list, MOD is the shortcut modifier. By default, it's (left) -Alt, but it can be configured by \-\-shortcut-mod. +Alt or (left) Super, but it can be configured by \-\-shortcut-mod. .TP .B MOD+f diff --git a/app/src/cli.c b/app/src/cli.c index de32810e..216763a2 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -149,7 +149,7 @@ scrcpy_print_usage(const char *arg0) { " For example, to use either LCtrl+LAlt or LSuper for scrcpy\n" " shortcuts, pass \"lctrl+lalt,lsuper\".\n" "\n" - " Default is \"lalt\" (left-Alt).\n" + " Default is \"lalt,lsuper\" (left-Alt or left-Super).\n" "\n" " -S, --turn-screen-off\n" " Turn the device screen off immediately.\n" @@ -199,7 +199,8 @@ scrcpy_print_usage(const char *arg0) { "Shortcuts:\n" "\n" " In the following list, MOD is the shortcut modifier. By default,\n" - " it's (left) Alt, but it can be configured by --shortcut-mod.\n" + " it's (left) Alt or (left) Super, but it can be configured by\n" + " --shortcut-mod.\n" "\n" " MOD+f\n" " Switch fullscreen mode\n" diff --git a/app/src/scrcpy.h b/app/src/scrcpy.h index 220fcc2c..05af002f 100644 --- a/app/src/scrcpy.h +++ b/app/src/scrcpy.h @@ -95,8 +95,8 @@ struct scrcpy_options { .last = DEFAULT_LOCAL_PORT_RANGE_LAST, \ }, \ .shortcut_mods = { \ - .data = {SC_MOD_LALT}, \ - .count = 1, \ + .data = {SC_MOD_LALT, SC_MOD_LSUPER}, \ + .count = 2, \ }, \ .max_size = DEFAULT_MAX_SIZE, \ .bit_rate = DEFAULT_BIT_RATE, \ From a5f8b577c50e9498c43f7d4d9a25287673244c0f Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 17 Jul 2020 01:23:08 +0200 Subject: [PATCH 167/450] Ignore text events for shortcuts Pressing Alt+c generates a text event containing "c", so "c" was sent to the device when --prefer-text was enabled. Ignore text events when the mod state matches a shortcut modifier. --- app/src/input_manager.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/src/input_manager.c b/app/src/input_manager.c index b954fbd9..78cf19a7 100644 --- a/app/src/input_manager.c +++ b/app/src/input_manager.c @@ -269,6 +269,10 @@ rotate_client_right(struct screen *screen) { void input_manager_process_text_input(struct input_manager *im, const SDL_TextInputEvent *event) { + if (is_shortcut_mod(im, SDL_GetModState())) { + // A shortcut must never generate text events + return; + } if (!im->prefer_text) { char c = event->text[0]; if (isalpha(c) || c == ' ') { From e6e528f228f762336ad6b37679c592cf46f6c4cf Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 17 Jul 2020 00:00:42 +0200 Subject: [PATCH 168/450] Forward Ctrl to the device Now that the scrcpy shortcut modifier is Alt by default (and can be configured), forward Ctrl to the device. This allows to trigger Android shortcuts. Fixes #555 --- app/src/event_converter.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/app/src/event_converter.c b/app/src/event_converter.c index 1054dcf9..fb835fa3 100644 --- a/app/src/event_converter.c +++ b/app/src/event_converter.c @@ -92,6 +92,8 @@ convert_keycode(SDL_Keycode from, enum android_keycode *to, uint16_t mod, MAP(SDLK_LEFT, AKEYCODE_DPAD_LEFT); MAP(SDLK_DOWN, AKEYCODE_DPAD_DOWN); MAP(SDLK_UP, AKEYCODE_DPAD_UP); + MAP(SDLK_LCTRL, AKEYCODE_CTRL_LEFT); + MAP(SDLK_RCTRL, AKEYCODE_CTRL_RIGHT); } if (!(mod & (KMOD_NUM | KMOD_SHIFT))) { @@ -111,8 +113,8 @@ convert_keycode(SDL_Keycode from, enum android_keycode *to, uint16_t mod, } } - if (prefer_text) { - // do not forward alpha and space key events + if (prefer_text && !(mod & KMOD_CTRL)) { + // do not forward alpha and space key events (unless Ctrl is pressed) return false; } From d4ca85d6a83ee7bdb3dce648b9739374ae770662 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 17 Jul 2020 00:00:42 +0200 Subject: [PATCH 169/450] Forward Shift to the device This allows to select text using Shift+(arrow keys). Fixes #942 --- app/src/event_converter.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/src/event_converter.c b/app/src/event_converter.c index fb835fa3..ab48898d 100644 --- a/app/src/event_converter.c +++ b/app/src/event_converter.c @@ -94,6 +94,8 @@ convert_keycode(SDL_Keycode from, enum android_keycode *to, uint16_t mod, MAP(SDLK_UP, AKEYCODE_DPAD_UP); MAP(SDLK_LCTRL, AKEYCODE_CTRL_LEFT); MAP(SDLK_RCTRL, AKEYCODE_CTRL_RIGHT); + MAP(SDLK_LSHIFT, AKEYCODE_SHIFT_LEFT); + MAP(SDLK_RSHIFT, AKEYCODE_SHIFT_RIGHT); } if (!(mod & (KMOD_NUM | KMOD_SHIFT))) { From 7683be8159c7372c7def0b868ba76a426fa7426e Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 17 Jul 2020 00:00:42 +0200 Subject: [PATCH 170/450] Synchronize clipboard on Ctrl+v Pressing Ctrl+v on the device will typically paste the clipboard content. Before sending the key event, synchronize the computer clipboard to the device clipboard to allow seamless copy-paste. --- app/src/input_manager.c | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/app/src/input_manager.c b/app/src/input_manager.c index 78cf19a7..625f79a7 100644 --- a/app/src/input_manager.c +++ b/app/src/input_manager.c @@ -326,13 +326,15 @@ input_manager_process_key(struct input_manager *im, struct controller *controller = im->controller; + SDL_Keycode keycode = event->keysym.sym; + bool down = event->type == SDL_KEYDOWN; + bool ctrl = event->keysym.mod & KMOD_CTRL; + bool shift = event->keysym.mod & KMOD_SHIFT; + bool repeat = event->repeat; + // The shortcut modifier is pressed if (smod) { - SDL_Keycode keycode = event->keysym.sym; - bool down = event->type == SDL_KEYDOWN; int action = down ? ACTION_DOWN : ACTION_UP; - bool repeat = event->repeat; - bool shift = event->keysym.mod & (KMOD_LSHIFT | KMOD_RSHIFT); switch (keycode) { case SDLK_h: if (control && !shift && !repeat) { @@ -457,6 +459,12 @@ input_manager_process_key(struct input_manager *im, im->repeat = 0; } + if (ctrl && !shift && keycode == SDLK_v && down && !repeat) { + // Synchronize the computer clipboard to the device clipboard before + // sending Ctrl+v, to allow seamless copy-paste. + set_device_clipboard(controller, false); + } + struct control_msg msg; if (convert_input_key(event, &msg, im->prefer_text, im->repeat)) { if (!controller_push_msg(controller, &msg)) { From 1223a72eb83ebafb878a25356250896c45b5cefa Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 17 Jul 2020 00:00:42 +0200 Subject: [PATCH 171/450] Set device clipboard only if necessary Do not explicitly set the clipboard text if it already contains the expected content. This avoids possible copy-paste loops between the computer and the device. --- server/src/main/java/com/genymobile/scrcpy/Device.java | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/server/src/main/java/com/genymobile/scrcpy/Device.java b/server/src/main/java/com/genymobile/scrcpy/Device.java index 375c9ed6..de551f35 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Device.java +++ b/server/src/main/java/com/genymobile/scrcpy/Device.java @@ -221,6 +221,16 @@ public final class Device { if (clipboardManager == null) { return false; } + + String currentClipboard = getClipboardText(); + if (currentClipboard == null || currentClipboard.equals(text)) { + // The clipboard already contains the requested text. + // Since pasting text from the computer involves setting the device clipboard, it could be set twice on a copy-paste. This would cause + // the clipboard listeners to be notified twice, and that would flood the Android keyboard clipboard history. To workaround this + // problem, do not explicitly set the clipboard text if it already contains the expected content. + return false; + } + isSettingClipboard.set(true); boolean ok = clipboardManager.setText(text); isSettingClipboard.set(false); From 20d39250997635ef882df4e2c11144e5f9f82448 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 17 Jul 2020 00:00:42 +0200 Subject: [PATCH 172/450] Set computer clipboard only if necessary Do not explicitly set the clipboard text if it already contains the expected content. Even if copy-paste loops are avoided by the previous commit, this avoids to trigger a clipboard change on the computer-side. Refs #1580 --- app/src/receiver.c | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/app/src/receiver.c b/app/src/receiver.c index 5ecff3b7..307eb5d5 100644 --- a/app/src/receiver.c +++ b/app/src/receiver.c @@ -25,10 +25,19 @@ receiver_destroy(struct receiver *receiver) { static void process_msg(struct device_msg *msg) { switch (msg->type) { - case DEVICE_MSG_TYPE_CLIPBOARD: + case DEVICE_MSG_TYPE_CLIPBOARD: { + char *current = SDL_GetClipboardText(); + bool same = current && !strcmp(current, msg->clipboard.text); + SDL_free(current); + if (same) { + LOGD("Computer clipboard unchanged"); + return; + } + LOGI("Device clipboard copied"); SDL_SetClipboardText(msg->clipboard.text); break; + } } } From bccd12bf5c268bdf096ef81971f13703c97bf474 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 17 Jul 2020 00:00:42 +0200 Subject: [PATCH 173/450] Remove "get clipboard" call Now that every device clipboard change is automatically synchronized to the computer, the "get clipboard" request (bound to MOD+c) is useless. --- README.md | 2 -- app/scrcpy.1 | 4 ---- app/src/cli.c | 3 --- app/src/input_manager.c | 15 --------------- 4 files changed, 24 deletions(-) diff --git a/README.md b/README.md index 3aa9180c..01c5df71 100644 --- a/README.md +++ b/README.md @@ -504,7 +504,6 @@ requested orientation. It is possible to synchronize clipboards between the computer and the device, in both directions: - - `MOD`+`c` copies the device clipboard to the computer clipboard; - `MOD`+`Shift`+`v` copies the computer clipboard to the device clipboard (and pastes if the device runs Android >= 7); - `MOD`+`v` _pastes_ the computer clipboard as a sequence of text events (but @@ -609,7 +608,6 @@ _[Super] is typically the "Windows" or "Cmd" key._ | Rotate device screen | `MOD`+`r` | Expand notification panel | `MOD`+`n` | Collapse notification panel | `MOD`+`Shift`+`n` - | Copy device clipboard to computer | `MOD`+`c` | Paste computer clipboard to device | `MOD`+`v` | Copy computer clipboard to device and paste | `MOD`+`Shift`+`v` | Enable/disable FPS counter (on stdout) | `MOD`+`i` diff --git a/app/scrcpy.1 b/app/scrcpy.1 index d79b3964..0353f59b 100644 --- a/app/scrcpy.1 +++ b/app/scrcpy.1 @@ -292,10 +292,6 @@ Expand notification panel .B MOD+Shift+n Collapse notification panel -.TP -.B MOD+c -Copy device clipboard to computer - .TP .B MOD+v Paste computer clipboard to device diff --git a/app/src/cli.c b/app/src/cli.c index 216763a2..95525401 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -260,9 +260,6 @@ scrcpy_print_usage(const char *arg0) { " MOD+Shift+n\n" " Collapse notification panel\n" "\n" - " MOD+c\n" - " Copy device clipboard to computer\n" - "\n" " MOD+v\n" " Paste computer clipboard to device\n" "\n" diff --git a/app/src/input_manager.c b/app/src/input_manager.c index 625f79a7..099906cb 100644 --- a/app/src/input_manager.c +++ b/app/src/input_manager.c @@ -160,16 +160,6 @@ collapse_notification_panel(struct controller *controller) { } } -static void -request_device_clipboard(struct controller *controller) { - struct control_msg msg; - msg.type = CONTROL_MSG_TYPE_GET_CLIPBOARD; - - if (!controller_push_msg(controller, &msg)) { - LOGW("Could not request device clipboard"); - } -} - static void set_device_clipboard(struct controller *controller, bool paste) { char *text = SDL_GetClipboardText(); @@ -392,11 +382,6 @@ input_manager_process_key(struct input_manager *im, rotate_client_right(im->screen); } return; - case SDLK_c: - if (control && !shift && !repeat && down) { - request_device_clipboard(controller); - } - return; case SDLK_v: if (control && !repeat && down) { if (shift) { From 8f64a5984b35af115aeb106ac7cb3731f83472ad Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 17 Jul 2020 00:00:42 +0200 Subject: [PATCH 174/450] Change "resize to fit" shortcut to MOD+w For convenience, MOD+x will be used for injecting the CUT keycode. --- README.md | 2 +- app/scrcpy.1 | 2 +- app/src/cli.c | 2 +- app/src/input_manager.c | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 01c5df71..5bfd32d4 100644 --- a/README.md +++ b/README.md @@ -594,7 +594,7 @@ _[Super] is typically the "Windows" or "Cmd" key._ | Rotate display left | `MOD`+`←` _(left)_ | Rotate display right | `MOD`+`→` _(right)_ | Resize window to 1:1 (pixel-perfect) | `MOD`+`g` - | Resize window to remove black borders | `MOD`+`x` \| _Double-click¹_ + | Resize window to remove black borders | `MOD`+`w` \| _Double-click¹_ | Click on `HOME` | `MOD`+`h` \| _Middle-click_ | Click on `BACK` | `MOD`+`b` \| _Right-click²_ | Click on `APP_SWITCH` | `MOD`+`s` diff --git a/app/scrcpy.1 b/app/scrcpy.1 index 0353f59b..ee32c8fb 100644 --- a/app/scrcpy.1 +++ b/app/scrcpy.1 @@ -237,7 +237,7 @@ Rotate display right Resize window to 1:1 (pixel\-perfect) .TP -.B MOD+x, Double\-click on black borders +.B MOD+w, Double\-click on black borders Resize window to remove black borders .TP diff --git a/app/src/cli.c b/app/src/cli.c index 95525401..0662a552 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -214,7 +214,7 @@ scrcpy_print_usage(const char *arg0) { " MOD+g\n" " Resize window to 1:1 (pixel-perfect)\n" "\n" - " MOD+x\n" + " MOD+w\n" " Double-click on black borders\n" " Resize window to remove black borders\n" "\n" diff --git a/app/src/input_manager.c b/app/src/input_manager.c index 099906cb..580f1a67 100644 --- a/app/src/input_manager.c +++ b/app/src/input_manager.c @@ -398,7 +398,7 @@ input_manager_process_key(struct input_manager *im, screen_switch_fullscreen(im->screen); } return; - case SDLK_x: + case SDLK_w: if (!shift && !repeat && down) { screen_resize_to_fit(im->screen); } From 56a115b5c5971b7035609d5e0e3eabf97e91f874 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 17 Jul 2020 00:00:42 +0200 Subject: [PATCH 175/450] Add shortcuts for COPY and CUT Send COPY and CUT on MOD+c and MOD+x (only supported for Android >= 7). The shortcuts Ctrl+c and Ctrl+x should generally also work (even before Android 7), but the active Android app may use them for other actions instead. --- README.md | 5 ++++- app/scrcpy.1 | 8 ++++++++ app/src/cli.c | 6 ++++++ app/src/input_manager.c | 20 ++++++++++++++++++++ 4 files changed, 38 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 5bfd32d4..96d3926b 100644 --- a/README.md +++ b/README.md @@ -608,12 +608,15 @@ _[Super] is typically the "Windows" or "Cmd" key._ | Rotate device screen | `MOD`+`r` | Expand notification panel | `MOD`+`n` | Collapse notification panel | `MOD`+`Shift`+`n` + | Copy to clipboard³ | `MOD`+`c` + | Cut to clipboard³ | `MOD`+`x` | Paste computer clipboard to device | `MOD`+`v` | Copy computer clipboard to device and paste | `MOD`+`Shift`+`v` | Enable/disable FPS counter (on stdout) | `MOD`+`i` _¹Double-click on black borders to remove them._ -_²Right-click turns the screen on if it was off, presses BACK otherwise._ +_²Right-click turns the screen on if it was off, presses BACK otherwise._ +_³Only on Android >= 7._ ## Custom paths diff --git a/app/scrcpy.1 b/app/scrcpy.1 index ee32c8fb..9193171e 100644 --- a/app/scrcpy.1 +++ b/app/scrcpy.1 @@ -292,6 +292,14 @@ Expand notification panel .B MOD+Shift+n Collapse notification panel +.TP +.B Mod+c +Copy to clipboard (inject COPY keycode, Android >= 7 only) + +.TP +.B Mod+x +Cut to clipboard (inject CUT keycode, Android >= 7 only) + .TP .B MOD+v Paste computer clipboard to device diff --git a/app/src/cli.c b/app/src/cli.c index 0662a552..ff810aed 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -260,6 +260,12 @@ scrcpy_print_usage(const char *arg0) { " MOD+Shift+n\n" " Collapse notification panel\n" "\n" + " MOD+c\n" + " Copy to clipboard (inject COPY keycode, Android >= 7 only)\n" + "\n" + " MOD+x\n" + " Cut to clipboard (inject CUT keycode, Android >= 7 only)\n" + "\n" " MOD+v\n" " Paste computer clipboard to device\n" "\n" diff --git a/app/src/input_manager.c b/app/src/input_manager.c index 580f1a67..75808f75 100644 --- a/app/src/input_manager.c +++ b/app/src/input_manager.c @@ -129,6 +129,16 @@ action_menu(struct controller *controller, int actions) { send_keycode(controller, AKEYCODE_MENU, actions, "MENU"); } +static inline void +action_copy(struct controller *controller, int actions) { + send_keycode(controller, AKEYCODE_COPY, actions, "COPY"); +} + +static inline void +action_cut(struct controller *controller, int actions) { + send_keycode(controller, AKEYCODE_CUT, actions, "CUT"); +} + // turn the screen on if it was off, press BACK otherwise static void press_back_or_turn_screen_on(struct controller *controller) { @@ -382,6 +392,16 @@ input_manager_process_key(struct input_manager *im, rotate_client_right(im->screen); } return; + case SDLK_c: + if (control && !shift && !repeat) { + action_copy(controller, action); + } + return; + case SDLK_x: + if (control && !shift && !repeat) { + action_cut(controller, action); + } + return; case SDLK_v: if (control && !repeat && down) { if (shift) { From 7ad47dfaab57a7e6c7462d7e6a2798a5f122ebaf Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 17 Jul 2020 00:00:42 +0200 Subject: [PATCH 176/450] Swap paste shortcuts For consistency with MOD+c and MOD+x, use MOD+v to inject PASTE. Use Mod+Shift+v to inject clipboard content as a sequence of text events. --- README.md | 4 ++-- app/scrcpy.1 | 4 ++-- app/src/cli.c | 6 +++--- app/src/input_manager.c | 6 +++--- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 96d3926b..c20be508 100644 --- a/README.md +++ b/README.md @@ -610,8 +610,8 @@ _[Super] is typically the "Windows" or "Cmd" key._ | Collapse notification panel | `MOD`+`Shift`+`n` | Copy to clipboard³ | `MOD`+`c` | Cut to clipboard³ | `MOD`+`x` - | Paste computer clipboard to device | `MOD`+`v` - | Copy computer clipboard to device and paste | `MOD`+`Shift`+`v` + | Synchronize clipboards and paste³ | `MOD`+`v` + | Inject computer clipboard text | `MOD`+`Shift`+`v` | Enable/disable FPS counter (on stdout) | `MOD`+`i` _¹Double-click on black borders to remove them._ diff --git a/app/scrcpy.1 b/app/scrcpy.1 index 9193171e..728d14fe 100644 --- a/app/scrcpy.1 +++ b/app/scrcpy.1 @@ -302,11 +302,11 @@ Cut to clipboard (inject CUT keycode, Android >= 7 only) .TP .B MOD+v -Paste computer clipboard to device +Copy computer clipboard to device, then paste (inject PASTE keycode, Android >= 7 only) .TP .B MOD+Shift+v -Copy computer clipboard to device (and paste if the device runs Android >= 7) +Inject computer clipboard text as a sequence of key events .TP .B MOD+i diff --git a/app/src/cli.c b/app/src/cli.c index ff810aed..b75ab41a 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -267,11 +267,11 @@ scrcpy_print_usage(const char *arg0) { " Cut to clipboard (inject CUT keycode, Android >= 7 only)\n" "\n" " MOD+v\n" - " Paste computer clipboard to device\n" + " Copy computer clipboard to device, then paste (inject PASTE\n" + " keycode, Android >= 7 only)\n" "\n" " MOD+Shift+v\n" - " Copy computer clipboard to device (and paste if the device\n" - " runs Android >= 7)\n" + " Inject computer clipboard text as a sequence of key events\n" "\n" " MOD+i\n" " Enable/disable FPS counter (print frames/second in logs)\n" diff --git a/app/src/input_manager.c b/app/src/input_manager.c index 75808f75..ec1d5a4e 100644 --- a/app/src/input_manager.c +++ b/app/src/input_manager.c @@ -405,11 +405,11 @@ input_manager_process_key(struct input_manager *im, case SDLK_v: if (control && !repeat && down) { if (shift) { - // store the text in the device clipboard and paste - set_device_clipboard(controller, true); - } else { // inject the text as input events clipboard_paste(controller); + } else { + // store the text in the device clipboard and paste + set_device_clipboard(controller, true); } } return; From d8b3ba170cb005e67be7dc866aa91134c4101edd Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 17 Jul 2020 00:00:42 +0200 Subject: [PATCH 177/450] Update copy-paste section in README Update documentation regarding the recent copy-paste changes. --- README.md | 35 +++++++++++++++++++++++++++-------- 1 file changed, 27 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index c20be508..0a8df973 100644 --- a/README.md +++ b/README.md @@ -501,16 +501,35 @@ requested orientation. #### Copy-paste -It is possible to synchronize clipboards between the computer and the device, in -both directions: +Any time the Android clipboard changes, it is automatically synchronized to the +computer clipboard. - - `MOD`+`Shift`+`v` copies the computer clipboard to the device clipboard (and - pastes if the device runs Android >= 7); - - `MOD`+`v` _pastes_ the computer clipboard as a sequence of text events (but - breaks non-ASCII characters). +Any `Ctrl` shortcut is forwarded to the device. In particular: + - `Ctrl`+`c` typically copies + - `Ctrl`+`x` typically cuts + - `Ctrl`+`v` typically pastes (after computer-to-device clipboard + synchronization) -Moreover, any time the Android clipboard changes, it is automatically -synchronized to the computer clipboard. +This typically works as you expect. + +The actual behavior depends on the active application though. For example, +_Termux_ sends SIGINT on `Ctrl`+`c` instead, and _K-9 Mail_ composes a new +message. + +To copy, cut and paste in such cases (but only supported on Android >= 7): + - `MOD`+`c` injects `COPY` + - `MOD`+`x` injects `CUT` + - `MOD`+`v` injects `PASTE` (after computer-to-device clipboard + synchronization) + +In addition, `MOD`+`Shift`+`v` allows to inject the computer clipboard text as a +sequence of key events. This is useful when the component does not accept text +pasting (for example in _Termux_), but it can break non-ASCII content. + +**WARNING:** Pasting the computer clipboard to the device (either via `Ctrl`+`v` +or `MOD`+`v`) copies the content into the device clipboard. As a consequence, +any Android application could read its content. You should avoid to paste +sensitive content (like passwords) that way. #### Text injection preference From dfb7324d7b06e649c8355164a7d97bd592ef5af8 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 17 Jul 2020 00:00:42 +0200 Subject: [PATCH 178/450] Mention in README that Ctrl is forwarded --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index 0a8df973..6df68fde 100644 --- a/README.md +++ b/README.md @@ -637,6 +637,9 @@ _¹Double-click on black borders to remove them._ _²Right-click turns the screen on if it was off, presses BACK otherwise._ _³Only on Android >= 7._ +All `Ctrl`+_key_ shortcuts are forwarded to the device, so they are handled by +the active application. + ## Custom paths From d49cffb93890982ba837e7b7bc4d9ee9405443d6 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sat, 1 Aug 2020 16:45:25 +0200 Subject: [PATCH 179/450] Use HTML tag for keys It's prettyier in a browser. --- README.md | 117 ++++++++++++++++++++++++++++-------------------------- 1 file changed, 61 insertions(+), 56 deletions(-) diff --git a/README.md b/README.md index 6df68fde..e1623a36 100644 --- a/README.md +++ b/README.md @@ -354,7 +354,7 @@ scrcpy --fullscreen scrcpy -f # short version ``` -Fullscreen can then be toggled dynamically with `MOD`+`f`. +Fullscreen can then be toggled dynamically with MOD+f. #### Rotation @@ -370,18 +370,19 @@ Possibles values are: - `2`: 180 degrees - `3`: 90 degrees clockwise -The rotation can also be changed dynamically with `MOD`+`←` _(left)_ and -`MOD`+`→` _(right)_. +The rotation can also be changed dynamically with MOD+ +_(left)_ and MOD+ _(right)_. Note that _scrcpy_ manages 3 different rotations: - - `MOD`+`r` requests the device to switch between portrait and landscape (the - current running app may refuse, if it does support the requested - orientation). +- MOD+r requests the device to switch between portrait and + landscape (the current running app may refuse, if it does 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` (or `MOD`+`←`/`MOD`+`→`) rotates only the window content. This - affects only the display, not the recording. + - `--rotation` (or MOD+/MOD+) + rotates only the window content. This affects only the display, not the + recording. ### Other mirroring options @@ -437,9 +438,10 @@ scrcpy --turn-screen-off scrcpy -S ``` -Or by pressing `MOD`+`o` at any time. +Or by pressing MOD+o at any time. -To turn it back on, press `MOD`+`Shift`+`o` (or `POWER`, `MOD`+`p`). +To turn it back on, press MOD+Shift+o (or +`POWER`, MOD+p). It can be useful to also prevent the device to sleep: @@ -494,7 +496,8 @@ scrcpy --disable-screensaver #### Rotate device screen -Press `MOD`+`r` to switch between portrait and landscape modes. +Press MOD+r to switch between portrait and landscape +modes. Note that it rotates only if the application in foreground supports the requested orientation. @@ -504,32 +507,34 @@ requested orientation. Any time the Android clipboard changes, it is automatically synchronized to the computer clipboard. -Any `Ctrl` shortcut is forwarded to the device. In particular: - - `Ctrl`+`c` typically copies - - `Ctrl`+`x` typically cuts - - `Ctrl`+`v` typically pastes (after computer-to-device clipboard - synchronization) +Any Ctrl shortcut is forwarded to the device. In particular: + - Ctrl+c typically copies + - Ctrl+x typically cuts + - Ctrl+v typically pastes (after computer-to-device + clipboard synchronization) This typically works as you expect. The actual behavior depends on the active application though. For example, -_Termux_ sends SIGINT on `Ctrl`+`c` instead, and _K-9 Mail_ composes a new -message. +_Termux_ sends SIGINT on Ctrl+c instead, and _K-9 Mail_ +composes a new message. To copy, cut and paste in such cases (but only supported on Android >= 7): - - `MOD`+`c` injects `COPY` - - `MOD`+`x` injects `CUT` - - `MOD`+`v` injects `PASTE` (after computer-to-device clipboard - synchronization) + - MOD+c injects `COPY` + - MOD+x injects `CUT` + - MOD+v injects `PASTE` (after computer-to-device + clipboard synchronization) -In addition, `MOD`+`Shift`+`v` allows to inject the computer clipboard text as a -sequence of key events. This is useful when the component does not accept text -pasting (for example in _Termux_), but it can break non-ASCII content. +In addition, MOD+Shift+v allows to inject the +computer clipboard text as a sequence of key events. This is useful when the +component does not accept text pasting (for example in _Termux_), but it can +break non-ASCII content. -**WARNING:** Pasting the computer clipboard to the device (either via `Ctrl`+`v` -or `MOD`+`v`) copies the content into the device clipboard. As a consequence, -any Android application could read its content. You should avoid to paste -sensitive content (like passwords) that way. +**WARNING:** Pasting the computer clipboard to the device (either via +Ctrl+v or MOD+v) copies the content +into the device clipboard. As a consequence, any Android application could read +its content. You should avoid to paste sensitive content (like passwords) that +way. #### Text injection preference @@ -589,8 +594,8 @@ Also see [issue #14]. ## Shortcuts -In the following list, `MOD` is the shortcut modifier. By default, it's (left) -`Alt` or (left) `Super`. +In the following list, MOD is the shortcut modifier. By default, it's +(left) Alt or (left) Super. It can be changed using `--shortcut-mod`. Possible keys are `lctrl`, `rctrl`, `lalt`, `ralt`, `lsuper` and `rsuper`. For example: @@ -603,42 +608,42 @@ scrcpy --shortcut-mod=rctrl scrcpy --shortcut-mod=lctrl+lalt,lsuper ``` -_[Super] is typically the "Windows" or "Cmd" key._ +_[Super] is typically the Windows or Cmd key._ [Super]: https://en.wikipedia.org/wiki/Super_key_(keyboard_button) | Action | Shortcut | ------------------------------------------- |:----------------------------- - | Switch fullscreen mode | `MOD`+`f` - | Rotate display left | `MOD`+`←` _(left)_ - | Rotate display right | `MOD`+`→` _(right)_ - | Resize window to 1:1 (pixel-perfect) | `MOD`+`g` - | Resize window to remove black borders | `MOD`+`w` \| _Double-click¹_ - | Click on `HOME` | `MOD`+`h` \| _Middle-click_ - | Click on `BACK` | `MOD`+`b` \| _Right-click²_ - | Click on `APP_SWITCH` | `MOD`+`s` - | Click on `MENU` | `MOD`+`m` - | Click on `VOLUME_UP` | `MOD`+`↑` _(up)_ - | Click on `VOLUME_DOWN` | `MOD`+`↓` _(down)_ - | Click on `POWER` | `MOD`+`p` + | Switch fullscreen mode | MOD+f + | Rotate display left | MOD+ _(left)_ + | Rotate display right | MOD+ _(right)_ + | Resize window to 1:1 (pixel-perfect) | MOD+g + | Resize window to remove black borders | MOD+w \| _Double-click¹_ + | Click on `HOME` | MOD+h \| _Middle-click_ + | Click on `BACK` | MOD+b \| _Right-click²_ + | Click on `APP_SWITCH` | MOD+s + | Click on `MENU` | MOD+m + | Click on `VOLUME_UP` | MOD+ _(up)_ + | Click on `VOLUME_DOWN` | MOD+ _(down)_ + | Click on `POWER` | MOD+p | Power on | _Right-click²_ - | Turn device screen off (keep mirroring) | `MOD`+`o` - | Turn device screen on | `MOD`+`Shift`+`o` - | Rotate device screen | `MOD`+`r` - | Expand notification panel | `MOD`+`n` - | Collapse notification panel | `MOD`+`Shift`+`n` - | Copy to clipboard³ | `MOD`+`c` - | Cut to clipboard³ | `MOD`+`x` - | Synchronize clipboards and paste³ | `MOD`+`v` - | Inject computer clipboard text | `MOD`+`Shift`+`v` - | Enable/disable FPS counter (on stdout) | `MOD`+`i` + | Turn device screen off (keep mirroring) | MOD+o + | Turn device screen on | MOD+Shift+o + | Rotate device screen | MOD+r + | Expand notification panel | MOD+n + | Collapse notification panel | MOD+Shift+n + | Copy to clipboard³ | MOD+c + | Cut to clipboard³ | MOD+x + | Synchronize clipboards and paste³ | MOD+v + | Inject computer clipboard text | MOD+Shift+v + | Enable/disable FPS counter (on stdout) | MOD+i _¹Double-click on black borders to remove them._ _²Right-click turns the screen on if it was off, presses BACK otherwise._ _³Only on Android >= 7._ -All `Ctrl`+_key_ shortcuts are forwarded to the device, so they are handled by -the active application. +All Ctrl+_key_ shortcuts are forwarded to the device, so they are +handled by the active application. ## Custom paths From 0870d8620f327768249214a5ba6a7bee7a8a75ec Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sat, 1 Aug 2020 17:10:09 +0200 Subject: [PATCH 180/450] Mention that MENU unlocks screen Pressing MENU while in lock screen unlocks (it still asks for the schema or code if enabled). --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index e1623a36..b3a5c6b6 100644 --- a/README.md +++ b/README.md @@ -622,7 +622,7 @@ _[Super] is typically the Windows or Cmd key._ | Click on `HOME` | MOD+h \| _Middle-click_ | Click on `BACK` | MOD+b \| _Right-click²_ | Click on `APP_SWITCH` | MOD+s - | Click on `MENU` | MOD+m + | Click on `MENU` (unlock screen) | MOD+m | Click on `VOLUME_UP` | MOD+ _(up)_ | Click on `VOLUME_DOWN` | MOD+ _(down)_ | Click on `POWER` | MOD+p From 74079ea5e4ce0282a5d078cfa80fc68d2d9da16e Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 2 Aug 2020 15:45:31 +0200 Subject: [PATCH 181/450] Copy the options used in input manager init This avoids to pass additional options to some input manager functions. Refs #1623 --- app/src/input_manager.c | 17 ++++++++++------- app/src/input_manager.h | 11 +++++------ app/src/scrcpy.c | 8 +++----- 3 files changed, 18 insertions(+), 18 deletions(-) diff --git a/app/src/input_manager.c b/app/src/input_manager.c index ec1d5a4e..3bf2282e 100644 --- a/app/src/input_manager.c +++ b/app/src/input_manager.c @@ -54,11 +54,13 @@ is_shortcut_mod(struct input_manager *im, uint16_t sdl_mod) { } void -input_manager_init(struct input_manager *im, bool prefer_text, - const struct sc_shortcut_mods *shortcut_mods) +input_manager_init(struct input_manager *im, + const struct scrcpy_options *options) { - im->prefer_text = prefer_text; + im->control = options->control; + im->prefer_text = options->prefer_text; + const struct sc_shortcut_mods *shortcut_mods = &options->shortcut_mods; assert(shortcut_mods->count); assert(shortcut_mods->count < SC_MAX_SHORTCUT_MODS); for (unsigned i = 0; i < shortcut_mods->count; ++i) { @@ -318,9 +320,9 @@ convert_input_key(const SDL_KeyboardEvent *from, struct control_msg *to, void input_manager_process_key(struct input_manager *im, - const SDL_KeyboardEvent *event, - bool control) { + const SDL_KeyboardEvent *event) { // control: indicates the state of the command-line option --no-control + bool control = im->control; bool smod = is_shortcut_mod(im, event->keysym.mod); @@ -573,8 +575,9 @@ convert_mouse_button(const SDL_MouseButtonEvent *from, struct screen *screen, void input_manager_process_mouse_button(struct input_manager *im, - const SDL_MouseButtonEvent *event, - bool control) { + const SDL_MouseButtonEvent *event) { + bool control = im->control; + if (event->which == SDL_TOUCH_MOUSEID) { // simulated from touch events, so it's a duplicate return; diff --git a/app/src/input_manager.h b/app/src/input_manager.h index c854664a..b3be6507 100644 --- a/app/src/input_manager.h +++ b/app/src/input_manager.h @@ -22,6 +22,7 @@ struct input_manager { // number of repetitions. This variable keeps track of the count. unsigned repeat; + bool control; bool prefer_text; struct { @@ -31,8 +32,8 @@ struct input_manager { }; void -input_manager_init(struct input_manager *im, bool prefer_text, - const struct sc_shortcut_mods *shortcut_mods); +input_manager_init(struct input_manager *im, + const struct scrcpy_options *options); void input_manager_process_text_input(struct input_manager *im, @@ -40,8 +41,7 @@ input_manager_process_text_input(struct input_manager *im, void input_manager_process_key(struct input_manager *im, - const SDL_KeyboardEvent *event, - bool control); + const SDL_KeyboardEvent *event); void input_manager_process_mouse_motion(struct input_manager *im, @@ -53,8 +53,7 @@ input_manager_process_touch(struct input_manager *im, void input_manager_process_mouse_button(struct input_manager *im, - const SDL_MouseButtonEvent *event, - bool control); + const SDL_MouseButtonEvent *event); void input_manager_process_mouse_wheel(struct input_manager *im, diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index 06a3b3d1..f290f1c2 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -201,7 +201,7 @@ handle_event(SDL_Event *event, bool control) { case SDL_KEYUP: // some key events do not interact with the device, so process the // event even if control is disabled - input_manager_process_key(&input_manager, &event->key, control); + input_manager_process_key(&input_manager, &event->key); break; case SDL_MOUSEMOTION: if (!control) { @@ -219,8 +219,7 @@ handle_event(SDL_Event *event, bool control) { case SDL_MOUSEBUTTONUP: // some mouse events do not interact with the device, so process // the event even if control is disabled - input_manager_process_mouse_button(&input_manager, &event->button, - control); + input_manager_process_mouse_button(&input_manager, &event->button); break; case SDL_FINGERMOTION: case SDL_FINGERDOWN: @@ -443,8 +442,7 @@ scrcpy(const struct scrcpy_options *options) { } } - input_manager_init(&input_manager, options->prefer_text, - &options->shortcut_mods); + input_manager_init(&input_manager, options); ret = event_loop(options->display, options->control); LOGD("quit..."); From 65d06a36634287426206b459d2f290142ea1bf46 Mon Sep 17 00:00:00 2001 From: xeropresence <3128949+xeropresence@users.noreply.github.com> Date: Mon, 27 Jul 2020 02:26:25 -0400 Subject: [PATCH 182/450] Pass full options struct to static functions This avoids to pass specific options values individually. Since these function are static (internal to the file), this is not a problem to make them depend on scrcpy_options. Refs #1623 Signed-off-by: Romain Vimont --- app/src/scrcpy.c | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index f290f1c2..45068cbb 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -170,7 +170,7 @@ enum event_result { }; static enum event_result -handle_event(SDL_Event *event, bool control) { +handle_event(SDL_Event *event, const struct scrcpy_options *options) { switch (event->type) { case EVENT_STREAM_STOPPED: LOGD("Video stream stopped"); @@ -192,7 +192,7 @@ handle_event(SDL_Event *event, bool control) { screen_handle_window_event(&screen, &event->window); break; case SDL_TEXTINPUT: - if (!control) { + if (!options->control) { break; } input_manager_process_text_input(&input_manager, &event->text); @@ -204,13 +204,13 @@ handle_event(SDL_Event *event, bool control) { input_manager_process_key(&input_manager, &event->key); break; case SDL_MOUSEMOTION: - if (!control) { + if (!options->control) { break; } input_manager_process_mouse_motion(&input_manager, &event->motion); break; case SDL_MOUSEWHEEL: - if (!control) { + if (!options->control) { break; } input_manager_process_mouse_wheel(&input_manager, &event->wheel); @@ -227,7 +227,7 @@ handle_event(SDL_Event *event, bool control) { input_manager_process_touch(&input_manager, &event->tfinger); break; case SDL_DROPFILE: { - if (!control) { + if (!options->control) { break; } file_handler_action_t action; @@ -244,16 +244,15 @@ handle_event(SDL_Event *event, bool control) { } static bool -event_loop(bool display, bool control) { - (void) display; +event_loop(const struct scrcpy_options *options) { #ifdef CONTINUOUS_RESIZING_WORKAROUND - if (display) { + if (options->display) { SDL_AddEventWatch(event_watcher, NULL); } #endif SDL_Event event; while (SDL_WaitEvent(&event)) { - enum event_result result = handle_event(&event, control); + enum event_result result = handle_event(&event, options); switch (result) { case EVENT_RESULT_STOPPED_BY_USER: return true; @@ -444,7 +443,7 @@ scrcpy(const struct scrcpy_options *options) { input_manager_init(&input_manager, options); - ret = event_loop(options->display, options->control); + ret = event_loop(options); LOGD("quit..."); screen_destroy(&screen); From 84f1d9e37502fbffc1e6b6754b930a37a5403703 Mon Sep 17 00:00:00 2001 From: xeropresence <3128949+xeropresence@users.noreply.github.com> Date: Mon, 27 Jul 2020 02:26:25 -0400 Subject: [PATCH 183/450] Add --no-key-repeat cli option Add an option to avoid forwarding repeated key events. PR #1623 Refs #1013 Signed-off-by: Romain Vimont --- README.md | 12 ++++++++++++ app/scrcpy.1 | 4 ++++ app/src/cli.c | 8 ++++++++ app/src/input_manager.c | 4 ++++ app/src/input_manager.h | 1 + app/src/scrcpy.h | 2 ++ 6 files changed, 31 insertions(+) diff --git a/README.md b/README.md index b3a5c6b6..d875fd99 100644 --- a/README.md +++ b/README.md @@ -558,6 +558,18 @@ scrcpy --prefer-text [prefertext]: https://github.com/Genymobile/scrcpy/issues/650#issuecomment-512945343 +#### Key repeat + +By default, holding a key down generates repeated key events. This can cause +performance problems in some games, where these events are useless anyway. + +To avoid forwarding repeated key events: + +```bash +scrcpy --no-key-repeat +``` + + ### File drop #### Install APK diff --git a/app/scrcpy.1 b/app/scrcpy.1 index 728d14fe..49fa78dc 100644 --- a/app/scrcpy.1 +++ b/app/scrcpy.1 @@ -96,6 +96,10 @@ Do not display device (only when screen recording is enabled). .B \-\-no\-mipmaps If the renderer is OpenGL 3.0+ or OpenGL ES 2.0+, then mipmaps are automatically generated to improve downscaling quality. This option disables the generation of mipmaps. +.TP +.B \-\-no\-key\-repeat +Do not forward repeated key events when a key is held down. + .TP .BI "\-p, \-\-port " port[:port] Set the TCP port (range) used by the client to listen. diff --git a/app/src/cli.c b/app/src/cli.c index b75ab41a..c957b22f 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -92,6 +92,9 @@ scrcpy_print_usage(const char *arg0) { " mipmaps are automatically generated to improve downscaling\n" " quality. This option disables the generation of mipmaps.\n" "\n" + " --no-key-repeat\n" + " Do not forward repeated key events when a key is held down.\n" + "\n" " -p, --port port[:port]\n" " Set the TCP port (range) used by the client to listen.\n" " Default is %d:%d.\n" @@ -642,6 +645,7 @@ guess_record_format(const char *filename) { #define OPT_FORCE_ADB_FORWARD 1019 #define OPT_DISABLE_SCREENSAVER 1020 #define OPT_SHORTCUT_MOD 1021 +#define OPT_NO_KEY_REPEAT 1022 bool scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) { @@ -664,6 +668,7 @@ scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) { {"no-control", no_argument, NULL, 'n'}, {"no-display", no_argument, NULL, 'N'}, {"no-mipmaps", no_argument, NULL, OPT_NO_MIPMAPS}, + {"no-key-repeat", no_argument, NULL, OPT_NO_KEY_REPEAT}, {"port", required_argument, NULL, 'p'}, {"prefer-text", no_argument, NULL, OPT_PREFER_TEXT}, {"push-target", required_argument, NULL, OPT_PUSH_TARGET}, @@ -829,6 +834,9 @@ scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) { case OPT_NO_MIPMAPS: opts->mipmaps = false; break; + case OPT_NO_KEY_REPEAT: + opts->forward_key_repeat = false; + break; case OPT_CODEC_OPTIONS: opts->codec_options = optarg; break; diff --git a/app/src/input_manager.c b/app/src/input_manager.c index 3bf2282e..02dd9cb5 100644 --- a/app/src/input_manager.c +++ b/app/src/input_manager.c @@ -58,6 +58,7 @@ input_manager_init(struct input_manager *im, const struct scrcpy_options *options) { im->control = options->control; + im->forward_key_repeat = options->forward_key_repeat; im->prefer_text = options->prefer_text; const struct sc_shortcut_mods *shortcut_mods = &options->shortcut_mods; @@ -461,6 +462,9 @@ input_manager_process_key(struct input_manager *im, } if (event->repeat) { + if (!im->forward_key_repeat) { + return; + } ++im->repeat; } else { im->repeat = 0; diff --git a/app/src/input_manager.h b/app/src/input_manager.h index b3be6507..8811c457 100644 --- a/app/src/input_manager.h +++ b/app/src/input_manager.h @@ -23,6 +23,7 @@ struct input_manager { unsigned repeat; bool control; + bool forward_key_repeat; bool prefer_text; struct { diff --git a/app/src/scrcpy.h b/app/src/scrcpy.h index 05af002f..86a2b57b 100644 --- a/app/src/scrcpy.h +++ b/app/src/scrcpy.h @@ -78,6 +78,7 @@ struct scrcpy_options { bool stay_awake; bool force_adb_forward; bool disable_screensaver; + bool forward_key_repeat; }; #define SCRCPY_OPTIONS_DEFAULT { \ @@ -121,6 +122,7 @@ struct scrcpy_options { .stay_awake = false, \ .force_adb_forward = false, \ .disable_screensaver = false, \ + .forward_key_repeat = true, \ } bool From cf9d44979cf4eca514a66cd9ec4d4bb8b5be1e19 Mon Sep 17 00:00:00 2001 From: brunoais Date: Wed, 8 Jul 2020 21:49:34 +0100 Subject: [PATCH 184/450] Keep the screen off on powering on PR #1577 Fixes #1573 Signed-off-by: Romain Vimont --- README.md | 8 ++++-- .../com/genymobile/scrcpy/Controller.java | 27 +++++++++++++++++++ 2 files changed, 33 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index d875fd99..4ef28ab2 100644 --- a/README.md +++ b/README.md @@ -440,8 +440,12 @@ scrcpy -S Or by pressing MOD+o at any time. -To turn it back on, press MOD+Shift+o (or -`POWER`, MOD+p). +To turn it back on, press MOD+Shift+o. + +On Android, the `POWER` button always turns the screen on. For convenience, if +`POWER` is sent via scrcpy (via right-click or Ctrl+p), it +will force to turn the screen off after a small delay (on a best effort basis). +The physical `POWER` button will still cause the screen to be turned on. It can be useful to also prevent the device to sleep: diff --git a/server/src/main/java/com/genymobile/scrcpy/Controller.java b/server/src/main/java/com/genymobile/scrcpy/Controller.java index f32e5ec0..2ad26a95 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Controller.java +++ b/server/src/main/java/com/genymobile/scrcpy/Controller.java @@ -8,11 +8,16 @@ import android.view.KeyEvent; import android.view.MotionEvent; import java.io.IOException; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; public class Controller { private static final int DEVICE_ID_VIRTUAL = -1; + private static final ScheduledExecutorService EXECUTOR = Executors.newSingleThreadScheduledExecutor(); + private final Device device; private final DesktopConnection connection; private final DeviceMessageSender sender; @@ -24,6 +29,8 @@ public class Controller { private final MotionEvent.PointerProperties[] pointerProperties = new MotionEvent.PointerProperties[PointersState.MAX_POINTERS]; private final MotionEvent.PointerCoords[] pointerCoords = new MotionEvent.PointerCoords[PointersState.MAX_POINTERS]; + private boolean keepPowerModeOff; + public Controller(Device device, DesktopConnection connection) { this.device = device; this.connection = connection; @@ -117,6 +124,7 @@ public class Controller { int mode = msg.getAction(); boolean setPowerModeOk = Device.setScreenPowerMode(mode); if (setPowerModeOk) { + keepPowerModeOff = mode == Device.POWER_MODE_OFF; Ln.i("Device screen turned " + (mode == Device.POWER_MODE_OFF ? "off" : "on")); } } @@ -130,6 +138,9 @@ public class Controller { } private boolean injectKeycode(int action, int keycode, int repeat, int metaState) { + if (keepPowerModeOff && action == KeyEvent.ACTION_UP && (keycode == KeyEvent.KEYCODE_POWER || keycode == KeyEvent.KEYCODE_WAKEUP)) { + schedulePowerModeOff(); + } return device.injectKeyEvent(action, keycode, repeat, metaState); } @@ -223,8 +234,24 @@ public class Controller { return device.injectEvent(event); } + /** + * Schedule a call to set power mode to off after a small delay. + */ + private static void schedulePowerModeOff() { + EXECUTOR.schedule(new Runnable() { + @Override + public void run() { + Ln.i("Forcing screen off"); + Device.setScreenPowerMode(Device.POWER_MODE_OFF); + } + }, 200, TimeUnit.MILLISECONDS); + } + private boolean pressBackOrTurnScreenOn() { int keycode = device.isScreenOn() ? KeyEvent.KEYCODE_BACK : KeyEvent.KEYCODE_WAKEUP; + if (keepPowerModeOff && keycode == KeyEvent.KEYCODE_WAKEUP) { + schedulePowerModeOff(); + } return device.injectKeycode(keycode); } From 1ba06037f88aedf01bae1933b404eac1cb3aa129 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 6 Aug 2020 21:00:48 +0200 Subject: [PATCH 185/450] Upgrade platform-tools (30.0.4) for Windows Include the latest version of adb in Windows releases. --- prebuilt-deps/Makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/prebuilt-deps/Makefile b/prebuilt-deps/Makefile index c6cbcf7b..b98864dc 100644 --- a/prebuilt-deps/Makefile +++ b/prebuilt-deps/Makefile @@ -35,6 +35,6 @@ prepare-sdl2: SDL2-2.0.12 prepare-adb: - @./prepare-dep https://dl.google.com/android/repository/platform-tools_r30.0.0-windows.zip \ - 854305f9a702f5ea2c3de73edde402bd26afa0ee944c9b0c4380420f5a862e0d \ + @./prepare-dep https://dl.google.com/android/repository/platform-tools_r30.0.4-windows.zip \ + 413182fff6c5957911e231b9e97e6be4fc6a539035e3dfb580b5c54bd5950fee \ platform-tools From 712f1fa6b2fa82f4916daad631bcae6c3de94e45 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 6 Aug 2020 21:00:48 +0200 Subject: [PATCH 186/450] Upgrade FFmpeg (4.3.1) for Windows Include the latest version of FFmpeg in Windows releases. --- Makefile.CrossWindows | 20 ++++++++++---------- cross_win32.txt | 4 ++-- cross_win64.txt | 4 ++-- prebuilt-deps/Makefile | 24 ++++++++++++------------ 4 files changed, 26 insertions(+), 26 deletions(-) diff --git a/Makefile.CrossWindows b/Makefile.CrossWindows index 3aed2019..0415af82 100644 --- a/Makefile.CrossWindows +++ b/Makefile.CrossWindows @@ -100,11 +100,11 @@ dist-win32: build-server build-win32 build-win32-noconsole cp "$(SERVER_BUILD_DIR)"/server/scrcpy-server "$(DIST)/$(WIN32_TARGET_DIR)/" cp "$(WIN32_BUILD_DIR)"/app/scrcpy.exe "$(DIST)/$(WIN32_TARGET_DIR)/" cp "$(WIN32_NOCONSOLE_BUILD_DIR)"/app/scrcpy.exe "$(DIST)/$(WIN32_TARGET_DIR)/scrcpy-noconsole.exe" - cp prebuilt-deps/ffmpeg-4.2.2-win32-shared/bin/avutil-56.dll "$(DIST)/$(WIN32_TARGET_DIR)/" - cp prebuilt-deps/ffmpeg-4.2.2-win32-shared/bin/avcodec-58.dll "$(DIST)/$(WIN32_TARGET_DIR)/" - cp prebuilt-deps/ffmpeg-4.2.2-win32-shared/bin/avformat-58.dll "$(DIST)/$(WIN32_TARGET_DIR)/" - cp prebuilt-deps/ffmpeg-4.2.2-win32-shared/bin/swresample-3.dll "$(DIST)/$(WIN32_TARGET_DIR)/" - cp prebuilt-deps/ffmpeg-4.2.2-win32-shared/bin/swscale-5.dll "$(DIST)/$(WIN32_TARGET_DIR)/" + cp prebuilt-deps/ffmpeg-4.3.1-win32-shared/bin/avutil-56.dll "$(DIST)/$(WIN32_TARGET_DIR)/" + cp prebuilt-deps/ffmpeg-4.3.1-win32-shared/bin/avcodec-58.dll "$(DIST)/$(WIN32_TARGET_DIR)/" + cp prebuilt-deps/ffmpeg-4.3.1-win32-shared/bin/avformat-58.dll "$(DIST)/$(WIN32_TARGET_DIR)/" + cp prebuilt-deps/ffmpeg-4.3.1-win32-shared/bin/swresample-3.dll "$(DIST)/$(WIN32_TARGET_DIR)/" + cp prebuilt-deps/ffmpeg-4.3.1-win32-shared/bin/swscale-5.dll "$(DIST)/$(WIN32_TARGET_DIR)/" cp prebuilt-deps/platform-tools/adb.exe "$(DIST)/$(WIN32_TARGET_DIR)/" cp prebuilt-deps/platform-tools/AdbWinApi.dll "$(DIST)/$(WIN32_TARGET_DIR)/" cp prebuilt-deps/platform-tools/AdbWinUsbApi.dll "$(DIST)/$(WIN32_TARGET_DIR)/" @@ -115,11 +115,11 @@ dist-win64: build-server build-win64 build-win64-noconsole cp "$(SERVER_BUILD_DIR)"/server/scrcpy-server "$(DIST)/$(WIN64_TARGET_DIR)/" cp "$(WIN64_BUILD_DIR)"/app/scrcpy.exe "$(DIST)/$(WIN64_TARGET_DIR)/" cp "$(WIN64_NOCONSOLE_BUILD_DIR)"/app/scrcpy.exe "$(DIST)/$(WIN64_TARGET_DIR)/scrcpy-noconsole.exe" - cp prebuilt-deps/ffmpeg-4.2.2-win64-shared/bin/avutil-56.dll "$(DIST)/$(WIN64_TARGET_DIR)/" - cp prebuilt-deps/ffmpeg-4.2.2-win64-shared/bin/avcodec-58.dll "$(DIST)/$(WIN64_TARGET_DIR)/" - cp prebuilt-deps/ffmpeg-4.2.2-win64-shared/bin/avformat-58.dll "$(DIST)/$(WIN64_TARGET_DIR)/" - cp prebuilt-deps/ffmpeg-4.2.2-win64-shared/bin/swresample-3.dll "$(DIST)/$(WIN64_TARGET_DIR)/" - cp prebuilt-deps/ffmpeg-4.2.2-win64-shared/bin/swscale-5.dll "$(DIST)/$(WIN64_TARGET_DIR)/" + cp prebuilt-deps/ffmpeg-4.3.1-win64-shared/bin/avutil-56.dll "$(DIST)/$(WIN64_TARGET_DIR)/" + cp prebuilt-deps/ffmpeg-4.3.1-win64-shared/bin/avcodec-58.dll "$(DIST)/$(WIN64_TARGET_DIR)/" + cp prebuilt-deps/ffmpeg-4.3.1-win64-shared/bin/avformat-58.dll "$(DIST)/$(WIN64_TARGET_DIR)/" + cp prebuilt-deps/ffmpeg-4.3.1-win64-shared/bin/swresample-3.dll "$(DIST)/$(WIN64_TARGET_DIR)/" + cp prebuilt-deps/ffmpeg-4.3.1-win64-shared/bin/swscale-5.dll "$(DIST)/$(WIN64_TARGET_DIR)/" cp prebuilt-deps/platform-tools/adb.exe "$(DIST)/$(WIN64_TARGET_DIR)/" cp prebuilt-deps/platform-tools/AdbWinApi.dll "$(DIST)/$(WIN64_TARGET_DIR)/" cp prebuilt-deps/platform-tools/AdbWinUsbApi.dll "$(DIST)/$(WIN64_TARGET_DIR)/" diff --git a/cross_win32.txt b/cross_win32.txt index 7b420690..bef2e5d5 100644 --- a/cross_win32.txt +++ b/cross_win32.txt @@ -15,6 +15,6 @@ cpu = 'i686' endian = 'little' [properties] -prebuilt_ffmpeg_shared = 'ffmpeg-4.2.2-win32-shared' -prebuilt_ffmpeg_dev = 'ffmpeg-4.2.2-win32-dev' +prebuilt_ffmpeg_shared = 'ffmpeg-4.3.1-win32-shared' +prebuilt_ffmpeg_dev = 'ffmpeg-4.3.1-win32-dev' prebuilt_sdl2 = 'SDL2-2.0.12/i686-w64-mingw32' diff --git a/cross_win64.txt b/cross_win64.txt index cb9e0954..5a348738 100644 --- a/cross_win64.txt +++ b/cross_win64.txt @@ -15,6 +15,6 @@ cpu = 'x86_64' endian = 'little' [properties] -prebuilt_ffmpeg_shared = 'ffmpeg-4.2.2-win64-shared' -prebuilt_ffmpeg_dev = 'ffmpeg-4.2.2-win64-dev' +prebuilt_ffmpeg_shared = 'ffmpeg-4.3.1-win64-shared' +prebuilt_ffmpeg_dev = 'ffmpeg-4.3.1-win64-dev' prebuilt_sdl2 = 'SDL2-2.0.12/x86_64-w64-mingw32' diff --git a/prebuilt-deps/Makefile b/prebuilt-deps/Makefile index b98864dc..9cccd0bd 100644 --- a/prebuilt-deps/Makefile +++ b/prebuilt-deps/Makefile @@ -10,24 +10,24 @@ prepare-win32: prepare-sdl2 prepare-ffmpeg-shared-win32 prepare-ffmpeg-dev-win32 prepare-win64: prepare-sdl2 prepare-ffmpeg-shared-win64 prepare-ffmpeg-dev-win64 prepare-adb prepare-ffmpeg-shared-win32: - @./prepare-dep https://ffmpeg.zeranoe.com/builds/win32/shared/ffmpeg-4.2.2-win32-shared.zip \ - ab5d603aaa54de360db2c2ffe378c82376b9343ea1175421dd644639aa07ee31 \ - ffmpeg-4.2.2-win32-shared + @./prepare-dep https://ffmpeg.zeranoe.com/builds/win32/shared/ffmpeg-4.3.1-win32-shared.zip \ + 357af9901a456f4dcbacd107e83a934d344c9cb07ddad8aaf80612eeab7d26d2 \ + ffmpeg-4.3.1-win32-shared prepare-ffmpeg-dev-win32: - @./prepare-dep https://ffmpeg.zeranoe.com/builds/win32/dev/ffmpeg-4.2.2-win32-dev.zip \ - 8d224be567a2950cad4be86f4aabdd045bfa52ad758e87c72cedd278613bc6c8 \ - ffmpeg-4.2.2-win32-dev + @./prepare-dep https://ffmpeg.zeranoe.com/builds/win32/dev/ffmpeg-4.3.1-win32-dev.zip \ + 230efb08e9bcf225bd474da29676c70e591fc94d8790a740ca801408fddcb78b \ + ffmpeg-4.3.1-win32-dev prepare-ffmpeg-shared-win64: - @./prepare-dep https://ffmpeg.zeranoe.com/builds/win64/shared/ffmpeg-4.2.2-win64-shared.zip \ - 5aedf268952b7d9f6541dbfcb47cd86a7e7881a3b7ba482fd3bc4ca33bda7bf5 \ - ffmpeg-4.2.2-win64-shared + @./prepare-dep https://ffmpeg.zeranoe.com/builds/win64/shared/ffmpeg-4.3.1-win64-shared.zip \ + dd29b7f92f48dead4dd940492c7509138c0f99db445076d0a597007298a79940 \ + ffmpeg-4.3.1-win64-shared prepare-ffmpeg-dev-win64: - @./prepare-dep https://ffmpeg.zeranoe.com/builds/win64/dev/ffmpeg-4.2.2-win64-dev.zip \ - f4885f859c5b0d6663c2a0a4c1cf035b1c60b146402790b796bd3ad84f4f3ca2 \ - ffmpeg-4.2.2-win64-dev + @./prepare-dep https://ffmpeg.zeranoe.com/builds/win64/dev/ffmpeg-4.3.1-win64-dev.zip \ + 2e8038242cf8e1bd095c2978f196ff0462b122cc6ef7e74626a6af15459d8b81 \ + ffmpeg-4.3.1-win64-dev prepare-sdl2: @./prepare-dep https://libsdl.org/release/SDL2-devel-2.0.12-mingw.tar.gz \ From edc4f7675f65d6c94a4c8935d13f84b6f601a79a Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 6 Aug 2020 21:00:48 +0200 Subject: [PATCH 187/450] Bump version to 1.15 --- meson.build | 2 +- server/build.gradle | 4 ++-- server/build_without_gradle.sh | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/meson.build b/meson.build index 46b9a092..c4018189 100644 --- a/meson.build +++ b/meson.build @@ -1,5 +1,5 @@ project('scrcpy', 'c', - version: '1.14', + version: '1.15', meson_version: '>= 0.48', default_options: [ 'c_std=c11', diff --git a/server/build.gradle b/server/build.gradle index c8ff85d6..9bcad1c1 100644 --- a/server/build.gradle +++ b/server/build.gradle @@ -6,8 +6,8 @@ android { applicationId "com.genymobile.scrcpy" minSdkVersion 21 targetSdkVersion 29 - versionCode 16 - versionName "1.14" + versionCode 17 + versionName "1.15" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" } buildTypes { diff --git a/server/build_without_gradle.sh b/server/build_without_gradle.sh index e3be2faf..4fad829c 100755 --- a/server/build_without_gradle.sh +++ b/server/build_without_gradle.sh @@ -12,7 +12,7 @@ set -e SCRCPY_DEBUG=false -SCRCPY_VERSION_NAME=1.14 +SCRCPY_VERSION_NAME=1.15 PLATFORM=${ANDROID_PLATFORM:-29} BUILD_TOOLS=${ANDROID_BUILD_TOOLS:-29.0.2} From 521f2fe994019065e938aa1a54b56b4f10a4ac4a Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 6 Aug 2020 21:56:06 +0200 Subject: [PATCH 188/450] Update links to v1.15 in README and BUILD --- BUILD.md | 6 +++--- README.md | 8 ++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/BUILD.md b/BUILD.md index 57b2db97..c584afd1 100644 --- a/BUILD.md +++ b/BUILD.md @@ -254,10 +254,10 @@ You can then [run](README.md#run) _scrcpy_. ## Prebuilt server - - [`scrcpy-server-v1.14`][direct-scrcpy-server] - _(SHA-256: 1d1b18a2b80e956771fd63b99b414d2d028713a8f12ddfa5a369709ad4295620)_ + - [`scrcpy-server-v1.15`][direct-scrcpy-server] + _(SHA-256: e160c46784f30566eae621cad88bfb34e124f945cb8274b8dfc615b613b86dd8)_ -[direct-scrcpy-server]: https://github.com/Genymobile/scrcpy/releases/download/v1.14/scrcpy-server-v1.14 +[direct-scrcpy-server]: https://github.com/Genymobile/scrcpy/releases/download/v1.15/scrcpy-server-v1.15 Download the prebuilt server somewhere, and specify its path during the Meson configuration: diff --git a/README.md b/README.md index 374d0821..0d6e2208 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# scrcpy (v1.14) +# scrcpy (v1.15) 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. @@ -74,10 +74,10 @@ hard). For Windows, for simplicity, a prebuilt archive with all the dependencies (including `adb`) is available: - - [`scrcpy-win64-v1.14.zip`][direct-win64] - _(SHA-256: 2be9139e46e29cf2f5f695848bb2b75a543b8f38be1133257dc5068252abc25f)_ + - [`scrcpy-win64-v1.15.zip`][direct-win64] + _(SHA-256: dd514bb591e63ef4cd52a53c30f1153a28f59722d64690eb07bd017849edcba2)_ -[direct-win64]: https://github.com/Genymobile/scrcpy/releases/download/v1.14/scrcpy-win64-v1.14.zip +[direct-win64]: https://github.com/Genymobile/scrcpy/releases/download/v1.15/scrcpy-win64-v1.15.zip It is also available in [Chocolatey]: From 976761956f5ff8a806a8547e7a1af9e4e7e3d77c Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 7 Aug 2020 09:21:13 +0200 Subject: [PATCH 189/450] Fix uninitialized repeat count in key events A new "repeat" field has been added by 3c1ed5d86c38bcbd5353b0a7e6ef5653d6c44d0d, but it was not initialized in every code path. As a consequence, keycodes generated by shortcuts were sent with an undetermined value, breaking some shortcuts (especially HOME) randomly. Fixes #1643 --- app/src/input_manager.c | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/input_manager.c b/app/src/input_manager.c index 02dd9cb5..1d73980c 100644 --- a/app/src/input_manager.c +++ b/app/src/input_manager.c @@ -80,6 +80,7 @@ send_keycode(struct controller *controller, enum android_keycode keycode, msg.type = CONTROL_MSG_TYPE_INJECT_KEYCODE; msg.inject_keycode.keycode = keycode; msg.inject_keycode.metastate = 0; + msg.inject_keycode.repeat = 0; if (actions & ACTION_DOWN) { msg.inject_keycode.action = AKEY_EVENT_ACTION_DOWN; From 633a51e9c4c64ed549a9a44f8348ded528268451 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 7 Aug 2020 12:01:34 +0200 Subject: [PATCH 190/450] Bump version to 1.15.1 --- meson.build | 2 +- server/build.gradle | 4 ++-- server/build_without_gradle.sh | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/meson.build b/meson.build index c4018189..8492f520 100644 --- a/meson.build +++ b/meson.build @@ -1,5 +1,5 @@ project('scrcpy', 'c', - version: '1.15', + version: '1.15.1', meson_version: '>= 0.48', default_options: [ 'c_std=c11', diff --git a/server/build.gradle b/server/build.gradle index 9bcad1c1..c7c0abb0 100644 --- a/server/build.gradle +++ b/server/build.gradle @@ -6,8 +6,8 @@ android { applicationId "com.genymobile.scrcpy" minSdkVersion 21 targetSdkVersion 29 - versionCode 17 - versionName "1.15" + versionCode 18 + versionName "1.15.1" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" } buildTypes { diff --git a/server/build_without_gradle.sh b/server/build_without_gradle.sh index 4fad829c..cedbe186 100755 --- a/server/build_without_gradle.sh +++ b/server/build_without_gradle.sh @@ -12,7 +12,7 @@ set -e SCRCPY_DEBUG=false -SCRCPY_VERSION_NAME=1.15 +SCRCPY_VERSION_NAME=1.15.1 PLATFORM=${ANDROID_PLATFORM:-29} BUILD_TOOLS=${ANDROID_BUILD_TOOLS:-29.0.2} From f03a3edde618402ecff442383845ec7ba7f0b829 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 7 Aug 2020 12:13:27 +0200 Subject: [PATCH 191/450] Update links to v1.15.1 in README and BUILD --- BUILD.md | 6 +++--- README.md | 8 ++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/BUILD.md b/BUILD.md index c584afd1..2f1c2be9 100644 --- a/BUILD.md +++ b/BUILD.md @@ -254,10 +254,10 @@ You can then [run](README.md#run) _scrcpy_. ## Prebuilt server - - [`scrcpy-server-v1.15`][direct-scrcpy-server] - _(SHA-256: e160c46784f30566eae621cad88bfb34e124f945cb8274b8dfc615b613b86dd8)_ + - [`scrcpy-server-v1.15.1`][direct-scrcpy-server] + _(SHA-256: fe06bd6a30da8c89860bf5e16eecce2b5054d4644c84289670ce00ca5d1637c3)_ -[direct-scrcpy-server]: https://github.com/Genymobile/scrcpy/releases/download/v1.15/scrcpy-server-v1.15 +[direct-scrcpy-server]: https://github.com/Genymobile/scrcpy/releases/download/v1.15.1/scrcpy-server-v1.15.1 Download the prebuilt server somewhere, and specify its path during the Meson configuration: diff --git a/README.md b/README.md index 0d6e2208..d45b6304 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# scrcpy (v1.15) +# scrcpy (v1.15.1) 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. @@ -74,10 +74,10 @@ hard). For Windows, for simplicity, a prebuilt archive with all the dependencies (including `adb`) is available: - - [`scrcpy-win64-v1.15.zip`][direct-win64] - _(SHA-256: dd514bb591e63ef4cd52a53c30f1153a28f59722d64690eb07bd017849edcba2)_ + - [`scrcpy-win64-v1.15.1.zip`][direct-win64] + _(SHA-256: 78fba4caad6328016ea93219254b5df391f24224c519a2c8e3f070695b8b38ff)_ -[direct-win64]: https://github.com/Genymobile/scrcpy/releases/download/v1.15/scrcpy-win64-v1.15.zip +[direct-win64]: https://github.com/Genymobile/scrcpy/releases/download/v1.15.1/scrcpy-win64-v1.15.1.zip It is also available in [Chocolatey]: From a2cb63e344077f7c34a71333217bc1bdf53ba633 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jozef=20Holl=C3=BD?= Date: Sat, 8 Aug 2020 13:03:08 +0200 Subject: [PATCH 192/450] Add packaging status PR #1650 Signed-off-by: Romain Vimont --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index d45b6304..5cf015cf 100644 --- a/README.md +++ b/README.md @@ -34,6 +34,7 @@ control it using keyboard and mouse. ## Get the app +Packaging status ### Linux From a59a15777de9c3af3da27d60181ae046a71c09f4 Mon Sep 17 00:00:00 2001 From: Yu-Chen Lin Date: Sun, 9 Aug 2020 14:47:22 +0800 Subject: [PATCH 193/450] Fix missing change of Ctrl key in README PR #1652 Signed-off-by: Romain Vimont --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 5cf015cf..ee6978a2 100644 --- a/README.md +++ b/README.md @@ -449,7 +449,7 @@ Or by pressing MOD+o at any time. To turn it back on, press MOD+Shift+o. On Android, the `POWER` button always turns the screen on. For convenience, if -`POWER` is sent via scrcpy (via right-click or Ctrl+p), it +`POWER` is sent via scrcpy (via right-click or MOD+p), it will force to turn the screen off after a small delay (on a best effort basis). The physical `POWER` button will still cause the screen to be turned on. From 38940ffe892cab44a05d727be26b7a71e725bdba Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 9 Aug 2020 17:09:32 +0200 Subject: [PATCH 194/450] Revert "Inject WAKEUP instead of POWER" WAKEUP does not work on some devices. Fixes #1655 This reverts commit 322f1512ea806611eb37f508cd952bbbc050f853. --- .../src/main/java/com/genymobile/scrcpy/Controller.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/Controller.java b/server/src/main/java/com/genymobile/scrcpy/Controller.java index 2ad26a95..9100a9db 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Controller.java +++ b/server/src/main/java/com/genymobile/scrcpy/Controller.java @@ -55,10 +55,10 @@ public class Controller { public void control() throws IOException { // on start, power on the device if (!device.isScreenOn()) { - device.injectKeycode(KeyEvent.KEYCODE_WAKEUP); + device.injectKeycode(KeyEvent.KEYCODE_POWER); // dirty hack - // After the keycode is injected, the device is powered on asynchronously. + // After POWER is injected, the device is powered on asynchronously. // To turn the device screen off while mirroring, the client will send a message that // would be handled before the device is actually powered on, so its effect would // be "canceled" once the device is turned back on. @@ -248,8 +248,8 @@ public class Controller { } private boolean pressBackOrTurnScreenOn() { - int keycode = device.isScreenOn() ? KeyEvent.KEYCODE_BACK : KeyEvent.KEYCODE_WAKEUP; - if (keepPowerModeOff && keycode == KeyEvent.KEYCODE_WAKEUP) { + int keycode = device.isScreenOn() ? KeyEvent.KEYCODE_BACK : KeyEvent.KEYCODE_POWER; + if (keepPowerModeOff && keycode == KeyEvent.KEYCODE_POWER) { schedulePowerModeOff(); } return device.injectKeycode(keycode); From 95f1ea0d80e6bb61579ae6c9a50554d235d91383 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Mon, 10 Aug 2020 13:15:44 +0200 Subject: [PATCH 195/450] Fix clipboard paste condition To avoid possible copy-paste loops between the computer and the device, the device clipboard is not set if it already contains the expected content. But the condition was wrong: it was not set also if it was empty. Refs 1223a72eb83ebafb878a25356250896c45b5cefa Fixes #1658 --- server/src/main/java/com/genymobile/scrcpy/Device.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/Device.java b/server/src/main/java/com/genymobile/scrcpy/Device.java index de551f35..f23dd056 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Device.java +++ b/server/src/main/java/com/genymobile/scrcpy/Device.java @@ -223,7 +223,7 @@ public final class Device { } String currentClipboard = getClipboardText(); - if (currentClipboard == null || currentClipboard.equals(text)) { + if (currentClipboard != null && currentClipboard.equals(text)) { // The clipboard already contains the requested text. // Since pasting text from the computer involves setting the device clipboard, it could be set twice on a copy-paste. This would cause // the clipboard listeners to be notified twice, and that would flood the Android keyboard clipboard history. To workaround this From b87a0df99aa276673307f648853651158c2657b7 Mon Sep 17 00:00:00 2001 From: RaenonX JELLYCAT Date: Mon, 27 Jul 2020 09:14:15 -0500 Subject: [PATCH 196/450] Add Traditional Chinese translation for README Reviewed-by: Yu-Chen Lin Signed-off-by: Romain Vimont --- README.md | 2 +- README.zh-Hant.md | 705 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 706 insertions(+), 1 deletion(-) create mode 100644 README.zh-Hant.md diff --git a/README.md b/README.md index ee6978a2..688f9356 100644 --- a/README.md +++ b/README.md @@ -733,4 +733,4 @@ Read the [developers page]. - [Scrcpy now works wirelessly][article-tcpip] [article-intro]: https://blog.rom1v.com/2018/03/introducing-scrcpy/ -[article-tcpip]: https://www.genymotion.com/blog/open-source-project-scrcpy-now-works-wirelessly/ +[article-tcpip]: https://www.genymotion.com/blog/open-source-project-scrcpy-now-works-wirelessly/ \ No newline at end of file diff --git a/README.zh-Hant.md b/README.zh-Hant.md new file mode 100644 index 00000000..82eb1d9b --- /dev/null +++ b/README.zh-Hant.md @@ -0,0 +1,705 @@ +_Only the original [README](README.md) is guaranteed to be up-to-date._ + +_只有原版的 [README](README.md)是保證最新的。_ + + +本文件翻譯時點: [521f2fe](https://github.com/Genymobile/scrcpy/commit/521f2fe994019065e938aa1a54b56b4f10a4ac4a#diff-04c6e90faac2675aa89e2176d2eec7d8) + + +# scrcpy (v1.15) + +Scrcpy 可以透過 USB、或是 [TCP/IP][article-tcpip] 來顯示或控制 Android 裝置。且 scrcpy 不需要 _root_ 權限。 + +Scrcpy 目前支援 _GNU/Linux_、_Windows_ 和 _macOS_。 + +![screenshot](assets/screenshot-debian-600.jpg) + +特色: + + - **輕量** (只顯示裝置螢幕) + - **效能** (30~60fps) + - **品質** (1920×1080 或更高) + - **低延遲** ([35~70ms][lowlatency]) + - **快速啟動** (~1 秒就可以顯示第一個畫面) + - **非侵入性** (不安裝、留下任何東西在裝置上) + +[lowlatency]: https://github.com/Genymobile/scrcpy/pull/646 + + +## 需求 + +Android 裝置必須是 API 21+ (Android 5.0+)。 + +請確認裝置上的 [adb 偵錯 (通常位於開發者模式內)][enable-adb] 已啟用。 + +[enable-adb]: https://developer.android.com/studio/command-line/adb.html#Enabling + + +在部分的裝置上,你也必須啟用[特定的額外選項][control]才能使用鍵盤和滑鼠控制。 + +[control]: https://github.com/Genymobile/scrcpy/issues/70#issuecomment-373286323 + + +## 下載/獲取軟體 + + +### Linux + +Debian (目前支援 _testing_ 和 _sid_) 和 Ubuntu (20.04): + +``` +apt install scrcpy +``` + +[Snap] 上也可以下載: [`scrcpy`][snap-link]. + +[snap-link]: https://snapstats.org/snaps/scrcpy + +[snap]: https://en.wikipedia.org/wiki/Snappy_(package_manager) + +在 Fedora 上也可以使用 [COPR] 下載: [`scrcpy`][copr-link]. + +[COPR]: https://fedoraproject.org/wiki/Category:Copr +[copr-link]: https://copr.fedorainfracloud.org/coprs/zeno/scrcpy/ + +在 Arch Linux 上也可以使用 [AUR] 下載: [`scrcpy`][aur-link]. + +[AUR]: https://wiki.archlinux.org/index.php/Arch_User_Repository +[aur-link]: https://aur.archlinux.org/packages/scrcpy/ + +在 Gentoo 上也可以使用 [Ebuild] 下載: [`scrcpy/`][ebuild-link]. + +[Ebuild]: https://wiki.gentoo.org/wiki/Ebuild +[ebuild-link]: https://github.com/maggu2810/maggu2810-overlay/tree/master/app-mobilephone/scrcpy + +你也可以自己[編譯 _Scrcpy_][BUILD]。別擔心,並沒有想像中的難。 + + + +### Windows + +為了保持簡單,Windows 用戶可以下載一個包含所有必需軟體 (包含 `adb`) 的壓縮包: + +- [`scrcpy-win64-v1.15.zip`][direct-win64] + _(SHA-256: dd514bb591e63ef4cd52a53c30f1153a28f59722d64690eb07bd017849edcba2)_ + +[direct-win64]: https://github.com/Genymobile/scrcpy/releases/download/v1.15/scrcpy-win64-v1.15.zip + +[Chocolatey] 上也可以下載: + +[Chocolatey]: https://chocolatey.org/ + +```bash +choco install scrcpy +choco install adb # 如果你還沒有安裝的話 +``` + +[Scoop] 上也可以下載: + +```bash +scoop install scrcpy +scoop install adb # 如果你還沒有安裝的話 +``` + +[Scoop]: https://scoop.sh + +你也可以自己[編譯 _Scrcpy_][BUILD]。 + + +### macOS + +_Scrcpy_ 可以在 [Homebrew] 上直接安裝: + +[Homebrew]: https://brew.sh/ + +```bash +brew install scrcpy +``` + +由於執行期間需要可以藉由 `PATH` 存取 `adb` 。如果還沒有安裝 `adb` 可以使用下列方式安裝: + +```bash +brew cask install android-platform-tools +``` + +你也可以自己[編譯 _Scrcpy_][BUILD]。 + + +## 執行 + +將電腦和你的 Android 裝置連線,然後執行: + +```bash +scrcpy +``` + +_Scrcpy_ 可以接受命令列參數。輸入下列指令就可以瀏覽可以使用的命令列參數: + +```bash +scrcpy --help +``` + + +## 功能 + +> 以下說明中,有關快捷鍵的說明可能會出現 MOD 按鈕。相關說明請參見[快捷鍵]內的說明。 + +[快捷鍵]: #快捷鍵 + +### 畫面擷取 + +#### 縮小尺寸 + +使用比較低的解析度來投放 Android 裝置在某些情況可以提升效能。 + +限制寬和高的最大值(例如: 1024): + +```bash +scrcpy --max-size 1024 +scrcpy -m 1024 # 縮短版本 +``` + +比較小的參數會根據螢幕比例重新計算。 +根據上面的範例,1920x1080 會被縮小成 1024x576。 + + +#### 更改 bit-rate + +預設的 bit-rate 是 8 Mbps。如果要更改 bit-rate (例如: 2 Mbps): + +```bash +scrcpy --bit-rate 2M +scrcpy -b 2M # 縮短版本 +``` + +#### 限制 FPS + +限制畫面最高的 FPS: + +```bash +scrcpy --max-fps 15 +``` + +僅在 Android 10 後正式支援,不過也有可能可以在 Android 10 以前的版本使用。 + +#### 裁切 + +裝置的螢幕可以裁切。如此一來,鏡像出來的螢幕就只會是原本的一部份。 + +假如只要鏡像 Oculus Go 的其中一隻眼睛: + +```bash +scrcpy --crop 1224:1440:0:0 # 位於 (0,0),大小1224x1440 +``` + +如果 `--max-size` 也有指定的話,裁切後才會縮放。 + + +#### 鎖定影像方向 + + +如果要鎖定鏡像影像方向: + +```bash +scrcpy --lock-video-orientation 0 # 原本的方向 +scrcpy --lock-video-orientation 1 # 逆轉 90° +scrcpy --lock-video-orientation 2 # 180° +scrcpy --lock-video-orientation 3 # 順轉 90° +``` + +這會影響錄影結果的影像方向。 + + +### 錄影 + +鏡像投放螢幕的同時也可以錄影: + +```bash +scrcpy --record file.mp4 +scrcpy -r file.mkv +``` + +如果只要錄影,不要投放螢幕鏡像的話: + +```bash +scrcpy --no-display --record file.mp4 +scrcpy -Nr file.mkv +# 用 Ctrl+C 停止錄影 +``` + +就算有些幀為了效能而被跳過,它們還是一樣會被錄製。 + +裝置上的每一幀都有時間戳記,所以 [封包延遲 (Packet Delay Variation, PDV)][packet delay variation] 並不會影響錄影的檔案。 + +[packet delay variation]: https://en.wikipedia.org/wiki/Packet_delay_variation + + +### 連線 + +#### 無線 + +_Scrcpy_ 利用 `adb` 和裝置通訊,而 `adb` 可以[透過 TCP/IP 連結][connect]: + +1. 讓電腦和裝置連到同一個 Wi-Fi。 +2. 獲取手機的 IP 位址(設定 → 關於手機 → 狀態). +3. 啟用裝置上的 `adb over TCP/IP`: `adb tcpip 5555`. +4. 拔掉裝置上的線。 +5. 透過 TCP/IP 連接裝置: `adb connect DEVICE_IP:5555` _(把 `DEVICE_IP` 換成裝置的IP位址)_. +6. 和平常一樣執行 `scrcpy`。 + +如果效能太差,可以降低 bit-rate 和解析度: + +```bash +scrcpy --bit-rate 2M --max-size 800 +scrcpy -b2M -m800 # 縮短版本 +``` + +[connect]: https://developer.android.com/studio/command-line/adb.html#wireless + + +#### 多裝置 + +如果 `adb devices` 內有多個裝置,則必須附上 _serial_: + +```bash +scrcpy --serial 0123456789abcdef +scrcpy -s 0123456789abcdef # 縮短版本 +``` + +如果裝置是透過 TCP/IP 連線: + +```bash +scrcpy --serial 192.168.0.1:5555 +scrcpy -s 192.168.0.1:5555 # 縮短版本 +``` + +你可以啟用復數個對應不同裝置的 _scrcpy_。 + +#### 裝置連結後自動啟動 + +你可以使用 [AutoAdb]: + +```bash +autoadb scrcpy -s '{}' +``` + +[AutoAdb]: https://github.com/rom1v/autoadb + +#### SSH tunnel + +本地的 `adb` 可以連接到遠端的 `adb` 伺服器(假設兩者使用相同版本的 _adb_ 通訊協定),以連接到遠端裝置: + +```bash +adb kill-server # 停止在 Port 5037 的本地 adb 伺服 +ssh -CN -L5037:localhost:5037 -R27183:localhost:27183 your_remote_computer +# 保持開啟 +``` + +從另外一個終端機: + +```bash +scrcpy +``` + +如果要避免啟用 remote port forwarding,你可以強制它使用 forward connection (注意 `-L` 和 `-R` 的差別): + +```bash +adb kill-server # 停止在 Port 5037 的本地 adb 伺服 +ssh -CN -L5037:localhost:5037 -L27183:localhost:27183 your_remote_computer +# 保持開啟 +``` + +從另外一個終端機: + +```bash +scrcpy --force-adb-forward +``` + + +和無線連接一樣,有時候降低品質會比較好: + +``` +scrcpy -b2M -m800 --max-fps 15 +``` + +### 視窗調整 + +#### 標題 + +預設標題是裝置的型號,不過可以透過以下方式修改: + +```bash +scrcpy --window-title 'My device' +``` + +#### 位置 & 大小 + +初始的視窗位置和大小也可以指定: + +```bash +scrcpy --window-x 100 --window-y 100 --window-width 800 --window-height 600 +``` + +#### 無邊框 + +如果要停用視窗裝飾: + +```bash +scrcpy --window-borderless +``` + +#### 保持最上層 + +如果要保持 `scrcpy` 的視窗在最上層: + +```bash +scrcpy --always-on-top +``` + +#### 全螢幕 + +這個軟體可以直接在全螢幕模式下起動: + +```bash +scrcpy --fullscreen +scrcpy -f # 縮短版本 +``` + +全螢幕可以使用 MOD+f 開關。 + +#### 旋轉 + +視窗可以旋轉: + +```bash +scrcpy --rotation 1 +``` + +可用的數值: + - `0`: 不旋轉 + - `1`: 90 度**逆**轉 + - `2`: 180 度 + - `3`: 90 度**順**轉 + +旋轉方向也可以使用 MOD+ _(左方向鍵)_ 和 MOD+ _(右方向鍵)_ 調整。 + +_scrcpy_ 有 3 種不同的旋轉: + - MOD+r 要求裝置在垂直、水平之間旋轉 (目前運行中的 App 有可能會因為不支援而拒絕)。 + - `--lock-video-orientation` 修改鏡像的方向 (裝置傳給電腦的影像)。這會影響錄影結果的影像方向。 + - `--rotation` (或是 MOD+ / MOD+) 只旋轉視窗的內容。這只會影響鏡像結果,不會影響錄影結果。 + + +### 其他鏡像選項 + +#### 唯讀 + +停用所有控制,包含鍵盤輸入、滑鼠事件、拖放檔案: + +```bash +scrcpy --no-control +scrcpy -n +``` + +#### 顯示螢幕 + +如果裝置有複數個螢幕,可以指定要鏡像哪個螢幕: + +```bash +scrcpy --display 1 +``` + +可以透過下列指令獲取螢幕 ID: + +``` +adb shell dumpsys display # 找輸出結果中的 "mDisplayId=" +``` + +第二螢幕只有在 Android 10+ 時可以控制。如果不是 Android 10+,螢幕就會在唯讀狀態下投放。 + + +#### 保持清醒 + +如果要避免裝置在連接狀態下進入睡眠: + +```bash +scrcpy --stay-awake +scrcpy -w +``` + +_scrcpy_ 關閉後就會回復成原本的設定。 + + +#### 關閉螢幕 + +鏡像開始時,可以要求裝置關閉螢幕: + +```bash +scrcpy --turn-screen-off +scrcpy -S +``` + +或是在任何時候輸入 MOD+o。 + +如果要開啟螢幕,輸入 MOD+Shift+o。 + +在 Android 上,`POWER` 按鈕總是開啟螢幕。 + +為了方便,如果 `POWER` 是透過 scrcpy 轉送 (右鍵 或 MOD+p)的話,螢幕將會在短暫的延遲後關閉。 + +實際在手機上的 `POWER` 還是會開啟螢幕。 + +防止裝置進入睡眠狀態: + +```bash +scrcpy --turn-screen-off --stay-awake +scrcpy -Sw +``` + + +#### 顯示過期的幀 + +為了降低延遲, _scrcpy_ 預設只會顯示最後解碼的幀,並且拋棄所有在這之前的幀。 + +如果要強制顯示所有的幀 (有可能會拉高延遲),輸入: + +```bash +scrcpy --render-expired-frames +``` + +#### 顯示觸控點 + +對於要報告的人來說,顯示裝置上的實際觸控點有時候是有幫助的。 + +Android 在_開發者選項_中有提供這個功能。 + +_Scrcpy_ 可以在啟動時啟用這個功能,並且在停止後恢復成原本的設定: + +```bash +scrcpy --show-touches +scrcpy -t +``` + +這個選項只會顯示**實際觸碰在裝置上的觸碰點**。 + + +### 輸入控制 + + +#### 旋轉裝置螢幕 + +輸入 MOD+r 以在垂直、水平之間切換。 + +如果使用中的程式不支援,則不會切換。 + + +#### 複製/貼上 + +如果 Android 剪貼簿上的內容有任何更動,電腦的剪貼簿也會一起更動。 + +任何與 Ctrl 相關的快捷鍵事件都會轉送到裝置上。特別來說: + - Ctrl+c 通常是複製 + - Ctrl+x 通常是剪下 + - Ctrl+v 通常是貼上 (在電腦的剪貼簿與裝置上的剪貼簿同步之後) + +這些跟你通常預期的行為一樣。 + +但是,實際上的行為是根據目前運行中的應用程式而定。 + +舉例來說, _Termux_ 在收到 Ctrl+c 後,會傳送 SIGINT;而 _K-9 Mail_ 則是建立新訊息。 + +如果在這情況下,要剪下、複製或貼上 (只有在Android 7+時才支援): + - MOD+c 注入 `複製` + - MOD+x 注入 `剪下` + - MOD+v 注入 `貼上` (在電腦的剪貼簿與裝置上的剪貼簿同步之後) + +另外,MOD+Shift+v 則是以一連串的按鍵事件貼上電腦剪貼簿中的內容。當元件不允許文字貼上 (例如 _Termux_) 時,這就很有用。不過,這在非 ASCII 內容上就無法使用。 + +**警告:** 貼上電腦的剪貼簿內容 (無論是從 Ctrl+vMOD+v) 時,會複製剪貼簿中的內容至裝置的剪貼簿上。這會讓所有 Android 程式讀取剪貼簿的內容。請避免貼上任何敏感內容 (像是密碼)。 + + +#### 文字輸入偏好 + +輸入文字時,有兩種[事件][textevents]會被觸發: + - _鍵盤事件 (key events)_,代表有一個按鍵被按下或放開 + - _文字事件 (text events)_,代表有一個文字被輸入 + +預設上,文字是被以鍵盤事件 (key events) 輸入的,所以鍵盤和遊戲內所預期的一樣 (通常是指 WASD)。 + +但是這可能造成[一些問題][prefertext]。如果在這輸入這方面遇到了問題,你可以試試: + +```bash +scrcpy --prefer-text +``` + +(不過遊戲內鍵盤就會不可用) + +[textevents]: https://blog.rom1v.com/2018/03/introducing-scrcpy/#handle-text-input +[prefertext]: https://github.com/Genymobile/scrcpy/issues/650#issuecomment-512945343 + + +#### 重複輸入 + +通常來說,長時間按住一個按鍵會重複觸發按鍵事件。這會在一些遊戲中造成效能問題,而且這個重複的按鍵事件是沒有意義的。 + +如果不要轉送這些重複的按鍵事件: + +```bash +scrcpy --no-key-repeat +``` + + +### 檔案 + +#### 安裝 APK + +如果要安裝 APK ,拖放一個 APK 檔案 (以 `.apk` 為副檔名) 到 _scrcpy_ 的視窗上。 + +視窗上不會有任何反饋;結果會顯示在命令列中。 + + +#### 推送檔案至裝置 + +如果要推送檔案到裝置上的 `/sdcard/` ,拖放一個非 APK 檔案 (**不**以 `.apk` 為副檔名) 到 _scrcpy_ 的視窗上。 + +視窗上不會有任何反饋;結果會顯示在命令列中。 + +推送檔案的目標路徑可以在啟動時指定: + +```bash +scrcpy --push-target /sdcard/foo/bar/ +``` + + +### 音訊轉送 + +_scrcpy_ **不**轉送音訊。請使用 [sndcpy]。另外,參見 [issue #14]。 + +[sndcpy]: https://github.com/rom1v/sndcpy +[issue #14]: https://github.com/Genymobile/scrcpy/issues/14 + + +## 快捷鍵 + +在以下的清單中,MOD 是快捷鍵的特殊按鍵。通常來說,這個按鍵是 (左) Alt 或是 (左) Super。 + +這個是可以使用 `--shortcut-mod` 更改的。可以用的選項有: +- `lctrl`: 左邊的 Ctrl +- `rctrl`: 右邊的 Ctrl +- `lalt`: 左邊的 Alt +- `ralt`: 右邊的 Alt +- `lsuper`: 左邊的 Super +- `rsuper`: 右邊的 Super + +```bash +# 以 右邊的 Ctrl 為快捷鍵特殊按鍵 +scrcpy --shortcut-mod=rctrl + +# 以 左邊的 Ctrl 和左邊的 Alt 或是 左邊的 Super 為快捷鍵特殊按鍵 +scrcpy --shortcut-mod=lctrl+lalt,lsuper +``` + +_[Super] 通常是 WindowsCmd 鍵。_ + +[Super]: https://en.wikipedia.org/wiki/Super_key_(keyboard_button) + + | Action | Shortcut + | ------------------------------------------------- |:----------------------------- + | 切換至全螢幕 | MOD+f + | 左旋顯示螢幕 | MOD+ _(左)_ + | 右旋顯示螢幕 | MOD+ _(右)_ + | 縮放視窗成 1:1 (pixel-perfect) | MOD+g + | 縮放視窗到沒有黑邊框為止 | MOD+w \| _雙擊¹_ + | 按下 `首頁` 鍵 | MOD+h \| _中鍵_ + | 按下 `返回` 鍵 | MOD+b \| _右鍵²_ + | 按下 `切換 APP` 鍵 | MOD+s + | 按下 `選單` 鍵 (或解鎖螢幕) | MOD+m + | 按下 `音量+` 鍵 | MOD+ _(上)_ + | 按下 `音量-` 鍵 | MOD+ _(下)_ + | 按下 `電源` 鍵 | MOD+p + | 開啟 | _右鍵²_ + | 關閉裝置螢幕(持續鏡像) | MOD+o + | 開啟裝置螢幕 | MOD+Shift+o + | 旋轉裝置螢幕 | MOD+r + | 開啟通知列 | MOD+n + | 關閉通知列 | MOD+Shift+n + | 複製至剪貼簿³ | MOD+c + | 剪下至剪貼簿³ | MOD+x + | 同步剪貼簿並貼上³ | MOD+v + | 複製電腦剪貼簿中的文字至裝置並貼上 | MOD+Shift+v + | 啟用/停用 FPS 計數器(顯示於 stdout - 通常是命令列) | MOD+i + +_¹在黑邊框上雙擊以移除它們。_ +_²右鍵會返回。如果螢幕是關閉狀態,則會打開螢幕。_ +_³只支援 Android 7+。_ + +所有 Ctrl+_按鍵_ 快捷鍵都會傳送到裝置上,所以它們是由目前運作的應用程式處理的。 + + +## 自訂路徑 + +如果要使用特定的 _adb_ ,將它設定到環境變數中的 `ADB`: + + ADB=/path/to/adb scrcpy + +如果要覆寫 `scrcpy-server` 檔案的路徑,則將路徑設定到環境變數中的 `SCRCPY_SERVER_PATH`。 + +[相關連結][useful] + +[useful]: https://github.com/Genymobile/scrcpy/issues/278#issuecomment-429330345 + + +## 為何叫 _scrcpy_ ? + +有一個同事要我找一個跟 [gnirehtet] 一樣難念的名字。 + +[`strcpy`] 複製一個字串 (**str**ing);`scrcpy` 複製一個螢幕 (**scr**een)。 + +[gnirehtet]: https://github.com/Genymobile/gnirehtet +[`strcpy`]: http://man7.org/linux/man-pages/man3/strcpy.3.html + + +## 如何編譯? + +請看[這份文件 (英文)][BUILD]。 + +[BUILD]: BUILD.md + + +## 常見問題 + +請看[這份文件 (英文)][FAQ]。 + +[FAQ]: FAQ.md + + +## 開發者文件 + +請看[這個頁面 (英文)][developers page]. + +[developers page]: DEVELOP.md + + +## Licence + + Copyright (C) 2018 Genymobile + Copyright (C) 2018-2020 Romain Vimont + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +## 相關文章 + +- [Scrcpy 簡介 (英文)][article-intro] +- [Scrcpy 可以無線連線了 (英文)][article-tcpip] + +[article-intro]: https://blog.rom1v.com/2018/03/introducing-scrcpy/ +[article-tcpip]: https://www.genymotion.com/blog/open-source-project-scrcpy-now-works-wirelessly/ From 8081e9cc11ac96b4dbe452991865d762f3dec468 Mon Sep 17 00:00:00 2001 From: RaenonX JELLYCAT Date: Mon, 27 Jul 2020 09:24:04 -0500 Subject: [PATCH 197/450] Add reference of the translations in README Reviewed-by: Yu-Chen Lin Signed-off-by: Romain Vimont --- README.md | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 688f9356..0bec9ea6 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@ # scrcpy (v1.15.1) +[Read in another language](#translations) + 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. It works on _GNU/Linux_, _Windows_ and _macOS_. @@ -733,4 +735,14 @@ Read the [developers page]. - [Scrcpy now works wirelessly][article-tcpip] [article-intro]: https://blog.rom1v.com/2018/03/introducing-scrcpy/ -[article-tcpip]: https://www.genymotion.com/blog/open-source-project-scrcpy-now-works-wirelessly/ \ No newline at end of file +[article-tcpip]: https://www.genymotion.com/blog/open-source-project-scrcpy-now-works-wirelessly/ + +## Translations + +This README is available in other languages: + +- [繁體中文 (Traditional Chinese, `zh-Hant`) - v1.15](README.zh-Hant.md) +- [한국어 (Korean, `ko`) - v1.11](README.ko.md) +- [português brasileiro (Brazilian Portuguese, `pt-BR`) - v1.12.1](README.pt-br.md) + +Only this README file is guaranteed to be up-to-date. From 198346d1482829119cf07c565086840254e519b9 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 9 Aug 2020 16:04:02 +0200 Subject: [PATCH 198/450] Add pinch-to-zoom simulation If Ctrl is hold when the left-click button is pressed, enable pinch-to-zoom to scale and rotate relative to the center of the screen. Fixes #24 --- README.md | 14 +++++++ app/scrcpy.1 | 4 ++ app/src/cli.c | 3 ++ app/src/control_msg.h | 1 + app/src/input_manager.c | 84 +++++++++++++++++++++++++++++++++++++---- app/src/input_manager.h | 2 + 6 files changed, 100 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index ee6978a2..3f6d6b68 100644 --- a/README.md +++ b/README.md @@ -546,6 +546,19 @@ into the device clipboard. As a consequence, any Android application could read its content. You should avoid to paste sensitive content (like passwords) that way. + +#### Pinch-to-zoom + +To simulate "pinch-to-zoom": Ctrl+_click-and-move_. + +More precisely, hold Ctrl while pressing the left-click button. Until +the left-click button is released, all mouse movements scale and rotate the +content (if supported by the app) relative to the center of the screen. + +Concretely, scrcpy generates additional touch events from a "virtual finger" at +a location inverted through the center of the screen. + + #### Text injection preference There are two kinds of [events][textevents] generated when typing text: @@ -659,6 +672,7 @@ _[Super] is typically the Windows or Cmd key._ | Synchronize clipboards and paste³ | MOD+v | Inject computer clipboard text | MOD+Shift+v | Enable/disable FPS counter (on stdout) | MOD+i + | Pinch-to-zoom | Ctrl+_click-and-move_ _¹Double-click on black borders to remove them._ _²Right-click turns the screen on if it was off, presses BACK otherwise._ diff --git a/app/scrcpy.1 b/app/scrcpy.1 index 49fa78dc..4f3a8b9c 100644 --- a/app/scrcpy.1 +++ b/app/scrcpy.1 @@ -316,6 +316,10 @@ Inject computer clipboard text as a sequence of key events .B MOD+i Enable/disable FPS counter (print frames/second in logs) +.TP +.B Ctrl+click-and-move +Pinch-to-zoom from the center of the screen + .TP .B Drag & drop APK file Install APK from computer diff --git a/app/src/cli.c b/app/src/cli.c index c957b22f..c960727e 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -279,6 +279,9 @@ scrcpy_print_usage(const char *arg0) { " MOD+i\n" " Enable/disable FPS counter (print frames/second in logs)\n" "\n" + " Ctrl+click-and-move\n" + " Pinch-to-zoom from the center of the screen\n" + "\n" " Drag & drop APK file\n" " Install APK from computer\n" "\n", diff --git a/app/src/control_msg.h b/app/src/control_msg.h index e0b480de..6e3f239c 100644 --- a/app/src/control_msg.h +++ b/app/src/control_msg.h @@ -17,6 +17,7 @@ #define CONTROL_MSG_CLIPBOARD_TEXT_MAX_LENGTH (CONTROL_MSG_MAX_SIZE - 6) #define POINTER_ID_MOUSE UINT64_C(-1); +#define POINTER_ID_VIRTUAL_FINGER UINT64_C(-2); enum control_msg_type { CONTROL_MSG_TYPE_INJECT_KEYCODE, diff --git a/app/src/input_manager.c b/app/src/input_manager.c index 1d73980c..962db1d3 100644 --- a/app/src/input_manager.c +++ b/app/src/input_manager.c @@ -70,6 +70,8 @@ input_manager_init(struct input_manager *im, im->sdl_shortcut_mods.data[i] = sdl_mod; } im->sdl_shortcut_mods.count = shortcut_mods->count; + + im->vfinger_down = false; } static void @@ -299,6 +301,36 @@ input_manager_process_text_input(struct input_manager *im, } } +static bool +simulate_virtual_finger(struct input_manager *im, + enum android_motionevent_action action, + struct point point) { + bool up = action == AMOTION_EVENT_ACTION_UP; + + struct control_msg msg; + msg.type = CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT; + msg.inject_touch_event.action = action; + msg.inject_touch_event.position.screen_size = im->screen->frame_size; + msg.inject_touch_event.position.point = point; + msg.inject_touch_event.pointer_id = POINTER_ID_VIRTUAL_FINGER; + msg.inject_touch_event.pressure = up ? 0.0f : 1.0f; + msg.inject_touch_event.buttons = 0; + + if (!controller_push_msg(im->controller, &msg)) { + LOGW("Could not request 'inject virtual finger event'"); + return false; + } + + return true; +} + +static struct point +inverse_point(struct point point, struct size size) { + point.x = size.width - point.x; + point.y = size.height - point.y; + return point; +} + static bool convert_input_key(const SDL_KeyboardEvent *from, struct control_msg *to, bool prefer_text, uint32_t repeat) { @@ -512,10 +544,18 @@ input_manager_process_mouse_motion(struct input_manager *im, return; } struct control_msg msg; - if (convert_mouse_motion(event, im->screen, &msg)) { - if (!controller_push_msg(im->controller, &msg)) { - LOGW("Could not request 'inject mouse motion event'"); - } + if (!convert_mouse_motion(event, im->screen, &msg)) { + return; + } + + if (!controller_push_msg(im->controller, &msg)) { + LOGW("Could not request 'inject mouse motion event'"); + } + + if (im->vfinger_down) { + struct point mouse = msg.inject_touch_event.position.point; + struct point vfinger = inverse_point(mouse, im->screen->frame_size); + simulate_virtual_finger(im, AMOTION_EVENT_ACTION_MOVE, vfinger); } } @@ -587,7 +627,9 @@ input_manager_process_mouse_button(struct input_manager *im, // simulated from touch events, so it's a duplicate return; } - if (event->type == SDL_MOUSEBUTTONDOWN) { + + bool down = event->type == SDL_MOUSEBUTTONDOWN; + if (down) { if (control && event->button == SDL_BUTTON_RIGHT) { press_back_or_turn_screen_on(im->controller); return; @@ -618,10 +660,36 @@ input_manager_process_mouse_button(struct input_manager *im, } struct control_msg msg; - if (convert_mouse_button(event, im->screen, &msg)) { - if (!controller_push_msg(im->controller, &msg)) { - LOGW("Could not request 'inject mouse button event'"); + if (!convert_mouse_button(event, im->screen, &msg)) { + return; + } + + if (!controller_push_msg(im->controller, &msg)) { + LOGW("Could not request 'inject mouse button event'"); + return; + } + + // Pinch-to-zoom simulation. + // + // If Ctrl is hold when the left-click button is pressed, then + // pinch-to-zoom mode is enabled: on every mouse event until the left-click + // button is released, an additional "virtual finger" event is generated, + // having a position inverted through the center of the screen. + // + // In other words, the center of the rotation/scaling is the center of the + // screen. +#define CTRL_PRESSED (SDL_GetModState() & (KMOD_LCTRL | KMOD_RCTRL)) + if ((down && !im->vfinger_down && CTRL_PRESSED) + || (!down && im->vfinger_down)) { + struct point mouse = msg.inject_touch_event.position.point; + struct point vfinger = inverse_point(mouse, im->screen->frame_size); + enum android_motionevent_action action = down + ? AMOTION_EVENT_ACTION_DOWN + : AMOTION_EVENT_ACTION_UP; + if (!simulate_virtual_finger(im, action, vfinger)) { + return; } + im->vfinger_down = down; } } diff --git a/app/src/input_manager.h b/app/src/input_manager.h index 8811c457..c3756e40 100644 --- a/app/src/input_manager.h +++ b/app/src/input_manager.h @@ -30,6 +30,8 @@ struct input_manager { unsigned data[SC_MAX_SHORTCUT_MODS]; unsigned count; } sdl_shortcut_mods; + + bool vfinger_down; }; void From d7779d08e8f9004ec44197cd8eb7fdec0e1d4dd1 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Mon, 10 Aug 2020 20:09:28 +0200 Subject: [PATCH 199/450] Bump version to 1.16 --- meson.build | 2 +- server/build.gradle | 4 ++-- server/build_without_gradle.sh | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/meson.build b/meson.build index 8492f520..11964cf6 100644 --- a/meson.build +++ b/meson.build @@ -1,5 +1,5 @@ project('scrcpy', 'c', - version: '1.15.1', + version: '1.16', meson_version: '>= 0.48', default_options: [ 'c_std=c11', diff --git a/server/build.gradle b/server/build.gradle index c7c0abb0..c7f8bc0f 100644 --- a/server/build.gradle +++ b/server/build.gradle @@ -6,8 +6,8 @@ android { applicationId "com.genymobile.scrcpy" minSdkVersion 21 targetSdkVersion 29 - versionCode 18 - versionName "1.15.1" + versionCode 19 + versionName "1.16" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" } buildTypes { diff --git a/server/build_without_gradle.sh b/server/build_without_gradle.sh index cedbe186..e757be9a 100755 --- a/server/build_without_gradle.sh +++ b/server/build_without_gradle.sh @@ -12,7 +12,7 @@ set -e SCRCPY_DEBUG=false -SCRCPY_VERSION_NAME=1.15.1 +SCRCPY_VERSION_NAME=1.16 PLATFORM=${ANDROID_PLATFORM:-29} BUILD_TOOLS=${ANDROID_BUILD_TOOLS:-29.0.2} From 479d10dc22b70272187e0963c6ad24d754a669a2 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Mon, 10 Aug 2020 20:34:51 +0200 Subject: [PATCH 200/450] Update links to v1.16 in README and BUILD --- BUILD.md | 6 +++--- README.md | 8 ++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/BUILD.md b/BUILD.md index 2f1c2be9..a7c395ee 100644 --- a/BUILD.md +++ b/BUILD.md @@ -254,10 +254,10 @@ You can then [run](README.md#run) _scrcpy_. ## Prebuilt server - - [`scrcpy-server-v1.15.1`][direct-scrcpy-server] - _(SHA-256: fe06bd6a30da8c89860bf5e16eecce2b5054d4644c84289670ce00ca5d1637c3)_ + - [`scrcpy-server-v1.16`][direct-scrcpy-server] + _(SHA-256: 94a79e05b4498d0460ab7bd9d12cbf05156e3a47bf0c5d1420cee1d4493b3832)_ -[direct-scrcpy-server]: https://github.com/Genymobile/scrcpy/releases/download/v1.15.1/scrcpy-server-v1.15.1 +[direct-scrcpy-server]: https://github.com/Genymobile/scrcpy/releases/download/v1.16/scrcpy-server-v1.16 Download the prebuilt server somewhere, and specify its path during the Meson configuration: diff --git a/README.md b/README.md index 55b0f127..e8b54465 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# scrcpy (v1.15.1) +# scrcpy (v1.16) [Read in another language](#translations) @@ -77,10 +77,10 @@ hard). For Windows, for simplicity, a prebuilt archive with all the dependencies (including `adb`) is available: - - [`scrcpy-win64-v1.15.1.zip`][direct-win64] - _(SHA-256: 78fba4caad6328016ea93219254b5df391f24224c519a2c8e3f070695b8b38ff)_ + - [`scrcpy-win64-v1.16.zip`][direct-win64] + _(SHA-256: 3f30dc5db1a2f95c2b40a0f5de91ec1642d9f53799250a8c529bc882bc0918f0)_ -[direct-win64]: https://github.com/Genymobile/scrcpy/releases/download/v1.15.1/scrcpy-win64-v1.15.1.zip +[direct-win64]: https://github.com/Genymobile/scrcpy/releases/download/v1.16/scrcpy-win64-v1.16.zip It is also available in [Chocolatey]: From 6cc22e1c5b370baecee9072f52c44fdd13ba9634 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 16 Aug 2020 13:44:42 +0200 Subject: [PATCH 201/450] Reference --shortcut-mod from shortcuts list Fixes #1681 Suggested-by: Moritz Schulz --- app/scrcpy.1 | 2 +- app/src/cli.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/scrcpy.1 b/app/scrcpy.1 index 4f3a8b9c..6a890f14 100644 --- a/app/scrcpy.1 +++ b/app/scrcpy.1 @@ -222,7 +222,7 @@ Default is 0 (automatic).\n .SH SHORTCUTS In the following list, MOD is the shortcut modifier. By default, it's (left) -Alt or (left) Super, but it can be configured by \-\-shortcut-mod. +Alt or (left) Super, but it can be configured by \-\-shortcut-mod (see above). .TP .B MOD+f diff --git a/app/src/cli.c b/app/src/cli.c index c960727e..df84f6f5 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -203,7 +203,7 @@ scrcpy_print_usage(const char *arg0) { "\n" " In the following list, MOD is the shortcut modifier. By default,\n" " it's (left) Alt or (left) Super, but it can be configured by\n" - " --shortcut-mod.\n" + " --shortcut-mod (see above).\n" "\n" " MOD+f\n" " Switch fullscreen mode\n" From d02789ce21a183682f968cc95dcd88c98efba0f0 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 16 Aug 2020 13:52:01 +0200 Subject: [PATCH 202/450] List available shortcut keys on error Fixes #1681 Suggested-by: Moritz Schulz --- app/src/cli.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/src/cli.c b/app/src/cli.c index df84f6f5..9c791fbf 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -532,7 +532,9 @@ parse_shortcut_mods_item(const char *item, size_t len) { } else if (STREQ("rsuper", item, key_len)) { mod |= SC_MOD_RSUPER; } else { - LOGW("Unknown modifier key: %.*s", (int) key_len, item); + LOGE("Unknown modifier key: %.*s " + "(must be one of: lctrl, rctrl, lalt, ralt, lsuper, rsuper)", + (int) key_len, item); return 0; } #undef STREQ From 0be766e71a30eec1709da7b24999b6df2b40dc65 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 20 Aug 2020 19:14:45 +0200 Subject: [PATCH 203/450] Add undetected device error message in FAQ --- FAQ.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/FAQ.md b/FAQ.md index 871ae8f0..ba33542e 100644 --- a/FAQ.md +++ b/FAQ.md @@ -37,6 +37,8 @@ Check [stackoverflow][device-unauthorized]. ### Device not detected +> adb: error: failed to get feature set: no devices/emulators found + If your device is not detected, you may need some [drivers] (on Windows). [drivers]: https://developer.android.com/studio/run/oem-usb.html From 0c5e0a4f6d30834e7aa1623636675df5d9bda1f2 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 21 Aug 2020 12:24:11 +0200 Subject: [PATCH 204/450] Make Device methods static when possible The behavior of some methods do not depend on the user-provided options. These methods can be static. This will allow to call them directly from the cleanup process. --- .../com/genymobile/scrcpy/Controller.java | 12 +++--- .../java/com/genymobile/scrcpy/Device.java | 40 +++++++++---------- .../java/com/genymobile/scrcpy/Server.java | 2 +- 3 files changed, 27 insertions(+), 27 deletions(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/Controller.java b/server/src/main/java/com/genymobile/scrcpy/Controller.java index 9100a9db..79feefc1 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Controller.java +++ b/server/src/main/java/com/genymobile/scrcpy/Controller.java @@ -54,7 +54,7 @@ public class Controller { public void control() throws IOException { // on start, power on the device - if (!device.isScreenOn()) { + if (!Device.isScreenOn()) { device.injectKeycode(KeyEvent.KEYCODE_POWER); // dirty hack @@ -105,13 +105,13 @@ public class Controller { } break; case ControlMessage.TYPE_EXPAND_NOTIFICATION_PANEL: - device.expandNotificationPanel(); + Device.expandNotificationPanel(); break; case ControlMessage.TYPE_COLLAPSE_NOTIFICATION_PANEL: - device.collapsePanels(); + Device.collapsePanels(); break; case ControlMessage.TYPE_GET_CLIPBOARD: - String clipboardText = device.getClipboardText(); + String clipboardText = Device.getClipboardText(); if (clipboardText != null) { sender.pushClipboardText(clipboardText); } @@ -130,7 +130,7 @@ public class Controller { } break; case ControlMessage.TYPE_ROTATE_DEVICE: - device.rotateDevice(); + Device.rotateDevice(); break; default: // do nothing @@ -248,7 +248,7 @@ public class Controller { } private boolean pressBackOrTurnScreenOn() { - int keycode = device.isScreenOn() ? KeyEvent.KEYCODE_BACK : KeyEvent.KEYCODE_POWER; + int keycode = Device.isScreenOn() ? KeyEvent.KEYCODE_BACK : KeyEvent.KEYCODE_POWER; if (keepPowerModeOff && keycode == KeyEvent.KEYCODE_POWER) { schedulePowerModeOff(); } diff --git a/server/src/main/java/com/genymobile/scrcpy/Device.java b/server/src/main/java/com/genymobile/scrcpy/Device.java index f23dd056..a8fdf677 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Device.java +++ b/server/src/main/java/com/genymobile/scrcpy/Device.java @@ -25,6 +25,8 @@ public final class Device { public static final int POWER_MODE_OFF = SurfaceControl.POWER_MODE_OFF; public static final int POWER_MODE_NORMAL = SurfaceControl.POWER_MODE_NORMAL; + private static final ServiceManager SERVICE_MANAGER = new ServiceManager(); + public interface RotationListener { void onRotationChanged(int rotation); } @@ -33,8 +35,6 @@ public final class Device { void onClipboardTextChanged(String text); } - private final ServiceManager serviceManager = new ServiceManager(); - private ScreenInfo screenInfo; private RotationListener rotationListener; private ClipboardListener clipboardListener; @@ -54,9 +54,9 @@ public final class Device { public Device(Options options) { displayId = options.getDisplayId(); - DisplayInfo displayInfo = serviceManager.getDisplayManager().getDisplayInfo(displayId); + DisplayInfo displayInfo = SERVICE_MANAGER.getDisplayManager().getDisplayInfo(displayId); if (displayInfo == null) { - int[] displayIds = serviceManager.getDisplayManager().getDisplayIds(); + int[] displayIds = SERVICE_MANAGER.getDisplayManager().getDisplayIds(); throw new InvalidDisplayIdException(displayId, displayIds); } @@ -65,7 +65,7 @@ public final class Device { screenInfo = ScreenInfo.computeScreenInfo(displayInfo, options.getCrop(), options.getMaxSize(), options.getLockedVideoOrientation()); layerStack = displayInfo.getLayerStack(); - serviceManager.getWindowManager().registerRotationWatcher(new IRotationWatcher.Stub() { + SERVICE_MANAGER.getWindowManager().registerRotationWatcher(new IRotationWatcher.Stub() { @Override public void onRotationChanged(int rotation) { synchronized (Device.this) { @@ -81,7 +81,7 @@ public final class Device { if (options.getControl()) { // If control is enabled, synchronize Android clipboard to the computer automatically - ClipboardManager clipboardManager = serviceManager.getClipboardManager(); + ClipboardManager clipboardManager = SERVICE_MANAGER.getClipboardManager(); if (clipboardManager != null) { clipboardManager.addPrimaryClipChangedListener(new IOnPrimaryClipChangedListener.Stub() { @Override @@ -166,7 +166,7 @@ public final class Device { return false; } - return serviceManager.getInputManager().injectInputEvent(inputEvent, mode); + return SERVICE_MANAGER.getInputManager().injectInputEvent(inputEvent, mode); } public boolean injectEvent(InputEvent event) { @@ -184,8 +184,8 @@ public final class Device { return injectKeyEvent(KeyEvent.ACTION_DOWN, keyCode, 0, 0) && injectKeyEvent(KeyEvent.ACTION_UP, keyCode, 0, 0); } - public boolean isScreenOn() { - return serviceManager.getPowerManager().isScreenOn(); + public static boolean isScreenOn() { + return SERVICE_MANAGER.getPowerManager().isScreenOn(); } public synchronized void setRotationListener(RotationListener rotationListener) { @@ -196,16 +196,16 @@ public final class Device { this.clipboardListener = clipboardListener; } - public void expandNotificationPanel() { - serviceManager.getStatusBarManager().expandNotificationsPanel(); + public static void expandNotificationPanel() { + SERVICE_MANAGER.getStatusBarManager().expandNotificationsPanel(); } - public void collapsePanels() { - serviceManager.getStatusBarManager().collapsePanels(); + public static void collapsePanels() { + SERVICE_MANAGER.getStatusBarManager().collapsePanels(); } - public String getClipboardText() { - ClipboardManager clipboardManager = serviceManager.getClipboardManager(); + public static String getClipboardText() { + ClipboardManager clipboardManager = SERVICE_MANAGER.getClipboardManager(); if (clipboardManager == null) { return null; } @@ -217,7 +217,7 @@ public final class Device { } public boolean setClipboardText(String text) { - ClipboardManager clipboardManager = serviceManager.getClipboardManager(); + ClipboardManager clipboardManager = SERVICE_MANAGER.getClipboardManager(); if (clipboardManager == null) { return false; } @@ -252,8 +252,8 @@ public final class Device { /** * Disable auto-rotation (if enabled), set the screen rotation and re-enable auto-rotation (if it was enabled). */ - public void rotateDevice() { - WindowManager wm = serviceManager.getWindowManager(); + public static void rotateDevice() { + WindowManager wm = SERVICE_MANAGER.getWindowManager(); boolean accelerometerRotation = !wm.isRotationFrozen(); @@ -270,7 +270,7 @@ public final class Device { } } - public ContentProvider createSettingsProvider() { - return serviceManager.getActivityManager().createSettingsProvider(); + public static ContentProvider createSettingsProvider() { + return SERVICE_MANAGER.getActivityManager().createSettingsProvider(); } } diff --git a/server/src/main/java/com/genymobile/scrcpy/Server.java b/server/src/main/java/com/genymobile/scrcpy/Server.java index d257e319..9b7f9de8 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Server.java +++ b/server/src/main/java/com/genymobile/scrcpy/Server.java @@ -26,7 +26,7 @@ public final class Server { boolean mustDisableShowTouchesOnCleanUp = false; int restoreStayOn = -1; if (options.getShowTouches() || options.getStayAwake()) { - try (ContentProvider settings = device.createSettingsProvider()) { + try (ContentProvider settings = Device.createSettingsProvider()) { if (options.getShowTouches()) { String oldValue = settings.getAndPutValue(ContentProvider.TABLE_SYSTEM, "show_touches", "1"); // If "show touches" was disabled, it must be disabled back on clean up From 0bf110dd5cc6a9793177c7764fe7fbfb452628f6 Mon Sep 17 00:00:00 2001 From: brunoais Date: Thu, 13 Aug 2020 07:33:36 +0100 Subject: [PATCH 205/450] Reset power mode only if screen is on PR #1670 Signed-off-by: Romain Vimont --- server/src/main/java/com/genymobile/scrcpy/CleanUp.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/CleanUp.java b/server/src/main/java/com/genymobile/scrcpy/CleanUp.java index d0ea141b..efaa059a 100644 --- a/server/src/main/java/com/genymobile/scrcpy/CleanUp.java +++ b/server/src/main/java/com/genymobile/scrcpy/CleanUp.java @@ -78,7 +78,9 @@ public final class CleanUp { if (restoreNormalPowerMode) { Ln.i("Restoring normal power mode"); - Device.setScreenPowerMode(Device.POWER_MODE_NORMAL); + if (Device.isScreenOn()) { + Device.setScreenPowerMode(Device.POWER_MODE_NORMAL); + } } } } From c243fd4c3fe91e83bacf89d2d6bbf642e5019f39 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Mon, 31 Aug 2020 13:37:09 +0200 Subject: [PATCH 206/450] Fix more log format warning The expression port + 1 is promoted to int, but printed as uint16_t. This is the same mistake fixed for a different log by 7eb16ce36458011d87972715f08bd9f03ff39807. Refs #1726 --- app/src/server.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/server.c b/app/src/server.c index 05b2cf91..422bbfa5 100644 --- a/app/src/server.c +++ b/app/src/server.c @@ -201,7 +201,7 @@ enable_tunnel_forward_any_port(struct server *server, if (port < port_range.last) { LOGW("Could not forward port %" PRIu16", retrying on %" PRIu16, - port, port + 1); + port, (uint16_t) (port + 1)); port++; continue; } From bd9f656933e79f7b21b42993f8a70a761ab47226 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Mon, 31 Aug 2020 13:52:29 +0200 Subject: [PATCH 207/450] Fix feature test macro The expected feature test macro is _POSIX_C_SOURCE having a value greater or equal to 200809L. Fixes #1726 --- app/src/sys/unix/command.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/src/sys/unix/command.c b/app/src/sys/unix/command.c index 64a54e71..3c2f587d 100644 --- a/app/src/sys/unix/command.c +++ b/app/src/sys/unix/command.c @@ -1,6 +1,6 @@ -// for portability -#define _POSIX_SOURCE // for kill() -#define _BSD_SOURCE // for readlink() +// for portability (kill, readlink, strdup, strtok_r) +#define _POSIX_C_SOURCE 200809L +#define _BSD_SOURCE // modern glibc will complain without this #define _DEFAULT_SOURCE From ae758f99d6af81123bb6b74d307d798c2c17acf3 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 13 Sep 2020 21:35:11 +0200 Subject: [PATCH 208/450] Adapt call() on ContentProvider for Android 11 This commit in AOSP framework_base added a parameter "attributionTag" to the call() method: https://android.googlesource.com/platform/frameworks/base.git/+/12ac3f406fed87cb9cd3a28b9947e7202a2d14bd%5E%21/#F17 As a consequence, the method did not exist, so scrcpy used the legacy call() method (using the authority "unknown") as a fallback, which fails for security reasons. Fixes #1468 --- .../scrcpy/wrappers/ContentProvider.java | 32 +++++++++++++------ 1 file changed, 23 insertions(+), 9 deletions(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/wrappers/ContentProvider.java b/server/src/main/java/com/genymobile/scrcpy/wrappers/ContentProvider.java index b43494c7..f8393e59 100644 --- a/server/src/main/java/com/genymobile/scrcpy/wrappers/ContentProvider.java +++ b/server/src/main/java/com/genymobile/scrcpy/wrappers/ContentProvider.java @@ -35,7 +35,7 @@ public class ContentProvider implements Closeable { private final IBinder token; private Method callMethod; - private boolean callMethodLegacy; + private int callMethodVersion; ContentProvider(ActivityManager manager, Object provider, String name, IBinder token) { this.manager = manager; @@ -46,12 +46,20 @@ public class ContentProvider implements Closeable { private Method getCallMethod() throws NoSuchMethodException { if (callMethod == null) { + try { - callMethod = provider.getClass().getMethod("call", String.class, String.class, String.class, String.class, Bundle.class); + callMethod = provider.getClass() + .getMethod("call", String.class, String.class, String.class, String.class, String.class, Bundle.class); + callMethodVersion = 0; } catch (NoSuchMethodException e) { - // old version - callMethod = provider.getClass().getMethod("call", String.class, String.class, String.class, Bundle.class); - callMethodLegacy = true; + // old versions + try { + callMethod = provider.getClass().getMethod("call", String.class, String.class, String.class, String.class, Bundle.class); + callMethodVersion = 1; + } catch (NoSuchMethodException e2) { + callMethod = provider.getClass().getMethod("call", String.class, String.class, String.class, Bundle.class); + callMethodVersion = 2; + } } } return callMethod; @@ -61,10 +69,16 @@ public class ContentProvider implements Closeable { try { Method method = getCallMethod(); Object[] args; - if (!callMethodLegacy) { - args = new Object[]{ServiceManager.PACKAGE_NAME, "settings", callMethod, arg, extras}; - } else { - args = new Object[]{ServiceManager.PACKAGE_NAME, callMethod, arg, extras}; + switch (callMethodVersion) { + case 0: + args = new Object[]{ServiceManager.PACKAGE_NAME, null, "settings", callMethod, arg, extras}; + break; + case 1: + args = new Object[]{ServiceManager.PACKAGE_NAME, "settings", callMethod, arg, extras}; + break; + default: + args = new Object[]{ServiceManager.PACKAGE_NAME, callMethod, arg, extras}; + break; } return (Bundle) method.invoke(provider, args); } catch (InvocationTargetException | IllegalAccessException | NoSuchMethodException e) { From cf7bf3148caca8d4428cbd70578696a8230689e0 Mon Sep 17 00:00:00 2001 From: CapsLock Date: Wed, 6 Feb 2019 01:13:31 +0100 Subject: [PATCH 209/450] Use "/usr/bin/env bash" for build-wrapper.sh PR #426 Signed-off-by: Romain Vimont --- server/scripts/build-wrapper.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/scripts/build-wrapper.sh b/server/scripts/build-wrapper.sh index f55e1ea4..7e16dc94 100755 --- a/server/scripts/build-wrapper.sh +++ b/server/scripts/build-wrapper.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash # Wrapper script to invoke gradle from meson set -e From 02a882a0a2fbe40504889bf753aa055bfc3c4fdf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lu=C3=ADs=20Ferreira?= Date: Thu, 27 Aug 2020 18:00:40 +0100 Subject: [PATCH 210/450] Use a more portable shebang for bash MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This should increase the portability of bash scripts across various *nix systems such as BSD-like distributions. PR #1716 Signed-off-by: Luís Ferreira Signed-off-by: Romain Vimont --- server/build_without_gradle.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/build_without_gradle.sh b/server/build_without_gradle.sh index e757be9a..3060b8e1 100755 --- a/server/build_without_gradle.sh +++ b/server/build_without_gradle.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash # # This script generates the scrcpy binary "manually" (without gradle). # From 1c44dc2259f7111f8432fcf1ab8509c68f48d9a0 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Tue, 15 Sep 2020 13:54:00 +0200 Subject: [PATCH 211/450] Use portable shebang for all bash scripts Refs #426 Refs #1716 --- prebuilt-deps/prepare-dep | 2 +- run | 2 +- scripts/run-scrcpy.sh | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/prebuilt-deps/prepare-dep b/prebuilt-deps/prepare-dep index 34ddcbf5..f152e6cf 100755 --- a/prebuilt-deps/prepare-dep +++ b/prebuilt-deps/prepare-dep @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash set -e url="$1" sum="$2" diff --git a/run b/run index bfb499ae..628c5c7e 100755 --- a/run +++ b/run @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash # Run scrcpy generated in the specified BUILDDIR. # # This provides the same feature as "ninja run", except that it is possible to diff --git a/scripts/run-scrcpy.sh b/scripts/run-scrcpy.sh index f3130ee9..e93b639f 100755 --- a/scripts/run-scrcpy.sh +++ b/scripts/run-scrcpy.sh @@ -1,2 +1,2 @@ -#!/bin/bash +#!/usr/bin/env bash SCRCPY_SERVER_PATH="$MESON_BUILD_ROOT/server/scrcpy-server" "$MESON_BUILD_ROOT/app/scrcpy" From d662f73bdceda1ceb95ea61fe5fd3b556c1ba932 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Tue, 15 Sep 2020 14:53:37 +0200 Subject: [PATCH 212/450] Upgrade Android SDK to 30 --- server/build.gradle | 4 ++-- server/build_without_gradle.sh | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/server/build.gradle b/server/build.gradle index c7f8bc0f..42252472 100644 --- a/server/build.gradle +++ b/server/build.gradle @@ -1,11 +1,11 @@ apply plugin: 'com.android.application' android { - compileSdkVersion 29 + compileSdkVersion 30 defaultConfig { applicationId "com.genymobile.scrcpy" minSdkVersion 21 - targetSdkVersion 29 + targetSdkVersion 30 versionCode 19 versionName "1.16" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" diff --git a/server/build_without_gradle.sh b/server/build_without_gradle.sh index 3060b8e1..e03939a5 100755 --- a/server/build_without_gradle.sh +++ b/server/build_without_gradle.sh @@ -14,8 +14,8 @@ set -e SCRCPY_DEBUG=false SCRCPY_VERSION_NAME=1.16 -PLATFORM=${ANDROID_PLATFORM:-29} -BUILD_TOOLS=${ANDROID_BUILD_TOOLS:-29.0.2} +PLATFORM=${ANDROID_PLATFORM:-30} +BUILD_TOOLS=${ANDROID_BUILD_TOOLS:-30.0.0} BUILD_DIR="$(realpath ${BUILD_DIR:-build_manual})" CLASSES_DIR="$BUILD_DIR/classes" From 52560faa344f19d6ec89a586ba85ab8958482e82 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 17 Sep 2020 13:03:18 +0200 Subject: [PATCH 213/450] Fix README indentation --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index e8b54465..da6833b5 100644 --- a/README.md +++ b/README.md @@ -382,9 +382,9 @@ The rotation can also be changed dynamically with MOD+ _(left)_ and MOD+ _(right)_. Note that _scrcpy_ manages 3 different rotations: -- MOD+r requests the device to switch between portrait and - landscape (the current running app may refuse, if it does support the - requested orientation). + - MOD+r requests the device to switch between portrait + and landscape (the current running app may refuse, if it does 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. From c136edf09d54b4f61e27577622e7ba93f72acb0f Mon Sep 17 00:00:00 2001 From: Win7GM Date: Thu, 10 Sep 2020 12:18:56 +0800 Subject: [PATCH 214/450] Add simplified Chinese translation for README.md PR #1723 Co-authored-by: Shaw Yu Signed-off-by: Romain Vimont --- README.md | 3 +- README.zh-Hans.md | 726 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 728 insertions(+), 1 deletion(-) create mode 100644 README.zh-Hans.md diff --git a/README.md b/README.md index da6833b5..2c5816a9 100644 --- a/README.md +++ b/README.md @@ -755,8 +755,9 @@ Read the [developers page]. This README is available in other languages: -- [繁體中文 (Traditional Chinese, `zh-Hant`) - v1.15](README.zh-Hant.md) - [한국어 (Korean, `ko`) - v1.11](README.ko.md) - [português brasileiro (Brazilian Portuguese, `pt-BR`) - v1.12.1](README.pt-br.md) +- [简体中文 (Simplified Chinese, `zh-Hans`) - v1.16](README.zh-Hans.md) +- [繁體中文 (Traditional Chinese, `zh-Hant`) - v1.15](README.zh-Hant.md) Only this README file is guaranteed to be up-to-date. diff --git a/README.zh-Hans.md b/README.zh-Hans.md new file mode 100644 index 00000000..85e178d6 --- /dev/null +++ b/README.zh-Hans.md @@ -0,0 +1,726 @@ +_Only the original [README](README.md) is guaranteed to be up-to-date._ + +只有原版的[README](README.md)会保持最新。 + +本文根据[479d10d]进行翻译。 + +[479d10d]: https://github.com/Genymobile/scrcpy/commit/479d10dc22b70272187e0963c6ad24d754a669a2#diff-04c6e90faac2675aa89e2176d2eec7d8 + + + +# scrcpy (v1.16) + +本应用程序可以通过USB(或 [TCP/IP][article-tcpip] )连接用于显示或控制安卓设备。这不需要获取 _root_ 权限。 + +该应用程序可以在 _GNU/Linux_, _Windows_ 和 _macOS_ 环境下运行。 + +[article-tcpip]:https://www.genymotion.com/blog/open-source-project-scrcpy-now-works-wirelessly/ + +![screenshot](assets/screenshot-debian-600.jpg) + +它专注于: + + - **轻量** (原生,仅显示设备屏幕) + - **性能** (30~60fps) + - **质量** (分辨率可达1920x1080或更高) + - **低延迟** (35-70ms) + - **快速启动** (数秒内即能开始显示) + - **无侵入性** (不需要在安卓设备上安装任何程序) + + +## 使用要求 + +安卓设备系统版本需要在Android 5.0(API 21)或以上。 + +确保您在设备上开启了[adb调试]。 + +[adb调试]: https://developer.android.com/studio/command-line/adb.html#Enabling + +在某些设备上,你还需要开启[额外的选项]以用鼠标和键盘进行控制。 + +[额外的选项]: https://github.com/Genymobile/scrcpy/issues/70#issuecomment-373286323 + + +## 获取scrcpy + +Packaging status + +### Linux + +在Debian(目前仅测试版和不稳定版,即 _testing_ 和 _sid_ 版本)和Ubuntu (20.04)上: + +``` +apt install scrcpy +``` + +[Snap]包也是可用的: [`scrcpy`][snap-link]. + +[snap-link]: https://snapstats.org/snaps/scrcpy + +[snap]: https://en.wikipedia.org/wiki/Snappy_(package_manager) + +对于Fedora用户,我们提供[COPR]包: [`scrcpy`][copr-link]. + +[COPR]: https://fedoraproject.org/wiki/Category:Copr +[copr-link]: https://copr.fedorainfracloud.org/coprs/zeno/scrcpy/ + +对于Arch Linux用户,我们提供[AUR]包: [`scrcpy`][aur-link]. + +[AUR]: https://wiki.archlinux.org/index.php/Arch_User_Repository +[aur-link]: https://aur.archlinux.org/packages/scrcpy/ + +对于Gentoo用户,我们提供[Ebuild]包:[`scrcpy/`][ebuild-link]. + +[Ebuild]: https://wiki.gentoo.org/wiki/Ebuild +[ebuild-link]: https://github.com/maggu2810/maggu2810-overlay/tree/master/app-mobilephone/scrcpy + +您也可以[自行编译][编译](不必担心,这并不困难)。 + + + +### Windows + +在Windows上,简便起见,我们准备了包含所有依赖项(包括adb)的程序包。 + + - [`scrcpy-win64-v1.16.zip`][direct-win64] + _(SHA-256: 3f30dc5db1a2f95c2b40a0f5de91ec1642d9f53799250a8c529bc882bc0918f0)_ + +[direct-win64]: https://github.com/Genymobile/scrcpy/releases/download/v1.16/scrcpy-win64-v1.16.zip + +您也可以在[Chocolatey]下载: + +[Chocolatey]: https://chocolatey.org/ + +```bash +choco install scrcpy +choco install adb # 如果你没有adb +``` + +也可以使用 [Scoop]: + +```bash +scoop install scrcpy +scoop install adb # 如果你没有adb +``` + +[Scoop]: https://scoop.sh + +您也可以[自行编译][编译]。 + + +### macOS + +您可以使用[Homebrew]下载scrcpy。直接安装就可以了: + +[Homebrew]: https://brew.sh/ + +```bash +brew install scrcpy +``` + +您需要 `adb`以使用scrcpy,并且它需要可以通过 `PATH`被访问。如果您没有: + +```bash +brew cask install android-platform-tools +``` + +您也可以[自行编译][编译]。 + + +## 运行scrcpy + +用USB链接电脑和安卓设备,并执行: + +```bash +scrcpy +``` + +支持带命令行参数执行,查看参数列表: + +```bash +scrcpy --help +``` + +## 功能介绍 + +### 画面设置 + +#### 缩小分辨率 + +有时候,将设备屏幕镜像分辨率降低可以有效地提升性能。 + +我们可以将高度和宽度都限制在一定大小内(如 1024): + +```bash +scrcpy --max-size 1024 +scrcpy -m 1024 # short version +``` + +较短的一边会被按比例缩小以保持设备的显示比例。 +这样,1920x1080 的设备会以 1024x576 的分辨率显示。 + + +#### 修改画面比特率 + +默认的比特率是8Mbps。如果要改变画面的比特率 (比如说改成2Mbps): + +```bash +scrcpy --bit-rate 2M +scrcpy -b 2M # short version +``` + +#### 限制画面帧率 + +画面的帧率可以通过下面的命令被限制: + +```bash +scrcpy --max-fps 15 +``` + +这个功能仅在Android 10和以后的版本被Android官方支持,但也有可能在更早的版本可用。 + +#### 画面裁剪 + +设备画面可在裁切后进行镜像,以显示部分屏幕。 + +这项功能可以用于,例如,只显示Oculus Go的一只眼睛。 + +```bash +scrcpy --crop 1224:1440:0:0 # 1224x1440 at offset (0,0) +``` + +如果`--max-size`在同时被指定,分辨率的改变将在画面裁切后进行。 + + +#### 锁定屏幕朝向 + + +可以使用如下命令锁定屏幕朝向: + +```bash +scrcpy --lock-video-orientation 0 # 自然朝向 +scrcpy --lock-video-orientation 1 # 90° 逆时针旋转 +scrcpy --lock-video-orientation 2 # 180° +scrcpy --lock-video-orientation 3 # 90° 顺时针旋转 +``` + +该设定影响录制。 + + +### 屏幕录制 + +可以在屏幕镜像的同时录制视频: + +```bash +scrcpy --record file.mp4 +scrcpy -r file.mkv +``` + +在不开启屏幕镜像的同时录制: + +```bash +scrcpy --no-display --record file.mp4 +scrcpy -Nr file.mkv +# 按Ctrl+C以停止录制 +``` + +在显示中“被跳过的帧”会被录制,虽然它们由于性能原因没有实时显示。 +在传输中每一帧都有 _时间戳_ ,所以 [包时延变化] 并不影响录制的文件。 + +[包时延变化]: https://en.wikipedia.org/wiki/Packet_delay_variation + + +### 连接方式 + +#### 无线 + +_Scrcpy_ 使用`adb`来与安卓设备连接。同时,`adb`能够通过TCP/IP[连接]到安卓设备: + +1. 将您的安卓设备和电脑连接至同一Wi-Fi。 +2. 获取安卓设备的IP地址(在设置-关于手机-状态信息)。 +3. 打开安卓设备的网络adb功能`adb tcpip 5555`。 +4. 将您的设备与电脑断开连接。 +5. 连接到您的设备:`adb connect DEVICE_IP:5555` _(用设备IP替换 `DEVICE_IP`)_. +6. 运行`scrcpy`。 + +降低比特率和分辨率可能有助于性能: + +```bash +scrcpy --bit-rate 2M --max-size 800 +scrcpy -b2M -m800 # short version +``` + +[连接]: https://developer.android.com/studio/command-line/adb.html#wireless + + +#### 多设备 + +如果多个设备在执行`adb devices`后被列出,您必须指定设备的 _序列号_ : + +```bash +scrcpy --serial 0123456789abcdef +scrcpy -s 0123456789abcdef # short version +``` + +如果设备是通过TCP/IP方式连接到电脑的: + +```bash +scrcpy --serial 192.168.0.1:5555 +scrcpy -s 192.168.0.1:5555 # short version +``` + +您可以同时启动多个 _scrcpy_ 实例以同时显示多个设备的画面。 + +#### 在设备连接时自动启动 + +您可以使用 [AutoAdb]: + +```bash +autoadb scrcpy -s '{}' +``` + +[AutoAdb]: https://github.com/rom1v/autoadb + +#### SSH 连接 + +本地的 adb 可以远程连接到另一个 adb 服务器(假设两者的adb版本相同),来远程连接到设备: + +```bash +adb kill-server # 关闭本地5037端口上的adb服务器 +ssh -CN -L5037:localhost:5037 -R27183:localhost:27183 your_remote_computer +# 保持该窗口开启 +``` + +从另一个终端: + +```bash +scrcpy +``` + +为了避免启动远程端口转发,你可以强制启动一个转发连接(注意`-L`和`-R`的区别: + +```bash +adb kill-server # kill the local adb server on 5037 +ssh -CN -L5037:localhost:5037 -L27183:localhost:27183 your_remote_computer +# 保持该窗口开启 +``` + +从另一个终端: + +```bash +scrcpy --force-adb-forward +``` + + +和无线网络连接类似,下列设置可能对改善性能有帮助: + +``` +scrcpy -b2M -m800 --max-fps 15 +``` + +### 窗口设置 + +#### 标题 + +窗口的标题默认为设备型号。您可以通过如下命令修改它: + +```bash +scrcpy --window-title 'My device' +``` + +#### 位置和大小 + +您可以指定初始的窗口位置和大小: + +```bash +scrcpy --window-x 100 --window-y 100 --window-width 800 --window-height 600 +``` + +#### 无边框 + +关闭边框: + +```bash +scrcpy --window-borderless +``` + +#### 保持窗口在最前 + +您可以通过如下命令保持窗口在最前面: + +```bash +scrcpy --always-on-top +``` + +#### 全屏 + +您可以通过如下命令直接全屏启动scrcpy: + +```bash +scrcpy --fullscreen +scrcpy -f # short version +``` + +全屏状态可以通过MOD+f实时改变。 + +#### 旋转 + +通过如下命令,窗口可以旋转: + +```bash +scrcpy --rotation 1 +``` + +可选的值有: + - `0`: 无旋转 + - `1`: 逆时针旋转90° + - `2`: 旋转180° + - `3`: 顺时针旋转90° + +这同样可以使用MOD+ +_(左)_ 和 MOD+ _(右)_ 的快捷键实时更改。 + +需要注意的是, _scrcpy_ 控制三个不同的朝向: + - MOD+r 请求设备在竖屏和横屏之间切换(如果前台应用程序不支持所请求的朝向,可能会拒绝该请求)。 + + - `--lock-video-orientation` 改变镜像的朝向(设备镜像到电脑的画面朝向)。这会影响录制。 + + - `--rotation` (或MOD+/MOD+) + 只旋转窗口的画面。这只影响显示,不影响录制。 + + +### 其他镜像设置 + +#### 只读 + +关闭电脑对设备的控制(如键盘输入、鼠标移动和文件传输): + +```bash +scrcpy --no-control +scrcpy -n +``` + +#### 显示屏 + +如果有多个显示屏可用,您可以选择特定显示屏进行镜像: + +```bash +scrcpy --display 1 +``` + +您可以通过如下命令找到显示屏的id: + +``` +adb shell dumpsys display # 在回显中搜索“mDisplayId=” +``` + +第二显示屏可能只能在设备运行Android 10或以上的情况下被控制(它可能会在电脑上显示,但无法通过电脑操作)。 + + +#### 保持常亮 + +防止设备在已连接的状态下休眠: + +```bash +scrcpy --stay-awake +scrcpy -w +``` + +程序关闭后,设备设置会恢复原样。 + + +#### 关闭设备屏幕 + +在启动屏幕镜像时,可以通过如下命令关闭设备的屏幕: + +```bash +scrcpy --turn-screen-off +scrcpy -S +``` + +或者在需要的时候按MOD+o。 + +要重新打开屏幕的话,需要按MOD+Shift+o. + +在Android上,`电源`按钮始终能把屏幕打开。 + +为了方便,如果按下`电源`按钮的事件是通过 _scrcpy_ 发出的(通过点按鼠标右键或MOD+p),它会在短暂的延迟后将屏幕关闭。 + +物理的`电源`按钮仍然能打开设备屏幕。 + +同时,这项功能还能被用于防止设备休眠: + +```bash +scrcpy --turn-screen-off --stay-awake +scrcpy -Sw +``` + + +#### 渲染超时帧 + +为了降低延迟, _scrcpy_ 默认渲染解码成功的最近一帧,并跳过前面任意帧。 + +强制渲染所有帧(可能导致延迟变高): + +```bash +scrcpy --render-expired-frames +``` + +#### 显示触摸 + +在展示时,有些时候可能会用到显示触摸点这项功能(在设备上显示)。 + +Android在 _开发者设置_ 中提供了这项功能。 + +_Scrcpy_ 提供一个选项可以在启动时开启这项功能并在退出时恢复初始设置: + +```bash +scrcpy --show-touches +scrcpy -t +``` + +请注意这项功能只能显示 _物理_ 触摸(要用手在屏幕上触摸)。 + + +#### 关闭屏保 + +_Scrcpy_ 不会默认关闭屏幕保护。 + +关闭屏幕保护: + +```bash +scrcpy --disable-screensaver +``` + + +### 输入控制 + +#### 旋转设备屏幕 + +使用MOD+r以在竖屏和横屏模式之间切换。 + +需要注意的是,只有在前台应用程序支持所要求的模式时,才会进行切换。 + +#### 复制黏贴 + +每次Android的剪贴板变化的时候,它都会被自动同步到电脑的剪贴板上。 + +所有的 Ctrl 快捷键都会被转发至设备。其中: + - Ctrl+c 复制 + - Ctrl+x 剪切 + - Ctrl+v 黏贴 (在电脑到设备的剪贴板同步完成之后) + +这通常如您所期望的那样运作。 + +但实际的行为取决于设备上的前台程序。 +例如 _Termux_ 在Ctrl+c被按下时发送 SIGINT, +又如 _K-9 Mail_ 会新建一封新邮件。 + +在这种情况下剪切复制黏贴(仅在Android >= 7时可用): + - MOD+c 注入 `COPY`(复制) + - MOD+x 注入 `CUT`(剪切) + - MOD+v 注入 `PASTE`(黏贴)(在电脑到设备的剪贴板同步完成之后) + +另外,MOD+Shift+v可以将电脑的剪贴板内容转换为一串按键事件输入到设备。 +在应用程序不接受黏贴时(比如 _Termux_ ),这项功能可以排上一定的用场。 +需要注意的是,这项功能可能会导致非ASCII编码的内容出现错误。 + +**警告:** 将电脑剪贴板的内容黏贴至设备(无论是通过Ctrl+v还是MOD+v) +都需要将内容保存至设备的剪贴板。如此,任何一个应用程序都可以读取它。 +您应当避免将敏感内容通过这种方式传输(如密码)。 + + +#### 捏拉缩放 + +模拟 “捏拉缩放”:Ctrl+_按住并移动鼠标_。 + +更准确的说,您需要在按住Ctrl的同时按住并移动鼠标。 +在鼠标左键松开之后,光标的任何操作都会相对于屏幕的中央进行。 + +具体来说, _scrcpy_ 使用“虚拟手指”以在相对于屏幕中央相反的位置产生触摸事件。 + + +#### 文字注入偏好 + +打字的时候,系统会产生两种[事件][textevents]: + - _按键事件_ ,代表一个按键被按下/松开。 + - _文本事件_ ,代表一个文本被输入。 + +程序默认使用按键事件来输入字母。只有这样,键盘才会在游戏中正常运作(尤其WASD键)。 + +但这也有可能[造成问题][prefertext]。如果您遇到了这样的问题,您可以通过下列操作避免它: + +```bash +scrcpy --prefer-text +``` + +(这会导致键盘在游戏中工作不正常) + +[textevents]: https://blog.rom1v.com/2018/03/introducing-scrcpy/#handle-text-input +[prefertext]: https://github.com/Genymobile/scrcpy/issues/650#issuecomment-512945343 + + +#### 按键重复 + +当你一直按着一个按键不放时,程序默认产生多个按键事件。 +在某些游戏中这可能会导致性能问题。 + +避免转发重复按键事件: + +```bash +scrcpy --no-key-repeat +``` + + +### 文件传输 + +#### 安装APK + +如果您要要安装APK,请拖放APK文件(文件名以`.apk`结尾)到 _scrcpy_ 窗口。 + +该操作在屏幕上不会出现任何变化,而会在控制台输出一条日志。 + + +#### 将文件推送至设备 + +如果您要推送文件到设备的 `/sdcard/`,请拖放文件至(不能是APK文件)_scrcpy_ 窗口。 + +该操作没有可见的响应,只会在控制台输出日志。 + +在启动时可以修改目标目录: + +```bash +scrcpy --push-target /sdcard/foo/bar/ +``` + + +### 音频转发 + +_scrcpy_ 不支持音频。请使用 [sndcpy]. + +另外请阅读 [issue #14]。 + +[sndcpy]: https://github.com/rom1v/sndcpy +[issue #14]: https://github.com/Genymobile/scrcpy/issues/14 + + +## 热键 + +在下列表格中, MOD 是热键的修饰键。 +默认是(左)Alt或者(左)Super。 + +您可以使用 `--shortcut-mod`后缀来修改它。可选的按键有`lctrl`、`rctrl`、 +`lalt`、`ralt`、`lsuper`和`rsuper`。如下例: + +```bash +# 使用右侧的Ctrl键 +scrcpy --shortcut-mod=rctrl + +# 使用左侧的Ctrl键、Alt键或Super键 +scrcpy --shortcut-mod=lctrl+lalt,lsuper +``` + +_一般来说,[Super]就是Windows或者Cmd。_ + +[Super]: https://en.wikipedia.org/wiki/Super_key_(keyboard_button) + + | 操作 | 快捷键 + | ------------------------------------------- |:----------------------------- + | 全屏 | MOD+f + | 向左旋转屏幕 | MOD+ _(左)_ + | 向右旋转屏幕 | MOD+ _(右)_ + | 将窗口大小重置为1:1 (像素优先) | MOD+g + | 将窗口大小重置为消除黑边 | MOD+w \| _双击¹_ + | 点按 `主屏幕` | MOD+h \| _点击鼠标中键_ + | 点按 `返回` | MOD+b \| _点击鼠标右键²_ + | 点按 `切换应用` | MOD+s + | 点按 `菜单` (解锁屏幕) | MOD+m + | 点按 `音量+` | MOD+ _(up)_ + | 点按 `音量-` | MOD+ _(down)_ + | 点按 `电源` | MOD+p + | 打开屏幕 | _点击鼠标右键²_ + | 关闭设备屏幕(但继续在电脑上显示) | MOD+o + | 打开设备屏幕 | MOD+Shift+o + | 旋转设备屏幕 | MOD+r + | 展开通知面板 | MOD+n + | 展开快捷操作 | MOD+Shift+n + | 复制到剪贴板³ | MOD+c + | 剪切到剪贴板³ | MOD+x + | 同步剪贴板并黏贴³ | MOD+v + | 导入电脑剪贴板文本 | MOD+Shift+v + | 打开/关闭FPS显示(在 stdout) | MOD+i + | 捏拉缩放 | Ctrl+_点按并移动鼠标_ + +_¹双击黑色边界以关闭黑色边界_ +_²点击鼠标右键将在屏幕熄灭时点亮屏幕,其余情况则视为按下 返回键 。_ +_³需要安卓版本 Android >= 7。_ + +所有的 Ctrl+_按键_ 的热键都是被转发到设备进行处理的,所以实际上会由当前应用程序对其做出响应。 + + +## 自定义路径 + +为了使用您想使用的 _adb_ ,您可以在环境变量 +`ADB`中设置它的路径: + + ADB=/path/to/adb scrcpy + +如果需要覆盖`scrcpy-server`的路径,您可以在 +`SCRCPY_SERVER_PATH`中设置它。 + +[useful]: https://github.com/Genymobile/scrcpy/issues/278#issuecomment-429330345 + + +## 为什么叫 _scrcpy_ ? + +一个同事让我找出一个和[gnirehtet]一样难以发音的名字。 + +[`strcpy`] 可以复制**str**ing; `scrcpy` 可以复制**scr**een。 + +[gnirehtet]: https://github.com/Genymobile/gnirehtet +[`strcpy`]: http://man7.org/linux/man-pages/man3/strcpy.3.html + + +## 如何编译? + +请查看[编译]。 + +[编译]: BUILD.md + + +## 常见问题 + +请查看[FAQ](FAQ.md). + + +## 开发者 + +请查看[开发者页面]。 + +[开发者页面]: DEVELOP.md + + +## 许可协议 + + Copyright (C) 2018 Genymobile + Copyright (C) 2018-2020 Romain Vimont + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +## 相关文章 + +- [Introducing scrcpy][article-intro] +- [Scrcpy now works wirelessly][article-tcpip] + +[article-intro]: https://blog.rom1v.com/2018/03/introducing-scrcpy/ +[article-tcpip]: https://www.genymotion.com/blog/open-source-project-scrcpy-now-works-wirelessly/ From a65ebceac1e1f214328b3be776e7dbc724656db6 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 20 Sep 2020 01:07:18 +0200 Subject: [PATCH 215/450] Add missing mutex unlock on error Fixes #1770 Reported-by: lordnn --- app/src/recorder.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/src/recorder.c b/app/src/recorder.c index 76edbd03..e31492c0 100644 --- a/app/src/recorder.c +++ b/app/src/recorder.c @@ -361,12 +361,14 @@ recorder_push(struct recorder *recorder, const AVPacket *packet) { if (recorder->failed) { // reject any new packet (this will stop the stream) + mutex_unlock(recorder->mutex); return false; } struct record_packet *rec = record_packet_new(packet); if (!rec) { LOGC("Could not allocate record packet"); + mutex_unlock(recorder->mutex); return false; } From acc65f8c9dc477e978d438a2f2a3501e49230211 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 20 Sep 2020 01:09:05 +0200 Subject: [PATCH 216/450] Remove unused field Fixes #1775 Reported-by: lordnn --- app/src/stream.h | 1 - 1 file changed, 1 deletion(-) diff --git a/app/src/stream.h b/app/src/stream.h index f7c5e475..cd09d959 100644 --- a/app/src/stream.h +++ b/app/src/stream.h @@ -14,7 +14,6 @@ struct video_buffer; struct stream { socket_t socket; - struct video_buffer *video_buffer; SDL_Thread *thread; struct decoder *decoder; struct recorder *recorder; From 56d237f152fc4d454a65fe594412b4bd9409fd64 Mon Sep 17 00:00:00 2001 From: Brinan Sjostrom Date: Tue, 22 Sep 2020 18:02:18 -0400 Subject: [PATCH 217/450] Fix "press Enter key" message The message said "Press any key to continue...", whereas only Enter/Return is accepted. PR #1783 Fixes #1757 Reviewed-by: Yu-Chen Lin Signed-off-by: Romain Vimont --- app/src/main.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main.c b/app/src/main.c index 202c217c..d32d9896 100644 --- a/app/src/main.c +++ b/app/src/main.c @@ -100,7 +100,7 @@ main(int argc, char *argv[]) { #if defined (__WINDOWS__) && ! defined (WINDOWS_NOCONSOLE) if (res != 0) { - fprintf(stderr, "Press any key to continue...\n"); + fprintf(stderr, "Press Enter to continue...\n"); getchar(); } #endif From 15b81367c973a4da7285c697ca2a04d87f44d656 Mon Sep 17 00:00:00 2001 From: redev <58584157+redev-5c@users.noreply.github.com> Date: Thu, 17 Sep 2020 09:16:48 +0900 Subject: [PATCH 218/450] Fix FAQ.ko.md PR #1767 Signed-off-by: Romain Vimont --- FAQ.ko.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/FAQ.ko.md b/FAQ.ko.md index 6cc1a1d9..c9e06e24 100644 --- a/FAQ.ko.md +++ b/FAQ.ko.md @@ -3,16 +3,16 @@ 다음은 자주 제보되는 문제들과 그들의 현황입니다. -### Window 운영체제에서, 디바이스가 발견되지 않습니다. +### Windows 운영체제에서, 디바이스가 발견되지 않습니다. 가장 흔한 제보는 `adb`에 발견되지 않는 디바이스 혹은 권한 관련 문제입니다. 다음 명령어를 호출하여 모든 것들에 이상이 없는지 확인하세요: adb devices -Window는 당신의 디바이스를 감지하기 위해 [drivers]가 필요할 수도 있습니다. +Windows는 당신의 디바이스를 감지하기 위해 [드라이버]가 필요할 수도 있습니다. -[drivers]: https://developer.android.com/studio/run/oem-usb.html +[드라이버]: https://developer.android.com/studio/run/oem-usb.html ### 내 디바이스의 미러링만 가능하고, 디바이스와 상호작용을 할 수 없습니다. From d50ecf40b603f8a0e5a73699babce7301c0bd870 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 1 Oct 2020 15:06:34 +0200 Subject: [PATCH 219/450] Fix options order --- app/scrcpy.1 | 8 ++++---- app/src/cli.c | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/app/scrcpy.1 b/app/scrcpy.1 index 6a890f14..e957a4d0 100644 --- a/app/scrcpy.1 +++ b/app/scrcpy.1 @@ -92,14 +92,14 @@ Disable device control (mirror the device in read\-only). .B \-N, \-\-no\-display Do not display device (only when screen recording is enabled). -.TP -.B \-\-no\-mipmaps -If the renderer is OpenGL 3.0+ or OpenGL ES 2.0+, then mipmaps are automatically generated to improve downscaling quality. This option disables the generation of mipmaps. - .TP .B \-\-no\-key\-repeat Do not forward repeated key events when a key is held down. +.TP +.B \-\-no\-mipmaps +If the renderer is OpenGL 3.0+ or OpenGL ES 2.0+, then mipmaps are automatically generated to improve downscaling quality. This option disables the generation of mipmaps. + .TP .BI "\-p, \-\-port " port[:port] Set the TCP port (range) used by the client to listen. diff --git a/app/src/cli.c b/app/src/cli.c index 9c791fbf..41de8ca5 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -87,14 +87,14 @@ scrcpy_print_usage(const char *arg0) { " Do not display device (only when screen recording is\n" " enabled).\n" "\n" + " --no-key-repeat\n" + " Do not forward repeated key events when a key is held down.\n" + "\n" " --no-mipmaps\n" " If the renderer is OpenGL 3.0+ or OpenGL ES 2.0+, then\n" " mipmaps are automatically generated to improve downscaling\n" " quality. This option disables the generation of mipmaps.\n" "\n" - " --no-key-repeat\n" - " Do not forward repeated key events when a key is held down.\n" - "\n" " -p, --port port[:port]\n" " Set the TCP port (range) used by the client to listen.\n" " Default is %d:%d.\n" @@ -672,8 +672,8 @@ scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) { {"max-size", required_argument, NULL, 'm'}, {"no-control", no_argument, NULL, 'n'}, {"no-display", no_argument, NULL, 'N'}, - {"no-mipmaps", no_argument, NULL, OPT_NO_MIPMAPS}, {"no-key-repeat", no_argument, NULL, OPT_NO_KEY_REPEAT}, + {"no-mipmaps", no_argument, NULL, OPT_NO_MIPMAPS}, {"port", required_argument, NULL, 'p'}, {"prefer-text", no_argument, NULL, OPT_PREFER_TEXT}, {"push-target", required_argument, NULL, OPT_PUSH_TARGET}, From 2edf192e3ae80252d136e6d1a614ab07f93fc561 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Mon, 5 Oct 2020 21:09:01 +0200 Subject: [PATCH 220/450] Remove deprecation warning As a workaround for some devices, we need to prepare the main looper. The method is now deprecated, but we still want to call it. --- server/src/main/java/com/genymobile/scrcpy/Workarounds.java | 1 + 1 file changed, 1 insertion(+) diff --git a/server/src/main/java/com/genymobile/scrcpy/Workarounds.java b/server/src/main/java/com/genymobile/scrcpy/Workarounds.java index 351cc574..0f473bc1 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Workarounds.java +++ b/server/src/main/java/com/genymobile/scrcpy/Workarounds.java @@ -16,6 +16,7 @@ public final class Workarounds { // not instantiable } + @SuppressWarnings("deprecation") public static void prepareMainLooper() { // Some devices internally create a Handler when creating an input Surface, causing an exception: // "Can't create handler inside thread that has not called Looper.prepare()" From 83082406d36d177976c54d93e8eda5a5ea4811e5 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Mon, 5 Oct 2020 21:11:50 +0200 Subject: [PATCH 221/450] Enable Java deprecation warnings details Without the option, gradle reports a lint issue, but without any details. --- build.gradle | 3 +++ 1 file changed, 3 insertions(+) diff --git a/build.gradle b/build.gradle index 94862d2e..2755fd62 100644 --- a/build.gradle +++ b/build.gradle @@ -19,6 +19,9 @@ allprojects { google() jcenter() } + tasks.withType(JavaCompile) { + options.compilerArgs << "-Xlint:deprecation" + } } task clean(type: Delete) { From aade92fd1058a88cfdcacf168f2b2b8857b5d562 Mon Sep 17 00:00:00 2001 From: yanuarakhid Date: Fri, 2 Oct 2020 00:50:38 +0700 Subject: [PATCH 222/450] Add Indonesian translation for README.md PR #1802 Signed-off-by: Romain Vimont --- README.id.md | 699 +++++++++++++++++++++++++++++++++++++++++++++++++++ README.md | 1 + 2 files changed, 700 insertions(+) create mode 100644 README.id.md diff --git a/README.id.md b/README.id.md new file mode 100644 index 00000000..5af56663 --- /dev/null +++ b/README.id.md @@ -0,0 +1,699 @@ +_Only the original [README](README.md) is guaranteed to be up-to-date._ + +# scrcpy (v1.16) + +Aplikasi ini menyediakan tampilan dan kontrol perangkat Android yang terhubung pada USB (atau [melalui TCP/IP][article-tcpip]). Ini tidak membutuhkan akses _root_ apa pun. Ini bekerja pada _GNU/Linux_, _Windows_ and _macOS_. + +![screenshot](assets/screenshot-debian-600.jpg) + +Ini berfokus pada: + + - **keringanan** (asli, hanya menampilkan layar perangkat) + - **kinerja** (30~60fps) + - **kualitas** (1920×1080 atau lebih) + - **latensi** rendah ([35~70ms][lowlatency]) + - **waktu startup rendah** (~1 detik untuk menampilkan gambar pertama) + - **tidak mengganggu** (tidak ada yang terpasang di perangkat) + + +[lowlatency]: https://github.com/Genymobile/scrcpy/pull/646 + + +## Persyaratan +Perangkat Android membutuhkan setidaknya API 21 (Android 5.0). + +Pastikan Anda [mengaktifkan debugging adb][enable-adb] pada perangkat Anda. + +[enable-adb]: https://developer.android.com/studio/command-line/adb.html#Enabling + +Di beberapa perangkat, Anda juga perlu mengaktifkan [opsi tambahan][control] untuk mengontrolnya menggunakan keyboard dan mouse. + +[control]: https://github.com/Genymobile/scrcpy/issues/70#issuecomment-373286323 + + +## Dapatkan aplikasinya + +### Linux + +Di Debian (_testing_ dan _sid_ untuk saat ini) dan Ubuntu (20.04): + +``` +apt install scrcpy +``` + +Paket [Snap] tersedia: [`scrcpy`][snap-link]. + +[snap-link]: https://snapstats.org/snaps/scrcpy + +[snap]: https://en.wikipedia.org/wiki/Snappy_(package_manager) + +Untuk Fedora, paket [COPR] tersedia: [`scrcpy`][copr-link]. + +[COPR]: https://fedoraproject.org/wiki/Category:Copr +[copr-link]: https://copr.fedorainfracloud.org/coprs/zeno/scrcpy/ + +Untuk Arch Linux, paket [AUR] tersedia: [`scrcpy`][aur-link]. + +[AUR]: https://wiki.archlinux.org/index.php/Arch_User_Repository +[aur-link]: https://aur.archlinux.org/packages/scrcpy/ + +Untuk Gentoo, tersedia [Ebuild]: [`scrcpy/`][ebuild-link]. + +[Ebuild]: https://wiki.gentoo.org/wiki/Ebuild +[ebuild-link]: https://github.com/maggu2810/maggu2810-overlay/tree/master/app-mobilephone/scrcpy + +Anda juga bisa [membangun aplikasi secara manual][BUILD] (jangan khawatir, tidak terlalu sulit). + + +### Windows + +Untuk Windows, untuk kesederhanaan, arsip prebuilt dengan semua dependensi (termasuk `adb`) tersedia : + + - [`scrcpy-win64-v1.16.zip`][direct-win64] + _(SHA-256: 3f30dc5db1a2f95c2b40a0f5de91ec1642d9f53799250a8c529bc882bc0918f0)_ + +[direct-win64]: https://github.com/Genymobile/scrcpy/releases/download/v1.16/scrcpy-win64-v1.16.zip + +Ini juga tersedia di [Chocolatey]: + +[Chocolatey]: https://chocolatey.org/ + +```bash +choco install scrcpy +choco install adb # jika Anda belum memilikinya +``` + +Dan di [Scoop]: + +```bash +scoop install scrcpy +scoop install adb # jika Anda belum memilikinya +``` + +[Scoop]: https://scoop.sh + +Anda juga dapat [membangun aplikasi secara manual][BUILD]. + + +### macOS + +Aplikasi ini tersedia di [Homebrew]. Instal saja: + +[Homebrew]: https://brew.sh/ + +```bash +brew install scrcpy +``` +Anda membutuhkan `adb`, dapat diakses dari `PATH` Anda. Jika Anda belum memilikinya: + +```bash +brew cask install android-platform-tools +``` + +Anda juga dapat [membangun aplikasi secara manual][BUILD]. + + +## Menjalankan + +Pasang perangkat Android, dan jalankan: + +```bash +scrcpy +``` + +Ini menerima argumen baris perintah, didaftarkan oleh: + +```bash +scrcpy --help +``` + +## Fitur + +### Menangkap konfigurasi + +#### Mengurangi ukuran + +Kadang-kadang, berguna untuk mencerminkan perangkat Android dengan definisi yang lebih rendah untuk meningkatkan kinerja. + +Untuk membatasi lebar dan tinggi ke beberapa nilai (mis. 1024): + +```bash +scrcpy --max-size 1024 +scrcpy -m 1024 # versi pendek +``` + +Dimensi lain dihitung agar rasio aspek perangkat dipertahankan. +Dengan begitu, perangkat 1920×1080 akan dicerminkan pada 1024×576. + +#### Ubah kecepatan bit + +Kecepatan bit default adalah 8 Mbps. Untuk mengubah bitrate video (mis. Menjadi 2 Mbps): + +```bash +scrcpy --bit-rate 2M +scrcpy -b 2M # versi pendek +``` + +#### Batasi frekuensi gambar + +Kecepatan bingkai pengambilan dapat dibatasi: + +```bash +scrcpy --max-fps 15 +``` + +Ini secara resmi didukung sejak Android 10, tetapi dapat berfungsi pada versi sebelumnya. + +#### Memotong + +Layar perangkat dapat dipotong untuk mencerminkan hanya sebagian dari layar. + +Ini berguna misalnya untuk mencerminkan hanya satu mata dari Oculus Go: + +```bash +scrcpy --crop 1224:1440:0:0 # 1224x1440 Mengimbangi (0,0) +``` + +Jika `--max-size` juga ditentukan, pengubahan ukuran diterapkan setelah pemotongan. + + +#### Kunci orientasi video + +Untuk mengunci orientasi pencerminan: + +```bash +scrcpy --lock-video-orientation 0 # orientasi alami +scrcpy --lock-video-orientation 1 # 90° berlawanan arah jarum jam +scrcpy --lock-video-orientation 2 # 180° +scrcpy --lock-video-orientation 3 # 90° searah jarum jam +``` + +Ini mempengaruhi orientasi perekaman. + + +### Rekaman + +Anda dapat merekam layar saat melakukan mirroring: + +```bash +scrcpy --record file.mp4 +scrcpy -r file.mkv +``` + +Untuk menonaktifkan pencerminan saat merekam: + +```bash +scrcpy --no-display --record file.mp4 +scrcpy -Nr file.mkv +# berhenti merekam dengan Ctrl+C +``` + +"Skipped frames" are recorded, even if they are not displayed in real time (for +performance reasons). Frames are _timestamped_ on the device, so [packet delay +variation] does not impact the recorded file. + +"Frame yang dilewati" direkam, meskipun tidak ditampilkan secara real time (untuk alasan performa). Bingkai *diberi stempel waktu* pada perangkat, jadi [variasi penundaan paket] tidak memengaruhi file yang direkam. + +[variasi penundaan paket]: https://en.wikipedia.org/wiki/Packet_delay_variation + + +### Koneksi + +#### Wireless + +_Scrcpy_ menggunakan `adb` untuk berkomunikasi dengan perangkat, dan` adb` dapat [terhubung] ke perangkat melalui TCP / IP: + +1. Hubungkan perangkat ke Wi-Fi yang sama dengan komputer Anda. +2. Dapatkan alamat IP perangkat Anda (dalam Pengaturan → Tentang ponsel → Status). +3. Aktifkan adb melalui TCP / IP pada perangkat Anda: `adb tcpip 5555`. +4. Cabut perangkat Anda. +5. Hubungkan ke perangkat Anda: `adb connect DEVICE_IP: 5555` (*ganti* *`DEVICE_IP`*). +6. Jalankan `scrcpy` seperti biasa. + +Mungkin berguna untuk menurunkan kecepatan bit dan definisi: + +```bash +scrcpy --bit-rate 2M --max-size 800 +scrcpy -b2M -m800 # versi pendek +``` + +[terhubung]: https://developer.android.com/studio/command-line/adb.html#wireless + + +#### Multi-perangkat + +Jika beberapa perangkat dicantumkan di `adb devices`, Anda harus menentukan _serial_: + +```bash +scrcpy --serial 0123456789abcdef +scrcpy -s 0123456789abcdef # versi pendek +``` + +If the device is connected over TCP/IP: + +```bash +scrcpy --serial 192.168.0.1:5555 +scrcpy -s 192.168.0.1:5555 # versi pendek +``` + +Anda dapat memulai beberapa contoh _scrcpy_ untuk beberapa perangkat. + +#### Mulai otomatis pada koneksi perangkat + +Anda bisa menggunakan [AutoAdb]: + +```bash +autoadb scrcpy -s '{}' +``` + +[AutoAdb]: https://github.com/rom1v/autoadb + +#### Koneksi via SSH tunnel + +Untuk menyambung ke perangkat jarak jauh, dimungkinkan untuk menghubungkan klien `adb` lokal ke server `adb` jarak jauh (asalkan mereka menggunakan versi yang sama dari _adb_ protocol): + +```bash +adb kill-server # matikan server adb lokal di 5037 +ssh -CN -L5037:localhost:5037 -R27183:localhost:27183 komputer_jarak_jauh_anda +# jaga agar tetap terbuka +``` + +Dari terminal lain: + +```bash +scrcpy +``` + +Untuk menghindari mengaktifkan penerusan port jarak jauh, Anda dapat memaksa sambungan maju sebagai gantinya (perhatikan `-L`, bukan` -R`): + +```bash +adb kill-server # matikan server adb lokal di 5037 +ssh -CN -L5037:localhost:5037 -L27183:localhost:27183 komputer_jarak_jauh_anda +# jaga agar tetap terbuka +``` + +Dari terminal lain: + +```bash +scrcpy --force-adb-forward +``` + +Seperti koneksi nirkabel, mungkin berguna untuk mengurangi kualitas: + +``` +scrcpy -b2M -m800 --max-fps 15 +``` + +### Konfigurasi Jendela + +#### Judul + +Secara default, judul jendela adalah model perangkat. Itu bisa diubah: + +```bash +scrcpy --window-title 'Perangkat Saya' +``` + +#### Posisi dan ukuran + +Posisi dan ukuran jendela awal dapat ditentukan: + +```bash +scrcpy --window-x 100 --window-y 100 --window-width 800 --window-height 600 +``` + +#### Jendela tanpa batas + +Untuk menonaktifkan dekorasi jendela: + +```bash +scrcpy --window-borderless +``` + +#### Selalu di atas + +Untuk menjaga jendela scrcpy selalu di atas: + +```bash +scrcpy --always-on-top +``` + +#### Layar penuh + +Aplikasi dapat dimulai langsung dalam layar penuh:: + +```bash +scrcpy --fullscreen +scrcpy -f # versi pendek +``` + +Layar penuh kemudian dapat diubah secara dinamis dengan MOD+f. + +#### Rotasi + +Jendela mungkin diputar: + +```bash +scrcpy --rotation 1 +``` + +Nilai yang mungkin adalah: + - `0`: tidak ada rotasi + - `1`: 90 derajat berlawanan arah jarum jam + - `2`: 180 derajat + - `3`: 90 derajat searah jarum jam + +Rotasi juga dapat diubah secara dinamis dengan MOD+ +_(kiri)_ and MOD+ _(kanan)_. + +Perhatikan bahwa _scrcpy_ mengelola 3 rotasi berbeda:: + - MOD+r meminta perangkat untuk beralih antara potret dan lanskap (aplikasi yang berjalan saat ini mungkin menolak, jika mendukung orientasi yang diminta). + - `--lock-video-orientation` mengubah orientasi pencerminan (orientasi video yang dikirim dari perangkat ke komputer). Ini mempengaruhi rekaman. + - `--rotation` (atau MOD+/MOD+) + memutar hanya konten jendela. Ini hanya mempengaruhi tampilan, bukan rekaman. + + +### Opsi pencerminan lainnya + +#### Hanya-baca + +Untuk menonaktifkan kontrol (semua yang dapat berinteraksi dengan perangkat: tombol input, peristiwa mouse, seret & lepas file): + +```bash +scrcpy --no-control +scrcpy -n +``` + +#### Layar + +Jika beberapa tampilan tersedia, Anda dapat memilih tampilan untuk cermin: + +```bash +scrcpy --display 1 +``` + +Daftar id tampilan dapat diambil dengan:: + +``` +adb shell dumpsys display # cari "mDisplayId=" di keluaran +``` + +Tampilan sekunder hanya dapat dikontrol jika perangkat menjalankan setidaknya Android 10 (jika tidak maka akan dicerminkan dalam hanya-baca). + + +#### Tetap terjaga + +Untuk mencegah perangkat tidur setelah beberapa penundaan saat perangkat dicolokkan: + +```bash +scrcpy --stay-awake +scrcpy -w +``` + +Keadaan awal dipulihkan ketika scrcpy ditutup. + + +#### Matikan layar + +Dimungkinkan untuk mematikan layar perangkat saat pencerminan mulai dengan opsi baris perintah: + +```bash +scrcpy --turn-screen-off +scrcpy -S +``` + +Atau dengan menekan MOD+o kapan saja. + +Untuk menyalakannya kembali, tekan MOD+Shift+o. + +Di Android, tombol `POWER` selalu menyalakan layar. Untuk kenyamanan, jika `POWER` dikirim melalui scrcpy (melalui klik kanan atauMOD+p), itu akan memaksa untuk mematikan layar setelah penundaan kecil (atas dasar upaya terbaik). +Tombol fisik `POWER` masih akan menyebabkan layar dihidupkan. + +Ini juga berguna untuk mencegah perangkat tidur: + +```bash +scrcpy --turn-screen-off --stay-awake +scrcpy -Sw +``` + +#### Render frame kedaluwarsa + +Secara default, untuk meminimalkan latensi, _scrcpy_ selalu menampilkan frame yang terakhir didekodekan tersedia, dan menghapus frame sebelumnya. + +Untuk memaksa rendering semua frame (dengan kemungkinan peningkatan latensi), gunakan: + +```bash +scrcpy --render-expired-frames +``` + +#### Tunjukkan sentuhan + +Untuk presentasi, mungkin berguna untuk menunjukkan sentuhan fisik (pada perangkat fisik). + +Android menyediakan fitur ini di _Opsi Pengembang_. + +_Scrcpy_ menyediakan opsi untuk mengaktifkan fitur ini saat mulai dan mengembalikan nilai awal saat keluar: + +```bash +scrcpy --show-touches +scrcpy -t +``` + +Perhatikan bahwa ini hanya menunjukkan sentuhan _fisik_ (dengan jari di perangkat). + + +#### Nonaktifkan screensaver + +Secara default, scrcpy tidak mencegah screensaver berjalan di komputer. + +Untuk menonaktifkannya: + +```bash +scrcpy --disable-screensaver +``` + + +### Kontrol masukan + +#### Putar layar perangkat + +Tekan MOD+r untuk beralih antara mode potret dan lanskap. + +Perhatikan bahwa itu berputar hanya jika aplikasi di latar depan mendukung orientasi yang diminta. + +#### Salin-tempel + +Setiap kali papan klip Android berubah, secara otomatis disinkronkan ke papan klip komputer. + +Apa saja Ctrl pintasan diteruskan ke perangkat. Khususnya: + - Ctrl+c biasanya salinan + - Ctrl+x biasanya memotong + - Ctrl+v biasanya menempel (setelah sinkronisasi papan klip komputer-ke-perangkat) + +Ini biasanya berfungsi seperti yang Anda harapkan. + +Perilaku sebenarnya tergantung pada aplikasi yang aktif. Sebagai contoh, +_Termux_ mengirim SIGINT ke Ctrl+c sebagai gantinya, dan _K-9 Mail_ membuat pesan baru. + +Untuk menyalin, memotong dan menempel dalam kasus seperti itu (tetapi hanya didukung di Android> = 7): + - MOD+c injeksi `COPY` _(salin)_ + - MOD+x injeksi `CUT` _(potong)_ + - MOD+v injeksi `PASTE` (setelah sinkronisasi papan klip komputer-ke-perangkat) + +Tambahan, MOD+Shift+v memungkinkan untuk memasukkan teks papan klip komputer sebagai urutan peristiwa penting. Ini berguna ketika komponen tidak menerima penempelan teks (misalnya di _Termux_), tetapi dapat merusak konten non-ASCII. + +**PERINGATAN:** Menempelkan papan klip komputer ke perangkat (baik melalui +Ctrl+v or MOD+v) menyalin konten ke clipboard perangkat. Akibatnya, aplikasi Android apa pun dapat membaca kontennya. Anda harus menghindari menempelkan konten sensitif (seperti kata sandi) seperti itu. + + +#### Cubit untuk memperbesar/memperkecil + +Untuk mensimulasikan "cubit-untuk-memperbesar/memperkecil": Ctrl+_klik-dan-pindah_. + +Lebih tepatnya, tahan Ctrl sambil menekan tombol klik kiri. Hingga tombol klik kiri dilepaskan, semua gerakan mouse berskala dan memutar konten (jika didukung oleh aplikasi) relatif ke tengah layar. + +Secara konkret, scrcpy menghasilkan kejadian sentuh tambahan dari "jari virtual" di lokasi yang dibalik melalui bagian tengah layar. + + +#### Preferensi injeksi teks + +Ada dua jenis [peristiwa][textevents] dihasilkan saat mengetik teks: +- _peristiwa penting_, menandakan bahwa tombol ditekan atau dilepaskan; +- _peristiwa teks_, menandakan bahwa teks telah dimasukkan. + +Secara default, huruf dimasukkan menggunakan peristiwa kunci, sehingga keyboard berperilaku seperti yang diharapkan dalam game (biasanya untuk tombol WASD). + +Tapi ini mungkin [menyebabkan masalah][prefertext]. Jika Anda mengalami masalah seperti itu, Anda dapat menghindarinya dengan: + +```bash +scrcpy --prefer-text +``` + +(tapi ini akan merusak perilaku keyboard dalam game) + +[textevents]: https://blog.rom1v.com/2018/03/introducing-scrcpy/#handle-text-input +[prefertext]: https://github.com/Genymobile/scrcpy/issues/650#issuecomment-512945343 + + +#### Ulangi kunci + +Secara default, menahan tombol akan menghasilkan peristiwa kunci yang berulang. Ini dapat menyebabkan masalah kinerja di beberapa game, di mana acara ini tidak berguna. + +Untuk menghindari penerusan peristiwa penting yang berulang: + +```bash +scrcpy --no-key-repeat +``` + + +### Seret/jatuhkan file + +#### Pasang APK + +Untuk menginstal APK, seret & lepas file APK (diakhiri dengan `.apk`) ke jendela _scrcpy_. + +Tidak ada umpan balik visual, log dicetak ke konsol. + + +#### Dorong file ke perangkat + +Untuk mendorong file ke `/sdcard/` di perangkat, seret & jatuhkan file (non-APK) ke jendela _scrcpy_. + +Tidak ada umpan balik visual, log dicetak ke konsol. + +Direktori target dapat diubah saat mulai: + +```bash +scrcpy --push-target /sdcard/foo/bar/ +``` + + +### Penerusan audio + +Audio tidak diteruskan oleh _scrcpy_. Gunakan [sndcpy]. + +Lihat juga [Masalah #14]. + +[sndcpy]: https://github.com/rom1v/sndcpy +[Masalah #14]: https://github.com/Genymobile/scrcpy/issues/14 + + +## Pintasan + +Dalam daftar berikut, MOD adalah pengubah pintasan. Secara default, ini (kiri) Alt atau (kiri) Super. + +Ini dapat diubah menggunakan `--shortcut-mod`. Kunci yang memungkinkan adalah `lctrl`,`rctrl`, `lalt`,` ralt`, `lsuper` dan` rsuper`. Sebagai contoh: + +```bash +# gunakan RCtrl untuk jalan pintas +scrcpy --shortcut-mod=rctrl + +# gunakan baik LCtrl+LAlt atau LSuper untuk jalan pintas +scrcpy --shortcut-mod=lctrl+lalt,lsuper +``` + +_[Super] biasanya adalah Windows atau Cmd key._ + +[Super]: https://en.wikipedia.org/wiki/Super_key_(keyboard_button) + + | Aksi | Pintasan + | ------------------------------------------------------|:----------------------------- + | Alihkan mode layar penuh | MOD+f + | Putar layar kiri | MOD+ _(kiri)_ + | Putar layar kanan | MOD+ _(kanan)_ + | Ubah ukuran jendela menjadi 1:1 (piksel-sempurna) | MOD+g + | Ubah ukuran jendela menjadi hapus batas hitam | MOD+w \| _klik-dua-kali¹_ + | Klik `HOME` | MOD+h \| _Klik-tengah_ + | Klik `BACK` | MOD+b \| _Klik-kanan²_ + | Klik `APP_SWITCH` | MOD+s + | Klik `MENU` (buka kunci layar) | MOD+m + | Klik `VOLUME_UP` | MOD+ _(naik)_ + | Klik `VOLUME_DOWN` | MOD+ _(turun)_ + | Klik `POWER` | MOD+p + | Menyalakan | _Klik-kanan²_ + | Matikan layar perangkat (tetap mirroring) | MOD+o + | Hidupkan layar perangkat | MOD+Shift+o + | Putar layar perangkat | MOD+r + | Luaskan panel notifikasi | MOD+n + | Ciutkan panel notifikasi | MOD+Shift+n + | Menyalin ke papan klip³ | MOD+c + | Potong ke papan klip³ | MOD+x + | Sinkronkan papan klip dan tempel³ | MOD+v + | Masukkan teks papan klip komputer | MOD+Shift+v + | Mengaktifkan/menonaktifkan penghitung FPS (di stdout) | MOD+i + | Cubit-untuk-memperbesar/memperkecil | Ctrl+_klik-dan-pindah_ + +_¹Klik-dua-kali pada batas hitam untuk menghapusnya._ +_²Klik-kanan akan menghidupkan layar jika mati, tekan BACK jika tidak._ +_³Hanya di Android >= 7._ + +Semua Ctrl+_key_ pintasan diteruskan ke perangkat, demikian adanya +ditangani oleh aplikasi aktif. + + +## Jalur kustom + +Untuk menggunakan biner _adb_ tertentu, konfigurasikan jalurnya di variabel lingkungan `ADB`: + + ADB=/path/to/adb scrcpy + +Untuk mengganti jalur file `scrcpy-server`, konfigurasikan jalurnya di +`SCRCPY_SERVER_PATH`. + +[useful]: https://github.com/Genymobile/scrcpy/issues/278#issuecomment-429330345 + + +## Mengapa _scrcpy_? + +Seorang kolega menantang saya untuk menemukan nama yang tidak dapat diucapkan seperti [gnirehtet]. + +[`strcpy`] menyalin sebuah **str**ing; `scrcpy` menyalin sebuah **scr**een. + +[gnirehtet]: https://github.com/Genymobile/gnirehtet +[`strcpy`]: http://man7.org/linux/man-pages/man3/strcpy.3.html + + +## Bagaimana Cara membangun? + +Lihat [BUILD]. + +[BUILD]: BUILD.md + + +## Masalah umum + +Lihat [FAQ](FAQ.md). + + +## Pengembang + +Baca [halaman pengembang]. + +[halaman pengembang]: DEVELOP.md + + +## Lisensi + + Copyright (C) 2018 Genymobile + Copyright (C) 2018-2020 Romain Vimont + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +## Artikel + +- [Introducing scrcpy][article-intro] +- [Scrcpy now works wirelessly][article-tcpip] + +[article-intro]: https://blog.rom1v.com/2018/03/introducing-scrcpy/ +[article-tcpip]: https://www.genymotion.com/blog/open-source-project-scrcpy-now-works-wirelessly/ + diff --git a/README.md b/README.md index 2c5816a9..4c7fc0ec 100644 --- a/README.md +++ b/README.md @@ -755,6 +755,7 @@ Read the [developers page]. This README is available in other languages: +- [Indonesian (Indonesia, `id`)](README.id.md) - [한국어 (Korean, `ko`) - v1.11](README.ko.md) - [português brasileiro (Brazilian Portuguese, `pt-BR`) - v1.12.1](README.pt-br.md) - [简体中文 (Simplified Chinese, `zh-Hans`) - v1.16](README.zh-Hans.md) From 25aff0093500c95c90192bc7e78f40ec3f617519 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Mon, 5 Oct 2020 21:22:54 +0200 Subject: [PATCH 223/450] Mention version of Indonesian translation --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 4c7fc0ec..d10aaa74 100644 --- a/README.md +++ b/README.md @@ -755,7 +755,7 @@ Read the [developers page]. This README is available in other languages: -- [Indonesian (Indonesia, `id`)](README.id.md) +- [Indonesian (Indonesia, `id`) - v1.16](README.id.md) - [한국어 (Korean, `ko`) - v1.11](README.ko.md) - [português brasileiro (Brazilian Portuguese, `pt-BR`) - v1.12.1](README.pt-br.md) - [简体中文 (Simplified Chinese, `zh-Hans`) - v1.16](README.zh-Hans.md) From ad5f567f07627baea09c7bcf596f921923dca4b2 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Tue, 3 Nov 2020 17:08:21 +0100 Subject: [PATCH 224/450] Remove spurious space --- app/src/scrcpy.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index 45068cbb..eae9a1eb 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -422,7 +422,7 @@ scrcpy(const struct scrcpy_options *options) { options->window_y, options->window_width, options->window_height, options->window_borderless, - options->rotation, options-> mipmaps)) { + options->rotation, options->mipmaps)) { goto end; } From 5dcfc0ebab563e7bfdcf4d28025fa1996a79c214 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Tue, 3 Nov 2020 17:09:03 +0100 Subject: [PATCH 225/450] Add local.properties to gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 7bc30289..2829d835 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,4 @@ build/ .idea/ .gradle/ /x/ +local.properties From adc547fa6e8e6167cd9633a97d98de6665b8c23a Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Mon, 5 Oct 2020 20:45:53 +0200 Subject: [PATCH 226/450] Add an option to forward all clicks Add --forward-all-clicks to disable mouse shortcuts and forward middle and right clicks to the device instead. Fixes #1302 Fixes #1613 --- README.md | 10 ++++++++++ app/scrcpy.1 | 4 ++++ app/src/cli.c | 11 +++++++++++ app/src/input_manager.c | 3 ++- app/src/input_manager.h | 1 + app/src/scrcpy.h | 2 ++ .../main/java/com/genymobile/scrcpy/Controller.java | 8 ++++++-- 7 files changed, 36 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index e8b54465..a61407ac 100644 --- a/README.md +++ b/README.md @@ -595,6 +595,16 @@ scrcpy --no-key-repeat ``` +#### Right-click and middle-click + +By default, right-click triggers BACK (or POWER on) and middle-click triggers +HOME. To disable these shortcuts and forward the clicks to the device instead: + +```bash +scrcpy --forward-all-clicks +``` + + ### File drop #### Install APK diff --git a/app/scrcpy.1 b/app/scrcpy.1 index e957a4d0..e2b999de 100644 --- a/app/scrcpy.1 +++ b/app/scrcpy.1 @@ -60,6 +60,10 @@ Default is 0. .B \-\-force\-adb\-forward Do not attempt to use "adb reverse" to connect to the device. +.TP +.B \-\-forward\-all\-clicks +By default, right-click triggers BACK (or POWER on) and middle-click triggers HOME. This option disables these shortcuts and forward the clicks to the device instead. + .TP .B \-f, \-\-fullscreen Start in fullscreen. diff --git a/app/src/cli.c b/app/src/cli.c index 41de8ca5..ffe31455 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -57,6 +57,11 @@ scrcpy_print_usage(const char *arg0) { " Do not attempt to use \"adb reverse\" to connect to the\n" " the device.\n" "\n" + " --forward-all-clicks\n" + " By default, right-click triggers BACK (or POWER on) and\n" + " middle-click triggers HOME. This option disables these\n" + " shortcuts and forward the clicks to the device instead.\n" + "\n" " -f, --fullscreen\n" " Start in fullscreen.\n" "\n" @@ -651,6 +656,7 @@ guess_record_format(const char *filename) { #define OPT_DISABLE_SCREENSAVER 1020 #define OPT_SHORTCUT_MOD 1021 #define OPT_NO_KEY_REPEAT 1022 +#define OPT_FORWARD_ALL_CLICKS 1023 bool scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) { @@ -664,6 +670,8 @@ scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) { {"display", required_argument, NULL, OPT_DISPLAY_ID}, {"force-adb-forward", no_argument, NULL, OPT_FORCE_ADB_FORWARD}, + {"forward-all-clicks", no_argument, NULL, + OPT_FORWARD_ALL_CLICKS}, {"fullscreen", no_argument, NULL, 'f'}, {"help", no_argument, NULL, 'h'}, {"lock-video-orientation", required_argument, NULL, @@ -856,6 +864,9 @@ scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) { return false; } break; + case OPT_FORWARD_ALL_CLICKS: + opts->forward_all_clicks = true; + break; default: // getopt prints the error message on stderr return false; diff --git a/app/src/input_manager.c b/app/src/input_manager.c index 962db1d3..b03da383 100644 --- a/app/src/input_manager.c +++ b/app/src/input_manager.c @@ -60,6 +60,7 @@ input_manager_init(struct input_manager *im, im->control = options->control; im->forward_key_repeat = options->forward_key_repeat; im->prefer_text = options->prefer_text; + im->forward_all_clicks = options->forward_all_clicks; const struct sc_shortcut_mods *shortcut_mods = &options->shortcut_mods; assert(shortcut_mods->count); @@ -629,7 +630,7 @@ input_manager_process_mouse_button(struct input_manager *im, } bool down = event->type == SDL_MOUSEBUTTONDOWN; - if (down) { + if (!im->forward_all_clicks && down) { if (control && event->button == SDL_BUTTON_RIGHT) { press_back_or_turn_screen_on(im->controller); return; diff --git a/app/src/input_manager.h b/app/src/input_manager.h index c3756e40..157c2832 100644 --- a/app/src/input_manager.h +++ b/app/src/input_manager.h @@ -25,6 +25,7 @@ struct input_manager { bool control; bool forward_key_repeat; bool prefer_text; + bool forward_all_clicks; struct { unsigned data[SC_MAX_SHORTCUT_MODS]; diff --git a/app/src/scrcpy.h b/app/src/scrcpy.h index 86a2b57b..8ab05a9d 100644 --- a/app/src/scrcpy.h +++ b/app/src/scrcpy.h @@ -79,6 +79,7 @@ struct scrcpy_options { bool force_adb_forward; bool disable_screensaver; bool forward_key_repeat; + bool forward_all_clicks; }; #define SCRCPY_OPTIONS_DEFAULT { \ @@ -123,6 +124,7 @@ struct scrcpy_options { .force_adb_forward = false, \ .disable_screensaver = false, \ .forward_key_repeat = true, \ + .forward_all_clicks = false, \ } bool diff --git a/server/src/main/java/com/genymobile/scrcpy/Controller.java b/server/src/main/java/com/genymobile/scrcpy/Controller.java index 79feefc1..84780239 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Controller.java +++ b/server/src/main/java/com/genymobile/scrcpy/Controller.java @@ -205,9 +205,13 @@ public class Controller { } } + // Right-click and middle-click only work if the source is a mouse + boolean nonPrimaryButtonPressed = (buttons & ~MotionEvent.BUTTON_PRIMARY) != 0; + int source = nonPrimaryButtonPressed ? InputDevice.SOURCE_MOUSE : InputDevice.SOURCE_TOUCHSCREEN; + MotionEvent event = MotionEvent - .obtain(lastTouchDown, now, action, pointerCount, pointerProperties, pointerCoords, 0, buttons, 1f, 1f, DEVICE_ID_VIRTUAL, 0, - InputDevice.SOURCE_TOUCHSCREEN, 0); + .obtain(lastTouchDown, now, action, pointerCount, pointerProperties, pointerCoords, 0, buttons, 1f, 1f, DEVICE_ID_VIRTUAL, 0, source, + 0); return device.injectEvent(event); } From d5f059c7cbef7d13889086670716d9f33f805dc4 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Tue, 6 Oct 2020 21:30:10 +0200 Subject: [PATCH 227/450] Add option to force legacy method for pasting Some devices do not behave as expected when setting the device clipboard programmatically. Add an option --legacy-paste to change the behavior of Ctrl+v and MOD+v so that they inject the computer clipboard text as a sequence of key events (the same way as MOD+Shift+v). Fixes #1750 Fixes #1771 --- README.md | 5 +++++ app/scrcpy.1 | 6 ++++++ app/src/cli.c | 11 +++++++++++ app/src/input_manager.c | 8 +++++++- app/src/input_manager.h | 1 + app/src/scrcpy.h | 2 ++ 6 files changed, 32 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index a61407ac..95a38409 100644 --- a/README.md +++ b/README.md @@ -548,6 +548,11 @@ into the device clipboard. As a consequence, any Android application could read its content. You should avoid to paste sensitive content (like passwords) that way. +Some devices do not behave as expected when setting the device clipboard +programmatically. An option `--legacy-paste` is provided to change the behavior +of Ctrl+v and MOD+v so that they +also inject the computer clipboard text as a sequence of key events (the same +way as MOD+Shift+v). #### Pinch-to-zoom diff --git a/app/scrcpy.1 b/app/scrcpy.1 index e2b999de..b16d1004 100644 --- a/app/scrcpy.1 +++ b/app/scrcpy.1 @@ -72,6 +72,12 @@ Start in fullscreen. .B \-h, \-\-help Print this help. +.TP +.B \-\-legacy\-paste +Inject computer clipboard text as a sequence of key events on Ctrl+v (like MOD+Shift+v). + +This is a workaround for some devices not behaving as expected when setting the device clipboard programmatically. + .TP .BI "\-\-lock\-video\-orientation " value Lock video orientation to \fIvalue\fR. Possible values are -1 (unlocked), 0, 1, 2 and 3. Natural device orientation is 0, and each increment adds a 90 degrees otation counterclockwise. diff --git a/app/src/cli.c b/app/src/cli.c index ffe31455..4f15f2e8 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -68,6 +68,12 @@ scrcpy_print_usage(const char *arg0) { " -h, --help\n" " Print this help.\n" "\n" + " --legacy-paste\n" + " Inject computer clipboard text as a sequence of key events\n" + " on Ctrl+v (like MOD+Shift+v).\n" + " This is a workaround for some devices not behaving as\n" + " expected when setting the device clipboard programmatically.\n" + "\n" " --lock-video-orientation value\n" " Lock video orientation to value.\n" " Possible values are -1 (unlocked), 0, 1, 2 and 3.\n" @@ -657,6 +663,7 @@ guess_record_format(const char *filename) { #define OPT_SHORTCUT_MOD 1021 #define OPT_NO_KEY_REPEAT 1022 #define OPT_FORWARD_ALL_CLICKS 1023 +#define OPT_LEGACY_PASTE 1024 bool scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) { @@ -674,6 +681,7 @@ scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) { OPT_FORWARD_ALL_CLICKS}, {"fullscreen", no_argument, NULL, 'f'}, {"help", no_argument, NULL, 'h'}, + {"legacy-paste", no_argument, NULL, OPT_LEGACY_PASTE}, {"lock-video-orientation", required_argument, NULL, OPT_LOCK_VIDEO_ORIENTATION}, {"max-fps", required_argument, NULL, OPT_MAX_FPS}, @@ -867,6 +875,9 @@ scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) { case OPT_FORWARD_ALL_CLICKS: opts->forward_all_clicks = true; break; + case OPT_LEGACY_PASTE: + opts->legacy_paste = true; + break; default: // getopt prints the error message on stderr return false; diff --git a/app/src/input_manager.c b/app/src/input_manager.c index b03da383..bab85660 100644 --- a/app/src/input_manager.c +++ b/app/src/input_manager.c @@ -61,6 +61,7 @@ input_manager_init(struct input_manager *im, im->forward_key_repeat = options->forward_key_repeat; im->prefer_text = options->prefer_text; im->forward_all_clicks = options->forward_all_clicks; + im->legacy_paste = options->legacy_paste; const struct sc_shortcut_mods *shortcut_mods = &options->shortcut_mods; assert(shortcut_mods->count); @@ -441,7 +442,7 @@ input_manager_process_key(struct input_manager *im, return; case SDLK_v: if (control && !repeat && down) { - if (shift) { + if (shift || im->legacy_paste) { // inject the text as input events clipboard_paste(controller); } else { @@ -505,6 +506,11 @@ input_manager_process_key(struct input_manager *im, } if (ctrl && !shift && keycode == SDLK_v && down && !repeat) { + if (im->legacy_paste) { + // inject the text as input events + clipboard_paste(controller); + return; + } // Synchronize the computer clipboard to the device clipboard before // sending Ctrl+v, to allow seamless copy-paste. set_device_clipboard(controller, false); diff --git a/app/src/input_manager.h b/app/src/input_manager.h index 157c2832..df9b071f 100644 --- a/app/src/input_manager.h +++ b/app/src/input_manager.h @@ -26,6 +26,7 @@ struct input_manager { bool forward_key_repeat; bool prefer_text; bool forward_all_clicks; + bool legacy_paste; struct { unsigned data[SC_MAX_SHORTCUT_MODS]; diff --git a/app/src/scrcpy.h b/app/src/scrcpy.h index 8ab05a9d..5f761f75 100644 --- a/app/src/scrcpy.h +++ b/app/src/scrcpy.h @@ -80,6 +80,7 @@ struct scrcpy_options { bool disable_screensaver; bool forward_key_repeat; bool forward_all_clicks; + bool legacy_paste; }; #define SCRCPY_OPTIONS_DEFAULT { \ @@ -125,6 +126,7 @@ struct scrcpy_options { .disable_screensaver = false, \ .forward_key_repeat = true, \ .forward_all_clicks = false, \ + .legacy_paste = false, \ } bool From 76c2c6e69dc074031a81c4e050347e374cbfb776 Mon Sep 17 00:00:00 2001 From: Tzah Mazuz Date: Mon, 12 Oct 2020 12:23:06 +0300 Subject: [PATCH 228/450] Adding new option --encoder Some devices have more than one encoder, and some encoders may cause issues or crash. With this option we can specify which encoder we want the device to use. PR #1827 Fixes #1810 Signed-off-by: Romain Vimont --- app/src/cli.c | 8 ++++++++ app/src/scrcpy.c | 1 + app/src/scrcpy.h | 2 ++ app/src/server.c | 1 + app/src/server.h | 1 + .../src/main/java/com/genymobile/scrcpy/Options.java | 9 +++++++++ .../java/com/genymobile/scrcpy/ScreenEncoder.java | 12 +++++++++--- .../src/main/java/com/genymobile/scrcpy/Server.java | 8 ++++++-- 8 files changed, 37 insertions(+), 5 deletions(-) diff --git a/app/src/cli.c b/app/src/cli.c index 4f15f2e8..f01b7941 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -53,6 +53,9 @@ scrcpy_print_usage(const char *arg0) { "\n" " Default is 0.\n" "\n" + " --encoder name\n" + " Use a specific MediaCodec encoder (must be a H.264 encoder).\n" + "\n" " --force-adb-forward\n" " Do not attempt to use \"adb reverse\" to connect to the\n" " the device.\n" @@ -664,6 +667,7 @@ guess_record_format(const char *filename) { #define OPT_NO_KEY_REPEAT 1022 #define OPT_FORWARD_ALL_CLICKS 1023 #define OPT_LEGACY_PASTE 1024 +#define OPT_ENCODER_NAME 1025 bool scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) { @@ -675,6 +679,7 @@ scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) { {"disable-screensaver", no_argument, NULL, OPT_DISABLE_SCREENSAVER}, {"display", required_argument, NULL, OPT_DISPLAY_ID}, + {"encoder", required_argument, NULL, OPT_ENCODER_NAME}, {"force-adb-forward", no_argument, NULL, OPT_FORCE_ADB_FORWARD}, {"forward-all-clicks", no_argument, NULL, @@ -861,6 +866,9 @@ scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) { case OPT_CODEC_OPTIONS: opts->codec_options = optarg; break; + case OPT_ENCODER_NAME: + opts->encoder_name = optarg; + break; case OPT_FORCE_ADB_FORWARD: opts->force_adb_forward = true; break; diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index eae9a1eb..a543e11c 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -318,6 +318,7 @@ scrcpy(const struct scrcpy_options *options) { .show_touches = options->show_touches, .stay_awake = options->stay_awake, .codec_options = options->codec_options, + .encoder_name = options->encoder_name, .force_adb_forward = options->force_adb_forward, }; if (!server_start(&server, options->serial, ¶ms)) { diff --git a/app/src/scrcpy.h b/app/src/scrcpy.h index 5f761f75..8548d1f7 100644 --- a/app/src/scrcpy.h +++ b/app/src/scrcpy.h @@ -51,6 +51,7 @@ struct scrcpy_options { const char *push_target; const char *render_driver; const char *codec_options; + const char *encoder_name; enum sc_log_level log_level; enum sc_record_format record_format; struct sc_port_range port_range; @@ -91,6 +92,7 @@ struct scrcpy_options { .push_target = NULL, \ .render_driver = NULL, \ .codec_options = NULL, \ + .encoder_name = NULL, \ .log_level = SC_LOG_LEVEL_INFO, \ .record_format = SC_RECORD_FORMAT_AUTO, \ .port_range = { \ diff --git a/app/src/server.c b/app/src/server.c index 422bbfa5..9267356b 100644 --- a/app/src/server.c +++ b/app/src/server.c @@ -294,6 +294,7 @@ execute_server(struct server *server, const struct server_params *params) { params->show_touches ? "true" : "false", params->stay_awake ? "true" : "false", params->codec_options ? params->codec_options : "-", + params->encoder_name ? params->encoder_name : "-", }; #ifdef SERVER_DEBUGGER LOGI("Server debugger waiting for a client on device port " diff --git a/app/src/server.h b/app/src/server.h index 254afe30..30ce09bc 100644 --- a/app/src/server.h +++ b/app/src/server.h @@ -48,6 +48,7 @@ struct server_params { enum sc_log_level log_level; const char *crop; const char *codec_options; + const char *encoder_name; struct sc_port_range port_range; uint16_t max_size; uint32_t bit_rate; diff --git a/server/src/main/java/com/genymobile/scrcpy/Options.java b/server/src/main/java/com/genymobile/scrcpy/Options.java index 06312a37..150d06a8 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Options.java +++ b/server/src/main/java/com/genymobile/scrcpy/Options.java @@ -16,6 +16,7 @@ public class Options { private boolean showTouches; private boolean stayAwake; private String codecOptions; + private String encoderName; public Ln.Level getLogLevel() { return logLevel; @@ -120,4 +121,12 @@ public class Options { public void setCodecOptions(String codecOptions) { this.codecOptions = codecOptions; } + + public String getEncoderName() { + return encoderName; + } + + public void setEncoderName(String encoderName) { + this.encoderName = encoderName; + } } diff --git a/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java b/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java index d722388c..ee9ea935 100644 --- a/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java +++ b/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java @@ -26,17 +26,19 @@ public class ScreenEncoder implements Device.RotationListener { private final AtomicBoolean rotationChanged = new AtomicBoolean(); private final ByteBuffer headerBuffer = ByteBuffer.allocate(12); + private String encoderName; private List codecOptions; private int bitRate; private int maxFps; private boolean sendFrameMeta; private long ptsOrigin; - public ScreenEncoder(boolean sendFrameMeta, int bitRate, int maxFps, List codecOptions) { + public ScreenEncoder(boolean sendFrameMeta, int bitRate, int maxFps, List codecOptions, String encoderName) { this.sendFrameMeta = sendFrameMeta; this.bitRate = bitRate; this.maxFps = maxFps; this.codecOptions = codecOptions; + this.encoderName = encoderName; } @Override @@ -69,7 +71,7 @@ public class ScreenEncoder implements Device.RotationListener { boolean alive; try { do { - MediaCodec codec = createCodec(); + MediaCodec codec = createCodec(encoderName); IBinder display = createDisplay(); ScreenInfo screenInfo = device.getScreenInfo(); Rect contentRect = screenInfo.getContentRect(); @@ -150,7 +152,11 @@ public class ScreenEncoder implements Device.RotationListener { IO.writeFully(fd, headerBuffer); } - private static MediaCodec createCodec() throws IOException { + private static MediaCodec createCodec(String encoderName) throws IOException { + if (encoderName != null) { + Ln.d("Creating encoder by name: '" + encoderName + "'"); + return MediaCodec.createByCodecName(encoderName); + } return MediaCodec.createEncoderByType(MediaFormat.MIMETYPE_VIDEO_AVC); } diff --git a/server/src/main/java/com/genymobile/scrcpy/Server.java b/server/src/main/java/com/genymobile/scrcpy/Server.java index 9b7f9de8..0e7bd244 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Server.java +++ b/server/src/main/java/com/genymobile/scrcpy/Server.java @@ -54,7 +54,8 @@ public final class Server { boolean tunnelForward = options.isTunnelForward(); try (DesktopConnection connection = DesktopConnection.open(device, tunnelForward)) { - ScreenEncoder screenEncoder = new ScreenEncoder(options.getSendFrameMeta(), options.getBitRate(), options.getMaxFps(), codecOptions); + ScreenEncoder screenEncoder = new ScreenEncoder(options.getSendFrameMeta(), options.getBitRate(), options.getMaxFps(), codecOptions, + options.getEncoderName()); if (options.getControl()) { final Controller controller = new Controller(device, connection); @@ -120,7 +121,7 @@ public final class Server { "The server version (" + BuildConfig.VERSION_NAME + ") does not match the client " + "(" + clientVersion + ")"); } - final int expectedParameters = 14; + final int expectedParameters = 15; if (args.length != expectedParameters) { throw new IllegalArgumentException("Expecting " + expectedParameters + " parameters"); } @@ -167,6 +168,9 @@ public final class Server { String codecOptions = args[13]; options.setCodecOptions(codecOptions); + String encoderName = "-".equals(args[14]) ? null : args[14]; + options.setEncoderName(encoderName); + return options; } From 363eeea19eb0d347643a4dd9592dac0063396dff Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 8 Nov 2020 17:27:33 +0100 Subject: [PATCH 229/450] Log encoder name When the encoder is selected automatically, log the name of the selected encoder. --- server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java b/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java index ee9ea935..1dac7629 100644 --- a/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java +++ b/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java @@ -157,7 +157,9 @@ public class ScreenEncoder implements Device.RotationListener { Ln.d("Creating encoder by name: '" + encoderName + "'"); return MediaCodec.createByCodecName(encoderName); } - return MediaCodec.createEncoderByType(MediaFormat.MIMETYPE_VIDEO_AVC); + MediaCodec codec = MediaCodec.createEncoderByType(MediaFormat.MIMETYPE_VIDEO_AVC); + Ln.d("Using encoder: '" + codec.getName() + "'"); + return codec; } private static void setCodecOption(MediaFormat format, CodecOption codecOption) { From 42ab8fd61156a7894fb8c6802f7a1b408598e14f Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sat, 7 Nov 2020 15:53:48 +0100 Subject: [PATCH 230/450] List available encoders on invalid name specified If an invalid encoder name is given via the --encoder option, list all the H.264 encoders available on the device. --- .../scrcpy/InvalidEncoderException.java | 23 +++++++++++++++++++ .../com/genymobile/scrcpy/ScreenEncoder.java | 21 ++++++++++++++++- .../java/com/genymobile/scrcpy/Server.java | 10 ++++++++ 3 files changed, 53 insertions(+), 1 deletion(-) create mode 100644 server/src/main/java/com/genymobile/scrcpy/InvalidEncoderException.java diff --git a/server/src/main/java/com/genymobile/scrcpy/InvalidEncoderException.java b/server/src/main/java/com/genymobile/scrcpy/InvalidEncoderException.java new file mode 100644 index 00000000..1efd2989 --- /dev/null +++ b/server/src/main/java/com/genymobile/scrcpy/InvalidEncoderException.java @@ -0,0 +1,23 @@ +package com.genymobile.scrcpy; + +import android.media.MediaCodecInfo; + +public class InvalidEncoderException extends RuntimeException { + + private final String name; + private final MediaCodecInfo[] availableEncoders; + + public InvalidEncoderException(String name, MediaCodecInfo[] availableEncoders) { + super("There is no encoder having name '" + name + '"'); + this.name = name; + this.availableEncoders = availableEncoders; + } + + public String getName() { + return name; + } + + public MediaCodecInfo[] getAvailableEncoders() { + return availableEncoders; + } +} diff --git a/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java b/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java index 1dac7629..c7c104e4 100644 --- a/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java +++ b/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java @@ -5,6 +5,7 @@ import com.genymobile.scrcpy.wrappers.SurfaceControl; import android.graphics.Rect; import android.media.MediaCodec; import android.media.MediaCodecInfo; +import android.media.MediaCodecList; import android.media.MediaFormat; import android.os.IBinder; import android.view.Surface; @@ -12,6 +13,8 @@ import android.view.Surface; import java.io.FileDescriptor; import java.io.IOException; import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import java.util.concurrent.atomic.AtomicBoolean; @@ -152,10 +155,26 @@ public class ScreenEncoder implements Device.RotationListener { IO.writeFully(fd, headerBuffer); } + private static MediaCodecInfo[] listEncoders() { + List result = new ArrayList<>(); + MediaCodecList list = new MediaCodecList(MediaCodecList.REGULAR_CODECS); + for (MediaCodecInfo codecInfo : list.getCodecInfos()) { + if (codecInfo.isEncoder() && Arrays.asList(codecInfo.getSupportedTypes()).contains(MediaFormat.MIMETYPE_VIDEO_AVC)) { + result.add(codecInfo); + } + } + return result.toArray(new MediaCodecInfo[result.size()]); + } + private static MediaCodec createCodec(String encoderName) throws IOException { if (encoderName != null) { Ln.d("Creating encoder by name: '" + encoderName + "'"); - return MediaCodec.createByCodecName(encoderName); + try { + return MediaCodec.createByCodecName(encoderName); + } catch (IllegalArgumentException e) { + MediaCodecInfo[] encoders = listEncoders(); + throw new InvalidEncoderException(encoderName, encoders); + } } MediaCodec codec = MediaCodec.createEncoderByType(MediaFormat.MIMETYPE_VIDEO_AVC); Ln.d("Using encoder: '" + codec.getName() + "'"); diff --git a/server/src/main/java/com/genymobile/scrcpy/Server.java b/server/src/main/java/com/genymobile/scrcpy/Server.java index 0e7bd244..f501d3d8 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Server.java +++ b/server/src/main/java/com/genymobile/scrcpy/Server.java @@ -4,6 +4,7 @@ import com.genymobile.scrcpy.wrappers.ContentProvider; import android.graphics.Rect; import android.media.MediaCodec; +import android.media.MediaCodecInfo; import android.os.BatteryManager; import android.os.Build; @@ -210,6 +211,15 @@ public final class Server { Ln.e(" scrcpy --display " + id); } } + } else if (e instanceof InvalidEncoderException) { + InvalidEncoderException iee = (InvalidEncoderException) e; + MediaCodecInfo[] encoders = iee.getAvailableEncoders(); + if (encoders != null && encoders.length > 0) { + Ln.e("Try to use one of the available encoders:"); + for (MediaCodecInfo encoder : encoders) { + Ln.e(" scrcpy --encoder-name '" + encoder.getName() + "'"); + } + } } } From 576814bcec5718d325de3a85528e5133cc46be68 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 8 Nov 2020 21:11:12 +0100 Subject: [PATCH 231/450] Document --encoder option Add documentation for the new option --encoder in the manpage and in README.md. --- README.md | 16 ++++++++++++++++ app/scrcpy.1 | 4 ++++ 2 files changed, 20 insertions(+) diff --git a/README.md b/README.md index 95a38409..1283aa50 100644 --- a/README.md +++ b/README.md @@ -203,6 +203,22 @@ scrcpy --lock-video-orientation 3 # 90° clockwise This affects recording orientation. +#### Encoder + +Some devices have more than one encoder, and some of them may cause issues or +crash. It is possible to select a different encoder: + +```bash +scrcpy --encoder OMX.qcom.video.encoder.avc +``` + +To list the available encoders, you could pass an invalid encoder name, the +error will give the available encoders: + +```bash +scrcpy --encoder _ +``` + ### Recording It is possible to record the screen while mirroring: diff --git a/app/scrcpy.1 b/app/scrcpy.1 index b16d1004..92b8e1e3 100644 --- a/app/scrcpy.1 +++ b/app/scrcpy.1 @@ -56,6 +56,10 @@ The list of possible display ids can be listed by "adb shell dumpsys display" Default is 0. +.TP +.BI "\-\-encoder " name +Use a specific MediaCodec encoder (must be a H.264 encoder). + .TP .B \-\-force\-adb\-forward Do not attempt to use "adb reverse" to connect to the device. From 868e762d71695f0a45f593c83215aeb428e475b3 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sat, 14 Nov 2020 22:08:59 +0100 Subject: [PATCH 232/450] Fix size_t format specifier for Windows MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Use "%Iu" on Windows. This fixes the following warning: ../app/src/sys/win/command.c:17:14: warning: unknown conversion type character ‘l’ in format [-Wformat=] 17 | LOGE("Command too long (%" PRIsizet " chars)", len - 1); --- app/src/command.h | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/app/src/command.h b/app/src/command.h index 28f9fbcf..7035139b 100644 --- a/app/src/command.h +++ b/app/src/command.h @@ -12,11 +12,7 @@ # define PATH_SEPARATOR '\\' # define PRIexitcode "lu" // -# ifdef _WIN64 -# define PRIsizet PRIu64 -# else -# define PRIsizet PRIu32 -# endif +# define PRIsizet "Iu" # define PROCESS_NONE NULL # define NO_EXIT_CODE -1u // max value as unsigned typedef HANDLE process_t; From 30434afc0ad89198e8f7f5567d9d50bbbf8c055c Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Tue, 24 Nov 2020 09:45:18 +0100 Subject: [PATCH 233/450] Upgrade gradle build tools to 4.0.1 --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 2755fd62..c977c398 100644 --- a/build.gradle +++ b/build.gradle @@ -7,7 +7,7 @@ buildscript { jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:3.6.2' + classpath 'com.android.tools.build:gradle:4.0.1' // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files From 47c897126758ed016881bdf51b2eb643c4a5fe07 Mon Sep 17 00:00:00 2001 From: "SamBe.ng" Date: Mon, 30 Nov 2020 10:16:50 +0100 Subject: [PATCH 234/450] Document shell command to get the device IP PR #1944 Signed-off-by: Romain Vimont --- README.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index d10aaa74..67f2c14c 100644 --- a/README.md +++ b/README.md @@ -235,7 +235,13 @@ _Scrcpy_ uses `adb` to communicate with the device, and `adb` can [connect] to a device over TCP/IP: 1. Connect the device to the same Wi-Fi as your computer. -2. Get your device IP address (in Settings → About phone → Status). +2. Get your device IP address, in Settings → About phone → Status, or by + executing this command: + + ```bash + adb shell ip route | awk '{print $9}' + ``` + 3. Enable adb over TCP/IP on your device: `adb tcpip 5555`. 4. Unplug your device. 5. Connect to your device: `adb connect DEVICE_IP:5555` _(replace `DEVICE_IP`)_. From d6078cf20244e6651aea5832e6b78b3aa3459766 Mon Sep 17 00:00:00 2001 From: jianzhang4 Date: Wed, 9 Dec 2020 11:41:17 +0800 Subject: [PATCH 235/450] Fix build errors for macOS PR #1960 Signed-off-by: Romain Vimont --- app/src/sys/unix/command.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/src/sys/unix/command.c b/app/src/sys/unix/command.c index 64a54e71..09925c50 100644 --- a/app/src/sys/unix/command.c +++ b/app/src/sys/unix/command.c @@ -5,6 +5,10 @@ // modern glibc will complain without this #define _DEFAULT_SOURCE +#ifdef __APPLE__ +# define _DARWIN_C_SOURCE // for strdup(), strtok_r(), memset_pattern4() +#endif + #include "command.h" #include "config.h" From c9a4bdb8905042cc9bf14e55b09f4430214c9b31 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sat, 12 Dec 2020 15:56:32 +0100 Subject: [PATCH 236/450] Upgrade platform-tools (30.0.5) for Windows Include the latest version of adb in Windows releases. --- prebuilt-deps/Makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/prebuilt-deps/Makefile b/prebuilt-deps/Makefile index 9cccd0bd..c2297b82 100644 --- a/prebuilt-deps/Makefile +++ b/prebuilt-deps/Makefile @@ -35,6 +35,6 @@ prepare-sdl2: SDL2-2.0.12 prepare-adb: - @./prepare-dep https://dl.google.com/android/repository/platform-tools_r30.0.4-windows.zip \ - 413182fff6c5957911e231b9e97e6be4fc6a539035e3dfb580b5c54bd5950fee \ + @./prepare-dep https://dl.google.com/android/repository/platform-tools_r30.0.5-windows.zip \ + 549ba2bdc31f335eb8a504f005f77606a479cc216d6b64a3e8b64c780003661f \ platform-tools From 5dc3285dbf807553f86346ab16d0891cfeb32ab7 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sat, 12 Dec 2020 15:57:54 +0100 Subject: [PATCH 237/450] Reference FFmpeg Windows builds from GitHub Refs #1753 --- prebuilt-deps/Makefile | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/prebuilt-deps/Makefile b/prebuilt-deps/Makefile index c2297b82..535d5692 100644 --- a/prebuilt-deps/Makefile +++ b/prebuilt-deps/Makefile @@ -10,22 +10,22 @@ prepare-win32: prepare-sdl2 prepare-ffmpeg-shared-win32 prepare-ffmpeg-dev-win32 prepare-win64: prepare-sdl2 prepare-ffmpeg-shared-win64 prepare-ffmpeg-dev-win64 prepare-adb prepare-ffmpeg-shared-win32: - @./prepare-dep https://ffmpeg.zeranoe.com/builds/win32/shared/ffmpeg-4.3.1-win32-shared.zip \ + @./prepare-dep https://github.com/Genymobile/scrcpy/releases/download/v1.16/ffmpeg-4.3.1-win32-shared.zip \ 357af9901a456f4dcbacd107e83a934d344c9cb07ddad8aaf80612eeab7d26d2 \ ffmpeg-4.3.1-win32-shared prepare-ffmpeg-dev-win32: - @./prepare-dep https://ffmpeg.zeranoe.com/builds/win32/dev/ffmpeg-4.3.1-win32-dev.zip \ + @./prepare-dep https://github.com/Genymobile/scrcpy/releases/download/v1.16/ffmpeg-4.3.1-win32-dev.zip \ 230efb08e9bcf225bd474da29676c70e591fc94d8790a740ca801408fddcb78b \ ffmpeg-4.3.1-win32-dev prepare-ffmpeg-shared-win64: - @./prepare-dep https://ffmpeg.zeranoe.com/builds/win64/shared/ffmpeg-4.3.1-win64-shared.zip \ + @./prepare-dep https://github.com/Genymobile/scrcpy/releases/download/v1.16/ffmpeg-4.3.1-win64-shared.zip \ dd29b7f92f48dead4dd940492c7509138c0f99db445076d0a597007298a79940 \ ffmpeg-4.3.1-win64-shared prepare-ffmpeg-dev-win64: - @./prepare-dep https://ffmpeg.zeranoe.com/builds/win64/dev/ffmpeg-4.3.1-win64-dev.zip \ + @./prepare-dep https://github.com/Genymobile/scrcpy/releases/download/v1.16/ffmpeg-4.3.1-win64-dev.zip \ 2e8038242cf8e1bd095c2978f196ff0462b122cc6ef7e74626a6af15459d8b81 \ ffmpeg-4.3.1-win64-dev From 6d151eaef914dd836e206e2877263e47882bba18 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Mon, 14 Dec 2020 09:04:14 +0100 Subject: [PATCH 238/450] Reference encoder section from FAQ --- FAQ.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/FAQ.md b/FAQ.md index ba33542e..a2298e60 100644 --- a/FAQ.md +++ b/FAQ.md @@ -199,3 +199,5 @@ scrcpy -m 1920 scrcpy -m 1024 scrcpy -m 800 ``` + +You could also try another [encoder](README.md#encoder). From 904d47057987fcd2bc2bb9494758a611fa6ab2bc Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Mon, 14 Dec 2020 09:35:55 +0100 Subject: [PATCH 239/450] Pause on error from a wrapper script On Windows, scrcpy paused on error before exiting to give the user a chance to see the user message. This was a hack and causes issues when using scrcpy from batch scripts. Disable this pause from the scrcpy binary, and provide a batch wrapper (scrcpy-console.bat) to pause on error. Fixes #1875 --- Makefile.CrossWindows | 2 ++ app/src/main.c | 6 ------ data/scrcpy-console.bat | 4 ++++ 3 files changed, 6 insertions(+), 6 deletions(-) create mode 100644 data/scrcpy-console.bat diff --git a/Makefile.CrossWindows b/Makefile.CrossWindows index 0415af82..f8a360d9 100644 --- a/Makefile.CrossWindows +++ b/Makefile.CrossWindows @@ -100,6 +100,7 @@ dist-win32: build-server build-win32 build-win32-noconsole cp "$(SERVER_BUILD_DIR)"/server/scrcpy-server "$(DIST)/$(WIN32_TARGET_DIR)/" cp "$(WIN32_BUILD_DIR)"/app/scrcpy.exe "$(DIST)/$(WIN32_TARGET_DIR)/" cp "$(WIN32_NOCONSOLE_BUILD_DIR)"/app/scrcpy.exe "$(DIST)/$(WIN32_TARGET_DIR)/scrcpy-noconsole.exe" + cp data/scrcpy-console.bat "$(DIST)/$(WIN32_TARGET_DIR)" cp prebuilt-deps/ffmpeg-4.3.1-win32-shared/bin/avutil-56.dll "$(DIST)/$(WIN32_TARGET_DIR)/" cp prebuilt-deps/ffmpeg-4.3.1-win32-shared/bin/avcodec-58.dll "$(DIST)/$(WIN32_TARGET_DIR)/" cp prebuilt-deps/ffmpeg-4.3.1-win32-shared/bin/avformat-58.dll "$(DIST)/$(WIN32_TARGET_DIR)/" @@ -115,6 +116,7 @@ dist-win64: build-server build-win64 build-win64-noconsole cp "$(SERVER_BUILD_DIR)"/server/scrcpy-server "$(DIST)/$(WIN64_TARGET_DIR)/" cp "$(WIN64_BUILD_DIR)"/app/scrcpy.exe "$(DIST)/$(WIN64_TARGET_DIR)/" cp "$(WIN64_NOCONSOLE_BUILD_DIR)"/app/scrcpy.exe "$(DIST)/$(WIN64_TARGET_DIR)/scrcpy-noconsole.exe" + cp data/scrcpy-console.bat "$(DIST)/$(WIN64_TARGET_DIR)" cp prebuilt-deps/ffmpeg-4.3.1-win64-shared/bin/avutil-56.dll "$(DIST)/$(WIN64_TARGET_DIR)/" cp prebuilt-deps/ffmpeg-4.3.1-win64-shared/bin/avcodec-58.dll "$(DIST)/$(WIN64_TARGET_DIR)/" cp prebuilt-deps/ffmpeg-4.3.1-win64-shared/bin/avformat-58.dll "$(DIST)/$(WIN64_TARGET_DIR)/" diff --git a/app/src/main.c b/app/src/main.c index d32d9896..71125673 100644 --- a/app/src/main.c +++ b/app/src/main.c @@ -98,11 +98,5 @@ main(int argc, char *argv[]) { avformat_network_deinit(); // ignore failure -#if defined (__WINDOWS__) && ! defined (WINDOWS_NOCONSOLE) - if (res != 0) { - fprintf(stderr, "Press Enter to continue...\n"); - getchar(); - } -#endif return res; } diff --git a/data/scrcpy-console.bat b/data/scrcpy-console.bat new file mode 100644 index 00000000..b90be29a --- /dev/null +++ b/data/scrcpy-console.bat @@ -0,0 +1,4 @@ +@echo off +scrcpy.exe %* +:: if the exit code is >= 1, then pause +if errorlevel 1 pause From ea12783bbc935e4bc74329a924cda784b5cfc25a Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Tue, 15 Dec 2020 21:50:58 +0100 Subject: [PATCH 240/450] Upgrade JUnit to 4.13 --- server/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/build.gradle b/server/build.gradle index 42252472..94a492c6 100644 --- a/server/build.gradle +++ b/server/build.gradle @@ -20,7 +20,7 @@ android { dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) - testImplementation 'junit:junit:4.12' + testImplementation 'junit:junit:4.13' } apply from: "$project.rootDir/config/android-checkstyle.gradle" From a5f4f582956264aa05563df5de847d4a36f8d2aa Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Tue, 22 Dec 2020 01:14:30 +0100 Subject: [PATCH 241/450] Remove duplicate include --- app/src/sys/unix/command.c | 1 - 1 file changed, 1 deletion(-) diff --git a/app/src/sys/unix/command.c b/app/src/sys/unix/command.c index 4c3ff7e2..c4c262e4 100644 --- a/app/src/sys/unix/command.c +++ b/app/src/sys/unix/command.c @@ -21,7 +21,6 @@ #include #include #include -#include #include #include From 43d3dcbd970640c70272fac9d5e54ff90f8e5eea Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Mon, 14 Dec 2020 10:12:01 +0100 Subject: [PATCH 242/450] Document Windows command line usage PR #1973 Reviewed-by: Yu-Chen Lin --- FAQ.md | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/FAQ.md b/FAQ.md index ba33542e..4fda4dd4 100644 --- a/FAQ.md +++ b/FAQ.md @@ -199,3 +199,36 @@ scrcpy -m 1920 scrcpy -m 1024 scrcpy -m 800 ``` + + +## Command line on Windows + +Some Windows users are not familiar with the command line. Here is how to open a +terminal and run `scrcpy` with arguments: + + 1. Press Windows+r, this opens a dialog box. + 2. Type `cmd` and press Enter, this opens a terminal. + 3. Go to your _scrcpy_ directory, by typing (adapt the path): + + ```bat + cd C:\Users\user\Downloads\scrcpy-win64-xxx + ``` + + and press Enter + 4. Type your command. For example: + + ```bat + scrcpy --record file.mkv + ``` + +If you plan to always use the same arguments, create a file `myscrcpy.bat` +(enable [show file extensions] to avoid confusion) in the `scrcpy` directory, +containing your command. For example: + +```bat +scrcpy --prefer-text --turn-screen-off --stay-awake +``` + +Then just double-click on that file. + +[show file extensions]: https://www.howtogeek.com/205086/beginner-how-to-make-windows-show-file-extensions/ From 431c9ee33b49f5ce7317b4c687e00bc436adbc02 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 20 Dec 2020 08:24:04 +0100 Subject: [PATCH 243/450] Improve rotation documentation --- README.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 67f2c14c..816afc79 100644 --- a/README.md +++ b/README.md @@ -202,6 +202,8 @@ scrcpy --lock-video-orientation 3 # 90° clockwise This affects recording orientation. +The [window may also be rotated](#rotation) independently. + ### Recording @@ -391,9 +393,9 @@ Note that _scrcpy_ manages 3 different rotations: - MOD+r requests the device to switch between portrait and landscape (the current running app may refuse, if it does 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. + - [`--lock-video-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` (or MOD+/MOD+) rotates only the window content. This affects only the display, not the recording. From a46733906acf3b59d9d1e26abef99ff4b003f5af Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Tue, 15 Dec 2020 21:50:46 +0100 Subject: [PATCH 244/450] Replace noconsole binary by a wrapper script This simplifies the build system. Refs --- Makefile.CrossWindows | 37 ++++++------------------------------- app/meson.build | 12 +----------- app/src/sys/win/command.c | 7 +------ data/scrcpy-noconsole.vbs | 1 + meson_options.txt | 1 - 5 files changed, 9 insertions(+), 49 deletions(-) create mode 100644 data/scrcpy-noconsole.vbs diff --git a/Makefile.CrossWindows b/Makefile.CrossWindows index f8a360d9..8c093a2f 100644 --- a/Makefile.CrossWindows +++ b/Makefile.CrossWindows @@ -11,8 +11,7 @@ .PHONY: default clean \ build-server \ prepare-deps-win32 prepare-deps-win64 \ - build-win32 build-win32-noconsole \ - build-win64 build-win64-noconsole \ + build-win32 build-win64 \ dist-win32 dist-win64 \ zip-win32 zip-win64 \ sums release @@ -21,9 +20,7 @@ GRADLE ?= ./gradlew SERVER_BUILD_DIR := build-server WIN32_BUILD_DIR := build-win32 -WIN32_NOCONSOLE_BUILD_DIR := build-win32-noconsole WIN64_BUILD_DIR := build-win64 -WIN64_NOCONSOLE_BUILD_DIR := build-win64-noconsole DIST := dist WIN32_TARGET_DIR := scrcpy-win32 @@ -39,7 +36,7 @@ release: clean zip-win32 zip-win64 sums clean: $(GRADLE) clean rm -rf "$(SERVER_BUILD_DIR)" "$(WIN32_BUILD_DIR)" "$(WIN64_BUILD_DIR)" \ - "$(WIN32_NOCONSOLE_BUILD_DIR)" "$(WIN64_NOCONSOLE_BUILD_DIR)" "$(DIST)" + "$(DIST)" build-server: [ -d "$(SERVER_BUILD_DIR)" ] || ( mkdir "$(SERVER_BUILD_DIR)" && \ @@ -60,17 +57,6 @@ build-win32: prepare-deps-win32 -Dportable=true ) ninja -C "$(WIN32_BUILD_DIR)" -build-win32-noconsole: prepare-deps-win32 - [ -d "$(WIN32_NOCONSOLE_BUILD_DIR)" ] || ( mkdir "$(WIN32_NOCONSOLE_BUILD_DIR)" && \ - meson "$(WIN32_NOCONSOLE_BUILD_DIR)" \ - --cross-file cross_win32.txt \ - --buildtype release --strip -Db_lto=true \ - -Dcrossbuild_windows=true \ - -Dcompile_server=false \ - -Dwindows_noconsole=true \ - -Dportable=true ) - ninja -C "$(WIN32_NOCONSOLE_BUILD_DIR)" - prepare-deps-win64: -$(MAKE) -C prebuilt-deps prepare-win64 @@ -84,23 +70,12 @@ build-win64: prepare-deps-win64 -Dportable=true ) ninja -C "$(WIN64_BUILD_DIR)" -build-win64-noconsole: prepare-deps-win64 - [ -d "$(WIN64_NOCONSOLE_BUILD_DIR)" ] || ( mkdir "$(WIN64_NOCONSOLE_BUILD_DIR)" && \ - meson "$(WIN64_NOCONSOLE_BUILD_DIR)" \ - --cross-file cross_win64.txt \ - --buildtype release --strip -Db_lto=true \ - -Dcrossbuild_windows=true \ - -Dcompile_server=false \ - -Dwindows_noconsole=true \ - -Dportable=true ) - ninja -C "$(WIN64_NOCONSOLE_BUILD_DIR)" - -dist-win32: build-server build-win32 build-win32-noconsole +dist-win32: build-server build-win32 mkdir -p "$(DIST)/$(WIN32_TARGET_DIR)" cp "$(SERVER_BUILD_DIR)"/server/scrcpy-server "$(DIST)/$(WIN32_TARGET_DIR)/" cp "$(WIN32_BUILD_DIR)"/app/scrcpy.exe "$(DIST)/$(WIN32_TARGET_DIR)/" - cp "$(WIN32_NOCONSOLE_BUILD_DIR)"/app/scrcpy.exe "$(DIST)/$(WIN32_TARGET_DIR)/scrcpy-noconsole.exe" cp data/scrcpy-console.bat "$(DIST)/$(WIN32_TARGET_DIR)" + cp data/scrcpy-noconsole.vbs "$(DIST)/$(WIN32_TARGET_DIR)" cp prebuilt-deps/ffmpeg-4.3.1-win32-shared/bin/avutil-56.dll "$(DIST)/$(WIN32_TARGET_DIR)/" cp prebuilt-deps/ffmpeg-4.3.1-win32-shared/bin/avcodec-58.dll "$(DIST)/$(WIN32_TARGET_DIR)/" cp prebuilt-deps/ffmpeg-4.3.1-win32-shared/bin/avformat-58.dll "$(DIST)/$(WIN32_TARGET_DIR)/" @@ -111,12 +86,12 @@ dist-win32: build-server build-win32 build-win32-noconsole cp prebuilt-deps/platform-tools/AdbWinUsbApi.dll "$(DIST)/$(WIN32_TARGET_DIR)/" cp prebuilt-deps/SDL2-2.0.12/i686-w64-mingw32/bin/SDL2.dll "$(DIST)/$(WIN32_TARGET_DIR)/" -dist-win64: build-server build-win64 build-win64-noconsole +dist-win64: build-server build-win64 mkdir -p "$(DIST)/$(WIN64_TARGET_DIR)" cp "$(SERVER_BUILD_DIR)"/server/scrcpy-server "$(DIST)/$(WIN64_TARGET_DIR)/" cp "$(WIN64_BUILD_DIR)"/app/scrcpy.exe "$(DIST)/$(WIN64_TARGET_DIR)/" - cp "$(WIN64_NOCONSOLE_BUILD_DIR)"/app/scrcpy.exe "$(DIST)/$(WIN64_TARGET_DIR)/scrcpy-noconsole.exe" cp data/scrcpy-console.bat "$(DIST)/$(WIN64_TARGET_DIR)" + cp data/scrcpy-noconsole.vbs "$(DIST)/$(WIN64_TARGET_DIR)" cp prebuilt-deps/ffmpeg-4.3.1-win64-shared/bin/avutil-56.dll "$(DIST)/$(WIN64_TARGET_DIR)/" cp prebuilt-deps/ffmpeg-4.3.1-win64-shared/bin/avcodec-58.dll "$(DIST)/$(WIN64_TARGET_DIR)/" cp prebuilt-deps/ffmpeg-4.3.1-win64-shared/bin/avformat-58.dll "$(DIST)/$(WIN64_TARGET_DIR)/" diff --git a/app/meson.build b/app/meson.build index 0163dd7f..28b9d141 100644 --- a/app/meson.build +++ b/app/meson.build @@ -119,9 +119,6 @@ conf.set('DEFAULT_BIT_RATE', '8000000') # 8Mbps # enable High DPI support conf.set('HIDPI_SUPPORT', get_option('hidpi_support')) -# disable console on Windows -conf.set('WINDOWS_NOCONSOLE', get_option('windows_noconsole')) - # run a server debugger and wait for a client to be attached conf.set('SERVER_DEBUGGER', get_option('server_debugger')) @@ -132,18 +129,11 @@ configure_file(configuration: conf, output: 'config.h') src_dir = include_directories('src') -if get_option('windows_noconsole') - link_args = [ '-Wl,--subsystem,windows' ] -else - link_args = [] -endif - executable('scrcpy', src, dependencies: dependencies, include_directories: src_dir, install: true, - c_args: [], - link_args: link_args) + c_args: []) install_man('scrcpy.1') diff --git a/app/src/sys/win/command.c b/app/src/sys/win/command.c index 105234b0..fbc0d04c 100644 --- a/app/src/sys/win/command.c +++ b/app/src/sys/win/command.c @@ -39,12 +39,7 @@ cmd_execute(const char *const argv[], HANDLE *handle) { return PROCESS_ERROR_GENERIC; } -#ifdef WINDOWS_NOCONSOLE - int flags = CREATE_NO_WINDOW; -#else - int flags = 0; -#endif - if (!CreateProcessW(NULL, wide, NULL, NULL, FALSE, flags, NULL, NULL, &si, + if (!CreateProcessW(NULL, wide, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi)) { SDL_free(wide); *handle = NULL; diff --git a/data/scrcpy-noconsole.vbs b/data/scrcpy-noconsole.vbs new file mode 100644 index 00000000..e11adba5 --- /dev/null +++ b/data/scrcpy-noconsole.vbs @@ -0,0 +1 @@ +CreateObject("Wscript.Shell").Run "cmd /c scrcpy.exe", 0, false diff --git a/meson_options.txt b/meson_options.txt index c213e7dd..b962380c 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -1,7 +1,6 @@ option('compile_app', type: 'boolean', value: true, description: 'Build the client') option('compile_server', type: 'boolean', value: true, description: 'Build the server') option('crossbuild_windows', type: 'boolean', value: false, description: 'Build for Windows from Linux') -option('windows_noconsole', type: 'boolean', value: false, description: 'Disable console on Windows (pass -mwindows flag)') option('prebuilt_server', type: 'string', description: 'Path of the prebuilt server') option('portable', type: 'boolean', value: false, description: 'Use scrcpy-server from the same directory as the scrcpy executable') option('hidpi_support', type: 'boolean', value: true, description: 'Enable High DPI support') From 230afd8966d3bb4d792a41e4d87dbdd1cf4e9347 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 17 Dec 2020 14:54:04 +0100 Subject: [PATCH 245/450] Unify release makefile Before this change, release.sh built some native stuff, and Makefile.CrossWindows built the Windows releases. Instead, use a single release.make to build the whole release. It also avoids to build the server one more time. --- Makefile.CrossWindows => release.make | 38 +++++++++++++++-------- release.sh | 44 +-------------------------- 2 files changed, 27 insertions(+), 55 deletions(-) rename Makefile.CrossWindows => release.make (82%) diff --git a/Makefile.CrossWindows b/release.make similarity index 82% rename from Makefile.CrossWindows rename to release.make index 8c093a2f..873762b4 100644 --- a/Makefile.CrossWindows +++ b/release.make @@ -9,15 +9,17 @@ # the server to the device. .PHONY: default clean \ + test \ build-server \ prepare-deps-win32 prepare-deps-win64 \ build-win32 build-win64 \ dist-win32 dist-win64 \ zip-win32 zip-win64 \ - sums release + release GRADLE ?= ./gradlew +TEST_BUILD_DIR := build-test SERVER_BUILD_DIR := build-server WIN32_BUILD_DIR := build-win32 WIN64_BUILD_DIR := build-win64 @@ -30,19 +32,35 @@ VERSION := $(shell git describe --tags --always) WIN32_TARGET := $(WIN32_TARGET_DIR)-$(VERSION).zip WIN64_TARGET := $(WIN64_TARGET_DIR)-$(VERSION).zip -release: clean zip-win32 zip-win64 sums - @echo "Windows archives generated in $(DIST)/" +RELEASE_DIR := release-$(VERSION) + +release: clean test build-server zip-win32 zip-win64 + mkdir -p "$(RELEASE_DIR)" + cp "$(SERVER_BUILD_DIR)/server/scrcpy-server" \ + "$(RELEASE_DIR)/scrcpy-server-$(VERSION)" + cp "$(DIST)/$(WIN32_TARGET)" "$(RELEASE_DIR)" + cp "$(DIST)/$(WIN64_TARGET)" "$(RELEASE_DIR)" + cd "$(RELEASE_DIR)" && \ + sha256sum "scrcpy-server-$(VERSION)" \ + "scrcpy-win32-$(VERSION).zip" \ + "scrcpy-win64-$(VERSION).zip" > SHA256SUMS.txt + @echo "Release generated in $(RELEASE_DIR)/" clean: $(GRADLE) clean - rm -rf "$(SERVER_BUILD_DIR)" "$(WIN32_BUILD_DIR)" "$(WIN64_BUILD_DIR)" \ - "$(DIST)" + rm -rf "$(DIST)" "$(TEST_BUILD_DIR)" "$(SERVER_BUILD_DIR)" \ + "$(WIN32_BUILD_DIR)" "$(WIN64_BUILD_DIR)" + +test: + [ -d "$(TEST_BUILD_DIR)" ] || ( mkdir "$(TEST_BUILD_DIR)" && \ + meson "$(TEST_BUILD_DIR)" -Db_sanitize=address ) + ninja -C "$(TEST_BUILD_DIR)" + $(GRADLE) -p server check build-server: [ -d "$(SERVER_BUILD_DIR)" ] || ( mkdir "$(SERVER_BUILD_DIR)" && \ - meson "$(SERVER_BUILD_DIR)" \ - --buildtype release -Dcompile_app=false ) - ninja -C "$(SERVER_BUILD_DIR)" + meson "$(SERVER_BUILD_DIR)" --buildtype release -Dcompile_app=false ) + ninja -C "$(SERVER_BUILD_DIR)" prepare-deps-win32: -$(MAKE) -C prebuilt-deps prepare-win32 @@ -109,7 +127,3 @@ zip-win32: dist-win32 zip-win64: dist-win64 cd "$(DIST)/$(WIN64_TARGET_DIR)"; \ zip -r "../$(WIN64_TARGET)" . - -sums: - cd "$(DIST)"; \ - sha256sum *.zip > SHA256SUMS.txt diff --git a/release.sh b/release.sh index 4c5afbf1..a824e958 100755 --- a/release.sh +++ b/release.sh @@ -1,44 +1,2 @@ #!/bin/bash -set -e - -# test locally -TESTDIR=build_test -rm -rf "$TESTDIR" -# run client tests with ASAN enabled -meson "$TESTDIR" -Db_sanitize=address -ninja -C"$TESTDIR" test - -# test server -GRADLE=${GRADLE:-./gradlew} -$GRADLE -p server check - -BUILDDIR=build_release -rm -rf "$BUILDDIR" -meson "$BUILDDIR" --buildtype release --strip -Db_lto=true -cd "$BUILDDIR" -ninja -cd - - -# build Windows releases -make -f Makefile.CrossWindows - -# the generated server must be the same everywhere -cmp "$BUILDDIR/server/scrcpy-server" dist/scrcpy-win32/scrcpy-server -cmp "$BUILDDIR/server/scrcpy-server" dist/scrcpy-win64/scrcpy-server - -# get version name -TAG=$(git describe --tags --always) - -# create release directory -mkdir -p "release-$TAG" -cp "$BUILDDIR/server/scrcpy-server" "release-$TAG/scrcpy-server-$TAG" -cp "dist/scrcpy-win32-$TAG.zip" "release-$TAG/" -cp "dist/scrcpy-win64-$TAG.zip" "release-$TAG/" - -# generate checksums -cd "release-$TAG" -sha256sum "scrcpy-server-$TAG" \ - "scrcpy-win32-$TAG.zip" \ - "scrcpy-win64-$TAG.zip" > SHA256SUMS.txt - -echo "Release generated in release-$TAG/" +make -f release.make From 6ab80e4ce8311ae65c09d2add5f620bdbef369a2 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 1 Jan 2021 15:49:28 +0100 Subject: [PATCH 246/450] Rename release.make to release.mk It's more standard, and benefits from syntax coloration in vi. --- release.make => release.mk | 0 release.sh | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename release.make => release.mk (100%) diff --git a/release.make b/release.mk similarity index 100% rename from release.make rename to release.mk diff --git a/release.sh b/release.sh index a824e958..51ce2e38 100755 --- a/release.sh +++ b/release.sh @@ -1,2 +1,2 @@ #!/bin/bash -make -f release.make +make -f release.mk From d039a7a39a80940c3eca226ce3d7d0555157fa8c Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 1 Jan 2021 15:53:56 +0100 Subject: [PATCH 247/450] Upgrade SDL (2.0.14) for Windows Include the latest version of SDL in Windows releases. --- cross_win32.txt | 2 +- cross_win64.txt | 2 +- prebuilt-deps/Makefile | 6 +++--- release.mk | 4 ++-- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/cross_win32.txt b/cross_win32.txt index bef2e5d5..a662ae20 100644 --- a/cross_win32.txt +++ b/cross_win32.txt @@ -17,4 +17,4 @@ endian = 'little' [properties] prebuilt_ffmpeg_shared = 'ffmpeg-4.3.1-win32-shared' prebuilt_ffmpeg_dev = 'ffmpeg-4.3.1-win32-dev' -prebuilt_sdl2 = 'SDL2-2.0.12/i686-w64-mingw32' +prebuilt_sdl2 = 'SDL2-2.0.14/i686-w64-mingw32' diff --git a/cross_win64.txt b/cross_win64.txt index 5a348738..de3836d5 100644 --- a/cross_win64.txt +++ b/cross_win64.txt @@ -17,4 +17,4 @@ endian = 'little' [properties] prebuilt_ffmpeg_shared = 'ffmpeg-4.3.1-win64-shared' prebuilt_ffmpeg_dev = 'ffmpeg-4.3.1-win64-dev' -prebuilt_sdl2 = 'SDL2-2.0.12/x86_64-w64-mingw32' +prebuilt_sdl2 = 'SDL2-2.0.14/x86_64-w64-mingw32' diff --git a/prebuilt-deps/Makefile b/prebuilt-deps/Makefile index 535d5692..356a944e 100644 --- a/prebuilt-deps/Makefile +++ b/prebuilt-deps/Makefile @@ -30,9 +30,9 @@ prepare-ffmpeg-dev-win64: ffmpeg-4.3.1-win64-dev prepare-sdl2: - @./prepare-dep https://libsdl.org/release/SDL2-devel-2.0.12-mingw.tar.gz \ - e614a60f797e35ef9f3f96aef3dc6a1d786de3cc7ca6216f97e435c0b6aafc46 \ - SDL2-2.0.12 + @./prepare-dep https://libsdl.org/release/SDL2-devel-2.0.14-mingw.tar.gz \ + 405eaff3eb18f2e08fe669ef9e63bc9a8710b7d343756f238619761e9b60407d \ + SDL2-2.0.14 prepare-adb: @./prepare-dep https://dl.google.com/android/repository/platform-tools_r30.0.5-windows.zip \ diff --git a/release.mk b/release.mk index 873762b4..2a026135 100644 --- a/release.mk +++ b/release.mk @@ -102,7 +102,7 @@ dist-win32: build-server build-win32 cp prebuilt-deps/platform-tools/adb.exe "$(DIST)/$(WIN32_TARGET_DIR)/" cp prebuilt-deps/platform-tools/AdbWinApi.dll "$(DIST)/$(WIN32_TARGET_DIR)/" cp prebuilt-deps/platform-tools/AdbWinUsbApi.dll "$(DIST)/$(WIN32_TARGET_DIR)/" - cp prebuilt-deps/SDL2-2.0.12/i686-w64-mingw32/bin/SDL2.dll "$(DIST)/$(WIN32_TARGET_DIR)/" + cp prebuilt-deps/SDL2-2.0.14/i686-w64-mingw32/bin/SDL2.dll "$(DIST)/$(WIN32_TARGET_DIR)/" dist-win64: build-server build-win64 mkdir -p "$(DIST)/$(WIN64_TARGET_DIR)" @@ -118,7 +118,7 @@ dist-win64: build-server build-win64 cp prebuilt-deps/platform-tools/adb.exe "$(DIST)/$(WIN64_TARGET_DIR)/" cp prebuilt-deps/platform-tools/AdbWinApi.dll "$(DIST)/$(WIN64_TARGET_DIR)/" cp prebuilt-deps/platform-tools/AdbWinUsbApi.dll "$(DIST)/$(WIN64_TARGET_DIR)/" - cp prebuilt-deps/SDL2-2.0.12/x86_64-w64-mingw32/bin/SDL2.dll "$(DIST)/$(WIN64_TARGET_DIR)/" + cp prebuilt-deps/SDL2-2.0.14/x86_64-w64-mingw32/bin/SDL2.dll "$(DIST)/$(WIN64_TARGET_DIR)/" zip-win32: dist-win32 cd "$(DIST)/$(WIN32_TARGET_DIR)"; \ From 112adbba87f93d85808561301588219b833f9686 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 1 Jan 2021 17:16:44 +0100 Subject: [PATCH 248/450] Happy new year 2021! --- LICENSE | 2 +- README.id.md | 2 +- README.ko.md | 2 +- README.md | 2 +- README.pt-br.md | 2 +- README.zh-Hans.md | 2 +- README.zh-Hant.md | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/LICENSE b/LICENSE index bc4bb77d..b320f699 100644 --- a/LICENSE +++ b/LICENSE @@ -188,7 +188,7 @@ identification within third-party archives. Copyright (C) 2018 Genymobile - Copyright (C) 2018-2020 Romain Vimont + Copyright (C) 2018-2021 Romain Vimont Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/README.id.md b/README.id.md index 5af56663..a2cfa3d5 100644 --- a/README.id.md +++ b/README.id.md @@ -675,7 +675,7 @@ Baca [halaman pengembang]. ## Lisensi Copyright (C) 2018 Genymobile - Copyright (C) 2018-2020 Romain Vimont + Copyright (C) 2018-2021 Romain Vimont Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/README.ko.md b/README.ko.md index 4e6d8fc5..58b44dfe 100644 --- a/README.ko.md +++ b/README.ko.md @@ -477,7 +477,7 @@ _²화면이 꺼진 상태에서 우클릭 시 다시 켜지며, 그 외의 상 ## 라이선스 Copyright (C) 2018 Genymobile - Copyright (C) 2018-2020 Romain Vimont + Copyright (C) 2018-2021 Romain Vimont Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/README.md b/README.md index 816afc79..2e31b6d5 100644 --- a/README.md +++ b/README.md @@ -737,7 +737,7 @@ Read the [developers page]. ## Licence Copyright (C) 2018 Genymobile - Copyright (C) 2018-2020 Romain Vimont + Copyright (C) 2018-2021 Romain Vimont Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/README.pt-br.md b/README.pt-br.md index 654f62cb..4f2122a6 100644 --- a/README.pt-br.md +++ b/README.pt-br.md @@ -508,7 +508,7 @@ Leia a [developers page]. ## Licença Copyright (C) 2018 Genymobile - Copyright (C) 2018-2020 Romain Vimont + Copyright (C) 2018-2021 Romain Vimont Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/README.zh-Hans.md b/README.zh-Hans.md index 85e178d6..3774c5d9 100644 --- a/README.zh-Hans.md +++ b/README.zh-Hans.md @@ -703,7 +703,7 @@ _³需要安卓版本 Android >= 7。_ ## 许可协议 Copyright (C) 2018 Genymobile - Copyright (C) 2018-2020 Romain Vimont + Copyright (C) 2018-2021 Romain Vimont Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/README.zh-Hant.md b/README.zh-Hant.md index 82eb1d9b..b4dc69ec 100644 --- a/README.zh-Hant.md +++ b/README.zh-Hant.md @@ -682,7 +682,7 @@ _³只支援 Android 7+。_ ## Licence Copyright (C) 2018 Genymobile - Copyright (C) 2018-2020 Romain Vimont + Copyright (C) 2018-2021 Romain Vimont Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. From 3ba51211d6bb21fa02c5d05079f15aad089de7ca Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 1 Jan 2021 17:31:07 +0100 Subject: [PATCH 249/450] Mention how to add default arguments on Windows Mention that it is possible to add default arguments by editing the wrapper scripts. --- FAQ.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/FAQ.md b/FAQ.md index 44cc06d8..9801f91c 100644 --- a/FAQ.md +++ b/FAQ.md @@ -233,4 +233,7 @@ scrcpy --prefer-text --turn-screen-off --stay-awake Then just double-click on that file. +You could also edit (a copy of) `scrcpy-console.bat` or `scrcpy-noconsole.vbs` +to add some arguments. + [show file extensions]: https://www.howtogeek.com/205086/beginner-how-to-make-windows-show-file-extensions/ From 90f835663004d9dc4969a76a1ef2641af9b02633 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 1 Jan 2021 12:26:44 +0100 Subject: [PATCH 250/450] Interrupt device threads on stop The (non-daemon) threads were not interrupted on video stream stopped, leaving the server process alive. Interrupt them to wake up their blocking call so that they terminate properly. Refs #1992 --- .../java/com/genymobile/scrcpy/Server.java | 29 ++++++++++++++----- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/Server.java b/server/src/main/java/com/genymobile/scrcpy/Server.java index f501d3d8..f58e3867 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Server.java +++ b/server/src/main/java/com/genymobile/scrcpy/Server.java @@ -58,12 +58,14 @@ public final class Server { ScreenEncoder screenEncoder = new ScreenEncoder(options.getSendFrameMeta(), options.getBitRate(), options.getMaxFps(), codecOptions, options.getEncoderName()); + Thread controllerThread = null; + Thread deviceMessageSenderThread = null; if (options.getControl()) { final Controller controller = new Controller(device, connection); // asynchronous - startController(controller); - startDeviceMessageSender(controller.getSender()); + controllerThread = startController(controller); + deviceMessageSenderThread = startDeviceMessageSender(controller.getSender()); device.setClipboardListener(new Device.ClipboardListener() { @Override @@ -79,12 +81,19 @@ public final class Server { } catch (IOException e) { // this is expected on close Ln.d("Screen streaming stopped"); + } finally { + if (controllerThread != null) { + controllerThread.interrupt(); + } + if (deviceMessageSenderThread != null) { + deviceMessageSenderThread.interrupt(); + } } } } - private static void startController(final Controller controller) { - new Thread(new Runnable() { + private static Thread startController(final Controller controller) { + Thread thread = new Thread(new Runnable() { @Override public void run() { try { @@ -94,11 +103,13 @@ public final class Server { Ln.d("Controller stopped"); } } - }).start(); + }); + thread.start(); + return thread; } - private static void startDeviceMessageSender(final DeviceMessageSender sender) { - new Thread(new Runnable() { + private static Thread startDeviceMessageSender(final DeviceMessageSender sender) { + Thread thread = new Thread(new Runnable() { @Override public void run() { try { @@ -108,7 +119,9 @@ public final class Server { Ln.d("Device message sender stopped"); } } - }).start(); + }); + thread.start(); + return thread; } private static Options createOptions(String... args) { From 83910d3b9c961aa30eec4cf01aee7d38fb050fae Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 1 Jan 2021 16:34:47 +0100 Subject: [PATCH 251/450] Initialize server struct dynamically This will allow to add mutex/cond fields. --- app/src/scrcpy.c | 35 +++++++++++++++++++++-------------- app/src/server.c | 21 +++++++++++++++++++-- app/src/server.h | 19 +------------------ 3 files changed, 41 insertions(+), 34 deletions(-) diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index a543e11c..4c502ff6 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -34,7 +34,7 @@ #include "util/log.h" #include "util/net.h" -static struct server server = SERVER_INITIALIZER; +static struct server server; static struct screen screen = SCREEN_INITIALIZER; static struct fps_counter fps_counter; static struct video_buffer video_buffer; @@ -304,6 +304,19 @@ av_log_callback(void *avcl, int level, const char *fmt, va_list vl) { bool scrcpy(const struct scrcpy_options *options) { + if (!server_init(&server)) { + return false; + } + + bool server_started = false; + bool fps_counter_initialized = false; + bool video_buffer_initialized = false; + bool file_handler_initialized = false; + bool recorder_initialized = false; + bool stream_started = false; + bool controller_initialized = false; + bool controller_started = false; + bool record = !!options->record_filename; struct server_params params = { .log_level = options->log_level, @@ -322,18 +335,10 @@ scrcpy(const struct scrcpy_options *options) { .force_adb_forward = options->force_adb_forward, }; if (!server_start(&server, options->serial, ¶ms)) { - return false; + goto end; } - bool ret = false; - - bool fps_counter_initialized = false; - bool video_buffer_initialized = false; - bool file_handler_initialized = false; - bool recorder_initialized = false; - bool stream_started = false; - bool controller_initialized = false; - bool controller_started = false; + server_started = true; if (!sdl_init_and_configure(options->display, options->render_driver, options->disable_screensaver)) { @@ -444,7 +449,7 @@ scrcpy(const struct scrcpy_options *options) { input_manager_init(&input_manager, options); - ret = event_loop(options); + bool ret = event_loop(options); LOGD("quit..."); screen_destroy(&screen); @@ -465,8 +470,10 @@ end: fps_counter_interrupt(&fps_counter); } - // shutdown the sockets and kill the server - server_stop(&server); + if (server_started) { + // shutdown the sockets and kill the server + server_stop(&server); + } // now that the sockets are shutdown, the stream and controller are // interrupted, we can join them diff --git a/app/src/server.c b/app/src/server.c index 9267356b..42e633af 100644 --- a/app/src/server.c +++ b/app/src/server.c @@ -353,9 +353,26 @@ close_socket(socket_t socket) { } } -void +bool server_init(struct server *server) { - *server = (struct server) SERVER_INITIALIZER; + server->serial = NULL; + server->process = PROCESS_NONE; + server->wait_server_thread = NULL; + atomic_flag_clear_explicit(&server->server_socket_closed, + memory_order_relaxed); + + server->server_socket = INVALID_SOCKET; + server->video_socket = INVALID_SOCKET; + server->control_socket = INVALID_SOCKET; + + server->port_range.first = 0; + server->port_range.last = 0; + server->local_port = 0; + + server->tunnel_enabled = false; + server->tunnel_forward = false; + + return true; } static int diff --git a/app/src/server.h b/app/src/server.h index 30ce09bc..823db0ea 100644 --- a/app/src/server.h +++ b/app/src/server.h @@ -27,23 +27,6 @@ struct server { bool tunnel_forward; // use "adb forward" instead of "adb reverse" }; -#define SERVER_INITIALIZER { \ - .serial = NULL, \ - .process = PROCESS_NONE, \ - .wait_server_thread = NULL, \ - .server_socket_closed = ATOMIC_FLAG_INIT, \ - .server_socket = INVALID_SOCKET, \ - .video_socket = INVALID_SOCKET, \ - .control_socket = INVALID_SOCKET, \ - .port_range = { \ - .first = 0, \ - .last = 0, \ - }, \ - .local_port = 0, \ - .tunnel_enabled = false, \ - .tunnel_forward = false, \ -} - struct server_params { enum sc_log_level log_level; const char *crop; @@ -62,7 +45,7 @@ struct server_params { }; // init default values -void +bool server_init(struct server *server); // push, enable tunnel et start the server From 05e8c1a3c5e955d2941d882e5fe784e6695e3ec4 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 1 Jan 2021 23:39:29 +0100 Subject: [PATCH 252/450] Call CloseHandle() after wait on Windows TerminateProcess() is "equivalent" to kill(), while WaitForSingleObject() is "equivalent" to waitpid(), so the handle must be closed after WaitForSingleObject(). --- app/src/sys/win/command.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/src/sys/win/command.c b/app/src/sys/win/command.c index fbc0d04c..7b483300 100644 --- a/app/src/sys/win/command.c +++ b/app/src/sys/win/command.c @@ -56,7 +56,7 @@ cmd_execute(const char *const argv[], HANDLE *handle) { bool cmd_terminate(HANDLE handle) { - return TerminateProcess(handle, 1) && CloseHandle(handle); + return TerminateProcess(handle, 1); } bool @@ -70,6 +70,7 @@ cmd_simple_wait(HANDLE handle, DWORD *exit_code) { if (exit_code) { *exit_code = code; } + CloseHandle(handle); return !code; } From 10b749e27d34f91d04ff165134fda23e6ec2cc3b Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 1 Jan 2021 12:41:25 +0100 Subject: [PATCH 253/450] Kill the server only after a small delay Let the server terminate properly once all the sockets are closed. If it does not terminate (this can happen if the device is asleep), then kill it. Note: since the server process termination is detected by a flag set after waitpid() returns, there is a small chance that the process terminates (and the PID assigned to a new process) before the flag is set but before the kill() call. This race condition already existed before this commit. Fixes #1992 --- app/src/server.c | 46 ++++++++++++++++++++++++++++++++++++++++++++-- app/src/server.h | 5 +++++ 2 files changed, 49 insertions(+), 2 deletions(-) diff --git a/app/src/server.c b/app/src/server.c index 42e633af..cac7b367 100644 --- a/app/src/server.c +++ b/app/src/server.c @@ -11,6 +11,7 @@ #include "config.h" #include "command.h" +#include "util/lock.h" #include "util/log.h" #include "util/net.h" #include "util/str_util.h" @@ -361,6 +362,19 @@ server_init(struct server *server) { atomic_flag_clear_explicit(&server->server_socket_closed, memory_order_relaxed); + server->mutex = SDL_CreateMutex(); + if (!server->mutex) { + return false; + } + + server->process_terminated_cond = SDL_CreateCond(); + if (!server->process_terminated_cond) { + SDL_DestroyMutex(server->mutex); + return false; + } + + server->process_terminated = false; + server->server_socket = INVALID_SOCKET; server->video_socket = INVALID_SOCKET; server->control_socket = INVALID_SOCKET; @@ -379,6 +393,12 @@ static int run_wait_server(void *data) { struct server *server = data; cmd_simple_wait(server->process, NULL); // ignore exit code + + mutex_lock(server->mutex); + server->process_terminated = true; + cond_signal(server->process_terminated_cond); + mutex_unlock(server->mutex); + // no need for synchronization, server_socket is initialized before this // thread was created if (server->server_socket != INVALID_SOCKET @@ -510,17 +530,39 @@ server_stop(struct server *server) { assert(server->process != PROCESS_NONE); - cmd_terminate(server->process); - if (server->tunnel_enabled) { // ignore failure disable_tunnel(server); } + // Give some delay for the server to terminate properly + mutex_lock(server->mutex); + int r = 0; + if (!server->process_terminated) { +#define WATCHDOG_DELAY_MS 1000 + r = cond_wait_timeout(server->process_terminated_cond, + server->mutex, + WATCHDOG_DELAY_MS); + } + mutex_unlock(server->mutex); + + // After this delay, kill the server if it's not dead already. + // On some devices, closing the sockets is not sufficient to wake up the + // blocking calls while the device is asleep. + if (r == SDL_MUTEX_TIMEDOUT) { + // FIXME There is a race condition here: there is a small chance that + // the process is already terminated, and the PID assigned to a new + // process. + LOGW("Killing the server..."); + cmd_terminate(server->process); + } + SDL_WaitThread(server->wait_server_thread, NULL); } void server_destroy(struct server *server) { SDL_free(server->serial); + SDL_DestroyCond(server->process_terminated_cond); + SDL_DestroyMutex(server->mutex); } diff --git a/app/src/server.h b/app/src/server.h index 823db0ea..9b558aee 100644 --- a/app/src/server.h +++ b/app/src/server.h @@ -18,6 +18,11 @@ struct server { process_t process; SDL_Thread *wait_server_thread; atomic_flag server_socket_closed; + + SDL_mutex *mutex; + SDL_cond *process_terminated_cond; + bool process_terminated; + socket_t server_socket; // only used if !tunnel_forward socket_t video_socket; socket_t control_socket; From f682b87ba5ed6a5126cc23dff905218b177a2436 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sat, 2 Jan 2021 00:53:32 +0100 Subject: [PATCH 254/450] Bump version to 1.17 --- meson.build | 2 +- server/build.gradle | 4 ++-- server/build_without_gradle.sh | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/meson.build b/meson.build index 11964cf6..230f8d21 100644 --- a/meson.build +++ b/meson.build @@ -1,5 +1,5 @@ project('scrcpy', 'c', - version: '1.16', + version: '1.17', meson_version: '>= 0.48', default_options: [ 'c_std=c11', diff --git a/server/build.gradle b/server/build.gradle index 94a492c6..08ce2d27 100644 --- a/server/build.gradle +++ b/server/build.gradle @@ -6,8 +6,8 @@ android { applicationId "com.genymobile.scrcpy" minSdkVersion 21 targetSdkVersion 30 - versionCode 19 - versionName "1.16" + versionCode 20 + versionName "1.17" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" } buildTypes { diff --git a/server/build_without_gradle.sh b/server/build_without_gradle.sh index e03939a5..9afc5ba7 100755 --- a/server/build_without_gradle.sh +++ b/server/build_without_gradle.sh @@ -12,7 +12,7 @@ set -e SCRCPY_DEBUG=false -SCRCPY_VERSION_NAME=1.16 +SCRCPY_VERSION_NAME=1.17 PLATFORM=${ANDROID_PLATFORM:-30} BUILD_TOOLS=${ANDROID_BUILD_TOOLS:-30.0.0} From c5c5fc18aec537a5c4134a5bcd45e9f8bbadb66d Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sat, 2 Jan 2021 01:26:23 +0100 Subject: [PATCH 255/450] Update links to v1.17 in README and BUILD --- BUILD.md | 6 +++--- README.md | 8 ++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/BUILD.md b/BUILD.md index a7c395ee..5dcf7b27 100644 --- a/BUILD.md +++ b/BUILD.md @@ -254,10 +254,10 @@ You can then [run](README.md#run) _scrcpy_. ## Prebuilt server - - [`scrcpy-server-v1.16`][direct-scrcpy-server] - _(SHA-256: 94a79e05b4498d0460ab7bd9d12cbf05156e3a47bf0c5d1420cee1d4493b3832)_ + - [`scrcpy-server-v1.17`][direct-scrcpy-server] + _(SHA-256: 11b5ad2d1bc9b9730fb7254a78efd71a8ff46b1938ff468e47a21b653a1b6725_ -[direct-scrcpy-server]: https://github.com/Genymobile/scrcpy/releases/download/v1.16/scrcpy-server-v1.16 +[direct-scrcpy-server]: https://github.com/Genymobile/scrcpy/releases/download/v1.17/scrcpy-server-v1.17 Download the prebuilt server somewhere, and specify its path during the Meson configuration: diff --git a/README.md b/README.md index 17b8e134..d8214e02 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# scrcpy (v1.16) +# scrcpy (v1.17) [Read in another language](#translations) @@ -77,10 +77,10 @@ hard). For Windows, for simplicity, a prebuilt archive with all the dependencies (including `adb`) is available: - - [`scrcpy-win64-v1.16.zip`][direct-win64] - _(SHA-256: 3f30dc5db1a2f95c2b40a0f5de91ec1642d9f53799250a8c529bc882bc0918f0)_ + - [`scrcpy-win64-v1.17.zip`][direct-win64] + _(SHA-256: 8b9e57993c707367ed10ebfe0e1ef563c7a29d9af4a355cd8b6a52a317c73eea)_ -[direct-win64]: https://github.com/Genymobile/scrcpy/releases/download/v1.16/scrcpy-win64-v1.16.zip +[direct-win64]: https://github.com/Genymobile/scrcpy/releases/download/v1.17/scrcpy-win64-v1.17.zip It is also available in [Chocolatey]: From 192fbd8450058deb5e70aa1b9bfec3637b5c5e72 Mon Sep 17 00:00:00 2001 From: clesiemo3 Date: Sat, 2 Jan 2021 11:18:23 -0600 Subject: [PATCH 256/450] Use --cask for latest versions of brew PR #2004 Signed-off-by: Romain Vimont --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index d8214e02..6a987a73 100644 --- a/README.md +++ b/README.md @@ -116,6 +116,10 @@ brew install scrcpy You need `adb`, accessible from your `PATH`. If you don't have it yet: ```bash +# Homebrew >= 2.6.0 +brew install --cask android-platform-tools + +# Homebrew < 2.6.0 brew cask install android-platform-tools ``` From ed130e05d55615d6014d93f15cfcb92ad62b01d8 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 3 Jan 2021 22:40:33 +0100 Subject: [PATCH 257/450] Fix possibly uninitialized value Due to gotos, "ret" may be returned uninitialized. --- app/src/scrcpy.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index 4c502ff6..3d043b95 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -308,6 +308,8 @@ scrcpy(const struct scrcpy_options *options) { return false; } + bool ret = false; + bool server_started = false; bool fps_counter_initialized = false; bool video_buffer_initialized = false; @@ -449,7 +451,7 @@ scrcpy(const struct scrcpy_options *options) { input_manager_init(&input_manager, options); - bool ret = event_loop(options); + ret = event_loop(options); LOGD("quit..."); screen_destroy(&screen); From aa8b5713896935e2dfe22ab01e21b84d660c22a1 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Mon, 4 Jan 2021 08:16:32 +0100 Subject: [PATCH 258/450] Increase display id range Some devices use big display id values. Refs #2009 --- app/src/cli.c | 6 +++--- app/src/scrcpy.h | 2 +- app/src/server.c | 4 ++-- app/src/server.h | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/app/src/cli.c b/app/src/cli.c index f01b7941..694282a2 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -478,14 +478,14 @@ parse_port_range(const char *s, struct sc_port_range *port_range) { } static bool -parse_display_id(const char *s, uint16_t *display_id) { +parse_display_id(const char *s, uint32_t *display_id) { long value; - bool ok = parse_integer_arg(s, &value, false, 0, 0xFFFF, "display id"); + bool ok = parse_integer_arg(s, &value, false, 0, 0x7FFFFFFF, "display id"); if (!ok) { return false; } - *display_id = (uint16_t) value; + *display_id = (uint32_t) value; return true; } diff --git a/app/src/scrcpy.h b/app/src/scrcpy.h index 8548d1f7..c1d7b612 100644 --- a/app/src/scrcpy.h +++ b/app/src/scrcpy.h @@ -65,7 +65,7 @@ struct scrcpy_options { int16_t window_y; // SC_WINDOW_POSITION_UNDEFINED for "auto" uint16_t window_width; uint16_t window_height; - uint16_t display_id; + uint32_t display_id; bool show_touches; bool fullscreen; bool always_on_top; diff --git a/app/src/server.c b/app/src/server.c index cac7b367..217e7e41 100644 --- a/app/src/server.c +++ b/app/src/server.c @@ -258,12 +258,12 @@ execute_server(struct server *server, const struct server_params *params) { char bit_rate_string[11]; char max_fps_string[6]; char lock_video_orientation_string[5]; - char display_id_string[6]; + char display_id_string[11]; sprintf(max_size_string, "%"PRIu16, params->max_size); sprintf(bit_rate_string, "%"PRIu32, params->bit_rate); sprintf(max_fps_string, "%"PRIu16, params->max_fps); sprintf(lock_video_orientation_string, "%"PRIi8, params->lock_video_orientation); - sprintf(display_id_string, "%"PRIu16, params->display_id); + sprintf(display_id_string, "%"PRIu32, params->display_id); const char *const cmd[] = { "shell", "CLASSPATH=" DEVICE_SERVER_PATH, diff --git a/app/src/server.h b/app/src/server.h index 9b558aee..c97a9153 100644 --- a/app/src/server.h +++ b/app/src/server.h @@ -43,7 +43,7 @@ struct server_params { uint16_t max_fps; int8_t lock_video_orientation; bool control; - uint16_t display_id; + uint32_t display_id; bool show_touches; bool stay_awake; bool force_adb_forward; From 4bd9da4c93d3727c6cd7597452cb255086b47818 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 3 Jan 2021 14:55:15 +0100 Subject: [PATCH 259/450] Split command into process and adb The process API provides the system-specific implementation, the adb API uses it to expose adb commands. --- app/meson.build | 7 +-- app/src/{command.c => adb.c} | 24 ++--------- app/src/adb.h | 34 +++++++++++++++ app/src/file_handler.c | 6 +-- app/src/file_handler.h | 2 +- app/src/scrcpy.c | 1 - app/src/server.c | 10 ++--- app/src/server.h | 2 +- app/src/sys/unix/{command.c => process.c} | 10 ++--- app/src/sys/win/{command.c => process.c} | 8 ++-- app/src/util/process.c | 21 +++++++++ app/src/{command.h => util/process.h} | 52 +++++++---------------- 12 files changed, 97 insertions(+), 80 deletions(-) rename app/src/{command.c => adb.c} (90%) create mode 100644 app/src/adb.h rename app/src/sys/unix/{command.c => process.c} (95%) rename app/src/sys/win/{command.c => process.c} (93%) create mode 100644 app/src/util/process.c rename app/src/{command.h => util/process.h} (59%) diff --git a/app/meson.build b/app/meson.build index 28b9d141..d031e5cc 100644 --- a/app/meson.build +++ b/app/meson.build @@ -1,7 +1,7 @@ src = [ 'src/main.c', + 'src/adb.c', 'src/cli.c', - 'src/command.c', 'src/control_msg.c', 'src/controller.c', 'src/decoder.c', @@ -21,6 +21,7 @@ src = [ 'src/tiny_xpm.c', 'src/video_buffer.c', 'src/util/net.c', + 'src/util/process.c', 'src/util/str_util.c' ] @@ -76,10 +77,10 @@ endif cc = meson.get_compiler('c') if host_machine.system() == 'windows' - src += [ 'src/sys/win/command.c' ] + src += [ 'src/sys/win/process.c' ] dependencies += cc.find_library('ws2_32') else - src += [ 'src/sys/unix/command.c' ] + src += [ 'src/sys/unix/process.c' ] endif conf = configuration_data() diff --git a/app/src/command.c b/app/src/adb.c similarity index 90% rename from app/src/command.c rename to app/src/adb.c index 81047b7a..339c854d 100644 --- a/app/src/command.c +++ b/app/src/adb.c @@ -1,4 +1,4 @@ -#include "command.h" +#include "adb.h" #include #include @@ -70,7 +70,7 @@ show_adb_installation_msg() { {"pacman", "pacman -S android-tools"}, }; for (size_t i = 0; i < ARRAY_LEN(pkg_managers); ++i) { - if (cmd_search(pkg_managers[i].binary)) { + if (search_executable(pkg_managers[i].binary)) { LOGI("You may install 'adb' by \"%s\"", pkg_managers[i].command); return; } @@ -118,7 +118,7 @@ adb_execute(const char *serial, const char *const adb_cmd[], size_t len) { memcpy(&cmd[i], adb_cmd, len * sizeof(const char *)); cmd[len + i] = NULL; - enum process_result r = cmd_execute(cmd, &process); + enum process_result r = process_execute(cmd, &process); if (r != PROCESS_SUCCESS) { show_adb_err_msg(r, cmd); return PROCESS_NONE; @@ -211,21 +211,3 @@ adb_install(const char *serial, const char *local) { return proc; } - -bool -process_check_success(process_t proc, const char *name) { - if (proc == PROCESS_NONE) { - LOGE("Could not execute \"%s\"", name); - return false; - } - exit_code_t exit_code; - if (!cmd_simple_wait(proc, &exit_code)) { - if (exit_code != NO_EXIT_CODE) { - LOGE("\"%s\" returned with value %" PRIexitcode, name, exit_code); - } else { - LOGE("\"%s\" exited unexpectedly", name); - } - return false; - } - return true; -} diff --git a/app/src/adb.h b/app/src/adb.h new file mode 100644 index 00000000..52cdf823 --- /dev/null +++ b/app/src/adb.h @@ -0,0 +1,34 @@ +#ifndef SC_ADB_H +#define SC_ADB_H + +#include +#include + +#include "config.h" + +#include "util/process.h" + +process_t +adb_execute(const char *serial, const char *const adb_cmd[], size_t len); + +process_t +adb_forward(const char *serial, uint16_t local_port, + const char *device_socket_name); + +process_t +adb_forward_remove(const char *serial, uint16_t local_port); + +process_t +adb_reverse(const char *serial, const char *device_socket_name, + uint16_t local_port); + +process_t +adb_reverse_remove(const char *serial, const char *device_socket_name); + +process_t +adb_push(const char *serial, const char *local, const char *remote); + +process_t +adb_install(const char *serial, const char *local); + +#endif diff --git a/app/src/file_handler.c b/app/src/file_handler.c index ba689404..502b3e96 100644 --- a/app/src/file_handler.c +++ b/app/src/file_handler.c @@ -4,7 +4,7 @@ #include #include "config.h" -#include "command.h" +#include "adb.h" #include "util/lock.h" #include "util/log.h" @@ -176,10 +176,10 @@ file_handler_stop(struct file_handler *file_handler) { file_handler->stopped = true; cond_signal(file_handler->event_cond); if (file_handler->current_process != PROCESS_NONE) { - if (!cmd_terminate(file_handler->current_process)) { + if (!process_terminate(file_handler->current_process)) { LOGW("Could not terminate install process"); } - cmd_simple_wait(file_handler->current_process, NULL); + process_simple_wait(file_handler->current_process, NULL); file_handler->current_process = PROCESS_NONE; } mutex_unlock(file_handler->mutex); diff --git a/app/src/file_handler.h b/app/src/file_handler.h index 078d0ca5..71b58952 100644 --- a/app/src/file_handler.h +++ b/app/src/file_handler.h @@ -6,7 +6,7 @@ #include #include "config.h" -#include "command.h" +#include "adb.h" #include "util/cbuf.h" typedef enum { diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index 3d043b95..2d593234 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -14,7 +14,6 @@ #endif #include "config.h" -#include "command.h" #include "common.h" #include "compat.h" #include "controller.h" diff --git a/app/src/server.c b/app/src/server.c index 217e7e41..2766b0e1 100644 --- a/app/src/server.c +++ b/app/src/server.c @@ -10,7 +10,7 @@ #include #include "config.h" -#include "command.h" +#include "adb.h" #include "util/lock.h" #include "util/log.h" #include "util/net.h" @@ -392,7 +392,7 @@ server_init(struct server *server) { static int run_wait_server(void *data) { struct server *server = data; - cmd_simple_wait(server->process, NULL); // ignore exit code + process_simple_wait(server->process, NULL); // ignore exit code mutex_lock(server->mutex); server->process_terminated = true; @@ -447,8 +447,8 @@ server_start(struct server *server, const char *serial, server->wait_server_thread = SDL_CreateThread(run_wait_server, "wait-server", server); if (!server->wait_server_thread) { - cmd_terminate(server->process); - cmd_simple_wait(server->process, NULL); // ignore exit code + process_terminate(server->process); + process_simple_wait(server->process, NULL); // ignore exit code goto error2; } @@ -554,7 +554,7 @@ server_stop(struct server *server) { // the process is already terminated, and the PID assigned to a new // process. LOGW("Killing the server..."); - cmd_terminate(server->process); + process_terminate(server->process); } SDL_WaitThread(server->wait_server_thread, NULL); diff --git a/app/src/server.h b/app/src/server.h index c97a9153..48d8cd32 100644 --- a/app/src/server.h +++ b/app/src/server.h @@ -7,7 +7,7 @@ #include #include "config.h" -#include "command.h" +#include "adb.h" #include "common.h" #include "scrcpy.h" #include "util/log.h" diff --git a/app/src/sys/unix/command.c b/app/src/sys/unix/process.c similarity index 95% rename from app/src/sys/unix/command.c rename to app/src/sys/unix/process.c index c4c262e4..d029319d 100644 --- a/app/src/sys/unix/command.c +++ b/app/src/sys/unix/process.c @@ -9,7 +9,7 @@ # define _DARWIN_C_SOURCE // for strdup(), strtok_r(), memset_pattern4() #endif -#include "command.h" +#include "util/process.h" #include "config.h" @@ -27,7 +27,7 @@ #include "util/log.h" bool -cmd_search(const char *file) { +search_executable(const char *file) { char *path = getenv("PATH"); if (!path) return false; @@ -63,7 +63,7 @@ cmd_search(const char *file) { } enum process_result -cmd_execute(const char *const argv[], pid_t *pid) { +process_execute(const char *const argv[], pid_t *pid) { int fd[2]; if (pipe(fd) == -1) { @@ -125,7 +125,7 @@ end: } bool -cmd_terminate(pid_t pid) { +process_terminate(pid_t pid) { if (pid <= 0) { LOGC("Requested to kill %d, this is an error. Please report the bug.\n", (int) pid); @@ -135,7 +135,7 @@ cmd_terminate(pid_t pid) { } bool -cmd_simple_wait(pid_t pid, int *exit_code) { +process_simple_wait(pid_t pid, int *exit_code) { int status; int code; if (waitpid(pid, &status, 0) == -1 || !WIFEXITED(status)) { diff --git a/app/src/sys/win/command.c b/app/src/sys/win/process.c similarity index 93% rename from app/src/sys/win/command.c rename to app/src/sys/win/process.c index 7b483300..da776e8c 100644 --- a/app/src/sys/win/command.c +++ b/app/src/sys/win/process.c @@ -1,4 +1,4 @@ -#include "command.h" +#include "util/process.h" #include @@ -21,7 +21,7 @@ build_cmd(char *cmd, size_t len, const char *const argv[]) { } enum process_result -cmd_execute(const char *const argv[], HANDLE *handle) { +process_execute(const char *const argv[], HANDLE *handle) { STARTUPINFOW si; PROCESS_INFORMATION pi; memset(&si, 0, sizeof(si)); @@ -55,12 +55,12 @@ cmd_execute(const char *const argv[], HANDLE *handle) { } bool -cmd_terminate(HANDLE handle) { +process_terminate(HANDLE handle) { return TerminateProcess(handle, 1); } bool -cmd_simple_wait(HANDLE handle, DWORD *exit_code) { +process_simple_wait(HANDLE handle, DWORD *exit_code) { DWORD code; if (WaitForSingleObject(handle, INFINITE) != WAIT_OBJECT_0 || !GetExitCodeProcess(handle, &code)) { diff --git a/app/src/util/process.c b/app/src/util/process.c new file mode 100644 index 00000000..e4eb8282 --- /dev/null +++ b/app/src/util/process.c @@ -0,0 +1,21 @@ +#include "process.h" + +#include "log.h" + +bool +process_check_success(process_t proc, const char *name) { + if (proc == PROCESS_NONE) { + LOGE("Could not execute \"%s\"", name); + return false; + } + exit_code_t exit_code; + if (!process_simple_wait(proc, &exit_code)) { + if (exit_code != NO_EXIT_CODE) { + LOGE("\"%s\" returned with value %" PRIexitcode, name, exit_code); + } else { + LOGE("\"%s\" exited unexpectedly", name); + } + return false; + } + return true; +} diff --git a/app/src/command.h b/app/src/util/process.h similarity index 59% rename from app/src/command.h rename to app/src/util/process.h index 7035139b..1e6f256b 100644 --- a/app/src/command.h +++ b/app/src/util/process.h @@ -1,8 +1,9 @@ -#ifndef COMMAND_H -#define COMMAND_H +#ifndef SC_PROCESS_H +#define SC_PROCESS_H #include -#include + +#include "config.h" #ifdef _WIN32 @@ -31,56 +32,35 @@ #endif -#include "config.h" - enum process_result { PROCESS_SUCCESS, PROCESS_ERROR_GENERIC, PROCESS_ERROR_MISSING_BINARY, }; -#ifndef __WINDOWS__ -bool -cmd_search(const char *file); -#endif - +// execute the command and write the result to the output parameter "process" enum process_result -cmd_execute(const char *const argv[], process_t *process); +process_execute(const char *const argv[], process_t *process); +// kill the process bool -cmd_terminate(process_t pid); +process_terminate(process_t pid); +// wait and close the process bool -cmd_simple_wait(process_t pid, exit_code_t *exit_code); - -process_t -adb_execute(const char *serial, const char *const adb_cmd[], size_t len); - -process_t -adb_forward(const char *serial, uint16_t local_port, - const char *device_socket_name); - -process_t -adb_forward_remove(const char *serial, uint16_t local_port); - -process_t -adb_reverse(const char *serial, const char *device_socket_name, - uint16_t local_port); - -process_t -adb_reverse_remove(const char *serial, const char *device_socket_name); - -process_t -adb_push(const char *serial, const char *local, const char *remote); - -process_t -adb_install(const char *serial, const char *local); +process_simple_wait(process_t pid, exit_code_t *exit_code); // convenience function to wait for a successful process execution // automatically log process errors with the provided process name bool process_check_success(process_t proc, const char *name); +#ifndef _WIN32 +// only used to find package manager, not implemented for Windows +bool +search_executable(const char *file); +#endif + // return the absolute path of the executable (the scrcpy binary) // may be NULL on error; to be freed by SDL_free char * From cc6f5020d8988c137f04eb22f45e70baa4c9465d Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 3 Jan 2021 15:09:01 +0100 Subject: [PATCH 260/450] Move conditional src files in meson.build Declare all the source files (including the platform-specific ones) at the beginning. --- app/meson.build | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/app/meson.build b/app/meson.build index d031e5cc..651e756b 100644 --- a/app/meson.build +++ b/app/meson.build @@ -25,6 +25,12 @@ src = [ 'src/util/str_util.c' ] +if host_machine.system() == 'windows' + src += [ 'src/sys/win/process.c' ] +else + src += [ 'src/sys/unix/process.c' ] +endif + if not get_option('crossbuild_windows') # native build @@ -77,10 +83,7 @@ endif cc = meson.get_compiler('c') if host_machine.system() == 'windows' - src += [ 'src/sys/win/process.c' ] dependencies += cc.find_library('ws2_32') -else - src += [ 'src/sys/unix/process.c' ] endif conf = configuration_data() From 821c175730df6d4d47241ae72346c3be35d1fe99 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 3 Jan 2021 16:40:34 +0100 Subject: [PATCH 261/450] Rename process_simple_wait to process_wait Adding "simple" in the function name brings no benefit. --- app/src/file_handler.c | 2 +- app/src/server.c | 4 ++-- app/src/sys/unix/process.c | 2 +- app/src/sys/win/process.c | 2 +- app/src/util/process.c | 2 +- app/src/util/process.h | 2 +- 6 files changed, 7 insertions(+), 7 deletions(-) diff --git a/app/src/file_handler.c b/app/src/file_handler.c index 502b3e96..5efbce0e 100644 --- a/app/src/file_handler.c +++ b/app/src/file_handler.c @@ -179,7 +179,7 @@ file_handler_stop(struct file_handler *file_handler) { if (!process_terminate(file_handler->current_process)) { LOGW("Could not terminate install process"); } - process_simple_wait(file_handler->current_process, NULL); + process_wait(file_handler->current_process, NULL); file_handler->current_process = PROCESS_NONE; } mutex_unlock(file_handler->mutex); diff --git a/app/src/server.c b/app/src/server.c index 2766b0e1..1697a000 100644 --- a/app/src/server.c +++ b/app/src/server.c @@ -392,7 +392,7 @@ server_init(struct server *server) { static int run_wait_server(void *data) { struct server *server = data; - process_simple_wait(server->process, NULL); // ignore exit code + process_wait(server->process, NULL); // ignore exit code mutex_lock(server->mutex); server->process_terminated = true; @@ -448,7 +448,7 @@ server_start(struct server *server, const char *serial, SDL_CreateThread(run_wait_server, "wait-server", server); if (!server->wait_server_thread) { process_terminate(server->process); - process_simple_wait(server->process, NULL); // ignore exit code + process_wait(server->process, NULL); // ignore exit code goto error2; } diff --git a/app/src/sys/unix/process.c b/app/src/sys/unix/process.c index d029319d..3f95ce7f 100644 --- a/app/src/sys/unix/process.c +++ b/app/src/sys/unix/process.c @@ -135,7 +135,7 @@ process_terminate(pid_t pid) { } bool -process_simple_wait(pid_t pid, int *exit_code) { +process_wait(pid_t pid, int *exit_code) { int status; int code; if (waitpid(pid, &status, 0) == -1 || !WIFEXITED(status)) { diff --git a/app/src/sys/win/process.c b/app/src/sys/win/process.c index da776e8c..c30e751b 100644 --- a/app/src/sys/win/process.c +++ b/app/src/sys/win/process.c @@ -60,7 +60,7 @@ process_terminate(HANDLE handle) { } bool -process_simple_wait(HANDLE handle, DWORD *exit_code) { +process_wait(HANDLE handle, DWORD *exit_code) { DWORD code; if (WaitForSingleObject(handle, INFINITE) != WAIT_OBJECT_0 || !GetExitCodeProcess(handle, &code)) { diff --git a/app/src/util/process.c b/app/src/util/process.c index e4eb8282..e485ce17 100644 --- a/app/src/util/process.c +++ b/app/src/util/process.c @@ -9,7 +9,7 @@ process_check_success(process_t proc, const char *name) { return false; } exit_code_t exit_code; - if (!process_simple_wait(proc, &exit_code)) { + if (!process_wait(proc, &exit_code)) { if (exit_code != NO_EXIT_CODE) { LOGE("\"%s\" returned with value %" PRIexitcode, name, exit_code); } else { diff --git a/app/src/util/process.h b/app/src/util/process.h index 1e6f256b..58328a3e 100644 --- a/app/src/util/process.h +++ b/app/src/util/process.h @@ -48,7 +48,7 @@ process_terminate(process_t pid); // wait and close the process bool -process_simple_wait(process_t pid, exit_code_t *exit_code); +process_wait(process_t pid, exit_code_t *exit_code); // convenience function to wait for a successful process execution // automatically log process errors with the provided process name From d580ee30f1b388667568b0a30710e365939ca994 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 3 Jan 2021 17:06:08 +0100 Subject: [PATCH 262/450] Separate process wait and close On Linux, waitpid() both waits for the process to terminate and reaps it (closes its handle). On Windows, these actions are separated into WaitForSingleObject() and CloseHandle(). Expose these actions separately, so that it is possible to send a signal to a process while waiting for its termination without race condition. This allows to wait for server termination normally, but kill the process without race condition if it is not terminated after some delay. --- app/src/server.c | 8 ++++---- app/src/sys/unix/process.c | 31 ++++++++++++++++++++++++++----- app/src/sys/win/process.c | 26 +++++++++++++++++++++++--- app/src/util/process.h | 12 +++++++++++- 4 files changed, 64 insertions(+), 13 deletions(-) diff --git a/app/src/server.c b/app/src/server.c index 1697a000..cb2a24db 100644 --- a/app/src/server.c +++ b/app/src/server.c @@ -392,7 +392,7 @@ server_init(struct server *server) { static int run_wait_server(void *data) { struct server *server = data; - process_wait(server->process, NULL); // ignore exit code + process_wait_noclose(server->process, NULL); // ignore exit code mutex_lock(server->mutex); server->process_terminated = true; @@ -550,14 +550,14 @@ server_stop(struct server *server) { // On some devices, closing the sockets is not sufficient to wake up the // blocking calls while the device is asleep. if (r == SDL_MUTEX_TIMEDOUT) { - // FIXME There is a race condition here: there is a small chance that - // the process is already terminated, and the PID assigned to a new - // process. + // The process is terminated, but not reaped (closed) yet, so its PID + // is still valid. LOGW("Killing the server..."); process_terminate(server->process); } SDL_WaitThread(server->wait_server_thread, NULL); + process_close(server->process); } void diff --git a/app/src/sys/unix/process.c b/app/src/sys/unix/process.c index 3f95ce7f..00a8d81c 100644 --- a/app/src/sys/unix/process.c +++ b/app/src/sys/unix/process.c @@ -134,15 +134,21 @@ process_terminate(pid_t pid) { return kill(pid, SIGTERM) != -1; } -bool -process_wait(pid_t pid, int *exit_code) { - int status; +static bool +process_wait_internal(pid_t pid, int *exit_code, bool close) { int code; - if (waitpid(pid, &status, 0) == -1 || !WIFEXITED(status)) { + int options = WEXITED; + if (!close) { + options |= WNOWAIT; + } + + siginfo_t info; + int r = waitid(P_PID, pid, &info, options); + if (r == -1 || info.si_code != CLD_EXITED) { // could not wait, or exited unexpectedly, probably by a signal code = -1; } else { - code = WEXITSTATUS(status); + code = info.si_status; } if (exit_code) { *exit_code = code; @@ -150,6 +156,21 @@ process_wait(pid_t pid, int *exit_code) { return !code; } +bool +process_wait(pid_t pid, int *exit_code) { + return process_wait_internal(pid, exit_code, true); +} + +bool +process_wait_noclose(pid_t pid, int *exit_code) { + return process_wait_internal(pid, exit_code, false); +} + +void +process_close(pid_t pid) { + process_wait_internal(pid, NULL, true); +} + char * get_executable_path(void) { // diff --git a/app/src/sys/win/process.c b/app/src/sys/win/process.c index c30e751b..f2a86b22 100644 --- a/app/src/sys/win/process.c +++ b/app/src/sys/win/process.c @@ -1,5 +1,6 @@ #include "util/process.h" +#include #include #include "config.h" @@ -59,8 +60,8 @@ process_terminate(HANDLE handle) { return TerminateProcess(handle, 1); } -bool -process_wait(HANDLE handle, DWORD *exit_code) { +static bool +process_wait_internal(HANDLE handle, DWORD *exit_code, bool close) { DWORD code; if (WaitForSingleObject(handle, INFINITE) != WAIT_OBJECT_0 || !GetExitCodeProcess(handle, &code)) { @@ -70,10 +71,29 @@ process_wait(HANDLE handle, DWORD *exit_code) { if (exit_code) { *exit_code = code; } - CloseHandle(handle); + if (close) { + CloseHandle(handle); + } return !code; } +bool +process_wait(HANDLE handle, DWORD *exit_code) { + return process_wait_internal(handle, exit_code, true); +} + +bool +process_wait_noclose(HANDLE handle, DWORD *exit_code) { + return process_wait_internal(handle, exit_code, false); +} + +void +process_close(HANDLE handle) { + bool closed = CloseHandle(handle); + assert(closed); + (void) closed; +} + char * get_executable_path(void) { HMODULE hModule = GetModuleHandleW(NULL); diff --git a/app/src/util/process.h b/app/src/util/process.h index 58328a3e..ad2bc0d0 100644 --- a/app/src/util/process.h +++ b/app/src/util/process.h @@ -46,10 +46,20 @@ process_execute(const char *const argv[], process_t *process); bool process_terminate(process_t pid); -// wait and close the process +// wait and close the process (like waitpid()) bool process_wait(process_t pid, exit_code_t *exit_code); +// wait (but does not close) the process (waitid() with WNOWAIT) +bool +process_wait_noclose(process_t pid, exit_code_t *exit_code); + +// close the process +// +// Semantically, process_wait = process_wait_noclose + process_close. +void +process_close(process_t pid); + // convenience function to wait for a successful process execution // automatically log process errors with the provided process name bool From 1e215199dc682c93b37ebaca9df6f1c0eec68e97 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 8 Jan 2021 19:13:53 +0100 Subject: [PATCH 263/450] Remove unused struct port_range It had been replaced by struct sc_port_range in scrcpy.h. --- app/src/common.h | 5 ----- 1 file changed, 5 deletions(-) diff --git a/app/src/common.h b/app/src/common.h index 4cbf1d74..e5cbe953 100644 --- a/app/src/common.h +++ b/app/src/common.h @@ -27,9 +27,4 @@ struct position { struct point point; }; -struct port_range { - uint16_t first; - uint16_t last; -}; - #endif From 037be4af218c2bb5521dce65d01fb8ebd150f113 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 8 Jan 2021 19:21:54 +0100 Subject: [PATCH 264/450] Fix compat missing include The header libavformat/version.h was included, but not libavcodec/version.h. As a consequence, the LIBAVCODEC_VERSION_INT definition depended on the caller includes. --- app/src/compat.h | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/compat.h b/app/src/compat.h index de667bbf..21f19214 100644 --- a/app/src/compat.h +++ b/app/src/compat.h @@ -1,6 +1,7 @@ #ifndef COMPAT_H #define COMPAT_H +#include #include #include From 6385b8c162e58ed167f317a333ceeb13f3979f8c Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 8 Jan 2021 19:15:29 +0100 Subject: [PATCH 265/450] Move common structs to coords.h The size, point and position structs were defined in common.h. Move them to coords.h so that common.h could be used for generic code to be included in all source files. --- app/src/common.h | 20 -------------------- app/src/control_msg.h | 2 +- app/src/coords.h | 24 ++++++++++++++++++++++++ app/src/device.h | 2 +- app/src/input_manager.h | 1 - app/src/recorder.h | 2 +- app/src/scrcpy.c | 1 - app/src/screen.h | 2 +- app/src/server.h | 1 - 9 files changed, 28 insertions(+), 27 deletions(-) create mode 100644 app/src/coords.h diff --git a/app/src/common.h b/app/src/common.h index e5cbe953..3f517d5b 100644 --- a/app/src/common.h +++ b/app/src/common.h @@ -1,30 +1,10 @@ #ifndef COMMON_H #define COMMON_H -#include - #include "config.h" #define ARRAY_LEN(a) (sizeof(a) / sizeof(a[0])) #define MIN(X,Y) (X) < (Y) ? (X) : (Y) #define MAX(X,Y) (X) > (Y) ? (X) : (Y) -struct size { - uint16_t width; - uint16_t height; -}; - -struct point { - int32_t x; - int32_t y; -}; - -struct position { - // The video screen size may be different from the real device screen size, - // so store to which size the absolute position apply, to scale it - // accordingly. - struct size screen_size; - struct point point; -}; - #endif diff --git a/app/src/control_msg.h b/app/src/control_msg.h index 6e3f239c..5014a84c 100644 --- a/app/src/control_msg.h +++ b/app/src/control_msg.h @@ -8,7 +8,7 @@ #include "config.h" #include "android/input.h" #include "android/keycodes.h" -#include "common.h" +#include "coords.h" #define CONTROL_MSG_MAX_SIZE (1 << 18) // 256k diff --git a/app/src/coords.h b/app/src/coords.h new file mode 100644 index 00000000..7be6836d --- /dev/null +++ b/app/src/coords.h @@ -0,0 +1,24 @@ +#ifndef SC_COORDS +#define SC_COORDS + +#include + +struct size { + uint16_t width; + uint16_t height; +}; + +struct point { + int32_t x; + int32_t y; +}; + +struct position { + // The video screen size may be different from the real device screen size, + // so store to which size the absolute position apply, to scale it + // accordingly. + struct size screen_size; + struct point point; +}; + +#endif diff --git a/app/src/device.h b/app/src/device.h index 8a94cd86..5f30194f 100644 --- a/app/src/device.h +++ b/app/src/device.h @@ -4,7 +4,7 @@ #include #include "config.h" -#include "common.h" +#include "coords.h" #include "util/net.h" #define DEVICE_NAME_FIELD_LENGTH 64 diff --git a/app/src/input_manager.h b/app/src/input_manager.h index df9b071f..ce53792b 100644 --- a/app/src/input_manager.h +++ b/app/src/input_manager.h @@ -6,7 +6,6 @@ #include #include "config.h" -#include "common.h" #include "controller.h" #include "fps_counter.h" #include "scrcpy.h" diff --git a/app/src/recorder.h b/app/src/recorder.h index bc87a23b..43a0a395 100644 --- a/app/src/recorder.h +++ b/app/src/recorder.h @@ -7,7 +7,7 @@ #include #include "config.h" -#include "common.h" +#include "coords.h" #include "scrcpy.h" #include "util/queue.h" diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index 2d593234..c1aedcca 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -14,7 +14,6 @@ #endif #include "config.h" -#include "common.h" #include "compat.h" #include "controller.h" #include "decoder.h" diff --git a/app/src/screen.h b/app/src/screen.h index c4fbbf66..0e87d1c9 100644 --- a/app/src/screen.h +++ b/app/src/screen.h @@ -6,7 +6,7 @@ #include #include "config.h" -#include "common.h" +#include "coords.h" #include "opengl.h" struct video_buffer; diff --git a/app/src/server.h b/app/src/server.h index 48d8cd32..b607d691 100644 --- a/app/src/server.h +++ b/app/src/server.h @@ -8,7 +8,6 @@ #include "config.h" #include "adb.h" -#include "common.h" #include "scrcpy.h" #include "util/log.h" #include "util/net.h" From 59feb2a15c2e48091675d49561a6258b9d083710 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 8 Jan 2021 19:18:02 +0100 Subject: [PATCH 266/450] Group common includes into common.h Include config.h and compat.h in common.h, and include common.h from all source files. --- app/src/adb.c | 2 -- app/src/adb.h | 2 +- app/src/cli.c | 1 - app/src/cli.h | 2 +- app/src/common.h | 1 + app/src/control_msg.c | 1 - app/src/control_msg.h | 2 +- app/src/controller.c | 1 - app/src/controller.h | 2 +- app/src/decoder.c | 2 -- app/src/decoder.h | 2 +- app/src/device.c | 1 - app/src/device.h | 2 +- app/src/device_msg.c | 1 - app/src/device_msg.h | 2 +- app/src/event_converter.c | 2 -- app/src/event_converter.h | 2 +- app/src/file_handler.c | 1 - app/src/file_handler.h | 2 +- app/src/fps_counter.c | 1 - app/src/fps_counter.h | 2 +- app/src/input_manager.c | 1 - app/src/input_manager.h | 2 +- app/src/main.c | 3 +-- app/src/opengl.h | 2 +- app/src/receiver.c | 1 - app/src/receiver.h | 2 +- app/src/recorder.c | 2 -- app/src/recorder.h | 2 +- app/src/scrcpy.c | 2 -- app/src/scrcpy.h | 2 +- app/src/screen.c | 3 --- app/src/screen.h | 2 +- app/src/server.c | 1 - app/src/server.h | 2 +- app/src/stream.c | 2 -- app/src/stream.h | 2 +- app/src/sys/unix/process.c | 2 -- app/src/sys/win/process.c | 1 - app/src/tiny_xpm.c | 1 - app/src/tiny_xpm.h | 2 +- app/src/util/buffer_util.h | 2 +- app/src/util/cbuf.h | 2 +- app/src/util/lock.h | 2 +- app/src/util/net.c | 1 - app/src/util/net.h | 2 +- app/src/util/process.h | 2 +- app/src/util/queue.h | 2 +- app/src/util/str_util.c | 2 -- app/src/util/str_util.h | 2 +- app/src/video_buffer.c | 1 - app/src/video_buffer.h | 2 +- app/tests/test_cli.c | 2 +- 53 files changed, 30 insertions(+), 63 deletions(-) diff --git a/app/src/adb.c b/app/src/adb.c index 339c854d..086db174 100644 --- a/app/src/adb.c +++ b/app/src/adb.c @@ -5,8 +5,6 @@ #include #include -#include "config.h" -#include "common.h" #include "util/log.h" #include "util/str_util.h" diff --git a/app/src/adb.h b/app/src/adb.h index 52cdf823..453e3019 100644 --- a/app/src/adb.h +++ b/app/src/adb.h @@ -4,7 +4,7 @@ #include #include -#include "config.h" +#include "common.h" #include "util/process.h" diff --git a/app/src/cli.c b/app/src/cli.c index 694282a2..fbdef07f 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -6,7 +6,6 @@ #include #include -#include "config.h" #include "scrcpy.h" #include "util/log.h" #include "util/str_util.h" diff --git a/app/src/cli.h b/app/src/cli.h index 73dfe228..22bded79 100644 --- a/app/src/cli.h +++ b/app/src/cli.h @@ -3,7 +3,7 @@ #include -#include "config.h" +#include "common.h" #include "scrcpy.h" struct scrcpy_cli_args { diff --git a/app/src/common.h b/app/src/common.h index 3f517d5b..27c8d2fb 100644 --- a/app/src/common.h +++ b/app/src/common.h @@ -2,6 +2,7 @@ #define COMMON_H #include "config.h" +#include "compat.h" #define ARRAY_LEN(a) (sizeof(a) / sizeof(a[0])) #define MIN(X,Y) (X) < (Y) ? (X) : (Y) diff --git a/app/src/control_msg.c b/app/src/control_msg.c index 27c7903d..77e534cd 100644 --- a/app/src/control_msg.c +++ b/app/src/control_msg.c @@ -3,7 +3,6 @@ #include #include -#include "config.h" #include "util/buffer_util.h" #include "util/log.h" #include "util/str_util.h" diff --git a/app/src/control_msg.h b/app/src/control_msg.h index 5014a84c..e23cc89c 100644 --- a/app/src/control_msg.h +++ b/app/src/control_msg.h @@ -5,7 +5,7 @@ #include #include -#include "config.h" +#include "common.h" #include "android/input.h" #include "android/keycodes.h" #include "coords.h" diff --git a/app/src/controller.c b/app/src/controller.c index c5897e5d..6bfb80d3 100644 --- a/app/src/controller.c +++ b/app/src/controller.c @@ -2,7 +2,6 @@ #include -#include "config.h" #include "util/lock.h" #include "util/log.h" diff --git a/app/src/controller.h b/app/src/controller.h index 8011ef6a..5ef8755a 100644 --- a/app/src/controller.h +++ b/app/src/controller.h @@ -5,7 +5,7 @@ #include #include -#include "config.h" +#include "common.h" #include "control_msg.h" #include "receiver.h" #include "util/cbuf.h" diff --git a/app/src/decoder.c b/app/src/decoder.c index 49d4ce86..50c55ab2 100644 --- a/app/src/decoder.c +++ b/app/src/decoder.c @@ -7,8 +7,6 @@ #include #include -#include "config.h" -#include "compat.h" #include "events.h" #include "recorder.h" #include "video_buffer.h" diff --git a/app/src/decoder.h b/app/src/decoder.h index f243812c..8a3bc297 100644 --- a/app/src/decoder.h +++ b/app/src/decoder.h @@ -4,7 +4,7 @@ #include #include -#include "config.h" +#include "common.h" struct video_buffer; diff --git a/app/src/device.c b/app/src/device.c index f4c2628b..094dc616 100644 --- a/app/src/device.c +++ b/app/src/device.c @@ -1,6 +1,5 @@ #include "device.h" -#include "config.h" #include "util/log.h" bool diff --git a/app/src/device.h b/app/src/device.h index 5f30194f..c21b1ace 100644 --- a/app/src/device.h +++ b/app/src/device.h @@ -3,7 +3,7 @@ #include -#include "config.h" +#include "common.h" #include "coords.h" #include "util/net.h" diff --git a/app/src/device_msg.c b/app/src/device_msg.c index 09e68936..c82ce628 100644 --- a/app/src/device_msg.c +++ b/app/src/device_msg.c @@ -2,7 +2,6 @@ #include -#include "config.h" #include "util/buffer_util.h" #include "util/log.h" diff --git a/app/src/device_msg.h b/app/src/device_msg.h index 4b681e2c..b1860726 100644 --- a/app/src/device_msg.h +++ b/app/src/device_msg.h @@ -5,7 +5,7 @@ #include #include -#include "config.h" +#include "common.h" #define DEVICE_MSG_MAX_SIZE (1 << 18) // 256k // type: 1 byte; length: 4 bytes diff --git a/app/src/event_converter.c b/app/src/event_converter.c index ab48898d..cf010a16 100644 --- a/app/src/event_converter.c +++ b/app/src/event_converter.c @@ -1,7 +1,5 @@ #include "event_converter.h" -#include "config.h" - #define MAP(FROM, TO) case FROM: *to = TO; return true #define FAIL default: return false diff --git a/app/src/event_converter.h b/app/src/event_converter.h index c41887e1..cbd578a0 100644 --- a/app/src/event_converter.h +++ b/app/src/event_converter.h @@ -4,7 +4,7 @@ #include #include -#include "config.h" +#include "common.h" #include "control_msg.h" bool diff --git a/app/src/file_handler.c b/app/src/file_handler.c index 5efbce0e..3ad92c05 100644 --- a/app/src/file_handler.c +++ b/app/src/file_handler.c @@ -3,7 +3,6 @@ #include #include -#include "config.h" #include "adb.h" #include "util/lock.h" #include "util/log.h" diff --git a/app/src/file_handler.h b/app/src/file_handler.h index 71b58952..6649b9ea 100644 --- a/app/src/file_handler.h +++ b/app/src/file_handler.h @@ -5,7 +5,7 @@ #include #include -#include "config.h" +#include "common.h" #include "adb.h" #include "util/cbuf.h" diff --git a/app/src/fps_counter.c b/app/src/fps_counter.c index b4dd8b9b..e7607409 100644 --- a/app/src/fps_counter.c +++ b/app/src/fps_counter.c @@ -3,7 +3,6 @@ #include #include -#include "config.h" #include "util/lock.h" #include "util/log.h" diff --git a/app/src/fps_counter.h b/app/src/fps_counter.h index 52157172..2c13b3e2 100644 --- a/app/src/fps_counter.h +++ b/app/src/fps_counter.h @@ -7,7 +7,7 @@ #include #include -#include "config.h" +#include "common.h" struct fps_counter { SDL_Thread *thread; diff --git a/app/src/input_manager.c b/app/src/input_manager.c index bab85660..df01ea38 100644 --- a/app/src/input_manager.c +++ b/app/src/input_manager.c @@ -3,7 +3,6 @@ #include #include -#include "config.h" #include "event_converter.h" #include "util/lock.h" #include "util/log.h" diff --git a/app/src/input_manager.h b/app/src/input_manager.h index ce53792b..66daa9dd 100644 --- a/app/src/input_manager.h +++ b/app/src/input_manager.h @@ -5,7 +5,7 @@ #include -#include "config.h" +#include "common.h" #include "controller.h" #include "fps_counter.h" #include "scrcpy.h" diff --git a/app/src/main.c b/app/src/main.c index 71125673..18b9c710 100644 --- a/app/src/main.c +++ b/app/src/main.c @@ -7,9 +7,8 @@ #define SDL_MAIN_HANDLED // avoid link error on Linux Windows Subsystem #include -#include "config.h" +#include "common.h" #include "cli.h" -#include "compat.h" #include "util/log.h" static void diff --git a/app/src/opengl.h b/app/src/opengl.h index f0a89a14..1c1e4658 100644 --- a/app/src/opengl.h +++ b/app/src/opengl.h @@ -4,7 +4,7 @@ #include #include -#include "config.h" +#include "common.h" struct sc_opengl { const char *version; diff --git a/app/src/receiver.c b/app/src/receiver.c index 307eb5d5..5b97f88b 100644 --- a/app/src/receiver.c +++ b/app/src/receiver.c @@ -3,7 +3,6 @@ #include #include -#include "config.h" #include "device_msg.h" #include "util/lock.h" #include "util/log.h" diff --git a/app/src/receiver.h b/app/src/receiver.h index 8387903b..8f628238 100644 --- a/app/src/receiver.h +++ b/app/src/receiver.h @@ -5,7 +5,7 @@ #include #include -#include "config.h" +#include "common.h" #include "util/net.h" // receive events from the device diff --git a/app/src/recorder.c b/app/src/recorder.c index e31492c0..6558d804 100644 --- a/app/src/recorder.c +++ b/app/src/recorder.c @@ -3,8 +3,6 @@ #include #include -#include "config.h" -#include "compat.h" #include "util/lock.h" #include "util/log.h" diff --git a/app/src/recorder.h b/app/src/recorder.h index 43a0a395..fc05d06c 100644 --- a/app/src/recorder.h +++ b/app/src/recorder.h @@ -6,7 +6,7 @@ #include #include -#include "config.h" +#include "common.h" #include "coords.h" #include "scrcpy.h" #include "util/queue.h" diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index c1aedcca..ecc7a0e6 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -13,8 +13,6 @@ # include #endif -#include "config.h" -#include "compat.h" #include "controller.h" #include "decoder.h" #include "device.h" diff --git a/app/src/scrcpy.h b/app/src/scrcpy.h index c1d7b612..08bce7e3 100644 --- a/app/src/scrcpy.h +++ b/app/src/scrcpy.h @@ -5,7 +5,7 @@ #include #include -#include "config.h" +#include "common.h" enum sc_log_level { SC_LOG_LEVEL_DEBUG, diff --git a/app/src/screen.c b/app/src/screen.c index fe2bc867..5bdfac2a 100644 --- a/app/src/screen.c +++ b/app/src/screen.c @@ -4,9 +4,6 @@ #include #include -#include "config.h" -#include "common.h" -#include "compat.h" #include "icon.xpm" #include "scrcpy.h" #include "tiny_xpm.h" diff --git a/app/src/screen.h b/app/src/screen.h index 0e87d1c9..820c7382 100644 --- a/app/src/screen.h +++ b/app/src/screen.h @@ -5,7 +5,7 @@ #include #include -#include "config.h" +#include "common.h" #include "coords.h" #include "opengl.h" diff --git a/app/src/server.c b/app/src/server.c index cb2a24db..841af82f 100644 --- a/app/src/server.c +++ b/app/src/server.c @@ -9,7 +9,6 @@ #include #include -#include "config.h" #include "adb.h" #include "util/lock.h" #include "util/log.h" diff --git a/app/src/server.h b/app/src/server.h index b607d691..b48bcd61 100644 --- a/app/src/server.h +++ b/app/src/server.h @@ -6,7 +6,7 @@ #include #include -#include "config.h" +#include "common.h" #include "adb.h" #include "scrcpy.h" #include "util/log.h" diff --git a/app/src/stream.c b/app/src/stream.c index dd2dbd76..e4c9f387 100644 --- a/app/src/stream.c +++ b/app/src/stream.c @@ -8,8 +8,6 @@ #include #include -#include "config.h" -#include "compat.h" #include "decoder.h" #include "events.h" #include "recorder.h" diff --git a/app/src/stream.h b/app/src/stream.h index cd09d959..7b0a5d42 100644 --- a/app/src/stream.h +++ b/app/src/stream.h @@ -7,7 +7,7 @@ #include #include -#include "config.h" +#include "common.h" #include "util/net.h" struct video_buffer; diff --git a/app/src/sys/unix/process.c b/app/src/sys/unix/process.c index 00a8d81c..dc4f5649 100644 --- a/app/src/sys/unix/process.c +++ b/app/src/sys/unix/process.c @@ -11,8 +11,6 @@ #include "util/process.h" -#include "config.h" - #include #include #include diff --git a/app/src/sys/win/process.c b/app/src/sys/win/process.c index f2a86b22..f087625c 100644 --- a/app/src/sys/win/process.c +++ b/app/src/sys/win/process.c @@ -3,7 +3,6 @@ #include #include -#include "config.h" #include "util/log.h" #include "util/str_util.h" diff --git a/app/src/tiny_xpm.c b/app/src/tiny_xpm.c index feb3d1cb..df1f9e53 100644 --- a/app/src/tiny_xpm.c +++ b/app/src/tiny_xpm.c @@ -6,7 +6,6 @@ #include #include -#include "config.h" #include "util/log.h" struct index { diff --git a/app/src/tiny_xpm.h b/app/src/tiny_xpm.h index 6e6f8035..2dcbeb07 100644 --- a/app/src/tiny_xpm.h +++ b/app/src/tiny_xpm.h @@ -3,7 +3,7 @@ #include -#include "config.h" +#include "common.h" SDL_Surface * read_xpm(char *xpm[]); diff --git a/app/src/util/buffer_util.h b/app/src/util/buffer_util.h index 17234e42..265704bc 100644 --- a/app/src/util/buffer_util.h +++ b/app/src/util/buffer_util.h @@ -4,7 +4,7 @@ #include #include -#include "config.h" +#include "common.h" static inline void buffer_write16be(uint8_t *buf, uint16_t value) { diff --git a/app/src/util/cbuf.h b/app/src/util/cbuf.h index c18e4680..6abc984b 100644 --- a/app/src/util/cbuf.h +++ b/app/src/util/cbuf.h @@ -5,7 +5,7 @@ #include #include -#include "config.h" +#include "common.h" // To define a circular buffer type of 20 ints: // struct cbuf_int CBUF(int, 20); diff --git a/app/src/util/lock.h b/app/src/util/lock.h index cb7c318c..a0a044b1 100644 --- a/app/src/util/lock.h +++ b/app/src/util/lock.h @@ -4,7 +4,7 @@ #include #include -#include "config.h" +#include "common.h" #include "log.h" static inline void diff --git a/app/src/util/net.c b/app/src/util/net.c index efce6fa9..bbf57bbc 100644 --- a/app/src/util/net.c +++ b/app/src/util/net.c @@ -3,7 +3,6 @@ #include #include -#include "config.h" #include "log.h" #ifdef __WINDOWS__ diff --git a/app/src/util/net.h b/app/src/util/net.h index ffd5dd89..f86c048d 100644 --- a/app/src/util/net.h +++ b/app/src/util/net.h @@ -17,7 +17,7 @@ typedef int socket_t; #endif -#include "config.h" +#include "common.h" bool net_init(void); diff --git a/app/src/util/process.h b/app/src/util/process.h index ad2bc0d0..f91553d4 100644 --- a/app/src/util/process.h +++ b/app/src/util/process.h @@ -3,7 +3,7 @@ #include -#include "config.h" +#include "common.h" #ifdef _WIN32 diff --git a/app/src/util/queue.h b/app/src/util/queue.h index 12bc9e89..6092c712 100644 --- a/app/src/util/queue.h +++ b/app/src/util/queue.h @@ -6,7 +6,7 @@ #include #include -#include "config.h" +#include "common.h" // To define a queue type of "struct foo": // struct queue_foo QUEUE(struct foo); diff --git a/app/src/util/str_util.c b/app/src/util/str_util.c index ce0498a5..babce4a1 100644 --- a/app/src/util/str_util.c +++ b/app/src/util/str_util.c @@ -12,8 +12,6 @@ #include -#include "config.h" - size_t xstrncpy(char *dest, const char *src, size_t n) { size_t i; diff --git a/app/src/util/str_util.h b/app/src/util/str_util.h index c7f26cdb..dd523400 100644 --- a/app/src/util/str_util.h +++ b/app/src/util/str_util.h @@ -4,7 +4,7 @@ #include #include -#include "config.h" +#include "common.h" // like strncpy, except: // - it copies at most n-1 chars diff --git a/app/src/video_buffer.c b/app/src/video_buffer.c index 629680d9..d656c6f4 100644 --- a/app/src/video_buffer.c +++ b/app/src/video_buffer.c @@ -5,7 +5,6 @@ #include #include -#include "config.h" #include "util/lock.h" #include "util/log.h" diff --git a/app/src/video_buffer.h b/app/src/video_buffer.h index 303b3fc2..ddd639ad 100644 --- a/app/src/video_buffer.h +++ b/app/src/video_buffer.h @@ -4,7 +4,7 @@ #include #include -#include "config.h" +#include "common.h" #include "fps_counter.h" // forward declarations diff --git a/app/tests/test_cli.c b/app/tests/test_cli.c index 1024dba6..9699b024 100644 --- a/app/tests/test_cli.c +++ b/app/tests/test_cli.c @@ -1,8 +1,8 @@ #include #include -#include "cli.h" #include "common.h" +#include "cli.h" #include "scrcpy.h" static void test_flag_version(void) { From af516e33ee2561dfe57b30cfe557bd134a6b50cc Mon Sep 17 00:00:00 2001 From: Natan Junges Date: Thu, 14 Jan 2021 01:03:10 -0300 Subject: [PATCH 267/450] Update README.pt-br to v1.17 PR #2034 Reviewed-by: latreta Signed-off-by: Romain Vimont --- README.md | 2 +- README.pt-br.md | 518 ++++++++++++++++++++++++++++++++++++------------ 2 files changed, 392 insertions(+), 128 deletions(-) diff --git a/README.md b/README.md index 6a987a73..c38ac2cf 100644 --- a/README.md +++ b/README.md @@ -800,7 +800,7 @@ This README is available in other languages: - [Indonesian (Indonesia, `id`) - v1.16](README.id.md) - [한국어 (Korean, `ko`) - v1.11](README.ko.md) -- [português brasileiro (Brazilian Portuguese, `pt-BR`) - v1.12.1](README.pt-br.md) +- [português brasileiro (Brazilian Portuguese, `pt-BR`) - v1.17](README.pt-br.md) - [简体中文 (Simplified Chinese, `zh-Hans`) - v1.16](README.zh-Hans.md) - [繁體中文 (Traditional Chinese, `zh-Hant`) - v1.15](README.zh-Hant.md) diff --git a/README.pt-br.md b/README.pt-br.md index 4f2122a6..ebf7c7ec 100644 --- a/README.pt-br.md +++ b/README.pt-br.md @@ -1,16 +1,16 @@ -_Only the original [README](README.md) is guaranteed to be up-to-date._ +_Apenas o [README](README.md) original é garantido estar atualizado._ -# scrcpy (v1.12.1) +# scrcpy (v1.17) -Esta aplicação fornece visualização e controle de dispositivos Android conectados via -USB (ou [via TCP/IP][article-tcpip]). Não requer nenhum acesso root. +Esta aplicação fornece exibição e controle de dispositivos Android conectados via +USB (ou [via TCP/IP][article-tcpip]). Não requer nenhum acesso _root_. Funciona em _GNU/Linux_, _Windows_ e _macOS_. ![screenshot](assets/screenshot-debian-600.jpg) Foco em: - - **leveza** (Nativo, mostra apenas a tela do dispositivo) + - **leveza** (nativo, mostra apenas a tela do dispositivo) - **performance** (30~60fps) - **qualidade** (1920×1080 ou acima) - **baixa latência** ([35~70ms][lowlatency]) @@ -22,36 +22,41 @@ Foco em: ## Requisitos -O Dispositivo Android requer pelo menos a API 21 (Android 5.0). +O dispositivo Android requer pelo menos a API 21 (Android 5.0). - -Tenha certeza de ter [ativado a depuração USB][enable-adb] no(s) seu(s) dispositivo(s). +Tenha certeza de ter [ativado a depuração adb][enable-adb] no(s) seu(s) dispositivo(s). [enable-adb]: https://developer.android.com/studio/command-line/adb.html#Enabling - -Em alguns dispositivos, você também precisará ativar [uma opção adicional][control] para controlá-lo usando o teclado e mouse. +Em alguns dispositivos, você também precisa ativar [uma opção adicional][control] para +controlá-lo usando teclado e mouse. [control]: https://github.com/Genymobile/scrcpy/issues/70#issuecomment-373286323 -## Obtendo o app +## Obter o app +Packaging status ### Linux -No Debian (_em testes_ e _sid_ por enquanto): +No Debian (_testing_ e _sid_ por enquanto) e Ubuntu (20.04): ``` apt install scrcpy ``` -O pacote [Snap] está disponível: [`scrcpy`][snap-link]. +Um pacote [Snap] está disponível: [`scrcpy`][snap-link]. [snap-link]: https://snapstats.org/snaps/scrcpy [snap]: https://en.wikipedia.org/wiki/Snappy_(package_manager) +Para Fedora, um pacote [COPR] está disponível: [`scrcpy`][copr-link]. + +[COPR]: https://fedoraproject.org/wiki/Category:Copr +[copr-link]: https://copr.fedorainfracloud.org/coprs/zeno/scrcpy/ + Para Arch Linux, um pacote [AUR] está disponível: [`scrcpy`][aur-link]. [AUR]: https://wiki.archlinux.org/index.php/Arch_User_Repository @@ -62,21 +67,22 @@ Para Gentoo, uma [Ebuild] está disponível: [`scrcpy/`][ebuild-link]. [Ebuild]: https://wiki.gentoo.org/wiki/Ebuild [ebuild-link]: https://github.com/maggu2810/maggu2810-overlay/tree/master/app-mobilephone/scrcpy +Você também pode [compilar o app manualmente][BUILD] (não se preocupe, não é tão +difícil). -Você também pode [compilar a aplicação manualmente][BUILD] (não se preocupe, não é tão difícil). ### Windows -Para Windows, para simplicidade, um arquivo pré-compilado com todas as dependências +Para Windows, por simplicidade, um arquivo pré-compilado com todas as dependências (incluindo `adb`) está disponível: - - [`scrcpy-win64-v1.12.1.zip`][direct-win64] - _(SHA-256: 57d34b6d16cfd9fe169bc37c4df58ebd256d05c1ea3febc63d9cb0a027ab47c9)_ + - [`scrcpy-win64-v1.17.zip`][direct-win64] + _(SHA-256: 8b9e57993c707367ed10ebfe0e1ef563c7a29d9af4a355cd8b6a52a317c73eea)_ -[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.17/scrcpy-win64-v1.17.zip -Também disponível em [Chocolatey]: +Também está disponível em [Chocolatey]: [Chocolatey]: https://chocolatey.org/ @@ -94,12 +100,12 @@ scoop install adb # se você ainda não o tem [Scoop]: https://scoop.sh -Você também pode [compilar a aplicação manualmente][BUILD]. +Você também pode [compilar o app manualmente][BUILD]. ### macOS -A aplicação está disponível em [Homebrew]. Apenas a instale: +A aplicação está disponível em [Homebrew]. Apenas instale-a: [Homebrew]: https://brew.sh/ @@ -107,18 +113,22 @@ A aplicação está disponível em [Homebrew]. Apenas a instale: brew install scrcpy ``` -Você precisa do `adb`, acessível através do seu `PATH`. Se você ainda não o tem: +Você precisa do `adb`, acessível pelo seu `PATH`. Se você ainda não o tem: ```bash +# Homebrew >= 2.6.0 +brew install --cask android-platform-tools + +# Homebrew < 2.6.0 brew cask install android-platform-tools ``` -Você também pode [compilar a aplicação manualmente][BUILD]. +Você também pode [compilar o app manualmente][BUILD]. ## Executar -Plugue um dispositivo Android e execute: +Conecte um dispositivo Android e execute: ```bash scrcpy @@ -134,52 +144,87 @@ scrcpy --help ### Configuração de captura -#### Redução de tamanho +#### Reduzir tamanho Algumas vezes, é útil espelhar um dispositivo Android em uma resolução menor para -aumentar performance. +aumentar a performance. -Para limitar ambos(largura e altura) para algum valor (ex: 1024): +Para limitar ambos (largura e altura) para algum valor (ex: 1024): ```bash scrcpy --max-size 1024 -scrcpy -m 1024 # versão reduzida +scrcpy -m 1024 # versão curta ``` A outra dimensão é calculada para que a proporção do dispositivo seja preservada. -Dessa forma, um dispositivo em 1920x1080 será espelhado em 1024x576. +Dessa forma, um dispositivo de 1920x1080 será espelhado em 1024x576. -#### Mudanças no bit-rate +#### Mudar bit-rate -O Padrão de bit-rate é 8 mbps. Para mudar o bitrate do vídeo (ex: para 2 Mbps): +O bit-rate padrão é 8 Mbps. Para mudar o bit-rate do vídeo (ex: para 2 Mbps): ```bash scrcpy --bit-rate 2M -scrcpy -b 2M # versão reduzida +scrcpy -b 2M # versão curta ``` -#### Limitar frame rates +#### Limitar frame rate -Em dispositivos com Android >= 10, a captura de frame rate pode ser limitada: +O frame rate de captura pode ser limitado: ```bash scrcpy --max-fps 15 ``` +Isso é oficialmente suportado desde o Android 10, mas pode funcionar em versões anteriores. + #### Cortar A tela do dispositivo pode ser cortada para espelhar apenas uma parte da tela. -Isso é útil por exemplo, ao espelhar apenas um olho do Oculus Go: +Isso é útil por exemplo, para espelhar apenas um olho do Oculus Go: ```bash scrcpy --crop 1224:1440:0:0 # 1224x1440 no deslocamento (0,0) ``` -Se `--max-size` também for especificado, redimensionar é aplicado após os cortes. +Se `--max-size` também for especificado, o redimensionamento é aplicado após o corte. +#### Travar orientação do vídeo + + +Para travar a orientação do espelhamento: + +```bash +scrcpy --lock-video-orientation 0 # orientação natural +scrcpy --lock-video-orientation 1 # 90° sentido anti-horário +scrcpy --lock-video-orientation 2 # 180° +scrcpy --lock-video-orientation 3 # 90° sentido horário +``` + +Isso afeta a orientação de gravação. + +A [janela também pode ser rotacionada](#rotação) independentemente. + + +#### Encoder + +Alguns dispositivos têm mais de um encoder, e alguns deles podem causar problemas ou +travar. É possível selecionar um encoder diferente: + +```bash +scrcpy --encoder OMX.qcom.video.encoder.avc +``` + +Para listar os encoders disponíveis, você pode passar um nome de encoder inválido, o +erro dará os encoders disponíveis: + +```bash +scrcpy --encoder _ +``` + ### Gravando É possível gravar a tela enquanto ocorre o espelhamento: @@ -194,65 +239,84 @@ Para desativar o espelhamento durante a gravação: ```bash scrcpy --no-display --record file.mp4 scrcpy -Nr file.mkv -# interrompe a gravação com Ctrl+C -# Ctrl+C não encerrar propriamente no Windows, então desconecte o dispositivo +# interrompa a gravação com Ctrl+C ``` -"Frames pulados" são gravados, mesmo que não sejam mostrado em tempo real (por motivos de performance). -Frames tem seu _horário_ _carimbado_ no dispositivo, então [Variação de atraso nos pacotes] não impacta na gravação do arquivo. +"Frames pulados" são gravados, mesmo que não sejam exibidos em tempo real (por +motivos de performance). Frames têm seu _horário carimbado_ no dispositivo, então [variação de atraso nos +pacotes][packet delay variation] não impacta o arquivo gravado. -[Variação de atraso de pacote]: https://en.wikipedia.org/wiki/Packet_delay_variation +[packet delay variation]: https://en.wikipedia.org/wiki/Packet_delay_variation ### Conexão -#### Wireless/Sem fio +#### Sem fio -_Scrcpy_ usa `adb` para se comunicar com o dispositivo, e `adb` pode [conectar-se] à um dispositivo via TCP/IP: +_Scrcpy_ usa `adb` para se comunicar com o dispositivo, e `adb` pode [conectar-se][connect] a um +dispositivo via TCP/IP: + +1. Conecte o dispositivo no mesmo Wi-Fi do seu computador. +2. Pegue o endereço IP do seu dispositivo, em Configurações → Sobre o telefone → Status, ou + executando este comando: + + ```bash + adb shell ip route | awk '{print $9}' + ``` -1. Conecte o dispositivo a mesma rede Wi-Fi do seu computador. -2. Pegue o endereço de IP do seu dispositivo (Em Configurações → Sobre o Telefone → Status). 3. Ative o adb via TCP/IP no seu dispositivo: `adb tcpip 5555`. -4. Desplugue seu dispositivo. -5. Conecte-se ao seu dispositivo: `adb connect DEVICE_IP:5555` _(substitua o `DEVICE_IP`)_. +4. Desconecte seu dispositivo. +5. Conecte-se ao seu dispositivo: `adb connect DEVICE_IP:5555` _(substitua `DEVICE_IP`)_. 6. Execute `scrcpy` como de costume. Pode ser útil diminuir o bit-rate e a resolução: ```bash scrcpy --bit-rate 2M --max-size 800 -scrcpy -b2M -m800 # versão reduzida +scrcpy -b2M -m800 # versão curta ``` -[conectar-se]: https://developer.android.com/studio/command-line/adb.html#wireless +[connect]: https://developer.android.com/studio/command-line/adb.html#wireless -#### N-dispositivos +#### Múltiplos dispositivos -Se alguns dispositivos estão listados em `adb devices`, você precisa especificar o _serial_: +Se vários dispositivos são listados em `adb devices`, você deve especificar o _serial_: ```bash scrcpy --serial 0123456789abcdef -scrcpy -s 0123456789abcdef # versão reduzida +scrcpy -s 0123456789abcdef # versão curta ``` Se o dispositivo está conectado via TCP/IP: ```bash scrcpy --serial 192.168.0.1:5555 -scrcpy -s 192.168.0.1:5555 # versão reduzida +scrcpy -s 192.168.0.1:5555 # versão curta ``` -Você pode iniciar algumas instâncias do _scrcpy_ para alguns dispositivos. +Você pode iniciar várias instâncias do _scrcpy_ para vários dispositivos. -#### Conexão via SSH +#### Iniciar automaticamente quando dispositivo é conectado -Para conectar-se à um dispositivo remoto, é possível se conectar um cliente local `adb` à um servidor `adb` remoto (contanto que eles usem a mesma versão do protocolo _adb_): +Você pode usar [AutoAdb]: ```bash -adb kill-server # encerra o servidor local na 5037 +autoadb scrcpy -s '{}' +``` + +[AutoAdb]: https://github.com/rom1v/autoadb + +#### Túnel SSH + +Para conectar-se a um dispositivo remoto, é possível conectar um cliente `adb` local a +um servidor `adb` remoto (contanto que eles usem a mesma versão do protocolo +_adb_): + +```bash +adb kill-server # encerra o servidor adb local em 5037 ssh -CN -L5037:localhost:5037 -R27183:localhost:27183 your_remote_computer -# mantém isso aberto +# mantenha isso aberto ``` De outro terminal: @@ -261,17 +325,33 @@ De outro terminal: scrcpy ``` -Igual para conexões sem fio, pode ser útil reduzir a qualidade: +Para evitar ativar o encaminhamento de porta remota, você pode forçar uma conexão +de encaminhamento (note o `-L` em vez de `-R`): + +```bash +adb kill-server # encerra o servidor adb local em 5037 +ssh -CN -L5037:localhost:5037 -L27183:localhost:27183 your_remote_computer +# mantenha isso aberto +``` + +De outro terminal: + +```bash +scrcpy --force-adb-forward +``` + + +Igual a conexões sem fio, pode ser útil reduzir a qualidade: ``` scrcpy -b2M -m800 --max-fps 15 ``` -### Configurações de Janela +### Configuração de janela #### Título -Por padrão, o título da janela é o modelo do dispositivo. Isto pode ser mudado: +Por padrão, o título da janela é o modelo do dispositivo. Isso pode ser mudado: ```bash scrcpy --window-title 'Meu dispositivo' @@ -287,15 +367,15 @@ scrcpy --window-x 100 --window-y 100 --window-width 800 --window-height 600 #### Sem bordas -Para desativar decorações da janela: +Para desativar decorações de janela: ```bash scrcpy --window-borderless ``` -#### Sempre visível +#### Sempre no topo -Para manter a janela do scrcpy sempre visível: +Para manter a janela do scrcpy sempre no topo: ```bash scrcpy --always-on-top @@ -307,41 +387,117 @@ A aplicação pode ser iniciada diretamente em tela cheia: ```bash scrcpy --fullscreen -scrcpy -f # versão reduzida +scrcpy -f # versão curta ``` -Tela cheia pode ser alternada dinamicamente com `Ctrl`+`f`. +Tela cheia pode ser alternada dinamicamente com MOD+f. + +#### Rotação + +A janela pode ser rotacionada: + +```bash +scrcpy --rotation 1 +``` + +Valores possíveis são: + - `0`: sem rotação + - `1`: 90 graus sentido anti-horário + - `2`: 180 graus + - `3`: 90 graus sentido horário + +A rotação também pode ser mudada dinamicamente com MOD+ +_(esquerda)_ e MOD+ _(direita)_. + +Note que _scrcpy_ controla 3 rotações diferentes: + - MOD+r requisita ao dispositivo para mudar entre retrato + e paisagem (a aplicação em execução pode se recusar, se ela não suporta a + orientação requisitada). + - [`--lock-video-orientation`](#travar-orientação-do-vídeo) muda a orientação de + espelhamento (a orientação do vídeo enviado pelo dispositivo para o + computador). Isso afeta a gravação. + - `--rotation` (ou MOD+/MOD+) + rotaciona apenas o conteúdo da janela. Isso afeta apenas a exibição, não a + gravação. ### Outras opções de espelhamento #### Apenas leitura -Para desativar controles (tudo que possa interagir com o dispositivo: teclas de entrada, eventos de mouse, arrastar e soltar arquivos): +Para desativar controles (tudo que possa interagir com o dispositivo: teclas de entrada, +eventos de mouse, arrastar e soltar arquivos): ```bash scrcpy --no-control scrcpy -n ``` -#### Desligar a tela +#### Display -É possível desligar a tela do dispositivo durante o início do espelhamento com uma opção de linha de comando: +Se vários displays estão disponíveis, é possível selecionar o display para +espelhar: + +```bash +scrcpy --display 1 +``` + +A lista de IDs dos displays pode ser obtida por: + +``` +adb shell dumpsys display # busca "mDisplayId=" na saída +``` + +O display secundário pode apenas ser controlado se o dispositivo roda pelo menos Android +10 (caso contrário é espelhado como apenas leitura). + + +#### Permanecer ativo + +Para evitar que o dispositivo seja suspenso após um delay quando o dispositivo é conectado: + +```bash +scrcpy --stay-awake +scrcpy -w +``` + +O estado inicial é restaurado quando o scrcpy é fechado. + + +#### Desligar tela + +É possível desligar a tela do dispositivo durante o início do espelhamento com uma +opção de linha de comando: ```bash scrcpy --turn-screen-off scrcpy -S ``` -Ou apertando `Ctrl`+`o` durante qualquer momento. +Ou apertando MOD+o a qualquer momento. -Para ligar novamente, pressione `POWER` (ou `Ctrl`+`p`). +Para ligar novamente, pressione MOD+Shift+o. -#### Frames expirados de renderização +No Android, o botão de `POWER` sempre liga a tela. Por conveniência, se +`POWER` é enviado via scrcpy (via clique-direito ou MOD+p), ele +forçará a desligar a tela após um delay pequeno (numa base de melhor esforço). +O botão `POWER` físico ainda causará a tela ser ligada. -Por padrão, para minimizar a latência, _scrcpy_ sempre renderiza o último frame decodificado disponível e descarta o anterior. +Também pode ser útil evitar que o dispositivo seja suspenso: -Para forçar a renderização de todos os frames ( com o custo de aumento de latência), use: +```bash +scrcpy --turn-screen-off --stay-awake +scrcpy -Sw +``` + + +#### Renderizar frames expirados + +Por padrão, para minimizar a latência, _scrcpy_ sempre renderiza o último frame decodificado +disponível, e descarta o anterior. + +Para forçar a renderização de todos os frames (com o custo de um possível aumento de +latência), use: ```bash scrcpy --render-expired-frames @@ -349,11 +505,13 @@ scrcpy --render-expired-frames #### Mostrar toques -Para apresentações, pode ser útil mostrar toques físicos(dispositivo físico). +Para apresentações, pode ser útil mostrar toques físicos (no dispositivo +físico). -Android fornece esta funcionalidade nas _Opções do Desenvolvedor_. +Android fornece esta funcionalidade nas _Opções do desenvolvedor_. -_Scrcpy_ fornece esta opção de ativar esta funcionalidade no início e desativar no encerramento: +_Scrcpy_ fornece esta opção de ativar esta funcionalidade no início e restaurar o +valor inicial no encerramento: ```bash scrcpy --show-touches @@ -363,59 +521,137 @@ scrcpy -t Note que isto mostra apenas toques _físicos_ (com o dedo no dispositivo). +#### Desativar descanso de tela + +Por padrão, scrcpy não evita que o descanso de tela rode no computador. + +Para desativá-lo: + +```bash +scrcpy --disable-screensaver +``` + + ### Controle de entrada #### Rotacionar a tela do dispositivo -Pressione `Ctrl`+`r` para mudar entre os modos Retrato e Paisagem. +Pressione MOD+r para mudar entre os modos retrato e +paisagem. -Note que só será rotacionado se a aplicação em primeiro plano tiver suporte para o modo requisitado. +Note que só será rotacionado se a aplicação em primeiro plano suportar a +orientação requisitada. -#### Copiar-Colar +#### Copiar-colar -É possível sincronizar áreas de transferência entre computador e o dispositivo, -para ambas direções: +Sempre que a área de transferência do Android muda, é automaticamente sincronizada com a +área de transferência do computador. - - `Ctrl`+`c` copia a área de transferência do dispositivo para a área de trasferência do computador; - - `Ctrl`+`Shift`+`v` copia a área de transferência do computador para a área de transferência do dispositivo; - - `Ctrl`+`v` _cola_ a área de transferência do computador como uma sequência de eventos de texto (mas - quebra caracteres não-ASCII). +Qualquer atalho com Ctrl é encaminhado para o dispositivo. Em particular: + - Ctrl+c tipicamente copia + - Ctrl+x tipicamente recorta + - Ctrl+v tipicamente cola (após a sincronização de área de transferência + computador-para-dispositivo) -#### Preferências de injeção de texto +Isso tipicamente funciona como esperado. -Existe dois tipos de [eventos][textevents] gerados ao digitar um texto: - - _eventos de teclas_, sinalizando que a tecla foi pressionada ou solta; +O comportamento de fato depende da aplicação ativa, no entanto. Por exemplo, +_Termux_ envia SIGINT com Ctrl+c, e _K-9 Mail_ +compõe uma nova mensagem. + +Para copiar, recortar e colar em tais casos (mas apenas suportado no Android >= 7): + - MOD+c injeta `COPY` + - MOD+x injeta `CUT` + - MOD+v injeta `PASTE` (após a sincronização de área de transferência + computador-para-dispositivo) + +Em adição, MOD+Shift+v permite injetar o +texto da área de transferência do computador como uma sequência de eventos de tecla. Isso é útil quando o +componente não aceita colar texto (por exemplo no _Termux_), mas pode +quebrar conteúdo não-ASCII. + +**ADVERTÊNCIA:** Colar a área de transferência do computador para o dispositivo (tanto via +Ctrl+v quanto MOD+v) copia o conteúdo +para a área de transferência do dispositivo. Como consequência, qualquer aplicação Android pode ler +o seu conteúdo. Você deve evitar colar conteúdo sensível (como senhas) dessa +forma. + +Alguns dispositivos não se comportam como esperado quando a área de transferência é definida +programaticamente. Uma opção `--legacy-paste` é fornecida para mudar o comportamento +de Ctrl+v e MOD+v para que eles +também injetem o texto da área de transferência do computador como uma sequência de eventos de tecla (da mesma +forma que MOD+Shift+v). + +#### Pinçar para dar zoom + +Para simular "pinçar para dar zoom": Ctrl+_clicar-e-mover_. + +Mais precisamente, segure Ctrl enquanto pressiona o botão de clique-esquerdo. Até que +o botão de clique-esquerdo seja liberado, todos os movimentos do mouse ampliar e rotacionam o +conteúdo (se suportado pelo app) relativo ao centro da tela. + +Concretamente, scrcpy gera eventos adicionais de toque de um "dedo virtual" em +uma posição invertida em relação ao centro da tela. + + +#### Preferência de injeção de texto + +Existem dois tipos de [eventos][textevents] gerados ao digitar um texto: + - _eventos de tecla_, sinalizando que a tecla foi pressionada ou solta; - _eventos de texto_, sinalizando que o texto foi inserido. -Por padrão, letras são injetadas usando eventos de teclas, assim teclados comportam-se -como esperado em jogos (normalmente para tecladas WASD) +Por padrão, letras são injetadas usando eventos de tecla, assim o teclado comporta-se +como esperado em jogos (normalmente para teclas WASD). -Mas isto pode [causar problemas][prefertext]. Se você encontrar tal problema, -pode evitá-lo usando: +Mas isso pode [causar problemas][prefertext]. Se você encontrar tal problema, você +pode evitá-lo com: ```bash scrcpy --prefer-text ``` -(mas isto vai quebrar o comportamento do teclado em jogos) +(mas isso vai quebrar o comportamento do teclado em jogos) [textevents]: https://blog.rom1v.com/2018/03/introducing-scrcpy/#handle-text-input [prefertext]: https://github.com/Genymobile/scrcpy/issues/650#issuecomment-512945343 -### Transferência de arquivo +#### Repetir tecla + +Por padrão, segurar uma tecla gera eventos de tecla repetidos. Isso pode causar +problemas de performance em alguns jogos, onde esses eventos são inúteis de qualquer forma. + +Para evitar o encaminhamento eventos de tecla repetidos: + +```bash +scrcpy --no-key-repeat +``` + + +#### Clique-direito e clique-do-meio + +Por padrão, clique-direito dispara BACK (ou POWER) e clique-do-meio dispara +HOME. Para desabilitar esses atalhos e encaminhar os cliques para o dispositivo: + +```bash +scrcpy --forward-all-clicks +``` + + +### Soltar arquivo #### Instalar APK -Para instalar um APK, arraste e solte o arquivo APK(com extensão `.apk`) na janela _scrcpy_. +Para instalar um APK, arraste e solte o arquivo APK (com extensão `.apk`) na janela +_scrcpy_. Não existe feedback visual, um log é imprimido no console. -#### Enviar arquivo para o dispositivo +#### Enviar arquivo para dispositivo -Para enviar um arquivo para o diretório `/sdcard/` no dispositivo, arraste e solte um arquivo não APK para a janela do -_scrcpy_. +Para enviar um arquivo para `/sdcard/` no dispositivo, arraste e solte um arquivo (não-APK) para a +janela do _scrcpy_. Não existe feedback visual, um log é imprimido no console. @@ -428,45 +664,73 @@ scrcpy --push-target /sdcard/foo/bar/ ### Encaminhamento de áudio -Áudio não é encaminhando pelo _scrcpy_. Use [USBaudio] (Apenas linux). +Áudio não é encaminhado pelo _scrcpy_. Use [sndcpy]. Também veja [issue #14]. -[USBaudio]: https://github.com/rom1v/usbaudio +[sndcpy]: https://github.com/rom1v/sndcpy [issue #14]: https://github.com/Genymobile/scrcpy/issues/14 ## Atalhos - | Ação | Atalho | Atalho (macOS) - | ------------------------------------------------------------- |:------------------------------- |:----------------------------- - | Alternar para modo de tela cheia | `Ctrl`+`f` | `Cmd`+`f` - | Redimensionar janela para pixel-perfect(Escala 1:1) | `Ctrl`+`g` | `Cmd`+`g` - | Redimensionar janela para tirar as bordas pretas | `Ctrl`+`x` \| _Clique-duplo¹_ | `Cmd`+`x` \| _Clique-duplo¹_ - | Clicar em `HOME` | `Ctrl`+`h` \| _Clique-central_ | `Ctrl`+`h` \| _Clique-central_ - | Clicar em `BACK` | `Ctrl`+`b` \| _Clique-direito²_ | `Cmd`+`b` \| _Clique-direito²_ - | Clicar em `APP_SWITCH` | `Ctrl`+`s` | `Cmd`+`s` - | Clicar em `MENU` | `Ctrl`+`m` | `Ctrl`+`m` - | Clicar em `VOLUME_UP` | `Ctrl`+`↑` _(cima)_ | `Cmd`+`↑` _(cima)_ - | Clicar em `VOLUME_DOWN` | `Ctrl`+`↓` _(baixo)_ | `Cmd`+`↓` _(baixo)_ - | Clicar em `POWER` | `Ctrl`+`p` | `Cmd`+`p` - | Ligar | _Clique-direito²_ | _Clique-direito²_ - | Desligar a tela do dispositivo | `Ctrl`+`o` | `Cmd`+`o` - | Rotacionar tela do dispositivo | `Ctrl`+`r` | `Cmd`+`r` - | Expandir painel de notificação | `Ctrl`+`n` | `Cmd`+`n` - | Esconder painel de notificação | `Ctrl`+`Shift`+`n` | `Cmd`+`Shift`+`n` - | Copiar área de transferência do dispositivo para o computador | `Ctrl`+`c` | `Cmd`+`c` - | Colar área de transferência do computador para o dispositivo | `Ctrl`+`v` | `Cmd`+`v` - | Copiar área de transferência do computador para dispositivo | `Ctrl`+`Shift`+`v` | `Cmd`+`Shift`+`v` - | Ativar/desativar contador de FPS(Frames por segundo) | `Ctrl`+`i` | `Cmd`+`i` +Na lista a seguir, MOD é o modificador de atalho. Por padrão, é +Alt (esquerdo) ou Super (esquerdo). + +Ele pode ser mudado usando `--shortcut-mod`. Possíveis teclas são `lctrl`, `rctrl`, +`lalt`, `ralt`, `lsuper` e `rsuper`. Por exemplo: + +```bash +# usar RCtrl para atalhos +scrcpy --shortcut-mod=rctrl + +# usar tanto LCtrl+LAlt quanto LSuper para atalhos +scrcpy --shortcut-mod=lctrl+lalt,lsuper +``` + +_[Super] é tipicamente a tecla Windows ou Cmd._ + +[Super]: https://en.wikipedia.org/wiki/Super_key_(keyboard_button) + + | Ação | Atalho + | ------------------------------------------- |:----------------------------- + | Mudar modo de tela cheia | MOD+f + | Rotacionar display para esquerda | MOD+ _(esquerda)_ + | Rotacionar display para direita | MOD+ _(direita)_ + | Redimensionar janela para 1:1 (pixel-perfect) | MOD+g + | Redimensionar janela para remover bordas pretas | MOD+w \| _Clique-duplo¹_ + | Clicar em `HOME` | MOD+h \| _Clique-do-meio_ + | Clicar em `BACK` | MOD+b \| _Clique-direito²_ + | Clicar em `APP_SWITCH` | MOD+s + | Clicar em `MENU` (desbloquear tela | MOD+m + | Clicar em `VOLUME_UP` | MOD+ _(cima)_ + | Clicar em `VOLUME_DOWN` | MOD+ _(baixo)_ + | Clicar em `POWER` | MOD+p + | Ligar | _Clique-direito²_ + | Desligar tela do dispositivo (continuar espelhando) | MOD+o + | Ligar tela do dispositivo | MOD+Shift+o + | Rotacionar tela do dispositivo | MOD+r + | Expandir painel de notificação | MOD+n + | Colapsar painel de notificação | MOD+Shift+n + | Copiar para área de transferência³ | MOD+c + | Recortar para área de transferência³ | MOD+x + | Sincronizar áreas de transferência e colar³ | MOD+v + | Injetar texto da área de transferência do computador | MOD+Shift+v + | Ativar/desativar contador de FPS (em stdout) | MOD+i + | Pinçar para dar zoom | Ctrl+_clicar-e-mover_ _¹Clique-duplo em bordas pretas para removê-las._ -_²Botão direito liga a tela se ela estiver desligada, clique BACK para o contrário._ +_²Clique-direito liga a tela se ela estiver desligada, pressiona BACK caso contrário._ +_³Apenas em Android >= 7._ + +Todos os atalhos Ctrl+_tecla_ são encaminhados para o dispositivo, para que eles sejam +tratados pela aplicação ativa. ## Caminhos personalizados -Para usar um binário específico _adb_, configure seu caminho na variável de ambiente `ADB`: +Para usar um binário _adb_ específico, configure seu caminho na variável de ambiente +`ADB`: ADB=/caminho/para/adb scrcpy @@ -478,7 +742,7 @@ Para sobrepor o caminho do arquivo `scrcpy-server`, configure seu caminho em ## Por quê _scrcpy_? -Um colega me desafiou a encontrar um nome impronunciável como [gnirehtet]. +Um colega me desafiou a encontrar um nome tão impronunciável quanto [gnirehtet]. [`strcpy`] copia uma **str**ing; `scrcpy` copia uma **scr**een. @@ -495,12 +759,12 @@ Veja [BUILD]. ## Problemas comuns -Veja [FAQ](FAQ.md). +Veja o [FAQ](FAQ.md). ## Desenvolvedores -Leia a [developers page]. +Leia a [página dos desenvolvedores][developers page]. [developers page]: DEVELOP.md From ce43fad645d4eb30f322dbeb50d5197601564931 Mon Sep 17 00:00:00 2001 From: Simon Chan Date: Tue, 12 Jan 2021 13:54:42 +0800 Subject: [PATCH 268/450] Update README.zh-Hans to v1.17 PR #2029 Reviewed-by: Win7GM Signed-off-by: Romain Vimont --- README.md | 2 +- README.zh-Hans.md | 443 +++++++++++++++++++++++----------------------- 2 files changed, 227 insertions(+), 218 deletions(-) diff --git a/README.md b/README.md index c38ac2cf..759e7920 100644 --- a/README.md +++ b/README.md @@ -801,7 +801,7 @@ This README is available in other languages: - [Indonesian (Indonesia, `id`) - v1.16](README.id.md) - [한국어 (Korean, `ko`) - v1.11](README.ko.md) - [português brasileiro (Brazilian Portuguese, `pt-BR`) - v1.17](README.pt-br.md) -- [简体中文 (Simplified Chinese, `zh-Hans`) - v1.16](README.zh-Hans.md) +- [简体中文 (Simplified Chinese, `zh-Hans`) - v1.17](README.zh-Hans.md) - [繁體中文 (Traditional Chinese, `zh-Hant`) - v1.15](README.zh-Hant.md) Only this README file is guaranteed to be up-to-date. diff --git a/README.zh-Hans.md b/README.zh-Hans.md index 3774c5d9..e758c18d 100644 --- a/README.zh-Hans.md +++ b/README.zh-Hans.md @@ -2,115 +2,111 @@ _Only the original [README](README.md) is guaranteed to be up-to-date._ 只有原版的[README](README.md)会保持最新。 -本文根据[479d10d]进行翻译。 +本文根据[ed130e05]进行翻译。 -[479d10d]: https://github.com/Genymobile/scrcpy/commit/479d10dc22b70272187e0963c6ad24d754a669a2#diff-04c6e90faac2675aa89e2176d2eec7d8 +[ed130e05]: https://github.com/Genymobile/scrcpy/blob/ed130e05d55615d6014d93f15cfcb92ad62b01d8/README.md +# scrcpy (v1.17) - -# scrcpy (v1.16) - -本应用程序可以通过USB(或 [TCP/IP][article-tcpip] )连接用于显示或控制安卓设备。这不需要获取 _root_ 权限。 - -该应用程序可以在 _GNU/Linux_, _Windows_ 和 _macOS_ 环境下运行。 - -[article-tcpip]:https://www.genymotion.com/blog/open-source-project-scrcpy-now-works-wirelessly/ +本应用程序可以显示并控制通过 USB (或 [TCP/IP][article-tcpip]) 连接的安卓设备,且不需要任何 _root_ 权限。本程序支持 _GNU/Linux_, _Windows_ 和 _macOS_。 ![screenshot](assets/screenshot-debian-600.jpg) 它专注于: - - **轻量** (原生,仅显示设备屏幕) - - **性能** (30~60fps) - - **质量** (分辨率可达1920x1080或更高) - - **低延迟** (35-70ms) - - **快速启动** (数秒内即能开始显示) - - **无侵入性** (不需要在安卓设备上安装任何程序) + - **轻量** (原生,仅显示设备屏幕) + - **性能** (30~60fps) + - **质量** (分辨率可达 1920×1080 或更高) + - **低延迟** ([35~70ms][lowlatency]) + - **快速启动** (最快 1 秒内即可显示第一帧) + - **无侵入性** (不会在设备上遗留任何程序) + +[lowlatency]: https://github.com/Genymobile/scrcpy/pull/646 -## 使用要求 +## 系统要求 -安卓设备系统版本需要在Android 5.0(API 21)或以上。 +安卓设备最低需要支持 API 21 (Android 5.0)。 -确保您在设备上开启了[adb调试]。 +确保设备已[开启 adb 调试][enable-adb]。 -[adb调试]: https://developer.android.com/studio/command-line/adb.html#Enabling +[enable-adb]: https://developer.android.com/studio/command-line/adb.html#Enabling -在某些设备上,你还需要开启[额外的选项]以用鼠标和键盘进行控制。 +在某些设备上,还需要开启[额外的选项][control]以使用鼠标和键盘进行控制。 -[额外的选项]: https://github.com/Genymobile/scrcpy/issues/70#issuecomment-373286323 +[control]: https://github.com/Genymobile/scrcpy/issues/70#issuecomment-373286323 -## 获取scrcpy +## 获取本程序 Packaging status ### Linux -在Debian(目前仅测试版和不稳定版,即 _testing_ 和 _sid_ 版本)和Ubuntu (20.04)上: +在 Debian (目前仅支持 _testing_ 和 _sid_ 分支) 和Ubuntu (20.04) 上: ``` apt install scrcpy ``` -[Snap]包也是可用的: [`scrcpy`][snap-link]. +我们也提供 [Snap] 包: [`scrcpy`][snap-link]。 [snap-link]: https://snapstats.org/snaps/scrcpy [snap]: https://en.wikipedia.org/wiki/Snappy_(package_manager) -对于Fedora用户,我们提供[COPR]包: [`scrcpy`][copr-link]. +对 Fedora 我们提供 [COPR] 包: [`scrcpy`][copr-link]。 [COPR]: https://fedoraproject.org/wiki/Category:Copr [copr-link]: https://copr.fedorainfracloud.org/coprs/zeno/scrcpy/ -对于Arch Linux用户,我们提供[AUR]包: [`scrcpy`][aur-link]. +对 Arch Linux 我们提供 [AUR] 包: [`scrcpy`][aur-link]。 [AUR]: https://wiki.archlinux.org/index.php/Arch_User_Repository [aur-link]: https://aur.archlinux.org/packages/scrcpy/ -对于Gentoo用户,我们提供[Ebuild]包:[`scrcpy/`][ebuild-link]. +对 Gentoo 我们提供 [Ebuild] 包:[`scrcpy/`][ebuild-link]。 [Ebuild]: https://wiki.gentoo.org/wiki/Ebuild [ebuild-link]: https://github.com/maggu2810/maggu2810-overlay/tree/master/app-mobilephone/scrcpy -您也可以[自行编译][编译](不必担心,这并不困难)。 +您也可以[自行构建][BUILD] (不必担心,这并不困难)。 ### Windows -在Windows上,简便起见,我们准备了包含所有依赖项(包括adb)的程序包。 +在 Windows 上,简便起见,我们提供包含了所有依赖 (包括 `adb`) 的预编译包。 - - [`scrcpy-win64-v1.16.zip`][direct-win64] - _(SHA-256: 3f30dc5db1a2f95c2b40a0f5de91ec1642d9f53799250a8c529bc882bc0918f0)_ + - [`scrcpy-win64-v1.17.zip`][direct-win64] + _(SHA-256: 8b9e57993c707367ed10ebfe0e1ef563c7a29d9af4a355cd8b6a52a317c73eea)_ -[direct-win64]: https://github.com/Genymobile/scrcpy/releases/download/v1.16/scrcpy-win64-v1.16.zip +[direct-win64]: https://github.com/Genymobile/scrcpy/releases/download/v1.17/scrcpy-win64-v1.17.zip -您也可以在[Chocolatey]下载: +也可以使用 [Chocolatey]: [Chocolatey]: https://chocolatey.org/ ```bash choco install scrcpy -choco install adb # 如果你没有adb +choco install adb # 如果还没有 adb ``` -也可以使用 [Scoop]: +或者 [Scoop]: ```bash scoop install scrcpy -scoop install adb # 如果你没有adb +scoop install adb # 如果还没有 adb ``` [Scoop]: https://scoop.sh -您也可以[自行编译][编译]。 +您也可以[自行构建][BUILD]。 ### macOS -您可以使用[Homebrew]下载scrcpy。直接安装就可以了: +本程序已发布到 [Homebrew]。直接安装即可: [Homebrew]: https://brew.sh/ @@ -118,24 +114,28 @@ scoop install adb # 如果你没有adb brew install scrcpy ``` -您需要 `adb`以使用scrcpy,并且它需要可以通过 `PATH`被访问。如果您没有: +你还需要在 `PATH` 内有 `adb`。如果还没有: ```bash +# Homebrew >= 2.6.0 +brew install --cask android-platform-tools + +# Homebrew < 2.6.0 brew cask install android-platform-tools ``` -您也可以[自行编译][编译]。 +您也可以[自行构建][BUILD]。 -## 运行scrcpy +## 运行 -用USB链接电脑和安卓设备,并执行: +连接安卓设备,然后执行: ```bash scrcpy ``` -支持带命令行参数执行,查看参数列表: +本程序支持命令行参数,查看参数列表: ```bash scrcpy --help @@ -143,111 +143,129 @@ scrcpy --help ## 功能介绍 -### 画面设置 +### 捕获设置 -#### 缩小分辨率 +#### 降低分辨率 -有时候,将设备屏幕镜像分辨率降低可以有效地提升性能。 +有时候,可以通过降低镜像的分辨率来提高性能。 -我们可以将高度和宽度都限制在一定大小内(如 1024): +要同时限制宽度和高度到某个值 (例如 1024): ```bash scrcpy --max-size 1024 -scrcpy -m 1024 # short version +scrcpy -m 1024 # 简写 ``` -较短的一边会被按比例缩小以保持设备的显示比例。 -这样,1920x1080 的设备会以 1024x576 的分辨率显示。 +另一边会被按比例缩小以保持设备的显示比例。这样,1920×1080 分辨率的设备会以 1024×576 的分辨率进行镜像。 -#### 修改画面比特率 +#### 修改码率 -默认的比特率是8Mbps。如果要改变画面的比特率 (比如说改成2Mbps): +默认码率是 8Mbps。要改变视频的码率 (例如改为 2Mbps): ```bash scrcpy --bit-rate 2M -scrcpy -b 2M # short version +scrcpy -b 2M # 简写 ``` -#### 限制画面帧率 +#### 限制帧率 -画面的帧率可以通过下面的命令被限制: +要限制捕获的帧率: ```bash scrcpy --max-fps 15 ``` -这个功能仅在Android 10和以后的版本被Android官方支持,但也有可能在更早的版本可用。 +本功能从 Android 10 开始才被官方支持,但在一些旧版本中也能生效。 #### 画面裁剪 -设备画面可在裁切后进行镜像,以显示部分屏幕。 +可以对设备屏幕进行裁剪,只镜像屏幕的一部分。 -这项功能可以用于,例如,只显示Oculus Go的一只眼睛。 +例如可以只镜像 Oculus Go 的一只眼睛。 ```bash -scrcpy --crop 1224:1440:0:0 # 1224x1440 at offset (0,0) +scrcpy --crop 1224:1440:0:0 # 以 (0,0) 为原点的 1224x1440 像素 ``` -如果`--max-size`在同时被指定,分辨率的改变将在画面裁切后进行。 +如果同时指定了 `--max-size`,会先进行裁剪,再进行缩放。 -#### 锁定屏幕朝向 +#### 锁定屏幕方向 -可以使用如下命令锁定屏幕朝向: +要锁定镜像画面的方向: ```bash -scrcpy --lock-video-orientation 0 # 自然朝向 -scrcpy --lock-video-orientation 1 # 90° 逆时针旋转 +scrcpy --lock-video-orientation 0 # 自然方向 +scrcpy --lock-video-orientation 1 # 逆时针旋转 90° scrcpy --lock-video-orientation 2 # 180° -scrcpy --lock-video-orientation 3 # 90° 顺时针旋转 +scrcpy --lock-video-orientation 3 # 顺时针旋转 90° ``` -该设定影响录制。 +只影响录制的方向。 +[窗口可以独立旋转](#旋转)。 + + +#### 编码器 + +一些设备内置了多种编码器,但是有的编码器会导致问题或崩溃。可以手动选择其它编码器: + +```bash +scrcpy --encoder OMX.qcom.video.encoder.avc +``` + +要列出可用的编码器,可以指定一个不存在的编码器名称,错误信息中会包含所有的编码器: + +```bash +scrcpy --encoder _ +``` ### 屏幕录制 -可以在屏幕镜像的同时录制视频: +可以在镜像的同时录制视频: ```bash scrcpy --record file.mp4 scrcpy -r file.mkv ``` -在不开启屏幕镜像的同时录制: +仅录制,不显示镜像: ```bash scrcpy --no-display --record file.mp4 scrcpy -Nr file.mkv -# 按Ctrl+C以停止录制 +# 按 Ctrl+C 停止录制 ``` -在显示中“被跳过的帧”会被录制,虽然它们由于性能原因没有实时显示。 -在传输中每一帧都有 _时间戳_ ,所以 [包时延变化] 并不影响录制的文件。 +录制时会包含“被跳过的帧”,即使它们由于性能原因没有实时显示。设备会为每一帧打上 _时间戳_ ,所以 [包时延抖动][packet delay variation] 不会影响录制的文件。 -[包时延变化]: https://en.wikipedia.org/wiki/Packet_delay_variation +[packet delay variation]: https://en.wikipedia.org/wiki/Packet_delay_variation -### 连接方式 +### 连接 #### 无线 -_Scrcpy_ 使用`adb`来与安卓设备连接。同时,`adb`能够通过TCP/IP[连接]到安卓设备: +_Scrcpy_ 使用 `adb` 与设备通信,并且 `adb` 支持通过 TCP/IP [连接]到设备: -1. 将您的安卓设备和电脑连接至同一Wi-Fi。 -2. 获取安卓设备的IP地址(在设置-关于手机-状态信息)。 -3. 打开安卓设备的网络adb功能`adb tcpip 5555`。 -4. 将您的设备与电脑断开连接。 -5. 连接到您的设备:`adb connect DEVICE_IP:5555` _(用设备IP替换 `DEVICE_IP`)_. -6. 运行`scrcpy`。 +1. 将设备和电脑连接至同一 Wi-Fi。 +2. 打开 设置 → 关于手机 → 状态信息,获取设备的 IP 地址,也可以执行以下的命令: + ```bash + adb shell ip route | awk '{print $9}' + ``` -降低比特率和分辨率可能有助于性能: +3. 启用设备的网络 adb 功能 `adb tcpip 5555`。 +4. 断开设备的 USB 连接。 +5. 连接到您的设备:`adb connect DEVICE_IP:5555` _(将 `DEVICE_IP` 替换为设备 IP)_. +6. 正常运行 `scrcpy`。 + +可能需要降低码率和分辨率: ```bash scrcpy --bit-rate 2M --max-size 800 -scrcpy -b2M -m800 # short version +scrcpy -b2M -m800 # 简写 ``` [连接]: https://developer.android.com/studio/command-line/adb.html#wireless @@ -255,18 +273,18 @@ scrcpy -b2M -m800 # short version #### 多设备 -如果多个设备在执行`adb devices`后被列出,您必须指定设备的 _序列号_ : +如果 `adb devices` 列出了多个设备,您必须指定设备的 _序列号_ : ```bash scrcpy --serial 0123456789abcdef -scrcpy -s 0123456789abcdef # short version +scrcpy -s 0123456789abcdef # 简写 ``` -如果设备是通过TCP/IP方式连接到电脑的: +如果设备通过 TCP/IP 连接: ```bash scrcpy --serial 192.168.0.1:5555 -scrcpy -s 192.168.0.1:5555 # short version +scrcpy -s 192.168.0.1:5555 # 简写 ``` 您可以同时启动多个 _scrcpy_ 实例以同时显示多个设备的画面。 @@ -281,38 +299,38 @@ autoadb scrcpy -s '{}' [AutoAdb]: https://github.com/rom1v/autoadb -#### SSH 连接 +#### SSH 隧道 -本地的 adb 可以远程连接到另一个 adb 服务器(假设两者的adb版本相同),来远程连接到设备: +要远程连接到设备,可以将本地的 adb 客户端连接到远程的 adb 服务端 (需要两端的 _adb_ 协议版本相同): ```bash -adb kill-server # 关闭本地5037端口上的adb服务器 +adb kill-server # 关闭本地 5037 端口上的 adb 服务端 ssh -CN -L5037:localhost:5037 -R27183:localhost:27183 your_remote_computer # 保持该窗口开启 ``` -从另一个终端: +在另一个终端: ```bash scrcpy ``` -为了避免启动远程端口转发,你可以强制启动一个转发连接(注意`-L`和`-R`的区别: +若要不使用远程端口转发,可以强制使用正向连接 (注意 `-L` 和 `-R` 的区别): ```bash -adb kill-server # kill the local adb server on 5037 +adb kill-server # 关闭本地 5037 端口上的 adb 服务端 ssh -CN -L5037:localhost:5037 -L27183:localhost:27183 your_remote_computer # 保持该窗口开启 ``` -从另一个终端: +在另一个终端: ```bash scrcpy --force-adb-forward ``` -和无线网络连接类似,下列设置可能对改善性能有帮助: +类似无线网络连接,可能需要降低画面质量: ``` scrcpy -b2M -m800 --max-fps 15 @@ -322,7 +340,7 @@ scrcpy -b2M -m800 --max-fps 15 #### 标题 -窗口的标题默认为设备型号。您可以通过如下命令修改它: +窗口的标题默认为设备型号。可以通过如下命令修改: ```bash scrcpy --window-title 'My device' @@ -358,14 +376,14 @@ scrcpy --always-on-top ```bash scrcpy --fullscreen -scrcpy -f # short version +scrcpy -f # 简写 ``` -全屏状态可以通过MOD+f实时改变。 +全屏状态可以通过 MOD+f 随时切换。 #### 旋转 -通过如下命令,窗口可以旋转: +可以通过以下命令旋转窗口: ```bash scrcpy --rotation 1 @@ -373,27 +391,23 @@ scrcpy --rotation 1 可选的值有: - `0`: 无旋转 - - `1`: 逆时针旋转90° - - `2`: 旋转180° - - `3`: 顺时针旋转90° + - `1`: 逆时针旋转 90° + - `2`: 旋转 180° + - `3`: 顺时针旋转 90° -这同样可以使用MOD+ -_(左)_ 和 MOD+ _(右)_ 的快捷键实时更改。 +也可以使用 MOD+ _(左箭头)_ 和 MOD+ _(右箭头)_ 随时更改。 -需要注意的是, _scrcpy_ 控制三个不同的朝向: - - MOD+r 请求设备在竖屏和横屏之间切换(如果前台应用程序不支持所请求的朝向,可能会拒绝该请求)。 - - - `--lock-video-orientation` 改变镜像的朝向(设备镜像到电脑的画面朝向)。这会影响录制。 - - - `--rotation` (或MOD+/MOD+) - 只旋转窗口的画面。这只影响显示,不影响录制。 +需要注意的是, _scrcpy_ 有三个不同的方向: + - MOD+r 请求设备在竖屏和横屏之间切换 (如果前台应用程序不支持请求的朝向,可能会拒绝该请求)。 + - [`--lock-video-orientation`](#锁定屏幕方向) 改变镜像的朝向 (设备传输到电脑的画面的朝向)。这会影响录制。 + - `--rotation` (或 MOD+/MOD+) 只旋转窗口的内容。这只影响显示,不影响录制。 ### 其他镜像设置 #### 只读 -关闭电脑对设备的控制(如键盘输入、鼠标移动和文件传输): +禁用电脑对设备的控制 (如键盘输入、鼠标事件和文件拖放): ```bash scrcpy --no-control @@ -402,53 +416,49 @@ scrcpy -n #### 显示屏 -如果有多个显示屏可用,您可以选择特定显示屏进行镜像: +如果设备有多个显示屏,可以选择要镜像的显示屏: ```bash scrcpy --display 1 ``` -您可以通过如下命令找到显示屏的id: +可以通过如下命令列出所有显示屏的 id: ``` -adb shell dumpsys display # 在回显中搜索“mDisplayId=” +adb shell dumpsys display # 在输出中搜索 “mDisplayId=” ``` -第二显示屏可能只能在设备运行Android 10或以上的情况下被控制(它可能会在电脑上显示,但无法通过电脑操作)。 +控制第二显示屏需要设备运行 Android 10 或更高版本 (否则将在只读状态下镜像)。 #### 保持常亮 -防止设备在已连接的状态下休眠: +阻止设备在连接时休眠: ```bash scrcpy --stay-awake scrcpy -w ``` -程序关闭后,设备设置会恢复原样。 +程序关闭时会恢复设备原来的设置。 #### 关闭设备屏幕 -在启动屏幕镜像时,可以通过如下命令关闭设备的屏幕: +可以通过以下的命令行参数在关闭设备屏幕的状态下进行镜像: ```bash scrcpy --turn-screen-off scrcpy -S ``` -或者在需要的时候按MOD+o。 +或者在任何时候按 MOD+o。 -要重新打开屏幕的话,需要按MOD+Shift+o. +要重新打开屏幕,按下 MOD+Shift+o. -在Android上,`电源`按钮始终能把屏幕打开。 +在Android上,`电源` 按钮始终能把屏幕打开。为了方便,对于在 _scrcpy_ 中发出的 `电源` 事件 (通过鼠标右键或 MOD+p),会 (尽最大的努力) 在短暂的延迟后将屏幕关闭。设备上的 `电源` 按钮仍然能打开设备屏幕。 -为了方便,如果按下`电源`按钮的事件是通过 _scrcpy_ 发出的(通过点按鼠标右键或MOD+p),它会在短暂的延迟后将屏幕关闭。 - -物理的`电源`按钮仍然能打开设备屏幕。 - -同时,这项功能还能被用于防止设备休眠: +还可以同时阻止设备休眠: ```bash scrcpy --turn-screen-off --stay-awake @@ -456,11 +466,11 @@ scrcpy -Sw ``` -#### 渲染超时帧 +#### 渲染过期帧 -为了降低延迟, _scrcpy_ 默认渲染解码成功的最近一帧,并跳过前面任意帧。 +默认状态下,为了降低延迟, _scrcpy_ 永远渲染解码成功的最近一帧,并跳过前面任意帧。 -强制渲染所有帧(可能导致延迟变高): +强制渲染所有帧 (可能导致延迟变高): ```bash scrcpy --render-expired-frames @@ -468,9 +478,9 @@ scrcpy --render-expired-frames #### 显示触摸 -在展示时,有些时候可能会用到显示触摸点这项功能(在设备上显示)。 +在演示时,可能会需要显示物理触摸点 (在物理设备上的触摸点)。 -Android在 _开发者设置_ 中提供了这项功能。 +Android 在 _开发者选项_ 中提供了这项功能。 _Scrcpy_ 提供一个选项可以在启动时开启这项功能并在退出时恢复初始设置: @@ -479,12 +489,12 @@ scrcpy --show-touches scrcpy -t ``` -请注意这项功能只能显示 _物理_ 触摸(要用手在屏幕上触摸)。 +请注意这项功能只能显示 _物理_ 触摸 (用手指在屏幕上的触摸)。 #### 关闭屏保 -_Scrcpy_ 不会默认关闭屏幕保护。 +_Scrcpy_ 默认不会阻止电脑上开启的屏幕保护。 关闭屏幕保护: @@ -497,64 +507,58 @@ scrcpy --disable-screensaver #### 旋转设备屏幕 -使用MOD+r以在竖屏和横屏模式之间切换。 +使用 MOD+r 在竖屏和横屏模式之间切换。 需要注意的是,只有在前台应用程序支持所要求的模式时,才会进行切换。 -#### 复制黏贴 +#### 复制粘贴 -每次Android的剪贴板变化的时候,它都会被自动同步到电脑的剪贴板上。 +每次安卓的剪贴板变化时,其内容都会被自动同步到电脑的剪贴板上。 所有的 Ctrl 快捷键都会被转发至设备。其中: - - Ctrl+c 复制 - - Ctrl+x 剪切 - - Ctrl+v 黏贴 (在电脑到设备的剪贴板同步完成之后) + - Ctrl+c 通常执行复制 + - Ctrl+x 通常执行剪切 + - Ctrl+v 通常执行粘贴 (在电脑到设备的剪贴板同步完成之后) -这通常如您所期望的那样运作。 +大多数时候这些按键都会执行以上的功能。 -但实际的行为取决于设备上的前台程序。 -例如 _Termux_ 在Ctrl+c被按下时发送 SIGINT, -又如 _K-9 Mail_ 会新建一封新邮件。 +但实际的行为取决于设备上的前台程序。例如,_Termux_ 会在按下 Ctrl+c 时发送 SIGINT,又如 _K-9 Mail_ 会新建一封邮件。 -在这种情况下剪切复制黏贴(仅在Android >= 7时可用): - - MOD+c 注入 `COPY`(复制) - - MOD+x 注入 `CUT`(剪切) - - MOD+v 注入 `PASTE`(黏贴)(在电脑到设备的剪贴板同步完成之后) +要在这种情况下进行剪切,复制和粘贴 (仅支持 Android >= 7): + - MOD+c 注入 `COPY` (复制) + - MOD+x 注入 `CUT` (剪切) + - MOD+v 注入 `PASTE` (粘贴) (在电脑到设备的剪贴板同步完成之后) -另外,MOD+Shift+v可以将电脑的剪贴板内容转换为一串按键事件输入到设备。 -在应用程序不接受黏贴时(比如 _Termux_ ),这项功能可以排上一定的用场。 -需要注意的是,这项功能可能会导致非ASCII编码的内容出现错误。 +另外,MOD+Shift+v 会将电脑的剪贴板内容转换为一串按键事件输入到设备。在应用程序不接受粘贴时 (比如 _Termux_),这项功能可以派上一定的用场。不过这项功能可能会导致非 ASCII 编码的内容出现错误。 -**警告:** 将电脑剪贴板的内容黏贴至设备(无论是通过Ctrl+v还是MOD+v) -都需要将内容保存至设备的剪贴板。如此,任何一个应用程序都可以读取它。 -您应当避免将敏感内容通过这种方式传输(如密码)。 +**警告:** 将电脑剪贴板的内容粘贴至设备 (无论是通过 Ctrl+v 还是 MOD+v) 都会将内容复制到设备的剪贴板。如此,任何安卓应用程序都能读取到。您应避免将敏感内容 (如密码) 通过这种方式粘贴。 +一些设备不支持通过程序设置剪贴板。通过 `--legacy-paste` 选项可以修改 Ctrl+vMOD+v 的工作方式,使它们通过按键事件 (同 MOD+Shift+v) 来注入电脑剪贴板内容。 -#### 捏拉缩放 +#### 双指缩放 -模拟 “捏拉缩放”:Ctrl+_按住并移动鼠标_。 +模拟“双指缩放”:Ctrl+_按住并移动鼠标_。 -更准确的说,您需要在按住Ctrl的同时按住并移动鼠标。 -在鼠标左键松开之后,光标的任何操作都会相对于屏幕的中央进行。 +更准确的说,在按住鼠标左键时按住 Ctrl。直到松开鼠标左键,所有鼠标移动将以屏幕中心为原点,缩放或旋转内容 (如果应用支持)。 -具体来说, _scrcpy_ 使用“虚拟手指”以在相对于屏幕中央相反的位置产生触摸事件。 +实际上,_scrcpy_ 会在以屏幕中心对称的位置上生成由“虚拟手指”发出的额外触摸事件。 #### 文字注入偏好 打字的时候,系统会产生两种[事件][textevents]: - - _按键事件_ ,代表一个按键被按下/松开。 - - _文本事件_ ,代表一个文本被输入。 + - _按键事件_ ,代表一个按键被按下或松开。 + - _文本事件_ ,代表一个字符被输入。 -程序默认使用按键事件来输入字母。只有这样,键盘才会在游戏中正常运作(尤其WASD键)。 +程序默认使用按键事件来输入字母。只有这样,键盘才会在游戏中正常运作 (例如 WASD 键)。 -但这也有可能[造成问题][prefertext]。如果您遇到了这样的问题,您可以通过下列操作避免它: +但这也有可能[造成一些问题][prefertext]。如果您遇到了问题,可以通过以下方式避免: ```bash scrcpy --prefer-text ``` -(这会导致键盘在游戏中工作不正常) +(这会导致键盘在游戏中工作不正常) [textevents]: https://blog.rom1v.com/2018/03/introducing-scrcpy/#handle-text-input [prefertext]: https://github.com/Genymobile/scrcpy/issues/650#issuecomment-512945343 @@ -562,8 +566,7 @@ scrcpy --prefer-text #### 按键重复 -当你一直按着一个按键不放时,程序默认产生多个按键事件。 -在某些游戏中这可能会导致性能问题。 +默认状态下,按住一个按键不放会生成多个重复按键事件。在某些游戏中这可能会导致性能问题。 避免转发重复按键事件: @@ -572,18 +575,27 @@ scrcpy --no-key-repeat ``` -### 文件传输 +#### 右键和中键 + +默认状态下,右键会触发返回键 (或电源键),中键会触发 HOME 键。要禁用这些快捷键并把所有点击转发到设备: + +```bash +scrcpy --forward-all-clicks +``` + + +### 文件拖放 #### 安装APK -如果您要要安装APK,请拖放APK文件(文件名以`.apk`结尾)到 _scrcpy_ 窗口。 +将 APK 文件 (文件名以 `.apk` 结尾) 拖放到 _scrcpy_ 窗口来安装。 该操作在屏幕上不会出现任何变化,而会在控制台输出一条日志。 #### 将文件推送至设备 -如果您要推送文件到设备的 `/sdcard/`,请拖放文件至(不能是APK文件)_scrcpy_ 窗口。 +要推送文件到设备的 `/sdcard/`,将 (非 APK) 文件拖放至 _scrcpy_ 窗口。 该操作没有可见的响应,只会在控制台输出日志。 @@ -596,7 +608,7 @@ scrcpy --push-target /sdcard/foo/bar/ ### 音频转发 -_scrcpy_ 不支持音频。请使用 [sndcpy]. +_Scrcpy_ 不支持音频。请使用 [sndcpy]. 另外请阅读 [issue #14]。 @@ -604,93 +616,90 @@ _scrcpy_ 不支持音频。请使用 [sndcpy]. [issue #14]: https://github.com/Genymobile/scrcpy/issues/14 -## 热键 +## 快捷键 -在下列表格中, MOD 是热键的修饰键。 -默认是(左)Alt或者(左)Super。 +在以下列表中, MOD 是快捷键的修饰键。 +默认是 (左) Alt 或 (左) Super。 -您可以使用 `--shortcut-mod`后缀来修改它。可选的按键有`lctrl`、`rctrl`、 -`lalt`、`ralt`、`lsuper`和`rsuper`。如下例: +您可以使用 `--shortcut-mod` 来修改。可选的按键有 `lctrl`、`rctrl`、`lalt`、`ralt`、`lsuper` 和 `rsuper`。例如: ```bash -# 使用右侧的Ctrl键 +# 使用右 Ctrl 键 scrcpy --shortcut-mod=rctrl -# 使用左侧的Ctrl键、Alt键或Super键 +# 使用左 Ctrl 键 + 左 Alt 键,或 Super 键 scrcpy --shortcut-mod=lctrl+lalt,lsuper ``` -_一般来说,[Super]就是Windows或者Cmd。_ +_[Super] 键通常是指 WindowsCmd 键。_ [Super]: https://en.wikipedia.org/wiki/Super_key_(keyboard_button) - | 操作 | 快捷键 - | ------------------------------------------- |:----------------------------- - | 全屏 | MOD+f - | 向左旋转屏幕 | MOD+ _(左)_ - | 向右旋转屏幕 | MOD+ _(右)_ - | 将窗口大小重置为1:1 (像素优先) | MOD+g - | 将窗口大小重置为消除黑边 | MOD+w \| _双击¹_ - | 点按 `主屏幕` | MOD+h \| _点击鼠标中键_ - | 点按 `返回` | MOD+b \| _点击鼠标右键²_ - | 点按 `切换应用` | MOD+s - | 点按 `菜单` (解锁屏幕) | MOD+m - | 点按 `音量+` | MOD+ _(up)_ - | 点按 `音量-` | MOD+ _(down)_ - | 点按 `电源` | MOD+p - | 打开屏幕 | _点击鼠标右键²_ - | 关闭设备屏幕(但继续在电脑上显示) | MOD+o - | 打开设备屏幕 | MOD+Shift+o - | 旋转设备屏幕 | MOD+r - | 展开通知面板 | MOD+n - | 展开快捷操作 | MOD+Shift+n - | 复制到剪贴板³ | MOD+c - | 剪切到剪贴板³ | MOD+x - | 同步剪贴板并黏贴³ | MOD+v - | 导入电脑剪贴板文本 | MOD+Shift+v - | 打开/关闭FPS显示(在 stdout) | MOD+i - | 捏拉缩放 | Ctrl+_点按并移动鼠标_ + | 操作 | 快捷键 | + | --------------------------------- | :------------------------------------------- | + | 全屏 | MOD+f | + | 向左旋转屏幕 | MOD+ _(左箭头)_ | + | 向右旋转屏幕 | MOD+ _(右箭头)_ | + | 将窗口大小重置为1:1 (匹配像素) | MOD+g | + | 将窗口大小重置为消除黑边 | MOD+w \| _双击¹_ | + | 点按 `主屏幕` | MOD+h \| _鼠标中键_ | + | 点按 `返回` | MOD+b \| _鼠标右键²_ | + | 点按 `切换应用` | MOD+s | + | 点按 `菜单` (解锁屏幕) | MOD+m | + | 点按 `音量+` | MOD+ _(上箭头)_ | + | 点按 `音量-` | MOD+ _(下箭头)_ | + | 点按 `电源` | MOD+p | + | 打开屏幕 | _鼠标右键²_ | + | 关闭设备屏幕 (但继续在电脑上显示) | MOD+o | + | 打开设备屏幕 | MOD+Shift+o | + | 旋转设备屏幕 | MOD+r | + | 展开通知面板 | MOD+n | + | 收起通知面板 | MOD+Shift+n | + | 复制到剪贴板³ | MOD+c | + | 剪切到剪贴板³ | MOD+x | + | 同步剪贴板并粘贴³ | MOD+v | + | 注入电脑剪贴板文本 | MOD+Shift+v | + | 打开/关闭FPS显示 (在 stdout) | MOD+i | + | 捏拉缩放 | Ctrl+_按住并移动鼠标_ | -_¹双击黑色边界以关闭黑色边界_ -_²点击鼠标右键将在屏幕熄灭时点亮屏幕,其余情况则视为按下 返回键 。_ +_¹双击黑边可以去除黑边_ +_²点击鼠标右键将在屏幕熄灭时点亮屏幕,其余情况则视为按下返回键 。_ _³需要安卓版本 Android >= 7。_ -所有的 Ctrl+_按键_ 的热键都是被转发到设备进行处理的,所以实际上会由当前应用程序对其做出响应。 +所有的 Ctrl+_按键_ 的快捷键都会被转发到设备,所以会由当前应用程序进行处理。 ## 自定义路径 -为了使用您想使用的 _adb_ ,您可以在环境变量 -`ADB`中设置它的路径: +要使用指定的 _adb_ 二进制文件,可以设置环境变量 `ADB`: ADB=/path/to/adb scrcpy -如果需要覆盖`scrcpy-server`的路径,您可以在 -`SCRCPY_SERVER_PATH`中设置它。 +要覆盖 `scrcpy-server` 的路径,可以设置 `SCRCPY_SERVER_PATH`。 [useful]: https://github.com/Genymobile/scrcpy/issues/278#issuecomment-429330345 ## 为什么叫 _scrcpy_ ? -一个同事让我找出一个和[gnirehtet]一样难以发音的名字。 +一个同事让我找出一个和 [gnirehtet] 一样难以发音的名字。 -[`strcpy`] 可以复制**str**ing; `scrcpy` 可以复制**scr**een。 +[`strcpy`] 复制一个 **str**ing; `scrcpy` 复制一个 **scr**een。 [gnirehtet]: https://github.com/Genymobile/gnirehtet [`strcpy`]: http://man7.org/linux/man-pages/man3/strcpy.3.html -## 如何编译? +## 如何构建? -请查看[编译]。 +请查看[BUILD]。 -[编译]: BUILD.md +[BUILD]: BUILD.md ## 常见问题 -请查看[FAQ](FAQ.md). +请查看[FAQ](FAQ.md)。 ## 开发者 From ab912c23e756a8a97c48a0735b8027b570f65298 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 8 Jan 2021 19:24:51 +0100 Subject: [PATCH 269/450] Define feature test macros in common.h This enables necessary functions once for all. As a consequence, define common.h before any other header. --- app/src/adb.h | 4 ++-- app/src/cli.h | 3 ++- app/src/compat.h | 7 +++++++ app/src/control_msg.h | 3 ++- app/src/controller.h | 3 ++- app/src/decoder.h | 4 ++-- app/src/device.h | 3 ++- app/src/device_msg.h | 4 ++-- app/src/event_converter.h | 3 ++- app/src/file_handler.h | 3 ++- app/src/fps_counter.h | 4 ++-- app/src/input_manager.h | 3 ++- app/src/main.c | 3 ++- app/src/opengl.h | 4 ++-- app/src/receiver.h | 3 ++- app/src/recorder.h | 3 ++- app/src/scrcpy.h | 4 ++-- app/src/screen.h | 3 ++- app/src/server.h | 3 ++- app/src/stream.h | 3 ++- app/src/sys/unix/process.c | 11 ----------- app/src/tiny_xpm.h | 4 ++-- app/src/util/buffer_util.h | 4 ++-- app/src/util/cbuf.h | 4 ++-- app/src/util/lock.h | 3 ++- app/src/util/net.h | 4 ++-- app/src/util/process.h | 4 ++-- app/src/util/queue.h | 4 ++-- app/src/util/str_util.h | 4 ++-- app/src/video_buffer.h | 3 ++- app/tests/test_buffer_util.c | 2 ++ app/tests/test_cbuf.c | 2 ++ app/tests/test_cli.c | 3 ++- app/tests/test_control_msg_serialize.c | 2 ++ app/tests/test_device_msg_deserialize.c | 2 ++ app/tests/test_queue.c | 2 ++ app/tests/test_strutil.c | 2 ++ 37 files changed, 77 insertions(+), 53 deletions(-) diff --git a/app/src/adb.h b/app/src/adb.h index 453e3019..e27f34fa 100644 --- a/app/src/adb.h +++ b/app/src/adb.h @@ -1,11 +1,11 @@ #ifndef SC_ADB_H #define SC_ADB_H +#include "common.h" + #include #include -#include "common.h" - #include "util/process.h" process_t diff --git a/app/src/cli.h b/app/src/cli.h index 22bded79..419f156f 100644 --- a/app/src/cli.h +++ b/app/src/cli.h @@ -1,9 +1,10 @@ #ifndef SCRCPY_CLI_H #define SCRCPY_CLI_H +#include "common.h" + #include -#include "common.h" #include "scrcpy.h" struct scrcpy_cli_args { diff --git a/app/src/compat.h b/app/src/compat.h index 21f19214..5464589a 100644 --- a/app/src/compat.h +++ b/app/src/compat.h @@ -1,6 +1,13 @@ #ifndef COMPAT_H #define COMPAT_H +#define _POSIX_C_SOURCE 200809L +#define _XOPEN_SOURCE 700 +#define _GNU_SOURCE +#ifdef __APPLE__ +# define _DARWIN_C_SOURCE +#endif + #include #include #include diff --git a/app/src/control_msg.h b/app/src/control_msg.h index e23cc89c..20b2ef45 100644 --- a/app/src/control_msg.h +++ b/app/src/control_msg.h @@ -1,11 +1,12 @@ #ifndef CONTROLMSG_H #define CONTROLMSG_H +#include "common.h" + #include #include #include -#include "common.h" #include "android/input.h" #include "android/keycodes.h" #include "coords.h" diff --git a/app/src/controller.h b/app/src/controller.h index 5ef8755a..d6fe35e2 100644 --- a/app/src/controller.h +++ b/app/src/controller.h @@ -1,11 +1,12 @@ #ifndef CONTROLLER_H #define CONTROLLER_H +#include "common.h" + #include #include #include -#include "common.h" #include "control_msg.h" #include "receiver.h" #include "util/cbuf.h" diff --git a/app/src/decoder.h b/app/src/decoder.h index 8a3bc297..27afcd8e 100644 --- a/app/src/decoder.h +++ b/app/src/decoder.h @@ -1,11 +1,11 @@ #ifndef DECODER_H #define DECODER_H +#include "common.h" + #include #include -#include "common.h" - struct video_buffer; struct decoder { diff --git a/app/src/device.h b/app/src/device.h index c21b1ace..376e3d4b 100644 --- a/app/src/device.h +++ b/app/src/device.h @@ -1,9 +1,10 @@ #ifndef DEVICE_H #define DEVICE_H +#include "common.h" + #include -#include "common.h" #include "coords.h" #include "util/net.h" diff --git a/app/src/device_msg.h b/app/src/device_msg.h index b1860726..bc13cebb 100644 --- a/app/src/device_msg.h +++ b/app/src/device_msg.h @@ -1,12 +1,12 @@ #ifndef DEVICEMSG_H #define DEVICEMSG_H +#include "common.h" + #include #include #include -#include "common.h" - #define DEVICE_MSG_MAX_SIZE (1 << 18) // 256k // type: 1 byte; length: 4 bytes #define DEVICE_MSG_TEXT_MAX_LENGTH (DEVICE_MSG_MAX_SIZE - 5) diff --git a/app/src/event_converter.h b/app/src/event_converter.h index cbd578a0..d28e9fdc 100644 --- a/app/src/event_converter.h +++ b/app/src/event_converter.h @@ -1,10 +1,11 @@ #ifndef CONVERT_H #define CONVERT_H +#include "common.h" + #include #include -#include "common.h" #include "control_msg.h" bool diff --git a/app/src/file_handler.h b/app/src/file_handler.h index 6649b9ea..a8f469e2 100644 --- a/app/src/file_handler.h +++ b/app/src/file_handler.h @@ -1,11 +1,12 @@ #ifndef FILE_HANDLER_H #define FILE_HANDLER_H +#include "common.h" + #include #include #include -#include "common.h" #include "adb.h" #include "util/cbuf.h" diff --git a/app/src/fps_counter.h b/app/src/fps_counter.h index 2c13b3e2..68255bb6 100644 --- a/app/src/fps_counter.h +++ b/app/src/fps_counter.h @@ -1,14 +1,14 @@ #ifndef FPSCOUNTER_H #define FPSCOUNTER_H +#include "common.h" + #include #include #include #include #include -#include "common.h" - struct fps_counter { SDL_Thread *thread; SDL_mutex *mutex; diff --git a/app/src/input_manager.h b/app/src/input_manager.h index 66daa9dd..a23a731d 100644 --- a/app/src/input_manager.h +++ b/app/src/input_manager.h @@ -1,11 +1,12 @@ #ifndef INPUTMANAGER_H #define INPUTMANAGER_H +#include "common.h" + #include #include -#include "common.h" #include "controller.h" #include "fps_counter.h" #include "scrcpy.h" diff --git a/app/src/main.c b/app/src/main.c index 18b9c710..e1e44f68 100644 --- a/app/src/main.c +++ b/app/src/main.c @@ -1,5 +1,7 @@ #include "scrcpy.h" +#include "common.h" + #include #include #include @@ -7,7 +9,6 @@ #define SDL_MAIN_HANDLED // avoid link error on Linux Windows Subsystem #include -#include "common.h" #include "cli.h" #include "util/log.h" diff --git a/app/src/opengl.h b/app/src/opengl.h index 1c1e4658..81163704 100644 --- a/app/src/opengl.h +++ b/app/src/opengl.h @@ -1,11 +1,11 @@ #ifndef SC_OPENGL_H #define SC_OPENGL_H +#include "common.h" + #include #include -#include "common.h" - struct sc_opengl { const char *version; bool is_opengles; diff --git a/app/src/receiver.h b/app/src/receiver.h index 8f628238..3b1cc03a 100644 --- a/app/src/receiver.h +++ b/app/src/receiver.h @@ -1,11 +1,12 @@ #ifndef RECEIVER_H #define RECEIVER_H +#include "common.h" + #include #include #include -#include "common.h" #include "util/net.h" // receive events from the device diff --git a/app/src/recorder.h b/app/src/recorder.h index fc05d06c..1e942110 100644 --- a/app/src/recorder.h +++ b/app/src/recorder.h @@ -1,12 +1,13 @@ #ifndef RECORDER_H #define RECORDER_H +#include "common.h" + #include #include #include #include -#include "common.h" #include "coords.h" #include "scrcpy.h" #include "util/queue.h" diff --git a/app/src/scrcpy.h b/app/src/scrcpy.h index 08bce7e3..2253cc28 100644 --- a/app/src/scrcpy.h +++ b/app/src/scrcpy.h @@ -1,12 +1,12 @@ #ifndef SCRCPY_H #define SCRCPY_H +#include "common.h" + #include #include #include -#include "common.h" - enum sc_log_level { SC_LOG_LEVEL_DEBUG, SC_LOG_LEVEL_INFO, diff --git a/app/src/screen.h b/app/src/screen.h index 820c7382..ea94d538 100644 --- a/app/src/screen.h +++ b/app/src/screen.h @@ -1,11 +1,12 @@ #ifndef SCREEN_H #define SCREEN_H +#include "common.h" + #include #include #include -#include "common.h" #include "coords.h" #include "opengl.h" diff --git a/app/src/server.h b/app/src/server.h index b48bcd61..7a66670f 100644 --- a/app/src/server.h +++ b/app/src/server.h @@ -1,12 +1,13 @@ #ifndef SERVER_H #define SERVER_H +#include "common.h" + #include #include #include #include -#include "common.h" #include "adb.h" #include "scrcpy.h" #include "util/log.h" diff --git a/app/src/stream.h b/app/src/stream.h index 7b0a5d42..d308df88 100644 --- a/app/src/stream.h +++ b/app/src/stream.h @@ -1,13 +1,14 @@ #ifndef STREAM_H #define STREAM_H +#include "common.h" + #include #include #include #include #include -#include "common.h" #include "util/net.h" struct video_buffer; diff --git a/app/src/sys/unix/process.c b/app/src/sys/unix/process.c index dc4f5649..3bb55010 100644 --- a/app/src/sys/unix/process.c +++ b/app/src/sys/unix/process.c @@ -1,14 +1,3 @@ -// for portability (kill, readlink, strdup, strtok_r) -#define _POSIX_C_SOURCE 200809L -#define _BSD_SOURCE - -// modern glibc will complain without this -#define _DEFAULT_SOURCE - -#ifdef __APPLE__ -# define _DARWIN_C_SOURCE // for strdup(), strtok_r(), memset_pattern4() -#endif - #include "util/process.h" #include diff --git a/app/src/tiny_xpm.h b/app/src/tiny_xpm.h index 2dcbeb07..29b42d14 100644 --- a/app/src/tiny_xpm.h +++ b/app/src/tiny_xpm.h @@ -1,10 +1,10 @@ #ifndef TINYXPM_H #define TINYXPM_H -#include - #include "common.h" +#include + SDL_Surface * read_xpm(char *xpm[]); diff --git a/app/src/util/buffer_util.h b/app/src/util/buffer_util.h index 265704bc..ab22ab24 100644 --- a/app/src/util/buffer_util.h +++ b/app/src/util/buffer_util.h @@ -1,11 +1,11 @@ #ifndef BUFFER_UTIL_H #define BUFFER_UTIL_H +#include "common.h" + #include #include -#include "common.h" - static inline void buffer_write16be(uint8_t *buf, uint16_t value) { buf[0] = value >> 8; diff --git a/app/src/util/cbuf.h b/app/src/util/cbuf.h index 6abc984b..01e41044 100644 --- a/app/src/util/cbuf.h +++ b/app/src/util/cbuf.h @@ -2,11 +2,11 @@ #ifndef CBUF_H #define CBUF_H +#include "common.h" + #include #include -#include "common.h" - // To define a circular buffer type of 20 ints: // struct cbuf_int CBUF(int, 20); // diff --git a/app/src/util/lock.h b/app/src/util/lock.h index a0a044b1..f031bd69 100644 --- a/app/src/util/lock.h +++ b/app/src/util/lock.h @@ -1,10 +1,11 @@ #ifndef LOCK_H #define LOCK_H +#include "common.h" + #include #include -#include "common.h" #include "log.h" static inline void diff --git a/app/src/util/net.h b/app/src/util/net.h index f86c048d..d3b1f941 100644 --- a/app/src/util/net.h +++ b/app/src/util/net.h @@ -1,6 +1,8 @@ #ifndef NET_H #define NET_H +#include "common.h" + #include #include #include @@ -17,8 +19,6 @@ typedef int socket_t; #endif -#include "common.h" - bool net_init(void); diff --git a/app/src/util/process.h b/app/src/util/process.h index f91553d4..46e481bb 100644 --- a/app/src/util/process.h +++ b/app/src/util/process.h @@ -1,10 +1,10 @@ #ifndef SC_PROCESS_H #define SC_PROCESS_H -#include - #include "common.h" +#include + #ifdef _WIN32 // not needed here, but winsock2.h must never be included AFTER windows.h diff --git a/app/src/util/queue.h b/app/src/util/queue.h index 6092c712..0681070c 100644 --- a/app/src/util/queue.h +++ b/app/src/util/queue.h @@ -2,12 +2,12 @@ #ifndef QUEUE_H #define QUEUE_H +#include "common.h" + #include #include #include -#include "common.h" - // To define a queue type of "struct foo": // struct queue_foo QUEUE(struct foo); #define QUEUE(TYPE) { \ diff --git a/app/src/util/str_util.h b/app/src/util/str_util.h index dd523400..25bec444 100644 --- a/app/src/util/str_util.h +++ b/app/src/util/str_util.h @@ -1,11 +1,11 @@ #ifndef STRUTIL_H #define STRUTIL_H +#include "common.h" + #include #include -#include "common.h" - // like strncpy, except: // - it copies at most n-1 chars // - the dest string is nul-terminated diff --git a/app/src/video_buffer.h b/app/src/video_buffer.h index ddd639ad..68ef8e04 100644 --- a/app/src/video_buffer.h +++ b/app/src/video_buffer.h @@ -1,10 +1,11 @@ #ifndef VIDEO_BUFFER_H #define VIDEO_BUFFER_H +#include "common.h" + #include #include -#include "common.h" #include "fps_counter.h" // forward declarations diff --git a/app/tests/test_buffer_util.c b/app/tests/test_buffer_util.c index d61e6918..c7c13bdd 100644 --- a/app/tests/test_buffer_util.c +++ b/app/tests/test_buffer_util.c @@ -1,3 +1,5 @@ +#include "common.h" + #include #include "util/buffer_util.h" diff --git a/app/tests/test_cbuf.c b/app/tests/test_cbuf.c index f8beb880..16674e92 100644 --- a/app/tests/test_cbuf.c +++ b/app/tests/test_cbuf.c @@ -1,3 +1,5 @@ +#include "common.h" + #include #include diff --git a/app/tests/test_cli.c b/app/tests/test_cli.c index 9699b024..cd222d63 100644 --- a/app/tests/test_cli.c +++ b/app/tests/test_cli.c @@ -1,7 +1,8 @@ +#include "common.h" + #include #include -#include "common.h" #include "cli.h" #include "scrcpy.h" diff --git a/app/tests/test_control_msg_serialize.c b/app/tests/test_control_msg_serialize.c index b58c8e20..ffd5f300 100644 --- a/app/tests/test_control_msg_serialize.c +++ b/app/tests/test_control_msg_serialize.c @@ -1,3 +1,5 @@ +#include "common.h" + #include #include diff --git a/app/tests/test_device_msg_deserialize.c b/app/tests/test_device_msg_deserialize.c index 3dfd0b0f..3427d640 100644 --- a/app/tests/test_device_msg_deserialize.c +++ b/app/tests/test_device_msg_deserialize.c @@ -1,3 +1,5 @@ +#include "common.h" + #include #include diff --git a/app/tests/test_queue.c b/app/tests/test_queue.c index e10821cd..fcbafc62 100644 --- a/app/tests/test_queue.c +++ b/app/tests/test_queue.c @@ -1,3 +1,5 @@ +#include "common.h" + #include #include "util/queue.h" diff --git a/app/tests/test_strutil.c b/app/tests/test_strutil.c index 7b9c61da..9d35f983 100644 --- a/app/tests/test_strutil.c +++ b/app/tests/test_strutil.c @@ -1,3 +1,5 @@ +#include "common.h" + #include #include #include From 8dbb1676b72f4f88dc7967c7d9f6f4802576bcfa Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 8 Jan 2021 19:13:53 +0100 Subject: [PATCH 270/450] Factorize meson compiler variable initialization --- app/meson.build | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/app/meson.build b/app/meson.build index 651e756b..8e77cb93 100644 --- a/app/meson.build +++ b/app/meson.build @@ -31,6 +31,8 @@ else src += [ 'src/sys/unix/process.c' ] endif +cc = meson.get_compiler('c') + if not get_option('crossbuild_windows') # native build @@ -44,8 +46,6 @@ if not get_option('crossbuild_windows') else # cross-compile mingw32 build (from Linux to Windows) - cc = meson.get_compiler('c') - prebuilt_sdl2 = meson.get_cross_property('prebuilt_sdl2') sdl2_bin_dir = meson.current_source_dir() + '/../prebuilt-deps/' + prebuilt_sdl2 + '/bin' sdl2_lib_dir = meson.current_source_dir() + '/../prebuilt-deps/' + prebuilt_sdl2 + '/lib' @@ -80,8 +80,6 @@ else endif -cc = meson.get_compiler('c') - if host_machine.system() == 'windows' dependencies += cc.find_library('ws2_32') endif From 94eff0a4bb0853bc30cbb131e38878f34e28c0f6 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 17 Jan 2021 14:22:23 +0100 Subject: [PATCH 271/450] Fix size_t incorrectly assigned to int The function control_msg_serialize() returns a size_t. --- app/src/controller.c | 4 ++-- app/tests/test_control_msg_serialize.c | 24 ++++++++++++------------ 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/app/src/controller.c b/app/src/controller.c index 6bfb80d3..da90fbf1 100644 --- a/app/src/controller.c +++ b/app/src/controller.c @@ -60,12 +60,12 @@ static bool process_msg(struct controller *controller, const struct control_msg *msg) { static unsigned char serialized_msg[CONTROL_MSG_MAX_SIZE]; - int length = control_msg_serialize(msg, serialized_msg); + size_t length = control_msg_serialize(msg, serialized_msg); if (!length) { return false; } int w = net_send_all(controller->control_socket, serialized_msg, length); - return w == length; + return (size_t) w == length; } static int diff --git a/app/tests/test_control_msg_serialize.c b/app/tests/test_control_msg_serialize.c index ffd5f300..dc6e0821 100644 --- a/app/tests/test_control_msg_serialize.c +++ b/app/tests/test_control_msg_serialize.c @@ -17,7 +17,7 @@ static void test_serialize_inject_keycode(void) { }; unsigned char buf[CONTROL_MSG_MAX_SIZE]; - int size = control_msg_serialize(&msg, buf); + size_t size = control_msg_serialize(&msg, buf); assert(size == 14); const unsigned char expected[] = { @@ -39,7 +39,7 @@ static void test_serialize_inject_text(void) { }; unsigned char buf[CONTROL_MSG_MAX_SIZE]; - int size = control_msg_serialize(&msg, buf); + size_t size = control_msg_serialize(&msg, buf); assert(size == 18); const unsigned char expected[] = { @@ -59,7 +59,7 @@ static void test_serialize_inject_text_long(void) { msg.inject_text.text = text; unsigned char buf[CONTROL_MSG_MAX_SIZE]; - int size = control_msg_serialize(&msg, buf); + size_t size = control_msg_serialize(&msg, buf); assert(size == 5 + CONTROL_MSG_INJECT_TEXT_MAX_LENGTH); unsigned char expected[5 + CONTROL_MSG_INJECT_TEXT_MAX_LENGTH]; @@ -95,7 +95,7 @@ static void test_serialize_inject_touch_event(void) { }; unsigned char buf[CONTROL_MSG_MAX_SIZE]; - int size = control_msg_serialize(&msg, buf); + size_t size = control_msg_serialize(&msg, buf); assert(size == 28); const unsigned char expected[] = { @@ -130,7 +130,7 @@ static void test_serialize_inject_scroll_event(void) { }; unsigned char buf[CONTROL_MSG_MAX_SIZE]; - int size = control_msg_serialize(&msg, buf); + size_t size = control_msg_serialize(&msg, buf); assert(size == 21); const unsigned char expected[] = { @@ -149,7 +149,7 @@ static void test_serialize_back_or_screen_on(void) { }; unsigned char buf[CONTROL_MSG_MAX_SIZE]; - int size = control_msg_serialize(&msg, buf); + size_t size = control_msg_serialize(&msg, buf); assert(size == 1); const unsigned char expected[] = { @@ -164,7 +164,7 @@ static void test_serialize_expand_notification_panel(void) { }; unsigned char buf[CONTROL_MSG_MAX_SIZE]; - int size = control_msg_serialize(&msg, buf); + size_t size = control_msg_serialize(&msg, buf); assert(size == 1); const unsigned char expected[] = { @@ -179,7 +179,7 @@ static void test_serialize_collapse_notification_panel(void) { }; unsigned char buf[CONTROL_MSG_MAX_SIZE]; - int size = control_msg_serialize(&msg, buf); + size_t size = control_msg_serialize(&msg, buf); assert(size == 1); const unsigned char expected[] = { @@ -194,7 +194,7 @@ static void test_serialize_get_clipboard(void) { }; unsigned char buf[CONTROL_MSG_MAX_SIZE]; - int size = control_msg_serialize(&msg, buf); + size_t size = control_msg_serialize(&msg, buf); assert(size == 1); const unsigned char expected[] = { @@ -213,7 +213,7 @@ static void test_serialize_set_clipboard(void) { }; unsigned char buf[CONTROL_MSG_MAX_SIZE]; - int size = control_msg_serialize(&msg, buf); + size_t size = control_msg_serialize(&msg, buf); assert(size == 19); const unsigned char expected[] = { @@ -234,7 +234,7 @@ static void test_serialize_set_screen_power_mode(void) { }; unsigned char buf[CONTROL_MSG_MAX_SIZE]; - int size = control_msg_serialize(&msg, buf); + size_t size = control_msg_serialize(&msg, buf); assert(size == 2); const unsigned char expected[] = { @@ -250,7 +250,7 @@ static void test_serialize_rotate_device(void) { }; unsigned char buf[CONTROL_MSG_MAX_SIZE]; - int size = control_msg_serialize(&msg, buf); + size_t size = control_msg_serialize(&msg, buf); assert(size == 1); const unsigned char expected[] = { From b8edcf52b0fb5e1502397c88929868bc79dac1ec Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 22 Jan 2021 18:29:21 +0100 Subject: [PATCH 272/450] Simplify process_wait() The function process_wait() returned a bool (true if the process terminated successfully) and provided the exit code via an output parameter exit_code. But the returned value was always equivalent to exit_code == 0, so just return the exit code instead. --- app/src/file_handler.c | 2 +- app/src/server.c | 4 ++-- app/src/sys/unix/process.c | 25 +++++++++++-------------- app/src/sys/win/process.c | 23 ++++++++++------------- app/src/util/process.c | 4 ++-- app/src/util/process.h | 8 ++++---- 6 files changed, 30 insertions(+), 36 deletions(-) diff --git a/app/src/file_handler.c b/app/src/file_handler.c index 3ad92c05..930fd218 100644 --- a/app/src/file_handler.c +++ b/app/src/file_handler.c @@ -178,7 +178,7 @@ file_handler_stop(struct file_handler *file_handler) { if (!process_terminate(file_handler->current_process)) { LOGW("Could not terminate install process"); } - process_wait(file_handler->current_process, NULL); + process_wait(file_handler->current_process); // ignore exit code file_handler->current_process = PROCESS_NONE; } mutex_unlock(file_handler->mutex); diff --git a/app/src/server.c b/app/src/server.c index 841af82f..c5982b18 100644 --- a/app/src/server.c +++ b/app/src/server.c @@ -391,7 +391,7 @@ server_init(struct server *server) { static int run_wait_server(void *data) { struct server *server = data; - process_wait_noclose(server->process, NULL); // ignore exit code + process_wait_noclose(server->process); // ignore exit code mutex_lock(server->mutex); server->process_terminated = true; @@ -447,7 +447,7 @@ server_start(struct server *server, const char *serial, SDL_CreateThread(run_wait_server, "wait-server", server); if (!server->wait_server_thread) { process_terminate(server->process); - process_wait(server->process, NULL); // ignore exit code + process_wait(server->process); // ignore exit code goto error2; } diff --git a/app/src/sys/unix/process.c b/app/src/sys/unix/process.c index 3bb55010..2d1d0ff7 100644 --- a/app/src/sys/unix/process.c +++ b/app/src/sys/unix/process.c @@ -121,8 +121,8 @@ process_terminate(pid_t pid) { return kill(pid, SIGTERM) != -1; } -static bool -process_wait_internal(pid_t pid, int *exit_code, bool close) { +static exit_code_t +process_wait_internal(pid_t pid, bool close) { int code; int options = WEXITED; if (!close) { @@ -133,29 +133,26 @@ process_wait_internal(pid_t pid, int *exit_code, bool close) { int r = waitid(P_PID, pid, &info, options); if (r == -1 || info.si_code != CLD_EXITED) { // could not wait, or exited unexpectedly, probably by a signal - code = -1; + code = NO_EXIT_CODE; } else { code = info.si_status; } - if (exit_code) { - *exit_code = code; - } - return !code; + return code; } -bool -process_wait(pid_t pid, int *exit_code) { - return process_wait_internal(pid, exit_code, true); +exit_code_t +process_wait(pid_t pid) { + return process_wait_internal(pid, true); } -bool -process_wait_noclose(pid_t pid, int *exit_code) { - return process_wait_internal(pid, exit_code, false); +exit_code_t +process_wait_noclose(pid_t pid) { + return process_wait_internal(pid, false); } void process_close(pid_t pid) { - process_wait_internal(pid, NULL, true); + process_wait_internal(pid, true); // ignore exit code } char * diff --git a/app/src/sys/win/process.c b/app/src/sys/win/process.c index f087625c..bb5d9bc6 100644 --- a/app/src/sys/win/process.c +++ b/app/src/sys/win/process.c @@ -59,31 +59,28 @@ process_terminate(HANDLE handle) { return TerminateProcess(handle, 1); } -static bool -process_wait_internal(HANDLE handle, DWORD *exit_code, bool close) { +static exit_code_t +process_wait_internal(HANDLE handle, bool close) { DWORD code; if (WaitForSingleObject(handle, INFINITE) != WAIT_OBJECT_0 || !GetExitCodeProcess(handle, &code)) { // could not wait or retrieve the exit code - code = -1; // max value, it's unsigned - } - if (exit_code) { - *exit_code = code; + code = NO_EXIT_CODE; // max value, it's unsigned } if (close) { CloseHandle(handle); } - return !code; + return code; } -bool -process_wait(HANDLE handle, DWORD *exit_code) { - return process_wait_internal(handle, exit_code, true); +exit_code_t +process_wait(HANDLE handle) { + return process_wait_internal(handle, true); } -bool -process_wait_noclose(HANDLE handle, DWORD *exit_code) { - return process_wait_internal(handle, exit_code, false); +exit_code_t +process_wait_noclose(HANDLE handle) { + return process_wait_internal(handle, false); } void diff --git a/app/src/util/process.c b/app/src/util/process.c index e485ce17..e9d71d86 100644 --- a/app/src/util/process.c +++ b/app/src/util/process.c @@ -8,8 +8,8 @@ process_check_success(process_t proc, const char *name) { LOGE("Could not execute \"%s\"", name); return false; } - exit_code_t exit_code; - if (!process_wait(proc, &exit_code)) { + exit_code_t exit_code = process_wait(proc); + if (exit_code) { if (exit_code != NO_EXIT_CODE) { LOGE("\"%s\" returned with value %" PRIexitcode, name, exit_code); } else { diff --git a/app/src/util/process.h b/app/src/util/process.h index 46e481bb..e199e317 100644 --- a/app/src/util/process.h +++ b/app/src/util/process.h @@ -47,12 +47,12 @@ bool process_terminate(process_t pid); // wait and close the process (like waitpid()) -bool -process_wait(process_t pid, exit_code_t *exit_code); +exit_code_t +process_wait(process_t pid); // wait (but does not close) the process (waitid() with WNOWAIT) -bool -process_wait_noclose(process_t pid, exit_code_t *exit_code); +exit_code_t +process_wait_noclose(process_t pid); // close the process // From 6a50231698f0ef635d4ab35ea01d531eb2e0ba2a Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 22 Jan 2021 18:48:17 +0100 Subject: [PATCH 273/450] Expose a single process_wait() There were two versions: process_wait() and process_wait_noclose(). Expose a single version with a flag (it was already implemented that way internally). --- app/src/file_handler.c | 2 +- app/src/server.c | 4 ++-- app/src/sys/unix/process.c | 16 +++------------- app/src/sys/win/process.c | 14 ++------------ app/src/util/process.c | 2 +- app/src/util/process.h | 10 ++++------ 6 files changed, 13 insertions(+), 35 deletions(-) diff --git a/app/src/file_handler.c b/app/src/file_handler.c index 930fd218..c39b7bf4 100644 --- a/app/src/file_handler.c +++ b/app/src/file_handler.c @@ -178,7 +178,7 @@ file_handler_stop(struct file_handler *file_handler) { if (!process_terminate(file_handler->current_process)) { LOGW("Could not terminate install process"); } - process_wait(file_handler->current_process); // ignore exit code + process_wait(file_handler->current_process, true); // ignore exit code file_handler->current_process = PROCESS_NONE; } mutex_unlock(file_handler->mutex); diff --git a/app/src/server.c b/app/src/server.c index c5982b18..8637ca4e 100644 --- a/app/src/server.c +++ b/app/src/server.c @@ -391,7 +391,7 @@ server_init(struct server *server) { static int run_wait_server(void *data) { struct server *server = data; - process_wait_noclose(server->process); // ignore exit code + process_wait(server->process, false); // ignore exit code mutex_lock(server->mutex); server->process_terminated = true; @@ -447,7 +447,7 @@ server_start(struct server *server, const char *serial, SDL_CreateThread(run_wait_server, "wait-server", server); if (!server->wait_server_thread) { process_terminate(server->process); - process_wait(server->process); // ignore exit code + process_wait(server->process, true); // ignore exit code goto error2; } diff --git a/app/src/sys/unix/process.c b/app/src/sys/unix/process.c index 2d1d0ff7..dc535dcf 100644 --- a/app/src/sys/unix/process.c +++ b/app/src/sys/unix/process.c @@ -121,8 +121,8 @@ process_terminate(pid_t pid) { return kill(pid, SIGTERM) != -1; } -static exit_code_t -process_wait_internal(pid_t pid, bool close) { +exit_code_t +process_wait(pid_t pid, bool close) { int code; int options = WEXITED; if (!close) { @@ -140,19 +140,9 @@ process_wait_internal(pid_t pid, bool close) { return code; } -exit_code_t -process_wait(pid_t pid) { - return process_wait_internal(pid, true); -} - -exit_code_t -process_wait_noclose(pid_t pid) { - return process_wait_internal(pid, false); -} - void process_close(pid_t pid) { - process_wait_internal(pid, true); // ignore exit code + process_wait(pid, true); // ignore exit code } char * diff --git a/app/src/sys/win/process.c b/app/src/sys/win/process.c index bb5d9bc6..88242af3 100644 --- a/app/src/sys/win/process.c +++ b/app/src/sys/win/process.c @@ -59,8 +59,8 @@ process_terminate(HANDLE handle) { return TerminateProcess(handle, 1); } -static exit_code_t -process_wait_internal(HANDLE handle, bool close) { +exit_code_t +process_wait(HANDLE handle, bool close) { DWORD code; if (WaitForSingleObject(handle, INFINITE) != WAIT_OBJECT_0 || !GetExitCodeProcess(handle, &code)) { @@ -73,16 +73,6 @@ process_wait_internal(HANDLE handle, bool close) { return code; } -exit_code_t -process_wait(HANDLE handle) { - return process_wait_internal(handle, true); -} - -exit_code_t -process_wait_noclose(HANDLE handle) { - return process_wait_internal(handle, false); -} - void process_close(HANDLE handle) { bool closed = CloseHandle(handle); diff --git a/app/src/util/process.c b/app/src/util/process.c index e9d71d86..667e614a 100644 --- a/app/src/util/process.c +++ b/app/src/util/process.c @@ -8,7 +8,7 @@ process_check_success(process_t proc, const char *name) { LOGE("Could not execute \"%s\"", name); return false; } - exit_code_t exit_code = process_wait(proc); + exit_code_t exit_code = process_wait(proc, true); if (exit_code) { if (exit_code != NO_EXIT_CODE) { LOGE("\"%s\" returned with value %" PRIexitcode, name, exit_code); diff --git a/app/src/util/process.h b/app/src/util/process.h index e199e317..5090eaf7 100644 --- a/app/src/util/process.h +++ b/app/src/util/process.h @@ -47,16 +47,14 @@ bool process_terminate(process_t pid); // wait and close the process (like waitpid()) +// the "close" flag indicates if the process must be "closed" (reaped) +// (passing false is equivalent to enable WNOWAIT in waitid()) exit_code_t -process_wait(process_t pid); - -// wait (but does not close) the process (waitid() with WNOWAIT) -exit_code_t -process_wait_noclose(process_t pid); +process_wait(process_t pid, bool close); // close the process // -// Semantically, process_wait = process_wait_noclose + process_close. +// Semantically, process_wait(close) = process_wait(noclose) + process_close void process_close(process_t pid); From 7afd149f4bccc6b8e0d5d27e48cb5fa05e9f2fec Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 22 Jan 2021 19:20:30 +0100 Subject: [PATCH 274/450] Fix file_handler process race condition The current process could be waited both by run_file_handler() and file_handler_stop(). To avoid the race condition, wait the process without closing, then close with mutex locked. --- app/src/file_handler.c | 14 ++++++++++---- app/src/server.c | 10 +++++----- app/src/util/process.c | 4 ++-- app/src/util/process.h | 2 +- 4 files changed, 18 insertions(+), 12 deletions(-) diff --git a/app/src/file_handler.c b/app/src/file_handler.c index c39b7bf4..d8881e30 100644 --- a/app/src/file_handler.c +++ b/app/src/file_handler.c @@ -135,13 +135,13 @@ run_file_handler(void *data) { mutex_unlock(file_handler->mutex); if (req.action == ACTION_INSTALL_APK) { - if (process_check_success(process, "adb install")) { + if (process_check_success(process, "adb install", false)) { LOGI("%s successfully installed", req.file); } else { LOGE("Failed to install %s", req.file); } } else { - if (process_check_success(process, "adb push")) { + if (process_check_success(process, "adb push", false)) { LOGI("%s successfully pushed to %s", req.file, file_handler->push_target); } else { @@ -150,6 +150,14 @@ run_file_handler(void *data) { } } + mutex_lock(file_handler->mutex); + // Close the process (it is necessary already terminated) + // Execute this call with mutex locked to avoid race conditions with + // file_handler_stop() + process_close(file_handler->current_process); + file_handler->current_process = PROCESS_NONE; + mutex_unlock(file_handler->mutex); + file_handler_request_destroy(&req); } return 0; @@ -178,8 +186,6 @@ file_handler_stop(struct file_handler *file_handler) { if (!process_terminate(file_handler->current_process)) { LOGW("Could not terminate install process"); } - process_wait(file_handler->current_process, true); // ignore exit code - file_handler->current_process = PROCESS_NONE; } mutex_unlock(file_handler->mutex); } diff --git a/app/src/server.c b/app/src/server.c index 8637ca4e..b7e7938c 100644 --- a/app/src/server.c +++ b/app/src/server.c @@ -100,31 +100,31 @@ push_server(const char *serial) { } process_t process = adb_push(serial, server_path, DEVICE_SERVER_PATH); SDL_free(server_path); - return process_check_success(process, "adb push"); + return process_check_success(process, "adb push", true); } static bool enable_tunnel_reverse(const char *serial, uint16_t local_port) { process_t process = adb_reverse(serial, SOCKET_NAME, local_port); - return process_check_success(process, "adb reverse"); + return process_check_success(process, "adb reverse", true); } static bool disable_tunnel_reverse(const char *serial) { process_t process = adb_reverse_remove(serial, SOCKET_NAME); - return process_check_success(process, "adb reverse --remove"); + return process_check_success(process, "adb reverse --remove", true); } static bool enable_tunnel_forward(const char *serial, uint16_t local_port) { process_t process = adb_forward(serial, local_port, SOCKET_NAME); - return process_check_success(process, "adb forward"); + return process_check_success(process, "adb forward", true); } static bool disable_tunnel_forward(const char *serial, uint16_t local_port) { process_t process = adb_forward_remove(serial, local_port); - return process_check_success(process, "adb forward --remove"); + return process_check_success(process, "adb forward --remove", true); } static bool diff --git a/app/src/util/process.c b/app/src/util/process.c index 667e614a..5edeeee6 100644 --- a/app/src/util/process.c +++ b/app/src/util/process.c @@ -3,12 +3,12 @@ #include "log.h" bool -process_check_success(process_t proc, const char *name) { +process_check_success(process_t proc, const char *name, bool close) { if (proc == PROCESS_NONE) { LOGE("Could not execute \"%s\"", name); return false; } - exit_code_t exit_code = process_wait(proc, true); + exit_code_t exit_code = process_wait(proc, close); if (exit_code) { if (exit_code != NO_EXIT_CODE) { LOGE("\"%s\" returned with value %" PRIexitcode, name, exit_code); diff --git a/app/src/util/process.h b/app/src/util/process.h index 5090eaf7..7e619a2b 100644 --- a/app/src/util/process.h +++ b/app/src/util/process.h @@ -61,7 +61,7 @@ process_close(process_t pid); // convenience function to wait for a successful process execution // automatically log process errors with the provided process name bool -process_check_success(process_t proc, const char *name); +process_check_success(process_t proc, const char *name, bool close); #ifndef _WIN32 // only used to find package manager, not implemented for Windows From b566700bfdedd4115ff9f701aa53eb6be2f8924a Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 22 Jan 2021 19:21:35 +0100 Subject: [PATCH 275/450] Kill process with SIGKILL signal An "adb push" command is not terminated by SIGTERM. --- app/src/sys/unix/process.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/sys/unix/process.c b/app/src/sys/unix/process.c index dc535dcf..293b2aff 100644 --- a/app/src/sys/unix/process.c +++ b/app/src/sys/unix/process.c @@ -118,7 +118,7 @@ process_terminate(pid_t pid) { (int) pid); abort(); } - return kill(pid, SIGTERM) != -1; + return kill(pid, SIGKILL) != -1; } exit_code_t From d8e9ad20b004549b8c001ae5624376fce2070cd4 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 22 Jan 2021 19:22:40 +0100 Subject: [PATCH 276/450] Improve file handler error message Terminating the file handler current process may be either a "push" or "install" command. --- app/src/file_handler.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/file_handler.c b/app/src/file_handler.c index d8881e30..3652ef90 100644 --- a/app/src/file_handler.c +++ b/app/src/file_handler.c @@ -184,7 +184,7 @@ file_handler_stop(struct file_handler *file_handler) { cond_signal(file_handler->event_cond); if (file_handler->current_process != PROCESS_NONE) { if (!process_terminate(file_handler->current_process)) { - LOGW("Could not terminate install process"); + LOGW("Could not terminate push/install process"); } } mutex_unlock(file_handler->mutex); From 97b001e7c0108ebe474494acba5a331aac4dedb3 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 24 Jan 2021 15:22:14 +0100 Subject: [PATCH 277/450] Fix undefined left shift Small unsigned integers promote to signed int. As a consequence, if v is a uint8_t, then (v << 24) yields an int, so the left shift is undefined if the MSB is 1. Cast to uint32_t to yield an unsigned value. Reported by USAN (meson x -Db_sanitize=undefined): runtime error: left shift of 255 by 24 places cannot be represented in type 'int' --- app/src/util/buffer_util.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/util/buffer_util.h b/app/src/util/buffer_util.h index ab22ab24..337bb262 100644 --- a/app/src/util/buffer_util.h +++ b/app/src/util/buffer_util.h @@ -33,7 +33,7 @@ buffer_read16be(const uint8_t *buf) { static inline uint32_t buffer_read32be(const uint8_t *buf) { - return (buf[0] << 24) | (buf[1] << 16) | (buf[2] << 8) | buf[3]; + return ((uint32_t) buf[0] << 24) | (buf[1] << 16) | (buf[2] << 8) | buf[3]; } static inline uint64_t From f8524a2be738e6471ed2e48265f2663d8c21c0be Mon Sep 17 00:00:00 2001 From: Michael Richardson Date: Wed, 27 Jan 2021 15:39:28 -0500 Subject: [PATCH 278/450] Update java version in BUILD.md PR #2064 Signed-off-by: Romain Vimont --- BUILD.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/BUILD.md b/BUILD.md index 5dcf7b27..52029b6b 100644 --- a/BUILD.md +++ b/BUILD.md @@ -64,7 +64,7 @@ sudo apt install gcc git pkg-config meson ninja-build \ libsdl2-dev # server build dependencies -sudo apt install openjdk-8-jdk +sudo apt install openjdk-11-jdk ``` On old versions (like Ubuntu 16.04), `meson` is too old. In that case, install From 8e83f3e8af595ee0a8fe0538abff7501146fcf12 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 7 Aug 2020 18:42:17 +0200 Subject: [PATCH 279/450] Remove unused custom event --- app/src/events.h | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/app/src/events.h b/app/src/events.h index e9512048..a4d6f3df 100644 --- a/app/src/events.h +++ b/app/src/events.h @@ -1,3 +1,2 @@ -#define EVENT_NEW_SESSION SDL_USEREVENT -#define EVENT_NEW_FRAME (SDL_USEREVENT + 1) -#define EVENT_STREAM_STOPPED (SDL_USEREVENT + 2) +#define EVENT_NEW_FRAME SDL_USEREVENT +#define EVENT_STREAM_STOPPED (SDL_USEREVENT + 1) From ace438e52abef2d0ccb8380a2c46501fcdc1c630 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 6 Dec 2020 15:18:33 +0100 Subject: [PATCH 280/450] Remove unused port_range field The port_range is used from "struct server_params", the copy in "struct server" was unused. --- app/src/server.c | 4 ---- app/src/server.h | 1 - 2 files changed, 5 deletions(-) diff --git a/app/src/server.c b/app/src/server.c index b7e7938c..f1ac7b46 100644 --- a/app/src/server.c +++ b/app/src/server.c @@ -378,8 +378,6 @@ server_init(struct server *server) { server->video_socket = INVALID_SOCKET; server->control_socket = INVALID_SOCKET; - server->port_range.first = 0; - server->port_range.last = 0; server->local_port = 0; server->tunnel_enabled = false; @@ -413,8 +411,6 @@ run_wait_server(void *data) { bool server_start(struct server *server, const char *serial, const struct server_params *params) { - server->port_range = params->port_range; - if (serial) { server->serial = SDL_strdup(serial); if (!server->serial) { diff --git a/app/src/server.h b/app/src/server.h index 7a66670f..1ac12b5f 100644 --- a/app/src/server.h +++ b/app/src/server.h @@ -26,7 +26,6 @@ struct server { socket_t server_socket; // only used if !tunnel_forward socket_t video_socket; socket_t control_socket; - struct sc_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" From c0dde0fade6d2b582015816a6f08f9114c919094 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 17 Jan 2021 14:11:59 +0100 Subject: [PATCH 281/450] Provide strdup() compat Make strdup() available on all platforms. --- app/meson.build | 12 ++++++++++++ app/src/compat.c | 14 ++++++++++++++ app/src/compat.h | 4 ++++ 3 files changed, 30 insertions(+) create mode 100644 app/src/compat.c diff --git a/app/meson.build b/app/meson.build index 8e77cb93..2dade249 100644 --- a/app/meson.build +++ b/app/meson.build @@ -2,6 +2,7 @@ src = [ 'src/main.c', 'src/adb.c', 'src/cli.c', + 'src/compat.c', 'src/control_msg.c', 'src/controller.c', 'src/decoder.c', @@ -31,6 +32,10 @@ else src += [ 'src/sys/unix/process.c' ] endif +check_functions = [ + 'strdup' +] + cc = meson.get_compiler('c') if not get_option('crossbuild_windows') @@ -86,6 +91,13 @@ endif conf = configuration_data() +foreach f : check_functions + if cc.has_function(f) + define = 'HAVE_' + f.underscorify().to_upper() + conf.set(define, true) + endif +endforeach + # expose the build type conf.set('NDEBUG', get_option('buildtype') != 'debug') diff --git a/app/src/compat.c b/app/src/compat.c new file mode 100644 index 00000000..b3b98bf1 --- /dev/null +++ b/app/src/compat.c @@ -0,0 +1,14 @@ +#include "compat.h" + +#include "config.h" + +#ifndef HAVE_STRDUP +char *strdup(const char *s) { + size_t size = strlen(s) + 1; + char *dup = malloc(size); + if (dup) { + memcpy(dup, s, size); + } + return dup; +} +#endif diff --git a/app/src/compat.h b/app/src/compat.h index 5464589a..9a84a4c1 100644 --- a/app/src/compat.h +++ b/app/src/compat.h @@ -56,4 +56,8 @@ # define SCRCPY_SDL_HAS_HINT_VIDEO_X11_NET_WM_BYPASS_COMPOSITOR #endif +#ifndef HAVE_STRDUP +char *strdup(const char *s); +#endif + #endif From 30e619d37f867621ada9d549dc735ceaff72a635 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 24 Jan 2021 15:14:53 +0100 Subject: [PATCH 282/450] Replace SDL_strdup() by strdup() The functions SDL_malloc(), SDL_free() and SDL_strdup() were used only because strdup() was not available everywhere. Now that it is available, use the native version of these functions. --- app/src/adb.c | 8 ++++---- app/src/control_msg.c | 5 +++-- app/src/control_msg.h | 4 ++-- app/src/device_msg.c | 5 +++-- app/src/device_msg.h | 2 +- app/src/file_handler.c | 6 +++--- app/src/file_handler.h | 2 +- app/src/input_manager.c | 26 ++++++++++++++++++++------ app/src/recorder.c | 14 +++++++------- app/src/scrcpy.c | 15 +++++++++++---- app/src/server.c | 20 ++++++++++---------- app/src/sys/unix/process.c | 2 +- app/src/sys/win/process.c | 6 +++--- app/src/util/process.h | 2 +- app/src/util/str_util.c | 8 +++----- app/tests/test_strutil.c | 3 +-- 16 files changed, 74 insertions(+), 54 deletions(-) diff --git a/app/src/adb.c b/app/src/adb.c index 086db174..be973c41 100644 --- a/app/src/adb.c +++ b/app/src/adb.c @@ -173,7 +173,7 @@ adb_push(const char *serial, const char *local, const char *remote) { } remote = strquote(remote); if (!remote) { - SDL_free((void *) local); + free((void *) local); return PROCESS_NONE; } #endif @@ -182,8 +182,8 @@ adb_push(const char *serial, const char *local, const char *remote) { process_t proc = adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd)); #ifdef __WINDOWS__ - SDL_free((void *) remote); - SDL_free((void *) local); + free((void *) remote); + free((void *) local); #endif return proc; @@ -204,7 +204,7 @@ adb_install(const char *serial, const char *local) { process_t proc = adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd)); #ifdef __WINDOWS__ - SDL_free((void *) local); + free((void *) local); #endif return proc; diff --git a/app/src/control_msg.c b/app/src/control_msg.c index 77e534cd..436a8861 100644 --- a/app/src/control_msg.c +++ b/app/src/control_msg.c @@ -1,6 +1,7 @@ #include "control_msg.h" #include +#include #include #include "util/buffer_util.h" @@ -93,10 +94,10 @@ void control_msg_destroy(struct control_msg *msg) { switch (msg->type) { case CONTROL_MSG_TYPE_INJECT_TEXT: - SDL_free(msg->inject_text.text); + free(msg->inject_text.text); break; case CONTROL_MSG_TYPE_SET_CLIPBOARD: - SDL_free(msg->set_clipboard.text); + free(msg->set_clipboard.text); break; default: // do nothing diff --git a/app/src/control_msg.h b/app/src/control_msg.h index 20b2ef45..1b25591d 100644 --- a/app/src/control_msg.h +++ b/app/src/control_msg.h @@ -50,7 +50,7 @@ struct control_msg { enum android_metastate metastate; } inject_keycode; struct { - char *text; // owned, to be freed by SDL_free() + char *text; // owned, to be freed by free() } inject_text; struct { enum android_motionevent_action action; @@ -65,7 +65,7 @@ struct control_msg { int32_t vscroll; } inject_scroll_event; struct { - char *text; // owned, to be freed by SDL_free() + char *text; // owned, to be freed by free() bool paste; } set_clipboard; struct { diff --git a/app/src/device_msg.c b/app/src/device_msg.c index c82ce628..827f4213 100644 --- a/app/src/device_msg.c +++ b/app/src/device_msg.c @@ -1,5 +1,6 @@ #include "device_msg.h" +#include #include #include "util/buffer_util.h" @@ -20,7 +21,7 @@ device_msg_deserialize(const unsigned char *buf, size_t len, if (clipboard_len > len - 5) { return 0; // not available } - char *text = SDL_malloc(clipboard_len + 1); + char *text = malloc(clipboard_len + 1); if (!text) { LOGW("Could not allocate text for clipboard"); return -1; @@ -42,6 +43,6 @@ device_msg_deserialize(const unsigned char *buf, size_t len, void device_msg_destroy(struct device_msg *msg) { if (msg->type == DEVICE_MSG_TYPE_CLIPBOARD) { - SDL_free(msg->clipboard.text); + free(msg->clipboard.text); } } diff --git a/app/src/device_msg.h b/app/src/device_msg.h index bc13cebb..888d9216 100644 --- a/app/src/device_msg.h +++ b/app/src/device_msg.h @@ -19,7 +19,7 @@ struct device_msg { enum device_msg_type type; union { struct { - char *text; // owned, to be freed by SDL_free() + char *text; // owned, to be freed by free() } clipboard; }; }; diff --git a/app/src/file_handler.c b/app/src/file_handler.c index 3652ef90..4f60a101 100644 --- a/app/src/file_handler.c +++ b/app/src/file_handler.c @@ -11,7 +11,7 @@ static void file_handler_request_destroy(struct file_handler_request *req) { - SDL_free(req->file); + free(req->file); } bool @@ -30,7 +30,7 @@ file_handler_init(struct file_handler *file_handler, const char *serial, } if (serial) { - file_handler->serial = SDL_strdup(serial); + file_handler->serial = strdup(serial); if (!file_handler->serial) { LOGW("Could not strdup serial"); SDL_DestroyCond(file_handler->event_cond); @@ -56,7 +56,7 @@ void file_handler_destroy(struct file_handler *file_handler) { SDL_DestroyCond(file_handler->event_cond); SDL_DestroyMutex(file_handler->mutex); - SDL_free(file_handler->serial); + free(file_handler->serial); struct file_handler_request req; while (cbuf_take(&file_handler->queue, &req)) { diff --git a/app/src/file_handler.h b/app/src/file_handler.h index a8f469e2..193f68c3 100644 --- a/app/src/file_handler.h +++ b/app/src/file_handler.h @@ -50,7 +50,7 @@ file_handler_stop(struct file_handler *file_handler); void file_handler_join(struct file_handler *file_handler); -// take ownership of file, and will SDL_free() it +// take ownership of file, and will free() it bool file_handler_request(struct file_handler *file_handler, file_handler_action_t action, diff --git a/app/src/input_manager.c b/app/src/input_manager.c index df01ea38..432c4c83 100644 --- a/app/src/input_manager.c +++ b/app/src/input_manager.c @@ -190,13 +190,20 @@ set_device_clipboard(struct controller *controller, bool paste) { return; } + char *text_dup = strdup(text); + SDL_free(text); + if (!text_dup) { + LOGW("Could not strdup input text"); + return; + } + struct control_msg msg; msg.type = CONTROL_MSG_TYPE_SET_CLIPBOARD; - msg.set_clipboard.text = text; + msg.set_clipboard.text = text_dup; msg.set_clipboard.paste = paste; if (!controller_push_msg(controller, &msg)) { - SDL_free(text); + free(text_dup); LOGW("Could not request 'set device clipboard'"); } } @@ -242,11 +249,18 @@ clipboard_paste(struct controller *controller) { return; } + char *text_dup = strdup(text); + SDL_free(text); + if (!text_dup) { + LOGW("Could not strdup input text"); + return; + } + struct control_msg msg; msg.type = CONTROL_MSG_TYPE_INJECT_TEXT; - msg.inject_text.text = text; + msg.inject_text.text = text_dup; if (!controller_push_msg(controller, &msg)) { - SDL_free(text); + free(text_dup); LOGW("Could not request 'paste clipboard'"); } } @@ -291,13 +305,13 @@ input_manager_process_text_input(struct input_manager *im, struct control_msg msg; msg.type = CONTROL_MSG_TYPE_INJECT_TEXT; - msg.inject_text.text = SDL_strdup(event->text); + msg.inject_text.text = strdup(event->text); if (!msg.inject_text.text) { LOGW("Could not strdup input text"); return; } if (!controller_push_msg(im->controller, &msg)) { - SDL_free(msg.inject_text.text); + free(msg.inject_text.text); LOGW("Could not request 'inject text'"); } } diff --git a/app/src/recorder.c b/app/src/recorder.c index 6558d804..12047369 100644 --- a/app/src/recorder.c +++ b/app/src/recorder.c @@ -27,7 +27,7 @@ find_muxer(const char *name) { static struct record_packet * record_packet_new(const AVPacket *packet) { - struct record_packet *rec = SDL_malloc(sizeof(*rec)); + struct record_packet *rec = malloc(sizeof(*rec)); if (!rec) { return NULL; } @@ -37,7 +37,7 @@ record_packet_new(const AVPacket *packet) { av_init_packet(&rec->packet); if (av_packet_ref(&rec->packet, packet)) { - SDL_free(rec); + free(rec); return NULL; } return rec; @@ -46,7 +46,7 @@ record_packet_new(const AVPacket *packet) { static void record_packet_delete(struct record_packet *rec) { av_packet_unref(&rec->packet); - SDL_free(rec); + free(rec); } static void @@ -63,7 +63,7 @@ recorder_init(struct recorder *recorder, const char *filename, enum sc_record_format format, struct size declared_frame_size) { - recorder->filename = SDL_strdup(filename); + recorder->filename = strdup(filename); if (!recorder->filename) { LOGE("Could not strdup filename"); return false; @@ -72,7 +72,7 @@ recorder_init(struct recorder *recorder, recorder->mutex = SDL_CreateMutex(); if (!recorder->mutex) { LOGC("Could not create mutex"); - SDL_free(recorder->filename); + free(recorder->filename); return false; } @@ -80,7 +80,7 @@ recorder_init(struct recorder *recorder, if (!recorder->queue_cond) { LOGC("Could not create cond"); SDL_DestroyMutex(recorder->mutex); - SDL_free(recorder->filename); + free(recorder->filename); return false; } @@ -99,7 +99,7 @@ void recorder_destroy(struct recorder *recorder) { SDL_DestroyCond(recorder->queue_cond); SDL_DestroyMutex(recorder->mutex); - SDL_free(recorder->filename); + free(recorder->filename); } static const char * diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index ecc7a0e6..f1560130 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -226,13 +226,20 @@ handle_event(SDL_Event *event, const struct scrcpy_options *options) { if (!options->control) { break; } + char *file = strdup(event->drop.file); + SDL_free(event->drop.file); + if (!file) { + LOGW("Could not strdup drop filename\n"); + break; + } + file_handler_action_t action; - if (is_apk(event->drop.file)) { + if (is_apk(file)) { action = ACTION_INSTALL_APK; } else { action = ACTION_PUSH_FILE; } - file_handler_request(&file_handler, action, event->drop.file); + file_handler_request(&file_handler, action, file); break; } } @@ -286,7 +293,7 @@ av_log_callback(void *avcl, int level, const char *fmt, va_list vl) { if (priority == 0) { return; } - char *local_fmt = SDL_malloc(strlen(fmt) + 10); + char *local_fmt = malloc(strlen(fmt) + 10); if (!local_fmt) { LOGC("Could not allocate string"); return; @@ -295,7 +302,7 @@ av_log_callback(void *avcl, int level, const char *fmt, va_list vl) { strcpy(local_fmt, "[FFmpeg] "); strcpy(local_fmt + 9, fmt); SDL_LogMessageV(SDL_LOG_CATEGORY_VIDEO, priority, local_fmt, vl); - SDL_free(local_fmt); + free(local_fmt); } bool diff --git a/app/src/server.c b/app/src/server.c index f1ac7b46..bb08d56e 100644 --- a/app/src/server.c +++ b/app/src/server.c @@ -33,7 +33,7 @@ get_server_path(void) { #ifdef __WINDOWS__ char *server_path = utf8_from_wide_char(server_path_env); #else - char *server_path = SDL_strdup(server_path_env); + char *server_path = strdup(server_path_env); #endif if (!server_path) { LOGE("Could not allocate memory"); @@ -45,7 +45,7 @@ get_server_path(void) { #ifndef PORTABLE LOGD("Using server: " DEFAULT_SERVER_PATH); - char *server_path = SDL_strdup(DEFAULT_SERVER_PATH); + char *server_path = strdup(DEFAULT_SERVER_PATH); if (!server_path) { LOGE("Could not allocate memory"); return NULL; @@ -67,11 +67,11 @@ get_server_path(void) { // sizeof(SERVER_FILENAME) gives statically the size including the null byte size_t len = dirlen + 1 + sizeof(SERVER_FILENAME); - char *server_path = SDL_malloc(len); + char *server_path = malloc(len); if (!server_path) { LOGE("Could not alloc server path string, " "using " SERVER_FILENAME " from current directory"); - SDL_free(executable_path); + free(executable_path); return SERVER_FILENAME; } @@ -80,7 +80,7 @@ get_server_path(void) { memcpy(&server_path[dirlen + 1], SERVER_FILENAME, sizeof(SERVER_FILENAME)); // the final null byte has been copied with SERVER_FILENAME - SDL_free(executable_path); + free(executable_path); LOGD("Using server (portable): %s", server_path); return server_path; @@ -95,11 +95,11 @@ push_server(const char *serial) { } if (!is_regular_file(server_path)) { LOGE("'%s' does not exist or is not a regular file\n", server_path); - SDL_free(server_path); + free(server_path); return false; } process_t process = adb_push(serial, server_path, DEVICE_SERVER_PATH); - SDL_free(server_path); + free(server_path); return process_check_success(process, "adb push", true); } @@ -412,7 +412,7 @@ bool server_start(struct server *server, const char *serial, const struct server_params *params) { if (serial) { - server->serial = SDL_strdup(serial); + server->serial = strdup(serial); if (!server->serial) { return false; } @@ -462,7 +462,7 @@ error2: } disable_tunnel(server); error1: - SDL_free(server->serial); + free(server->serial); return false; } @@ -557,7 +557,7 @@ server_stop(struct server *server) { void server_destroy(struct server *server) { - SDL_free(server->serial); + free(server->serial); SDL_DestroyCond(server->process_terminated_cond); SDL_DestroyMutex(server->mutex); } diff --git a/app/src/sys/unix/process.c b/app/src/sys/unix/process.c index 293b2aff..8683a2da 100644 --- a/app/src/sys/unix/process.c +++ b/app/src/sys/unix/process.c @@ -156,7 +156,7 @@ get_executable_path(void) { return NULL; } buf[len] = '\0'; - return SDL_strdup(buf); + return strdup(buf); #else // in practice, we only need this feature for portable builds, only used on // Windows, so we don't care implementing it for every platform diff --git a/app/src/sys/win/process.c b/app/src/sys/win/process.c index 88242af3..f170e40d 100644 --- a/app/src/sys/win/process.c +++ b/app/src/sys/win/process.c @@ -41,7 +41,7 @@ process_execute(const char *const argv[], HANDLE *handle) { if (!CreateProcessW(NULL, wide, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi)) { - SDL_free(wide); + free(wide); *handle = NULL; if (GetLastError() == ERROR_FILE_NOT_FOUND) { return PROCESS_ERROR_MISSING_BINARY; @@ -49,7 +49,7 @@ process_execute(const char *const argv[], HANDLE *handle) { return PROCESS_ERROR_GENERIC; } - SDL_free(wide); + free(wide); *handle = pi.hProcess; return PROCESS_SUCCESS; } @@ -105,7 +105,7 @@ is_regular_file(const char *path) { struct _stat path_stat; int r = _wstat(wide_path, &path_stat); - SDL_free(wide_path); + free(wide_path); if (r) { perror("stat"); diff --git a/app/src/util/process.h b/app/src/util/process.h index 7e619a2b..7838a848 100644 --- a/app/src/util/process.h +++ b/app/src/util/process.h @@ -70,7 +70,7 @@ search_executable(const char *file); #endif // return the absolute path of the executable (the scrcpy binary) -// may be NULL on error; to be freed by SDL_free +// may be NULL on error; to be freed by free() char * get_executable_path(void); diff --git a/app/src/util/str_util.c b/app/src/util/str_util.c index babce4a1..352d1d2f 100644 --- a/app/src/util/str_util.c +++ b/app/src/util/str_util.c @@ -10,8 +10,6 @@ # include #endif -#include - size_t xstrncpy(char *dest, const char *src, size_t n) { size_t i; @@ -49,7 +47,7 @@ truncated: char * strquote(const char *src) { size_t len = strlen(src); - char *quoted = SDL_malloc(len + 3); + char *quoted = malloc(len + 3); if (!quoted) { return NULL; } @@ -167,7 +165,7 @@ utf8_to_wide_char(const char *utf8) { return NULL; } - wchar_t *wide = SDL_malloc(len * sizeof(wchar_t)); + wchar_t *wide = malloc(len * sizeof(wchar_t)); if (!wide) { return NULL; } @@ -183,7 +181,7 @@ utf8_from_wide_char(const wchar_t *ws) { return NULL; } - char *utf8 = SDL_malloc(len); + char *utf8 = malloc(len); if (!utf8) { return NULL; } diff --git a/app/tests/test_strutil.c b/app/tests/test_strutil.c index 9d35f983..ce0d5d30 100644 --- a/app/tests/test_strutil.c +++ b/app/tests/test_strutil.c @@ -4,7 +4,6 @@ #include #include #include -#include #include "util/str_util.h" @@ -138,7 +137,7 @@ static void test_strquote(void) { // add '"' at the beginning and the end assert(!strcmp("\"abcde\"", out)); - SDL_free(out); + free(out); } static void test_utf8_truncate(void) { From f6320c7e317ba9c5e5fda1fd7d4358646060412b Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 31 Jan 2021 18:24:35 +0100 Subject: [PATCH 283/450] Wrap SDL thread functions into scrcpy-specific API The goal is to expose a consistent API for system tools, and paves the way to make the "core" independant of SDL in the future. --- app/meson.build | 3 +- app/src/controller.c | 46 +++++++------- app/src/controller.h | 9 ++- app/src/decoder.c | 2 - app/src/file_handler.c | 49 +++++++-------- app/src/file_handler.h | 9 ++- app/src/fps_counter.c | 66 ++++++++++---------- app/src/fps_counter.h | 12 ++-- app/src/input_manager.c | 1 - app/src/receiver.c | 13 ++-- app/src/receiver.h | 7 +-- app/src/recorder.c | 50 +++++++-------- app/src/recorder.h | 9 ++- app/src/scrcpy.c | 1 - app/src/screen.c | 7 +-- app/src/server.c | 45 +++++++------- app/src/server.h | 8 +-- app/src/stream.c | 8 +-- app/src/stream.h | 4 +- app/src/util/lock.h | 75 ---------------------- app/src/util/thread.c | 133 ++++++++++++++++++++++++++++++++++++++++ app/src/util/thread.h | 66 ++++++++++++++++++++ app/src/video_buffer.c | 34 +++++----- app/src/video_buffer.h | 12 ++-- 24 files changed, 395 insertions(+), 274 deletions(-) delete mode 100644 app/src/util/lock.h create mode 100644 app/src/util/thread.c create mode 100644 app/src/util/thread.h diff --git a/app/meson.build b/app/meson.build index 2dade249..1f50209e 100644 --- a/app/meson.build +++ b/app/meson.build @@ -23,7 +23,8 @@ src = [ 'src/video_buffer.c', 'src/util/net.c', 'src/util/process.c', - 'src/util/str_util.c' + 'src/util/str_util.c', + 'src/util/thread.c', ] if host_machine.system() == 'windows' diff --git a/app/src/controller.c b/app/src/controller.c index da90fbf1..38b5e702 100644 --- a/app/src/controller.c +++ b/app/src/controller.c @@ -2,25 +2,27 @@ #include -#include "util/lock.h" #include "util/log.h" bool controller_init(struct controller *controller, socket_t control_socket) { cbuf_init(&controller->queue); - if (!receiver_init(&controller->receiver, control_socket)) { + bool ok = receiver_init(&controller->receiver, control_socket); + if (!ok) { return false; } - if (!(controller->mutex = SDL_CreateMutex())) { + ok = sc_mutex_init(&controller->mutex); + if (!ok) { receiver_destroy(&controller->receiver); return false; } - if (!(controller->msg_cond = SDL_CreateCond())) { + ok = sc_cond_init(&controller->msg_cond); + if (!ok) { receiver_destroy(&controller->receiver); - SDL_DestroyMutex(controller->mutex); + sc_mutex_destroy(&controller->mutex); return false; } @@ -32,8 +34,8 @@ controller_init(struct controller *controller, socket_t control_socket) { void controller_destroy(struct controller *controller) { - SDL_DestroyCond(controller->msg_cond); - SDL_DestroyMutex(controller->mutex); + sc_cond_destroy(&controller->msg_cond); + sc_mutex_destroy(&controller->mutex); struct control_msg msg; while (cbuf_take(&controller->queue, &msg)) { @@ -46,13 +48,13 @@ controller_destroy(struct controller *controller) { bool controller_push_msg(struct controller *controller, const struct control_msg *msg) { - mutex_lock(controller->mutex); + sc_mutex_lock(&controller->mutex); bool was_empty = cbuf_is_empty(&controller->queue); bool res = cbuf_push(&controller->queue, *msg); if (was_empty) { - cond_signal(controller->msg_cond); + sc_cond_signal(&controller->msg_cond); } - mutex_unlock(controller->mutex); + sc_mutex_unlock(&controller->mutex); return res; } @@ -73,20 +75,20 @@ run_controller(void *data) { struct controller *controller = data; for (;;) { - mutex_lock(controller->mutex); + sc_mutex_lock(&controller->mutex); while (!controller->stopped && cbuf_is_empty(&controller->queue)) { - cond_wait(controller->msg_cond, controller->mutex); + sc_cond_wait(&controller->msg_cond, &controller->mutex); } if (controller->stopped) { // stop immediately, do not process further msgs - mutex_unlock(controller->mutex); + sc_mutex_unlock(&controller->mutex); break; } struct control_msg msg; bool non_empty = cbuf_take(&controller->queue, &msg); assert(non_empty); (void) non_empty; - mutex_unlock(controller->mutex); + sc_mutex_unlock(&controller->mutex); bool ok = process_msg(controller, &msg); control_msg_destroy(&msg); @@ -102,16 +104,16 @@ bool controller_start(struct controller *controller) { LOGD("Starting controller thread"); - controller->thread = SDL_CreateThread(run_controller, "controller", - controller); - if (!controller->thread) { + bool ok = sc_thread_create(&controller->thread, run_controller, + "controller", controller); + if (!ok) { LOGC("Could not start controller thread"); return false; } if (!receiver_start(&controller->receiver)) { controller_stop(controller); - SDL_WaitThread(controller->thread, NULL); + sc_thread_join(&controller->thread, NULL); return false; } @@ -120,14 +122,14 @@ controller_start(struct controller *controller) { void controller_stop(struct controller *controller) { - mutex_lock(controller->mutex); + sc_mutex_lock(&controller->mutex); controller->stopped = true; - cond_signal(controller->msg_cond); - mutex_unlock(controller->mutex); + sc_cond_signal(&controller->msg_cond); + sc_mutex_unlock(&controller->mutex); } void controller_join(struct controller *controller) { - SDL_WaitThread(controller->thread, NULL); + sc_thread_join(&controller->thread, NULL); receiver_join(&controller->receiver); } diff --git a/app/src/controller.h b/app/src/controller.h index d6fe35e2..c53d0a61 100644 --- a/app/src/controller.h +++ b/app/src/controller.h @@ -4,21 +4,20 @@ #include "common.h" #include -#include -#include #include "control_msg.h" #include "receiver.h" #include "util/cbuf.h" #include "util/net.h" +#include "util/thread.h" struct control_msg_queue CBUF(struct control_msg, 64); struct controller { socket_t control_socket; - SDL_Thread *thread; - SDL_mutex *mutex; - SDL_cond *msg_cond; + sc_thread thread; + sc_mutex mutex; + sc_cond msg_cond; bool stopped; struct control_msg_queue queue; struct receiver receiver; diff --git a/app/src/decoder.c b/app/src/decoder.c index 50c55ab2..23f9ce9d 100644 --- a/app/src/decoder.c +++ b/app/src/decoder.c @@ -3,8 +3,6 @@ #include #include #include -#include -#include #include #include "events.h" diff --git a/app/src/file_handler.c b/app/src/file_handler.c index 4f60a101..2b08240c 100644 --- a/app/src/file_handler.c +++ b/app/src/file_handler.c @@ -4,7 +4,6 @@ #include #include "adb.h" -#include "util/lock.h" #include "util/log.h" #define DEFAULT_PUSH_TARGET "/sdcard/" @@ -20,12 +19,14 @@ file_handler_init(struct file_handler *file_handler, const char *serial, cbuf_init(&file_handler->queue); - if (!(file_handler->mutex = SDL_CreateMutex())) { + bool ok = sc_mutex_init(&file_handler->mutex); + if (!ok) { return false; } - if (!(file_handler->event_cond = SDL_CreateCond())) { - SDL_DestroyMutex(file_handler->mutex); + ok = sc_cond_init(&file_handler->event_cond); + if (!ok) { + sc_mutex_destroy(&file_handler->mutex); return false; } @@ -33,8 +34,8 @@ file_handler_init(struct file_handler *file_handler, const char *serial, file_handler->serial = strdup(serial); if (!file_handler->serial) { LOGW("Could not strdup serial"); - SDL_DestroyCond(file_handler->event_cond); - SDL_DestroyMutex(file_handler->mutex); + sc_cond_destroy(&file_handler->event_cond); + sc_mutex_destroy(&file_handler->mutex); return false; } } else { @@ -54,8 +55,8 @@ file_handler_init(struct file_handler *file_handler, const char *serial, void file_handler_destroy(struct file_handler *file_handler) { - SDL_DestroyCond(file_handler->event_cond); - SDL_DestroyMutex(file_handler->mutex); + sc_cond_destroy(&file_handler->event_cond); + sc_mutex_destroy(&file_handler->mutex); free(file_handler->serial); struct file_handler_request req; @@ -92,13 +93,13 @@ file_handler_request(struct file_handler *file_handler, .file = file, }; - mutex_lock(file_handler->mutex); + sc_mutex_lock(&file_handler->mutex); bool was_empty = cbuf_is_empty(&file_handler->queue); bool res = cbuf_push(&file_handler->queue, req); if (was_empty) { - cond_signal(file_handler->event_cond); + sc_cond_signal(&file_handler->event_cond); } - mutex_unlock(file_handler->mutex); + sc_mutex_unlock(&file_handler->mutex); return res; } @@ -107,14 +108,14 @@ run_file_handler(void *data) { struct file_handler *file_handler = data; for (;;) { - mutex_lock(file_handler->mutex); + sc_mutex_lock(&file_handler->mutex); file_handler->current_process = PROCESS_NONE; while (!file_handler->stopped && cbuf_is_empty(&file_handler->queue)) { - cond_wait(file_handler->event_cond, file_handler->mutex); + sc_cond_wait(&file_handler->event_cond, &file_handler->mutex); } if (file_handler->stopped) { // stop immediately, do not process further events - mutex_unlock(file_handler->mutex); + sc_mutex_unlock(&file_handler->mutex); break; } struct file_handler_request req; @@ -132,7 +133,7 @@ run_file_handler(void *data) { file_handler->push_target); } file_handler->current_process = process; - mutex_unlock(file_handler->mutex); + sc_mutex_unlock(&file_handler->mutex); if (req.action == ACTION_INSTALL_APK) { if (process_check_success(process, "adb install", false)) { @@ -150,13 +151,13 @@ run_file_handler(void *data) { } } - mutex_lock(file_handler->mutex); + sc_mutex_lock(&file_handler->mutex); // Close the process (it is necessary already terminated) // Execute this call with mutex locked to avoid race conditions with // file_handler_stop() process_close(file_handler->current_process); file_handler->current_process = PROCESS_NONE; - mutex_unlock(file_handler->mutex); + sc_mutex_unlock(&file_handler->mutex); file_handler_request_destroy(&req); } @@ -167,9 +168,9 @@ bool file_handler_start(struct file_handler *file_handler) { LOGD("Starting file_handler thread"); - file_handler->thread = SDL_CreateThread(run_file_handler, "file_handler", - file_handler); - if (!file_handler->thread) { + bool ok = sc_thread_create(&file_handler->thread, run_file_handler, + "file_handler", file_handler); + if (!ok) { LOGC("Could not start file_handler thread"); return false; } @@ -179,18 +180,18 @@ file_handler_start(struct file_handler *file_handler) { void file_handler_stop(struct file_handler *file_handler) { - mutex_lock(file_handler->mutex); + sc_mutex_lock(&file_handler->mutex); file_handler->stopped = true; - cond_signal(file_handler->event_cond); + sc_cond_signal(&file_handler->event_cond); if (file_handler->current_process != PROCESS_NONE) { if (!process_terminate(file_handler->current_process)) { LOGW("Could not terminate push/install process"); } } - mutex_unlock(file_handler->mutex); + sc_mutex_unlock(&file_handler->mutex); } void file_handler_join(struct file_handler *file_handler) { - SDL_WaitThread(file_handler->thread, NULL); + sc_thread_join(&file_handler->thread, NULL); } diff --git a/app/src/file_handler.h b/app/src/file_handler.h index 193f68c3..fe1d1804 100644 --- a/app/src/file_handler.h +++ b/app/src/file_handler.h @@ -4,11 +4,10 @@ #include "common.h" #include -#include -#include #include "adb.h" #include "util/cbuf.h" +#include "util/thread.h" typedef enum { ACTION_INSTALL_APK, @@ -25,9 +24,9 @@ struct file_handler_request_queue CBUF(struct file_handler_request, 16); struct file_handler { char *serial; const char *push_target; - SDL_Thread *thread; - SDL_mutex *mutex; - SDL_cond *event_cond; + sc_thread thread; + sc_mutex mutex; + sc_cond event_cond; bool stopped; bool initialized; process_t current_process; diff --git a/app/src/fps_counter.c b/app/src/fps_counter.c index e7607409..281c58cf 100644 --- a/app/src/fps_counter.c +++ b/app/src/fps_counter.c @@ -3,25 +3,24 @@ #include #include -#include "util/lock.h" #include "util/log.h" #define FPS_COUNTER_INTERVAL_MS 1000 bool fps_counter_init(struct fps_counter *counter) { - counter->mutex = SDL_CreateMutex(); - if (!counter->mutex) { + bool ok = sc_mutex_init(&counter->mutex); + if (!ok) { return false; } - counter->state_cond = SDL_CreateCond(); - if (!counter->state_cond) { - SDL_DestroyMutex(counter->mutex); + ok = sc_cond_init(&counter->state_cond); + if (!ok) { + sc_mutex_destroy(&counter->mutex); return false; } - counter->thread = NULL; + counter->thread_started = false; atomic_init(&counter->started, 0); // no need to initialize the other fields, they are unused until started @@ -30,8 +29,8 @@ fps_counter_init(struct fps_counter *counter) { void fps_counter_destroy(struct fps_counter *counter) { - SDL_DestroyCond(counter->state_cond); - SDL_DestroyMutex(counter->mutex); + sc_cond_destroy(&counter->state_cond); + sc_mutex_destroy(&counter->mutex); } static inline bool @@ -77,10 +76,10 @@ static int run_fps_counter(void *data) { struct fps_counter *counter = data; - mutex_lock(counter->mutex); + sc_mutex_lock(&counter->mutex); while (!counter->interrupted) { while (!counter->interrupted && !is_started(counter)) { - cond_wait(counter->state_cond, counter->mutex); + sc_cond_wait(&counter->state_cond, &counter->mutex); } while (!counter->interrupted && is_started(counter)) { uint32_t now = SDL_GetTicks(); @@ -90,32 +89,35 @@ run_fps_counter(void *data) { uint32_t remaining = counter->next_timestamp - now; // ignore the reason (timeout or signaled), we just loop anyway - cond_wait_timeout(counter->state_cond, counter->mutex, remaining); + sc_cond_timedwait(&counter->state_cond, &counter->mutex, remaining); } } - mutex_unlock(counter->mutex); + sc_mutex_unlock(&counter->mutex); return 0; } bool fps_counter_start(struct fps_counter *counter) { - mutex_lock(counter->mutex); + sc_mutex_lock(&counter->mutex); counter->next_timestamp = SDL_GetTicks() + FPS_COUNTER_INTERVAL_MS; counter->nr_rendered = 0; counter->nr_skipped = 0; - mutex_unlock(counter->mutex); + sc_mutex_unlock(&counter->mutex); set_started(counter, true); - cond_signal(counter->state_cond); + sc_cond_signal(&counter->state_cond); - // counter->thread is always accessed from the same thread, no need to lock - if (!counter->thread) { - counter->thread = - SDL_CreateThread(run_fps_counter, "fps counter", counter); - if (!counter->thread) { + // counter->thread_started and counter->thread are always accessed from the + // same thread, no need to lock + if (!counter->thread_started) { + bool ok = sc_thread_create(&counter->thread, run_fps_counter, + "fps counter", counter); + if (!ok) { LOGE("Could not start FPS counter thread"); return false; } + + counter->thread_started = true; } return true; @@ -124,7 +126,7 @@ fps_counter_start(struct fps_counter *counter) { void fps_counter_stop(struct fps_counter *counter) { set_started(counter, false); - cond_signal(counter->state_cond); + sc_cond_signal(&counter->state_cond); } bool @@ -134,21 +136,21 @@ fps_counter_is_started(struct fps_counter *counter) { void fps_counter_interrupt(struct fps_counter *counter) { - if (!counter->thread) { + if (!counter->thread_started) { return; } - mutex_lock(counter->mutex); + sc_mutex_lock(&counter->mutex); counter->interrupted = true; - mutex_unlock(counter->mutex); + sc_mutex_unlock(&counter->mutex); // wake up blocking wait - cond_signal(counter->state_cond); + sc_cond_signal(&counter->state_cond); } void fps_counter_join(struct fps_counter *counter) { - if (counter->thread) { - SDL_WaitThread(counter->thread, NULL); + if (counter->thread_started) { + sc_thread_join(&counter->thread, NULL); } } @@ -158,11 +160,11 @@ fps_counter_add_rendered_frame(struct fps_counter *counter) { return; } - mutex_lock(counter->mutex); + sc_mutex_lock(&counter->mutex); uint32_t now = SDL_GetTicks(); check_interval_expired(counter, now); ++counter->nr_rendered; - mutex_unlock(counter->mutex); + sc_mutex_unlock(&counter->mutex); } void @@ -171,9 +173,9 @@ fps_counter_add_skipped_frame(struct fps_counter *counter) { return; } - mutex_lock(counter->mutex); + sc_mutex_lock(&counter->mutex); uint32_t now = SDL_GetTicks(); check_interval_expired(counter, now); ++counter->nr_skipped; - mutex_unlock(counter->mutex); + sc_mutex_unlock(&counter->mutex); } diff --git a/app/src/fps_counter.h b/app/src/fps_counter.h index 68255bb6..de252586 100644 --- a/app/src/fps_counter.h +++ b/app/src/fps_counter.h @@ -6,13 +6,15 @@ #include #include #include -#include -#include + +#include "util/thread.h" struct fps_counter { - SDL_Thread *thread; - SDL_mutex *mutex; - SDL_cond *state_cond; + sc_thread thread; + sc_mutex mutex; + sc_cond state_cond; + + bool thread_started; // atomic so that we can check without locking the mutex // if the FPS counter is disabled, we don't want to lock unnecessarily diff --git a/app/src/input_manager.c b/app/src/input_manager.c index 432c4c83..fd780ae6 100644 --- a/app/src/input_manager.c +++ b/app/src/input_manager.c @@ -4,7 +4,6 @@ #include #include "event_converter.h" -#include "util/lock.h" #include "util/log.h" static const int ACTION_DOWN = 1; diff --git a/app/src/receiver.c b/app/src/receiver.c index 5b97f88b..337d2a17 100644 --- a/app/src/receiver.c +++ b/app/src/receiver.c @@ -4,12 +4,12 @@ #include #include "device_msg.h" -#include "util/lock.h" #include "util/log.h" bool receiver_init(struct receiver *receiver, socket_t control_socket) { - if (!(receiver->mutex = SDL_CreateMutex())) { + bool ok = sc_mutex_init(&receiver->mutex); + if (!ok) { return false; } receiver->control_socket = control_socket; @@ -18,7 +18,7 @@ receiver_init(struct receiver *receiver, socket_t control_socket) { void receiver_destroy(struct receiver *receiver) { - SDL_DestroyMutex(receiver->mutex); + sc_mutex_destroy(&receiver->mutex); } static void @@ -101,8 +101,9 @@ bool receiver_start(struct receiver *receiver) { LOGD("Starting receiver thread"); - receiver->thread = SDL_CreateThread(run_receiver, "receiver", receiver); - if (!receiver->thread) { + bool ok = sc_thread_create(&receiver->thread, run_receiver, "receiver", + receiver); + if (!ok) { LOGC("Could not start receiver thread"); return false; } @@ -112,5 +113,5 @@ receiver_start(struct receiver *receiver) { void receiver_join(struct receiver *receiver) { - SDL_WaitThread(receiver->thread, NULL); + sc_thread_join(&receiver->thread, NULL); } diff --git a/app/src/receiver.h b/app/src/receiver.h index 3b1cc03a..36523b62 100644 --- a/app/src/receiver.h +++ b/app/src/receiver.h @@ -4,17 +4,16 @@ #include "common.h" #include -#include -#include #include "util/net.h" +#include "util/thread.h" // receive events from the device // managed by the controller struct receiver { socket_t control_socket; - SDL_Thread *thread; - SDL_mutex *mutex; + sc_thread thread; + sc_mutex mutex; }; bool diff --git a/app/src/recorder.c b/app/src/recorder.c index 12047369..0aacb1a4 100644 --- a/app/src/recorder.c +++ b/app/src/recorder.c @@ -3,7 +3,6 @@ #include #include -#include "util/lock.h" #include "util/log.h" static const AVRational SCRCPY_TIME_BASE = {1, 1000000}; // timestamps in us @@ -69,17 +68,17 @@ recorder_init(struct recorder *recorder, return false; } - recorder->mutex = SDL_CreateMutex(); - if (!recorder->mutex) { + bool ok = sc_mutex_init(&recorder->mutex); + if (!ok) { LOGC("Could not create mutex"); free(recorder->filename); return false; } - recorder->queue_cond = SDL_CreateCond(); - if (!recorder->queue_cond) { + ok = sc_cond_init(&recorder->queue_cond); + if (!ok) { LOGC("Could not create cond"); - SDL_DestroyMutex(recorder->mutex); + sc_mutex_destroy(&recorder->mutex); free(recorder->filename); return false; } @@ -97,8 +96,8 @@ recorder_init(struct recorder *recorder, void recorder_destroy(struct recorder *recorder) { - SDL_DestroyCond(recorder->queue_cond); - SDL_DestroyMutex(recorder->mutex); + sc_cond_destroy(&recorder->queue_cond); + sc_mutex_destroy(&recorder->mutex); free(recorder->filename); } @@ -258,17 +257,17 @@ run_recorder(void *data) { struct recorder *recorder = data; for (;;) { - mutex_lock(recorder->mutex); + sc_mutex_lock(&recorder->mutex); while (!recorder->stopped && queue_is_empty(&recorder->queue)) { - cond_wait(recorder->queue_cond, recorder->mutex); + sc_cond_wait(&recorder->queue_cond, &recorder->mutex); } // if stopped is set, continue to process the remaining events (to // finish the recording) before actually stopping if (recorder->stopped && queue_is_empty(&recorder->queue)) { - mutex_unlock(recorder->mutex); + sc_mutex_unlock(&recorder->mutex); struct record_packet *last = recorder->previous; if (last) { // assign an arbitrary duration to the last packet @@ -288,7 +287,7 @@ run_recorder(void *data) { struct record_packet *rec; queue_take(&recorder->queue, next, &rec); - mutex_unlock(recorder->mutex); + sc_mutex_unlock(&recorder->mutex); // recorder->previous is only written from this thread, no need to lock struct record_packet *previous = recorder->previous; @@ -311,11 +310,11 @@ run_recorder(void *data) { if (!ok) { LOGE("Could not record packet"); - mutex_lock(recorder->mutex); + sc_mutex_lock(&recorder->mutex); recorder->failed = true; // discard pending packets recorder_queue_clear(&recorder->queue); - mutex_unlock(recorder->mutex); + sc_mutex_unlock(&recorder->mutex); break; } @@ -330,8 +329,9 @@ bool recorder_start(struct recorder *recorder) { LOGD("Starting recorder thread"); - recorder->thread = SDL_CreateThread(run_recorder, "recorder", recorder); - if (!recorder->thread) { + bool ok = sc_thread_create(&recorder->thread, run_recorder, "recorder", + recorder); + if (!ok) { LOGC("Could not start recorder thread"); return false; } @@ -341,38 +341,38 @@ recorder_start(struct recorder *recorder) { void recorder_stop(struct recorder *recorder) { - mutex_lock(recorder->mutex); + sc_mutex_lock(&recorder->mutex); recorder->stopped = true; - cond_signal(recorder->queue_cond); - mutex_unlock(recorder->mutex); + sc_cond_signal(&recorder->queue_cond); + sc_mutex_unlock(&recorder->mutex); } void recorder_join(struct recorder *recorder) { - SDL_WaitThread(recorder->thread, NULL); + sc_thread_join(&recorder->thread, NULL); } bool recorder_push(struct recorder *recorder, const AVPacket *packet) { - mutex_lock(recorder->mutex); + sc_mutex_lock(&recorder->mutex); assert(!recorder->stopped); if (recorder->failed) { // reject any new packet (this will stop the stream) - mutex_unlock(recorder->mutex); + sc_mutex_unlock(&recorder->mutex); return false; } struct record_packet *rec = record_packet_new(packet); if (!rec) { LOGC("Could not allocate record packet"); - mutex_unlock(recorder->mutex); + sc_mutex_unlock(&recorder->mutex); return false; } queue_push(&recorder->queue, next, rec); - cond_signal(recorder->queue_cond); + sc_cond_signal(&recorder->queue_cond); - mutex_unlock(recorder->mutex); + sc_mutex_unlock(&recorder->mutex); return true; } diff --git a/app/src/recorder.h b/app/src/recorder.h index 1e942110..be2b2dff 100644 --- a/app/src/recorder.h +++ b/app/src/recorder.h @@ -5,12 +5,11 @@ #include #include -#include -#include #include "coords.h" #include "scrcpy.h" #include "util/queue.h" +#include "util/thread.h" struct record_packet { AVPacket packet; @@ -26,9 +25,9 @@ struct recorder { struct size declared_frame_size; bool header_written; - SDL_Thread *thread; - SDL_mutex *mutex; - SDL_cond *queue_cond; + sc_thread thread; + sc_mutex mutex; + sc_cond queue_cond; bool stopped; // set on recorder_stop() by the stream reader bool failed; // set on packet write failure struct recorder_queue queue; diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index f1560130..747e25a6 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -26,7 +26,6 @@ #include "stream.h" #include "tiny_xpm.h" #include "video_buffer.h" -#include "util/lock.h" #include "util/log.h" #include "util/net.h" diff --git a/app/src/screen.c b/app/src/screen.c index 5bdfac2a..0f8a2226 100644 --- a/app/src/screen.c +++ b/app/src/screen.c @@ -8,7 +8,6 @@ #include "scrcpy.h" #include "tiny_xpm.h" #include "video_buffer.h" -#include "util/lock.h" #include "util/log.h" #define DISPLAY_MARGINS 96 @@ -454,15 +453,15 @@ update_texture(struct screen *screen, const AVFrame *frame) { bool screen_update_frame(struct screen *screen, struct video_buffer *vb) { - mutex_lock(vb->mutex); + sc_mutex_lock(&vb->mutex); const AVFrame *frame = video_buffer_consume_rendered_frame(vb); struct size new_frame_size = {frame->width, frame->height}; if (!prepare_for_frame(screen, new_frame_size)) { - mutex_unlock(vb->mutex); + sc_mutex_unlock(&vb->mutex); return false; } update_texture(screen, frame); - mutex_unlock(vb->mutex); + sc_mutex_unlock(&vb->mutex); screen_render(screen, false); return true; diff --git a/app/src/server.c b/app/src/server.c index bb08d56e..096ac18f 100644 --- a/app/src/server.c +++ b/app/src/server.c @@ -5,12 +5,10 @@ #include #include #include -#include #include #include #include "adb.h" -#include "util/lock.h" #include "util/log.h" #include "util/net.h" #include "util/str_util.h" @@ -357,18 +355,17 @@ bool server_init(struct server *server) { server->serial = NULL; server->process = PROCESS_NONE; - server->wait_server_thread = NULL; atomic_flag_clear_explicit(&server->server_socket_closed, memory_order_relaxed); - server->mutex = SDL_CreateMutex(); - if (!server->mutex) { + bool ok = sc_mutex_init(&server->mutex); + if (!ok) { return false; } - server->process_terminated_cond = SDL_CreateCond(); - if (!server->process_terminated_cond) { - SDL_DestroyMutex(server->mutex); + ok = sc_cond_init(&server->process_terminated_cond); + if (!ok) { + sc_mutex_destroy(&server->mutex); return false; } @@ -391,10 +388,10 @@ run_wait_server(void *data) { struct server *server = data; process_wait(server->process, false); // ignore exit code - mutex_lock(server->mutex); + sc_mutex_lock(&server->mutex); server->process_terminated = true; - cond_signal(server->process_terminated_cond); - mutex_unlock(server->mutex); + sc_cond_signal(&server->process_terminated_cond); + sc_mutex_unlock(&server->mutex); // no need for synchronization, server_socket is initialized before this // thread was created @@ -439,9 +436,9 @@ server_start(struct server *server, const char *serial, // things simple and multiplatform, just spawn a new thread waiting for the // server process and calling shutdown()/close() on the server socket if // necessary to wake up any accept() blocking call. - server->wait_server_thread = - SDL_CreateThread(run_wait_server, "wait-server", server); - if (!server->wait_server_thread) { + bool ok = sc_thread_create(&server->wait_server_thread, run_wait_server, + "wait-server", server); + if (!ok) { process_terminate(server->process); process_wait(server->process, true); // ignore exit code goto error2; @@ -531,33 +528,33 @@ server_stop(struct server *server) { } // Give some delay for the server to terminate properly - mutex_lock(server->mutex); - int r = 0; + sc_mutex_lock(&server->mutex); + bool signaled = false; if (!server->process_terminated) { #define WATCHDOG_DELAY_MS 1000 - r = cond_wait_timeout(server->process_terminated_cond, - server->mutex, - WATCHDOG_DELAY_MS); + signaled = sc_cond_timedwait(&server->process_terminated_cond, + &server->mutex, + WATCHDOG_DELAY_MS); } - mutex_unlock(server->mutex); + sc_mutex_unlock(&server->mutex); // After this delay, kill the server if it's not dead already. // On some devices, closing the sockets is not sufficient to wake up the // blocking calls while the device is asleep. - if (r == SDL_MUTEX_TIMEDOUT) { + if (!signaled) { // The process is terminated, but not reaped (closed) yet, so its PID // is still valid. LOGW("Killing the server..."); process_terminate(server->process); } - SDL_WaitThread(server->wait_server_thread, NULL); + sc_thread_join(&server->wait_server_thread, NULL); process_close(server->process); } void server_destroy(struct server *server) { free(server->serial); - SDL_DestroyCond(server->process_terminated_cond); - SDL_DestroyMutex(server->mutex); + sc_cond_destroy(&server->process_terminated_cond); + sc_mutex_destroy(&server->mutex); } diff --git a/app/src/server.h b/app/src/server.h index 1ac12b5f..83c528ef 100644 --- a/app/src/server.h +++ b/app/src/server.h @@ -6,21 +6,21 @@ #include #include #include -#include #include "adb.h" #include "scrcpy.h" #include "util/log.h" #include "util/net.h" +#include "util/thread.h" struct server { char *serial; process_t process; - SDL_Thread *wait_server_thread; + sc_thread wait_server_thread; atomic_flag server_socket_closed; - SDL_mutex *mutex; - SDL_cond *process_terminated_cond; + sc_mutex mutex; + sc_cond process_terminated_cond; bool process_terminated; socket_t server_socket; // only used if !tunnel_forward diff --git a/app/src/stream.c b/app/src/stream.c index e4c9f387..ba72f164 100644 --- a/app/src/stream.c +++ b/app/src/stream.c @@ -4,8 +4,6 @@ #include #include #include -#include -#include #include #include "decoder.h" @@ -279,8 +277,8 @@ bool stream_start(struct stream *stream) { LOGD("Starting stream thread"); - stream->thread = SDL_CreateThread(run_stream, "stream", stream); - if (!stream->thread) { + bool ok = sc_thread_create(&stream->thread, run_stream, "stream", stream); + if (!ok) { LOGC("Could not start stream thread"); return false; } @@ -296,5 +294,5 @@ stream_stop(struct stream *stream) { void stream_join(struct stream *stream) { - SDL_WaitThread(stream->thread, NULL); + sc_thread_join(&stream->thread, NULL); } diff --git a/app/src/stream.h b/app/src/stream.h index d308df88..784e0402 100644 --- a/app/src/stream.h +++ b/app/src/stream.h @@ -7,15 +7,15 @@ #include #include #include -#include #include "util/net.h" +#include "util/thread.h" struct video_buffer; struct stream { socket_t socket; - SDL_Thread *thread; + sc_thread thread; struct decoder *decoder; struct recorder *recorder; AVCodecContext *codec_ctx; diff --git a/app/src/util/lock.h b/app/src/util/lock.h deleted file mode 100644 index f031bd69..00000000 --- a/app/src/util/lock.h +++ /dev/null @@ -1,75 +0,0 @@ -#ifndef LOCK_H -#define LOCK_H - -#include "common.h" - -#include -#include - -#include "log.h" - -static inline void -mutex_lock(SDL_mutex *mutex) { - int r = SDL_LockMutex(mutex); -#ifndef NDEBUG - if (r) { - LOGC("Could not lock mutex: %s", SDL_GetError()); - abort(); - } -#else - (void) r; -#endif -} - -static inline void -mutex_unlock(SDL_mutex *mutex) { - int r = SDL_UnlockMutex(mutex); -#ifndef NDEBUG - if (r) { - LOGC("Could not unlock mutex: %s", SDL_GetError()); - abort(); - } -#else - (void) r; -#endif -} - -static inline void -cond_wait(SDL_cond *cond, SDL_mutex *mutex) { - int r = SDL_CondWait(cond, mutex); -#ifndef NDEBUG - if (r) { - LOGC("Could not wait on condition: %s", SDL_GetError()); - abort(); - } -#else - (void) r; -#endif -} - -static inline int -cond_wait_timeout(SDL_cond *cond, SDL_mutex *mutex, uint32_t ms) { - int r = SDL_CondWaitTimeout(cond, mutex, ms); -#ifndef NDEBUG - if (r < 0) { - LOGC("Could not wait on condition with timeout: %s", SDL_GetError()); - abort(); - } -#endif - return r; -} - -static inline void -cond_signal(SDL_cond *cond) { - int r = SDL_CondSignal(cond); -#ifndef NDEBUG - if (r) { - LOGC("Could not signal a condition: %s", SDL_GetError()); - abort(); - } -#else - (void) r; -#endif -} - -#endif diff --git a/app/src/util/thread.c b/app/src/util/thread.c new file mode 100644 index 00000000..bb4feb62 --- /dev/null +++ b/app/src/util/thread.c @@ -0,0 +1,133 @@ +#include "thread.h" + +#include +#include + +#include "log.h" + +bool +sc_thread_create(sc_thread *thread, sc_thread_fn fn, const char *name, + void *userdata) { + SDL_Thread *sdl_thread = SDL_CreateThread(fn, name, userdata); + if (!sdl_thread) { + return false; + } + + thread->thread = sdl_thread; + return true; +} + +void +sc_thread_join(sc_thread *thread, int *status) { + SDL_WaitThread(thread->thread, status); +} + +bool +sc_mutex_init(sc_mutex *mutex) { + SDL_mutex *sdl_mutex = SDL_CreateMutex(); + if (!sdl_mutex) { + return false; + } + + mutex->mutex = sdl_mutex; + return true; +} + +void +sc_mutex_destroy(sc_mutex *mutex) { + SDL_DestroyMutex(mutex->mutex); +} + +void +sc_mutex_lock(sc_mutex *mutex) { + int r = SDL_LockMutex(mutex->mutex); +#ifndef NDEBUG + if (r) { + LOGC("Could not lock mutex: %s", SDL_GetError()); + abort(); + } +#else + (void) r; +#endif +} + +void +sc_mutex_unlock(sc_mutex *mutex) { + int r = SDL_UnlockMutex(mutex->mutex); +#ifndef NDEBUG + if (r) { + LOGC("Could not lock mutex: %s", SDL_GetError()); + abort(); + } +#else + (void) r; +#endif +} + +bool +sc_cond_init(sc_cond *cond) { + SDL_cond *sdl_cond = SDL_CreateCond(); + if (!sdl_cond) { + return false; + } + + cond->cond = sdl_cond; + return true; +} + +void +sc_cond_destroy(sc_cond *cond) { + SDL_DestroyCond(cond->cond); +} + +void +sc_cond_wait(sc_cond *cond, sc_mutex *mutex) { + int r = SDL_CondWait(cond->cond, mutex->mutex); +#ifndef NDEBUG + if (r) { + LOGC("Could not wait on condition: %s", SDL_GetError()); + abort(); + } +#else + (void) r; +#endif +} + +bool +sc_cond_timedwait(sc_cond *cond, sc_mutex *mutex, uint32_t ms) { + int r = SDL_CondWaitTimeout(cond->cond, mutex->mutex, ms); +#ifndef NDEBUG + if (r < 0) { + LOGC("Could not wait on condition with timeout: %s", SDL_GetError()); + abort(); + } +#endif + assert(r == 0 || r == SDL_MUTEX_TIMEDOUT); + return r == 0; +} + +void +sc_cond_signal(sc_cond *cond) { + int r = SDL_CondSignal(cond->cond); +#ifndef NDEBUG + if (r) { + LOGC("Could not signal a condition: %s", SDL_GetError()); + abort(); + } +#else + (void) r; +#endif +} + +void +sc_cond_broadcast(sc_cond *cond) { + int r = SDL_CondBroadcast(cond->cond); +#ifndef NDEBUG + if (r) { + LOGC("Could not broadcast a condition: %s", SDL_GetError()); + abort(); + } +#else + (void) r; +#endif +} diff --git a/app/src/util/thread.h b/app/src/util/thread.h new file mode 100644 index 00000000..5fbaf3c1 --- /dev/null +++ b/app/src/util/thread.h @@ -0,0 +1,66 @@ +#ifndef SC_THREAD_H +#define SC_THREAD_H + +#include "common.h" + +#include +#include + +/* Forward declarations */ +typedef struct SDL_Thread SDL_Thread; +typedef struct SDL_mutex SDL_mutex; +typedef struct SDL_cond SDL_cond; + +typedef int sc_thread_fn(void *); + +typedef struct sc_thread { + SDL_Thread *thread; +} sc_thread; + +typedef struct sc_mutex { + SDL_mutex *mutex; +} sc_mutex; + +typedef struct sc_cond { + SDL_cond *cond; +} sc_cond; + +bool +sc_thread_create(sc_thread *thread, sc_thread_fn fn, const char *name, + void *userdata); + +void +sc_thread_join(sc_thread *thread, int *status); + +bool +sc_mutex_init(sc_mutex *mutex); + +void +sc_mutex_destroy(sc_mutex *mutex); + +void +sc_mutex_lock(sc_mutex *mutex); + +void +sc_mutex_unlock(sc_mutex *mutex); + +bool +sc_cond_init(sc_cond *cond); + +void +sc_cond_destroy(sc_cond *cond); + +void +sc_cond_wait(sc_cond *cond, sc_mutex *mutex); + +// return true on signaled, false on timeout +bool +sc_cond_timedwait(sc_cond *cond, sc_mutex *mutex, uint32_t ms); + +void +sc_cond_signal(sc_cond *cond); + +void +sc_cond_broadcast(sc_cond *cond); + +#endif diff --git a/app/src/video_buffer.c b/app/src/video_buffer.c index d656c6f4..660c6929 100644 --- a/app/src/video_buffer.c +++ b/app/src/video_buffer.c @@ -1,11 +1,9 @@ #include "video_buffer.h" #include -#include #include #include -#include "util/lock.h" #include "util/log.h" bool @@ -13,22 +11,26 @@ video_buffer_init(struct video_buffer *vb, struct fps_counter *fps_counter, bool render_expired_frames) { vb->fps_counter = fps_counter; - if (!(vb->decoding_frame = av_frame_alloc())) { + vb->decoding_frame = av_frame_alloc(); + if (!vb->decoding_frame) { goto error_0; } - if (!(vb->rendering_frame = av_frame_alloc())) { + vb->rendering_frame = av_frame_alloc(); + if (!vb->rendering_frame) { goto error_1; } - if (!(vb->mutex = SDL_CreateMutex())) { + bool ok = sc_mutex_init(&vb->mutex); + if (!ok) { goto error_2; } vb->render_expired_frames = render_expired_frames; if (render_expired_frames) { - if (!(vb->rendering_frame_consumed_cond = SDL_CreateCond())) { - SDL_DestroyMutex(vb->mutex); + ok = sc_cond_init(&vb->rendering_frame_consumed_cond); + if (!ok) { + sc_mutex_destroy(&vb->mutex); goto error_2; } // interrupted is not used if expired frames are not rendered @@ -53,9 +55,9 @@ error_0: void video_buffer_destroy(struct video_buffer *vb) { if (vb->render_expired_frames) { - SDL_DestroyCond(vb->rendering_frame_consumed_cond); + sc_cond_destroy(&vb->rendering_frame_consumed_cond); } - SDL_DestroyMutex(vb->mutex); + sc_mutex_destroy(&vb->mutex); av_frame_free(&vb->rendering_frame); av_frame_free(&vb->decoding_frame); } @@ -70,11 +72,11 @@ video_buffer_swap_frames(struct video_buffer *vb) { void video_buffer_offer_decoded_frame(struct video_buffer *vb, bool *previous_frame_skipped) { - mutex_lock(vb->mutex); + sc_mutex_lock(&vb->mutex); if (vb->render_expired_frames) { // wait for the current (expired) frame to be consumed while (!vb->rendering_frame_consumed && !vb->interrupted) { - cond_wait(vb->rendering_frame_consumed_cond, vb->mutex); + sc_cond_wait(&vb->rendering_frame_consumed_cond, &vb->mutex); } } else if (!vb->rendering_frame_consumed) { fps_counter_add_skipped_frame(vb->fps_counter); @@ -85,7 +87,7 @@ video_buffer_offer_decoded_frame(struct video_buffer *vb, *previous_frame_skipped = !vb->rendering_frame_consumed; vb->rendering_frame_consumed = false; - mutex_unlock(vb->mutex); + sc_mutex_unlock(&vb->mutex); } const AVFrame * @@ -95,7 +97,7 @@ video_buffer_consume_rendered_frame(struct video_buffer *vb) { fps_counter_add_rendered_frame(vb->fps_counter); if (vb->render_expired_frames) { // unblock video_buffer_offer_decoded_frame() - cond_signal(vb->rendering_frame_consumed_cond); + sc_cond_signal(&vb->rendering_frame_consumed_cond); } return vb->rendering_frame; } @@ -103,10 +105,10 @@ video_buffer_consume_rendered_frame(struct video_buffer *vb) { void video_buffer_interrupt(struct video_buffer *vb) { if (vb->render_expired_frames) { - mutex_lock(vb->mutex); + sc_mutex_lock(&vb->mutex); vb->interrupted = true; - mutex_unlock(vb->mutex); + sc_mutex_unlock(&vb->mutex); // wake up blocking wait - cond_signal(vb->rendering_frame_consumed_cond); + sc_cond_signal(&vb->rendering_frame_consumed_cond); } } diff --git a/app/src/video_buffer.h b/app/src/video_buffer.h index 68ef8e04..fbab046b 100644 --- a/app/src/video_buffer.h +++ b/app/src/video_buffer.h @@ -4,9 +4,9 @@ #include "common.h" #include -#include #include "fps_counter.h" +#include "util/thread.h" // forward declarations typedef struct AVFrame AVFrame; @@ -14,10 +14,10 @@ typedef struct AVFrame AVFrame; struct video_buffer { AVFrame *decoding_frame; AVFrame *rendering_frame; - SDL_mutex *mutex; + sc_mutex mutex; bool render_expired_frames; bool interrupted; - SDL_cond *rendering_frame_consumed_cond; + sc_cond rendering_frame_consumed_cond; bool rendering_frame_consumed; struct fps_counter *fps_counter; }; @@ -30,16 +30,16 @@ void video_buffer_destroy(struct video_buffer *vb); // set the decoded frame as ready for rendering -// this function locks frames->mutex during its execution +// this function locks vb->mutex during its execution // the output flag is set to report whether the previous frame has been skipped void video_buffer_offer_decoded_frame(struct video_buffer *vb, bool *previous_frame_skipped); // mark the rendering frame as consumed and return it -// MUST be called with frames->mutex locked!!! +// MUST be called with vb->mutex locked!!! // the caller is expected to render the returned frame to some texture before -// unlocking frames->mutex +// unlocking vb->mutex const AVFrame * video_buffer_consume_rendered_frame(struct video_buffer *vb); From d2689fc1683a18bfc9e0af9b5dcbe623a28b6e02 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 31 Jan 2021 18:54:52 +0100 Subject: [PATCH 284/450] Expose thread id --- app/src/util/thread.c | 5 +++++ app/src/util/thread.h | 4 ++++ 2 files changed, 9 insertions(+) diff --git a/app/src/util/thread.c b/app/src/util/thread.c index bb4feb62..1fc6bb19 100644 --- a/app/src/util/thread.c +++ b/app/src/util/thread.c @@ -64,6 +64,11 @@ sc_mutex_unlock(sc_mutex *mutex) { #endif } +sc_thread_id +sc_thread_get_id(void) { + return SDL_ThreadID(); +} + bool sc_cond_init(sc_cond *cond) { SDL_cond *sdl_cond = SDL_CreateCond(); diff --git a/app/src/util/thread.h b/app/src/util/thread.h index 5fbaf3c1..85a2aca0 100644 --- a/app/src/util/thread.h +++ b/app/src/util/thread.h @@ -12,6 +12,7 @@ typedef struct SDL_mutex SDL_mutex; typedef struct SDL_cond SDL_cond; typedef int sc_thread_fn(void *); +typedef unsigned int sc_thread_id; typedef struct sc_thread { SDL_Thread *thread; @@ -44,6 +45,9 @@ sc_mutex_lock(sc_mutex *mutex); void sc_mutex_unlock(sc_mutex *mutex); +sc_thread_id +sc_thread_get_id(void); + bool sc_cond_init(sc_cond *cond); From 21d206f360535047e16cc4ea7de889a55bd9444d Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 31 Jan 2021 18:55:03 +0100 Subject: [PATCH 285/450] Expose mutex assertions Add a function to assert that the mutex is held (or not). --- app/src/util/thread.c | 19 +++++++++++++++++++ app/src/util/thread.h | 11 +++++++++++ 2 files changed, 30 insertions(+) diff --git a/app/src/util/thread.c b/app/src/util/thread.c index 1fc6bb19..fa774cfe 100644 --- a/app/src/util/thread.c +++ b/app/src/util/thread.c @@ -30,6 +30,9 @@ sc_mutex_init(sc_mutex *mutex) { } mutex->mutex = sdl_mutex; +#ifndef NDEBUG + mutex->locker = 0; +#endif return true; } @@ -46,6 +49,8 @@ sc_mutex_lock(sc_mutex *mutex) { LOGC("Could not lock mutex: %s", SDL_GetError()); abort(); } + + mutex->locker = sc_thread_get_id(); #else (void) r; #endif @@ -53,6 +58,9 @@ sc_mutex_lock(sc_mutex *mutex) { void sc_mutex_unlock(sc_mutex *mutex) { +#ifndef NDEBUG + mutex->locker = 0; +#endif int r = SDL_UnlockMutex(mutex->mutex); #ifndef NDEBUG if (r) { @@ -69,6 +77,13 @@ sc_thread_get_id(void) { return SDL_ThreadID(); } +#ifndef NDEBUG +bool +sc_mutex_held(struct sc_mutex *mutex) { + return mutex->locker == sc_thread_get_id(); +} +#endif + bool sc_cond_init(sc_cond *cond) { SDL_cond *sdl_cond = SDL_CreateCond(); @@ -93,6 +108,8 @@ sc_cond_wait(sc_cond *cond, sc_mutex *mutex) { LOGC("Could not wait on condition: %s", SDL_GetError()); abort(); } + + mutex->locker = sc_thread_get_id(); #else (void) r; #endif @@ -106,6 +123,8 @@ sc_cond_timedwait(sc_cond *cond, sc_mutex *mutex, uint32_t ms) { LOGC("Could not wait on condition with timeout: %s", SDL_GetError()); abort(); } + + mutex->locker = sc_thread_get_id(); #endif assert(r == 0 || r == SDL_MUTEX_TIMEDOUT); return r == 0; diff --git a/app/src/util/thread.h b/app/src/util/thread.h index 85a2aca0..d23e1432 100644 --- a/app/src/util/thread.h +++ b/app/src/util/thread.h @@ -20,6 +20,9 @@ typedef struct sc_thread { typedef struct sc_mutex { SDL_mutex *mutex; +#ifndef NDEBUG + sc_thread_id locker; +#endif } sc_mutex; typedef struct sc_cond { @@ -48,6 +51,14 @@ sc_mutex_unlock(sc_mutex *mutex); sc_thread_id sc_thread_get_id(void); +#ifndef NDEBUG +bool +sc_mutex_held(struct sc_mutex *mutex); +# define sc_mutex_assert(mutex) assert(sc_mutex_held(mutex)) +#else +# define sc_mutex_assert(mutex) +#endif + bool sc_cond_init(sc_cond *cond); From 54f5c42d7b0e8b9778f9a6423a8cfe174ae06d08 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 31 Jan 2021 18:56:50 +0100 Subject: [PATCH 286/450] Add mutex assertions --- app/src/video_buffer.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/src/video_buffer.c b/app/src/video_buffer.c index 660c6929..95cfd755 100644 --- a/app/src/video_buffer.c +++ b/app/src/video_buffer.c @@ -64,6 +64,7 @@ video_buffer_destroy(struct video_buffer *vb) { static void video_buffer_swap_frames(struct video_buffer *vb) { + sc_mutex_assert(&vb->mutex); AVFrame *tmp = vb->decoding_frame; vb->decoding_frame = vb->rendering_frame; vb->rendering_frame = tmp; @@ -92,6 +93,7 @@ video_buffer_offer_decoded_frame(struct video_buffer *vb, const AVFrame * video_buffer_consume_rendered_frame(struct video_buffer *vb) { + sc_mutex_assert(&vb->mutex); assert(!vb->rendering_frame_consumed); vb->rendering_frame_consumed = true; fps_counter_add_rendered_frame(vb->fps_counter); From c53bd4d8b68d63ed7e6c4cb20e81fd0d7bb9c0ec Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Mon, 1 Feb 2021 22:20:02 +0100 Subject: [PATCH 287/450] Assert non-recursive usage of mutexes --- app/src/util/thread.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/src/util/thread.c b/app/src/util/thread.c index fa774cfe..a0a99f20 100644 --- a/app/src/util/thread.c +++ b/app/src/util/thread.c @@ -43,6 +43,8 @@ sc_mutex_destroy(sc_mutex *mutex) { void sc_mutex_lock(sc_mutex *mutex) { + // SDL mutexes are recursive, but we don't want to use recursive mutexes + assert(!sc_mutex_held(mutex)); int r = SDL_LockMutex(mutex->mutex); #ifndef NDEBUG if (r) { @@ -59,6 +61,7 @@ sc_mutex_lock(sc_mutex *mutex) { void sc_mutex_unlock(sc_mutex *mutex) { #ifndef NDEBUG + assert(sc_mutex_held(mutex)); mutex->locker = 0; #endif int r = SDL_UnlockMutex(mutex->mutex); From c0c4ba700904d54f087f6cc5a2e5821124ff42db Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Mon, 1 Feb 2021 09:38:11 +0100 Subject: [PATCH 288/450] Add intermediate frame in video buffer There were only two frames simultaneously: - one used by the decoder; - one used by the renderer. When the decoder finished decoding a frame, it swapped it with the rendering frame. Adding a third frame provides several benefits: - the decoder do not have to wait for the renderer to release the mutex; - it simplifies the video_buffer API; - it makes the rendering frame valid until the next call to video_buffer_take_rendering_frame(), which will be useful for swscaling on window resize. --- app/src/screen.c | 5 +--- app/src/video_buffer.c | 65 ++++++++++++++++++++++++++++-------------- app/src/video_buffer.h | 32 ++++++++++++++++----- 3 files changed, 70 insertions(+), 32 deletions(-) diff --git a/app/src/screen.c b/app/src/screen.c index 0f8a2226..5ec416f9 100644 --- a/app/src/screen.c +++ b/app/src/screen.c @@ -453,15 +453,12 @@ update_texture(struct screen *screen, const AVFrame *frame) { bool screen_update_frame(struct screen *screen, struct video_buffer *vb) { - sc_mutex_lock(&vb->mutex); - const AVFrame *frame = video_buffer_consume_rendered_frame(vb); + const AVFrame *frame = video_buffer_take_rendering_frame(vb); struct size new_frame_size = {frame->width, frame->height}; if (!prepare_for_frame(screen, new_frame_size)) { - sc_mutex_unlock(&vb->mutex); return false; } update_texture(screen, frame); - sc_mutex_unlock(&vb->mutex); screen_render(screen, false); return true; diff --git a/app/src/video_buffer.c b/app/src/video_buffer.c index 95cfd755..4c8dd5da 100644 --- a/app/src/video_buffer.c +++ b/app/src/video_buffer.c @@ -16,19 +16,24 @@ video_buffer_init(struct video_buffer *vb, struct fps_counter *fps_counter, goto error_0; } + vb->pending_frame = av_frame_alloc(); + if (!vb->pending_frame) { + goto error_1; + } + vb->rendering_frame = av_frame_alloc(); if (!vb->rendering_frame) { - goto error_1; + goto error_2; } bool ok = sc_mutex_init(&vb->mutex); if (!ok) { - goto error_2; + goto error_3; } vb->render_expired_frames = render_expired_frames; if (render_expired_frames) { - ok = sc_cond_init(&vb->rendering_frame_consumed_cond); + ok = sc_cond_init(&vb->pending_frame_consumed_cond); if (!ok) { sc_mutex_destroy(&vb->mutex); goto error_2; @@ -40,12 +45,14 @@ video_buffer_init(struct video_buffer *vb, struct fps_counter *fps_counter, // there is initially no rendering frame, so consider it has already been // consumed - vb->rendering_frame_consumed = true; + vb->pending_frame_consumed = true; return true; -error_2: +error_3: av_frame_free(&vb->rendering_frame); +error_2: + av_frame_free(&vb->pending_frame); error_1: av_frame_free(&vb->decoding_frame); error_0: @@ -55,19 +62,28 @@ error_0: void video_buffer_destroy(struct video_buffer *vb) { if (vb->render_expired_frames) { - sc_cond_destroy(&vb->rendering_frame_consumed_cond); + sc_cond_destroy(&vb->pending_frame_consumed_cond); } sc_mutex_destroy(&vb->mutex); av_frame_free(&vb->rendering_frame); + av_frame_free(&vb->pending_frame); av_frame_free(&vb->decoding_frame); } static void -video_buffer_swap_frames(struct video_buffer *vb) { +video_buffer_swap_decoding_frame(struct video_buffer *vb) { sc_mutex_assert(&vb->mutex); AVFrame *tmp = vb->decoding_frame; - vb->decoding_frame = vb->rendering_frame; - vb->rendering_frame = tmp; + vb->decoding_frame = vb->pending_frame; + vb->pending_frame = tmp; +} + +static void +video_buffer_swap_rendering_frame(struct video_buffer *vb) { + sc_mutex_assert(&vb->mutex); + AVFrame *tmp = vb->rendering_frame; + vb->rendering_frame = vb->pending_frame; + vb->pending_frame = tmp; } void @@ -76,31 +92,38 @@ video_buffer_offer_decoded_frame(struct video_buffer *vb, sc_mutex_lock(&vb->mutex); if (vb->render_expired_frames) { // wait for the current (expired) frame to be consumed - while (!vb->rendering_frame_consumed && !vb->interrupted) { - sc_cond_wait(&vb->rendering_frame_consumed_cond, &vb->mutex); + while (!vb->pending_frame_consumed && !vb->interrupted) { + sc_cond_wait(&vb->pending_frame_consumed_cond, &vb->mutex); } - } else if (!vb->rendering_frame_consumed) { + } else if (!vb->pending_frame_consumed) { fps_counter_add_skipped_frame(vb->fps_counter); } - video_buffer_swap_frames(vb); + video_buffer_swap_decoding_frame(vb); - *previous_frame_skipped = !vb->rendering_frame_consumed; - vb->rendering_frame_consumed = false; + *previous_frame_skipped = !vb->pending_frame_consumed; + vb->pending_frame_consumed = false; sc_mutex_unlock(&vb->mutex); } const AVFrame * -video_buffer_consume_rendered_frame(struct video_buffer *vb) { - sc_mutex_assert(&vb->mutex); - assert(!vb->rendering_frame_consumed); - vb->rendering_frame_consumed = true; +video_buffer_take_rendering_frame(struct video_buffer *vb) { + sc_mutex_lock(&vb->mutex); + assert(!vb->pending_frame_consumed); + vb->pending_frame_consumed = true; + fps_counter_add_rendered_frame(vb->fps_counter); + + video_buffer_swap_rendering_frame(vb); + if (vb->render_expired_frames) { // unblock video_buffer_offer_decoded_frame() - sc_cond_signal(&vb->rendering_frame_consumed_cond); + sc_cond_signal(&vb->pending_frame_consumed_cond); } + sc_mutex_unlock(&vb->mutex); + + // rendering_frame is only written from this thread, no need to lock return vb->rendering_frame; } @@ -111,6 +134,6 @@ video_buffer_interrupt(struct video_buffer *vb) { vb->interrupted = true; sc_mutex_unlock(&vb->mutex); // wake up blocking wait - sc_cond_signal(&vb->rendering_frame_consumed_cond); + sc_cond_signal(&vb->pending_frame_consumed_cond); } } diff --git a/app/src/video_buffer.h b/app/src/video_buffer.h index fbab046b..3e52caa7 100644 --- a/app/src/video_buffer.h +++ b/app/src/video_buffer.h @@ -11,14 +11,35 @@ // forward declarations typedef struct AVFrame AVFrame; +/** + * There are 3 frames in memory: + * - one frame is held by the decoder (decoding_frame) + * - one frame is held by the renderer (rendering_frame) + * - one frame is shared between the decoder and the renderer (pending_frame) + * + * The decoder decodes a packet into the decoding_frame (it may takes time). + * + * Once the frame is decoded, it calls video_buffer_offer_decoded_frame(), + * which swaps the decoding and pending frames. + * + * When the renderer is notified that a new frame is available, it calls + * video_buffer_take_rendering_frame() to retrieve it, which swaps the pending + * and rendering frames. The frame is valid until the next call, without + * blocking the decoder. + */ + struct video_buffer { AVFrame *decoding_frame; + AVFrame *pending_frame; AVFrame *rendering_frame; + sc_mutex mutex; bool render_expired_frames; bool interrupted; - sc_cond rendering_frame_consumed_cond; - bool rendering_frame_consumed; + + sc_cond pending_frame_consumed_cond; + bool pending_frame_consumed; + struct fps_counter *fps_counter; }; @@ -30,18 +51,15 @@ void video_buffer_destroy(struct video_buffer *vb); // set the decoded frame as ready for rendering -// this function locks vb->mutex during its execution // the output flag is set to report whether the previous frame has been skipped void video_buffer_offer_decoded_frame(struct video_buffer *vb, bool *previous_frame_skipped); // mark the rendering frame as consumed and return it -// MUST be called with vb->mutex locked!!! -// the caller is expected to render the returned frame to some texture before -// unlocking vb->mutex +// the frame is valid until the next call to this function const AVFrame * -video_buffer_consume_rendered_frame(struct video_buffer *vb); +video_buffer_take_rendering_frame(struct video_buffer *vb); // wake up and avoid any blocking call void From 862948b13267fa1516840a464c59a8566d9ac28a Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 7 Feb 2021 19:12:14 +0100 Subject: [PATCH 289/450] Make use_opengl local The flag is used only locally, there is no need to store it in the screen structure. --- app/src/screen.c | 5 ++--- app/src/screen.h | 2 -- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/app/src/screen.c b/app/src/screen.c index 5ec416f9..fb2eb6cd 100644 --- a/app/src/screen.c +++ b/app/src/screen.c @@ -280,8 +280,8 @@ screen_init_rendering(struct screen *screen, const char *window_title, LOGI("Renderer: %s", renderer_name ? renderer_name : "(unknown)"); // starts with "opengl" - screen->use_opengl = renderer_name && !strncmp(renderer_name, "opengl", 6); - if (screen->use_opengl) { + bool use_opengl = renderer_name && !strncmp(renderer_name, "opengl", 6); + if (use_opengl) { struct sc_opengl *gl = &screen->gl; sc_opengl_init(gl); @@ -444,7 +444,6 @@ update_texture(struct screen *screen, const AVFrame *frame) { frame->data[2], frame->linesize[2]); if (screen->mipmaps) { - assert(screen->use_opengl); SDL_GL_BindTexture(screen->texture, NULL, NULL); screen->gl.GenerateMipmap(GL_TEXTURE_2D); SDL_GL_UnbindTexture(screen->texture); diff --git a/app/src/screen.h b/app/src/screen.h index ea94d538..35d5df50 100644 --- a/app/src/screen.h +++ b/app/src/screen.h @@ -16,7 +16,6 @@ struct screen { SDL_Window *window; SDL_Renderer *renderer; SDL_Texture *texture; - bool use_opengl; struct sc_opengl gl; struct size frame_size; struct size content_size; // rotated frame_size @@ -41,7 +40,6 @@ struct screen { .window = NULL, \ .renderer = NULL, \ .texture = NULL, \ - .use_opengl = false, \ .gl = {0}, \ .frame_size = { \ .width = 0, \ From a566635c43ea7b8d6c60a9cad9b63dca8ac3d078 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sat, 13 Feb 2021 14:32:24 +0100 Subject: [PATCH 290/450] Log mipmaps error only if mipmaps are enabled --- app/src/screen.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/screen.c b/app/src/screen.c index fb2eb6cd..1136d547 100644 --- a/app/src/screen.c +++ b/app/src/screen.c @@ -301,7 +301,7 @@ screen_init_rendering(struct screen *screen, const char *window_title, } else { LOGI("Trilinear filtering disabled"); } - } else { + } else if (mipmaps) { LOGD("Trilinear filtering disabled (not an OpenGL renderer)"); } From 626094ad130225df0bcd319656c97fe44c7024e7 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 12 Feb 2021 23:18:13 +0100 Subject: [PATCH 291/450] Handle window events only once visible This will avoid corner cases where we need to resize while no frame has been received yet. --- app/src/scrcpy.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index 747e25a6..505b5eaf 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -184,7 +184,9 @@ handle_event(SDL_Event *event, const struct scrcpy_options *options) { } break; case SDL_WINDOWEVENT: - screen_handle_window_event(&screen, &event->window); + if (screen.has_frame) { + screen_handle_window_event(&screen, &event->window); + } break; case SDL_TEXTINPUT: if (!options->control) { From 0538e9645b3aebddbe9d579cbc5c4ca8f64c3659 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sat, 13 Feb 2021 14:40:00 +0100 Subject: [PATCH 292/450] Improve error handling in screen initialization After the struct screen is initialized, the window, the renderer and the texture are necessarily valid, so there is no need to check in screen_destroy(). --- app/src/screen.c | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/app/src/screen.c b/app/src/screen.c index 1136d547..959af9c7 100644 --- a/app/src/screen.c +++ b/app/src/screen.c @@ -270,7 +270,7 @@ screen_init_rendering(struct screen *screen, const char *window_title, SDL_RENDERER_ACCELERATED); if (!screen->renderer) { LOGC("Could not create renderer: %s", SDL_GetError()); - screen_destroy(screen); + SDL_DestroyWindow(screen->window); return false; } @@ -318,7 +318,8 @@ screen_init_rendering(struct screen *screen, const char *window_title, screen->texture = create_texture(screen); if (!screen->texture) { LOGC("Could not create texture: %s", SDL_GetError()); - screen_destroy(screen); + SDL_DestroyRenderer(screen->renderer); + SDL_DestroyWindow(screen->window); return false; } @@ -339,15 +340,9 @@ screen_show_window(struct screen *screen) { void screen_destroy(struct screen *screen) { - if (screen->texture) { - SDL_DestroyTexture(screen->texture); - } - if (screen->renderer) { - SDL_DestroyRenderer(screen->renderer); - } - if (screen->window) { - SDL_DestroyWindow(screen->window); - } + SDL_DestroyTexture(screen->texture); + SDL_DestroyRenderer(screen->renderer); + SDL_DestroyWindow(screen->window); } static void From ea2369f568ccb2dd8fceb83487171eb0ffba26c9 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Mon, 15 Feb 2021 18:28:41 +0100 Subject: [PATCH 293/450] Reference video buffer from screen This paves the way to handle EVENT_NEW_FRAME from screen.c, by allowing to call screen_update_frame() without an explicit video_buffer instance. --- app/src/scrcpy.c | 6 ++++-- app/src/screen.c | 7 ++++--- app/src/screen.h | 6 ++++-- 3 files changed, 12 insertions(+), 7 deletions(-) diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index 505b5eaf..f669ef40 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -30,7 +30,7 @@ #include "util/net.h" static struct server server; -static struct screen screen = SCREEN_INITIALIZER; +static struct screen screen; static struct fps_counter fps_counter; static struct video_buffer video_buffer; static struct stream stream; @@ -179,7 +179,7 @@ handle_event(SDL_Event *event, const struct scrcpy_options *options) { // this is the very first frame, show the window screen_show_window(&screen); } - if (!screen_update_frame(&screen, &video_buffer)) { + if (!screen_update_frame(&screen)) { return EVENT_RESULT_CONTINUE; } break; @@ -429,6 +429,8 @@ scrcpy(const struct scrcpy_options *options) { const char *window_title = options->window_title ? options->window_title : device_name; + screen_init(&screen, &video_buffer); + if (!screen_init_rendering(&screen, window_title, frame_size, options->always_on_top, options->window_x, options->window_y, options->window_width, diff --git a/app/src/screen.c b/app/src/screen.c index 959af9c7..4d01ba7e 100644 --- a/app/src/screen.c +++ b/app/src/screen.c @@ -191,8 +191,9 @@ screen_update_content_rect(struct screen *screen) { } void -screen_init(struct screen *screen) { +screen_init(struct screen *screen, struct video_buffer *vb) { *screen = (struct screen) SCREEN_INITIALIZER; + screen->vb = vb; } static inline SDL_Texture * @@ -446,8 +447,8 @@ update_texture(struct screen *screen, const AVFrame *frame) { } bool -screen_update_frame(struct screen *screen, struct video_buffer *vb) { - const AVFrame *frame = video_buffer_take_rendering_frame(vb); +screen_update_frame(struct screen *screen) { + const AVFrame *frame = video_buffer_take_rendering_frame(screen->vb); struct size new_frame_size = {frame->width, frame->height}; if (!prepare_for_frame(screen, new_frame_size)) { return false; diff --git a/app/src/screen.h b/app/src/screen.h index 35d5df50..6db52dec 100644 --- a/app/src/screen.h +++ b/app/src/screen.h @@ -13,6 +13,7 @@ struct video_buffer; struct screen { + struct video_buffer *vb; SDL_Window *window; SDL_Renderer *renderer; SDL_Texture *texture; @@ -37,6 +38,7 @@ struct screen { }; #define SCREEN_INITIALIZER { \ + .vb = NULL, \ .window = NULL, \ .renderer = NULL, \ .texture = NULL, \ @@ -70,7 +72,7 @@ struct screen { // initialize default values void -screen_init(struct screen *screen); +screen_init(struct screen *screen, struct video_buffer *vb); // initialize screen, create window, renderer and texture (window is hidden) // window_x and window_y accept SC_WINDOW_POSITION_UNDEFINED @@ -91,7 +93,7 @@ screen_destroy(struct screen *screen); // resize if necessary and write the rendered frame into the texture bool -screen_update_frame(struct screen *screen, struct video_buffer *vb); +screen_update_frame(struct screen *screen); // render the texture to the renderer // From 50b4a730e31e220a465e3b72bd8a5a10f0c6ef08 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Mon, 15 Feb 2021 18:44:53 +0100 Subject: [PATCH 294/450] Handle screen-related events from screen.c --- app/src/scrcpy.c | 19 ++++--------------- app/src/screen.c | 32 ++++++++++++++++++++++++++++++-- app/src/screen.h | 10 +++------- 3 files changed, 37 insertions(+), 24 deletions(-) diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index f669ef40..a150c3dd 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -173,21 +173,6 @@ handle_event(SDL_Event *event, const struct scrcpy_options *options) { case SDL_QUIT: LOGD("User requested to quit"); return EVENT_RESULT_STOPPED_BY_USER; - case EVENT_NEW_FRAME: - if (!screen.has_frame) { - screen.has_frame = true; - // this is the very first frame, show the window - screen_show_window(&screen); - } - if (!screen_update_frame(&screen)) { - return EVENT_RESULT_CONTINUE; - } - break; - case SDL_WINDOWEVENT: - if (screen.has_frame) { - screen_handle_window_event(&screen, &event->window); - } - break; case SDL_TEXTINPUT: if (!options->control) { break; @@ -244,6 +229,10 @@ handle_event(SDL_Event *event, const struct scrcpy_options *options) { break; } } + + bool consumed = screen_handle_event(&screen, event); + (void) consumed; + return EVENT_RESULT_CONTINUE; } diff --git a/app/src/screen.c b/app/src/screen.c index 4d01ba7e..0cc90ad8 100644 --- a/app/src/screen.c +++ b/app/src/screen.c @@ -4,6 +4,7 @@ #include #include +#include "events.h" #include "icon.xpm" #include "scrcpy.h" #include "tiny_xpm.h" @@ -446,7 +447,7 @@ update_texture(struct screen *screen, const AVFrame *frame) { } } -bool +static bool screen_update_frame(struct screen *screen) { const AVFrame *frame = video_buffer_take_rendering_frame(screen->vb); struct size new_frame_size = {frame->width, frame->height}; @@ -540,7 +541,7 @@ screen_resize_to_pixel_perfect(struct screen *screen) { content_size.height); } -void +static void screen_handle_window_event(struct screen *screen, const SDL_WindowEvent *event) { switch (event->event) { @@ -567,6 +568,33 @@ screen_handle_window_event(struct screen *screen, } } + +bool +screen_handle_event(struct screen *screen, SDL_Event *event) { + switch (event->type) { + case EVENT_NEW_FRAME: + if (!screen->has_frame) { + screen->has_frame = true; + // this is the very first frame, show the window + screen_show_window(screen); + } + bool ok = screen_update_frame(screen); + if (!ok) { + LOGW("Frame update failed\n"); + } + return true; + case SDL_WINDOWEVENT: + if (!screen->has_frame) { + // Do nothing + return true; + } + screen_handle_window_event(screen, &event->window); + return true; + } + + return false; +} + struct point screen_convert_drawable_to_frame_coords(struct screen *screen, int32_t x, int32_t y) { diff --git a/app/src/screen.h b/app/src/screen.h index 6db52dec..171717ab 100644 --- a/app/src/screen.h +++ b/app/src/screen.h @@ -91,10 +91,6 @@ screen_show_window(struct screen *screen); void screen_destroy(struct screen *screen); -// resize if necessary and write the rendered frame into the texture -bool -screen_update_frame(struct screen *screen); - // render the texture to the renderer // // Set the update_content_rect flag if the window or content size may have @@ -118,9 +114,9 @@ screen_resize_to_pixel_perfect(struct screen *screen); void screen_set_rotation(struct screen *screen, unsigned rotation); -// react to window events -void -screen_handle_window_event(struct screen *screen, const SDL_WindowEvent *event); +// react to SDL events +bool +screen_handle_event(struct screen *screen, SDL_Event *event); // convert point from window coordinates to frame coordinates // x and y are expressed in pixels From 76a3d9805bf44f1d73fc47605b26f031c23773fa Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Mon, 15 Feb 2021 18:47:17 +0100 Subject: [PATCH 295/450] Inline window events handling Now that all screen-related events are handled from screen.c, there is no need for a separate method for window events. --- app/src/screen.c | 52 +++++++++++++++++++++--------------------------- 1 file changed, 23 insertions(+), 29 deletions(-) diff --git a/app/src/screen.c b/app/src/screen.c index 0cc90ad8..4b0321eb 100644 --- a/app/src/screen.c +++ b/app/src/screen.c @@ -541,34 +541,6 @@ screen_resize_to_pixel_perfect(struct screen *screen) { content_size.height); } -static void -screen_handle_window_event(struct screen *screen, - const SDL_WindowEvent *event) { - switch (event->event) { - case SDL_WINDOWEVENT_EXPOSED: - screen_render(screen, true); - break; - case SDL_WINDOWEVENT_SIZE_CHANGED: - screen_render(screen, true); - break; - case SDL_WINDOWEVENT_MAXIMIZED: - screen->maximized = true; - break; - case SDL_WINDOWEVENT_RESTORED: - if (screen->fullscreen) { - // On Windows, in maximized+fullscreen, disabling fullscreen - // mode unexpectedly triggers the "restored" then "maximized" - // events, leaving the window in a weird state (maximized - // according to the events, but not maximized visually). - break; - } - screen->maximized = false; - apply_pending_resize(screen); - break; - } -} - - bool screen_handle_event(struct screen *screen, SDL_Event *event) { switch (event->type) { @@ -588,7 +560,29 @@ screen_handle_event(struct screen *screen, SDL_Event *event) { // Do nothing return true; } - screen_handle_window_event(screen, &event->window); + switch (event->window.event) { + case SDL_WINDOWEVENT_EXPOSED: + screen_render(screen, true); + break; + case SDL_WINDOWEVENT_SIZE_CHANGED: + screen_render(screen, true); + break; + case SDL_WINDOWEVENT_MAXIMIZED: + screen->maximized = true; + break; + case SDL_WINDOWEVENT_RESTORED: + if (screen->fullscreen) { + // On Windows, in maximized+fullscreen, disabling + // fullscreen mode unexpectedly triggers the "restored" + // then "maximized" events, leaving the window in a + // weird state (maximized according to the events, but + // not maximized visually). + break; + } + screen->maximized = false; + apply_pending_resize(screen); + break; + } return true; } From 24b637b9728818755f2db84f18025261047d213a Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Mon, 15 Feb 2021 18:53:23 +0100 Subject: [PATCH 296/450] Handle im-related events from input_manager.c --- app/src/input_manager.c | 55 ++++++++++++++++++++++++++++++++++++----- app/src/input_manager.h | 25 ++----------------- app/src/scrcpy.c | 41 +++++------------------------- 3 files changed, 57 insertions(+), 64 deletions(-) diff --git a/app/src/input_manager.c b/app/src/input_manager.c index fd780ae6..20ea05c5 100644 --- a/app/src/input_manager.c +++ b/app/src/input_manager.c @@ -286,7 +286,7 @@ rotate_client_right(struct screen *screen) { screen_set_rotation(screen, new_rotation); } -void +static void input_manager_process_text_input(struct input_manager *im, const SDL_TextInputEvent *event) { if (is_shortcut_mod(im, SDL_GetModState())) { @@ -366,7 +366,7 @@ convert_input_key(const SDL_KeyboardEvent *from, struct control_msg *to, return true; } -void +static void input_manager_process_key(struct input_manager *im, const SDL_KeyboardEvent *event) { // control: indicates the state of the command-line option --no-control @@ -551,7 +551,7 @@ convert_mouse_motion(const SDL_MouseMotionEvent *from, struct screen *screen, return true; } -void +static void input_manager_process_mouse_motion(struct input_manager *im, const SDL_MouseMotionEvent *event) { if (!event->state) { @@ -605,7 +605,7 @@ convert_touch(const SDL_TouchFingerEvent *from, struct screen *screen, return true; } -void +static void input_manager_process_touch(struct input_manager *im, const SDL_TouchFingerEvent *event) { struct control_msg msg; @@ -637,7 +637,7 @@ convert_mouse_button(const SDL_MouseButtonEvent *from, struct screen *screen, return true; } -void +static void input_manager_process_mouse_button(struct input_manager *im, const SDL_MouseButtonEvent *event) { bool control = im->control; @@ -736,7 +736,7 @@ convert_mouse_wheel(const SDL_MouseWheelEvent *from, struct screen *screen, return true; } -void +static void input_manager_process_mouse_wheel(struct input_manager *im, const SDL_MouseWheelEvent *event) { struct control_msg msg; @@ -746,3 +746,46 @@ input_manager_process_mouse_wheel(struct input_manager *im, } } } + +bool +input_manager_handle_event(struct input_manager *im, SDL_Event *event) { + switch (event->type) { + case SDL_TEXTINPUT: + if (!im->control) { + return true; + } + input_manager_process_text_input(im, &event->text); + return true; + case SDL_KEYDOWN: + case SDL_KEYUP: + // some key events do not interact with the device, so process the + // event even if control is disabled + input_manager_process_key(im, &event->key); + return true; + case SDL_MOUSEMOTION: + if (!im->control) { + break; + } + input_manager_process_mouse_motion(im, &event->motion); + return true; + case SDL_MOUSEWHEEL: + if (!im->control) { + break; + } + input_manager_process_mouse_wheel(im, &event->wheel); + return true; + case SDL_MOUSEBUTTONDOWN: + case SDL_MOUSEBUTTONUP: + // some mouse events do not interact with the device, so process + // the event even if control is disabled + input_manager_process_mouse_button(im, &event->button); + return true; + case SDL_FINGERMOTION: + case SDL_FINGERDOWN: + case SDL_FINGERUP: + input_manager_process_touch(im, &event->tfinger); + return true; + } + + return false; +} diff --git a/app/src/input_manager.h b/app/src/input_manager.h index a23a731d..6acb354d 100644 --- a/app/src/input_manager.h +++ b/app/src/input_manager.h @@ -40,28 +40,7 @@ void input_manager_init(struct input_manager *im, const struct scrcpy_options *options); -void -input_manager_process_text_input(struct input_manager *im, - const SDL_TextInputEvent *event); - -void -input_manager_process_key(struct input_manager *im, - const SDL_KeyboardEvent *event); - -void -input_manager_process_mouse_motion(struct input_manager *im, - const SDL_MouseMotionEvent *event); - -void -input_manager_process_touch(struct input_manager *im, - const SDL_TouchFingerEvent *event); - -void -input_manager_process_mouse_button(struct input_manager *im, - const SDL_MouseButtonEvent *event); - -void -input_manager_process_mouse_wheel(struct input_manager *im, - const SDL_MouseWheelEvent *event); +bool +input_manager_handle_event(struct input_manager *im, SDL_Event *event); #endif diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index a150c3dd..d24bba2c 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -173,41 +173,6 @@ handle_event(SDL_Event *event, const struct scrcpy_options *options) { case SDL_QUIT: LOGD("User requested to quit"); return EVENT_RESULT_STOPPED_BY_USER; - case SDL_TEXTINPUT: - if (!options->control) { - break; - } - input_manager_process_text_input(&input_manager, &event->text); - break; - case SDL_KEYDOWN: - case SDL_KEYUP: - // some key events do not interact with the device, so process the - // event even if control is disabled - input_manager_process_key(&input_manager, &event->key); - break; - case SDL_MOUSEMOTION: - if (!options->control) { - break; - } - input_manager_process_mouse_motion(&input_manager, &event->motion); - break; - case SDL_MOUSEWHEEL: - if (!options->control) { - break; - } - input_manager_process_mouse_wheel(&input_manager, &event->wheel); - break; - case SDL_MOUSEBUTTONDOWN: - case SDL_MOUSEBUTTONUP: - // some mouse events do not interact with the device, so process - // the event even if control is disabled - input_manager_process_mouse_button(&input_manager, &event->button); - break; - case SDL_FINGERMOTION: - case SDL_FINGERDOWN: - case SDL_FINGERUP: - input_manager_process_touch(&input_manager, &event->tfinger); - break; case SDL_DROPFILE: { if (!options->control) { break; @@ -231,8 +196,14 @@ handle_event(SDL_Event *event, const struct scrcpy_options *options) { } bool consumed = screen_handle_event(&screen, event); + if (consumed) { + goto end; + } + + consumed = input_manager_handle_event(&input_manager, event); (void) consumed; +end: return EVENT_RESULT_CONTINUE; } From 9cd1a7380db1760807f5ea8077d315e678dabaf2 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Tue, 16 Feb 2021 23:26:35 +0100 Subject: [PATCH 297/450] Enable NDEBUG via Meson built-in option --- app/meson.build | 3 --- meson.build | 1 + 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/app/meson.build b/app/meson.build index 1f50209e..75a2fd71 100644 --- a/app/meson.build +++ b/app/meson.build @@ -99,9 +99,6 @@ foreach f : check_functions endif endforeach -# expose the build type -conf.set('NDEBUG', get_option('buildtype') != 'debug') - # the version, updated on release conf.set_quoted('SCRCPY_VERSION', meson.project_version()) diff --git a/meson.build b/meson.build index 230f8d21..c2989ec7 100644 --- a/meson.build +++ b/meson.build @@ -4,6 +4,7 @@ project('scrcpy', 'c', default_options: [ 'c_std=c11', 'warning_level=2', + 'b_ndebug=if-release', ]) if get_option('compile_app') From 0207e3df334435d481fec23465f4165c46e6ba22 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 21 Feb 2021 21:44:30 +0100 Subject: [PATCH 298/450] Remove unused no_window field --- app/src/screen.h | 2 -- 1 file changed, 2 deletions(-) diff --git a/app/src/screen.h b/app/src/screen.h index 171717ab..f2224ff1 100644 --- a/app/src/screen.h +++ b/app/src/screen.h @@ -33,7 +33,6 @@ struct screen { bool has_frame; bool fullscreen; bool maximized; - bool no_window; bool mipmaps; }; @@ -66,7 +65,6 @@ struct screen { .has_frame = false, \ .fullscreen = false, \ .maximized = false, \ - .no_window = false, \ .mipmaps = false, \ } From a3aa5ac95e4cd5179e79881f15e8152933cbdd4d Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Mon, 22 Feb 2021 22:03:50 +0100 Subject: [PATCH 299/450] Insert numerical values statically in usage string --- app/src/cli.c | 29 +++++++++++++++++++---------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/app/src/cli.c b/app/src/cli.c index fbdef07f..d9cacf15 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -10,6 +10,9 @@ #include "util/log.h" #include "util/str_util.h" +#define STR_IMPL_(x) #x +#define STR(x) STR_IMPL_(x) + void scrcpy_print_usage(const char *arg0) { fprintf(stderr, @@ -23,7 +26,7 @@ scrcpy_print_usage(const char *arg0) { " -b, --bit-rate value\n" " Encode the video at the given bit-rate, expressed in bits/s.\n" " Unit suffixes are supported: 'K' (x1000) and 'M' (x1000000).\n" - " Default is %d.\n" + " Default is " STR(DEFAULT_BIT_RATE) ".\n" "\n" " --codec-options key[:type]=value[,...]\n" " Set a list of comma-separated key:type=value options for the\n" @@ -81,7 +84,12 @@ scrcpy_print_usage(const char *arg0) { " Possible values are -1 (unlocked), 0, 1, 2 and 3.\n" " Natural device orientation is 0, and each increment adds a\n" " 90 degrees rotation counterclockwise.\n" - " Default is %d%s.\n" +#if DEFAULT_LOCK_VIDEO_ORIENTATION == -1 +# define DEFAULT_LOCK_VIDEO_ORIENTATION_STR "-1 (unlocked)" +#else +# define DEFAULT_LOCK_VIDEO_ORIENTATION_STR STR(DEFAULT_LOCK_VIDEO_ORIENTATION) +#endif + " Default is " DEFAULT_LOCK_VIDEO_ORIENTATION_STR ".\n" "\n" " --max-fps value\n" " Limit the frame rate of screen capture (officially supported\n" @@ -91,7 +99,12 @@ scrcpy_print_usage(const char *arg0) { " Limit both the width and height of the video to value. The\n" " other dimension is computed so that the device aspect-ratio\n" " is preserved.\n" - " Default is %d%s.\n" +#if DEFAULT_MAX_SIZE == 0 +# define DEFAULT_MAX_SIZE_STR "0 (unlimited)" +#else +# define DEFAULT_MAX_SIZE_STR STR(DEFAULT_MAX_SIZE) +#endif + " Default is " DEFAULT_MAX_SIZE_STR ".\n" "\n" " -n, --no-control\n" " Disable device control (mirror the device in read-only).\n" @@ -110,7 +123,8 @@ scrcpy_print_usage(const char *arg0) { "\n" " -p, --port port[:port]\n" " Set the TCP port (range) used by the client to listen.\n" - " Default is %d:%d.\n" + " Default is " STR(DEFAULT_LOCAL_PORT_RANGE_FIRST) ":" + STR(DEFAULT_LOCAL_PORT_RANGE_LAST) ".\n" "\n" " --prefer-text\n" " Inject alpha characters and space as text events instead of\n" @@ -297,12 +311,7 @@ scrcpy_print_usage(const char *arg0) { "\n" " Drag & drop APK file\n" " Install APK from computer\n" - "\n", - arg0, - DEFAULT_BIT_RATE, - DEFAULT_LOCK_VIDEO_ORIENTATION, DEFAULT_LOCK_VIDEO_ORIENTATION >= 0 ? "" : " (unlocked)", - DEFAULT_MAX_SIZE, DEFAULT_MAX_SIZE ? "" : " (unlimited)", - DEFAULT_LOCAL_PORT_RANGE_FIRST, DEFAULT_LOCAL_PORT_RANGE_LAST); + "\n", arg0); } static bool From b16b65a715485b3bf2babe51e7c17e3c3a355adf Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Tue, 23 Feb 2021 09:37:16 +0100 Subject: [PATCH 300/450] Simplify default values It makes sense to extract default values for bitrate and port range (which are arbitrary and might be changed in the future). However, the default values for "max size" and "lock video orientation" are naturally unlimited/unlocked, and will never be changed. Extracting these options just added complexity for no benefit, so hardcode them. --- app/meson.build | 10 ---------- app/src/cli.c | 14 ++------------ app/src/scrcpy.h | 4 ++-- 3 files changed, 4 insertions(+), 24 deletions(-) diff --git a/app/meson.build b/app/meson.build index 75a2fd71..d230702e 100644 --- a/app/meson.build +++ b/app/meson.build @@ -114,16 +114,6 @@ conf.set('PORTABLE', get_option('portable')) 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 -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 # overridden by option --bit-rate conf.set('DEFAULT_BIT_RATE', '8000000') # 8Mbps diff --git a/app/src/cli.c b/app/src/cli.c index d9cacf15..7f31b32c 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -84,12 +84,7 @@ scrcpy_print_usage(const char *arg0) { " Possible values are -1 (unlocked), 0, 1, 2 and 3.\n" " Natural device orientation is 0, and each increment adds a\n" " 90 degrees rotation counterclockwise.\n" -#if DEFAULT_LOCK_VIDEO_ORIENTATION == -1 -# define DEFAULT_LOCK_VIDEO_ORIENTATION_STR "-1 (unlocked)" -#else -# define DEFAULT_LOCK_VIDEO_ORIENTATION_STR STR(DEFAULT_LOCK_VIDEO_ORIENTATION) -#endif - " Default is " DEFAULT_LOCK_VIDEO_ORIENTATION_STR ".\n" + " Default is -1 (unlocked).\n" "\n" " --max-fps value\n" " Limit the frame rate of screen capture (officially supported\n" @@ -99,12 +94,7 @@ scrcpy_print_usage(const char *arg0) { " Limit both the width and height of the video to value. The\n" " other dimension is computed so that the device aspect-ratio\n" " is preserved.\n" -#if DEFAULT_MAX_SIZE == 0 -# define DEFAULT_MAX_SIZE_STR "0 (unlimited)" -#else -# define DEFAULT_MAX_SIZE_STR STR(DEFAULT_MAX_SIZE) -#endif - " Default is " DEFAULT_MAX_SIZE_STR ".\n" + " Default is 0 (unlimited).\n" "\n" " -n, --no-control\n" " Disable device control (mirror the device in read-only).\n" diff --git a/app/src/scrcpy.h b/app/src/scrcpy.h index 2253cc28..b877a987 100644 --- a/app/src/scrcpy.h +++ b/app/src/scrcpy.h @@ -103,10 +103,10 @@ struct scrcpy_options { .data = {SC_MOD_LALT, SC_MOD_LSUPER}, \ .count = 2, \ }, \ - .max_size = DEFAULT_MAX_SIZE, \ + .max_size = 0, \ .bit_rate = DEFAULT_BIT_RATE, \ .max_fps = 0, \ - .lock_video_orientation = DEFAULT_LOCK_VIDEO_ORIENTATION, \ + .lock_video_orientation = -1, \ .rotation = 0, \ .window_x = SC_WINDOW_POSITION_UNDEFINED, \ .window_y = SC_WINDOW_POSITION_UNDEFINED, \ From a2919b3ef2d3b08f426ac53ff1999c0f7f925e9f Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sat, 27 Feb 2021 00:20:18 +0100 Subject: [PATCH 301/450] Fix release instructions in BUILD.md Makefile.CrossWindows have been renamed to release.mk, which is called from release.sh. --- BUILD.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/BUILD.md b/BUILD.md index 52029b6b..30a911e1 100644 --- a/BUILD.md +++ b/BUILD.md @@ -112,7 +112,7 @@ sudo apt install openjdk-8-jdk Then generate the releases: ```bash -make -f Makefile.CrossWindows +./release.sh ``` It will generate win32 and win64 releases into `dist/`. From 7b51a0313ed9423f9252108fa573af68c11e6a2c Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sat, 27 Feb 2021 00:27:58 +0100 Subject: [PATCH 302/450] Update another java version in BUILD.md Commit f8524a2be738e6471ed2e48265f2663d8c21c0be updated one reference to the openjdk package, but there was another one. --- BUILD.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/BUILD.md b/BUILD.md index 30a911e1..fdcc569d 100644 --- a/BUILD.md +++ b/BUILD.md @@ -106,7 +106,7 @@ sudo apt install mingw-w64 mingw-w64-tools You also need the JDK to build the server: ```bash -sudo apt install openjdk-8-jdk +sudo apt install openjdk-11-jdk ``` Then generate the releases: From 1863ca7ad1b87f9b32aa1c5aa9554741f5845fba Mon Sep 17 00:00:00 2001 From: yangfl Date: Tue, 16 Feb 2021 00:52:53 +0800 Subject: [PATCH 303/450] Remove unnecessary escape characters in manpage PR #2123 Signed-off-by: Romain Vimont --- app/scrcpy.1 | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/scrcpy.1 b/app/scrcpy.1 index 92b8e1e3..f5fc5d75 100644 --- a/app/scrcpy.1 +++ b/app/scrcpy.1 @@ -213,25 +213,25 @@ Set a custom window title. .BI "\-\-window\-x " value Set the initial window horizontal position. -Default is "auto".\n +Default is "auto". .TP .BI "\-\-window\-y " value Set the initial window vertical position. -Default is "auto".\n +Default is "auto". .TP .BI "\-\-window\-width " value Set the initial window width. -Default is 0 (automatic).\n +Default is 0 (automatic). .TP .BI "\-\-window\-height " value Set the initial window height. -Default is 0 (automatic).\n +Default is 0 (automatic). .SH SHORTCUTS From 218636dc1045d17c8c58e08cda5961a5a83a7281 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Wed, 3 Mar 2021 18:15:49 +0100 Subject: [PATCH 304/450] Inject touch events with smallest detectable size A value of 1 means the "largest detectable size", a value of 0 means the "smallest detectable size". https://developer.android.com/reference/android/view/MotionEvent.PointerCoords#size https://developer.android.com/reference/android/view/MotionEvent#AXIS_SIZE Fixes #2125 --- server/src/main/java/com/genymobile/scrcpy/Controller.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/Controller.java b/server/src/main/java/com/genymobile/scrcpy/Controller.java index 84780239..d8059b43 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Controller.java +++ b/server/src/main/java/com/genymobile/scrcpy/Controller.java @@ -45,7 +45,7 @@ public class Controller { MotionEvent.PointerCoords coords = new MotionEvent.PointerCoords(); coords.orientation = 0; - coords.size = 1; + coords.size = 0; pointerProperties[i] = props; pointerCoords[i] = coords; From 08baaf4b575aef7ee56d14683be3f4e3a86d39aa Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 4 Mar 2021 15:19:00 +0100 Subject: [PATCH 305/450] Mention adb debugging in FAQ --- FAQ.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/FAQ.md b/FAQ.md index 9801f91c..59a8648f 100644 --- a/FAQ.md +++ b/FAQ.md @@ -39,8 +39,11 @@ Check [stackoverflow][device-unauthorized]. > adb: error: failed to get feature set: no devices/emulators found +Check that you correctly enabled [adb debugging][enable-adb]. + If your device is not detected, you may need some [drivers] (on Windows). +[enable-adb]: https://developer.android.com/studio/command-line/adb.html#Enabling [drivers]: https://developer.android.com/studio/run/oem-usb.html From dca11f6c513e3b63e5494fce5bfffdc5c29be002 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sat, 6 Mar 2021 14:28:06 +0100 Subject: [PATCH 306/450] Remove obsolete FAQ entry Issue #15 had been fixed in v1.14 by e40532a3761da8b3aef484f1d435ef5f50d94574. --- FAQ.md | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/FAQ.md b/FAQ.md index 59a8648f..a84aade8 100644 --- a/FAQ.md +++ b/FAQ.md @@ -114,16 +114,6 @@ In developer options, enable: [simulating input]: https://github.com/Genymobile/scrcpy/issues/70#issuecomment-373286323 -### Mouse clicks at wrong location - -On MacOS, with HiDPI support and multiple screens, input location are wrongly -scaled. See [#15]. - -[#15]: https://github.com/Genymobile/scrcpy/issues/15 - -Open _scrcpy_ directly on the monitor you use it. - - ### Special characters do not work Injecting text input is [limited to ASCII characters][text-input]. A trick From cb197ee3a2215784f088768f5e39102fd5f55ddd Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 19 Feb 2021 20:56:09 +0100 Subject: [PATCH 307/450] Move fps counter out of video buffer In order to make video buffer more generic, move out its specific responsibility to count the fps between the decoder and the renderer. --- app/src/decoder.c | 5 ++++- app/src/decoder.h | 5 ++++- app/src/input_manager.c | 4 +--- app/src/input_manager.h | 1 + app/src/scrcpy.c | 8 ++++---- app/src/screen.c | 7 ++++++- app/src/screen.h | 6 +++++- app/src/video_buffer.c | 9 +-------- app/src/video_buffer.h | 5 +---- 9 files changed, 27 insertions(+), 23 deletions(-) diff --git a/app/src/decoder.c b/app/src/decoder.c index 23f9ce9d..4b1f0fce 100644 --- a/app/src/decoder.c +++ b/app/src/decoder.c @@ -18,6 +18,7 @@ push_frame(struct decoder *decoder) { video_buffer_offer_decoded_frame(decoder->video_buffer, &previous_frame_skipped); if (previous_frame_skipped) { + fps_counter_add_skipped_frame(decoder->fps_counter); // the previous EVENT_NEW_FRAME will consume this frame return; } @@ -28,8 +29,10 @@ push_frame(struct decoder *decoder) { } void -decoder_init(struct decoder *decoder, struct video_buffer *vb) { +decoder_init(struct decoder *decoder, struct video_buffer *vb, + struct fps_counter *fps_counter) { decoder->video_buffer = vb; + decoder->fps_counter = fps_counter; } bool diff --git a/app/src/decoder.h b/app/src/decoder.h index 27afcd8e..306cc77c 100644 --- a/app/src/decoder.h +++ b/app/src/decoder.h @@ -10,11 +10,14 @@ struct video_buffer; struct decoder { struct video_buffer *video_buffer; + struct fps_counter *fps_counter; + AVCodecContext *codec_ctx; }; void -decoder_init(struct decoder *decoder, struct video_buffer *vb); +decoder_init(struct decoder *decoder, struct video_buffer *vb, + struct fps_counter *fps_counter); bool decoder_open(struct decoder *decoder, const AVCodec *codec); diff --git a/app/src/input_manager.c b/app/src/input_manager.c index 20ea05c5..7226d68f 100644 --- a/app/src/input_manager.c +++ b/app/src/input_manager.c @@ -480,9 +480,7 @@ input_manager_process_key(struct input_manager *im, return; case SDLK_i: if (!shift && !repeat && down) { - struct fps_counter *fps_counter = - im->video_buffer->fps_counter; - switch_fps_counter_state(fps_counter); + switch_fps_counter_state(im->fps_counter); } return; case SDLK_n: diff --git a/app/src/input_manager.h b/app/src/input_manager.h index 6acb354d..d10c96b5 100644 --- a/app/src/input_manager.h +++ b/app/src/input_manager.h @@ -16,6 +16,7 @@ struct input_manager { struct controller *controller; struct video_buffer *video_buffer; + struct fps_counter *fps_counter; struct screen *screen; // SDL reports repeated events as a boolean, but Android expects the actual diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index d24bba2c..a671c5e4 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -42,6 +42,7 @@ static struct file_handler file_handler; static struct input_manager input_manager = { .controller = &controller, .video_buffer = &video_buffer, + .fps_counter = &fps_counter, .screen = &screen, .repeat = 0, @@ -332,8 +333,7 @@ scrcpy(const struct scrcpy_options *options) { } fps_counter_initialized = true; - if (!video_buffer_init(&video_buffer, &fps_counter, - options->render_expired_frames)) { + if (!video_buffer_init(&video_buffer, options->render_expired_frames)) { goto end; } video_buffer_initialized = true; @@ -346,7 +346,7 @@ scrcpy(const struct scrcpy_options *options) { file_handler_initialized = true; } - decoder_init(&decoder, &video_buffer); + decoder_init(&decoder, &video_buffer, &fps_counter); dec = &decoder; } @@ -389,7 +389,7 @@ scrcpy(const struct scrcpy_options *options) { const char *window_title = options->window_title ? options->window_title : device_name; - screen_init(&screen, &video_buffer); + screen_init(&screen, &video_buffer, &fps_counter); if (!screen_init_rendering(&screen, window_title, frame_size, options->always_on_top, options->window_x, diff --git a/app/src/screen.c b/app/src/screen.c index 4b0321eb..7c2cea04 100644 --- a/app/src/screen.c +++ b/app/src/screen.c @@ -192,9 +192,11 @@ screen_update_content_rect(struct screen *screen) { } void -screen_init(struct screen *screen, struct video_buffer *vb) { +screen_init(struct screen *screen, struct video_buffer *vb, + struct fps_counter *fps_counter) { *screen = (struct screen) SCREEN_INITIALIZER; screen->vb = vb; + screen->fps_counter = fps_counter; } static inline SDL_Texture * @@ -450,6 +452,9 @@ update_texture(struct screen *screen, const AVFrame *frame) { static bool screen_update_frame(struct screen *screen) { const AVFrame *frame = video_buffer_take_rendering_frame(screen->vb); + + fps_counter_add_rendered_frame(screen->fps_counter); + struct size new_frame_size = {frame->width, frame->height}; if (!prepare_for_frame(screen, new_frame_size)) { return false; diff --git a/app/src/screen.h b/app/src/screen.h index f2224ff1..8941416e 100644 --- a/app/src/screen.h +++ b/app/src/screen.h @@ -14,6 +14,8 @@ struct video_buffer; struct screen { struct video_buffer *vb; + struct fps_counter *fps_counter; + SDL_Window *window; SDL_Renderer *renderer; SDL_Texture *texture; @@ -38,6 +40,7 @@ struct screen { #define SCREEN_INITIALIZER { \ .vb = NULL, \ + .fps_counter = NULL, \ .window = NULL, \ .renderer = NULL, \ .texture = NULL, \ @@ -70,7 +73,8 @@ struct screen { // initialize default values void -screen_init(struct screen *screen, struct video_buffer *vb); +screen_init(struct screen *screen, struct video_buffer *vb, + struct fps_counter *fps_counter); // initialize screen, create window, renderer and texture (window is hidden) // window_x and window_y accept SC_WINDOW_POSITION_UNDEFINED diff --git a/app/src/video_buffer.c b/app/src/video_buffer.c index 4c8dd5da..903df070 100644 --- a/app/src/video_buffer.c +++ b/app/src/video_buffer.c @@ -7,10 +7,7 @@ #include "util/log.h" bool -video_buffer_init(struct video_buffer *vb, struct fps_counter *fps_counter, - bool render_expired_frames) { - vb->fps_counter = fps_counter; - +video_buffer_init(struct video_buffer *vb, bool render_expired_frames) { vb->decoding_frame = av_frame_alloc(); if (!vb->decoding_frame) { goto error_0; @@ -95,8 +92,6 @@ video_buffer_offer_decoded_frame(struct video_buffer *vb, while (!vb->pending_frame_consumed && !vb->interrupted) { sc_cond_wait(&vb->pending_frame_consumed_cond, &vb->mutex); } - } else if (!vb->pending_frame_consumed) { - fps_counter_add_skipped_frame(vb->fps_counter); } video_buffer_swap_decoding_frame(vb); @@ -113,8 +108,6 @@ video_buffer_take_rendering_frame(struct video_buffer *vb) { assert(!vb->pending_frame_consumed); vb->pending_frame_consumed = true; - fps_counter_add_rendered_frame(vb->fps_counter); - video_buffer_swap_rendering_frame(vb); if (vb->render_expired_frames) { diff --git a/app/src/video_buffer.h b/app/src/video_buffer.h index 3e52caa7..4c82fba4 100644 --- a/app/src/video_buffer.h +++ b/app/src/video_buffer.h @@ -39,13 +39,10 @@ struct video_buffer { sc_cond pending_frame_consumed_cond; bool pending_frame_consumed; - - struct fps_counter *fps_counter; }; bool -video_buffer_init(struct video_buffer *vb, struct fps_counter *fps_counter, - bool render_expired_frames); +video_buffer_init(struct video_buffer *vb, bool render_expired_frames); void video_buffer_destroy(struct video_buffer *vb); From 441d3fb119ce05287e94b0ae0befbb2e75173688 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 19 Feb 2021 21:16:57 +0100 Subject: [PATCH 308/450] Make video buffer more generic Video buffer is a tool between a frame producer and a frame consumer. For now, it is used between a decoder and a renderer, but in the future another instance might be used to swscale decoded frames. --- app/src/decoder.c | 6 ++-- app/src/screen.c | 2 +- app/src/video_buffer.c | 63 +++++++++++++++++++++--------------------- app/src/video_buffer.h | 38 ++++++++++++------------- 4 files changed, 54 insertions(+), 55 deletions(-) diff --git a/app/src/decoder.c b/app/src/decoder.c index 4b1f0fce..87cd0fe7 100644 --- a/app/src/decoder.c +++ b/app/src/decoder.c @@ -15,8 +15,8 @@ static void push_frame(struct decoder *decoder) { bool previous_frame_skipped; - video_buffer_offer_decoded_frame(decoder->video_buffer, - &previous_frame_skipped); + video_buffer_producer_offer_frame(decoder->video_buffer, + &previous_frame_skipped); if (previous_frame_skipped) { fps_counter_add_skipped_frame(decoder->fps_counter); // the previous EVENT_NEW_FRAME will consume this frame @@ -69,7 +69,7 @@ decoder_push(struct decoder *decoder, const AVPacket *packet) { return false; } ret = avcodec_receive_frame(decoder->codec_ctx, - decoder->video_buffer->decoding_frame); + decoder->video_buffer->producer_frame); if (!ret) { // a frame was received push_frame(decoder); diff --git a/app/src/screen.c b/app/src/screen.c index 7c2cea04..473d2e35 100644 --- a/app/src/screen.c +++ b/app/src/screen.c @@ -451,7 +451,7 @@ update_texture(struct screen *screen, const AVFrame *frame) { static bool screen_update_frame(struct screen *screen) { - const AVFrame *frame = video_buffer_take_rendering_frame(screen->vb); + const AVFrame *frame = video_buffer_consumer_take_frame(screen->vb); fps_counter_add_rendered_frame(screen->fps_counter); diff --git a/app/src/video_buffer.c b/app/src/video_buffer.c index 903df070..e7e150a2 100644 --- a/app/src/video_buffer.c +++ b/app/src/video_buffer.c @@ -7,9 +7,9 @@ #include "util/log.h" bool -video_buffer_init(struct video_buffer *vb, bool render_expired_frames) { - vb->decoding_frame = av_frame_alloc(); - if (!vb->decoding_frame) { +video_buffer_init(struct video_buffer *vb, bool wait_consumer) { + vb->producer_frame = av_frame_alloc(); + if (!vb->producer_frame) { goto error_0; } @@ -18,8 +18,8 @@ video_buffer_init(struct video_buffer *vb, bool render_expired_frames) { goto error_1; } - vb->rendering_frame = av_frame_alloc(); - if (!vb->rendering_frame) { + vb->consumer_frame = av_frame_alloc(); + if (!vb->consumer_frame) { goto error_2; } @@ -28,73 +28,72 @@ video_buffer_init(struct video_buffer *vb, bool render_expired_frames) { goto error_3; } - vb->render_expired_frames = render_expired_frames; - if (render_expired_frames) { + vb->wait_consumer = wait_consumer; + if (wait_consumer) { ok = sc_cond_init(&vb->pending_frame_consumed_cond); if (!ok) { sc_mutex_destroy(&vb->mutex); goto error_2; } - // interrupted is not used if expired frames are not rendered - // since offering a frame will never block + // interrupted is not used if wait_consumer is disabled since offering + // a frame will never block vb->interrupted = false; } - // there is initially no rendering frame, so consider it has already been - // consumed + // there is initially no frame, so consider it has already been consumed vb->pending_frame_consumed = true; return true; error_3: - av_frame_free(&vb->rendering_frame); + av_frame_free(&vb->consumer_frame); error_2: av_frame_free(&vb->pending_frame); error_1: - av_frame_free(&vb->decoding_frame); + av_frame_free(&vb->producer_frame); error_0: return false; } void video_buffer_destroy(struct video_buffer *vb) { - if (vb->render_expired_frames) { + if (vb->wait_consumer) { sc_cond_destroy(&vb->pending_frame_consumed_cond); } sc_mutex_destroy(&vb->mutex); - av_frame_free(&vb->rendering_frame); + av_frame_free(&vb->consumer_frame); av_frame_free(&vb->pending_frame); - av_frame_free(&vb->decoding_frame); + av_frame_free(&vb->producer_frame); } static void -video_buffer_swap_decoding_frame(struct video_buffer *vb) { +video_buffer_swap_producer_frame(struct video_buffer *vb) { sc_mutex_assert(&vb->mutex); - AVFrame *tmp = vb->decoding_frame; - vb->decoding_frame = vb->pending_frame; + AVFrame *tmp = vb->producer_frame; + vb->producer_frame = vb->pending_frame; vb->pending_frame = tmp; } static void -video_buffer_swap_rendering_frame(struct video_buffer *vb) { +video_buffer_swap_consumer_frame(struct video_buffer *vb) { sc_mutex_assert(&vb->mutex); - AVFrame *tmp = vb->rendering_frame; - vb->rendering_frame = vb->pending_frame; + AVFrame *tmp = vb->consumer_frame; + vb->consumer_frame = vb->pending_frame; vb->pending_frame = tmp; } void -video_buffer_offer_decoded_frame(struct video_buffer *vb, - bool *previous_frame_skipped) { +video_buffer_producer_offer_frame(struct video_buffer *vb, + bool *previous_frame_skipped) { sc_mutex_lock(&vb->mutex); - if (vb->render_expired_frames) { + if (vb->wait_consumer) { // wait for the current (expired) frame to be consumed while (!vb->pending_frame_consumed && !vb->interrupted) { sc_cond_wait(&vb->pending_frame_consumed_cond, &vb->mutex); } } - video_buffer_swap_decoding_frame(vb); + video_buffer_swap_producer_frame(vb); *previous_frame_skipped = !vb->pending_frame_consumed; vb->pending_frame_consumed = false; @@ -103,26 +102,26 @@ video_buffer_offer_decoded_frame(struct video_buffer *vb, } const AVFrame * -video_buffer_take_rendering_frame(struct video_buffer *vb) { +video_buffer_consumer_take_frame(struct video_buffer *vb) { sc_mutex_lock(&vb->mutex); assert(!vb->pending_frame_consumed); vb->pending_frame_consumed = true; - video_buffer_swap_rendering_frame(vb); + video_buffer_swap_consumer_frame(vb); - if (vb->render_expired_frames) { + if (vb->wait_consumer) { // unblock video_buffer_offer_decoded_frame() sc_cond_signal(&vb->pending_frame_consumed_cond); } sc_mutex_unlock(&vb->mutex); - // rendering_frame is only written from this thread, no need to lock - return vb->rendering_frame; + // consumer_frame is only written from this thread, no need to lock + return vb->consumer_frame; } void video_buffer_interrupt(struct video_buffer *vb) { - if (vb->render_expired_frames) { + if (vb->wait_consumer) { sc_mutex_lock(&vb->mutex); vb->interrupted = true; sc_mutex_unlock(&vb->mutex); diff --git a/app/src/video_buffer.h b/app/src/video_buffer.h index 4c82fba4..6c5d197c 100644 --- a/app/src/video_buffer.h +++ b/app/src/video_buffer.h @@ -13,28 +13,28 @@ typedef struct AVFrame AVFrame; /** * There are 3 frames in memory: - * - one frame is held by the decoder (decoding_frame) - * - one frame is held by the renderer (rendering_frame) - * - one frame is shared between the decoder and the renderer (pending_frame) + * - one frame is held by the producer (producer_frame) + * - one frame is held by the consumer (consumer_frame) + * - one frame is shared between the producer and the consumer (pending_frame) * - * The decoder decodes a packet into the decoding_frame (it may takes time). + * The producer generates a frame into the producer_frame (it may takes time). * - * Once the frame is decoded, it calls video_buffer_offer_decoded_frame(), - * which swaps the decoding and pending frames. + * Once the frame is produced, it calls video_buffer_producer_offer_frame(), + * which swaps the producer and pending frames. * - * When the renderer is notified that a new frame is available, it calls - * video_buffer_take_rendering_frame() to retrieve it, which swaps the pending - * and rendering frames. The frame is valid until the next call, without - * blocking the decoder. + * When the consumer is notified that a new frame is available, it calls + * video_buffer_consumer_take_frame() to retrieve it, which swaps the pending + * and consumer frames. The frame is valid until the next call, without + * blocking the producer. */ struct video_buffer { - AVFrame *decoding_frame; + AVFrame *producer_frame; AVFrame *pending_frame; - AVFrame *rendering_frame; + AVFrame *consumer_frame; sc_mutex mutex; - bool render_expired_frames; + bool wait_consumer; // never overwrite a pending frame if it is not consumed bool interrupted; sc_cond pending_frame_consumed_cond; @@ -42,21 +42,21 @@ struct video_buffer { }; bool -video_buffer_init(struct video_buffer *vb, bool render_expired_frames); +video_buffer_init(struct video_buffer *vb, bool wait_consumer); void video_buffer_destroy(struct video_buffer *vb); -// set the decoded frame as ready for rendering +// set the producer frame as ready for consuming // the output flag is set to report whether the previous frame has been skipped void -video_buffer_offer_decoded_frame(struct video_buffer *vb, - bool *previous_frame_skipped); +video_buffer_producer_offer_frame(struct video_buffer *vb, + bool *previous_frame_skipped); -// mark the rendering frame as consumed and return it +// mark the consumer frame as consumed and return it // the frame is valid until the next call to this function const AVFrame * -video_buffer_take_rendering_frame(struct video_buffer *vb); +video_buffer_consumer_take_frame(struct video_buffer *vb); // wake up and avoid any blocking call void From c50b958ee415843aebc23cd7cc45d7b1e7642c71 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 19 Feb 2021 22:40:12 +0100 Subject: [PATCH 309/450] Initialize screen before starting the stream As soon as the stream is started, the video buffer could notify a new frame available. In order to pass this event to the screen without race condition, the screen must be initialized before the screen is started. --- app/src/scrcpy.c | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index a671c5e4..f3bfecfb 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -366,13 +366,6 @@ scrcpy(const struct scrcpy_options *options) { stream_init(&stream, server.video_socket, dec, rec); - // now we consumed the header values, the socket receives the video stream - // start the stream - if (!stream_start(&stream)) { - goto end; - } - stream_started = true; - if (options->display) { if (options->control) { if (!controller_init(&controller, server.control_socket)) { @@ -415,6 +408,13 @@ scrcpy(const struct scrcpy_options *options) { } } + // now we consumed the header values, the socket receives the video stream + // start the stream + if (!stream_start(&stream)) { + goto end; + } + stream_started = true; + input_manager_init(&input_manager, options); ret = event_loop(options); From fb9f9848bd2c5196a91bbe04fc1d22eacd7abe12 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 19 Feb 2021 22:02:36 +0100 Subject: [PATCH 310/450] Use a callback to notify a new frame Make the decoder independant of the SDL even mechanism, by making the consumer register a callback on the video_buffer. --- app/src/decoder.c | 6 ------ app/src/screen.c | 19 +++++++++++++++++++ app/src/video_buffer.c | 26 +++++++++++++++++++++++++- app/src/video_buffer.h | 15 +++++++++++++++ 4 files changed, 59 insertions(+), 7 deletions(-) diff --git a/app/src/decoder.c b/app/src/decoder.c index 87cd0fe7..7b016d21 100644 --- a/app/src/decoder.c +++ b/app/src/decoder.c @@ -19,13 +19,7 @@ push_frame(struct decoder *decoder) { &previous_frame_skipped); if (previous_frame_skipped) { fps_counter_add_skipped_frame(decoder->fps_counter); - // the previous EVENT_NEW_FRAME will consume this frame - return; } - static SDL_Event new_frame_event = { - .type = EVENT_NEW_FRAME, - }; - SDL_PushEvent(&new_frame_event); } void diff --git a/app/src/screen.c b/app/src/screen.c index 473d2e35..397aa2d1 100644 --- a/app/src/screen.c +++ b/app/src/screen.c @@ -191,12 +191,31 @@ screen_update_content_rect(struct screen *screen) { } } +static void +on_frame_available(struct video_buffer *vb, void *userdata) { + (void) vb; + (void) userdata; + + static SDL_Event new_frame_event = { + .type = EVENT_NEW_FRAME, + }; + + // Post the event on the UI thread + SDL_PushEvent(&new_frame_event); +} + void screen_init(struct screen *screen, struct video_buffer *vb, struct fps_counter *fps_counter) { *screen = (struct screen) SCREEN_INITIALIZER; screen->vb = vb; screen->fps_counter = fps_counter; + + static const struct video_buffer_callbacks cbs = { + .on_frame_available = on_frame_available, + }; + + video_buffer_set_consumer_callbacks(vb, &cbs, NULL); } static inline SDL_Texture * diff --git a/app/src/video_buffer.c b/app/src/video_buffer.c index e7e150a2..c145de08 100644 --- a/app/src/video_buffer.c +++ b/app/src/video_buffer.c @@ -43,6 +43,10 @@ video_buffer_init(struct video_buffer *vb, bool wait_consumer) { // there is initially no frame, so consider it has already been consumed vb->pending_frame_consumed = true; + // The callbacks must be set by the consumer via + // video_buffer_set_consumer_callbacks() + vb->cbs = NULL; + return true; error_3: @@ -82,9 +86,22 @@ video_buffer_swap_consumer_frame(struct video_buffer *vb) { vb->pending_frame = tmp; } +void +video_buffer_set_consumer_callbacks(struct video_buffer *vb, + const struct video_buffer_callbacks *cbs, + void *cbs_userdata) { + assert(!vb->cbs); // must be set only once + assert(cbs); + assert(cbs->on_frame_available); + vb->cbs = cbs; + vb->cbs_userdata = cbs_userdata; +} + void video_buffer_producer_offer_frame(struct video_buffer *vb, bool *previous_frame_skipped) { + assert(vb->cbs); + sc_mutex_lock(&vb->mutex); if (vb->wait_consumer) { // wait for the current (expired) frame to be consumed @@ -95,10 +112,17 @@ video_buffer_producer_offer_frame(struct video_buffer *vb, video_buffer_swap_producer_frame(vb); - *previous_frame_skipped = !vb->pending_frame_consumed; + bool skipped = !vb->pending_frame_consumed; + *previous_frame_skipped = skipped; vb->pending_frame_consumed = false; sc_mutex_unlock(&vb->mutex); + + if (!skipped) { + // If skipped, then the previous call will consume this frame, the + // callback must not be called + vb->cbs->on_frame_available(vb, vb->cbs_userdata); + } } const AVFrame * diff --git a/app/src/video_buffer.h b/app/src/video_buffer.h index 6c5d197c..a41521a3 100644 --- a/app/src/video_buffer.h +++ b/app/src/video_buffer.h @@ -39,6 +39,16 @@ struct video_buffer { sc_cond pending_frame_consumed_cond; bool pending_frame_consumed; + + const struct video_buffer_callbacks *cbs; + void *cbs_userdata; +}; + +struct video_buffer_callbacks { + // Called when a new frame can be consumed by + // video_buffer_consumer_take_frame(vb) + // This callback is mandatory (it must not be NULL). + void (*on_frame_available)(struct video_buffer *vb, void *userdata); }; bool @@ -47,6 +57,11 @@ video_buffer_init(struct video_buffer *vb, bool wait_consumer); void video_buffer_destroy(struct video_buffer *vb); +void +video_buffer_set_consumer_callbacks(struct video_buffer *vb, + const struct video_buffer_callbacks *cbs, + void *cbs_userdata); + // set the producer frame as ready for consuming // the output flag is set to report whether the previous frame has been skipped void From cb9c42bdcb198b0e3ebed893e00257b95f4dcedb Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Tue, 23 Feb 2021 19:59:43 +0100 Subject: [PATCH 311/450] Use a callback to notify frame skip A skipped frame is detected when the producer offers a frame while the current pending frame has not been consumed. However, the producer (in practice the decoder) is not interested in the fact that a frame has been skipped, only the consumer (the renderer) is. Therefore, notify frame skip via a consumer callback. This allows to manage the skipped and rendered frames count at the same place, and remove fps_counter from decoder. --- app/src/decoder.c | 17 ++--------------- app/src/decoder.h | 4 +--- app/src/scrcpy.c | 2 +- app/src/screen.c | 11 ++++++++++- app/src/video_buffer.c | 11 +++++------ app/src/video_buffer.h | 8 +++++--- 6 files changed, 24 insertions(+), 29 deletions(-) diff --git a/app/src/decoder.c b/app/src/decoder.c index 7b016d21..7da959c6 100644 --- a/app/src/decoder.c +++ b/app/src/decoder.c @@ -11,22 +11,9 @@ #include "util/buffer_util.h" #include "util/log.h" -// set the decoded frame as ready for rendering, and notify -static void -push_frame(struct decoder *decoder) { - bool previous_frame_skipped; - video_buffer_producer_offer_frame(decoder->video_buffer, - &previous_frame_skipped); - if (previous_frame_skipped) { - fps_counter_add_skipped_frame(decoder->fps_counter); - } -} - void -decoder_init(struct decoder *decoder, struct video_buffer *vb, - struct fps_counter *fps_counter) { +decoder_init(struct decoder *decoder, struct video_buffer *vb) { decoder->video_buffer = vb; - decoder->fps_counter = fps_counter; } bool @@ -66,7 +53,7 @@ decoder_push(struct decoder *decoder, const AVPacket *packet) { decoder->video_buffer->producer_frame); if (!ret) { // a frame was received - push_frame(decoder); + video_buffer_producer_offer_frame(decoder->video_buffer); } else if (ret != AVERROR(EAGAIN)) { LOGE("Could not receive video frame: %d", ret); return false; diff --git a/app/src/decoder.h b/app/src/decoder.h index 306cc77c..bbd7a9a7 100644 --- a/app/src/decoder.h +++ b/app/src/decoder.h @@ -10,14 +10,12 @@ struct video_buffer; struct decoder { struct video_buffer *video_buffer; - struct fps_counter *fps_counter; AVCodecContext *codec_ctx; }; void -decoder_init(struct decoder *decoder, struct video_buffer *vb, - struct fps_counter *fps_counter); +decoder_init(struct decoder *decoder, struct video_buffer *vb); bool decoder_open(struct decoder *decoder, const AVCodec *codec); diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index f3bfecfb..e7879d5b 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -346,7 +346,7 @@ scrcpy(const struct scrcpy_options *options) { file_handler_initialized = true; } - decoder_init(&decoder, &video_buffer, &fps_counter); + decoder_init(&decoder, &video_buffer); dec = &decoder; } diff --git a/app/src/screen.c b/app/src/screen.c index 397aa2d1..bcccdd32 100644 --- a/app/src/screen.c +++ b/app/src/screen.c @@ -204,6 +204,14 @@ on_frame_available(struct video_buffer *vb, void *userdata) { SDL_PushEvent(&new_frame_event); } +static void +on_frame_skipped(struct video_buffer *vb, void *userdata) { + (void) vb; + + struct screen *screen = userdata; + fps_counter_add_skipped_frame(screen->fps_counter); +} + void screen_init(struct screen *screen, struct video_buffer *vb, struct fps_counter *fps_counter) { @@ -213,9 +221,10 @@ screen_init(struct screen *screen, struct video_buffer *vb, static const struct video_buffer_callbacks cbs = { .on_frame_available = on_frame_available, + .on_frame_skipped = on_frame_skipped, }; - video_buffer_set_consumer_callbacks(vb, &cbs, NULL); + video_buffer_set_consumer_callbacks(vb, &cbs, screen); } static inline SDL_Texture * diff --git a/app/src/video_buffer.c b/app/src/video_buffer.c index c145de08..3ebffa7e 100644 --- a/app/src/video_buffer.c +++ b/app/src/video_buffer.c @@ -98,8 +98,7 @@ video_buffer_set_consumer_callbacks(struct video_buffer *vb, } void -video_buffer_producer_offer_frame(struct video_buffer *vb, - bool *previous_frame_skipped) { +video_buffer_producer_offer_frame(struct video_buffer *vb) { assert(vb->cbs); sc_mutex_lock(&vb->mutex); @@ -113,14 +112,14 @@ video_buffer_producer_offer_frame(struct video_buffer *vb, video_buffer_swap_producer_frame(vb); bool skipped = !vb->pending_frame_consumed; - *previous_frame_skipped = skipped; vb->pending_frame_consumed = false; sc_mutex_unlock(&vb->mutex); - if (!skipped) { - // If skipped, then the previous call will consume this frame, the - // callback must not be called + if (skipped) { + if (vb->cbs->on_frame_skipped) + vb->cbs->on_frame_skipped(vb, vb->cbs_userdata); + } else { vb->cbs->on_frame_available(vb, vb->cbs_userdata); } } diff --git a/app/src/video_buffer.h b/app/src/video_buffer.h index a41521a3..4d11e3ab 100644 --- a/app/src/video_buffer.h +++ b/app/src/video_buffer.h @@ -49,6 +49,10 @@ struct video_buffer_callbacks { // video_buffer_consumer_take_frame(vb) // This callback is mandatory (it must not be NULL). void (*on_frame_available)(struct video_buffer *vb, void *userdata); + + // Called when a pending frame has been overwritten by the producer + // This callback is optional (it may be NULL). + void (*on_frame_skipped)(struct video_buffer *vb, void *userdata); }; bool @@ -63,10 +67,8 @@ video_buffer_set_consumer_callbacks(struct video_buffer *vb, void *cbs_userdata); // set the producer frame as ready for consuming -// the output flag is set to report whether the previous frame has been skipped void -video_buffer_producer_offer_frame(struct video_buffer *vb, - bool *previous_frame_skipped); +video_buffer_producer_offer_frame(struct video_buffer *vb); // mark the consumer frame as consumed and return it // the frame is valid until the next call to this function From 955da3b57801b6ea14abdb6cbc7e530670b66ae7 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 21 Feb 2021 21:47:55 +0100 Subject: [PATCH 312/450] Remove screen static initializer Most of the fields are initialized dynamically. --- app/src/screen.c | 8 +++++++- app/src/screen.h | 33 --------------------------------- 2 files changed, 7 insertions(+), 34 deletions(-) diff --git a/app/src/screen.c b/app/src/screen.c index bcccdd32..362ba1a8 100644 --- a/app/src/screen.c +++ b/app/src/screen.c @@ -215,10 +215,14 @@ on_frame_skipped(struct video_buffer *vb, void *userdata) { void screen_init(struct screen *screen, struct video_buffer *vb, struct fps_counter *fps_counter) { - *screen = (struct screen) SCREEN_INITIALIZER; screen->vb = vb; screen->fps_counter = fps_counter; + screen->resize_pending = false; + screen->has_frame = false; + screen->fullscreen = false; + screen->maximized = false; + static const struct video_buffer_callbacks cbs = { .on_frame_available = on_frame_available, .on_frame_skipped = on_frame_skipped, @@ -311,6 +315,8 @@ screen_init_rendering(struct screen *screen, const char *window_title, const char *renderer_name = r ? NULL : renderer_info.name; LOGI("Renderer: %s", renderer_name ? renderer_name : "(unknown)"); + screen->mipmaps = false; + // starts with "opengl" bool use_opengl = renderer_name && !strncmp(renderer_name, "opengl", 6); if (use_opengl) { diff --git a/app/src/screen.h b/app/src/screen.h index 8941416e..da80fa2d 100644 --- a/app/src/screen.h +++ b/app/src/screen.h @@ -38,39 +38,6 @@ struct screen { bool mipmaps; }; -#define SCREEN_INITIALIZER { \ - .vb = NULL, \ - .fps_counter = NULL, \ - .window = NULL, \ - .renderer = NULL, \ - .texture = NULL, \ - .gl = {0}, \ - .frame_size = { \ - .width = 0, \ - .height = 0, \ - }, \ - .content_size = { \ - .width = 0, \ - .height = 0, \ - }, \ - .resize_pending = false, \ - .windowed_content_size = { \ - .width = 0, \ - .height = 0, \ - }, \ - .rotation = 0, \ - .rect = { \ - .x = 0, \ - .y = 0, \ - .w = 0, \ - .h = 0, \ - }, \ - .has_frame = false, \ - .fullscreen = false, \ - .maximized = false, \ - .mipmaps = false, \ -} - // initialize default values void screen_init(struct screen *screen, struct video_buffer *vb, From 597c54f0491eb279fed52a1386120ba06ababceb Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 25 Feb 2021 22:00:34 +0100 Subject: [PATCH 313/450] Group screen parameters into a struct The function screen_init_rendering had too many parameters. --- app/src/scrcpy.c | 20 ++++++++++++++------ app/src/screen.c | 45 ++++++++++++++++++++++----------------------- app/src/screen.h | 24 ++++++++++++++++++------ 3 files changed, 54 insertions(+), 35 deletions(-) diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index e7879d5b..e683956c 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -384,12 +384,20 @@ scrcpy(const struct scrcpy_options *options) { screen_init(&screen, &video_buffer, &fps_counter); - if (!screen_init_rendering(&screen, window_title, frame_size, - options->always_on_top, options->window_x, - options->window_y, options->window_width, - options->window_height, - options->window_borderless, - options->rotation, options->mipmaps)) { + struct screen_params screen_params = { + .window_title = window_title, + .frame_size = frame_size, + .always_on_top = options->always_on_top, + .window_x = options->window_x, + .window_y = options->window_y, + .window_width = options->window_width, + .window_height = options->window_height, + .window_borderless = options->window_borderless, + .rotation = options->rotation, + .mipmaps = options->mipmaps, + }; + + if (!screen_init_rendering(&screen, &screen_params)) { goto end; } diff --git a/app/src/screen.c b/app/src/screen.c index 362ba1a8..850d0405 100644 --- a/app/src/screen.c +++ b/app/src/screen.c @@ -259,26 +259,25 @@ create_texture(struct screen *screen) { } bool -screen_init_rendering(struct screen *screen, const char *window_title, - struct size frame_size, bool always_on_top, - int16_t window_x, int16_t window_y, uint16_t window_width, - uint16_t window_height, bool window_borderless, - uint8_t rotation, bool mipmaps) { - screen->frame_size = frame_size; - screen->rotation = rotation; - if (rotation) { - LOGI("Initial display rotation set to %u", rotation); +screen_init_rendering(struct screen *screen, + const struct screen_params *params) { + screen->frame_size = params->frame_size; + screen->rotation = params->rotation; + if (screen->rotation) { + LOGI("Initial display rotation set to %u", screen->rotation); } - struct size content_size = get_rotated_size(frame_size, screen->rotation); + struct size content_size = + get_rotated_size(screen->frame_size, screen->rotation); screen->content_size = content_size; - struct size window_size = - get_initial_optimal_size(content_size, window_width, window_height); + struct size window_size = get_initial_optimal_size(content_size, + params->window_width, + params->window_height); uint32_t window_flags = SDL_WINDOW_HIDDEN | SDL_WINDOW_RESIZABLE; #ifdef HIDPI_SUPPORT window_flags |= SDL_WINDOW_ALLOW_HIGHDPI; #endif - if (always_on_top) { + if (params->always_on_top) { #ifdef SCRCPY_SDL_HAS_WINDOW_ALWAYS_ON_TOP window_flags |= SDL_WINDOW_ALWAYS_ON_TOP; #else @@ -286,15 +285,15 @@ screen_init_rendering(struct screen *screen, const char *window_title, "(compile with SDL >= 2.0.5 to enable it)"); #endif } - if (window_borderless) { + if (params->window_borderless) { window_flags |= SDL_WINDOW_BORDERLESS; } - int x = window_x != SC_WINDOW_POSITION_UNDEFINED - ? window_x : (int) SDL_WINDOWPOS_UNDEFINED; - int y = window_y != SC_WINDOW_POSITION_UNDEFINED - ? window_y : (int) SDL_WINDOWPOS_UNDEFINED; - screen->window = SDL_CreateWindow(window_title, x, y, + int x = params->window_x != SC_WINDOW_POSITION_UNDEFINED + ? params->window_x : (int) SDL_WINDOWPOS_UNDEFINED; + int y = params->window_y != SC_WINDOW_POSITION_UNDEFINED + ? params->window_y : (int) SDL_WINDOWPOS_UNDEFINED; + screen->window = SDL_CreateWindow(params->window_title, x, y, window_size.width, window_size.height, window_flags); if (!screen->window) { @@ -325,7 +324,7 @@ screen_init_rendering(struct screen *screen, const char *window_title, LOGI("OpenGL version: %s", gl->version); - if (mipmaps) { + if (params->mipmaps) { bool supports_mipmaps = sc_opengl_version_at_least(gl, 3, 0, /* OpenGL 3.0+ */ 2, 0 /* OpenGL ES 2.0+ */); @@ -339,7 +338,7 @@ screen_init_rendering(struct screen *screen, const char *window_title, } else { LOGI("Trilinear filtering disabled"); } - } else if (mipmaps) { + } else if (params->mipmaps) { LOGD("Trilinear filtering disabled (not an OpenGL renderer)"); } @@ -351,8 +350,8 @@ screen_init_rendering(struct screen *screen, const char *window_title, LOGW("Could not load icon"); } - LOGI("Initial texture: %" PRIu16 "x%" PRIu16, frame_size.width, - frame_size.height); + LOGI("Initial texture: %" PRIu16 "x%" PRIu16, params->frame_size.width, + params->frame_size.height); screen->texture = create_texture(screen); if (!screen->texture) { LOGC("Could not create texture: %s", SDL_GetError()); diff --git a/app/src/screen.h b/app/src/screen.h index da80fa2d..1d22cbc5 100644 --- a/app/src/screen.h +++ b/app/src/screen.h @@ -38,19 +38,31 @@ struct screen { bool mipmaps; }; +struct screen_params { + const char *window_title; + struct size frame_size; + bool always_on_top; + + int16_t window_x; + int16_t window_y; + uint16_t window_width; // accepts SC_WINDOW_POSITION_UNDEFINED + uint16_t window_height; // accepts SC_WINDOW_POSITION_UNDEFINED + + bool window_borderless; + + uint8_t rotation; + bool mipmaps; +}; + // initialize default values void screen_init(struct screen *screen, struct video_buffer *vb, struct fps_counter *fps_counter); // initialize screen, create window, renderer and texture (window is hidden) -// window_x and window_y accept SC_WINDOW_POSITION_UNDEFINED bool -screen_init_rendering(struct screen *screen, const char *window_title, - struct size frame_size, bool always_on_top, - int16_t window_x, int16_t window_y, uint16_t window_width, - uint16_t window_height, bool window_borderless, - uint8_t rotation, bool mipmaps); +screen_init_rendering(struct screen *screen, + const struct screen_params *params); // show the window void From cc48b243240c36e2a2043098ec80fb037d0b0e9b Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 25 Feb 2021 22:06:33 +0100 Subject: [PATCH 314/450] Simplify screen initialization Use a single function to initialize the screen instance. --- app/src/scrcpy.c | 5 ++--- app/src/screen.c | 38 +++++++++++++++++--------------------- app/src/screen.h | 10 +++------- 3 files changed, 22 insertions(+), 31 deletions(-) diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index e683956c..7ed9cb2c 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -382,8 +382,6 @@ scrcpy(const struct scrcpy_options *options) { const char *window_title = options->window_title ? options->window_title : device_name; - screen_init(&screen, &video_buffer, &fps_counter); - struct screen_params screen_params = { .window_title = window_title, .frame_size = frame_size, @@ -397,7 +395,8 @@ scrcpy(const struct scrcpy_options *options) { .mipmaps = options->mipmaps, }; - if (!screen_init_rendering(&screen, &screen_params)) { + if (!screen_init(&screen, &video_buffer, &fps_counter, + &screen_params)) { goto end; } diff --git a/app/src/screen.c b/app/src/screen.c index 850d0405..aa6f32b7 100644 --- a/app/src/screen.c +++ b/app/src/screen.c @@ -212,25 +212,6 @@ on_frame_skipped(struct video_buffer *vb, void *userdata) { fps_counter_add_skipped_frame(screen->fps_counter); } -void -screen_init(struct screen *screen, struct video_buffer *vb, - struct fps_counter *fps_counter) { - screen->vb = vb; - screen->fps_counter = fps_counter; - - screen->resize_pending = false; - screen->has_frame = false; - screen->fullscreen = false; - screen->maximized = false; - - static const struct video_buffer_callbacks cbs = { - .on_frame_available = on_frame_available, - .on_frame_skipped = on_frame_skipped, - }; - - video_buffer_set_consumer_callbacks(vb, &cbs, screen); -} - static inline SDL_Texture * create_texture(struct screen *screen) { SDL_Renderer *renderer = screen->renderer; @@ -259,8 +240,23 @@ create_texture(struct screen *screen) { } bool -screen_init_rendering(struct screen *screen, - const struct screen_params *params) { +screen_init(struct screen *screen, struct video_buffer *vb, + struct fps_counter *fps_counter, + const struct screen_params *params) { + screen->vb = vb; + screen->fps_counter = fps_counter; + + screen->resize_pending = false; + screen->has_frame = false; + screen->fullscreen = false; + screen->maximized = false; + + static const struct video_buffer_callbacks cbs = { + .on_frame_available = on_frame_available, + .on_frame_skipped = on_frame_skipped, + }; + video_buffer_set_consumer_callbacks(vb, &cbs, screen); + screen->frame_size = params->frame_size; screen->rotation = params->rotation; if (screen->rotation) { diff --git a/app/src/screen.h b/app/src/screen.h index 1d22cbc5..4e1d5e63 100644 --- a/app/src/screen.h +++ b/app/src/screen.h @@ -54,15 +54,11 @@ struct screen_params { bool mipmaps; }; -// initialize default values -void -screen_init(struct screen *screen, struct video_buffer *vb, - struct fps_counter *fps_counter); - // initialize screen, create window, renderer and texture (window is hidden) bool -screen_init_rendering(struct screen *screen, - const struct screen_params *params); +screen_init(struct screen *screen, struct video_buffer *vb, + struct fps_counter *fps_counter, + const struct screen_params *params); // show the window void From 386f017ba9ae52b55e32d3e3c1f068e29e893e1f Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 21 Feb 2021 17:22:25 +0100 Subject: [PATCH 315/450] Factorize frame swap --- app/src/video_buffer.c | 23 +++++++---------------- 1 file changed, 7 insertions(+), 16 deletions(-) diff --git a/app/src/video_buffer.c b/app/src/video_buffer.c index 3ebffa7e..1aaf6945 100644 --- a/app/src/video_buffer.c +++ b/app/src/video_buffer.c @@ -70,20 +70,11 @@ video_buffer_destroy(struct video_buffer *vb) { av_frame_free(&vb->producer_frame); } -static void -video_buffer_swap_producer_frame(struct video_buffer *vb) { - sc_mutex_assert(&vb->mutex); - AVFrame *tmp = vb->producer_frame; - vb->producer_frame = vb->pending_frame; - vb->pending_frame = tmp; -} - -static void -video_buffer_swap_consumer_frame(struct video_buffer *vb) { - sc_mutex_assert(&vb->mutex); - AVFrame *tmp = vb->consumer_frame; - vb->consumer_frame = vb->pending_frame; - vb->pending_frame = tmp; +static inline void +swap_frames(AVFrame **lhs, AVFrame **rhs) { + AVFrame *tmp = *lhs; + *lhs = *rhs; + *rhs = tmp; } void @@ -109,7 +100,7 @@ video_buffer_producer_offer_frame(struct video_buffer *vb) { } } - video_buffer_swap_producer_frame(vb); + swap_frames(&vb->producer_frame, &vb->pending_frame); bool skipped = !vb->pending_frame_consumed; vb->pending_frame_consumed = false; @@ -130,7 +121,7 @@ video_buffer_consumer_take_frame(struct video_buffer *vb) { assert(!vb->pending_frame_consumed); vb->pending_frame_consumed = true; - video_buffer_swap_consumer_frame(vb); + swap_frames(&vb->consumer_frame, &vb->pending_frame); if (vb->wait_consumer) { // unblock video_buffer_offer_decoded_frame() From eb7e1070cf4d0e2940a27664ae17ae7f974eb839 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 21 Feb 2021 17:29:45 +0100 Subject: [PATCH 316/450] Release frame data as soon as possible During a frame swap, one of the two frames involved can be released. --- app/src/video_buffer.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/src/video_buffer.c b/app/src/video_buffer.c index 1aaf6945..94619840 100644 --- a/app/src/video_buffer.c +++ b/app/src/video_buffer.c @@ -100,6 +100,7 @@ video_buffer_producer_offer_frame(struct video_buffer *vb) { } } + av_frame_unref(vb->pending_frame); swap_frames(&vb->producer_frame, &vb->pending_frame); bool skipped = !vb->pending_frame_consumed; @@ -122,6 +123,7 @@ video_buffer_consumer_take_frame(struct video_buffer *vb) { vb->pending_frame_consumed = true; swap_frames(&vb->consumer_frame, &vb->pending_frame); + av_frame_unref(vb->pending_frame); if (vb->wait_consumer) { // unblock video_buffer_offer_decoded_frame() From d1789f082a6bbb883e964f2b6136f0c89ead3cde Mon Sep 17 00:00:00 2001 From: Biswapriyo Nath Date: Tue, 9 Mar 2021 13:22:58 +0530 Subject: [PATCH 317/450] meson: Do not use full path with mingw tools name This helps to use mingw toolchains which are not in /usr/bin path. PR #2185 Signed-off-by: Romain Vimont --- cross_win32.txt | 10 +++++----- cross_win64.txt | 10 +++++----- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/cross_win32.txt b/cross_win32.txt index a662ae20..0d8a843a 100644 --- a/cross_win32.txt +++ b/cross_win32.txt @@ -2,11 +2,11 @@ [binaries] name = 'mingw' -c = '/usr/bin/i686-w64-mingw32-gcc' -cpp = '/usr/bin/i686-w64-mingw32-g++' -ar = '/usr/bin/i686-w64-mingw32-ar' -strip = '/usr/bin/i686-w64-mingw32-strip' -pkgconfig = '/usr/bin/i686-w64-mingw32-pkg-config' +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' [host_machine] system = 'windows' diff --git a/cross_win64.txt b/cross_win64.txt index de3836d5..6a39c391 100644 --- a/cross_win64.txt +++ b/cross_win64.txt @@ -2,11 +2,11 @@ [binaries] name = 'mingw' -c = '/usr/bin/x86_64-w64-mingw32-gcc' -cpp = '/usr/bin/x86_64-w64-mingw32-g++' -ar = '/usr/bin/x86_64-w64-mingw32-ar' -strip = '/usr/bin/x86_64-w64-mingw32-strip' -pkgconfig = '/usr/bin/x86_64-w64-mingw32-pkg-config' +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' [host_machine] system = 'windows' From 429fdef04fbc40da9050f4b008a438feb56d3450 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 11 Mar 2021 10:55:12 +0100 Subject: [PATCH 318/450] Fix encoder parameter suggestion The option is --encoder, not --encoder-name. --- server/src/main/java/com/genymobile/scrcpy/Server.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/Server.java b/server/src/main/java/com/genymobile/scrcpy/Server.java index f58e3867..7887a5a7 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Server.java +++ b/server/src/main/java/com/genymobile/scrcpy/Server.java @@ -230,7 +230,7 @@ public final class Server { if (encoders != null && encoders.length > 0) { Ln.e("Try to use one of the available encoders:"); for (MediaCodecInfo encoder : encoders) { - Ln.e(" scrcpy --encoder-name '" + encoder.getName() + "'"); + Ln.e(" scrcpy --encoder '" + encoder.getName() + "'"); } } } From 40febf4a91f0d9c9d9e1a6390930639b3bf5a70e Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 14 Mar 2021 18:03:42 +0100 Subject: [PATCH 319/450] Use device id 0 for touch/mouse events Virtual device is only for keyboard sources, not mouse or touchscreen sources. Here is the value of InputDevice.getDevice(-1).toString(): Input Device -1: Virtual Descriptor: ... Generation: 2 Location: built-in Keyboard Type: alphabetic Has Vibrator: false Has mic: false Sources: 0x301 ( keyboard dpad ) InputDevice.getDeviceId() documentation says: > An id of zero indicates that the event didn't come from a physical > device and maps to the default keymap. However, injecting events with a device id of 0 causes event.getDevice() to be null on the client-side. Commit 26529d377fe8aae2fd1ffa91405aa801a0ce8a57 used -1 as a workaround to avoid a NPE on a specific Android TV device. But this is a bug in the device system, which wrongly assumes that input device may not be null. A similar issue was present in Flutter, but it is now fixed: - - On the other hand, using an id of -1 for touchscreen events (which is invalid) causes issues for some apps: Therefore, use a device id of 0. An alternative could be to find an existing device matching the source, like "adb shell input" does. See getInputDeviceId(): But it seems better to indicate that the event didn't come from a physical device, and it would not solve #962 anyway, because an Android TV has no touchscreen. Refs #962 Fixes #2125 --- server/src/main/java/com/genymobile/scrcpy/Controller.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/Controller.java b/server/src/main/java/com/genymobile/scrcpy/Controller.java index d8059b43..2c5a1277 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Controller.java +++ b/server/src/main/java/com/genymobile/scrcpy/Controller.java @@ -14,7 +14,7 @@ import java.util.concurrent.TimeUnit; public class Controller { - private static final int DEVICE_ID_VIRTUAL = -1; + private static final int DEFAULT_DEVICE_ID = 0; private static final ScheduledExecutorService EXECUTOR = Executors.newSingleThreadScheduledExecutor(); @@ -210,7 +210,7 @@ public class Controller { int source = nonPrimaryButtonPressed ? InputDevice.SOURCE_MOUSE : InputDevice.SOURCE_TOUCHSCREEN; MotionEvent event = MotionEvent - .obtain(lastTouchDown, now, action, pointerCount, pointerProperties, pointerCoords, 0, buttons, 1f, 1f, DEVICE_ID_VIRTUAL, 0, source, + .obtain(lastTouchDown, now, action, pointerCount, pointerProperties, pointerCoords, 0, buttons, 1f, 1f, DEFAULT_DEVICE_ID, 0, source, 0); return device.injectEvent(event); } @@ -233,7 +233,7 @@ public class Controller { coords.setAxisValue(MotionEvent.AXIS_VSCROLL, vScroll); MotionEvent event = MotionEvent - .obtain(lastTouchDown, now, MotionEvent.ACTION_SCROLL, 1, pointerProperties, pointerCoords, 0, 0, 1f, 1f, DEVICE_ID_VIRTUAL, 0, + .obtain(lastTouchDown, now, MotionEvent.ACTION_SCROLL, 1, pointerProperties, pointerCoords, 0, 0, 1f, 1f, DEFAULT_DEVICE_ID, 0, InputDevice.SOURCE_TOUCHSCREEN, 0); return device.injectEvent(event); } From 0308ef43f22b5e3e937869b30326bd7209242e9a Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Mon, 15 Mar 2021 18:43:30 +0100 Subject: [PATCH 320/450] Do not set buttons on touch events BUTTON_PRIMARY must not be set for touch events: > This button constant is not set in response to simple touches with a > finger or stylus tip. The user must actually push a button. Fixes #2169 --- server/src/main/java/com/genymobile/scrcpy/Controller.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/server/src/main/java/com/genymobile/scrcpy/Controller.java b/server/src/main/java/com/genymobile/scrcpy/Controller.java index 2c5a1277..8f262ab6 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Controller.java +++ b/server/src/main/java/com/genymobile/scrcpy/Controller.java @@ -208,6 +208,10 @@ public class Controller { // Right-click and middle-click only work if the source is a mouse boolean nonPrimaryButtonPressed = (buttons & ~MotionEvent.BUTTON_PRIMARY) != 0; int source = nonPrimaryButtonPressed ? InputDevice.SOURCE_MOUSE : InputDevice.SOURCE_TOUCHSCREEN; + if (source != InputDevice.SOURCE_MOUSE) { + // Buttons must not be set for touch events + buttons = 0; + } MotionEvent event = MotionEvent .obtain(lastTouchDown, now, action, pointerCount, pointerProperties, pointerCoords, 0, buttons, 1f, 1f, DEFAULT_DEVICE_ID, 0, source, From dd453ad041b0dcf0486610a2215c2d86499d66a7 Mon Sep 17 00:00:00 2001 From: slingmint Date: Wed, 20 Jan 2021 13:13:42 -0600 Subject: [PATCH 321/450] Pass scrcpy-noconsole arguments through to scrcpy PR #2052 Signed-off-by: Romain Vimont --- data/scrcpy-noconsole.vbs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/data/scrcpy-noconsole.vbs b/data/scrcpy-noconsole.vbs index e11adba5..d509ad7f 100644 --- a/data/scrcpy-noconsole.vbs +++ b/data/scrcpy-noconsole.vbs @@ -1 +1,7 @@ -CreateObject("Wscript.Shell").Run "cmd /c scrcpy.exe", 0, false +strCommand = "cmd /c scrcpy.exe" + +For Each Arg In WScript.Arguments + strCommand = strCommand & " """ & replace(Arg, """", """""""""") & """" +Next + +CreateObject("Wscript.Shell").Run strCommand, 0, false From fb0bcaebc2d081f0b6992b49bfe2ce9b12298b51 Mon Sep 17 00:00:00 2001 From: Yu-Chen Lin Date: Sun, 21 Feb 2021 08:31:37 +0800 Subject: [PATCH 322/450] Export static method to power off screen in Device PR #824 Signed-off-by: Yu-Chen Lin Signed-off-by: Romain Vimont --- .../java/com/genymobile/scrcpy/Device.java | 40 +++++++++++++++++-- 1 file changed, 37 insertions(+), 3 deletions(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/Device.java b/server/src/main/java/com/genymobile/scrcpy/Device.java index a8fdf677..624c9fa0 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Device.java +++ b/server/src/main/java/com/genymobile/scrcpy/Device.java @@ -153,13 +153,17 @@ public final class Device { return Build.MODEL; } + public static boolean supportsInputEvents(int displayId) { + return displayId == 0 || Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q; + } + public boolean supportsInputEvents() { return supportsInputEvents; } - public boolean injectEvent(InputEvent inputEvent, int mode) { - if (!supportsInputEvents()) { - throw new AssertionError("Could not inject input event if !supportsInputEvents()"); + public static boolean injectEvent(InputEvent inputEvent, int mode, int displayId) { + if (!supportsInputEvents(displayId)) { + return false; } if (displayId != 0 && !InputManager.setDisplayId(inputEvent, displayId)) { @@ -169,10 +173,29 @@ public final class Device { return SERVICE_MANAGER.getInputManager().injectInputEvent(inputEvent, mode); } + public boolean injectEvent(InputEvent inputEvent, int mode) { + if (!supportsInputEvents()) { + throw new AssertionError("Could not inject input event if !supportsInputEvents()"); + } + + return injectEvent(inputEvent, mode, displayId); + } + + public static boolean injectEventOnDisplay(InputEvent event, int displayId) { + return injectEvent(event, InputManager.INJECT_INPUT_EVENT_MODE_ASYNC, displayId); + } + public boolean injectEvent(InputEvent event) { return injectEvent(event, InputManager.INJECT_INPUT_EVENT_MODE_ASYNC); } + public static boolean injectKeyEvent(int action, int keyCode, int repeat, int metaState, int displayId) { + long now = SystemClock.uptimeMillis(); + KeyEvent event = new KeyEvent(now, now, action, keyCode, repeat, metaState, KeyCharacterMap.VIRTUAL_KEYBOARD, 0, 0, + InputDevice.SOURCE_KEYBOARD); + return injectEventOnDisplay(event, displayId); + } + public boolean injectKeyEvent(int action, int keyCode, int repeat, int metaState) { long now = SystemClock.uptimeMillis(); KeyEvent event = new KeyEvent(now, now, action, keyCode, repeat, metaState, KeyCharacterMap.VIRTUAL_KEYBOARD, 0, 0, @@ -180,6 +203,10 @@ public final class Device { return injectEvent(event); } + public static boolean injectKeycode(int keyCode, int displayId) { + return injectKeyEvent(KeyEvent.ACTION_DOWN, keyCode, 0, 0, displayId) && injectKeyEvent(KeyEvent.ACTION_UP, keyCode, 0, 0, displayId); + } + public boolean injectKeycode(int keyCode) { return injectKeyEvent(KeyEvent.ACTION_DOWN, keyCode, 0, 0) && injectKeyEvent(KeyEvent.ACTION_UP, keyCode, 0, 0); } @@ -249,6 +276,13 @@ public final class Device { return SurfaceControl.setDisplayPowerMode(d, mode); } + public static boolean powerOffScreen(int displayId) { + if (!isScreenOn()) { + return true; + } + return injectKeycode(KeyEvent.KEYCODE_POWER, displayId); + } + /** * Disable auto-rotation (if enabled), set the screen rotation and re-enable auto-rotation (if it was enabled). */ From 1d615a0d51d5f912a415c03009e3da058992fcf6 Mon Sep 17 00:00:00 2001 From: Yu-Chen Lin Date: Sun, 21 Feb 2021 08:42:04 +0800 Subject: [PATCH 323/450] Support power off on close PR #824 Signed-off-by: Yu-Chen Lin Signed-off-by: Romain Vimont --- app/src/cli.c | 6 +++++ app/src/scrcpy.c | 1 + app/src/scrcpy.h | 2 ++ app/src/server.c | 1 + app/src/server.h | 1 + .../java/com/genymobile/scrcpy/CleanUp.java | 23 ++++++++++++------- .../java/com/genymobile/scrcpy/Options.java | 9 ++++++++ .../java/com/genymobile/scrcpy/Server.java | 7 ++++-- 8 files changed, 40 insertions(+), 10 deletions(-) diff --git a/app/src/cli.c b/app/src/cli.c index 7f31b32c..484c48ef 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -666,6 +666,7 @@ guess_record_format(const char *filename) { #define OPT_FORWARD_ALL_CLICKS 1023 #define OPT_LEGACY_PASTE 1024 #define OPT_ENCODER_NAME 1025 +#define OPT_POWER_OFF_ON_CLOSE 1026 bool scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) { @@ -716,6 +717,8 @@ scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) { {"window-height", required_argument, NULL, OPT_WINDOW_HEIGHT}, {"window-borderless", no_argument, NULL, OPT_WINDOW_BORDERLESS}, + {"power-off-on-close", no_argument, NULL, + OPT_POWER_OFF_ON_CLOSE}, {NULL, 0, NULL, 0 }, }; @@ -884,6 +887,9 @@ scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) { case OPT_LEGACY_PASTE: opts->legacy_paste = true; break; + case OPT_POWER_OFF_ON_CLOSE: + opts->power_off_on_close = true; + break; default: // getopt prints the error message on stderr return false; diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index 7ed9cb2c..624ddbca 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -300,6 +300,7 @@ scrcpy(const struct scrcpy_options *options) { .codec_options = options->codec_options, .encoder_name = options->encoder_name, .force_adb_forward = options->force_adb_forward, + .power_off_on_close = options->power_off_on_close, }; if (!server_start(&server, options->serial, ¶ms)) { goto end; diff --git a/app/src/scrcpy.h b/app/src/scrcpy.h index b877a987..8ee70f60 100644 --- a/app/src/scrcpy.h +++ b/app/src/scrcpy.h @@ -82,6 +82,7 @@ struct scrcpy_options { bool forward_key_repeat; bool forward_all_clicks; bool legacy_paste; + bool power_off_on_close; }; #define SCRCPY_OPTIONS_DEFAULT { \ @@ -129,6 +130,7 @@ struct scrcpy_options { .forward_key_repeat = true, \ .forward_all_clicks = false, \ .legacy_paste = false, \ + .power_off_on_close = false, \ } bool diff --git a/app/src/server.c b/app/src/server.c index 096ac18f..a0b40f96 100644 --- a/app/src/server.c +++ b/app/src/server.c @@ -293,6 +293,7 @@ execute_server(struct server *server, const struct server_params *params) { params->stay_awake ? "true" : "false", params->codec_options ? params->codec_options : "-", params->encoder_name ? params->encoder_name : "-", + params->power_off_on_close ? "true" : "false", }; #ifdef SERVER_DEBUGGER LOGI("Server debugger waiting for a client on device port " diff --git a/app/src/server.h b/app/src/server.h index 83c528ef..15306e4f 100644 --- a/app/src/server.h +++ b/app/src/server.h @@ -46,6 +46,7 @@ struct server_params { bool show_touches; bool stay_awake; bool force_adb_forward; + bool power_off_on_close; }; // init default values diff --git a/server/src/main/java/com/genymobile/scrcpy/CleanUp.java b/server/src/main/java/com/genymobile/scrcpy/CleanUp.java index efaa059a..ccdc9fd8 100644 --- a/server/src/main/java/com/genymobile/scrcpy/CleanUp.java +++ b/server/src/main/java/com/genymobile/scrcpy/CleanUp.java @@ -19,19 +19,21 @@ public final class CleanUp { // not instantiable } - public static void configure(boolean disableShowTouches, int restoreStayOn, boolean restoreNormalPowerMode) throws IOException { - boolean needProcess = disableShowTouches || restoreStayOn != -1 || restoreNormalPowerMode; + public static void configure(boolean disableShowTouches, int restoreStayOn, boolean restoreNormalPowerMode, boolean powerOffScreen, int displayId) + throws IOException { + boolean needProcess = disableShowTouches || restoreStayOn != -1 || restoreNormalPowerMode || powerOffScreen; if (needProcess) { - startProcess(disableShowTouches, restoreStayOn, restoreNormalPowerMode); + startProcess(disableShowTouches, restoreStayOn, restoreNormalPowerMode, powerOffScreen, displayId); } else { // There is no additional clean up to do when scrcpy dies unlinkSelf(); } } - private static void startProcess(boolean disableShowTouches, int restoreStayOn, boolean restoreNormalPowerMode) throws IOException { + private static void startProcess(boolean disableShowTouches, int restoreStayOn, boolean restoreNormalPowerMode, boolean powerOffScreen, + int displayId) throws IOException { String[] cmd = {"app_process", "/", CleanUp.class.getName(), String.valueOf(disableShowTouches), String.valueOf( - restoreStayOn), String.valueOf(restoreNormalPowerMode)}; + restoreStayOn), String.valueOf(restoreNormalPowerMode), String.valueOf(powerOffScreen), String.valueOf(displayId)}; ProcessBuilder builder = new ProcessBuilder(cmd); builder.environment().put("CLASSPATH", SERVER_PATH); @@ -61,6 +63,8 @@ public final class CleanUp { boolean disableShowTouches = Boolean.parseBoolean(args[0]); int restoreStayOn = Integer.parseInt(args[1]); boolean restoreNormalPowerMode = Boolean.parseBoolean(args[2]); + boolean powerOffScreen = Boolean.parseBoolean(args[3]); + int displayId = Integer.parseInt(args[4]); if (disableShowTouches || restoreStayOn != -1) { ServiceManager serviceManager = new ServiceManager(); @@ -76,9 +80,12 @@ public final class CleanUp { } } - if (restoreNormalPowerMode) { - Ln.i("Restoring normal power mode"); - if (Device.isScreenOn()) { + if (Device.isScreenOn()) { + if (powerOffScreen) { + Ln.i("Power off screen"); + Device.powerOffScreen(displayId); + } else if (restoreNormalPowerMode) { + Ln.i("Restoring normal power mode"); Device.setScreenPowerMode(Device.POWER_MODE_NORMAL); } } diff --git a/server/src/main/java/com/genymobile/scrcpy/Options.java b/server/src/main/java/com/genymobile/scrcpy/Options.java index 150d06a8..cf11df0f 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Options.java +++ b/server/src/main/java/com/genymobile/scrcpy/Options.java @@ -17,6 +17,7 @@ public class Options { private boolean stayAwake; private String codecOptions; private String encoderName; + private boolean powerOffScreenOnClose; public Ln.Level getLogLevel() { return logLevel; @@ -129,4 +130,12 @@ public class Options { public void setEncoderName(String encoderName) { this.encoderName = encoderName; } + + public void setPowerOffScreenOnClose(boolean powerOffScreenOnClose) { + this.powerOffScreenOnClose = powerOffScreenOnClose; + } + + public boolean getPowerOffScreenOnClose() { + return this.powerOffScreenOnClose; + } } diff --git a/server/src/main/java/com/genymobile/scrcpy/Server.java b/server/src/main/java/com/genymobile/scrcpy/Server.java index 7887a5a7..674f314b 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Server.java +++ b/server/src/main/java/com/genymobile/scrcpy/Server.java @@ -50,7 +50,7 @@ public final class Server { } } - CleanUp.configure(mustDisableShowTouchesOnCleanUp, restoreStayOn, true); + CleanUp.configure(mustDisableShowTouchesOnCleanUp, restoreStayOn, true, options.getPowerOffScreenOnClose(), options.getDisplayId()); boolean tunnelForward = options.isTunnelForward(); @@ -135,7 +135,7 @@ public final class Server { "The server version (" + BuildConfig.VERSION_NAME + ") does not match the client " + "(" + clientVersion + ")"); } - final int expectedParameters = 15; + final int expectedParameters = 16; if (args.length != expectedParameters) { throw new IllegalArgumentException("Expecting " + expectedParameters + " parameters"); } @@ -185,6 +185,9 @@ public final class Server { String encoderName = "-".equals(args[14]) ? null : args[14]; options.setEncoderName(encoderName); + boolean powerOffScreenOnClose = Boolean.parseBoolean(args[15]); + options.setPowerOffScreenOnClose(powerOffScreenOnClose); + return options; } From 19ad107f1f277b1a7dcdf3678ed5e14f3a706be7 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Tue, 16 Mar 2021 21:38:12 +0100 Subject: [PATCH 324/450] Add "Get the app" summary section Give quick instructions to download/install scrcpy. --- README.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/README.md b/README.md index 759e7920..74925f0d 100644 --- a/README.md +++ b/README.md @@ -38,6 +38,15 @@ control it using keyboard and mouse. Packaging status +### Summary + + - Linux: `apt install scrcpy` + - Windows: [download][direct-win64] + - macOS: `brew install scrcpy` + +Build from sources: [BUILD] + + ### Linux On Debian (_testing_ and _sid_ for now) and Ubuntu (20.04): From 1fb79575250460e159161bbe5ca55335409ffec5 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Wed, 17 Mar 2021 08:52:16 +0100 Subject: [PATCH 325/450] Fix typo in README Refs --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 74925f0d..dd8a1850 100644 --- a/README.md +++ b/README.md @@ -420,7 +420,7 @@ _(left)_ and MOD+ _(right)_. Note that _scrcpy_ manages 3 different rotations: - MOD+r requests the device to switch between portrait - and landscape (the current running app may refuse, if it does support the + and landscape (the current running app may refuse, if it does not support the requested orientation). - [`--lock-video-orientation`](#lock-video-orientation) changes the mirroring orientation (the orientation of the video sent from the device to the From 3a4b10a38dec2514d4f588abe04dee508fdd772b Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 28 Mar 2021 12:22:11 +0200 Subject: [PATCH 326/450] Improve --push-target example in README A common use case for the --push-target option is to pass the device Download directory. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index dd8a1850..b396571b 100644 --- a/README.md +++ b/README.md @@ -667,7 +667,7 @@ There is no visual feedback, a log is printed to the console. The target directory can be changed on start: ```bash -scrcpy --push-target /sdcard/foo/bar/ +scrcpy --push-target=/sdcard/Download/ ``` From 6a217b70f4f4f751a869597670df1a87656a4597 Mon Sep 17 00:00:00 2001 From: aruko-210 Date: Sun, 14 Mar 2021 00:21:26 +0900 Subject: [PATCH 327/450] Add Japanese translation for README.md PR #2195 Signed-off-by: Romain Vimont --- README.jp.md | 728 +++++++++++++++++++++++++++++++++++++++++++++++++++ README.md | 1 + 2 files changed, 729 insertions(+) create mode 100644 README.jp.md diff --git a/README.jp.md b/README.jp.md new file mode 100644 index 00000000..a37a6ba1 --- /dev/null +++ b/README.jp.md @@ -0,0 +1,728 @@ +_Only the original [README](README.md) is guaranteed to be up-to-date._ + +# scrcpy (v1.17) + +このアプリケーションはUSB(もしくは[TCP/IP経由][article-tcpip])で接続されたAndroidデバイスの表示と制御を提供します。このアプリケーションは _root_ でのアクセスを必要としません。このアプリケーションは _GNU/Linux_ 、 _Windows_ そして _macOS_ 上で動作します。 + +![screenshot](assets/screenshot-debian-600.jpg) + +以下に焦点を当てています: + + - **軽量** (ネイティブ、デバイス画面表示のみ) + - **パフォーマンス** (30~60fps) + - **クオリティ** (1920x1080以上) + - **低遅延** ([35~70ms][lowlatency]) + - **短い起動時間** (初回画像を1秒以内に表示) + - **非侵入型** (デバイスに何もインストールされていない状態になる) + +[lowlatency]: https://github.com/Genymobile/scrcpy/pull/646 + + +## 必要要件 + +AndroidデバイスはAPI21(Android 5.0)以上。 + +Androidデバイスで[adbデバッグが有効][enable-adb]であること。 + +[enable-adb]: https://developer.android.com/studio/command-line/adb.html#Enabling + +一部のAndroidデバイスでは、キーボードとマウスを使用して制御する[追加オプション][control]を有効にする必要がある。 + +[control]: https://github.com/Genymobile/scrcpy/issues/70#issuecomment-373286323 + + +## アプリの取得 + +Packaging status + +### Linux + +Debian (_testing_ と _sid_) とUbuntu(20.04): + +``` +apt install scrcpy +``` + +[Snap]パッケージが利用可能: [`scrcpy`][snap-link] + +[snap-link]: https://snapstats.org/snaps/scrcpy + +[snap]: https://en.wikipedia.org/wiki/Snappy_(package_manager) + +Fedora用[COPR]パッケージが利用可能: [`scrcpy`][copr-link] + +[COPR]: https://fedoraproject.org/wiki/Category:Copr +[copr-link]: https://copr.fedorainfracloud.org/coprs/zeno/scrcpy/ + +Arch Linux用[AUR]パッケージが利用可能: [`scrcpy`][aur-link] + +[AUR]: https://wiki.archlinux.org/index.php/Arch_User_Repository +[aur-link]: https://aur.archlinux.org/packages/scrcpy/ + +Gentoo用[Ebuild]が利用可能: [`scrcpy`][ebuild-link] + +[Ebuild]: https://wiki.gentoo.org/wiki/Ebuild +[ebuild-link]: https://github.com/maggu2810/maggu2810-overlay/tree/master/app-mobilephone/scrcpy + +[自分でビルド][BUILD]も可能(心配しないでください、それほど難しくはありません。) + + +### Windows + +Windowsでは簡単に、(`adb`を含む)すべての依存関係を構築済みのアーカイブを利用可能です。 + + - [`scrcpy-win64-v1.17.zip`][direct-win64] + _(SHA-256: 8b9e57993c707367ed10ebfe0e1ef563c7a29d9af4a355cd8b6a52a317c73eea)_ + +[direct-win64]: https://github.com/Genymobile/scrcpy/releases/download/v1.17/scrcpy-win64-v1.17.zip + +[Chocolatey]でも利用可能です: + +[Chocolatey]: https://chocolatey.org/ + +```bash +choco install scrcpy +choco install adb # まだ入手していない場合 +``` + +[Scoop]でも利用可能です: + +```bash +scoop install scrcpy +scoop install adb # まだ入手していない場合 +``` + +[Scoop]: https://scoop.sh + +また、[アプリケーションをビルド][BUILD]することも可能です。 + +### macOS + +アプリケーションは[Homebrew]で利用可能です。ただインストールするだけです。 + +[Homebrew]: https://brew.sh/ + +```bash +brew install scrcpy +``` + +`PATH`から`adb`へのアクセスが必要です。もしまだ持っていない場合: + +```bash +# Homebrew >= 2.6.0 +brew install --cask android-platform-tools + +# Homebrew < 2.6.0 +brew cask install android-platform-tools +``` + +また、[アプリケーションをビルド][BUILD]することも可能です。 + + +## 実行 + +Androidデバイスを接続し、実行: + +```bash +scrcpy +``` + +次のコマンドでリストされるコマンドライン引数も受け付けます: + +```bash +scrcpy --help +``` + +## 機能 + +### キャプチャ構成 + +#### サイズ削減 + +Androidデバイスを低解像度でミラーリングする場合、パフォーマンス向上に便利な場合があります。 + +幅と高さをある値(例:1024)に制限するには: + +```bash +scrcpy --max-size 1024 +scrcpy -m 1024 # 短縮版 +``` + +一方のサイズはデバイスのアスペクト比が維持されるように計算されます。この方法では、1920x1080のデバイスでは1024x576にミラーリングされます。 + + +#### ビットレート変更 + +ビットレートの初期値は8Mbpsです。ビットレートを変更するには(例:2Mbpsに変更): + +```bash +scrcpy --bit-rate 2M +scrcpy -b 2M # 短縮版 +``` + +#### フレームレート制限 + +キャプチャするフレームレートを制限できます: + +```bash +scrcpy --max-fps 15 +``` + +この機能はAndroid 10からオフィシャルサポートとなっていますが、以前のバージョンでも動作する可能性があります。 + +#### トリミング + +デバイスの画面は、画面の一部のみをミラーリングするようにトリミングできます。 + +これは、例えばOculus Goの片方の目をミラーリングする場合に便利です。: + +```bash +scrcpy --crop 1224:1440:0:0 # オフセット位置(0,0)で1224x1440 +``` + +もし`--max-size`も指定されている場合、トリミング後にサイズ変更が適用されます。 + +#### ビデオの向きをロックする + +ミラーリングの向きをロックするには: + +```bash +scrcpy --lock-video-orientation 0 # 自然な向き +scrcpy --lock-video-orientation 1 # 90°反時計回り +scrcpy --lock-video-orientation 2 # 180° +scrcpy --lock-video-orientation 3 # 90°時計回り +``` + +この設定は録画の向きに影響します。 + +[ウィンドウは独立して回転することもできます](#回転)。 + + +#### エンコーダ + +いくつかのデバイスでは一つ以上のエンコーダを持ちます。それらのいくつかは、問題やクラッシュを引き起こします。別のエンコーダを選択することが可能です: + + +```bash +scrcpy --encoder OMX.qcom.video.encoder.avc +``` + +利用可能なエンコーダをリストするために、無効なエンコーダ名を渡すことができます。エラー表示で利用可能なエンコーダを提供します。 + +```bash +scrcpy --encoder _ +``` + +### 録画 + +ミラーリング中に画面の録画をすることが可能です: + +```bash +scrcpy --record file.mp4 +scrcpy -r file.mkv +``` + +録画中にミラーリングを無効にするには: + +```bash +scrcpy --no-display --record file.mp4 +scrcpy -Nr file.mkv +# Ctrl+Cで録画を中断する +``` + +"スキップされたフレーム"は(パフォーマンス上の理由で)リアルタイムで表示されなくても録画されます。 + +フレームはデバイス上で _タイムスタンプされる_ ため [パケット遅延のバリエーション] は録画されたファイルに影響を与えません。 + +[パケット遅延のバリエーション]: https://en.wikipedia.org/wiki/Packet_delay_variation + + +### 接続 + +#### ワイヤレス + +_Scrcpy_ はデバイスとの通信に`adb`を使用します。そして`adb`はTCP/IPを介しデバイスに[接続]することができます: + +1. あなたのコンピュータと同じWi-Fiに接続します。 +2. あなたのIPアドレスを取得します。設定 → 端末情報 → ステータス情報、もしくは、このコマンドを実行します: + + ```bash + adb shell ip route | awk '{print $9}' + ``` + +3. あなたのデバイスでTCP/IPを介したadbを有効にします: `adb tcpip 5555` +4. あなたのデバイスの接続を外します。 +5. あなたのデバイスに接続します: + `adb connect DEVICE_IP:5555` _(`DEVICE_IP`は置き換える)_ +6. 通常通り`scrcpy`を実行します。 + +この方法はビットレートと解像度を減らすのにおそらく有用です: + +```bash +scrcpy --bit-rate 2M --max-size 800 +scrcpy -b2M -m800 # 短縮版 +``` + +[接続]: https://developer.android.com/studio/command-line/adb.html#wireless + + +#### マルチデバイス + +もし`adb devices`でいくつかのデバイスがリストされる場合、 _シリアルナンバー_ を指定する必要があります: + +```bash +scrcpy --serial 0123456789abcdef +scrcpy -s 0123456789abcdef # 短縮版 +``` + +デバイスがTCP/IPを介して接続されている場合: + +```bash +scrcpy --serial 192.168.0.1:5555 +scrcpy -s 192.168.0.1:5555 # 短縮版 +``` + +複数のデバイスに対して、複数の _scrcpy_ インスタンスを開始することができます。 + +#### デバイス接続での自動起動 + +[AutoAdb]を使用可能です: + +```bash +autoadb scrcpy -s '{}' +``` + +[AutoAdb]: https://github.com/rom1v/autoadb + +#### SSHトンネル + +リモートデバイスに接続するため、ローカル`adb`クライアントからリモート`adb`サーバーへ接続することが可能です(同じバージョンの _adb_ プロトコルを使用している場合): + +```bash +adb kill-server # 5037ポートのローカルadbサーバーを終了する +ssh -CN -L5037:localhost:5037 -R27183:localhost:27183 your_remote_computer +# オープンしたままにする +``` + +他の端末から: + +```bash +scrcpy +``` + +リモートポート転送の有効化を回避するためには、代わりに転送接続を強制することができます(`-R`の代わりに`-L`を使用することに注意): + +```bash +adb kill-server # 5037ポートのローカルadbサーバーを終了する +ssh -CN -L5037:localhost:5037 -L27183:localhost:27183 your_remote_computer +# オープンしたままにする +``` + +他の端末から: + +```bash +scrcpy --force-adb-forward +``` + + +ワイヤレス接続と同様に、クオリティを下げると便利な場合があります: + +``` +scrcpy -b2M -m800 --max-fps 15 +``` + +### ウィンドウ構成 + +#### タイトル + +ウィンドウのタイトルはデバイスモデルが初期値です。これは変更できます: + +```bash +scrcpy --window-title 'My device' +``` + +#### 位置とサイズ + +ウィンドウの位置とサイズの初期値を指定できます: + +```bash +scrcpy --window-x 100 --window-y 100 --window-width 800 --window-height 600 +``` + +#### ボーダーレス + +ウィンドウの装飾を無効化するには: + +```bash +scrcpy --window-borderless +``` + +#### 常に画面のトップ + +scrcpyの画面を常にトップにするには: + +```bash +scrcpy --always-on-top +``` + +#### フルスクリーン + +アプリケーションを直接フルスクリーンで開始できます: + +```bash +scrcpy --fullscreen +scrcpy -f # 短縮版 +``` + +フルスクリーンは、次のコマンドで動的に切り替えることができます MOD+f + + +#### 回転 + +ウィンドウは回転することができます: + +```bash +scrcpy --rotation 1 +``` + +設定可能な値: + - `0`: 回転なし + - `1`: 90° 反時計回り + - `2`: 180° + - `3`: 90° 時計回り + +回転は次のコマンドで動的に変更することができます。 MOD+_(左)_ 、 MOD+_(右)_ + +_scrcpy_ は3つの回転を管理することに注意: + - MOD+rはデバイスに縦向きと横向きの切り替えを要求する(現在実行中のアプリで要求している向きをサポートしていない場合、拒否することがある) + - [`--lock-video-orientation`](#ビデオの向きをロックする)は、ミラーリングする向きを変更する(デバイスからPCへ送信される向き)。録画に影響します。 + - `--rotation` (もしくはMOD+/MOD+)は、ウィンドウのコンテンツのみを回転します。これは表示にのみに影響し、録画には影響しません。 + +### 他のミラーリングオプション + +#### Read-only リードオンリー + +制御を無効にするには(デバイスと対話する全てのもの:入力キー、マウスイベント、ファイルのドラッグ&ドロップ): + +```bash +scrcpy --no-control +scrcpy -n +``` + +#### ディスプレイ + +いくつか利用可能なディスプレイがある場合、ミラーリングするディスプレイを選択できます: + +```bash +scrcpy --display 1 +``` + +ディスプレイIDのリストは次の方法で取得できます: + +``` +adb shell dumpsys display # search "mDisplayId=" in the output +``` + +セカンダリディスプレイは、デバイスが少なくともAndroid 10の場合にコントロール可能です。(それ以外ではリードオンリーでミラーリングされます) + + +#### 起動状態にする + +デバイス接続時、少し遅れてからデバイスのスリープを防ぐには: + +```bash +scrcpy --stay-awake +scrcpy -w +``` + +scrcpyが閉じられた時、初期状態に復元されます。 + +#### 画面OFF + +コマンドラインオプションを使用することで、ミラーリングの開始時にデバイスの画面をOFFにすることができます: + +```bash +scrcpy --turn-screen-off +scrcpy -S +``` + +もしくは、MOD+oを押すことでいつでもできます。 + +元に戻すには、MOD+Shift+oを押します。 + +Androidでは、`POWER`ボタンはいつでも画面を表示します。便宜上、`POWER`がscrcpyを介して(右クリックもしくはMOD+pを介して)送信される場合、(ベストエフォートベースで)少し遅れて、強制的に画面を非表示にします。ただし、物理的な`POWER`ボタンを押した場合は、画面は表示されます。 + +このオプションはデバイスがスリープしないようにすることにも役立ちます: + +```bash +scrcpy --turn-screen-off --stay-awake +scrcpy -Sw +``` + + +#### 期限切れフレームをレンダリングする + +初期状態では、待ち時間を最小限にするために、_scrcpy_ は最後にデコードされたフレームをレンダリングし、前のフレームを削除します。 + +全フレームのレンダリングを強制するには(待ち時間が長くなる可能性があります): + +```bash +scrcpy --render-expired-frames +``` + +#### タッチを表示 + +プレゼンテーションの場合(物理デバイス上で)物理的なタッチを表示すると便利な場合があります。 + +Androidはこの機能を _開発者オプション_ で提供します。 + +_Scrcpy_ は開始時にこの機能を有効にし、終了時に初期値を復元するオプションを提供します: + +```bash +scrcpy --show-touches +scrcpy -t +``` + +(デバイス上で指を使った) _物理的な_ タッチのみ表示されることに注意してください。 + + +#### スクリーンセーバー無効 + +初期状態では、scrcpyはコンピュータ上でスクリーンセーバーが実行される事を妨げません。 + +これを無効にするには: + +```bash +scrcpy --disable-screensaver +``` + + +### 入力制御 + +#### デバイス画面の回転 + +MOD+rを押すことで、縦向きと横向きを切り替えます。 + +フォアグラウンドのアプリケーションが要求された向きをサポートしている場合のみ回転することに注意してください。 + +#### コピー-ペースト + +Androidのクリップボードが変更される度に、コンピュータのクリップボードに自動的に同期されます。 + +Ctrlのショートカットは全てデバイスに転送されます。特に: + - Ctrl+c 通常はコピーします + - Ctrl+x 通常はカットします + - Ctrl+v 通常はペーストします(コンピュータとデバイスのクリップボードが同期された後) + +通常は期待通りに動作します。 + +しかしながら、実際の動作はアクティブなアプリケーションに依存します。例えば、_Termux_ は代わりにCtrl+cでSIGINTを送信します、そして、_K-9 Mail_ は新しいメッセージを作成します。 + +このようなケースでコピー、カットそしてペーストをするには(Android 7以上でのサポートのみですが): + - MOD+c `COPY`を挿入 + - MOD+x `CUT`を挿入 + - MOD+v `PASTE`を挿入(コンピュータとデバイスのクリップボードが同期された後) + +加えて、MOD+Shift+vはコンピュータのクリップボードテキストにキーイベントのシーケンスとして挿入することを許可します。これはコンポーネントがテキストのペーストを許可しない場合(例えば _Termux_)に有用ですが、非ASCIIコンテンツを壊す可能性があります。 + +**警告:** デバイスにコンピュータのクリップボードを(Ctrl+vまたはMOD+vを介して)ペーストすることは、デバイスのクリップボードにコンテンツをコピーします。結果としてどのAndoridアプリケーションもそのコンテンツを読み取ることができます。機密性の高いコンテンツ(例えばパスワードなど)をこの方法でペーストすることは避けてください。 + +プログラムでデバイスのクリップボードを設定した場合、一部のデバイスは期待どおりに動作しません。`--legacy-paste`オプションは、コンピュータのクリップボードテキストをキーイベントのシーケンスとして挿入するため(MOD+Shift+vと同じ方法)、Ctrl+vMOD+vの動作の変更を提供します。 + +#### ピンチしてズームする + +"ピンチしてズームする"をシミュレートするには: Ctrl+_クリック&移動_ + +より正確にするには、左クリックボタンを押している間、Ctrlを押したままにします。左クリックボタンを離すまで、全てのマウスの動きは、(アプリでサポートされている場合)画面の中心を基準として、コンテンツを拡大縮小および回転します。 + +具体的には、scrcpyは画面の中央を反転した位置にある"バーチャルフィンガー"から追加のタッチイベントを生成します。 + + +#### テキストインジェクション環境設定 + +テキストをタイプした時に生成される2種類の[イベント][textevents]があります: + - _key events_ はキーを押したときと離したことを通知します。 + - _text events_ はテキストが入力されたことを通知します。 + +初期状態で、文字はキーイベントで挿入されるため、キーボードはゲームで期待通りに動作します(通常はWASDキー)。 + +しかし、これは[問題を引き起こす][prefertext]かもしれません。もしこのような問題が発生した場合は、この方法で回避できます: + +```bash +scrcpy --prefer-text +``` + +(しかしこの方法はゲームのキーボードの動作を壊します) + +[textevents]: https://blog.rom1v.com/2018/03/introducing-scrcpy/#handle-text-input +[prefertext]: https://github.com/Genymobile/scrcpy/issues/650#issuecomment-512945343 + + +#### キーの繰り返し + +初期状態では、キーの押しっぱなしは繰り返しのキーイベントを生成します。これらのイベントが使われない場合でも、この方法は一部のゲームでパフォーマンスの問題を引き起す可能性があります。 + +繰り返しのキーイベントの転送を回避するためには: + +```bash +scrcpy --no-key-repeat +``` + + +#### 右クリックと真ん中クリック + +初期状態では、右クリックはバックの動作(もしくはパワーオン)を起こし、真ん中クリックではホーム画面へ戻ります。このショートカットを無効にし、代わりにデバイスへクリックを転送するには: + +```bash +scrcpy --forward-all-clicks +``` + + +### ファイルのドロップ + +#### APKのインストール + +APKをインストールするには、(`.apk`で終わる)APKファイルを _scrcpy_ の画面にドラッグ&ドロップします。 + +見た目のフィードバックはありません。コンソールにログが出力されます。 + + +#### デバイスにファイルを送る + +デバイスの`/sdcard/`ディレクトリにファイルを送るには、(APKではない)ファイルを _scrcpy_ の画面にドラッグ&ドロップします。 + +見た目のフィードバックはありません。コンソールにログが出力されます。 + +転送先ディレクトリを起動時に変更することができます: + +```bash +scrcpy --push-target /sdcard/foo/bar/ +``` + + +### 音声転送 + +音声は _scrcpy_ では転送されません。[sndcpy]を使用します。 + +[issue #14]も参照ください。 + +[sndcpy]: https://github.com/rom1v/sndcpy +[issue #14]: https://github.com/Genymobile/scrcpy/issues/14 + + +## ショートカット + +次のリストでは、MODでショートカット変更します。初期状態では、(left)Altまたは(left)Superです。 + +これは`--shortcut-mod`で変更することができます。可能なキーは`lctrl`、`rctrl`、`lalt`、 `ralt`、 `lsuper`そして`rsuper`です。例えば: + +```bash +# RCtrlをショートカットとして使用します +scrcpy --shortcut-mod=rctrl + +# ショートカットにLCtrl+LAltまたはLSuperのいずれかを使用します +scrcpy --shortcut-mod=lctrl+lalt,lsuper +``` + +_[Super]は通常WindowsもしくはCmdキーです。_ + +[Super]: https://en.wikipedia.org/wiki/Super_key_(keyboard_button) + + | アクション | ショートカット + | ------------------------------------------- |:----------------------------- + | フルスクリーンモードへの切り替え | MOD+f + | ディスプレイを左に回転 | MOD+ _(左)_ + | ディスプレイを右に回転 | MOD+ _(右)_ + | ウィンドウサイズを変更して1:1に変更(ピクセルパーフェクト) | MOD+g + | ウィンドウサイズを変更して黒い境界線を削除 | MOD+w \| _ダブルクリック¹_ + | `HOME`をクリック | MOD+h \| _真ん中クリック_ + | `BACK`をクリック | MOD+b \| _右クリック²_ + | `APP_SWITCH`をクリック | MOD+s + | `MENU` (画面のアンロック)をクリック | MOD+m + | `VOLUME_UP`をクリック | MOD+ _(上)_ + | `VOLUME_DOWN`をクリック | MOD+ _(下)_ + | `POWER`をクリック | MOD+p + | 電源オン | _右クリック²_ + | デバイス画面をオフにする(ミラーリングしたまま) | MOD+o + | デバイス画面をオンにする | MOD+Shift+o + | デバイス画面を回転する | MOD+r + | 通知パネルを展開する | MOD+n + | 通知パネルを折りたたむ | MOD+Shift+n + | クリップボードへのコピー³ | MOD+c + | クリップボードへのカット³ | MOD+x + | クリップボードの同期とペースト³ | MOD+v + | コンピュータのクリップボードテキストの挿入 | MOD+Shift+v + | FPSカウンタ有効/無効(標準入出力上) | MOD+i + | ピンチしてズームする | Ctrl+_クリック&移動_ + +_¹黒い境界線を削除するため、境界線上でダブルクリック_ +_²もしスクリーンがオフの場合、右クリックでスクリーンをオンする。それ以外の場合はBackを押します._ +_³Android 7以上のみ._ + +全てのCtrl+_キー_ ショートカットはデバイスに転送されます、そのためアクティブなアプリケーションによって処理されます。 + + +## カスタムパス + +特定の _adb_ バイナリを使用する場合、そのパスを環境変数`ADB`で構成します: + + ADB=/path/to/adb scrcpy + +`scrcpy-server`ファイルのパスを上書きするには、`SCRCPY_SERVER_PATH`でそのパスを構成します。 + +[useful]: https://github.com/Genymobile/scrcpy/issues/278#issuecomment-429330345 + + +## なぜ _scrcpy_? + +同僚が私に、[gnirehtet]のように発音できない名前を見つけるように要求しました。 + +[`strcpy`]は**str**ingをコピーします。`scrcpy`は**scr**eenをコピーします。 + +[gnirehtet]: https://github.com/Genymobile/gnirehtet +[`strcpy`]: http://man7.org/linux/man-pages/man3/strcpy.3.html + + +## ビルド方法は? + +[BUILD]を参照してください。 + +[BUILD]: BUILD.md + + +## よくある質問 + +[FAQ](FAQ.md)を参照してください。 + + +## 開発者 + +[開発者のページ]を読んでください。 + +[開発者のページ]: DEVELOP.md + + +## ライセンス + + Copyright (C) 2018 Genymobile + Copyright (C) 2018-2021 Romain Vimont + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +## 記事 + +- [Introducing scrcpy][article-intro] +- [Scrcpy now works wirelessly][article-tcpip] + +[article-intro]: https://blog.rom1v.com/2018/03/introducing-scrcpy/ +[article-tcpip]: https://www.genymotion.com/blog/open-source-project-scrcpy-now-works-wirelessly/ diff --git a/README.md b/README.md index b396571b..a62d2295 100644 --- a/README.md +++ b/README.md @@ -808,6 +808,7 @@ Read the [developers page]. This README is available in other languages: - [Indonesian (Indonesia, `id`) - v1.16](README.id.md) +- [日本語 (Japanese, `jp`) - v1.17](README.jp.md) - [한국어 (Korean, `ko`) - v1.11](README.ko.md) - [português brasileiro (Brazilian Portuguese, `pt-BR`) - v1.17](README.pt-br.md) - [简体中文 (Simplified Chinese, `zh-Hans`) - v1.17](README.zh-Hans.md) From 8414b688f00182e6e5a9f94c880f70dd76fa6ddf Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 28 Mar 2021 21:33:53 +0200 Subject: [PATCH 328/450] Link release to main README in translations This avoids to link an older version. --- README.id.md | 5 +---- README.jp.md | 5 +---- README.ko.md | 4 +--- README.pt-br.md | 5 +---- README.zh-Hans.md | 5 +---- README.zh-Hant.md | 5 +---- 6 files changed, 6 insertions(+), 23 deletions(-) diff --git a/README.id.md b/README.id.md index a2cfa3d5..b4b16735 100644 --- a/README.id.md +++ b/README.id.md @@ -69,10 +69,7 @@ Anda juga bisa [membangun aplikasi secara manual][BUILD] (jangan khawatir, tidak Untuk Windows, untuk kesederhanaan, arsip prebuilt dengan semua dependensi (termasuk `adb`) tersedia : - - [`scrcpy-win64-v1.16.zip`][direct-win64] - _(SHA-256: 3f30dc5db1a2f95c2b40a0f5de91ec1642d9f53799250a8c529bc882bc0918f0)_ - -[direct-win64]: https://github.com/Genymobile/scrcpy/releases/download/v1.16/scrcpy-win64-v1.16.zip + - [README](README.md#windows) Ini juga tersedia di [Chocolatey]: diff --git a/README.jp.md b/README.jp.md index a37a6ba1..e42c528e 100644 --- a/README.jp.md +++ b/README.jp.md @@ -71,10 +71,7 @@ Gentoo用[Ebuild]が利用可能: [`scrcpy`][ebuild-link] Windowsでは簡単に、(`adb`を含む)すべての依存関係を構築済みのアーカイブを利用可能です。 - - [`scrcpy-win64-v1.17.zip`][direct-win64] - _(SHA-256: 8b9e57993c707367ed10ebfe0e1ef563c7a29d9af4a355cd8b6a52a317c73eea)_ - -[direct-win64]: https://github.com/Genymobile/scrcpy/releases/download/v1.17/scrcpy-win64-v1.17.zip + - [README](README.md#windows) [Chocolatey]でも利用可能です: diff --git a/README.ko.md b/README.ko.md index 58b44dfe..2da50562 100644 --- a/README.ko.md +++ b/README.ko.md @@ -68,9 +68,7 @@ Gentoo에서 ,[Ebuild] 가 가능합니다 : [`scrcpy/`][ebuild-link]. 윈도우 상에서, 간단하게 설치하기 위해 종속성이 있는 사전 구축된 아카이브가 제공됩니다 (`adb` 포함) : 해당 파일은 Readme원본 링크를 통해서 다운로드가 가능합니다. - - [`scrcpy-win`][direct-win] - -[direct-win]: https://github.com/Genymobile/scrcpy/blob/master/README.md#windows + - [README](README.md#windows) [어플을 직접 설치][BUILD] 할 수도 있습니다. diff --git a/README.pt-br.md b/README.pt-br.md index ebf7c7ec..3549f0fb 100644 --- a/README.pt-br.md +++ b/README.pt-br.md @@ -77,10 +77,7 @@ difícil). Para Windows, por simplicidade, um arquivo pré-compilado com todas as dependências (incluindo `adb`) está disponível: - - [`scrcpy-win64-v1.17.zip`][direct-win64] - _(SHA-256: 8b9e57993c707367ed10ebfe0e1ef563c7a29d9af4a355cd8b6a52a317c73eea)_ - -[direct-win64]: https://github.com/Genymobile/scrcpy/releases/download/v1.17/scrcpy-win64-v1.17.zip + - [README](README.md#windows) Também está disponível em [Chocolatey]: diff --git a/README.zh-Hans.md b/README.zh-Hans.md index e758c18d..bdd8023c 100644 --- a/README.zh-Hans.md +++ b/README.zh-Hans.md @@ -78,10 +78,7 @@ apt install scrcpy 在 Windows 上,简便起见,我们提供包含了所有依赖 (包括 `adb`) 的预编译包。 - - [`scrcpy-win64-v1.17.zip`][direct-win64] - _(SHA-256: 8b9e57993c707367ed10ebfe0e1ef563c7a29d9af4a355cd8b6a52a317c73eea)_ - -[direct-win64]: https://github.com/Genymobile/scrcpy/releases/download/v1.17/scrcpy-win64-v1.17.zip + - [README](README.md#windows) 也可以使用 [Chocolatey]: diff --git a/README.zh-Hant.md b/README.zh-Hant.md index b4dc69ec..c0e30254 100644 --- a/README.zh-Hant.md +++ b/README.zh-Hant.md @@ -80,10 +80,7 @@ apt install scrcpy 為了保持簡單,Windows 用戶可以下載一個包含所有必需軟體 (包含 `adb`) 的壓縮包: -- [`scrcpy-win64-v1.15.zip`][direct-win64] - _(SHA-256: dd514bb591e63ef4cd52a53c30f1153a28f59722d64690eb07bd017849edcba2)_ - -[direct-win64]: https://github.com/Genymobile/scrcpy/releases/download/v1.15/scrcpy-win64-v1.15.zip + - [README](README.md#windows) [Chocolatey] 上也可以下載: From 38f392f08ff9d473a052205e3217c38a3fbff492 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Mon, 29 Mar 2021 08:39:02 +0200 Subject: [PATCH 329/450] Fix typo in BUILD.md --- BUILD.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/BUILD.md b/BUILD.md index fdcc569d..326c5971 100644 --- a/BUILD.md +++ b/BUILD.md @@ -255,7 +255,7 @@ You can then [run](README.md#run) _scrcpy_. ## Prebuilt server - [`scrcpy-server-v1.17`][direct-scrcpy-server] - _(SHA-256: 11b5ad2d1bc9b9730fb7254a78efd71a8ff46b1938ff468e47a21b653a1b6725_ + _(SHA-256: 11b5ad2d1bc9b9730fb7254a78efd71a8ff46b1938ff468e47a21b653a1b6725)_ [direct-scrcpy-server]: https://github.com/Genymobile/scrcpy/releases/download/v1.17/scrcpy-server-v1.17 From fda293f9bbdfeb21c010550051dfb3aa87aea8dc Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Mon, 29 Mar 2021 09:11:07 +0200 Subject: [PATCH 330/450] Fix BUILD.md line wrapping --- BUILD.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/BUILD.md b/BUILD.md index 326c5971..a0c0f868 100644 --- a/BUILD.md +++ b/BUILD.md @@ -60,8 +60,7 @@ sudo apt install ffmpeg libsdl2-2.0-0 adb # client build dependencies sudo apt install gcc git pkg-config meson ninja-build \ - libavcodec-dev libavformat-dev libavutil-dev \ - libsdl2-dev + libavcodec-dev libavformat-dev libavutil-dev libsdl2-dev # server build dependencies sudo apt install openjdk-11-jdk From fc5de88eaacfd3ef0af2939f19706d72f9bc427d Mon Sep 17 00:00:00 2001 From: Ray Foss Date: Sun, 28 Mar 2021 23:11:04 -0500 Subject: [PATCH 331/450] Clarify the order of operations in BUILD.md PR #2223 Signed-off-by: Romain Vimont --- BUILD.md | 82 +++++++++++++++++++++++++++++--------------------------- 1 file changed, 42 insertions(+), 40 deletions(-) diff --git a/BUILD.md b/BUILD.md index a0c0f868..98837e69 100644 --- a/BUILD.md +++ b/BUILD.md @@ -2,11 +2,6 @@ Here are the instructions to build _scrcpy_ (client and server). -You may want to build only the client: the server binary, which will be pushed -to the Android device, does not depend on your system and architecture. In that -case, use the [prebuilt server] (so you will not need Java or the Android SDK). - -[prebuilt server]: #prebuilt-server ## Branches @@ -188,8 +183,27 @@ See [pierlon/scrcpy-docker](https://github.com/pierlon/scrcpy-docker). ## Common steps -If you want to build the server, install the [Android SDK] (_Android Studio_), -and set `ANDROID_SDK_ROOT` to its directory. For example: +**As a non-root user**, clone the project: + +```bash +git clone https://github.com/Genymobile/scrcpy +cd scrcpy +``` + + +### Build + +You may want to build only the client: the server binary, which will be pushed +to the Android device, does not depend on your system and architecture. In that +case, use the [prebuilt server] (so you will not need Java or the Android SDK). + +[prebuilt server]: #option-2-use-prebuilt-server + + +#### Option 1: Build everything from sources + +Install the [Android SDK] (_Android Studio_), and set `ANDROID_SDK_ROOT` to its +directory. For example: [Android SDK]: https://developer.android.com/studio/index.html @@ -202,20 +216,11 @@ export ANDROID_SDK_ROOT=~/Library/Android/sdk set ANDROID_SDK_ROOT=%LOCALAPPDATA%\Android\sdk ``` -If you don't want to build the server, use the [prebuilt server]. - -Clone the project: - -```bash -git clone https://github.com/Genymobile/scrcpy -cd scrcpy -``` - Then, build: ```bash meson x --buildtype release --strip -Db_lto=true -ninja -Cx +ninja -Cx # DO NOT RUN AS ROOT ``` _Note: `ninja` [must][ninja-user] be run as a non-root user (only `ninja @@ -224,9 +229,27 @@ install` must be run as root)._ [ninja-user]: https://github.com/Genymobile/scrcpy/commit/4c49b27e9f6be02b8e63b508b60535426bd0291a -### Run +#### Option 2: Use prebuilt server -To run without installing: + - [`scrcpy-server-v1.17`][direct-scrcpy-server] + _(SHA-256: 11b5ad2d1bc9b9730fb7254a78efd71a8ff46b1938ff468e47a21b653a1b6725)_ + +[direct-scrcpy-server]: https://github.com/Genymobile/scrcpy/releases/download/v1.17/scrcpy-server-v1.17 + +Download the prebuilt server somewhere, and specify its path during the Meson +configuration: + +```bash +meson x --buildtype release --strip -Db_lto=true \ + -Dprebuilt_server=/path/to/scrcpy-server +ninja -Cx # DO NOT RUN AS ROOT +``` + +The server only works with a matching client version (this server works with the +`master` branch). + + +### Run without installing: ```bash ./run x [options] @@ -249,24 +272,3 @@ This installs two files: Just remove them to "uninstall" the application. You can then [run](README.md#run) _scrcpy_. - - -## Prebuilt server - - - [`scrcpy-server-v1.17`][direct-scrcpy-server] - _(SHA-256: 11b5ad2d1bc9b9730fb7254a78efd71a8ff46b1938ff468e47a21b653a1b6725)_ - -[direct-scrcpy-server]: https://github.com/Genymobile/scrcpy/releases/download/v1.17/scrcpy-server-v1.17 - -Download the prebuilt server somewhere, and specify its path during the Meson -configuration: - -```bash -meson x --buildtype release --strip -Db_lto=true \ - -Dprebuilt_server=/path/to/scrcpy-server -ninja -Cx -sudo ninja -Cx install -``` - -The server only works with a matching client version (this server works with the -`master` branch). From 47d16a57aca02907e9e9a39c86d359ccf200181c Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Mon, 29 Mar 2021 09:16:42 +0200 Subject: [PATCH 332/450] Add simplified installation script Add a script to download the server and build scrcpy using the prebuilt server. --- BUILD.md | 31 +++++++++++++++++++++++++++++++ README.md | 12 ++++++------ install_release.sh | 21 +++++++++++++++++++++ 3 files changed, 58 insertions(+), 6 deletions(-) create mode 100755 install_release.sh diff --git a/BUILD.md b/BUILD.md index 98837e69..f81ca495 100644 --- a/BUILD.md +++ b/BUILD.md @@ -3,6 +3,37 @@ Here are the instructions to build _scrcpy_ (client and server). +## Simple + +If you just want to install the latest release from `master`, follow this +simplified process. + +First, you need to install the required packages: + +```bash +# for Debian/Ubuntu +sudo apt install ffmpeg libsdl2-2.0-0 adb wget \ + gcc git pkg-config meson ninja-build \ + libavcodec-dev libavformat-dev libavutil-dev libsdl2-dev +``` + +Then clone the repo and execute the installation script +([source](install_release.sh)): + +```bash +git clone https://github.com/Genymobile/scrcpy +cd scrcpy +./install_release.sh +``` + +When a new release is out, update the repo and reinstall: + +```bash +git pull +./install_release.sh +``` + + ## Branches ### `master` diff --git a/README.md b/README.md index a62d2295..0b0dc8a6 100644 --- a/README.md +++ b/README.md @@ -44,7 +44,10 @@ control it using keyboard and mouse. - Windows: [download][direct-win64] - macOS: `brew install scrcpy` -Build from sources: [BUILD] +Build from sources: [BUILD] ([simplified process][BUILD_simple]) + +[BUILD]: BUILD.md +[BUILD_simple]: BUILD.md#simple ### Linux @@ -76,9 +79,8 @@ For Gentoo, an [Ebuild] is available: [`scrcpy/`][ebuild-link]. [Ebuild]: https://wiki.gentoo.org/wiki/Ebuild [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). - +You could also [build the app manually][BUILD] ([simplified +process][BUILD_simple]). ### Windows @@ -763,8 +765,6 @@ A colleague challenged me to find a name as unpronounceable as [gnirehtet]. See [BUILD]. -[BUILD]: BUILD.md - ## Common issues diff --git a/install_release.sh b/install_release.sh new file mode 100755 index 00000000..5179c447 --- /dev/null +++ b/install_release.sh @@ -0,0 +1,21 @@ +#!/usr/bin/env bash +set -e + +BUILDDIR=build-auto +PREBUILT_SERVER_URL=https://github.com/Genymobile/scrcpy/releases/download/v1.17/scrcpy-server-v1.17 +PREBUILT_SERVER_SHA256=11b5ad2d1bc9b9730fb7254a78efd71a8ff46b1938ff468e47a21b653a1b6725 + +echo "[scrcpy] Downloading prebuilt server..." +wget "$PREBUILT_SERVER_URL" -O scrcpy-server +echo "[scrcpy] Verifying prebuilt server..." +echo "$PREBUILT_SERVER_SHA256 scrcpy-server" | sha256sum --check + +echo "[scrcpy] Building client..." +rm -rf "$BUILDDIR" +meson "$BUILDDIR" --buildtype release --strip -Db_lto=true \ + -Dprebuilt_server=scrcpy-server +cd "$BUILDDIR" +ninja + +echo "[scrcpy] Installing (sudo)..." +sudo ninja install From b77932a5b70bc3f60dbe589cfa5ae92472e139dd Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 1 Apr 2021 09:47:15 +0200 Subject: [PATCH 333/450] Add instructions to uninstall --- BUILD.md | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/BUILD.md b/BUILD.md index f81ca495..f20d5921 100644 --- a/BUILD.md +++ b/BUILD.md @@ -33,6 +33,12 @@ git pull ./install_release.sh ``` +To uninstall: + +```bash +sudo ninja -Cbuild-auto uninstall +``` + ## Branches @@ -295,11 +301,16 @@ After a successful build, you can install _scrcpy_ on the system: sudo ninja -Cx install # without sudo on Windows ``` -This installs two files: +This installs three files: - `/usr/local/bin/scrcpy` - `/usr/local/share/scrcpy/scrcpy-server` - -Just remove them to "uninstall" the application. + - `/usr/local/share/man/man1/scrcpy.1` You can then [run](README.md#run) _scrcpy_. + +### Uninstall + +```bash +sudo ninja -Cx uninstall # without sudo on Windows +``` From d50c678a5f9174b6ba99b27bed8faa789ed5e070 Mon Sep 17 00:00:00 2001 From: quyleanh Date: Fri, 2 Apr 2021 20:47:37 +0700 Subject: [PATCH 334/450] Update brew cask documentation The command `brew cask` has been deprecated: Should be `brew install` directly now. PR #2231 Signed-off-by: Romain Vimont --- BUILD.md | 2 +- README.md | 6 +----- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/BUILD.md b/BUILD.md index f20d5921..2ca08173 100644 --- a/BUILD.md +++ b/BUILD.md @@ -208,7 +208,7 @@ make it avaliable from the `PATH`: ```bash brew tap homebrew/cask-versions -brew cask install adoptopenjdk/openjdk/adoptopenjdk8 +brew install adoptopenjdk/openjdk/adoptopenjdk8 export JAVA_HOME="$(/usr/libexec/java_home --version 1.8)" export PATH="$JAVA_HOME/bin:$PATH" ``` diff --git a/README.md b/README.md index 0b0dc8a6..7a4b7d41 100644 --- a/README.md +++ b/README.md @@ -127,11 +127,7 @@ brew install scrcpy You need `adb`, accessible from your `PATH`. If you don't have it yet: ```bash -# Homebrew >= 2.6.0 -brew install --cask android-platform-tools - -# Homebrew < 2.6.0 -brew cask install android-platform-tools +brew install android-platform-tools ``` You can also [build the app manually][BUILD]. From 2812de8a9a0f20fa17126924407ecb6f613ac7e4 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 4 Apr 2021 14:36:48 +0200 Subject: [PATCH 335/450] Update brew java version to JDK 11 Refs f8524a2be738e6471ed2e48265f2663d8c21c0be Refs 7b51a0313ed9423f9252108fa573af68c11e6a2c --- BUILD.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/BUILD.md b/BUILD.md index 2ca08173..5fc505bf 100644 --- a/BUILD.md +++ b/BUILD.md @@ -208,8 +208,8 @@ make it avaliable from the `PATH`: ```bash brew tap homebrew/cask-versions -brew install adoptopenjdk/openjdk/adoptopenjdk8 -export JAVA_HOME="$(/usr/libexec/java_home --version 1.8)" +brew install adoptopenjdk/openjdk/adoptopenjdk11 +export JAVA_HOME="$(/usr/libexec/java_home --version 1.11)" export PATH="$JAVA_HOME/bin:$PATH" ``` From 9826c5c4a4d19f59a732c3322338b0df37fa2f5e Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 4 Apr 2021 15:00:13 +0200 Subject: [PATCH 336/450] Remove HiDPI compilation flag Always enable HiDPI support, there is no reason to expose a compilation flag. --- app/meson.build | 3 --- app/src/screen.c | 7 +++---- meson_options.txt | 1 - 3 files changed, 3 insertions(+), 8 deletions(-) diff --git a/app/meson.build b/app/meson.build index d230702e..c2a59741 100644 --- a/app/meson.build +++ b/app/meson.build @@ -118,9 +118,6 @@ conf.set('DEFAULT_LOCAL_PORT_RANGE_LAST', '27199') # overridden by option --bit-rate conf.set('DEFAULT_BIT_RATE', '8000000') # 8Mbps -# enable High DPI support -conf.set('HIDPI_SUPPORT', get_option('hidpi_support')) - # run a server debugger and wait for a client to be attached conf.set('SERVER_DEBUGGER', get_option('server_debugger')) diff --git a/app/src/screen.c b/app/src/screen.c index aa6f32b7..93c059e6 100644 --- a/app/src/screen.c +++ b/app/src/screen.c @@ -269,10 +269,9 @@ screen_init(struct screen *screen, struct video_buffer *vb, struct size window_size = get_initial_optimal_size(content_size, params->window_width, params->window_height); - uint32_t window_flags = SDL_WINDOW_HIDDEN | SDL_WINDOW_RESIZABLE; -#ifdef HIDPI_SUPPORT - window_flags |= SDL_WINDOW_ALLOW_HIGHDPI; -#endif + uint32_t window_flags = SDL_WINDOW_HIDDEN + | SDL_WINDOW_RESIZABLE + | SDL_WINDOW_ALLOW_HIGHDPI; if (params->always_on_top) { #ifdef SCRCPY_SDL_HAS_WINDOW_ALWAYS_ON_TOP window_flags |= SDL_WINDOW_ALWAYS_ON_TOP; diff --git a/meson_options.txt b/meson_options.txt index b962380c..66ad5b25 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -3,6 +3,5 @@ option('compile_server', type: 'boolean', value: true, description: 'Build the s option('crossbuild_windows', type: 'boolean', value: false, description: 'Build for Windows from Linux') option('prebuilt_server', type: 'string', description: 'Path of the prebuilt server') option('portable', type: 'boolean', value: false, description: 'Use scrcpy-server from the same directory as the scrcpy executable') -option('hidpi_support', type: 'boolean', value: true, description: 'Enable High DPI support') option('server_debugger', type: 'boolean', value: false, description: 'Run a server debugger and wait for a client to be attached') option('server_debugger_method', type: 'combo', choices: ['old', 'new'], value: 'new', description: 'Select the debugger method (Android < 9: "old", Android >= 9: "new")') From 6231f683af03d967eeb1266272beae208abb919f Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Mon, 5 Apr 2021 18:13:23 +0200 Subject: [PATCH 337/450] Fix compilation error for old decoding API Commits cb9c42bdcb198b0e3ebed893e00257b95f4dcedb and 441d3fb119ce05287e94b0ae0befbb2e75173688 updated the code only for the new decoding API. --- app/src/decoder.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/decoder.c b/app/src/decoder.c index 7da959c6..3408138c 100644 --- a/app/src/decoder.c +++ b/app/src/decoder.c @@ -61,7 +61,7 @@ decoder_push(struct decoder *decoder, const AVPacket *packet) { #else int got_picture; int len = avcodec_decode_video2(decoder->codec_ctx, - decoder->video_buffer->decoding_frame, + decoder->video_buffer->producer_frame, &got_picture, packet); if (len < 0) { @@ -69,7 +69,7 @@ decoder_push(struct decoder *decoder, const AVPacket *packet) { return false; } if (got_picture) { - push_frame(decoder); + video_buffer_producer_offer_frame(decoder->video_buffer); } #endif return true; From 07a85b7c94fc519c4950a12d7c992f51117b7e81 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Wed, 7 Apr 2021 15:00:30 +0200 Subject: [PATCH 338/450] Fix typo in command-line help Refs #2237 --- app/src/cli.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/cli.c b/app/src/cli.c index f01b7941..5d5bcf10 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -210,7 +210,7 @@ scrcpy_print_usage(const char *arg0) { " Default is 0 (automatic).\n" "\n" " --window-height value\n" - " Set the initial window width.\n" + " Set the initial window height.\n" " Default is 0 (automatic).\n" "\n" "Shortcuts:\n" From a09733d175dc610bf81edf5273d028cf03e766bc Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Wed, 7 Apr 2021 10:21:03 +0200 Subject: [PATCH 339/450] Remove useless includes from decoder.c --- app/src/decoder.c | 5 ----- 1 file changed, 5 deletions(-) diff --git a/app/src/decoder.c b/app/src/decoder.c index 3408138c..a13cf75e 100644 --- a/app/src/decoder.c +++ b/app/src/decoder.c @@ -1,14 +1,9 @@ #include "decoder.h" #include -#include -#include -#include #include "events.h" -#include "recorder.h" #include "video_buffer.h" -#include "util/buffer_util.h" #include "util/log.h" void From 33006561c74c1655aaeb29c9a13ba704892beb37 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sat, 10 Apr 2021 18:44:59 +0200 Subject: [PATCH 340/450] Remove useless forward declaration from stream.h --- app/src/stream.h | 2 -- 1 file changed, 2 deletions(-) diff --git a/app/src/stream.h b/app/src/stream.h index 784e0402..d4a9bd4a 100644 --- a/app/src/stream.h +++ b/app/src/stream.h @@ -11,8 +11,6 @@ #include "util/net.h" #include "util/thread.h" -struct video_buffer; - struct stream { socket_t socket; sc_thread thread; From fb7870500a8e69bd79b6ca9f0e9ec95e8fb1315e Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sat, 10 Apr 2021 18:04:31 +0200 Subject: [PATCH 341/450] Remove unused field from input_manager --- app/src/input_manager.h | 2 -- app/src/scrcpy.c | 1 - 2 files changed, 3 deletions(-) diff --git a/app/src/input_manager.h b/app/src/input_manager.h index d10c96b5..160977b3 100644 --- a/app/src/input_manager.h +++ b/app/src/input_manager.h @@ -11,11 +11,9 @@ #include "fps_counter.h" #include "scrcpy.h" #include "screen.h" -#include "video_buffer.h" struct input_manager { struct controller *controller; - struct video_buffer *video_buffer; struct fps_counter *fps_counter; struct screen *screen; diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index 624ddbca..f996f2cf 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -41,7 +41,6 @@ static struct file_handler file_handler; static struct input_manager input_manager = { .controller = &controller, - .video_buffer = &video_buffer, .fps_counter = &fps_counter, .screen = &screen, .repeat = 0, From d0983db5924cc1bee69fb5fa868e7b74541a4b8c Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Wed, 7 Apr 2021 16:41:57 +0200 Subject: [PATCH 342/450] Make internal recorder function static --- app/src/recorder.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/recorder.c b/app/src/recorder.c index 0aacb1a4..2e3b0c28 100644 --- a/app/src/recorder.c +++ b/app/src/recorder.c @@ -228,7 +228,7 @@ recorder_rescale_packet(struct recorder *recorder, AVPacket *packet) { av_packet_rescale_ts(packet, SCRCPY_TIME_BASE, ostream->time_base); } -bool +static bool recorder_write(struct recorder *recorder, AVPacket *packet) { if (!recorder->header_written) { if (packet->pts != AV_NOPTS_VALUE) { From 08fc6694e1adf98014d3fdfa3d4cfd886618aba0 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 11 Apr 2021 13:07:44 +0200 Subject: [PATCH 343/450] Do not destroy uninitialized screen When --no-display was passed, screen_destroy() was called while screen_init() was never called. In practice, it did not crash because it just freed NULL pointers, but it was still incorrect. --- app/src/scrcpy.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index f996f2cf..6278cfd7 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -282,6 +282,7 @@ scrcpy(const struct scrcpy_options *options) { bool stream_started = false; bool controller_initialized = false; bool controller_started = false; + bool screen_initialized = false; bool record = !!options->record_filename; struct server_params params = { @@ -399,6 +400,7 @@ scrcpy(const struct scrcpy_options *options) { &screen_params)) { goto end; } + screen_initialized = true; if (options->turn_screen_off) { struct control_msg msg; @@ -427,9 +429,11 @@ scrcpy(const struct scrcpy_options *options) { ret = event_loop(options); LOGD("quit..."); - screen_destroy(&screen); - end: + if (screen_initialized) { + screen_destroy(&screen); + } + // stop stream and controller so that they don't continue once their socket // is shutdown if (stream_started) { From 28f6cbaea6a309d86d3267a9189cb0035df99860 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 11 Apr 2021 14:32:42 +0200 Subject: [PATCH 344/450] Destroy screen once stream is finished The screen receives callbacks from the decoder, fed by the stream. The decoder is run from the stream thread, so waiting for the end of stream is sufficient to avoid possible use-after-destroy. --- app/src/scrcpy.c | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index 6278cfd7..e6ae4f4c 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -430,10 +430,6 @@ scrcpy(const struct scrcpy_options *options) { LOGD("quit..."); end: - if (screen_initialized) { - screen_destroy(&screen); - } - // stop stream and controller so that they don't continue once their socket // is shutdown if (stream_started) { @@ -459,6 +455,13 @@ end: if (stream_started) { stream_join(&stream); } + + // Destroy the screen only after the stream is guaranteed to be finished, + // because otherwise the screen could receive new frames after destruction + if (screen_initialized) { + screen_destroy(&screen); + } + if (controller_started) { controller_join(&controller); } From c6d7f5ee96b8e23267fbc6f7810b139ead39f3e7 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Tue, 13 Apr 2021 22:04:38 +0200 Subject: [PATCH 345/450] Make screen_show_window() static It is only used from screen.c now. --- app/src/screen.c | 2 +- app/src/screen.h | 4 ---- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/app/src/screen.c b/app/src/screen.c index 93c059e6..3ded4263 100644 --- a/app/src/screen.c +++ b/app/src/screen.c @@ -365,7 +365,7 @@ screen_init(struct screen *screen, struct video_buffer *vb, return true; } -void +static void screen_show_window(struct screen *screen) { SDL_ShowWindow(screen->window); } diff --git a/app/src/screen.h b/app/src/screen.h index 4e1d5e63..e725bad6 100644 --- a/app/src/screen.h +++ b/app/src/screen.h @@ -60,10 +60,6 @@ screen_init(struct screen *screen, struct video_buffer *vb, struct fps_counter *fps_counter, const struct screen_params *params); -// show the window -void -screen_show_window(struct screen *screen); - // destroy window, renderer and texture (if any) void screen_destroy(struct screen *screen); From 65c4f487b306cb288ec9ee1155356de7939eac16 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Tue, 13 Apr 2021 22:10:45 +0200 Subject: [PATCH 346/450] Set initial fullscreen from screen.c --- app/src/scrcpy.c | 5 +---- app/src/screen.c | 4 ++++ app/src/screen.h | 2 ++ 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index e6ae4f4c..6f803d23 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -394,6 +394,7 @@ scrcpy(const struct scrcpy_options *options) { .window_borderless = options->window_borderless, .rotation = options->rotation, .mipmaps = options->mipmaps, + .fullscreen = options->fullscreen, }; if (!screen_init(&screen, &video_buffer, &fps_counter, @@ -411,10 +412,6 @@ scrcpy(const struct scrcpy_options *options) { LOGW("Could not request 'set screen power mode'"); } } - - if (options->fullscreen) { - screen_switch_fullscreen(&screen); - } } // now we consumed the header values, the socket receives the video stream diff --git a/app/src/screen.c b/app/src/screen.c index 3ded4263..ed810264 100644 --- a/app/src/screen.c +++ b/app/src/screen.c @@ -362,6 +362,10 @@ screen_init(struct screen *screen, struct video_buffer *vb, screen_update_content_rect(screen); + if (params->fullscreen) { + screen_switch_fullscreen(screen); + } + return true; } diff --git a/app/src/screen.h b/app/src/screen.h index e725bad6..dca65d41 100644 --- a/app/src/screen.h +++ b/app/src/screen.h @@ -52,6 +52,8 @@ struct screen_params { uint8_t rotation; bool mipmaps; + + bool fullscreen; }; // initialize screen, create window, renderer and texture (window is hidden) From c23c38f99dbed2776d9c0ec92b37da5788866df7 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Tue, 13 Apr 2021 22:14:09 +0200 Subject: [PATCH 347/450] Move resizing workaround to screen.c --- app/src/scrcpy.c | 29 ----------------------------- app/src/screen.c | 27 +++++++++++++++++++++++++++ 2 files changed, 27 insertions(+), 29 deletions(-) diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index 6f803d23..7d47d016 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -128,30 +128,6 @@ sdl_init_and_configure(bool display, const char *render_driver, return true; } - -#if defined(__APPLE__) || defined(__WINDOWS__) -# define CONTINUOUS_RESIZING_WORKAROUND -#endif - -#ifdef CONTINUOUS_RESIZING_WORKAROUND -// On Windows and MacOS, resizing blocks the event loop, so resizing events are -// not triggered. As a workaround, handle them in an event handler. -// -// -// -static int -event_watcher(void *data, SDL_Event *event) { - (void) data; - if (event->type == SDL_WINDOWEVENT - && event->window.event == SDL_WINDOWEVENT_RESIZED) { - // In practice, it seems to always be called from the same thread in - // that specific case. Anyway, it's just a workaround. - screen_render(&screen, true); - } - return 0; -} -#endif - static bool is_apk(const char *file) { const char *ext = strrchr(file, '.'); @@ -209,11 +185,6 @@ end: static bool event_loop(const struct scrcpy_options *options) { -#ifdef CONTINUOUS_RESIZING_WORKAROUND - if (options->display) { - SDL_AddEventWatch(event_watcher, NULL); - } -#endif SDL_Event event; while (SDL_WaitEvent(&event)) { enum event_result result = handle_event(&event, options); diff --git a/app/src/screen.c b/app/src/screen.c index ed810264..934e418f 100644 --- a/app/src/screen.c +++ b/app/src/screen.c @@ -239,6 +239,29 @@ create_texture(struct screen *screen) { return texture; } +#if defined(__APPLE__) || defined(__WINDOWS__) +# define CONTINUOUS_RESIZING_WORKAROUND +#endif + +#ifdef CONTINUOUS_RESIZING_WORKAROUND +// On Windows and MacOS, resizing blocks the event loop, so resizing events are +// not triggered. As a workaround, handle them in an event handler. +// +// +// +static int +event_watcher(void *data, SDL_Event *event) { + struct screen *screen = data; + if (event->type == SDL_WINDOWEVENT + && event->window.event == SDL_WINDOWEVENT_RESIZED) { + // In practice, it seems to always be called from the same thread in + // that specific case. Anyway, it's just a workaround. + screen_render(screen, true); + } + return 0; +} +#endif + bool screen_init(struct screen *screen, struct video_buffer *vb, struct fps_counter *fps_counter, @@ -366,6 +389,10 @@ screen_init(struct screen *screen, struct video_buffer *vb, screen_switch_fullscreen(screen); } +#ifdef CONTINUOUS_RESIZING_WORKAROUND + SDL_AddEventWatch(event_watcher, screen); +#endif + return true; } From 8ef4c044fa476dc40e20ac8d3045c0de41371a7c Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Tue, 13 Apr 2021 22:17:46 +0200 Subject: [PATCH 348/450] Do not forward SDL_DROPFILE event The event is handled by scrcpy.c, it is not necessary to send it to screen or input_manager. --- app/src/scrcpy.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index 7d47d016..388bb73d 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -167,7 +167,7 @@ handle_event(SDL_Event *event, const struct scrcpy_options *options) { action = ACTION_PUSH_FILE; } file_handler_request(&file_handler, action, file); - break; + goto end; } } From edee69d6374639d165d12fecc5614f38c6b117c8 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 16 Apr 2021 17:40:39 +0200 Subject: [PATCH 349/450] Fix options alphabetical order "verbosity" < "version" --- app/scrcpy.1 | 8 ++++---- app/src/cli.c | 6 +++--- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/app/scrcpy.1 b/app/scrcpy.1 index 92b8e1e3..bf3bc9cd 100644 --- a/app/scrcpy.1 +++ b/app/scrcpy.1 @@ -187,16 +187,16 @@ Enable "show touches" on start, restore the initial value on exit. It only shows physical touches (not clicks from scrcpy). -.TP -.B \-v, \-\-version -Print the version of scrcpy. - .TP .BI "\-V, \-\-verbosity " value Set the log level ("debug", "info", "warn" or "error"). Default is "info" for release builds, "debug" for debug builds. +.TP +.B \-v, \-\-version +Print the version of scrcpy. + .TP .B \-w, \-\-stay-awake Keep the device on while scrcpy is running, when the device is plugged in. diff --git a/app/src/cli.c b/app/src/cli.c index 484c48ef..042a1f4c 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -179,9 +179,6 @@ scrcpy_print_usage(const char *arg0) { " on exit.\n" " It only shows physical touches (not clicks from scrcpy).\n" "\n" - " -v, --version\n" - " Print the version of scrcpy.\n" - "\n" " -V, --verbosity value\n" " Set the log level (debug, info, warn or error).\n" #ifndef NDEBUG @@ -189,6 +186,9 @@ scrcpy_print_usage(const char *arg0) { #else " Default is info.\n" #endif + "\n" + " -v, --version\n" + " Print the version of scrcpy.\n" "\n" " -w, --stay-awake\n" " Keep the device on while scrcpy is running, when the device\n" From 8cc057c8f14d051b2480bad5116083bbeb2a2270 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 16 Apr 2021 17:53:37 +0200 Subject: [PATCH 350/450] Prevent forwarding only "mouse released" events Some mouse clicks DOWN are captured for shortcuts, but the matching UP events were still forwarded to the device. Instead, capture both DOWN and UP for shortcuts, and do nothing on UP. PR #2259 Refs #2258 --- app/src/input_manager.c | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/app/src/input_manager.c b/app/src/input_manager.c index 7226d68f..f6b1a96a 100644 --- a/app/src/input_manager.c +++ b/app/src/input_manager.c @@ -646,13 +646,17 @@ input_manager_process_mouse_button(struct input_manager *im, } bool down = event->type == SDL_MOUSEBUTTONDOWN; - if (!im->forward_all_clicks && down) { + if (!im->forward_all_clicks) { if (control && event->button == SDL_BUTTON_RIGHT) { - press_back_or_turn_screen_on(im->controller); + if (down) { + press_back_or_turn_screen_on(im->controller); + } return; } if (control && event->button == SDL_BUTTON_MIDDLE) { - action_home(im->controller, ACTION_DOWN | ACTION_UP); + if (down) { + action_home(im->controller, ACTION_DOWN | ACTION_UP); + } return; } @@ -665,7 +669,9 @@ input_manager_process_mouse_button(struct input_manager *im, bool outside = x < r->x || x >= r->x + r->w || y < r->y || y >= r->y + r->h; if (outside) { - screen_resize_to_fit(im->screen); + if (down) { + screen_resize_to_fit(im->screen); + } return; } } From 964b6d2243fa1921543e48810f3064b9bd2d50d1 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 16 Apr 2021 17:58:22 +0200 Subject: [PATCH 351/450] Forward DOWN and UP separately for middle-click As a consequence of this change, the HOME button is now handled by Android on mouse released. This is consistent with the keyboard shortcut (MOD+h) behavior. PR #2259 Refs #2258 --- app/src/input_manager.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/src/input_manager.c b/app/src/input_manager.c index f6b1a96a..10af6e8b 100644 --- a/app/src/input_manager.c +++ b/app/src/input_manager.c @@ -647,6 +647,8 @@ input_manager_process_mouse_button(struct input_manager *im, bool down = event->type == SDL_MOUSEBUTTONDOWN; if (!im->forward_all_clicks) { + int action = down ? ACTION_DOWN : ACTION_UP; + if (control && event->button == SDL_BUTTON_RIGHT) { if (down) { press_back_or_turn_screen_on(im->controller); @@ -654,9 +656,7 @@ input_manager_process_mouse_button(struct input_manager *im, return; } if (control && event->button == SDL_BUTTON_MIDDLE) { - if (down) { - action_home(im->controller, ACTION_DOWN | ACTION_UP); - } + action_home(im->controller, action); return; } From 498ad23e9804cd7d7bb56bb1cd0d26b88e1830b9 Mon Sep 17 00:00:00 2001 From: Andrea Gelmini Date: Sun, 18 Apr 2021 13:33:15 +0200 Subject: [PATCH 352/450] Fix typos PR #2263 Signed-off-by: Romain Vimont --- BUILD.md | 2 +- DEVELOP.md | 2 +- app/src/android/input.h | 2 +- app/src/event_converter.c | 2 +- app/src/util/str_util.h | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/BUILD.md b/BUILD.md index 5fc505bf..4dc6d64c 100644 --- a/BUILD.md +++ b/BUILD.md @@ -204,7 +204,7 @@ brew install pkg-config meson ``` Additionally, if you want to build the server, install Java 8 from Caskroom, and -make it avaliable from the `PATH`: +make it available from the `PATH`: ```bash brew tap homebrew/cask-versions diff --git a/DEVELOP.md b/DEVELOP.md index 4d8acc59..d11f139e 100644 --- a/DEVELOP.md +++ b/DEVELOP.md @@ -211,7 +211,7 @@ There are two [frames][video_buffer] simultaneously in memory: - the **rendering** frame, rendered in a texture from the main thread. 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 immediately starts 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 diff --git a/app/src/android/input.h b/app/src/android/input.h index b51731b4..30c4bcb9 100644 --- a/app/src/android/input.h +++ b/app/src/android/input.h @@ -21,7 +21,7 @@ #define _ANDROID_INPUT_H /** - * Meta key / modifer state. + * Meta key / modifier state. */ enum android_metastate { /** No meta keys are pressed. */ diff --git a/app/src/event_converter.c b/app/src/event_converter.c index ab48898d..84c3999a 100644 --- a/app/src/event_converter.c +++ b/app/src/event_converter.c @@ -16,7 +16,7 @@ convert_keycode_action(SDL_EventType from, enum android_keyevent_action *to) { static enum android_metastate autocomplete_metastate(enum android_metastate metastate) { - // fill dependant flags + // fill dependent flags if (metastate & (AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_RIGHT_ON)) { metastate |= AMETA_SHIFT_ON; } diff --git a/app/src/util/str_util.h b/app/src/util/str_util.h index c7f26cdb..2fb6b4ee 100644 --- a/app/src/util/str_util.h +++ b/app/src/util/str_util.h @@ -16,7 +16,7 @@ size_t xstrncpy(char *dest, const char *src, size_t n); // join tokens by sep into dst -// returns the number of chars actually written (max n-1) if no trucation +// returns the number of chars actually written (max n-1) if no truncation // occurred, or n if truncated size_t xstrjoin(char *dst, const char *const tokens[], char sep, size_t n); From d0739911a3e413b70275ded3eef839f9dc57ba7a Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 16 Apr 2021 18:37:50 +0200 Subject: [PATCH 353/450] Forward DOWN and UP separately for right-click The shortcut "back on screen on" is a bit special: the control is requested by the client, but the actual event injection (POWER or BACK) is determined on the device. To properly inject DOWN and UP events for BACK, transmit the action as a control parameter. If the screen is off: - on DOWN, inject POWER (DOWN and UP) (wake up the device immediately) - on UP, do nothing If the screen is on: - on DOWN, inject BACK DOWN - on UP, inject BACK UP A corner case is when the screen turns off between the DOWN and UP event. In that case, a BACK UP event will be injected, so it's harmless. As a consequence of this change, the BACK button is now handled by Android on mouse released. This is consistent with the keyboard shortcut (Mod+b) behavior. PR #2259 Refs #2258 --- app/src/control_msg.c | 4 +++- app/src/control_msg.h | 4 ++++ app/src/input_manager.c | 22 ++++++++++++++----- app/tests/test_control_msg_serialize.c | 6 ++++- .../com/genymobile/scrcpy/ControlMessage.java | 7 ++++++ .../scrcpy/ControlMessageReader.java | 13 ++++++++++- .../com/genymobile/scrcpy/Controller.java | 20 ++++++++++++----- .../scrcpy/ControlMessageReaderTest.java | 2 ++ 8 files changed, 64 insertions(+), 14 deletions(-) diff --git a/app/src/control_msg.c b/app/src/control_msg.c index 436a8861..69e75014 100644 --- a/app/src/control_msg.c +++ b/app/src/control_msg.c @@ -67,6 +67,9 @@ control_msg_serialize(const struct control_msg *msg, unsigned char *buf) { buffer_write32be(&buf[17], (uint32_t) msg->inject_scroll_event.vscroll); return 21; + case CONTROL_MSG_TYPE_BACK_OR_SCREEN_ON: + buf[1] = msg->inject_keycode.action; + return 2; case CONTROL_MSG_TYPE_SET_CLIPBOARD: { buf[1] = !!msg->set_clipboard.paste; size_t len = write_string(msg->set_clipboard.text, @@ -77,7 +80,6 @@ control_msg_serialize(const struct control_msg *msg, unsigned char *buf) { case CONTROL_MSG_TYPE_SET_SCREEN_POWER_MODE: buf[1] = msg->set_screen_power_mode.mode; return 2; - case CONTROL_MSG_TYPE_BACK_OR_SCREEN_ON: case CONTROL_MSG_TYPE_EXPAND_NOTIFICATION_PANEL: case CONTROL_MSG_TYPE_COLLAPSE_NOTIFICATION_PANEL: case CONTROL_MSG_TYPE_GET_CLIPBOARD: diff --git a/app/src/control_msg.h b/app/src/control_msg.h index 1b25591d..8d9ab7d4 100644 --- a/app/src/control_msg.h +++ b/app/src/control_msg.h @@ -64,6 +64,10 @@ struct control_msg { int32_t hscroll; int32_t vscroll; } inject_scroll_event; + struct { + enum android_keyevent_action action; // action for the BACK key + // screen may only be turned on on ACTION_DOWN + } back_or_screen_on; struct { char *text; // owned, to be freed by free() bool paste; diff --git a/app/src/input_manager.c b/app/src/input_manager.c index 10af6e8b..c10e53af 100644 --- a/app/src/input_manager.c +++ b/app/src/input_manager.c @@ -146,13 +146,25 @@ action_cut(struct controller *controller, int actions) { } // turn the screen on if it was off, press BACK otherwise +// If the screen is off, it is turned on only on ACTION_DOWN static void -press_back_or_turn_screen_on(struct controller *controller) { +press_back_or_turn_screen_on(struct controller *controller, int actions) { struct control_msg msg; msg.type = CONTROL_MSG_TYPE_BACK_OR_SCREEN_ON; - if (!controller_push_msg(controller, &msg)) { - LOGW("Could not request 'press back or turn screen on'"); + if (actions & ACTION_DOWN) { + msg.back_or_screen_on.action = AKEY_EVENT_ACTION_DOWN; + if (!controller_push_msg(controller, &msg)) { + LOGW("Could not request 'press back or turn screen on'"); + return; + } + } + + if (actions & ACTION_UP) { + msg.back_or_screen_on.action = AKEY_EVENT_ACTION_UP; + if (!controller_push_msg(controller, &msg)) { + LOGW("Could not request 'press back or turn screen on'"); + } } } @@ -650,9 +662,7 @@ input_manager_process_mouse_button(struct input_manager *im, int action = down ? ACTION_DOWN : ACTION_UP; if (control && event->button == SDL_BUTTON_RIGHT) { - if (down) { - press_back_or_turn_screen_on(im->controller); - } + press_back_or_turn_screen_on(im->controller, action); return; } if (control && event->button == SDL_BUTTON_MIDDLE) { diff --git a/app/tests/test_control_msg_serialize.c b/app/tests/test_control_msg_serialize.c index dc6e0821..4771ce1f 100644 --- a/app/tests/test_control_msg_serialize.c +++ b/app/tests/test_control_msg_serialize.c @@ -146,14 +146,18 @@ static void test_serialize_inject_scroll_event(void) { static void test_serialize_back_or_screen_on(void) { struct control_msg msg = { .type = CONTROL_MSG_TYPE_BACK_OR_SCREEN_ON, + .back_or_screen_on = { + .action = AKEY_EVENT_ACTION_UP, + }, }; unsigned char buf[CONTROL_MSG_MAX_SIZE]; size_t size = control_msg_serialize(&msg, buf); - assert(size == 1); + assert(size == 2); const unsigned char expected[] = { CONTROL_MSG_TYPE_BACK_OR_SCREEN_ON, + 0x01, // AKEY_EVENT_ACTION_UP }; assert(!memcmp(buf, expected, sizeof(expected))); } diff --git a/server/src/main/java/com/genymobile/scrcpy/ControlMessage.java b/server/src/main/java/com/genymobile/scrcpy/ControlMessage.java index 736acf80..44cb1b59 100644 --- a/server/src/main/java/com/genymobile/scrcpy/ControlMessage.java +++ b/server/src/main/java/com/genymobile/scrcpy/ControlMessage.java @@ -71,6 +71,13 @@ public final class ControlMessage { return msg; } + public static ControlMessage createBackOrScreenOn(int action) { + ControlMessage msg = new ControlMessage(); + msg.type = TYPE_BACK_OR_SCREEN_ON; + msg.action = action; + return msg; + } + public static ControlMessage createSetClipboard(String text, boolean paste) { ControlMessage msg = new ControlMessage(); msg.type = TYPE_SET_CLIPBOARD; diff --git a/server/src/main/java/com/genymobile/scrcpy/ControlMessageReader.java b/server/src/main/java/com/genymobile/scrcpy/ControlMessageReader.java index ce185103..7ebecf76 100644 --- a/server/src/main/java/com/genymobile/scrcpy/ControlMessageReader.java +++ b/server/src/main/java/com/genymobile/scrcpy/ControlMessageReader.java @@ -11,6 +11,7 @@ public class ControlMessageReader { static final int INJECT_KEYCODE_PAYLOAD_LENGTH = 13; static final int INJECT_TOUCH_EVENT_PAYLOAD_LENGTH = 27; static final int INJECT_SCROLL_EVENT_PAYLOAD_LENGTH = 20; + static final int BACK_OR_SCREEN_ON_LENGTH = 1; static final int SET_SCREEN_POWER_MODE_PAYLOAD_LENGTH = 1; static final int SET_CLIPBOARD_FIXED_PAYLOAD_LENGTH = 1; @@ -66,13 +67,15 @@ public class ControlMessageReader { case ControlMessage.TYPE_INJECT_SCROLL_EVENT: msg = parseInjectScrollEvent(); break; + case ControlMessage.TYPE_BACK_OR_SCREEN_ON: + msg = parseBackOrScreenOnEvent(); + break; case ControlMessage.TYPE_SET_CLIPBOARD: msg = parseSetClipboard(); break; case ControlMessage.TYPE_SET_SCREEN_POWER_MODE: msg = parseSetScreenPowerMode(); break; - case ControlMessage.TYPE_BACK_OR_SCREEN_ON: case ControlMessage.TYPE_EXPAND_NOTIFICATION_PANEL: case ControlMessage.TYPE_COLLAPSE_NOTIFICATION_PANEL: case ControlMessage.TYPE_GET_CLIPBOARD: @@ -150,6 +153,14 @@ public class ControlMessageReader { return ControlMessage.createInjectScrollEvent(position, hScroll, vScroll); } + private ControlMessage parseBackOrScreenOnEvent() { + if (buffer.remaining() < BACK_OR_SCREEN_ON_LENGTH) { + return null; + } + int action = toUnsigned(buffer.get()); + return ControlMessage.createBackOrScreenOn(action); + } + private ControlMessage parseSetClipboard() { if (buffer.remaining() < SET_CLIPBOARD_FIXED_PAYLOAD_LENGTH) { return null; diff --git a/server/src/main/java/com/genymobile/scrcpy/Controller.java b/server/src/main/java/com/genymobile/scrcpy/Controller.java index 8f262ab6..6af5ddf6 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Controller.java +++ b/server/src/main/java/com/genymobile/scrcpy/Controller.java @@ -101,7 +101,7 @@ public class Controller { break; case ControlMessage.TYPE_BACK_OR_SCREEN_ON: if (device.supportsInputEvents()) { - pressBackOrTurnScreenOn(); + pressBackOrTurnScreenOn(msg.getAction()); } break; case ControlMessage.TYPE_EXPAND_NOTIFICATION_PANEL: @@ -255,12 +255,22 @@ public class Controller { }, 200, TimeUnit.MILLISECONDS); } - private boolean pressBackOrTurnScreenOn() { - int keycode = Device.isScreenOn() ? KeyEvent.KEYCODE_BACK : KeyEvent.KEYCODE_POWER; - if (keepPowerModeOff && keycode == KeyEvent.KEYCODE_POWER) { + private boolean pressBackOrTurnScreenOn(int action) { + if (Device.isScreenOn()) { + return device.injectKeyEvent(action, KeyEvent.KEYCODE_BACK, 0, 0); + } + + // Screen is off + // Only press POWER on ACTION_DOWN + if (action != KeyEvent.ACTION_DOWN) { + // do nothing, + return true; + } + + if (keepPowerModeOff) { schedulePowerModeOff(); } - return device.injectKeycode(keycode); + return device.injectKeycode(KeyEvent.KEYCODE_POWER); } private boolean setClipboard(String text, boolean paste) { diff --git a/server/src/test/java/com/genymobile/scrcpy/ControlMessageReaderTest.java b/server/src/test/java/com/genymobile/scrcpy/ControlMessageReaderTest.java index 5eb52760..6167ddeb 100644 --- a/server/src/test/java/com/genymobile/scrcpy/ControlMessageReaderTest.java +++ b/server/src/test/java/com/genymobile/scrcpy/ControlMessageReaderTest.java @@ -154,6 +154,7 @@ public class ControlMessageReaderTest { ByteArrayOutputStream bos = new ByteArrayOutputStream(); DataOutputStream dos = new DataOutputStream(bos); dos.writeByte(ControlMessage.TYPE_BACK_OR_SCREEN_ON); + dos.writeByte(KeyEvent.ACTION_UP); byte[] packet = bos.toByteArray(); @@ -161,6 +162,7 @@ public class ControlMessageReaderTest { ControlMessage event = reader.next(); Assert.assertEquals(ControlMessage.TYPE_BACK_OR_SCREEN_ON, event.getType()); + Assert.assertEquals(KeyEvent.ACTION_UP, event.getAction()); } @Test From b9c3f65fd84f27ebbe18f96c9eb207b6bf04eb78 Mon Sep 17 00:00:00 2001 From: brunoais Date: Mon, 12 Apr 2021 10:15:33 +0100 Subject: [PATCH 354/450] Provide actions for the extra mouse buttons Bind APP_SWITCH to button 4 and expand notification panel on button 5. PR #2258 Signed-off-by: Romain Vimont --- app/src/input_manager.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/app/src/input_manager.c b/app/src/input_manager.c index c10e53af..b8a8c846 100644 --- a/app/src/input_manager.c +++ b/app/src/input_manager.c @@ -661,6 +661,14 @@ input_manager_process_mouse_button(struct input_manager *im, if (!im->forward_all_clicks) { int action = down ? ACTION_DOWN : ACTION_UP; + if (control && event->button == SDL_BUTTON_X1) { + action_app_switch(im->controller, action); + return; + } + if (control && event->button == SDL_BUTTON_X2 && down) { + expand_notification_panel(im->controller); + return; + } if (control && event->button == SDL_BUTTON_RIGHT) { press_back_or_turn_screen_on(im->controller, action); return; From aaf7875d923e492047e66ca88585079704abf5ad Mon Sep 17 00:00:00 2001 From: Frank Leon Rose Date: Thu, 22 Apr 2021 13:59:46 -0400 Subject: [PATCH 355/450] Ensure get_server_path() retval is freeable PR #2276 Signed-off-by: Romain Vimont --- app/src/server.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/server.c b/app/src/server.c index a0b40f96..a4bba33c 100644 --- a/app/src/server.c +++ b/app/src/server.c @@ -58,7 +58,7 @@ get_server_path(void) { LOGE("Could not get executable path, " "using " SERVER_FILENAME " from current directory"); // not found, use current directory - return SERVER_FILENAME; + return strdup(SERVER_FILENAME); } char *dir = dirname(executable_path); size_t dirlen = strlen(dir); @@ -70,7 +70,7 @@ get_server_path(void) { LOGE("Could not alloc server path string, " "using " SERVER_FILENAME " from current directory"); free(executable_path); - return SERVER_FILENAME; + return strdup(SERVER_FILENAME); } memcpy(server_path, dir, dirlen); From bb4614d55899bd1df00b14d3c7f3bdac3a5c818a Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Tue, 20 Apr 2021 20:59:58 +0200 Subject: [PATCH 356/450] Reverse boolean logic for readability Refs #2260 --- .../com/genymobile/scrcpy/wrappers/ActivityManager.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/wrappers/ActivityManager.java b/server/src/main/java/com/genymobile/scrcpy/wrappers/ActivityManager.java index 71967c50..93ed4528 100644 --- a/server/src/main/java/com/genymobile/scrcpy/wrappers/ActivityManager.java +++ b/server/src/main/java/com/genymobile/scrcpy/wrappers/ActivityManager.java @@ -14,7 +14,7 @@ public class ActivityManager { private final IInterface manager; private Method getContentProviderExternalMethod; - private boolean getContentProviderExternalMethodLegacy; + private boolean getContentProviderExternalMethodNewVersion = true; private Method removeContentProviderExternalMethod; public ActivityManager(IInterface manager) { @@ -29,7 +29,7 @@ public class ActivityManager { } catch (NoSuchMethodException e) { // old version getContentProviderExternalMethod = manager.getClass().getMethod("getContentProviderExternal", String.class, int.class, IBinder.class); - getContentProviderExternalMethodLegacy = true; + getContentProviderExternalMethodNewVersion = false; } } return getContentProviderExternalMethod; @@ -46,7 +46,7 @@ public class ActivityManager { try { Method method = getGetContentProviderExternalMethod(); Object[] args; - if (!getContentProviderExternalMethodLegacy) { + if (getContentProviderExternalMethodNewVersion) { // new version args = new Object[]{name, ServiceManager.USER_ID, token, null}; } else { From 66c581851fe968a12bf66c225dd76ff4690841f1 Mon Sep 17 00:00:00 2001 From: brunoais Date: Sat, 17 Apr 2021 13:26:54 +0100 Subject: [PATCH 357/450] Rename control message type to COLLAPSE_PANELS The collapsing action collapses any panels. By the way, the Android method is named collapsePanels(). PR #2260 Signed-off-by: Romain Vimont --- app/src/control_msg.c | 2 +- app/src/control_msg.h | 2 +- app/src/input_manager.c | 6 +++--- app/tests/test_control_msg_serialize.c | 8 ++++---- .../main/java/com/genymobile/scrcpy/ControlMessage.java | 2 +- .../java/com/genymobile/scrcpy/ControlMessageReader.java | 2 +- .../src/main/java/com/genymobile/scrcpy/Controller.java | 2 +- .../com/genymobile/scrcpy/ControlMessageReaderTest.java | 6 +++--- 8 files changed, 15 insertions(+), 15 deletions(-) diff --git a/app/src/control_msg.c b/app/src/control_msg.c index 69e75014..7d18fb6a 100644 --- a/app/src/control_msg.c +++ b/app/src/control_msg.c @@ -81,7 +81,7 @@ control_msg_serialize(const struct control_msg *msg, unsigned char *buf) { buf[1] = msg->set_screen_power_mode.mode; return 2; case CONTROL_MSG_TYPE_EXPAND_NOTIFICATION_PANEL: - case CONTROL_MSG_TYPE_COLLAPSE_NOTIFICATION_PANEL: + case CONTROL_MSG_TYPE_COLLAPSE_PANELS: case CONTROL_MSG_TYPE_GET_CLIPBOARD: case CONTROL_MSG_TYPE_ROTATE_DEVICE: // no additional data diff --git a/app/src/control_msg.h b/app/src/control_msg.h index 8d9ab7d4..1e9481d9 100644 --- a/app/src/control_msg.h +++ b/app/src/control_msg.h @@ -27,7 +27,7 @@ enum control_msg_type { CONTROL_MSG_TYPE_INJECT_SCROLL_EVENT, CONTROL_MSG_TYPE_BACK_OR_SCREEN_ON, CONTROL_MSG_TYPE_EXPAND_NOTIFICATION_PANEL, - CONTROL_MSG_TYPE_COLLAPSE_NOTIFICATION_PANEL, + CONTROL_MSG_TYPE_COLLAPSE_PANELS, CONTROL_MSG_TYPE_GET_CLIPBOARD, CONTROL_MSG_TYPE_SET_CLIPBOARD, CONTROL_MSG_TYPE_SET_SCREEN_POWER_MODE, diff --git a/app/src/input_manager.c b/app/src/input_manager.c index b8a8c846..574e02d4 100644 --- a/app/src/input_manager.c +++ b/app/src/input_manager.c @@ -179,9 +179,9 @@ expand_notification_panel(struct controller *controller) { } static void -collapse_notification_panel(struct controller *controller) { +collapse_panels(struct controller *controller) { struct control_msg msg; - msg.type = CONTROL_MSG_TYPE_COLLAPSE_NOTIFICATION_PANEL; + msg.type = CONTROL_MSG_TYPE_COLLAPSE_PANELS; if (!controller_push_msg(controller, &msg)) { LOGW("Could not request 'collapse notification panel'"); @@ -498,7 +498,7 @@ input_manager_process_key(struct input_manager *im, case SDLK_n: if (control && !repeat && down) { if (shift) { - collapse_notification_panel(controller); + collapse_panels(controller); } else { expand_notification_panel(controller); } diff --git a/app/tests/test_control_msg_serialize.c b/app/tests/test_control_msg_serialize.c index 4771ce1f..d7b89929 100644 --- a/app/tests/test_control_msg_serialize.c +++ b/app/tests/test_control_msg_serialize.c @@ -177,9 +177,9 @@ static void test_serialize_expand_notification_panel(void) { assert(!memcmp(buf, expected, sizeof(expected))); } -static void test_serialize_collapse_notification_panel(void) { +static void test_serialize_collapse_panels(void) { struct control_msg msg = { - .type = CONTROL_MSG_TYPE_COLLAPSE_NOTIFICATION_PANEL, + .type = CONTROL_MSG_TYPE_COLLAPSE_PANELS, }; unsigned char buf[CONTROL_MSG_MAX_SIZE]; @@ -187,7 +187,7 @@ static void test_serialize_collapse_notification_panel(void) { assert(size == 1); const unsigned char expected[] = { - CONTROL_MSG_TYPE_COLLAPSE_NOTIFICATION_PANEL, + CONTROL_MSG_TYPE_COLLAPSE_PANELS, }; assert(!memcmp(buf, expected, sizeof(expected))); } @@ -274,7 +274,7 @@ int main(int argc, char *argv[]) { test_serialize_inject_scroll_event(); test_serialize_back_or_screen_on(); test_serialize_expand_notification_panel(); - test_serialize_collapse_notification_panel(); + test_serialize_collapse_panels(); test_serialize_get_clipboard(); test_serialize_set_clipboard(); test_serialize_set_screen_power_mode(); diff --git a/server/src/main/java/com/genymobile/scrcpy/ControlMessage.java b/server/src/main/java/com/genymobile/scrcpy/ControlMessage.java index 44cb1b59..34ce1941 100644 --- a/server/src/main/java/com/genymobile/scrcpy/ControlMessage.java +++ b/server/src/main/java/com/genymobile/scrcpy/ControlMessage.java @@ -11,7 +11,7 @@ public final class ControlMessage { public static final int TYPE_INJECT_SCROLL_EVENT = 3; public static final int TYPE_BACK_OR_SCREEN_ON = 4; public static final int TYPE_EXPAND_NOTIFICATION_PANEL = 5; - public static final int TYPE_COLLAPSE_NOTIFICATION_PANEL = 6; + public static final int TYPE_COLLAPSE_PANELS = 6; public static final int TYPE_GET_CLIPBOARD = 7; public static final int TYPE_SET_CLIPBOARD = 8; public static final int TYPE_SET_SCREEN_POWER_MODE = 9; diff --git a/server/src/main/java/com/genymobile/scrcpy/ControlMessageReader.java b/server/src/main/java/com/genymobile/scrcpy/ControlMessageReader.java index 7ebecf76..97f027ff 100644 --- a/server/src/main/java/com/genymobile/scrcpy/ControlMessageReader.java +++ b/server/src/main/java/com/genymobile/scrcpy/ControlMessageReader.java @@ -77,7 +77,7 @@ public class ControlMessageReader { msg = parseSetScreenPowerMode(); break; case ControlMessage.TYPE_EXPAND_NOTIFICATION_PANEL: - case ControlMessage.TYPE_COLLAPSE_NOTIFICATION_PANEL: + case ControlMessage.TYPE_COLLAPSE_PANELS: case ControlMessage.TYPE_GET_CLIPBOARD: case ControlMessage.TYPE_ROTATE_DEVICE: msg = ControlMessage.createEmpty(type); diff --git a/server/src/main/java/com/genymobile/scrcpy/Controller.java b/server/src/main/java/com/genymobile/scrcpy/Controller.java index 6af5ddf6..15c0584a 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Controller.java +++ b/server/src/main/java/com/genymobile/scrcpy/Controller.java @@ -107,7 +107,7 @@ public class Controller { case ControlMessage.TYPE_EXPAND_NOTIFICATION_PANEL: Device.expandNotificationPanel(); break; - case ControlMessage.TYPE_COLLAPSE_NOTIFICATION_PANEL: + case ControlMessage.TYPE_COLLAPSE_PANELS: Device.collapsePanels(); break; case ControlMessage.TYPE_GET_CLIPBOARD: diff --git a/server/src/test/java/com/genymobile/scrcpy/ControlMessageReaderTest.java b/server/src/test/java/com/genymobile/scrcpy/ControlMessageReaderTest.java index 6167ddeb..97429026 100644 --- a/server/src/test/java/com/genymobile/scrcpy/ControlMessageReaderTest.java +++ b/server/src/test/java/com/genymobile/scrcpy/ControlMessageReaderTest.java @@ -182,19 +182,19 @@ public class ControlMessageReaderTest { } @Test - public void testParseCollapseNotificationPanelEvent() throws IOException { + public void testParseCollapsePanelsEvent() throws IOException { ControlMessageReader reader = new ControlMessageReader(); ByteArrayOutputStream bos = new ByteArrayOutputStream(); DataOutputStream dos = new DataOutputStream(bos); - dos.writeByte(ControlMessage.TYPE_COLLAPSE_NOTIFICATION_PANEL); + dos.writeByte(ControlMessage.TYPE_COLLAPSE_PANELS); byte[] packet = bos.toByteArray(); reader.readFrom(new ByteArrayInputStream(packet)); ControlMessage event = reader.next(); - Assert.assertEquals(ControlMessage.TYPE_COLLAPSE_NOTIFICATION_PANEL, event.getType()); + Assert.assertEquals(ControlMessage.TYPE_COLLAPSE_PANELS, event.getType()); } @Test From 9576283907fed9cf7baad1bb15613a811894f719 Mon Sep 17 00:00:00 2001 From: brunoais Date: Sat, 17 Apr 2021 13:15:31 +0100 Subject: [PATCH 358/450] Count repeated identical key events This will allow shortcuts such as MOD+n+n to open the settings panel. PR #2260 Signed-off-by: Romain Vimont --- app/src/input_manager.c | 19 +++++++++++++++++-- app/src/input_manager.h | 7 +++++++ 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/app/src/input_manager.c b/app/src/input_manager.c index 574e02d4..b6006285 100644 --- a/app/src/input_manager.c +++ b/app/src/input_manager.c @@ -72,6 +72,10 @@ input_manager_init(struct input_manager *im, im->sdl_shortcut_mods.count = shortcut_mods->count; im->vfinger_down = false; + + im->last_keycode = SDLK_UNKNOWN; + im->last_mod = 0; + im->key_repeat = 0; } static void @@ -384,16 +388,27 @@ input_manager_process_key(struct input_manager *im, // control: indicates the state of the command-line option --no-control bool control = im->control; - bool smod = is_shortcut_mod(im, event->keysym.mod); - struct controller *controller = im->controller; SDL_Keycode keycode = event->keysym.sym; + uint16_t mod = event->keysym.mod; bool down = event->type == SDL_KEYDOWN; bool ctrl = event->keysym.mod & KMOD_CTRL; bool shift = event->keysym.mod & KMOD_SHIFT; bool repeat = event->repeat; + bool smod = is_shortcut_mod(im, mod); + + if (down && !repeat) { + if (keycode == im->last_keycode && mod == im->last_mod) { + ++im->key_repeat; + } else { + im->key_repeat = 0; + im->last_keycode = keycode; + im->last_mod = mod; + } + } + // The shortcut modifier is pressed if (smod) { int action = down ? ACTION_DOWN : ACTION_UP; diff --git a/app/src/input_manager.h b/app/src/input_manager.h index 160977b3..5c7e2b91 100644 --- a/app/src/input_manager.h +++ b/app/src/input_manager.h @@ -33,6 +33,13 @@ struct input_manager { } sdl_shortcut_mods; bool vfinger_down; + + // Tracks the number of identical consecutive shortcut key down events. + // Not to be confused with event->repeat, which counts the number of + // system-generated repeated key presses. + unsigned key_repeat; + SDL_Keycode last_keycode; + uint16_t last_mod; }; void From 50eecdab285692ca1cb58b47552af6e1dde9c615 Mon Sep 17 00:00:00 2001 From: brunoais Date: Sat, 17 Apr 2021 13:32:18 +0100 Subject: [PATCH 359/450] Add control message to expand settings panel PR #2260 Signed-off-by: Romain Vimont --- app/src/control_msg.c | 1 + app/src/control_msg.h | 1 + app/src/input_manager.c | 10 ++++++ app/tests/test_control_msg_serialize.c | 16 ++++++++++ .../com/genymobile/scrcpy/ControlMessage.java | 11 ++++--- .../scrcpy/ControlMessageReader.java | 1 + .../com/genymobile/scrcpy/Controller.java | 3 ++ .../java/com/genymobile/scrcpy/Device.java | 4 +++ .../scrcpy/wrappers/StatusBarManager.java | 31 +++++++++++++++++++ .../scrcpy/ControlMessageReaderTest.java | 16 ++++++++++ 10 files changed, 89 insertions(+), 5 deletions(-) diff --git a/app/src/control_msg.c b/app/src/control_msg.c index 7d18fb6a..8908c546 100644 --- a/app/src/control_msg.c +++ b/app/src/control_msg.c @@ -81,6 +81,7 @@ control_msg_serialize(const struct control_msg *msg, unsigned char *buf) { buf[1] = msg->set_screen_power_mode.mode; return 2; case CONTROL_MSG_TYPE_EXPAND_NOTIFICATION_PANEL: + case CONTROL_MSG_TYPE_EXPAND_SETTINGS_PANEL: case CONTROL_MSG_TYPE_COLLAPSE_PANELS: case CONTROL_MSG_TYPE_GET_CLIPBOARD: case CONTROL_MSG_TYPE_ROTATE_DEVICE: diff --git a/app/src/control_msg.h b/app/src/control_msg.h index 1e9481d9..c1099c79 100644 --- a/app/src/control_msg.h +++ b/app/src/control_msg.h @@ -27,6 +27,7 @@ enum control_msg_type { CONTROL_MSG_TYPE_INJECT_SCROLL_EVENT, CONTROL_MSG_TYPE_BACK_OR_SCREEN_ON, CONTROL_MSG_TYPE_EXPAND_NOTIFICATION_PANEL, + CONTROL_MSG_TYPE_EXPAND_SETTINGS_PANEL, CONTROL_MSG_TYPE_COLLAPSE_PANELS, CONTROL_MSG_TYPE_GET_CLIPBOARD, CONTROL_MSG_TYPE_SET_CLIPBOARD, diff --git a/app/src/input_manager.c b/app/src/input_manager.c index b6006285..0d3175e6 100644 --- a/app/src/input_manager.c +++ b/app/src/input_manager.c @@ -182,6 +182,16 @@ expand_notification_panel(struct controller *controller) { } } +static void +expand_settings_panel(struct controller *controller) { + struct control_msg msg; + msg.type = CONTROL_MSG_TYPE_EXPAND_SETTINGS_PANEL; + + if (!controller_push_msg(controller, &msg)) { + LOGW("Could not request 'expand settings panel'"); + } +} + static void collapse_panels(struct controller *controller) { struct control_msg msg; diff --git a/app/tests/test_control_msg_serialize.c b/app/tests/test_control_msg_serialize.c index d7b89929..ef9247ca 100644 --- a/app/tests/test_control_msg_serialize.c +++ b/app/tests/test_control_msg_serialize.c @@ -177,6 +177,21 @@ static void test_serialize_expand_notification_panel(void) { assert(!memcmp(buf, expected, sizeof(expected))); } +static void test_serialize_expand_settings_panel(void) { + struct control_msg msg = { + .type = CONTROL_MSG_TYPE_EXPAND_SETTINGS_PANEL, + }; + + unsigned char buf[CONTROL_MSG_MAX_SIZE]; + size_t size = control_msg_serialize(&msg, buf); + assert(size == 1); + + const unsigned char expected[] = { + CONTROL_MSG_TYPE_EXPAND_SETTINGS_PANEL, + }; + assert(!memcmp(buf, expected, sizeof(expected))); +} + static void test_serialize_collapse_panels(void) { struct control_msg msg = { .type = CONTROL_MSG_TYPE_COLLAPSE_PANELS, @@ -274,6 +289,7 @@ int main(int argc, char *argv[]) { test_serialize_inject_scroll_event(); test_serialize_back_or_screen_on(); test_serialize_expand_notification_panel(); + test_serialize_expand_settings_panel(); test_serialize_collapse_panels(); test_serialize_get_clipboard(); test_serialize_set_clipboard(); diff --git a/server/src/main/java/com/genymobile/scrcpy/ControlMessage.java b/server/src/main/java/com/genymobile/scrcpy/ControlMessage.java index 34ce1941..f8edd53c 100644 --- a/server/src/main/java/com/genymobile/scrcpy/ControlMessage.java +++ b/server/src/main/java/com/genymobile/scrcpy/ControlMessage.java @@ -11,11 +11,12 @@ public final class ControlMessage { public static final int TYPE_INJECT_SCROLL_EVENT = 3; public static final int TYPE_BACK_OR_SCREEN_ON = 4; public static final int TYPE_EXPAND_NOTIFICATION_PANEL = 5; - public static final int TYPE_COLLAPSE_PANELS = 6; - public static final int TYPE_GET_CLIPBOARD = 7; - public static final int TYPE_SET_CLIPBOARD = 8; - public static final int TYPE_SET_SCREEN_POWER_MODE = 9; - public static final int TYPE_ROTATE_DEVICE = 10; + public static final int TYPE_EXPAND_SETTINGS_PANEL = 6; + public static final int TYPE_COLLAPSE_PANELS = 7; + public static final int TYPE_GET_CLIPBOARD = 8; + public static final int TYPE_SET_CLIPBOARD = 9; + public static final int TYPE_SET_SCREEN_POWER_MODE = 10; + public static final int TYPE_ROTATE_DEVICE = 11; private int type; private String text; diff --git a/server/src/main/java/com/genymobile/scrcpy/ControlMessageReader.java b/server/src/main/java/com/genymobile/scrcpy/ControlMessageReader.java index 97f027ff..e4ab8402 100644 --- a/server/src/main/java/com/genymobile/scrcpy/ControlMessageReader.java +++ b/server/src/main/java/com/genymobile/scrcpy/ControlMessageReader.java @@ -77,6 +77,7 @@ public class ControlMessageReader { msg = parseSetScreenPowerMode(); break; case ControlMessage.TYPE_EXPAND_NOTIFICATION_PANEL: + case ControlMessage.TYPE_EXPAND_SETTINGS_PANEL: case ControlMessage.TYPE_COLLAPSE_PANELS: case ControlMessage.TYPE_GET_CLIPBOARD: case ControlMessage.TYPE_ROTATE_DEVICE: diff --git a/server/src/main/java/com/genymobile/scrcpy/Controller.java b/server/src/main/java/com/genymobile/scrcpy/Controller.java index 15c0584a..3760bbd4 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Controller.java +++ b/server/src/main/java/com/genymobile/scrcpy/Controller.java @@ -107,6 +107,9 @@ public class Controller { case ControlMessage.TYPE_EXPAND_NOTIFICATION_PANEL: Device.expandNotificationPanel(); break; + case ControlMessage.TYPE_EXPAND_SETTINGS_PANEL: + Device.expandSettingsPanel(); + break; case ControlMessage.TYPE_COLLAPSE_PANELS: Device.collapsePanels(); break; diff --git a/server/src/main/java/com/genymobile/scrcpy/Device.java b/server/src/main/java/com/genymobile/scrcpy/Device.java index 624c9fa0..a63976fd 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Device.java +++ b/server/src/main/java/com/genymobile/scrcpy/Device.java @@ -227,6 +227,10 @@ public final class Device { SERVICE_MANAGER.getStatusBarManager().expandNotificationsPanel(); } + public static void expandSettingsPanel() { + SERVICE_MANAGER.getStatusBarManager().expandSettingsPanel(); + } + public static void collapsePanels() { SERVICE_MANAGER.getStatusBarManager().collapsePanels(); } diff --git a/server/src/main/java/com/genymobile/scrcpy/wrappers/StatusBarManager.java b/server/src/main/java/com/genymobile/scrcpy/wrappers/StatusBarManager.java index 6f8941bd..5b1e5f5e 100644 --- a/server/src/main/java/com/genymobile/scrcpy/wrappers/StatusBarManager.java +++ b/server/src/main/java/com/genymobile/scrcpy/wrappers/StatusBarManager.java @@ -11,6 +11,8 @@ public class StatusBarManager { private final IInterface manager; private Method expandNotificationsPanelMethod; + private Method expandSettingsPanelMethod; + private boolean expandSettingsPanelMethodNewVersion = true; private Method collapsePanelsMethod; public StatusBarManager(IInterface manager) { @@ -24,6 +26,20 @@ public class StatusBarManager { return expandNotificationsPanelMethod; } + private Method getExpandSettingsPanel() throws NoSuchMethodException { + if (expandSettingsPanelMethod == null) { + try { + // Since Android 7: https://android.googlesource.com/platform/frameworks/base.git/+/a9927325eda025504d59bb6594fee8e240d95b01%5E%21/ + expandSettingsPanelMethod = manager.getClass().getMethod("expandSettingsPanel", String.class); + } catch (NoSuchMethodException e) { + // old version + expandSettingsPanelMethod = manager.getClass().getMethod("expandSettingsPanel"); + expandSettingsPanelMethodNewVersion = false; + } + } + return expandSettingsPanelMethod; + } + private Method getCollapsePanelsMethod() throws NoSuchMethodException { if (collapsePanelsMethod == null) { collapsePanelsMethod = manager.getClass().getMethod("collapsePanels"); @@ -40,6 +56,21 @@ public class StatusBarManager { } } + public void expandSettingsPanel() { + try { + Method method = getExpandSettingsPanel(); + if (expandSettingsPanelMethodNewVersion) { + // new version + method.invoke(manager, (Object) null); + } else { + // old version + method.invoke(manager); + } + } catch (InvocationTargetException | IllegalAccessException | NoSuchMethodException e) { + Ln.e("Could not invoke method", e); + } + } + public void collapsePanels() { try { Method method = getCollapsePanelsMethod(); diff --git a/server/src/test/java/com/genymobile/scrcpy/ControlMessageReaderTest.java b/server/src/test/java/com/genymobile/scrcpy/ControlMessageReaderTest.java index 97429026..da568486 100644 --- a/server/src/test/java/com/genymobile/scrcpy/ControlMessageReaderTest.java +++ b/server/src/test/java/com/genymobile/scrcpy/ControlMessageReaderTest.java @@ -181,6 +181,22 @@ public class ControlMessageReaderTest { Assert.assertEquals(ControlMessage.TYPE_EXPAND_NOTIFICATION_PANEL, event.getType()); } + @Test + public void testParseExpandSettingsPanelEvent() throws IOException { + ControlMessageReader reader = new ControlMessageReader(); + + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + DataOutputStream dos = new DataOutputStream(bos); + dos.writeByte(ControlMessage.TYPE_EXPAND_SETTINGS_PANEL); + + byte[] packet = bos.toByteArray(); + + reader.readFrom(new ByteArrayInputStream(packet)); + ControlMessage event = reader.next(); + + Assert.assertEquals(ControlMessage.TYPE_EXPAND_SETTINGS_PANEL, event.getType()); + } + @Test public void testParseCollapsePanelsEvent() throws IOException { ControlMessageReader reader = new ControlMessageReader(); From 6fa63cf6f86ff1f566587b0f0819d5dbf397a06e Mon Sep 17 00:00:00 2001 From: brunoais Date: Tue, 20 Apr 2021 18:31:39 +0200 Subject: [PATCH 360/450] Add keyboard shortcut to expand settings panel PR #2260 Signed-off-by: Romain Vimont --- app/src/input_manager.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/src/input_manager.c b/app/src/input_manager.c index 0d3175e6..d5b0c505 100644 --- a/app/src/input_manager.c +++ b/app/src/input_manager.c @@ -524,8 +524,10 @@ input_manager_process_key(struct input_manager *im, if (control && !repeat && down) { if (shift) { collapse_panels(controller); - } else { + } else if (im->key_repeat == 0) { expand_notification_panel(controller); + } else { + expand_settings_panel(controller); } } return; From b4ee9f27ce522710a52c1b688d1524bb1c904dff Mon Sep 17 00:00:00 2001 From: brunoais Date: Sat, 17 Apr 2021 13:56:53 +0100 Subject: [PATCH 361/450] Add mouse shortcut to expand settings panel Double-click on extra mouse button to open the settings panel (a single-click opens the notification panel). This is consistent with the keyboard shortcut MOD+n+n. PR #2264 Signed-off-by: Romain Vimont --- app/src/input_manager.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/app/src/input_manager.c b/app/src/input_manager.c index d5b0c505..94086616 100644 --- a/app/src/input_manager.c +++ b/app/src/input_manager.c @@ -693,7 +693,11 @@ input_manager_process_mouse_button(struct input_manager *im, return; } if (control && event->button == SDL_BUTTON_X2 && down) { - expand_notification_panel(im->controller); + if (event->clicks < 2) { + expand_notification_panel(im->controller); + } else { + expand_settings_panel(im->controller); + } return; } if (control && event->button == SDL_BUTTON_RIGHT) { From d7e658967780bca3631ac630bdb57fc071c2cae3 Mon Sep 17 00:00:00 2001 From: brunoais Date: Thu, 22 Apr 2021 21:57:31 +0100 Subject: [PATCH 362/450] Document 4th+5th + 2xn shortcuts PR #2260 Signed-off-by: Romain Vimont --- README.md | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 6a987a73..747fb5e3 100644 --- a/README.md +++ b/README.md @@ -698,10 +698,10 @@ _[Super] is typically the Windows or Cmd key._ | Rotate display left | MOD+ _(left)_ | Rotate display right | MOD+ _(right)_ | Resize window to 1:1 (pixel-perfect) | MOD+g - | Resize window to remove black borders | MOD+w \| _Double-click¹_ + | Resize window to remove black borders | MOD+w \| _Double-left-click¹_ | Click on `HOME` | MOD+h \| _Middle-click_ | Click on `BACK` | MOD+b \| _Right-click²_ - | Click on `APP_SWITCH` | MOD+s + | Click on `APP_SWITCH` | MOD+s \| _4th-click³_ | Click on `MENU` (unlock screen) | MOD+m | Click on `VOLUME_UP` | MOD+ _(up)_ | Click on `VOLUME_DOWN` | MOD+ _(down)_ @@ -710,18 +710,27 @@ _[Super] is typically the Windows or Cmd key._ | Turn device screen off (keep mirroring) | MOD+o | Turn device screen on | MOD+Shift+o | Rotate device screen | MOD+r - | Expand notification panel | MOD+n - | Collapse notification panel | MOD+Shift+n - | Copy to clipboard³ | MOD+c - | Cut to clipboard³ | MOD+x - | Synchronize clipboards and paste³ | MOD+v + | Expand notification panel | MOD+n \| _5th-click³_ + | Expand settings panel | MOD+n+n \| _Double-5th-click³_ + | Collapse panels | MOD+Shift+n + | Copy to clipboard⁴ | MOD+c + | Cut to clipboard⁴ | MOD+x + | Synchronize clipboards and paste⁴ | MOD+v | Inject computer clipboard text | MOD+Shift+v | Enable/disable FPS counter (on stdout) | MOD+i | Pinch-to-zoom | Ctrl+_click-and-move_ _¹Double-click on black borders to remove them._ _²Right-click turns the screen on if it was off, presses BACK otherwise._ -_³Only on Android >= 7._ +_³4th and 5th mouse buttons, if your mouse has them._ +_⁴Only on Android >= 7._ + +Shortcuts with repeated keys are executted by releasing and pressing the key a +second time. For example, to execute "Expand settings panel": + + 1. Press and keep pressing MOD. + 2. Then double-press n. + 3. Finally, release MOD. All Ctrl+_key_ shortcuts are forwarded to the device, so they are handled by the active application. From 21b590b766e1efa4ab75ed685449d1dc09ab9b20 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 11 Apr 2021 15:01:05 +0200 Subject: [PATCH 363/450] Write trailer from recorder thread The recorder thread wrote the whole content except the trailer, which was odd. --- app/src/recorder.c | 36 +++++++++++++++++++----------------- 1 file changed, 19 insertions(+), 17 deletions(-) diff --git a/app/src/recorder.c b/app/src/recorder.c index 2e3b0c28..3f5eb0d0 100644 --- a/app/src/recorder.c +++ b/app/src/recorder.c @@ -171,25 +171,8 @@ recorder_open(struct recorder *recorder, const AVCodec *input_codec) { void recorder_close(struct recorder *recorder) { - if (recorder->header_written) { - int ret = av_write_trailer(recorder->ctx); - if (ret < 0) { - LOGE("Failed to write trailer to %s", recorder->filename); - recorder->failed = true; - } - } else { - // the recorded file is empty - recorder->failed = true; - } avio_close(recorder->ctx->pb); avformat_free_context(recorder->ctx); - - if (recorder->failed) { - LOGE("Recording failed to %s", recorder->filename); - } else { - const char *format_name = recorder_get_format_name(recorder->format); - LOGI("Recording complete to %s file: %s", format_name, recorder->filename); - } } static bool @@ -317,7 +300,26 @@ run_recorder(void *data) { sc_mutex_unlock(&recorder->mutex); break; } + } + if (!recorder->failed) { + if (recorder->header_written) { + int ret = av_write_trailer(recorder->ctx); + if (ret < 0) { + LOGE("Failed to write trailer to %s", recorder->filename); + recorder->failed = true; + } + } else { + // the recorded file is empty + recorder->failed = true; + } + } + + if (recorder->failed) { + LOGE("Recording failed to %s", recorder->filename); + } else { + const char *format_name = recorder_get_format_name(recorder->format); + LOGI("Recording complete to %s file: %s", format_name, recorder->filename); } LOGD("Recorder thread ended"); From 55806e7d312f2acabd99d1a3c707f7e42fbf90e2 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 11 Apr 2021 15:01:05 +0200 Subject: [PATCH 364/450] Remove option --render-expired-frames This flag forced the decoder to wait for the previous frame to be consumed by the display. It was initially implemented as a compilation flag for testing, not intended to be exposed at runtime. But to remove ifdefs and to allow users to test this flag easily, it had finally been exposed by commit ebccb9f6cc111e8acfbe10d656cac5c1f1b744a0. In practice, it turned out to be useless: it had no practical impact, and it did not solve or mitigate any performance issues causing frame skipping. But that added some complexity to the codebase: it required an additional condition variable, and made video buffer calls possibly blocking, which in turn required code to interrupt it on exit. To prepare support for multiple sinks plugged to the decoder (display and v4l2 for example), the blocking call used for pacing the decoder output becomes unacceptable, so just remove this useless "feature". --- README.md | 12 ------------ app/scrcpy.1 | 4 ---- app/src/cli.c | 9 ++------- app/src/decoder.c | 5 ----- app/src/decoder.h | 3 --- app/src/scrcpy.c | 9 +++------ app/src/scrcpy.h | 2 -- app/src/stream.c | 7 ------- app/src/stream.h | 3 --- app/src/video_buffer.c | 38 +------------------------------------- app/src/video_buffer.h | 9 +-------- app/tests/test_cli.c | 2 -- 12 files changed, 7 insertions(+), 96 deletions(-) diff --git a/README.md b/README.md index 747fb5e3..d9aeb142 100644 --- a/README.md +++ b/README.md @@ -491,18 +491,6 @@ scrcpy -Sw ``` -#### Render expired frames - -By default, to minimize latency, _scrcpy_ always renders the last decoded frame -available, and drops any previous one. - -To force the rendering of all frames (at a cost of a possible increased -latency), use: - -```bash -scrcpy --render-expired-frames -``` - #### Show touches For presentations, it may be useful to show physical touches (on the physical diff --git a/app/scrcpy.1 b/app/scrcpy.1 index bf3bc9cd..ea9bce9b 100644 --- a/app/scrcpy.1 +++ b/app/scrcpy.1 @@ -155,10 +155,6 @@ Supported names are currently "direct3d", "opengl", "opengles2", "opengles", "me .UR https://wiki.libsdl.org/SDL_HINT_RENDER_DRIVER .UE -.TP -.B \-\-render\-expired\-frames -By default, to minimize latency, scrcpy always renders the last available decoded frame, and drops any previous ones. This flag forces to render all frames, at a cost of a possible increased latency. - .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. diff --git a/app/src/cli.c b/app/src/cli.c index 042a1f4c..ec3c1294 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -143,12 +143,6 @@ scrcpy_print_usage(const char *arg0) { " \"opengles2\", \"opengles\", \"metal\" and \"software\".\n" " \n" "\n" - " --render-expired-frames\n" - " By default, to minimize latency, scrcpy always renders the\n" - " last available decoded frame, and drops any previous ones.\n" - " This flag forces to render all frames, at a cost of a\n" - " possible increased latency.\n" - "\n" " --rotation value\n" " Set the initial display rotation.\n" " Possibles values are 0, 1, 2 and 3. Each increment adds a 90\n" @@ -816,7 +810,8 @@ scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) { opts->stay_awake = true; break; case OPT_RENDER_EXPIRED_FRAMES: - opts->render_expired_frames = true; + LOGW("Option --render-expired-frames has been removed. This " + "flag has been ignored."); break; case OPT_WINDOW_TITLE: opts->window_title = optarg; diff --git a/app/src/decoder.c b/app/src/decoder.c index a13cf75e..b7101194 100644 --- a/app/src/decoder.c +++ b/app/src/decoder.c @@ -69,8 +69,3 @@ decoder_push(struct decoder *decoder, const AVPacket *packet) { #endif return true; } - -void -decoder_interrupt(struct decoder *decoder) { - video_buffer_interrupt(decoder->video_buffer); -} diff --git a/app/src/decoder.h b/app/src/decoder.h index bbd7a9a7..ba06583e 100644 --- a/app/src/decoder.h +++ b/app/src/decoder.h @@ -26,7 +26,4 @@ decoder_close(struct decoder *decoder); bool decoder_push(struct decoder *decoder, const AVPacket *packet); -void -decoder_interrupt(struct decoder *decoder); - #endif diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index 388bb73d..2f8abd64 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -305,7 +305,7 @@ scrcpy(const struct scrcpy_options *options) { } fps_counter_initialized = true; - if (!video_buffer_init(&video_buffer, options->render_expired_frames)) { + if (!video_buffer_init(&video_buffer)) { goto end; } video_buffer_initialized = true; @@ -398,11 +398,8 @@ scrcpy(const struct scrcpy_options *options) { LOGD("quit..."); end: - // stop stream and controller so that they don't continue once their socket - // is shutdown - if (stream_started) { - stream_stop(&stream); - } + // The stream is not stopped explicitly, because it will stop by itself on + // end-of-stream if (controller_started) { controller_stop(&controller); } diff --git a/app/src/scrcpy.h b/app/src/scrcpy.h index 8ee70f60..f91cb6b8 100644 --- a/app/src/scrcpy.h +++ b/app/src/scrcpy.h @@ -72,7 +72,6 @@ struct scrcpy_options { bool control; bool display; bool turn_screen_off; - bool render_expired_frames; bool prefer_text; bool window_borderless; bool mipmaps; @@ -120,7 +119,6 @@ struct scrcpy_options { .control = true, \ .display = true, \ .turn_screen_off = false, \ - .render_expired_frames = false, \ .prefer_text = false, \ .window_borderless = false, \ .mipmaps = true, \ diff --git a/app/src/stream.c b/app/src/stream.c index ba72f164..e0a223be 100644 --- a/app/src/stream.c +++ b/app/src/stream.c @@ -285,13 +285,6 @@ stream_start(struct stream *stream) { return true; } -void -stream_stop(struct stream *stream) { - if (stream->decoder) { - decoder_interrupt(stream->decoder); - } -} - void stream_join(struct stream *stream) { sc_thread_join(&stream->thread, NULL); diff --git a/app/src/stream.h b/app/src/stream.h index d4a9bd4a..421c1bf0 100644 --- a/app/src/stream.h +++ b/app/src/stream.h @@ -31,9 +31,6 @@ stream_init(struct stream *stream, socket_t socket, bool stream_start(struct stream *stream); -void -stream_stop(struct stream *stream); - void stream_join(struct stream *stream); diff --git a/app/src/video_buffer.c b/app/src/video_buffer.c index 94619840..d4954afc 100644 --- a/app/src/video_buffer.c +++ b/app/src/video_buffer.c @@ -7,7 +7,7 @@ #include "util/log.h" bool -video_buffer_init(struct video_buffer *vb, bool wait_consumer) { +video_buffer_init(struct video_buffer *vb) { vb->producer_frame = av_frame_alloc(); if (!vb->producer_frame) { goto error_0; @@ -28,18 +28,6 @@ video_buffer_init(struct video_buffer *vb, bool wait_consumer) { goto error_3; } - vb->wait_consumer = wait_consumer; - if (wait_consumer) { - ok = sc_cond_init(&vb->pending_frame_consumed_cond); - if (!ok) { - sc_mutex_destroy(&vb->mutex); - goto error_2; - } - // interrupted is not used if wait_consumer is disabled since offering - // a frame will never block - vb->interrupted = false; - } - // there is initially no frame, so consider it has already been consumed vb->pending_frame_consumed = true; @@ -61,9 +49,6 @@ error_0: void video_buffer_destroy(struct video_buffer *vb) { - if (vb->wait_consumer) { - sc_cond_destroy(&vb->pending_frame_consumed_cond); - } sc_mutex_destroy(&vb->mutex); av_frame_free(&vb->consumer_frame); av_frame_free(&vb->pending_frame); @@ -93,12 +78,6 @@ video_buffer_producer_offer_frame(struct video_buffer *vb) { assert(vb->cbs); sc_mutex_lock(&vb->mutex); - if (vb->wait_consumer) { - // wait for the current (expired) frame to be consumed - while (!vb->pending_frame_consumed && !vb->interrupted) { - sc_cond_wait(&vb->pending_frame_consumed_cond, &vb->mutex); - } - } av_frame_unref(vb->pending_frame); swap_frames(&vb->producer_frame, &vb->pending_frame); @@ -125,23 +104,8 @@ video_buffer_consumer_take_frame(struct video_buffer *vb) { swap_frames(&vb->consumer_frame, &vb->pending_frame); av_frame_unref(vb->pending_frame); - if (vb->wait_consumer) { - // unblock video_buffer_offer_decoded_frame() - sc_cond_signal(&vb->pending_frame_consumed_cond); - } sc_mutex_unlock(&vb->mutex); // consumer_frame is only written from this thread, no need to lock return vb->consumer_frame; } - -void -video_buffer_interrupt(struct video_buffer *vb) { - if (vb->wait_consumer) { - sc_mutex_lock(&vb->mutex); - vb->interrupted = true; - sc_mutex_unlock(&vb->mutex); - // wake up blocking wait - sc_cond_signal(&vb->pending_frame_consumed_cond); - } -} diff --git a/app/src/video_buffer.h b/app/src/video_buffer.h index 4d11e3ab..48e57ff4 100644 --- a/app/src/video_buffer.h +++ b/app/src/video_buffer.h @@ -34,10 +34,7 @@ struct video_buffer { AVFrame *consumer_frame; sc_mutex mutex; - bool wait_consumer; // never overwrite a pending frame if it is not consumed - bool interrupted; - sc_cond pending_frame_consumed_cond; bool pending_frame_consumed; const struct video_buffer_callbacks *cbs; @@ -56,7 +53,7 @@ struct video_buffer_callbacks { }; bool -video_buffer_init(struct video_buffer *vb, bool wait_consumer); +video_buffer_init(struct video_buffer *vb); void video_buffer_destroy(struct video_buffer *vb); @@ -75,8 +72,4 @@ video_buffer_producer_offer_frame(struct video_buffer *vb); const AVFrame * video_buffer_consumer_take_frame(struct video_buffer *vb); -// wake up and avoid any blocking call -void -video_buffer_interrupt(struct video_buffer *vb); - #endif diff --git a/app/tests/test_cli.c b/app/tests/test_cli.c index cd222d63..3fa9b3d7 100644 --- a/app/tests/test_cli.c +++ b/app/tests/test_cli.c @@ -58,7 +58,6 @@ static void test_options(void) { "--push-target", "/sdcard/Movies", "--record", "file", "--record-format", "mkv", - "--render-expired-frames", "--serial", "0123456789abcdef", "--show-touches", "--turn-screen-off", @@ -87,7 +86,6 @@ static void test_options(void) { assert(!strcmp(opts->push_target, "/sdcard/Movies")); assert(!strcmp(opts->record_filename, "file")); assert(opts->record_format == SC_RECORD_FORMAT_MKV); - assert(opts->render_expired_frames); assert(!strcmp(opts->serial, "0123456789abcdef")); assert(opts->show_touches); assert(opts->turn_screen_off); From de9b79ec2dd73e5442c6bf0161669bcd8ca7d5be Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 11 Apr 2021 15:01:05 +0200 Subject: [PATCH 365/450] Remove compat with old FFmpeg decoding API The new API has been introduced in 2016 in libavcodec 57.xx, it's very old. This will avoid to maintain two code paths for decoding. --- app/src/compat.h | 10 ---------- app/src/decoder.c | 17 ----------------- 2 files changed, 27 deletions(-) diff --git a/app/src/compat.h b/app/src/compat.h index 9a84a4c1..f3e7bd7a 100644 --- a/app/src/compat.h +++ b/app/src/compat.h @@ -8,7 +8,6 @@ # define _DARWIN_C_SOURCE #endif -#include #include #include @@ -33,15 +32,6 @@ # define SCRCPY_LAVF_REQUIRES_REGISTER_ALL #endif -// In ffmpeg/doc/APIchanges: -// 2016-04-21 - 7fc329e - lavc 57.37.100 - avcodec.h -// Add a new audio/video encoding and decoding API with decoupled input -// and output -- avcodec_send_packet(), avcodec_receive_frame(), -// avcodec_send_frame() and avcodec_receive_packet(). -#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(57, 37, 100) -# define SCRCPY_LAVF_HAS_NEW_ENCODING_DECODING_API -#endif - #if SDL_VERSION_ATLEAST(2, 0, 5) // # define SCRCPY_SDL_HAS_HINT_MOUSE_FOCUS_CLICKTHROUGH diff --git a/app/src/decoder.c b/app/src/decoder.c index b7101194..f05303a3 100644 --- a/app/src/decoder.c +++ b/app/src/decoder.c @@ -36,9 +36,6 @@ decoder_close(struct decoder *decoder) { bool decoder_push(struct decoder *decoder, const AVPacket *packet) { -// the new decoding/encoding API has been introduced by: -// -#ifdef SCRCPY_LAVF_HAS_NEW_ENCODING_DECODING_API int ret; if ((ret = avcodec_send_packet(decoder->codec_ctx, packet)) < 0) { LOGE("Could not send video packet: %d", ret); @@ -53,19 +50,5 @@ decoder_push(struct decoder *decoder, const AVPacket *packet) { LOGE("Could not receive video frame: %d", ret); return false; } -#else - int got_picture; - int len = avcodec_decode_video2(decoder->codec_ctx, - decoder->video_buffer->producer_frame, - &got_picture, - packet); - if (len < 0) { - LOGE("Could not decode video packet: %d", len); - return false; - } - if (got_picture) { - video_buffer_producer_offer_frame(decoder->video_buffer); - } -#endif return true; } From 5d9e96dc4eaa41b185c41e8a6df6191762764b1c Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 11 Apr 2021 15:01:05 +0200 Subject: [PATCH 366/450] Remove compat with old FFmpeg codec params API The new API has been introduced in 2016 in libavformat 57.xx, it's very old. This will avoid to maintain two code paths for codec parameters. --- app/src/compat.h | 10 ---------- app/src/recorder.c | 13 ------------- 2 files changed, 23 deletions(-) diff --git a/app/src/compat.h b/app/src/compat.h index f3e7bd7a..9d9a7884 100644 --- a/app/src/compat.h +++ b/app/src/compat.h @@ -11,16 +11,6 @@ #include #include -// In ffmpeg/doc/APIchanges: -// 2016-04-11 - 6f69f7a / 9200514 - lavf 57.33.100 / 57.5.0 - avformat.h -// Add AVStream.codecpar, deprecate AVStream.codec. -#if (LIBAVFORMAT_VERSION_MICRO >= 100 /* FFmpeg */ && \ - LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(57, 33, 100)) \ - || (LIBAVFORMAT_VERSION_MICRO < 100 && /* Libav */ \ - LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(57, 5, 0)) -# define SCRCPY_LAVF_HAS_NEW_CODEC_PARAMS_API -#endif - // In ffmpeg/doc/APIchanges: // 2018-02-06 - 0694d87024 - lavf 58.9.100 - avformat.h // Deprecate use of av_register_input_format(), av_register_output_format(), diff --git a/app/src/recorder.c b/app/src/recorder.c index 3f5eb0d0..c0d7aed1 100644 --- a/app/src/recorder.c +++ b/app/src/recorder.c @@ -141,19 +141,11 @@ recorder_open(struct recorder *recorder, const AVCodec *input_codec) { return false; } -#ifdef SCRCPY_LAVF_HAS_NEW_CODEC_PARAMS_API ostream->codecpar->codec_type = AVMEDIA_TYPE_VIDEO; ostream->codecpar->codec_id = input_codec->id; ostream->codecpar->format = AV_PIX_FMT_YUV420P; ostream->codecpar->width = recorder->declared_frame_size.width; ostream->codecpar->height = recorder->declared_frame_size.height; -#else - ostream->codec->codec_type = AVMEDIA_TYPE_VIDEO; - ostream->codec->codec_id = input_codec->id; - ostream->codec->pix_fmt = AV_PIX_FMT_YUV420P; - ostream->codec->width = recorder->declared_frame_size.width; - ostream->codec->height = recorder->declared_frame_size.height; -#endif int ret = avio_open(&recorder->ctx->pb, recorder->filename, AVIO_FLAG_WRITE); @@ -188,13 +180,8 @@ recorder_write_header(struct recorder *recorder, const AVPacket *packet) { // copy the first packet to the extra data memcpy(extradata, packet->data, packet->size); -#ifdef SCRCPY_LAVF_HAS_NEW_CODEC_PARAMS_API ostream->codecpar->extradata = extradata; ostream->codecpar->extradata_size = packet->size; -#else - ostream->codec->extradata = extradata; - ostream->codec->extradata_size = packet->size; -#endif int ret = avformat_write_header(recorder->ctx, NULL); if (ret < 0) { From 2ddf760c091ecaed6d6dcb4131dd2c468453a0da Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 11 Apr 2021 15:01:05 +0200 Subject: [PATCH 367/450] Make video_buffer more generic The video buffer took ownership of the producer frame (so that it could swap frames quickly). In order to support multiple sinks plugged to the decoder, the decoded frame must not be consumed by the display video buffer. Therefore, move the producer and consumer frames out of the video buffer, and use FFmpeg AVFrame refcounting to share ownership while avoiding copies. --- app/src/decoder.c | 17 ++++++++++-- app/src/decoder.h | 1 + app/src/screen.c | 14 +++++++++- app/src/screen.h | 2 ++ app/src/video_buffer.c | 62 ++++++++++++++++++++---------------------- app/src/video_buffer.h | 41 ++++++++++++---------------- 6 files changed, 77 insertions(+), 60 deletions(-) diff --git a/app/src/decoder.c b/app/src/decoder.c index f05303a3..247b459c 100644 --- a/app/src/decoder.c +++ b/app/src/decoder.c @@ -25,11 +25,20 @@ decoder_open(struct decoder *decoder, const AVCodec *codec) { return false; } + decoder->frame = av_frame_alloc(); + if (!decoder->frame) { + LOGE("Could not create decoder frame"); + avcodec_close(decoder->codec_ctx); + avcodec_free_context(&decoder->codec_ctx); + return false; + } + return true; } void decoder_close(struct decoder *decoder) { + av_frame_free(&decoder->frame); avcodec_close(decoder->codec_ctx); avcodec_free_context(&decoder->codec_ctx); } @@ -41,11 +50,13 @@ decoder_push(struct decoder *decoder, const AVPacket *packet) { LOGE("Could not send video packet: %d", ret); return false; } - ret = avcodec_receive_frame(decoder->codec_ctx, - decoder->video_buffer->producer_frame); + ret = avcodec_receive_frame(decoder->codec_ctx, decoder->frame); if (!ret) { // a frame was received - video_buffer_producer_offer_frame(decoder->video_buffer); + bool ok = video_buffer_push(decoder->video_buffer, decoder->frame); + // A frame lost should not make the whole pipeline fail. The error, if + // any, is already logged. + (void) ok; } else if (ret != AVERROR(EAGAIN)) { LOGE("Could not receive video frame: %d", ret); return false; diff --git a/app/src/decoder.h b/app/src/decoder.h index ba06583e..50dd7fe0 100644 --- a/app/src/decoder.h +++ b/app/src/decoder.h @@ -12,6 +12,7 @@ struct decoder { struct video_buffer *video_buffer; AVCodecContext *codec_ctx; + AVFrame *frame; }; void diff --git a/app/src/screen.c b/app/src/screen.c index 934e418f..de734554 100644 --- a/app/src/screen.c +++ b/app/src/screen.c @@ -378,6 +378,15 @@ screen_init(struct screen *screen, struct video_buffer *vb, return false; } + screen->frame = av_frame_alloc(); + if (!screen->frame) { + LOGC("Could not create screen frame"); + SDL_DestroyTexture(screen->texture); + SDL_DestroyRenderer(screen->renderer); + SDL_DestroyWindow(screen->window); + return false; + } + // Reset the window size to trigger a SIZE_CHANGED event, to workaround // HiDPI issues with some SDL renderers when several displays having // different HiDPI scaling are connected @@ -403,6 +412,7 @@ screen_show_window(struct screen *screen) { void screen_destroy(struct screen *screen) { + av_frame_free(&screen->frame); SDL_DestroyTexture(screen->texture); SDL_DestroyRenderer(screen->renderer); SDL_DestroyWindow(screen->window); @@ -510,7 +520,9 @@ update_texture(struct screen *screen, const AVFrame *frame) { static bool screen_update_frame(struct screen *screen) { - const AVFrame *frame = video_buffer_consumer_take_frame(screen->vb); + av_frame_unref(screen->frame); + video_buffer_consume(screen->vb, screen->frame); + AVFrame *frame = screen->frame; fps_counter_add_rendered_frame(screen->fps_counter); diff --git a/app/src/screen.h b/app/src/screen.h index dca65d41..cd849779 100644 --- a/app/src/screen.h +++ b/app/src/screen.h @@ -36,6 +36,8 @@ struct screen { bool fullscreen; bool maximized; bool mipmaps; + + AVFrame *frame; }; struct screen_params { diff --git a/app/src/video_buffer.c b/app/src/video_buffer.c index d4954afc..18a180fa 100644 --- a/app/src/video_buffer.c +++ b/app/src/video_buffer.c @@ -8,24 +8,22 @@ bool video_buffer_init(struct video_buffer *vb) { - vb->producer_frame = av_frame_alloc(); - if (!vb->producer_frame) { - goto error_0; - } - vb->pending_frame = av_frame_alloc(); if (!vb->pending_frame) { - goto error_1; + return false; } - vb->consumer_frame = av_frame_alloc(); - if (!vb->consumer_frame) { - goto error_2; + vb->tmp_frame = av_frame_alloc(); + if (!vb->tmp_frame) { + av_frame_free(&vb->pending_frame); + return false; } bool ok = sc_mutex_init(&vb->mutex); if (!ok) { - goto error_3; + av_frame_free(&vb->pending_frame); + av_frame_free(&vb->tmp_frame); + return false; } // there is initially no frame, so consider it has already been consumed @@ -36,23 +34,13 @@ video_buffer_init(struct video_buffer *vb) { vb->cbs = NULL; return true; - -error_3: - av_frame_free(&vb->consumer_frame); -error_2: - av_frame_free(&vb->pending_frame); -error_1: - av_frame_free(&vb->producer_frame); -error_0: - return false; } void video_buffer_destroy(struct video_buffer *vb) { sc_mutex_destroy(&vb->mutex); - av_frame_free(&vb->consumer_frame); av_frame_free(&vb->pending_frame); - av_frame_free(&vb->producer_frame); + av_frame_free(&vb->tmp_frame); } static inline void @@ -73,14 +61,24 @@ video_buffer_set_consumer_callbacks(struct video_buffer *vb, vb->cbs_userdata = cbs_userdata; } -void -video_buffer_producer_offer_frame(struct video_buffer *vb) { +bool +video_buffer_push(struct video_buffer *vb, const AVFrame *frame) { assert(vb->cbs); sc_mutex_lock(&vb->mutex); - av_frame_unref(vb->pending_frame); - swap_frames(&vb->producer_frame, &vb->pending_frame); + // Use a temporary frame to preserve pending_frame in case of error. + // tmp_frame is an empty frame, no need to call av_frame_unref() beforehand. + int r = av_frame_ref(vb->tmp_frame, frame); + if (r) { + LOGE("Could not ref frame: %d", r); + return false; + } + + // Now that av_frame_ref() succeeded, we can replace the previous + // pending_frame + swap_frames(&vb->pending_frame, &vb->tmp_frame); + av_frame_unref(vb->tmp_frame); bool skipped = !vb->pending_frame_consumed; vb->pending_frame_consumed = false; @@ -93,19 +91,19 @@ video_buffer_producer_offer_frame(struct video_buffer *vb) { } else { vb->cbs->on_frame_available(vb, vb->cbs_userdata); } + + return true; } -const AVFrame * -video_buffer_consumer_take_frame(struct video_buffer *vb) { +void +video_buffer_consume(struct video_buffer *vb, AVFrame *dst) { sc_mutex_lock(&vb->mutex); assert(!vb->pending_frame_consumed); vb->pending_frame_consumed = true; - swap_frames(&vb->consumer_frame, &vb->pending_frame); - av_frame_unref(vb->pending_frame); + av_frame_move_ref(dst, vb->pending_frame); + // av_frame_move_ref() resets its source frame, so no need to call + // av_frame_unref() sc_mutex_unlock(&vb->mutex); - - // consumer_frame is only written from this thread, no need to lock - return vb->consumer_frame; } diff --git a/app/src/video_buffer.h b/app/src/video_buffer.h index 48e57ff4..cdecb259 100644 --- a/app/src/video_buffer.h +++ b/app/src/video_buffer.h @@ -12,26 +12,23 @@ typedef struct AVFrame AVFrame; /** - * There are 3 frames in memory: - * - one frame is held by the producer (producer_frame) - * - one frame is held by the consumer (consumer_frame) - * - one frame is shared between the producer and the consumer (pending_frame) + * A video buffer holds 1 pending frame, which is the last frame received from + * the producer (typically, the decoder). * - * The producer generates a frame into the producer_frame (it may takes time). + * If a pending frame has not been consumed when the producer pushes a new + * frame, then it is lost. The intent is to always provide access to the very + * last frame to minimize latency. * - * Once the frame is produced, it calls video_buffer_producer_offer_frame(), - * which swaps the producer and pending frames. - * - * When the consumer is notified that a new frame is available, it calls - * video_buffer_consumer_take_frame() to retrieve it, which swaps the pending - * and consumer frames. The frame is valid until the next call, without - * blocking the producer. + * The producer and the consumer typically do not live in the same thread. + * That's the reason why the callback on_frame_available() does not provide the + * frame as parameter: the consumer might post an event to its own thread to + * retrieve the pending frame from there, and that frame may have changed since + * the callback if producer pushed a new one in between. */ struct video_buffer { - AVFrame *producer_frame; AVFrame *pending_frame; - AVFrame *consumer_frame; + AVFrame *tmp_frame; // To preserve the pending frame on error sc_mutex mutex; @@ -42,12 +39,11 @@ struct video_buffer { }; struct video_buffer_callbacks { - // Called when a new frame can be consumed by - // video_buffer_consumer_take_frame(vb) + // Called when a new frame can be consumed. // This callback is mandatory (it must not be NULL). void (*on_frame_available)(struct video_buffer *vb, void *userdata); - // Called when a pending frame has been overwritten by the producer + // Called when a pending frame has been overwritten by the producer. // This callback is optional (it may be NULL). void (*on_frame_skipped)(struct video_buffer *vb, void *userdata); }; @@ -63,13 +59,10 @@ video_buffer_set_consumer_callbacks(struct video_buffer *vb, const struct video_buffer_callbacks *cbs, void *cbs_userdata); -// set the producer frame as ready for consuming -void -video_buffer_producer_offer_frame(struct video_buffer *vb); +bool +video_buffer_push(struct video_buffer *vb, const AVFrame *frame); -// mark the consumer frame as consumed and return it -// the frame is valid until the next call to this function -const AVFrame * -video_buffer_consumer_take_frame(struct video_buffer *vb); +void +video_buffer_consume(struct video_buffer *vb, AVFrame *dst); #endif From 08f1fd46c8f2ff70b1cc4464ec9dea5c6fd898c9 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 11 Apr 2021 15:01:05 +0200 Subject: [PATCH 368/450] Add container_of() macro This will allow to get the parent of an embedded struct. --- app/src/common.h | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/src/common.h b/app/src/common.h index 27c8d2fb..accbc615 100644 --- a/app/src/common.h +++ b/app/src/common.h @@ -8,4 +8,7 @@ #define MIN(X,Y) (X) < (Y) ? (X) : (Y) #define MAX(X,Y) (X) > (Y) ? (X) : (Y) +#define container_of(ptr, type, member) \ + ((type *) (((char *) (ptr)) - offsetof(type, member))) + #endif From 1b072a24c4e58361d353a0bfb5b5d63174e47524 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 11 Apr 2021 15:01:05 +0200 Subject: [PATCH 369/450] Add packet sink trait This trait will allow to abstract the concrete sink types from the packet producer (stream.c). --- app/src/trait/packet_sink.h | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 app/src/trait/packet_sink.h diff --git a/app/src/trait/packet_sink.h b/app/src/trait/packet_sink.h new file mode 100644 index 00000000..fe9c137d --- /dev/null +++ b/app/src/trait/packet_sink.h @@ -0,0 +1,27 @@ +#ifndef SC_PACKET_SINK +#define SC_PACKET_SINK + +#include "common.h" + +#include +#include + +typedef struct AVCodec AVCodec; +typedef struct AVPacket AVPacket; + +/** + * Packet sink trait. + * + * Component able to receive AVPackets should implement this trait. + */ +struct sc_packet_sink { + const struct sc_packet_sink_ops *ops; +}; + +struct sc_packet_sink_ops { + bool (*open)(struct sc_packet_sink *sink, const AVCodec *codec); + void (*close)(struct sc_packet_sink *sink); + bool (*push)(struct sc_packet_sink *sink, const AVPacket *packet); +}; + +#endif From a974483c1560042085a25412f8cf19329505447c Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 11 Apr 2021 15:01:05 +0200 Subject: [PATCH 370/450] Reorder recorder functions This will make further commits more readable. --- app/src/recorder.c | 202 ++++++++++++++++++++++----------------------- 1 file changed, 101 insertions(+), 101 deletions(-) diff --git a/app/src/recorder.c b/app/src/recorder.c index c0d7aed1..c24393de 100644 --- a/app/src/recorder.c +++ b/app/src/recorder.c @@ -57,50 +57,6 @@ recorder_queue_clear(struct recorder_queue *queue) { } } -bool -recorder_init(struct recorder *recorder, - const char *filename, - enum sc_record_format format, - struct size declared_frame_size) { - recorder->filename = strdup(filename); - if (!recorder->filename) { - LOGE("Could not strdup filename"); - return false; - } - - bool ok = sc_mutex_init(&recorder->mutex); - if (!ok) { - LOGC("Could not create mutex"); - free(recorder->filename); - return false; - } - - ok = sc_cond_init(&recorder->queue_cond); - if (!ok) { - LOGC("Could not create cond"); - sc_mutex_destroy(&recorder->mutex); - free(recorder->filename); - return false; - } - - queue_init(&recorder->queue); - recorder->stopped = false; - recorder->failed = false; - recorder->format = format; - recorder->declared_frame_size = declared_frame_size; - recorder->header_written = false; - recorder->previous = NULL; - - return true; -} - -void -recorder_destroy(struct recorder *recorder) { - sc_cond_destroy(&recorder->queue_cond); - sc_mutex_destroy(&recorder->mutex); - free(recorder->filename); -} - static const char * recorder_get_format_name(enum sc_record_format format) { switch (format) { @@ -110,63 +66,6 @@ recorder_get_format_name(enum sc_record_format format) { } } -bool -recorder_open(struct recorder *recorder, const AVCodec *input_codec) { - const char *format_name = recorder_get_format_name(recorder->format); - assert(format_name); - const AVOutputFormat *format = find_muxer(format_name); - if (!format) { - LOGE("Could not find muxer"); - return false; - } - - recorder->ctx = avformat_alloc_context(); - if (!recorder->ctx) { - LOGE("Could not allocate output context"); - return false; - } - - // contrary to the deprecated API (av_oformat_next()), av_muxer_iterate() - // returns (on purpose) a pointer-to-const, but AVFormatContext.oformat - // still expects a pointer-to-non-const (it has not be updated accordingly) - // - recorder->ctx->oformat = (AVOutputFormat *) format; - - av_dict_set(&recorder->ctx->metadata, "comment", - "Recorded by scrcpy " SCRCPY_VERSION, 0); - - AVStream *ostream = avformat_new_stream(recorder->ctx, input_codec); - if (!ostream) { - avformat_free_context(recorder->ctx); - return false; - } - - ostream->codecpar->codec_type = AVMEDIA_TYPE_VIDEO; - ostream->codecpar->codec_id = input_codec->id; - ostream->codecpar->format = AV_PIX_FMT_YUV420P; - ostream->codecpar->width = recorder->declared_frame_size.width; - ostream->codecpar->height = recorder->declared_frame_size.height; - - int ret = avio_open(&recorder->ctx->pb, recorder->filename, - AVIO_FLAG_WRITE); - if (ret < 0) { - LOGE("Failed to open output file: %s", recorder->filename); - // ostream will be cleaned up during context cleaning - avformat_free_context(recorder->ctx); - return false; - } - - LOGI("Recording started to %s file: %s", format_name, recorder->filename); - - return true; -} - -void -recorder_close(struct recorder *recorder) { - avio_close(recorder->ctx->pb); - avformat_free_context(recorder->ctx); -} - static bool recorder_write_header(struct recorder *recorder, const AVPacket *packet) { AVStream *ostream = recorder->ctx->streams[0]; @@ -314,6 +213,63 @@ run_recorder(void *data) { return 0; } +bool +recorder_open(struct recorder *recorder, const AVCodec *input_codec) { + const char *format_name = recorder_get_format_name(recorder->format); + assert(format_name); + const AVOutputFormat *format = find_muxer(format_name); + if (!format) { + LOGE("Could not find muxer"); + return false; + } + + recorder->ctx = avformat_alloc_context(); + if (!recorder->ctx) { + LOGE("Could not allocate output context"); + return false; + } + + // contrary to the deprecated API (av_oformat_next()), av_muxer_iterate() + // returns (on purpose) a pointer-to-const, but AVFormatContext.oformat + // still expects a pointer-to-non-const (it has not be updated accordingly) + // + recorder->ctx->oformat = (AVOutputFormat *) format; + + av_dict_set(&recorder->ctx->metadata, "comment", + "Recorded by scrcpy " SCRCPY_VERSION, 0); + + AVStream *ostream = avformat_new_stream(recorder->ctx, input_codec); + if (!ostream) { + avformat_free_context(recorder->ctx); + return false; + } + + ostream->codecpar->codec_type = AVMEDIA_TYPE_VIDEO; + ostream->codecpar->codec_id = input_codec->id; + ostream->codecpar->format = AV_PIX_FMT_YUV420P; + ostream->codecpar->width = recorder->declared_frame_size.width; + ostream->codecpar->height = recorder->declared_frame_size.height; + + int ret = avio_open(&recorder->ctx->pb, recorder->filename, + AVIO_FLAG_WRITE); + if (ret < 0) { + LOGE("Failed to open output file: %s", recorder->filename); + // ostream will be cleaned up during context cleaning + avformat_free_context(recorder->ctx); + return false; + } + + LOGI("Recording started to %s file: %s", format_name, recorder->filename); + + return true; +} + +void +recorder_close(struct recorder *recorder) { + avio_close(recorder->ctx->pb); + avformat_free_context(recorder->ctx); +} + bool recorder_start(struct recorder *recorder) { LOGD("Starting recorder thread"); @@ -365,3 +321,47 @@ recorder_push(struct recorder *recorder, const AVPacket *packet) { sc_mutex_unlock(&recorder->mutex); return true; } + +bool +recorder_init(struct recorder *recorder, + const char *filename, + enum sc_record_format format, + struct size declared_frame_size) { + recorder->filename = strdup(filename); + if (!recorder->filename) { + LOGE("Could not strdup filename"); + return false; + } + + bool ok = sc_mutex_init(&recorder->mutex); + if (!ok) { + LOGC("Could not create mutex"); + free(recorder->filename); + return false; + } + + ok = sc_cond_init(&recorder->queue_cond); + if (!ok) { + LOGC("Could not create cond"); + sc_mutex_destroy(&recorder->mutex); + free(recorder->filename); + return false; + } + + queue_init(&recorder->queue); + recorder->stopped = false; + recorder->failed = false; + recorder->format = format; + recorder->declared_frame_size = declared_frame_size; + recorder->header_written = false; + recorder->previous = NULL; + + return true; +} + +void +recorder_destroy(struct recorder *recorder) { + sc_cond_destroy(&recorder->queue_cond); + sc_mutex_destroy(&recorder->mutex); + free(recorder->filename); +} From fe8de893ca58f3cce53819bc8409fc33d416387c Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 11 Apr 2021 15:01:05 +0200 Subject: [PATCH 371/450] Privatize recorder threading The fact that the recorder uses a separate thread is an internal detail, so the functions _start(), _stop() and _join() should not be exposed. Instead, start the thread on _open() and _stop()+_join() on close(). This paves the way to expose the recorder as a packet sink trait. --- app/src/recorder.c | 36 +++++++++++++----------------------- app/src/recorder.h | 11 +---------- app/src/stream.c | 13 +------------ 3 files changed, 15 insertions(+), 45 deletions(-) diff --git a/app/src/recorder.c b/app/src/recorder.c index c24393de..387d8f79 100644 --- a/app/src/recorder.c +++ b/app/src/recorder.c @@ -259,6 +259,16 @@ recorder_open(struct recorder *recorder, const AVCodec *input_codec) { return false; } + LOGD("Starting recorder thread"); + bool ok = sc_thread_create(&recorder->thread, run_recorder, "recorder", + recorder); + if (!ok) { + LOGC("Could not start recorder thread"); + avio_close(recorder->ctx->pb); + avformat_free_context(recorder->ctx); + return false; + } + LOGI("Recording started to %s file: %s", format_name, recorder->filename); return true; @@ -266,35 +276,15 @@ recorder_open(struct recorder *recorder, const AVCodec *input_codec) { void recorder_close(struct recorder *recorder) { - avio_close(recorder->ctx->pb); - avformat_free_context(recorder->ctx); -} - -bool -recorder_start(struct recorder *recorder) { - LOGD("Starting recorder thread"); - - bool ok = sc_thread_create(&recorder->thread, run_recorder, "recorder", - recorder); - if (!ok) { - LOGC("Could not start recorder thread"); - return false; - } - - return true; -} - -void -recorder_stop(struct recorder *recorder) { sc_mutex_lock(&recorder->mutex); recorder->stopped = true; sc_cond_signal(&recorder->queue_cond); sc_mutex_unlock(&recorder->mutex); -} -void -recorder_join(struct recorder *recorder) { sc_thread_join(&recorder->thread, NULL); + + avio_close(recorder->ctx->pb); + avformat_free_context(recorder->ctx); } bool diff --git a/app/src/recorder.h b/app/src/recorder.h index be2b2dff..97e499bc 100644 --- a/app/src/recorder.h +++ b/app/src/recorder.h @@ -28,7 +28,7 @@ struct recorder { sc_thread thread; sc_mutex mutex; sc_cond queue_cond; - bool stopped; // set on recorder_stop() by the stream reader + bool stopped; // set on recorder_close() bool failed; // set on packet write failure struct recorder_queue queue; @@ -52,15 +52,6 @@ recorder_open(struct recorder *recorder, const AVCodec *input_codec); void recorder_close(struct recorder *recorder); -bool -recorder_start(struct recorder *recorder); - -void -recorder_stop(struct recorder *recorder); - -void -recorder_join(struct recorder *recorder); - bool recorder_push(struct recorder *recorder, const AVPacket *packet); diff --git a/app/src/stream.c b/app/src/stream.c index e0a223be..787e9515 100644 --- a/app/src/stream.c +++ b/app/src/stream.c @@ -203,17 +203,12 @@ run_stream(void *data) { LOGE("Could not open recorder"); goto finally_close_decoder; } - - if (!recorder_start(stream->recorder)) { - LOGE("Could not start recorder"); - goto finally_close_recorder; - } } stream->parser = av_parser_init(AV_CODEC_ID_H264); if (!stream->parser) { LOGE("Could not initialize parser"); - goto finally_stop_and_join_recorder; + goto finally_close_recorder; } // We must only pass complete frames to av_parser_parse2()! @@ -243,12 +238,6 @@ run_stream(void *data) { } av_parser_close(stream->parser); -finally_stop_and_join_recorder: - if (stream->recorder) { - recorder_stop(stream->recorder); - LOGI("Finishing recording..."); - recorder_join(stream->recorder); - } finally_close_recorder: if (stream->recorder) { recorder_close(stream->recorder); From 5980183a33bd75235c3dcffdf6e0027462493749 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 11 Apr 2021 15:01:05 +0200 Subject: [PATCH 372/450] Expose recorder as packet sink Make recorder implement the packet sink trait. This will allow the stream to push packets without depending on the concrete sink type. --- app/src/recorder.c | 29 +++++++++++++++++++++++++++++ app/src/recorder.h | 3 +++ 2 files changed, 32 insertions(+) diff --git a/app/src/recorder.c b/app/src/recorder.c index 387d8f79..fabbc95c 100644 --- a/app/src/recorder.c +++ b/app/src/recorder.c @@ -5,6 +5,9 @@ #include "util/log.h" +/** Downcast packet_sink to recorder */ +#define DOWNCAST(SINK) container_of(SINK, struct recorder, packet_sink) + static const AVRational SCRCPY_TIME_BASE = {1, 1000000}; // timestamps in us static const AVOutputFormat * @@ -312,6 +315,24 @@ recorder_push(struct recorder *recorder, const AVPacket *packet) { return true; } +static bool +recorder_packet_sink_open(struct sc_packet_sink *sink, const AVCodec *codec) { + struct recorder *recorder = DOWNCAST(sink); + return recorder_open(recorder, codec); +} + +static void +recorder_packet_sink_close(struct sc_packet_sink *sink) { + struct recorder *recorder = DOWNCAST(sink); + recorder_close(recorder); +} + +static bool +recorder_packet_sink_push(struct sc_packet_sink *sink, const AVPacket *packet) { + struct recorder *recorder = DOWNCAST(sink); + return recorder_push(recorder, packet); +} + bool recorder_init(struct recorder *recorder, const char *filename, @@ -346,6 +367,14 @@ recorder_init(struct recorder *recorder, recorder->header_written = false; recorder->previous = NULL; + static const struct sc_packet_sink_ops ops = { + .open = recorder_packet_sink_open, + .close = recorder_packet_sink_close, + .push = recorder_packet_sink_push, + }; + + recorder->packet_sink.ops = &ops; + return true; } diff --git a/app/src/recorder.h b/app/src/recorder.h index 97e499bc..4991f0cf 100644 --- a/app/src/recorder.h +++ b/app/src/recorder.h @@ -8,6 +8,7 @@ #include "coords.h" #include "scrcpy.h" +#include "trait/packet_sink.h" #include "util/queue.h" #include "util/thread.h" @@ -19,6 +20,8 @@ struct record_packet { struct recorder_queue QUEUE(struct record_packet); struct recorder { + struct sc_packet_sink packet_sink; // packet sink trait + char *filename; enum sc_record_format format; AVFormatContext *ctx; From 5beb7d6c0242bbb9995041fdd559982280a3499b Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 11 Apr 2021 15:01:05 +0200 Subject: [PATCH 373/450] Reorder decoder functions This will make further commits more readable. --- app/src/decoder.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/app/src/decoder.c b/app/src/decoder.c index 247b459c..3b94afef 100644 --- a/app/src/decoder.c +++ b/app/src/decoder.c @@ -6,11 +6,6 @@ #include "video_buffer.h" #include "util/log.h" -void -decoder_init(struct decoder *decoder, struct video_buffer *vb) { - decoder->video_buffer = vb; -} - bool decoder_open(struct decoder *decoder, const AVCodec *codec) { decoder->codec_ctx = avcodec_alloc_context3(codec); @@ -63,3 +58,8 @@ decoder_push(struct decoder *decoder, const AVPacket *packet) { } return true; } + +void +decoder_init(struct decoder *decoder, struct video_buffer *vb) { + decoder->video_buffer = vb; +} From cbed38799e2e39bdf368bfd90ab028a896e307e3 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 11 Apr 2021 15:01:05 +0200 Subject: [PATCH 374/450] Expose decoder as packet sink Make decoder implement the packet sink trait. This will allow the stream to push packets without depending on the concrete sink type. --- app/src/decoder.c | 29 +++++++++++++++++++++++++++++ app/src/decoder.h | 4 ++++ 2 files changed, 33 insertions(+) diff --git a/app/src/decoder.c b/app/src/decoder.c index 3b94afef..41e2fe85 100644 --- a/app/src/decoder.c +++ b/app/src/decoder.c @@ -6,6 +6,9 @@ #include "video_buffer.h" #include "util/log.h" +/** Downcast packet_sink to decoder */ +#define DOWNCAST(SINK) container_of(SINK, struct decoder, packet_sink) + bool decoder_open(struct decoder *decoder, const AVCodec *codec) { decoder->codec_ctx = avcodec_alloc_context3(codec); @@ -59,7 +62,33 @@ decoder_push(struct decoder *decoder, const AVPacket *packet) { return true; } +static bool +decoder_packet_sink_open(struct sc_packet_sink *sink, const AVCodec *codec) { + struct decoder *decoder = DOWNCAST(sink); + return decoder_open(decoder, codec); +} + +static void +decoder_packet_sink_close(struct sc_packet_sink *sink) { + struct decoder *decoder = DOWNCAST(sink); + decoder_close(decoder); +} + +static bool +decoder_packet_sink_push(struct sc_packet_sink *sink, const AVPacket *packet) { + struct decoder *decoder = DOWNCAST(sink); + return decoder_push(decoder, packet); +} + void decoder_init(struct decoder *decoder, struct video_buffer *vb) { decoder->video_buffer = vb; + + static const struct sc_packet_sink_ops ops = { + .open = decoder_packet_sink_open, + .close = decoder_packet_sink_close, + .push = decoder_packet_sink_push, + }; + + decoder->packet_sink.ops = &ops; } diff --git a/app/src/decoder.h b/app/src/decoder.h index 50dd7fe0..c3f7cb73 100644 --- a/app/src/decoder.h +++ b/app/src/decoder.h @@ -3,12 +3,16 @@ #include "common.h" +#include "trait/packet_sink.h" + #include #include struct video_buffer; struct decoder { + struct sc_packet_sink packet_sink; // packet sink trait + struct video_buffer *video_buffer; AVCodecContext *codec_ctx; From f7a1b67d66e8b6bd569a5ac608d85a595df3ec27 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 11 Apr 2021 15:01:05 +0200 Subject: [PATCH 375/450] Make stream push packets to sinks Now that decoder and recorder implement the packet sink trait, make stream push packets to the sinks without depending on the concrete sink types. --- app/src/decoder.c | 8 +++- app/src/decoder.h | 3 -- app/src/recorder.c | 6 +-- app/src/recorder.h | 9 ----- app/src/scrcpy.c | 10 ++++- app/src/stream.c | 96 +++++++++++++++++++++++++--------------------- app/src/stream.h | 15 ++++++-- 7 files changed, 82 insertions(+), 65 deletions(-) diff --git a/app/src/decoder.c b/app/src/decoder.c index 41e2fe85..134ffd3c 100644 --- a/app/src/decoder.c +++ b/app/src/decoder.c @@ -41,8 +41,14 @@ decoder_close(struct decoder *decoder) { avcodec_free_context(&decoder->codec_ctx); } -bool +static bool decoder_push(struct decoder *decoder, const AVPacket *packet) { + bool is_config = packet->pts == AV_NOPTS_VALUE; + if (is_config) { + // nothing to do + return true; + } + int ret; if ((ret = avcodec_send_packet(decoder->codec_ctx, packet)) < 0) { LOGE("Could not send video packet: %d", ret); diff --git a/app/src/decoder.h b/app/src/decoder.h index c3f7cb73..e3730db5 100644 --- a/app/src/decoder.h +++ b/app/src/decoder.h @@ -28,7 +28,4 @@ decoder_open(struct decoder *decoder, const AVCodec *codec); void decoder_close(struct decoder *decoder); -bool -decoder_push(struct decoder *decoder, const AVPacket *packet); - #endif diff --git a/app/src/recorder.c b/app/src/recorder.c index fabbc95c..f0ec86dc 100644 --- a/app/src/recorder.c +++ b/app/src/recorder.c @@ -216,7 +216,7 @@ run_recorder(void *data) { return 0; } -bool +static bool recorder_open(struct recorder *recorder, const AVCodec *input_codec) { const char *format_name = recorder_get_format_name(recorder->format); assert(format_name); @@ -277,7 +277,7 @@ recorder_open(struct recorder *recorder, const AVCodec *input_codec) { return true; } -void +static void recorder_close(struct recorder *recorder) { sc_mutex_lock(&recorder->mutex); recorder->stopped = true; @@ -290,7 +290,7 @@ recorder_close(struct recorder *recorder) { avformat_free_context(recorder->ctx); } -bool +static bool recorder_push(struct recorder *recorder, const AVPacket *packet) { sc_mutex_lock(&recorder->mutex); assert(!recorder->stopped); diff --git a/app/src/recorder.h b/app/src/recorder.h index 4991f0cf..1b2b9284 100644 --- a/app/src/recorder.h +++ b/app/src/recorder.h @@ -49,13 +49,4 @@ recorder_init(struct recorder *recorder, const char *filename, void recorder_destroy(struct recorder *recorder); -bool -recorder_open(struct recorder *recorder, const AVCodec *input_codec); - -void -recorder_close(struct recorder *recorder); - -bool -recorder_push(struct recorder *recorder, const AVPacket *packet); - #endif diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index 2f8abd64..ad705c4a 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -336,7 +336,15 @@ scrcpy(const struct scrcpy_options *options) { av_log_set_callback(av_log_callback); - stream_init(&stream, server.video_socket, dec, rec); + stream_init(&stream, server.video_socket); + + if (dec) { + stream_add_sink(&stream, &dec->packet_sink); + } + + if (rec) { + stream_add_sink(&stream, &rec->packet_sink); + } if (options->display) { if (options->control) { diff --git a/app/src/stream.c b/app/src/stream.c index 787e9515..a11218e3 100644 --- a/app/src/stream.c +++ b/app/src/stream.c @@ -66,25 +66,11 @@ notify_stopped(void) { } static bool -process_config_packet(struct stream *stream, AVPacket *packet) { - if (stream->recorder && !recorder_push(stream->recorder, packet)) { - LOGE("Could not send config packet to recorder"); - return false; - } - return true; -} - -static bool -process_frame(struct stream *stream, AVPacket *packet) { - if (stream->decoder && !decoder_push(stream->decoder, packet)) { - return false; - } - - if (stream->recorder) { - packet->dts = packet->pts; - - if (!recorder_push(stream->recorder, packet)) { - LOGE("Could not send packet to recorder"); +push_packet_to_sinks(struct stream *stream, const AVPacket *packet) { + for (unsigned i = 0; i < stream->sink_count; ++i) { + struct sc_packet_sink *sink = stream->sinks[i]; + if (!sink->ops->push(sink, packet)) { + LOGE("Could not send config packet to sink %d", i); return false; } } @@ -111,9 +97,11 @@ stream_parse(struct stream *stream, AVPacket *packet) { packet->flags |= AV_PKT_FLAG_KEY; } - bool ok = process_frame(stream, packet); + packet->dts = packet->pts; + + bool ok = push_packet_to_sinks(stream, packet); if (!ok) { - LOGE("Could not process frame"); + LOGE("Could not process packet"); return false; } @@ -156,7 +144,7 @@ stream_push_packet(struct stream *stream, AVPacket *packet) { if (is_config) { // config packet - bool ok = process_config_packet(stream, packet); + bool ok = push_packet_to_sinks(stream, packet); if (!ok) { return false; } @@ -177,6 +165,33 @@ stream_push_packet(struct stream *stream, AVPacket *packet) { return true; } +static void +stream_close_first_sinks(struct stream *stream, unsigned count) { + while (count) { + struct sc_packet_sink *sink = stream->sinks[--count]; + sink->ops->close(sink); + } +} + +static inline void +stream_close_sinks(struct stream *stream) { + stream_close_first_sinks(stream, stream->sink_count); +} + +static bool +stream_open_sinks(struct stream *stream, const AVCodec *codec) { + for (unsigned i = 0; i < stream->sink_count; ++i) { + struct sc_packet_sink *sink = stream->sinks[i]; + if (!sink->ops->open(sink, codec)) { + LOGE("Could not open packet sink %d", i); + stream_close_first_sinks(stream, i); + return false; + } + } + + return true; +} + static int run_stream(void *data) { struct stream *stream = data; @@ -193,22 +208,15 @@ run_stream(void *data) { goto end; } - if (stream->decoder && !decoder_open(stream->decoder, codec)) { - LOGE("Could not open decoder"); + if (!stream_open_sinks(stream, codec)) { + LOGE("Could not open stream sinks"); goto finally_free_codec_ctx; } - if (stream->recorder) { - if (!recorder_open(stream->recorder, codec)) { - LOGE("Could not open recorder"); - goto finally_close_decoder; - } - } - stream->parser = av_parser_init(AV_CODEC_ID_H264); if (!stream->parser) { LOGE("Could not initialize parser"); - goto finally_close_recorder; + goto finally_close_sinks; } // We must only pass complete frames to av_parser_parse2()! @@ -238,14 +246,8 @@ run_stream(void *data) { } av_parser_close(stream->parser); -finally_close_recorder: - if (stream->recorder) { - recorder_close(stream->recorder); - } -finally_close_decoder: - if (stream->decoder) { - decoder_close(stream->decoder); - } +finally_close_sinks: + stream_close_sinks(stream); finally_free_codec_ctx: avcodec_free_context(&stream->codec_ctx); end: @@ -254,12 +256,18 @@ end: } void -stream_init(struct stream *stream, socket_t socket, - struct decoder *decoder, struct recorder *recorder) { +stream_init(struct stream *stream, socket_t socket) { stream->socket = socket; - stream->decoder = decoder, - stream->recorder = recorder; stream->has_pending = false; + stream->sink_count = 0; +} + +void +stream_add_sink(struct stream *stream, struct sc_packet_sink *sink) { + assert(stream->sink_count < STREAM_MAX_SINKS); + assert(sink); + assert(sink->ops); + stream->sinks[stream->sink_count++] = sink; } bool diff --git a/app/src/stream.h b/app/src/stream.h index 421c1bf0..81175420 100644 --- a/app/src/stream.h +++ b/app/src/stream.h @@ -8,14 +8,19 @@ #include #include +#include "trait/packet_sink.h" #include "util/net.h" #include "util/thread.h" +#define STREAM_MAX_SINKS 2 + struct stream { socket_t socket; sc_thread thread; - struct decoder *decoder; - struct recorder *recorder; + + struct sc_packet_sink *sinks[STREAM_MAX_SINKS]; + unsigned sink_count; + AVCodecContext *codec_ctx; AVCodecParserContext *parser; // successive packets may need to be concatenated, until a non-config @@ -25,8 +30,10 @@ struct stream { }; void -stream_init(struct stream *stream, socket_t socket, - struct decoder *decoder, struct recorder *recorder); +stream_init(struct stream *stream, socket_t socket); + +void +stream_add_sink(struct stream *stream, struct sc_packet_sink *sink); bool stream_start(struct stream *stream); From deab7da761d856de22e5880e5376d53cd2e8fb77 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 11 Apr 2021 15:39:00 +0200 Subject: [PATCH 376/450] Add frame sink trait This trait will allow to abstract the concrete sink types from the frame producer (decoder.c). --- app/src/trait/frame_sink.h | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 app/src/trait/frame_sink.h diff --git a/app/src/trait/frame_sink.h b/app/src/trait/frame_sink.h new file mode 100644 index 00000000..64ab0de9 --- /dev/null +++ b/app/src/trait/frame_sink.h @@ -0,0 +1,26 @@ +#ifndef SC_FRAME_SINK +#define SC_FRAME_SINK + +#include "common.h" + +#include +#include + +typedef struct AVFrame AVFrame; + +/** + * Frame sink trait. + * + * Component able to receive AVFrames should implement this trait. + */ +struct sc_frame_sink { + const struct sc_frame_sink_ops *ops; +}; + +struct sc_frame_sink_ops { + bool (*open)(struct sc_frame_sink *sink); + void (*close)(struct sc_frame_sink *sink); + bool (*push)(struct sc_frame_sink *sink, const AVFrame *frame); +}; + +#endif From 08b3086ffc95f846bc3a420f5d4296092020eb8c Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 11 Apr 2021 15:01:05 +0200 Subject: [PATCH 377/450] Expose screen as frame sink Make screen implement the frame sink trait. This will allow the decoder to push frames without depending on the concrete sink type. --- app/src/screen.c | 33 +++++++++++++++++++++++++++++++++ app/src/screen.h | 3 +++ 2 files changed, 36 insertions(+) diff --git a/app/src/screen.c b/app/src/screen.c index de734554..0aa0b832 100644 --- a/app/src/screen.c +++ b/app/src/screen.c @@ -13,6 +13,8 @@ #define DISPLAY_MARGINS 96 +#define DOWNCAST(SINK) container_of(SINK, struct screen, frame_sink) + static inline struct size get_rotated_size(struct size size, int rotation) { struct size rotated_size; @@ -262,6 +264,29 @@ event_watcher(void *data, SDL_Event *event) { } #endif +static bool +screen_frame_sink_open(struct sc_frame_sink *sink) { + struct screen *screen = DOWNCAST(sink); + (void) screen; + + // nothing to do, the screen is already open on the main thread + return true; +} + +static void +screen_frame_sink_close(struct sc_frame_sink *sink) { + struct screen *screen = DOWNCAST(sink); + (void) screen; + + // nothing to do, the screen lifecycle is not managed by the frame producer +} + +static bool +screen_frame_sink_push(struct sc_frame_sink *sink, const AVFrame *frame) { + struct screen *screen = DOWNCAST(sink); + return video_buffer_push(screen->vb, frame); +} + bool screen_init(struct screen *screen, struct video_buffer *vb, struct fps_counter *fps_counter, @@ -402,6 +427,14 @@ screen_init(struct screen *screen, struct video_buffer *vb, SDL_AddEventWatch(event_watcher, screen); #endif + static const struct sc_frame_sink_ops ops = { + .open = screen_frame_sink_open, + .close = screen_frame_sink_close, + .push = screen_frame_sink_push, + }; + + screen->frame_sink.ops = &ops; + return true; } diff --git a/app/src/screen.h b/app/src/screen.h index cd849779..d57b7152 100644 --- a/app/src/screen.h +++ b/app/src/screen.h @@ -9,10 +9,13 @@ #include "coords.h" #include "opengl.h" +#include "trait/frame_sink.h" struct video_buffer; struct screen { + struct sc_frame_sink frame_sink; // frame sink trait + struct video_buffer *vb; struct fps_counter *fps_counter; From 6f5ad21f5757482b71a5d197f920ea39fb2f1167 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 11 Apr 2021 15:01:05 +0200 Subject: [PATCH 378/450] Make decoder push frames to sinks Now that screen implements the packet sink trait, make decoder push packets to the sinks without depending on the concrete sink types. --- app/src/decoder.c | 68 ++++++++++++++++++++++++++++++++++++++++++----- app/src/decoder.h | 12 ++++----- app/src/scrcpy.c | 4 ++- 3 files changed, 70 insertions(+), 14 deletions(-) diff --git a/app/src/decoder.c b/app/src/decoder.c index 134ffd3c..34f2a15f 100644 --- a/app/src/decoder.c +++ b/app/src/decoder.c @@ -4,12 +4,40 @@ #include "events.h" #include "video_buffer.h" +#include "trait/frame_sink.h" #include "util/log.h" /** Downcast packet_sink to decoder */ #define DOWNCAST(SINK) container_of(SINK, struct decoder, packet_sink) -bool +static void +decoder_close_first_sinks(struct decoder *decoder, unsigned count) { + while (count) { + struct sc_frame_sink *sink = decoder->sinks[--count]; + sink->ops->close(sink); + } +} + +static inline void +decoder_close_sinks(struct decoder *decoder) { + decoder_close_first_sinks(decoder, decoder->sink_count); +} + +static bool +decoder_open_sinks(struct decoder *decoder) { + for (unsigned i = 0; i < decoder->sink_count; ++i) { + struct sc_frame_sink *sink = decoder->sinks[i]; + if (!sink->ops->open(sink)) { + LOGE("Could not open frame sink %d", i); + decoder_close_first_sinks(decoder, i); + return false; + } + } + + return true; +} + +static bool decoder_open(struct decoder *decoder, const AVCodec *codec) { decoder->codec_ctx = avcodec_alloc_context3(codec); if (!decoder->codec_ctx) { @@ -31,16 +59,38 @@ decoder_open(struct decoder *decoder, const AVCodec *codec) { return false; } + if (!decoder_open_sinks(decoder)) { + LOGE("Could not open decoder sinks"); + av_frame_free(&decoder->frame); + avcodec_close(decoder->codec_ctx); + avcodec_free_context(&decoder->codec_ctx); + return false; + } + return true; } -void +static void decoder_close(struct decoder *decoder) { + decoder_close_sinks(decoder); av_frame_free(&decoder->frame); avcodec_close(decoder->codec_ctx); avcodec_free_context(&decoder->codec_ctx); } +static bool +push_frame_to_sinks(struct decoder *decoder, const AVFrame *frame) { + for (unsigned i = 0; i < decoder->sink_count; ++i) { + struct sc_frame_sink *sink = decoder->sinks[i]; + if (!sink->ops->push(sink, frame)) { + LOGE("Could not send frame to sink %d", i); + return false; + } + } + + return true; +} + static bool decoder_push(struct decoder *decoder, const AVPacket *packet) { bool is_config = packet->pts == AV_NOPTS_VALUE; @@ -57,7 +107,7 @@ decoder_push(struct decoder *decoder, const AVPacket *packet) { ret = avcodec_receive_frame(decoder->codec_ctx, decoder->frame); if (!ret) { // a frame was received - bool ok = video_buffer_push(decoder->video_buffer, decoder->frame); + bool ok = push_frame_to_sinks(decoder, decoder->frame); // A frame lost should not make the whole pipeline fail. The error, if // any, is already logged. (void) ok; @@ -87,9 +137,7 @@ decoder_packet_sink_push(struct sc_packet_sink *sink, const AVPacket *packet) { } void -decoder_init(struct decoder *decoder, struct video_buffer *vb) { - decoder->video_buffer = vb; - +decoder_init(struct decoder *decoder) { static const struct sc_packet_sink_ops ops = { .open = decoder_packet_sink_open, .close = decoder_packet_sink_close, @@ -98,3 +146,11 @@ decoder_init(struct decoder *decoder, struct video_buffer *vb) { decoder->packet_sink.ops = &ops; } + +void +decoder_add_sink(struct decoder *decoder, struct sc_frame_sink *sink) { + assert(decoder->sink_count < DECODER_MAX_SINKS); + assert(sink); + assert(sink->ops); + decoder->sinks[decoder->sink_count++] = sink; +} diff --git a/app/src/decoder.h b/app/src/decoder.h index e3730db5..bae97869 100644 --- a/app/src/decoder.h +++ b/app/src/decoder.h @@ -8,24 +8,22 @@ #include #include -struct video_buffer; +#define DECODER_MAX_SINKS 1 struct decoder { struct sc_packet_sink packet_sink; // packet sink trait - struct video_buffer *video_buffer; + struct sc_frame_sink *sinks[DECODER_MAX_SINKS]; + unsigned sink_count; AVCodecContext *codec_ctx; AVFrame *frame; }; void -decoder_init(struct decoder *decoder, struct video_buffer *vb); - -bool -decoder_open(struct decoder *decoder, const AVCodec *codec); +decoder_init(struct decoder *decoder); void -decoder_close(struct decoder *decoder); +decoder_add_sink(struct decoder *decoder, struct sc_frame_sink *sink); #endif diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index ad705c4a..e8715cbe 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -318,7 +318,7 @@ scrcpy(const struct scrcpy_options *options) { file_handler_initialized = true; } - decoder_init(&decoder, &video_buffer); + decoder_init(&decoder); dec = &decoder; } @@ -382,6 +382,8 @@ scrcpy(const struct scrcpy_options *options) { } screen_initialized = true; + decoder_add_sink(&decoder, &screen.frame_sink); + if (options->turn_screen_off) { struct control_msg msg; msg.type = CONTROL_MSG_TYPE_SET_SCREEN_POWER_MODE; From e91acdb0c43f2db43c211f43c4b9a46ce3e76084 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 11 Apr 2021 15:01:05 +0200 Subject: [PATCH 379/450] Move video_buffer to screen The video buffer is now an internal detail of the screen component. Since the screen is plugged to the decoder via the frame sink trait, the decoder does not access to the video buffer anymore. --- app/src/scrcpy.c | 15 +-------------- app/src/screen.c | 20 ++++++++++++++------ app/src/screen.h | 8 +++----- 3 files changed, 18 insertions(+), 25 deletions(-) diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index e8715cbe..cab63522 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -25,14 +25,12 @@ #include "server.h" #include "stream.h" #include "tiny_xpm.h" -#include "video_buffer.h" #include "util/log.h" #include "util/net.h" static struct server server; static struct screen screen; static struct fps_counter fps_counter; -static struct video_buffer video_buffer; static struct stream stream; static struct decoder decoder; static struct recorder recorder; @@ -247,7 +245,6 @@ scrcpy(const struct scrcpy_options *options) { bool server_started = false; bool fps_counter_initialized = false; - bool video_buffer_initialized = false; bool file_handler_initialized = false; bool recorder_initialized = false; bool stream_started = false; @@ -305,11 +302,6 @@ scrcpy(const struct scrcpy_options *options) { } fps_counter_initialized = true; - if (!video_buffer_init(&video_buffer)) { - goto end; - } - video_buffer_initialized = true; - if (options->control) { if (!file_handler_init(&file_handler, server.serial, options->push_target)) { @@ -376,8 +368,7 @@ scrcpy(const struct scrcpy_options *options) { .fullscreen = options->fullscreen, }; - if (!screen_init(&screen, &video_buffer, &fps_counter, - &screen_params)) { + if (!screen_init(&screen, &fps_counter, &screen_params)) { goto end; } screen_initialized = true; @@ -453,10 +444,6 @@ end: file_handler_destroy(&file_handler); } - if (video_buffer_initialized) { - video_buffer_destroy(&video_buffer); - } - if (fps_counter_initialized) { fps_counter_join(&fps_counter); fps_counter_destroy(&fps_counter); diff --git a/app/src/screen.c b/app/src/screen.c index 0aa0b832..c067ab7a 100644 --- a/app/src/screen.c +++ b/app/src/screen.c @@ -284,14 +284,12 @@ screen_frame_sink_close(struct sc_frame_sink *sink) { static bool screen_frame_sink_push(struct sc_frame_sink *sink, const AVFrame *frame) { struct screen *screen = DOWNCAST(sink); - return video_buffer_push(screen->vb, frame); + return video_buffer_push(&screen->vb, frame); } bool -screen_init(struct screen *screen, struct video_buffer *vb, - struct fps_counter *fps_counter, +screen_init(struct screen *screen, struct fps_counter *fps_counter, const struct screen_params *params) { - screen->vb = vb; screen->fps_counter = fps_counter; screen->resize_pending = false; @@ -299,11 +297,17 @@ screen_init(struct screen *screen, struct video_buffer *vb, screen->fullscreen = false; screen->maximized = false; + bool ok = video_buffer_init(&screen->vb); + if (!ok) { + LOGE("Could not initialize video buffer"); + return false; + } + static const struct video_buffer_callbacks cbs = { .on_frame_available = on_frame_available, .on_frame_skipped = on_frame_skipped, }; - video_buffer_set_consumer_callbacks(vb, &cbs, screen); + video_buffer_set_consumer_callbacks(&screen->vb, &cbs, screen); screen->frame_size = params->frame_size; screen->rotation = params->rotation; @@ -349,6 +353,7 @@ screen_init(struct screen *screen, struct video_buffer *vb, if (!screen->renderer) { LOGC("Could not create renderer: %s", SDL_GetError()); SDL_DestroyWindow(screen->window); + video_buffer_destroy(&screen->vb); return false; } @@ -400,6 +405,7 @@ screen_init(struct screen *screen, struct video_buffer *vb, LOGC("Could not create texture: %s", SDL_GetError()); SDL_DestroyRenderer(screen->renderer); SDL_DestroyWindow(screen->window); + video_buffer_destroy(&screen->vb); return false; } @@ -409,6 +415,7 @@ screen_init(struct screen *screen, struct video_buffer *vb, SDL_DestroyTexture(screen->texture); SDL_DestroyRenderer(screen->renderer); SDL_DestroyWindow(screen->window); + video_buffer_destroy(&screen->vb); return false; } @@ -449,6 +456,7 @@ screen_destroy(struct screen *screen) { SDL_DestroyTexture(screen->texture); SDL_DestroyRenderer(screen->renderer); SDL_DestroyWindow(screen->window); + video_buffer_destroy(&screen->vb); } static void @@ -554,7 +562,7 @@ update_texture(struct screen *screen, const AVFrame *frame) { static bool screen_update_frame(struct screen *screen) { av_frame_unref(screen->frame); - video_buffer_consume(screen->vb, screen->frame); + video_buffer_consume(&screen->vb, screen->frame); AVFrame *frame = screen->frame; fps_counter_add_rendered_frame(screen->fps_counter); diff --git a/app/src/screen.h b/app/src/screen.h index d57b7152..3b4506e3 100644 --- a/app/src/screen.h +++ b/app/src/screen.h @@ -10,13 +10,12 @@ #include "coords.h" #include "opengl.h" #include "trait/frame_sink.h" - -struct video_buffer; +#include "video_buffer.h" struct screen { struct sc_frame_sink frame_sink; // frame sink trait - struct video_buffer *vb; + struct video_buffer vb; struct fps_counter *fps_counter; SDL_Window *window; @@ -63,8 +62,7 @@ struct screen_params { // initialize screen, create window, renderer and texture (window is hidden) bool -screen_init(struct screen *screen, struct video_buffer *vb, - struct fps_counter *fps_counter, +screen_init(struct screen *screen, struct fps_counter *fps_counter, const struct screen_params *params); // destroy window, renderer and texture (if any) From 2a94a2b119a9efd2cd06ded03280872c81abe6b4 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 11 Apr 2021 15:01:05 +0200 Subject: [PATCH 380/450] Remove video_buffer callbacks Now that screen is both the owner and the listener of the video buffer, execute the code directly without callbacks. --- app/src/screen.c | 49 ++++++++++++++++++------------------------ app/src/video_buffer.c | 31 +++++--------------------- app/src/video_buffer.h | 20 +---------------- 3 files changed, 27 insertions(+), 73 deletions(-) diff --git a/app/src/screen.c b/app/src/screen.c index c067ab7a..ddf81b08 100644 --- a/app/src/screen.c +++ b/app/src/screen.c @@ -193,27 +193,6 @@ screen_update_content_rect(struct screen *screen) { } } -static void -on_frame_available(struct video_buffer *vb, void *userdata) { - (void) vb; - (void) userdata; - - static SDL_Event new_frame_event = { - .type = EVENT_NEW_FRAME, - }; - - // Post the event on the UI thread - SDL_PushEvent(&new_frame_event); -} - -static void -on_frame_skipped(struct video_buffer *vb, void *userdata) { - (void) vb; - - struct screen *screen = userdata; - fps_counter_add_skipped_frame(screen->fps_counter); -} - static inline SDL_Texture * create_texture(struct screen *screen) { SDL_Renderer *renderer = screen->renderer; @@ -284,7 +263,27 @@ screen_frame_sink_close(struct sc_frame_sink *sink) { static bool screen_frame_sink_push(struct sc_frame_sink *sink, const AVFrame *frame) { struct screen *screen = DOWNCAST(sink); - return video_buffer_push(&screen->vb, frame); + + bool previous_frame_skipped; + bool ok = video_buffer_push(&screen->vb, frame, &previous_frame_skipped); + if (!ok) { + return false; + } + + if (previous_frame_skipped) { + fps_counter_add_skipped_frame(screen->fps_counter); + // The EVENT_NEW_FRAME triggered for the previous frame will consume + // this new frame instead + } else { + static SDL_Event new_frame_event = { + .type = EVENT_NEW_FRAME, + }; + + // Post the event on the UI thread + SDL_PushEvent(&new_frame_event); + } + + return true; } bool @@ -303,12 +302,6 @@ screen_init(struct screen *screen, struct fps_counter *fps_counter, return false; } - static const struct video_buffer_callbacks cbs = { - .on_frame_available = on_frame_available, - .on_frame_skipped = on_frame_skipped, - }; - video_buffer_set_consumer_callbacks(&screen->vb, &cbs, screen); - screen->frame_size = params->frame_size; screen->rotation = params->rotation; if (screen->rotation) { diff --git a/app/src/video_buffer.c b/app/src/video_buffer.c index 18a180fa..7adf098b 100644 --- a/app/src/video_buffer.c +++ b/app/src/video_buffer.c @@ -29,10 +29,6 @@ video_buffer_init(struct video_buffer *vb) { // there is initially no frame, so consider it has already been consumed vb->pending_frame_consumed = true; - // The callbacks must be set by the consumer via - // video_buffer_set_consumer_callbacks() - vb->cbs = NULL; - return true; } @@ -50,21 +46,9 @@ swap_frames(AVFrame **lhs, AVFrame **rhs) { *rhs = tmp; } -void -video_buffer_set_consumer_callbacks(struct video_buffer *vb, - const struct video_buffer_callbacks *cbs, - void *cbs_userdata) { - assert(!vb->cbs); // must be set only once - assert(cbs); - assert(cbs->on_frame_available); - vb->cbs = cbs; - vb->cbs_userdata = cbs_userdata; -} - bool -video_buffer_push(struct video_buffer *vb, const AVFrame *frame) { - assert(vb->cbs); - +video_buffer_push(struct video_buffer *vb, const AVFrame *frame, + bool *previous_frame_skipped) { sc_mutex_lock(&vb->mutex); // Use a temporary frame to preserve pending_frame in case of error. @@ -80,18 +64,13 @@ video_buffer_push(struct video_buffer *vb, const AVFrame *frame) { swap_frames(&vb->pending_frame, &vb->tmp_frame); av_frame_unref(vb->tmp_frame); - bool skipped = !vb->pending_frame_consumed; + if (previous_frame_skipped) { + *previous_frame_skipped = !vb->pending_frame_consumed; + } vb->pending_frame_consumed = false; sc_mutex_unlock(&vb->mutex); - if (skipped) { - if (vb->cbs->on_frame_skipped) - vb->cbs->on_frame_skipped(vb, vb->cbs_userdata); - } else { - vb->cbs->on_frame_available(vb, vb->cbs_userdata); - } - return true; } diff --git a/app/src/video_buffer.h b/app/src/video_buffer.h index cdecb259..b9478f4c 100644 --- a/app/src/video_buffer.h +++ b/app/src/video_buffer.h @@ -33,19 +33,6 @@ struct video_buffer { sc_mutex mutex; bool pending_frame_consumed; - - const struct video_buffer_callbacks *cbs; - void *cbs_userdata; -}; - -struct video_buffer_callbacks { - // Called when a new frame can be consumed. - // This callback is mandatory (it must not be NULL). - void (*on_frame_available)(struct video_buffer *vb, void *userdata); - - // Called when a pending frame has been overwritten by the producer. - // This callback is optional (it may be NULL). - void (*on_frame_skipped)(struct video_buffer *vb, void *userdata); }; bool @@ -54,13 +41,8 @@ video_buffer_init(struct video_buffer *vb); void video_buffer_destroy(struct video_buffer *vb); -void -video_buffer_set_consumer_callbacks(struct video_buffer *vb, - const struct video_buffer_callbacks *cbs, - void *cbs_userdata); - bool -video_buffer_push(struct video_buffer *vb, const AVFrame *frame); +video_buffer_push(struct video_buffer *vb, const AVFrame *frame, bool *skipped); void video_buffer_consume(struct video_buffer *vb, AVFrame *dst); From 0272e6dc772664bdbad21eab5c28919e63102bdd Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 11 Apr 2021 15:01:05 +0200 Subject: [PATCH 381/450] Assert screen closed on destroy The destruction order is important, but tricky, because the screen is open/close by the decoder, but destroyed by scrcpy.c on the main thread. Add assertions to guarantee that the screen is not destroyed before being closed. --- app/src/screen.c | 13 +++++++++++++ app/src/screen.h | 4 ++++ 2 files changed, 17 insertions(+) diff --git a/app/src/screen.c b/app/src/screen.c index ddf81b08..1b3c5179 100644 --- a/app/src/screen.c +++ b/app/src/screen.c @@ -247,6 +247,9 @@ static bool screen_frame_sink_open(struct sc_frame_sink *sink) { struct screen *screen = DOWNCAST(sink); (void) screen; +#ifndef NDEBUG + screen->open = true; +#endif // nothing to do, the screen is already open on the main thread return true; @@ -256,6 +259,9 @@ static void screen_frame_sink_close(struct sc_frame_sink *sink) { struct screen *screen = DOWNCAST(sink); (void) screen; +#ifndef NDEBUG + screen->open = false; +#endif // nothing to do, the screen lifecycle is not managed by the frame producer } @@ -435,6 +441,10 @@ screen_init(struct screen *screen, struct fps_counter *fps_counter, screen->frame_sink.ops = &ops; +#ifndef NDEBUG + screen->open = false; +#endif + return true; } @@ -445,6 +455,9 @@ screen_show_window(struct screen *screen) { void screen_destroy(struct screen *screen) { +#ifndef NDEBUG + assert(!screen->open); +#endif av_frame_free(&screen->frame); SDL_DestroyTexture(screen->texture); SDL_DestroyRenderer(screen->renderer); diff --git a/app/src/screen.h b/app/src/screen.h index 3b4506e3..4a0bad09 100644 --- a/app/src/screen.h +++ b/app/src/screen.h @@ -15,6 +15,10 @@ struct screen { struct sc_frame_sink frame_sink; // frame sink trait +#ifndef NDEBUG + bool open; // track the open/close state to assert correct behavior +#endif + struct video_buffer vb; struct fps_counter *fps_counter; From 0541f1bff2be8dcf4de0765ce357836c0b2bf4ca Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Tue, 13 Apr 2021 22:22:54 +0200 Subject: [PATCH 382/450] Hide the window immediately on close The screen may not be destroyed immediately on close to avoid undefined behavior, because it may still receive events from the decoder. But the visual window must still be closed immediately. --- app/src/scrcpy.c | 4 ++++ app/src/screen.c | 5 +++++ app/src/screen.h | 7 +++++++ 3 files changed, 16 insertions(+) diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index cab63522..4de62389 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -398,6 +398,10 @@ scrcpy(const struct scrcpy_options *options) { ret = event_loop(options); LOGD("quit..."); + // Close the window immediately on closing, because screen_destroy() may + // only be called once the stream thread is joined (it may take time) + screen_hide_window(&screen); + end: // The stream is not stopped explicitly, because it will stop by itself on // end-of-stream diff --git a/app/src/screen.c b/app/src/screen.c index 1b3c5179..0598ccf4 100644 --- a/app/src/screen.c +++ b/app/src/screen.c @@ -453,6 +453,11 @@ screen_show_window(struct screen *screen) { SDL_ShowWindow(screen->window); } +void +screen_hide_window(struct screen *screen) { + SDL_HideWindow(screen->window); +} + void screen_destroy(struct screen *screen) { #ifndef NDEBUG diff --git a/app/src/screen.h b/app/src/screen.h index 4a0bad09..2921c701 100644 --- a/app/src/screen.h +++ b/app/src/screen.h @@ -73,6 +73,13 @@ screen_init(struct screen *screen, struct fps_counter *fps_counter, void screen_destroy(struct screen *screen); +// hide the window +// +// It is used to hide the window immediately on closing without waiting for +// screen_destroy() +void +screen_hide_window(struct screen *screen); + // render the texture to the renderer // // Set the update_content_rect flag if the window or content size may have From e3fccc5a5e697d55283a2a038a455af372ed6da6 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 18 Apr 2021 11:25:58 +0200 Subject: [PATCH 383/450] Initialize recorder fields on open Only initialize ops and parameters copy from recorder_init(). It was inconsistent to initialize some fields from _init() and some others from _open(). --- app/src/recorder.c | 55 +++++++++++++++++++++++++++------------------- 1 file changed, 32 insertions(+), 23 deletions(-) diff --git a/app/src/recorder.c b/app/src/recorder.c index f0ec86dc..e96c4a52 100644 --- a/app/src/recorder.c +++ b/app/src/recorder.c @@ -218,17 +218,40 @@ run_recorder(void *data) { static bool recorder_open(struct recorder *recorder, const AVCodec *input_codec) { + bool ok = sc_mutex_init(&recorder->mutex); + if (!ok) { + LOGC("Could not create mutex"); + return false; + } + + ok = sc_cond_init(&recorder->queue_cond); + if (!ok) { + LOGC("Could not create cond"); + sc_mutex_destroy(&recorder->mutex); + return false; + } + + queue_init(&recorder->queue); + recorder->stopped = false; + recorder->failed = false; + recorder->header_written = false; + recorder->previous = NULL; + const char *format_name = recorder_get_format_name(recorder->format); assert(format_name); const AVOutputFormat *format = find_muxer(format_name); if (!format) { LOGE("Could not find muxer"); + sc_cond_destroy(&recorder->queue_cond); + sc_mutex_destroy(&recorder->mutex); return false; } recorder->ctx = avformat_alloc_context(); if (!recorder->ctx) { LOGE("Could not allocate output context"); + sc_cond_destroy(&recorder->queue_cond); + sc_mutex_destroy(&recorder->mutex); return false; } @@ -244,6 +267,8 @@ recorder_open(struct recorder *recorder, const AVCodec *input_codec) { AVStream *ostream = avformat_new_stream(recorder->ctx, input_codec); if (!ostream) { avformat_free_context(recorder->ctx); + sc_cond_destroy(&recorder->queue_cond); + sc_mutex_destroy(&recorder->mutex); return false; } @@ -259,16 +284,20 @@ recorder_open(struct recorder *recorder, const AVCodec *input_codec) { LOGE("Failed to open output file: %s", recorder->filename); // ostream will be cleaned up during context cleaning avformat_free_context(recorder->ctx); + sc_cond_destroy(&recorder->queue_cond); + sc_mutex_destroy(&recorder->mutex); return false; } LOGD("Starting recorder thread"); - bool ok = sc_thread_create(&recorder->thread, run_recorder, "recorder", + ok = sc_thread_create(&recorder->thread, run_recorder, "recorder", recorder); if (!ok) { LOGC("Could not start recorder thread"); avio_close(recorder->ctx->pb); avformat_free_context(recorder->ctx); + sc_cond_destroy(&recorder->queue_cond); + sc_mutex_destroy(&recorder->mutex); return false; } @@ -288,6 +317,8 @@ recorder_close(struct recorder *recorder) { avio_close(recorder->ctx->pb); avformat_free_context(recorder->ctx); + sc_cond_destroy(&recorder->queue_cond); + sc_mutex_destroy(&recorder->mutex); } static bool @@ -344,28 +375,8 @@ recorder_init(struct recorder *recorder, return false; } - bool ok = sc_mutex_init(&recorder->mutex); - if (!ok) { - LOGC("Could not create mutex"); - free(recorder->filename); - return false; - } - - ok = sc_cond_init(&recorder->queue_cond); - if (!ok) { - LOGC("Could not create cond"); - sc_mutex_destroy(&recorder->mutex); - free(recorder->filename); - return false; - } - - queue_init(&recorder->queue); - recorder->stopped = false; - recorder->failed = false; recorder->format = format; recorder->declared_frame_size = declared_frame_size; - recorder->header_written = false; - recorder->previous = NULL; static const struct sc_packet_sink_ops ops = { .open = recorder_packet_sink_open, @@ -380,7 +391,5 @@ recorder_init(struct recorder *recorder, void recorder_destroy(struct recorder *recorder) { - sc_cond_destroy(&recorder->queue_cond); - sc_mutex_destroy(&recorder->mutex); free(recorder->filename); } From 2a5dfc1c177bfeaf034b29c3128bcab5eadff969 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 18 Apr 2021 11:32:21 +0200 Subject: [PATCH 384/450] Handle errors using gotos in recorder_open() There are many initializations in recorder_open(). Handle RAII-like deinitialization using gotos. --- app/src/recorder.c | 38 +++++++++++++++++--------------------- 1 file changed, 17 insertions(+), 21 deletions(-) diff --git a/app/src/recorder.c b/app/src/recorder.c index e96c4a52..479bdb39 100644 --- a/app/src/recorder.c +++ b/app/src/recorder.c @@ -227,8 +227,7 @@ recorder_open(struct recorder *recorder, const AVCodec *input_codec) { ok = sc_cond_init(&recorder->queue_cond); if (!ok) { LOGC("Could not create cond"); - sc_mutex_destroy(&recorder->mutex); - return false; + goto error_mutex_destroy; } queue_init(&recorder->queue); @@ -242,17 +241,13 @@ recorder_open(struct recorder *recorder, const AVCodec *input_codec) { const AVOutputFormat *format = find_muxer(format_name); if (!format) { LOGE("Could not find muxer"); - sc_cond_destroy(&recorder->queue_cond); - sc_mutex_destroy(&recorder->mutex); - return false; + goto error_cond_destroy; } recorder->ctx = avformat_alloc_context(); if (!recorder->ctx) { LOGE("Could not allocate output context"); - sc_cond_destroy(&recorder->queue_cond); - sc_mutex_destroy(&recorder->mutex); - return false; + goto error_cond_destroy; } // contrary to the deprecated API (av_oformat_next()), av_muxer_iterate() @@ -266,10 +261,7 @@ recorder_open(struct recorder *recorder, const AVCodec *input_codec) { AVStream *ostream = avformat_new_stream(recorder->ctx, input_codec); if (!ostream) { - avformat_free_context(recorder->ctx); - sc_cond_destroy(&recorder->queue_cond); - sc_mutex_destroy(&recorder->mutex); - return false; + goto error_avformat_free_context; } ostream->codecpar->codec_type = AVMEDIA_TYPE_VIDEO; @@ -283,10 +275,7 @@ recorder_open(struct recorder *recorder, const AVCodec *input_codec) { if (ret < 0) { LOGE("Failed to open output file: %s", recorder->filename); // ostream will be cleaned up during context cleaning - avformat_free_context(recorder->ctx); - sc_cond_destroy(&recorder->queue_cond); - sc_mutex_destroy(&recorder->mutex); - return false; + goto error_avformat_free_context; } LOGD("Starting recorder thread"); @@ -294,16 +283,23 @@ recorder_open(struct recorder *recorder, const AVCodec *input_codec) { recorder); if (!ok) { LOGC("Could not start recorder thread"); - avio_close(recorder->ctx->pb); - avformat_free_context(recorder->ctx); - sc_cond_destroy(&recorder->queue_cond); - sc_mutex_destroy(&recorder->mutex); - return false; + goto error_avio_close; } LOGI("Recording started to %s file: %s", format_name, recorder->filename); return true; + +error_avio_close: + avio_close(recorder->ctx->pb); +error_avformat_free_context: + avformat_free_context(recorder->ctx); +error_cond_destroy: + sc_cond_destroy(&recorder->queue_cond); +error_mutex_destroy: + sc_mutex_destroy(&recorder->mutex); + + return false; } static void From 8b90dc61b95679eadc82b2e4c193faa6621fe24e Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 18 Apr 2021 17:13:58 +0200 Subject: [PATCH 385/450] Handle EAGAIN on send_packet in decoder EAGAIN was only handled on receive_frame. In practice, it should not be necessary, since one packet always contains one frame. But just in case. --- app/src/decoder.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/decoder.c b/app/src/decoder.c index 34f2a15f..476158d9 100644 --- a/app/src/decoder.c +++ b/app/src/decoder.c @@ -99,8 +99,8 @@ decoder_push(struct decoder *decoder, const AVPacket *packet) { return true; } - int ret; - if ((ret = avcodec_send_packet(decoder->codec_ctx, packet)) < 0) { + int ret = avcodec_send_packet(decoder->codec_ctx, packet); + if (ret < 0 && ret != AVERROR(EAGAIN)) { LOGE("Could not send video packet: %d", ret); return false; } From 243854a408a79896dbe7ff566d258082b13160f6 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 18 Apr 2021 17:23:09 +0200 Subject: [PATCH 386/450] Fix recorder comment --- app/src/recorder.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/recorder.c b/app/src/recorder.c index 479bdb39..e33d1a3b 100644 --- a/app/src/recorder.c +++ b/app/src/recorder.c @@ -22,7 +22,7 @@ find_muxer(const char *name) { #else oformat = av_oformat_next(oformat); #endif - // until null or with name "mp4" + // until null or with having the requested name } while (oformat && strcmp(oformat->name, name)); return oformat; } From ffc00210e9deb8364bffffe1313fc123b9621bb9 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Mon, 19 Apr 2021 09:22:53 +0200 Subject: [PATCH 387/450] Add strlist_contains() Add a function to know if a string list, using some separator, contains a specific string. --- app/src/util/str_util.c | 18 ++++++++++++++++++ app/src/util/str_util.h | 5 +++++ app/tests/test_strutil.c | 13 +++++++++++++ 3 files changed, 36 insertions(+) diff --git a/app/src/util/str_util.c b/app/src/util/str_util.c index 352d1d2f..287c08de 100644 --- a/app/src/util/str_util.c +++ b/app/src/util/str_util.c @@ -140,6 +140,24 @@ parse_integer_with_suffix(const char *s, long *out) { return true; } +bool +strlist_contains(const char *list, char sep, const char *s) { + char *p; + do { + p = strchr(list, sep); + + size_t token_len = p ? (size_t) (p - list) : strlen(list); + if (!strncmp(list, s, token_len)) { + return true; + } + + if (p) { + list = p + 1; + } + } while (p); + return false; +} + size_t utf8_truncation_index(const char *utf8, size_t max_len) { size_t len = strlen(utf8); diff --git a/app/src/util/str_util.h b/app/src/util/str_util.h index 25bec444..c016a625 100644 --- a/app/src/util/str_util.h +++ b/app/src/util/str_util.h @@ -43,6 +43,11 @@ parse_integers(const char *s, const char sep, size_t max_items, long *out); bool parse_integer_with_suffix(const char *s, long *out); +// search s in the list separated by sep +// for example, strlist_contains("a,bc,def", ',', "bc") returns true +bool +strlist_contains(const char *list, char sep, const char *s); + // return the index to truncate a UTF-8 string at a valid position size_t utf8_truncation_index(const char *utf8, size_t max_len); diff --git a/app/tests/test_strutil.c b/app/tests/test_strutil.c index ce0d5d30..dfd99658 100644 --- a/app/tests/test_strutil.c +++ b/app/tests/test_strutil.c @@ -287,6 +287,18 @@ static void test_parse_integer_with_suffix(void) { assert(!ok); } +static void test_strlist_contains(void) { + assert(strlist_contains("a,bc,def", ',', "bc")); + assert(!strlist_contains("a,bc,def", ',', "b")); + assert(strlist_contains("", ',', "")); + assert(strlist_contains("abc,", ',', "")); + assert(strlist_contains(",abc", ',', "")); + assert(strlist_contains("abc,,def", ',', "")); + assert(!strlist_contains("abc", ',', "")); + assert(strlist_contains(",,|x", '|', ",,")); + assert(strlist_contains("xyz", '\0', "xyz")); +} + int main(int argc, char *argv[]) { (void) argc; (void) argv; @@ -304,5 +316,6 @@ int main(int argc, char *argv[]) { test_parse_integer(); test_parse_integers(); test_parse_integer_with_suffix(); + test_strlist_contains(); return 0; } From 151bc166444ba1a8aaaaa651532f42e3a3741061 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Mon, 19 Apr 2021 09:28:28 +0200 Subject: [PATCH 388/450] Use strlist_contains() to find a muxer The AVOutputFormat name is a string list: it contains names separated by ',' (possibly only one). --- app/src/recorder.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/app/src/recorder.c b/app/src/recorder.c index e33d1a3b..91390ff1 100644 --- a/app/src/recorder.c +++ b/app/src/recorder.c @@ -4,6 +4,7 @@ #include #include "util/log.h" +#include "util/str_util.h" /** Downcast packet_sink to recorder */ #define DOWNCAST(SINK) container_of(SINK, struct recorder, packet_sink) @@ -22,8 +23,8 @@ find_muxer(const char *name) { #else oformat = av_oformat_next(oformat); #endif - // until null or with having the requested name - } while (oformat && strcmp(oformat->name, name)); + // until null or containing the requested name + } while (oformat && !strlist_contains(oformat->name, ',', name)); return oformat; } From fd0dc6c0cdc22a283e56d1852bbccc57138fb12d Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Mon, 19 Apr 2021 18:42:20 +0200 Subject: [PATCH 389/450] Add --lock-video-orientation=initial Add a new mode to the --lock-video-orientation option, to lock the initial orientation of the device. This avoids to pass an explicit value (0, 1, 2 or 3) and think about which is the right one. --- README.md | 1 + app/scrcpy.1 | 4 +-- app/src/cli.c | 26 ++++++++++++++----- app/src/scrcpy.h | 14 ++++++++-- .../java/com/genymobile/scrcpy/Device.java | 3 +++ .../com/genymobile/scrcpy/ScreenInfo.java | 6 +++++ 6 files changed, 44 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index d9aeb142..037c5828 100644 --- a/README.md +++ b/README.md @@ -198,6 +198,7 @@ If `--max-size` is also specified, resizing is applied after cropping. To lock the orientation of the mirroring: ```bash +scrcpy --lock-video-orientation initial # initial (current) orientation scrcpy --lock-video-orientation 0 # natural orientation scrcpy --lock-video-orientation 1 # 90° counterclockwise scrcpy --lock-video-orientation 2 # 180° diff --git a/app/scrcpy.1 b/app/scrcpy.1 index ea9bce9b..89b7ee75 100644 --- a/app/scrcpy.1 +++ b/app/scrcpy.1 @@ -84,9 +84,9 @@ This is a workaround for some devices not behaving as expected when setting the .TP .BI "\-\-lock\-video\-orientation " value -Lock video orientation to \fIvalue\fR. Possible values are -1 (unlocked), 0, 1, 2 and 3. Natural device orientation is 0, and each increment adds a 90 degrees otation counterclockwise. +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 otation counterclockwise. -Default is -1 (unlocked). +Default is "unlocked". .TP .BI "\-\-max\-fps " value diff --git a/app/src/cli.c b/app/src/cli.c index ec3c1294..05e178e0 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -81,10 +81,11 @@ scrcpy_print_usage(const char *arg0) { "\n" " --lock-video-orientation value\n" " Lock video orientation to value.\n" - " Possible values are -1 (unlocked), 0, 1, 2 and 3.\n" + " Possible values are \"unlocked\", \"initial\" (locked to the\n" + " initial orientation), 0, 1, 2 and 3.\n" " Natural device orientation is 0, and each increment adds a\n" " 90 degrees rotation counterclockwise.\n" - " Default is -1 (unlocked).\n" + " Default is \"unlocked\".\n" "\n" " --max-fps value\n" " Limit the frame rate of screen capture (officially supported\n" @@ -383,15 +384,27 @@ parse_max_fps(const char *s, uint16_t *max_fps) { } static bool -parse_lock_video_orientation(const char *s, int8_t *lock_video_orientation) { +parse_lock_video_orientation(const char *s, + enum sc_lock_video_orientation *lock_mode) { + if (!strcmp(s, "initial")) { + // Without argument, lock the initial orientation + *lock_mode = SC_LOCK_VIDEO_ORIENTATION_INITIAL; + return true; + } + + if (!strcmp(s, "unlocked")) { + *lock_mode = SC_LOCK_VIDEO_ORIENTATION_UNLOCKED; + return true; + } + long value; - bool ok = parse_integer_arg(s, &value, false, -1, 3, + bool ok = parse_integer_arg(s, &value, false, 0, 3, "lock video orientation"); if (!ok) { return false; } - *lock_video_orientation = (int8_t) value; + *lock_mode = (enum sc_lock_video_orientation) value; return true; } @@ -765,7 +778,8 @@ scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) { } break; case OPT_LOCK_VIDEO_ORIENTATION: - if (!parse_lock_video_orientation(optarg, &opts->lock_video_orientation)) { + if (!parse_lock_video_orientation(optarg, + &opts->lock_video_orientation)) { return false; } break; diff --git a/app/src/scrcpy.h b/app/src/scrcpy.h index f91cb6b8..7c2689ab 100644 --- a/app/src/scrcpy.h +++ b/app/src/scrcpy.h @@ -20,6 +20,16 @@ enum sc_record_format { SC_RECORD_FORMAT_MKV, }; +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, +}; + #define SC_MAX_SHORTCUT_MODS 8 enum sc_shortcut_mod { @@ -59,7 +69,7 @@ struct scrcpy_options { uint16_t max_size; uint32_t bit_rate; uint16_t max_fps; - int8_t lock_video_orientation; + enum sc_lock_video_orientation lock_video_orientation; uint8_t rotation; int16_t window_x; // SC_WINDOW_POSITION_UNDEFINED for "auto" int16_t window_y; // SC_WINDOW_POSITION_UNDEFINED for "auto" @@ -106,7 +116,7 @@ struct scrcpy_options { .max_size = 0, \ .bit_rate = DEFAULT_BIT_RATE, \ .max_fps = 0, \ - .lock_video_orientation = -1, \ + .lock_video_orientation = SC_LOCK_VIDEO_ORIENTATION_UNLOCKED, \ .rotation = 0, \ .window_x = SC_WINDOW_POSITION_UNDEFINED, \ .window_y = SC_WINDOW_POSITION_UNDEFINED, \ diff --git a/server/src/main/java/com/genymobile/scrcpy/Device.java b/server/src/main/java/com/genymobile/scrcpy/Device.java index a63976fd..2d67ab6f 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Device.java +++ b/server/src/main/java/com/genymobile/scrcpy/Device.java @@ -25,6 +25,9 @@ public final class Device { public static final int POWER_MODE_OFF = SurfaceControl.POWER_MODE_OFF; public static final int POWER_MODE_NORMAL = SurfaceControl.POWER_MODE_NORMAL; + public static final int LOCK_VIDEO_ORIENTATION_UNLOCKED = -1; + public static final int LOCK_VIDEO_ORIENTATION_INITIAL = -2; + private static final ServiceManager SERVICE_MANAGER = new ServiceManager(); public interface RotationListener { diff --git a/server/src/main/java/com/genymobile/scrcpy/ScreenInfo.java b/server/src/main/java/com/genymobile/scrcpy/ScreenInfo.java index 10acfb50..c27322ef 100644 --- a/server/src/main/java/com/genymobile/scrcpy/ScreenInfo.java +++ b/server/src/main/java/com/genymobile/scrcpy/ScreenInfo.java @@ -82,6 +82,12 @@ public final class ScreenInfo { public static ScreenInfo computeScreenInfo(DisplayInfo displayInfo, Rect crop, int maxSize, int lockedVideoOrientation) { int rotation = displayInfo.getRotation(); + + if (lockedVideoOrientation == Device.LOCK_VIDEO_ORIENTATION_INITIAL) { + // The user requested to lock the video orientation to the current orientation + lockedVideoOrientation = rotation; + } + Size deviceSize = displayInfo.getSize(); Rect contentRect = new Rect(0, 0, deviceSize.getWidth(), deviceSize.getHeight()); if (crop != null) { From 5af9d0ee0faddfca689f468c7d463c86233e8d93 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Mon, 19 Apr 2021 18:40:48 +0200 Subject: [PATCH 390/450] Make --lock-video-orientation argument optional If the option is set without argument, lock the initial device orientation (as if the value "initial" was passed). --- README.md | 2 +- app/scrcpy.1 | 4 +++- app/src/cli.c | 8 +++++--- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 037c5828..f162fb3d 100644 --- a/README.md +++ b/README.md @@ -198,7 +198,7 @@ If `--max-size` is also specified, resizing is applied after cropping. To lock the orientation of the mirroring: ```bash -scrcpy --lock-video-orientation initial # initial (current) orientation +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° diff --git a/app/scrcpy.1 b/app/scrcpy.1 index 89b7ee75..51c618a8 100644 --- a/app/scrcpy.1 +++ b/app/scrcpy.1 @@ -83,11 +83,13 @@ Inject computer clipboard text as a sequence of key events on Ctrl+v (like MOD+S This is a workaround for some devices not behaving as expected when setting the device clipboard programmatically. .TP -.BI "\-\-lock\-video\-orientation " value +.BI "\-\-lock\-video\-orientation " [value] 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 otation counterclockwise. Default is "unlocked". +Passing the option without argument is equivalent to passing "initial". + .TP .BI "\-\-max\-fps " value Limit the framerate of screen capture (officially supported since Android 10, but may work on earlier versions). diff --git a/app/src/cli.c b/app/src/cli.c index 05e178e0..6d5fd6b8 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -79,13 +79,15 @@ scrcpy_print_usage(const char *arg0) { " This is a workaround for some devices not behaving as\n" " expected when setting the device clipboard programmatically.\n" "\n" - " --lock-video-orientation value\n" + " --lock-video-orientation [value]\n" " Lock video orientation to value.\n" " Possible values are \"unlocked\", \"initial\" (locked to the\n" " initial orientation), 0, 1, 2 and 3.\n" " Natural device orientation is 0, and each increment adds a\n" " 90 degrees rotation counterclockwise.\n" " Default is \"unlocked\".\n" + " Passing the option without argument is equivalent to passing\n" + " \"initial\".\n" "\n" " --max-fps value\n" " Limit the frame rate of screen capture (officially supported\n" @@ -386,7 +388,7 @@ parse_max_fps(const char *s, uint16_t *max_fps) { static bool parse_lock_video_orientation(const char *s, enum sc_lock_video_orientation *lock_mode) { - if (!strcmp(s, "initial")) { + if (!s || !strcmp(s, "initial")) { // Without argument, lock the initial orientation *lock_mode = SC_LOCK_VIDEO_ORIENTATION_INITIAL; return true; @@ -693,7 +695,7 @@ scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) { {"fullscreen", no_argument, NULL, 'f'}, {"help", no_argument, NULL, 'h'}, {"legacy-paste", no_argument, NULL, OPT_LEGACY_PASTE}, - {"lock-video-orientation", required_argument, NULL, + {"lock-video-orientation", optional_argument, NULL, OPT_LOCK_VIDEO_ORIENTATION}, {"max-fps", required_argument, NULL, OPT_MAX_FPS}, {"max-size", required_argument, NULL, 'm'}, From d39161f7539b9d335e38385ffff5c3b4c21c7122 Mon Sep 17 00:00:00 2001 From: Marco Martinelli <6640057+martinellimarco@users.noreply.github.com> Date: Sun, 4 Apr 2021 00:10:44 +0200 Subject: [PATCH 391/450] Add support for v4l2loopback It allows to send the video stream to /dev/videoN, so that it can be captured (like a webcam) by any v4l2-capable tool. PR #2232 PR #2233 PR #2268 Co-authored-by: Romain Vimont --- app/meson.build | 12 ++ app/scrcpy.1 | 6 + app/src/cli.c | 31 ++++ app/src/decoder.h | 2 +- app/src/main.c | 14 ++ app/src/scrcpy.c | 35 ++++- app/src/scrcpy.h | 2 + app/src/v4l2_sink.c | 341 ++++++++++++++++++++++++++++++++++++++++++++ app/src/v4l2_sink.h | 39 +++++ 9 files changed, 480 insertions(+), 2 deletions(-) create mode 100644 app/src/v4l2_sink.c create mode 100644 app/src/v4l2_sink.h diff --git a/app/meson.build b/app/meson.build index c2a59741..c1dbf489 100644 --- a/app/meson.build +++ b/app/meson.build @@ -33,6 +33,11 @@ else src += [ 'src/sys/unix/process.c' ] endif +v4l2_support = host_machine.system() == 'linux' +if v4l2_support + src += [ 'src/v4l2_sink.c' ] +endif + check_functions = [ 'strdup' ] @@ -49,6 +54,10 @@ if not get_option('crossbuild_windows') dependency('sdl2'), ] + if v4l2_support + dependencies += dependency('libavdevice') + endif + else # cross-compile mingw32 build (from Linux to Windows) @@ -124,6 +133,9 @@ conf.set('SERVER_DEBUGGER', get_option('server_debugger')) # select the debugger method ('old' for Android < 9, 'new' for Android >= 9) conf.set('SERVER_DEBUGGER_METHOD_NEW', get_option('server_debugger_method') == 'new') +# enable V4L2 support (linux only) +conf.set('HAVE_V4L2', v4l2_support) + configure_file(configuration: conf, output: 'config.h') src_dir = include_directories('src') diff --git a/app/scrcpy.1 b/app/scrcpy.1 index 51c618a8..644d9bcc 100644 --- a/app/scrcpy.1 +++ b/app/scrcpy.1 @@ -185,6 +185,12 @@ Enable "show touches" on start, restore the initial value on exit. It only shows physical touches (not clicks from scrcpy). +.TP +.BI "\-\-v4l2_sink " /dev/videoN +Output to v4l2loopback device. + +It requires to lock the video orientation (see --lock-video-orientation). + .TP .BI "\-V, \-\-verbosity " value Set the log level ("debug", "info", "warn" or "error"). diff --git a/app/src/cli.c b/app/src/cli.c index 6d5fd6b8..16c56c6a 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -176,6 +176,13 @@ scrcpy_print_usage(const char *arg0) { " on exit.\n" " It only shows physical touches (not clicks from scrcpy).\n" "\n" +#ifdef HAVE_V4L2 + " --v4l2_sink /dev/videoN\n" + " Output to v4l2loopback device.\n" + " It requires to lock the video orientation (see\n" + " --lock-video-orientation).\n" + "\n" +#endif " -V, --verbosity value\n" " Set the log level (debug, info, warn or error).\n" #ifndef NDEBUG @@ -676,6 +683,7 @@ guess_record_format(const char *filename) { #define OPT_LEGACY_PASTE 1024 #define OPT_ENCODER_NAME 1025 #define OPT_POWER_OFF_ON_CLOSE 1026 +#define OPT_V4L2_SINK 1027 bool scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) { @@ -717,6 +725,9 @@ scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) { {"show-touches", no_argument, NULL, 't'}, {"stay-awake", no_argument, NULL, 'w'}, {"turn-screen-off", no_argument, NULL, 'S'}, +#ifdef HAVE_V4L2 + {"v4l2_sink", required_argument, NULL, OPT_V4L2_SINK}, +#endif {"verbosity", required_argument, NULL, 'V'}, {"version", no_argument, NULL, 'v'}, {"window-title", required_argument, NULL, OPT_WINDOW_TITLE}, @@ -901,16 +912,36 @@ scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) { case OPT_POWER_OFF_ON_CLOSE: opts->power_off_on_close = true; break; +#ifdef HAVE_V4L2 + case OPT_V4L2_SINK: + opts->v4l2_device = optarg; + break; +#endif default: // getopt prints the error message on stderr return false; } } +#ifdef HAVE_V4L2 + if (!opts->display && !opts->record_filename && !opts->v4l2_device) { + LOGE("-N/--no-display requires either screen recording (-r/--record)" + " or sink to v4l2loopback device (--v4l2_sink)"); + return false; + } + + if (opts->v4l2_device && opts->lock_video_orientation + == SC_LOCK_VIDEO_ORIENTATION_UNLOCKED) { + LOGI("Video orientation is locked for v4l2 sink. " + "See --lock-video-orientation."); + opts->lock_video_orientation = SC_LOCK_VIDEO_ORIENTATION_INITIAL; + } +#else if (!opts->display && !opts->record_filename) { LOGE("-N/--no-display requires screen recording (-r/--record)"); return false; } +#endif int index = optind; if (index < argc) { diff --git a/app/src/decoder.h b/app/src/decoder.h index bae97869..257f751a 100644 --- a/app/src/decoder.h +++ b/app/src/decoder.h @@ -8,7 +8,7 @@ #include #include -#define DECODER_MAX_SINKS 1 +#define DECODER_MAX_SINKS 2 struct decoder { struct sc_packet_sink packet_sink; // packet sink trait diff --git a/app/src/main.c b/app/src/main.c index e1e44f68..a468aed7 100644 --- a/app/src/main.c +++ b/app/src/main.c @@ -6,6 +6,9 @@ #include #include #include +#ifdef HAVE_V4L2 +# include +#endif #define SDL_MAIN_HANDLED // avoid link error on Linux Windows Subsystem #include @@ -28,6 +31,11 @@ print_version(void) { fprintf(stderr, " - libavutil %d.%d.%d\n", LIBAVUTIL_VERSION_MAJOR, LIBAVUTIL_VERSION_MINOR, LIBAVUTIL_VERSION_MICRO); +#ifdef HAVE_V4L2 + fprintf(stderr, " - libavdevice %d.%d.%d\n", LIBAVDEVICE_VERSION_MAJOR, + LIBAVDEVICE_VERSION_MINOR, + LIBAVDEVICE_VERSION_MICRO); +#endif } static SDL_LogPriority @@ -90,6 +98,12 @@ main(int argc, char *argv[]) { av_register_all(); #endif +#ifdef HAVE_V4L2 + if (args.opts.v4l2_device) { + avdevice_register_all(); + } +#endif + if (avformat_network_init()) { return 1; } diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index 4de62389..e847037e 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -27,6 +27,9 @@ #include "tiny_xpm.h" #include "util/log.h" #include "util/net.h" +#ifdef HAVE_V4L2 +# include "v4l2_sink.h" +#endif static struct server server; static struct screen screen; @@ -34,6 +37,9 @@ static struct fps_counter fps_counter; static struct stream stream; static struct decoder decoder; static struct recorder recorder; +#ifdef HAVE_V4L2 +static struct sc_v4l2_sink v4l2_sink; +#endif static struct controller controller; static struct file_handler file_handler; @@ -247,6 +253,9 @@ scrcpy(const struct scrcpy_options *options) { bool fps_counter_initialized = false; bool file_handler_initialized = false; bool recorder_initialized = false; +#ifdef HAVE_V4L2 + bool v4l2_sink_initialized = false; +#endif bool stream_started = false; bool controller_initialized = false; bool controller_started = false; @@ -295,7 +304,6 @@ scrcpy(const struct scrcpy_options *options) { goto end; } - struct decoder *dec = NULL; if (options->display) { if (!fps_counter_init(&fps_counter)) { goto end; @@ -309,7 +317,14 @@ scrcpy(const struct scrcpy_options *options) { } file_handler_initialized = true; } + } + struct decoder *dec = NULL; + bool needs_decoder = options->display; +#ifdef HAVE_V4L2 + needs_decoder |= !!options->v4l2_device; +#endif + if (needs_decoder) { decoder_init(&decoder); dec = &decoder; } @@ -386,6 +401,18 @@ scrcpy(const struct scrcpy_options *options) { } } +#ifdef HAVE_V4L2 + if (options->v4l2_device) { + if (!sc_v4l2_sink_init(&v4l2_sink, options->v4l2_device, frame_size)) { + goto end; + } + + decoder_add_sink(&decoder, &v4l2_sink.frame_sink); + + v4l2_sink_initialized = true; + } +#endif + // now we consumed the header values, the socket receives the video stream // start the stream if (!stream_start(&stream)) { @@ -426,6 +453,12 @@ end: stream_join(&stream); } +#ifdef HAVE_V4L2 + if (v4l2_sink_initialized) { + sc_v4l2_sink_destroy(&v4l2_sink); + } +#endif + // Destroy the screen only after the stream is guaranteed to be finished, // because otherwise the screen could receive new frames after destruction if (screen_initialized) { diff --git a/app/src/scrcpy.h b/app/src/scrcpy.h index 7c2689ab..405dc7f3 100644 --- a/app/src/scrcpy.h +++ b/app/src/scrcpy.h @@ -62,6 +62,7 @@ struct scrcpy_options { const char *render_driver; const char *codec_options; const char *encoder_name; + const char *v4l2_device; enum sc_log_level log_level; enum sc_record_format record_format; struct sc_port_range port_range; @@ -103,6 +104,7 @@ struct scrcpy_options { .render_driver = NULL, \ .codec_options = NULL, \ .encoder_name = NULL, \ + .v4l2_device = NULL, \ .log_level = SC_LOG_LEVEL_INFO, \ .record_format = SC_RECORD_FORMAT_AUTO, \ .port_range = { \ diff --git a/app/src/v4l2_sink.c b/app/src/v4l2_sink.c new file mode 100644 index 00000000..b7d06bb6 --- /dev/null +++ b/app/src/v4l2_sink.c @@ -0,0 +1,341 @@ +#include "v4l2_sink.h" + +#include "util/log.h" +#include "util/str_util.h" + +/** Downcast frame_sink to sc_v4l2_sink */ +#define DOWNCAST(SINK) container_of(SINK, struct sc_v4l2_sink, frame_sink) + +static const AVRational SCRCPY_TIME_BASE = {1, 1000000}; // timestamps in us + +static const AVOutputFormat * +find_muxer(const char *name) { +#ifdef SCRCPY_LAVF_HAS_NEW_MUXER_ITERATOR_API + void *opaque = NULL; +#endif + const AVOutputFormat *oformat = NULL; + do { +#ifdef SCRCPY_LAVF_HAS_NEW_MUXER_ITERATOR_API + oformat = av_muxer_iterate(&opaque); +#else + oformat = av_oformat_next(oformat); +#endif + // until null or containing the requested name + } while (oformat && !strlist_contains(oformat->name, ',', name)); + return oformat; +} + +static bool +write_header(struct sc_v4l2_sink *vs, const AVPacket *packet) { + AVStream *ostream = vs->format_ctx->streams[0]; + + uint8_t *extradata = av_malloc(packet->size * sizeof(uint8_t)); + if (!extradata) { + LOGC("Could not allocate extradata"); + return false; + } + + // copy the first packet to the extra data + memcpy(extradata, packet->data, packet->size); + + ostream->codecpar->extradata = extradata; + ostream->codecpar->extradata_size = packet->size; + + int ret = avformat_write_header(vs->format_ctx, NULL); + if (ret < 0) { + LOGE("Failed to write header to %s", vs->device_name); + return false; + } + + return true; +} + +static void +rescale_packet(struct sc_v4l2_sink *vs, AVPacket *packet) { + AVStream *ostream = vs->format_ctx->streams[0]; + av_packet_rescale_ts(packet, SCRCPY_TIME_BASE, ostream->time_base); +} + +static bool +write_packet(struct sc_v4l2_sink *vs, AVPacket *packet) { + if (!vs->header_written) { + bool ok = write_header(vs, packet); + if (!ok) { + return false; + } + vs->header_written = true; + return true; + } + + rescale_packet(vs, packet); + + bool ok = av_write_frame(vs->format_ctx, packet) >= 0; + + // Failing to write the last frame is not very serious, no future frame may + // depend on it, so the resulting file will still be valid + (void) ok; + + return true; +} + +static bool +encode_and_write_frame(struct sc_v4l2_sink *vs, const AVFrame *frame) { + int ret = avcodec_send_frame(vs->encoder_ctx, frame); + if (ret < 0 && ret != AVERROR(EAGAIN)) { + LOGE("Could not send v4l2 video frame: %d", ret); + return false; + } + + AVPacket *packet = &vs->packet; + ret = avcodec_receive_packet(vs->encoder_ctx, packet); + if (ret == 0) { + // A packet was received + + bool ok = write_packet(vs, packet); + if (!ok) { + LOGW("Could not send packet to v4l2 sink"); + return false; + } + av_packet_unref(packet); + } else if (ret != AVERROR(EAGAIN)) { + LOGE("Could not receive v4l2 video packet: %d", ret); + return false; + } + + return true; +} + +static int +run_v4l2_sink(void *data) { + struct sc_v4l2_sink *vs = data; + + for (;;) { + sc_mutex_lock(&vs->mutex); + + while (!vs->stopped && vs->vb.pending_frame_consumed) { + sc_cond_wait(&vs->cond, &vs->mutex); + } + + if (vs->stopped) { + sc_mutex_unlock(&vs->mutex); + break; + } + + sc_mutex_unlock(&vs->mutex); + + video_buffer_consume(&vs->vb, vs->frame); + bool ok = encode_and_write_frame(vs, vs->frame); + if (!ok) { + LOGE("Could not send frame to v4l2 sink"); + break; + } + } + + LOGD("V4l2 thread ended"); + + return 0; +} + +static bool +sc_v4l2_sink_open(struct sc_v4l2_sink *vs) { + bool ok = video_buffer_init(&vs->vb); + if (!ok) { + return false; + } + + ok = sc_mutex_init(&vs->mutex); + if (!ok) { + LOGC("Could not create mutex"); + goto error_video_buffer_destroy; + } + + ok = sc_cond_init(&vs->cond); + if (!ok) { + LOGC("Could not create cond"); + goto error_mutex_destroy; + } + + // FIXME + const AVOutputFormat *format = find_muxer("video4linux2,v4l2"); + if (!format) { + LOGE("Could not find v4l2 muxer"); + goto error_cond_destroy; + } + + const AVCodec *encoder = avcodec_find_encoder(AV_CODEC_ID_RAWVIDEO); + if (!encoder) { + LOGE("Raw video encoder not found"); + return false; + } + + vs->format_ctx = avformat_alloc_context(); + if (!vs->format_ctx) { + LOGE("Could not allocate v4l2 output context"); + return false; + } + + // contrary to the deprecated API (av_oformat_next()), av_muxer_iterate() + // returns (on purpose) a pointer-to-const, but AVFormatContext.oformat + // still expects a pointer-to-non-const (it has not be updated accordingly) + // + vs->format_ctx->oformat = (AVOutputFormat *) format; + vs->format_ctx->url = strdup(vs->device_name); + if (!vs->format_ctx->url) { + LOGE("Could not strdup v4l2 device name"); + goto error_avformat_free_context; + return false; + } + + AVStream *ostream = avformat_new_stream(vs->format_ctx, encoder); + if (!ostream) { + LOGE("Could not allocate new v4l2 stream"); + goto error_avformat_free_context; + return false; + } + + ostream->codecpar->codec_type = AVMEDIA_TYPE_VIDEO; + ostream->codecpar->codec_id = encoder->id; + ostream->codecpar->format = AV_PIX_FMT_YUV420P; + ostream->codecpar->width = vs->frame_size.width; + ostream->codecpar->height = vs->frame_size.height; + + int ret = avio_open(&vs->format_ctx->pb, vs->device_name, AVIO_FLAG_WRITE); + if (ret < 0) { + LOGE("Failed to open output device: %s", vs->device_name); + // ostream will be cleaned up during context cleaning + goto error_avformat_free_context; + } + + vs->encoder_ctx = avcodec_alloc_context3(encoder); + if (!vs->encoder_ctx) { + LOGC("Could not allocate codec context for v4l2"); + goto error_avio_close; + } + + vs->encoder_ctx->width = vs->frame_size.width; + vs->encoder_ctx->height = vs->frame_size.height; + vs->encoder_ctx->pix_fmt = AV_PIX_FMT_YUV420P; + vs->encoder_ctx->time_base.num = 1; + vs->encoder_ctx->time_base.den = 1; + + if (avcodec_open2(vs->encoder_ctx, encoder, NULL) < 0) { + LOGE("Could not open codec for v4l2"); + goto error_avcodec_free_context; + } + + vs->frame = av_frame_alloc(); + if (!vs->frame) { + LOGE("Could not create v4l2 frame"); + goto error_avcodec_close; + } + + LOGD("Starting v4l2 thread"); + ok = sc_thread_create(&vs->thread, run_v4l2_sink, "v4l2", vs); + if (!ok) { + LOGC("Could not start v4l2 thread"); + goto error_av_frame_free; + } + + vs->header_written = false; + vs->stopped = false; + + LOGI("v4l2 sink started to device: %s", vs->device_name); + + return true; + +error_av_frame_free: + av_frame_free(&vs->frame); +error_avcodec_close: + avcodec_close(vs->encoder_ctx); +error_avcodec_free_context: + avcodec_free_context(&vs->encoder_ctx); +error_avio_close: + avio_close(vs->format_ctx->pb); +error_avformat_free_context: + avformat_free_context(vs->format_ctx); +error_cond_destroy: + sc_cond_destroy(&vs->cond); +error_mutex_destroy: + sc_mutex_destroy(&vs->mutex); +error_video_buffer_destroy: + video_buffer_destroy(&vs->vb); + + return false; +} + +static void +sc_v4l2_sink_close(struct sc_v4l2_sink *vs) { + sc_mutex_lock(&vs->mutex); + vs->stopped = true; + sc_cond_signal(&vs->cond); + sc_mutex_unlock(&vs->mutex); + + sc_thread_join(&vs->thread, NULL); + + av_frame_free(&vs->frame); + avcodec_close(vs->encoder_ctx); + avcodec_free_context(&vs->encoder_ctx); + avio_close(vs->format_ctx->pb); + avformat_free_context(vs->format_ctx); + sc_cond_destroy(&vs->cond); + sc_mutex_destroy(&vs->mutex); + video_buffer_destroy(&vs->vb); +} + +static bool +sc_v4l2_sink_push(struct sc_v4l2_sink *vs, const AVFrame *frame) { + bool ok = video_buffer_push(&vs->vb, frame, NULL); + if (!ok) { + return false; + } + + // signal possible change of vs->vb.pending_frame_consumed + sc_cond_signal(&vs->cond); + + return true; +} + +static bool +sc_v4l2_frame_sink_open(struct sc_frame_sink *sink) { + struct sc_v4l2_sink *vs = DOWNCAST(sink); + return sc_v4l2_sink_open(vs); +} + +static void +sc_v4l2_frame_sink_close(struct sc_frame_sink *sink) { + struct sc_v4l2_sink *vs = DOWNCAST(sink); + sc_v4l2_sink_close(vs); +} + +static bool +sc_v4l2_frame_sink_push(struct sc_frame_sink *sink, const AVFrame *frame) { + struct sc_v4l2_sink *vs = DOWNCAST(sink); + return sc_v4l2_sink_push(vs, frame); +} + +bool +sc_v4l2_sink_init(struct sc_v4l2_sink *vs, const char *device_name, + struct size frame_size) { + vs->device_name = strdup(device_name); + if (!vs->device_name) { + LOGE("Could not strdup v4l2 device name"); + return false; + } + + vs->frame_size = frame_size; + + static const struct sc_frame_sink_ops ops = { + .open = sc_v4l2_frame_sink_open, + .close = sc_v4l2_frame_sink_close, + .push = sc_v4l2_frame_sink_push, + }; + + vs->frame_sink.ops = &ops; + + return true; +} + +void +sc_v4l2_sink_destroy(struct sc_v4l2_sink *vs) { + free(vs->device_name); +} diff --git a/app/src/v4l2_sink.h b/app/src/v4l2_sink.h new file mode 100644 index 00000000..9d2ee149 --- /dev/null +++ b/app/src/v4l2_sink.h @@ -0,0 +1,39 @@ +#ifndef SC_V4L2_SINK_H +#define SC_V4L2_SINK_H + +#include "common.h" + +#include "coords.h" +#include "trait/frame_sink.h" +#include "video_buffer.h" + +#include + +struct sc_v4l2_sink { + struct sc_frame_sink frame_sink; // frame sink trait + + struct video_buffer vb; + AVFormatContext *format_ctx; + AVCodecContext *encoder_ctx; + + char *device_name; + struct size frame_size; + + sc_thread thread; + sc_mutex mutex; + sc_cond cond; + bool stopped; + bool header_written; + + AVFrame *frame; + AVPacket packet; +}; + +bool +sc_v4l2_sink_init(struct sc_v4l2_sink *vs, const char *device_name, + struct size frame_size); + +void +sc_v4l2_sink_destroy(struct sc_v4l2_sink *vs); + +#endif From 41a0383d7ce42de79158ecd2f6eca3fd42602760 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Mon, 19 Apr 2021 20:12:08 +0200 Subject: [PATCH 392/450] Document v4l2 sink in README --- README.md | 56 ++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 55 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index f162fb3d..71eec7e2 100644 --- a/README.md +++ b/README.md @@ -226,7 +226,9 @@ error will give the available encoders: scrcpy --encoder _ ``` -### Recording +### Capture + +#### Recording It is possible to record the screen while mirroring: @@ -250,6 +252,58 @@ variation] does not impact the recorded file. [packet delay variation]: https://en.wikipedia.org/wiki/Packet_delay_variation +#### v4l2loopback + +On Linux, it is possible to send the video stream to a v4l2 loopback device, so +that the Android device can be opened like a webcam by any v4l2-capable tool. + +The module `v4l2loopback` must be installed: + +```bash +sudo apt install v4l2loopback-dkms +``` + +To create a v4l2 device: + +```bash +sudo modprobe v4l2loopback +``` + +This will create a new video device in `/dev/videoN`, where `N` is an integer +(more [options](https://github.com/umlaeute/v4l2loopback#options) are available +to create several devices or devices with specific IDs). + +To list the enabled devices: + +```bash +# requires v4l-utils package +v4l2-ctl --list-devices + +# simple but might be sufficient +ls /dev/video* +``` + +To start scrcpy using a v4l2 sink: + +```bash +scrcpy --v4l2-sink=/dev/videoN +scrcpy --v4l2-sink=/dev/videoN -N # --no-display to disable mirroring window +``` + +(replace `N` by the device ID, check with `ls /dev/video*`) + +Once enabled, you can open your video stream with a v4l2-capable tool: + +```bash +ffplay -i /dev/videoN +vlc v4l2:///dev/videoN # VLC might add some buffering delay +``` + +For example, you could capture the video within [OBS]. + +[OBS]: https://obsproject.com/fr + + ### Connection #### Wireless From 45e72801484deae6c931a4a79595b606227e547a Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Mon, 26 Apr 2021 17:59:35 +0200 Subject: [PATCH 393/450] Rename --v4l2_sink to --v4l2-sink This was a rebase issue, the previous version in #2268 was correct. Refs #2268 --- app/scrcpy.1 | 2 +- app/src/cli.c | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/scrcpy.1 b/app/scrcpy.1 index 644d9bcc..7ef69640 100644 --- a/app/scrcpy.1 +++ b/app/scrcpy.1 @@ -186,7 +186,7 @@ Enable "show touches" on start, restore the initial value on exit. It only shows physical touches (not clicks from scrcpy). .TP -.BI "\-\-v4l2_sink " /dev/videoN +.BI "\-\-v4l2-sink " /dev/videoN Output to v4l2loopback device. It requires to lock the video orientation (see --lock-video-orientation). diff --git a/app/src/cli.c b/app/src/cli.c index 16c56c6a..841b7c39 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -177,7 +177,7 @@ scrcpy_print_usage(const char *arg0) { " It only shows physical touches (not clicks from scrcpy).\n" "\n" #ifdef HAVE_V4L2 - " --v4l2_sink /dev/videoN\n" + " --v4l2-sink /dev/videoN\n" " Output to v4l2loopback device.\n" " It requires to lock the video orientation (see\n" " --lock-video-orientation).\n" @@ -726,7 +726,7 @@ scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) { {"stay-awake", no_argument, NULL, 'w'}, {"turn-screen-off", no_argument, NULL, 'S'}, #ifdef HAVE_V4L2 - {"v4l2_sink", required_argument, NULL, OPT_V4L2_SINK}, + {"v4l2-sink", required_argument, NULL, OPT_V4L2_SINK}, #endif {"verbosity", required_argument, NULL, 'V'}, {"version", no_argument, NULL, 'v'}, @@ -926,7 +926,7 @@ scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) { #ifdef HAVE_V4L2 if (!opts->display && !opts->record_filename && !opts->v4l2_device) { LOGE("-N/--no-display requires either screen recording (-r/--record)" - " or sink to v4l2loopback device (--v4l2_sink)"); + " or sink to v4l2loopback device (--v4l2-sink)"); return false; } From 1cde68a1fa01a9b37eeede3310edf163d4e79970 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Mon, 26 Apr 2021 18:00:47 +0200 Subject: [PATCH 394/450] Fix v4l2 AVFrame memory leak Unref frame immediately once encoded. Fixes #2279 --- app/src/v4l2_sink.c | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/v4l2_sink.c b/app/src/v4l2_sink.c index b7d06bb6..9b4a9c85 100644 --- a/app/src/v4l2_sink.c +++ b/app/src/v4l2_sink.c @@ -125,6 +125,7 @@ run_v4l2_sink(void *data) { video_buffer_consume(&vs->vb, vs->frame); bool ok = encode_and_write_frame(vs, vs->frame); + av_frame_unref(vs->frame); if (!ok) { LOGE("Could not send frame to v4l2 sink"); break; From 84f17fdeab518cbe9f9c1a5fdf7dc50981c01806 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Mon, 26 Apr 2021 18:05:11 +0200 Subject: [PATCH 395/450] Fix v4l2 AVPacket memory leak on error Unref v4l2 AVPacket even if writing failed. --- app/src/v4l2_sink.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/v4l2_sink.c b/app/src/v4l2_sink.c index 9b4a9c85..fd0bda12 100644 --- a/app/src/v4l2_sink.c +++ b/app/src/v4l2_sink.c @@ -92,11 +92,11 @@ encode_and_write_frame(struct sc_v4l2_sink *vs, const AVFrame *frame) { // A packet was received bool ok = write_packet(vs, packet); + av_packet_unref(packet); if (!ok) { LOGW("Could not send packet to v4l2 sink"); return false; } - av_packet_unref(packet); } else if (ret != AVERROR(EAGAIN)) { LOGE("Could not receive v4l2 video packet: %d", ret); return false; From ae6ec7a194257ddc72b8fa55f8a39486fb01b4c2 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Mon, 26 Apr 2021 18:05:43 +0200 Subject: [PATCH 396/450] Unref decoder AVFrame immediately The frame can be unref immediately after it is pushed to the frame sinks. It was not really a memory leak because the frame was unref every time by avcodec_receive_frame() (and freed on close), but a reference was unnecessarily kept for too long. --- app/src/decoder.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/src/decoder.c b/app/src/decoder.c index 476158d9..dbaa3d39 100644 --- a/app/src/decoder.c +++ b/app/src/decoder.c @@ -111,6 +111,8 @@ decoder_push(struct decoder *decoder, const AVPacket *packet) { // A frame lost should not make the whole pipeline fail. The error, if // any, is already logged. (void) ok; + + av_frame_unref(decoder->frame); } else if (ret != AVERROR(EAGAIN)) { LOGE("Could not receive video frame: %d", ret); return false; From d00ee640c0704412322aa4652cdccf5f3f3f9250 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 30 Apr 2021 22:57:42 +0200 Subject: [PATCH 397/450] Simplify Device.java Remove useless intermediate method with a "mode" parameter (it's always called with the same mode). This also avoids the need for a specific injectEventOnDisplay() method, since it does not conflict with another injectEvent() method anymore. --- .../java/com/genymobile/scrcpy/Device.java | 22 +++++-------------- 1 file changed, 5 insertions(+), 17 deletions(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/Device.java b/server/src/main/java/com/genymobile/scrcpy/Device.java index 2d67ab6f..7d1d3aa8 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Device.java +++ b/server/src/main/java/com/genymobile/scrcpy/Device.java @@ -164,39 +164,27 @@ public final class Device { return supportsInputEvents; } - public static boolean injectEvent(InputEvent inputEvent, int mode, int displayId) { + public static boolean injectEvent(InputEvent inputEvent, int displayId) { if (!supportsInputEvents(displayId)) { - return false; + throw new AssertionError("Could not inject input event if !supportsInputEvents()"); } if (displayId != 0 && !InputManager.setDisplayId(inputEvent, displayId)) { return false; } - return SERVICE_MANAGER.getInputManager().injectInputEvent(inputEvent, mode); - } - - public boolean injectEvent(InputEvent inputEvent, int mode) { - if (!supportsInputEvents()) { - throw new AssertionError("Could not inject input event if !supportsInputEvents()"); - } - - return injectEvent(inputEvent, mode, displayId); - } - - public static boolean injectEventOnDisplay(InputEvent event, int displayId) { - return injectEvent(event, InputManager.INJECT_INPUT_EVENT_MODE_ASYNC, displayId); + return SERVICE_MANAGER.getInputManager().injectInputEvent(inputEvent, InputManager.INJECT_INPUT_EVENT_MODE_ASYNC); } public boolean injectEvent(InputEvent event) { - return injectEvent(event, InputManager.INJECT_INPUT_EVENT_MODE_ASYNC); + return injectEvent(event, displayId); } public static boolean injectKeyEvent(int action, int keyCode, int repeat, int metaState, int displayId) { long now = SystemClock.uptimeMillis(); KeyEvent event = new KeyEvent(now, now, action, keyCode, repeat, metaState, KeyCharacterMap.VIRTUAL_KEYBOARD, 0, 0, InputDevice.SOURCE_KEYBOARD); - return injectEventOnDisplay(event, displayId); + return injectEvent(event, displayId); } public boolean injectKeyEvent(int action, int keyCode, int repeat, int metaState) { From 9a7d351d67396363f0d78473842d321f6c764877 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 30 Apr 2021 23:01:58 +0200 Subject: [PATCH 398/450] Simplify non-static injectEvent() implementation Just call the static version (having a displayId) from the non-static version (using the displayId field). --- server/src/main/java/com/genymobile/scrcpy/Device.java | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/Device.java b/server/src/main/java/com/genymobile/scrcpy/Device.java index 7d1d3aa8..4374ffc5 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Device.java +++ b/server/src/main/java/com/genymobile/scrcpy/Device.java @@ -188,10 +188,7 @@ public final class Device { } public boolean injectKeyEvent(int action, int keyCode, int repeat, int metaState) { - long now = SystemClock.uptimeMillis(); - KeyEvent event = new KeyEvent(now, now, action, keyCode, repeat, metaState, KeyCharacterMap.VIRTUAL_KEYBOARD, 0, 0, - InputDevice.SOURCE_KEYBOARD); - return injectEvent(event); + return injectKeyEvent(action, keyCode, repeat, metaState, displayId); } public static boolean injectKeycode(int keyCode, int displayId) { @@ -199,7 +196,7 @@ public final class Device { } public boolean injectKeycode(int keyCode) { - return injectKeyEvent(KeyEvent.ACTION_DOWN, keyCode, 0, 0) && injectKeyEvent(KeyEvent.ACTION_UP, keyCode, 0, 0); + return injectKeycode(keyCode, displayId); } public static boolean isScreenOn() { From 233f8e6cc4e84baaf3761ea61a60ba802aafeb88 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 30 Apr 2021 23:05:15 +0200 Subject: [PATCH 399/450] Rename keycode injection method Make it explicit that it injects both "press" and "release" events. --- .../src/main/java/com/genymobile/scrcpy/Controller.java | 6 +++--- server/src/main/java/com/genymobile/scrcpy/Device.java | 8 ++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/Controller.java b/server/src/main/java/com/genymobile/scrcpy/Controller.java index 3760bbd4..92986241 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Controller.java +++ b/server/src/main/java/com/genymobile/scrcpy/Controller.java @@ -55,7 +55,7 @@ public class Controller { public void control() throws IOException { // on start, power on the device if (!Device.isScreenOn()) { - device.injectKeycode(KeyEvent.KEYCODE_POWER); + device.pressReleaseKeycode(KeyEvent.KEYCODE_POWER); // dirty hack // After POWER is injected, the device is powered on asynchronously. @@ -273,7 +273,7 @@ public class Controller { if (keepPowerModeOff) { schedulePowerModeOff(); } - return device.injectKeycode(KeyEvent.KEYCODE_POWER); + return device.pressReleaseKeycode(KeyEvent.KEYCODE_POWER); } private boolean setClipboard(String text, boolean paste) { @@ -284,7 +284,7 @@ public class Controller { // On Android >= 7, also press the PASTE key if requested if (paste && Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && device.supportsInputEvents()) { - device.injectKeycode(KeyEvent.KEYCODE_PASTE); + device.pressReleaseKeycode(KeyEvent.KEYCODE_PASTE); } return ok; diff --git a/server/src/main/java/com/genymobile/scrcpy/Device.java b/server/src/main/java/com/genymobile/scrcpy/Device.java index 4374ffc5..3e71fe9c 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Device.java +++ b/server/src/main/java/com/genymobile/scrcpy/Device.java @@ -191,12 +191,12 @@ public final class Device { return injectKeyEvent(action, keyCode, repeat, metaState, displayId); } - public static boolean injectKeycode(int keyCode, int displayId) { + public static boolean pressReleaseKeycode(int keyCode, int displayId) { return injectKeyEvent(KeyEvent.ACTION_DOWN, keyCode, 0, 0, displayId) && injectKeyEvent(KeyEvent.ACTION_UP, keyCode, 0, 0, displayId); } - public boolean injectKeycode(int keyCode) { - return injectKeycode(keyCode, displayId); + public boolean pressReleaseKeycode(int keyCode) { + return pressReleaseKeycode(keyCode, displayId); } public static boolean isScreenOn() { @@ -272,7 +272,7 @@ public final class Device { if (!isScreenOn()) { return true; } - return injectKeycode(KeyEvent.KEYCODE_POWER, displayId); + return pressReleaseKeycode(KeyEvent.KEYCODE_POWER, displayId); } /** From 1b9dcce23c8f52aa1ba97fe30fad83930c60578d Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Mon, 3 May 2021 20:39:49 +0200 Subject: [PATCH 400/450] Fix double-free on error On error, server->serial was freed twice: immediately and in server_destroy(). Refs #2292 --- app/src/server.c | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/app/src/server.c b/app/src/server.c index a4bba33c..276e0bd6 100644 --- a/app/src/server.c +++ b/app/src/server.c @@ -417,18 +417,19 @@ server_start(struct server *server, const char *serial, } if (!push_server(serial)) { - goto error1; + /* server->serial will be freed on server_destroy() */ + return false; } if (!enable_tunnel_any_port(server, params->port_range, params->force_adb_forward)) { - goto error1; + return false; } // server will connect to our server socket server->process = execute_server(server, params); if (server->process == PROCESS_NONE) { - goto error2; + goto error; } // If the server process dies before connecting to the server socket, then @@ -442,14 +443,14 @@ server_start(struct server *server, const char *serial, if (!ok) { process_terminate(server->process); process_wait(server->process, true); // ignore exit code - goto error2; + goto error; } server->tunnel_enabled = true; return true; -error2: +error: if (!server->tunnel_forward) { bool was_closed = atomic_flag_test_and_set(&server->server_socket_closed); @@ -459,8 +460,7 @@ error2: close_socket(server->server_socket); } disable_tunnel(server); -error1: - free(server->serial); + return false; } From 644a5ef14ae9559ca72365c37d8f6b63edbdbd6f Mon Sep 17 00:00:00 2001 From: Haren S Date: Thu, 6 May 2021 17:56:41 +0100 Subject: [PATCH 401/450] Add MacPorts documentation PR #2299 Signed-off-by: Romain Vimont --- README.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/README.md b/README.md index 7a4b7d41..21f789d9 100644 --- a/README.md +++ b/README.md @@ -130,6 +130,15 @@ You need `adb`, accessible from your `PATH`. If you don't have it yet: brew install android-platform-tools ``` +It's also available in [MacPorts], which sets up adb for you: + +```bash +sudo port install scrcpy +``` + +[MacPorts]: https://www.macports.org/ + + You can also [build the app manually][BUILD]. From 8fb5715740e52bcb2ba8d4bac1e53f4ef76ef688 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 9 May 2021 10:49:46 +0200 Subject: [PATCH 402/450] Add libavdevice-dev in BUILD instructions On Linux, scrcpy now depends on libavdevice for v4l2. --- BUILD.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/BUILD.md b/BUILD.md index 4dc6d64c..d5aead0f 100644 --- a/BUILD.md +++ b/BUILD.md @@ -13,8 +13,8 @@ First, you need to install the required packages: ```bash # for Debian/Ubuntu sudo apt install ffmpeg libsdl2-2.0-0 adb wget \ - gcc git pkg-config meson ninja-build \ - libavcodec-dev libavformat-dev libavutil-dev libsdl2-dev + gcc git pkg-config meson ninja-build libsdl2-dev \ + libavcodec-dev libavdevice-dev libavformat-dev libavutil-dev ``` Then clone the repo and execute the installation script @@ -91,8 +91,8 @@ Install the required packages from your package manager. sudo apt install ffmpeg libsdl2-2.0-0 adb # client build dependencies -sudo apt install gcc git pkg-config meson ninja-build \ - libavcodec-dev libavformat-dev libavutil-dev libsdl2-dev +sudo apt install gcc git pkg-config meson ninja-build libsdl2-dev \ + libavcodec-dev libavdevice-dev libavformat-dev libavutil-dev # server build dependencies sudo apt install openjdk-11-jdk From 1e64f0f931c2484ea6955205b83703776c03992b Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 9 May 2021 11:06:02 +0200 Subject: [PATCH 403/450] Use ARRAY_LEN() macro --- app/src/server.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/server.c b/app/src/server.c index 276e0bd6..4448ab95 100644 --- a/app/src/server.c +++ b/app/src/server.c @@ -306,7 +306,7 @@ execute_server(struct server *server, const struct server_params *params) { // Port: 5005 // Then click on "Debug" #endif - return adb_execute(server->serial, cmd, sizeof(cmd) / sizeof(cmd[0])); + return adb_execute(server->serial, cmd, ARRAY_LEN(cmd)); } static socket_t From 42b3d1e66e91aa4c07e8c9ab5e8bb71ba309abbe Mon Sep 17 00:00:00 2001 From: LYK Date: Fri, 14 May 2021 12:07:48 +0900 Subject: [PATCH 404/450] Reformat README.ko.md PR #2311 Signed-off-by: Romain Vimont --- README.ko.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.ko.md b/README.ko.md index 2da50562..31e38c6f 100644 --- a/README.ko.md +++ b/README.ko.md @@ -112,7 +112,7 @@ scrcpy --help ### 캡쳐 환경 설정 -###사이즈 재정의 +### 사이즈 재정의 가끔씩 성능을 향상시키기위해 안드로이드 디바이스를 낮은 해상도에서 미러링하는 것이 유용할 때도 있습니다. @@ -136,7 +136,7 @@ scrcpy --bit-rate 2M scrcpy -b 2M # 축약 버전 ``` -###프레임 비율 제한 +### 프레임 비율 제한 안드로이드 버전 10이상의 디바이스에서는, 다음의 명령어로 캡쳐 화면의 프레임 비율을 제한할 수 있습니다: From 72081a241b85edc64569ece05d2178018e4913ca Mon Sep 17 00:00:00 2001 From: Alberto Pasqualetto Date: Fri, 14 May 2021 14:57:38 +0200 Subject: [PATCH 405/450] Fix visualization of comment in code block PR #2315 Signed-off-by: Romain Vimont --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 21f789d9..e0d40984 100644 --- a/README.md +++ b/README.md @@ -460,7 +460,7 @@ scrcpy --display 1 The list of display ids can be retrieved by: -``` +```bash adb shell dumpsys display # search "mDisplayId=" in the output ``` From 83116fc19984bbed9da0200e341d140626fee3fd Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 16 May 2021 15:32:31 +0200 Subject: [PATCH 406/450] Notify end-of-stream via callback Do not send a SDL event directly, to make stream independent of SDL. --- app/src/scrcpy.c | 15 ++++++++++++++- app/src/stream.c | 19 +++++++++---------- app/src/stream.h | 11 +++++++++-- 3 files changed, 32 insertions(+), 13 deletions(-) diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index e847037e..1cb66bc1 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -241,6 +241,16 @@ av_log_callback(void *avcl, int level, const char *fmt, va_list vl) { free(local_fmt); } +static void +stream_on_eos(struct stream *stream, void *userdata) { + (void) stream; + (void) userdata; + + SDL_Event stop_event; + stop_event.type = EVENT_STREAM_STOPPED; + SDL_PushEvent(&stop_event); +} + bool scrcpy(const struct scrcpy_options *options) { if (!server_init(&server)) { @@ -343,7 +353,10 @@ scrcpy(const struct scrcpy_options *options) { av_log_set_callback(av_log_callback); - stream_init(&stream, server.video_socket); + const struct stream_callbacks stream_cbs = { + .on_eos = stream_on_eos, + }; + stream_init(&stream, server.video_socket, &stream_cbs, NULL); if (dec) { stream_add_sink(&stream, &dec->packet_sink); diff --git a/app/src/stream.c b/app/src/stream.c index a11218e3..2d9a0ab4 100644 --- a/app/src/stream.c +++ b/app/src/stream.c @@ -3,7 +3,6 @@ #include #include #include -#include #include #include "decoder.h" @@ -58,13 +57,6 @@ stream_recv_packet(struct stream *stream, AVPacket *packet) { return true; } -static void -notify_stopped(void) { - SDL_Event stop_event; - stop_event.type = EVENT_STREAM_STOPPED; - SDL_PushEvent(&stop_event); -} - static bool push_packet_to_sinks(struct stream *stream, const AVPacket *packet) { for (unsigned i = 0; i < stream->sink_count; ++i) { @@ -251,15 +243,22 @@ finally_close_sinks: finally_free_codec_ctx: avcodec_free_context(&stream->codec_ctx); end: - notify_stopped(); + stream->cbs->on_eos(stream, stream->cbs_userdata); + return 0; } void -stream_init(struct stream *stream, socket_t socket) { +stream_init(struct stream *stream, socket_t socket, + const struct stream_callbacks *cbs, void *cbs_userdata) { stream->socket = socket; stream->has_pending = false; stream->sink_count = 0; + + assert(cbs && cbs->on_eos); + + stream->cbs = cbs; + stream->cbs_userdata = cbs_userdata; } void diff --git a/app/src/stream.h b/app/src/stream.h index 81175420..9fc4d1e1 100644 --- a/app/src/stream.h +++ b/app/src/stream.h @@ -6,7 +6,6 @@ #include #include #include -#include #include "trait/packet_sink.h" #include "util/net.h" @@ -27,10 +26,18 @@ struct stream { // packet is available bool has_pending; AVPacket pending; + + const struct stream_callbacks *cbs; + void *cbs_userdata; +}; + +struct stream_callbacks { + void (*on_eos)(struct stream *stream, void *userdata); }; void -stream_init(struct stream *stream, socket_t socket); +stream_init(struct stream *stream, socket_t socket, + const struct stream_callbacks *cbs, void *cbs_userdata); void stream_add_sink(struct stream *stream, struct sc_packet_sink *sink); From f19c455110fb7d11864692208458591ab06f622d Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 16 May 2021 15:54:02 +0200 Subject: [PATCH 407/450] Fix leak on error Destroy video buffer if screen window creation failed. --- app/src/screen.c | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/screen.c b/app/src/screen.c index 0598ccf4..55a832ef 100644 --- a/app/src/screen.c +++ b/app/src/screen.c @@ -344,6 +344,7 @@ screen_init(struct screen *screen, struct fps_counter *fps_counter, window_flags); if (!screen->window) { LOGC("Could not create window: %s", SDL_GetError()); + video_buffer_destroy(&screen->vb); return false; } From e604e8a752f1a041bea2c782223e2b2d6769bfa7 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 16 May 2021 18:26:20 +0200 Subject: [PATCH 408/450] Move fps_counter to screen The FPS counter specifically count frames from the screen video buffer, so it is specific to the screen. --- app/src/fps_counter.c | 4 ++++ app/src/input_manager.c | 2 +- app/src/input_manager.h | 1 - app/src/scrcpy.c | 31 ++++++++----------------------- app/src/screen.c | 30 ++++++++++++++++++++++++------ app/src/screen.h | 14 +++++++++++--- 6 files changed, 48 insertions(+), 34 deletions(-) diff --git a/app/src/fps_counter.c b/app/src/fps_counter.c index 281c58cf..bbf71887 100644 --- a/app/src/fps_counter.c +++ b/app/src/fps_counter.c @@ -150,6 +150,10 @@ fps_counter_interrupt(struct fps_counter *counter) { void fps_counter_join(struct fps_counter *counter) { if (counter->thread_started) { + // interrupted must be set by the thread calling join(), so no need to + // lock for the assertion + assert(counter->interrupted); + sc_thread_join(&counter->thread, NULL); } } diff --git a/app/src/input_manager.c b/app/src/input_manager.c index 94086616..55b5fae2 100644 --- a/app/src/input_manager.c +++ b/app/src/input_manager.c @@ -517,7 +517,7 @@ input_manager_process_key(struct input_manager *im, return; case SDLK_i: if (!shift && !repeat && down) { - switch_fps_counter_state(im->fps_counter); + switch_fps_counter_state(&im->screen->fps_counter); } return; case SDLK_n: diff --git a/app/src/input_manager.h b/app/src/input_manager.h index 5c7e2b91..76690757 100644 --- a/app/src/input_manager.h +++ b/app/src/input_manager.h @@ -14,7 +14,6 @@ struct input_manager { struct controller *controller; - struct fps_counter *fps_counter; struct screen *screen; // SDL reports repeated events as a boolean, but Android expects the actual diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index 1cb66bc1..e7a349d8 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -18,7 +18,6 @@ #include "device.h" #include "events.h" #include "file_handler.h" -#include "fps_counter.h" #include "input_manager.h" #include "recorder.h" #include "screen.h" @@ -33,7 +32,6 @@ static struct server server; static struct screen screen; -static struct fps_counter fps_counter; static struct stream stream; static struct decoder decoder; static struct recorder recorder; @@ -45,7 +43,6 @@ static struct file_handler file_handler; static struct input_manager input_manager = { .controller = &controller, - .fps_counter = &fps_counter, .screen = &screen, .repeat = 0, @@ -260,7 +257,6 @@ scrcpy(const struct scrcpy_options *options) { bool ret = false; bool server_started = false; - bool fps_counter_initialized = false; bool file_handler_initialized = false; bool recorder_initialized = false; #ifdef HAVE_V4L2 @@ -314,19 +310,12 @@ scrcpy(const struct scrcpy_options *options) { goto end; } - if (options->display) { - if (!fps_counter_init(&fps_counter)) { + if (options->display && options->control) { + if (!file_handler_init(&file_handler, server.serial, + options->push_target)) { goto end; } - fps_counter_initialized = true; - - if (options->control) { - if (!file_handler_init(&file_handler, server.serial, - options->push_target)) { - goto end; - } - file_handler_initialized = true; - } + file_handler_initialized = true; } struct decoder *dec = NULL; @@ -396,7 +385,7 @@ scrcpy(const struct scrcpy_options *options) { .fullscreen = options->fullscreen, }; - if (!screen_init(&screen, &fps_counter, &screen_params)) { + if (!screen_init(&screen, &screen_params)) { goto end; } screen_initialized = true; @@ -451,8 +440,8 @@ end: if (file_handler_initialized) { file_handler_stop(&file_handler); } - if (fps_counter_initialized) { - fps_counter_interrupt(&fps_counter); + if (screen_initialized) { + screen_interrupt(&screen); } if (server_started) { @@ -475,6 +464,7 @@ end: // Destroy the screen only after the stream is guaranteed to be finished, // because otherwise the screen could receive new frames after destruction if (screen_initialized) { + screen_join(&screen); screen_destroy(&screen); } @@ -494,11 +484,6 @@ end: file_handler_destroy(&file_handler); } - if (fps_counter_initialized) { - fps_counter_join(&fps_counter); - fps_counter_destroy(&fps_counter); - } - server_destroy(&server); return ret; diff --git a/app/src/screen.c b/app/src/screen.c index 55a832ef..12ad7d0a 100644 --- a/app/src/screen.c +++ b/app/src/screen.c @@ -277,7 +277,7 @@ screen_frame_sink_push(struct sc_frame_sink *sink, const AVFrame *frame) { } if (previous_frame_skipped) { - fps_counter_add_skipped_frame(screen->fps_counter); + fps_counter_add_skipped_frame(&screen->fps_counter); // The EVENT_NEW_FRAME triggered for the previous frame will consume // this new frame instead } else { @@ -293,10 +293,7 @@ screen_frame_sink_push(struct sc_frame_sink *sink, const AVFrame *frame) { } bool -screen_init(struct screen *screen, struct fps_counter *fps_counter, - const struct screen_params *params) { - screen->fps_counter = fps_counter; - +screen_init(struct screen *screen, const struct screen_params *params) { screen->resize_pending = false; screen->has_frame = false; screen->fullscreen = false; @@ -308,6 +305,12 @@ screen_init(struct screen *screen, struct fps_counter *fps_counter, return false; } + if (!fps_counter_init(&screen->fps_counter)) { + LOGE("Could not initialize FPS counter"); + video_buffer_destroy(&screen->vb); + return false; + } + screen->frame_size = params->frame_size; screen->rotation = params->rotation; if (screen->rotation) { @@ -344,6 +347,7 @@ screen_init(struct screen *screen, struct fps_counter *fps_counter, window_flags); if (!screen->window) { LOGC("Could not create window: %s", SDL_GetError()); + fps_counter_destroy(&screen->fps_counter); video_buffer_destroy(&screen->vb); return false; } @@ -353,6 +357,7 @@ screen_init(struct screen *screen, struct fps_counter *fps_counter, if (!screen->renderer) { LOGC("Could not create renderer: %s", SDL_GetError()); SDL_DestroyWindow(screen->window); + fps_counter_destroy(&screen->fps_counter); video_buffer_destroy(&screen->vb); return false; } @@ -405,6 +410,7 @@ screen_init(struct screen *screen, struct fps_counter *fps_counter, LOGC("Could not create texture: %s", SDL_GetError()); SDL_DestroyRenderer(screen->renderer); SDL_DestroyWindow(screen->window); + fps_counter_destroy(&screen->fps_counter); video_buffer_destroy(&screen->vb); return false; } @@ -415,6 +421,7 @@ screen_init(struct screen *screen, struct fps_counter *fps_counter, SDL_DestroyTexture(screen->texture); SDL_DestroyRenderer(screen->renderer); SDL_DestroyWindow(screen->window); + fps_counter_destroy(&screen->fps_counter); video_buffer_destroy(&screen->vb); return false; } @@ -459,6 +466,16 @@ screen_hide_window(struct screen *screen) { SDL_HideWindow(screen->window); } +void +screen_interrupt(struct screen *screen) { + fps_counter_interrupt(&screen->fps_counter); +} + +void +screen_join(struct screen *screen) { + fps_counter_join(&screen->fps_counter); +} + void screen_destroy(struct screen *screen) { #ifndef NDEBUG @@ -468,6 +485,7 @@ screen_destroy(struct screen *screen) { SDL_DestroyTexture(screen->texture); SDL_DestroyRenderer(screen->renderer); SDL_DestroyWindow(screen->window); + fps_counter_destroy(&screen->fps_counter); video_buffer_destroy(&screen->vb); } @@ -577,7 +595,7 @@ screen_update_frame(struct screen *screen) { video_buffer_consume(&screen->vb, screen->frame); AVFrame *frame = screen->frame; - fps_counter_add_rendered_frame(screen->fps_counter); + fps_counter_add_rendered_frame(&screen->fps_counter); struct size new_frame_size = {frame->width, frame->height}; if (!prepare_for_frame(screen, new_frame_size)) { diff --git a/app/src/screen.h b/app/src/screen.h index 2921c701..e2a43da7 100644 --- a/app/src/screen.h +++ b/app/src/screen.h @@ -20,7 +20,7 @@ struct screen { #endif struct video_buffer vb; - struct fps_counter *fps_counter; + struct fps_counter fps_counter; SDL_Window *window; SDL_Renderer *renderer; @@ -66,8 +66,16 @@ struct screen_params { // initialize screen, create window, renderer and texture (window is hidden) bool -screen_init(struct screen *screen, struct fps_counter *fps_counter, - const struct screen_params *params); +screen_init(struct screen *screen, const struct screen_params *params); + +// request to interrupt any inner thread +// must be called before screen_join() +void +screen_interrupt(struct screen *screen); + +// join any inner thread +void +screen_join(struct screen *screen); // destroy window, renderer and texture (if any) void From dcee7c0f7f2fe5709dc947fa6bc4ff4cdc07654e Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 16 May 2021 18:36:07 +0200 Subject: [PATCH 409/450] Factorize screen_init() error management --- app/src/screen.c | 38 ++++++++++++++++++-------------------- 1 file changed, 18 insertions(+), 20 deletions(-) diff --git a/app/src/screen.c b/app/src/screen.c index 12ad7d0a..a09831a3 100644 --- a/app/src/screen.c +++ b/app/src/screen.c @@ -307,8 +307,7 @@ screen_init(struct screen *screen, const struct screen_params *params) { if (!fps_counter_init(&screen->fps_counter)) { LOGE("Could not initialize FPS counter"); - video_buffer_destroy(&screen->vb); - return false; + goto error_destroy_video_buffer; } screen->frame_size = params->frame_size; @@ -347,19 +346,14 @@ screen_init(struct screen *screen, const struct screen_params *params) { window_flags); if (!screen->window) { LOGC("Could not create window: %s", SDL_GetError()); - fps_counter_destroy(&screen->fps_counter); - video_buffer_destroy(&screen->vb); - return false; + goto error_destroy_fps_counter; } screen->renderer = SDL_CreateRenderer(screen->window, -1, SDL_RENDERER_ACCELERATED); if (!screen->renderer) { LOGC("Could not create renderer: %s", SDL_GetError()); - SDL_DestroyWindow(screen->window); - fps_counter_destroy(&screen->fps_counter); - video_buffer_destroy(&screen->vb); - return false; + goto error_destroy_window; } SDL_RendererInfo renderer_info; @@ -408,22 +402,13 @@ screen_init(struct screen *screen, const struct screen_params *params) { screen->texture = create_texture(screen); if (!screen->texture) { LOGC("Could not create texture: %s", SDL_GetError()); - SDL_DestroyRenderer(screen->renderer); - SDL_DestroyWindow(screen->window); - fps_counter_destroy(&screen->fps_counter); - video_buffer_destroy(&screen->vb); - return false; + goto error_destroy_renderer; } screen->frame = av_frame_alloc(); if (!screen->frame) { LOGC("Could not create screen frame"); - SDL_DestroyTexture(screen->texture); - SDL_DestroyRenderer(screen->renderer); - SDL_DestroyWindow(screen->window); - fps_counter_destroy(&screen->fps_counter); - video_buffer_destroy(&screen->vb); - return false; + goto error_destroy_texture; } // Reset the window size to trigger a SIZE_CHANGED event, to workaround @@ -454,6 +439,19 @@ screen_init(struct screen *screen, const struct screen_params *params) { #endif return true; + +error_destroy_texture: + SDL_DestroyTexture(screen->texture); +error_destroy_renderer: + SDL_DestroyRenderer(screen->renderer); +error_destroy_window: + SDL_DestroyWindow(screen->window); +error_destroy_fps_counter: + fps_counter_destroy(&screen->fps_counter); +error_destroy_video_buffer: + video_buffer_destroy(&screen->vb); + + return false; } static void From 6a2cd089a176bc907c151f7245df66f347c0ed28 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Mon, 17 May 2021 08:46:38 +0200 Subject: [PATCH 410/450] Initialize input manager dynamically The input manager was partially initialized statically, but a call to input_manager_init() was needed anyway, so initialize all the fields from the "constructor". This is consistent with the initialization of the other structs. --- app/src/input_manager.c | 10 +++++++--- app/src/input_manager.h | 4 ++-- app/src/scrcpy.c | 16 ++-------------- 3 files changed, 11 insertions(+), 19 deletions(-) diff --git a/app/src/input_manager.c b/app/src/input_manager.c index 55b5fae2..b008c4db 100644 --- a/app/src/input_manager.c +++ b/app/src/input_manager.c @@ -52,9 +52,13 @@ is_shortcut_mod(struct input_manager *im, uint16_t sdl_mod) { } void -input_manager_init(struct input_manager *im, - const struct scrcpy_options *options) -{ +input_manager_init(struct input_manager *im, struct controller *controller, + struct screen *screen, + const struct scrcpy_options *options) { + im->controller = controller; + im->screen = screen; + im->repeat = 0; + im->control = options->control; im->forward_key_repeat = options->forward_key_repeat; im->prefer_text = options->prefer_text; diff --git a/app/src/input_manager.h b/app/src/input_manager.h index 76690757..1dd7825f 100644 --- a/app/src/input_manager.h +++ b/app/src/input_manager.h @@ -42,8 +42,8 @@ struct input_manager { }; void -input_manager_init(struct input_manager *im, - const struct scrcpy_options *options); +input_manager_init(struct input_manager *im, struct controller *controller, + struct screen *screen, const struct scrcpy_options *options); bool input_manager_handle_event(struct input_manager *im, SDL_Event *event); diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index e7a349d8..8d14a21b 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -40,19 +40,7 @@ static struct sc_v4l2_sink v4l2_sink; #endif static struct controller controller; static struct file_handler file_handler; - -static struct input_manager input_manager = { - .controller = &controller, - .screen = &screen, - .repeat = 0, - - // initialized later - .prefer_text = false, - .sdl_shortcut_mods = { - .data = {0}, - .count = 0, - }, -}; +static struct input_manager input_manager; #ifdef _WIN32 BOOL WINAPI windows_ctrl_handler(DWORD ctrl_type) { @@ -422,7 +410,7 @@ scrcpy(const struct scrcpy_options *options) { } stream_started = true; - input_manager_init(&input_manager, options); + input_manager_init(&input_manager, &controller, &screen, options); ret = event_loop(options); LOGD("quit..."); From 6adc97198bd6fc9122c9cd9cef319420219a384d Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 9 May 2021 16:52:22 +0200 Subject: [PATCH 411/450] Provide device info directly on server connection This avoids to retrieve them in a separate step. --- app/meson.build | 1 - app/src/device.c | 23 ----------------------- app/src/device.h | 17 ----------------- app/src/scrcpy.c | 10 +--------- app/src/server.c | 25 +++++++++++++++++++++++-- app/src/server.h | 5 ++++- 6 files changed, 28 insertions(+), 53 deletions(-) delete mode 100644 app/src/device.c delete mode 100644 app/src/device.h diff --git a/app/meson.build b/app/meson.build index c1dbf489..6b6991aa 100644 --- a/app/meson.build +++ b/app/meson.build @@ -6,7 +6,6 @@ src = [ 'src/control_msg.c', 'src/controller.c', 'src/decoder.c', - 'src/device.c', 'src/device_msg.c', 'src/event_converter.c', 'src/file_handler.c', diff --git a/app/src/device.c b/app/src/device.c deleted file mode 100644 index 094dc616..00000000 --- a/app/src/device.c +++ /dev/null @@ -1,23 +0,0 @@ -#include "device.h" - -#include "util/log.h" - -bool -device_read_info(socket_t device_socket, char *device_name, struct size *size) { - unsigned char buf[DEVICE_NAME_FIELD_LENGTH + 4]; - int r = net_recv_all(device_socket, buf, sizeof(buf)); - if (r < DEVICE_NAME_FIELD_LENGTH + 4) { - LOGE("Could not retrieve device information"); - return false; - } - // in case the client sends garbage - buf[DEVICE_NAME_FIELD_LENGTH - 1] = '\0'; - // strcpy is safe here, since name contains at least - // DEVICE_NAME_FIELD_LENGTH bytes and strlen(buf) < DEVICE_NAME_FIELD_LENGTH - strcpy(device_name, (char *) buf); - size->width = (buf[DEVICE_NAME_FIELD_LENGTH] << 8) - | buf[DEVICE_NAME_FIELD_LENGTH + 1]; - size->height = (buf[DEVICE_NAME_FIELD_LENGTH + 2] << 8) - | buf[DEVICE_NAME_FIELD_LENGTH + 3]; - return true; -} diff --git a/app/src/device.h b/app/src/device.h deleted file mode 100644 index 376e3d4b..00000000 --- a/app/src/device.h +++ /dev/null @@ -1,17 +0,0 @@ -#ifndef DEVICE_H -#define DEVICE_H - -#include "common.h" - -#include - -#include "coords.h" -#include "util/net.h" - -#define DEVICE_NAME_FIELD_LENGTH 64 - -// name must be at least DEVICE_NAME_FIELD_LENGTH bytes -bool -device_read_info(socket_t device_socket, char *device_name, struct size *size); - -#endif diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index 8d14a21b..8418353d 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -15,7 +15,6 @@ #include "controller.h" #include "decoder.h" -#include "device.h" #include "events.h" #include "file_handler.h" #include "input_manager.h" @@ -284,17 +283,10 @@ scrcpy(const struct scrcpy_options *options) { goto end; } - if (!server_connect_to(&server)) { - goto end; - } - char device_name[DEVICE_NAME_FIELD_LENGTH]; struct size frame_size; - // screenrecord does not send frames when the screen content does not - // change therefore, we transmit the screen size before the video stream, - // to be able to init the window immediately - if (!device_read_info(server.video_socket, device_name, &frame_size)) { + if (!server_connect_to(&server, device_name, &frame_size)) { goto end; } diff --git a/app/src/server.c b/app/src/server.c index 4448ab95..646006d6 100644 --- a/app/src/server.c +++ b/app/src/server.c @@ -464,8 +464,28 @@ error: return false; } +static bool +device_read_info(socket_t device_socket, char *device_name, struct size *size) { + unsigned char buf[DEVICE_NAME_FIELD_LENGTH + 4]; + int r = net_recv_all(device_socket, buf, sizeof(buf)); + if (r < DEVICE_NAME_FIELD_LENGTH + 4) { + LOGE("Could not retrieve device information"); + return false; + } + // in case the client sends garbage + buf[DEVICE_NAME_FIELD_LENGTH - 1] = '\0'; + // strcpy is safe here, since name contains at least + // DEVICE_NAME_FIELD_LENGTH bytes and strlen(buf) < DEVICE_NAME_FIELD_LENGTH + strcpy(device_name, (char *) buf); + size->width = (buf[DEVICE_NAME_FIELD_LENGTH] << 8) + | buf[DEVICE_NAME_FIELD_LENGTH + 1]; + size->height = (buf[DEVICE_NAME_FIELD_LENGTH + 2] << 8) + | buf[DEVICE_NAME_FIELD_LENGTH + 3]; + return true; +} + bool -server_connect_to(struct server *server) { +server_connect_to(struct server *server, char *device_name, struct size *size) { if (!server->tunnel_forward) { server->video_socket = net_accept(server->server_socket); if (server->video_socket == INVALID_SOCKET) { @@ -505,7 +525,8 @@ server_connect_to(struct server *server) { disable_tunnel(server); // ignore failure server->tunnel_enabled = false; - return true; + // The sockets will be closed on stop if device_read_info() fails + return device_read_info(server->video_socket, device_name, size); } void diff --git a/app/src/server.h b/app/src/server.h index 15306e4f..ad49a982 100644 --- a/app/src/server.h +++ b/app/src/server.h @@ -8,6 +8,7 @@ #include #include "adb.h" +#include "coords.h" #include "scrcpy.h" #include "util/log.h" #include "util/net.h" @@ -58,9 +59,11 @@ bool server_start(struct server *server, const char *serial, const struct server_params *params); +#define DEVICE_NAME_FIELD_LENGTH 64 // block until the communication with the server is established +// device_name must point to a buffer of at least DEVICE_NAME_FIELD_LENGTH bytes bool -server_connect_to(struct server *server); +server_connect_to(struct server *server, char *device_name, struct size *size); // disconnect and kill the server process void From 9bed8cf3f44b551cbb9337b67581ee4f6fb5a23a Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 28 May 2021 10:05:34 +0200 Subject: [PATCH 412/450] Fix syntax highlighting in README --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index e0d40984..ae0c6b03 100644 --- a/README.md +++ b/README.md @@ -748,7 +748,9 @@ handled by the active application. To use a specific _adb_ binary, configure its path in the environment variable `ADB`: - ADB=/path/to/adb scrcpy +```bash +ADB=/path/to/adb scrcpy +``` To override the path of the `scrcpy-server` file, configure its path in `SCRCPY_SERVER_PATH`. From 1b7c9b3e1c4a8f934268bd5d0b3db6a558695c7d Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 28 May 2021 10:18:36 +0200 Subject: [PATCH 413/450] Reference translations from FAQ.md --- FAQ.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/FAQ.md b/FAQ.md index a84aade8..2fd209b6 100644 --- a/FAQ.md +++ b/FAQ.md @@ -1,5 +1,7 @@ # Frequently Asked Questions +[Read in another language](#translations) + Here are the common reported problems and their status. @@ -230,3 +232,10 @@ You could also edit (a copy of) `scrcpy-console.bat` or `scrcpy-noconsole.vbs` to add some arguments. [show file extensions]: https://www.howtogeek.com/205086/beginner-how-to-make-windows-show-file-extensions/ + + +## Translations + +This FAQ is available in other languages: + + - [한국어 (Korean, `ko`) - v1.11](FAQ.ko.md) From 2e9d52008084696c2ad8809a03ff8e92988591df Mon Sep 17 00:00:00 2001 From: Alberto Pasqualetto Date: Sat, 15 May 2021 00:13:56 +0200 Subject: [PATCH 414/450] README in Italian PR #2316 Signed-off-by: Romain Vimont --- README.it.md | 742 +++++++++++++++++++++++++++++++++++++++++++++++++++ README.md | 1 + 2 files changed, 743 insertions(+) create mode 100644 README.it.md diff --git a/README.it.md b/README.it.md new file mode 100644 index 00000000..37416f1d --- /dev/null +++ b/README.it.md @@ -0,0 +1,742 @@ +_Apri il [README](README.md) originale e sempre aggiornato._ + +# scrcpy (v1.17) + +Questa applicazione fornisce la visualizzazione e il controllo dei dispositivi Android collegati via USB (o [via TCP/IP][article-tcpip]). Non richiede alcun accesso _root_. +Funziona su _GNU/Linux_, _Windows_ e _macOS_. + +![screenshot](assets/screenshot-debian-600.jpg) + +Si concentra su: + + - **leggerezza** (nativo, mostra solo lo schermo del dispositivo) + - **prestazioni** (30~60fps) + - **qualità** (1920×1080 o superiore) + - **bassa latenza** ([35~70ms][lowlatency]) + - **tempo di avvio basso** (~ 1secondo per visualizzare la prima immagine) + - **non invadenza** (nulla viene lasciato installato sul dispositivo) + +[lowlatency]: https://github.com/Genymobile/scrcpy/pull/646 + + +## Requisiti + +Il dispositivo Android richiede almeno le API 21 (Android 5.0). + +Assiucurati di aver [attivato il debug usb][enable-adb] sul(/i) tuo(i) dispositivo(/i). + +[enable-adb]: https://developer.android.com/studio/command-line/adb.html#Enabling + +In alcuni dispositivi, devi anche abilitare [un'opzione aggiuntiva][control] per controllarli con tastiera e mouse. + +[control]: https://github.com/Genymobile/scrcpy/issues/70#issuecomment-373286323 + +## Ottieni l'app + +Packaging status + +### Sommario + + - Linux: `apt install scrcpy` + - Windows: [download](README.md#windows) + - macOS: `brew install scrcpy` + +Compila dai sorgenti: [BUILD] (in inglese) ([procedimento semplificato][BUILD_simple] (in inglese)) + +[BUILD]: BUILD.md +[BUILD_simple]: BUILD.md#simple + + +### Linux + +Su Debian (_testing_ e _sid_ per ora) e Ubuntu (20.04): + +``` +apt install scrcpy +``` + +È disponibile anche un pacchetto [Snap]: [`scrcpy`][snap-link]. + +[snap-link]: https://snapstats.org/snaps/scrcpy + +[snap]: https://it.wikipedia.org/wiki/Snappy_(gestore_pacchetti) + +Per Fedora, è disponibile un pacchetto [COPR]: [`scrcpy`][copr-link]. + +[COPR]: https://fedoraproject.org/wiki/Category:Copr +[copr-link]: https://copr.fedorainfracloud.org/coprs/zeno/scrcpy/ + +Per Arch Linux, è disponibile un pacchetto [AUR]: [`scrcpy`][aur-link]. + +[AUR]: https://wiki.archlinux.org/index.php/Arch_User_Repository +[aur-link]: https://aur.archlinux.org/packages/scrcpy/ + +Per Gentoo, è disponibile una [Ebuild]: [`scrcpy/`][ebuild-link]. + +[Ebuild]: https://wiki.gentoo.org/wiki/Ebuild +[ebuild-link]: https://github.com/maggu2810/maggu2810-overlay/tree/master/app-mobilephone/scrcpy + +Puoi anche [compilare l'app manualmente][BUILD] (in inglese) ([procedimento semplificato][BUILD_simple] (in inglese)). + + +### Windows + +Per Windows, per semplicità è disponibile un archivio precompilato con tutte le dipendenze (incluso `adb`): + + - [README](README.md#windows) (Link al README originale per l'ultima versione) + +È anche disponibile in [Chocolatey]: + +[Chocolatey]: https://chocolatey.org/ + +```bash +choco install scrcpy +choco install adb # se non lo hai già +``` + +E in [Scoop]: + +```bash +scoop install scrcpy +scoop install adb # se non lo hai già +``` + +[Scoop]: https://scoop.sh + +Puoi anche [compilare l'app manualmente][BUILD] (in inglese). + + +### macOS + +L'applicazione è disponibile in [Homebrew]. Basta installarlo: + +[Homebrew]: https://brew.sh/ + +```bash +brew install scrcpy +``` + +Serve che `adb` sia accessibile dal tuo `PATH`. Se non lo hai già: + +```bash +brew install android-platform-tools +``` + +È anche disponibile in [MacPorts], che imposta adb per te: + +```bash +sudo port install scrcpy +``` + +[MacPorts]: https://www.macports.org/ + + +Puoi anche [compilare l'app manualmente][BUILD] (in inglese). + + +## Esecuzione + +Collega un dispositivo Android ed esegui: + +```bash +scrcpy +``` + +Scrcpy accetta argomenti da riga di comando, essi sono listati con: + +```bash +scrcpy --help +``` + +## Funzionalità + +### Configurazione di acquisizione + +#### Riduci dimensione + +Qualche volta è utile trasmettere un dispositvo Android ad una definizione inferiore per aumentare le prestazioni. + +Per limitare sia larghezza che altezza ad un certo valore (ad es. 1024): + +```bash +scrcpy --max-size 1024 +scrcpy -m 1024 # versione breve +``` + +L'altra dimensione è calcolata in modo tale che il rapporto di forma del dispositivo sia preservato. +In questo esempio un dispositivo in 1920x1080 viene trasmesso a 1024x576. + + +#### Cambia bit-rate (velocità di trasmissione) + +Il bit-rate predefinito è 8 Mbps. Per cambiare il bitrate video (ad es. a 2 Mbps): + +```bash +scrcpy --bit-rate 2M +scrcpy -b 2M # versione breve +``` + +#### Limitare il frame rate (frequenza di fotogrammi) + +Il frame rate di acquisizione può essere limitato: + +```bash +scrcpy --max-fps 15 +``` + +Questo è supportato ufficialmente a partire da Android 10, ma potrebbe funzionare in versioni precedenti. + +#### Ritaglio + +Lo schermo del dispositivo può essere ritagliato per visualizzare solo parte di esso. + +Questo può essere utile, per esempio, per trasmettere solo un occhio dell'Oculus Go: + +```bash +scrcpy --crop 1224:1440:0:0 # 1224x1440 at offset (0,0) +``` + +Se anche `--max-size` è specificata, il ridimensionamento è applicato dopo il ritaglio. + + +#### Blocca orientamento del video + + +Per bloccare l'orientamento della trasmissione: + +```bash +scrcpy --lock-video-orientation 0 # orientamento naturale +scrcpy --lock-video-orientation 1 # 90° antiorario +scrcpy --lock-video-orientation 2 # 180° +scrcpy --lock-video-orientation 3 # 90° orario +``` + +Questo influisce sull'orientamento della registrazione. + + +La [finestra può anche essere ruotata](#rotazione) indipendentemente. + + +#### Codificatore + +Alcuni dispositivi hanno più di un codificatore e alcuni di questi possono provocare problemi o crash. È possibile selezionare un encoder diverso: + +```bash +scrcpy --encoder OMX.qcom.video.encoder.avc +``` + +Per elencare i codificatori disponibili puoi immettere un nome di codificatore non valido e l'errore mostrerà i codificatori disponibili: + +```bash +scrcpy --encoder _ +``` + +### Registrazione + +È possibile registrare lo schermo durante la trasmissione: + +```bash +scrcpy --record file.mp4 +scrcpy -r file.mkv +``` + +Per disabilitare la trasmissione durante la registrazione: + +```bash +scrcpy --no-display --record file.mp4 +scrcpy -Nr file.mkv +# interrompere la registrazione con Ctrl+C +``` + +I "fotogrammi saltati" sono registrati nonostante non siano mostrati in tempo reale (per motivi di prestazioni). I fotogrammi sono _datati_ sul dispositivo, così una [variazione di latenza dei pacchetti][packet delay variation] non impatta il file registrato. + +[packet delay variation]: https://en.wikipedia.org/wiki/Packet_delay_variation + + +### Connessione + +#### Wireless + + +_Scrcpy_ usa `adb` per comunicare col dispositivo e `adb` può [connettersi][connect] al dispositivo mediante TCP/IP: + +1. Connetti il dispositivo alla stessa rete Wi-Fi del tuo computer. +2. Trova l'indirizzo IP del tuo dispositivo in Impostazioni → Informazioni sul telefono → Stato, oppure eseguendo questo comando: + + ```bash + adb shell ip route | awk '{print $9}' + ``` + +3. Abilita adb via TCP/IP sul tuo dispositivo: `adb tcpip 5555`. +4. Scollega il tuo dispositivo. +5. Connetti il tuo dispositivo: `adb connect IP_DISPOSITVO:5555` _(rimpiazza `IP_DISPOSITIVO`)_. +6. Esegui `scrcpy` come al solito. + +Potrebbe essere utile diminuire il bit-rate e la definizione + +```bash +scrcpy --bit-rate 2M --max-size 800 +scrcpy -b2M -m800 # versione breve +``` + +[connect]: https://developer.android.com/studio/command-line/adb.html#wireless + + +#### Multi dispositivo + +Se in `adb devices` sono listati più dispositivi, è necessario specificare il _seriale_: + +```bash +scrcpy --serial 0123456789abcdef +scrcpy -s 0123456789abcdef # versione breve +``` + +Se il dispositivo è collegato mediante TCP/IP: + +```bash +scrcpy --serial 192.168.0.1:5555 +scrcpy -s 192.168.0.1:5555 # versione breve +``` + +Puoi avviare più istanze di _scrcpy_ per diversi dispositivi. + + +#### Avvio automativo alla connessione del dispositivo + +Potresti usare [AutoAdb]: + +```bash +autoadb scrcpy -s '{}' +``` + +[AutoAdb]: https://github.com/rom1v/autoadb + +#### Tunnel SSH + +Per connettersi a un dispositivo remoto è possibile collegare un client `adb` locale ad un server `adb` remoto (assunto che entrambi stiano usando la stessa versione del protocollo _adb_): + +```bash +adb kill-server # termina il server adb locale su 5037 +ssh -CN -L5037:localhost:5037 -R27183:localhost:27183 your_remote_computer +# tieni questo aperto +``` + +Da un altro terminale: + +```bash +scrcpy +``` + +Per evitare l'abilitazione dell'apertura porte remota potresti invece forzare una "forward connection" (notare il `-L` invece di `-R`) + +```bash +adb kill-server # termina il server adb locale su 5037 +ssh -CN -L5037:localhost:5037 -L27183:localhost:27183 your_remote_computer +# tieni questo aperto +``` + +Da un altro terminale: + +```bash +scrcpy --force-adb-forward +``` + + +Come per le connessioni wireless potrebbe essere utile ridurre la qualità: + +``` +scrcpy -b2M -m800 --max-fps 15 +``` + +### Configurazione della finestra + +#### Titolo + +Il titolo della finestra è il modello del dispositivo per impostazione predefinita. Esso può essere cambiato: + +```bash +scrcpy --window-title 'My device' +``` + +#### Posizione e dimensione + +La posizione e la dimensione iniziale della finestra può essere specificata: + +```bash +scrcpy --window-x 100 --window-y 100 --window-width 800 --window-height 600 +``` + +#### Senza bordi + +Per disabilitare le decorazioni della finestra: + +```bash +scrcpy --window-borderless +``` + +#### Sempre in primo piano + +Per tenere scrcpy sempre in primo piano: + +```bash +scrcpy --always-on-top +``` + +#### Schermo intero + +L'app può essere avviata direttamente a schermo intero: + +```bash +scrcpy --fullscreen +scrcpy -f # versione breve +``` + +Lo schermo intero può anche essere attivato/disattivato con MOD+f. + +#### Rotazione + +La finestra può essere ruotata: + +```bash +scrcpy --rotation 1 +``` + +I valori possibili sono: + - `0`: nessuna rotazione + - `1`: 90 gradi antiorari + - `2`: 180 gradi + - `3`: 90 gradi orari + +La rotazione può anche essere cambiata dinamicamente con MOD+ +_(sinistra)_ e MOD+ _(destra)_. + +Notare che _scrcpy_ gestisce 3 diversi tipi di rotazione: + - MOD+r richiede al dispositvo di cambiare tra orientamento verticale (portrait) e orizzontale (landscape) (l'app in uso potrebbe rifiutarsi se non supporta l'orientamento richiesto). + - [`--lock-video-orientation`](#blocca-orientamento-del-video) cambia l'orientamento della trasmissione (l'orientamento del video inviato dal dispositivo al computer). Questo influenza la registrazione. + - `--rotation` (o MOD+/MOD+) ruota solo il contenuto della finestra. Questo influenza solo la visualizzazione, non la registrazione. + + +### Altre opzioni di trasmissione + +#### "Sola lettura" + +Per disabilitare i controlli (tutto ciò che può interagire col dispositivo: tasti di input, eventi del mouse, trascina e rilascia (drag&drop) file): + +```bash +scrcpy --no-control +scrcpy -n +``` + +#### Schermo + +Se sono disponibili più schermi, è possibile selezionare lo schermo da trasmettere: + +```bash +scrcpy --display 1 +``` + +La lista degli id schermo può essere ricavata da: + +```bash +adb shell dumpsys display # cerca "mDisplayId=" nell'output +``` + +Lo schermo secondario potrebbe essere possibile controllarlo solo se il dispositivo esegue almeno Android 10 (in caso contrario è trasmesso in modalità sola lettura). + + +#### Mantenere sbloccato + +Per evitare che il dispositivo si blocchi dopo un po' che il dispositivo è collegato: + +```bash +scrcpy --stay-awake +scrcpy -w +``` + +Lo stato iniziale è ripristinato quando scrcpy viene chiuso. + + +#### Spegnere lo schermo + +È possibile spegnere lo schermo del dispositivo durante la trasmissione con un'opzione da riga di comando: + +```bash +scrcpy --turn-screen-off +scrcpy -S +``` + +Oppure premendo MOD+o in qualsiasi momento. + +Per riaccenderlo premere MOD+Shift+o. + +In Android il pulsante `POWER` (tasto di accensione) accende sempre lo schermo. Per comodità, se `POWER` è inviato via scrcpy (con click destro o con MOD+p), si forza il dispositivo a spegnere lo schermo dopo un piccolo ritardo (appena possibile). +Il pulsante fisico `POWER` continuerà ad accendere lo schermo normalmente. + +Può anche essere utile evitare il blocco del dispositivo: + +```bash +scrcpy --turn-screen-off --stay-awake +scrcpy -Sw +``` + +#### Renderizzare i fotogrammi scaduti + +Per minimizzare la latenza _scrcpy_ renderizza sempre l'ultimo fotogramma decodificato disponibile in maniera predefinita e tralascia quelli precedenti. + +Per forzare la renderizzazione di tutti i fotogrammi (a costo di una possibile latenza superiore), utilizzare: + +```bash +scrcpy --render-expired-frames +``` + +#### Mostrare i tocchi + +Per le presentazioni può essere utile mostrare i tocchi fisici (sul dispositivo fisico). + +Android fornisce questa funzionalità nelle _Opzioni sviluppatore_. + +_Scrcpy_ fornisce un'opzione per abilitare questa funzionalità all'avvio e ripristinare il valore iniziale alla chiusura: + +```bash +scrcpy --show-touches +scrcpy -t +``` + +Notare che mostra solo i tocchi _fisici_ (con le dita sul dispositivo). + + +#### Disabilitare il salvaschermo + +In maniera predefinita scrcpy non previene l'attivazione del salvaschermo del computer. + +Per disabilitarlo: + +```bash +scrcpy --disable-screensaver +``` + + +### Input di controlli + +#### Rotazione dello schermo del dispostivo + +Premere MOD+r per cambiare tra le modalità verticale (portrait) e orizzontale (landscape). + +Notare che la rotazione avviene solo se l'applicazione in primo piano supporta l'orientamento richiesto. + +#### Copia-incolla + +Quando gli appunti di Android cambiano, essi vengono automaticamente sincronizzati con gli appunti del computer. + +Qualsiasi scorciatoia Ctrl viene inoltrata al dispositivo. In particolare: + - Ctrl+c copia + - Ctrl+x taglia + - Ctrl+v incolla (dopo la sincronizzazione degli appunti da computer a dispositivo) + +Questo solitamente funziona nella maniera più comune. + +Il comportamento reale, però, dipende dall'applicazione attiva. Per esempio _Termux_ invia SIGINT con Ctrl+c, e _K-9 Mail_ compone un nuovo messaggio. + +Per copiare, tagliare e incollare in questi casi (ma è solo supportato in Android >= 7): + - MOD+c inietta `COPY` + - MOD+x inietta `CUT` + - MOD+v inietta `PASTE` (dopo la sincronizzazione degli appunti da computer a dispositivo) + +In aggiunta, MOD+Shift+v permette l'iniezione del testo degli appunti del computer come una sequenza di eventi pressione dei tasti. Questo è utile quando il componente non accetta l'incollaggio di testo (per esempio in _Termux_), ma questo può rompere il contenuto non ASCII. + +**AVVISO:** Incollare gli appunti del computer nel dispositivo (sia con Ctrl+v che con MOD+v) copia il contenuto negli appunti del dispositivo. Come conseguenza, qualsiasi applicazione Android potrebbe leggere il suo contenuto. Dovresti evitare di incollare contenuti sensibili (come password) in questa maniera. + +Alcuni dispositivi non si comportano come aspettato quando si modificano gli appunti del dispositivo a livello di codice. L'opzione `--legacy-paste` è fornita per cambiare il comportamento di Ctrl+v and MOD+v in modo tale che anch'essi iniettino il testo gli appunti del computer come una sequenza di eventi pressione dei tasti (nella stessa maniera di MOD+Shift+v). + +#### Pizzica per zoomare (pinch-to-zoom) + +Per simulare il "pizzica per zoomare": Ctrl+_click e trascina_. + +Più precisamente, tieni premuto Ctrl mentre premi il pulsante sinistro. Finchè il pulsante non sarà rilasciato, tutti i movimenti del mouse ridimensioneranno e ruoteranno il contenuto (se supportato dall'applicazione) relativamente al centro dello schermo. + +Concretamente scrcpy genera degli eventi di tocco addizionali di un "dito virtuale" nella posizione simmetricamente opposta rispetto al centro dello schermo. + + +#### Preferenze di iniezione del testo + +Ci sono due tipi di [eventi][textevents] generati quando si scrive testo: + - _eventi di pressione_, segnalano che tasto è stato premuto o rilasciato; + - _eventi di testo_, segnalano che del testo è stato inserito. + +In maniera predefinita le lettere sono "iniettate" usando gli eventi di pressione, in maniera tale che la tastiera si comporti come aspettato nei giochi (come accade solitamente per i tasti WASD). + +Questo, però, può [causare problemi][prefertext]. Se incontri un problema del genere, puoi evitarlo con: + +```bash +scrcpy --prefer-text +``` + +(ma questo romperà il normale funzionamento della tastiera nei giochi) + +[textevents]: https://blog.rom1v.com/2018/03/introducing-scrcpy/#handle-text-input +[prefertext]: https://github.com/Genymobile/scrcpy/issues/650#issuecomment-512945343 + + +#### Ripetizione di tasti + +In maniera predefinita tenere premuto un tasto genera una ripetizione degli eventi di pressione di tale tasto. Questo può creare problemi di performance in alcuni giochi, dove questi eventi sono inutilizzati. + +Per prevenire l'inoltro ripetuto degli eventi di pressione: + +```bash +scrcpy --no-key-repeat +``` + +#### Click destro e click centrale + +In maniera predefinita, click destro aziona BACK (indietro) e il click centrale aziona HOME. Per disabilitare queste scorciatoie e, invece, inviare i click al dispositivo: + +```bash +scrcpy --forward-all-clicks +``` + + +### Rilascio di file + +#### Installare APK + +Per installare un APK, trascina e rilascia un file APK (finisce con `.apk`) nella finestra di _scrcpy_. + +Non c'è alcuna risposta visiva, un log è stampato nella console. + + +#### Trasferimento di file verso il dispositivo + +Per trasferire un file in `/sdcard/` del dispositivo trascina e rilascia un file (non APK) nella finestra di _scrcpy_. + +Non c'è alcuna risposta visiva, un log è stampato nella console. + +La cartella di destinazione può essere cambiata all'avvio: + +```bash +scrcpy --push-target=/sdcard/Download/ +``` + + +### Inoltro dell'audio + +L'audio non è inoltrato da _scrcpy_. Usa [sndcpy]. + +Vedi anche la [issue #14]. + +[sndcpy]: https://github.com/rom1v/sndcpy +[issue #14]: https://github.com/Genymobile/scrcpy/issues/14 + + +## Scociatoie + +Nella lista seguente, MOD è il modificatore delle scorciatoie. In maniera predefinita è Alt (sinistro) o Super (sinistro). + +Può essere cambiato usando `--shortcut-mod`. I tasti possibili sono `lctrl`, `rctrl`, `lalt`, `ralt`, `lsuper` and `rsuper` (`l` significa sinistro e `r` significa destro). Per esempio: + +```bash +# usa ctrl destro per le scorciatoie +scrcpy --shortcut-mod=rctrl + +# use sia "ctrl sinistro"+"alt sinistro" che "super sinistro" per le scorciatoie +scrcpy --shortcut-mod=lctrl+lalt,lsuper +``` + +_[Super] è il pulsante Windows o Cmd._ + +[Super]: https://it.wikipedia.org/wiki/Tasto_Windows + + + | Azione | Scorciatoia + | ------------------------------------------- |:----------------------------- + | Schermo intero | MOD+f + | Rotazione schermo a sinistra | MOD+ _(sinistra)_ + | Rotazione schermo a destra | MOD+ _(destra)_ + | Ridimensiona finestra a 1:1 (pixel-perfect) | MOD+g + | Ridimensiona la finestra per rimuovere i bordi neri | MOD+w \| _Doppio click¹_ + | Premi il tasto `HOME` | MOD+h \| _Click centrale_ + | Premi il tasto `BACK` | MOD+b \| _Click destro²_ + | Premi il tasto `APP_SWITCH` | MOD+s + | Premi il tasto `MENU` (sblocca lo schermo) | MOD+m + | Premi il tasto `VOLUME_UP` | MOD+ _(su)_ + | Premi il tasto `VOLUME_DOWN` | MOD+ _(giù)_ + | Premi il tasto `POWER` | MOD+p + | Accendi | _Click destro²_ + | Spegni lo schermo del dispositivo (continua a trasmettere) | MOD+o + | Accendi lo schermo del dispositivo | MOD+Shift+o + | Ruota lo schermo del dispositivo | MOD+r + | Espandi il pannello delle notifiche | MOD+n + | Chiudi il pannello delle notifiche | MOD+Shift+n + | Copia negli appunti³ | MOD+c + | Taglia negli appunti³ | MOD+x + | Sincronizza gli appunti e incolla³ | MOD+v + | Inietta il testo degli appunti del computer | MOD+Shift+v + | Abilita/Disabilita il contatore FPS (su stdout) | MOD+i + | Pizzica per zoomare | Ctrl+_click e trascina_ + +_¹Doppio click sui bordi neri per rimuoverli._ +_²Il tasto destro accende lo schermo se era spento, preme BACK in caso contrario._ +_³Solo in Android >= 7._ + +Tutte le scorciatoie Ctrl+_tasto_ sono inoltrate al dispositivo, così sono gestite dall'applicazione attiva. + +## Path personalizzati + +Per utilizzare dei binari _adb_ specifici, configura il suo path nella variabile d'ambente `ADB`: + +```bash +ADB=/percorso/per/adb scrcpy +``` + +Per sovrascrivere il percorso del file `scrcpy-server`, configura il percorso in `SCRCPY_SERVER_PATH`. + +## Perchè _scrcpy_? + +Un collega mi ha sfidato a trovare un nome tanto impronunciabile quanto [gnirehtet]. + +[`strcpy`] copia una **str**ing (stringa); `scrcpy` copia uno **scr**een (schermo). + +[gnirehtet]: https://github.com/Genymobile/gnirehtet +[`strcpy`]: http://man7.org/linux/man-pages/man3/strcpy.3.html + +## Come compilare? + +Vedi [BUILD] (in inglese). + + +## Problemi comuni + +Vedi le [FAQ](FAQ.it.md). + + +## Sviluppatori + +Leggi la [pagina per sviluppatori]. + +[pagina per sviluppatori]: DEVELOP.md + + +## Licenza (in inglese) + + Copyright (C) 2018 Genymobile + Copyright (C) 2018-2021 Romain Vimont + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +## Articoli (in inglese) + +- [Introducendo scrcpy][article-intro] +- [Scrcpy ora funziona wireless][article-tcpip] + +[article-intro]: https://blog.rom1v.com/2018/03/introducing-scrcpy/ +[article-tcpip]: https://www.genymotion.com/blog/open-source-project-scrcpy-now-works-wirelessly/ diff --git a/README.md b/README.md index ae0c6b03..835dad1e 100644 --- a/README.md +++ b/README.md @@ -815,6 +815,7 @@ Read the [developers page]. This README is available in other languages: - [Indonesian (Indonesia, `id`) - v1.16](README.id.md) +- [Italiano (Italiano, `it`) - v1.17](README.it.md) - [日本語 (Japanese, `jp`) - v1.17](README.jp.md) - [한국어 (Korean, `ko`) - v1.11](README.ko.md) - [português brasileiro (Brazilian Portuguese, `pt-BR`) - v1.17](README.pt-br.md) From 8121b0b7e72b393818f592a7858b5003a8de9c59 Mon Sep 17 00:00:00 2001 From: Alberto Pasqualetto Date: Sat, 15 May 2021 00:14:08 +0200 Subject: [PATCH 415/450] FAQ in Italian PR #2316 Signed-off-by: Romain Vimont --- FAQ.it.md | 217 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ FAQ.md | 1 + 2 files changed, 218 insertions(+) create mode 100644 FAQ.it.md diff --git a/FAQ.it.md b/FAQ.it.md new file mode 100644 index 00000000..5c5830ce --- /dev/null +++ b/FAQ.it.md @@ -0,0 +1,217 @@ +_Apri le [FAQ](FAQ.md) originali e sempre aggiornate._ + +# Domande Frequenti (FAQ) + +Questi sono i problemi più comuni riportati e i loro stati. + + +## Problemi di `adb` + +`scrcpy` esegue comandi `adb` per inizializzare la connessione con il dispositivo. Se `adb` fallisce, scrcpy non funzionerà. + +In questo caso sarà stampato questo errore: + +> ERROR: "adb push" returned with value 1 + +Questo solitamente non è un bug di _scrcpy_, ma un problema del tuo ambiente. + +Per trovare la causa, esegui: + +```bash +adb devices +``` + +### `adb` not found (`adb` non trovato) + +È necessario che `adb` sia accessibile dal tuo `PATH`. + +In Windows, la cartella corrente è nel tuo `PATH` e `adb.exe` è incluso nella release, perciò dovrebbe già essere pronto all'uso. + + +### Device unauthorized (Dispositivo non autorizzato) + +Controlla [stackoverflow][device-unauthorized] (in inglese). + +[device-unauthorized]: https://stackoverflow.com/questions/23081263/adb-android-device-unauthorized + + +### Device not detected (Dispositivo non rilevato) + +> adb: error: failed to get feature set: no devices/emulators found + +Controlla di aver abilitato correttamente il [debug con adb][enable-adb] (link in inglese). + +Se il tuo dispositivo non è rilevato, potresti avere bisogno dei [driver][drivers] (link in inglese) (in Windows). + +[enable-adb]: https://developer.android.com/studio/command-line/adb.html#Enabling +[drivers]: https://developer.android.com/studio/run/oem-usb.html + + +### Più dispositivi connessi + +Se più dispositivi sono connessi, riscontrerai questo errore: + +> adb: error: failed to get feature set: more than one device/emulator + +l'identificatore del tuo dispositivo deve essere fornito: + +```bash +scrcpy -s 01234567890abcdef +``` + +Notare che se il tuo dispositivo è connesso mediante TCP/IP, riscontrerai questo messaggio: + +> adb: error: more than one device/emulator +> ERROR: "adb reverse" returned with value 1 +> WARN: 'adb reverse' failed, fallback to 'adb forward' + +Questo è un problema atteso (a causa di un bug di una vecchia versione di Android, vedi [#5] (link in inglese)), ma in quel caso scrcpy ripiega su un metodo differente, il quale dovrebbe funzionare. + +[#5]: https://github.com/Genymobile/scrcpy/issues/5 + + +### Conflitti tra versioni di adb + +> adb server version (41) doesn't match this client (39); killing... + +L'errore compare quando usi più versioni di `adb` simultaneamente. Devi trovare il programma che sta utilizzando una versione differente di `adb` e utilizzare la stessa versione dappertutto. + +Puoi sovrascrivere i binari di `adb` nell'altro programma, oppure chiedere a _scrcpy_ di usare un binario specifico di `adb`, impostando la variabile d'ambiente `ADB`: + +```bash +set ADB=/path/to/your/adb +scrcpy +``` + + +### Device disconnected (Dispositivo disconnesso) + +Se _scrcpy_ si interrompe con l'avviso "Device disconnected", allora la connessione `adb` è stata chiusa. + +Prova con un altro cavo USB o inseriscilo in un'altra porta USB. Vedi [#281] (in inglese) e [#283] (in inglese). + +[#281]: https://github.com/Genymobile/scrcpy/issues/281 +[#283]: https://github.com/Genymobile/scrcpy/issues/283 + + + +## Problemi di controllo + +### Mouse e tastiera non funzionano + +Su alcuni dispositivi potresti dover abilitare un opzione che permette l'[input simulato][simulating input] (link in inglese). Nelle opzioni sviluppatore, abilita: + +> **Debug USB (Impostazioni di sicurezza)** +> _Permetti la concessione dei permessi e la simulazione degli input mediante il debug USB_ + + +[simulating input]: https://github.com/Genymobile/scrcpy/issues/70#issuecomment-373286323 + + +### I caratteri speciali non funzionano + +Iniettare del testo in input è [limitato ai caratteri ASCII][text-input] (link in inglese). Un trucco permette di iniettare dei [caratteri accentati][accented-characters] (link in inglese), ma questo è tutto. Vedi [#37] (link in inglese). + +[text-input]: https://github.com/Genymobile/scrcpy/issues?q=is%3Aopen+is%3Aissue+label%3Aunicode +[accented-characters]: https://blog.rom1v.com/2018/03/introducing-scrcpy/#handle-accented-characters +[#37]: https://github.com/Genymobile/scrcpy/issues/37 + + +## Problemi del client + +### La qualità è bassa + +Se la definizione della finestra del tuo client è minore di quella del tuo dispositivo, allora potresti avere una bassa qualità di visualizzazione, specialmente individuabile nei testi (vedi [#40] (link in inglese)). + +[#40]: https://github.com/Genymobile/scrcpy/issues/40 + +Per migliorare la qualità di ridimensionamento (downscaling), il filtro trilineare è applicato automaticamente se il renderizzatore è OpenGL e se supporta la creazione di mipmap. + +In Windows, potresti voler forzare OpenGL: + +``` +scrcpy --render-driver=opengl +``` + +Potresti anche dover configurare il [comportamento di ridimensionamento][scaling behavior] (link in inglese): + +> `scrcpy.exe` > Propietà > Compatibilità > Modifica impostazioni DPI elevati > Esegui l'override del comportamento di ridimensionamento DPI elevati > Ridimensionamento eseguito per: _Applicazione_. + +[scaling behavior]: https://github.com/Genymobile/scrcpy/issues/40#issuecomment-424466723 + + + +### Crash del compositore KWin + +In Plasma Desktop, il compositore è disabilitato mentre _scrcpy_ è in esecuzione. + +Come soluzione alternativa, [disattiva la "composizione dei blocchi"][kwin] (link in inglese). + + +[kwin]: https://github.com/Genymobile/scrcpy/issues/114#issuecomment-378778613 + + +## Crash + +### Eccezione + +Ci potrebbero essere molte ragioni. Una causa comune è che il codificatore hardware del tuo dispositivo non riesce a codificare alla definizione selezionata: + +> ``` +> ERROR: Exception on thread Thread[main,5,main] +> android.media.MediaCodec$CodecException: Error 0xfffffc0e +> ... +> Exit due to uncaughtException in main thread: +> ERROR: Could not open video stream +> INFO: Initial texture: 1080x2336 +> ``` + +o + +> ``` +> ERROR: Exception on thread Thread[main,5,main] +> java.lang.IllegalStateException +> at android.media.MediaCodec.native_dequeueOutputBuffer(Native Method) +> ``` + +Prova con una definizione inferiore: + +``` +scrcpy -m 1920 +scrcpy -m 1024 +scrcpy -m 800 +``` + +Potresti anche provare un altro [codificatore](README.it.md#codificatore). + + +## Linea di comando in Windows + +Alcuni utenti Windows non sono familiari con la riga di comando. Qui è descritto come aprire un terminale ed eseguire `scrcpy` con gli argomenti: + + 1. Premi Windows+r, questo apre una finestra di dialogo. + 2. Scrivi `cmd` e premi Enter, questo apre un terminale. + 3. Vai nella tua cartella di _scrcpy_ scrivendo (adatta il percorso): + + ```bat + cd C:\Users\user\Downloads\scrcpy-win64-xxx + ``` + + e premi Enter + 4. Scrivi il tuo comando. Per esempio: + + ```bat + scrcpy --record file.mkv + ``` + +Se pianifichi di utilizzare sempre gli stessi argomenti, crea un file `myscrcpy.bat` (abilita mostra [estensioni nomi file][show file extensions] per evitare di far confusione) contenente il tuo comando nella cartella di `scrcpy`. Per esempio: + +```bat +scrcpy --prefer-text --turn-screen-off --stay-awake +``` + +Poi fai doppio click su quel file. + +Potresti anche modificare (una copia di) `scrcpy-console.bat` o `scrcpy-noconsole.vbs` per aggiungere alcuni argomenti. + +[show file extensions]: https://www.techpedia.it/14-windows/windows-10/171-visualizzare-le-estensioni-nomi-file-con-windows-10 diff --git a/FAQ.md b/FAQ.md index 2fd209b6..c1e39a39 100644 --- a/FAQ.md +++ b/FAQ.md @@ -238,4 +238,5 @@ to add some arguments. This FAQ is available in other languages: + - [Italiano (Italiano, `it`) - v1.17](FAQ.it.md) - [한국어 (Korean, `ko`) - v1.11](FAQ.ko.md) From d3d955f67b92d08bbee21bebce40fa072eea38f6 Mon Sep 17 00:00:00 2001 From: Secreto31126 <46955459+Secreto31126@users.noreply.github.com> Date: Sat, 15 May 2021 04:08:28 -0300 Subject: [PATCH 416/450] Translate README.md into Spanish PR #2318 Signed-off-by: Romain Vimont --- README.md | 1 + README.sp.md | 743 +++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 744 insertions(+) create mode 100644 README.sp.md diff --git a/README.md b/README.md index 835dad1e..477c6d25 100644 --- a/README.md +++ b/README.md @@ -819,6 +819,7 @@ This README is available in other languages: - [日本語 (Japanese, `jp`) - v1.17](README.jp.md) - [한국어 (Korean, `ko`) - v1.11](README.ko.md) - [português brasileiro (Brazilian Portuguese, `pt-BR`) - v1.17](README.pt-br.md) +- [Español (Spanish, `sp`) - v1.17](README.sp.md) - [简体中文 (Simplified Chinese, `zh-Hans`) - v1.17](README.zh-Hans.md) - [繁體中文 (Traditional Chinese, `zh-Hant`) - v1.15](README.zh-Hant.md) diff --git a/README.sp.md b/README.sp.md new file mode 100644 index 00000000..6f76a7be --- /dev/null +++ b/README.sp.md @@ -0,0 +1,743 @@ +Solo se garantiza que el archivo [README](README.md) original esté actualizado. + +# scrcpy (v1.17) + +Esta aplicación proporciona imagen y control de un dispositivo Android conectado +por USB (o [por TCP/IP][article-tcpip]). No requiere acceso _root_. +Compatible con _GNU/Linux_, _Windows_ y _macOS_. + +![screenshot](assets/screenshot-debian-600.jpg) + +Sus características principales son: + + - **ligero** (nativo, solo muestra la imagen del dispositivo) + - **desempeño** (30~60fps) + - **calidad** (1920×1080 o superior) + - **baja latencia** ([35~70ms][lowlatency]) + - **corto tiempo de inicio** (~1 segundo para mostrar la primera imagen) + - **no intrusivo** (no se deja nada instalado en el dispositivo) + +[lowlatency]: https://github.com/Genymobile/scrcpy/pull/646 + + +## Requisitos + +El dispositivo Android requiere como mínimo API 21 (Android 5.0). + +Asegurate de [habilitar el adb debugging][enable-adb] en tu(s) dispositivo(s). + +[enable-adb]: https://developer.android.com/studio/command-line/adb.html#Enabling + +En algunos dispositivos, también necesitas habilitar [una opción adicional][control] para controlarlo con el teclado y ratón. + +[control]: https://github.com/Genymobile/scrcpy/issues/70#issuecomment-373286323 + + +## Consigue la app + +Packaging status + +### Resumen + + - Linux: `apt install scrcpy` + - Windows: [download](README.md#windows) + - macOS: `brew install scrcpy` + +Construir desde la fuente: [BUILD] ([proceso simplificado][BUILD_simple]) + +[BUILD]: BUILD.md +[BUILD_simple]: BUILD.md#simple + + +### Linux + +En Debian (_test_ y _sid_ por ahora) y Ubuntu (20.04): + +``` +apt install scrcpy +``` + +Hay un paquete [Snap]: [`scrcpy`][snap-link]. + +[snap-link]: https://snapstats.org/snaps/scrcpy + +[snap]: https://en.wikipedia.org/wiki/Snappy_(package_manager) + +Para Fedora, hay un paquete [COPR]: [`scrcpy`][copr-link]. + +[COPR]: https://fedoraproject.org/wiki/Category:Copr +[copr-link]: https://copr.fedorainfracloud.org/coprs/zeno/scrcpy/ + +Para Arch Linux, hay un paquete [AUR]: [`scrcpy`][aur-link]. + +[AUR]: https://wiki.archlinux.org/index.php/Arch_User_Repository +[aur-link]: https://aur.archlinux.org/packages/scrcpy/ + +Para Gentoo, hay un paquete [Ebuild]: [`scrcpy/`][ebuild-link]. + +[Ebuild]: https://wiki.gentoo.org/wiki/Ebuild +[ebuild-link]: https://github.com/maggu2810/maggu2810-overlay/tree/master/app-mobilephone/scrcpy + +También puedes [construir la aplicación manualmente][BUILD] ([proceso simplificado][BUILD_simple]). + + +### Windows + +Para Windows, por simplicidad, hay un pre-compilado con todas las dependencias +(incluyendo `adb`): + + - [README](README.md#windows) + +También está disponible en [Chocolatey]: + +[Chocolatey]: https://chocolatey.org/ + +```bash +choco install scrcpy +choco install adb # si aún no está instalado +``` + +Y en [Scoop]: + +```bash +scoop install scrcpy +scoop install adb # si aún no está instalado +``` + +[Scoop]: https://scoop.sh + +También puedes [construir la aplicación manualmente][BUILD]. + + +### macOS + +La aplicación está disponible en [Homebrew]. Solo instalala: + +[Homebrew]: https://brew.sh/ + +```bash +brew install scrcpy +``` + +Necesitarás `adb`, accesible desde `PATH`. Si aún no lo tienes: + +```bash +brew install android-platform-tools +``` + +También está disponible en [MacPorts], que configurará el adb automáticamente: + +```bash +sudo port install scrcpy +``` + +[MacPorts]: https://www.macports.org/ + + +También puedes [construir la aplicación manualmente][BUILD]. + + +## Ejecutar + +Enchufa el dispositivo Android, y ejecuta: + +```bash +scrcpy +``` + +Acepta argumentos desde la línea de comandos, listados en: + +```bash +scrcpy --help +``` + +## Características + +### Capturar configuración + +#### Reducir la definición + +A veces es útil reducir la definición de la imagen del dispositivo Android para aumentar el desempeño. + +Para limitar el ancho y la altura a un valor específico (ej. 1024): + +```bash +scrcpy --max-size 1024 +scrcpy -m 1024 # versión breve +``` + +La otra dimensión es calculada para conservar el aspect ratio del dispositivo. +De esta forma, un dispositivo en 1920×1080 será transmitido a 1024×576. + + +#### Cambiar el bit-rate + +El bit-rate por defecto es 8 Mbps. Para cambiar el bit-rate del video (ej. a 2 Mbps): + +```bash +scrcpy --bit-rate 2M +scrcpy -b 2M # versión breve +``` + +#### Limitar los fps + +El fps puede ser limitado: + +```bash +scrcpy --max-fps 15 +``` + +Es oficialmente soportado desde Android 10, pero puede funcionar en versiones anteriores. + +#### Recortar + +La imagen del dispositivo puede ser recortada para transmitir solo una parte de la pantalla. + +Por ejemplo, puede ser útil para transmitir la imagen de un solo ojo del Oculus Go: + +```bash +scrcpy --crop 1224:1440:0:0 # 1224x1440 con coordenadas de origen en (0,0) +``` + +Si `--max-size` también está especificado, el cambio de tamaño es aplicado después de cortar. + + +#### Fijar la rotación del video + + +Para fijar la rotación de la transmisión: + +```bash +scrcpy --lock-video-orientation 0 # orientación normal +scrcpy --lock-video-orientation 1 # 90° contrarreloj +scrcpy --lock-video-orientation 2 # 180° +scrcpy --lock-video-orientation 3 # 90° sentido de las agujas del reloj +``` + +Esto afecta la rotación de la grabación. + +La [ventana también puede ser rotada](#rotación) independientemente. + + +#### Codificador + +Algunos dispositivos pueden tener más de una rotación, y algunos pueden causar problemas o errores. Es posible seleccionar un codificador diferente: + +```bash +scrcpy --encoder OMX.qcom.video.encoder.avc +``` + +Para listar los codificadores disponibles, puedes pasar un nombre de codificador inválido, el error te dará los codificadores disponibles: + +```bash +scrcpy --encoder _ +``` + +### Grabación + +Es posible grabar la pantalla mientras se transmite: + +```bash +scrcpy --record file.mp4 +scrcpy -r file.mkv +``` + +Para grabar sin transmitir la pantalla: + +```bash +scrcpy --no-display --record file.mp4 +scrcpy -Nr file.mkv +# interrumpe la grabación con Ctrl+C +``` + +"Skipped frames" son grabados, incluso si no son mostrados en tiempo real (por razones de desempeño). Los frames tienen _marcas de tiempo_ en el dispositivo, por lo que el "[packet delay +variation]" no impacta el archivo grabado. + +[packet delay variation]: https://en.wikipedia.org/wiki/Packet_delay_variation + + +### Conexión + +#### Inalámbrica + +_Scrcpy_ usa `adb` para comunicarse con el dispositivo, y `adb` puede [conectarse] vía TCP/IP: + +1. Conecta el dispositivo al mismo Wi-Fi que tu computadora. +2. Obtén la dirección IP del dispositivo, en Ajustes → Acerca del dispositivo → Estado, o ejecutando este comando: + + ```bash + adb shell ip route | awk '{print $9}' + ``` + +3. Habilita adb vía TCP/IP en el dispositivo: `adb tcpip 5555`. +4. Desenchufa el dispositivo. +5. Conéctate a tu dispositivo: `adb connect IP_DEL_DISPOSITIVO:5555` _(reemplaza `IP_DEL_DISPOSITIVO`)_. +6. Ejecuta `scrcpy` con normalidad. + +Podría resultar útil reducir el bit-rate y la definición: + +```bash +scrcpy --bit-rate 2M --max-size 800 +scrcpy -b2M -m800 # versión breve +``` + +[conectarse]: https://developer.android.com/studio/command-line/adb.html#wireless + + +#### Múltiples dispositivos + +Si hay muchos dispositivos listados en `adb devices`, será necesario especificar el _número de serie_: + +```bash +scrcpy --serial 0123456789abcdef +scrcpy -s 0123456789abcdef # versión breve +``` + +Si el dispositivo está conectado por TCP/IP: + +```bash +scrcpy --serial 192.168.0.1:5555 +scrcpy -s 192.168.0.1:5555 # versión breve +``` + +Puedes iniciar múltiples instancias de _scrcpy_ para múltiples dispositivos. + +#### Autoiniciar al detectar dispositivo + +Puedes utilizar [AutoAdb]: + +```bash +autoadb scrcpy -s '{}' +``` + +[AutoAdb]: https://github.com/rom1v/autoadb + +#### Túnel SSH + +Para conectarse a un dispositivo remoto, es posible conectar un cliente local de `adb` a un servidor remoto `adb` (siempre y cuando utilicen la misma versión de protocolos _adb_): + +```bash +adb kill-server # cierra el servidor local adb en 5037 +ssh -CN -L5037:localhost:5037 -R27183:localhost:27183 your_remote_computer +# conserva este servidor abierto +``` + +Desde otra terminal: + +```bash +scrcpy +``` + +Para evitar habilitar "remote port forwarding", puedes forzar una "forward connection" (nótese el argumento `-L` en vez de `-R`): + +```bash +adb kill-server # cierra el servidor local adb en 5037 +ssh -CN -L5037:localhost:5037 -L27183:localhost:27183 your_remote_computer +# conserva este servidor abierto +``` + +Desde otra terminal: + +```bash +scrcpy --force-adb-forward +``` + + +Al igual que las conexiones inalámbricas, puede resultar útil reducir la calidad: + +``` +scrcpy -b2M -m800 --max-fps 15 +``` + +### Configuración de la ventana + +#### Título + +Por defecto, el título de la ventana es el modelo del dispositivo. Puede ser modificado: + +```bash +scrcpy --window-title 'My device' +``` + +#### Posición y tamaño + +La posición y tamaño inicial de la ventana puede ser especificado: + +```bash +scrcpy --window-x 100 --window-y 100 --window-width 800 --window-height 600 +``` + +#### Sin bordes + +Para deshabilitar el diseño de la ventana: + +```bash +scrcpy --window-borderless +``` + +#### Siempre adelante + +Para mantener la ventana de scrcpy siempre adelante: + +```bash +scrcpy --always-on-top +``` + +#### Pantalla completa + +La aplicación puede ser iniciada en pantalla completa: + +```bash +scrcpy --fullscreen +scrcpy -f # versión breve +``` + +Puede entrar y salir de la pantalla completa con la combinación MOD+f. + +#### Rotación + +Se puede rotar la ventana: + +```bash +scrcpy --rotation 1 +``` + +Los valores posibles son: + - `0`: sin rotación + - `1`: 90 grados contrarreloj + - `2`: 180 grados + - `3`: 90 grados en sentido de las agujas del reloj + +La rotación también puede ser modificada con la combinación de teclas MOD+ _(izquierda)_ y MOD+ _(derecha)_. + +Nótese que _scrcpy_ maneja 3 diferentes rotaciones: + - MOD+r solicita al dispositivo cambiar entre vertical y horizontal (la aplicación en uso puede rechazarlo si no soporta la orientación solicitada). + - [`--lock-video-orientation`](#fijar-la-rotación-del-video) cambia la rotación de la transmisión (la orientación del video enviado a la PC). Esto afecta a la grabación. + - `--rotation` (o MOD+/MOD+) rota solo el contenido de la imagen. Esto solo afecta a la imagen mostrada, no a la grabación. + + +### Otras opciones menores + +#### Solo lectura ("Read-only") + +Para deshabilitar los controles (todo lo que interactúe con el dispositivo: eventos del teclado, eventos del mouse, arrastrar y soltar archivos): + +```bash +scrcpy --no-control +scrcpy -n # versión breve +``` + +#### Pantalla + +Si múltiples pantallas están disponibles, es posible elegir cual transmitir: + +```bash +scrcpy --display 1 +``` + +Los ids de las pantallas se pueden obtener con el siguiente comando: + +```bash +adb shell dumpsys display # busque "mDisplayId=" en la respuesta +``` + +La segunda pantalla solo puede ser manejada si el dispositivo cuenta con Android 10 (en caso contrario será transmitida en el modo solo lectura). + + +#### Permanecer activo + +Para evitar que el dispositivo descanse después de un tiempo mientras está conectado: + +```bash +scrcpy --stay-awake +scrcpy -w # versión breve +``` + +La configuración original se restaura al cerrar scrcpy. + + +#### Apagar la pantalla + +Es posible apagar la pantalla mientras se transmite al iniciar con el siguiente comando: + +```bash +scrcpy --turn-screen-off +scrcpy -S # versión breve +``` + +O presionando MOD+o en cualquier momento. + +Para volver a prenderla, presione MOD+Shift+o. + +En Android, el botón de `POWER` siempre prende la pantalla. Por conveniencia, si `POWER` es enviado vía scrcpy (con click-derecho o MOD+p), esto forzará a apagar la pantalla con un poco de atraso (en la mejor de las situaciones). El botón físico `POWER` seguirá prendiendo la pantalla. + +También puede resultar útil para evitar que el dispositivo entre en inactividad: + +```bash +scrcpy --turn-screen-off --stay-awake +scrcpy -Sw # versión breve +``` + + +#### Renderizar frames vencidos + +Por defecto, para minimizar la latencia, _scrcpy_ siempre renderiza el último frame disponible decodificado, e ignora cualquier frame anterior. + +Para forzar el renderizado de todos los frames (a costo de posible aumento de latencia), use: + +```bash +scrcpy --render-expired-frames +``` + +#### Mostrar clicks + +Para presentaciones, puede resultar útil mostrar los clicks físicos (en el dispositivo físicamente). + +Android provee esta opción en _Opciones para desarrolladores_. + +_Scrcpy_ provee una opción para habilitar esta función al iniciar la aplicación y restaurar el valor original al salir: + +```bash +scrcpy --show-touches +scrcpy -t # versión breve +``` + +Nótese que solo muestra los clicks _físicos_ (con el dedo en el dispositivo). + + +#### Desactivar protector de pantalla + +Por defecto, scrcpy no evita que el protector de pantalla se active en la computadora. + +Para deshabilitarlo: + +```bash +scrcpy --disable-screensaver +``` + + +### Control + +#### Rotar pantalla del dispositivo + +Presione MOD+r para cambiar entre posición vertical y horizontal. + +Nótese que solo rotará si la aplicación activa soporta la orientación solicitada. + +#### Copiar y pegar + +Cuando que el portapapeles de Android cambia, automáticamente se sincroniza al portapapeles de la computadora. + +Cualquier shortcut con Ctrl es enviado al dispositivo. En particular: + - Ctrl+c normalmente copia + - Ctrl+x normalmente corta + - Ctrl+v normalmente pega (después de la sincronización de portapapeles entre la computadora y el dispositivo) + +Esto normalmente funciona como es esperado. + +Sin embargo, este comportamiento depende de la aplicación en uso. Por ejemplo, _Termux_ envía SIGINT con Ctrl+c, y _K-9 Mail_ crea un nuevo mensaje. + +Para copiar, cortar y pegar, en tales casos (solo soportado en Android >= 7): + - MOD+c inyecta `COPY` + - MOD+x inyecta `CUT` + - MOD+v inyecta `PASTE` (después de la sincronización de portapapeles entre la computadora y el dispositivo) + +Además, MOD+Shift+v permite inyectar el texto en el portapapeles de la computadora como una secuencia de teclas. Esto es útil cuando el componente no acepta pegado de texto (por ejemplo en _Termux_), pero puede romper caracteres no pertenecientes a ASCII. + +**AVISO:** Pegar de la computadora al dispositivo (tanto con Ctrl+v o MOD+v) copia el contenido al portapapeles del dispositivo. Como consecuencia, cualquier aplicación de Android puede leer su contenido. Debería evitar pegar contenido sensible (como contraseñas) de esta forma. + +Algunos dispositivos no se comportan como es esperado al establecer el portapapeles programáticamente. La opción `--legacy-paste` está disponible para cambiar el comportamiento de Ctrl+v y MOD+v para que también inyecten el texto del portapapeles de la computadora como una secuencia de teclas (de la misma forma que MOD+Shift+v). + +#### Pellizcar para zoom + +Para simular "pinch-to-zoom": Ctrl+_click-y-mover_. + +Más precisamente, mantén Ctrl mientras presionas botón izquierdo. Hasta que no se suelte el botón, todos los movimientos del mouse cambiarán el tamaño y rotación del contenido (si es soportado por la app en uso) respecto al centro de la pantalla. + +Concretamente, scrcpy genera clicks adicionales con un "dedo virtual" en la posición invertida respecto al centro de la pantalla. + + +#### Preferencias de inyección de texto + +Existen dos tipos de [eventos][textevents] generados al escribir texto: + - _key events_, marcando si la tecla es presionada o soltada; + - _text events_, marcando si un texto fue introducido. + +Por defecto, las letras son inyectadas usando _key events_, para que el teclado funcione como es esperado en juegos (típicamente las teclas WASD). + +Pero esto puede [causar problemas][prefertext]. Si encuentras tales problemas, los puedes evitar con: + +```bash +scrcpy --prefer-text +``` + +(Pero esto romperá el comportamiento del teclado en los juegos) + +[textevents]: https://blog.rom1v.com/2018/03/introducing-scrcpy/#handle-text-input +[prefertext]: https://github.com/Genymobile/scrcpy/issues/650#issuecomment-512945343 + + +#### Repetir tecla + +Por defecto, mantener una tecla presionada genera múltiples _key events_. Esto puede causar problemas de desempeño en algunos juegos, donde estos eventos no tienen sentido de todos modos. + +Para evitar enviar _key events_ repetidos: + +```bash +scrcpy --no-key-repeat +``` + + +#### Botón derecho y botón del medio + +Por defecto, botón derecho ejecuta RETROCEDER (o ENCENDIDO) y botón del medio INICIO. Para inhabilitar estos atajos y enviar los clicks al dispositivo: + +```bash +scrcpy --forward-all-clicks +``` + + +### Arrastrar y soltar archivos + +#### Instalar APKs + +Para instalar un APK, arrastre y suelte el archivo APK (terminado en `.apk`) a la ventana de _scrcpy_. + +No hay respuesta visual, un mensaje se escribirá en la consola. + + +#### Enviar archivos al dispositivo + +Para enviar un archivo a `/sdcard/` en el dispositivo, arrastre y suelte un archivo (no APK) a la ventana de _scrcpy_. + +No hay respuesta visual, un mensaje se escribirá en la consola. + +El directorio de destino puede ser modificado al iniciar: + +```bash +scrcpy --push-target=/sdcard/Download/ +``` + + +### Envío de Audio + +_Scrcpy_ no envía el audio. Use [sndcpy]. + +También lea [issue #14]. + +[sndcpy]: https://github.com/rom1v/sndcpy +[issue #14]: https://github.com/Genymobile/scrcpy/issues/14 + + +## Atajos + +En la siguiente lista, MOD es el atajo modificador. Por defecto es Alt (izquierdo) o Super (izquierdo). + +Se puede modificar usando `--shortcut-mod`. Las posibles teclas son `lctrl` (izquierdo), `rctrl` (derecho), `lalt` (izquierdo), `ralt` (derecho), `lsuper` (izquierdo) y `rsuper` (derecho). Por ejemplo: + +```bash +# use RCtrl para los atajos +scrcpy --shortcut-mod=rctrl + +# use tanto LCtrl+LAlt o LSuper para los atajos +scrcpy --shortcut-mod=lctrl+lalt,lsuper +``` + +_[Super] es generalmente la tecla Windows o Cmd._ + +[Super]: https://en.wikipedia.org/wiki/Super_key_(keyboard_button) + + | Acción | Atajo + | ------------------------------------------- |:----------------------------- + | Alterne entre pantalla compelta | MOD+f + | Rotar pantalla hacia la izquierda | MOD+ _(izquierda)_ + | Rotar pantalla hacia la derecha | MOD+ _(derecha)_ + | Ajustar ventana a 1:1 ("pixel-perfect") | MOD+g + | Ajustar ventana para quitar los bordes negros| MOD+w \| _Doble click¹_ + | Click en `INICIO` | MOD+h \| _Botón del medio_ + | Click en `RETROCEDER` | MOD+b \| _Botón derecho²_ + | Click en `CAMBIAR APLICACIÓN` | MOD+s + | Click en `MENÚ` (desbloquear pantalla) | MOD+m + | Click en `SUBIR VOLUMEN` | MOD+ _(arriba)_ + | Click en `BAJAR VOLUME` | MOD+ _(abajo)_ + | Click en `ENCENDIDO` | MOD+p + | Encendido | _Botón derecho²_ + | Apagar pantalla (manteniendo la transmisión)| MOD+o + | Encender pantalla | MOD+Shift+o + | Rotar pantalla del dispositivo | MOD+r + | Abrir panel de notificaciones | MOD+n + | Cerrar panel de notificaciones | MOD+Shift+n + | Copiar al portapapeles³ | MOD+c + | Cortar al portapapeles³ | MOD+x + | Synchronizar portapapeles y pegar³ | MOD+v + | inyectar texto del portapapeles de la PC | MOD+Shift+v + | Habilitar/Deshabilitar contador de FPS (en stdout) | MOD+i + | Pellizcar para zoom | Ctrl+_click-y-mover_ + +_¹Doble click en los bordes negros para eliminarlos._ +_²Botón derecho enciende la pantalla si estaba apagada, sino ejecuta RETROCEDER._ +_³Solo en Android >= 7._ + +Todos los atajos Ctrl+_tecla_ son enviados al dispositivo para que sean manejados por la aplicación activa. + + +## Path personalizado + +Para usar un binario de _adb_ en particular, configure el path `ADB` en las variables de entorno: + +```bash +ADB=/path/to/adb scrcpy +``` + +Para sobreescribir el path del archivo `scrcpy-server`, configure el path en `SCRCPY_SERVER_PATH`. + + +## ¿Por qué _scrcpy_? + +Un colega me retó a encontrar un nombre tan impronunciable como [gnirehtet]. + +[`strcpy`] copia un **str**ing; `scrcpy` copia un **scr**een. + +[gnirehtet]: https://github.com/Genymobile/gnirehtet +[`strcpy`]: http://man7.org/linux/man-pages/man3/strcpy.3.html + + +## ¿Cómo construir (BUILD)? + +Véase [BUILD] (en inglés). + + +## Problemas generales + +Vea las [preguntas frecuentes (en inglés)](FAQ.md). + + +## Desarrolladores + +Lea la [hoja de desarrolladores (en inglés)](DEVELOP.md). + + +## Licencia + + Copyright (C) 2018 Genymobile + Copyright (C) 2018-2021 Romain Vimont + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +## Artículos + +- [Introducing scrcpy][article-intro] (en inglés) +- [Scrcpy now works wirelessly][article-tcpip] (en inglés) + +[article-intro]: https://blog.rom1v.com/2018/03/introducing-scrcpy/ +[article-tcpip]: https://www.genymotion.com/blog/open-source-project-scrcpy-now-works-wirelessly/ From 4c31911df2cfb5ae7d918894df394f8a7a317e23 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Mon, 17 May 2021 09:41:22 +0200 Subject: [PATCH 417/450] Pass serial within struct server_params This was the only option passed separately. --- app/src/scrcpy.c | 3 ++- app/src/server.c | 9 ++++----- app/src/server.h | 4 ++-- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index 8418353d..29ebe129 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -256,6 +256,7 @@ scrcpy(const struct scrcpy_options *options) { bool record = !!options->record_filename; struct server_params params = { + .serial = options->serial, .log_level = options->log_level, .crop = options->crop, .port_range = options->port_range, @@ -272,7 +273,7 @@ scrcpy(const struct scrcpy_options *options) { .force_adb_forward = options->force_adb_forward, .power_off_on_close = options->power_off_on_close, }; - if (!server_start(&server, options->serial, ¶ms)) { + if (!server_start(&server, ¶ms)) { goto end; } diff --git a/app/src/server.c b/app/src/server.c index 646006d6..41e8166c 100644 --- a/app/src/server.c +++ b/app/src/server.c @@ -407,16 +407,15 @@ run_wait_server(void *data) { } bool -server_start(struct server *server, const char *serial, - const struct server_params *params) { - if (serial) { - server->serial = strdup(serial); +server_start(struct server *server, const struct server_params *params) { + if (params->serial) { + server->serial = strdup(params->serial); if (!server->serial) { return false; } } - if (!push_server(serial)) { + if (!push_server(params->serial)) { /* server->serial will be freed on server_destroy() */ return false; } diff --git a/app/src/server.h b/app/src/server.h index ad49a982..c249b374 100644 --- a/app/src/server.h +++ b/app/src/server.h @@ -33,6 +33,7 @@ struct server { }; struct server_params { + const char *serial; enum sc_log_level log_level; const char *crop; const char *codec_options; @@ -56,8 +57,7 @@ server_init(struct server *server); // push, enable tunnel et start the server bool -server_start(struct server *server, const char *serial, - const struct server_params *params); +server_start(struct server *server, const struct server_params *params); #define DEVICE_NAME_FIELD_LENGTH 64 // block until the communication with the server is established From 6fd7e89da1d3982697af9c581b322a1ca4398621 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 28 May 2021 21:23:10 +0200 Subject: [PATCH 418/450] Explicitly initialize decoder sink_count The zero-initialization relied on the fact that the decoder instance is static. --- app/src/decoder.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/src/decoder.c b/app/src/decoder.c index dbaa3d39..aa5018b3 100644 --- a/app/src/decoder.c +++ b/app/src/decoder.c @@ -140,6 +140,8 @@ decoder_packet_sink_push(struct sc_packet_sink *sink, const AVPacket *packet) { void decoder_init(struct decoder *decoder) { + decoder->sink_count = 0; + static const struct sc_packet_sink_ops ops = { .open = decoder_packet_sink_open, .close = decoder_packet_sink_close, From 506f918fb7cc944e08b108ff357c70d26bed90d2 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 28 May 2021 21:29:14 +0200 Subject: [PATCH 419/450] Group components into struct scrcpy This avoids to refer to many structs globally. --- app/src/scrcpy.c | 108 +++++++++++++++++++++++++---------------------- 1 file changed, 57 insertions(+), 51 deletions(-) diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index 29ebe129..17902156 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -29,17 +29,19 @@ # include "v4l2_sink.h" #endif -static struct server server; -static struct screen screen; -static struct stream stream; -static struct decoder decoder; -static struct recorder recorder; +struct scrcpy { + struct server server; + struct screen screen; + struct stream stream; + struct decoder decoder; + struct recorder recorder; #ifdef HAVE_V4L2 -static struct sc_v4l2_sink v4l2_sink; + struct sc_v4l2_sink v4l2_sink; #endif -static struct controller controller; -static struct file_handler file_handler; -static struct input_manager input_manager; + struct controller controller; + struct file_handler file_handler; + struct input_manager input_manager; +}; #ifdef _WIN32 BOOL WINAPI windows_ctrl_handler(DWORD ctrl_type) { @@ -129,7 +131,8 @@ enum event_result { }; static enum event_result -handle_event(SDL_Event *event, const struct scrcpy_options *options) { +handle_event(struct scrcpy *s, const struct scrcpy_options *options, + SDL_Event *event) { switch (event->type) { case EVENT_STREAM_STOPPED: LOGD("Video stream stopped"); @@ -154,17 +157,17 @@ handle_event(SDL_Event *event, const struct scrcpy_options *options) { } else { action = ACTION_PUSH_FILE; } - file_handler_request(&file_handler, action, file); + file_handler_request(&s->file_handler, action, file); goto end; } } - bool consumed = screen_handle_event(&screen, event); + bool consumed = screen_handle_event(&s->screen, event); if (consumed) { goto end; } - consumed = input_manager_handle_event(&input_manager, event); + consumed = input_manager_handle_event(&s->input_manager, event); (void) consumed; end: @@ -172,10 +175,10 @@ end: } static bool -event_loop(const struct scrcpy_options *options) { +event_loop(struct scrcpy *s, const struct scrcpy_options *options) { SDL_Event event; while (SDL_WaitEvent(&event)) { - enum event_result result = handle_event(&event, options); + enum event_result result = handle_event(s, options, &event); switch (result) { case EVENT_RESULT_STOPPED_BY_USER: return true; @@ -237,7 +240,10 @@ stream_on_eos(struct stream *stream, void *userdata) { bool scrcpy(const struct scrcpy_options *options) { - if (!server_init(&server)) { + static struct scrcpy scrcpy; + struct scrcpy *s = &scrcpy; + + if (!server_init(&s->server)) { return false; } @@ -273,7 +279,7 @@ scrcpy(const struct scrcpy_options *options) { .force_adb_forward = options->force_adb_forward, .power_off_on_close = options->power_off_on_close, }; - if (!server_start(&server, ¶ms)) { + if (!server_start(&s->server, ¶ms)) { goto end; } @@ -287,12 +293,12 @@ scrcpy(const struct scrcpy_options *options) { char device_name[DEVICE_NAME_FIELD_LENGTH]; struct size frame_size; - if (!server_connect_to(&server, device_name, &frame_size)) { + if (!server_connect_to(&s->server, device_name, &frame_size)) { goto end; } if (options->display && options->control) { - if (!file_handler_init(&file_handler, server.serial, + if (!file_handler_init(&s->file_handler, s->server.serial, options->push_target)) { goto end; } @@ -305,19 +311,19 @@ scrcpy(const struct scrcpy_options *options) { needs_decoder |= !!options->v4l2_device; #endif if (needs_decoder) { - decoder_init(&decoder); - dec = &decoder; + decoder_init(&s->decoder); + dec = &s->decoder; } struct recorder *rec = NULL; if (record) { - if (!recorder_init(&recorder, + if (!recorder_init(&s->recorder, options->record_filename, options->record_format, frame_size)) { goto end; } - rec = &recorder; + rec = &s->recorder; recorder_initialized = true; } @@ -326,24 +332,24 @@ scrcpy(const struct scrcpy_options *options) { const struct stream_callbacks stream_cbs = { .on_eos = stream_on_eos, }; - stream_init(&stream, server.video_socket, &stream_cbs, NULL); + stream_init(&s->stream, s->server.video_socket, &stream_cbs, NULL); if (dec) { - stream_add_sink(&stream, &dec->packet_sink); + stream_add_sink(&s->stream, &dec->packet_sink); } if (rec) { - stream_add_sink(&stream, &rec->packet_sink); + stream_add_sink(&s->stream, &rec->packet_sink); } if (options->display) { if (options->control) { - if (!controller_init(&controller, server.control_socket)) { + if (!controller_init(&s->controller, s->server.control_socket)) { goto end; } controller_initialized = true; - if (!controller_start(&controller)) { + if (!controller_start(&s->controller)) { goto end; } controller_started = true; @@ -366,19 +372,19 @@ scrcpy(const struct scrcpy_options *options) { .fullscreen = options->fullscreen, }; - if (!screen_init(&screen, &screen_params)) { + if (!screen_init(&s->screen, &screen_params)) { goto end; } screen_initialized = true; - decoder_add_sink(&decoder, &screen.frame_sink); + decoder_add_sink(&s->decoder, &s->screen.frame_sink); if (options->turn_screen_off) { struct control_msg msg; msg.type = CONTROL_MSG_TYPE_SET_SCREEN_POWER_MODE; msg.set_screen_power_mode.mode = SCREEN_POWER_MODE_OFF; - if (!controller_push_msg(&controller, &msg)) { + if (!controller_push_msg(&s->controller, &msg)) { LOGW("Could not request 'set screen power mode'"); } } @@ -386,11 +392,11 @@ scrcpy(const struct scrcpy_options *options) { #ifdef HAVE_V4L2 if (options->v4l2_device) { - if (!sc_v4l2_sink_init(&v4l2_sink, options->v4l2_device, frame_size)) { + if (!sc_v4l2_sink_init(&s->v4l2_sink, options->v4l2_device, frame_size)) { goto end; } - decoder_add_sink(&decoder, &v4l2_sink.frame_sink); + decoder_add_sink(&s->decoder, &s->v4l2_sink.frame_sink); v4l2_sink_initialized = true; } @@ -398,74 +404,74 @@ scrcpy(const struct scrcpy_options *options) { // now we consumed the header values, the socket receives the video stream // start the stream - if (!stream_start(&stream)) { + if (!stream_start(&s->stream)) { goto end; } stream_started = true; - input_manager_init(&input_manager, &controller, &screen, options); + input_manager_init(&s->input_manager, &s->controller, &s->screen, options); - ret = event_loop(options); + ret = event_loop(s, options); LOGD("quit..."); // Close the window immediately on closing, because screen_destroy() may // only be called once the stream thread is joined (it may take time) - screen_hide_window(&screen); + screen_hide_window(&s->screen); end: // The stream is not stopped explicitly, because it will stop by itself on // end-of-stream if (controller_started) { - controller_stop(&controller); + controller_stop(&s->controller); } if (file_handler_initialized) { - file_handler_stop(&file_handler); + file_handler_stop(&s->file_handler); } if (screen_initialized) { - screen_interrupt(&screen); + screen_interrupt(&s->screen); } if (server_started) { // shutdown the sockets and kill the server - server_stop(&server); + server_stop(&s->server); } // now that the sockets are shutdown, the stream and controller are // interrupted, we can join them if (stream_started) { - stream_join(&stream); + stream_join(&s->stream); } #ifdef HAVE_V4L2 if (v4l2_sink_initialized) { - sc_v4l2_sink_destroy(&v4l2_sink); + sc_v4l2_sink_destroy(&s->v4l2_sink); } #endif // Destroy the screen only after the stream is guaranteed to be finished, // because otherwise the screen could receive new frames after destruction if (screen_initialized) { - screen_join(&screen); - screen_destroy(&screen); + screen_join(&s->screen); + screen_destroy(&s->screen); } if (controller_started) { - controller_join(&controller); + controller_join(&s->controller); } if (controller_initialized) { - controller_destroy(&controller); + controller_destroy(&s->controller); } if (recorder_initialized) { - recorder_destroy(&recorder); + recorder_destroy(&s->recorder); } if (file_handler_initialized) { - file_handler_join(&file_handler); - file_handler_destroy(&file_handler); + file_handler_join(&s->file_handler); + file_handler_destroy(&s->file_handler); } - server_destroy(&server); + server_destroy(&s->server); return ret; } From 969bfd43744a88109d369089f9ee1313211ac9e1 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sat, 1 May 2021 00:18:55 +0200 Subject: [PATCH 420/450] Serialize clean-up configuration This avoids to pass each option as individual parameter and parse them manually (it's still "manual" in the Parcelable implementation). Refs #824 Reviewed-by: Yu-Chen Lin --- .../java/com/genymobile/scrcpy/CleanUp.java | 138 +++++++++++++++--- .../java/com/genymobile/scrcpy/Server.java | 2 +- 2 files changed, 119 insertions(+), 21 deletions(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/CleanUp.java b/server/src/main/java/com/genymobile/scrcpy/CleanUp.java index ccdc9fd8..ec61a1c0 100644 --- a/server/src/main/java/com/genymobile/scrcpy/CleanUp.java +++ b/server/src/main/java/com/genymobile/scrcpy/CleanUp.java @@ -3,6 +3,10 @@ package com.genymobile.scrcpy; import com.genymobile.scrcpy.wrappers.ContentProvider; import com.genymobile.scrcpy.wrappers.ServiceManager; +import android.os.Parcel; +import android.os.Parcelable; +import android.util.Base64; + import java.io.File; import java.io.IOException; @@ -15,25 +19,123 @@ 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 { + + public static final Creator CREATOR = new Creator() { + @Override + public Config createFromParcel(Parcel in) { + return new Config(in); + } + + @Override + public Config[] newArray(int size) { + return new Config[size]; + } + }; + + private static final int FLAG_DISABLE_SHOW_TOUCHES = 1; + private static final int FLAG_RESTORE_NORMAL_POWER_MODE = 2; + private static final int FLAG_POWER_OFF_SCREEN = 4; + + private int displayId; + + // Restore the value (between 0 and 7), -1 to not restore + // + private int restoreStayOn = -1; + + private boolean disableShowTouches; + private boolean restoreNormalPowerMode; + private boolean powerOffScreen; + + public Config() { + // Default constructor, the fields are initialized by CleanUp.configure() + } + + protected Config(Parcel in) { + displayId = in.readInt(); + restoreStayOn = in.readInt(); + byte options = in.readByte(); + disableShowTouches = (options & FLAG_DISABLE_SHOW_TOUCHES) != 0; + restoreNormalPowerMode = (options & FLAG_RESTORE_NORMAL_POWER_MODE) != 0; + powerOffScreen = (options & FLAG_POWER_OFF_SCREEN) != 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(displayId); + dest.writeInt(restoreStayOn); + byte options = 0; + if (disableShowTouches) { + options |= FLAG_DISABLE_SHOW_TOUCHES; + } + if (restoreNormalPowerMode) { + options |= FLAG_RESTORE_NORMAL_POWER_MODE; + } + if (powerOffScreen) { + options |= FLAG_POWER_OFF_SCREEN; + } + dest.writeByte(options); + } + + private boolean hasWork() { + return disableShowTouches || restoreStayOn != -1 || restoreNormalPowerMode || powerOffScreen; + } + + @Override + public int describeContents() { + return 0; + } + + byte[] serialize() { + Parcel parcel = Parcel.obtain(); + writeToParcel(parcel, 0); + byte[] bytes = parcel.marshall(); + parcel.recycle(); + return bytes; + } + + static Config deserialize(byte[] bytes) { + Parcel parcel = Parcel.obtain(); + parcel.unmarshall(bytes, 0, bytes.length); + parcel.setDataPosition(0); + return CREATOR.createFromParcel(parcel); + } + + static Config fromBase64(String base64) { + byte[] bytes = Base64.decode(base64, Base64.NO_WRAP); + return deserialize(bytes); + } + + String toBase64() { + byte[] bytes = serialize(); + return Base64.encodeToString(bytes, Base64.NO_WRAP); + } + } + private CleanUp() { // not instantiable } - public static void configure(boolean disableShowTouches, int restoreStayOn, boolean restoreNormalPowerMode, boolean powerOffScreen, int displayId) + public static void configure(int displayId, int restoreStayOn, boolean disableShowTouches, boolean restoreNormalPowerMode, boolean powerOffScreen) throws IOException { - boolean needProcess = disableShowTouches || restoreStayOn != -1 || restoreNormalPowerMode || powerOffScreen; - if (needProcess) { - startProcess(disableShowTouches, restoreStayOn, restoreNormalPowerMode, powerOffScreen, displayId); + Config config = new Config(); + config.displayId = displayId; + config.disableShowTouches = disableShowTouches; + config.restoreStayOn = restoreStayOn; + config.restoreNormalPowerMode = restoreNormalPowerMode; + config.powerOffScreen = powerOffScreen; + + if (config.hasWork()) { + startProcess(config); } else { // There is no additional clean up to do when scrcpy dies unlinkSelf(); } } - private static void startProcess(boolean disableShowTouches, int restoreStayOn, boolean restoreNormalPowerMode, boolean powerOffScreen, - int displayId) throws IOException { - String[] cmd = {"app_process", "/", CleanUp.class.getName(), String.valueOf(disableShowTouches), String.valueOf( - restoreStayOn), String.valueOf(restoreNormalPowerMode), String.valueOf(powerOffScreen), String.valueOf(displayId)}; + private static void startProcess(Config config) throws IOException { + String[] cmd = {"app_process", "/", CleanUp.class.getName(), config.toBase64()}; ProcessBuilder builder = new ProcessBuilder(cmd); builder.environment().put("CLASSPATH", SERVER_PATH); @@ -60,31 +162,27 @@ public final class CleanUp { Ln.i("Cleaning up"); - boolean disableShowTouches = Boolean.parseBoolean(args[0]); - int restoreStayOn = Integer.parseInt(args[1]); - boolean restoreNormalPowerMode = Boolean.parseBoolean(args[2]); - boolean powerOffScreen = Boolean.parseBoolean(args[3]); - int displayId = Integer.parseInt(args[4]); + Config config = Config.fromBase64(args[0]); - if (disableShowTouches || restoreStayOn != -1) { + if (config.disableShowTouches || config.restoreStayOn != -1) { ServiceManager serviceManager = new ServiceManager(); try (ContentProvider settings = serviceManager.getActivityManager().createSettingsProvider()) { - if (disableShowTouches) { + if (config.disableShowTouches) { Ln.i("Disabling \"show touches\""); settings.putValue(ContentProvider.TABLE_SYSTEM, "show_touches", "0"); } - if (restoreStayOn != -1) { + if (config.restoreStayOn != -1) { Ln.i("Restoring \"stay awake\""); - settings.putValue(ContentProvider.TABLE_GLOBAL, "stay_on_while_plugged_in", String.valueOf(restoreStayOn)); + settings.putValue(ContentProvider.TABLE_GLOBAL, "stay_on_while_plugged_in", String.valueOf(config.restoreStayOn)); } } } if (Device.isScreenOn()) { - if (powerOffScreen) { + if (config.powerOffScreen) { Ln.i("Power off screen"); - Device.powerOffScreen(displayId); - } else if (restoreNormalPowerMode) { + Device.powerOffScreen(config.displayId); + } else if (config.restoreNormalPowerMode) { Ln.i("Restoring normal power mode"); Device.setScreenPowerMode(Device.POWER_MODE_NORMAL); } diff --git a/server/src/main/java/com/genymobile/scrcpy/Server.java b/server/src/main/java/com/genymobile/scrcpy/Server.java index 674f314b..fdd9db88 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Server.java +++ b/server/src/main/java/com/genymobile/scrcpy/Server.java @@ -50,7 +50,7 @@ public final class Server { } } - CleanUp.configure(mustDisableShowTouchesOnCleanUp, restoreStayOn, true, options.getPowerOffScreenOnClose(), options.getDisplayId()); + CleanUp.configure(options.getDisplayId(), restoreStayOn, mustDisableShowTouchesOnCleanUp, true, options.getPowerOffScreenOnClose()); boolean tunnelForward = options.isTunnelForward(); From f7533e8896c5e362af9a2f31b2b8d849d3207cc2 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 19 Feb 2021 17:11:44 +0100 Subject: [PATCH 421/450] Use non-secure display for Android >= 12 Since Android 12, secure displays could not be created with shell permissions anymore. Refs commit 1fdde490fd2a0b89680a2b5da5e5274192398023 Fixes #2129 --- .../src/main/java/com/genymobile/scrcpy/ScreenEncoder.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java b/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java index c7c104e4..9a149241 100644 --- a/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java +++ b/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java @@ -7,6 +7,7 @@ import android.media.MediaCodec; import android.media.MediaCodecInfo; import android.media.MediaCodecList; import android.media.MediaFormat; +import android.os.Build; import android.os.IBinder; import android.view.Surface; @@ -225,7 +226,9 @@ public class ScreenEncoder implements Device.RotationListener { } private static IBinder createDisplay() { - return SurfaceControl.createDisplay("scrcpy", true); + // Since Android 12, secure displays could not be created with shell permissions anymore + boolean secure = Build.VERSION.SDK_INT <= Build.VERSION_CODES.R; + return SurfaceControl.createDisplay("scrcpy", secure); } private static void configure(MediaCodec codec, MediaFormat format) { From 16a63e0917e423fb417eaad3ae0ca05f16227db6 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 11 Jun 2021 10:02:52 +0200 Subject: [PATCH 422/450] Use non-secure display for Android 12 preview Android 12 preview identifies as Android 11, but its codename is "S". Refs #2129 --- .../src/main/java/com/genymobile/scrcpy/ScreenEncoder.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java b/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java index 9a149241..2f7109c5 100644 --- a/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java +++ b/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java @@ -226,8 +226,10 @@ public class ScreenEncoder implements Device.RotationListener { } private static IBinder createDisplay() { - // Since Android 12, secure displays could not be created with shell permissions anymore - boolean secure = Build.VERSION.SDK_INT <= Build.VERSION_CODES.R; + // Since Android 12 (preview), secure displays could not be created with shell permissions anymore. + // On Android 12 preview, SDK_INT is still R (not S), but CODENAME is "S". + boolean secure = Build.VERSION.SDK_INT < Build.VERSION_CODES.R || (Build.VERSION.SDK_INT == Build.VERSION_CODES.R && !"S" + .equals(Build.VERSION.CODENAME)); return SurfaceControl.createDisplay("scrcpy", secure); } From f76fe2c0d4847d0e40d8708f97591abf3fa22ea5 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 11 Jun 2021 18:39:23 +0200 Subject: [PATCH 423/450] Fix --lock-video-orientation syntax The argument for option --lock-video-orientation has been made optional by 5af9d0ee0faddfca689f468c7d463c86233e8d93. With getopt_long(), contrary to mandatory arguments, optional arguments must be given with a '=': --lock-video-orientation 2 # wrong, parse error --lock-video-orientation=2 # correct --- README.md | 8 ++++---- app/tests/test_cli.c | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index c3bab060..503ef87d 100644 --- a/README.md +++ b/README.md @@ -215,10 +215,10 @@ To lock the orientation of the mirroring: ```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=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. diff --git a/app/tests/test_cli.c b/app/tests/test_cli.c index 3fa9b3d7..94740a9a 100644 --- a/app/tests/test_cli.c +++ b/app/tests/test_cli.c @@ -51,7 +51,7 @@ static void test_options(void) { "--fullscreen", "--max-fps", "30", "--max-size", "1024", - "--lock-video-orientation", "2", + "--lock-video-orientation=2", // optional arguments require '=' // "--no-control" is not compatible with "--turn-screen-off" // "--no-display" is not compatible with "--fulscreen" "--port", "1234:1236", From af228706f14696e818f4004691d4de0006ac4910 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 13 Jun 2021 18:31:48 +0200 Subject: [PATCH 424/450] Fix compatibility with old FFmpeg V4L2 sink used a "url" field format AVFormatContext which has been introduced in lavf 58.7.100. Fixes #2382 Refs Refs --- app/src/compat.h | 12 ++++++++++++ app/src/v4l2_sink.c | 5 +++++ 2 files changed, 17 insertions(+) diff --git a/app/src/compat.h b/app/src/compat.h index 9d9a7884..8e2d18f4 100644 --- a/app/src/compat.h +++ b/app/src/compat.h @@ -22,6 +22,18 @@ # define SCRCPY_LAVF_REQUIRES_REGISTER_ALL #endif + +// In ffmpeg/doc/APIchanges: +// 2018-01-28 - ea3672b7d6 - lavf 58.7.100 - avformat.h +// Deprecate AVFormatContext filename field which had limited length, use the +// new dynamically allocated url field instead. +// +// 2018-01-28 - ea3672b7d6 - lavf 58.7.100 - avformat.h +// Add url field to AVFormatContext and add ff_format_set_url helper function. +#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(58, 7, 100) +# define SCRCPY_LAVF_HAS_AVFORMATCONTEXT_URL +#endif + #if SDL_VERSION_ATLEAST(2, 0, 5) // # define SCRCPY_SDL_HAS_HINT_MOUSE_FOCUS_CLICKTHROUGH diff --git a/app/src/v4l2_sink.c b/app/src/v4l2_sink.c index fd0bda12..d7c1a667 100644 --- a/app/src/v4l2_sink.c +++ b/app/src/v4l2_sink.c @@ -180,12 +180,17 @@ sc_v4l2_sink_open(struct sc_v4l2_sink *vs) { // still expects a pointer-to-non-const (it has not be updated accordingly) // vs->format_ctx->oformat = (AVOutputFormat *) format; +#ifdef SCRCPY_LAVF_HAS_AVFORMATCONTEXT_URL vs->format_ctx->url = strdup(vs->device_name); if (!vs->format_ctx->url) { LOGE("Could not strdup v4l2 device name"); goto error_avformat_free_context; return false; } +#else + strncpy(vs->format_ctx->filename, vs->device_name, + sizeof(vs->format_ctx->filename)); +#endif AVStream *ostream = avformat_new_stream(vs->format_ctx, encoder); if (!ostream) { From a5d71eee450b3c91c942d15b598959021126f31d Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Mon, 14 Jun 2021 08:16:05 +0200 Subject: [PATCH 425/450] Clarify --no-display usage with v4l2 --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 503ef87d..ba68dfb1 100644 --- a/README.md +++ b/README.md @@ -303,7 +303,8 @@ To start scrcpy using a v4l2 sink: ```bash scrcpy --v4l2-sink=/dev/videoN -scrcpy --v4l2-sink=/dev/videoN -N # --no-display to disable mirroring window +scrcpy --v4l2-sink=/dev/videoN --no-display # disable mirroring window +scrcpy --v4l2-sink=/dev/videoN -N # short version ``` (replace `N` by the device ID, check with `ls /dev/video*`) From e8b053ad2f1da25f2b0e06d2874cabb12ff44e01 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Mon, 14 Jun 2021 09:07:49 +0200 Subject: [PATCH 426/450] Allocate AVPacket for stream->pending From FFmpeg/doc/APIchanges: 2021-03-17 - f7db77bd87 - lavc 58.133.100 - codec.h Deprecated av_init_packet(). Once removed, sizeof(AVPacket) will no longer be a part of the public ABI. Remove the has_pending boolean, which can be replaced by: stream->pending != NULL Refs #2302 --- app/src/stream.c | 44 +++++++++++++++++++++++++------------------- app/src/stream.h | 3 +-- 2 files changed, 26 insertions(+), 21 deletions(-) diff --git a/app/src/stream.c b/app/src/stream.c index 2d9a0ab4..86f5d87a 100644 --- a/app/src/stream.c +++ b/app/src/stream.c @@ -104,33 +104,38 @@ static bool stream_push_packet(struct stream *stream, AVPacket *packet) { bool is_config = packet->pts == AV_NOPTS_VALUE; - // A config packet must not be decoded immetiately (it contains no + // A config packet must not be decoded immediately (it contains no // frame); instead, it must be concatenated with the future data packet. - if (stream->has_pending || is_config) { + if (stream->pending || is_config) { size_t offset; - if (stream->has_pending) { - offset = stream->pending.size; - if (av_grow_packet(&stream->pending, packet->size)) { + if (stream->pending) { + offset = stream->pending->size; + if (av_grow_packet(stream->pending, packet->size)) { LOGE("Could not grow packet"); return false; } } else { offset = 0; - if (av_new_packet(&stream->pending, packet->size)) { - LOGE("Could not create packet"); + stream->pending = av_packet_alloc(); + if (!stream->pending) { + LOGE("Could not allocate packet"); + return false; + } + if (av_new_packet(stream->pending, packet->size)) { + LOGE("Could not create packet"); + av_packet_free(&stream->pending); return false; } - stream->has_pending = true; } - memcpy(stream->pending.data + offset, packet->data, packet->size); + memcpy(stream->pending->data + offset, packet->data, packet->size); if (!is_config) { // prepare the concat packet to send to the decoder - stream->pending.pts = packet->pts; - stream->pending.dts = packet->dts; - stream->pending.flags = packet->flags; - packet = &stream->pending; + stream->pending->pts = packet->pts; + stream->pending->dts = packet->dts; + stream->pending->flags = packet->flags; + packet = stream->pending; } } @@ -144,10 +149,10 @@ stream_push_packet(struct stream *stream, AVPacket *packet) { // data packet bool ok = stream_parse(stream, packet); - if (stream->has_pending) { + if (stream->pending) { // the pending packet must be discarded (consumed or error) - stream->has_pending = false; - av_packet_unref(&stream->pending); + av_packet_unref(stream->pending); + av_packet_free(&stream->pending); } if (!ok) { @@ -233,8 +238,9 @@ run_stream(void *data) { LOGD("End of frames"); - if (stream->has_pending) { - av_packet_unref(&stream->pending); + if (stream->pending) { + av_packet_unref(stream->pending); + av_packet_free(&stream->pending); } av_parser_close(stream->parser); @@ -252,7 +258,7 @@ void stream_init(struct stream *stream, socket_t socket, const struct stream_callbacks *cbs, void *cbs_userdata) { stream->socket = socket; - stream->has_pending = false; + stream->pending = NULL; stream->sink_count = 0; assert(cbs && cbs->on_eos); diff --git a/app/src/stream.h b/app/src/stream.h index 9fc4d1e1..d7047c95 100644 --- a/app/src/stream.h +++ b/app/src/stream.h @@ -24,8 +24,7 @@ struct stream { AVCodecParserContext *parser; // successive packets may need to be concatenated, until a non-config // packet is available - bool has_pending; - AVPacket pending; + AVPacket *pending; const struct stream_callbacks *cbs; void *cbs_userdata; From 318b6a572e0e866554c5aa5a2ed8c4d0d774a5dc Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Mon, 14 Jun 2021 09:07:49 +0200 Subject: [PATCH 427/450] Allocate AVPacket for local stream packet From FFmpeg/doc/APIchanges: 2021-03-17 - f7db77bd87 - lavc 58.133.100 - codec.h Deprecated av_init_packet(). Once removed, sizeof(AVPacket) will no longer be a part of the public ABI. Refs #2302 --- app/src/stream.c | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/app/src/stream.c b/app/src/stream.c index 86f5d87a..d1b8b9f3 100644 --- a/app/src/stream.c +++ b/app/src/stream.c @@ -220,16 +220,21 @@ run_stream(void *data) { // It's more complicated, but this allows to reduce the latency by 1 frame! stream->parser->flags |= PARSER_FLAG_COMPLETE_FRAMES; + AVPacket *packet = av_packet_alloc(); + if (!packet) { + LOGE("Could not allocate packet"); + goto finally_close_parser; + } + for (;;) { - AVPacket packet; - bool ok = stream_recv_packet(stream, &packet); + bool ok = stream_recv_packet(stream, packet); if (!ok) { // end of stream break; } - ok = stream_push_packet(stream, &packet); - av_packet_unref(&packet); + ok = stream_push_packet(stream, packet); + av_packet_unref(packet); if (!ok) { // cannot process packet (error already logged) break; @@ -243,6 +248,8 @@ run_stream(void *data) { av_packet_free(&stream->pending); } + av_packet_free(&packet); +finally_close_parser: av_parser_close(stream->parser); finally_close_sinks: stream_close_sinks(stream); From 4af317d40da70d63dee44bf645694ed5c4f05d65 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Mon, 14 Jun 2021 09:07:49 +0200 Subject: [PATCH 428/450] Allocate AVPacket for recorder From FFmpeg/doc/APIchanges: 2021-03-17 - f7db77bd87 - lavc 58.133.100 - codec.h Deprecated av_init_packet(). Once removed, sizeof(AVPacket) will no longer be a part of the public ABI. Refs #2302 --- app/src/recorder.c | 27 ++++++++++++++++----------- app/src/recorder.h | 2 +- 2 files changed, 17 insertions(+), 12 deletions(-) diff --git a/app/src/recorder.c b/app/src/recorder.c index 91390ff1..85570324 100644 --- a/app/src/recorder.c +++ b/app/src/recorder.c @@ -35,11 +35,14 @@ record_packet_new(const AVPacket *packet) { return NULL; } - // av_packet_ref() does not initialize all fields in old FFmpeg versions - // See - av_init_packet(&rec->packet); + rec->packet = av_packet_alloc(); + if (!rec->packet) { + free(rec); + return NULL; + } - if (av_packet_ref(&rec->packet, packet)) { + if (av_packet_ref(rec->packet, packet)) { + av_packet_free(&rec->packet); free(rec); return NULL; } @@ -48,7 +51,8 @@ record_packet_new(const AVPacket *packet) { static void record_packet_delete(struct record_packet *rec) { - av_packet_unref(&rec->packet); + av_packet_unref(rec->packet); + av_packet_free(&rec->packet); free(rec); } @@ -144,8 +148,8 @@ run_recorder(void *data) { struct record_packet *last = recorder->previous; if (last) { // assign an arbitrary duration to the last packet - last->packet.duration = 100000; - bool ok = recorder_write(recorder, &last->packet); + last->packet->duration = 100000; + bool ok = recorder_write(recorder, last->packet); if (!ok) { // failing to write the last frame is not very serious, no // future frame may depend on it, so the resulting file @@ -172,13 +176,14 @@ run_recorder(void *data) { } // config packets have no PTS, we must ignore them - if (rec->packet.pts != AV_NOPTS_VALUE - && previous->packet.pts != AV_NOPTS_VALUE) { + if (rec->packet->pts != AV_NOPTS_VALUE + && previous->packet->pts != AV_NOPTS_VALUE) { // we now know the duration of the previous packet - previous->packet.duration = rec->packet.pts - previous->packet.pts; + previous->packet->duration = + rec->packet->pts - previous->packet->pts; } - bool ok = recorder_write(recorder, &previous->packet); + bool ok = recorder_write(recorder, previous->packet); record_packet_delete(previous); if (!ok) { LOGE("Could not record packet"); diff --git a/app/src/recorder.h b/app/src/recorder.h index 1b2b9284..0c376cd1 100644 --- a/app/src/recorder.h +++ b/app/src/recorder.h @@ -13,7 +13,7 @@ #include "util/thread.h" struct record_packet { - AVPacket packet; + AVPacket *packet; struct record_packet *next; }; From cd2894570d224d854d6e27abb401434fa6ec2840 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Mon, 14 Jun 2021 09:07:49 +0200 Subject: [PATCH 429/450] Allocate AVPacket for v4l2_sink From FFmpeg/doc/APIchanges: 2021-03-17 - f7db77bd87 - lavc 58.133.100 - codec.h Deprecated av_init_packet(). Once removed, sizeof(AVPacket) will no longer be a part of the public ABI. Refs #2302 --- app/src/v4l2_sink.c | 13 +++++++++++-- app/src/v4l2_sink.h | 2 +- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/app/src/v4l2_sink.c b/app/src/v4l2_sink.c index d7c1a667..bd184b4d 100644 --- a/app/src/v4l2_sink.c +++ b/app/src/v4l2_sink.c @@ -86,7 +86,7 @@ encode_and_write_frame(struct sc_v4l2_sink *vs, const AVFrame *frame) { return false; } - AVPacket *packet = &vs->packet; + AVPacket *packet = vs->packet; ret = avcodec_receive_packet(vs->encoder_ctx, packet); if (ret == 0) { // A packet was received @@ -235,11 +235,17 @@ sc_v4l2_sink_open(struct sc_v4l2_sink *vs) { goto error_avcodec_close; } + vs->packet = av_packet_alloc(); + if (!vs->packet) { + LOGE("Could not allocate packet"); + goto error_av_frame_free; + } + LOGD("Starting v4l2 thread"); ok = sc_thread_create(&vs->thread, run_v4l2_sink, "v4l2", vs); if (!ok) { LOGC("Could not start v4l2 thread"); - goto error_av_frame_free; + goto error_av_packet_free; } vs->header_written = false; @@ -249,6 +255,8 @@ sc_v4l2_sink_open(struct sc_v4l2_sink *vs) { return true; +error_av_packet_free: + av_packet_free(&vs->packet); error_av_frame_free: av_frame_free(&vs->frame); error_avcodec_close: @@ -278,6 +286,7 @@ sc_v4l2_sink_close(struct sc_v4l2_sink *vs) { sc_thread_join(&vs->thread, NULL); + av_packet_free(&vs->packet); av_frame_free(&vs->frame); avcodec_close(vs->encoder_ctx); avcodec_free_context(&vs->encoder_ctx); diff --git a/app/src/v4l2_sink.h b/app/src/v4l2_sink.h index 9d2ee149..81bcdd1e 100644 --- a/app/src/v4l2_sink.h +++ b/app/src/v4l2_sink.h @@ -26,7 +26,7 @@ struct sc_v4l2_sink { bool header_written; AVFrame *frame; - AVPacket packet; + AVPacket *packet; }; bool From 7343b233e457076d9419fb0f6e587ba13a7e79c8 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Mon, 14 Jun 2021 09:33:10 +0200 Subject: [PATCH 430/450] Render screen on window restored It should not be necessary, since screen_render() is called just after on SDL_WINDOWEVENT_EXPOSED, but in practice the window content might not be correctly displayed on restored if a rotation occurred while minimized. Note that calling screen_render() twice in a row on SDL_WINDOWEVENT_EXPOSED also "fixes" the issue. --- app/src/screen.c | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/screen.c b/app/src/screen.c index a09831a3..c70a95e3 100644 --- a/app/src/screen.c +++ b/app/src/screen.c @@ -726,6 +726,7 @@ screen_handle_event(struct screen *screen, SDL_Event *event) { } screen->maximized = false; apply_pending_resize(screen); + screen_render(screen, true); break; } return true; From 9b89b7ab7208cac0d7da91bb9a1811d6738c82a7 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Mon, 14 Jun 2021 21:24:51 +0200 Subject: [PATCH 431/450] Center the window on resize-to-fit When removing the black borders (by double-clicking on them, or by pressing MOD+w), the window is resized to fit the device screen, but its top-left position was left unchanged. Instead, move the window so that the new window area is at the center of the old window area. Refs #2387 --- app/src/screen.c | 32 ++++++++++++++++++++++++-------- 1 file changed, 24 insertions(+), 8 deletions(-) diff --git a/app/src/screen.c b/app/src/screen.c index c70a95e3..99327b3b 100644 --- a/app/src/screen.c +++ b/app/src/screen.c @@ -41,6 +41,18 @@ get_window_size(const struct screen *screen) { return size; } +static struct point +get_window_position(const struct screen *screen) { + int x; + int y; + SDL_GetWindowPosition(screen->window, &x, &y); + + struct point point; + point.x = x; + point.y = y; + return point; +} + // set the window size to be applied when fullscreen is disabled static void set_window_size(struct screen *screen, struct size new_size) { @@ -122,13 +134,6 @@ get_optimal_size(struct size current_size, struct size content_size) { return window_size; } -// same as get_optimal_size(), but read the current size from the window -static inline struct size -get_optimal_window_size(const struct screen *screen, struct size content_size) { - struct size window_size = get_window_size(screen); - return get_optimal_size(window_size, content_size); -} - // initially, there is no current size, so use the frame size as current size // req_width and req_height, if not 0, are the sizes requested by the user static inline struct size @@ -662,9 +667,20 @@ screen_resize_to_fit(struct screen *screen) { return; } + struct point point = get_window_position(screen); + struct size window_size = get_window_size(screen); + struct size optimal_size = - get_optimal_window_size(screen, screen->content_size); + get_optimal_size(window_size, screen->content_size); + + // Center the window related to the device screen + assert(optimal_size.width <= window_size.width); + assert(optimal_size.height <= window_size.height); + uint32_t new_x = point.x + (window_size.width - optimal_size.width) / 2; + uint32_t new_y = point.y + (window_size.height - optimal_size.height) / 2; + SDL_SetWindowSize(screen->window, optimal_size.width, optimal_size.height); + SDL_SetWindowPosition(screen->window, new_x, new_y); LOGD("Resized to optimal size: %ux%u", optimal_size.width, optimal_size.height); } From a1f2094787cf6267d9913dcf32e1782002a76729 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 13 Jun 2021 22:47:16 +0200 Subject: [PATCH 432/450] Push to /sdcard/Download/ by default Change the default push target from /sdcard/ to /sdcard/Download/. Pushing to the root of /sdcard/ is not very convenient, many apps do not expose its content directly. It can still be changed by --push-target. PR #2384 --- README.md | 6 +++--- app/scrcpy.1 | 2 +- app/src/cli.c | 2 +- app/src/file_handler.c | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index ba68dfb1..150319d2 100644 --- a/README.md +++ b/README.md @@ -710,15 +710,15 @@ There is no visual feedback, a log is printed to the console. #### Push file to device -To push a file to `/sdcard/` on the device, drag & drop a (non-APK) file to the -_scrcpy_ window. +To push a file to `/sdcard/Download/` on the device, drag & drop a (non-APK) +file to the _scrcpy_ window. There is no visual feedback, a log is printed to the console. The target directory can be changed on start: ```bash -scrcpy --push-target=/sdcard/Download/ +scrcpy --push-target=/sdcard/Movies/ ``` diff --git a/app/scrcpy.1 b/app/scrcpy.1 index 80467d10..7b6f1faa 100644 --- a/app/scrcpy.1 +++ b/app/scrcpy.1 @@ -133,7 +133,7 @@ but breaks the expected behavior of alpha keys in games (typically WASD). .BI "\-\-push\-target " path Set the target directory for pushing files to the device by drag & drop. It is passed as\-is to "adb push". -Default is "/sdcard/". +Default is "/sdcard/Download/". .TP .BI "\-r, \-\-record " file diff --git a/app/src/cli.c b/app/src/cli.c index 3e5d613d..6e1180bc 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -129,7 +129,7 @@ scrcpy_print_usage(const char *arg0) { " --push-target path\n" " Set the target directory for pushing files to the device by\n" " drag & drop. It is passed as-is to \"adb push\".\n" - " Default is \"/sdcard/\".\n" + " Default is \"/sdcard/Download/\".\n" "\n" " -r, --record file.mp4\n" " Record screen to file.\n" diff --git a/app/src/file_handler.c b/app/src/file_handler.c index 2b08240c..27fe6fa3 100644 --- a/app/src/file_handler.c +++ b/app/src/file_handler.c @@ -6,7 +6,7 @@ #include "adb.h" #include "util/log.h" -#define DEFAULT_PUSH_TARGET "/sdcard/" +#define DEFAULT_PUSH_TARGET "/sdcard/Download/" static void file_handler_request_destroy(struct file_handler_request *req) { From b846d3a085cc4a55a5a5751c2ab6114310997e30 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sat, 19 Jun 2021 08:43:49 +0200 Subject: [PATCH 433/450] Adapt call() on ContentProvider for Android 12 Android 12 changed one of the call() overloads with a new parameter AttributionSource. Adapt the wrapper. Fixes #2402 --- .../scrcpy/wrappers/ContentProvider.java | 45 ++++++++++++++----- 1 file changed, 35 insertions(+), 10 deletions(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/wrappers/ContentProvider.java b/server/src/main/java/com/genymobile/scrcpy/wrappers/ContentProvider.java index f8393e59..387c7a60 100644 --- a/server/src/main/java/com/genymobile/scrcpy/wrappers/ContentProvider.java +++ b/server/src/main/java/com/genymobile/scrcpy/wrappers/ContentProvider.java @@ -2,6 +2,7 @@ package com.genymobile.scrcpy.wrappers; import com.genymobile.scrcpy.Ln; +import android.annotation.SuppressLint; import android.os.Bundle; import android.os.IBinder; @@ -37,6 +38,8 @@ public class ContentProvider implements Closeable { private Method callMethod; private int callMethodVersion; + private Object attributionSource; + ContentProvider(ActivityManager manager, Object provider, String name, IBinder token) { this.manager = manager; this.provider = provider; @@ -44,36 +47,58 @@ public class ContentProvider implements Closeable { this.token = token; } + @SuppressLint("PrivateApi") private Method getCallMethod() throws NoSuchMethodException { if (callMethod == null) { - try { - callMethod = provider.getClass() - .getMethod("call", String.class, String.class, String.class, String.class, String.class, Bundle.class); + Class attributionSourceClass = Class.forName("android.content.AttributionSource"); + callMethod = provider.getClass().getMethod("call", attributionSourceClass, String.class, String.class, String.class, Bundle.class); callMethodVersion = 0; - } catch (NoSuchMethodException e) { + } catch (NoSuchMethodException | ClassNotFoundException e0) { // old versions try { - callMethod = provider.getClass().getMethod("call", String.class, String.class, String.class, String.class, Bundle.class); + callMethod = provider.getClass() + .getMethod("call", String.class, String.class, String.class, String.class, String.class, Bundle.class); callMethodVersion = 1; - } catch (NoSuchMethodException e2) { - callMethod = provider.getClass().getMethod("call", String.class, String.class, String.class, Bundle.class); - callMethodVersion = 2; + } catch (NoSuchMethodException e1) { + try { + callMethod = provider.getClass().getMethod("call", String.class, String.class, String.class, String.class, Bundle.class); + callMethodVersion = 2; + } catch (NoSuchMethodException e2) { + callMethod = provider.getClass().getMethod("call", String.class, String.class, String.class, Bundle.class); + callMethodVersion = 3; + } } } } return callMethod; } + @SuppressLint("PrivateApi") + private Object getAttributionSource() + throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException { + if (attributionSource == null) { + Class cl = Class.forName("android.content.AttributionSource$Builder"); + Object builder = cl.getConstructor(int.class).newInstance(ServiceManager.USER_ID); + cl.getDeclaredMethod("setPackageName", String.class).invoke(builder, ServiceManager.PACKAGE_NAME); + attributionSource = cl.getDeclaredMethod("build").invoke(builder); + } + + return attributionSource; + } + private Bundle call(String callMethod, String arg, Bundle extras) { try { Method method = getCallMethod(); Object[] args; switch (callMethodVersion) { case 0: - args = new Object[]{ServiceManager.PACKAGE_NAME, null, "settings", callMethod, arg, extras}; + args = new Object[]{getAttributionSource(), "settings", callMethod, arg, extras}; break; case 1: + args = new Object[]{ServiceManager.PACKAGE_NAME, null, "settings", callMethod, arg, extras}; + break; + case 2: args = new Object[]{ServiceManager.PACKAGE_NAME, "settings", callMethod, arg, extras}; break; default: @@ -81,7 +106,7 @@ public class ContentProvider implements Closeable { break; } return (Bundle) method.invoke(provider, args); - } catch (InvocationTargetException | IllegalAccessException | NoSuchMethodException e) { + } catch (InvocationTargetException | IllegalAccessException | NoSuchMethodException | ClassNotFoundException | InstantiationException e) { Ln.e("Could not invoke method", e); return null; } From df017160ed9a9856c3a8eb08de29d77c14df70f0 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 20 Jun 2021 12:33:05 +0200 Subject: [PATCH 434/450] Replace strcpy() by memcpy() It was safe to call strcpy() since the input length was checked, but then it is more straightforward to call memcpy() directly. --- app/src/scrcpy.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index 17902156..4dcb412f 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -216,14 +216,15 @@ av_log_callback(void *avcl, int level, const char *fmt, va_list vl) { if (priority == 0) { return; } - char *local_fmt = malloc(strlen(fmt) + 10); + + size_t fmt_len = strlen(fmt); + char *local_fmt = malloc(fmt_len + 10); if (!local_fmt) { LOGC("Could not allocate string"); return; } - // strcpy is safe here, the destination is large enough - strcpy(local_fmt, "[FFmpeg] "); - strcpy(local_fmt + 9, fmt); + memcpy(local_fmt, "[FFmpeg] ", 9); // do not write the final '\0' + memcpy(local_fmt + 9, fmt, fmt_len + 1); // include '\0' SDL_LogMessageV(SDL_LOG_CATEGORY_VIDEO, priority, local_fmt, vl); free(local_fmt); } From 8b90e1d3f46b77e162caa29a89952182edaa465c Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 20 Jun 2021 00:32:55 +0200 Subject: [PATCH 435/450] Remove extra ';' in #define --- app/src/control_msg.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/control_msg.h b/app/src/control_msg.h index c1099c79..a259d0db 100644 --- a/app/src/control_msg.h +++ b/app/src/control_msg.h @@ -17,8 +17,8 @@ // type: 1 byte; paste flag: 1 byte; length: 4 bytes #define CONTROL_MSG_CLIPBOARD_TEXT_MAX_LENGTH (CONTROL_MSG_MAX_SIZE - 6) -#define POINTER_ID_MOUSE UINT64_C(-1); -#define POINTER_ID_VIRTUAL_FINGER UINT64_C(-2); +#define POINTER_ID_MOUSE UINT64_C(-1) +#define POINTER_ID_VIRTUAL_FINGER UINT64_C(-2) enum control_msg_type { CONTROL_MSG_TYPE_INJECT_KEYCODE, From 7db0189f23bc3dd3802c751ed44a1dd367523a5a Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 20 Jun 2021 00:42:55 +0200 Subject: [PATCH 436/450] Forward mouse motion only on main clicks Mouse motion events were forwarded as soon as any mouse button was pressed. Instead, only consider left-click (and also middle-click and right-click if --forward-all-clicks is enabled). --- app/src/input_manager.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/app/src/input_manager.c b/app/src/input_manager.c index b008c4db..a5d0ad07 100644 --- a/app/src/input_manager.c +++ b/app/src/input_manager.c @@ -595,8 +595,12 @@ convert_mouse_motion(const SDL_MouseMotionEvent *from, struct screen *screen, static void input_manager_process_mouse_motion(struct input_manager *im, const SDL_MouseMotionEvent *event) { - if (!event->state) { - // do not send motion events when no button is pressed + uint32_t mask = SDL_BUTTON_LMASK; + if (im->forward_all_clicks) { + mask |= SDL_BUTTON_MMASK | SDL_BUTTON_RMASK; + } + if (!(event->state & mask)) { + // do not send motion events when no click is pressed return; } if (event->which == SDL_TOUCH_MOUSEID) { From 937fa704a6bb07a264e247bb41de08b5378de39e Mon Sep 17 00:00:00 2001 From: Marti Raudsepp Date: Thu, 17 Jun 2021 22:40:30 +0300 Subject: [PATCH 437/450] Add --verbosity=verbose log level PR #2371 Signed-off-by: Romain Vimont --- app/scrcpy.1 | 2 +- app/src/cli.c | 7 ++++++- app/src/main.c | 2 ++ app/src/scrcpy.h | 1 + app/src/server.c | 2 ++ server/src/main/java/com/genymobile/scrcpy/Ln.java | 9 ++++++++- 6 files changed, 20 insertions(+), 3 deletions(-) diff --git a/app/scrcpy.1 b/app/scrcpy.1 index 7b6f1faa..62dc9677 100644 --- a/app/scrcpy.1 +++ b/app/scrcpy.1 @@ -193,7 +193,7 @@ It requires to lock the video orientation (see --lock-video-orientation). .TP .BI "\-V, \-\-verbosity " value -Set the log level ("debug", "info", "warn" or "error"). +Set the log level ("verbose", "debug", "info", "warn" or "error"). Default is "info" for release builds, "debug" for debug builds. diff --git a/app/src/cli.c b/app/src/cli.c index 6e1180bc..4c1fbb2f 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -184,7 +184,7 @@ scrcpy_print_usage(const char *arg0) { "\n" #endif " -V, --verbosity value\n" - " Set the log level (debug, info, warn or error).\n" + " Set the log level (verbose, debug, info, warn or error).\n" #ifndef NDEBUG " Default is debug.\n" #else @@ -505,6 +505,11 @@ parse_display_id(const char *s, uint32_t *display_id) { static bool parse_log_level(const char *s, enum sc_log_level *log_level) { + if (!strcmp(s, "verbose")) { + *log_level = SC_LOG_LEVEL_VERBOSE; + return true; + } + if (!strcmp(s, "debug")) { *log_level = SC_LOG_LEVEL_DEBUG; return true; diff --git a/app/src/main.c b/app/src/main.c index a468aed7..c3a7ad2e 100644 --- a/app/src/main.c +++ b/app/src/main.c @@ -41,6 +41,8 @@ print_version(void) { static SDL_LogPriority convert_log_level_to_sdl(enum sc_log_level level) { switch (level) { + case SC_LOG_LEVEL_VERBOSE: + return SDL_LOG_PRIORITY_VERBOSE; case SC_LOG_LEVEL_DEBUG: return SDL_LOG_PRIORITY_DEBUG; case SC_LOG_LEVEL_INFO: diff --git a/app/src/scrcpy.h b/app/src/scrcpy.h index 405dc7f3..0a2deb71 100644 --- a/app/src/scrcpy.h +++ b/app/src/scrcpy.h @@ -8,6 +8,7 @@ #include enum sc_log_level { + SC_LOG_LEVEL_VERBOSE, SC_LOG_LEVEL_DEBUG, SC_LOG_LEVEL_INFO, SC_LOG_LEVEL_WARN, diff --git a/app/src/server.c b/app/src/server.c index 41e8166c..a4cdb0c9 100644 --- a/app/src/server.c +++ b/app/src/server.c @@ -235,6 +235,8 @@ enable_tunnel_any_port(struct server *server, struct sc_port_range port_range, static const char * log_level_to_server_string(enum sc_log_level level) { switch (level) { + case SC_LOG_LEVEL_VERBOSE: + return "verbose"; case SC_LOG_LEVEL_DEBUG: return "debug"; case SC_LOG_LEVEL_INFO: diff --git a/server/src/main/java/com/genymobile/scrcpy/Ln.java b/server/src/main/java/com/genymobile/scrcpy/Ln.java index c218fa0f..061cda95 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Ln.java +++ b/server/src/main/java/com/genymobile/scrcpy/Ln.java @@ -12,7 +12,7 @@ public final class Ln { private static final String PREFIX = "[server] "; enum Level { - DEBUG, INFO, WARN, ERROR + VERBOSE, DEBUG, INFO, WARN, ERROR } private static Level threshold = Level.INFO; @@ -36,6 +36,13 @@ public final class Ln { return level.ordinal() >= threshold.ordinal(); } + public static void v(String message) { + if (isEnabled(Level.VERBOSE)) { + Log.v(TAG, message); + System.out.println(PREFIX + "VERBOSE: " + message); + } + } + public static void d(String message) { if (isEnabled(Level.DEBUG)) { Log.d(TAG, message); From 19ca02cd8f1a7554cd7c895482dca87134378baf Mon Sep 17 00:00:00 2001 From: Marti Raudsepp Date: Tue, 8 Jun 2021 19:14:20 +0300 Subject: [PATCH 438/450] Log control messages in verbose mode PR #2371 Signed-off-by: Romain Vimont --- app/src/control_msg.c | 130 ++++++++++++++++++++++++++++++++++++++++++ app/src/control_msg.h | 3 + app/src/controller.c | 1 + 3 files changed, 134 insertions(+) diff --git a/app/src/control_msg.c b/app/src/control_msg.c index 8908c546..aba4bd16 100644 --- a/app/src/control_msg.c +++ b/app/src/control_msg.c @@ -1,6 +1,7 @@ #include "control_msg.h" #include +#include #include #include @@ -8,6 +9,52 @@ #include "util/log.h" #include "util/str_util.h" +/** + * Map an enum value to a string based on an array, without crashing on an + * out-of-bounds index. + */ +#define ENUM_TO_LABEL(labels, value) \ + ((size_t) (value) < ARRAY_LEN(labels) ? labels[value] : "???") + +#define KEYEVENT_ACTION_LABEL(value) \ + ENUM_TO_LABEL(android_keyevent_action_labels, value) + +#define MOTIONEVENT_ACTION_LABEL(value) \ + ENUM_TO_LABEL(android_motionevent_action_labels, value) + +#define SCREEN_POWER_MODE_LABEL(value) \ + ENUM_TO_LABEL(screen_power_mode_labels, value) + +static const char *const android_keyevent_action_labels[] = { + "down", + "up", + "multi", +}; + +static const char *const android_motionevent_action_labels[] = { + "down", + "up", + "move", + "cancel", + "outside", + "ponter-down", + "pointer-up", + "hover-move", + "scroll", + "hover-enter" + "hover-exit", + "btn-press", + "btn-release", +}; + +static const char *const screen_power_mode_labels[] = { + "off", + "doze", + "normal", + "doze-suspend", + "suspend", +}; + static void write_position(uint8_t *buf, const struct position *position) { buffer_write32be(&buf[0], position->point.x); @@ -93,6 +140,89 @@ control_msg_serialize(const struct control_msg *msg, unsigned char *buf) { } } +void +control_msg_log(const struct control_msg *msg) { +#define LOG_CMSG(fmt, ...) LOGV("input: " fmt, ## __VA_ARGS__) + switch (msg->type) { + case CONTROL_MSG_TYPE_INJECT_KEYCODE: + LOG_CMSG("key %-4s code=%d repeat=%" PRIu32 " meta=%06lx", + KEYEVENT_ACTION_LABEL(msg->inject_keycode.action), + (int) msg->inject_keycode.keycode, + msg->inject_keycode.repeat, + (long) msg->inject_keycode.metastate); + break; + case CONTROL_MSG_TYPE_INJECT_TEXT: + LOG_CMSG("text \"%s\"", msg->inject_text.text); + break; + case CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT: { + int action = msg->inject_touch_event.action + & AMOTION_EVENT_ACTION_MASK; + uint64_t id = msg->inject_touch_event.pointer_id; + if (id == POINTER_ID_MOUSE || id == POINTER_ID_VIRTUAL_FINGER) { + // string pointer id + LOG_CMSG("touch [id=%s] %-4s position=%" PRIi32 ",%" PRIi32 + " pressure=%g buttons=%06lx", + id == POINTER_ID_MOUSE ? "mouse" : "vfinger", + MOTIONEVENT_ACTION_LABEL(action), + msg->inject_touch_event.position.point.x, + msg->inject_touch_event.position.point.y, + msg->inject_touch_event.pressure, + (long) msg->inject_touch_event.buttons); + } else { + // numeric pointer id + LOG_CMSG("touch [id=%" PRIu64 "] %-4s position=%" PRIi32 ",%" + PRIi32 " pressure=%g buttons=%06lx", + id, + MOTIONEVENT_ACTION_LABEL(action), + msg->inject_touch_event.position.point.x, + msg->inject_touch_event.position.point.y, + msg->inject_touch_event.pressure, + (long) msg->inject_touch_event.buttons); + } + break; + } + case CONTROL_MSG_TYPE_INJECT_SCROLL_EVENT: + LOG_CMSG("scroll position=%" PRIi32 ",%" PRIi32 " hscroll=%" PRIi32 + " vscroll=%" PRIi32, + msg->inject_scroll_event.position.point.x, + msg->inject_scroll_event.position.point.y, + msg->inject_scroll_event.hscroll, + msg->inject_scroll_event.vscroll); + break; + case CONTROL_MSG_TYPE_BACK_OR_SCREEN_ON: + LOG_CMSG("back-or-screen-on %s", + KEYEVENT_ACTION_LABEL(msg->inject_keycode.action)); + break; + case CONTROL_MSG_TYPE_SET_CLIPBOARD: + LOG_CMSG("clipboard %s \"%s\"", + msg->set_clipboard.paste ? "paste" : "copy", + msg->set_clipboard.text); + break; + case CONTROL_MSG_TYPE_SET_SCREEN_POWER_MODE: + LOG_CMSG("power mode %s", + SCREEN_POWER_MODE_LABEL(msg->set_screen_power_mode.mode)); + break; + case CONTROL_MSG_TYPE_EXPAND_NOTIFICATION_PANEL: + LOG_CMSG("expand notification panel"); + break; + case CONTROL_MSG_TYPE_EXPAND_SETTINGS_PANEL: + LOG_CMSG("expand settings panel"); + break; + case CONTROL_MSG_TYPE_COLLAPSE_PANELS: + LOG_CMSG("collapse panels"); + break; + case CONTROL_MSG_TYPE_GET_CLIPBOARD: + LOG_CMSG("get clipboard"); + break; + case CONTROL_MSG_TYPE_ROTATE_DEVICE: + LOG_CMSG("rotate device"); + break; + default: + LOG_CMSG("unknown type: %u", (unsigned) msg->type); + break; + } +} + void control_msg_destroy(struct control_msg *msg) { switch (msg->type) { diff --git a/app/src/control_msg.h b/app/src/control_msg.h index a259d0db..920a493a 100644 --- a/app/src/control_msg.h +++ b/app/src/control_msg.h @@ -84,6 +84,9 @@ struct control_msg { size_t control_msg_serialize(const struct control_msg *msg, unsigned char *buf); +void +control_msg_log(const struct control_msg *msg); + void control_msg_destroy(struct control_msg *msg); diff --git a/app/src/controller.c b/app/src/controller.c index 38b5e702..9284ae52 100644 --- a/app/src/controller.c +++ b/app/src/controller.c @@ -48,6 +48,7 @@ controller_destroy(struct controller *controller) { bool controller_push_msg(struct controller *controller, const struct control_msg *msg) { + control_msg_log(msg); sc_mutex_lock(&controller->mutex); bool was_empty = cbuf_is_empty(&controller->queue); bool res = cbuf_push(&controller->queue, *msg); From 1039f9b531e57f710432073b2fd525d8ecb857c5 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 20 Jun 2021 01:30:06 +0200 Subject: [PATCH 439/450] Workaround PRIu64 on Windows On Windows, PRIu64 is defined to "llu", which is not supported: error: unknown conversion type character 'l' in format --- app/src/control_msg.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/app/src/control_msg.c b/app/src/control_msg.c index aba4bd16..1257010e 100644 --- a/app/src/control_msg.c +++ b/app/src/control_msg.c @@ -170,7 +170,12 @@ control_msg_log(const struct control_msg *msg) { (long) msg->inject_touch_event.buttons); } else { // numeric pointer id - LOG_CMSG("touch [id=%" PRIu64 "] %-4s position=%" PRIi32 ",%" +#ifndef __WIN32 +# define PRIu64_ PRIu64 +#else +# define PRIu64_ "I64u" // Windows... +#endif + LOG_CMSG("touch [id=%" PRIu64_ "] %-4s position=%" PRIi32 ",%" PRIi32 " pressure=%g buttons=%06lx", id, MOTIONEVENT_ACTION_LABEL(action), From 5c95d18beb55e9dc24bf8b650042fcabeccce63f Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 20 Jun 2021 12:46:41 +0200 Subject: [PATCH 440/450] Move log level conversion to log API --- app/meson.build | 1 + app/src/main.c | 23 +---------------------- app/src/util/log.c | 28 ++++++++++++++++++++++++++++ app/src/util/log.h | 11 +++++++++-- 4 files changed, 39 insertions(+), 24 deletions(-) create mode 100644 app/src/util/log.c diff --git a/app/meson.build b/app/meson.build index 6b6991aa..0663c641 100644 --- a/app/meson.build +++ b/app/meson.build @@ -20,6 +20,7 @@ src = [ 'src/stream.c', 'src/tiny_xpm.c', 'src/video_buffer.c', + 'src/util/log.c', 'src/util/net.c', 'src/util/process.c', 'src/util/str_util.c', diff --git a/app/src/main.c b/app/src/main.c index c3a7ad2e..2afa3c4e 100644 --- a/app/src/main.c +++ b/app/src/main.c @@ -38,26 +38,6 @@ print_version(void) { #endif } -static SDL_LogPriority -convert_log_level_to_sdl(enum sc_log_level level) { - switch (level) { - case SC_LOG_LEVEL_VERBOSE: - return SDL_LOG_PRIORITY_VERBOSE; - case SC_LOG_LEVEL_DEBUG: - return SDL_LOG_PRIORITY_DEBUG; - case SC_LOG_LEVEL_INFO: - return SDL_LOG_PRIORITY_INFO; - case SC_LOG_LEVEL_WARN: - return SDL_LOG_PRIORITY_WARN; - case SC_LOG_LEVEL_ERROR: - return SDL_LOG_PRIORITY_ERROR; - default: - assert(!"unexpected log level"); - return SDL_LOG_PRIORITY_INFO; - } -} - - int main(int argc, char *argv[]) { #ifdef __WINDOWS__ @@ -81,8 +61,7 @@ main(int argc, char *argv[]) { return 1; } - SDL_LogPriority sdl_log = convert_log_level_to_sdl(args.opts.log_level); - SDL_LogSetPriority(SDL_LOG_CATEGORY_APPLICATION, sdl_log); + sc_set_log_level(args.opts.log_level); if (args.help) { scrcpy_print_usage(argv[0]); diff --git a/app/src/util/log.c b/app/src/util/log.c new file mode 100644 index 00000000..ac26de1c --- /dev/null +++ b/app/src/util/log.c @@ -0,0 +1,28 @@ +#include "log.h" + +#include + +static SDL_LogPriority +log_level_sc_to_sdl(enum sc_log_level level) { + switch (level) { + case SC_LOG_LEVEL_VERBOSE: + return SDL_LOG_PRIORITY_VERBOSE; + case SC_LOG_LEVEL_DEBUG: + return SDL_LOG_PRIORITY_DEBUG; + case SC_LOG_LEVEL_INFO: + return SDL_LOG_PRIORITY_INFO; + case SC_LOG_LEVEL_WARN: + return SDL_LOG_PRIORITY_WARN; + case SC_LOG_LEVEL_ERROR: + return SDL_LOG_PRIORITY_ERROR; + default: + assert(!"unexpected log level"); + return SDL_LOG_PRIORITY_INFO; + } +} + +void +sc_set_log_level(enum sc_log_level level) { + SDL_LogPriority sdl_log = log_level_sc_to_sdl(level); + SDL_LogSetPriority(SDL_LOG_CATEGORY_APPLICATION, sdl_log); +} diff --git a/app/src/util/log.h b/app/src/util/log.h index 5955c7fb..8b6c175e 100644 --- a/app/src/util/log.h +++ b/app/src/util/log.h @@ -1,8 +1,12 @@ -#ifndef LOG_H -#define LOG_H +#ifndef SC_LOG_H +#define SC_LOG_H + +#include "common.h" #include +#include "scrcpy.h" + #define LOGV(...) SDL_LogVerbose(SDL_LOG_CATEGORY_APPLICATION, __VA_ARGS__) #define LOGD(...) SDL_LogDebug(SDL_LOG_CATEGORY_APPLICATION, __VA_ARGS__) #define LOGI(...) SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, __VA_ARGS__) @@ -10,4 +14,7 @@ #define LOGE(...) SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, __VA_ARGS__) #define LOGC(...) SDL_LogCritical(SDL_LOG_CATEGORY_APPLICATION, __VA_ARGS__) +void +sc_set_log_level(enum sc_log_level level); + #endif From 488991116b9c56112a26daa1bb1069b57b9832ec Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 20 Jun 2021 12:49:45 +0200 Subject: [PATCH 441/450] Expose function to get the current log level This will allow to avoid unnecessary processing for creating logs which will be discarded anyway. --- app/src/util/log.c | 25 +++++++++++++++++++++++++ app/src/util/log.h | 3 +++ 2 files changed, 28 insertions(+) diff --git a/app/src/util/log.c b/app/src/util/log.c index ac26de1c..a285fffb 100644 --- a/app/src/util/log.c +++ b/app/src/util/log.c @@ -21,8 +21,33 @@ log_level_sc_to_sdl(enum sc_log_level level) { } } +static enum sc_log_level +log_level_sdl_to_sc(SDL_LogPriority priority) { + switch (priority) { + case SDL_LOG_PRIORITY_VERBOSE: + return SC_LOG_LEVEL_VERBOSE; + case SDL_LOG_PRIORITY_DEBUG: + return SC_LOG_LEVEL_DEBUG; + case SDL_LOG_PRIORITY_INFO: + return SC_LOG_LEVEL_INFO; + case SDL_LOG_PRIORITY_WARN: + return SC_LOG_LEVEL_WARN; + case SDL_LOG_PRIORITY_ERROR: + return SC_LOG_LEVEL_ERROR; + default: + assert(!"unexpected log level"); + return SC_LOG_LEVEL_INFO; + } +} + void sc_set_log_level(enum sc_log_level level) { SDL_LogPriority sdl_log = log_level_sc_to_sdl(level); SDL_LogSetPriority(SDL_LOG_CATEGORY_APPLICATION, sdl_log); } + +enum sc_log_level +sc_get_log_level(void) { + SDL_LogPriority sdl_log = SDL_LogGetPriority(SDL_LOG_CATEGORY_APPLICATION); + return log_level_sdl_to_sc(sdl_log); +} diff --git a/app/src/util/log.h b/app/src/util/log.h index 8b6c175e..30934b5c 100644 --- a/app/src/util/log.h +++ b/app/src/util/log.h @@ -17,4 +17,7 @@ void sc_set_log_level(enum sc_log_level level); +enum sc_log_level +sc_get_log_level(void); + #endif From 1c950434784823d13e6bb9397bed1d6bfdad54ef Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 20 Jun 2021 12:54:09 +0200 Subject: [PATCH 442/450] Attempt to log message only in verbose mode If the log level is not verbose, there is no need to attempt to log control messages at all. --- app/src/controller.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/app/src/controller.c b/app/src/controller.c index 9284ae52..3a428aa8 100644 --- a/app/src/controller.c +++ b/app/src/controller.c @@ -48,7 +48,10 @@ controller_destroy(struct controller *controller) { bool controller_push_msg(struct controller *controller, const struct control_msg *msg) { - control_msg_log(msg); + if (sc_get_log_level() <= SC_LOG_LEVEL_VERBOSE) { + control_msg_log(msg); + } + sc_mutex_lock(&controller->mutex); bool was_empty = cbuf_is_empty(&controller->queue); bool res = cbuf_push(&controller->queue, *msg); From 77e96d745b0243323fc4cacec0081da062d2506f Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 20 Jun 2021 20:57:52 +0200 Subject: [PATCH 443/450] Suggest --record-format instead of -F on error The short option -F has been deprecated by ff061b4f30c54dedc5073a588c6c697477b805db. On error, suggest the long option --record-format instead. --- app/src/cli.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/src/cli.c b/app/src/cli.c index 4c1fbb2f..3eab8d27 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -962,7 +962,8 @@ scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) { if (opts->record_filename && !opts->record_format) { opts->record_format = guess_record_format(opts->record_filename); if (!opts->record_format) { - LOGE("No format specified for \"%s\" (try with -F mkv)", + LOGE("No format specified for \"%s\" " + "(try with --record-format=mkv)", opts->record_filename); return false; } From 710e80aa0dcef5e35f0a40fd0b3668cef8e9b5c9 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sat, 19 Jun 2021 22:53:40 +0200 Subject: [PATCH 444/450] Return build_cmd() success via a boolean For consistency with other functions in the codebase. --- app/src/sys/win/process.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/src/sys/win/process.c b/app/src/sys/win/process.c index f170e40d..7f5da6af 100644 --- a/app/src/sys/win/process.c +++ b/app/src/sys/win/process.c @@ -6,7 +6,7 @@ #include "util/log.h" #include "util/str_util.h" -static int +static bool build_cmd(char *cmd, size_t len, const char *const argv[]) { // Windows command-line parsing is WTF: // @@ -15,9 +15,9 @@ build_cmd(char *cmd, size_t len, const char *const argv[]) { size_t ret = xstrjoin(cmd, argv, ' ', len); if (ret >= len) { LOGE("Command too long (%" PRIsizet " chars)", len - 1); - return -1; + return false; } - return 0; + return true; } enum process_result @@ -28,7 +28,7 @@ process_execute(const char *const argv[], HANDLE *handle) { si.cb = sizeof(si); char cmd[256]; - if (build_cmd(cmd, sizeof(cmd), argv)) { + if (!build_cmd(cmd, sizeof(cmd), argv)) { *handle = NULL; return PROCESS_ERROR_GENERIC; } From fda32928c1bc02afb2c1068084c228565305ce87 Mon Sep 17 00:00:00 2001 From: Wirtos_new Date: Sat, 19 Jun 2021 18:47:57 +0300 Subject: [PATCH 445/450] Rename cmd to argv This is more explicit. PR #2405 Signed-off-by: Romain Vimont --- app/src/adb.c | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/app/src/adb.c b/app/src/adb.c index be973c41..44c80be6 100644 --- a/app/src/adb.c +++ b/app/src/adb.c @@ -102,23 +102,23 @@ show_adb_err_msg(enum process_result err, const char *const argv[]) { process_t adb_execute(const char *serial, const char *const adb_cmd[], size_t len) { - const char *cmd[len + 4]; + const char *argv[len + 4]; int i; process_t process; - cmd[0] = get_adb_command(); + argv[0] = get_adb_command(); if (serial) { - cmd[1] = "-s"; - cmd[2] = serial; + argv[1] = "-s"; + argv[2] = serial; i = 3; } else { i = 1; } - memcpy(&cmd[i], adb_cmd, len * sizeof(const char *)); - cmd[len + i] = NULL; - enum process_result r = process_execute(cmd, &process); + memcpy(&argv[i], adb_cmd, len * sizeof(const char *)); + argv[len + i] = NULL; + enum process_result r = process_execute(argv, &process); if (r != PROCESS_SUCCESS) { - show_adb_err_msg(r, cmd); + show_adb_err_msg(r, argv); return PROCESS_NONE; } return process; From a9d9cbf8b502cb537a1c7caaf2689be9eca3af60 Mon Sep 17 00:00:00 2001 From: Wirtos_new Date: Sat, 19 Jun 2021 18:47:57 +0300 Subject: [PATCH 446/450] Replace VLA by dynamic allocation And increase the command buffer size. Refs #1358 PR #2405 Signed-off-by: Romain Vimont --- app/src/adb.c | 25 ++++++++++++++++++++----- app/src/sys/win/process.c | 7 +++++-- 2 files changed, 25 insertions(+), 7 deletions(-) diff --git a/app/src/adb.c b/app/src/adb.c index 44c80be6..5bb9df30 100644 --- a/app/src/adb.c +++ b/app/src/adb.c @@ -81,14 +81,20 @@ show_adb_installation_msg() { static void show_adb_err_msg(enum process_result err, const char *const argv[]) { - char buf[512]; +#define MAX_COMMAND_STRING_LEN 1024 + char *buf = malloc(MAX_COMMAND_STRING_LEN); + if (!buf) { + LOGE("Failed to execute (could not allocate error message)"); + return; + } + switch (err) { case PROCESS_ERROR_GENERIC: - argv_to_string(argv, buf, sizeof(buf)); + argv_to_string(argv, buf, MAX_COMMAND_STRING_LEN); LOGE("Failed to execute: %s", buf); break; case PROCESS_ERROR_MISSING_BINARY: - argv_to_string(argv, buf, sizeof(buf)); + argv_to_string(argv, buf, MAX_COMMAND_STRING_LEN); LOGE("Command not found: %s", buf); LOGE("(make 'adb' accessible from your PATH or define its full" "path in the ADB environment variable)"); @@ -98,13 +104,20 @@ show_adb_err_msg(enum process_result err, const char *const argv[]) { // do nothing break; } + + free(buf); } process_t adb_execute(const char *serial, const char *const adb_cmd[], size_t len) { - const char *argv[len + 4]; int i; process_t process; + + const char **argv = malloc((len + 4) * sizeof(*argv)); + if (!argv) { + return PROCESS_NONE; + } + argv[0] = get_adb_command(); if (serial) { argv[1] = "-s"; @@ -119,8 +132,10 @@ adb_execute(const char *serial, const char *const adb_cmd[], size_t len) { enum process_result r = process_execute(argv, &process); if (r != PROCESS_SUCCESS) { show_adb_err_msg(r, argv); - return PROCESS_NONE; + process = PROCESS_NONE; } + + free(argv); return process; } diff --git a/app/src/sys/win/process.c b/app/src/sys/win/process.c index 7f5da6af..aafd5d34 100644 --- a/app/src/sys/win/process.c +++ b/app/src/sys/win/process.c @@ -6,6 +6,8 @@ #include "util/log.h" #include "util/str_util.h" +#define CMD_MAX_LEN 8192 + static bool build_cmd(char *cmd, size_t len, const char *const argv[]) { // Windows command-line parsing is WTF: @@ -27,13 +29,14 @@ process_execute(const char *const argv[], HANDLE *handle) { memset(&si, 0, sizeof(si)); si.cb = sizeof(si); - char cmd[256]; - if (!build_cmd(cmd, sizeof(cmd), argv)) { + char *cmd = malloc(CMD_MAX_LEN); + if (!cmd || !build_cmd(cmd, CMD_MAX_LEN, argv)) { *handle = NULL; return PROCESS_ERROR_GENERIC; } wchar_t *wide = utf8_to_wide_char(cmd); + free(cmd); if (!wide) { LOGC("Could not allocate wide char string"); return PROCESS_ERROR_GENERIC; From ff7baad7093f4574eb04e82cebd7c1829310b663 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 11 Jun 2021 19:31:13 +0200 Subject: [PATCH 447/450] Upgrade platform-tools (31.0.2) for Windows Include the latest version of adb in Windows releases. --- prebuilt-deps/Makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/prebuilt-deps/Makefile b/prebuilt-deps/Makefile index 356a944e..d75d0a5c 100644 --- a/prebuilt-deps/Makefile +++ b/prebuilt-deps/Makefile @@ -35,6 +35,6 @@ prepare-sdl2: SDL2-2.0.14 prepare-adb: - @./prepare-dep https://dl.google.com/android/repository/platform-tools_r30.0.5-windows.zip \ - 549ba2bdc31f335eb8a504f005f77606a479cc216d6b64a3e8b64c780003661f \ + @./prepare-dep https://dl.google.com/android/repository/platform-tools_r31.0.2-windows.zip \ + d560cb8ded83ae04763b94632673481f14843a5969256569623cfeac82db4ba5 \ platform-tools From 60c4e886d409ec8a0688e2fd43d8d12c06ebd8df Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 20 Jun 2021 22:01:07 +0200 Subject: [PATCH 448/450] Bump version to 1.18 Make the versionCode a decimal representation of the scrcpy version. This will for example allow to correctly number the versionCode of v1.17.1 after a v1.18 is released: - v1.18 -> 11800 - v1.17.1 -> 11701 - v1.18.1 -> 11801 --- meson.build | 2 +- server/build.gradle | 4 ++-- server/build_without_gradle.sh | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/meson.build b/meson.build index c2989ec7..2d76f1e9 100644 --- a/meson.build +++ b/meson.build @@ -1,5 +1,5 @@ project('scrcpy', 'c', - version: '1.17', + version: '1.18', meson_version: '>= 0.48', default_options: [ 'c_std=c11', diff --git a/server/build.gradle b/server/build.gradle index 08ce2d27..f088ba9d 100644 --- a/server/build.gradle +++ b/server/build.gradle @@ -6,8 +6,8 @@ android { applicationId "com.genymobile.scrcpy" minSdkVersion 21 targetSdkVersion 30 - versionCode 20 - versionName "1.17" + versionCode 11800 + versionName "1.18" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" } buildTypes { diff --git a/server/build_without_gradle.sh b/server/build_without_gradle.sh index 9afc5ba7..302d3aaa 100755 --- a/server/build_without_gradle.sh +++ b/server/build_without_gradle.sh @@ -12,7 +12,7 @@ set -e SCRCPY_DEBUG=false -SCRCPY_VERSION_NAME=1.17 +SCRCPY_VERSION_NAME=1.18 PLATFORM=${ANDROID_PLATFORM:-30} BUILD_TOOLS=${ANDROID_BUILD_TOOLS:-30.0.0} From 376201a83cfd3960027e471c9383d1a1f554ac20 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 20 Jun 2021 22:04:52 +0200 Subject: [PATCH 449/450] Update links to v1.18 in README and BUILD --- BUILD.md | 6 +++--- README.md | 8 ++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/BUILD.md b/BUILD.md index d5aead0f..87078b71 100644 --- a/BUILD.md +++ b/BUILD.md @@ -268,10 +268,10 @@ install` must be run as root)._ #### Option 2: Use prebuilt server - - [`scrcpy-server-v1.17`][direct-scrcpy-server] - _(SHA-256: 11b5ad2d1bc9b9730fb7254a78efd71a8ff46b1938ff468e47a21b653a1b6725)_ + - [`scrcpy-server-v1.18`][direct-scrcpy-server] + _(SHA-256: 641c5c6beda9399dfae72d116f5ff43b5ed1059d871c9ebc3f47610fd33c51a3)_ -[direct-scrcpy-server]: https://github.com/Genymobile/scrcpy/releases/download/v1.17/scrcpy-server-v1.17 +[direct-scrcpy-server]: https://github.com/Genymobile/scrcpy/releases/download/v1.18/scrcpy-server-v1.18 Download the prebuilt server somewhere, and specify its path during the Meson configuration: diff --git a/README.md b/README.md index 36426afa..0dfa068c 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# scrcpy (v1.17) +# scrcpy (v1.18) [Read in another language](#translations) @@ -88,10 +88,10 @@ process][BUILD_simple]). For Windows, for simplicity, a prebuilt archive with all the dependencies (including `adb`) is available: - - [`scrcpy-win64-v1.17.zip`][direct-win64] - _(SHA-256: 8b9e57993c707367ed10ebfe0e1ef563c7a29d9af4a355cd8b6a52a317c73eea)_ + - [`scrcpy-win64-v1.18.zip`][direct-win64] + _(SHA-256: 37212f5087fe6f3e258f1d44fa5c02207496b30e1d7ec442cbcf8358910a5c63)_ -[direct-win64]: https://github.com/Genymobile/scrcpy/releases/download/v1.17/scrcpy-win64-v1.17.zip +[direct-win64]: https://github.com/Genymobile/scrcpy/releases/download/v1.18/scrcpy-win64-v1.18.zip It is also available in [Chocolatey]: From ab12b6c981d62aac2c4c86c5436378970723cc38 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Mon, 21 Jun 2021 09:47:15 +0200 Subject: [PATCH 450/450] Update scrcpy-server in install-release.sh Make the install script download the new prebuilt server (v1.18). Fixes #2409 --- install_release.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/install_release.sh b/install_release.sh index 5179c447..9158bdd4 100755 --- a/install_release.sh +++ b/install_release.sh @@ -2,8 +2,8 @@ set -e BUILDDIR=build-auto -PREBUILT_SERVER_URL=https://github.com/Genymobile/scrcpy/releases/download/v1.17/scrcpy-server-v1.17 -PREBUILT_SERVER_SHA256=11b5ad2d1bc9b9730fb7254a78efd71a8ff46b1938ff468e47a21b653a1b6725 +PREBUILT_SERVER_URL=https://github.com/Genymobile/scrcpy/releases/download/v1.18/scrcpy-server-v1.18 +PREBUILT_SERVER_SHA256=641c5c6beda9399dfae72d116f5ff43b5ed1059d871c9ebc3f47610fd33c51a3 echo "[scrcpy] Downloading prebuilt server..." wget "$PREBUILT_SERVER_URL" -O scrcpy-server