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:
parent
3b310f8317
commit
19858e6aeb
8 changed files with 244 additions and 4 deletions
|
@ -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.
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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)"
|
||||||
|
|
|
@ -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,
|
||||||
};
|
};
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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 = {
|
||||||
|
|
203
app/src/server.c
203
app/src/server.c
|
@ -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;
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
Loading…
Reference in a new issue