Run the server from a dedicated thread
Define server callbacks, start the server asynchronously and listen to connection events to initialize scrcpy properly. It will help to simplify the server code, and allows to run the UI event loop while the server is connecting. In particular, this will allow to receive SIGINT/Ctrl+C events during connection to interrupt immediately.
This commit is contained in:
parent
5b9c88693e
commit
0426708544
4 changed files with 181 additions and 78 deletions
|
@ -1,2 +1,4 @@
|
||||||
#define EVENT_NEW_FRAME SDL_USEREVENT
|
#define EVENT_NEW_FRAME SDL_USEREVENT
|
||||||
#define EVENT_STREAM_STOPPED (SDL_USEREVENT + 1)
|
#define EVENT_STREAM_STOPPED (SDL_USEREVENT + 1)
|
||||||
|
#define EVENT_SERVER_CONNECTION_FAILED (SDL_USEREVENT + 2)
|
||||||
|
#define EVENT_SERVER_CONNECTED (SDL_USEREVENT + 3)
|
||||||
|
|
|
@ -217,6 +217,29 @@ event_loop(struct scrcpy *s, const struct scrcpy_options *options) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool
|
||||||
|
await_for_server(void) {
|
||||||
|
SDL_Event event;
|
||||||
|
while (SDL_WaitEvent(&event)) {
|
||||||
|
switch (event.type) {
|
||||||
|
case SDL_QUIT:
|
||||||
|
LOGD("User requested to quit");
|
||||||
|
return false;
|
||||||
|
case EVENT_SERVER_CONNECTION_FAILED:
|
||||||
|
LOGE("Server connection failed");
|
||||||
|
return false;
|
||||||
|
case EVENT_SERVER_CONNECTED:
|
||||||
|
LOGD("Server connected");
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
LOGE("SDL_WaitEvent() error: %s", SDL_GetError());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
static SDL_LogPriority
|
static SDL_LogPriority
|
||||||
sdl_priority_from_av_level(int level) {
|
sdl_priority_from_av_level(int level) {
|
||||||
switch (level) {
|
switch (level) {
|
||||||
|
@ -262,6 +285,32 @@ stream_on_eos(struct stream *stream, void *userdata) {
|
||||||
PUSH_EVENT(EVENT_STREAM_STOPPED);
|
PUSH_EVENT(EVENT_STREAM_STOPPED);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
server_on_connection_failed(struct server *server, void *userdata) {
|
||||||
|
(void) server;
|
||||||
|
(void) userdata;
|
||||||
|
|
||||||
|
PUSH_EVENT(EVENT_SERVER_CONNECTION_FAILED);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
server_on_connected(struct server *server, void *userdata) {
|
||||||
|
(void) server;
|
||||||
|
(void) userdata;
|
||||||
|
|
||||||
|
PUSH_EVENT(EVENT_SERVER_CONNECTED);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
server_on_disconnected(struct server *server, void *userdata) {
|
||||||
|
(void) server;
|
||||||
|
(void) userdata;
|
||||||
|
|
||||||
|
LOGD("Server disconnected");
|
||||||
|
// Do nothing, the disconnection will be handled by the "stream stopped"
|
||||||
|
// event
|
||||||
|
}
|
||||||
|
|
||||||
bool
|
bool
|
||||||
scrcpy(struct scrcpy_options *options) {
|
scrcpy(struct scrcpy_options *options) {
|
||||||
static struct scrcpy scrcpy;
|
static struct scrcpy scrcpy;
|
||||||
|
@ -310,7 +359,12 @@ scrcpy(struct scrcpy_options *options) {
|
||||||
.power_off_on_close = options->power_off_on_close,
|
.power_off_on_close = options->power_off_on_close,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!server_init(&s->server, ¶ms)) {
|
static const struct server_callbacks cbs = {
|
||||||
|
.on_connection_failed = server_on_connection_failed,
|
||||||
|
.on_connected = server_on_connected,
|
||||||
|
.on_disconnected = server_on_disconnected,
|
||||||
|
};
|
||||||
|
if (!server_init(&s->server, ¶ms, &cbs, NULL)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -332,12 +386,14 @@ scrcpy(struct scrcpy_options *options) {
|
||||||
|
|
||||||
sdl_configure(options->display, options->disable_screensaver);
|
sdl_configure(options->display, options->disable_screensaver);
|
||||||
|
|
||||||
struct server_info info;
|
// Await for server without blocking Ctrl+C handling
|
||||||
|
if (!await_for_server()) {
|
||||||
if (!server_connect_to(&s->server, &info)) {
|
|
||||||
goto end;
|
goto end;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// It is necessarily initialized here, since the device is connected
|
||||||
|
struct server_info *info = &s->server.info;
|
||||||
|
|
||||||
if (options->display && options->control) {
|
if (options->display && options->control) {
|
||||||
if (!file_handler_init(&s->file_handler, options->serial,
|
if (!file_handler_init(&s->file_handler, options->serial,
|
||||||
options->push_target)) {
|
options->push_target)) {
|
||||||
|
@ -361,7 +417,7 @@ scrcpy(struct scrcpy_options *options) {
|
||||||
if (!recorder_init(&s->recorder,
|
if (!recorder_init(&s->recorder,
|
||||||
options->record_filename,
|
options->record_filename,
|
||||||
options->record_format,
|
options->record_format,
|
||||||
info.frame_size)) {
|
info->frame_size)) {
|
||||||
goto end;
|
goto end;
|
||||||
}
|
}
|
||||||
rec = &s->recorder;
|
rec = &s->recorder;
|
||||||
|
@ -407,11 +463,11 @@ scrcpy(struct scrcpy_options *options) {
|
||||||
|
|
||||||
if (options->display) {
|
if (options->display) {
|
||||||
const char *window_title =
|
const char *window_title =
|
||||||
options->window_title ? options->window_title : info.device_name;
|
options->window_title ? options->window_title : info->device_name;
|
||||||
|
|
||||||
struct screen_params screen_params = {
|
struct screen_params screen_params = {
|
||||||
.window_title = window_title,
|
.window_title = window_title,
|
||||||
.frame_size = info.frame_size,
|
.frame_size = info->frame_size,
|
||||||
.always_on_top = options->always_on_top,
|
.always_on_top = options->always_on_top,
|
||||||
.window_x = options->window_x,
|
.window_x = options->window_x,
|
||||||
.window_y = options->window_y,
|
.window_y = options->window_y,
|
||||||
|
@ -435,7 +491,7 @@ scrcpy(struct scrcpy_options *options) {
|
||||||
#ifdef HAVE_V4L2
|
#ifdef HAVE_V4L2
|
||||||
if (options->v4l2_device) {
|
if (options->v4l2_device) {
|
||||||
if (!sc_v4l2_sink_init(&s->v4l2_sink, options->v4l2_device,
|
if (!sc_v4l2_sink_init(&s->v4l2_sink, options->v4l2_device,
|
||||||
info.frame_size, options->v4l2_buffer)) {
|
info->frame_size, options->v4l2_buffer)) {
|
||||||
goto end;
|
goto end;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
144
app/src/server.c
144
app/src/server.c
|
@ -402,7 +402,8 @@ connect_to_server(struct server *server, uint32_t attempts, sc_tick delay) {
|
||||||
}
|
}
|
||||||
|
|
||||||
bool
|
bool
|
||||||
server_init(struct server *server, const struct server_params *params) {
|
server_init(struct server *server, const struct server_params *params,
|
||||||
|
const struct server_callbacks *cbs, void *cbs_userdata) {
|
||||||
bool ok = server_params_copy(&server->params, params);
|
bool ok = server_params_copy(&server->params, params);
|
||||||
if (!ok) {
|
if (!ok) {
|
||||||
LOGE("Could not copy server params");
|
LOGE("Could not copy server params");
|
||||||
|
@ -424,7 +425,6 @@ server_init(struct server *server, const struct server_params *params) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
server->process = SC_PROCESS_NONE;
|
|
||||||
server->stopped = false;
|
server->stopped = false;
|
||||||
|
|
||||||
server->server_socket = SC_INVALID_SOCKET;
|
server->server_socket = SC_INVALID_SOCKET;
|
||||||
|
@ -436,6 +436,14 @@ server_init(struct server *server, const struct server_params *params) {
|
||||||
server->tunnel_enabled = false;
|
server->tunnel_enabled = false;
|
||||||
server->tunnel_forward = false;
|
server->tunnel_forward = false;
|
||||||
|
|
||||||
|
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;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -458,7 +466,7 @@ device_read_info(sc_socket device_socket, struct server_info *info) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool
|
static bool
|
||||||
server_connect_to(struct server *server, struct server_info *info) {
|
server_connect_to(struct server *server, struct server_info *info) {
|
||||||
assert(server->tunnel_enabled);
|
assert(server->tunnel_enabled);
|
||||||
|
|
||||||
|
@ -531,6 +539,9 @@ fail:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Always leave this function with tunnel disabled
|
||||||
|
disable_tunnel(server);
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -547,57 +558,68 @@ server_on_terminated(void *userdata) {
|
||||||
net_interrupt(server->server_socket);
|
net_interrupt(server->server_socket);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
server->cbs->on_disconnected(server, server->cbs_userdata);
|
||||||
|
|
||||||
LOGD("Server terminated");
|
LOGD("Server terminated");
|
||||||
}
|
}
|
||||||
|
|
||||||
bool
|
static int
|
||||||
server_start(struct server *server) {
|
run_server(void *data) {
|
||||||
|
struct server *server = data;
|
||||||
|
|
||||||
const struct server_params *params = &server->params;
|
const struct server_params *params = &server->params;
|
||||||
|
|
||||||
if (!push_server(params->serial)) {
|
bool ok = push_server(params->serial);
|
||||||
/* server->serial will be freed on server_destroy() */
|
if (!ok) {
|
||||||
return false;
|
goto error_connection_failed;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!enable_tunnel_any_port(server, params->port_range,
|
ok = enable_tunnel_any_port(server, params->port_range,
|
||||||
params->force_adb_forward)) {
|
params->force_adb_forward);
|
||||||
return false;
|
if (!ok) {
|
||||||
|
goto error_connection_failed;
|
||||||
}
|
}
|
||||||
|
|
||||||
// server will connect to our server socket
|
// server will connect to our server socket
|
||||||
server->process = execute_server(server, params);
|
sc_pid pid = execute_server(server, params);
|
||||||
if (server->process == SC_PROCESS_NONE) {
|
if (pid == SC_PROCESS_NONE) {
|
||||||
goto error;
|
disable_tunnel(server);
|
||||||
|
goto error_connection_failed;
|
||||||
}
|
}
|
||||||
|
|
||||||
static const struct sc_process_listener listener = {
|
static const struct sc_process_listener listener = {
|
||||||
.on_terminated = server_on_terminated,
|
.on_terminated = server_on_terminated,
|
||||||
};
|
};
|
||||||
bool ok = sc_process_observer_init(&server->observer, server->process,
|
struct sc_process_observer observer;
|
||||||
&listener, server);
|
ok = sc_process_observer_init(&observer, pid, &listener, server);
|
||||||
if (!ok) {
|
if (!ok) {
|
||||||
sc_process_terminate(server->process);
|
sc_process_terminate(pid);
|
||||||
sc_process_wait(server->process, true); // ignore exit code
|
sc_process_wait(pid, true); // ignore exit code
|
||||||
goto error;
|
disable_tunnel(server);
|
||||||
|
goto error_connection_failed;
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
ok = 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;
|
||||||
|
}
|
||||||
|
|
||||||
error:
|
// Now connected
|
||||||
// The server socket (if any) will be closed on server_destroy()
|
server->cbs->on_connected(server, server->cbs_userdata);
|
||||||
|
|
||||||
disable_tunnel(server);
|
// Wait for server_stop()
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
server_stop(struct server *server) {
|
|
||||||
sc_mutex_lock(&server->mutex);
|
sc_mutex_lock(&server->mutex);
|
||||||
server->stopped = true;
|
while (!server->stopped) {
|
||||||
sc_cond_signal(&server->cond_stopped);
|
sc_cond_wait(&server->cond_stopped, &server->mutex);
|
||||||
|
}
|
||||||
sc_mutex_unlock(&server->mutex);
|
sc_mutex_unlock(&server->mutex);
|
||||||
|
|
||||||
|
// Server stop has been requested
|
||||||
if (server->server_socket != SC_INVALID_SOCKET) {
|
if (server->server_socket != SC_INVALID_SOCKET) {
|
||||||
if (!net_interrupt(server->server_socket)) {
|
if (!net_interrupt(server->server_socket)) {
|
||||||
LOGW("Could not interrupt server socket");
|
LOGW("Could not interrupt server socket");
|
||||||
|
@ -614,18 +636,10 @@ server_stop(struct server *server) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
assert(server->process != SC_PROCESS_NONE);
|
|
||||||
|
|
||||||
if (server->tunnel_enabled) {
|
|
||||||
// ignore failure
|
|
||||||
disable_tunnel(server);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Give some delay for the server to terminate properly
|
// Give some delay for the server to terminate properly
|
||||||
#define WATCHDOG_DELAY SC_TICK_FROM_SEC(1)
|
#define WATCHDOG_DELAY SC_TICK_FROM_SEC(1)
|
||||||
sc_tick deadline = sc_tick_now() + WATCHDOG_DELAY;
|
sc_tick deadline = sc_tick_now() + WATCHDOG_DELAY;
|
||||||
bool terminated =
|
bool terminated = sc_process_observer_timedwait(&observer, deadline);
|
||||||
sc_process_observer_timedwait(&server->observer, deadline);
|
|
||||||
|
|
||||||
// After this delay, kill the server if it's not dead already.
|
// 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
|
// On some devices, closing the sockets is not sufficient to wake up the
|
||||||
|
@ -635,32 +649,44 @@ server_stop(struct server *server) {
|
||||||
// reaped (closed) yet, so its PID is still valid, and it is ok to call
|
// reaped (closed) yet, so its PID is still valid, and it is ok to call
|
||||||
// sc_process_terminate() even in that case.
|
// sc_process_terminate() even in that case.
|
||||||
LOGW("Killing the server...");
|
LOGW("Killing the server...");
|
||||||
sc_process_terminate(server->process);
|
sc_process_terminate(pid);
|
||||||
}
|
}
|
||||||
|
|
||||||
sc_process_observer_join(&server->observer);
|
sc_process_observer_join(&observer);
|
||||||
sc_process_observer_destroy(&server->observer);
|
sc_process_observer_destroy(&observer);
|
||||||
|
|
||||||
sc_process_close(server->process);
|
sc_process_close(pid);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
error_connection_failed:
|
||||||
|
server->cbs->on_connection_failed(server, server->cbs_userdata);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
server_start(struct server *server) {
|
||||||
|
bool ok = sc_thread_create(&server->thread, run_server, "server", server);
|
||||||
|
if (!ok) {
|
||||||
|
LOGE("Could not create server thread");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
server_stop(struct server *server) {
|
||||||
|
sc_mutex_lock(&server->mutex);
|
||||||
|
server->stopped = true;
|
||||||
|
sc_cond_signal(&server->cond_stopped);
|
||||||
|
sc_mutex_unlock(&server->mutex);
|
||||||
|
|
||||||
|
sc_thread_join(&server->thread, NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
server_destroy(struct server *server) {
|
server_destroy(struct server *server) {
|
||||||
if (server->server_socket != SC_INVALID_SOCKET) {
|
|
||||||
if (!net_close(server->server_socket)) {
|
|
||||||
LOGW("Could not close server socket");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (server->video_socket != SC_INVALID_SOCKET) {
|
|
||||||
if (!net_close(server->video_socket)) {
|
|
||||||
LOGW("Could not close video socket");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (server->control_socket != SC_INVALID_SOCKET) {
|
|
||||||
if (!net_close(server->control_socket)) {
|
|
||||||
LOGW("Could not close control socket");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
server_params_destroy(&server->params);
|
server_params_destroy(&server->params);
|
||||||
sc_cond_destroy(&server->cond_stopped);
|
sc_cond_destroy(&server->cond_stopped);
|
||||||
sc_mutex_destroy(&server->mutex);
|
sc_mutex_destroy(&server->mutex);
|
||||||
|
|
|
@ -43,9 +43,8 @@ struct server {
|
||||||
// The internal allocated strings are copies owned by the server
|
// The internal allocated strings are copies owned by the server
|
||||||
struct server_params params;
|
struct server_params params;
|
||||||
|
|
||||||
sc_pid process;
|
sc_thread thread;
|
||||||
// alive only between start() and stop()
|
struct server_info info; // initialized once connected
|
||||||
struct sc_process_observer observer;
|
|
||||||
|
|
||||||
sc_mutex mutex;
|
sc_mutex mutex;
|
||||||
sc_cond cond_stopped;
|
sc_cond cond_stopped;
|
||||||
|
@ -57,20 +56,40 @@ struct server {
|
||||||
uint16_t local_port; // selected from port_range
|
uint16_t local_port; // selected from port_range
|
||||||
bool tunnel_enabled;
|
bool tunnel_enabled;
|
||||||
bool tunnel_forward; // use "adb forward" instead of "adb reverse"
|
bool tunnel_forward; // use "adb forward" instead of "adb reverse"
|
||||||
|
|
||||||
|
const struct server_callbacks *cbs;
|
||||||
|
void *cbs_userdata;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct server_callbacks {
|
||||||
|
/**
|
||||||
|
* Called when the server failed to connect
|
||||||
|
*
|
||||||
|
* If it is called, then on_connected() and on_disconnected() will never be
|
||||||
|
* called.
|
||||||
|
*/
|
||||||
|
void (*on_connection_failed)(struct server *server, void *userdata);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called on server connection
|
||||||
|
*/
|
||||||
|
void (*on_connected)(struct server *server, void *userdata);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called on server disconnection (after it has been connected)
|
||||||
|
*/
|
||||||
|
void (*on_disconnected)(struct server *server, void *userdata);
|
||||||
};
|
};
|
||||||
|
|
||||||
// init the server with the given params
|
// init the server with the given params
|
||||||
bool
|
bool
|
||||||
server_init(struct server *server, const struct server_params *params);
|
server_init(struct server *server, const struct server_params *params,
|
||||||
|
const struct server_callbacks *cbs, void *cbs_userdata);
|
||||||
|
|
||||||
// push, enable tunnel et start the server
|
// start the server asynchronously
|
||||||
bool
|
bool
|
||||||
server_start(struct server *server);
|
server_start(struct server *server);
|
||||||
|
|
||||||
// block until the communication with the server is established
|
|
||||||
bool
|
|
||||||
server_connect_to(struct server *server, struct server_info *info);
|
|
||||||
|
|
||||||
// disconnect and kill the server process
|
// disconnect and kill the server process
|
||||||
void
|
void
|
||||||
server_stop(struct server *server);
|
server_stop(struct server *server);
|
||||||
|
|
Loading…
Reference in a new issue