From e2a7abcd5349fc986696edbd991575e0cd492231 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Wed, 7 Mar 2018 15:29:33 +0100 Subject: [PATCH] Implement clipboard paste Paste computer clipboard to the device on Ctrl+v. The other direction (pasting the device clipboard to the computer) is not implemented. It would require a communication channel from the device to the computer, other than the socket used by the video stream. --- README.md | 1 + app/src/controlevent.c | 15 +++++++-- app/src/controlevent.h | 6 ++-- app/src/controller.c | 4 ++- app/src/inputmanager.c | 31 +++++++++++++++++-- app/src/main.c | 3 ++ .../genymobile/scrcpy/ControlEventReader.java | 4 +-- .../genymobile/scrcpy/EventController.java | 17 +++++----- 8 files changed, 62 insertions(+), 19 deletions(-) diff --git a/README.md b/README.md index e2ac7eb0..e197c498 100644 --- a/README.md +++ b/README.md @@ -182,6 +182,7 @@ If several devices are listed in `adb devices`, you must specify the _serial_: | click on `VOLUME_DOWN` | `Ctrl`+`-` | | click on `POWER` | `Ctrl`+`p` | | turn screen on | _Right-click_ | + | paste computer clipboard to device | `Ctrl`+`v` | | enable/disable FPS counter (on stdout) | `Ctrl`+`i` | diff --git a/app/src/controlevent.c b/app/src/controlevent.c index e88ab936..d288cee4 100644 --- a/app/src/controlevent.c +++ b/app/src/controlevent.c @@ -36,10 +36,11 @@ int control_event_serialize(const struct control_event *event, unsigned char *bu // write length (1 byte) + date (non nul-terminated) size_t len = strlen(event->text_event.text); if (len > TEXT_MAX_LENGTH) { + // injecting a text takes time, so limit the text length len = TEXT_MAX_LENGTH; } buf[1] = (Uint8) len; - memcpy(&buf[2], &event->text_event.text, len); + memcpy(&buf[2], event->text_event.text, len); return 2 + len; } case CONTROL_EVENT_TYPE_MOUSE: @@ -61,6 +62,12 @@ int control_event_serialize(const struct control_event *event, unsigned char *bu } } +void control_event_destroy(struct control_event *event) { + if (event->type == CONTROL_EVENT_TYPE_TEXT) { + SDL_free(event->text_event.text); + } +} + SDL_bool control_event_queue_is_empty(const struct control_event_queue *queue) { return queue->head == queue->tail; } @@ -77,7 +84,11 @@ SDL_bool control_event_queue_init(struct control_event_queue *queue) { } void control_event_queue_destroy(struct control_event_queue *queue) { - // nothing to do in the current implementation + int i = queue->tail; + while (i != queue->head) { + control_event_destroy(&queue->data[i]); + i = (i + 1) % CONTROL_EVENT_QUEUE_SIZE; + } } SDL_bool control_event_queue_push(struct control_event_queue *queue, const struct control_event *event) { diff --git a/app/src/controlevent.h b/app/src/controlevent.h index 6b87294d..7c52d6e0 100644 --- a/app/src/controlevent.h +++ b/app/src/controlevent.h @@ -10,7 +10,7 @@ #define CONTROL_EVENT_QUEUE_SIZE 64 #define SERIALIZED_EVENT_MAX_SIZE 33 -#define TEXT_MAX_LENGTH 31 +#define TEXT_MAX_LENGTH 256 enum control_event_type { CONTROL_EVENT_TYPE_KEYCODE, @@ -31,7 +31,7 @@ struct control_event { enum android_metastate metastate; } keycode_event; struct { - char text[TEXT_MAX_LENGTH + 1]; // nul-terminated string + char *text; // owned, to be freed by SDL_free() } text_event; struct { enum android_motionevent_action action; @@ -68,4 +68,6 @@ SDL_bool control_event_queue_is_full(const struct control_event_queue *queue); SDL_bool control_event_queue_push(struct control_event_queue *queue, const struct control_event *event); SDL_bool control_event_queue_take(struct control_event_queue *queue, struct control_event *event); +void control_event_destroy(struct control_event *event); + #endif diff --git a/app/src/controller.c b/app/src/controller.c index 2de0e86f..a8ef9f46 100644 --- a/app/src/controller.c +++ b/app/src/controller.c @@ -65,7 +65,9 @@ static int run_controller(void *data) { } struct control_event event; while (control_event_queue_take(&controller->queue, &event)) { - if (!process_event(controller, &event)) { + SDL_bool ok = process_event(controller, &event); + control_event_destroy(&event); + if (!ok) { LOGD("Cannot write event to socket"); goto end; } diff --git a/app/src/inputmanager.c b/app/src/inputmanager.c index ff9120ab..81fa9619 100644 --- a/app/src/inputmanager.c +++ b/app/src/inputmanager.c @@ -100,6 +100,27 @@ static void switch_fps_counter_state(struct frames *frames) { mutex_unlock(frames->mutex); } +static void clipboard_paste(struct controller *controller) { + char *text = SDL_GetClipboardText(); + if (!text) { + LOGW("Cannot get clipboard text: %s", SDL_GetError()); + return; + } + if (!*text) { + // empty text + SDL_free(text); + return; + } + + struct control_event control_event; + control_event.type = CONTROL_EVENT_TYPE_TEXT; + control_event.text_event.text = text; + if (!controller_push_event(controller, &control_event)) { + SDL_free(text); + LOGW("Cannot send clipboard paste event"); + } +} + void input_manager_process_text_input(struct input_manager *input_manager, const SDL_TextInputEvent *event) { if (is_ctrl_down()) { @@ -116,8 +137,11 @@ void input_manager_process_text_input(struct input_manager *input_manager, struct control_event control_event; control_event.type = CONTROL_EVENT_TYPE_TEXT; - strncpy(control_event.text_event.text, event->text, TEXT_MAX_LENGTH); - control_event.text_event.text[TEXT_MAX_LENGTH] = '\0'; + control_event.text_event.text = SDL_strdup(event->text); + if (!control_event.text_event.text) { + LOGW("Cannot strdup input text"); + return; + } if (!controller_push_event(input_manager->controller, &control_event)) { LOGW("Cannot send text event"); } @@ -157,6 +181,9 @@ void input_manager_process_key(struct input_manager *input_manager, case SDLK_p: action_power(input_manager->controller); return; + case SDLK_v: + clipboard_paste(input_manager->controller); + return; case SDLK_f: screen_switch_fullscreen(input_manager->screen); return; diff --git a/app/src/main.c b/app/src/main.c index 46abd845..1f2164b6 100644 --- a/app/src/main.c +++ b/app/src/main.c @@ -82,6 +82,9 @@ static void usage(const char *arg0) { " Right-click\n" " turn screen on\n" "\n" + " Ctrl+v\n" + " paste computer clipboard to device\n" + "\n" " Ctrl+i\n" " enable/disable FPS counter (print frames/second in logs)\n" "\n", diff --git a/server/src/main/java/com/genymobile/scrcpy/ControlEventReader.java b/server/src/main/java/com/genymobile/scrcpy/ControlEventReader.java index c752c6cd..c1250896 100644 --- a/server/src/main/java/com/genymobile/scrcpy/ControlEventReader.java +++ b/server/src/main/java/com/genymobile/scrcpy/ControlEventReader.java @@ -13,12 +13,12 @@ public class ControlEventReader { private static final int SCROLL_PAYLOAD_LENGTH = 16; private static final int COMMAND_PAYLOAD_LENGTH = 1; - private static final int MAX_TEXT_LENGTH = 32; + private static final int TEXT_MAX_LENGTH = 256; private static final int RAW_BUFFER_SIZE = 128; private final byte[] rawBuffer = new byte[RAW_BUFFER_SIZE]; private final ByteBuffer buffer = ByteBuffer.wrap(rawBuffer); - private final byte[] textBuffer = new byte[MAX_TEXT_LENGTH]; + private final byte[] textBuffer = new byte[TEXT_MAX_LENGTH]; public ControlEventReader() { // invariant: the buffer is always in "get" mode diff --git a/server/src/main/java/com/genymobile/scrcpy/EventController.java b/server/src/main/java/com/genymobile/scrcpy/EventController.java index 8787a048..a7733f96 100644 --- a/server/src/main/java/com/genymobile/scrcpy/EventController.java +++ b/server/src/main/java/com/genymobile/scrcpy/EventController.java @@ -93,14 +93,12 @@ public class EventController { return injectKeyEvent(action, keycode, 0, metaState); } - private boolean injectText(String text) { - return injectText(text, true); - } - - private boolean injectText(String text, boolean decomposeOnFailure) { - KeyEvent[] events = charMap.getEvents(text.toCharArray()); + private boolean injectChar(char c) { + String decomposed = KeyComposition.decompose(c); + char[] chars = decomposed != null ? decomposed.toCharArray() : new char[] {c}; + KeyEvent[] events = charMap.getEvents(chars); if (events == null) { - return decomposeOnFailure ? injectDecomposition(text) : false; + return false; } for (KeyEvent event : events) { if (!injectEvent(event)) { @@ -110,10 +108,9 @@ public class EventController { return true; } - private boolean injectDecomposition(String text) { + private boolean injectText(String text) { for (char c : text.toCharArray()) { - String composedText = KeyComposition.decompose(c); - if (composedText == null || !injectText(composedText, false)) { + if (!injectChar(c)) { return false; } }