diff --git a/app/scrcpy.1 b/app/scrcpy.1 index 122af757..715000b6 100644 --- a/app/scrcpy.1 +++ b/app/scrcpy.1 @@ -199,6 +199,14 @@ For example, to use either LCtrl+LAlt or LSuper for scrcpy shortcuts, pass "lctr Default is "lalt,lsuper" (left-Alt or left-Super). +.TP +.BI "\-\-tcpip[=ip[:port]] +Configure and reconnect the device over TCP/IP. + +If a destination address is provided, then scrcpy connects to this address before starting. The device must listen on the given TCP port (default is 5555). + +If no destination address is provided, then scrcpy attempts to find the IP address of the current device (typically connected over USB), enables TCP/IP mode, then connects to this address before starting. + .TP .B \-S, \-\-turn\-screen\-off Turn the device screen off immediately. diff --git a/app/src/adb.h b/app/src/adb.h index 8d7f3ea1..4d1278cf 100644 --- a/app/src/adb.h +++ b/app/src/adb.h @@ -12,6 +12,8 @@ #define SC_ADB_NO_STDERR (1 << 1) #define SC_ADB_NO_LOGERR (1 << 2) +#define SC_ADB_SILENT (SC_ADB_NO_STDOUT | SC_ADB_NO_STDERR | SC_ADB_NO_LOGERR) + sc_pid adb_execute(const char *serial, const char *const adb_cmd[], size_t len, unsigned flags); diff --git a/app/src/cli.c b/app/src/cli.c index 791d3c43..8cfaf57b 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -50,6 +50,7 @@ #define OPT_TUNNEL_HOST 1030 #define OPT_TUNNEL_PORT 1031 #define OPT_NO_CLIPBOARD_AUTOSYNC 1032 +#define OPT_TCPIP 1033 struct sc_option { char shortopt; @@ -404,6 +405,20 @@ static const struct sc_option options[] = { .text = "Keep the device on while scrcpy is running, when the device " "is plugged in.", }, + { + .longopt_id = OPT_TCPIP, + .longopt = "tcpip", + .argdesc = "ip[:port]", + .optional_arg = true, + .text = "Configure and reconnect the device over TCP/IP.\n" + "If a destination address is provided, then scrcpy connects to " + "this address before starting. The device must listen on the " + "given TCP port (default is 5555).\n" + "If no destination address is provided, then scrcpy attempts " + "to find the IP address of the current device (typically " + "connected over USB), enables TCP/IP mode, then connects to " + "this address before starting.", + }, { .longopt_id = OPT_WINDOW_BORDERLESS, .longopt = "window-borderless", @@ -1378,6 +1393,10 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[], case OPT_NO_CLIPBOARD_AUTOSYNC: opts->clipboard_autosync = false; break; + case OPT_TCPIP: + opts->tcpip = true; + opts->tcpip_dst = optarg; + break; #ifdef HAVE_V4L2 case OPT_V4L2_SINK: opts->v4l2_device = optarg; @@ -1400,6 +1419,14 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[], return false; } + // If a TCP/IP address is provided, then tcpip must be enabled + assert(opts->tcpip || !opts->tcpip_dst); + + if (opts->serial && opts->tcpip_dst) { + LOGE("Incompatible options: -s/--serial and --tcpip with an argument"); + return false; + } + #ifdef HAVE_V4L2 if (!opts->display && !opts->record_filename && !opts->v4l2_device) { LOGE("-N/--no-display requires either screen recording (-r/--record)" diff --git a/app/src/options.c b/app/src/options.c index a99b09da..a14bda9a 100644 --- a/app/src/options.c +++ b/app/src/options.c @@ -54,4 +54,6 @@ const struct scrcpy_options scrcpy_options_default = { .legacy_paste = false, .power_off_on_close = false, .clipboard_autosync = true, + .tcpip = false, + .tcpip_dst = NULL, }; diff --git a/app/src/options.h b/app/src/options.h index d10b2e8a..f183bd73 100644 --- a/app/src/options.h +++ b/app/src/options.h @@ -109,6 +109,8 @@ struct scrcpy_options { bool legacy_paste; bool power_off_on_close; bool clipboard_autosync; + bool tcpip; + const char *tcpip_dst; }; extern const struct scrcpy_options scrcpy_options_default; diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index f0142b46..99317ffc 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -365,6 +365,8 @@ scrcpy(struct scrcpy_options *options) { .force_adb_forward = options->force_adb_forward, .power_off_on_close = options->power_off_on_close, .clipboard_autosync = options->clipboard_autosync, + .tcpip = options->tcpip, + .tcpip_dst = options->tcpip_dst, }; static const struct sc_server_callbacks cbs = { diff --git a/app/src/server.c b/app/src/server.c index 6211b08b..06cb7b72 100644 --- a/app/src/server.c +++ b/app/src/server.c @@ -69,6 +69,7 @@ sc_server_params_destroy(struct sc_server_params *params) { free((char *) params->crop); free((char *) params->codec_options); free((char *) params->encoder_name); + free((char *) params->tcpip_dst); } static bool @@ -92,6 +93,7 @@ sc_server_params_copy(struct sc_server_params *dst, COPY(crop); COPY(codec_options); COPY(encoder_name); + COPY(tcpip_dst); #undef COPY return true; @@ -494,23 +496,216 @@ sc_server_fill_serial(struct sc_server *server) { LOGE("Could not get device serial"); return false; } + + LOGD("Device serial: %s", server->params.serial); } return true; } +static bool +is_tcpip_mode_enabled(struct sc_server *server) { + struct sc_intr *intr = &server->intr; + const char *serial = server->params.serial; + + char *current_port = + adb_getprop(intr, serial, "service.adb.tcp.port", SC_ADB_SILENT); + if (!current_port) { + return false; + } + + // Is the device is listening on TCP on port 5555? + bool enabled = !strcmp("5555", current_port); + free(current_port); + return enabled; +} + +static bool +wait_tcpip_mode_enabled(struct sc_server *server, unsigned attempts, + sc_tick delay) { + if (is_tcpip_mode_enabled(server)) { + LOGI("TCP/IP mode enabled"); + return true; + } + + // Only print this log if TCP/IP is not enabled + LOGI("Waiting for TCP/IP mode enabled..."); + + do { + sc_tick deadline = sc_tick_now() + delay; + if (!sc_server_sleep(server, deadline)) { + LOGI("TCP/IP mode waiting interrupted"); + return false; + } + + if (is_tcpip_mode_enabled(server)) { + LOGI("TCP/IP mode enabled"); + return true; + } + } while (--attempts); + return false; +} + +char * +append_port_5555(const char *ip) { + size_t len = strlen(ip); + + // sizeof counts the final '\0' + char *ip_port = malloc(len + sizeof(":5555")); + if (!ip_port) { + LOG_OOM(); + return NULL; + } + + memcpy(ip_port, ip, len); + memcpy(ip_port + len, ":5555", sizeof(":5555")); + + return ip_port; +} + +static bool +sc_server_switch_to_tcpip(struct sc_server *server, char **out_ip_port) { + const char *serial = server->params.serial; + assert(serial); + + struct sc_intr *intr = &server->intr; + + char *ip = adb_get_device_ip(intr, serial, 0); + if (!ip) { + LOGE("Device IP not found"); + return false; + } + + char *ip_port = append_port_5555(ip); + free(ip); + if (!ip_port) { + return false; + } + + bool tcp_mode = is_tcpip_mode_enabled(server); + + if (!tcp_mode) { + bool ok = adb_tcpip(intr, serial, 5555, SC_ADB_NO_STDOUT); + if (!ok) { + LOGE("Could not restart adbd in TCP/IP mode"); + goto error; + } + + unsigned attempts = 40; + sc_tick delay = SC_TICK_FROM_MS(250); + ok = wait_tcpip_mode_enabled(server, attempts, delay); + if (!ok) { + goto error; + } + } + + *out_ip_port = ip_port; + + return true; + +error: + free(ip_port); + return false; +} + +static bool +sc_server_connect_to_tcpip(struct sc_server *server, const char *ip_port) { + struct sc_intr *intr = &server->intr; + + // Error expected if not connected, do not report any error + adb_disconnect(intr, ip_port, SC_ADB_SILENT); + + bool ok = adb_connect(intr, ip_port, 0); + if (!ok) { + LOGE("Could not connect to %s", ip_port); + return false; + } + + // Override the serial, owned by the sc_server_params + free((void *) server->params.serial); + server->params.serial = strdup(ip_port); + if (!server->params.serial) { + LOG_OOM(); + return false; + } + + LOGI("Connected to %s", ip_port); + return true; +} + + +static bool +sc_server_configure_tcpip(struct sc_server *server) { + char *ip_port; + + const struct sc_server_params *params = &server->params; + + // If tcpip parameter is given, then it must connect to this address. + // Therefore, the device is unknown, so serial is meaningless at this point. + assert(!params->serial || !params->tcpip_dst); + + if (params->tcpip_dst) { + // Append ":5555" if no port is present + bool contains_port = strchr(params->tcpip_dst, ':'); + ip_port = contains_port ? strdup(params->tcpip_dst) + : append_port_5555(params->tcpip_dst); + if (!ip_port) { + LOG_OOM(); + return false; + } + } else { + // The device IP address must be retrieved from the current + // connected device + if (!sc_server_fill_serial(server)) { + return false; + } + + // The serial is either the real serial when connected via USB, or + // the IP:PORT when connected over TCP/IP. Only the latter contains + // a colon. + bool is_already_tcpip = strchr(params->serial, ':'); + if (is_already_tcpip) { + // Nothing to do + LOGI("Device already connected via TCP/IP: %s", params->serial); + return true; + } + + bool ok = sc_server_switch_to_tcpip(server, &ip_port); + if (!ok) { + return false; + } + } + + // On success, this call changes params->serial + bool ok = sc_server_connect_to_tcpip(server, ip_port); + free(ip_port); + return ok; +} + static int run_server(void *data) { struct sc_server *server = data; + const struct sc_server_params *params = &server->params; + + if (params->serial) { + LOGD("Device serial: %s", params->serial); + } + + if (params->tcpip) { + // params->serial may be changed after this call + bool ok = sc_server_configure_tcpip(server); + if (!ok) { + goto error_connection_failed; + } + } + + // It is ok to call this function even if the device serial has been + // changed by switching over TCP/IP if (!sc_server_fill_serial(server)) { goto error_connection_failed; } - const struct sc_server_params *params = &server->params; - - LOGD("Device serial: %s", params->serial); - bool ok = push_server(&server->intr, params->serial); if (!ok) { goto error_connection_failed; diff --git a/app/src/server.h b/app/src/server.h index 5b25ff46..8ea20dc7 100644 --- a/app/src/server.h +++ b/app/src/server.h @@ -42,6 +42,8 @@ struct sc_server_params { bool force_adb_forward; bool power_off_on_close; bool clipboard_autosync; + bool tcpip; + const char *tcpip_dst; }; struct sc_server {