c996a6d462
The server needs to interrupt the sockets on stop, but it must not close
them while other threads may attempt to read from or write to them.
In particular, the video_socket is read by the stream thread, and the
control_socket is written by the controller and read by receiver.
Therefore, close the socket only on sc_server_destroy(), which is called
after all other threads are joined.
Reported by TSAN on close:
WARNING: ThreadSanitizer: data race (pid=3287612)
Write of size 8 at 0x7ba000000080 by thread T1:
#0 close ../../../../src/libsanitizer/tsan/tsan_interceptors_posix.cpp:1690 (libtsan.so.0+0x359d8)
#1 net_close ../app/src/util/net.c:280 (scrcpy+0x23643)
#2 run_server ../app/src/server.c:772 (scrcpy+0x20047)
#3 <null> <null> (libSDL2-2.0.so.0+0x905a0)
Previous read of size 8 at 0x7ba000000080 by thread T16:
#0 recv ../../../../src/libsanitizer/sanitizer_common/sanitizer_common_interceptors.inc:6603 (libtsan.so.0+0x4f4a6)
#1 net_recv_all ../app/src/util/net.c:228 (scrcpy+0x234a9)
#2 stream_recv_packet ../app/src/stream.c:33 (scrcpy+0x2045c)
#3 run_stream ../app/src/stream.c:228 (scrcpy+0x21169)
#4 <null> <null> (libSDL2-2.0.so.0+0x905a0)
Refs ddb9396743
842 lines
24 KiB
C
842 lines
24 KiB
C
#include "server.h"
|
|
|
|
#include <assert.h>
|
|
#include <errno.h>
|
|
#include <inttypes.h>
|
|
#include <stdio.h>
|
|
#include <SDL2/SDL_timer.h>
|
|
#include <SDL2/SDL_platform.h>
|
|
|
|
#include "adb.h"
|
|
#include "util/file.h"
|
|
#include "util/log.h"
|
|
#include "util/net_intr.h"
|
|
#include "util/process_intr.h"
|
|
#include "util/str.h"
|
|
|
|
#define SC_SERVER_FILENAME "scrcpy-server"
|
|
|
|
#define SC_SERVER_PATH_DEFAULT PREFIX "/share/scrcpy/" SC_SERVER_FILENAME
|
|
#define SC_DEVICE_SERVER_PATH "/data/local/tmp/scrcpy-server.jar"
|
|
|
|
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 = sc_str_from_wchars(server_path_env);
|
|
#else
|
|
char *server_path = strdup(server_path_env);
|
|
#endif
|
|
if (!server_path) {
|
|
LOG_OOM();
|
|
return NULL;
|
|
}
|
|
LOGD("Using SCRCPY_SERVER_PATH: %s", server_path);
|
|
return server_path;
|
|
}
|
|
|
|
#ifndef PORTABLE
|
|
LOGD("Using server: " SC_SERVER_PATH_DEFAULT);
|
|
char *server_path = strdup(SC_SERVER_PATH_DEFAULT);
|
|
if (!server_path) {
|
|
LOG_OOM();
|
|
return NULL;
|
|
}
|
|
#else
|
|
char *server_path = sc_file_get_local_path(SC_SERVER_FILENAME);
|
|
if (!server_path) {
|
|
LOGE("Could not get local file path, "
|
|
"using " SC_SERVER_FILENAME " from current directory");
|
|
return strdup(SC_SERVER_FILENAME);
|
|
}
|
|
|
|
LOGD("Using server (portable): %s", server_path);
|
|
#endif
|
|
|
|
return server_path;
|
|
}
|
|
|
|
static void
|
|
sc_server_params_destroy(struct sc_server_params *params) {
|
|
// The server stores a copy of the params provided by the user
|
|
free((char *) params->serial);
|
|
free((char *) params->crop);
|
|
free((char *) params->codec_options);
|
|
free((char *) params->encoder_name);
|
|
free((char *) params->tcpip_dst);
|
|
}
|
|
|
|
static bool
|
|
sc_server_params_copy(struct sc_server_params *dst,
|
|
const struct sc_server_params *src) {
|
|
*dst = *src;
|
|
|
|
// The params reference user-allocated memory, so we must copy them to
|
|
// handle them from another thread
|
|
|
|
#define COPY(FIELD) \
|
|
dst->FIELD = NULL; \
|
|
if (src->FIELD) { \
|
|
dst->FIELD = strdup(src->FIELD); \
|
|
if (!dst->FIELD) { \
|
|
goto error; \
|
|
} \
|
|
}
|
|
|
|
COPY(serial);
|
|
COPY(crop);
|
|
COPY(codec_options);
|
|
COPY(encoder_name);
|
|
COPY(tcpip_dst);
|
|
#undef COPY
|
|
|
|
return true;
|
|
|
|
error:
|
|
sc_server_params_destroy(dst);
|
|
return false;
|
|
}
|
|
|
|
static bool
|
|
push_server(struct sc_intr *intr, const char *serial) {
|
|
char *server_path = get_server_path();
|
|
if (!server_path) {
|
|
return false;
|
|
}
|
|
if (!sc_file_is_regular(server_path)) {
|
|
LOGE("'%s' does not exist or is not a regular file\n", server_path);
|
|
free(server_path);
|
|
return false;
|
|
}
|
|
bool ok = adb_push(intr, serial, server_path, SC_DEVICE_SERVER_PATH, 0);
|
|
free(server_path);
|
|
return ok;
|
|
}
|
|
|
|
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:
|
|
return "info";
|
|
case SC_LOG_LEVEL_WARN:
|
|
return "warn";
|
|
case SC_LOG_LEVEL_ERROR:
|
|
return "error";
|
|
default:
|
|
assert(!"unexpected log level");
|
|
return "(unknown)";
|
|
}
|
|
}
|
|
|
|
static bool
|
|
sc_server_sleep(struct sc_server *server, sc_tick deadline) {
|
|
sc_mutex_lock(&server->mutex);
|
|
bool timed_out = false;
|
|
while (!server->stopped && !timed_out) {
|
|
timed_out = !sc_cond_timedwait(&server->cond_stopped,
|
|
&server->mutex, deadline);
|
|
}
|
|
bool stopped = server->stopped;
|
|
sc_mutex_unlock(&server->mutex);
|
|
|
|
return !stopped;
|
|
}
|
|
|
|
static sc_pid
|
|
execute_server(struct sc_server *server,
|
|
const struct sc_server_params *params) {
|
|
sc_pid pid = SC_PROCESS_NONE;
|
|
|
|
const char *cmd[128];
|
|
unsigned count = 0;
|
|
cmd[count++] = "shell";
|
|
cmd[count++] = "CLASSPATH=" SC_DEVICE_SERVER_PATH;
|
|
cmd[count++] = "app_process";
|
|
|
|
#ifdef SERVER_DEBUGGER
|
|
# define SERVER_DEBUGGER_PORT "5005"
|
|
cmd[count++] =
|
|
# 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
|
|
cmd[count++] = "/"; // unused
|
|
cmd[count++] = "com.genymobile.scrcpy.Server";
|
|
cmd[count++] = SCRCPY_VERSION;
|
|
|
|
unsigned dyn_idx = count; // from there, the strings are allocated
|
|
#define ADD_PARAM(fmt, ...) { \
|
|
char *p = (char *) &cmd[count]; \
|
|
if (asprintf(&p, fmt, ## __VA_ARGS__) == -1) { \
|
|
goto end; \
|
|
} \
|
|
cmd[count++] = p; \
|
|
}
|
|
|
|
ADD_PARAM("log_level=%s", log_level_to_server_string(params->log_level));
|
|
ADD_PARAM("bit_rate=%" PRIu32, params->bit_rate);
|
|
|
|
if (params->max_size) {
|
|
ADD_PARAM("max_size=%" PRIu16, params->max_size);
|
|
}
|
|
if (params->max_fps) {
|
|
ADD_PARAM("max_fps=%" PRIu16, params->max_fps);
|
|
}
|
|
if (params->lock_video_orientation != SC_LOCK_VIDEO_ORIENTATION_UNLOCKED) {
|
|
ADD_PARAM("lock_video_orientation=%" PRIi8,
|
|
params->lock_video_orientation);
|
|
}
|
|
if (server->tunnel.forward) {
|
|
ADD_PARAM("tunnel_forward=true");
|
|
}
|
|
if (params->crop) {
|
|
ADD_PARAM("crop=%s", params->crop);
|
|
}
|
|
if (!params->control) {
|
|
// By default, control is true
|
|
ADD_PARAM("control=false");
|
|
}
|
|
if (params->display_id) {
|
|
ADD_PARAM("display_id=%" PRIu32, params->display_id);
|
|
}
|
|
if (params->show_touches) {
|
|
ADD_PARAM("show_touches=true");
|
|
}
|
|
if (params->stay_awake) {
|
|
ADD_PARAM("stay_awake=true");
|
|
}
|
|
if (params->codec_options) {
|
|
ADD_PARAM("codec_options=%s", params->codec_options);
|
|
}
|
|
if (params->encoder_name) {
|
|
ADD_PARAM("encoder_name=%s", params->encoder_name);
|
|
}
|
|
if (params->power_off_on_close) {
|
|
ADD_PARAM("power_off_on_close=true");
|
|
}
|
|
if (!params->clipboard_autosync) {
|
|
// By default, clipboard_autosync is true
|
|
ADD_PARAM("clipboard_autosync=false");
|
|
}
|
|
if (!params->downsize_on_error) {
|
|
// By default, downsize_on_error is true
|
|
ADD_PARAM("downsize_on_error=false");
|
|
}
|
|
|
|
#undef ADD_PARAM
|
|
#undef STRBOOL
|
|
|
|
#ifdef SERVER_DEBUGGER
|
|
LOGI("Server debugger waiting for a client on device port "
|
|
SERVER_DEBUGGER_PORT "...");
|
|
// From the computer, run
|
|
// adb forward tcp:5005 tcp:5005
|
|
// Then, from Android Studio: Run > Debug > Edit configurations...
|
|
// On the left, click on '+', "Remote", with:
|
|
// Host: localhost
|
|
// Port: 5005
|
|
// Then click on "Debug"
|
|
#endif
|
|
// Inherit both stdout and stderr (all server logs are printed to stdout)
|
|
pid = adb_execute(params->serial, cmd, count, 0);
|
|
|
|
end:
|
|
for (unsigned i = dyn_idx; i < count; ++i) {
|
|
free((char *) cmd[i]);
|
|
}
|
|
|
|
return pid;
|
|
}
|
|
|
|
static bool
|
|
connect_and_read_byte(struct sc_intr *intr, sc_socket socket,
|
|
uint32_t tunnel_host, uint16_t tunnel_port) {
|
|
bool ok = net_connect_intr(intr, socket, tunnel_host, tunnel_port);
|
|
if (!ok) {
|
|
return false;
|
|
}
|
|
|
|
char byte;
|
|
// the connection may succeed even if the server behind the "adb tunnel"
|
|
// is not listening, so read one byte to detect a working connection
|
|
if (net_recv_intr(intr, socket, &byte, 1) != 1) {
|
|
// the server is not listening yet behind the adb tunnel
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static sc_socket
|
|
connect_to_server(struct sc_server *server, unsigned attempts, sc_tick delay,
|
|
uint32_t host, uint16_t port) {
|
|
do {
|
|
LOGD("Remaining connection attempts: %u", attempts);
|
|
sc_socket socket = net_socket();
|
|
if (socket != SC_SOCKET_NONE) {
|
|
bool ok = connect_and_read_byte(&server->intr, socket, host, port);
|
|
if (ok) {
|
|
// it worked!
|
|
return socket;
|
|
}
|
|
|
|
net_close(socket);
|
|
}
|
|
|
|
if (sc_intr_is_interrupted(&server->intr)) {
|
|
// Stop immediately
|
|
break;
|
|
}
|
|
|
|
if (attempts) {
|
|
sc_tick deadline = sc_tick_now() + delay;
|
|
bool ok = sc_server_sleep(server, deadline);
|
|
if (!ok) {
|
|
LOGI("Connection attempt stopped");
|
|
break;
|
|
}
|
|
}
|
|
} while (--attempts);
|
|
return SC_SOCKET_NONE;
|
|
}
|
|
|
|
bool
|
|
sc_server_init(struct sc_server *server, const struct sc_server_params *params,
|
|
const struct sc_server_callbacks *cbs, void *cbs_userdata) {
|
|
bool ok = sc_server_params_copy(&server->params, params);
|
|
if (!ok) {
|
|
LOG_OOM();
|
|
return false;
|
|
}
|
|
|
|
ok = sc_mutex_init(&server->mutex);
|
|
if (!ok) {
|
|
sc_server_params_destroy(&server->params);
|
|
return false;
|
|
}
|
|
|
|
ok = sc_cond_init(&server->cond_stopped);
|
|
if (!ok) {
|
|
sc_mutex_destroy(&server->mutex);
|
|
sc_server_params_destroy(&server->params);
|
|
return false;
|
|
}
|
|
|
|
ok = sc_intr_init(&server->intr);
|
|
if (!ok) {
|
|
sc_cond_destroy(&server->cond_stopped);
|
|
sc_mutex_destroy(&server->mutex);
|
|
sc_server_params_destroy(&server->params);
|
|
return false;
|
|
}
|
|
|
|
server->stopped = false;
|
|
|
|
server->video_socket = SC_SOCKET_NONE;
|
|
server->control_socket = SC_SOCKET_NONE;
|
|
|
|
sc_adb_tunnel_init(&server->tunnel);
|
|
|
|
assert(cbs);
|
|
assert(cbs->on_connection_failed);
|
|
assert(cbs->on_connected);
|
|
assert(cbs->on_disconnected);
|
|
|
|
server->cbs = cbs;
|
|
server->cbs_userdata = cbs_userdata;
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool
|
|
device_read_info(struct sc_intr *intr, sc_socket device_socket,
|
|
struct sc_server_info *info) {
|
|
unsigned char buf[SC_DEVICE_NAME_FIELD_LENGTH + 4];
|
|
ssize_t r = net_recv_all_intr(intr, device_socket, buf, sizeof(buf));
|
|
if (r < SC_DEVICE_NAME_FIELD_LENGTH + 4) {
|
|
LOGE("Could not retrieve device information");
|
|
return false;
|
|
}
|
|
// in case the client sends garbage
|
|
buf[SC_DEVICE_NAME_FIELD_LENGTH - 1] = '\0';
|
|
memcpy(info->device_name, (char *) buf, sizeof(info->device_name));
|
|
|
|
info->frame_size.width = (buf[SC_DEVICE_NAME_FIELD_LENGTH] << 8)
|
|
| buf[SC_DEVICE_NAME_FIELD_LENGTH + 1];
|
|
info->frame_size.height = (buf[SC_DEVICE_NAME_FIELD_LENGTH + 2] << 8)
|
|
| buf[SC_DEVICE_NAME_FIELD_LENGTH + 3];
|
|
return true;
|
|
}
|
|
|
|
static bool
|
|
sc_server_connect_to(struct sc_server *server, struct sc_server_info *info) {
|
|
struct sc_adb_tunnel *tunnel = &server->tunnel;
|
|
|
|
assert(tunnel->enabled);
|
|
|
|
const char *serial = server->params.serial;
|
|
bool control = server->params.control;
|
|
|
|
sc_socket video_socket = SC_SOCKET_NONE;
|
|
sc_socket control_socket = SC_SOCKET_NONE;
|
|
if (!tunnel->forward) {
|
|
video_socket = net_accept_intr(&server->intr, tunnel->server_socket);
|
|
if (video_socket == SC_SOCKET_NONE) {
|
|
goto fail;
|
|
}
|
|
|
|
if (control) {
|
|
control_socket =
|
|
net_accept_intr(&server->intr, tunnel->server_socket);
|
|
if (control_socket == SC_SOCKET_NONE) {
|
|
goto fail;
|
|
}
|
|
}
|
|
} else {
|
|
uint32_t tunnel_host = server->params.tunnel_host;
|
|
if (!tunnel_host) {
|
|
tunnel_host = IPV4_LOCALHOST;
|
|
}
|
|
|
|
uint16_t tunnel_port = server->params.tunnel_port;
|
|
if (!tunnel_port) {
|
|
tunnel_port = tunnel->local_port;
|
|
}
|
|
|
|
unsigned attempts = 100;
|
|
sc_tick delay = SC_TICK_FROM_MS(100);
|
|
video_socket = connect_to_server(server, attempts, delay, tunnel_host,
|
|
tunnel_port);
|
|
if (video_socket == SC_SOCKET_NONE) {
|
|
goto fail;
|
|
}
|
|
|
|
if (control) {
|
|
// we know that the device is listening, we don't need several
|
|
// attempts
|
|
control_socket = net_socket();
|
|
if (control_socket == SC_SOCKET_NONE) {
|
|
goto fail;
|
|
}
|
|
bool ok = net_connect_intr(&server->intr, control_socket,
|
|
tunnel_host, tunnel_port);
|
|
if (!ok) {
|
|
goto fail;
|
|
}
|
|
}
|
|
}
|
|
|
|
// we don't need the adb tunnel anymore
|
|
sc_adb_tunnel_close(tunnel, &server->intr, serial);
|
|
|
|
// The sockets will be closed on stop if device_read_info() fails
|
|
bool ok = device_read_info(&server->intr, video_socket, info);
|
|
if (!ok) {
|
|
goto fail;
|
|
}
|
|
|
|
assert(video_socket != SC_SOCKET_NONE);
|
|
assert(!control || control_socket != SC_SOCKET_NONE);
|
|
|
|
server->video_socket = video_socket;
|
|
server->control_socket = control_socket;
|
|
|
|
return true;
|
|
|
|
fail:
|
|
if (video_socket != SC_SOCKET_NONE) {
|
|
if (!net_close(video_socket)) {
|
|
LOGW("Could not close video socket");
|
|
}
|
|
}
|
|
|
|
if (control_socket != SC_SOCKET_NONE) {
|
|
if (!net_close(control_socket)) {
|
|
LOGW("Could not close control socket");
|
|
}
|
|
}
|
|
|
|
// Always leave this function with tunnel disabled
|
|
sc_adb_tunnel_close(tunnel, &server->intr, serial);
|
|
|
|
return false;
|
|
}
|
|
|
|
static void
|
|
sc_server_on_terminated(void *userdata) {
|
|
struct sc_server *server = userdata;
|
|
|
|
// If the server process dies before connecting to the server socket,
|
|
// then the client will be stuck forever on accept(). To avoid the problem,
|
|
// wake up the accept() call (or any other) when the server dies, like on
|
|
// stop() (it is safe to call interrupt() twice).
|
|
sc_intr_interrupt(&server->intr);
|
|
|
|
server->cbs->on_disconnected(server, server->cbs_userdata);
|
|
|
|
LOGD("Server terminated");
|
|
}
|
|
|
|
static bool
|
|
sc_server_fill_serial(struct sc_server *server) {
|
|
// Retrieve the actual device immediately if not provided, so that all
|
|
// future adb commands are executed for this specific device, even if other
|
|
// devices are connected afterwards (without "more than one
|
|
// device/emulator" error)
|
|
if (!server->params.serial) {
|
|
// The serial is owned by sc_server_params, and will be freed on destroy
|
|
server->params.serial = adb_get_serialno(&server->intr, 0);
|
|
if (!server->params.serial) {
|
|
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;
|
|
}
|
|
|
|
bool ok = push_server(&server->intr, params->serial);
|
|
if (!ok) {
|
|
goto error_connection_failed;
|
|
}
|
|
|
|
ok = sc_adb_tunnel_open(&server->tunnel, &server->intr, params->serial,
|
|
params->port_range, params->force_adb_forward);
|
|
if (!ok) {
|
|
goto error_connection_failed;
|
|
}
|
|
|
|
// server will connect to our server socket
|
|
sc_pid pid = execute_server(server, params);
|
|
if (pid == SC_PROCESS_NONE) {
|
|
sc_adb_tunnel_close(&server->tunnel, &server->intr, params->serial);
|
|
goto error_connection_failed;
|
|
}
|
|
|
|
static const struct sc_process_listener listener = {
|
|
.on_terminated = sc_server_on_terminated,
|
|
};
|
|
struct sc_process_observer observer;
|
|
ok = sc_process_observer_init(&observer, pid, &listener, server);
|
|
if (!ok) {
|
|
sc_process_terminate(pid);
|
|
sc_process_wait(pid, true); // ignore exit code
|
|
sc_adb_tunnel_close(&server->tunnel, &server->intr, params->serial);
|
|
goto error_connection_failed;
|
|
}
|
|
|
|
ok = sc_server_connect_to(server, &server->info);
|
|
// The tunnel is always closed by server_connect_to()
|
|
if (!ok) {
|
|
sc_process_terminate(pid);
|
|
sc_process_wait(pid, true); // ignore exit code
|
|
sc_process_observer_join(&observer);
|
|
sc_process_observer_destroy(&observer);
|
|
goto error_connection_failed;
|
|
}
|
|
|
|
// Now connected
|
|
server->cbs->on_connected(server, server->cbs_userdata);
|
|
|
|
// Wait for server_stop()
|
|
sc_mutex_lock(&server->mutex);
|
|
while (!server->stopped) {
|
|
sc_cond_wait(&server->cond_stopped, &server->mutex);
|
|
}
|
|
sc_mutex_unlock(&server->mutex);
|
|
|
|
// Interrupt sockets to wake up socket blocking calls on the server
|
|
assert(server->video_socket != SC_SOCKET_NONE);
|
|
net_interrupt(server->video_socket);
|
|
|
|
if (server->control_socket != SC_SOCKET_NONE) {
|
|
// There is no control_socket if --no-control is set
|
|
net_interrupt(server->control_socket);
|
|
}
|
|
|
|
// Give some delay for the server to terminate properly
|
|
#define WATCHDOG_DELAY SC_TICK_FROM_SEC(1)
|
|
sc_tick deadline = sc_tick_now() + WATCHDOG_DELAY;
|
|
bool terminated = sc_process_observer_timedwait(&observer, deadline);
|
|
|
|
// 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 (!terminated) {
|
|
// The process may have terminated since the check, but it is not
|
|
// reaped (closed) yet, so its PID is still valid, and it is ok to call
|
|
// sc_process_terminate() even in that case.
|
|
LOGW("Killing the server...");
|
|
sc_process_terminate(pid);
|
|
}
|
|
|
|
sc_process_observer_join(&observer);
|
|
sc_process_observer_destroy(&observer);
|
|
|
|
sc_process_close(pid);
|
|
|
|
return 0;
|
|
|
|
error_connection_failed:
|
|
server->cbs->on_connection_failed(server, server->cbs_userdata);
|
|
return -1;
|
|
}
|
|
|
|
bool
|
|
sc_server_start(struct sc_server *server) {
|
|
bool ok =
|
|
sc_thread_create(&server->thread, run_server, "scrcpy-server", server);
|
|
if (!ok) {
|
|
LOGE("Could not create server thread");
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void
|
|
sc_server_stop(struct sc_server *server) {
|
|
sc_mutex_lock(&server->mutex);
|
|
server->stopped = true;
|
|
sc_cond_signal(&server->cond_stopped);
|
|
sc_intr_interrupt(&server->intr);
|
|
sc_mutex_unlock(&server->mutex);
|
|
|
|
sc_thread_join(&server->thread, NULL);
|
|
}
|
|
|
|
void
|
|
sc_server_destroy(struct sc_server *server) {
|
|
if (server->video_socket != SC_SOCKET_NONE) {
|
|
net_close(server->video_socket);
|
|
}
|
|
if (server->control_socket != SC_SOCKET_NONE) {
|
|
net_close(server->control_socket);
|
|
}
|
|
|
|
sc_server_params_destroy(&server->params);
|
|
sc_intr_destroy(&server->intr);
|
|
sc_cond_destroy(&server->cond_stopped);
|
|
sc_mutex_destroy(&server->mutex);
|
|
}
|