Compare commits

...

15 Commits

Author SHA1 Message Date
Romain Vimont
f7d4b6d0db Do not crash on missing clipboard manager
Some devices have no clipboard manager.

In that case, do not try to enable clipboard synchronization to avoid
a crash.

Fixes #1440 <https://github.com/Genymobile/scrcpy/issues/1440>
Fixes #1556 <https://github.com/Genymobile/scrcpy/issues/1556>
2020-07-03 08:53:51 +02:00
Romain Vimont
e8a565f9ea Fix touch events HiDPI-scaling
Touch events were HiDPI-scaled twice:
 - once because the position (provided as floats between 0 and 1) were
   converted in pixels using the drawable size (not the window size)
 - once due to screen_convert_to_frame_coords()

One possible fix could be to compute the position in pixels from the
window size instead, but this would unnecessarily round the event
position to the nearest window coordinates (instead of drawable
coordinates).

Instead, expose two separate functions to convert to frame coordinates
from either window or drawable coordinates.

Fixes #1536 <https://github.com/Genymobile/scrcpy/issues/1536>
Refs #15 <https://github.com/Genymobile/scrcpy/issues/15>
Refs e40532a376
2020-06-27 15:45:28 +02:00
xeropresence
3c1ed5d86c Handle repeating keycodes
Initialize "repeat" count on key events.

PR #1519 <https://github.com/Genymobile/scrcpy/pull/1519>
Refs #1013 <https://github.com/Genymobile/scrcpy/pull/1013>

Signed-off-by: Romain Vimont <rom@rom1v.com>
2020-06-19 22:30:17 +02:00
Romain Vimont
0ba74fbd9a Make scrcpy.h independant of other headers
The header scrcpy.h is intended to be the "public" API. It should not
depend on other internal headers.

Therefore, declare all required structs in this header and adapt
internal code.
2020-06-19 22:30:02 +02:00
Romain Vimont
29e5af76d4 Remove fprintf() call in tests
It should never have been committed.
2020-06-19 21:54:46 +02:00
Ivan Keliukh
dc7b60e619 Add option for disabling screensaver
PR #1502 <https://github.com/Genymobile/scrcpy/pull/1502>
Fixes #1370 <https://github.com/Genymobile/scrcpy/issues/1370>

Signed-off-by: Romain Vimont <rom@rom1v.com>
2020-06-18 21:35:28 +02:00
Romain Vimont
1e4ee547b5 Make message buffer static
Now that the message max size is 256k, do not put the buffer on the
stack.
2020-06-11 23:11:20 +02:00
Romain Vimont
488d22d4e2 Increase clipboard size from 4k to 256k
Beyond 256k, SDL_GetClipboardText() returns an empty string on my
computer.

Fixes #1117 <https://github.com/Genymobile/scrcpy/issues/1117>
2020-06-11 23:11:20 +02:00
Romain Vimont
00d292b2f5 Fix receiver on partial device messages
If a single device message was read in several chunks from the control
socket, the communication was broken.
2020-06-11 23:11:20 +02:00
Romain Vimont
245999aec4 Serialize text size on 4 bytes
This will allow to send text having a size greater than 65535 bytes.
2020-06-11 23:11:17 +02:00
Romain Vimont
d91c5dcfd5 Rename MSG_SERIALIZED_MAX_SIZE to MSG_MAX_SIZE
For simplicity and consistency with the server part.
2020-06-11 23:08:04 +02:00
Romain Vimont
d202d7b205 Add unit test for big clipboard device message
Test clipboard synchronization from the device to the computer.
2020-06-11 23:06:02 +02:00
Romain Vimont
08c0c64af6 Rename test names from "event" to "msg"
The meson test names had not been changed when "event" had been renamed
to "message".

Ref: 28980bbc90
2020-06-11 23:06:02 +02:00
Romain Vimont
a00a8763d6 Avoid additional copy on Java text parsing
Directly pass the buffer range to the String constructor.
2020-06-11 23:06:02 +02:00
Romain Vimont
8f314c74b0 Reorganize message size constants
Make the max clipboard text length depend on the max message size.
2020-06-11 22:58:54 +02:00
33 changed files with 304 additions and 162 deletions

View File

@@ -479,6 +479,17 @@ scrcpy -t
Note that it only shows _physical_ touches (with the finger on the device). Note that it only shows _physical_ touches (with the finger on the device).
#### Disable screensaver
By default, scrcpy does not prevent the screensaver to run on the computer.
To disable it:
```bash
scrcpy --disable-screensaver
```
### Input control ### Input control
#### Rotate device screen #### Rotate device screen

View File

@@ -164,12 +164,12 @@ if get_option('buildtype') == 'debug'
'src/cli.c', 'src/cli.c',
'src/util/str_util.c', 'src/util/str_util.c',
]], ]],
['test_control_event_serialize', [ ['test_control_msg_serialize', [
'tests/test_control_msg_serialize.c', 'tests/test_control_msg_serialize.c',
'src/control_msg.c', 'src/control_msg.c',
'src/util/str_util.c', 'src/util/str_util.c',
]], ]],
['test_device_event_deserialize', [ ['test_device_msg_deserialize', [
'tests/test_device_msg_deserialize.c', 'tests/test_device_msg_deserialize.c',
'src/device_msg.c', 'src/device_msg.c',
]], ]],

View File

