diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 00000000..2bbb73dd --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "abstractcat"] + path = abstractcat + url = https://github.com/excitoon/abstractcat diff --git a/abstractcat b/abstractcat new file mode 160000 index 00000000..f232a5a7 --- /dev/null +++ b/abstractcat @@ -0,0 +1 @@ +Subproject commit f232a5a7d590089721c64b5257198064bdf9fab7 diff --git a/app/src/cli.c b/app/src/cli.c index 9c791fbf..f822572a 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -651,6 +651,8 @@ guess_record_format(const char *filename) { #define OPT_DISABLE_SCREENSAVER 1020 #define OPT_SHORTCUT_MOD 1021 #define OPT_NO_KEY_REPEAT 1022 +#define OPT_USE_SSH 1024 +#define OPT_SSH_ENDPOINT 1025 bool scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) { @@ -686,6 +688,7 @@ scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) { {"serial", required_argument, NULL, 's'}, {"shortcut-mod", required_argument, NULL, OPT_SHORTCUT_MOD}, {"show-touches", no_argument, NULL, 't'}, + {"ssh-endpoint", required_argument, NULL, OPT_SSH_ENDPOINT}, {"stay-awake", no_argument, NULL, 'w'}, {"turn-screen-off", no_argument, NULL, 'S'}, {"verbosity", required_argument, NULL, 'V'}, @@ -697,6 +700,7 @@ 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}, + {"use-ssh", no_argument, NULL, OPT_USE_SSH}, {NULL, 0, NULL, 0 }, }; @@ -799,6 +803,9 @@ scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) { case OPT_WINDOW_TITLE: opts->window_title = optarg; break; + case OPT_SSH_ENDPOINT: + opts->ssh_endpoint = optarg; + break; case OPT_WINDOW_X: if (!parse_window_position(optarg, &opts->window_x)) { return false; @@ -856,6 +863,9 @@ scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) { return false; } break; + case OPT_USE_SSH: + opts->use_ssh = true; + break; default: // getopt prints the error message on stderr return false; diff --git a/app/src/command.c b/app/src/command.c index 81047b7a..4f4ae938 100644 --- a/app/src/command.c +++ b/app/src/command.c @@ -102,6 +102,49 @@ show_adb_err_msg(enum process_result err, const char *const argv[]) { } } +process_t +scp_execute(const char *const scp_cmd[], size_t len) { + const char *cmd[len + 2]; + unsigned i = 0; + process_t process; + + cmd[i++] = "scp"; + memcpy(&cmd[i], scp_cmd, len * sizeof(const char *)); + i += len; + cmd[i] = NULL; + enum process_result r = cmd_execute(cmd, &process); + if (r != PROCESS_SUCCESS) { + show_adb_err_msg(r, cmd); + return PROCESS_NONE; + } + return process; +} + +process_t +ssh_execute(const char *endpoint, const char *const ssh_cmd[], size_t len, + const char *const prefix_cmd[], size_t prefix_cmd_len, + const char *const ssh_options[], size_t ssh_options_len) { + const char *cmd[len + prefix_cmd_len + ssh_options_len + 3]; + unsigned i = 0; + process_t process; + + cmd[i++] = "ssh"; + memcpy(&cmd[i], ssh_options, ssh_options_len * sizeof(const char *)); + i += ssh_options_len; + cmd[i++] = endpoint; + memcpy(&cmd[i], prefix_cmd, prefix_cmd_len * sizeof(const char *)); + i += prefix_cmd_len; + memcpy(&cmd[i], ssh_cmd, len * sizeof(const char *)); + i += len; + cmd[i] = NULL; + enum process_result r = cmd_execute(cmd, &process); + if (r != PROCESS_SUCCESS) { + show_adb_err_msg(r, cmd); + return PROCESS_NONE; + } + return process; +} + process_t adb_execute(const char *serial, const char *const adb_cmd[], size_t len) { const char *cmd[len + 4]; @@ -164,6 +207,38 @@ adb_reverse_remove(const char *serial, const char *device_socket_name) { return adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd)); } +process_t +ssh_push(const char *endpoint, const char *local, const char *remote) { +#ifdef __WINDOWS__ + // Windows will parse the string, so the paths must be quoted + // (see sys/win/command.c) + local = strquote(local); + if (!local) { + return PROCESS_NONE; + } + remote = strquote(remote); + if (!remote) { + SDL_free((void *) local); + return PROCESS_NONE; + } +#endif + + char * destination = (char *) SDL_malloc(strlen(remote) + strlen(endpoint) + 2); + strcpy(destination, endpoint); + strcat(destination, ":"); + strcat(destination, remote); + const char *const scp_cmd[] = {local, destination}; + process_t proc = scp_execute(scp_cmd, ARRAY_LEN(scp_cmd)); + +#ifdef __WINDOWS__ + SDL_free((void *) remote); + SDL_free((void *) local); +#endif + SDL_free((void *) destination); + + return proc; +} + process_t adb_push(const char *serial, const char *local, const char *remote) { #ifdef __WINDOWS__ diff --git a/app/src/command.h b/app/src/command.h index 28f9fbcf..d69cddd4 100644 --- a/app/src/command.h +++ b/app/src/command.h @@ -57,6 +57,14 @@ cmd_terminate(process_t pid); bool cmd_simple_wait(process_t pid, exit_code_t *exit_code); +process_t +scp_execute(const char *const ssh_cmd[], size_t len); + +process_t +ssh_execute(const char *endpoint, const char *const ssh_cmd[], size_t len, + const char *const prefix_cmd[], size_t prefix_cmd_len, + const char *const ssh_options[], size_t ssh_options_len); + process_t adb_execute(const char *serial, const char *const adb_cmd[], size_t len); @@ -74,6 +82,9 @@ adb_reverse(const char *serial, const char *device_socket_name, process_t adb_reverse_remove(const char *serial, const char *device_socket_name); +process_t +ssh_push(const char *endpoint, const char *local, const char *remote); + process_t adb_push(const char *serial, const char *local, const char *remote); diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index 45068cbb..ad1b1255 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -319,6 +319,8 @@ scrcpy(const struct scrcpy_options *options) { .stay_awake = options->stay_awake, .codec_options = options->codec_options, .force_adb_forward = options->force_adb_forward, + .use_ssh = options->use_ssh, + .ssh_endpoint = options->ssh_endpoint, }; if (!server_start(&server, options->serial, ¶ms)) { return false; diff --git a/app/src/scrcpy.h b/app/src/scrcpy.h index 86a2b57b..22d4a510 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 *ssh_endpoint; enum sc_log_level log_level; enum sc_record_format record_format; struct sc_port_range port_range; @@ -79,6 +80,7 @@ struct scrcpy_options { bool force_adb_forward; bool disable_screensaver; bool forward_key_repeat; + bool use_ssh; }; #define SCRCPY_OPTIONS_DEFAULT { \ @@ -123,6 +125,8 @@ struct scrcpy_options { .force_adb_forward = false, \ .disable_screensaver = false, \ .forward_key_repeat = true, \ + .use_ssh = false, \ + .ssh_endpoint = NULL, \ } bool diff --git a/app/src/server.c b/app/src/server.c index 05b2cf91..bf367660 100644 --- a/app/src/server.c +++ b/app/src/server.c @@ -16,11 +16,18 @@ #include "util/str_util.h" #define SOCKET_NAME "scrcpy" +#define SSH_SOCKET_NAME "/dev/socket/scrcpy" #define SERVER_FILENAME "scrcpy-server" +#define ABSTRACTCAT_FILENAME "abstractcat" #define DEFAULT_SERVER_PATH PREFIX "/share/scrcpy/" SERVER_FILENAME #define DEVICE_SERVER_PATH "/data/local/tmp/scrcpy-server.jar" +#define DEFAULT_ABSTRACTCAT_PATH PREFIX "/share/scrcpy/" ABSTRACTCAT_FILENAME +#define DEVICE_ABSTRACTCAT_PATH "/data/local/tmp/" ABSTRACTCAT_FILENAME + +static void close_socket(socket_t socket); + static char * get_server_path(void) { #ifdef __WINDOWS__ @@ -87,8 +94,90 @@ get_server_path(void) { #endif } +static char * +get_abstractcat_path(void) { +#ifdef __WINDOWS__ + const wchar_t *abstractcat_path_env = _wgetenv(L"SCRCPY_ABSTRACTCAT_PATH"); +#else + const char *abstractcat_path_env = getenv("SCRCPY_ABSTRACTCAT_PATH"); +#endif + if (abstractcat_path_env) { + // if the envvar is set, use it +#ifdef __WINDOWS__ + char *abstractcat_path = utf8_from_wide_char(abstractcat_path_env); +#else + char *abstractcat_path = SDL_strdup(abstractcat_path_env); +#endif + if (!abstractcat_path) { + LOGE("Could not allocate memory"); + return NULL; + } + LOGD("Using SCRCPY_ABSTRACTCAT_PATH: %s", abstractcat_path); + return abstractcat_path; + } + +#ifndef PORTABLE + LOGD("Using abstractcat: " DEFAULT_ABSTRACTCAT_PATH); + char *abstractcat_path = SDL_strdup(DEFAULT_ABSTRACTCAT_PATH); + if (!abstractcat_path) { + LOGE("Could not allocate memory"); + return NULL; + } + // the absolute path is hardcoded + return abstractcat_path; +#else + + // use scrcpy-server in the same directory as the executable + char *executable_path = get_executable_path(); + if (!executable_path) { + LOGE("Could not get executable path, " + "using " ABSTRACTCAT_FILENAME " from current directory"); + // not found, use current directory + return ABSTRACTCAT_FILENAME; + } + char *dir = dirname(executable_path); + size_t dirlen = strlen(dir); + + // sizeof(ABSTRACTCAT_FILENAME) gives statically the size including the null byte + size_t len = dirlen + 1 + sizeof(ABSTRACTCAT_FILENAME); + char *abstractcat_path = SDL_malloc(len); + if (!abstractcat_path) { + LOGE("Could not alloc abstractcat path string, " + "using " ABSTRACTCAT_FILENAME " from current directory"); + SDL_free(executable_path); + return ABSTRACTCAT_FILENAME; + } + + memcpy(abstractcat_path, dir, dirlen); + abstractcat_path[dirlen] = PATH_SEPARATOR; + memcpy(&abstractcat_path[dirlen + 1], ABSTRACTCAT_FILENAME, sizeof(ABSTRACTCAT_FILENAME)); + // the final null byte has been copied with ABSTRACTCAT_FILENAME + + SDL_free(executable_path); + + LOGD("Using abstractcat (portable): %s", abstractcat_path); + return abstractcat_path; +#endif +} + static bool -push_server(const char *serial) { +push_abstractcat(const struct server_params *params) { + char *abstractcat_path = get_abstractcat_path(); + if (!abstractcat_path) { + return false; + } + if (!is_regular_file(abstractcat_path)) { + LOGE("'%s' does not exist or is not a regular file\n", abstractcat_path); + SDL_free(abstractcat_path); + return false; + } + process_t process = ssh_push(params->ssh_endpoint, abstractcat_path, DEVICE_ABSTRACTCAT_PATH); + SDL_free(abstractcat_path); + return process_check_success(process, "scp"); +} + +static bool +push_server(const char *serial, const struct server_params *params) { char *server_path = get_server_path(); if (!server_path) { return false; @@ -98,11 +187,26 @@ push_server(const char *serial) { SDL_free(server_path); return false; } - process_t process = adb_push(serial, server_path, DEVICE_SERVER_PATH); + process_t process; + if (params->use_ssh) + process = ssh_push(params->ssh_endpoint, server_path, DEVICE_SERVER_PATH); + else + process = adb_push(serial, server_path, DEVICE_SERVER_PATH); SDL_free(server_path); return process_check_success(process, "adb push"); } +static bool prepare_ssh_socket_path(const struct server_params *params) { + const char *const cmd[] = { + "rm", + "-f", + SSH_SOCKET_NAME, + }; + process_t process = ssh_execute(params->ssh_endpoint, cmd, sizeof(cmd) / sizeof(cmd[0]), + NULL, 0, NULL, 0); + return process_check_success(process, "ssh rm -f " SSH_SOCKET_NAME); +} + static bool enable_tunnel_reverse(const char *serial, uint16_t local_port) { process_t process = adb_reverse(serial, SOCKET_NAME, local_port); @@ -264,9 +368,9 @@ execute_server(struct server *server, const struct server_params *params) { sprintf(lock_video_orientation_string, "%"PRIi8, params->lock_video_orientation); sprintf(display_id_string, "%"PRIu16, params->display_id); const char *const cmd[] = { - "shell", + params->use_ssh ? "ANDROID_DATA=/data" : "shell", "CLASSPATH=" DEVICE_SERVER_PATH, - "app_process", + "/system/bin/app_process", #ifdef SERVER_DEBUGGER # define SERVER_DEBUGGER_PORT "5005" # ifdef SERVER_DEBUGGER_METHOD_NEW @@ -286,7 +390,7 @@ execute_server(struct server *server, const struct server_params *params) { bit_rate_string, max_fps_string, lock_video_orientation_string, - server->tunnel_forward ? "true" : "false", + server->tunnel_forward || params->force_adb_forward ? "true" : "false", params->crop ? params->crop : "-", "true", // always send frame meta (packet boundaries + timestamp) params->control ? "true" : "false", @@ -306,7 +410,59 @@ 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])); + if (params->use_ssh) + { + for (uint16_t port = server->port_range.first;;) { + server->server_socket = listen_on_port(port); + if (server->server_socket == INVALID_SOCKET) { + if (port < server->port_range.last) { + LOGW("Could not listen on port %" PRIu16", retrying on %" PRIu16, + port, (uint16_t) (port + 1)); + port++; + continue; + } + + if (server->port_range.first == server->port_range.last) { + LOGE("Could not listen on port %" PRIu16, server->port_range.first); + } else { + LOGE("Could not listen on any port in range %" PRIu16 ":%" PRIu16, + server->port_range.first, server->port_range.last); + } + return PROCESS_NONE; + } + server->local_port = port; + char *tunnel_desc = SDL_strdup(SSH_SOCKET_NAME ":localhost:12345"); + + if (params->force_adb_forward) + sprintf(tunnel_desc, "localhost:%d:" SSH_SOCKET_NAME, port); + else + sprintf(tunnel_desc, SSH_SOCKET_NAME ":localhost:%d", port); + + const char *const ssh_options[] = { + "-o", "ExitOnForwardFailure=yes", + params->force_adb_forward ? "-L" : "-R", tunnel_desc, + }; + + const char *const prefix_cmd[] = { + DEVICE_ABSTRACTCAT_PATH, + params->force_adb_forward ? SSH_SOCKET_NAME : "@" SOCKET_NAME, // Source. + params->force_adb_forward ? "@" SOCKET_NAME : SSH_SOCKET_NAME, // Destination. + "2", // Maximum connections to forward. + }; + + if (params->force_adb_forward) { + close_socket(server->server_socket); + server->server_socket = INVALID_SOCKET; + server->tunnel_forward = true; + } + + return ssh_execute(params->ssh_endpoint, cmd, sizeof(cmd) / sizeof(cmd[0]), + prefix_cmd, sizeof(prefix_cmd) / sizeof(prefix_cmd[0]), + ssh_options, sizeof(ssh_options) / sizeof(ssh_options[0])); + } + } + else + return adb_execute(server->serial, cmd, sizeof(cmd) / sizeof(cmd[0])); } static socket_t @@ -385,11 +541,19 @@ server_start(struct server *server, const char *serial, } } - if (!push_server(serial)) { + server->use_ssh = params->use_ssh; + + if (!push_server(serial, params)) { goto error1; } - if (!enable_tunnel_any_port(server, params->port_range, + if (params->use_ssh && !push_abstractcat(params)) + goto error1; + + if (params->use_ssh && !prepare_ssh_socket_path(params)) + goto error1; + + if (!params->use_ssh && !enable_tunnel_any_port(server, params->port_range, params->force_adb_forward)) { goto error1; } @@ -427,7 +591,10 @@ error2: (void) was_closed; close_socket(server->server_socket); } - disable_tunnel(server); + + if (!server->use_ssh) { + disable_tunnel(server); + } error1: SDL_free(server->serial); return false; @@ -470,9 +637,12 @@ server_connect_to(struct server *server) { } } - // we don't need the adb tunnel anymore - disable_tunnel(server); // ignore failure - server->tunnel_enabled = false; + if (server->tunnel_enabled) { + // we don't need the adb tunnel anymore + if (!server->use_ssh) + disable_tunnel(server); // ignore failure + server->tunnel_enabled = false; + } return true; } @@ -494,7 +664,7 @@ server_stop(struct server *server) { cmd_terminate(server->process); - if (server->tunnel_enabled) { + if (server->tunnel_enabled && !server->use_ssh) { // ignore failure disable_tunnel(server); } diff --git a/app/src/server.h b/app/src/server.h index 254afe30..43ed02a7 100644 --- a/app/src/server.h +++ b/app/src/server.h @@ -25,6 +25,7 @@ struct server { uint16_t local_port; // selected from port_range bool tunnel_enabled; bool tunnel_forward; // use "adb forward" instead of "adb reverse" + bool use_ssh; }; #define SERVER_INITIALIZER { \ @@ -42,12 +43,14 @@ struct server { .local_port = 0, \ .tunnel_enabled = false, \ .tunnel_forward = false, \ + .use_ssh = false, \ } struct server_params { enum sc_log_level log_level; const char *crop; const char *codec_options; + const char *ssh_endpoint; struct sc_port_range port_range; uint16_t max_size; uint32_t bit_rate; @@ -58,6 +61,7 @@ struct server_params { bool show_touches; bool stay_awake; bool force_adb_forward; + bool use_ssh; }; // init default values