diff --git a/app/src/command.c b/app/src/command.c index cd8b0a70..a2769832 100644 --- a/app/src/command.c +++ b/app/src/command.c @@ -3,6 +3,7 @@ #include #include #include +#include #define ARRAY_LEN(a) (sizeof(a) / sizeof(a[0])) @@ -63,3 +64,20 @@ process_t adb_push(const char *serial, const char *local, const char *remote) { const char *const adb_cmd[] = {"push", (char *) local, (char *) remote}; return adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd)); } + +SDL_bool process_check_success(process_t proc, const char *name) { + if (proc == PROCESS_NONE) { + SDL_LogError(SDL_LOG_CATEGORY_SYSTEM, "Could not execute \"%s\"", name); + return SDL_FALSE; + } + exit_code_t exit_code; + if (!cmd_simple_wait(proc, &exit_code)) { + if (exit_code != NO_EXIT_CODE) { + SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "\"%s\" returned with value %" PRIexitcode, name, exit_code); + } else { + SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "\"%s\" exited unexpectedly", name); + } + return SDL_FALSE; + } + return SDL_TRUE; +} diff --git a/app/src/command.h b/app/src/command.h index 54fe7aca..adaad836 100644 --- a/app/src/command.h +++ b/app/src/command.h @@ -42,4 +42,8 @@ process_t adb_reverse(const char *serial, const char *device_socket_name, uint16 process_t adb_reverse_remove(const char *serial, const char *device_socket_name); process_t adb_push(const char *serial, const char *local, const char *remote); +// convenience function to wait for a successful process execution +// automatically log process errors with the provided process name +SDL_bool process_check_success(process_t process, const char *name); + #endif diff --git a/app/src/netutil.c b/app/src/netutil.c index 8558edbb..b6d2fdf8 100644 --- a/app/src/netutil.c +++ b/app/src/netutil.c @@ -4,7 +4,7 @@ // contrary to SDLNet_TCP_Send and SDLNet_TCP_Recv, SDLNet_TCP_Accept is non-blocking // so we need to block before calling it -TCPsocket blocking_accept(TCPsocket server_socket) { +TCPsocket server_socket_accept(TCPsocket server_socket) { SDLNet_SocketSet set = SDLNet_AllocSocketSet(1); if (!set) { SDL_LogCritical(SDL_LOG_CATEGORY_SYSTEM, "Could not allocate socket set"); @@ -19,7 +19,7 @@ TCPsocket blocking_accept(TCPsocket server_socket) { // timeout is (2^32-1) milliseconds, this should be sufficient if (SDLNet_CheckSockets(set, -1) != 1) { - SDL_LogError(SDL_LOG_CATEGORY_SYSTEM, "Could not check socket: %s", SDL_GetError()); + SDL_LogError(SDL_LOG_CATEGORY_SYSTEM, "Could not check socket"); SDLNet_FreeSocketSet(set); return NULL; } diff --git a/app/src/netutil.h b/app/src/netutil.h index 3c2659ba..aa903cdd 100644 --- a/app/src/netutil.h +++ b/app/src/netutil.h @@ -3,6 +3,7 @@ #include -TCPsocket blocking_accept(TCPsocket server_socket); +// blocking accept on the server socket +TCPsocket server_socket_accept(TCPsocket server_socket); #endif diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index f45e7546..a791903c 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -23,6 +23,7 @@ #define DEVICE_NAME_FIELD_LENGTH 64 +static struct server server = SERVER_INITIALIZER; static struct screen screen = SCREEN_INITIALIZER; static struct frames frames; static struct decoder decoder; @@ -46,14 +47,6 @@ static void count_frame(void) { } } -static TCPsocket listen_on_port(Uint16 port) { - IPaddress addr = { - .host = INADDR_ANY, - .port = SDL_SwapBE16(port), - }; - return SDLNet_TCP_Open(&addr); -} - // name must be at least DEVICE_NAME_FIELD_LENGTH bytes static SDL_bool read_initial_device_info(TCPsocket socket, char *device_name, struct size *size) { unsigned char buf[DEVICE_NAME_FIELD_LENGTH + 4]; @@ -80,23 +73,6 @@ static struct point get_mouse_point(void) { }; } -static int wait_for_success(process_t proc, const char *name) { - if (proc == PROCESS_NONE) { - SDL_LogError(SDL_LOG_CATEGORY_SYSTEM, "Could not execute \"%s\"", name); - return -1; - } - exit_code_t exit_code; - if (!cmd_simple_wait(proc, &exit_code)) { - if (exit_code != NO_EXIT_CODE) { - SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "\"%s\" returned with value %" PRIexitcode, name, exit_code); - } else { - SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "\"%s\" exited unexpectedly", name); - } - return -1; - } - return 0; -} - static void send_keycode(enum android_keycode keycode, const char *name) { // send DOWN event struct control_event control_event = { @@ -344,43 +320,17 @@ static void event_loop(void) { } SDL_bool scrcpy(const char *serial, Uint16 local_port, Uint16 max_size, Uint32 bit_rate) { - SDL_bool ret = SDL_TRUE; - - process_t push_proc = push_server(serial); - if (wait_for_success(push_proc, "adb push")) { + if (!server_start(&server, serial, local_port, max_size, bit_rate)) { return SDL_FALSE; } - process_t reverse_tunnel_proc = enable_tunnel(serial, local_port); - if (wait_for_success(reverse_tunnel_proc, "adb reverse")) { - return SDL_FALSE; - } - - TCPsocket server_socket = listen_on_port(local_port); - if (!server_socket) { - SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Could not open video socket"); - goto finally_adb_reverse_remove; - } - - // server will connect to our socket - process_t server = start_server(serial, max_size, bit_rate); - if (server == PROCESS_NONE) { - ret = SDL_FALSE; - SDLNet_TCP_Close(server_socket); - goto finally_adb_reverse_remove; - } - // to reduce startup time, we could be tempted to init other stuff before blocking here // but we should not block after SDL_Init since it handles the signals (Ctrl+C) in its // event loop: blocking could lead to deadlock - TCPsocket device_socket = blocking_accept(server_socket); - // we don't need the server socket anymore - SDLNet_TCP_Close(server_socket); + TCPsocket device_socket = server_connect_to(&server); if (!device_socket) { - SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Could not accept video socket: %s", SDL_GetError()); - ret = SDL_FALSE; - stop_server(server); - goto finally_adb_reverse_remove; + server_stop(&server, serial); + return SDL_FALSE; } char device_name[DEVICE_NAME_FIELD_LENGTH]; @@ -391,19 +341,17 @@ SDL_bool scrcpy(const char *serial, Uint16 local_port, Uint16 max_size, Uint32 b // to init the window immediately if (!read_initial_device_info(device_socket, device_name, &frame_size)) { SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Could not retrieve initial screen size"); - ret = SDL_FALSE; - SDLNet_TCP_Close(device_socket); - stop_server(server); - goto finally_adb_reverse_remove; + server_stop(&server, serial); + return SDL_FALSE; } if (!frames_init(&frames)) { - ret = SDL_FALSE; - SDLNet_TCP_Close(device_socket); - stop_server(server); - goto finally_adb_reverse_remove; + server_stop(&server, serial); + return SDL_FALSE; } + SDL_bool ret = SDL_TRUE; + decoder.frames = &frames; decoder.video_socket = device_socket; @@ -411,22 +359,17 @@ SDL_bool scrcpy(const char *serial, Uint16 local_port, Uint16 max_size, Uint32 b // start the decoder if (!decoder_start(&decoder)) { ret = SDL_FALSE; - SDLNet_TCP_Close(device_socket); - stop_server(server); + server_stop(&server, serial); goto finally_destroy_frames; } if (!controller_init(&controller, device_socket)) { ret = SDL_FALSE; - SDLNet_TCP_Close(device_socket); - stop_server(server); goto finally_stop_decoder; } if (!controller_start(&controller)) { ret = SDL_FALSE; - SDLNet_TCP_Close(device_socket); - stop_server(server); goto finally_destroy_controller; } @@ -450,28 +393,11 @@ finally_stop_and_join_controller: finally_destroy_controller: controller_destroy(&controller); finally_stop_decoder: - SDLNet_TCP_Close(device_socket); - - // let the server some time to print any exception trace before killing it - struct timespec timespec = { - .tv_sec = 0, - .tv_nsec = 100000000, // 100ms - }; - nanosleep(×pec, NULL); // ignore error - // kill the server before decoder_join() to wake up the decoder - stop_server(server); + server_stop(&server, serial); decoder_join(&decoder); finally_destroy_frames: frames_destroy(&frames); -finally_adb_reverse_remove: - { - process_t remove = disable_tunnel(serial); - if (remove != PROCESS_NONE) { - // ignore failure - cmd_simple_wait(remove, NULL); - } - } return ret; } diff --git a/app/src/server.c b/app/src/server.c index 2c00c45e..ff0e4663 100644 --- a/app/src/server.c +++ b/app/src/server.c @@ -1,28 +1,34 @@ -#include "command.h" +#include "server.h" #include +#include #include #include +#include +#include "netutil.h" #define SOCKET_NAME "scrcpy" -process_t push_server(const char *serial) { +static SDL_bool push_server(const char *serial) { const char *server_path = getenv("SCRCPY_SERVER_JAR"); if (!server_path) { server_path = "scrcpy-server.jar"; } - return adb_push(serial, server_path, "/data/local/tmp/scrcpy-server.jar"); + process_t process = adb_push(serial, server_path, "/data/local/tmp/scrcpy-server.jar"); + return process_check_success(process, "adb push"); } -process_t enable_tunnel(const char *serial, Uint16 local_port) { - return adb_reverse(serial, SOCKET_NAME, local_port); +static SDL_bool enable_tunnel(const char *serial, Uint16 local_port) { + process_t process = adb_reverse(serial, SOCKET_NAME, local_port); + return process_check_success(process, "adb reverse"); } -process_t disable_tunnel(const char *serial) { - return adb_reverse_remove(serial, SOCKET_NAME); +static SDL_bool disable_tunnel(const char *serial) { + process_t process = adb_reverse_remove(serial, SOCKET_NAME); + return process_check_success(process, "adb reverse --remove"); } -process_t start_server(const char *serial, Uint16 max_size, Uint32 bit_rate) { +static process_t execute_server(const char *serial, Uint16 max_size, Uint32 bit_rate) { char max_size_string[6]; char bit_rate_string[11]; sprintf(max_size_string, "%"PRIu16, max_size); @@ -39,8 +45,89 @@ process_t start_server(const char *serial, Uint16 max_size, Uint32 bit_rate) { return adb_execute(serial, cmd, sizeof(cmd) / sizeof(cmd[0])); } -void stop_server(process_t server) { +static void terminate_server(process_t server) { if (!cmd_terminate(server)) { SDL_LogError(SDL_LOG_CATEGORY_SYSTEM, "Could not terminate server: %s", strerror(errno)); } } + +static TCPsocket listen_on_port(Uint16 port) { + IPaddress addr = { + .host = INADDR_ANY, + .port = SDL_SwapBE16(port), + }; + return SDLNet_TCP_Open(&addr); +} + +void server_init(struct server *server) { + *server = (struct server) SERVER_INITIALIZER; +} + +SDL_bool server_start(struct server *server, const char *serial, Uint16 local_port, + Uint16 max_size, Uint32 bit_rate) { + if (!push_server(serial)) { + return SDL_FALSE; + } + + if (!enable_tunnel(serial, local_port)) { + return SDL_FALSE; + } + + // At the application level, the device part is "the server" because it + // serves video stream and control. However, at network level, the client + // listens and the server connects to the client. That way, the client can + // listen before starting the server app, so there is no need to try to + // connect until the server socket is listening on the device. + + server->server_socket = listen_on_port(local_port); + if (!server->server_socket) { + SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Could not listen on port %" PRIu16, local_port); + disable_tunnel(serial); + return SDL_FALSE; + } + + // server will connect to our server socket + server->process = execute_server(serial, max_size, bit_rate); + if (server->process == PROCESS_NONE) { + SDLNet_TCP_Close(server->server_socket); + disable_tunnel(serial); + return SDL_FALSE; + } + + server->adb_reverse_enabled = SDL_TRUE; + + return SDL_TRUE; +} + +TCPsocket server_connect_to(struct server *server) { + SDL_assert(server->server_socket); + server->device_socket = server_socket_accept(server->server_socket); + // we don't need the server socket anymore + SDLNet_TCP_Close(server->server_socket); + server->server_socket = NULL; + return server->device_socket; +} + +void server_stop(struct server *server, const char *serial) { + SDL_assert(server->process != PROCESS_NONE); + if (server->server_socket) { + SDLNet_TCP_Close(server->server_socket); + } + if (server->device_socket) { + SDLNet_TCP_Close(server->device_socket); + } + + // let the server some time to print any exception trace before killing it + struct timespec timespec = { + .tv_sec = 0, + .tv_nsec = 100000000, // 100ms + }; + nanosleep(×pec, NULL); // ignore error + + terminate_server(server->process); + + if (server->adb_reverse_enabled) { + // ignore failure + disable_tunnel(serial); + } +} diff --git a/app/src/server.h b/app/src/server.h index adc38ab4..bec0c14d 100644 --- a/app/src/server.h +++ b/app/src/server.h @@ -1,13 +1,34 @@ #ifndef SERVER_H #define SERVER_H +#include #include "command.h" -process_t push_server(const char *serial); -process_t enable_tunnel(const char *serial, Uint16 local_port); -process_t disable_tunnel(const char *serial); +struct server { + process_t process; + TCPsocket server_socket; + TCPsocket device_socket; + SDL_bool adb_reverse_enabled; +}; -process_t start_server(const char *serial, Uint16 max_size, Uint32 bit_rate); -void stop_server(process_t server); +#define SERVER_INITIALIZER { \ + .process = PROCESS_NONE, \ + .server_socket = NULL, \ + .device_socket = NULL, \ + .adb_reverse_enabled = SDL_FALSE, \ +} + +// init default values +void server_init(struct server *server); + +// push, enable tunnel et start the server +SDL_bool server_start(struct server *server, const char *serial, Uint16 local_port, + Uint16 max_size, Uint32 bit_rate); + +// block until the communication with the server is established +TCPsocket server_connect_to(struct server *server); + +// disconnect and kill the server process +void server_stop(struct server *server, const char *serial); #endif