mirror of https://github.com/Genymobile/scrcpy
Merge branch 'dev'
commit
c2df0228a3
@ -0,0 +1,50 @@
|
|||||||
|
// generic circular buffer (bounded queue) implementation
|
||||||
|
#ifndef CBUF_H
|
||||||
|
#define CBUF_H
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
// To define a circular buffer type of 20 ints:
|
||||||
|
// typedef CBUF(int, 20) my_cbuf_t;
|
||||||
|
//
|
||||||
|
// data has length CAP + 1 to distinguish empty vs full.
|
||||||
|
#define CBUF(TYPE, CAP) { \
|
||||||
|
TYPE data[(CAP) + 1]; \
|
||||||
|
size_t head; \
|
||||||
|
size_t tail; \
|
||||||
|
}
|
||||||
|
|
||||||
|
#define cbuf_size_(PCBUF) \
|
||||||
|
(sizeof((PCBUF)->data) / sizeof(*(PCBUF)->data))
|
||||||
|
|
||||||
|
#define cbuf_is_empty(PCBUF) \
|
||||||
|
((PCBUF)->head == (PCBUF)->tail)
|
||||||
|
|
||||||
|
#define cbuf_is_full(PCBUF) \
|
||||||
|
(((PCBUF)->head + 1) % cbuf_size_(PCBUF) == (PCBUF)->tail)
|
||||||
|
|
||||||
|
#define cbuf_init(PCBUF) \
|
||||||
|
(void) ((PCBUF)->head = (PCBUF)->tail = 0)
|
||||||
|
|
||||||
|
#define cbuf_push(PCBUF, ITEM) \
|
||||||
|
({ \
|
||||||
|
bool ok = !cbuf_is_full(PCBUF); \
|
||||||
|
if (ok) { \
|
||||||
|
(PCBUF)->data[(PCBUF)->head] = (ITEM); \
|
||||||
|
(PCBUF)->head = ((PCBUF)->head + 1) % cbuf_size_(PCBUF); \
|
||||||
|
} \
|
||||||
|
ok; \
|
||||||
|
}) \
|
||||||
|
|
||||||
|
#define cbuf_take(PCBUF, PITEM) \
|
||||||
|
({ \
|
||||||
|
bool ok = !cbuf_is_empty(PCBUF); \
|
||||||
|
if (ok) { \
|
||||||
|
*(PITEM) = (PCBUF)->data[(PCBUF)->tail]; \
|
||||||
|
(PCBUF)->tail = ((PCBUF)->tail + 1) % cbuf_size_(PCBUF); \
|
||||||
|
} \
|
||||||
|
ok; \
|
||||||
|
})
|
||||||
|
|
||||||
|
#endif
|
@ -1,110 +0,0 @@
|
|||||||
#include "control_event.h"
|
|
||||||
|
|
||||||
#include <string.h>
|
|
||||||
|
|
||||||
#include "buffer_util.h"
|
|
||||||
#include "lock_util.h"
|
|
||||||
#include "log.h"
|
|
||||||
|
|
||||||
static void
|
|
||||||
write_position(uint8_t *buf, const struct position *position) {
|
|
||||||
buffer_write32be(&buf[0], position->point.x);
|
|
||||||
buffer_write32be(&buf[4], position->point.y);
|
|
||||||
buffer_write16be(&buf[8], position->screen_size.width);
|
|
||||||
buffer_write16be(&buf[10], position->screen_size.height);
|
|
||||||
}
|
|
||||||
|
|
||||||
int
|
|
||||||
control_event_serialize(const struct control_event *event, unsigned char *buf) {
|
|
||||||
buf[0] = event->type;
|
|
||||||
switch (event->type) {
|
|
||||||
case CONTROL_EVENT_TYPE_KEYCODE:
|
|
||||||
buf[1] = event->keycode_event.action;
|
|
||||||
buffer_write32be(&buf[2], event->keycode_event.keycode);
|
|
||||||
buffer_write32be(&buf[6], event->keycode_event.metastate);
|
|
||||||
return 10;
|
|
||||||
case CONTROL_EVENT_TYPE_TEXT: {
|
|
||||||
// write length (2 bytes) + string (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;
|
|
||||||
}
|
|
||||||
buffer_write16be(&buf[1], (uint16_t) len);
|
|
||||||
memcpy(&buf[3], event->text_event.text, len);
|
|
||||||
return 3 + len;
|
|
||||||
}
|
|
||||||
case CONTROL_EVENT_TYPE_MOUSE:
|
|
||||||
buf[1] = event->mouse_event.action;
|
|
||||||
buffer_write32be(&buf[2], event->mouse_event.buttons);
|
|
||||||
write_position(&buf[6], &event->mouse_event.position);
|
|
||||||
return 18;
|
|
||||||
case CONTROL_EVENT_TYPE_SCROLL:
|
|
||||||
write_position(&buf[1], &event->scroll_event.position);
|
|
||||||
buffer_write32be(&buf[13], (uint32_t) event->scroll_event.hscroll);
|
|
||||||
buffer_write32be(&buf[17], (uint32_t) event->scroll_event.vscroll);
|
|
||||||
return 21;
|
|
||||||
case CONTROL_EVENT_TYPE_COMMAND:
|
|
||||||
buf[1] = event->command_event.action;
|
|
||||||
return 2;
|
|
||||||
default:
|
|
||||||
LOGW("Unknown event type: %u", (unsigned) event->type);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
control_event_destroy(struct control_event *event) {
|
|
||||||
if (event->type == CONTROL_EVENT_TYPE_TEXT) {
|
|
||||||
SDL_free(event->text_event.text);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool
|
|
||||||
control_event_queue_is_empty(const struct control_event_queue *queue) {
|
|
||||||
return queue->head == queue->tail;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool
|
|
||||||
control_event_queue_is_full(const struct control_event_queue *queue) {
|
|
||||||
return (queue->head + 1) % CONTROL_EVENT_QUEUE_SIZE == queue->tail;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool
|
|
||||||
control_event_queue_init(struct control_event_queue *queue) {
|
|
||||||
queue->head = 0;
|
|
||||||
queue->tail = 0;
|
|
||||||
// the current implementation may not fail
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
control_event_queue_destroy(struct control_event_queue *queue) {
|
|
||||||
int i = queue->tail;
|
|
||||||
while (i != queue->head) {
|
|
||||||
control_event_destroy(&queue->data[i]);
|
|
||||||
i = (i + 1) % CONTROL_EVENT_QUEUE_SIZE;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool
|
|
||||||
control_event_queue_push(struct control_event_queue *queue,
|
|
||||||
const struct control_event *event) {
|
|
||||||
if (control_event_queue_is_full(queue)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
queue->data[queue->head] = *event;
|
|
||||||
queue->head = (queue->head + 1) % CONTROL_EVENT_QUEUE_SIZE;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool
|
|
||||||
control_event_queue_take(struct control_event_queue *queue,
|
|
||||||
struct control_event *event) {
|
|
||||||
if (control_event_queue_is_empty(queue)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
*event = queue->data[queue->tail];
|
|
||||||
queue->tail = (queue->tail + 1) % CONTROL_EVENT_QUEUE_SIZE;
|
|
||||||
return true;
|
|
||||||
}
|
|
@ -1,91 +0,0 @@
|
|||||||
#ifndef CONTROLEVENT_H
|
|
||||||
#define CONTROLEVENT_H
|
|
||||||
|
|
||||||
#include <stdbool.h>
|
|
||||||
#include <stdint.h>
|
|
||||||
#include <SDL2/SDL_mutex.h>
|
|
||||||
|
|
||||||
#include "android/input.h"
|
|
||||||
#include "android/keycodes.h"
|
|
||||||
#include "common.h"
|
|
||||||
|
|
||||||
#define CONTROL_EVENT_QUEUE_SIZE 64
|
|
||||||
#define TEXT_MAX_LENGTH 300
|
|
||||||
#define SERIALIZED_EVENT_MAX_SIZE 3 + TEXT_MAX_LENGTH
|
|
||||||
|
|
||||||
enum control_event_type {
|
|
||||||
CONTROL_EVENT_TYPE_KEYCODE,
|
|
||||||
CONTROL_EVENT_TYPE_TEXT,
|
|
||||||
CONTROL_EVENT_TYPE_MOUSE,
|
|
||||||
CONTROL_EVENT_TYPE_SCROLL,
|
|
||||||
CONTROL_EVENT_TYPE_COMMAND,
|
|
||||||
};
|
|
||||||
|
|
||||||
enum control_event_command {
|
|
||||||
CONTROL_EVENT_COMMAND_BACK_OR_SCREEN_ON,
|
|
||||||
CONTROL_EVENT_COMMAND_EXPAND_NOTIFICATION_PANEL,
|
|
||||||
CONTROL_EVENT_COMMAND_COLLAPSE_NOTIFICATION_PANEL,
|
|
||||||
};
|
|
||||||
|
|
||||||
struct control_event {
|
|
||||||
enum control_event_type type;
|
|
||||||
union {
|
|
||||||
struct {
|
|
||||||
enum android_keyevent_action action;
|
|
||||||
enum android_keycode keycode;
|
|
||||||
enum android_metastate metastate;
|
|
||||||
} keycode_event;
|
|
||||||
struct {
|
|
||||||
char *text; // owned, to be freed by SDL_free()
|
|
||||||
} text_event;
|
|
||||||
struct {
|
|
||||||
enum android_motionevent_action action;
|
|
||||||
enum android_motionevent_buttons buttons;
|
|
||||||
struct position position;
|
|
||||||
} mouse_event;
|
|
||||||
struct {
|
|
||||||
struct position position;
|
|
||||||
int32_t hscroll;
|
|
||||||
int32_t vscroll;
|
|
||||||
} scroll_event;
|
|
||||||
struct {
|
|
||||||
enum control_event_command action;
|
|
||||||
} command_event;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
struct control_event_queue {
|
|
||||||
struct control_event data[CONTROL_EVENT_QUEUE_SIZE];
|
|
||||||
int head;
|
|
||||||
int tail;
|
|
||||||
};
|
|
||||||
|
|
||||||
// buf size must be at least SERIALIZED_EVENT_MAX_SIZE
|
|
||||||
int
|
|
||||||
control_event_serialize(const struct control_event *event, unsigned char *buf);
|
|
||||||
|
|
||||||
bool
|
|
||||||
control_event_queue_init(struct control_event_queue *queue);
|
|
||||||
|
|
||||||
void
|
|
||||||
control_event_queue_destroy(struct control_event_queue *queue);
|
|
||||||
|
|
||||||
bool
|
|
||||||
control_event_queue_is_empty(const struct control_event_queue *queue);
|
|
||||||
|
|
||||||
bool
|
|
||||||
control_event_queue_is_full(const struct control_event_queue *queue);
|
|
||||||
|
|
||||||
// event is copied, the queue does not use the event after the function returns
|
|
||||||
bool
|
|
||||||
control_event_queue_push(struct control_event_queue *queue,
|
|
||||||
const struct control_event *event);
|
|
||||||
|
|
||||||
bool
|
|
||||||
control_event_queue_take(struct control_event_queue *queue,
|
|
||||||
struct control_event *event);
|
|
||||||
|
|
||||||
void
|
|
||||||
control_event_destroy(struct control_event *event);
|
|
||||||
|
|
||||||
#endif
|
|
@ -0,0 +1,86 @@
|
|||||||
|
#include "control_msg.h"
|
||||||
|
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include "buffer_util.h"
|
||||||
|
#include "log.h"
|
||||||
|
#include "str_util.h"
|
||||||
|
|
||||||
|
static void
|
||||||
|
write_position(uint8_t *buf, const struct position *position) {
|
||||||
|
buffer_write32be(&buf[0], position->point.x);
|
||||||
|
buffer_write32be(&buf[4], position->point.y);
|
||||||
|
buffer_write16be(&buf[8], position->screen_size.width);
|
||||||
|
buffer_write16be(&buf[10], position->screen_size.height);
|
||||||
|
}
|
||||||
|
|
||||||
|
// write length (2 bytes) + string (non nul-terminated)
|
||||||
|
static size_t
|
||||||
|
write_string(const char *utf8, size_t max_len, unsigned char *buf) {
|
||||||
|
size_t len = utf8_truncation_index(utf8, max_len);
|
||||||
|
buffer_write16be(buf, (uint16_t) len);
|
||||||
|
memcpy(&buf[2], utf8, len);
|
||||||
|
return 2 + len;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t
|
||||||
|
control_msg_serialize(const struct control_msg *msg, unsigned char *buf) {
|
||||||
|
buf[0] = msg->type;
|
||||||
|
switch (msg->type) {
|
||||||
|
case CONTROL_MSG_TYPE_INJECT_KEYCODE:
|
||||||
|
buf[1] = msg->inject_keycode.action;
|
||||||
|
buffer_write32be(&buf[2], msg->inject_keycode.keycode);
|
||||||
|
buffer_write32be(&buf[6], msg->inject_keycode.metastate);
|
||||||
|
return 10;
|
||||||
|
case CONTROL_MSG_TYPE_INJECT_TEXT: {
|
||||||
|
size_t len = write_string(msg->inject_text.text,
|
||||||
|
CONTROL_MSG_TEXT_MAX_LENGTH, &buf[1]);
|
||||||
|
return 1 + len;
|
||||||
|
}
|
||||||
|
case CONTROL_MSG_TYPE_INJECT_MOUSE_EVENT:
|
||||||
|
buf[1] = msg->inject_mouse_event.action;
|
||||||
|
buffer_write32be(&buf[2], msg->inject_mouse_event.buttons);
|
||||||
|
write_position(&buf[6], &msg->inject_mouse_event.position);
|
||||||
|
return 18;
|
||||||
|
case CONTROL_MSG_TYPE_INJECT_SCROLL_EVENT:
|
||||||
|
write_position(&buf[1], &msg->inject_scroll_event.position);
|
||||||
|
buffer_write32be(&buf[13],
|
||||||
|
(uint32_t) msg->inject_scroll_event.hscroll);
|
||||||
|
buffer_write32be(&buf[17],
|
||||||
|
(uint32_t) msg->inject_scroll_event.vscroll);
|
||||||
|
return 21;
|
||||||
|
case CONTROL_MSG_TYPE_SET_CLIPBOARD: {
|
||||||
|
size_t len = write_string(msg->inject_text.text,
|
||||||
|
CONTROL_MSG_CLIPBOARD_TEXT_MAX_LENGTH,
|
||||||
|
&buf[1]);
|
||||||
|
return 1 + len;
|
||||||
|
}
|
||||||
|
case CONTROL_MSG_TYPE_SET_SCREEN_POWER_MODE:
|
||||||
|
buf[1] = msg->set_screen_power_mode.mode;
|
||||||
|
return 2;
|
||||||
|
case CONTROL_MSG_TYPE_BACK_OR_SCREEN_ON:
|
||||||
|
case CONTROL_MSG_TYPE_EXPAND_NOTIFICATION_PANEL:
|
||||||
|
case CONTROL_MSG_TYPE_COLLAPSE_NOTIFICATION_PANEL:
|
||||||
|
case CONTROL_MSG_TYPE_GET_CLIPBOARD:
|
||||||
|
// no additional data
|
||||||
|
return 1;
|
||||||
|
default:
|
||||||
|
LOGW("Unknown message type: %u", (unsigned) msg->type);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
control_msg_destroy(struct control_msg *msg) {
|
||||||
|
switch (msg->type) {
|
||||||
|
case CONTROL_MSG_TYPE_INJECT_TEXT:
|
||||||
|
SDL_free(msg->inject_text.text);
|
||||||
|
break;
|
||||||
|
case CONTROL_MSG_TYPE_SET_CLIPBOARD:
|
||||||
|
SDL_free(msg->set_clipboard.text);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
// do nothing
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,74 @@
|
|||||||
|
#ifndef CONTROLMSG_H
|
||||||
|
#define CONTROLMSG_H
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#include "android/input.h"
|
||||||
|
#include "android/keycodes.h"
|
||||||
|
#include "common.h"
|
||||||
|
|
||||||
|
#define CONTROL_MSG_TEXT_MAX_LENGTH 300
|
||||||
|
#define CONTROL_MSG_CLIPBOARD_TEXT_MAX_LENGTH 4093
|
||||||
|
#define CONTROL_MSG_SERIALIZED_MAX_SIZE \
|
||||||
|
(3 + CONTROL_MSG_CLIPBOARD_TEXT_MAX_LENGTH)
|
||||||
|
|
||||||
|
enum control_msg_type {
|
||||||
|
CONTROL_MSG_TYPE_INJECT_KEYCODE,
|
||||||
|
CONTROL_MSG_TYPE_INJECT_TEXT,
|
||||||
|
CONTROL_MSG_TYPE_INJECT_MOUSE_EVENT,
|
||||||
|
CONTROL_MSG_TYPE_INJECT_SCROLL_EVENT,
|
||||||
|
CONTROL_MSG_TYPE_BACK_OR_SCREEN_ON,
|
||||||
|
CONTROL_MSG_TYPE_EXPAND_NOTIFICATION_PANEL,
|
||||||
|
CONTROL_MSG_TYPE_COLLAPSE_NOTIFICATION_PANEL,
|
||||||
|
CONTROL_MSG_TYPE_GET_CLIPBOARD,
|
||||||
|
CONTROL_MSG_TYPE_SET_CLIPBOARD,
|
||||||
|
CONTROL_MSG_TYPE_SET_SCREEN_POWER_MODE,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum screen_power_mode {
|
||||||
|
// see <https://android.googlesource.com/platform/frameworks/base.git/+/pie-release-2/core/java/android/view/SurfaceControl.java#305>
|
||||||
|
SCREEN_POWER_MODE_OFF = 0,
|
||||||
|
SCREEN_POWER_MODE_NORMAL = 2,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct control_msg {
|
||||||
|
enum control_msg_type type;
|
||||||
|
union {
|
||||||
|
struct {
|
||||||
|
enum android_keyevent_action action;
|
||||||
|
enum android_keycode keycode;
|
||||||
|
enum android_metastate metastate;
|
||||||
|
} inject_keycode;
|
||||||
|
struct {
|
||||||
|
char *text; // owned, to be freed by SDL_free()
|
||||||
|
} inject_text;
|
||||||
|
struct {
|
||||||
|
enum android_motionevent_action action;
|
||||||
|
enum android_motionevent_buttons buttons;
|
||||||
|
struct position position;
|
||||||
|
} inject_mouse_event;
|
||||||
|
struct {
|
||||||
|
struct position position;
|
||||||
|
int32_t hscroll;
|
||||||
|
int32_t vscroll;
|
||||||
|
} inject_scroll_event;
|
||||||
|
struct {
|
||||||
|
char *text; // owned, to be freed by SDL_free()
|
||||||
|
} set_clipboard;
|
||||||
|
struct {
|
||||||
|
enum screen_power_mode mode;
|
||||||
|
} set_screen_power_mode;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
// buf size must be at least CONTROL_MSG_SERIALIZED_MAX_SIZE
|
||||||
|
// return the number of bytes written
|
||||||
|
size_t
|
||||||
|
control_msg_serialize(const struct control_msg *msg, unsigned char *buf);
|
||||||
|
|
||||||
|
void
|
||||||
|
control_msg_destroy(struct control_msg *msg);
|
||||||
|
|
||||||
|
#endif
|
@ -0,0 +1,48 @@
|
|||||||
|
#include "device_msg.h"
|
||||||
|
|
||||||
|
#include <string.h>
|
||||||
|
#include <SDL2/SDL_assert.h>
|
||||||
|
|
||||||
|
#include "buffer_util.h"
|
||||||
|
#include "log.h"
|
||||||
|
|
||||||
|
ssize_t
|
||||||
|
device_msg_deserialize(const unsigned char *buf, size_t len,
|
||||||
|
struct device_msg *msg) {
|
||||||
|
if (len < 3) {
|
||||||
|
// at least type + empty string length
|
||||||
|
return 0; // not available
|
||||||
|
}
|
||||||
|
|
||||||
|
msg->type = buf[0];
|
||||||
|
switch (msg->type) {
|
||||||
|
case DEVICE_MSG_TYPE_CLIPBOARD: {
|
||||||
|
uint16_t clipboard_len = buffer_read16be(&buf[1]);
|
||||||
|
if (clipboard_len > len - 3) {
|
||||||
|
return 0; // not available
|
||||||
|
}
|
||||||
|
char *text = SDL_malloc(clipboard_len + 1);
|
||||||
|
if (!text) {
|
||||||
|
LOGW("Could not allocate text for clipboard");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
if (clipboard_len) {
|
||||||
|
memcpy(text, &buf[3], clipboard_len);
|
||||||
|
}
|
||||||
|
text[clipboard_len] = '\0';
|
||||||
|
|
||||||
|
msg->clipboard.text = text;
|
||||||
|
return 3 + clipboard_len;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
LOGW("Unknown device message type: %d", (int) msg->type);
|
||||||
|
return -1; // error, we cannot recover
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
device_msg_destroy(struct device_msg *msg) {
|
||||||
|
if (msg->type == DEVICE_MSG_TYPE_CLIPBOARD) {
|
||||||
|
SDL_free(msg->clipboard.text);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,32 @@
|
|||||||
|
#ifndef DEVICEMSG_H
|
||||||
|
#define DEVICEMSG_H
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#define DEVICE_MSG_TEXT_MAX_LENGTH 4093
|
||||||
|
#define DEVICE_MSG_SERIALIZED_MAX_SIZE (3 + DEVICE_MSG_TEXT_MAX_LENGTH)
|
||||||
|
|
||||||
|
enum device_msg_type {
|
||||||
|
DEVICE_MSG_TYPE_CLIPBOARD,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct device_msg {
|
||||||
|
enum device_msg_type type;
|
||||||
|
union {
|
||||||
|
struct {
|
||||||
|
char *text; // owned, to be freed by SDL_free()
|
||||||
|
} clipboard;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
// return the number of bytes consumed (0 for no msg available, -1 on error)
|
||||||
|
ssize_t
|
||||||
|
device_msg_deserialize(const unsigned char *buf, size_t len,
|
||||||
|
struct device_msg *msg);
|
||||||
|
|
||||||
|
void
|
||||||
|
device_msg_destroy(struct device_msg *msg);
|
||||||
|
|
||||||
|
#endif
|
@ -1,38 +0,0 @@
|
|||||||
#include <lock_util.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <SDL2/SDL_mutex.h>
|
|
||||||
|
|
||||||
#include "log.h"
|
|
||||||
|
|
||||||
void
|
|
||||||
mutex_lock(SDL_mutex *mutex) {
|
|
||||||
if (SDL_LockMutex(mutex)) {
|
|
||||||
LOGC("Could not lock mutex");
|
|
||||||
abort();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
mutex_unlock(SDL_mutex *mutex) {
|
|
||||||
if (SDL_UnlockMutex(mutex)) {
|
|
||||||
LOGC("Could not unlock mutex");
|
|
||||||
abort();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
cond_wait(SDL_cond *cond, SDL_mutex *mutex) {
|
|
||||||
if (SDL_CondWait(cond, mutex)) {
|
|
||||||
LOGC("Could not wait on condition");
|
|
||||||
abort();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
cond_signal(SDL_cond *cond) {
|
|
||||||
if (SDL_CondSignal(cond)) {
|
|
||||||
LOGC("Could not signal a condition");
|
|
||||||
abort();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,20 +1,51 @@
|
|||||||
#ifndef LOCKUTIL_H
|
#ifndef LOCKUTIL_H
|
||||||
#define LOCKUTIL_H
|
#define LOCKUTIL_H
|
||||||
|
|
||||||
// forward declarations
|
#include <stdint.h>
|
||||||
typedef struct SDL_mutex SDL_mutex;
|
#include <SDL2/SDL_mutex.h>
|
||||||
typedef struct SDL_cond SDL_cond;
|
|
||||||
|
|
||||||
void
|
#include "log.h"
|
||||||
mutex_lock(SDL_mutex *mutex);
|
|
||||||
|
|
||||||
void
|
static inline void
|
||||||
mutex_unlock(SDL_mutex *mutex);
|
mutex_lock(SDL_mutex *mutex) {
|
||||||
|
if (SDL_LockMutex(mutex)) {
|
||||||
|
LOGC("Could not lock mutex");
|
||||||
|
abort();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void
|
static inline void
|
||||||
cond_wait(SDL_cond *cond, SDL_mutex *mutex);
|
mutex_unlock(SDL_mutex *mutex) {
|
||||||
|
if (SDL_UnlockMutex(mutex)) {
|
||||||
|
LOGC("Could not unlock mutex");
|
||||||
|
abort();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void
|
static inline void
|
||||||
cond_signal(SDL_cond *cond);
|
cond_wait(SDL_cond *cond, SDL_mutex *mutex) {
|
||||||
|
if (SDL_CondWait(cond, mutex)) {
|
||||||
|
LOGC("Could not wait on condition");
|
||||||
|
abort();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline int
|
||||||
|
cond_wait_timeout(SDL_cond *cond, SDL_mutex *mutex, uint32_t ms) {
|
||||||
|
int r = SDL_CondWaitTimeout(cond, mutex, ms);
|
||||||
|
if (r < 0) {
|
||||||
|
LOGC("Could not wait on condition with timeout");
|
||||||
|
abort();
|
||||||
|
}
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void
|
||||||
|
cond_signal(SDL_cond *cond) {
|
||||||
|
if (SDL_CondSignal(cond)) {
|
||||||
|
LOGC("Could not signal a condition");
|
||||||
|
abort();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
@ -0,0 +1,107 @@
|
|||||||
|
#include "receiver.h"
|
||||||
|
|
||||||
|
#include <SDL2/SDL_assert.h>
|
||||||
|
#include <SDL2/SDL_clipboard.h>
|
||||||
|
|
||||||
|
#include "config.h"
|
||||||
|
#include "device_msg.h"
|
||||||
|
#include "lock_util.h"
|
||||||
|
#include "log.h"
|
||||||
|
|
||||||
|
bool
|
||||||
|
receiver_init(struct receiver *receiver, socket_t control_socket) {
|
||||||
|
if (!(receiver->mutex = SDL_CreateMutex())) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
receiver->control_socket = control_socket;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
receiver_destroy(struct receiver *receiver) {
|
||||||
|
SDL_DestroyMutex(receiver->mutex);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
process_msg(struct receiver *receiver, struct device_msg *msg) {
|
||||||
|
switch (msg->type) {
|
||||||
|
case DEVICE_MSG_TYPE_CLIPBOARD:
|
||||||
|
LOGI("Device clipboard copied");
|
||||||
|
SDL_SetClipboardText(msg->clipboard.text);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static ssize_t
|
||||||
|
process_msgs(struct receiver *receiver, const unsigned char *buf, size_t len) {
|
||||||
|
size_t head = 0;
|
||||||
|
for (;;) {
|
||||||
|
struct device_msg msg;
|
||||||
|
ssize_t r = device_msg_deserialize(&buf[head], len - head, &msg);
|
||||||
|
if (r == -1) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
if (r == 0) {
|
||||||
|
return head;
|
||||||
|
}
|
||||||
|
|
||||||
|
process_msg(receiver, &msg);
|
||||||
|
device_msg_destroy(&msg);
|
||||||
|
|
||||||
|
head += r;
|
||||||
|
SDL_assert(head <= len);
|
||||||
|
if (head == len) {
|
||||||
|
return head;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
run_receiver(void *data) {
|
||||||
|
struct receiver *receiver = data;
|
||||||
|
|
||||||
|
unsigned char buf[DEVICE_MSG_SERIALIZED_MAX_SIZE];
|
||||||
|
size_t head = 0;
|
||||||
|
|
||||||
|
for (;;) {
|
||||||
|
SDL_assert(head < DEVICE_MSG_SERIALIZED_MAX_SIZE);
|
||||||
|
ssize_t r = net_recv(receiver->control_socket, buf,
|
||||||
|
DEVICE_MSG_SERIALIZED_MAX_SIZE - head);
|
||||||
|
if (r <= 0) {
|
||||||
|
LOGD("Receiver stopped");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
ssize_t consumed = process_msgs(receiver, buf, r);
|
||||||
|
if (consumed == -1) {
|
||||||
|
// an error occurred
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (consumed) {
|
||||||
|
// shift the remaining data in the buffer
|
||||||
|
memmove(buf, &buf[consumed], r - consumed);
|
||||||
|
head = r - consumed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
receiver_start(struct receiver *receiver) {
|
||||||
|
LOGD("Starting receiver thread");
|
||||||
|
|
||||||
|
receiver->thread = SDL_CreateThread(run_receiver, "receiver", receiver);
|
||||||
|
if (!receiver->thread) {
|
||||||
|
LOGC("Could not start receiver thread");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
receiver_join(struct receiver *receiver) {
|
||||||
|
SDL_WaitThread(receiver->thread, NULL);
|
||||||
|
}
|
@ -0,0 +1,32 @@
|
|||||||
|
#ifndef RECEIVER_H
|
||||||
|
#define RECEIVER_H
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <SDL2/SDL_mutex.h>
|
||||||
|
#include <SDL2/SDL_thread.h>
|
||||||
|
|
||||||
|
#include "net.h"
|
||||||
|
|
||||||
|
// receive events from the device
|
||||||
|
// managed by the controller
|
||||||
|
struct receiver {
|
||||||
|
socket_t control_socket;
|
||||||
|
SDL_Thread *thread;
|
||||||
|
SDL_mutex *mutex;
|
||||||
|
};
|
||||||
|
|
||||||
|
bool
|
||||||
|
receiver_init(struct receiver *receiver, socket_t control_socket);
|
||||||
|
|
||||||
|
void
|
||||||
|
receiver_destroy(struct receiver *receiver);
|
||||||
|
|
||||||
|
bool
|
||||||
|
receiver_start(struct receiver *receiver);
|
||||||
|
|
||||||
|
// no receiver_stop(), it will automatically stop on control_socket shutdown
|
||||||
|
|
||||||
|
void
|
||||||
|
receiver_join(struct receiver *receiver);
|
||||||
|
|
||||||
|
#endif
|
@ -0,0 +1,73 @@
|
|||||||
|
#include <assert.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include "cbuf.h"
|
||||||
|
|
||||||
|
struct int_queue CBUF(int, 32);
|
||||||
|
|
||||||
|
static void test_cbuf_empty(void) {
|
||||||
|
struct int_queue queue;
|
||||||
|
cbuf_init(&queue);
|
||||||
|
|
||||||
|
assert(cbuf_is_empty(&queue));
|
||||||
|
|
||||||
|
bool push_ok = cbuf_push(&queue, 42);
|
||||||
|
assert(push_ok);
|
||||||
|
assert(!cbuf_is_empty(&queue));
|
||||||
|
|
||||||
|
int item;
|
||||||
|
bool take_ok = cbuf_take(&queue, &item);
|
||||||
|
assert(take_ok);
|
||||||
|
assert(cbuf_is_empty(&queue));
|
||||||
|
|
||||||
|
bool take_empty_ok = cbuf_take(&queue, &item);
|
||||||
|
assert(!take_empty_ok); // the queue is empty
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_cbuf_full(void) {
|
||||||
|
struct int_queue queue;
|
||||||
|
cbuf_init(&queue);
|
||||||
|
|
||||||
|
assert(!cbuf_is_full(&queue));
|
||||||
|
|
||||||
|
// fill the queue
|
||||||
|
for (int i = 0; i < 32; ++i) {
|
||||||
|
bool ok = cbuf_push(&queue, i);
|
||||||
|
assert(ok);
|
||||||
|
}
|
||||||
|
bool ok = cbuf_push(&queue, 42);
|
||||||
|
assert(!ok); // the queue if full
|
||||||
|
|
||||||
|
int item;
|
||||||
|
bool take_ok = cbuf_take(&queue, &item);
|
||||||
|
assert(take_ok);
|
||||||
|
assert(!cbuf_is_full(&queue));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_cbuf_push_take(void) {
|
||||||
|
struct int_queue queue;
|
||||||
|
cbuf_init(&queue);
|
||||||
|
|
||||||
|
bool push1_ok = cbuf_push(&queue, 42);
|
||||||
|
assert(push1_ok);
|
||||||
|
|
||||||
|
bool push2_ok = cbuf_push(&queue, 35);
|
||||||
|
assert(push2_ok);
|
||||||
|
|
||||||
|
int item;
|
||||||
|
|
||||||
|
bool take1_ok = cbuf_take(&queue, &item);
|
||||||
|
assert(take1_ok);
|
||||||
|
assert(item == 42);
|
||||||
|
|
||||||
|
bool take2_ok = cbuf_take(&queue, &item);
|
||||||
|
assert(take2_ok);
|
||||||
|
assert(item == 35);
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(void) {
|
||||||
|
test_cbuf_empty();
|
||||||
|
test_cbuf_full();
|
||||||
|
test_cbuf_push_take();
|
||||||
|
return 0;
|
||||||
|
}
|
@ -1,95 +0,0 @@
|
|||||||
#include <assert.h>
|
|
||||||
#include <string.h>
|
|
||||||
|
|
||||||
#include "control_event.h"
|
|
||||||
|
|
||||||
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);
|
|
||||||
|
|
||||||
assert(control_event_queue_is_empty(&queue));
|
|
||||||
|
|
||||||
struct control_event dummy_event;
|
|
||||||
SDL_bool push_ok = control_event_queue_push(&queue, &dummy_event);
|
|
||||||
assert(push_ok);
|
|
||||||
assert(!control_event_queue_is_empty(&queue));
|
|
||||||
|
|
||||||
SDL_bool take_ok = control_event_queue_take(&queue, &dummy_event);
|
|
||||||
assert(take_ok);
|
|
||||||
assert(control_event_queue_is_empty(&queue));
|
|
||||||
|
|
||||||
SDL_bool take_empty_ok = control_event_queue_take(&queue, &dummy_event);
|
|
||||||
assert(!take_empty_ok); // the queue is empty
|
|
||||||
|
|
||||||
control_event_queue_destroy(&queue);
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
|
|
||||||
assert(!control_event_queue_is_full(&queue));
|
|
||||||
|
|
||||||
struct control_event dummy_event;
|
|
||||||
// fill the queue
|
|
||||||
while (control_event_queue_push(&queue, &dummy_event));
|
|
||||||
|
|
||||||
SDL_bool take_ok = control_event_queue_take(&queue, &dummy_event);
|
|
||||||
assert(take_ok);
|
|
||||||
assert(!control_event_queue_is_full(&queue));
|
|
||||||
|
|
||||||
control_event_queue_destroy(&queue);
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
|
|
||||||
struct control_event event = {
|
|
||||||
.type = CONTROL_EVENT_TYPE_KEYCODE,
|
|
||||||
.keycode_event = {
|
|
||||||
.action = AKEY_EVENT_ACTION_DOWN,
|
|
||||||
.keycode = AKEYCODE_ENTER,
|
|
||||||
.metastate = AMETA_CTRL_LEFT_ON | AMETA_CTRL_ON,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
SDL_bool push1_ok = control_event_queue_push(&queue, &event);
|
|
||||||
assert(push1_ok);
|
|
||||||
|
|
||||||
event = (struct control_event) {
|
|
||||||
.type = CONTROL_EVENT_TYPE_TEXT,
|
|
||||||
.text_event = {
|
|
||||||
.text = "abc",
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
SDL_bool push2_ok = control_event_queue_push(&queue, &event);
|
|
||||||
assert(push2_ok);
|
|
||||||
|
|
||||||
// overwrite event
|
|
||||||
SDL_bool take1_ok = control_event_queue_take(&queue, &event);
|
|
||||||
assert(take1_ok);
|
|
||||||
assert(event.type == CONTROL_EVENT_TYPE_KEYCODE);
|
|
||||||
assert(event.keycode_event.action == AKEY_EVENT_ACTION_DOWN);
|
|
||||||
assert(event.keycode_event.keycode == AKEYCODE_ENTER);
|
|
||||||
assert(event.keycode_event.metastate == (AMETA_CTRL_LEFT_ON | AMETA_CTRL_ON));
|
|
||||||
|
|
||||||
// overwrite event
|
|
||||||
SDL_bool take2_ok = control_event_queue_take(&queue, &event);
|
|
||||||
assert(take2_ok);
|
|
||||||
assert(event.type == CONTROL_EVENT_TYPE_TEXT);
|
|
||||||
assert(!strcmp(event.text_event.text, "abc"));
|
|
||||||
|
|
||||||
control_event_queue_destroy(&queue);
|
|
||||||
}
|
|
||||||
|
|
||||||
int main(void) {
|
|
||||||
test_control_event_queue_empty();
|
|
||||||
test_control_event_queue_full();
|
|
||||||
test_control_event_queue_push_take();
|
|
||||||
return 0;
|
|
||||||
}
|
|
@ -1,142 +0,0 @@
|
|||||||
#include <assert.h>
|
|
||||||
#include <string.h>
|
|
||||||
|
|
||||||
#include "control_event.h"
|
|
||||||
|
|
||||||
static void test_serialize_keycode_event(void) {
|
|
||||||
struct control_event event = {
|
|
||||||
.type = CONTROL_EVENT_TYPE_KEYCODE,
|
|
||||||
.keycode_event = {
|
|
||||||
.action = AKEY_EVENT_ACTION_UP,
|
|
||||||
.keycode = AKEYCODE_ENTER,
|
|
||||||
.metastate = AMETA_SHIFT_ON | AMETA_SHIFT_LEFT_ON,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
unsigned char buf[SERIALIZED_EVENT_MAX_SIZE];
|
|
||||||
int size = control_event_serialize(&event, buf);
|
|
||||||
assert(size == 10);
|
|
||||||
|
|
||||||
const unsigned char expected[] = {
|
|
||||||
0x00, // CONTROL_EVENT_TYPE_KEYCODE
|
|
||||||
0x01, // AKEY_EVENT_ACTION_UP
|
|
||||||
0x00, 0x00, 0x00, 0x42, // AKEYCODE_ENTER
|
|
||||||
0x00, 0x00, 0x00, 0x41, // AMETA_SHIFT_ON | AMETA_SHIFT_LEFT_ON
|
|
||||||
};
|
|
||||||
assert(!memcmp(buf, expected, sizeof(expected)));
|
|
||||||
}
|
|
||||||
|
|
||||||
static void test_serialize_text_event(void) {
|
|
||||||
struct control_event event = {
|
|
||||||
.type = CONTROL_EVENT_TYPE_TEXT,
|
|
||||||
.text_event = {
|
|
||||||
.text = "hello, world!",
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
unsigned char buf[SERIALIZED_EVENT_MAX_SIZE];
|
|
||||||
int size = control_event_serialize(&event, buf);
|
|
||||||
assert(size == 16);
|
|
||||||
|
|
||||||
const unsigned char expected[] = {
|
|
||||||
0x01, // CONTROL_EVENT_TYPE_KEYCODE
|
|
||||||
0x00, 0x0d, // text length
|
|
||||||
'h', 'e', 'l', 'l', 'o', ',', ' ', 'w', 'o', 'r', 'l', 'd', '!', // text
|
|
||||||
};
|
|
||||||
assert(!memcmp(buf, expected, sizeof(expected)));
|
|
||||||
}
|
|
||||||
|
|
||||||
static void test_serialize_long_text_event(void) {
|
|
||||||
struct control_event event;
|
|
||||||
event.type = CONTROL_EVENT_TYPE_TEXT;
|
|
||||||
char text[TEXT_MAX_LENGTH + 1];
|
|
||||||
memset(text, 'a', sizeof(text));
|
|
||||||
text[TEXT_MAX_LENGTH] = '\0';
|
|
||||||
event.text_event.text = text;
|
|
||||||
|
|
||||||
unsigned char buf[SERIALIZED_EVENT_MAX_SIZE];
|
|
||||||
int size = control_event_serialize(&event, buf);
|
|
||||||
assert(size == 3 + TEXT_MAX_LENGTH);
|
|
||||||
|
|
||||||
unsigned char expected[3 + TEXT_MAX_LENGTH];
|
|
||||||
expected[0] = 0x01; // CONTROL_EVENT_TYPE_KEYCODE
|
|
||||||
expected[1] = 0x01;
|
|
||||||
expected[2] = 0x2c; // text length (16 bits)
|
|
||||||
memset(&expected[3], 'a', TEXT_MAX_LENGTH);
|
|
||||||
|
|
||||||
assert(!memcmp(buf, expected, sizeof(expected)));
|
|
||||||
}
|
|
||||||
|
|
||||||
static void test_serialize_mouse_event(void) {
|
|
||||||
struct control_event event = {
|
|
||||||
.type = CONTROL_EVENT_TYPE_MOUSE,
|
|
||||||
.mouse_event = {
|
|
||||||
.action = AMOTION_EVENT_ACTION_DOWN,
|
|
||||||
.buttons = AMOTION_EVENT_BUTTON_PRIMARY,
|
|
||||||
.position = {
|
|
||||||
.point = {
|
|
||||||
.x = 260,
|
|
||||||
.y = 1026,
|
|
||||||
},
|
|
||||||
.screen_size = {
|
|
||||||
.width = 1080,
|
|
||||||
.height = 1920,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
unsigned char buf[SERIALIZED_EVENT_MAX_SIZE];
|
|
||||||
int size = control_event_serialize(&event, buf);
|
|
||||||
assert(size == 18);
|
|
||||||
|
|
||||||
const unsigned char expected[] = {
|
|
||||||
0x02, // CONTROL_EVENT_TYPE_MOUSE
|
|
||||||
0x00, // AKEY_EVENT_ACTION_DOWN
|
|
||||||
0x00, 0x00, 0x00, 0x01, // AMOTION_EVENT_BUTTON_PRIMARY
|
|
||||||
0x00, 0x00, 0x01, 0x04, 0x00, 0x00, 0x04, 0x02, // 260 1026
|
|
||||||
0x04, 0x38, 0x07, 0x80, // 1080 1920
|
|
||||||
};
|
|
||||||
assert(!memcmp(buf, expected, sizeof(expected)));
|
|
||||||
}
|
|
||||||
|
|
||||||
static void test_serialize_scroll_event(void) {
|
|
||||||
struct control_event event = {
|
|
||||||
.type = CONTROL_EVENT_TYPE_SCROLL,
|
|
||||||
.scroll_event = {
|
|
||||||
.position = {
|
|
||||||
.point = {
|
|
||||||
.x = 260,
|
|
||||||
.y = 1026,
|
|
||||||
},
|
|
||||||
.screen_size = {
|
|
||||||
.width = 1080,
|
|
||||||
.height = 1920,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
.hscroll = 1,
|
|
||||||
.vscroll = -1,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
unsigned char buf[SERIALIZED_EVENT_MAX_SIZE];
|
|
||||||
int size = control_event_serialize(&event, buf);
|
|
||||||
assert(size == 21);
|
|
||||||
|
|
||||||
const unsigned char expected[] = {
|
|
||||||
0x03, // CONTROL_EVENT_TYPE_SCROLL
|
|
||||||
0x00, 0x00, 0x01, 0x04, 0x00, 0x00, 0x04, 0x02, // 260 1026
|
|
||||||
0x04, 0x38, 0x07, 0x80, // 1080 1920
|
|
||||||
0x00, 0x00, 0x00, 0x01, // 1
|
|
||||||
0xFF, 0xFF, 0xFF, 0xFF, // -1
|
|
||||||
};
|
|
||||||
assert(!memcmp(buf, expected, sizeof(expected)));
|
|
||||||
}
|
|
||||||
|
|
||||||
int main(void) {
|
|
||||||
test_serialize_keycode_event();
|
|
||||||
test_serialize_text_event();
|
|
||||||
test_serialize_long_text_event();
|
|
||||||
test_serialize_mouse_event();
|
|
||||||
test_serialize_scroll_event();
|
|
||||||
}
|
|
@ -0,0 +1,248 @@
|
|||||||
|
#include <assert.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include "control_msg.h"
|
||||||
|
|
||||||
|
static void test_serialize_inject_keycode(void) {
|
||||||
|
struct control_msg msg = {
|
||||||
|
.type = CONTROL_MSG_TYPE_INJECT_KEYCODE,
|
||||||
|
.inject_keycode = {
|
||||||
|
.action = AKEY_EVENT_ACTION_UP,
|
||||||
|
.keycode = AKEYCODE_ENTER,
|
||||||
|
.metastate = AMETA_SHIFT_ON | AMETA_SHIFT_LEFT_ON,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
unsigned char buf[CONTROL_MSG_SERIALIZED_MAX_SIZE];
|
||||||
|
int size = control_msg_serialize(&msg, buf);
|
||||||
|
assert(size == 10);
|
||||||
|
|
||||||
|
const unsigned char expected[] = {
|
||||||
|
CONTROL_MSG_TYPE_INJECT_KEYCODE,
|
||||||
|
0x01, // AKEY_EVENT_ACTION_UP
|
||||||
|
0x00, 0x00, 0x00, 0x42, // AKEYCODE_ENTER
|
||||||
|
0x00, 0x00, 0x00, 0x41, // AMETA_SHIFT_ON | AMETA_SHIFT_LEFT_ON
|
||||||
|
};
|
||||||
|
assert(!memcmp(buf, expected, sizeof(expected)));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_serialize_inject_text(void) {
|
||||||
|
struct control_msg msg = {
|
||||||
|
.type = CONTROL_MSG_TYPE_INJECT_TEXT,
|
||||||
|
.inject_text = {
|
||||||
|
.text = "hello, world!",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
unsigned char buf[CONTROL_MSG_SERIALIZED_MAX_SIZE];
|
||||||
|
int size = control_msg_serialize(&msg, buf);
|
||||||
|
assert(size == 16);
|
||||||
|
|
||||||
|
const unsigned char expected[] = {
|
||||||
|
CONTROL_MSG_TYPE_INJECT_TEXT,
|
||||||
|
0x00, 0x0d, // text length
|
||||||
|
'h', 'e', 'l', 'l', 'o', ',', ' ', 'w', 'o', 'r', 'l', 'd', '!', // text
|
||||||
|
};
|
||||||
|
assert(!memcmp(buf, expected, sizeof(expected)));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_serialize_inject_text_long(void) {
|
||||||
|
struct control_msg msg;
|
||||||
|
msg.type = CONTROL_MSG_TYPE_INJECT_TEXT;
|
||||||
|
char text[CONTROL_MSG_TEXT_MAX_LENGTH + 1];
|
||||||
|
memset(text, 'a', sizeof(text));
|
||||||
|
text[CONTROL_MSG_TEXT_MAX_LENGTH] = '\0';
|
||||||
|
msg.inject_text.text = text;
|
||||||
|
|
||||||
|
unsigned char buf[CONTROL_MSG_SERIALIZED_MAX_SIZE];
|
||||||
|
int size = control_msg_serialize(&msg, buf);
|
||||||
|
assert(size == 3 + CONTROL_MSG_TEXT_MAX_LENGTH);
|
||||||
|
|
||||||
|
unsigned char expected[3 + CONTROL_MSG_TEXT_MAX_LENGTH];
|
||||||
|
expected[0] = CONTROL_MSG_TYPE_INJECT_TEXT;
|
||||||
|
expected[1] = 0x01;
|
||||||
|
expected[2] = 0x2c; // text length (16 bits)
|
||||||
|
memset(&expected[3], 'a', CONTROL_MSG_TEXT_MAX_LENGTH);
|
||||||
|
|
||||||
|
assert(!memcmp(buf, expected, sizeof(expected)));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_serialize_inject_mouse_event(void) {
|
||||||
|
struct control_msg msg = {
|
||||||
|
.type = CONTROL_MSG_TYPE_INJECT_MOUSE_EVENT,
|
||||||
|
.inject_mouse_event = {
|
||||||
|
.action = AMOTION_EVENT_ACTION_DOWN,
|
||||||
|
.buttons = AMOTION_EVENT_BUTTON_PRIMARY,
|
||||||
|
.position = {
|
||||||
|
.point = {
|
||||||
|
.x = 260,
|
||||||
|
.y = 1026,
|
||||||
|
},
|
||||||
|
.screen_size = {
|
||||||
|
.width = 1080,
|
||||||
|
.height = 1920,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
unsigned char buf[CONTROL_MSG_SERIALIZED_MAX_SIZE];
|
||||||
|
int size = control_msg_serialize(&msg, buf);
|
||||||
|
assert(size == 18);
|
||||||
|
|
||||||
|
const unsigned char expected[] = {
|
||||||
|
CONTROL_MSG_TYPE_INJECT_MOUSE_EVENT,
|
||||||
|
0x00, // AKEY_EVENT_ACTION_DOWN
|
||||||
|
0x00, 0x00, 0x00, 0x01, // AMOTION_EVENT_BUTTON_PRIMARY
|
||||||
|
0x00, 0x00, 0x01, 0x04, 0x00, 0x00, 0x04, 0x02, // 260 1026
|
||||||
|
0x04, 0x38, 0x07, 0x80, // 1080 1920
|
||||||
|
};
|
||||||
|
assert(!memcmp(buf, expected, sizeof(expected)));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_serialize_inject_scroll_event(void) {
|
||||||
|
struct control_msg msg = {
|
||||||
|
.type = CONTROL_MSG_TYPE_INJECT_SCROLL_EVENT,
|
||||||
|
.inject_scroll_event = {
|
||||||
|
.position = {
|
||||||
|
.point = {
|
||||||
|
.x = 260,
|
||||||
|
.y = 1026,
|
||||||
|
},
|
||||||
|
.screen_size = {
|
||||||
|
.width = 1080,
|
||||||
|
.height = 1920,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
.hscroll = 1,
|
||||||
|
.vscroll = -1,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
unsigned char buf[CONTROL_MSG_SERIALIZED_MAX_SIZE];
|
||||||
|
int size = control_msg_serialize(&msg, buf);
|
||||||
|
assert(size == 21);
|
||||||
|
|
||||||
|
const unsigned char expected[] = {
|
||||||
|
CONTROL_MSG_TYPE_INJECT_SCROLL_EVENT,
|
||||||
|
0x00, 0x00, 0x01, 0x04, 0x00, 0x00, 0x04, 0x02, // 260 1026
|
||||||
|
0x04, 0x38, 0x07, 0x80, // 1080 1920
|
||||||
|
0x00, 0x00, 0x00, 0x01, // 1
|
||||||
|
0xFF, 0xFF, 0xFF, 0xFF, // -1
|
||||||
|
};
|
||||||
|
assert(!memcmp(buf, expected, sizeof(expected)));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_serialize_back_or_screen_on(void) {
|
||||||
|
struct control_msg msg = {
|
||||||
|
.type = CONTROL_MSG_TYPE_BACK_OR_SCREEN_ON,
|
||||||
|
};
|
||||||
|
|
||||||
|
unsigned char buf[CONTROL_MSG_SERIALIZED_MAX_SIZE];
|
||||||
|
int size = control_msg_serialize(&msg, buf);
|
||||||
|
assert(size == 1);
|
||||||
|
|
||||||
|
const unsigned char expected[] = {
|
||||||
|
CONTROL_MSG_TYPE_BACK_OR_SCREEN_ON,
|
||||||
|
};
|
||||||
|
assert(!memcmp(buf, expected, sizeof(expected)));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_serialize_expand_notification_panel(void) {
|
||||||
|
struct control_msg msg = {
|
||||||
|
.type = CONTROL_MSG_TYPE_EXPAND_NOTIFICATION_PANEL,
|
||||||
|
};
|
||||||
|
|
||||||
|
unsigned char buf[CONTROL_MSG_SERIALIZED_MAX_SIZE];
|
||||||
|
int size = control_msg_serialize(&msg, buf);
|
||||||
|
assert(size == 1);
|
||||||
|
|
||||||
|
const unsigned char expected[] = {
|
||||||
|
CONTROL_MSG_TYPE_EXPAND_NOTIFICATION_PANEL,
|
||||||
|
};
|
||||||
|
assert(!memcmp(buf, expected, sizeof(expected)));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_serialize_collapse_notification_panel(void) {
|
||||||
|
struct control_msg msg = {
|
||||||
|
.type = CONTROL_MSG_TYPE_COLLAPSE_NOTIFICATION_PANEL,
|
||||||
|
};
|
||||||
|
|
||||||
|
unsigned char buf[CONTROL_MSG_SERIALIZED_MAX_SIZE];
|
||||||
|
int size = control_msg_serialize(&msg, buf);
|
||||||
|
assert(size == 1);
|
||||||
|
|
||||||
|
const unsigned char expected[] = {
|
||||||
|
CONTROL_MSG_TYPE_COLLAPSE_NOTIFICATION_PANEL,
|
||||||
|
};
|
||||||
|
assert(!memcmp(buf, expected, sizeof(expected)));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_serialize_get_clipboard(void) {
|
||||||
|
struct control_msg msg = {
|
||||||
|
.type = CONTROL_MSG_TYPE_GET_CLIPBOARD,
|
||||||
|
};
|
||||||
|
|
||||||
|
unsigned char buf[CONTROL_MSG_SERIALIZED_MAX_SIZE];
|
||||||
|
int size = control_msg_serialize(&msg, buf);
|
||||||
|
assert(size == 1);
|
||||||
|
|
||||||
|
const unsigned char expected[] = {
|
||||||
|
CONTROL_MSG_TYPE_GET_CLIPBOARD,
|
||||||
|
};
|
||||||
|
assert(!memcmp(buf, expected, sizeof(expected)));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_serialize_set_clipboard(void) {
|
||||||
|
struct control_msg msg = {
|
||||||
|
.type = CONTROL_MSG_TYPE_SET_CLIPBOARD,
|
||||||
|
.inject_text = {
|
||||||
|
.text = "hello, world!",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
unsigned char buf[CONTROL_MSG_SERIALIZED_MAX_SIZE];
|
||||||
|
int size = control_msg_serialize(&msg, buf);
|
||||||
|
assert(size == 16);
|
||||||
|
|
||||||
|
const unsigned char expected[] = {
|
||||||
|
CONTROL_MSG_TYPE_SET_CLIPBOARD,
|
||||||
|
0x00, 0x0d, // text length
|
||||||
|
'h', 'e', 'l', 'l', 'o', ',', ' ', 'w', 'o', 'r', 'l', 'd', '!', // text
|
||||||
|
};
|
||||||
|
assert(!memcmp(buf, expected, sizeof(expected)));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_serialize_set_screen_power_mode(void) {
|
||||||
|
struct control_msg msg = {
|
||||||
|
.type = CONTROL_MSG_TYPE_SET_SCREEN_POWER_MODE,
|
||||||
|
.set_screen_power_mode = {
|
||||||
|
.mode = SCREEN_POWER_MODE_NORMAL,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
unsigned char buf[CONTROL_MSG_SERIALIZED_MAX_SIZE];
|
||||||
|
int size = control_msg_serialize(&msg, buf);
|
||||||
|
assert(size == 2);
|
||||||
|
|
||||||
|
const unsigned char expected[] = {
|
||||||
|
CONTROL_MSG_TYPE_SET_SCREEN_POWER_MODE,
|
||||||
|
0x02, // SCREEN_POWER_MODE_NORMAL
|
||||||
|
};
|
||||||
|
assert(!memcmp(buf, expected, sizeof(expected)));
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(void) {
|
||||||
|
test_serialize_inject_keycode();
|
||||||
|
test_serialize_inject_text();
|
||||||
|
test_serialize_inject_text_long();
|
||||||
|
test_serialize_inject_mouse_event();
|
||||||
|
test_serialize_inject_scroll_event();
|
||||||
|
test_serialize_back_or_screen_on();
|
||||||
|
test_serialize_expand_notification_panel();
|
||||||
|
test_serialize_collapse_notification_panel();
|
||||||
|
test_serialize_get_clipboard();
|
||||||
|
test_serialize_set_clipboard();
|
||||||
|
test_serialize_set_screen_power_mode();
|
||||||
|
return 0;
|
||||||
|
}
|
@ -0,0 +1,28 @@
|
|||||||
|
#include <assert.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include "device_msg.h"
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
static void test_deserialize_clipboard(void) {
|
||||||
|
const unsigned char input[] = {
|
||||||
|
DEVICE_MSG_TYPE_CLIPBOARD,
|
||||||
|
0x00, 0x03, // text length
|
||||||
|
0x41, 0x42, 0x43, // "ABC"
|
||||||
|
};
|
||||||
|
|
||||||
|
struct device_msg msg;
|
||||||
|
ssize_t r = device_msg_deserialize(input, sizeof(input), &msg);
|
||||||
|
assert(r == 6);
|
||||||
|
|
||||||
|
assert(msg.type == DEVICE_MSG_TYPE_CLIPBOARD);
|
||||||
|
assert(msg.clipboard.text);
|
||||||
|
assert(!strcmp("ABC", msg.clipboard.text));
|
||||||
|
|
||||||
|
device_msg_destroy(&msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(void) {
|
||||||
|
test_deserialize_clipboard();
|
||||||
|
return 0;
|
||||||
|
}
|
@ -1,6 +1,6 @@
|
|||||||
#Mon Jun 04 11:48:32 CEST 2018
|
#Thu Apr 18 11:45:59 CEST 2019
|
||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-4.4-all.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.1-all.zip
|
||||||
|
@ -1,107 +0,0 @@
|
|||||||
package com.genymobile.scrcpy;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Union of all supported event types, identified by their {@code type}.
|
|
||||||
*/
|
|
||||||
public final class ControlEvent {
|
|
||||||
|
|
||||||
public static final int TYPE_KEYCODE = 0;
|
|
||||||
public static final int TYPE_TEXT = 1;
|
|
||||||
public static final int TYPE_MOUSE = 2;
|
|
||||||
public static final int TYPE_SCROLL = 3;
|
|
||||||
public static final int TYPE_COMMAND = 4;
|
|
||||||
|
|
||||||
public static final int COMMAND_BACK_OR_SCREEN_ON = 0;
|
|
||||||
public static final int COMMAND_EXPAND_NOTIFICATION_PANEL = 1;
|
|
||||||
public static final int COMMAND_COLLAPSE_NOTIFICATION_PANEL = 2;
|
|
||||||
|
|
||||||
private int type;
|
|
||||||
private String text;
|
|
||||||
private int metaState; // KeyEvent.META_*
|
|
||||||
private int action; // KeyEvent.ACTION_* or MotionEvent.ACTION_* or COMMAND_*
|
|
||||||
private int keycode; // KeyEvent.KEYCODE_*
|
|
||||||
private int buttons; // MotionEvent.BUTTON_*
|
|
||||||
private Position position;
|
|
||||||
private int hScroll;
|
|
||||||
private int vScroll;
|
|
||||||
|
|
||||||
private ControlEvent() {
|
|
||||||
}
|
|
||||||
|
|
||||||
public static ControlEvent createKeycodeControlEvent(int action, int keycode, int metaState) {
|
|
||||||
ControlEvent event = new ControlEvent();
|
|
||||||
event.type = TYPE_KEYCODE;
|
|
||||||
event.action = action;
|
|
||||||
event.keycode = keycode;
|
|
||||||
event.metaState = metaState;
|
|
||||||
return event;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static ControlEvent createTextControlEvent(String text) {
|
|
||||||
ControlEvent event = new ControlEvent();
|
|
||||||
event.type = TYPE_TEXT;
|
|
||||||
event.text = text;
|
|
||||||
return event;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static ControlEvent createMotionControlEvent(int action, int buttons, Position position) {
|
|
||||||
ControlEvent event = new ControlEvent();
|
|
||||||
event.type = TYPE_MOUSE;
|
|
||||||
event.action = action;
|
|
||||||
event.buttons = buttons;
|
|
||||||
event.position = position;
|
|
||||||
return event;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static ControlEvent createScrollControlEvent(Position position, int hScroll, int vScroll) {
|
|
||||||
ControlEvent event = new ControlEvent();
|
|
||||||
event.type = TYPE_SCROLL;
|
|
||||||
event.position = position;
|
|
||||||
event.hScroll = hScroll;
|
|
||||||
event.vScroll = vScroll;
|
|
||||||
return event;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static ControlEvent createCommandControlEvent(int action) {
|
|
||||||
ControlEvent event = new ControlEvent();
|
|
||||||
event.type = TYPE_COMMAND;
|
|
||||||
event.action = action;
|
|
||||||
return event;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getType() {
|
|
||||||
return type;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getText() {
|
|
||||||
return text;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getMetaState() {
|
|
||||||
return metaState;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getAction() {
|
|
||||||
return action;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getKeycode() {
|
|
||||||
return keycode;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getButtons() {
|
|
||||||
return buttons;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Position getPosition() {
|
|
||||||
return position;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getHScroll() {
|
|
||||||
return hScroll;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getVScroll() {
|
|
||||||
return vScroll;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,151 +0,0 @@
|
|||||||
package com.genymobile.scrcpy;
|
|
||||||
|
|
||||||
import java.io.EOFException;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.nio.ByteBuffer;
|
|
||||||
import java.nio.charset.StandardCharsets;
|
|
||||||
|
|
||||||
public class ControlEventReader {
|
|
||||||
|
|
||||||
private static final int KEYCODE_PAYLOAD_LENGTH = 9;
|
|
||||||
private static final int MOUSE_PAYLOAD_LENGTH = 17;
|
|
||||||
private static final int SCROLL_PAYLOAD_LENGTH = 20;
|
|
||||||
private static final int COMMAND_PAYLOAD_LENGTH = 1;
|
|
||||||
|
|
||||||
public static final int TEXT_MAX_LENGTH = 300;
|
|
||||||
private static final int RAW_BUFFER_SIZE = 1024;
|
|
||||||
|
|
||||||
private final byte[] rawBuffer = new byte[RAW_BUFFER_SIZE];
|
|
||||||
private final ByteBuffer buffer = ByteBuffer.wrap(rawBuffer);
|
|
||||||
private final byte[] textBuffer = new byte[TEXT_MAX_LENGTH];
|
|
||||||
|
|
||||||
public ControlEventReader() {
|
|
||||||
// invariant: the buffer is always in "get" mode
|
|
||||||
buffer.limit(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isFull() {
|
|
||||||
return buffer.remaining() == rawBuffer.length;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void readFrom(InputStream input) throws IOException {
|
|
||||||
if (isFull()) {
|
|
||||||
throw new IllegalStateException("Buffer full, call next() to consume");
|
|
||||||
}
|
|
||||||
buffer.compact();
|
|
||||||
int head = buffer.position();
|
|
||||||
int r = input.read(rawBuffer, head, rawBuffer.length - head);
|
|
||||||
if (r == -1) {
|
|
||||||
throw new EOFException("Event controller socket closed");
|
|
||||||
}
|
|
||||||
buffer.position(head + r);
|
|
||||||
buffer.flip();
|
|
||||||
}
|
|
||||||
|
|
||||||
public ControlEvent next() {
|
|
||||||
if (!buffer.hasRemaining()) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
int savedPosition = buffer.position();
|
|
||||||
|
|
||||||
int type = buffer.get();
|
|
||||||
ControlEvent controlEvent;
|
|
||||||
switch (type) {
|
|
||||||
case ControlEvent.TYPE_KEYCODE:
|
|
||||||
controlEvent = parseKeycodeControlEvent();
|
|
||||||
break;
|
|
||||||
case ControlEvent.TYPE_TEXT:
|
|
||||||
controlEvent = parseTextControlEvent();
|
|
||||||
break;
|
|
||||||
case ControlEvent.TYPE_MOUSE:
|
|
||||||
controlEvent = parseMouseControlEvent();
|
|
||||||
break;
|
|
||||||
case ControlEvent.TYPE_SCROLL:
|
|
||||||
controlEvent = parseScrollControlEvent();
|
|
||||||
break;
|
|
||||||
case ControlEvent.TYPE_COMMAND:
|
|
||||||
controlEvent = parseCommandControlEvent();
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
Ln.w("Unknown event type: " + type);
|
|
||||||
controlEvent = null;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (controlEvent == null) {
|
|
||||||
// failure, reset savedPosition
|
|
||||||
buffer.position(savedPosition);
|
|
||||||
}
|
|
||||||
return controlEvent;
|
|
||||||
}
|
|
||||||
|
|
||||||
private ControlEvent parseKeycodeControlEvent() {
|
|
||||||
if (buffer.remaining() < KEYCODE_PAYLOAD_LENGTH) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
int action = toUnsigned(buffer.get());
|
|
||||||
int keycode = buffer.getInt();
|
|
||||||
int metaState = buffer.getInt();
|
|
||||||
return ControlEvent.createKeycodeControlEvent(action, keycode, metaState);
|
|
||||||
}
|
|
||||||
|
|
||||||
private ControlEvent parseTextControlEvent() {
|
|
||||||
if (buffer.remaining() < 1) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
int len = toUnsigned(buffer.getShort());
|
|
||||||
if (buffer.remaining() < len) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
buffer.get(textBuffer, 0, len);
|
|
||||||
String text = new String(textBuffer, 0, len, StandardCharsets.UTF_8);
|
|
||||||
return ControlEvent.createTextControlEvent(text);
|
|
||||||
}
|
|
||||||
|
|
||||||
private ControlEvent parseMouseControlEvent() {
|
|
||||||
if (buffer.remaining() < MOUSE_PAYLOAD_LENGTH) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
int action = toUnsigned(buffer.get());
|
|
||||||
int buttons = buffer.getInt();
|
|
||||||
Position position = readPosition(buffer);
|
|
||||||
return ControlEvent.createMotionControlEvent(action, buttons, position);
|
|
||||||
}
|
|
||||||
|
|
||||||
private ControlEvent parseScrollControlEvent() {
|
|
||||||
if (buffer.remaining() < SCROLL_PAYLOAD_LENGTH) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
Position position = readPosition(buffer);
|
|
||||||
int hScroll = buffer.getInt();
|
|
||||||
int vScroll = buffer.getInt();
|
|
||||||
return ControlEvent.createScrollControlEvent(position, hScroll, vScroll);
|
|
||||||
}
|
|
||||||
|
|
||||||
private ControlEvent parseCommandControlEvent() {
|
|
||||||
if (buffer.remaining() < COMMAND_PAYLOAD_LENGTH) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
int action = toUnsigned(buffer.get());
|
|
||||||
return ControlEvent.createCommandControlEvent(action);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Position readPosition(ByteBuffer buffer) {
|
|
||||||
int x = buffer.getInt();
|
|
||||||
int y = buffer.getInt();
|
|
||||||
int screenWidth = toUnsigned(buffer.getShort());
|
|
||||||
int screenHeight = toUnsigned(buffer.getShort());
|
|
||||||
return new Position(x, y, screenWidth, screenHeight);
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("checkstyle:MagicNumber")
|
|
||||||
private static int toUnsigned(short value) {
|
|
||||||
return value & 0xffff;
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("checkstyle:MagicNumber")
|
|
||||||
private static int toUnsigned(byte value) {
|
|
||||||
return value & 0xff;
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,124 @@
|
|||||||
|
package com.genymobile.scrcpy;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Union of all supported event types, identified by their {@code type}.
|
||||||
|
*/
|
||||||
|
public final class ControlMessage {
|
||||||
|
|
||||||
|
public static final int TYPE_INJECT_KEYCODE = 0;
|
||||||
|
public static final int TYPE_INJECT_TEXT = 1;
|
||||||
|
public static final int TYPE_INJECT_MOUSE_EVENT = 2;
|
||||||
|
public static final int TYPE_INJECT_SCROLL_EVENT = 3;
|
||||||
|
public static final int TYPE_BACK_OR_SCREEN_ON = 4;
|
||||||
|
public static final int TYPE_EXPAND_NOTIFICATION_PANEL = 5;
|
||||||
|
public static final int TYPE_COLLAPSE_NOTIFICATION_PANEL = 6;
|
||||||
|
public static final int TYPE_GET_CLIPBOARD = 7;
|
||||||
|
public static final int TYPE_SET_CLIPBOARD = 8;
|
||||||
|
public static final int TYPE_SET_SCREEN_POWER_MODE = 9;
|
||||||
|
|
||||||
|
private int type;
|
||||||
|
private String text;
|
||||||
|
private int metaState; // KeyEvent.META_*
|
||||||
|
private int action; // KeyEvent.ACTION_* or MotionEvent.ACTION_* or POWER_MODE_*
|
||||||
|
private int keycode; // KeyEvent.KEYCODE_*
|
||||||
|
private int buttons; // MotionEvent.BUTTON_*
|
||||||
|
private Position position;
|
||||||
|
private int hScroll;
|
||||||
|
private int vScroll;
|
||||||
|
|
||||||
|
private ControlMessage() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ControlMessage createInjectKeycode(int action, int keycode, int metaState) {
|
||||||
|
ControlMessage event = new ControlMessage();
|
||||||
|
event.type = TYPE_INJECT_KEYCODE;
|
||||||
|
event.action = action;
|
||||||
|
event.keycode = keycode;
|
||||||
|
event.metaState = metaState;
|
||||||
|
return event;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ControlMessage createInjectText(String text) {
|
||||||
|
ControlMessage event = new ControlMessage();
|
||||||
|
event.type = TYPE_INJECT_TEXT;
|
||||||
|
event.text = text;
|
||||||
|
return event;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ControlMessage createInjectMouseEvent(int action, int buttons, Position position) {
|
||||||
|
ControlMessage event = new ControlMessage();
|
||||||
|
event.type = TYPE_INJECT_MOUSE_EVENT;
|
||||||
|
event.action = action;
|
||||||
|
event.buttons = buttons;
|
||||||
|
event.position = position;
|
||||||
|
return event;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ControlMessage createInjectScrollEvent(Position position, int hScroll, int vScroll) {
|
||||||
|
ControlMessage event = new ControlMessage();
|
||||||
|
event.type = TYPE_INJECT_SCROLL_EVENT;
|
||||||
|
event.position = position;
|
||||||
|
event.hScroll = hScroll;
|
||||||
|
event.vScroll = vScroll;
|
||||||
|
return event;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ControlMessage createSetClipboard(String text) {
|
||||||
|
ControlMessage event = new ControlMessage();
|
||||||
|
event.type = TYPE_SET_CLIPBOARD;
|
||||||
|
event.text = text;
|
||||||
|
return event;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param mode one of the {@code Device.SCREEN_POWER_MODE_*} constants
|
||||||
|
*/
|
||||||
|
public static ControlMessage createSetScreenPowerMode(int mode) {
|
||||||
|
ControlMessage event = new ControlMessage();
|
||||||
|
event.type = TYPE_SET_SCREEN_POWER_MODE;
|
||||||
|
event.action = mode;
|
||||||
|
return event;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ControlMessage createEmpty(int type) {
|
||||||
|
ControlMessage event = new ControlMessage();
|
||||||
|
event.type = type;
|
||||||
|
return event;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getType() {
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getText() {
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getMetaState() {
|
||||||
|
return metaState;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getAction() {
|
||||||
|
return action;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getKeycode() {
|
||||||
|
return keycode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getButtons() {
|
||||||
|
return buttons;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Position getPosition() {
|
||||||
|
return position;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getHScroll() {
|
||||||
|
return hScroll;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getVScroll() {
|
||||||
|
return vScroll;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,176 @@
|
|||||||
|
package com.genymobile.scrcpy;
|
||||||
|
|
||||||
|
import java.io.EOFException;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
|
||||||
|
public class ControlMessageReader {
|
||||||
|
|
||||||
|
private static final int INJECT_KEYCODE_PAYLOAD_LENGTH = 9;
|
||||||
|
private static final int INJECT_MOUSE_EVENT_PAYLOAD_LENGTH = 17;
|
||||||
|
private static final int INJECT_SCROLL_EVENT_PAYLOAD_LENGTH = 20;
|
||||||
|
private static final int SET_SCREEN_POWER_MODE_PAYLOAD_LENGTH = 1;
|
||||||
|
|
||||||
|
public static final int TEXT_MAX_LENGTH = 300;
|
||||||
|
public static final int CLIPBOARD_TEXT_MAX_LENGTH = 4093;
|
||||||
|
private static final int RAW_BUFFER_SIZE = 1024;
|
||||||
|
|
||||||
|
private final byte[] rawBuffer = new byte[RAW_BUFFER_SIZE];
|
||||||
|
private final ByteBuffer buffer = ByteBuffer.wrap(rawBuffer);
|
||||||
|
private final byte[] textBuffer = new byte[CLIPBOARD_TEXT_MAX_LENGTH];
|
||||||
|
|
||||||
|
public ControlMessageReader() {
|
||||||
|
// invariant: the buffer is always in "get" mode
|
||||||
|
buffer.limit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isFull() {
|
||||||
|
return buffer.remaining() == rawBuffer.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void readFrom(InputStream input) throws IOException {
|
||||||
|
if (isFull()) {
|
||||||
|
throw new IllegalStateException("Buffer full, call next() to consume");
|
||||||
|
}
|
||||||
|
buffer.compact();
|
||||||
|
int head = buffer.position();
|
||||||
|
int r = input.read(rawBuffer, head, rawBuffer.length - head);
|
||||||
|
if (r == -1) {
|
||||||
|
throw new EOFException("Controller socket closed");
|
||||||
|
}
|
||||||
|
buffer.position(head + r);
|
||||||
|
buffer.flip();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ControlMessage next() {
|
||||||
|
if (!buffer.hasRemaining()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
int savedPosition = buffer.position();
|
||||||
|
|
||||||
|
int type = buffer.get();
|
||||||
|
ControlMessage msg;
|
||||||
|
switch (type) {
|
||||||
|
case ControlMessage.TYPE_INJECT_KEYCODE:
|
||||||
|
msg = parseInjectKeycode();
|
||||||
|
break;
|
||||||
|
case ControlMessage.TYPE_INJECT_TEXT:
|
||||||
|
msg = parseInjectText();
|
||||||
|
break;
|
||||||
|
case ControlMessage.TYPE_INJECT_MOUSE_EVENT:
|
||||||
|
msg = parseInjectMouseEvent();
|
||||||
|
break;
|
||||||
|
case ControlMessage.TYPE_INJECT_SCROLL_EVENT:
|
||||||
|
msg = parseInjectScrollEvent();
|
||||||
|
break;
|
||||||
|
case ControlMessage.TYPE_SET_CLIPBOARD:
|
||||||
|
msg = parseSetClipboard();
|
||||||
|
break;
|
||||||
|
case ControlMessage.TYPE_SET_SCREEN_POWER_MODE:
|
||||||
|
msg = parseSetScreenPowerMode();
|
||||||
|
break;
|
||||||
|
case ControlMessage.TYPE_BACK_OR_SCREEN_ON:
|
||||||
|
case ControlMessage.TYPE_EXPAND_NOTIFICATION_PANEL:
|
||||||
|
case ControlMessage.TYPE_COLLAPSE_NOTIFICATION_PANEL:
|
||||||
|
case ControlMessage.TYPE_GET_CLIPBOARD:
|
||||||
|
msg = ControlMessage.createEmpty(type);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
Ln.w("Unknown event type: " + type);
|
||||||
|
msg = null;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (msg == null) {
|
||||||
|
// failure, reset savedPosition
|
||||||
|
buffer.position(savedPosition);
|
||||||
|
}
|
||||||
|
return msg;
|
||||||
|
}
|
||||||
|
|
||||||
|
private ControlMessage parseInjectKeycode() {
|
||||||
|
if (buffer.remaining() < INJECT_KEYCODE_PAYLOAD_LENGTH) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
int action = toUnsigned(buffer.get());
|
||||||
|
int keycode = buffer.getInt();
|
||||||
|
int metaState = buffer.getInt();
|
||||||
|
return ControlMessage.createInjectKeycode(action, keycode, metaState);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String parseString() {
|
||||||
|
if (buffer.remaining() < 2) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
int len = toUnsigned(buffer.getShort());
|
||||||
|
if (buffer.remaining() < len) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
buffer.get(textBuffer, 0, len);
|
||||||
|
return new String(textBuffer, 0, len, StandardCharsets.UTF_8);
|
||||||
|
}
|
||||||
|
|
||||||
|
private ControlMessage parseInjectText() {
|
||||||
|
String text = parseString();
|
||||||
|
if (text == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return ControlMessage.createInjectText(text);
|
||||||
|
}
|
||||||
|
|
||||||
|
private ControlMessage parseInjectMouseEvent() {
|
||||||
|
if (buffer.remaining() < INJECT_MOUSE_EVENT_PAYLOAD_LENGTH) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
int action = toUnsigned(buffer.get());
|
||||||
|
int buttons = buffer.getInt();
|
||||||
|
Position position = readPosition(buffer);
|
||||||
|
return ControlMessage.createInjectMouseEvent(action, buttons, position);
|
||||||
|
}
|
||||||
|
|
||||||
|
private ControlMessage parseInjectScrollEvent() {
|
||||||
|
if (buffer.remaining() < INJECT_SCROLL_EVENT_PAYLOAD_LENGTH) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
Position position = readPosition(buffer);
|
||||||
|
int hScroll = buffer.getInt();
|
||||||
|
int vScroll = buffer.getInt();
|
||||||
|
return ControlMessage.createInjectScrollEvent(position, hScroll, vScroll);
|
||||||
|
}
|
||||||
|
|
||||||
|
private ControlMessage parseSetClipboard() {
|
||||||
|
String text = parseString();
|
||||||
|
if (text == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return ControlMessage.createSetClipboard(text);
|
||||||
|
}
|
||||||
|
|
||||||
|
private ControlMessage parseSetScreenPowerMode() {
|
||||||
|
if (buffer.remaining() < SET_SCREEN_POWER_MODE_PAYLOAD_LENGTH) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
int mode = buffer.get();
|
||||||
|
return ControlMessage.createSetScreenPowerMode(mode);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Position readPosition(ByteBuffer buffer) {
|
||||||
|
int x = buffer.getInt();
|
||||||
|
int y = buffer.getInt();
|
||||||
|
int screenWidth = toUnsigned(buffer.getShort());
|
||||||
|
int screenHeight = toUnsigned(buffer.getShort());
|
||||||
|
return new Position(x, y, screenWidth, screenHeight);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("checkstyle:MagicNumber")
|
||||||
|
private static int toUnsigned(short value) {
|
||||||
|
return value & 0xffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("checkstyle:MagicNumber")
|
||||||
|
private static int toUnsigned(byte value) {
|
||||||
|
return value & 0xff;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,27 @@
|
|||||||
|
package com.genymobile.scrcpy;
|
||||||
|
|
||||||
|
public final class DeviceMessage {
|
||||||
|
|
||||||
|
public static final int TYPE_CLIPBOARD = 0;
|
||||||
|
|
||||||
|
private int type;
|
||||||
|
private String text;
|
||||||
|
|
||||||
|
private DeviceMessage() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static DeviceMessage createClipboard(String text) {
|
||||||
|
DeviceMessage event = new DeviceMessage();
|
||||||
|
event.type = TYPE_CLIPBOARD;
|
||||||
|
event.text = text;
|
||||||
|
return event;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getType() {
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getText() {
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,34 @@
|
|||||||
|
package com.genymobile.scrcpy;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
public final class DeviceMessageSender {
|
||||||
|
|
||||||
|
private final DesktopConnection connection;
|
||||||
|
|
||||||
|
private String clipboardText;
|
||||||
|
|
||||||
|
public DeviceMessageSender(DesktopConnection connection) {
|
||||||
|
this.connection = connection;
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized void pushClipboardText(String text) {
|
||||||
|
clipboardText = text;
|
||||||
|
notify();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void loop() throws IOException, InterruptedException {
|
||||||
|
while (true) {
|
||||||
|
String text;
|
||||||
|
synchronized (this) {
|
||||||
|
while (clipboardText == null) {
|
||||||
|
wait();
|
||||||
|
}
|
||||||
|
text = clipboardText;
|
||||||
|
clipboardText = null;
|
||||||
|
}
|
||||||
|
DeviceMessage event = DeviceMessage.createClipboard(text);
|
||||||
|
connection.sendDeviceMessage(event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,34 @@
|
|||||||
|
package com.genymobile.scrcpy;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
|
||||||
|
public class DeviceMessageWriter {
|
||||||
|
|
||||||
|
public static final int CLIPBOARD_TEXT_MAX_LENGTH = 4093;
|
||||||
|
private static final int MAX_EVENT_SIZE = CLIPBOARD_TEXT_MAX_LENGTH + 3;
|
||||||
|
|
||||||
|
private final byte[] rawBuffer = new byte[MAX_EVENT_SIZE];
|
||||||
|
private final ByteBuffer buffer = ByteBuffer.wrap(rawBuffer);
|
||||||
|
|
||||||
|
@SuppressWarnings("checkstyle:MagicNumber")
|
||||||
|
public void writeTo(DeviceMessage msg, OutputStream output) throws IOException {
|
||||||
|
buffer.clear();
|
||||||
|
buffer.put((byte) DeviceMessage.TYPE_CLIPBOARD);
|
||||||
|
switch (msg.getType()) {
|
||||||
|
case DeviceMessage.TYPE_CLIPBOARD:
|
||||||
|
String text = msg.getText();
|
||||||
|
byte[] raw = text.getBytes(StandardCharsets.UTF_8);
|
||||||
|
int len = StringUtils.getUtf8TruncationIndex(raw, CLIPBOARD_TEXT_MAX_LENGTH);
|
||||||
|
buffer.putShort((short) len);
|
||||||
|
buffer.put(raw, 0, len);
|
||||||
|
output.write(rawBuffer, 0, buffer.position());
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
Ln.w("Unknown device message: " + msg.getType());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,47 @@
|
|||||||
|
package com.genymobile.scrcpy;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
public class Point {
|
||||||
|
private final int x;
|
||||||
|
private final int y;
|
||||||
|
|
||||||
|
public Point(int x, int y) {
|
||||||
|
this.x = x;
|
||||||
|
this.y = y;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getX() {
|
||||||
|
return x;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getY() {
|
||||||
|
return y;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (o == null || getClass() != o.getClass()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
Point point = (Point) o;
|
||||||
|
return x == point.x
|
||||||
|
&& y == point.y;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return Objects.hash(x, y);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "Point{"
|
||||||
|
+ "x=" + x
|
||||||
|
+ ", y=" + y
|
||||||
|
+ '}';
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,23 @@
|
|||||||
|
package com.genymobile.scrcpy;
|
||||||
|
|
||||||
|
public final class StringUtils {
|
||||||
|
private StringUtils() {
|
||||||
|
// not instantiable
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("checkstyle:MagicNumber")
|
||||||
|
public static int getUtf8TruncationIndex(byte[] utf8, int maxLength) {
|
||||||
|
int len = utf8.length;
|
||||||
|
if (len <= maxLength) {
|
||||||
|
return len;
|
||||||
|
}
|
||||||
|
len = maxLength;
|
||||||
|
// see UTF-8 encoding <https://en.wikipedia.org/wiki/UTF-8#Description>
|
||||||
|
while ((utf8[len] & 0x80) != 0 && (utf8[len] & 0xc0) != 0xc0) {
|
||||||
|
// the next byte is not the start of a new UTF-8 codepoint
|
||||||
|
// so if we would cut there, the character would be truncated
|
||||||
|
len--;
|
||||||
|
}
|
||||||
|
return len;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,44 @@
|
|||||||
|
package com.genymobile.scrcpy.wrappers;
|
||||||
|
|
||||||
|
import android.content.ClipData;
|
||||||
|
import android.os.IInterface;
|
||||||
|
|
||||||
|
import java.lang.reflect.InvocationTargetException;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
|
||||||
|
public class ClipboardManager {
|
||||||
|
private final IInterface manager;
|
||||||
|
private final Method getPrimaryClipMethod;
|
||||||
|
private final Method setPrimaryClipMethod;
|
||||||
|
|
||||||
|
public ClipboardManager(IInterface manager) {
|
||||||
|
this.manager = manager;
|
||||||
|
try {
|
||||||
|
getPrimaryClipMethod = manager.getClass().getMethod("getPrimaryClip", String.class);
|
||||||
|
setPrimaryClipMethod = manager.getClass().getMethod("setPrimaryClip", ClipData.class, String.class);
|
||||||
|
} catch (NoSuchMethodException e) {
|
||||||
|
throw new AssertionError(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public CharSequence getText() {
|
||||||
|
try {
|
||||||
|
ClipData clipData = (ClipData) getPrimaryClipMethod.invoke(manager, "com.android.shell");
|
||||||
|
if (clipData == null || clipData.getItemCount() == 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return clipData.getItemAt(0).getText();
|
||||||
|
} catch (InvocationTargetException | IllegalAccessException e) {
|
||||||
|
throw new AssertionError(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setText(CharSequence text) {
|
||||||
|
ClipData clipData = ClipData.newPlainText(null, text);
|
||||||
|
try {
|
||||||
|
setPrimaryClipMethod.invoke(manager, clipData, "com.android.shell");
|
||||||
|
} catch (InvocationTargetException | IllegalAccessException e) {
|
||||||
|
throw new AssertionError(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,173 +0,0 @@
|
|||||||
package com.genymobile.scrcpy;
|
|
||||||
|
|
||||||
import android.view.KeyEvent;
|
|
||||||
import android.view.MotionEvent;
|
|
||||||
|
|
||||||
import org.junit.Assert;
|
|
||||||
import org.junit.Test;
|
|
||||||
|
|
||||||
import java.io.ByteArrayInputStream;
|
|
||||||
import java.io.ByteArrayOutputStream;
|
|
||||||
import java.io.DataOutputStream;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.nio.charset.StandardCharsets;
|
|
||||||
import java.util.Arrays;
|
|
||||||
|
|
||||||
|
|
||||||
public class ControlEventReaderTest {
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testParseKeycodeEvent() throws IOException {
|
|
||||||
ControlEventReader reader = new ControlEventReader();
|
|
||||||
|
|
||||||
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
|
||||||
DataOutputStream dos = new DataOutputStream(bos);
|
|
||||||
dos.writeByte(ControlEvent.TYPE_KEYCODE);
|
|
||||||
dos.writeByte(KeyEvent.ACTION_UP);
|
|
||||||
dos.writeInt(KeyEvent.KEYCODE_ENTER);
|
|
||||||
dos.writeInt(KeyEvent.META_CTRL_ON);
|
|
||||||
byte[] packet = bos.toByteArray();
|
|
||||||
|
|
||||||
reader.readFrom(new ByteArrayInputStream(packet));
|
|
||||||
ControlEvent event = reader.next();
|
|
||||||
|
|
||||||
Assert.assertEquals(ControlEvent.TYPE_KEYCODE, event.getType());
|
|
||||||
Assert.assertEquals(KeyEvent.ACTION_UP, event.getAction());
|
|
||||||
Assert.assertEquals(KeyEvent.KEYCODE_ENTER, event.getKeycode());
|
|
||||||
Assert.assertEquals(KeyEvent.META_CTRL_ON, event.getMetaState());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testParseTextEvent() throws IOException {
|
|
||||||
ControlEventReader reader = new ControlEventReader();
|
|
||||||
|
|
||||||
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
|
||||||
DataOutputStream dos = new DataOutputStream(bos);
|
|
||||||
dos.writeByte(ControlEvent.TYPE_TEXT);
|
|
||||||
byte[] text = "testé".getBytes(StandardCharsets.UTF_8);
|
|
||||||
dos.writeShort(text.length);
|
|
||||||
dos.write(text);
|
|
||||||
byte[] packet = bos.toByteArray();
|
|
||||||
|
|
||||||
reader.readFrom(new ByteArrayInputStream(packet));
|
|
||||||
ControlEvent event = reader.next();
|
|
||||||
|
|
||||||
Assert.assertEquals(ControlEvent.TYPE_TEXT, event.getType());
|
|
||||||
Assert.assertEquals("testé", event.getText());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testParseLongTextEvent() throws IOException {
|
|
||||||
ControlEventReader reader = new ControlEventReader();
|
|
||||||
|
|
||||||
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
|
||||||
DataOutputStream dos = new DataOutputStream(bos);
|
|
||||||
dos.writeByte(ControlEvent.TYPE_TEXT);
|
|
||||||
byte[] text = new byte[ControlEventReader.TEXT_MAX_LENGTH];
|
|
||||||
Arrays.fill(text, (byte) 'a');
|
|
||||||
dos.writeShort(text.length);
|
|
||||||
dos.write(text);
|
|
||||||
byte[] packet = bos.toByteArray();
|
|
||||||
|
|
||||||
reader.readFrom(new ByteArrayInputStream(packet));
|
|
||||||
ControlEvent event = reader.next();
|
|
||||||
|
|
||||||
Assert.assertEquals(ControlEvent.TYPE_TEXT, event.getType());
|
|
||||||
Assert.assertEquals(new String(text, StandardCharsets.US_ASCII), event.getText());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testParseMouseEvent() throws IOException {
|
|
||||||
ControlEventReader reader = new ControlEventReader();
|
|
||||||
|
|
||||||
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
|
||||||
DataOutputStream dos = new DataOutputStream(bos);
|
|
||||||
dos.writeByte(ControlEvent.TYPE_KEYCODE);
|
|
||||||
dos.writeByte(MotionEvent.ACTION_DOWN);
|
|
||||||
dos.writeInt(MotionEvent.BUTTON_PRIMARY);
|
|
||||||
dos.writeInt(KeyEvent.META_CTRL_ON);
|
|
||||||
byte[] packet = bos.toByteArray();
|
|
||||||
|
|
||||||
reader.readFrom(new ByteArrayInputStream(packet));
|
|
||||||
ControlEvent event = reader.next();
|
|
||||||
|
|
||||||
Assert.assertEquals(ControlEvent.TYPE_KEYCODE, event.getType());
|
|
||||||
Assert.assertEquals(MotionEvent.ACTION_DOWN, event.getAction());
|
|
||||||
Assert.assertEquals(MotionEvent.BUTTON_PRIMARY, event.getKeycode());
|
|
||||||
Assert.assertEquals(KeyEvent.META_CTRL_ON, event.getMetaState());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testMultiEvents() throws IOException {
|
|
||||||
ControlEventReader reader = new ControlEventReader();
|
|
||||||
|
|
||||||
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
|
||||||
DataOutputStream dos = new DataOutputStream(bos);
|
|
||||||
|
|
||||||
dos.writeByte(ControlEvent.TYPE_KEYCODE);
|
|
||||||
dos.writeByte(KeyEvent.ACTION_UP);
|
|
||||||
dos.writeInt(KeyEvent.KEYCODE_ENTER);
|
|
||||||
dos.writeInt(KeyEvent.META_CTRL_ON);
|
|
||||||
|
|
||||||
dos.writeByte(ControlEvent.TYPE_KEYCODE);
|
|
||||||
dos.writeByte(MotionEvent.ACTION_DOWN);
|
|
||||||
dos.writeInt(MotionEvent.BUTTON_PRIMARY);
|
|
||||||
dos.writeInt(KeyEvent.META_CTRL_ON);
|
|
||||||
|
|
||||||
byte[] packet = bos.toByteArray();
|
|
||||||
reader.readFrom(new ByteArrayInputStream(packet));
|
|
||||||
|
|
||||||
ControlEvent event = reader.next();
|
|
||||||
Assert.assertEquals(ControlEvent.TYPE_KEYCODE, event.getType());
|
|
||||||
Assert.assertEquals(KeyEvent.ACTION_UP, event.getAction());
|
|
||||||
Assert.assertEquals(KeyEvent.KEYCODE_ENTER, event.getKeycode());
|
|
||||||
Assert.assertEquals(KeyEvent.META_CTRL_ON, event.getMetaState());
|
|
||||||
|
|
||||||
event = reader.next();
|
|
||||||
Assert.assertEquals(ControlEvent.TYPE_KEYCODE, event.getType());
|
|
||||||
Assert.assertEquals(MotionEvent.ACTION_DOWN, event.getAction());
|
|
||||||
Assert.assertEquals(MotionEvent.BUTTON_PRIMARY, event.getKeycode());
|
|
||||||
Assert.assertEquals(KeyEvent.META_CTRL_ON, event.getMetaState());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testPartialEvents() throws IOException {
|
|
||||||
ControlEventReader reader = new ControlEventReader();
|
|
||||||
|
|
||||||
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
|
||||||
DataOutputStream dos = new DataOutputStream(bos);
|
|
||||||
|
|
||||||
dos.writeByte(ControlEvent.TYPE_KEYCODE);
|
|
||||||
dos.writeByte(KeyEvent.ACTION_UP);
|
|
||||||
dos.writeInt(KeyEvent.KEYCODE_ENTER);
|
|
||||||
dos.writeInt(KeyEvent.META_CTRL_ON);
|
|
||||||
|
|
||||||
dos.writeByte(ControlEvent.TYPE_KEYCODE);
|
|
||||||
dos.writeByte(MotionEvent.ACTION_DOWN);
|
|
||||||
|
|
||||||
byte[] packet = bos.toByteArray();
|
|
||||||
reader.readFrom(new ByteArrayInputStream(packet));
|
|
||||||
|
|
||||||
ControlEvent event = reader.next();
|
|
||||||
Assert.assertEquals(ControlEvent.TYPE_KEYCODE, event.getType());
|
|
||||||
Assert.assertEquals(KeyEvent.ACTION_UP, event.getAction());
|
|
||||||
Assert.assertEquals(KeyEvent.KEYCODE_ENTER, event.getKeycode());
|
|
||||||
Assert.assertEquals(KeyEvent.META_CTRL_ON, event.getMetaState());
|
|
||||||
|
|
||||||
event = reader.next();
|
|
||||||
Assert.assertNull(event); // the event is not complete
|
|
||||||
|
|
||||||
bos.reset();
|
|
||||||
dos.writeInt(MotionEvent.BUTTON_PRIMARY);
|
|
||||||
dos.writeInt(KeyEvent.META_CTRL_ON);
|
|
||||||
packet = bos.toByteArray();
|
|
||||||
reader.readFrom(new ByteArrayInputStream(packet));
|
|
||||||
|
|
||||||
// the event is now complete
|
|
||||||
event = reader.next();
|
|
||||||
Assert.assertEquals(ControlEvent.TYPE_KEYCODE, event.getType());
|
|
||||||
Assert.assertEquals(MotionEvent.ACTION_DOWN, event.getAction());
|
|
||||||
Assert.assertEquals(MotionEvent.BUTTON_PRIMARY, event.getKeycode());
|
|
||||||
Assert.assertEquals(KeyEvent.META_CTRL_ON, event.getMetaState());
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,304 @@
|
|||||||
|
package com.genymobile.scrcpy;
|
||||||
|
|
||||||
|
import android.view.KeyEvent;
|
||||||
|
import android.view.MotionEvent;
|
||||||
|
|
||||||
|
import org.junit.Assert;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.DataOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
|
||||||
|
public class ControlMessageReaderTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testParseKeycodeEvent() throws IOException {
|
||||||
|
ControlMessageReader reader = new ControlMessageReader();
|
||||||
|
|
||||||
|
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||||
|
DataOutputStream dos = new DataOutputStream(bos);
|
||||||
|
dos.writeByte(ControlMessage.TYPE_INJECT_KEYCODE);
|
||||||
|
dos.writeByte(KeyEvent.ACTION_UP);
|
||||||
|
dos.writeInt(KeyEvent.KEYCODE_ENTER);
|
||||||
|
dos.writeInt(KeyEvent.META_CTRL_ON);
|
||||||
|
byte[] packet = bos.toByteArray();
|
||||||
|
|
||||||
|
reader.readFrom(new ByteArrayInputStream(packet));
|
||||||
|
ControlMessage event = reader.next();
|
||||||
|
|
||||||
|
Assert.assertEquals(ControlMessage.TYPE_INJECT_KEYCODE, event.getType());
|
||||||
|
Assert.assertEquals(KeyEvent.ACTION_UP, event.getAction());
|
||||||
|
Assert.assertEquals(KeyEvent.KEYCODE_ENTER, event.getKeycode());
|
||||||
|
Assert.assertEquals(KeyEvent.META_CTRL_ON, event.getMetaState());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testParseTextEvent() throws IOException {
|
||||||
|
ControlMessageReader reader = new ControlMessageReader();
|
||||||
|
|
||||||
|
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||||
|
DataOutputStream dos = new DataOutputStream(bos);
|
||||||
|
dos.writeByte(ControlMessage.TYPE_INJECT_TEXT);
|
||||||
|
byte[] text = "testé".getBytes(StandardCharsets.UTF_8);
|
||||||
|
dos.writeShort(text.length);
|
||||||
|
dos.write(text);
|
||||||
|
byte[] packet = bos.toByteArray();
|
||||||
|
|
||||||
|
reader.readFrom(new ByteArrayInputStream(packet));
|
||||||
|
ControlMessage event = reader.next();
|
||||||
|
|
||||||
|
Assert.assertEquals(ControlMessage.TYPE_INJECT_TEXT, event.getType());
|
||||||
|
Assert.assertEquals("testé", event.getText());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testParseLongTextEvent() throws IOException {
|
||||||
|
ControlMessageReader reader = new ControlMessageReader();
|
||||||
|
|
||||||
|
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||||
|
DataOutputStream dos = new DataOutputStream(bos);
|
||||||
|
dos.writeByte(ControlMessage.TYPE_INJECT_TEXT);
|
||||||
|
byte[] text = new byte[ControlMessageReader.TEXT_MAX_LENGTH];
|
||||||
|
Arrays.fill(text, (byte) 'a');
|
||||||
|
dos.writeShort(text.length);
|
||||||
|
dos.write(text);
|
||||||
|
byte[] packet = bos.toByteArray();
|
||||||
|
|
||||||
|
reader.readFrom(new ByteArrayInputStream(packet));
|
||||||
|
ControlMessage event = reader.next();
|
||||||
|
|
||||||
|
Assert.assertEquals(ControlMessage.TYPE_INJECT_TEXT, event.getType());
|
||||||
|
Assert.assertEquals(new String(text, StandardCharsets.US_ASCII), event.getText());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testParseMouseEvent() throws IOException {
|
||||||
|
ControlMessageReader reader = new ControlMessageReader();
|
||||||
|
|
||||||
|
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||||
|
DataOutputStream dos = new DataOutputStream(bos);
|
||||||
|
dos.writeByte(ControlMessage.TYPE_INJECT_KEYCODE);
|
||||||
|
dos.writeByte(MotionEvent.ACTION_DOWN);
|
||||||
|
dos.writeInt(MotionEvent.BUTTON_PRIMARY);
|
||||||
|
dos.writeInt(KeyEvent.META_CTRL_ON);
|
||||||
|
byte[] packet = bos.toByteArray();
|
||||||
|
|
||||||
|
reader.readFrom(new ByteArrayInputStream(packet));
|
||||||
|
ControlMessage event = reader.next();
|
||||||
|
|
||||||
|
Assert.assertEquals(ControlMessage.TYPE_INJECT_KEYCODE, event.getType());
|
||||||
|
Assert.assertEquals(MotionEvent.ACTION_DOWN, event.getAction());
|
||||||
|
Assert.assertEquals(MotionEvent.BUTTON_PRIMARY, event.getKeycode());
|
||||||
|
Assert.assertEquals(KeyEvent.META_CTRL_ON, event.getMetaState());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@SuppressWarnings("checkstyle:MagicNumber")
|
||||||
|
public void testParseScrollEvent() throws IOException {
|
||||||
|
ControlMessageReader reader = new ControlMessageReader();
|
||||||
|
|
||||||
|
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||||
|
DataOutputStream dos = new DataOutputStream(bos);
|
||||||
|
dos.writeByte(ControlMessage.TYPE_INJECT_SCROLL_EVENT);
|
||||||
|
dos.writeInt(260);
|
||||||
|
dos.writeInt(1026);
|
||||||
|
dos.writeShort(1080);
|
||||||
|
dos.writeShort(1920);
|
||||||
|
dos.writeInt(1);
|
||||||
|
dos.writeInt(-1);
|
||||||
|
|
||||||
|
byte[] packet = bos.toByteArray();
|
||||||
|
|
||||||
|
reader.readFrom(new ByteArrayInputStream(packet));
|
||||||
|
ControlMessage event = reader.next();
|
||||||
|
|
||||||
|
Assert.assertEquals(ControlMessage.TYPE_INJECT_SCROLL_EVENT, event.getType());
|
||||||
|
Assert.assertEquals(260, event.getPosition().getPoint().getX());
|
||||||
|
Assert.assertEquals(1026, event.getPosition().getPoint().getY());
|
||||||
|
Assert.assertEquals(1080, event.getPosition().getScreenSize().getWidth());
|
||||||
|
Assert.assertEquals(1920, event.getPosition().getScreenSize().getHeight());
|
||||||
|
Assert.assertEquals(1, event.getHScroll());
|
||||||
|
Assert.assertEquals(-1, event.getVScroll());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testParseBackOrScreenOnEvent() throws IOException {
|
||||||
|
ControlMessageReader reader = new ControlMessageReader();
|
||||||
|
|
||||||
|
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||||
|
DataOutputStream dos = new DataOutputStream(bos);
|
||||||
|
dos.writeByte(ControlMessage.TYPE_BACK_OR_SCREEN_ON);
|
||||||
|
|
||||||
|
byte[] packet = bos.toByteArray();
|
||||||
|
|
||||||
|
reader.readFrom(new ByteArrayInputStream(packet));
|
||||||
|
ControlMessage event = reader.next();
|
||||||
|
|
||||||
|
Assert.assertEquals(ControlMessage.TYPE_BACK_OR_SCREEN_ON, event.getType());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testParseExpandNotificationPanelEvent() throws IOException {
|
||||||
|
ControlMessageReader reader = new ControlMessageReader();
|
||||||
|
|
||||||
|
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||||
|
DataOutputStream dos = new DataOutputStream(bos);
|
||||||
|
dos.writeByte(ControlMessage.TYPE_EXPAND_NOTIFICATION_PANEL);
|
||||||
|
|
||||||
|
byte[] packet = bos.toByteArray();
|
||||||
|
|
||||||
|
reader.readFrom(new ByteArrayInputStream(packet));
|
||||||
|
ControlMessage event = reader.next();
|
||||||
|
|
||||||
|
Assert.assertEquals(ControlMessage.TYPE_EXPAND_NOTIFICATION_PANEL, event.getType());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testParseCollapseNotificationPanelEvent() throws IOException {
|
||||||
|
ControlMessageReader reader = new ControlMessageReader();
|
||||||
|
|
||||||
|
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||||
|
DataOutputStream dos = new DataOutputStream(bos);
|
||||||
|
dos.writeByte(ControlMessage.TYPE_COLLAPSE_NOTIFICATION_PANEL);
|
||||||
|
|
||||||
|
byte[] packet = bos.toByteArray();
|
||||||
|
|
||||||
|
reader.readFrom(new ByteArrayInputStream(packet));
|
||||||
|
ControlMessage event = reader.next();
|
||||||
|
|
||||||
|
Assert.assertEquals(ControlMessage.TYPE_COLLAPSE_NOTIFICATION_PANEL, event.getType());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testParseGetClipboardEvent() throws IOException {
|
||||||
|
ControlMessageReader reader = new ControlMessageReader();
|
||||||
|
|
||||||
|
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||||
|
DataOutputStream dos = new DataOutputStream(bos);
|
||||||
|
dos.writeByte(ControlMessage.TYPE_GET_CLIPBOARD);
|
||||||
|
|
||||||
|
byte[] packet = bos.toByteArray();
|
||||||
|
|
||||||
|
reader.readFrom(new ByteArrayInputStream(packet));
|
||||||
|
ControlMessage event = reader.next();
|
||||||
|
|
||||||
|
Assert.assertEquals(ControlMessage.TYPE_GET_CLIPBOARD, event.getType());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testParseSetClipboardEvent() throws IOException {
|
||||||
|
ControlMessageReader reader = new ControlMessageReader();
|
||||||
|
|
||||||
|
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||||
|
DataOutputStream dos = new DataOutputStream(bos);
|
||||||
|
dos.writeByte(ControlMessage.TYPE_SET_CLIPBOARD);
|
||||||
|
byte[] text = "testé".getBytes(StandardCharsets.UTF_8);
|
||||||
|
dos.writeShort(text.length);
|
||||||
|
dos.write(text);
|
||||||
|
|
||||||
|
byte[] packet = bos.toByteArray();
|
||||||
|
|
||||||
|
reader.readFrom(new ByteArrayInputStream(packet));
|
||||||
|
ControlMessage event = reader.next();
|
||||||
|
|
||||||
|
Assert.assertEquals(ControlMessage.TYPE_SET_CLIPBOARD, event.getType());
|
||||||
|
Assert.assertEquals("testé", event.getText());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testParseSetScreenPowerMode() throws IOException {
|
||||||
|
ControlMessageReader reader = new ControlMessageReader();
|
||||||
|
|
||||||
|
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||||
|
DataOutputStream dos = new DataOutputStream(bos);
|
||||||
|
dos.writeByte(ControlMessage.TYPE_SET_SCREEN_POWER_MODE);
|
||||||
|
dos.writeByte(Device.POWER_MODE_NORMAL);
|
||||||
|
|
||||||
|
byte[] packet = bos.toByteArray();
|
||||||
|
|
||||||
|
reader.readFrom(new ByteArrayInputStream(packet));
|
||||||
|
ControlMessage event = reader.next();
|
||||||
|
|
||||||
|
Assert.assertEquals(ControlMessage.TYPE_SET_SCREEN_POWER_MODE, event.getType());
|
||||||
|
Assert.assertEquals(Device.POWER_MODE_NORMAL, event.getAction());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testMultiEvents() throws IOException {
|
||||||
|
ControlMessageReader reader = new ControlMessageReader();
|
||||||
|
|
||||||
|
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||||
|
DataOutputStream dos = new DataOutputStream(bos);
|
||||||
|
|
||||||
|
dos.writeByte(ControlMessage.TYPE_INJECT_KEYCODE);
|
||||||
|
dos.writeByte(KeyEvent.ACTION_UP);
|
||||||
|
dos.writeInt(KeyEvent.KEYCODE_ENTER);
|
||||||
|
dos.writeInt(KeyEvent.META_CTRL_ON);
|
||||||
|
|
||||||
|
dos.writeByte(ControlMessage.TYPE_INJECT_KEYCODE);
|
||||||
|
dos.writeByte(MotionEvent.ACTION_DOWN);
|
||||||
|
dos.writeInt(MotionEvent.BUTTON_PRIMARY);
|
||||||
|
dos.writeInt(KeyEvent.META_CTRL_ON);
|
||||||
|
|
||||||
|
byte[] packet = bos.toByteArray();
|
||||||
|
reader.readFrom(new ByteArrayInputStream(packet));
|
||||||
|
|
||||||
|
ControlMessage event = reader.next();
|
||||||
|
Assert.assertEquals(ControlMessage.TYPE_INJECT_KEYCODE, event.getType());
|
||||||
|
Assert.assertEquals(KeyEvent.ACTION_UP, event.getAction());
|
||||||
|
Assert.assertEquals(KeyEvent.KEYCODE_ENTER, event.getKeycode());
|
||||||
|
Assert.assertEquals(KeyEvent.META_CTRL_ON, event.getMetaState());
|
||||||
|
|
||||||
|
event = reader.next();
|
||||||
|
Assert.assertEquals(ControlMessage.TYPE_INJECT_KEYCODE, event.getType());
|
||||||
|
Assert.assertEquals(MotionEvent.ACTION_DOWN, event.getAction());
|
||||||
|
Assert.assertEquals(MotionEvent.BUTTON_PRIMARY, event.getKeycode());
|
||||||
|
Assert.assertEquals(KeyEvent.META_CTRL_ON, event.getMetaState());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testPartialEvents() throws IOException {
|
||||||
|
ControlMessageReader reader = new ControlMessageReader();
|
||||||
|
|
||||||
|
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||||
|
DataOutputStream dos = new DataOutputStream(bos);
|
||||||
|
|
||||||
|
dos.writeByte(ControlMessage.TYPE_INJECT_KEYCODE);
|
||||||
|
dos.writeByte(KeyEvent.ACTION_UP);
|
||||||
|
dos.writeInt(KeyEvent.KEYCODE_ENTER);
|
||||||
|
dos.writeInt(KeyEvent.META_CTRL_ON);
|
||||||
|
|
||||||
|
dos.writeByte(ControlMessage.TYPE_INJECT_KEYCODE);
|
||||||
|
dos.writeByte(MotionEvent.ACTION_DOWN);
|
||||||
|
|
||||||
|
byte[] packet = bos.toByteArray();
|
||||||
|
reader.readFrom(new ByteArrayInputStream(packet));
|
||||||
|
|
||||||
|
ControlMessage event = reader.next();
|
||||||
|
Assert.assertEquals(ControlMessage.TYPE_INJECT_KEYCODE, event.getType());
|
||||||
|
Assert.assertEquals(KeyEvent.ACTION_UP, event.getAction());
|
||||||
|
Assert.assertEquals(KeyEvent.KEYCODE_ENTER, event.getKeycode());
|
||||||
|
Assert.assertEquals(KeyEvent.META_CTRL_ON, event.getMetaState());
|
||||||
|
|
||||||
|
event = reader.next();
|
||||||
|
Assert.assertNull(event); // the event is not complete
|
||||||
|
|
||||||
|
bos.reset();
|
||||||
|
dos.writeInt(MotionEvent.BUTTON_PRIMARY);
|
||||||
|
dos.writeInt(KeyEvent.META_CTRL_ON);
|
||||||
|
packet = bos.toByteArray();
|
||||||
|
reader.readFrom(new ByteArrayInputStream(packet));
|
||||||
|
|
||||||
|
// the event is now complete
|
||||||
|
event = reader.next();
|
||||||
|
Assert.assertEquals(ControlMessage.TYPE_INJECT_KEYCODE, event.getType());
|
||||||
|
Assert.assertEquals(MotionEvent.ACTION_DOWN, event.getAction());
|
||||||
|
Assert.assertEquals(MotionEvent.BUTTON_PRIMARY, event.getKeycode());
|
||||||
|
Assert.assertEquals(KeyEvent.META_CTRL_ON, event.getMetaState());
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,43 @@
|
|||||||
|
package com.genymobile.scrcpy;
|
||||||
|
|
||||||
|
import org.junit.Assert;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
|
||||||
|
public class StringUtilsTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@SuppressWarnings("checkstyle:MagicNumber")
|
||||||
|
public void testUtf8Truncate() {
|
||||||
|
String s = "aÉbÔc";
|
||||||
|
byte[] utf8 = s.getBytes(StandardCharsets.UTF_8);
|
||||||
|
Assert.assertEquals(7, utf8.length);
|
||||||
|
|
||||||
|
int count;
|
||||||
|
|
||||||
|
count = StringUtils.getUtf8TruncationIndex(utf8, 1);
|
||||||
|
Assert.assertEquals(1, count);
|
||||||
|
|
||||||
|
count = StringUtils.getUtf8TruncationIndex(utf8, 2);
|
||||||
|
Assert.assertEquals(1, count); // É is 2 bytes-wide
|
||||||
|
|
||||||
|
count = StringUtils.getUtf8TruncationIndex(utf8, 3);
|
||||||
|
Assert.assertEquals(3, count);
|
||||||
|
|
||||||
|
count = StringUtils.getUtf8TruncationIndex(utf8, 4);
|
||||||
|
Assert.assertEquals(4, count);
|
||||||
|
|
||||||
|
count = StringUtils.getUtf8TruncationIndex(utf8, 5);
|
||||||
|
Assert.assertEquals(4, count); // Ô is 2 bytes-wide
|
||||||
|
|
||||||
|
count = StringUtils.getUtf8TruncationIndex(utf8, 6);
|
||||||
|
Assert.assertEquals(6, count);
|
||||||
|
|
||||||
|
count = StringUtils.getUtf8TruncationIndex(utf8, 7);
|
||||||
|
Assert.assertEquals(7, count);
|
||||||
|
|
||||||
|
count = StringUtils.getUtf8TruncationIndex(utf8, 8);
|
||||||
|
Assert.assertEquals(7, count); // no more chars
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue