diff --git a/app/meson.build b/app/meson.build index a206dd58..88c26db7 100644 --- a/app/meson.build +++ b/app/meson.build @@ -9,6 +9,7 @@ src = [ 'src/fpscounter.c', 'src/frames.c', 'src/inputmanager.c', + 'src/installer.c', 'src/lockutil.c', 'src/net.c', 'src/scrcpy.c', diff --git a/app/src/command.c b/app/src/command.c index 2bbea4ba..f40ff359 100644 --- a/app/src/command.c +++ b/app/src/command.c @@ -72,6 +72,11 @@ process_t adb_push(const char *serial, const char *local, const char *remote) { return adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd)); } +process_t adb_install(const char *serial, const char *local) { + const char *const adb_cmd[] = {"install", "-r", local}; + return adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd)); +} + process_t adb_remove_path(const char *serial, const char *path) { const char *const adb_cmd[] = {"shell", "rm", path}; return adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd)); diff --git a/app/src/command.h b/app/src/command.h index 24ddd21d..4113f251 100644 --- a/app/src/command.h +++ b/app/src/command.h @@ -42,6 +42,7 @@ process_t adb_forward_remove(const char *serial, uint16_t local_port); process_t adb_reverse(const char *serial, const char *device_socket_name, uint16_t local_port); 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); +process_t adb_install(const char *serial, const char *local); process_t adb_remove_path(const char *serial, const char *path); // convenience function to wait for a successful process execution diff --git a/app/src/installer.c b/app/src/installer.c new file mode 100644 index 00000000..2bef19dd --- /dev/null +++ b/app/src/installer.c @@ -0,0 +1,178 @@ +#include "installer.h" + +#include +#include "command.h" +#include "lockutil.h" +#include "log.h" + +// NOTE(adopi) this can be more generic: +// it could be used with a command queue instead of a filename queue +// then we would have a generic invoker (useful if we want to handle more async commands) + +SDL_bool apk_queue_is_empty(const struct apk_queue *queue) { + return queue->head == queue->tail; +} + +SDL_bool apk_queue_is_full(const struct apk_queue *queue) { + return (queue->head + 1) % APK_QUEUE_SIZE == queue->tail; +} + +SDL_bool apk_queue_init(struct apk_queue *queue) { + queue->head = 0; + queue->tail = 0; + return SDL_TRUE; +} + +void apk_queue_destroy(struct apk_queue *queue) { + int i = queue->tail; + while (i != queue->head) { + SDL_free(queue->data[i]); + i = (i + 1) % APK_QUEUE_SIZE; + } +} + +SDL_bool apk_queue_push(struct apk_queue *queue, const char *apk) { + if (apk_queue_is_full(queue)) { + return SDL_FALSE; + } + queue->data[queue->head] = SDL_strdup(apk); + queue->head = (queue->head + 1) % APK_QUEUE_SIZE; + return SDL_TRUE; +} + +SDL_bool apk_queue_take(struct apk_queue *queue, char **apk) { + if (apk_queue_is_empty(queue)) { + return SDL_FALSE; + } + // transfer ownership + *apk = queue->data[queue->tail]; + queue->tail = (queue->tail + 1) % APK_QUEUE_SIZE; + return SDL_TRUE; +} + +SDL_bool installer_init(struct installer *installer, const char *serial) { + + if (!apk_queue_init(&installer->queue)) { + return SDL_FALSE; + } + + if (!(installer->mutex = SDL_CreateMutex())) { + return SDL_FALSE; + } + + if (!(installer->event_cond = SDL_CreateCond())) { + SDL_DestroyMutex(installer->mutex); + return SDL_FALSE; + } + + if (serial) { + installer->serial = SDL_strdup(serial); + if (!installer->serial) { + LOGW("Cannot strdup serial"); + return SDL_FALSE; + } + } else { + installer->serial = NULL; + } + + // lazy initialization + installer->initialized = SDL_FALSE; + + installer->stopped = SDL_FALSE; + return SDL_TRUE; +} + +void installer_destroy(struct installer *installer) { + SDL_DestroyCond(installer->event_cond); + SDL_DestroyMutex(installer->mutex); + apk_queue_destroy(&installer->queue); + SDL_free((void *) installer->serial); +} + +SDL_bool installer_install_apk(struct installer *installer, const char *apk) { + SDL_bool res; + + // start installer if it's used for the first time + if (!installer->initialized) { + if (!installer_start(installer)) { + return SDL_FALSE; + } + installer->initialized = SDL_TRUE; + } + + mutex_lock(installer->mutex); + SDL_bool was_empty = apk_queue_is_empty(&installer->queue); + res = apk_queue_push(&installer->queue, apk); + if (was_empty) { + cond_signal(installer->event_cond); + } + mutex_unlock(installer->mutex); + return res; +} + +static int run_installer(void *data) { + struct installer *installer = data; + + for (;;) { + mutex_lock(installer->mutex); + while (!installer->stopped && apk_queue_is_empty(&installer->queue)) { + cond_wait(installer->event_cond, installer->mutex); + } + if (installer->stopped) { + // stop immediately, do not process further events + mutex_unlock(installer->mutex); + break; + } + char *current_apk; +#ifdef BUILD_DEBUG + bool non_empty = apk_queue_take(&installer->queue, ¤t_apk); + SDL_assert(non_empty); +#else + apk_queue_take(&installer->queue, ¤t_apk); +#endif + + LOGI("Installing %s...", current_apk); + process_t process = adb_install(installer->serial, current_apk); + installer->current_process = process; + + mutex_unlock(installer->mutex); + + if (process_check_success(process, "adb install")) { + LOGI("%s installed successfully", current_apk); + } else { + LOGE("Failed to install %s", current_apk); + } + SDL_free(current_apk); + } + return 0; +} + +SDL_bool installer_start(struct installer *installer) { + LOGD("Starting installer thread"); + + installer->thread = SDL_CreateThread(run_installer, "installer", installer); + if (!installer->thread) { + LOGC("Could not start installer thread"); + return SDL_FALSE; + } + + return SDL_TRUE; +} + +void installer_stop(struct installer *installer) { + mutex_lock(installer->mutex); + installer->stopped = SDL_TRUE; + cond_signal(installer->event_cond); + if (installer->current_process != PROCESS_NONE) { + if (!cmd_terminate(installer->current_process)) { + LOGW("Cannot terminate install process"); + } + cmd_simple_wait(installer->current_process, NULL); + installer->current_process = PROCESS_NONE; + } + mutex_unlock(installer->mutex); +} + +void installer_join(struct installer *installer) { + SDL_WaitThread(installer->thread, NULL); +} diff --git a/app/src/installer.h b/app/src/installer.h new file mode 100644 index 00000000..0ff5f380 --- /dev/null +++ b/app/src/installer.h @@ -0,0 +1,40 @@ +#ifndef APK_INSTALLER_H +#define APK_INSTALLER_H + +#include +#include +#include +#include "command.h" + +#define APK_QUEUE_SIZE 16 + +// NOTE(AdoPi) apk_queue and control_event can use a generic queue + +struct apk_queue { + char *data[APK_QUEUE_SIZE]; + int tail; + int head; +}; + +struct installer { + const char *serial; + SDL_Thread *thread; + SDL_mutex *mutex; + SDL_cond *event_cond; + SDL_bool stopped; + SDL_bool initialized; + process_t current_process; + struct apk_queue queue; +}; + +SDL_bool installer_init(struct installer *installer, const char *serial); +void installer_destroy(struct installer *installer); + +SDL_bool installer_start(struct installer *installer); +void installer_stop(struct installer *installer); +void installer_join(struct installer *installer); + +// install an apk +SDL_bool installer_install_apk(struct installer *installer, const char *filename); + +#endif diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index 4c9f6302..b53f3d47 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -22,12 +22,14 @@ #include "screen.h" #include "server.h" #include "tinyxpm.h" +#include "installer.h" static struct server server = SERVER_INITIALIZER; static struct screen screen = SCREEN_INITIALIZER; static struct frames frames; static struct decoder decoder; static struct controller controller; +static struct installer installer; static struct input_manager input_manager = { .controller = &controller, @@ -102,6 +104,9 @@ static void event_loop(void) { case SDL_MOUSEBUTTONUP: input_manager_process_mouse_button(&input_manager, &event.button); break; + case SDL_DROPFILE: + installer_install_apk(&installer, event.drop.file); + break; } } } @@ -169,6 +174,12 @@ SDL_bool scrcpy(const struct scrcpy_options *options) { goto finally_destroy_server; } + if (!installer_init(&installer, server.serial)) { + ret = SDL_FALSE; + server_stop(&server); + goto finally_destroy_frames; + } + decoder_init(&decoder, &frames, device_socket); // now we consumed the header values, the socket receives the video stream @@ -176,7 +187,7 @@ SDL_bool scrcpy(const struct scrcpy_options *options) { if (!decoder_start(&decoder)) { ret = SDL_FALSE; server_stop(&server); - goto finally_destroy_frames; + goto finally_destroy_installer; } if (!controller_init(&controller, device_socket)) { @@ -214,6 +225,10 @@ finally_stop_decoder: // stop the server before decoder_join() to wake up the decoder server_stop(&server); decoder_join(&decoder); +finally_destroy_installer: + installer_stop(&installer); + installer_join(&installer); + installer_destroy(&installer); finally_destroy_frames: frames_destroy(&frames); finally_destroy_server: