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.
This commit is contained in:
Romain Vimont 2018-03-07 15:29:33 +01:00
parent e4d64e8752
commit e2a7abcd53
8 changed files with 62 additions and 19 deletions

View file

@ -182,6 +182,7 @@ If several devices are listed in `adb devices`, you must specify the _serial_:
| click on `VOLUME_DOWN` | `Ctrl`+`-` | | click on `VOLUME_DOWN` | `Ctrl`+`-` |
| click on `POWER` | `Ctrl`+`p` | | click on `POWER` | `Ctrl`+`p` |
| turn screen on | _Right-click_ | | turn screen on | _Right-click_ |
| paste computer clipboard to device | `Ctrl`+`v` |
| enable/disable FPS counter (on stdout) | `Ctrl`+`i` | | enable/disable FPS counter (on stdout) | `Ctrl`+`i` |

View file

@ -36,10 +36,11 @@ int control_event_serialize(const struct control_event *event, unsigned char *bu
// write length (1 byte) + date (non nul-terminated) // write length (1 byte) + date (non nul-terminated)
size_t len = strlen(event->text_event.text); size_t len = strlen(event->text_event.text);
if (len > TEXT_MAX_LENGTH) { if (len > TEXT_MAX_LENGTH) {
// injecting a text takes time, so limit the text length
len = TEXT_MAX_LENGTH; len = TEXT_MAX_LENGTH;
} }
buf[1] = (Uint8) len; buf[1] = (Uint8) len;
memcpy(&buf[2], &event->text_event.text, len); memcpy(&buf[2], event->text_event.text, len);
return 2 + len; return 2 + len;
} }
case CONTROL_EVENT_TYPE_MOUSE: 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) { SDL_bool control_event_queue_is_empty(const struct control_event_queue *queue) {
return queue->head == queue->tail; 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) { 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) { SDL_bool control_event_queue_push(struct control_event_queue *queue, const struct control_event *event) {

View file

@ -10,7 +10,7 @@
#define CONTROL_EVENT_QUEUE_SIZE 64 #define CONTROL_EVENT_QUEUE_SIZE 64
#define SERIALIZED_EVENT_MAX_SIZE 33 #define SERIALIZED_EVENT_MAX_SIZE 33
#define TEXT_MAX_LENGTH 31 #define TEXT_MAX_LENGTH 256
enum control_event_type { enum control_event_type {
CONTROL_EVENT_TYPE_KEYCODE, CONTROL_EVENT_TYPE_KEYCODE,
@ -31,7 +31,7 @@ struct control_event {
enum android_metastate metastate; enum android_metastate metastate;
} keycode_event; } keycode_event;
struct { struct {
char text[TEXT_MAX_LENGTH + 1]; // nul-terminated string char *text; // owned, to be freed by SDL_free()
} text_event; } text_event;
struct { struct {
enum android_motionevent_action action; 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_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); SDL_bool control_event_queue_take(struct control_event_queue *queue, struct control_event *event);
void control_event_destroy(struct control_event *event);
#endif #endif

View file

@ -65,7 +65,9 @@ static int run_controller(void *data) {
} }
struct control_event event; struct control_event event;
while (control_event_queue_take(&controller->queue, &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"); LOGD("Cannot write event to socket");
goto end; goto end;
} }

View file

@ -100,6 +100,27 @@ static void switch_fps_counter_state(struct frames *frames) {
mutex_unlock(frames->mutex); 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, void input_manager_process_text_input(struct input_manager *input_manager,
const SDL_TextInputEvent *event) { const SDL_TextInputEvent *event) {
if (is_ctrl_down()) { if (is_ctrl_down()) {
@ -116,8 +137,11 @@ void input_manager_process_text_input(struct input_manager *input_manager,
struct control_event control_event; struct control_event control_event;
control_event.type = CONTROL_EVENT_TYPE_TEXT; control_event.type = CONTROL_EVENT_TYPE_TEXT;
strncpy(control_event.text_event.text, event->text, TEXT_MAX_LENGTH); control_event.text_event.text = SDL_strdup(event->text);
control_event.text_event.text[TEXT_MAX_LENGTH] = '\0'; if (!control_event.text_event.text) {
LOGW("Cannot strdup input text");
return;
}
if (!controller_push_event(input_manager->controller, &control_event)) { if (!controller_push_event(input_manager->controller, &control_event)) {
LOGW("Cannot send text event"); LOGW("Cannot send text event");
} }
@ -157,6 +181,9 @@ void input_manager_process_key(struct input_manager *input_manager,
case SDLK_p: case SDLK_p:
action_power(input_manager->controller); action_power(input_manager->controller);
return; return;
case SDLK_v:
clipboard_paste(input_manager->controller);
return;
case SDLK_f: case SDLK_f:
screen_switch_fullscreen(input_manager->screen); screen_switch_fullscreen(input_manager->screen);
return; return;

View file

@ -82,6 +82,9 @@ static void usage(const char *arg0) {
" Right-click\n" " Right-click\n"
" turn screen on\n" " turn screen on\n"
"\n" "\n"
" Ctrl+v\n"
" paste computer clipboard to device\n"
"\n"
" Ctrl+i\n" " Ctrl+i\n"
" enable/disable FPS counter (print frames/second in logs)\n" " enable/disable FPS counter (print frames/second in logs)\n"
"\n", "\n",

View file

@ -13,12 +13,12 @@ public class ControlEventReader {
private static final int SCROLL_PAYLOAD_LENGTH = 16; private static final int SCROLL_PAYLOAD_LENGTH = 16;
private static final int COMMAND_PAYLOAD_LENGTH = 1; 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 static final int RAW_BUFFER_SIZE = 128;
private final byte[] rawBuffer = new byte[RAW_BUFFER_SIZE]; private final byte[] rawBuffer = new byte[RAW_BUFFER_SIZE];
private final ByteBuffer buffer = ByteBuffer.wrap(rawBuffer); 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() { public ControlEventReader() {
// invariant: the buffer is always in "get" mode // invariant: the buffer is always in "get" mode

View file

@ -93,14 +93,12 @@ public class EventController {
return injectKeyEvent(action, keycode, 0, metaState); return injectKeyEvent(action, keycode, 0, metaState);
} }
private boolean injectText(String text) { private boolean injectChar(char c) {
return injectText(text, true); String decomposed = KeyComposition.decompose(c);
} char[] chars = decomposed != null ? decomposed.toCharArray() : new char[] {c};
KeyEvent[] events = charMap.getEvents(chars);
private boolean injectText(String text, boolean decomposeOnFailure) {
KeyEvent[] events = charMap.getEvents(text.toCharArray());
if (events == null) { if (events == null) {
return decomposeOnFailure ? injectDecomposition(text) : false; return false;
} }
for (KeyEvent event : events) { for (KeyEvent event : events) {
if (!injectEvent(event)) { if (!injectEvent(event)) {
@ -110,10 +108,9 @@ public class EventController {
return true; return true;
} }
private boolean injectDecomposition(String text) { private boolean injectText(String text) {
for (char c : text.toCharArray()) { for (char c : text.toCharArray()) {
String composedText = KeyComposition.decompose(c); if (!injectChar(c)) {
if (composedText == null || !injectText(composedText, false)) {
return false; return false;
} }
} }