diff --git a/app/src/events.h b/app/src/events.h index a4d6f3df..abe1a72c 100644 --- a/app/src/events.h +++ b/app/src/events.h @@ -1,2 +1,4 @@ -#define EVENT_NEW_FRAME SDL_USEREVENT -#define EVENT_STREAM_STOPPED (SDL_USEREVENT + 1) +#define EVENT_NEW_FRAME SDL_USEREVENT +#define EVENT_STREAM_STOPPED (SDL_USEREVENT + 1) +#define EVENT_SERVER_CONNECTION_FAILED (SDL_USEREVENT + 2) +#define EVENT_SERVER_CONNECTED (SDL_USEREVENT + 3) diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index 43ed428e..38a50a0b 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -217,6 +217,29 @@ event_loop(struct scrcpy *s, const struct scrcpy_options *options) { 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 sdl_priority_from_av_level(int level) { switch (level) { @@ -262,6 +285,32 @@ stream_on_eos(struct stream *stream, void *userdata) { 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 scrcpy(struct scrcpy_options *options) { static struct scrcpy scrcpy; @@ -310,7 +359,12 @@ scrcpy(struct scrcpy_options *options) { .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; } @@ -332,12 +386,14 @@ scrcpy(struct scrcpy_options *options) { sdl_configure(options->display, options->disable_screensaver); - struct server_info info; - - if (!server_connect_to(&s->server, &info)) { + // Await for server without blocking Ctrl+C handling + if (!await_for_server()) { 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 (!file_handler_init(&s->file_handler, options->serial, options->push_target)) { @@ -361,7 +417,7 @@ scrcpy(struct scrcpy_options *options) { if (!recorder_init(&s->recorder, options->record_filename, options->record_format, - info.frame_size)) { + info->frame_size)) { goto end; } rec = &s->recorder; @@ -407,11 +463,11 @@ scrcpy(struct scrcpy_options *options) { if (options->display) { 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 = { .window_title = window_title, - .frame_size = info.frame_size, + .frame_size = info->frame_size, .always_on_top = options->always_on_top, .window_x = options->window_x, .window_y = options->window_y, @@ -435,7 +491,7 @@ scrcpy(struct scrcpy_options *options) { #ifdef HAVE_V4L2 if (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; } diff --git a/app/src/server.c b/app/src/server.c index 5ed1984c..4709059f 100644 --- a/app/src/server.c +++ b/app/src/server.c @@ -402,7 +402,8 @@ connect_to_server(struct server *server, uint32_t attempts, sc_tick delay) { } 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); if (!ok) { LOGE("Could not copy server params"); @@ -424,7 +425,6 @@ server_init(struct server *server, const struct server_params *params) { return false; } - server->process = SC_PROCESS_NONE; server->stopped = false; 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_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; } @@ -458,7 +466,7 @@ device_read_info(sc_socket device_socket, struct server_info *info) { return true; } -bool +static bool server_connect_to(struct server *server, struct server_info *info) { assert(server->tunnel_enabled); @@ -531,6 +539,9 @@ fail: } } + // Always leave this function with tunnel disabled + disable_tunnel(server); + return false; } @@ -547,57 +558,68 @@ server_on_terminated(void *userdata) { net_interrupt(server->server_socket); } + server->cbs->on_disconnected(server, server->cbs_userdata); + LOGD("Server terminated"); } -bool -server_start(struct server *server) { +static int +run_server(void *data) { + struct server *server = data; + const struct server_params *params = &server->params; - if (!push_server(params->serial)) { - /* server->serial will be freed on server_destroy() */ - return false; + bool ok = push_server(params->serial); + if (!ok) { + goto error_connection_failed; } - if (!enable_tunnel_any_port(server, params->port_range, - params->force_adb_forward)) { - return false; + ok = enable_tunnel_any_port(server, params->port_range, + params->force_adb_forward); + if (!ok) { + goto error_connection_failed; } // server will connect to our server socket - server->process = execute_server(server, params); - if (server->process == SC_PROCESS_NONE) { - goto error; + sc_pid pid = execute_server(server, params); + if (pid == SC_PROCESS_NONE) { + disable_tunnel(server); + goto error_connection_failed; } static const struct sc_process_listener listener = { .on_terminated = server_on_terminated, }; - bool ok = sc_process_observer_init(&server->observer, server->process, - &listener, server); + struct sc_process_observer observer; + ok = sc_process_observer_init(&observer, pid, &listener, server); if (!ok) { - sc_process_terminate(server->process); - sc_process_wait(server->process, true); // ignore exit code - goto error; + sc_process_terminate(pid); + sc_process_wait(pid, true); // ignore exit code + 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: - // The server socket (if any) will be closed on server_destroy() + // Now connected + server->cbs->on_connected(server, server->cbs_userdata); - disable_tunnel(server); - - return false; -} - -void -server_stop(struct server *server) { + // Wait for server_stop() sc_mutex_lock(&server->mutex); - server->stopped = true; - sc_cond_signal(&server->cond_stopped); + while (!server->stopped) { + sc_cond_wait(&server->cond_stopped, &server->mutex); + } sc_mutex_unlock(&server->mutex); + // Server stop has been requested if (server->server_socket != SC_INVALID_SOCKET) { if (!net_interrupt(server->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 #define WATCHDOG_DELAY SC_TICK_FROM_SEC(1) sc_tick deadline = sc_tick_now() + WATCHDOG_DELAY; - bool terminated = - sc_process_observer_timedwait(&server->observer, deadline); + 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 @@ -635,32 +649,44 @@ server_stop(struct server *server) { // 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(server->process); + sc_process_terminate(pid); } - sc_process_observer_join(&server->observer); - sc_process_observer_destroy(&server->observer); + sc_process_observer_join(&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 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); sc_cond_destroy(&server->cond_stopped); sc_mutex_destroy(&server->mutex); diff --git a/app/src/server.h b/app/src/server.h index 5807c322..e4666346 100644 --- a/app/src/server.h +++ b/app/src/server.h @@ -43,9 +43,8 @@ struct server { // The internal allocated strings are copies owned by the server struct server_params params; - sc_pid process; - // alive only between start() and stop() - struct sc_process_observer observer; + sc_thread thread; + struct server_info info; // initialized once connected sc_mutex mutex; sc_cond cond_stopped; @@ -57,20 +56,40 @@ struct server { uint16_t local_port; // selected from port_range bool tunnel_enabled; 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 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 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 void server_stop(struct server *server);