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:
parent
e4d64e8752
commit
e2a7abcd53
8 changed files with 62 additions and 19 deletions
|
@ -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` |
|
||||
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue