Add --tcpip feature

Expose an option to automatically configure and reconnect the device
over TCP/IP, to simplify wireless connection without using adb
explicitly.

There are two variants:
 - 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.

PR #2827 <https://github.com/Genymobile/scrcpy/pull/2827>
This commit is contained in:
Romain Vimont 2021-11-25 22:22:49 +01:00
parent 3b310f8317
commit 19858e6aeb
8 changed files with 244 additions and 4 deletions

View file

@ -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). 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 .TP
.B \-S, \-\-turn\-screen\-off .B \-S, \-\-turn\-screen\-off
Turn the device screen off immediately. Turn the device screen off immediately.

View file

@ -12,6 +12,8 @@
#define SC_ADB_NO_STDERR (1 << 1) #define SC_ADB_NO_STDERR (1 << 1)
#define SC_ADB_NO_LOGERR (1 << 2) #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 sc_pid
adb_execute(const char *serial, const char *const adb_cmd[], size_t len, adb_execute(const char *serial, const char *const adb_cmd[], size_t len,
unsigned flags); unsigned flags);

View file

@ -50,6 +50,7 @@
#define OPT_TUNNEL_HOST 1030 #define OPT_TUNNEL_HOST 1030
#define OPT_TUNNEL_PORT 1031 #define OPT_TUNNEL_PORT 1031
#define OPT_NO_CLIPBOARD_AUTOSYNC 1032 #define OPT_NO_CLIPBOARD_AUTOSYNC 1032
#define OPT_TCPIP 1033
struct sc_option { struct sc_option {
char shortopt; char shortopt;
@ -404,6 +405,20 @@ static const struct sc_option options[] = {
.text = "Keep the device on while scrcpy is running, when the device " .text = "Keep the device on while scrcpy is running, when the device "
"is plugged in.", "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_id = OPT_WINDOW_BORDERLESS,
.longopt = "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: case OPT_NO_CLIPBOARD_AUTOSYNC:
opts->clipboard_autosync = false; opts->clipboard_autosync = false;
break; break;
case OPT_TCPIP:
opts->tcpip = true;
opts->tcpip_dst = optarg;
break;
#ifdef HAVE_V4L2 #ifdef HAVE_V4L2
case OPT_V4L2_SINK: case OPT_V4L2_SINK:
opts->v4l2_device = optarg; opts->v4l2_device = optarg;
@ -1400,6 +1419,14 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
return false; 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 #ifdef HAVE_V4L2
if (!opts->display && !opts->record_filename && !opts->v4l2_device) { if (!opts->display && !opts->record_filename && !opts->v4l2_device) {
LOGE("-N/--no-display requires either screen recording (-r/--record)" LOGE("-N/--no-display requires either screen recording (-r/--record)"

View file

@ -54,4 +54,6 @@ const struct scrcpy_options scrcpy_options_default = {
.legacy_paste = false, .legacy_paste = false,
.power_off_on_close = false, .power_off_on_close = false,
.clipboard_autosync = true, .clipboard_autosync = true,
.tcpip = false,
.tcpip_dst = NULL,
}; };

View file

@ -109,6 +109,8 @@ struct scrcpy_options {
bool legacy_paste; bool legacy_paste;
bool power_off_on_close; bool power_off_on_close;
bool clipboard_autosync; bool clipboard_autosync;
bool tcpip;
const char *tcpip_dst;
}; };
extern const struct scrcpy_options scrcpy_options_default; extern const struct scrcpy_options scrcpy_options_default;

View file

@ -365,6 +365,8 @@ scrcpy(struct scrcpy_options *options) {
.force_adb_forward = options->force_adb_forward, .force_adb_forward = options->force_adb_forward,
.power_off_on_close = options->power_off_on_close, .power_off_on_close = options->power_off_on_close,
.clipboard_autosync = options->clipboard_autosync, .clipboard_autosync = options->clipboard_autosync,
.tcpip = options->tcpip,
.tcpip_dst = options->tcpip_dst,
}; };
static const struct sc_server_callbacks cbs = { static const struct sc_server_callbacks cbs = {

View file

@ -69,6 +69,7 @@ sc_server_params_destroy(struct sc_server_params *params) {
free((char *) params->crop); free((char *) params->crop);
free((char *) params->codec_options); free((char *) params->codec_options);
free((char *) params->encoder_name); free((char *) params->encoder_name);
free((char *) params->tcpip_dst);
} }
static bool static bool
@ -92,6 +93,7 @@ sc_server_params_copy(struct sc_server_params *dst,
COPY(crop); COPY(crop);
COPY(codec_options); COPY(codec_options);
COPY(encoder_name); COPY(encoder_name);
COPY(tcpip_dst);
#undef COPY #undef COPY
return true; return true;
@ -494,23 +496,216 @@ sc_server_fill_serial(struct sc_server *server) {
LOGE("Could not get device serial"); LOGE("Could not get device serial");
return false; return false;
} }
LOGD("Device serial: %s", server->params.serial);
} }
return true; 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 static int
run_server(void *data) { run_server(void *data) {
struct sc_server *server = 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)) { if (!sc_server_fill_serial(server)) {
goto error_connection_failed; 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); bool ok = push_server(&server->intr, params->serial);
if (!ok) { if (!ok) {
goto error_connection_failed; goto error_connection_failed;

View file

@ -42,6 +42,8 @@ struct sc_server_params {
bool force_adb_forward; bool force_adb_forward;
bool power_off_on_close; bool power_off_on_close;
bool clipboard_autosync; bool clipboard_autosync;
bool tcpip;
const char *tcpip_dst;
}; };
struct sc_server { struct sc_server {