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 `POWER` | `Ctrl`+`p` |
| turn screen on | _Right-click_ |
| paste computer clipboard to device | `Ctrl`+`v` |
| 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)
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) {

View file

@ -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

View file

@ -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;
}

View file

@ -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;

View file

@ -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",

View file

@ -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

View file

@ -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;
}
}