@@ -43,6 +43,10 @@ The values are expressed in the device natural orientation (typically, portrait
.B \-\-max\-size .B \-\-max\-size
value is computed on the cropped size. value is computed on the cropped size.
.TP
.BI "\-\-disable-screensaver"
Disable screensaver while scrcpy is running.
.TP .TP
.BI "\-\-display " id .BI "\-\-display " id
Specify the display id to mirror. Specify the display id to mirror.

View File

@@ -3,10 +3,11 @@
#include <assert.h> #include <assert.h>
#include <getopt.h> #include <getopt.h>
#include <stdint.h> #include <stdint.h>
#include <stdio.h>
#include <unistd.h> #include <unistd.h>
#include "config.h" #include "config.h"
#include "recorder.h" #include "scrcpy.h"
#include "util/log.h" #include "util/log.h"
#include "util/str_util.h" #include "util/str_util.h"
@@ -45,6 +46,9 @@ scrcpy_print_usage(const char *arg0) {
" (typically, portrait for a phone, landscape for a tablet).\n" " (typically, portrait for a phone, landscape for a tablet).\n"
" Any --max-size value is computed on the cropped size.\n" " Any --max-size value is computed on the cropped size.\n"
"\n" "\n"
" --disable-screensaver\n"
" Disable screensaver while scrcpy is running.\n"
"\n"
" --display id\n" " --display id\n"
" Specify the display id to mirror.\n" " Specify the display id to mirror.\n"
"\n" "\n"
@@ -379,10 +383,10 @@ parse_rotation(const char *s, uint8_t *rotation) {
static bool static bool
parse_window_position(const char *s, int16_t *position) { parse_window_position(const char *s, int16_t *position) {
// special value for "auto" // special value for "auto"
static_assert(WINDOW_POSITION_UNDEFINED == -0x8000, "unexpected value"); static_assert(SC_WINDOW_POSITION_UNDEFINED == -0x8000, "unexpected value");
if (!strcmp(s, "auto")) { if (!strcmp(s, "auto")) {
*position = WINDOW_POSITION_UNDEFINED; *position = SC_WINDOW_POSITION_UNDEFINED;
return true; return true;
} }
@@ -411,7 +415,7 @@ parse_window_dimension(const char *s, uint16_t *dimension) {
} }
static bool static bool
parse_port_range(const char *s, struct port_range *port_range) { parse_port_range(const char *s, struct sc_port_range *port_range) {
long values[2]; long values[2];
size_t count = parse_integers_arg(s, 2, values, 0, 0xFFFF, "port"); size_t count = parse_integers_arg(s, 2, values, 0, 0xFFFF, "port");
if (!count) { if (!count) {
@@ -477,20 +481,20 @@ parse_log_level(const char *s, enum sc_log_level *log_level) {
} }
static bool static bool
parse_record_format(const char *optarg, enum recorder_format *format) { parse_record_format(const char *optarg, enum sc_record_format *format) {
if (!strcmp(optarg, "mp4")) { if (!strcmp(optarg, "mp4")) {
*format = RECORDER_FORMAT_MP4; *format = SC_RECORD_FORMAT_MP4;
return true; return true;
} }
if (!strcmp(optarg, "mkv")) { if (!strcmp(optarg, "mkv")) {
*format = RECORDER_FORMAT_MKV; *format = SC_RECORD_FORMAT_MKV;
return true; return true;
} }
LOGE("Unsupported format: %s (expected mp4 or mkv)", optarg); LOGE("Unsupported format: %s (expected mp4 or mkv)", optarg);
return false; return false;
} }
static enum recorder_format static enum sc_record_format
guess_record_format(const char *filename) { guess_record_format(const char *filename) {
size_t len = strlen(filename); size_t len = strlen(filename);
if (len < 4) { if (len < 4) {
@@ -498,10 +502,10 @@ guess_record_format(const char *filename) {
} }
const char *ext = &filename[len - 4]; const char *ext = &filename[len - 4];
if (!strcmp(ext, ".mp4")) { if (!strcmp(ext, ".mp4")) {
return RECORDER_FORMAT_MP4; return SC_RECORD_FORMAT_MP4;
} }
if (!strcmp(ext, ".mkv")) { if (!strcmp(ext, ".mkv")) {
return RECORDER_FORMAT_MKV; return SC_RECORD_FORMAT_MKV;
} }
return 0; return 0;
} }
@@ -526,6 +530,7 @@ guess_record_format(const char *filename) {
#define OPT_NO_MIPMAPS 1017 #define OPT_NO_MIPMAPS 1017
#define OPT_CODEC_OPTIONS 1018 #define OPT_CODEC_OPTIONS 1018
#define OPT_FORCE_ADB_FORWARD 1019 #define OPT_FORCE_ADB_FORWARD 1019
#define OPT_DISABLE_SCREENSAVER 1020
bool bool
scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) { scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) {
@@ -534,6 +539,8 @@ scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) {
{"bit-rate", required_argument, NULL, 'b'}, {"bit-rate", required_argument, NULL, 'b'},
{"codec-options", required_argument, NULL, OPT_CODEC_OPTIONS}, {"codec-options", required_argument, NULL, OPT_CODEC_OPTIONS},
{"crop", required_argument, NULL, OPT_CROP}, {"crop", required_argument, NULL, OPT_CROP},
{"disable-screensaver", no_argument, NULL,
OPT_DISABLE_SCREENSAVER},
{"display", required_argument, NULL, OPT_DISPLAY_ID}, {"display", required_argument, NULL, OPT_DISPLAY_ID},
{"force-adb-forward", no_argument, NULL, {"force-adb-forward", no_argument, NULL,
OPT_FORCE_ADB_FORWARD}, OPT_FORCE_ADB_FORWARD},
@@ -716,6 +723,9 @@ scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) {
case OPT_FORCE_ADB_FORWARD: case OPT_FORCE_ADB_FORWARD:
opts->force_adb_forward = true; opts->force_adb_forward = true;
break; break;
case OPT_DISABLE_SCREENSAVER:
opts->disable_screensaver = true;
break;
default: default:
// getopt prints the error message on stderr // getopt prints the error message on stderr
return false; return false;

View File

@@ -20,9 +20,9 @@ write_position(uint8_t *buf, const struct position *position) {
static size_t static size_t
write_string(const char *utf8, size_t max_len, unsigned char *buf) { write_string(const char *utf8, size_t max_len, unsigned char *buf) {
size_t len = utf8_truncation_index(utf8, max_len); size_t len = utf8_truncation_index(utf8, max_len);
buffer_write16be(buf, (uint16_t) len); buffer_write32be(buf, len);
memcpy(&buf[2], utf8, len); memcpy(&buf[4], utf8, len);
return 2 + len; return 4 + len;
} }
static uint16_t static uint16_t
@@ -42,8 +42,9 @@ control_msg_serialize(const struct control_msg *msg, unsigned char *buf) {
case CONTROL_MSG_TYPE_INJECT_KEYCODE: case CONTROL_MSG_TYPE_INJECT_KEYCODE:
buf[1] = msg->inject_keycode.action; buf[1] = msg->inject_keycode.action;
buffer_write32be(&buf[2], msg->inject_keycode.keycode); buffer_write32be(&buf[2], msg->inject_keycode.keycode);
buffer_write32be(&buf[6], msg->inject_keycode.metastate); buffer_write32be(&buf[6], msg->inject_keycode.repeat);
return 10; buffer_write32be(&buf[10], msg->inject_keycode.metastate);
return 14;
case CONTROL_MSG_TYPE_INJECT_TEXT: { case CONTROL_MSG_TYPE_INJECT_TEXT: {
size_t len = size_t len =
write_string(msg->inject_text.text, write_string(msg->inject_text.text,

View File

@@ -10,10 +10,11 @@
#include "android/keycodes.h" #include "android/keycodes.h"
#include "common.h" #include "common.h"
#define CONTROL_MSG_MAX_SIZE (1 << 18) // 256k
#define CONTROL_MSG_INJECT_TEXT_MAX_LENGTH 300 #define CONTROL_MSG_INJECT_TEXT_MAX_LENGTH 300
#define CONTROL_MSG_CLIPBOARD_TEXT_MAX_LENGTH 4092 // type: 1 byte; paste flag: 1 byte; length: 4 bytes
#define CONTROL_MSG_SERIALIZED_MAX_SIZE \ #define CONTROL_MSG_CLIPBOARD_TEXT_MAX_LENGTH (CONTROL_MSG_MAX_SIZE - 6)
(4 + CONTROL_MSG_CLIPBOARD_TEXT_MAX_LENGTH)
#define POINTER_ID_MOUSE UINT64_C(-1); #define POINTER_ID_MOUSE UINT64_C(-1);
@@ -43,6 +44,7 @@ struct control_msg {
struct { struct {
enum android_keyevent_action action; enum android_keyevent_action action;
enum android_keycode keycode; enum android_keycode keycode;
uint32_t repeat;
enum android_metastate metastate; enum android_metastate metastate;
} inject_keycode; } inject_keycode;
struct { struct {
@@ -70,7 +72,7 @@ struct control_msg {
}; };
}; };
// buf size must be at least CONTROL_MSG_SERIALIZED_MAX_SIZE // buf size must be at least CONTROL_MSG_MAX_SIZE
// return the number of bytes written // return the number of bytes written
size_t size_t
control_msg_serialize(const struct control_msg *msg, unsigned char *buf); control_msg_serialize(const struct control_msg *msg, unsigned char *buf);

View File

@@ -60,7 +60,7 @@ controller_push_msg(struct controller *controller,
static bool static bool
process_msg(struct controller *controller, process_msg(struct controller *controller,
const struct control_msg *msg) { const struct control_msg *msg) {
unsigned char serialized_msg[CONTROL_MSG_SERIALIZED_MAX_SIZE]; static unsigned char serialized_msg[CONTROL_MSG_MAX_SIZE];
int length = control_msg_serialize(msg, serialized_msg); int length = control_msg_serialize(msg, serialized_msg);
if (!length) { if (!length) {
return false; return false;

View File

@@ -9,7 +9,7 @@
ssize_t ssize_t
device_msg_deserialize(const unsigned char *buf, size_t len, device_msg_deserialize(const unsigned char *buf, size_t len,
struct device_msg *msg) { struct device_msg *msg) {
if (len < 3) { if (len < 5) {
// at least type + empty string length // at least type + empty string length
return 0; // not available return 0; // not available
} }
@@ -17,8 +17,8 @@ device_msg_deserialize(const unsigned char *buf, size_t len,
msg->type = buf[0]; msg->type = buf[0];
switch (msg->type) { switch (msg->type) {
case DEVICE_MSG_TYPE_CLIPBOARD: { case DEVICE_MSG_TYPE_CLIPBOARD: {
uint16_t clipboard_len = buffer_read16be(&buf[1]); size_t clipboard_len = buffer_read32be(&buf[1]);
if (clipboard_len > len - 3) { if (clipboard_len > len - 5) {
return 0; // not available return 0; // not available
} }
char *text = SDL_malloc(clipboard_len + 1); char *text = SDL_malloc(clipboard_len + 1);
@@ -27,12 +27,12 @@ device_msg_deserialize(const unsigned char *buf, size_t len,
return -1; return -1;
} }
if (clipboard_len) { if (clipboard_len) {
memcpy(text, &buf[3], clipboard_len); memcpy(text, &buf[5], clipboard_len);
} }
text[clipboard_len] = '\0'; text[clipboard_len] = '\0';
msg->clipboard.text = text; msg->clipboard.text = text;
return 3 + clipboard_len; return 5 + clipboard_len;
} }
default: default:
LOGW("Unknown device message type: %d", (int) msg->type); LOGW("Unknown device message type: %d", (int) msg->type);

View File

@@ -7,8 +7,9 @@
#include "config.h" #include "config.h"
#define DEVICE_MSG_TEXT_MAX_LENGTH 4093 #define DEVICE_MSG_MAX_SIZE (1 << 18) // 256k
#define DEVICE_MSG_SERIALIZED_MAX_SIZE (3 + DEVICE_MSG_TEXT_MAX_LENGTH) // type: 1 byte; length: 4 bytes
#define DEVICE_MSG_TEXT_MAX_LENGTH (DEVICE_MSG_MAX_SIZE - 5)
enum device_msg_type { enum device_msg_type {
DEVICE_MSG_TYPE_CLIPBOARD, DEVICE_MSG_TYPE_CLIPBOARD,

View File

@@ -234,7 +234,7 @@ input_manager_process_text_input(struct input_manager *im,
static bool static bool
convert_input_key(const SDL_KeyboardEvent *from, struct control_msg *to, convert_input_key(const SDL_KeyboardEvent *from, struct control_msg *to,
bool prefer_text) { bool prefer_text, uint32_t repeat) {
to->type = CONTROL_MSG_TYPE_INJECT_KEYCODE; to->type = CONTROL_MSG_TYPE_INJECT_KEYCODE;
if (!convert_keycode_action(from->type, &to->inject_keycode.action)) { if (!convert_keycode_action(from->type, &to->inject_keycode.action)) {
@@ -247,6 +247,7 @@ convert_input_key(const SDL_KeyboardEvent *from, struct control_msg *to,
return false; return false;
} }
to->inject_keycode.repeat = repeat;
to->inject_keycode.metastate = convert_meta_state(mod); to->inject_keycode.metastate = convert_meta_state(mod);
return true; return true;
@@ -411,8 +412,14 @@ input_manager_process_key(struct input_manager *im,
return; return;
} }
if (event->repeat) {
++im->repeat;
} else {
im->repeat = 0;
}
struct control_msg msg; struct control_msg msg;
if (convert_input_key(event, &msg, im->prefer_text)) { if (convert_input_key(event, &msg, im->prefer_text, im->repeat)) {
if (!controller_push_msg(controller, &msg)) { if (!controller_push_msg(controller, &msg)) {
LOGW("Could not request 'inject keycode'"); LOGW("Could not request 'inject keycode'");
} }
@@ -427,7 +434,7 @@ convert_mouse_motion(const SDL_MouseMotionEvent *from, struct screen *screen,
to->inject_touch_event.pointer_id = POINTER_ID_MOUSE; to->inject_touch_event.pointer_id = POINTER_ID_MOUSE;
to->inject_touch_event.position.screen_size = screen->frame_size; to->inject_touch_event.position.screen_size = screen->frame_size;
to->inject_touch_event.position.point = to->inject_touch_event.position.point =
screen_convert_to_frame_coords(screen, from->x, from->y); screen_convert_window_to_frame_coords(screen, from->x, from->y);
to->inject_touch_event.pressure = 1.f; to->inject_touch_event.pressure = 1.f;
to->inject_touch_event.buttons = convert_mouse_buttons(from->state); to->inject_touch_event.buttons = convert_mouse_buttons(from->state);
@@ -465,15 +472,15 @@ convert_touch(const SDL_TouchFingerEvent *from, struct screen *screen,
to->inject_touch_event.pointer_id = from->fingerId; to->inject_touch_event.pointer_id = from->fingerId;
to->inject_touch_event.position.screen_size = screen->frame_size; to->inject_touch_event.position.screen_size = screen->frame_size;
int ww; int dw;
int wh; int dh;
SDL_GL_GetDrawableSize(screen->window, &ww, &wh); SDL_GL_GetDrawableSize(screen->window, &dw, &dh);
// SDL touch event coordinates are normalized in the range [0; 1] // SDL touch event coordinates are normalized in the range [0; 1]
int32_t x = from->x * ww; int32_t x = from->x * dw;
int32_t y = from->y * wh; int32_t y = from->y * dh;
to->inject_touch_event.position.point = to->inject_touch_event.position.point =
screen_convert_to_frame_coords(screen, x, y); screen_convert_drawable_to_frame_coords(screen, x, y);
to->inject_touch_event.pressure = from->pressure; to->inject_touch_event.pressure = from->pressure;
to->inject_touch_event.buttons = 0; to->inject_touch_event.buttons = 0;
@@ -503,7 +510,7 @@ convert_mouse_button(const SDL_MouseButtonEvent *from, struct screen *screen,
to->inject_touch_event.pointer_id = POINTER_ID_MOUSE; to->inject_touch_event.pointer_id = POINTER_ID_MOUSE;
to->inject_touch_event.position.screen_size = screen->frame_size; to->inject_touch_event.position.screen_size = screen->frame_size;
to->inject_touch_event.position.point = to->inject_touch_event.position.point =
screen_convert_to_frame_coords(screen, from->x, from->y); screen_convert_window_to_frame_coords(screen, from->x, from->y);
to->inject_touch_event.pressure = 1.f; to->inject_touch_event.pressure = 1.f;
to->inject_touch_event.buttons = to->inject_touch_event.buttons =
convert_mouse_buttons(SDL_BUTTON(from->button)); convert_mouse_buttons(SDL_BUTTON(from->button));
@@ -568,7 +575,8 @@ convert_mouse_wheel(const SDL_MouseWheelEvent *from, struct screen *screen,
struct position position = { struct position position = {
.screen_size = screen->frame_size, .screen_size = screen->frame_size,
.point = screen_convert_to_frame_coords(screen, mouse_x, mouse_y), .point = screen_convert_window_to_frame_coords(screen,
mouse_x, mouse_y),
}; };
to->type = CONTROL_MSG_TYPE_INJECT_SCROLL_EVENT; to->type = CONTROL_MSG_TYPE_INJECT_SCROLL_EVENT;

View File

@@ -14,6 +14,11 @@ struct input_manager {
struct controller *controller; struct controller *controller;
struct video_buffer *video_buffer; struct video_buffer *video_buffer;
struct screen *screen; struct screen *screen;
// SDL reports repeated events as a boolean, but Android expects the actual
// number of repetitions. This variable keeps track of the count.
unsigned repeat;
bool prefer_text; bool prefer_text;
}; };

View File

@@ -1,5 +1,6 @@
#include "scrcpy.h" #include "scrcpy.h"
#include <assert.h>
#include <stdbool.h> #include <stdbool.h>
#include <unistd.h> #include <unistd.h>
#include <libavformat/avformat.h> #include <libavformat/avformat.h>

View File

@@ -60,28 +60,29 @@ static int
run_receiver(void *data) { run_receiver(void *data) {
struct receiver *receiver = data; struct receiver *receiver = data;
unsigned char buf[DEVICE_MSG_SERIALIZED_MAX_SIZE]; static unsigned char buf[DEVICE_MSG_MAX_SIZE];
size_t head = 0; size_t head = 0;
for (;;) { for (;;) {
assert(head < DEVICE_MSG_SERIALIZED_MAX_SIZE); assert(head < DEVICE_MSG_MAX_SIZE);
ssize_t r = net_recv(receiver->control_socket, buf, ssize_t r = net_recv(receiver->control_socket, buf + head,
DEVICE_MSG_SERIALIZED_MAX_SIZE - head); DEVICE_MSG_MAX_SIZE - head);
if (r <= 0) { if (r <= 0) {
LOGD("Receiver stopped"); LOGD("Receiver stopped");
break; break;
} }
ssize_t consumed = process_msgs(buf, r); head += r;
ssize_t consumed = process_msgs(buf, head);
if (consumed == -1) { if (consumed == -1) {
// an error occurred // an error occurred
break; break;
} }
if (consumed) { if (consumed) {
head -= consumed;
// shift the remaining data in the buffer // shift the remaining data in the buffer
memmove(buf, &buf[consumed], r - consumed); memmove(buf, &buf[consumed], head);
head = r - consumed;
} }
} }

View File

@@ -63,7 +63,7 @@ recorder_queue_clear(struct recorder_queue *queue) {
bool bool
recorder_init(struct recorder *recorder, recorder_init(struct recorder *recorder,
const char *filename, const char *filename,
enum recorder_format format, enum sc_record_format format,
struct size declared_frame_size) { struct size declared_frame_size) {
recorder->filename = SDL_strdup(filename); recorder->filename = SDL_strdup(filename);
if (!recorder->filename) { if (!recorder->filename) {
@@ -105,10 +105,10 @@ recorder_destroy(struct recorder *recorder) {
} }
static const char * static const char *
recorder_get_format_name(enum recorder_format format) { recorder_get_format_name(enum sc_record_format format) {
switch (format) { switch (format) {
case RECORDER_FORMAT_MP4: return "mp4"; case SC_RECORD_FORMAT_MP4: return "mp4";
case RECORDER_FORMAT_MKV: return "matroska"; case SC_RECORD_FORMAT_MKV: return "matroska";
default: return NULL; default: return NULL;
} }
} }

View File

@@ -8,14 +8,9 @@
#include "config.h" #include "config.h"
#include "common.h" #include "common.h"
#include "scrcpy.h"
#include "util/queue.h" #include "util/queue.h"
enum recorder_format {
RECORDER_FORMAT_AUTO,
RECORDER_FORMAT_MP4,
RECORDER_FORMAT_MKV,
};
struct record_packet { struct record_packet {
AVPacket packet; AVPacket packet;
struct record_packet *next; struct record_packet *next;
@@ -25,7 +20,7 @@ struct recorder_queue QUEUE(struct record_packet);
struct recorder { struct recorder {
char *filename; char *filename;
enum recorder_format format; enum sc_record_format format;
AVFormatContext *ctx; AVFormatContext *ctx;
struct size declared_frame_size; struct size declared_frame_size;
bool header_written; bool header_written;
@@ -46,7 +41,7 @@ struct recorder {
bool bool
recorder_init(struct recorder *recorder, const char *filename, recorder_init(struct recorder *recorder, const char *filename,
enum recorder_format format, struct size declared_frame_size); enum sc_record_format format, struct size declared_frame_size);
void void
recorder_destroy(struct recorder *recorder); recorder_destroy(struct recorder *recorder);

View File

@@ -8,6 +8,8 @@
#include <SDL2/SDL.h> #include <SDL2/SDL.h>
#ifdef _WIN32 #ifdef _WIN32
// not needed here, but winsock2.h must never be included AFTER windows.h
# include <winsock2.h>
# include <windows.h> # include <windows.h>
#endif #endif
@@ -46,6 +48,7 @@ static struct input_manager input_manager = {
.controller = &controller, .controller = &controller,
.video_buffer = &video_buffer, .video_buffer = &video_buffer,
.screen = &screen, .screen = &screen,
.repeat = 0,
.prefer_text = false, // initialized later .prefer_text = false, // initialized later
}; };
@@ -63,7 +66,8 @@ BOOL WINAPI windows_ctrl_handler(DWORD ctrl_type) {
// init SDL and set appropriate hints // init SDL and set appropriate hints
static bool static bool
sdl_init_and_configure(bool display, const char *render_driver) { sdl_init_and_configure(bool display, const char *render_driver,
bool disable_screensaver) {
uint32_t flags = display ? SDL_INIT_VIDEO : SDL_INIT_EVENTS; uint32_t flags = display ? SDL_INIT_VIDEO : SDL_INIT_EVENTS;
if (SDL_Init(flags)) { if (SDL_Init(flags)) {
LOGC("Could not initialize SDL: %s", SDL_GetError()); LOGC("Could not initialize SDL: %s", SDL_GetError());
@@ -112,8 +116,13 @@ sdl_init_and_configure(bool display, const char *render_driver) {
LOGW("Could not disable minimize on focus loss"); LOGW("Could not disable minimize on focus loss");
} }
// Do not disable the screensaver when scrcpy is running if (disable_screensaver) {
LOGD("Screensaver disabled");
SDL_DisableScreenSaver();
} else {
LOGD("Screensaver enabled");
SDL_EnableScreenSaver(); SDL_EnableScreenSaver();
}
return true; return true;
} }
@@ -321,7 +330,8 @@ scrcpy(const struct scrcpy_options *options) {
bool controller_initialized = false; bool controller_initialized = false;
bool controller_started = false; bool controller_started = false;
if (!sdl_init_and_configure(options->display, options->render_driver)) { if (!sdl_init_and_configure(options->display, options->render_driver,
options->disable_screensaver)) {
goto end; goto end;
} }

View File

@@ -2,13 +2,30 @@
#define SCRCPY_H #define SCRCPY_H
#include <stdbool.h> #include <stdbool.h>
#include <stddef.h>
#include <stdint.h> #include <stdint.h>
#include "config.h" #include "config.h"
#include "common.h"
#include "input_manager.h" enum sc_log_level {
#include "recorder.h" SC_LOG_LEVEL_DEBUG,
#include "util/log.h" SC_LOG_LEVEL_INFO,
SC_LOG_LEVEL_WARN,
SC_LOG_LEVEL_ERROR,
};
enum sc_record_format {
SC_RECORD_FORMAT_AUTO,
SC_RECORD_FORMAT_MP4,
SC_RECORD_FORMAT_MKV,
};
struct sc_port_range {
uint16_t first;
uint16_t last;
};
#define SC_WINDOW_POSITION_UNDEFINED (-0x8000)
struct scrcpy_options { struct scrcpy_options {
const char *serial; const char *serial;
@@ -19,15 +36,15 @@ struct scrcpy_options {
const char *render_driver; const char *render_driver;
const char *codec_options; const char *codec_options;
enum sc_log_level log_level; enum sc_log_level log_level;
enum recorder_format record_format; enum sc_record_format record_format;
struct port_range port_range; struct sc_port_range port_range;
uint16_t max_size; uint16_t max_size;
uint32_t bit_rate; uint32_t bit_rate;
uint16_t max_fps; uint16_t max_fps;
int8_t lock_video_orientation; int8_t lock_video_orientation;
uint8_t rotation; uint8_t rotation;
int16_t window_x; // WINDOW_POSITION_UNDEFINED for "auto" int16_t window_x; // SC_WINDOW_POSITION_UNDEFINED for "auto"
int16_t window_y; // WINDOW_POSITION_UNDEFINED for "auto" int16_t window_y; // SC_WINDOW_POSITION_UNDEFINED for "auto"
uint16_t window_width; uint16_t window_width;
uint16_t window_height; uint16_t window_height;
uint16_t display_id; uint16_t display_id;
@@ -43,6 +60,7 @@ struct scrcpy_options {
bool mipmaps; bool mipmaps;
bool stay_awake; bool stay_awake;
bool force_adb_forward; bool force_adb_forward;
bool disable_screensaver;
}; };
#define SCRCPY_OPTIONS_DEFAULT { \ #define SCRCPY_OPTIONS_DEFAULT { \
@@ -54,7 +72,7 @@ struct scrcpy_options {
.render_driver = NULL, \ .render_driver = NULL, \
.codec_options = NULL, \ .codec_options = NULL, \
.log_level = SC_LOG_LEVEL_INFO, \ .log_level = SC_LOG_LEVEL_INFO, \
.record_format = RECORDER_FORMAT_AUTO, \ .record_format = SC_RECORD_FORMAT_AUTO, \
.port_range = { \ .port_range = { \
.first = DEFAULT_LOCAL_PORT_RANGE_FIRST, \ .first = DEFAULT_LOCAL_PORT_RANGE_FIRST, \
.last = DEFAULT_LOCAL_PORT_RANGE_LAST, \ .last = DEFAULT_LOCAL_PORT_RANGE_LAST, \
@@ -64,8 +82,8 @@ struct scrcpy_options {
.max_fps = 0, \ .max_fps = 0, \
.lock_video_orientation = DEFAULT_LOCK_VIDEO_ORIENTATION, \ .lock_video_orientation = DEFAULT_LOCK_VIDEO_ORIENTATION, \
.rotation = 0, \ .rotation = 0, \
.window_x = WINDOW_POSITION_UNDEFINED, \ .window_x = SC_WINDOW_POSITION_UNDEFINED, \
.window_y = WINDOW_POSITION_UNDEFINED, \ .window_y = SC_WINDOW_POSITION_UNDEFINED, \
.window_width = 0, \ .window_width = 0, \
.window_height = 0, \ .window_height = 0, \
.display_id = 0, \ .display_id = 0, \
@@ -81,6 +99,7 @@ struct scrcpy_options {
.mipmaps = true, \ .mipmaps = true, \
.stay_awake = false, \ .stay_awake = false, \
.force_adb_forward = false, \ .force_adb_forward = false, \
.disable_screensaver = false, \
} }
bool bool

View File

@@ -8,6 +8,7 @@
#include "common.h" #include "common.h"
#include "compat.h" #include "compat.h"
#include "icon.xpm" #include "icon.xpm"
#include "scrcpy.h"
#include "tiny_xpm.h" #include "tiny_xpm.h"
#include "video_buffer.h" #include "video_buffer.h"
#include "util/lock.h" #include "util/lock.h"
@@ -257,9 +258,9 @@ screen_init_rendering(struct screen *screen, const char *window_title,
window_flags |= SDL_WINDOW_BORDERLESS; window_flags |= SDL_WINDOW_BORDERLESS;
} }
int x = window_x != WINDOW_POSITION_UNDEFINED int x = window_x != SC_WINDOW_POSITION_UNDEFINED
? window_x : (int) SDL_WINDOWPOS_UNDEFINED; ? window_x : (int) SDL_WINDOWPOS_UNDEFINED;
int y = window_y != WINDOW_POSITION_UNDEFINED int y = window_y != SC_WINDOW_POSITION_UNDEFINED
? window_y : (int) SDL_WINDOWPOS_UNDEFINED; ? window_y : (int) SDL_WINDOWPOS_UNDEFINED;
screen->window = SDL_CreateWindow(window_title, x, y, screen->window = SDL_CreateWindow(window_title, x, y,
window_size.width, window_size.height, window_size.width, window_size.height,
@@ -579,14 +580,14 @@ screen_handle_window_event(struct screen *screen,
} }
struct point struct point
screen_convert_to_frame_coords(struct screen *screen, int32_t x, int32_t y) { screen_convert_drawable_to_frame_coords(struct screen *screen,
int32_t x, int32_t y) {
unsigned rotation = screen->rotation; unsigned rotation = screen->rotation;
assert(rotation < 4); assert(rotation < 4);
int32_t w = screen->content_size.width; int32_t w = screen->content_size.width;
int32_t h = screen->content_size.height; int32_t h = screen->content_size.height;
screen_hidpi_scale_coords(screen, &x, &y);
x = (int64_t) (x - screen->rect.x) * w / screen->rect.w; x = (int64_t) (x - screen->rect.x) * w / screen->rect.w;
y = (int64_t) (y - screen->rect.y) * h / screen->rect.h; y = (int64_t) (y - screen->rect.y) * h / screen->rect.h;
@@ -615,6 +616,13 @@ screen_convert_to_frame_coords(struct screen *screen, int32_t x, int32_t y) {
return result; return result;
} }
struct point
screen_convert_window_to_frame_coords(struct screen *screen,
int32_t x, int32_t y) {
screen_hidpi_scale_coords(screen, &x, &y);
return screen_convert_drawable_to_frame_coords(screen, x, y);
}
void void
screen_hidpi_scale_coords(struct screen *screen, int32_t *x, int32_t *y) { screen_hidpi_scale_coords(struct screen *screen, int32_t *x, int32_t *y) {
// take the HiDPI scaling (dw/ww and dh/wh) into account // take the HiDPI scaling (dw/ww and dh/wh) into account

View File

@@ -9,8 +9,6 @@
#include "common.h" #include "common.h"
#include "opengl.h" #include "opengl.h"
#define WINDOW_POSITION_UNDEFINED (-0x8000)
struct video_buffer; struct video_buffer;
struct screen { struct screen {
@@ -76,7 +74,7 @@ void
screen_init(struct screen *screen); screen_init(struct screen *screen);
// initialize screen, create window, renderer and texture (window is hidden) // initialize screen, create window, renderer and texture (window is hidden)
// window_x and window_y accept WINDOW_POSITION_UNDEFINED // window_x and window_y accept SC_WINDOW_POSITION_UNDEFINED
bool bool
screen_init_rendering(struct screen *screen, const char *window_title, screen_init_rendering(struct screen *screen, const char *window_title,
struct size frame_size, bool always_on_top, struct size frame_size, bool always_on_top,
@@ -126,7 +124,14 @@ screen_handle_window_event(struct screen *screen, const SDL_WindowEvent *event);
// convert point from window coordinates to frame coordinates // convert point from window coordinates to frame coordinates
// x and y are expressed in pixels // x and y are expressed in pixels
struct point struct point
screen_convert_to_frame_coords(struct screen *screen, int32_t x, int32_t y); screen_convert_window_to_frame_coords(struct screen *screen,
int32_t x, int32_t y);
// convert point from drawable coordinates to frame coordinates
// x and y are expressed in pixels
struct point
screen_convert_drawable_to_frame_coords(struct screen *screen,
int32_t x, int32_t y);
// Convert coordinates from window to drawable. // Convert coordinates from window to drawable.
// Events are expressed in window coordinates, but content is expressed in // Events are expressed in window coordinates, but content is expressed in

View File

@@ -143,7 +143,7 @@ listen_on_port(uint16_t port) {
static bool static bool
enable_tunnel_reverse_any_port(struct server *server, enable_tunnel_reverse_any_port(struct server *server,
struct port_range port_range) { struct sc_port_range port_range) {
uint16_t port = port_range.first; uint16_t port = port_range.first;
for (;;) { for (;;) {
if (!enable_tunnel_reverse(server->serial, port)) { if (!enable_tunnel_reverse(server->serial, port)) {
@@ -189,7 +189,7 @@ enable_tunnel_reverse_any_port(struct server *server,
static bool static bool
enable_tunnel_forward_any_port(struct server *server, enable_tunnel_forward_any_port(struct server *server,
struct port_range port_range) { struct sc_port_range port_range) {
server->tunnel_forward = true; server->tunnel_forward = true;
uint16_t port = port_range.first; uint16_t port = port_range.first;
for (;;) { for (;;) {
@@ -217,7 +217,7 @@ enable_tunnel_forward_any_port(struct server *server,
} }
static bool static bool
enable_tunnel_any_port(struct server *server, struct port_range port_range, enable_tunnel_any_port(struct server *server, struct sc_port_range port_range,
bool force_adb_forward) { bool force_adb_forward) {
if (!force_adb_forward) { if (!force_adb_forward) {
// Attempt to use "adb reverse" // Attempt to use "adb reverse"

View File

@@ -9,6 +9,7 @@
#include "config.h" #include "config.h"
#include "command.h" #include "command.h"
#include "common.h" #include "common.h"
#include "scrcpy.h"
#include "util/log.h" #include "util/log.h"
#include "util/net.h" #include "util/net.h"
@@ -20,7 +21,7 @@ struct server {
socket_t server_socket; // only used if !tunnel_forward socket_t server_socket; // only used if !tunnel_forward
socket_t video_socket; socket_t video_socket;
socket_t control_socket; socket_t control_socket;
struct port_range port_range; struct sc_port_range port_range;
uint16_t local_port; // selected from port_range uint16_t local_port; // selected from port_range
bool tunnel_enabled; bool tunnel_enabled;
bool tunnel_forward; // use "adb forward" instead of "adb reverse" bool tunnel_forward; // use "adb forward" instead of "adb reverse"
@@ -47,7 +48,7 @@ struct server_params {
enum sc_log_level log_level; enum sc_log_level log_level;
const char *crop; const char *crop;
const char *codec_options; const char *codec_options;
struct port_range port_range; struct sc_port_range port_range;
uint16_t max_size; uint16_t max_size;
uint32_t bit_rate; uint32_t bit_rate;
uint16_t max_fps; uint16_t max_fps;

View File

@@ -3,13 +3,6 @@
#include <SDL2/SDL_log.h> #include <SDL2/SDL_log.h>
enum sc_log_level {
SC_LOG_LEVEL_DEBUG,
SC_LOG_LEVEL_INFO,
SC_LOG_LEVEL_WARN,
SC_LOG_LEVEL_ERROR,
};
#define LOGV(...) SDL_LogVerbose(SDL_LOG_CATEGORY_APPLICATION, __VA_ARGS__) #define LOGV(...) SDL_LogVerbose(SDL_LOG_CATEGORY_APPLICATION, __VA_ARGS__)
#define LOGD(...) SDL_LogDebug(SDL_LOG_CATEGORY_APPLICATION, __VA_ARGS__) #define LOGD(...) SDL_LogDebug(SDL_LOG_CATEGORY_APPLICATION, __VA_ARGS__)
#define LOGI(...) SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, __VA_ARGS__) #define LOGI(...) SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, __VA_ARGS__)

View File

@@ -1,4 +1,5 @@
#include <assert.h> #include <assert.h>
#include <string.h>
#include "cli.h" #include "cli.h"
#include "common.h" #include "common.h"
@@ -73,7 +74,6 @@ static void test_options(void) {
const struct scrcpy_options *opts = &args.opts; const struct scrcpy_options *opts = &args.opts;
assert(opts->always_on_top); assert(opts->always_on_top);
fprintf(stderr, "%d\n", (int) opts->bit_rate);
assert(opts->bit_rate == 5000000); assert(opts->bit_rate == 5000000);
assert(!strcmp(opts->crop, "100:200:300:400")); assert(!strcmp(opts->crop, "100:200:300:400"));
assert(opts->fullscreen); assert(opts->fullscreen);
@@ -84,7 +84,7 @@ static void test_options(void) {
assert(opts->port_range.last == 1236); assert(opts->port_range.last == 1236);
assert(!strcmp(opts->push_target, "/sdcard/Movies")); assert(!strcmp(opts->push_target, "/sdcard/Movies"));
assert(!strcmp(opts->record_filename, "file")); assert(!strcmp(opts->record_filename, "file"));
assert(opts->record_format == RECORDER_FORMAT_MKV); assert(opts->record_format == SC_RECORD_FORMAT_MKV);
assert(opts->render_expired_frames); assert(opts->render_expired_frames);
assert(!strcmp(opts->serial, "0123456789abcdef")); assert(!strcmp(opts->serial, "0123456789abcdef"));
assert(opts->show_touches); assert(opts->show_touches);
@@ -119,7 +119,7 @@ static void test_options2(void) {
assert(!opts->control); assert(!opts->control);
assert(!opts->display); assert(!opts->display);
assert(!strcmp(opts->record_filename, "file.mp4")); assert(!strcmp(opts->record_filename, "file.mp4"));
assert(opts->record_format == RECORDER_FORMAT_MP4); assert(opts->record_format == SC_RECORD_FORMAT_MP4);
} }
int main(void) { int main(void) {

View File

@@ -9,18 +9,20 @@ static void test_serialize_inject_keycode(void) {
.inject_keycode = { .inject_keycode = {
.action = AKEY_EVENT_ACTION_UP, .action = AKEY_EVENT_ACTION_UP,
.keycode = AKEYCODE_ENTER, .keycode = AKEYCODE_ENTER,
.repeat = 5,
.metastate = AMETA_SHIFT_ON | AMETA_SHIFT_LEFT_ON, .metastate = AMETA_SHIFT_ON | AMETA_SHIFT_LEFT_ON,
}, },
}; };
unsigned char buf[CONTROL_MSG_SERIALIZED_MAX_SIZE]; unsigned char buf[CONTROL_MSG_MAX_SIZE];
int size = control_msg_serialize(&msg, buf); int size = control_msg_serialize(&msg, buf);
assert(size == 10); assert(size == 14);
const unsigned char expected[] = { const unsigned char expected[] = {
CONTROL_MSG_TYPE_INJECT_KEYCODE, CONTROL_MSG_TYPE_INJECT_KEYCODE,
0x01, // AKEY_EVENT_ACTION_UP 0x01, // AKEY_EVENT_ACTION_UP
0x00, 0x00, 0x00, 0x42, // AKEYCODE_ENTER 0x00, 0x00, 0x00, 0x42, // AKEYCODE_ENTER
0x00, 0x00, 0x00, 0X05, // repeat
0x00, 0x00, 0x00, 0x41, // AMETA_SHIFT_ON | AMETA_SHIFT_LEFT_ON 0x00, 0x00, 0x00, 0x41, // AMETA_SHIFT_ON | AMETA_SHIFT_LEFT_ON
}; };
assert(!memcmp(buf, expected, sizeof(expected))); assert(!memcmp(buf, expected, sizeof(expected)));
@@ -34,13 +36,13 @@ static void test_serialize_inject_text(void) {
}, },
}; };
unsigned char buf[CONTROL_MSG_SERIALIZED_MAX_SIZE]; unsigned char buf[CONTROL_MSG_MAX_SIZE];
int size = control_msg_serialize(&msg, buf); int size = control_msg_serialize(&msg, buf);
assert(size == 16); assert(size == 18);
const unsigned char expected[] = { const unsigned char expected[] = {
CONTROL_MSG_TYPE_INJECT_TEXT, CONTROL_MSG_TYPE_INJECT_TEXT,
0x00, 0x0d, // text length 0x00, 0x00, 0x00, 0x0d, // text length
'h', 'e', 'l', 'l', 'o', ',', ' ', 'w', 'o', 'r', 'l', 'd', '!', // text 'h', 'e', 'l', 'l', 'o', ',', ' ', 'w', 'o', 'r', 'l', 'd', '!', // text
}; };
assert(!memcmp(buf, expected, sizeof(expected))); assert(!memcmp(buf, expected, sizeof(expected)));
@@ -54,15 +56,17 @@ static void test_serialize_inject_text_long(void) {
text[CONTROL_MSG_INJECT_TEXT_MAX_LENGTH] = '\0'; text[CONTROL_MSG_INJECT_TEXT_MAX_LENGTH] = '\0';
msg.inject_text.text = text; msg.inject_text.text = text;
unsigned char buf[CONTROL_MSG_SERIALIZED_MAX_SIZE]; unsigned char buf[CONTROL_MSG_MAX_SIZE];
int size = control_msg_serialize(&msg, buf); int size = control_msg_serialize(&msg, buf);
assert(size == 3 + CONTROL_MSG_INJECT_TEXT_MAX_LENGTH); assert(size == 5 + CONTROL_MSG_INJECT_TEXT_MAX_LENGTH);
unsigned char expected[3 + CONTROL_MSG_INJECT_TEXT_MAX_LENGTH]; unsigned char expected[5 + CONTROL_MSG_INJECT_TEXT_MAX_LENGTH];
expected[0] = CONTROL_MSG_TYPE_INJECT_TEXT; expected[0] = CONTROL_MSG_TYPE_INJECT_TEXT;
expected[1] = 0x01; expected[1] = 0x00;
expected[2] = 0x2c; // text length (16 bits) expected[2] = 0x00;
memset(&expected[3], 'a', CONTROL_MSG_INJECT_TEXT_MAX_LENGTH); expected[3] = 0x01;
expected[4] = 0x2c; // text length (32 bits)
memset(&expected[5], 'a', CONTROL_MSG_INJECT_TEXT_MAX_LENGTH);
assert(!memcmp(buf, expected, sizeof(expected))); assert(!memcmp(buf, expected, sizeof(expected)));
} }
@@ -88,7 +92,7 @@ static void test_serialize_inject_touch_event(void) {
}, },
}; };
unsigned char buf[CONTROL_MSG_SERIALIZED_MAX_SIZE]; unsigned char buf[CONTROL_MSG_MAX_SIZE];
int size = control_msg_serialize(&msg, buf); int size = control_msg_serialize(&msg, buf);
assert(size == 28); assert(size == 28);
@@ -123,7 +127,7 @@ static void test_serialize_inject_scroll_event(void) {
}, },
}; };
unsigned char buf[CONTROL_MSG_SERIALIZED_MAX_SIZE]; unsigned char buf[CONTROL_MSG_MAX_SIZE];
int size = control_msg_serialize(&msg, buf); int size = control_msg_serialize(&msg, buf);
assert(size == 21); assert(size == 21);
@@ -142,7 +146,7 @@ static void test_serialize_back_or_screen_on(void) {
.type = CONTROL_MSG_TYPE_BACK_OR_SCREEN_ON, .type = CONTROL_MSG_TYPE_BACK_OR_SCREEN_ON,
}; };
unsigned char buf[CONTROL_MSG_SERIALIZED_MAX_SIZE]; unsigned char buf[CONTROL_MSG_MAX_SIZE];
int size = control_msg_serialize(&msg, buf); int size = control_msg_serialize(&msg, buf);
assert(size == 1); assert(size == 1);
@@ -157,7 +161,7 @@ static void test_serialize_expand_notification_panel(void) {
.type = CONTROL_MSG_TYPE_EXPAND_NOTIFICATION_PANEL, .type = CONTROL_MSG_TYPE_EXPAND_NOTIFICATION_PANEL,
}; };
unsigned char buf[CONTROL_MSG_SERIALIZED_MAX_SIZE]; unsigned char buf[CONTROL_MSG_MAX_SIZE];
int size = control_msg_serialize(&msg, buf); int size = control_msg_serialize(&msg, buf);
assert(size == 1); assert(size == 1);
@@ -172,7 +176,7 @@ static void test_serialize_collapse_notification_panel(void) {
.type = CONTROL_MSG_TYPE_COLLAPSE_NOTIFICATION_PANEL, .type = CONTROL_MSG_TYPE_COLLAPSE_NOTIFICATION_PANEL,
}; };
unsigned char buf[CONTROL_MSG_SERIALIZED_MAX_SIZE]; unsigned char buf[CONTROL_MSG_MAX_SIZE];
int size = control_msg_serialize(&msg, buf); int size = control_msg_serialize(&msg, buf);
assert(size == 1); assert(size == 1);
@@ -187,7 +191,7 @@ static void test_serialize_get_clipboard(void) {
.type = CONTROL_MSG_TYPE_GET_CLIPBOARD, .type = CONTROL_MSG_TYPE_GET_CLIPBOARD,
}; };
unsigned char buf[CONTROL_MSG_SERIALIZED_MAX_SIZE]; unsigned char buf[CONTROL_MSG_MAX_SIZE];
int size = control_msg_serialize(&msg, buf); int size = control_msg_serialize(&msg, buf);
assert(size == 1); assert(size == 1);
@@ -206,14 +210,14 @@ static void test_serialize_set_clipboard(void) {
}, },
}; };
unsigned char buf[CONTROL_MSG_SERIALIZED_MAX_SIZE]; unsigned char buf[CONTROL_MSG_MAX_SIZE];
int size = control_msg_serialize(&msg, buf); int size = control_msg_serialize(&msg, buf);
assert(size == 17); assert(size == 19);
const unsigned char expected[] = { const unsigned char expected[] = {
CONTROL_MSG_TYPE_SET_CLIPBOARD, CONTROL_MSG_TYPE_SET_CLIPBOARD,
1, // paste 1, // paste
0x00, 0x0d, // text length 0x00, 0x00, 0x00, 0x0d, // text length
'h', 'e', 'l', 'l', 'o', ',', ' ', 'w', 'o', 'r', 'l', 'd', '!', // text 'h', 'e', 'l', 'l', 'o', ',', ' ', 'w', 'o', 'r', 'l', 'd', '!', // text
}; };
assert(!memcmp(buf, expected, sizeof(expected))); assert(!memcmp(buf, expected, sizeof(expected)));
@@ -227,7 +231,7 @@ static void test_serialize_set_screen_power_mode(void) {
}, },
}; };
unsigned char buf[CONTROL_MSG_SERIALIZED_MAX_SIZE]; unsigned char buf[CONTROL_MSG_MAX_SIZE];
int size = control_msg_serialize(&msg, buf); int size = control_msg_serialize(&msg, buf);
assert(size == 2); assert(size == 2);
@@ -243,7 +247,7 @@ static void test_serialize_rotate_device(void) {
.type = CONTROL_MSG_TYPE_ROTATE_DEVICE, .type = CONTROL_MSG_TYPE_ROTATE_DEVICE,
}; };
unsigned char buf[CONTROL_MSG_SERIALIZED_MAX_SIZE]; unsigned char buf[CONTROL_MSG_MAX_SIZE];
int size = control_msg_serialize(&msg, buf); int size = control_msg_serialize(&msg, buf);
assert(size == 1); assert(size == 1);

View File

@@ -4,16 +4,17 @@
#include "device_msg.h" #include "device_msg.h"
#include <stdio.h> #include <stdio.h>
static void test_deserialize_clipboard(void) { static void test_deserialize_clipboard(void) {
const unsigned char input[] = { const unsigned char input[] = {
DEVICE_MSG_TYPE_CLIPBOARD, DEVICE_MSG_TYPE_CLIPBOARD,
0x00, 0x03, // text length 0x00, 0x00, 0x00, 0x03, // text length
0x41, 0x42, 0x43, // "ABC" 0x41, 0x42, 0x43, // "ABC"
}; };
struct device_msg msg; struct device_msg msg;
ssize_t r = device_msg_deserialize(input, sizeof(input), &msg); ssize_t r = device_msg_deserialize(input, sizeof(input), &msg);
assert(r == 6); assert(r == 8);
assert(msg.type == DEVICE_MSG_TYPE_CLIPBOARD); assert(msg.type == DEVICE_MSG_TYPE_CLIPBOARD);
assert(msg.clipboard.text); assert(msg.clipboard.text);
@@ -22,7 +23,30 @@ static void test_deserialize_clipboard(void) {
device_msg_destroy(&msg); device_msg_destroy(&msg);
} }
static void test_deserialize_clipboard_big(void) {
unsigned char input[DEVICE_MSG_MAX_SIZE];
input[0] = DEVICE_MSG_TYPE_CLIPBOARD;
input[1] = (DEVICE_MSG_TEXT_MAX_LENGTH & 0xff000000u) >> 24;
input[2] = (DEVICE_MSG_TEXT_MAX_LENGTH & 0x00ff0000u) >> 16;
input[3] = (DEVICE_MSG_TEXT_MAX_LENGTH & 0x0000ff00u) >> 8;
input[4] = DEVICE_MSG_TEXT_MAX_LENGTH & 0x000000ffu;
memset(input + 5, 'a', DEVICE_MSG_TEXT_MAX_LENGTH);
struct device_msg msg;
ssize_t r = device_msg_deserialize(input, sizeof(input), &msg);
assert(r == DEVICE_MSG_MAX_SIZE);
assert(msg.type == DEVICE_MSG_TYPE_CLIPBOARD);
assert(msg.clipboard.text);
assert(strlen(msg.clipboard.text) == DEVICE_MSG_TEXT_MAX_LENGTH);
assert(msg.clipboard.text[0] == 'a');
device_msg_destroy(&msg);
}
int main(void) { int main(void) {
test_deserialize_clipboard(); test_deserialize_clipboard();
test_deserialize_clipboard_big();
return 0; return 0;
} }

View File

@@ -31,15 +31,17 @@ public final class ControlMessage {
private int hScroll; private int hScroll;
private int vScroll; private int vScroll;
private int flags; private int flags;
private int repeat;
private ControlMessage() { private ControlMessage() {
} }
public static ControlMessage createInjectKeycode(int action, int keycode, int metaState) { public static ControlMessage createInjectKeycode(int action, int keycode, int repeat, int metaState) {
ControlMessage msg = new ControlMessage(); ControlMessage msg = new ControlMessage();
msg.type = TYPE_INJECT_KEYCODE; msg.type = TYPE_INJECT_KEYCODE;
msg.action = action; msg.action = action;
msg.keycode = keycode; msg.keycode = keycode;
msg.repeat = repeat;
msg.metaState = metaState; msg.metaState = metaState;
return msg; return msg;
} }
@@ -144,4 +146,8 @@ public final class ControlMessage {
public int getFlags() { public int getFlags() {
return flags; return flags;
} }
public int getRepeat() {
return repeat;
}
} }

View File

@@ -8,20 +8,19 @@ import java.nio.charset.StandardCharsets;
public class ControlMessageReader { public class ControlMessageReader {
static final int INJECT_KEYCODE_PAYLOAD_LENGTH = 9; static final int INJECT_KEYCODE_PAYLOAD_LENGTH = 13;
static final int INJECT_TOUCH_EVENT_PAYLOAD_LENGTH = 27; static final int INJECT_TOUCH_EVENT_PAYLOAD_LENGTH = 27;
static final int INJECT_SCROLL_EVENT_PAYLOAD_LENGTH = 20; static final int INJECT_SCROLL_EVENT_PAYLOAD_LENGTH = 20;
static final int SET_SCREEN_POWER_MODE_PAYLOAD_LENGTH = 1; static final int SET_SCREEN_POWER_MODE_PAYLOAD_LENGTH = 1;
static final int SET_CLIPBOARD_FIXED_PAYLOAD_LENGTH = 1; static final int SET_CLIPBOARD_FIXED_PAYLOAD_LENGTH = 1;
public static final int CLIPBOARD_TEXT_MAX_LENGTH = 4092; // 4096 - 1 (type) - 1 (parse flag) - 2 (length) private static final int MESSAGE_MAX_SIZE = 1 << 18; // 256k
public static final int CLIPBOARD_TEXT_MAX_LENGTH = MESSAGE_MAX_SIZE - 6; // type: 1 byte; paste flag: 1 byte; length: 4 bytes
public static final int INJECT_TEXT_MAX_LENGTH = 300; public static final int INJECT_TEXT_MAX_LENGTH = 300;
private static final int RAW_BUFFER_SIZE = 4096; private final byte[] rawBuffer = new byte[MESSAGE_MAX_SIZE];
private final byte[] rawBuffer = new byte[RAW_BUFFER_SIZE];
private final ByteBuffer buffer = ByteBuffer.wrap(rawBuffer); private final ByteBuffer buffer = ByteBuffer.wrap(rawBuffer);
private final byte[] textBuffer = new byte[CLIPBOARD_TEXT_MAX_LENGTH];
public ControlMessageReader() { public ControlMessageReader() {
// invariant: the buffer is always in "get" mode // invariant: the buffer is always in "get" mode
@@ -99,20 +98,23 @@ public class ControlMessageReader {
} }
int action = toUnsigned(buffer.get()); int action = toUnsigned(buffer.get());
int keycode = buffer.getInt(); int keycode = buffer.getInt();
int repeat = buffer.getInt();
int metaState = buffer.getInt(); int metaState = buffer.getInt();
return ControlMessage.createInjectKeycode(action, keycode, metaState); return ControlMessage.createInjectKeycode(action, keycode, repeat, metaState);
} }
private String parseString() { private String parseString() {
if (buffer.remaining() < 2) { if (buffer.remaining() < 4) {
return null; return null;
} }
int len = toUnsigned(buffer.getShort()); int len = buffer.getInt();
if (buffer.remaining() < len) { if (buffer.remaining() < len) {
return null; return null;
} }
buffer.get(textBuffer, 0, len); int position = buffer.position();
return new String(textBuffer, 0, len, StandardCharsets.UTF_8); // Move the buffer position to consume the text
buffer.position(position + len);
return new String(rawBuffer, position, len, StandardCharsets.UTF_8);
} }
private ControlMessage parseInjectText() { private ControlMessage parseInjectText() {

View File

@@ -74,7 +74,7 @@ public class Controller {
switch (msg.getType()) { switch (msg.getType()) {
case ControlMessage.TYPE_INJECT_KEYCODE: case ControlMessage.TYPE_INJECT_KEYCODE:
if (device.supportsInputEvents()) { if (device.supportsInputEvents()) {
injectKeycode(msg.getAction(), msg.getKeycode(), msg.getMetaState()); injectKeycode(msg.getAction(), msg.getKeycode(), msg.getRepeat(), msg.getMetaState());
} }
break; break;
case ControlMessage.TYPE_INJECT_TEXT: case ControlMessage.TYPE_INJECT_TEXT:
@@ -130,8 +130,8 @@ public class Controller {
} }
} }
private boolean injectKeycode(int action, int keycode, int metaState) { private boolean injectKeycode(int action, int keycode, int repeat, int metaState) {
return device.injectKeyEvent(action, keycode, 0, metaState); return device.injectKeyEvent(action, keycode, repeat, metaState);
} }
private boolean injectChar(char c) { private boolean injectChar(char c) {

View File

@@ -1,5 +1,6 @@
package com.genymobile.scrcpy; package com.genymobile.scrcpy;
import com.genymobile.scrcpy.wrappers.ClipboardManager;
import com.genymobile.scrcpy.wrappers.ContentProvider; import com.genymobile.scrcpy.wrappers.ContentProvider;
import com.genymobile.scrcpy.wrappers.InputManager; import com.genymobile.scrcpy.wrappers.InputManager;
import com.genymobile.scrcpy.wrappers.ServiceManager; import com.genymobile.scrcpy.wrappers.ServiceManager;
@@ -80,7 +81,9 @@ public final class Device {
if (options.getControl()) { if (options.getControl()) {
// If control is enabled, synchronize Android clipboard to the computer automatically // If control is enabled, synchronize Android clipboard to the computer automatically
serviceManager.getClipboardManager().addPrimaryClipChangedListener(new IOnPrimaryClipChangedListener.Stub() { ClipboardManager clipboardManager = serviceManager.getClipboardManager();
if (clipboardManager != null) {
clipboardManager.addPrimaryClipChangedListener(new IOnPrimaryClipChangedListener.Stub() {
@Override @Override
public void dispatchPrimaryClipChanged() { public void dispatchPrimaryClipChanged() {
if (isSettingClipboard.get()) { if (isSettingClipboard.get()) {
@@ -97,6 +100,9 @@ public final class Device {
} }
} }
}); });
} else {
Ln.w("No clipboard manager, copy-paste between device and computer will not work");
}
} }
if ((displayInfoFlags & DisplayInfo.FLAG_SUPPORTS_PROTECTED_BUFFERS) == 0) { if ((displayInfoFlags & DisplayInfo.FLAG_SUPPORTS_PROTECTED_BUFFERS) == 0) {
@@ -199,7 +205,11 @@ public final class Device {
} }
public String getClipboardText() { public String getClipboardText() {
CharSequence s = serviceManager.getClipboardManager().getText(); ClipboardManager clipboardManager = serviceManager.getClipboardManager();
if (clipboardManager == null) {
return null;
}
CharSequence s = clipboardManager.getText();
if (s == null) { if (s == null) {
return null; return null;
} }
@@ -207,8 +217,12 @@ public final class Device {
} }
public boolean setClipboardText(String text) { public boolean setClipboardText(String text) {
ClipboardManager clipboardManager = serviceManager.getClipboardManager();
if (clipboardManager == null) {
return false;
}
isSettingClipboard.set(true); isSettingClipboard.set(true);
boolean ok = serviceManager.getClipboardManager().setText(text); boolean ok = clipboardManager.setText(text);
isSettingClipboard.set(false); isSettingClipboard.set(false);
return ok; return ok;
} }

View File

@@ -7,10 +7,10 @@ import java.nio.charset.StandardCharsets;
public class DeviceMessageWriter { public class DeviceMessageWriter {
public static final int CLIPBOARD_TEXT_MAX_LENGTH = 4093; private static final int MESSAGE_MAX_SIZE = 1 << 18; // 256k
private static final int MAX_EVENT_SIZE = CLIPBOARD_TEXT_MAX_LENGTH + 3; public static final int CLIPBOARD_TEXT_MAX_LENGTH = MESSAGE_MAX_SIZE - 5; // type: 1 byte; length: 4 bytes
private final byte[] rawBuffer = new byte[MAX_EVENT_SIZE]; private final byte[] rawBuffer = new byte[MESSAGE_MAX_SIZE];
private final ByteBuffer buffer = ByteBuffer.wrap(rawBuffer); private final ByteBuffer buffer = ByteBuffer.wrap(rawBuffer);
public void writeTo(DeviceMessage msg, OutputStream output) throws IOException { public void writeTo(DeviceMessage msg, OutputStream output) throws IOException {
@@ -21,7 +21,7 @@ public class DeviceMessageWriter {
String text = msg.getText(); String text = msg.getText();
byte[] raw = text.getBytes(StandardCharsets.UTF_8); byte[] raw = text.getBytes(StandardCharsets.UTF_8);
int len = StringUtils.getUtf8TruncationIndex(raw, CLIPBOARD_TEXT_MAX_LENGTH); int len = StringUtils.getUtf8TruncationIndex(raw, CLIPBOARD_TEXT_MAX_LENGTH);
buffer.putShort((short) len); buffer.putInt(len);
buffer.put(raw, 0, len); buffer.put(raw, 0, len);
output.write(rawBuffer, 0, buffer.position()); output.write(rawBuffer, 0, buffer.position());
break; break;

View File

@@ -77,7 +77,14 @@ public final class ServiceManager {
public ClipboardManager getClipboardManager() { public ClipboardManager getClipboardManager() {
if (clipboardManager == null) { if (clipboardManager == null) {
clipboardManager = new ClipboardManager(getService("clipboard", "android.content.IClipboard")); IInterface clipboard = getService("clipboard", "android.content.IClipboard");
if (clipboard == null) {
// Some devices have no clipboard manager
// <https://github.com/Genymobile/scrcpy/issues/1440>
// <https://github.com/Genymobile/scrcpy/issues/1556>
return null;
}
clipboardManager = new ClipboardManager(clipboard);
} }
return clipboardManager; return clipboardManager;
} }

View File

@@ -25,6 +25,7 @@ public class ControlMessageReaderTest {
dos.writeByte(ControlMessage.TYPE_INJECT_KEYCODE); dos.writeByte(ControlMessage.TYPE_INJECT_KEYCODE);
dos.writeByte(KeyEvent.ACTION_UP); dos.writeByte(KeyEvent.ACTION_UP);
dos.writeInt(KeyEvent.KEYCODE_ENTER); dos.writeInt(KeyEvent.KEYCODE_ENTER);
dos.writeInt(5); // repeat
dos.writeInt(KeyEvent.META_CTRL_ON); dos.writeInt(KeyEvent.META_CTRL_ON);
byte[] packet = bos.toByteArray(); byte[] packet = bos.toByteArray();
@@ -37,6 +38,7 @@ public class ControlMessageReaderTest {
Assert.assertEquals(ControlMessage.TYPE_INJECT_KEYCODE, event.getType()); Assert.assertEquals(ControlMessage.TYPE_INJECT_KEYCODE, event.getType());
Assert.assertEquals(KeyEvent.ACTION_UP, event.getAction()); Assert.assertEquals(KeyEvent.ACTION_UP, event.getAction());
Assert.assertEquals(KeyEvent.KEYCODE_ENTER, event.getKeycode()); Assert.assertEquals(KeyEvent.KEYCODE_ENTER, event.getKeycode());
Assert.assertEquals(5, event.getRepeat());
Assert.assertEquals(KeyEvent.META_CTRL_ON, event.getMetaState()); Assert.assertEquals(KeyEvent.META_CTRL_ON, event.getMetaState());
} }
@@ -48,7 +50,7 @@ public class ControlMessageReaderTest {
DataOutputStream dos = new DataOutputStream(bos); DataOutputStream dos = new DataOutputStream(bos);
dos.writeByte(ControlMessage.TYPE_INJECT_TEXT); dos.writeByte(ControlMessage.TYPE_INJECT_TEXT);
byte[] text = "testé".getBytes(StandardCharsets.UTF_8); byte[] text = "testé".getBytes(StandardCharsets.UTF_8);
dos.writeShort(text.length); dos.writeInt(text.length);
dos.write(text); dos.write(text);
byte[] packet = bos.toByteArray(); byte[] packet = bos.toByteArray();
@@ -68,7 +70,7 @@ public class ControlMessageReaderTest {
dos.writeByte(ControlMessage.TYPE_INJECT_TEXT); dos.writeByte(ControlMessage.TYPE_INJECT_TEXT);
byte[] text = new byte[ControlMessageReader.INJECT_TEXT_MAX_LENGTH]; byte[] text = new byte[ControlMessageReader.INJECT_TEXT_MAX_LENGTH];
Arrays.fill(text, (byte) 'a'); Arrays.fill(text, (byte) 'a');
dos.writeShort(text.length); dos.writeInt(text.length);
dos.write(text); dos.write(text);
byte[] packet = bos.toByteArray(); byte[] packet = bos.toByteArray();
@@ -218,7 +220,7 @@ public class ControlMessageReaderTest {
dos.writeByte(ControlMessage.TYPE_SET_CLIPBOARD); dos.writeByte(ControlMessage.TYPE_SET_CLIPBOARD);
dos.writeByte(1); // paste dos.writeByte(1); // paste
byte[] text = "testé".getBytes(StandardCharsets.UTF_8); byte[] text = "testé".getBytes(StandardCharsets.UTF_8);
dos.writeShort(text.length); dos.writeInt(text.length);
dos.write(text); dos.write(text);
byte[] packet = bos.toByteArray(); byte[] packet = bos.toByteArray();
@@ -246,7 +248,7 @@ public class ControlMessageReaderTest {
Arrays.fill(rawText, (byte) 'a'); Arrays.fill(rawText, (byte) 'a');
String text = new String(rawText, 0, rawText.length); String text = new String(rawText, 0, rawText.length);
dos.writeShort(rawText.length); dos.writeInt(rawText.length);
dos.write(rawText); dos.write(rawText);
byte[] packet = bos.toByteArray(); byte[] packet = bos.toByteArray();
@@ -308,11 +310,13 @@ public class ControlMessageReaderTest {
dos.writeByte(ControlMessage.TYPE_INJECT_KEYCODE); dos.writeByte(ControlMessage.TYPE_INJECT_KEYCODE);
dos.writeByte(KeyEvent.ACTION_UP); dos.writeByte(KeyEvent.ACTION_UP);
dos.writeInt(KeyEvent.KEYCODE_ENTER); dos.writeInt(KeyEvent.KEYCODE_ENTER);
dos.writeInt(0); // repeat
dos.writeInt(KeyEvent.META_CTRL_ON); dos.writeInt(KeyEvent.META_CTRL_ON);
dos.writeByte(ControlMessage.TYPE_INJECT_KEYCODE); dos.writeByte(ControlMessage.TYPE_INJECT_KEYCODE);
dos.writeByte(MotionEvent.ACTION_DOWN); dos.writeByte(MotionEvent.ACTION_DOWN);
dos.writeInt(MotionEvent.BUTTON_PRIMARY); dos.writeInt(MotionEvent.BUTTON_PRIMARY);
dos.writeInt(1); // repeat
dos.writeInt(KeyEvent.META_CTRL_ON); dos.writeInt(KeyEvent.META_CTRL_ON);
byte[] packet = bos.toByteArray(); byte[] packet = bos.toByteArray();
@@ -322,12 +326,14 @@ public class ControlMessageReaderTest {
Assert.assertEquals(ControlMessage.TYPE_INJECT_KEYCODE, event.getType()); Assert.assertEquals(ControlMessage.TYPE_INJECT_KEYCODE, event.getType());
Assert.assertEquals(KeyEvent.ACTION_UP, event.getAction()); Assert.assertEquals(KeyEvent.ACTION_UP, event.getAction());
Assert.assertEquals(KeyEvent.KEYCODE_ENTER, event.getKeycode()); Assert.assertEquals(KeyEvent.KEYCODE_ENTER, event.getKeycode());
Assert.assertEquals(0, event.getRepeat());
Assert.assertEquals(KeyEvent.META_CTRL_ON, event.getMetaState()); Assert.assertEquals(KeyEvent.META_CTRL_ON, event.getMetaState());
event = reader.next(); event = reader.next();
Assert.assertEquals(ControlMessage.TYPE_INJECT_KEYCODE, event.getType()); Assert.assertEquals(ControlMessage.TYPE_INJECT_KEYCODE, event.getType());
Assert.assertEquals(MotionEvent.ACTION_DOWN, event.getAction()); Assert.assertEquals(MotionEvent.ACTION_DOWN, event.getAction());
Assert.assertEquals(MotionEvent.BUTTON_PRIMARY, event.getKeycode()); Assert.assertEquals(MotionEvent.BUTTON_PRIMARY, event.getKeycode());
Assert.assertEquals(1, event.getRepeat());
Assert.assertEquals(KeyEvent.META_CTRL_ON, event.getMetaState()); Assert.assertEquals(KeyEvent.META_CTRL_ON, event.getMetaState());
} }
@@ -341,6 +347,7 @@ public class ControlMessageReaderTest {
dos.writeByte(ControlMessage.TYPE_INJECT_KEYCODE); dos.writeByte(ControlMessage.TYPE_INJECT_KEYCODE);
dos.writeByte(KeyEvent.ACTION_UP); dos.writeByte(KeyEvent.ACTION_UP);
dos.writeInt(KeyEvent.KEYCODE_ENTER); dos.writeInt(KeyEvent.KEYCODE_ENTER);
dos.writeInt(4); // repeat
dos.writeInt(KeyEvent.META_CTRL_ON); dos.writeInt(KeyEvent.META_CTRL_ON);
dos.writeByte(ControlMessage.TYPE_INJECT_KEYCODE); dos.writeByte(ControlMessage.TYPE_INJECT_KEYCODE);
@@ -353,6 +360,7 @@ public class ControlMessageReaderTest {
Assert.assertEquals(ControlMessage.TYPE_INJECT_KEYCODE, event.getType()); Assert.assertEquals(ControlMessage.TYPE_INJECT_KEYCODE, event.getType());
Assert.assertEquals(KeyEvent.ACTION_UP, event.getAction()); Assert.assertEquals(KeyEvent.ACTION_UP, event.getAction());
Assert.assertEquals(KeyEvent.KEYCODE_ENTER, event.getKeycode()); Assert.assertEquals(KeyEvent.KEYCODE_ENTER, event.getKeycode());
Assert.assertEquals(4, event.getRepeat());
Assert.assertEquals(KeyEvent.META_CTRL_ON, event.getMetaState()); Assert.assertEquals(KeyEvent.META_CTRL_ON, event.getMetaState());
event = reader.next(); event = reader.next();
@@ -360,6 +368,7 @@ public class ControlMessageReaderTest {
bos.reset(); bos.reset();
dos.writeInt(MotionEvent.BUTTON_PRIMARY); dos.writeInt(MotionEvent.BUTTON_PRIMARY);
dos.writeInt(5); // repeat
dos.writeInt(KeyEvent.META_CTRL_ON); dos.writeInt(KeyEvent.META_CTRL_ON);
packet = bos.toByteArray(); packet = bos.toByteArray();
reader.readFrom(new ByteArrayInputStream(packet)); reader.readFrom(new ByteArrayInputStream(packet));
@@ -369,6 +378,7 @@ public class ControlMessageReaderTest {
Assert.assertEquals(ControlMessage.TYPE_INJECT_KEYCODE, event.getType()); Assert.assertEquals(ControlMessage.TYPE_INJECT_KEYCODE, event.getType());
Assert.assertEquals(MotionEvent.ACTION_DOWN, event.getAction()); Assert.assertEquals(MotionEvent.ACTION_DOWN, event.getAction());
Assert.assertEquals(MotionEvent.BUTTON_PRIMARY, event.getKeycode()); Assert.assertEquals(MotionEvent.BUTTON_PRIMARY, event.getKeycode());
Assert.assertEquals(5, event.getRepeat());
Assert.assertEquals(KeyEvent.META_CTRL_ON, event.getMetaState()); Assert.assertEquals(KeyEvent.META_CTRL_ON, event.getMetaState());
} }
} }

View File

@@ -19,7 +19,7 @@ public class DeviceMessageWriterTest {
ByteArrayOutputStream bos = new ByteArrayOutputStream(); ByteArrayOutputStream bos = new ByteArrayOutputStream();
DataOutputStream dos = new DataOutputStream(bos); DataOutputStream dos = new DataOutputStream(bos);
dos.writeByte(DeviceMessage.TYPE_CLIPBOARD); dos.writeByte(DeviceMessage.TYPE_CLIPBOARD);
dos.writeShort(data.length); dos.writeInt(data.length);
dos.write(data); dos.write(data);
byte[] expected = bos.toByteArray(); byte[] expected = bos.toByteArray();