diff --git a/BUILD.md b/BUILD.md index 745ac50c..6d4d6431 100644 --- a/BUILD.md +++ b/BUILD.md @@ -219,10 +219,10 @@ You can then [run](README.md#run) _scrcpy_. ## Prebuilt server - - [`scrcpy-server-v1.3.jar`][direct-scrcpy-server]. - _(SHA-256: 0f9a5a217f33f0ed7a1498ceb3c0cccf31c53533893aa952e674c1571d2740c1)_ + - [`scrcpy-server-v1.4.jar`][direct-scrcpy-server] + _(SHA-256: 1ff7a72fcfe81dadccfab9d6f86c971cd7c7f38f17196748fe05480e301b443d)_ -[direct-scrcpy-server]: https://github.com/Genymobile/scrcpy/releases/download/v1.3/scrcpy-server-v1.3.jar +[direct-scrcpy-server]: https://github.com/Genymobile/scrcpy/releases/download/v1.4/scrcpy-server-v1.4.jar Download the prebuilt server somewhere, and specify its path during the Meson configuration: diff --git a/Makefile.CrossWindows b/Makefile.CrossWindows index 2dd6c192..9beb44e8 100644 --- a/Makefile.CrossWindows +++ b/Makefile.CrossWindows @@ -100,10 +100,10 @@ dist-win32: build-server build-win32 build-win32-noconsole cp "$(SERVER_BUILD_DIR)"/server/scrcpy-server.jar "$(DIST)/$(WIN32_TARGET_DIR)/" cp "$(WIN32_BUILD_DIR)"/app/scrcpy.exe "$(DIST)/$(WIN32_TARGET_DIR)/" cp "$(WIN32_NOCONSOLE_BUILD_DIR)"/app/scrcpy.exe "$(DIST)/$(WIN32_TARGET_DIR)/scrcpy-noconsole.exe" - cp prebuilt-deps/ffmpeg-4.0-win32-shared/bin/avutil-56.dll "$(DIST)/$(WIN32_TARGET_DIR)/" - cp prebuilt-deps/ffmpeg-4.0-win32-shared/bin/avcodec-58.dll "$(DIST)/$(WIN32_TARGET_DIR)/" - cp prebuilt-deps/ffmpeg-4.0-win32-shared/bin/avformat-58.dll "$(DIST)/$(WIN32_TARGET_DIR)/" - cp prebuilt-deps/ffmpeg-4.0-win32-shared/bin/swresample-3.dll "$(DIST)/$(WIN32_TARGET_DIR)/" + cp prebuilt-deps/ffmpeg-4.0.2-win32-shared/bin/avutil-56.dll "$(DIST)/$(WIN32_TARGET_DIR)/" + cp prebuilt-deps/ffmpeg-4.0.2-win32-shared/bin/avcodec-58.dll "$(DIST)/$(WIN32_TARGET_DIR)/" + cp prebuilt-deps/ffmpeg-4.0.2-win32-shared/bin/avformat-58.dll "$(DIST)/$(WIN32_TARGET_DIR)/" + cp prebuilt-deps/ffmpeg-4.0.2-win32-shared/bin/swresample-3.dll "$(DIST)/$(WIN32_TARGET_DIR)/" cp prebuilt-deps/platform-tools/adb.exe "$(DIST)/$(WIN32_TARGET_DIR)/" cp prebuilt-deps/platform-tools/AdbWinApi.dll "$(DIST)/$(WIN32_TARGET_DIR)/" cp prebuilt-deps/platform-tools/AdbWinUsbApi.dll "$(DIST)/$(WIN32_TARGET_DIR)/" @@ -114,10 +114,10 @@ dist-win64: build-server build-win64 build-win64-noconsole cp "$(SERVER_BUILD_DIR)"/server/scrcpy-server.jar "$(DIST)/$(WIN64_TARGET_DIR)/" cp "$(WIN64_BUILD_DIR)"/app/scrcpy.exe "$(DIST)/$(WIN64_TARGET_DIR)/" cp "$(WIN64_NOCONSOLE_BUILD_DIR)"/app/scrcpy.exe "$(DIST)/$(WIN64_TARGET_DIR)/scrcpy-noconsole.exe" - cp prebuilt-deps/ffmpeg-4.0-win64-shared/bin/avutil-56.dll "$(DIST)/$(WIN64_TARGET_DIR)/" - cp prebuilt-deps/ffmpeg-4.0-win64-shared/bin/avcodec-58.dll "$(DIST)/$(WIN64_TARGET_DIR)/" - cp prebuilt-deps/ffmpeg-4.0-win64-shared/bin/avformat-58.dll "$(DIST)/$(WIN64_TARGET_DIR)/" - cp prebuilt-deps/ffmpeg-4.0-win64-shared/bin/swresample-3.dll "$(DIST)/$(WIN64_TARGET_DIR)/" + cp prebuilt-deps/ffmpeg-4.0.2-win64-shared/bin/avutil-56.dll "$(DIST)/$(WIN64_TARGET_DIR)/" + cp prebuilt-deps/ffmpeg-4.0.2-win64-shared/bin/avcodec-58.dll "$(DIST)/$(WIN64_TARGET_DIR)/" + cp prebuilt-deps/ffmpeg-4.0.2-win64-shared/bin/avformat-58.dll "$(DIST)/$(WIN64_TARGET_DIR)/" + cp prebuilt-deps/ffmpeg-4.0.2-win64-shared/bin/swresample-3.dll "$(DIST)/$(WIN64_TARGET_DIR)/" cp prebuilt-deps/platform-tools/adb.exe "$(DIST)/$(WIN64_TARGET_DIR)/" cp prebuilt-deps/platform-tools/AdbWinApi.dll "$(DIST)/$(WIN64_TARGET_DIR)/" cp prebuilt-deps/platform-tools/AdbWinUsbApi.dll "$(DIST)/$(WIN64_TARGET_DIR)/" diff --git a/README.md b/README.md index c16b1c78..28e76e24 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# scrcpy (v1.3) +# scrcpy (v1.4) This application provides display and control of Android devices connected on USB (or [over TCP/IP][article-tcpip]). It does not require any _root_ access. @@ -42,13 +42,13 @@ For Gentoo, an [Ebuild] is available: [`scrcpy/`][ebuild-link]. For Windows, for simplicity, prebuilt archives with all the dependencies (including `adb`) are available: - - [`scrcpy-win32-v1.3.zip`][direct-win32]. - _(SHA-256: 51a2990e631ed469a7a86ff38107d517a91d313fb3f8327eb7bc71dde40870b5)_ - - [`scrcpy-win64-v1.3.zip`][direct-win64]. - _(SHA-256: 0768a80d3d600d0bbcd220ca150ae88a3a58d1fe85c308a8c61f44480b711e43)_ + - [`scrcpy-win32-v1.4.zip`][direct-win32] + _(SHA-256: 1f72fa520980727e8943b7214b64c66b00b9b5267f7cffefb64fa37c3ca803cf)_ + - [`scrcpy-win64-v1.4.zip`][direct-win64] + _(SHA-256: 382f02bd8ed3db2cc7ab15aabdb83674744993b936d602b01e6959a150584a79)_ -[direct-win32]: https://github.com/Genymobile/scrcpy/releases/download/v1.3/scrcpy-win32-v1.3.zip -[direct-win64]: https://github.com/Genymobile/scrcpy/releases/download/v1.3/scrcpy-win64-v1.3.zip +[direct-win32]: https://github.com/Genymobile/scrcpy/releases/download/v1.4/scrcpy-win32-v1.4.zip +[direct-win64]: https://github.com/Genymobile/scrcpy/releases/download/v1.4/scrcpy-win64-v1.4.zip You can also [build the app manually][BUILD]. @@ -117,6 +117,12 @@ To show physical touches while scrcpy is running: scrcpy -t ``` +The app may be started directly in fullscreen: + +``` +scrcpy -f +``` + ## Shortcuts | Action | Shortcut | @@ -135,6 +141,7 @@ scrcpy -t | paste computer clipboard to device | `Ctrl`+`v` | | enable/disable FPS counter (on stdout) | `Ctrl`+`i` | | install APK from computer | drag & drop APK file | + | push file to `/sdcard/` | drag & drop non-APK file | _¹Double-click on black borders to remove them._ _²Right-click turns the screen on if it was off, presses BACK otherwise._ diff --git a/app/meson.build b/app/meson.build index b74b75fa..3e309cdf 100644 --- a/app/meson.build +++ b/app/meson.build @@ -1,22 +1,22 @@ src = [ 'src/main.c', 'src/command.c', - 'src/controlevent.c', + 'src/control_event.c', 'src/controller.c', 'src/convert.c', 'src/decoder.c', 'src/device.c', - 'src/fpscounter.c', + 'src/file_handler.c', + 'src/fps_counter.c', 'src/frames.c', - 'src/inputmanager.c', - 'src/installer.c', - 'src/lockutil.c', + 'src/input_manager.c', + 'src/lock_util.c', 'src/net.c', 'src/scrcpy.c', 'src/screen.c', 'src/server.c', - 'src/strutil.c', - 'src/tinyxpm.c', + 'src/str_util.c', + 'src/tiny_xpm.c', ] if not get_option('crossbuild_windows') @@ -85,7 +85,7 @@ conf = configuration_data() conf.set('BUILD_DEBUG', get_option('buildtype') == 'debug') # the version, updated on release -conf.set_quoted('SCRCPY_VERSION', '1.3') +conf.set_quoted('SCRCPY_VERSION', '1.4') # the prefix used during configuration (meson --prefix=PREFIX) conf.set_quoted('PREFIX', get_option('prefix')) @@ -147,9 +147,9 @@ executable('scrcpy', src, dependencies: dependencies, include_directories: src_d ### TESTS tests = [ - ['test_control_event_queue', ['tests/test_control_event_queue.c', 'src/controlevent.c']], - ['test_control_event_serialize', ['tests/test_control_event_serialize.c', 'src/controlevent.c']], - ['test_strutil', ['tests/test_strutil.c', 'src/strutil.c']], + ['test_control_event_queue', ['tests/test_control_event_queue.c', 'src/control_event.c']], + ['test_control_event_serialize', ['tests/test_control_event_serialize.c', 'src/control_event.c']], + ['test_strutil', ['tests/test_strutil.c', 'src/str_util.c']], ] foreach t : tests diff --git a/app/src/command.c b/app/src/command.c index b7ea67d2..b5bb9572 100644 --- a/app/src/command.c +++ b/app/src/command.c @@ -6,10 +6,11 @@ #include "common.h" #include "log.h" +#include "str_util.h" static const char *adb_command; -static inline const char *get_adb_command() { +static inline const char *get_adb_command(void) { if (!adb_command) { adb_command = getenv("ADB"); if (!adb_command) @@ -18,9 +19,25 @@ static inline const char *get_adb_command() { return adb_command; } +static void show_adb_err_msg(enum process_result err) { + switch (err) { + case PROCESS_ERROR_GENERIC: + LOGE("Failed to execute adb"); + break; + case PROCESS_ERROR_MISSING_BINARY: + LOGE("'adb' command not found (make it accessible from your PATH " + "or define its full path in the ADB environment variable)"); + break; + case PROCESS_SUCCESS: + /* do nothing */ + break; + } +} + process_t adb_execute(const char *serial, const char *const adb_cmd[], int len) { const char *cmd[len + 4]; int i; + process_t process; cmd[0] = get_adb_command(); if (serial) { cmd[1] = "-s"; @@ -32,7 +49,12 @@ process_t adb_execute(const char *serial, const char *const adb_cmd[], int len) memcpy(&cmd[i], adb_cmd, len * sizeof(const char *)); cmd[len + i] = NULL; - return cmd_execute(cmd[0], cmd); + enum process_result r = cmd_execute(cmd[0], cmd, &process); + if (r != PROCESS_SUCCESS) { + show_adb_err_msg(r); + return PROCESS_NONE; + } + return process; } process_t adb_forward(const char *serial, uint16_t local_port, const char *device_socket_name) { @@ -68,23 +90,49 @@ 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) { +#ifdef __WINDOWS__ + // Windows will parse the string, so the paths must be quoted + // (see sys/win/command.c) + local = strquote(local); + if (!local) { + return PROCESS_NONE; + } + remote = strquote(remote); + if (!remote) { + free((void *) local); + return PROCESS_NONE; + } +#endif + const char *const adb_cmd[] = {"push", local, remote}; - return adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd)); + process_t proc = adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd)); + +#ifdef __WINDOWS__ + free((void *) remote); + free((void *) local); +#endif + + return proc; } process_t adb_install(const char *serial, const char *local) { #ifdef __WINDOWS__ - // Windows will parse the string, so the local name must be quoted (see sys/win/command.c) - size_t len = strlen(local); - char quoted[len + 3]; - memcpy("ed[1], local, len); - quoted[0] = '"'; - quoted[len + 1] = '"'; - quoted[len + 2] = '\0'; - local = quoted; + // Windows will parse the string, so the local name must be quoted + // (see sys/win/command.c) + local = strquote(local); + if (!local) { + return PROCESS_NONE; + } #endif + const char *const adb_cmd[] = {"install", "-r", local}; - return adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd)); + process_t proc = adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd)); + +#ifdef __WINDOWS__ + free((void *) local); +#endif + + return proc; } process_t adb_remove_path(const char *serial, const char *path) { diff --git a/app/src/command.h b/app/src/command.h index 4113f251..3e0fcca6 100644 --- a/app/src/command.h +++ b/app/src/command.h @@ -32,7 +32,13 @@ #endif # define NO_EXIT_CODE -1 -process_t cmd_execute(const char *path, const char *const argv[]); +enum process_result { + PROCESS_SUCCESS, + PROCESS_ERROR_GENERIC, + PROCESS_ERROR_MISSING_BINARY, +}; + +enum process_result cmd_execute(const char *path, const char *const argv[], process_t *process); SDL_bool cmd_terminate(process_t pid); SDL_bool cmd_simple_wait(process_t pid, exit_code_t *exit_code); diff --git a/app/src/controlevent.c b/app/src/control_event.c similarity index 98% rename from app/src/controlevent.c rename to app/src/control_event.c index edb7a1a2..78b6a4c6 100644 --- a/app/src/controlevent.c +++ b/app/src/control_event.c @@ -1,9 +1,9 @@ -#include "controlevent.h" +#include "control_event.h" #include #include -#include "lockutil.h" +#include "lock_util.h" #include "log.h" static inline void write16(Uint8 *buf, Uint16 value) { diff --git a/app/src/controlevent.h b/app/src/control_event.h similarity index 100% rename from app/src/controlevent.h rename to app/src/control_event.h diff --git a/app/src/controller.c b/app/src/controller.c index 468885f6..f659d4b9 100644 --- a/app/src/controller.c +++ b/app/src/controller.c @@ -1,6 +1,8 @@ #include "controller.h" -#include "lockutil.h" +#include +#include "config.h" +#include "lock_util.h" #include "log.h" SDL_bool controller_init(struct controller *controller, socket_t video_socket) { @@ -65,12 +67,8 @@ static int run_controller(void *data) { break; } struct control_event event; -#ifdef BUILD_DEBUG - bool non_empty = control_event_queue_take(&controller->queue, &event); + SDL_bool non_empty = control_event_queue_take(&controller->queue, &event); SDL_assert(non_empty); -#else - control_event_queue_take(&controller->queue, &event); -#endif mutex_unlock(controller->mutex); SDL_bool ok = process_event(controller, &event); diff --git a/app/src/controller.h b/app/src/controller.h index 7c22fe07..08d639a6 100644 --- a/app/src/controller.h +++ b/app/src/controller.h @@ -1,7 +1,7 @@ #ifndef CONTROL_H #define CONTROL_H -#include "controlevent.h" +#include "control_event.h" #include #include diff --git a/app/src/convert.c b/app/src/convert.c index 9d947cb3..7d40b938 100644 --- a/app/src/convert.c +++ b/app/src/convert.c @@ -70,7 +70,7 @@ static enum android_metastate convert_meta_state(SDL_Keymod mod) { return autocomplete_metastate(metastate); } -static SDL_bool convert_keycode(SDL_Keycode from, enum android_keycode *to) { +static SDL_bool convert_keycode(SDL_Keycode from, enum android_keycode *to, Uint16 mod) { switch (from) { MAP(SDLK_RETURN, AKEYCODE_ENTER); MAP(SDLK_KP_ENTER, AKEYCODE_NUMPAD_ENTER); @@ -86,6 +86,39 @@ static SDL_bool convert_keycode(SDL_Keycode from, enum android_keycode *to) { MAP(SDLK_LEFT, AKEYCODE_DPAD_LEFT); MAP(SDLK_DOWN, AKEYCODE_DPAD_DOWN); MAP(SDLK_UP, AKEYCODE_DPAD_UP); + } + if (mod & (KMOD_LALT | KMOD_RALT | KMOD_LGUI | KMOD_RGUI)) { + return SDL_FALSE; + } + // if ALT and META are not pressed, also handle letters and space + switch (from) { + MAP(SDLK_a, AKEYCODE_A); + MAP(SDLK_b, AKEYCODE_B); + MAP(SDLK_c, AKEYCODE_C); + MAP(SDLK_d, AKEYCODE_D); + MAP(SDLK_e, AKEYCODE_E); + MAP(SDLK_f, AKEYCODE_F); + MAP(SDLK_g, AKEYCODE_G); + MAP(SDLK_h, AKEYCODE_H); + MAP(SDLK_i, AKEYCODE_I); + MAP(SDLK_j, AKEYCODE_J); + MAP(SDLK_k, AKEYCODE_K); + MAP(SDLK_l, AKEYCODE_L); + MAP(SDLK_m, AKEYCODE_M); + MAP(SDLK_n, AKEYCODE_N); + MAP(SDLK_o, AKEYCODE_O); + MAP(SDLK_p, AKEYCODE_P); + MAP(SDLK_q, AKEYCODE_Q); + MAP(SDLK_r, AKEYCODE_R); + MAP(SDLK_s, AKEYCODE_S); + MAP(SDLK_t, AKEYCODE_T); + MAP(SDLK_u, AKEYCODE_U); + MAP(SDLK_v, AKEYCODE_V); + MAP(SDLK_w, AKEYCODE_W); + MAP(SDLK_x, AKEYCODE_X); + MAP(SDLK_y, AKEYCODE_Y); + MAP(SDLK_z, AKEYCODE_Z); + MAP(SDLK_SPACE, AKEYCODE_SPACE); FAIL; } } @@ -126,11 +159,12 @@ SDL_bool input_key_from_sdl_to_android(const SDL_KeyboardEvent *from, return SDL_FALSE; } - if (!convert_keycode(from->keysym.sym, &to->keycode_event.keycode)) { + Uint16 mod = from->keysym.mod; + if (!convert_keycode(from->keysym.sym, &to->keycode_event.keycode, mod)) { return SDL_FALSE; } - to->keycode_event.metastate = convert_meta_state(from->keysym.mod); + to->keycode_event.metastate = convert_meta_state(mod); return SDL_TRUE; } diff --git a/app/src/convert.h b/app/src/convert.h index 5a535a37..30f0dd3d 100644 --- a/app/src/convert.h +++ b/app/src/convert.h @@ -3,7 +3,7 @@ #include #include -#include "controlevent.h" +#include "control_event.h" struct complete_mouse_motion_event { SDL_MouseMotionEvent *mouse_motion_event; diff --git a/app/src/decoder.c b/app/src/decoder.c index 94dc9401..9e90e4b7 100644 --- a/app/src/decoder.c +++ b/app/src/decoder.c @@ -9,7 +9,7 @@ #include "config.h" #include "events.h" #include "frames.h" -#include "lockutil.h" +#include "lock_util.h" #include "log.h" #define BUFSIZE 0x10000 diff --git a/app/src/device.h b/app/src/device.h index d01d6ed2..125dda3a 100644 --- a/app/src/device.h +++ b/app/src/device.h @@ -7,6 +7,7 @@ #include "net.h" #define DEVICE_NAME_FIELD_LENGTH 64 +#define DEVICE_SDCARD_PATH "/sdcard/" // name must be at least DEVICE_NAME_FIELD_LENGTH bytes SDL_bool device_read_info(socket_t device_socket, char *name, struct size *frame_size); diff --git a/app/src/file_handler.c b/app/src/file_handler.c new file mode 100644 index 00000000..d1e0fd7b --- /dev/null +++ b/app/src/file_handler.c @@ -0,0 +1,231 @@ +#include "file_handler.h" + +#include +#include +#include "config.h" +#include "command.h" +#include "device.h" +#include "lock_util.h" +#include "log.h" + +struct request { + file_handler_action_t action; + const char *file; +}; + +static struct request *request_new(file_handler_action_t action, const char *file) { + struct request *req = SDL_malloc(sizeof(*req)); + if (!req) { + return NULL; + } + req->action = action; + req->file = file; + return req; +} + +static void request_free(struct request *req) { + if (!req) { + return; + } + SDL_free((void *) req->file); + SDL_free((void *) req); +} + +static SDL_bool request_queue_is_empty(const struct request_queue *queue) { + return queue->head == queue->tail; +} + +static SDL_bool request_queue_is_full(const struct request_queue *queue) { + return (queue->head + 1) % REQUEST_QUEUE_SIZE == queue->tail; +} + +static SDL_bool request_queue_init(struct request_queue *queue) { + queue->head = 0; + queue->tail = 0; + return SDL_TRUE; +} + +static void request_queue_destroy(struct request_queue *queue) { + int i = queue->tail; + while (i != queue->head) { + request_free(queue->reqs[i]); + i = (i + 1) % REQUEST_QUEUE_SIZE; + } +} + +static SDL_bool request_queue_push(struct request_queue *queue, struct request *req) { + if (request_queue_is_full(queue)) { + return SDL_FALSE; + } + queue->reqs[queue->head] = req; + queue->head = (queue->head + 1) % REQUEST_QUEUE_SIZE; + return SDL_TRUE; +} + +static SDL_bool request_queue_take(struct request_queue *queue, struct request **req) { + if (request_queue_is_empty(queue)) { + return SDL_FALSE; + } + // transfer ownership + *req = queue->reqs[queue->tail]; + queue->tail = (queue->tail + 1) % REQUEST_QUEUE_SIZE; + return SDL_TRUE; +} + +SDL_bool file_handler_init(struct file_handler *file_handler, const char *serial) { + + if (!request_queue_init(&file_handler->queue)) { + return SDL_FALSE; + } + + if (!(file_handler->mutex = SDL_CreateMutex())) { + return SDL_FALSE; + } + + if (!(file_handler->event_cond = SDL_CreateCond())) { + SDL_DestroyMutex(file_handler->mutex); + return SDL_FALSE; + } + + if (serial) { + file_handler->serial = SDL_strdup(serial); + if (!file_handler->serial) { + LOGW("Cannot strdup serial"); + SDL_DestroyMutex(file_handler->mutex); + return SDL_FALSE; + } + } else { + file_handler->serial = NULL; + } + + // lazy initialization + file_handler->initialized = SDL_FALSE; + + file_handler->stopped = SDL_FALSE; + file_handler->current_process = PROCESS_NONE; + + return SDL_TRUE; +} + +void file_handler_destroy(struct file_handler *file_handler) { + SDL_DestroyCond(file_handler->event_cond); + SDL_DestroyMutex(file_handler->mutex); + request_queue_destroy(&file_handler->queue); + SDL_free((void *) file_handler->serial); +} + +static process_t install_apk(const char *serial, const char *file) { + return adb_install(serial, file); +} + +static process_t push_file(const char *serial, const char *file) { + return adb_push(serial, file, DEVICE_SDCARD_PATH); +} + +SDL_bool file_handler_request(struct file_handler *file_handler, + file_handler_action_t action, + const char *file) { + SDL_bool res; + + // start file_handler if it's used for the first time + if (!file_handler->initialized) { + if (!file_handler_start(file_handler)) { + return SDL_FALSE; + } + file_handler->initialized = SDL_TRUE; + } + + LOGI("Request to %s %s", action == ACTION_INSTALL_APK ? "install" : "push", file); + struct request *req = request_new(action, file); + if (!req) { + LOGE("Could not create request"); + return SDL_FALSE; + } + + mutex_lock(file_handler->mutex); + SDL_bool was_empty = request_queue_is_empty(&file_handler->queue); + res = request_queue_push(&file_handler->queue, req); + if (was_empty) { + cond_signal(file_handler->event_cond); + } + mutex_unlock(file_handler->mutex); + return res; +} + +static int run_file_handler(void *data) { + struct file_handler *file_handler = data; + + for (;;) { + mutex_lock(file_handler->mutex); + file_handler->current_process = PROCESS_NONE; + while (!file_handler->stopped && request_queue_is_empty(&file_handler->queue)) { + cond_wait(file_handler->event_cond, file_handler->mutex); + } + if (file_handler->stopped) { + // stop immediately, do not process further events + mutex_unlock(file_handler->mutex); + break; + } + struct request *req; + SDL_bool non_empty = request_queue_take(&file_handler->queue, &req); + SDL_assert(non_empty); + + process_t process; + if (req->action == ACTION_INSTALL_APK) { + LOGI("Installing %s...", req->file); + process = install_apk(file_handler->serial, req->file); + } else { + LOGI("Pushing %s...", req->file); + process = push_file(file_handler->serial, req->file); + } + file_handler->current_process = process; + mutex_unlock(file_handler->mutex); + + if (req->action == ACTION_INSTALL_APK) { + if (process_check_success(process, "adb install")) { + LOGI("%s successfully installed", req->file); + } else { + LOGE("Failed to install %s", req->file); + } + } else { + if (process_check_success(process, "adb push")) { + LOGI("%s successfully pushed to /sdcard/", req->file); + } else { + LOGE("Failed to push %s to /sdcard/", req->file); + } + } + + request_free(req); + } + return 0; +} + +SDL_bool file_handler_start(struct file_handler *file_handler) { + LOGD("Starting file_handler thread"); + + file_handler->thread = SDL_CreateThread(run_file_handler, "file_handler", file_handler); + if (!file_handler->thread) { + LOGC("Could not start file_handler thread"); + return SDL_FALSE; + } + + return SDL_TRUE; +} + +void file_handler_stop(struct file_handler *file_handler) { + mutex_lock(file_handler->mutex); + file_handler->stopped = SDL_TRUE; + cond_signal(file_handler->event_cond); + if (file_handler->current_process != PROCESS_NONE) { + if (!cmd_terminate(file_handler->current_process)) { + LOGW("Cannot terminate install process"); + } + cmd_simple_wait(file_handler->current_process, NULL); + file_handler->current_process = PROCESS_NONE; + } + mutex_unlock(file_handler->mutex); +} + +void file_handler_join(struct file_handler *file_handler) { + SDL_WaitThread(file_handler->thread, NULL); +} diff --git a/app/src/file_handler.h b/app/src/file_handler.h new file mode 100644 index 00000000..9ce39367 --- /dev/null +++ b/app/src/file_handler.h @@ -0,0 +1,44 @@ +#ifndef FILE_HANDLER_H +#define FILE_HANDLER_H + +#include +#include +#include +#include "command.h" + +#define REQUEST_QUEUE_SIZE 16 + +typedef enum { + ACTION_INSTALL_APK, + ACTION_PUSH_FILE, +} file_handler_action_t; + +struct request_queue { + struct request *reqs[REQUEST_QUEUE_SIZE]; + int tail; + int head; +}; + +struct file_handler { + const char *serial; + SDL_Thread *thread; + SDL_mutex *mutex; + SDL_cond *event_cond; + SDL_bool stopped; + SDL_bool initialized; + process_t current_process; + struct request_queue queue; +}; + +SDL_bool file_handler_init(struct file_handler *file_handler, const char *serial); +void file_handler_destroy(struct file_handler *file_handler); + +SDL_bool file_handler_start(struct file_handler *file_handler); +void file_handler_stop(struct file_handler *file_handler); +void file_handler_join(struct file_handler *file_handler); + +SDL_bool file_handler_request(struct file_handler *file_handler, + file_handler_action_t action, + const char *file); + +#endif diff --git a/app/src/fpscounter.c b/app/src/fps_counter.c similarity index 98% rename from app/src/fpscounter.c rename to app/src/fps_counter.c index 4cb512f3..27aa4ee0 100644 --- a/app/src/fpscounter.c +++ b/app/src/fps_counter.c @@ -1,4 +1,4 @@ -#include "fpscounter.h" +#include "fps_counter.h" #include diff --git a/app/src/fpscounter.h b/app/src/fps_counter.h similarity index 100% rename from app/src/fpscounter.h rename to app/src/fps_counter.h diff --git a/app/src/frames.c b/app/src/frames.c index 27fcc1fb..514d4788 100644 --- a/app/src/frames.c +++ b/app/src/frames.c @@ -6,7 +6,7 @@ #include #include "config.h" -#include "lockutil.h" +#include "lock_util.h" #include "log.h" SDL_bool frames_init(struct frames *frames) { diff --git a/app/src/frames.h b/app/src/frames.h index 7a52d2c8..437838fc 100644 --- a/app/src/frames.h +++ b/app/src/frames.h @@ -5,7 +5,7 @@ #include #include "config.h" -#include "fpscounter.h" +#include "fps_counter.h" // forward declarations typedef struct AVFrame AVFrame; diff --git a/app/src/inputmanager.c b/app/src/input_manager.c similarity index 97% rename from app/src/inputmanager.c rename to app/src/input_manager.c index 9dd95264..af84c8f3 100644 --- a/app/src/inputmanager.c +++ b/app/src/input_manager.c @@ -1,7 +1,8 @@ -#include "inputmanager.h" +#include "input_manager.h" +#include #include "convert.h" -#include "lockutil.h" +#include "lock_util.h" #include "log.h" // Convert window coordinates (as provided by SDL_GetMouseState() to renderer coordinates (as provided in SDL mouse events) @@ -129,6 +130,12 @@ static void clipboard_paste(struct controller *controller) { void input_manager_process_text_input(struct input_manager *input_manager, const SDL_TextInputEvent *event) { + char c = event->text[0]; + if (isalpha(c) || c == ' ') { + SDL_assert(event->text[1] == '\0'); + // letters and space are handled as raw key event + return; + } struct control_event control_event; control_event.type = CONTROL_EVENT_TYPE_TEXT; control_event.text_event.text = SDL_strdup(event->text); diff --git a/app/src/inputmanager.h b/app/src/input_manager.h similarity index 97% rename from app/src/inputmanager.h rename to app/src/input_manager.h index d7eaccbc..b9037aa1 100644 --- a/app/src/inputmanager.h +++ b/app/src/input_manager.h @@ -3,7 +3,7 @@ #include "common.h" #include "controller.h" -#include "fpscounter.h" +#include "fps_counter.h" #include "frames.h" #include "screen.h" diff --git a/app/src/installer.c b/app/src/installer.c deleted file mode 100644 index 5b9e6637..00000000 --- a/app/src/installer.c +++ /dev/null @@ -1,182 +0,0 @@ -#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"); - SDL_DestroyMutex(installer->mutex); - return SDL_FALSE; - } - } else { - installer->serial = NULL; - } - - // lazy initialization - installer->initialized = SDL_FALSE; - - installer->stopped = SDL_FALSE; - installer->current_process = PROCESS_NONE; - - 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); - installer->current_process = PROCESS_NONE; - 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 deleted file mode 100644 index 0ff5f380..00000000 --- a/app/src/installer.h +++ /dev/null @@ -1,40 +0,0 @@ -#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/lockutil.c b/app/src/lock_util.c similarity index 96% rename from app/src/lockutil.c rename to app/src/lock_util.c index 67183d71..723d3b13 100644 --- a/app/src/lockutil.c +++ b/app/src/lock_util.c @@ -1,3 +1,4 @@ +#include #include #include diff --git a/app/src/lockutil.h b/app/src/lock_util.h similarity index 100% rename from app/src/lockutil.h rename to app/src/lock_util.h diff --git a/app/src/main.c b/app/src/main.c index 1317da79..e1d6782e 100644 --- a/app/src/main.c +++ b/app/src/main.c @@ -11,6 +11,7 @@ struct args { const char *serial; const char *crop; + SDL_bool fullscreen; SDL_bool help; SDL_bool version; SDL_bool show_touches; @@ -36,6 +37,9 @@ static void usage(const char *arg0) { " (typically, portrait for a phone, landscape for a tablet).\n" " Any --max-size value is computed on the cropped size.\n" "\n" + " -f, --fullscreen\n" + " Start in fullscreen.\n" + "\n" " -h, --help\n" " Print this help.\n" "\n" @@ -200,6 +204,7 @@ static SDL_bool parse_args(struct args *args, int argc, char *argv[]) { static const struct option long_options[] = { {"bit-rate", required_argument, NULL, 'b'}, {"crop", required_argument, NULL, 'c'}, + {"fullscreen", no_argument, NULL, 'f'}, {"help", no_argument, NULL, 'h'}, {"max-size", required_argument, NULL, 'm'}, {"port", required_argument, NULL, 'p'}, @@ -209,7 +214,7 @@ static SDL_bool parse_args(struct args *args, int argc, char *argv[]) { {NULL, 0, NULL, 0 }, }; int c; - while ((c = getopt_long(argc, argv, "b:c:hm:p:s:tv", long_options, NULL)) != -1) { + while ((c = getopt_long(argc, argv, "b:c:fhm:p:s:tv", long_options, NULL)) != -1) { switch (c) { case 'b': if (!parse_bit_rate(optarg, &args->bit_rate)) { @@ -219,6 +224,9 @@ static SDL_bool parse_args(struct args *args, int argc, char *argv[]) { case 'c': args->crop = optarg; break; + case 'f': + args->fullscreen = SDL_TRUE; + break; case 'h': args->help = SDL_TRUE; break; @@ -305,10 +313,17 @@ int main(int argc, char *argv[]) { .max_size = args.max_size, .bit_rate = args.bit_rate, .show_touches = args.show_touches, + .fullscreen = args.fullscreen, }; int res = scrcpy(&options) ? 0 : 1; avformat_network_deinit(); // ignore failure +#if defined (__WINDOWS__) && ! defined (WINDOWS_NOCONSOLE) + if (res != 0) { + fprintf(stderr, "Press any key to continue...\n"); + getchar(); + } +#endif return res; } diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index 23de4984..54a7f993 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -13,23 +13,23 @@ #include "decoder.h" #include "device.h" #include "events.h" +#include "file_handler.h" #include "frames.h" -#include "fpscounter.h" -#include "inputmanager.h" +#include "fps_counter.h" +#include "input_manager.h" #include "log.h" -#include "lockutil.h" +#include "lock_util.h" #include "net.h" #include "screen.h" #include "server.h" -#include "tinyxpm.h" -#include "installer.h" +#include "tiny_xpm.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 file_handler file_handler; static struct input_manager input_manager = { .controller = &controller, @@ -56,6 +56,11 @@ static int event_watcher(void *data, SDL_Event *event) { } #endif +static SDL_bool is_apk(const char *file) { + const char *ext = strrchr(file, '.'); + return ext && !strcmp(ext, ".apk"); +} + static SDL_bool event_loop(void) { #ifdef CONTINUOUS_RESIZING_WORKAROUND SDL_AddEventWatch(event_watcher, NULL); @@ -104,9 +109,16 @@ static SDL_bool 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); + case SDL_DROPFILE: { + file_handler_action_t action; + if (is_apk(event.drop.file)) { + action = ACTION_INSTALL_APK; + } else { + action = ACTION_PUSH_FILE; + } + file_handler_request(&file_handler, action, event.drop.file); break; + } } } return SDL_FALSE; @@ -175,7 +187,7 @@ SDL_bool scrcpy(const struct scrcpy_options *options) { goto finally_destroy_server; } - if (!installer_init(&installer, server.serial)) { + if (!file_handler_init(&file_handler, server.serial)) { ret = SDL_FALSE; server_stop(&server); goto finally_destroy_frames; @@ -188,7 +200,7 @@ SDL_bool scrcpy(const struct scrcpy_options *options) { if (!decoder_start(&decoder)) { ret = SDL_FALSE; server_stop(&server); - goto finally_destroy_installer; + goto finally_destroy_file_handler; } if (!controller_init(&controller, device_socket)) { @@ -211,6 +223,10 @@ SDL_bool scrcpy(const struct scrcpy_options *options) { show_touches_waited = SDL_TRUE; } + if (options->fullscreen) { + screen_switch_fullscreen(&screen); + } + ret = event_loop(); LOGD("quit..."); @@ -226,10 +242,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_file_handler: + file_handler_stop(&file_handler); + file_handler_join(&file_handler); + file_handler_destroy(&file_handler); finally_destroy_frames: frames_destroy(&frames); finally_destroy_server: diff --git a/app/src/scrcpy.h b/app/src/scrcpy.h index 7ddabf28..f64d4c02 100644 --- a/app/src/scrcpy.h +++ b/app/src/scrcpy.h @@ -10,6 +10,7 @@ struct scrcpy_options { Uint16 max_size; Uint32 bit_rate; SDL_bool show_touches; + SDL_bool fullscreen; }; SDL_bool scrcpy(const struct scrcpy_options *options); diff --git a/app/src/screen.c b/app/src/screen.c index 5b4f0634..5d7a4009 100644 --- a/app/src/screen.c +++ b/app/src/screen.c @@ -4,9 +4,9 @@ #include #include "icon.xpm" -#include "lockutil.h" +#include "lock_util.h" #include "log.h" -#include "tinyxpm.h" +#include "tiny_xpm.h" #define DISPLAY_MARGINS 96 diff --git a/app/src/strutil.c b/app/src/str_util.c similarity index 68% rename from app/src/strutil.c rename to app/src/str_util.c index 0b5ac12d..f234346e 100644 --- a/app/src/strutil.c +++ b/app/src/str_util.c @@ -1,4 +1,7 @@ -#include "strutil.h" +#include "str_util.h" + +#include +#include size_t xstrncpy(char *dest, const char *src, size_t n) { size_t i; @@ -31,3 +34,16 @@ truncated: dst[n - 1] = '\0'; return n; } + +char *strquote(const char *src) { + size_t len = strlen(src); + char *quoted = malloc(len + 3); + if (!quoted) { + return NULL; + } + memcpy("ed[1], src, len); + quoted[0] = '"'; + quoted[len + 1] = '"'; + quoted[len + 2] = '\0'; + return quoted; +} diff --git a/app/src/strutil.h b/app/src/str_util.h similarity index 84% rename from app/src/strutil.h rename to app/src/str_util.h index 1bf8088e..9433f768 100644 --- a/app/src/strutil.h +++ b/app/src/str_util.h @@ -16,4 +16,8 @@ size_t xstrncpy(char *dest, const char *src, size_t n); // occurred, or n if truncated size_t xstrjoin(char *dst, const char *const tokens[], char sep, size_t n); +// quote a string +// returns the new allocated string, to be freed by the caller +char *strquote(const char *src); + #endif diff --git a/app/src/sys/unix/command.c b/app/src/sys/unix/command.c index 0083a93b..ed9bbeac 100644 --- a/app/src/sys/unix/command.c +++ b/app/src/sys/unix/command.c @@ -1,5 +1,7 @@ #include "command.h" +#include +#include #include #include #include @@ -7,18 +9,65 @@ #include #include "log.h" -pid_t cmd_execute(const char *path, const char *const argv[]) { - pid_t pid = fork(); - if (pid == -1) { - perror("fork"); - return -1; +enum process_result cmd_execute(const char *path, const char *const argv[], pid_t *pid) { + int fd[2]; + + if (pipe(fd) == -1) { + perror("pipe"); + return PROCESS_ERROR_GENERIC; } - if (pid == 0) { - execvp(path, (char *const *)argv); - perror("exec"); + + enum process_result ret = PROCESS_SUCCESS; + + *pid = fork(); + if (*pid == -1) { + perror("fork"); + ret = PROCESS_ERROR_GENERIC; + goto end; + } + + if (*pid > 0) { + // parent close write side + close(fd[1]); + fd[1] = -1; + // wait for EOF or receive errno from child + if (read(fd[0], &ret, sizeof(ret)) == -1) { + perror("read"); + ret = PROCESS_ERROR_GENERIC; + goto end; + } + } else if (*pid == 0) { + // child close read side + close(fd[0]); + if (fcntl(fd[1], F_SETFD, FD_CLOEXEC) == 0) { + execvp(path, (char *const *)argv); + if (errno == ENOENT) { + ret = PROCESS_ERROR_MISSING_BINARY; + } else { + ret = PROCESS_ERROR_GENERIC; + } + perror("exec"); + } else { + perror("fcntl"); + ret = PROCESS_ERROR_GENERIC; + } + // send ret to the parent + if (write(fd[1], &ret, sizeof(ret)) == -1) { + perror("write"); + } + // close write side before exiting + close(fd[1]); _exit(1); } - return pid; + +end: + if (fd[0] != -1) { + close(fd[0]); + } + if (fd[1] != -1) { + close(fd[1]); + } + return ret; } SDL_bool cmd_terminate(pid_t pid) { diff --git a/app/src/sys/win/command.c b/app/src/sys/win/command.c index 32d2a8b8..1a14d89a 100644 --- a/app/src/sys/win/command.c +++ b/app/src/sys/win/command.c @@ -2,9 +2,9 @@ #include "config.h" #include "log.h" -#include "strutil.h" +#include "str_util.h" -HANDLE cmd_execute(const char *path, const char *const argv[]) { +enum process_result cmd_execute(const char *path, const char *const argv[], HANDLE *handle) { STARTUPINFO si; PROCESS_INFORMATION pi; memset(&si, 0, sizeof(si)); @@ -18,7 +18,8 @@ HANDLE cmd_execute(const char *path, const char *const argv[]) { size_t ret = xstrjoin(cmd, argv, ' ', sizeof(cmd)); if (ret >= sizeof(cmd)) { LOGE("Command too long (%" PRIsizet " chars)", sizeof(cmd) - 1); - return NULL; + *handle = NULL; + return PROCESS_ERROR_GENERIC; } #ifdef WINDOWS_NOCONSOLE @@ -27,10 +28,15 @@ HANDLE cmd_execute(const char *path, const char *const argv[]) { int flags = 0; #endif if (!CreateProcess(NULL, cmd, NULL, NULL, FALSE, flags, NULL, NULL, &si, &pi)) { - return NULL; + *handle = NULL; + if (GetLastError() == ERROR_FILE_NOT_FOUND) { + return PROCESS_ERROR_MISSING_BINARY; + } + return PROCESS_ERROR_GENERIC; } - return pi.hProcess; + *handle = pi.hProcess; + return PROCESS_SUCCESS; } SDL_bool cmd_terminate(HANDLE handle) { diff --git a/app/src/tinyxpm.c b/app/src/tiny_xpm.c similarity index 99% rename from app/src/tinyxpm.c rename to app/src/tiny_xpm.c index 949aeeae..3b80b88e 100644 --- a/app/src/tinyxpm.c +++ b/app/src/tiny_xpm.c @@ -1,4 +1,4 @@ -#include "tinyxpm.h" +#include "tiny_xpm.h" #include #include diff --git a/app/src/tinyxpm.h b/app/src/tiny_xpm.h similarity index 100% rename from app/src/tinyxpm.h rename to app/src/tiny_xpm.h diff --git a/app/tests/test_control_event_queue.c b/app/tests/test_control_event_queue.c index dc89fa2d..a27181b6 100644 --- a/app/tests/test_control_event_queue.c +++ b/app/tests/test_control_event_queue.c @@ -1,9 +1,9 @@ #include #include -#include "controlevent.h" +#include "control_event.h" -static void test_control_event_queue_empty() { +static void test_control_event_queue_empty(void) { struct control_event_queue queue; SDL_bool init_ok = control_event_queue_init(&queue); assert(init_ok); @@ -25,7 +25,7 @@ static void test_control_event_queue_empty() { control_event_queue_destroy(&queue); } -static void test_control_event_queue_full() { +static void test_control_event_queue_full(void) { struct control_event_queue queue; SDL_bool init_ok = control_event_queue_init(&queue); assert(init_ok); @@ -43,7 +43,7 @@ static void test_control_event_queue_full() { control_event_queue_destroy(&queue); } -static void test_control_event_queue_push_take() { +static void test_control_event_queue_push_take(void) { struct control_event_queue queue; SDL_bool init_ok = control_event_queue_init(&queue); assert(init_ok); @@ -87,7 +87,7 @@ static void test_control_event_queue_push_take() { control_event_queue_destroy(&queue); } -int main() { +int main(void) { test_control_event_queue_empty(); test_control_event_queue_full(); test_control_event_queue_push_take(); diff --git a/app/tests/test_control_event_serialize.c b/app/tests/test_control_event_serialize.c index e15c64bf..21d7909b 100644 --- a/app/tests/test_control_event_serialize.c +++ b/app/tests/test_control_event_serialize.c @@ -1,9 +1,9 @@ #include #include -#include "controlevent.h" +#include "control_event.h" -static void test_serialize_keycode_event() { +static void test_serialize_keycode_event(void) { struct control_event event = { .type = CONTROL_EVENT_TYPE_KEYCODE, .keycode_event = { @@ -26,7 +26,7 @@ static void test_serialize_keycode_event() { assert(!memcmp(buf, expected, sizeof(expected))); } -static void test_serialize_text_event() { +static void test_serialize_text_event(void) { struct control_event event = { .type = CONTROL_EVENT_TYPE_TEXT, .text_event = { @@ -46,7 +46,7 @@ static void test_serialize_text_event() { assert(!memcmp(buf, expected, sizeof(expected))); } -static void test_serialize_long_text_event() { +static void test_serialize_long_text_event(void) { struct control_event event; event.type = CONTROL_EVENT_TYPE_TEXT; char text[TEXT_MAX_LENGTH]; @@ -66,7 +66,7 @@ static void test_serialize_long_text_event() { assert(!memcmp(buf, expected, sizeof(expected))); } -static void test_serialize_mouse_event() { +static void test_serialize_mouse_event(void) { struct control_event event = { .type = CONTROL_EVENT_TYPE_MOUSE, .mouse_event = { @@ -99,7 +99,7 @@ static void test_serialize_mouse_event() { assert(!memcmp(buf, expected, sizeof(expected))); } -static void test_serialize_scroll_event() { +static void test_serialize_scroll_event(void) { struct control_event event = { .type = CONTROL_EVENT_TYPE_SCROLL, .scroll_event = { @@ -132,11 +132,10 @@ static void test_serialize_scroll_event() { assert(!memcmp(buf, expected, sizeof(expected))); } -int main() { +int main(void) { test_serialize_keycode_event(); test_serialize_text_event(); test_serialize_long_text_event(); test_serialize_mouse_event(); test_serialize_scroll_event(); - return 0; } diff --git a/app/tests/test_strutil.c b/app/tests/test_strutil.c index 6dbc2ff3..1dd7fbbe 100644 --- a/app/tests/test_strutil.c +++ b/app/tests/test_strutil.c @@ -1,9 +1,9 @@ #include #include -#include "strutil.h" +#include "str_util.h" -static void test_xstrncpy_simple() { +static void test_xstrncpy_simple(void) { char s[] = "xxxxxxxxxx"; size_t w = xstrncpy(s, "abcdef", sizeof(s)); @@ -20,7 +20,7 @@ static void test_xstrncpy_simple() { assert(!strcmp("abcdef", s)); } -static void test_xstrncpy_just_fit() { +static void test_xstrncpy_just_fit(void) { char s[] = "xxxxxx"; size_t w = xstrncpy(s, "abcdef", sizeof(s)); @@ -34,7 +34,7 @@ static void test_xstrncpy_just_fit() { assert(!strcmp("abcdef", s)); } -static void test_xstrncpy_truncated() { +static void test_xstrncpy_truncated(void) { char s[] = "xxx"; size_t w = xstrncpy(s, "abcdef", sizeof(s)); @@ -48,7 +48,7 @@ static void test_xstrncpy_truncated() { assert(!strncmp("abcdef", s, 3)); } -static void test_xstrjoin_simple() { +static void test_xstrjoin_simple(void) { const char *const tokens[] = { "abc", "de", "fghi", NULL }; char s[] = "xxxxxxxxxxxxxx"; size_t w = xstrjoin(s, tokens, ' ', sizeof(s)); @@ -66,7 +66,7 @@ static void test_xstrjoin_simple() { assert(!strcmp("abc de fghi", s)); } -static void test_xstrjoin_just_fit() { +static void test_xstrjoin_just_fit(void) { const char *const tokens[] = { "abc", "de", "fghi", NULL }; char s[] = "xxxxxxxxxxx"; size_t w = xstrjoin(s, tokens, ' ', sizeof(s)); @@ -81,7 +81,7 @@ static void test_xstrjoin_just_fit() { assert(!strcmp("abc de fghi", s)); } -static void test_xstrjoin_truncated_in_token() { +static void test_xstrjoin_truncated_in_token(void) { const char *const tokens[] = { "abc", "de", "fghi", NULL }; char s[] = "xxxxx"; size_t w = xstrjoin(s, tokens, ' ', sizeof(s)); @@ -96,7 +96,7 @@ static void test_xstrjoin_truncated_in_token() { assert(!strcmp("abc d", s)); } -static void test_xstrjoin_truncated_before_sep() { +static void test_xstrjoin_truncated_before_sep(void) { const char *const tokens[] = { "abc", "de", "fghi", NULL }; char s[] = "xxxxxx"; size_t w = xstrjoin(s, tokens, ' ', sizeof(s)); @@ -111,7 +111,7 @@ static void test_xstrjoin_truncated_before_sep() { assert(!strcmp("abc de", s)); } -static void test_xstrjoin_truncated_after_sep() { +static void test_xstrjoin_truncated_after_sep(void) { const char *const tokens[] = { "abc", "de", "fghi", NULL }; char s[] = "xxxxxxx"; size_t w = xstrjoin(s, tokens, ' ', sizeof(s)); @@ -126,7 +126,7 @@ static void test_xstrjoin_truncated_after_sep() { assert(!strcmp("abc de ", s)); } -int main() { +int main(void) { test_xstrncpy_simple(); test_xstrncpy_just_fit(); test_xstrncpy_truncated(); diff --git a/cross_win32.txt b/cross_win32.txt index 8cb650aa..05167a33 100644 --- a/cross_win32.txt +++ b/cross_win32.txt @@ -15,6 +15,6 @@ cpu = 'i686' endian = 'little' [properties] -prebuilt_ffmpeg_shared = 'ffmpeg-4.0-win32-shared' -prebuilt_ffmpeg_dev = 'ffmpeg-4.0-win32-dev' +prebuilt_ffmpeg_shared = 'ffmpeg-4.0.2-win32-shared' +prebuilt_ffmpeg_dev = 'ffmpeg-4.0.2-win32-dev' prebuilt_sdl2 = 'SDL2-2.0.8/i686-w64-mingw32' diff --git a/cross_win64.txt b/cross_win64.txt index 0110d5bb..f358b56d 100644 --- a/cross_win64.txt +++ b/cross_win64.txt @@ -15,6 +15,6 @@ cpu = 'x86_64' endian = 'little' [properties] -prebuilt_ffmpeg_shared = 'ffmpeg-4.0-win64-shared' -prebuilt_ffmpeg_dev = 'ffmpeg-4.0-win64-dev' +prebuilt_ffmpeg_shared = 'ffmpeg-4.0.2-win64-shared' +prebuilt_ffmpeg_dev = 'ffmpeg-4.0.2-win64-dev' prebuilt_sdl2 = 'SDL2-2.0.8/x86_64-w64-mingw32' diff --git a/prebuilt-deps/Makefile b/prebuilt-deps/Makefile index 557cc675..c49b9211 100644 --- a/prebuilt-deps/Makefile +++ b/prebuilt-deps/Makefile @@ -10,24 +10,24 @@ prepare-win32: prepare-sdl2 prepare-ffmpeg-shared-win32 prepare-ffmpeg-dev-win32 prepare-win64: prepare-sdl2 prepare-ffmpeg-shared-win64 prepare-ffmpeg-dev-win64 prepare-adb prepare-ffmpeg-shared-win32: - @./prepare-dep https://ffmpeg.zeranoe.com/builds/win32/shared/ffmpeg-4.0-win32-shared.zip \ - 530c92df0ca14c35901b4b681847d62da3c50a0cc9b7ced37b04968f6b5c243d \ - ffmpeg-4.0-win32-shared + @./prepare-dep https://ffmpeg.zeranoe.com/builds/win32/shared/ffmpeg-4.0.2-win32-shared.zip \ + cc190a3a4cf7bfbd4fbaa92609c1501a1de458055e6cfea8b745c1d515013aa8 \ + ffmpeg-4.0.2-win32-shared prepare-ffmpeg-dev-win32: - @./prepare-dep https://ffmpeg.zeranoe.com/builds/win32/dev/ffmpeg-4.0-win32-dev.zip \ - e2f5200b5e73c4d0abb9b89c4ffc0438f92a0aadc54c81cf57e18c81a9f11c6b \ - ffmpeg-4.0-win32-dev + @./prepare-dep https://ffmpeg.zeranoe.com/builds/win32/dev/ffmpeg-4.0.2-win32-dev.zip \ + c72c74bad74ac0541f1b43090c26a50017c49041c182a703abd2057bb8cdc238 \ + ffmpeg-4.0.2-win32-dev prepare-ffmpeg-shared-win64: - @./prepare-dep https://ffmpeg.zeranoe.com/builds/win64/shared/ffmpeg-4.0-win64-shared.zip \ - 8fe2d344463dbefc2db4239a4203a55ed0324faceaae57276a40c4fabda84c37 \ - ffmpeg-4.0-win64-shared + @./prepare-dep https://ffmpeg.zeranoe.com/builds/win64/shared/ffmpeg-4.0.2-win64-shared.zip \ + ede566aca8b5348dff85570f9638c6bad33209f9419f79db7bde7daa37599bff \ + ffmpeg-4.0.2-win64-shared prepare-ffmpeg-dev-win64: - @./prepare-dep https://ffmpeg.zeranoe.com/builds/win64/dev/ffmpeg-4.0-win64-dev.zip \ - facced738eabfc53fa92834dea8b24426f64db61298688fed480145945be07fa \ - ffmpeg-4.0-win64-dev + @./prepare-dep https://ffmpeg.zeranoe.com/builds/win64/dev/ffmpeg-4.0.2-win64-dev.zip \ + 23ee994161c51285cb956b98d3caa499d48083dae7b26c1fdf77f22e98df1c5f \ + ffmpeg-4.0.2-win64-dev prepare-sdl2: @./prepare-dep https://libsdl.org/release/SDL2-devel-2.0.8-mingw.tar.gz \ @@ -35,6 +35,6 @@ prepare-sdl2: SDL2-2.0.8 prepare-adb: - @./prepare-dep https://dl.google.com/android/repository/platform-tools_r27.0.1-windows.zip \ - 880662adfb0d6911ff250b9e13930ae1a4110fc36d5866afd4f8f56d935f7939 \ + @./prepare-dep https://dl.google.com/android/repository/platform-tools_r28.0.1-windows.zip \ + db78f726d5dc653706dcd15a462ab1b946c643f598df76906c4c1858411c54df \ platform-tools diff --git a/server/build.gradle b/server/build.gradle index 3e1c67e4..957a61d7 100644 --- a/server/build.gradle +++ b/server/build.gradle @@ -6,8 +6,8 @@ android { applicationId "com.genymobile.scrcpy" minSdkVersion 21 targetSdkVersion 27 - versionCode 4 - versionName "1.3" + versionCode 5 + versionName "1.4" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" } buildTypes { diff --git a/server/src/main/java/com/genymobile/scrcpy/DesktopConnection.java b/server/src/main/java/com/genymobile/scrcpy/DesktopConnection.java index d5740c15..d87a7fd8 100644 --- a/server/src/main/java/com/genymobile/scrcpy/DesktopConnection.java +++ b/server/src/main/java/com/genymobile/scrcpy/DesktopConnection.java @@ -5,9 +5,9 @@ import android.net.LocalSocket; import android.net.LocalSocketAddress; import java.io.Closeable; +import java.io.FileDescriptor; import java.io.IOException; import java.io.InputStream; -import java.io.OutputStream; import java.nio.charset.StandardCharsets; public final class DesktopConnection implements Closeable { @@ -18,14 +18,14 @@ public final class DesktopConnection implements Closeable { private final LocalSocket socket; private final InputStream inputStream; - private final OutputStream outputStream; + private final FileDescriptor fd; private final ControlEventReader reader = new ControlEventReader(); private DesktopConnection(LocalSocket socket) throws IOException { this.socket = socket; inputStream = socket.getInputStream(); - outputStream = socket.getOutputStream(); + fd = socket.getFileDescriptor(); } private static LocalSocket connect(String abstractName) throws IOException { @@ -78,11 +78,11 @@ public final class DesktopConnection implements Closeable { buffer[DEVICE_NAME_FIELD_LENGTH + 1] = (byte) width; buffer[DEVICE_NAME_FIELD_LENGTH + 2] = (byte) (height >> 8); buffer[DEVICE_NAME_FIELD_LENGTH + 3] = (byte) height; - outputStream.write(buffer, 0, buffer.length); + IO.writeFully(fd, buffer, 0, buffer.length); } - public OutputStream getOutputStream() { - return outputStream; + public FileDescriptor getFd() { + return fd; } public ControlEvent receiveControlEvent() throws IOException { diff --git a/server/src/main/java/com/genymobile/scrcpy/IO.java b/server/src/main/java/com/genymobile/scrcpy/IO.java new file mode 100644 index 00000000..bfd48be2 --- /dev/null +++ b/server/src/main/java/com/genymobile/scrcpy/IO.java @@ -0,0 +1,31 @@ +package com.genymobile.scrcpy; + +import android.system.ErrnoException; +import android.system.Os; +import android.system.OsConstants; + +import java.io.FileDescriptor; +import java.io.IOException; +import java.nio.ByteBuffer; + +public class IO { + private IO() { + // not instantiable + } + + public static void writeFully(FileDescriptor fd, ByteBuffer from) throws IOException { + while (from.hasRemaining()) { + try { + Os.write(fd, from); + } catch (ErrnoException e) { + if (e.errno != OsConstants.EINTR) { + throw new IOException(e); + } + } + } + } + + public static void writeFully(FileDescriptor fd, byte[] buffer, int offset, int len) throws IOException { + writeFully(fd, ByteBuffer.wrap(buffer, offset, len)); + } +} diff --git a/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java b/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java index e2ee8122..636bbb00 100644 --- a/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java +++ b/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java @@ -9,8 +9,8 @@ import android.media.MediaFormat; import android.os.IBinder; import android.view.Surface; +import java.io.FileDescriptor; import java.io.IOException; -import java.io.OutputStream; import java.nio.ByteBuffer; import java.util.concurrent.atomic.AtomicBoolean; @@ -48,7 +48,7 @@ public class ScreenEncoder implements Device.RotationListener { return rotationChanged.getAndSet(false); } - public void streamScreen(Device device, OutputStream outputStream) throws IOException { + public void streamScreen(Device device, FileDescriptor fd) throws IOException { MediaFormat format = createFormat(bitRate, frameRate, iFrameInterval); device.setRotationListener(this); boolean alive; @@ -64,7 +64,7 @@ public class ScreenEncoder implements Device.RotationListener { setDisplaySurface(display, surface, contentRect, videoRect); codec.start(); try { - alive = encode(codec, outputStream); + alive = encode(codec, fd); } finally { codec.stop(); destroyDisplay(display); @@ -77,9 +77,7 @@ public class ScreenEncoder implements Device.RotationListener { } } - private boolean encode(MediaCodec codec, OutputStream outputStream) throws IOException { - @SuppressWarnings("checkstyle:MagicNumber") - byte[] buf = new byte[bitRate / 8]; // may contain up to 1 second of video + private boolean encode(MediaCodec codec, FileDescriptor fd) throws IOException { boolean eof = false; MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo(); while (!consumeRotationChange() && !eof) { @@ -91,15 +89,8 @@ public class ScreenEncoder implements Device.RotationListener { break; } if (outputBufferId >= 0) { - ByteBuffer outputBuffer = codec.getOutputBuffer(outputBufferId); - while (outputBuffer.hasRemaining()) { - int remaining = outputBuffer.remaining(); - int len = Math.min(buf.length, remaining); - // the outputBuffer is probably direct (it has no underlying array), and LocalSocket does not expose channels, - // so we must copy the data locally to write them manually to the output stream - outputBuffer.get(buf, 0, len); - outputStream.write(buf, 0, len); - } + ByteBuffer codecBuffer = codec.getOutputBuffer(outputBufferId); + IO.writeFully(fd, codecBuffer); } } finally { if (outputBufferId >= 0) { diff --git a/server/src/main/java/com/genymobile/scrcpy/Server.java b/server/src/main/java/com/genymobile/scrcpy/Server.java index 9fd93863..b218e83d 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Server.java +++ b/server/src/main/java/com/genymobile/scrcpy/Server.java @@ -21,7 +21,7 @@ public final class Server { try { // synchronous - screenEncoder.streamScreen(device, connection.getOutputStream()); + screenEncoder.streamScreen(device, connection.getFd()); } catch (IOException e) { // this is expected on close Ln.d("Screen streaming stopped");