Compare commits

..

1 Commits

Author SHA1 Message Date
Romain Vimont
0ae688764c Synchronize device clipboard to computer
Automatically synchronize the device clipboard to the computer any time
it changes.

This allows seamless copy-paste from Android to the computer.

Fixes #1056 <https://github.com/Genymobile/scrcpy/issues/1056#issuecomment-631363684>
2020-05-23 19:13:28 +02:00
45 changed files with 282 additions and 992 deletions

3
.gitignore vendored
View File

@@ -1,8 +1,5 @@
build/
/dist/
/build-*/
/build_*/
/release-*/
.idea/
.gradle/
/x/

View File

@@ -249,10 +249,10 @@ You can then [run](README.md#run) _scrcpy_.
## Prebuilt server
- [`scrcpy-server-v1.14`][direct-scrcpy-server]
_(SHA-256: 1d1b18a2b80e956771fd63b99b414d2d028713a8f12ddfa5a369709ad4295620)_
- [`scrcpy-server-v1.13`][direct-scrcpy-server]
_(SHA-256: 5fee64ca1ccdc2f38550f31f5353c66de3de30c2e929a964e30fa2d005d5f885)_
[direct-scrcpy-server]: https://github.com/Genymobile/scrcpy/releases/download/v1.14/scrcpy-server-v1.14
[direct-scrcpy-server]: https://github.com/Genymobile/scrcpy/releases/download/v1.13/scrcpy-server-v1.13
Download the prebuilt server somewhere, and specify its path during the Meson
configuration:

View File

@@ -1,4 +1,4 @@
# scrcpy (v1.14)
# scrcpy (v1.13)
This application provides display and control of Android devices connected on
USB (or [over TCP/IP][article-tcpip]). It does not require any _root_ access.
@@ -69,10 +69,10 @@ hard).
For Windows, for simplicity, a prebuilt archive with all the dependencies
(including `adb`) is available:
- [`scrcpy-win64-v1.14.zip`][direct-win64]
_(SHA-256: 2be9139e46e29cf2f5f695848bb2b75a543b8f38be1133257dc5068252abc25f)_
- [`scrcpy-win64-v1.13.zip`][direct-win64]
_(SHA-256: 806aafc00d4db01513193addaa24f47858893ba5efe75770bfef6ae1ea987d27)_
[direct-win64]: https://github.com/Genymobile/scrcpy/releases/download/v1.14/scrcpy-win64-v1.14.zip
[direct-win64]: https://github.com/Genymobile/scrcpy/releases/download/v1.13/scrcpy-win64-v1.13.zip
It is also available in [Chocolatey]:
@@ -289,22 +289,6 @@ From another terminal:
scrcpy
```
To avoid enabling remote port forwarding, you could force a forward connection
instead (notice the `-L` instead of `-R`):
```bash
adb kill-server # kill the local adb server on 5037
ssh -CN -L5037:localhost:5037 -L27183:localhost:27183 your_remote_computer
# keep this open
```
From another terminal:
```bash
scrcpy --force-adb-forwrad
```
Like for wireless connections, it may be useful to reduce quality:
```
@@ -417,7 +401,7 @@ The secondary display may only be controlled if the device runs at least Android
#### Stay awake
To prevent the device to sleep after some delay when the device is plugged in:
To prevent the device to sleep after some delay:
```bash
scrcpy --stay-awake
@@ -439,7 +423,7 @@ scrcpy -S
Or by pressing `Ctrl`+`o` at any time.
To turn it back on, press `Ctrl`+`Shift`+`o` (or `POWER`, `Ctrl`+`p`).
To turn it back on, press `POWER` (or `Ctrl`+`p`).
It can be useful to also prevent the device to sleep:
@@ -479,17 +463,6 @@ scrcpy -t
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
#### Rotate device screen
@@ -505,8 +478,7 @@ It is possible to synchronize clipboards between the computer and the device, in
both directions:
- `Ctrl`+`c` copies the device clipboard to the computer clipboard;
- `Ctrl`+`Shift`+`v` copies the computer clipboard to the device clipboard (and
pastes if the device runs Android >= 7);
- `Ctrl`+`Shift`+`v` copies the computer clipboard to the device clipboard;
- `Ctrl`+`v` _pastes_ the computer clipboard as a sequence of text events (but
breaks non-ASCII characters).
@@ -571,30 +543,29 @@ Also see [issue #14].
## Shortcuts
| Action | Shortcut | Shortcut (macOS)
| ------------------------------------------- |:----------------------------- |:-----------------------------
| Switch fullscreen mode | `Ctrl`+`f` | `Cmd`+`f`
| Rotate display left | `Ctrl`+`←` _(left)_ | `Cmd`+`←` _(left)_
| Rotate display right | `Ctrl`+`→` _(right)_ | `Cmd`+`→` _(right)_
| Resize window to 1:1 (pixel-perfect) | `Ctrl`+`g` | `Cmd`+`g`
| Resize window to remove black borders | `Ctrl`+`x` \| _Double-click¹_ | `Cmd`+`x` \| _Double-click¹_
| Click on `HOME` | `Ctrl`+`h` \| _Middle-click_ | `Ctrl`+`h` \| _Middle-click_
| Click on `BACK` | `Ctrl`+`b` \| _Right-click²_ | `Cmd`+`b` \| _Right-click²_
| Click on `APP_SWITCH` | `Ctrl`+`s` | `Cmd`+`s`
| Click on `MENU` | `Ctrl`+`m` | `Ctrl`+`m`
| Click on `VOLUME_UP` | `Ctrl`+`↑` _(up)_ | `Cmd`+`↑` _(up)_
| Click on `VOLUME_DOWN` | `Ctrl`+`↓` _(down)_ | `Cmd`+`↓` _(down)_
| Click on `POWER` | `Ctrl`+`p` | `Cmd`+`p`
| Power on | _Right-click²_ | _Right-click²_
| Turn device screen off (keep mirroring) | `Ctrl`+`o` | `Cmd`+`o`
| Turn device screen on | `Ctrl`+`Shift`+`o` | `Cmd`+`Shift`+`o`
| Rotate device screen | `Ctrl`+`r` | `Cmd`+`r`
| Expand notification panel | `Ctrl`+`n` | `Cmd`+`n`
| Collapse notification panel | `Ctrl`+`Shift`+`n` | `Cmd`+`Shift`+`n`
| Copy device clipboard to computer | `Ctrl`+`c` | `Cmd`+`c`
| Paste computer clipboard to device | `Ctrl`+`v` | `Cmd`+`v`
| Copy computer clipboard to device and paste | `Ctrl`+`Shift`+`v` | `Cmd`+`Shift`+`v`
| Enable/disable FPS counter (on stdout) | `Ctrl`+`i` | `Cmd`+`i`
| Action | Shortcut | Shortcut (macOS)
| -------------------------------------- |:----------------------------- |:-----------------------------
| Switch fullscreen mode | `Ctrl`+`f` | `Cmd`+`f`
| Rotate display left | `Ctrl`+`←` _(left)_ | `Cmd`+`←` _(left)_
| Rotate display right | `Ctrl`+`→` _(right)_ | `Cmd`+`→` _(right)_
| Resize window to 1:1 (pixel-perfect) | `Ctrl`+`g` | `Cmd`+`g`
| Resize window to remove black borders | `Ctrl`+`x` \| _Double-click¹_ | `Cmd`+`x` \| _Double-click¹_
| Click on `HOME` | `Ctrl`+`h` \| _Middle-click_ | `Ctrl`+`h` \| _Middle-click_
| Click on `BACK` | `Ctrl`+`b` \| _Right-click²_ | `Cmd`+`b` \| _Right-click²_
| Click on `APP_SWITCH` | `Ctrl`+`s` | `Cmd`+`s`
| Click on `MENU` | `Ctrl`+`m` | `Ctrl`+`m`
| Click on `VOLUME_UP` | `Ctrl`+`↑` _(up)_ | `Cmd`+`↑` _(up)_
| Click on `VOLUME_DOWN` | `Ctrl`+`↓` _(down)_ | `Cmd`+`↓` _(down)_
| Click on `POWER` | `Ctrl`+`p` | `Cmd`+`p`
| Power on | _Right-click²_ | _Right-click²_
| Turn device screen off (keep mirroring)| `Ctrl`+`o` | `Cmd`+`o`
| Rotate device screen | `Ctrl`+`r` | `Cmd`+`r`
| Expand notification panel | `Ctrl`+`n` | `Cmd`+`n`
| Collapse notification panel | `Ctrl`+`Shift`+`n` | `Cmd`+`Shift`+`n`
| Copy device clipboard to computer | `Ctrl`+`c` | `Cmd`+`c`
| Paste computer clipboard to device | `Ctrl`+`v` | `Cmd`+`v`
| Copy computer clipboard to device | `Ctrl`+`Shift`+`v` | `Cmd`+`Shift`+`v`
| Enable/disable FPS counter (on stdout) | `Ctrl`+`i` | `Cmd`+`i`
_¹Double-click on black borders to remove them._
_²Right-click turns the screen on if it was off, presses BACK otherwise._

View File

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

View File

@@ -25,16 +25,6 @@ Encode the video at the given bit\-rate, expressed in bits/s. Unit suffixes are
Default is 8000000.
.TP
.BI "\-\-codec\-options " key[:type]=value[,...]
Set a list of comma-separated key:type=value options for the device encoder.
The possible values for 'type' are 'int' (default), 'long', 'float' and 'string'.
The list of possible codec options is available in the Android documentation
.UR https://d.android.com/reference/android/media/MediaFormat
.UE .
.TP
.BI "\-\-crop " width\fR:\fIheight\fR:\fIx\fR:\fIy
Crop the device screen on the server.
@@ -43,10 +33,6 @@ The values are expressed in the device natural orientation (typically, portrait
.B \-\-max\-size
value is computed on the cropped size.
.TP
.BI "\-\-disable-screensaver"
Disable screensaver while scrcpy is running.
.TP
.BI "\-\-display " id
Specify the display id to mirror.
@@ -56,10 +42,6 @@ The list of possible display ids can be listed by "adb shell dumpsys display"
Default is 0.
.TP
.B \-\-force\-adb\-forward
Do not attempt to use "adb reverse" to connect to the device.
.TP
.B \-f, \-\-fullscreen
Start in fullscreen.
@@ -133,7 +115,6 @@ Force recording format (either mp4 or mkv).
Request SDL to use the given render driver (this is just a hint).
Supported names are currently "direct3d", "opengl", "opengles2", "opengles", "metal" and "software".
.UR https://wiki.libsdl.org/SDL_HINT_RENDER_DRIVER
.UE
@@ -155,7 +136,7 @@ Turn the device screen off immediately.
.TP
.B \-t, \-\-show\-touches
Enable "show touches" on start, restore the initial value on exit.
Enable "show touches" on start, restore the initial value on exit..
It only shows physical touches (not clicks from scrcpy).
@@ -163,15 +144,9 @@ It only shows physical touches (not clicks from scrcpy).
.B \-v, \-\-version
Print the version of scrcpy.
.TP
.BI "\-V, \-\-verbosity " value
Set the log level ("debug", "info", "warn" or "error").
Default is "info" for release builds, "debug" for debug builds.
.TP
.B \-w, \-\-stay-awake
Keep the device on while scrcpy is running, when the device is plugged in.
Keep the device on while scrcpy is running.
.TP
.B \-\-window\-borderless
@@ -263,10 +238,6 @@ Turn screen on
.B Ctrl+o
Turn device screen off (keep mirroring)
.TP
.B Ctrl+Shift+o
Turn device screen on
.TP
.B Ctrl+r
Rotate device screen
@@ -289,7 +260,7 @@ Paste computer clipboard to device
.TP
.B Ctrl+Shift+v
Copy computer clipboard to device (and paste if the device runs Android >= 7)
Copy computer clipboard to device
.TP
.B Ctrl+i

View File

@@ -3,11 +3,10 @@
#include <assert.h>
#include <getopt.h>
#include <stdint.h>
#include <stdio.h>
#include <unistd.h>
#include "config.h"
#include "scrcpy.h"
#include "recorder.h"
#include "util/log.h"
#include "util/str_util.h"
@@ -31,24 +30,12 @@ scrcpy_print_usage(const char *arg0) {
" Unit suffixes are supported: 'K' (x1000) and 'M' (x1000000).\n"
" Default is %d.\n"
"\n"
" --codec-options key[:type]=value[,...]\n"
" Set a list of comma-separated key:type=value options for the\n"
" device encoder.\n"
" The possible values for 'type' are 'int' (default), 'long',\n"
" 'float' and 'string'.\n"
" The list of possible codec options is available in the\n"
" Android documentation:\n"
" <https://d.android.com/reference/android/media/MediaFormat>\n"
"\n"
" --crop width:height:x:y\n"
" Crop the device screen on the server.\n"
" The values are expressed in the device natural orientation\n"
" (typically, portrait for a phone, landscape for a tablet).\n"
" Any --max-size value is computed on the cropped size.\n"
"\n"
" --disable-screensaver\n"
" Disable screensaver while scrcpy is running.\n"
"\n"
" --display id\n"
" Specify the display id to mirror.\n"
"\n"
@@ -58,10 +45,6 @@ scrcpy_print_usage(const char *arg0) {
"\n"
" Default is 0.\n"
"\n"
" --force-adb-forward\n"
" Do not attempt to use \"adb reverse\" to connect to the\n"
" the device.\n"
"\n"
" -f, --fullscreen\n"
" Start in fullscreen.\n"
"\n"
@@ -153,18 +136,9 @@ scrcpy_print_usage(const char *arg0) {
"\n"
" -v, --version\n"
" Print the version of scrcpy.\n"
"\n"
" -V, --verbosity value\n"
" Set the log level (debug, info, warn or error).\n"
#ifndef NDEBUG
" Default is debug.\n"
#else
" Default is info.\n"
#endif
"\n"
" -w, --stay-awake\n"
" Keep the device on while scrcpy is running, when the device\n"
" is plugged in.\n"
" Keep the device on while scrcpy is running.\n"
"\n"
" --window-borderless\n"
" Disable window decorations (display borderless window).\n"
@@ -236,9 +210,6 @@ scrcpy_print_usage(const char *arg0) {
" " CTRL_OR_CMD "+o\n"
" Turn device screen off (keep mirroring)\n"
"\n"
" " CTRL_OR_CMD "+Shift+o\n"
" Turn device screen on\n"
"\n"
" " CTRL_OR_CMD "+r\n"
" Rotate device screen\n"
"\n"
@@ -255,8 +226,7 @@ scrcpy_print_usage(const char *arg0) {
" Paste computer clipboard to device\n"
"\n"
" " CTRL_OR_CMD "+Shift+v\n"
" Copy computer clipboard to device (and paste if the device\n"
" runs Android >= 7)\n"
" Copy computer clipboard to device\n"
"\n"
" " CTRL_OR_CMD "+i\n"
" Enable/disable FPS counter (print frames/second in logs)\n"
@@ -383,10 +353,10 @@ parse_rotation(const char *s, uint8_t *rotation) {
static bool
parse_window_position(const char *s, int16_t *position) {
// special value for "auto"
static_assert(SC_WINDOW_POSITION_UNDEFINED == -0x8000, "unexpected value");
static_assert(WINDOW_POSITION_UNDEFINED == -0x8000, "unexpected value");
if (!strcmp(s, "auto")) {
*position = SC_WINDOW_POSITION_UNDEFINED;
*position = WINDOW_POSITION_UNDEFINED;
return true;
}
@@ -415,7 +385,7 @@ parse_window_dimension(const char *s, uint16_t *dimension) {
}
static bool
parse_port_range(const char *s, struct sc_port_range *port_range) {
parse_port_range(const char *s, struct port_range *port_range) {
long values[2];
size_t count = parse_integers_arg(s, 2, values, 0, 0xFFFF, "port");
if (!count) {
@@ -455,46 +425,20 @@ parse_display_id(const char *s, uint16_t *display_id) {
}
static bool
parse_log_level(const char *s, enum sc_log_level *log_level) {
if (!strcmp(s, "debug")) {
*log_level = SC_LOG_LEVEL_DEBUG;
return true;
}
if (!strcmp(s, "info")) {
*log_level = SC_LOG_LEVEL_INFO;
return true;
}
if (!strcmp(s, "warn")) {
*log_level = SC_LOG_LEVEL_WARN;
return true;
}
if (!strcmp(s, "error")) {
*log_level = SC_LOG_LEVEL_ERROR;
return true;
}
LOGE("Could not parse log level: %s", s);
return false;
}
static bool
parse_record_format(const char *optarg, enum sc_record_format *format) {
parse_record_format(const char *optarg, enum recorder_format *format) {
if (!strcmp(optarg, "mp4")) {
*format = SC_RECORD_FORMAT_MP4;
*format = RECORDER_FORMAT_MP4;
return true;
}
if (!strcmp(optarg, "mkv")) {
*format = SC_RECORD_FORMAT_MKV;
*format = RECORDER_FORMAT_MKV;
return true;
}
LOGE("Unsupported format: %s (expected mp4 or mkv)", optarg);
return false;
}
static enum sc_record_format
static enum recorder_format
guess_record_format(const char *filename) {
size_t len = strlen(filename);
if (len < 4) {
@@ -502,10 +446,10 @@ guess_record_format(const char *filename) {
}
const char *ext = &filename[len - 4];
if (!strcmp(ext, ".mp4")) {
return SC_RECORD_FORMAT_MP4;
return RECORDER_FORMAT_MP4;
}
if (!strcmp(ext, ".mkv")) {
return SC_RECORD_FORMAT_MKV;
return RECORDER_FORMAT_MKV;
}
return 0;
}
@@ -528,22 +472,14 @@ guess_record_format(const char *filename) {
#define OPT_ROTATION 1015
#define OPT_RENDER_DRIVER 1016
#define OPT_NO_MIPMAPS 1017
#define OPT_CODEC_OPTIONS 1018
#define OPT_FORCE_ADB_FORWARD 1019
#define OPT_DISABLE_SCREENSAVER 1020
bool
scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) {
static const struct option long_options[] = {
{"always-on-top", no_argument, NULL, OPT_ALWAYS_ON_TOP},
{"bit-rate", required_argument, NULL, 'b'},
{"codec-options", required_argument, NULL, OPT_CODEC_OPTIONS},
{"crop", required_argument, NULL, OPT_CROP},
{"disable-screensaver", no_argument, NULL,
OPT_DISABLE_SCREENSAVER},
{"display", required_argument, NULL, OPT_DISPLAY_ID},
{"force-adb-forward", no_argument, NULL,
OPT_FORCE_ADB_FORWARD},
{"fullscreen", no_argument, NULL, 'f'},
{"help", no_argument, NULL, 'h'},
{"lock-video-orientation", required_argument, NULL,
@@ -566,7 +502,6 @@ scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) {
{"show-touches", no_argument, NULL, 't'},
{"stay-awake", no_argument, NULL, 'w'},
{"turn-screen-off", no_argument, NULL, 'S'},
{"verbosity", required_argument, NULL, 'V'},
{"version", no_argument, NULL, 'v'},
{"window-title", required_argument, NULL, OPT_WINDOW_TITLE},
{"window-x", required_argument, NULL, OPT_WINDOW_X},
@@ -583,8 +518,8 @@ scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) {
optind = 0; // reset to start from the first argument in tests
int c;
while ((c = getopt_long(argc, argv, "b:c:fF:hm:nNp:r:s:StTvV:w",
long_options, NULL)) != -1) {
while ((c = getopt_long(argc, argv, "b:c:fF:hm:nNp:r:s:StTvw", long_options,
NULL)) != -1) {
switch (c) {
case 'b':
if (!parse_bit_rate(optarg, &opts->bit_rate)) {
@@ -663,11 +598,6 @@ scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) {
case 'v':
args->version = true;
break;
case 'V':
if (!parse_log_level(optarg, &opts->log_level)) {
return false;
}
break;
case 'w':
opts->stay_awake = true;
break;
@@ -717,15 +647,6 @@ scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) {
case OPT_NO_MIPMAPS:
opts->mipmaps = false;
break;
case OPT_CODEC_OPTIONS:
opts->codec_options = optarg;
break;
case OPT_FORCE_ADB_FORWARD:
opts->force_adb_forward = true;
break;
case OPT_DISABLE_SCREENSAVER:
opts->disable_screensaver = true;
break;
default:
// getopt prints the error message on stderr
return false;

View File

@@ -20,9 +20,9 @@ write_position(uint8_t *buf, const struct position *position) {
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_write32be(buf, len);
memcpy(&buf[4], utf8, len);
return 4 + len;
buffer_write16be(buf, (uint16_t) len);
memcpy(&buf[2], utf8, len);
return 2 + len;
}
static uint16_t
@@ -42,9 +42,8 @@ control_msg_serialize(const struct control_msg *msg, unsigned char *buf) {
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.repeat);
buffer_write32be(&buf[10], msg->inject_keycode.metastate);
return 14;
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,
@@ -68,11 +67,10 @@ control_msg_serialize(const struct control_msg *msg, unsigned char *buf) {
(uint32_t) msg->inject_scroll_event.vscroll);
return 21;
case CONTROL_MSG_TYPE_SET_CLIPBOARD: {
buf[1] = !!msg->set_clipboard.paste;
size_t len = write_string(msg->set_clipboard.text,
size_t len = write_string(msg->inject_text.text,
CONTROL_MSG_CLIPBOARD_TEXT_MAX_LENGTH,
&buf[2]);
return 2 + len;
&buf[1]);
return 1 + len;
}
case CONTROL_MSG_TYPE_SET_SCREEN_POWER_MODE:
buf[1] = msg->set_screen_power_mode.mode;

View File

@@ -10,11 +10,10 @@
#include "android/keycodes.h"
#include "common.h"
#define CONTROL_MSG_MAX_SIZE (1 << 18) // 256k
#define CONTROL_MSG_INJECT_TEXT_MAX_LENGTH 300
// type: 1 byte; paste flag: 1 byte; length: 4 bytes
#define CONTROL_MSG_CLIPBOARD_TEXT_MAX_LENGTH (CONTROL_MSG_MAX_SIZE - 6)
#define CONTROL_MSG_CLIPBOARD_TEXT_MAX_LENGTH 4093
#define CONTROL_MSG_SERIALIZED_MAX_SIZE \
(3 + CONTROL_MSG_CLIPBOARD_TEXT_MAX_LENGTH)
#define POINTER_ID_MOUSE UINT64_C(-1);
@@ -44,7 +43,6 @@ struct control_msg {
struct {
enum android_keyevent_action action;
enum android_keycode keycode;
uint32_t repeat;
enum android_metastate metastate;
} inject_keycode;
struct {
@@ -64,7 +62,6 @@ struct control_msg {
} inject_scroll_event;
struct {
char *text; // owned, to be freed by SDL_free()
bool paste;
} set_clipboard;
struct {
enum screen_power_mode mode;
@@ -72,7 +69,7 @@ struct control_msg {
};
};
// buf size must be at least CONTROL_MSG_MAX_SIZE
// 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);

View File

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

View File

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

View File

@@ -7,9 +7,8 @@
#include "config.h"
#define DEVICE_MSG_MAX_SIZE (1 << 18) // 256k
// type: 1 byte; length: 4 bytes
#define DEVICE_MSG_TEXT_MAX_LENGTH (DEVICE_MSG_MAX_SIZE - 5)
#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,

View File

@@ -112,7 +112,7 @@ request_device_clipboard(struct controller *controller) {
}
static void
set_device_clipboard(struct controller *controller, bool paste) {
set_device_clipboard(struct controller *controller) {
char *text = SDL_GetClipboardText();
if (!text) {
LOGW("Could not get clipboard text: %s", SDL_GetError());
@@ -127,7 +127,6 @@ set_device_clipboard(struct controller *controller, bool paste) {
struct control_msg msg;
msg.type = CONTROL_MSG_TYPE_SET_CLIPBOARD;
msg.set_clipboard.text = text;
msg.set_clipboard.paste = paste;
if (!controller_push_msg(controller, &msg)) {
SDL_free(text);
@@ -234,7 +233,7 @@ input_manager_process_text_input(struct input_manager *im,
static bool
convert_input_key(const SDL_KeyboardEvent *from, struct control_msg *to,
bool prefer_text, uint32_t repeat) {
bool prefer_text) {
to->type = CONTROL_MSG_TYPE_INJECT_KEYCODE;
if (!convert_keycode_action(from->type, &to->inject_keycode.action)) {
@@ -247,7 +246,6 @@ convert_input_key(const SDL_KeyboardEvent *from, struct control_msg *to,
return false;
}
to->inject_keycode.repeat = repeat;
to->inject_keycode.metastate = convert_meta_state(mod);
return true;
@@ -322,11 +320,8 @@ input_manager_process_key(struct input_manager *im,
}
return;
case SDLK_o:
if (control && cmd && !repeat && down) {
enum screen_power_mode mode = shift
? SCREEN_POWER_MODE_NORMAL
: SCREEN_POWER_MODE_OFF;
set_screen_power_mode(controller, mode);
if (control && cmd && !shift && down) {
set_screen_power_mode(controller, SCREEN_POWER_MODE_OFF);
}
return;
case SDLK_DOWN:
@@ -342,12 +337,12 @@ input_manager_process_key(struct input_manager *im,
}
return;
case SDLK_LEFT:
if (cmd && !shift && !repeat && down) {
if (cmd && !shift && down) {
rotate_client_left(im->screen);
}
return;
case SDLK_RIGHT:
if (cmd && !shift && !repeat && down) {
if (cmd && !shift && down) {
rotate_client_right(im->screen);
}
return;
@@ -359,8 +354,8 @@ input_manager_process_key(struct input_manager *im,
case SDLK_v:
if (control && cmd && !repeat && down) {
if (shift) {
// store the text in the device clipboard and paste
set_device_clipboard(controller, true);
// store the text in the device clipboard
set_device_clipboard(controller);
} else {
// inject the text as input events
clipboard_paste(controller);
@@ -412,14 +407,8 @@ input_manager_process_key(struct input_manager *im,
return;
}
if (event->repeat) {
++im->repeat;
} else {
im->repeat = 0;
}
struct control_msg msg;
if (convert_input_key(event, &msg, im->prefer_text, im->repeat)) {
if (convert_input_key(event, &msg, im->prefer_text)) {
if (!controller_push_msg(controller, &msg)) {
LOGW("Could not request 'inject keycode'");
}
@@ -434,7 +423,7 @@ convert_mouse_motion(const SDL_MouseMotionEvent *from, struct screen *screen,
to->inject_touch_event.pointer_id = POINTER_ID_MOUSE;
to->inject_touch_event.position.screen_size = screen->frame_size;
to->inject_touch_event.position.point =
screen_convert_window_to_frame_coords(screen, from->x, from->y);
screen_convert_to_frame_coords(screen, from->x, from->y);
to->inject_touch_event.pressure = 1.f;
to->inject_touch_event.buttons = convert_mouse_buttons(from->state);
@@ -472,15 +461,15 @@ convert_touch(const SDL_TouchFingerEvent *from, struct screen *screen,
to->inject_touch_event.pointer_id = from->fingerId;
to->inject_touch_event.position.screen_size = screen->frame_size;
int dw;
int dh;
SDL_GL_GetDrawableSize(screen->window, &dw, &dh);
int ww;
int wh;
SDL_GL_GetDrawableSize(screen->window, &ww, &wh);
// SDL touch event coordinates are normalized in the range [0; 1]
int32_t x = from->x * dw;
int32_t y = from->y * dh;
int32_t x = from->x * ww;
int32_t y = from->y * wh;
to->inject_touch_event.position.point =
screen_convert_drawable_to_frame_coords(screen, x, y);
screen_convert_to_frame_coords(screen, x, y);
to->inject_touch_event.pressure = from->pressure;
to->inject_touch_event.buttons = 0;
@@ -510,9 +499,8 @@ convert_mouse_button(const SDL_MouseButtonEvent *from, struct screen *screen,
to->inject_touch_event.pointer_id = POINTER_ID_MOUSE;
to->inject_touch_event.position.screen_size = screen->frame_size;
to->inject_touch_event.position.point =
screen_convert_window_to_frame_coords(screen, from->x, from->y);
to->inject_touch_event.pressure =
from->type == SDL_MOUSEBUTTONDOWN ? 1.f : 0.f;
screen_convert_to_frame_coords(screen, from->x, from->y);
to->inject_touch_event.pressure = 1.f;
to->inject_touch_event.buttons =
convert_mouse_buttons(SDL_BUTTON(from->button));
@@ -576,8 +564,7 @@ convert_mouse_wheel(const SDL_MouseWheelEvent *from, struct screen *screen,
struct position position = {
.screen_size = screen->frame_size,
.point = screen_convert_window_to_frame_coords(screen,
mouse_x, mouse_y),
.point = screen_convert_to_frame_coords(screen, mouse_x, mouse_y),
};
to->type = CONTROL_MSG_TYPE_INJECT_SCROLL_EVENT;

View File

@@ -14,11 +14,6 @@ struct input_manager {
struct controller *controller;
struct video_buffer *video_buffer;
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;
};

View File

@@ -1,6 +1,5 @@
#include "scrcpy.h"
#include <assert.h>
#include <stdbool.h>
#include <unistd.h>
#include <libavformat/avformat.h>
@@ -30,24 +29,6 @@ print_version(void) {
LIBAVUTIL_VERSION_MICRO);
}
static SDL_LogPriority
convert_log_level_to_sdl(enum sc_log_level level) {
switch (level) {
case SC_LOG_LEVEL_DEBUG:
return SDL_LOG_PRIORITY_DEBUG;
case SC_LOG_LEVEL_INFO:
return SDL_LOG_PRIORITY_INFO;
case SC_LOG_LEVEL_WARN:
return SDL_LOG_PRIORITY_WARN;
case SC_LOG_LEVEL_ERROR:
return SDL_LOG_PRIORITY_ERROR;
default:
assert(!"unexpected log level");
return SDL_LOG_PRIORITY_INFO;
}
}
int
main(int argc, char *argv[]) {
#ifdef __WINDOWS__
@@ -57,23 +38,20 @@ main(int argc, char *argv[]) {
setbuf(stderr, NULL);
#endif
#ifndef NDEBUG
SDL_LogSetAllPriority(SDL_LOG_PRIORITY_DEBUG);
#endif
struct scrcpy_cli_args args = {
.opts = SCRCPY_OPTIONS_DEFAULT,
.help = false,
.version = false,
};
#ifndef NDEBUG
args.opts.log_level = SC_LOG_LEVEL_DEBUG;
#endif
if (!scrcpy_parse_args(&args, argc, argv)) {
return 1;
}
SDL_LogPriority sdl_log = convert_log_level_to_sdl(args.opts.log_level);
SDL_LogSetPriority(SDL_LOG_CATEGORY_APPLICATION, sdl_log);
if (args.help) {
scrcpy_print_usage(argv[0]);
return 0;

View File

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

View File

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

View File

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

View File

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

View File

@@ -2,30 +2,12 @@
#define SCRCPY_H
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include "config.h"
enum sc_log_level {
SC_LOG_LEVEL_DEBUG,
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)
#include "common.h"
#include "input_manager.h"
#include "recorder.h"
struct scrcpy_options {
const char *serial;
@@ -34,17 +16,15 @@ struct scrcpy_options {
const char *window_title;
const char *push_target;
const char *render_driver;
const char *codec_options;
enum sc_log_level log_level;
enum sc_record_format record_format;
struct sc_port_range port_range;
enum recorder_format record_format;
struct port_range port_range;
uint16_t max_size;
uint32_t bit_rate;
uint16_t max_fps;
int8_t lock_video_orientation;
uint8_t rotation;
int16_t window_x; // SC_WINDOW_POSITION_UNDEFINED for "auto"
int16_t window_y; // SC_WINDOW_POSITION_UNDEFINED for "auto"
int16_t window_x; // WINDOW_POSITION_UNDEFINED for "auto"
int16_t window_y; // WINDOW_POSITION_UNDEFINED for "auto"
uint16_t window_width;
uint16_t window_height;
uint16_t display_id;
@@ -59,8 +39,6 @@ struct scrcpy_options {
bool window_borderless;
bool mipmaps;
bool stay_awake;
bool force_adb_forward;
bool disable_screensaver;
};
#define SCRCPY_OPTIONS_DEFAULT { \
@@ -70,9 +48,7 @@ struct scrcpy_options {
.window_title = NULL, \
.push_target = NULL, \
.render_driver = NULL, \
.codec_options = NULL, \
.log_level = SC_LOG_LEVEL_INFO, \
.record_format = SC_RECORD_FORMAT_AUTO, \
.record_format = RECORDER_FORMAT_AUTO, \
.port_range = { \
.first = DEFAULT_LOCAL_PORT_RANGE_FIRST, \
.last = DEFAULT_LOCAL_PORT_RANGE_LAST, \
@@ -82,8 +58,8 @@ struct scrcpy_options {
.max_fps = 0, \
.lock_video_orientation = DEFAULT_LOCK_VIDEO_ORIENTATION, \
.rotation = 0, \
.window_x = SC_WINDOW_POSITION_UNDEFINED, \
.window_y = SC_WINDOW_POSITION_UNDEFINED, \
.window_x = WINDOW_POSITION_UNDEFINED, \
.window_y = WINDOW_POSITION_UNDEFINED, \
.window_width = 0, \
.window_height = 0, \
.display_id = 0, \
@@ -98,8 +74,6 @@ struct scrcpy_options {
.window_borderless = false, \
.mipmaps = true, \
.stay_awake = false, \
.force_adb_forward = false, \
.disable_screensaver = false, \
}
bool

View File

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

View File

@@ -9,6 +9,8 @@
#include "common.h"
#include "opengl.h"
#define WINDOW_POSITION_UNDEFINED (-0x8000)
struct video_buffer;
struct screen {
@@ -74,7 +76,7 @@ void
screen_init(struct screen *screen);
// initialize screen, create window, renderer and texture (window is hidden)
// window_x and window_y accept SC_WINDOW_POSITION_UNDEFINED
// window_x and window_y accept WINDOW_POSITION_UNDEFINED
bool
screen_init_rendering(struct screen *screen, const char *window_title,
struct size frame_size, bool always_on_top,
@@ -124,14 +126,7 @@ screen_handle_window_event(struct screen *screen, const SDL_WindowEvent *event);
// convert point from window coordinates to frame coordinates
// x and y are expressed in pixels
struct point
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);
screen_convert_to_frame_coords(struct screen *screen, int32_t x, int32_t y);
// Convert coordinates from window to drawable.
// 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
enable_tunnel_reverse_any_port(struct server *server,
struct sc_port_range port_range) {
struct port_range port_range) {
uint16_t port = port_range.first;
for (;;) {
if (!enable_tunnel_reverse(server->serial, port)) {
@@ -189,7 +189,7 @@ enable_tunnel_reverse_any_port(struct server *server,
static bool
enable_tunnel_forward_any_port(struct server *server,
struct sc_port_range port_range) {
struct port_range port_range) {
server->tunnel_forward = true;
uint16_t port = port_range.first;
for (;;) {
@@ -217,46 +217,24 @@ enable_tunnel_forward_any_port(struct server *server,
}
static bool
enable_tunnel_any_port(struct server *server, struct sc_port_range port_range,
bool force_adb_forward) {
if (!force_adb_forward) {
// Attempt to use "adb reverse"
if (enable_tunnel_reverse_any_port(server, port_range)) {
return true;
}
// if "adb reverse" does not work (e.g. over "adb connect"), it
// fallbacks to "adb forward", so the app socket is the client
LOGW("'adb reverse' failed, fallback to 'adb forward'");
enable_tunnel_any_port(struct server *server, struct port_range port_range) {
if (enable_tunnel_reverse_any_port(server, port_range)) {
return true;
}
// if "adb reverse" does not work (e.g. over "adb connect"), it fallbacks to
// "adb forward", so the app socket is the client
LOGW("'adb reverse' failed, fallback to 'adb forward'");
return enable_tunnel_forward_any_port(server, port_range);
}
static const char *
log_level_to_server_string(enum sc_log_level level) {
switch (level) {
case SC_LOG_LEVEL_DEBUG:
return "debug";
case SC_LOG_LEVEL_INFO:
return "info";
case SC_LOG_LEVEL_WARN:
return "warn";
case SC_LOG_LEVEL_ERROR:
return "error";
default:
assert(!"unexpected log level");
return "(unknown)";
}
}
static process_t
execute_server(struct server *server, const struct server_params *params) {
char max_size_string[6];
char bit_rate_string[11];
char max_fps_string[6];
char lock_video_orientation_string[5];
char lock_video_orientation_string[3];
char display_id_string[6];
sprintf(max_size_string, "%"PRIu16, params->max_size);
sprintf(bit_rate_string, "%"PRIu32, params->bit_rate);
@@ -281,7 +259,6 @@ execute_server(struct server *server, const struct server_params *params) {
"/", // unused
"com.genymobile.scrcpy.Server",
SCRCPY_VERSION,
log_level_to_server_string(params->log_level),
max_size_string,
bit_rate_string,
max_fps_string,
@@ -293,7 +270,6 @@ execute_server(struct server *server, const struct server_params *params) {
display_id_string,
params->show_touches ? "true" : "false",
params->stay_awake ? "true" : "false",
params->codec_options ? params->codec_options : "-",
};
#ifdef SERVER_DEBUGGER
LOGI("Server debugger waiting for a client on device port "
@@ -389,8 +365,7 @@ server_start(struct server *server, const char *serial,
goto error1;
}
if (!enable_tunnel_any_port(server, params->port_range,
params->force_adb_forward)) {
if (!enable_tunnel_any_port(server, params->port_range)) {
goto error1;
}

View File

@@ -9,8 +9,6 @@
#include "config.h"
#include "command.h"
#include "common.h"
#include "scrcpy.h"
#include "util/log.h"
#include "util/net.h"
struct server {
@@ -21,7 +19,7 @@ struct server {
socket_t server_socket; // only used if !tunnel_forward
socket_t video_socket;
socket_t control_socket;
struct sc_port_range port_range;
struct port_range port_range;
uint16_t local_port; // selected from port_range
bool tunnel_enabled;
bool tunnel_forward; // use "adb forward" instead of "adb reverse"
@@ -45,10 +43,8 @@ struct server {
}
struct server_params {
enum sc_log_level log_level;
const char *crop;
const char *codec_options;
struct sc_port_range port_range;
struct port_range port_range;
uint16_t max_size;
uint32_t bit_rate;
uint16_t max_fps;
@@ -57,7 +53,6 @@ struct server_params {
uint16_t display_id;
bool show_touches;
bool stay_awake;
bool force_adb_forward;
};
// init default values

View File

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

View File

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

View File

@@ -4,17 +4,16 @@
#include "device_msg.h"
#include <stdio.h>
static void test_deserialize_clipboard(void) {
const unsigned char input[] = {
DEVICE_MSG_TYPE_CLIPBOARD,
0x00, 0x00, 0x00, 0x03, // text length
0x00, 0x03, // text length
0x41, 0x42, 0x43, // "ABC"
};
struct device_msg msg;
ssize_t r = device_msg_deserialize(input, sizeof(input), &msg);
assert(r == 8);
assert(r == 6);
assert(msg.type == DEVICE_MSG_TYPE_CLIPBOARD);
assert(msg.clipboard.text);
@@ -23,30 +22,7 @@ static void test_deserialize_clipboard(void) {
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) {
test_deserialize_clipboard();
test_deserialize_clipboard_big();
return 0;
}

View File

@@ -1,5 +1,5 @@
project('scrcpy', 'c',
version: '1.14',
version: '1.13',
meson_version: '>= 0.48',
default_options: [
'c_std=c11',

View File

@@ -35,6 +35,6 @@ prepare-sdl2:
SDL2-2.0.12
prepare-adb:
@./prepare-dep https://dl.google.com/android/repository/platform-tools_r30.0.0-windows.zip \
854305f9a702f5ea2c3de73edde402bd26afa0ee944c9b0c4380420f5a862e0d \
@./prepare-dep https://dl.google.com/android/repository/platform-tools_r29.0.5-windows.zip \
2df06160056ec9a84c7334af2a1e42740befbb1a2e34370e7af544a2cc78152c \
platform-tools

View File

@@ -6,8 +6,8 @@ android {
applicationId "com.genymobile.scrcpy"
minSdkVersion 21
targetSdkVersion 29
versionCode 16
versionName "1.14"
versionCode 15
versionName "1.13"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {

View File

@@ -12,7 +12,7 @@
set -e
SCRCPY_DEBUG=false
SCRCPY_VERSION_NAME=1.14
SCRCPY_VERSION_NAME=1.13
PLATFORM=${ANDROID_PLATFORM:-29}
BUILD_TOOLS=${ANDROID_BUILD_TOOLS:-29.0.2}

View File

@@ -19,19 +19,18 @@ public final class CleanUp {
// not instantiable
}
public static void configure(boolean disableShowTouches, int restoreStayOn, boolean restoreNormalPowerMode) throws IOException {
boolean needProcess = disableShowTouches || restoreStayOn != -1 || restoreNormalPowerMode;
public static void configure(boolean disableShowTouches, int restoreStayOn) throws IOException {
boolean needProcess = disableShowTouches || restoreStayOn != -1;
if (needProcess) {
startProcess(disableShowTouches, restoreStayOn, restoreNormalPowerMode);
startProcess(disableShowTouches, restoreStayOn);
} else {
// There is no additional clean up to do when scrcpy dies
unlinkSelf();
}
}
private static void startProcess(boolean disableShowTouches, int restoreStayOn, boolean restoreNormalPowerMode) throws IOException {
String[] cmd = {"app_process", "/", CleanUp.class.getName(), String.valueOf(disableShowTouches), String.valueOf(
restoreStayOn), String.valueOf(restoreNormalPowerMode)};
private static void startProcess(boolean disableShowTouches, int restoreStayOn) throws IOException {
String[] cmd = {"app_process", "/", CleanUp.class.getName(), String.valueOf(disableShowTouches), String.valueOf(restoreStayOn)};
ProcessBuilder builder = new ProcessBuilder(cmd);
builder.environment().put("CLASSPATH", SERVER_PATH);
@@ -60,7 +59,6 @@ public final class CleanUp {
boolean disableShowTouches = Boolean.parseBoolean(args[0]);
int restoreStayOn = Integer.parseInt(args[1]);
boolean restoreNormalPowerMode = Boolean.parseBoolean(args[2]);
if (disableShowTouches || restoreStayOn != -1) {
ServiceManager serviceManager = new ServiceManager();
@@ -75,10 +73,5 @@ public final class CleanUp {
}
}
}
if (restoreNormalPowerMode) {
Ln.i("Restoring normal power mode");
Device.setScreenPowerMode(Device.POWER_MODE_NORMAL);
}
}
}

View File

@@ -1,112 +0,0 @@
package com.genymobile.scrcpy;
import java.util.ArrayList;
import java.util.List;
public class CodecOption {
private String key;
private Object value;
public CodecOption(String key, Object value) {
this.key = key;
this.value = value;
}
public String getKey() {
return key;
}
public Object getValue() {
return value;
}
public static List<CodecOption> parse(String codecOptions) {
if ("-".equals(codecOptions)) {
return null;
}
List<CodecOption> result = new ArrayList<>();
boolean escape = false;
StringBuilder buf = new StringBuilder();
for (char c : codecOptions.toCharArray()) {
switch (c) {
case '\\':
if (escape) {
buf.append('\\');
escape = false;
} else {
escape = true;
}
break;
case ',':
if (escape) {
buf.append(',');
escape = false;
} else {
// This comma is a separator between codec options
String codecOption = buf.toString();
result.add(parseOption(codecOption));
// Clear buf
buf.setLength(0);
}
break;
default:
buf.append(c);
break;
}
}
if (buf.length() > 0) {
String codecOption = buf.toString();
result.add(parseOption(codecOption));
}
return result;
}
private static CodecOption parseOption(String option) {
int equalSignIndex = option.indexOf('=');
if (equalSignIndex == -1) {
throw new IllegalArgumentException("'=' expected");
}
String keyAndType = option.substring(0, equalSignIndex);
if (keyAndType.length() == 0) {
throw new IllegalArgumentException("Key may not be null");
}
String key;
String type;
int colonIndex = keyAndType.indexOf(':');
if (colonIndex != -1) {
key = keyAndType.substring(0, colonIndex);
type = keyAndType.substring(colonIndex + 1);
} else {
key = keyAndType;
type = "int"; // assume int by default
}
Object value;
String valueString = option.substring(equalSignIndex + 1);
switch (type) {
case "int":
value = Integer.parseInt(valueString);
break;
case "long":
value = Long.parseLong(valueString);
break;
case "float":
value = Float.parseFloat(valueString);
break;
case "string":
value = valueString;
break;
default:
throw new IllegalArgumentException("Invalid codec option type (int, long, float, str): " + type);
}
return new CodecOption(key, value);
}
}

View File

@@ -17,8 +17,6 @@ public final class ControlMessage {
public static final int TYPE_SET_SCREEN_POWER_MODE = 9;
public static final int TYPE_ROTATE_DEVICE = 10;
public static final int FLAGS_PASTE = 1;
private int type;
private String text;
private int metaState; // KeyEvent.META_*
@@ -30,18 +28,15 @@ public final class ControlMessage {
private Position position;
private int hScroll;
private int vScroll;
private int flags;
private int repeat;
private ControlMessage() {
}
public static ControlMessage createInjectKeycode(int action, int keycode, int repeat, int metaState) {
public static ControlMessage createInjectKeycode(int action, int keycode, int metaState) {
ControlMessage msg = new ControlMessage();
msg.type = TYPE_INJECT_KEYCODE;
msg.action = action;
msg.keycode = keycode;
msg.repeat = repeat;
msg.metaState = metaState;
return msg;
}
@@ -73,13 +68,10 @@ public final class ControlMessage {
return msg;
}
public static ControlMessage createSetClipboard(String text, boolean paste) {
public static ControlMessage createSetClipboard(String text) {
ControlMessage msg = new ControlMessage();
msg.type = TYPE_SET_CLIPBOARD;
msg.text = text;
if (paste) {
msg.flags = FLAGS_PASTE;
}
return msg;
}
@@ -142,12 +134,4 @@ public final class ControlMessage {
public int getVScroll() {
return vScroll;
}
public int getFlags() {
return flags;
}
public int getRepeat() {
return repeat;
}
}

View File

@@ -8,19 +8,18 @@ import java.nio.charset.StandardCharsets;
public class ControlMessageReader {
static final int INJECT_KEYCODE_PAYLOAD_LENGTH = 13;
static final int INJECT_KEYCODE_PAYLOAD_LENGTH = 9;
static final int INJECT_TOUCH_EVENT_PAYLOAD_LENGTH = 27;
static final int INJECT_SCROLL_EVENT_PAYLOAD_LENGTH = 20;
static final int SET_SCREEN_POWER_MODE_PAYLOAD_LENGTH = 1;
static final int SET_CLIPBOARD_FIXED_PAYLOAD_LENGTH = 1;
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 CLIPBOARD_TEXT_MAX_LENGTH = 4093;
public static final int INJECT_TEXT_MAX_LENGTH = 300;
private static final int RAW_BUFFER_SIZE = 1024;
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 byte[] textBuffer = new byte[CLIPBOARD_TEXT_MAX_LENGTH];
public ControlMessageReader() {
// invariant: the buffer is always in "get" mode
@@ -98,23 +97,20 @@ public class ControlMessageReader {
}
int action = toUnsigned(buffer.get());
int keycode = buffer.getInt();
int repeat = buffer.getInt();
int metaState = buffer.getInt();
return ControlMessage.createInjectKeycode(action, keycode, repeat, metaState);
return ControlMessage.createInjectKeycode(action, keycode, metaState);
}
private String parseString() {
if (buffer.remaining() < 4) {
if (buffer.remaining() < 2) {
return null;
}
int len = buffer.getInt();
int len = toUnsigned(buffer.getShort());
if (buffer.remaining() < len) {
return null;
}
int position = buffer.position();
// Move the buffer position to consume the text
buffer.position(position + len);
return new String(rawBuffer, position, len, StandardCharsets.UTF_8);
buffer.get(textBuffer, 0, len);
return new String(textBuffer, 0, len, StandardCharsets.UTF_8);
}
private ControlMessage parseInjectText() {
@@ -151,15 +147,11 @@ public class ControlMessageReader {
}
private ControlMessage parseSetClipboard() {
if (buffer.remaining() < SET_CLIPBOARD_FIXED_PAYLOAD_LENGTH) {
return null;
}
boolean parse = buffer.get() != 0;
String text = parseString();
if (text == null) {
return null;
}
return ControlMessage.createSetClipboard(text, parse);
return ControlMessage.createSetClipboard(text);
}
private ControlMessage parseSetScreenPowerMode() {

View File

@@ -1,8 +1,10 @@
package com.genymobile.scrcpy;
import android.os.Build;
import com.genymobile.scrcpy.wrappers.InputManager;
import android.os.SystemClock;
import android.view.InputDevice;
import android.view.InputEvent;
import android.view.KeyCharacterMap;
import android.view.KeyEvent;
import android.view.MotionEvent;
@@ -48,7 +50,7 @@ public class Controller {
public void control() throws IOException {
// on start, power on the device
if (!device.isScreenOn()) {
device.injectKeycode(KeyEvent.KEYCODE_POWER);
injectKeycode(KeyEvent.KEYCODE_POWER);
// dirty hack
// After POWER is injected, the device is powered on asynchronously.
@@ -74,7 +76,7 @@ public class Controller {
switch (msg.getType()) {
case ControlMessage.TYPE_INJECT_KEYCODE:
if (device.supportsInputEvents()) {
injectKeycode(msg.getAction(), msg.getKeycode(), msg.getRepeat(), msg.getMetaState());
injectKeycode(msg.getAction(), msg.getKeycode(), msg.getMetaState());
}
break;
case ControlMessage.TYPE_INJECT_TEXT:
@@ -105,21 +107,14 @@ public class Controller {
break;
case ControlMessage.TYPE_GET_CLIPBOARD:
String clipboardText = device.getClipboardText();
if (clipboardText != null) {
sender.pushClipboardText(clipboardText);
}
sender.pushClipboardText(clipboardText);
break;
case ControlMessage.TYPE_SET_CLIPBOARD:
boolean paste = (msg.getFlags() & ControlMessage.FLAGS_PASTE) != 0;
setClipboard(msg.getText(), paste);
device.setClipboardText(msg.getText());
break;
case ControlMessage.TYPE_SET_SCREEN_POWER_MODE:
if (device.supportsInputEvents()) {
int mode = msg.getAction();
boolean setPowerModeOk = Device.setScreenPowerMode(mode);
if (setPowerModeOk) {
Ln.i("Device screen turned " + (mode == Device.POWER_MODE_OFF ? "off" : "on"));
}
device.setScreenPowerMode(msg.getAction());
}
break;
case ControlMessage.TYPE_ROTATE_DEVICE:
@@ -130,8 +125,8 @@ public class Controller {
}
}
private boolean injectKeycode(int action, int keycode, int repeat, int metaState) {
return device.injectKeyEvent(action, keycode, repeat, metaState);
private boolean injectKeycode(int action, int keycode, int metaState) {
return injectKeyEvent(action, keycode, 0, metaState);
}
private boolean injectChar(char c) {
@@ -142,7 +137,7 @@ public class Controller {
return false;
}
for (KeyEvent event : events) {
if (!device.injectEvent(event)) {
if (!injectEvent(event)) {
return false;
}
}
@@ -166,7 +161,7 @@ public class Controller {
Point point = device.getPhysicalPoint(position);
if (point == null) {
Ln.w("Ignore touch event, it was generated for a different device size");
// ignore event
return false;
}
@@ -198,7 +193,7 @@ public class Controller {
MotionEvent event = MotionEvent
.obtain(lastTouchDown, now, action, pointerCount, pointerProperties, pointerCoords, 0, buttons, 1f, 1f, DEVICE_ID_VIRTUAL, 0,
InputDevice.SOURCE_TOUCHSCREEN, 0);
return device.injectEvent(event);
return injectEvent(event);
}
private boolean injectScroll(Position position, int hScroll, int vScroll) {
@@ -221,25 +216,26 @@ public class Controller {
MotionEvent event = MotionEvent
.obtain(lastTouchDown, now, MotionEvent.ACTION_SCROLL, 1, pointerProperties, pointerCoords, 0, 0, 1f, 1f, DEVICE_ID_VIRTUAL, 0,
InputDevice.SOURCE_TOUCHSCREEN, 0);
return device.injectEvent(event);
return injectEvent(event);
}
private boolean injectKeyEvent(int action, int keyCode, int repeat, int metaState) {
long now = SystemClock.uptimeMillis();
KeyEvent event = new KeyEvent(now, now, action, keyCode, repeat, metaState, KeyCharacterMap.VIRTUAL_KEYBOARD, 0, 0,
InputDevice.SOURCE_KEYBOARD);
return injectEvent(event);
}
private boolean injectKeycode(int keyCode) {
return injectKeyEvent(KeyEvent.ACTION_DOWN, keyCode, 0, 0) && injectKeyEvent(KeyEvent.ACTION_UP, keyCode, 0, 0);
}
private boolean injectEvent(InputEvent event) {
return device.injectInputEvent(event, InputManager.INJECT_INPUT_EVENT_MODE_ASYNC);
}
private boolean pressBackOrTurnScreenOn() {
int keycode = device.isScreenOn() ? KeyEvent.KEYCODE_BACK : KeyEvent.KEYCODE_POWER;
return device.injectKeycode(keycode);
}
private boolean setClipboard(String text, boolean paste) {
boolean ok = device.setClipboardText(text);
if (ok) {
Ln.i("Device clipboard set");
}
// On Android >= 7, also press the PASTE key if requested
if (paste && Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && device.supportsInputEvents()) {
device.injectKeycode(KeyEvent.KEYCODE_PASTE);
}
return ok;
return injectKeycode(keycode);
}
}

View File

@@ -1,6 +1,5 @@
package com.genymobile.scrcpy;
import com.genymobile.scrcpy.wrappers.ClipboardManager;
import com.genymobile.scrcpy.wrappers.ContentProvider;
import com.genymobile.scrcpy.wrappers.InputManager;
import com.genymobile.scrcpy.wrappers.ServiceManager;
@@ -11,14 +10,8 @@ import android.content.IOnPrimaryClipChangedListener;
import android.graphics.Rect;
import android.os.Build;
import android.os.IBinder;
import android.os.SystemClock;
import android.view.IRotationWatcher;
import android.view.InputDevice;
import android.view.InputEvent;
import android.view.KeyCharacterMap;
import android.view.KeyEvent;
import java.util.concurrent.atomic.AtomicBoolean;
public final class Device {
@@ -38,7 +31,6 @@ public final class Device {
private ScreenInfo screenInfo;
private RotationListener rotationListener;
private ClipboardListener clipboardListener;
private final AtomicBoolean isSettingClipboard = new AtomicBoolean();
/**
* Logical display identifier
@@ -81,28 +73,19 @@ public final class Device {
if (options.getControl()) {
// If control is enabled, synchronize Android clipboard to the computer automatically
ClipboardManager clipboardManager = serviceManager.getClipboardManager();
if (clipboardManager != null) {
clipboardManager.addPrimaryClipChangedListener(new IOnPrimaryClipChangedListener.Stub() {
@Override
public void dispatchPrimaryClipChanged() {
if (isSettingClipboard.get()) {
// This is a notification for the change we are currently applying, ignore it
return;
}
synchronized (Device.this) {
if (clipboardListener != null) {
String text = getClipboardText();
if (text != null) {
clipboardListener.onClipboardTextChanged(text);
}
serviceManager.getClipboardManager().addPrimaryClipChangedListener(new IOnPrimaryClipChangedListener.Stub() {
@Override
public void dispatchPrimaryClipChanged() {
synchronized (Device.this) {
if (clipboardListener != null) {
String text = getClipboardText();
if (text != null) {
clipboardListener.onClipboardTextChanged(text);
}
}
}
});
} else {
Ln.w("No clipboard manager, copy-paste between device and computer will not work");
}
}
});
}
if ((displayInfoFlags & DisplayInfo.FLAG_SUPPORTS_PROTECTED_BUFFERS) == 0) {
@@ -157,7 +140,7 @@ public final class Device {
return supportsInputEvents;
}
public boolean injectEvent(InputEvent inputEvent, int mode) {
public boolean injectInputEvent(InputEvent inputEvent, int mode) {
if (!supportsInputEvents()) {
throw new AssertionError("Could not inject input event if !supportsInputEvents()");
}
@@ -169,21 +152,6 @@ public final class Device {
return serviceManager.getInputManager().injectInputEvent(inputEvent, mode);
}
public boolean injectEvent(InputEvent event) {
return injectEvent(event, InputManager.INJECT_INPUT_EVENT_MODE_ASYNC);
}
public boolean injectKeyEvent(int action, int keyCode, int repeat, int metaState) {
long now = SystemClock.uptimeMillis();
KeyEvent event = new KeyEvent(now, now, action, keyCode, repeat, metaState, KeyCharacterMap.VIRTUAL_KEYBOARD, 0, 0,
InputDevice.SOURCE_KEYBOARD);
return injectEvent(event);
}
public boolean injectKeycode(int keyCode) {
return injectKeyEvent(KeyEvent.ACTION_DOWN, keyCode, 0, 0) && injectKeyEvent(KeyEvent.ACTION_UP, keyCode, 0, 0);
}
public boolean isScreenOn() {
return serviceManager.getPowerManager().isScreenOn();
}
@@ -205,38 +173,33 @@ public final class Device {
}
public String getClipboardText() {
ClipboardManager clipboardManager = serviceManager.getClipboardManager();
if (clipboardManager == null) {
return null;
}
CharSequence s = clipboardManager.getText();
CharSequence s = serviceManager.getClipboardManager().getText();
if (s == null) {
return null;
}
return s.toString();
}
public boolean setClipboardText(String text) {
ClipboardManager clipboardManager = serviceManager.getClipboardManager();
if (clipboardManager == null) {
return false;
public void setClipboardText(String text) {
boolean ok = serviceManager.getClipboardManager().setText(text);
if (ok) {
Ln.i("Device clipboard set");
}
isSettingClipboard.set(true);
boolean ok = clipboardManager.setText(text);
isSettingClipboard.set(false);
return ok;
}
/**
* @param mode one of the {@code POWER_MODE_*} constants
* @param mode one of the {@code SCREEN_POWER_MODE_*} constants
*/
public static boolean setScreenPowerMode(int mode) {
public void setScreenPowerMode(int mode) {
IBinder d = SurfaceControl.getBuiltInDisplay();
if (d == null) {
Ln.e("Could not get built-in display");
return false;
return;
}
boolean ok = SurfaceControl.setDisplayPowerMode(d, mode);
if (ok) {
Ln.i("Device screen turned " + (mode == Device.POWER_MODE_OFF ? "off" : "on"));
}
return SurfaceControl.setDisplayPowerMode(d, mode);
}
/**

View File

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

View File

@@ -15,25 +15,14 @@ public final class Ln {
DEBUG, INFO, WARN, ERROR
}
private static Level threshold = Level.INFO;
private static final Level THRESHOLD = BuildConfig.DEBUG ? Level.DEBUG : Level.INFO;
private Ln() {
// not instantiable
}
/**
* Initialize the log level.
* <p>
* Must be called before starting any new thread.
*
* @param level the log level
*/
public static void initLogLevel(Level level) {
threshold = level;
}
public static boolean isEnabled(Level level) {
return level.ordinal() >= threshold.ordinal();
return level.ordinal() >= THRESHOLD.ordinal();
}
public static void d(String message) {

View File

@@ -3,7 +3,6 @@ package com.genymobile.scrcpy;
import android.graphics.Rect;
public class Options {
private Ln.Level logLevel;
private int maxSize;
private int bitRate;
private int maxFps;
@@ -15,15 +14,6 @@ public class Options {
private int displayId;
private boolean showTouches;
private boolean stayAwake;
private String codecOptions;
public Ln.Level getLogLevel() {
return logLevel;
}
public void setLogLevel(Ln.Level logLevel) {
this.logLevel = logLevel;
}
public int getMaxSize() {
return maxSize;
@@ -112,12 +102,4 @@ public class Options {
public void setStayAwake(boolean stayAwake) {
this.stayAwake = stayAwake;
}
public String getCodecOptions() {
return codecOptions;
}
public void setCodecOptions(String codecOptions) {
this.codecOptions = codecOptions;
}
}

View File

@@ -12,7 +12,6 @@ import android.view.Surface;
import java.io.FileDescriptor;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
public class ScreenEncoder implements Device.RotationListener {
@@ -26,17 +25,15 @@ public class ScreenEncoder implements Device.RotationListener {
private final AtomicBoolean rotationChanged = new AtomicBoolean();
private final ByteBuffer headerBuffer = ByteBuffer.allocate(12);
private List<CodecOption> codecOptions;
private int bitRate;
private int maxFps;
private boolean sendFrameMeta;
private long ptsOrigin;
public ScreenEncoder(boolean sendFrameMeta, int bitRate, int maxFps, List<CodecOption> codecOptions) {
public ScreenEncoder(boolean sendFrameMeta, int bitRate, int maxFps) {
this.sendFrameMeta = sendFrameMeta;
this.bitRate = bitRate;
this.maxFps = maxFps;
this.codecOptions = codecOptions;
}
@Override
@@ -64,7 +61,7 @@ public class ScreenEncoder implements Device.RotationListener {
}
private void internalStreamScreen(Device device, FileDescriptor fd) throws IOException {
MediaFormat format = createFormat(bitRate, maxFps, codecOptions);
MediaFormat format = createFormat(bitRate, maxFps);
device.setRotationListener(this);
boolean alive;
try {
@@ -154,24 +151,7 @@ public class ScreenEncoder implements Device.RotationListener {
return MediaCodec.createEncoderByType(MediaFormat.MIMETYPE_VIDEO_AVC);
}
private static void setCodecOption(MediaFormat format, CodecOption codecOption) {
String key = codecOption.getKey();
Object value = codecOption.getValue();
if (value instanceof Integer) {
format.setInteger(key, (Integer) value);
} else if (value instanceof Long) {
format.setLong(key, (Long) value);
} else if (value instanceof Float) {
format.setFloat(key, (Float) value);
} else if (value instanceof String) {
format.setString(key, (String) value);
}
Ln.d("Codec option set: " + key + " (" + value.getClass().getSimpleName() + ") = " + value);
}
private static MediaFormat createFormat(int bitRate, int maxFps, List<CodecOption> codecOptions) {
private static MediaFormat createFormat(int bitRate, int maxFps) {
MediaFormat format = new MediaFormat();
format.setString(MediaFormat.KEY_MIME, MediaFormat.MIMETYPE_VIDEO_AVC);
format.setInteger(MediaFormat.KEY_BIT_RATE, bitRate);
@@ -187,13 +167,6 @@ public class ScreenEncoder implements Device.RotationListener {
// <https://github.com/Genymobile/scrcpy/issues/488#issuecomment-567321437>
format.setFloat(KEY_MAX_FPS_TO_ENCODER, maxFps);
}
if (codecOptions != null) {
for (CodecOption option : codecOptions) {
setCodecOption(format, option);
}
}
return format;
}

View File

@@ -8,8 +8,6 @@ import android.os.BatteryManager;
import android.os.Build;
import java.io.IOException;
import java.util.List;
import java.util.Locale;
public final class Server {
@@ -21,7 +19,6 @@ public final class Server {
private static void scrcpy(Options options) throws IOException {
Ln.i("Device: " + Build.MANUFACTURER + " " + Build.MODEL + " (Android " + Build.VERSION.RELEASE + ")");
final Device device = new Device(options);
List<CodecOption> codecOptions = CodecOption.parse(options.getCodecOptions());
boolean mustDisableShowTouchesOnCleanUp = false;
int restoreStayOn = -1;
@@ -49,12 +46,11 @@ public final class Server {
}
}
CleanUp.configure(mustDisableShowTouchesOnCleanUp, restoreStayOn, true);
CleanUp.configure(mustDisableShowTouchesOnCleanUp, restoreStayOn);
boolean tunnelForward = options.isTunnelForward();
try (DesktopConnection connection = DesktopConnection.open(device, tunnelForward)) {
ScreenEncoder screenEncoder = new ScreenEncoder(options.getSendFrameMeta(), options.getBitRate(), options.getMaxFps(), codecOptions);
ScreenEncoder screenEncoder = new ScreenEncoder(options.getSendFrameMeta(), options.getBitRate(), options.getMaxFps());
if (options.getControl()) {
final Controller controller = new Controller(device, connection);
@@ -120,53 +116,47 @@ public final class Server {
"The server version (" + BuildConfig.VERSION_NAME + ") does not match the client " + "(" + clientVersion + ")");
}
final int expectedParameters = 14;
final int expectedParameters = 12;
if (args.length != expectedParameters) {
throw new IllegalArgumentException("Expecting " + expectedParameters + " parameters");
}
Options options = new Options();
Ln.Level level = Ln.Level.valueOf(args[1].toUpperCase(Locale.ENGLISH));
options.setLogLevel(level);
int maxSize = Integer.parseInt(args[2]) & ~7; // multiple of 8
int maxSize = Integer.parseInt(args[1]) & ~7; // multiple of 8
options.setMaxSize(maxSize);
int bitRate = Integer.parseInt(args[3]);
int bitRate = Integer.parseInt(args[2]);
options.setBitRate(bitRate);
int maxFps = Integer.parseInt(args[4]);
int maxFps = Integer.parseInt(args[3]);
options.setMaxFps(maxFps);
int lockedVideoOrientation = Integer.parseInt(args[5]);
int lockedVideoOrientation = Integer.parseInt(args[4]);
options.setLockedVideoOrientation(lockedVideoOrientation);
// use "adb forward" instead of "adb tunnel"? (so the server must listen)
boolean tunnelForward = Boolean.parseBoolean(args[6]);
boolean tunnelForward = Boolean.parseBoolean(args[5]);
options.setTunnelForward(tunnelForward);
Rect crop = parseCrop(args[7]);
Rect crop = parseCrop(args[6]);
options.setCrop(crop);
boolean sendFrameMeta = Boolean.parseBoolean(args[8]);
boolean sendFrameMeta = Boolean.parseBoolean(args[7]);
options.setSendFrameMeta(sendFrameMeta);
boolean control = Boolean.parseBoolean(args[9]);
boolean control = Boolean.parseBoolean(args[8]);
options.setControl(control);
int displayId = Integer.parseInt(args[10]);
int displayId = Integer.parseInt(args[9]);
options.setDisplayId(displayId);
boolean showTouches = Boolean.parseBoolean(args[11]);
boolean showTouches = Boolean.parseBoolean(args[10]);
options.setShowTouches(showTouches);
boolean stayAwake = Boolean.parseBoolean(args[12]);
boolean stayAwake = Boolean.parseBoolean(args[11]);
options.setStayAwake(stayAwake);
String codecOptions = args[13];
options.setCodecOptions(codecOptions);
return options;
}
@@ -219,9 +209,6 @@ public final class Server {
});
Options options = createOptions(args);
Ln.initLogLevel(options.getLogLevel());
scrcpy(options);
}
}

View File

@@ -77,14 +77,7 @@ public final class ServiceManager {
public ClipboardManager getClipboardManager() {
if (clipboardManager == null) {
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);
clipboardManager = new ClipboardManager(getService("clipboard", "android.content.IClipboard"));
}
return clipboardManager;
}

View File

@@ -1,114 +0,0 @@
package com.genymobile.scrcpy;
import org.junit.Assert;
import org.junit.Test;
import java.util.List;
public class CodecOptionsTest {
@Test
public void testIntegerImplicit() {
List<CodecOption> codecOptions = CodecOption.parse("some_key=5");
Assert.assertEquals(1, codecOptions.size());
CodecOption option = codecOptions.get(0);
Assert.assertEquals("some_key", option.getKey());
Assert.assertEquals(5, option.getValue());
}
@Test
public void testInteger() {
List<CodecOption> codecOptions = CodecOption.parse("some_key:int=5");
Assert.assertEquals(1, codecOptions.size());
CodecOption option = codecOptions.get(0);
Assert.assertEquals("some_key", option.getKey());
Assert.assertTrue(option.getValue() instanceof Integer);
Assert.assertEquals(5, option.getValue());
}
@Test
public void testLong() {
List<CodecOption> codecOptions = CodecOption.parse("some_key:long=5");
Assert.assertEquals(1, codecOptions.size());
CodecOption option = codecOptions.get(0);
Assert.assertEquals("some_key", option.getKey());
Assert.assertTrue(option.getValue() instanceof Long);
Assert.assertEquals(5L, option.getValue());
}
@Test
public void testFloat() {
List<CodecOption> codecOptions = CodecOption.parse("some_key:float=4.5");
Assert.assertEquals(1, codecOptions.size());
CodecOption option = codecOptions.get(0);
Assert.assertEquals("some_key", option.getKey());
Assert.assertTrue(option.getValue() instanceof Float);
Assert.assertEquals(4.5f, option.getValue());
}
@Test
public void testString() {
List<CodecOption> codecOptions = CodecOption.parse("some_key:string=some_value");
Assert.assertEquals(1, codecOptions.size());
CodecOption option = codecOptions.get(0);
Assert.assertEquals("some_key", option.getKey());
Assert.assertTrue(option.getValue() instanceof String);
Assert.assertEquals("some_value", option.getValue());
}
@Test
public void testStringEscaped() {
List<CodecOption> codecOptions = CodecOption.parse("some_key:string=warning\\,this_is_not=a_new_key");
Assert.assertEquals(1, codecOptions.size());
CodecOption option = codecOptions.get(0);
Assert.assertEquals("some_key", option.getKey());
Assert.assertTrue(option.getValue() instanceof String);
Assert.assertEquals("warning,this_is_not=a_new_key", option.getValue());
}
@Test
public void testList() {
List<CodecOption> codecOptions = CodecOption.parse("a=1,b:int=2,c:long=3,d:float=4.5,e:string=a\\,b=c");
Assert.assertEquals(5, codecOptions.size());
CodecOption option;
option = codecOptions.get(0);
Assert.assertEquals("a", option.getKey());
Assert.assertTrue(option.getValue() instanceof Integer);
Assert.assertEquals(1, option.getValue());
option = codecOptions.get(1);
Assert.assertEquals("b", option.getKey());
Assert.assertTrue(option.getValue() instanceof Integer);
Assert.assertEquals(2, option.getValue());
option = codecOptions.get(2);
Assert.assertEquals("c", option.getKey());
Assert.assertTrue(option.getValue() instanceof Long);
Assert.assertEquals(3L, option.getValue());
option = codecOptions.get(3);
Assert.assertEquals("d", option.getKey());
Assert.assertTrue(option.getValue() instanceof Float);
Assert.assertEquals(4.5f, option.getValue());
option = codecOptions.get(4);
Assert.assertEquals("e", option.getKey());
Assert.assertTrue(option.getValue() instanceof String);
Assert.assertEquals("a,b=c", option.getValue());
}
}

View File

@@ -25,7 +25,6 @@ public class ControlMessageReaderTest {
dos.writeByte(ControlMessage.TYPE_INJECT_KEYCODE);
dos.writeByte(KeyEvent.ACTION_UP);
dos.writeInt(KeyEvent.KEYCODE_ENTER);
dos.writeInt(5); // repeat
dos.writeInt(KeyEvent.META_CTRL_ON);
byte[] packet = bos.toByteArray();
@@ -38,7 +37,6 @@ public class ControlMessageReaderTest {
Assert.assertEquals(ControlMessage.TYPE_INJECT_KEYCODE, event.getType());
Assert.assertEquals(KeyEvent.ACTION_UP, event.getAction());
Assert.assertEquals(KeyEvent.KEYCODE_ENTER, event.getKeycode());
Assert.assertEquals(5, event.getRepeat());
Assert.assertEquals(KeyEvent.META_CTRL_ON, event.getMetaState());
}
@@ -50,7 +48,7 @@ public class ControlMessageReaderTest {
DataOutputStream dos = new DataOutputStream(bos);
dos.writeByte(ControlMessage.TYPE_INJECT_TEXT);
byte[] text = "testé".getBytes(StandardCharsets.UTF_8);
dos.writeInt(text.length);
dos.writeShort(text.length);
dos.write(text);
byte[] packet = bos.toByteArray();
@@ -70,7 +68,7 @@ public class ControlMessageReaderTest {
dos.writeByte(ControlMessage.TYPE_INJECT_TEXT);
byte[] text = new byte[ControlMessageReader.INJECT_TEXT_MAX_LENGTH];
Arrays.fill(text, (byte) 'a');
dos.writeInt(text.length);
dos.writeShort(text.length);
dos.write(text);
byte[] packet = bos.toByteArray();
@@ -218,9 +216,8 @@ public class ControlMessageReaderTest {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
DataOutputStream dos = new DataOutputStream(bos);
dos.writeByte(ControlMessage.TYPE_SET_CLIPBOARD);
dos.writeByte(1); // paste
byte[] text = "testé".getBytes(StandardCharsets.UTF_8);
dos.writeInt(text.length);
dos.writeShort(text.length);
dos.write(text);
byte[] packet = bos.toByteArray();
@@ -230,37 +227,6 @@ public class ControlMessageReaderTest {
Assert.assertEquals(ControlMessage.TYPE_SET_CLIPBOARD, event.getType());
Assert.assertEquals("testé", event.getText());
boolean parse = (event.getFlags() & ControlMessage.FLAGS_PASTE) != 0;
Assert.assertTrue(parse);
}
@Test
public void testParseBigSetClipboardEvent() throws IOException {
ControlMessageReader reader = new ControlMessageReader();
ByteArrayOutputStream bos = new ByteArrayOutputStream();
DataOutputStream dos = new DataOutputStream(bos);
dos.writeByte(ControlMessage.TYPE_SET_CLIPBOARD);
byte[] rawText = new byte[ControlMessageReader.CLIPBOARD_TEXT_MAX_LENGTH];
dos.writeByte(1); // paste
Arrays.fill(rawText, (byte) 'a');
String text = new String(rawText, 0, rawText.length);
dos.writeInt(rawText.length);
dos.write(rawText);
byte[] packet = bos.toByteArray();
reader.readFrom(new ByteArrayInputStream(packet));
ControlMessage event = reader.next();
Assert.assertEquals(ControlMessage.TYPE_SET_CLIPBOARD, event.getType());
Assert.assertEquals(text, event.getText());
boolean parse = (event.getFlags() & ControlMessage.FLAGS_PASTE) != 0;
Assert.assertTrue(parse);
}
@Test
@@ -310,13 +276,11 @@ public class ControlMessageReaderTest {
dos.writeByte(ControlMessage.TYPE_INJECT_KEYCODE);
dos.writeByte(KeyEvent.ACTION_UP);
dos.writeInt(KeyEvent.KEYCODE_ENTER);
dos.writeInt(0); // repeat
dos.writeInt(KeyEvent.META_CTRL_ON);
dos.writeByte(ControlMessage.TYPE_INJECT_KEYCODE);
dos.writeByte(MotionEvent.ACTION_DOWN);
dos.writeInt(MotionEvent.BUTTON_PRIMARY);
dos.writeInt(1); // repeat
dos.writeInt(KeyEvent.META_CTRL_ON);
byte[] packet = bos.toByteArray();
@@ -326,14 +290,12 @@ public class ControlMessageReaderTest {
Assert.assertEquals(ControlMessage.TYPE_INJECT_KEYCODE, event.getType());
Assert.assertEquals(KeyEvent.ACTION_UP, event.getAction());
Assert.assertEquals(KeyEvent.KEYCODE_ENTER, event.getKeycode());
Assert.assertEquals(0, event.getRepeat());
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(1, event.getRepeat());
Assert.assertEquals(KeyEvent.META_CTRL_ON, event.getMetaState());
}
@@ -347,7 +309,6 @@ public class ControlMessageReaderTest {
dos.writeByte(ControlMessage.TYPE_INJECT_KEYCODE);
dos.writeByte(KeyEvent.ACTION_UP);
dos.writeInt(KeyEvent.KEYCODE_ENTER);
dos.writeInt(4); // repeat
dos.writeInt(KeyEvent.META_CTRL_ON);
dos.writeByte(ControlMessage.TYPE_INJECT_KEYCODE);
@@ -360,7 +321,6 @@ public class ControlMessageReaderTest {
Assert.assertEquals(ControlMessage.TYPE_INJECT_KEYCODE, event.getType());
Assert.assertEquals(KeyEvent.ACTION_UP, event.getAction());
Assert.assertEquals(KeyEvent.KEYCODE_ENTER, event.getKeycode());
Assert.assertEquals(4, event.getRepeat());
Assert.assertEquals(KeyEvent.META_CTRL_ON, event.getMetaState());
event = reader.next();
@@ -368,7 +328,6 @@ public class ControlMessageReaderTest {
bos.reset();
dos.writeInt(MotionEvent.BUTTON_PRIMARY);
dos.writeInt(5); // repeat
dos.writeInt(KeyEvent.META_CTRL_ON);
packet = bos.toByteArray();
reader.readFrom(new ByteArrayInputStream(packet));
@@ -378,7 +337,6 @@ public class ControlMessageReaderTest {
Assert.assertEquals(ControlMessage.TYPE_INJECT_KEYCODE, event.getType());
Assert.assertEquals(MotionEvent.ACTION_DOWN, event.getAction());
Assert.assertEquals(MotionEvent.BUTTON_PRIMARY, event.getKeycode());
Assert.assertEquals(5, event.getRepeat());
Assert.assertEquals(KeyEvent.META_CTRL_ON, event.getMetaState());
}
}

View File

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