mirror of
https://github.com/Genymobile/scrcpy.git
synced 2026-02-24 07:14:29 +01:00
Compare commits
11 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9a7e8da12c | ||
|
|
dc7b60e619 | ||
|
|
1e4ee547b5 | ||
|
|
488d22d4e2 | ||
|
|
00d292b2f5 | ||
|
|
245999aec4 | ||
|
|
d91c5dcfd5 | ||
|
|
d202d7b205 | ||
|
|
08c0c64af6 | ||
|
|
a00a8763d6 | ||
|
|
8f314c74b0 |
97
README.md
97
README.md
@@ -354,7 +354,7 @@ scrcpy --fullscreen
|
||||
scrcpy -f # short version
|
||||
```
|
||||
|
||||
Fullscreen can then be toggled dynamically with `RCtrl`+`f`.
|
||||
Fullscreen can then be toggled dynamically with `Ctrl`+`f`.
|
||||
|
||||
#### Rotation
|
||||
|
||||
@@ -370,18 +370,18 @@ Possibles values are:
|
||||
- `2`: 180 degrees
|
||||
- `3`: 90 degrees clockwise
|
||||
|
||||
The rotation can also be changed dynamically with `RCtrl`+`←` _(left)_ and
|
||||
`RCtrl`+`→` _(right)_.
|
||||
The rotation can also be changed dynamically with `Ctrl`+`←` _(left)_ and
|
||||
`Ctrl`+`→` _(right)_.
|
||||
|
||||
Note that _scrcpy_ manages 3 different rotations:
|
||||
- `RCtrl`+`r` requests the device to switch between portrait and landscape (the
|
||||
- `Ctrl`+`r` requests the device to switch between portrait and landscape (the
|
||||
current running app may refuse, if it does support the requested
|
||||
orientation).
|
||||
- `--lock-video-orientation` changes the mirroring orientation (the orientation
|
||||
of the video sent from the device to the computer). This affects the
|
||||
recording.
|
||||
- `--rotation` (or `RCtrl`+`←`/`RCtrl`+`→`) rotates only the window content.
|
||||
This affects only the display, not the recording.
|
||||
- `--rotation` (or `Ctrl`+`←`/`Ctrl`+`→`) rotates only the window content. This
|
||||
affects only the display, not the recording.
|
||||
|
||||
|
||||
### Other mirroring options
|
||||
@@ -437,9 +437,9 @@ scrcpy --turn-screen-off
|
||||
scrcpy -S
|
||||
```
|
||||
|
||||
Or by pressing `RCtrl`+`o` at any time.
|
||||
Or by pressing `Ctrl`+`o` at any time.
|
||||
|
||||
To turn it back on, press `RCtrl`+`Shift`+`o` (or `POWER`, `RCtrl`+`p`).
|
||||
To turn it back on, press `Ctrl`+`Shift`+`o` (or `POWER`, `Ctrl`+`p`).
|
||||
|
||||
It can be useful to also prevent the device to sleep:
|
||||
|
||||
@@ -479,26 +479,39 @@ 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
|
||||
|
||||
Press `RCtrl`+`r` to switch between portrait and landscape modes.
|
||||
Press `Ctrl`+`r` to switch between portrait and landscape modes.
|
||||
|
||||
Note that it rotates only if the application in foreground supports the
|
||||
requested orientation.
|
||||
|
||||
#### Copy-paste
|
||||
|
||||
Any time the Android clipboard changes, it is automatically synchronized to the
|
||||
computer clipboard.
|
||||
It is possible to synchronize clipboards between the computer and the device, in
|
||||
both directions:
|
||||
|
||||
`Ctrl`+`c` (copy), `Ctrl`+`x` (cut) and `LCtrl`+`v` (paste) work as you expect.
|
||||
|
||||
In addition, `RCtrl`+`v` allows to inject the computer clipboard content as a
|
||||
sequence of text event. Even if it can break non-ASCII content, this is
|
||||
sometimes necessary when pasting directly is not possible.
|
||||
- `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`+`v` _pastes_ the computer clipboard as a sequence of text events (but
|
||||
breaks non-ASCII characters).
|
||||
|
||||
Moreover, any time the Android clipboard changes, it is automatically
|
||||
synchronized to the computer clipboard.
|
||||
|
||||
#### Text injection preference
|
||||
|
||||
@@ -558,34 +571,30 @@ Also see [issue #14].
|
||||
|
||||
## Shortcuts
|
||||
|
||||
`RCtrl` is the right `Ctrl` key (the left `Ctrl` key is forwarded to the
|
||||
device).
|
||||
|
||||
On macOS, `Cmd` also works (for shortcuts which are not already captured by the
|
||||
system).
|
||||
|
||||
| Action | Shortcut
|
||||
| ------------------------------------------- |:-----------------------------
|
||||
| Switch fullscreen mode | `RCtrl`+`f`
|
||||
| Rotate display left | `RCtrl`+`←`
|
||||
| Rotate display right | `RCtrl`+`→`
|
||||
| Resize window to 1:1 (pixel-perfect) | `RCtrl`+`g`
|
||||
| Resize window to remove black borders | `RCtrl`+`w` \| _Double-click¹_
|
||||
| Click on `HOME` | `RCtrl`+`h` \| _Middle-click_
|
||||
| Click on `BACK` | `RCtrl`+`b` \| _Right-click²_
|
||||
| Click on `APP_SWITCH` | `RCtrl`+`s`
|
||||
| Click on `MENU` | `RCtrl`+`m`
|
||||
| Click on `VOLUME_UP` | `RCtrl`+`↑` _(up)_
|
||||
| Click on `VOLUME_DOWN` | `RCtrl`+`↓` _(down)_
|
||||
| Click on `POWER` | `RCtrl`+`p`
|
||||
| Power on | _Right-click²_
|
||||
| Turn device screen off (keep mirroring) | `RCtrl`+`o`
|
||||
| Turn device screen on | `RCtrl`+`Shift`+`o`
|
||||
| Rotate device screen | `RCtrl`+`r`
|
||||
| Expand notification panel | `RCtrl`+`n`
|
||||
| Collapse notification panel | `RCtrl`+`Shift`+`n`
|
||||
| Inject computer clipboard text | `RCtrl`+`v`
|
||||
| Enable/disable FPS counter (on stdout) | `RCtrl`+`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`
|
||||
| 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`
|
||||
|
||||
_¹Double-click on black borders to remove them._
|
||||
_²Right-click turns the screen on if it was off, presses BACK otherwise._
|
||||
|
||||
@@ -164,12 +164,12 @@ if get_option('buildtype') == 'debug'
|
||||
'src/cli.c',
|
||||
'src/util/str_util.c',
|
||||
]],
|
||||
['test_control_event_serialize', [
|
||||
['test_control_msg_serialize', [
|
||||
'tests/test_control_msg_serialize.c',
|
||||
'src/control_msg.c',
|
||||
'src/util/str_util.c',
|
||||
]],
|
||||
['test_device_event_deserialize', [
|
||||
['test_device_msg_deserialize', [
|
||||
'tests/test_device_msg_deserialize.c',
|
||||
'src/device_msg.c',
|
||||
]],
|
||||
|
||||
54
app/scrcpy.1
54
app/scrcpy.1
@@ -43,6 +43,10 @@ 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.
|
||||
@@ -203,54 +207,52 @@ Default is 0 (automatic).\n
|
||||
|
||||
.SH SHORTCUTS
|
||||
|
||||
RCtrl is the right Ctrl key (the left Ctrl key is forwarded to the device).
|
||||
|
||||
.TP
|
||||
.B RCtrl+f
|
||||
.B Ctrl+f
|
||||
Switch fullscreen mode
|
||||
|
||||
.TP
|
||||
.B RCtrl+Left
|
||||
.B Ctrl+Left
|
||||
Rotate display left
|
||||
|
||||
.TP
|
||||
.B RCtrl+Right
|
||||
.B Ctrl+Right
|
||||
Rotate display right
|
||||
|
||||
.TP
|
||||
.B RCtrl+g
|
||||
.B Ctrl+g
|
||||
Resize window to 1:1 (pixel\-perfect)
|
||||
|
||||
.TP
|
||||
.B RCtrl+w, Double\-click on black borders
|
||||
.B Ctrl+x, Double\-click on black borders
|
||||
Resize window to remove black borders
|
||||
|
||||
.TP
|
||||
.B RCtrl+h, Home, Middle\-click
|
||||
.B Ctrl+h, Home, Middle\-click
|
||||
Click on HOME
|
||||
|
||||
.TP
|
||||
.B RCtrl+b, RCtrl+Backspace, Right\-click (when screen is on)
|
||||
.B Ctrl+b, Ctrl+Backspace, Right\-click (when screen is on)
|
||||
Click on BACK
|
||||
|
||||
.TP
|
||||
.B RCtrl+s
|
||||
.B Ctrl+s
|
||||
Click on APP_SWITCH
|
||||
|
||||
.TP
|
||||
.B RCtrl+m
|
||||
.B Ctrl+m
|
||||
Click on MENU
|
||||
|
||||
.TP
|
||||
.B RCtrl+Up
|
||||
.B Ctrl+Up
|
||||
Click on VOLUME_UP
|
||||
|
||||
.TP
|
||||
.B RCtrl+Down
|
||||
.B Ctrl+Down
|
||||
Click on VOLUME_DOWN
|
||||
|
||||
.TP
|
||||
.B RCtrl+p
|
||||
.B Ctrl+p
|
||||
Click on POWER (turn screen on/off)
|
||||
|
||||
.TP
|
||||
@@ -258,31 +260,39 @@ Click on POWER (turn screen on/off)
|
||||
Turn screen on
|
||||
|
||||
.TP
|
||||
.B RCtrl+o
|
||||
.B Ctrl+o
|
||||
Turn device screen off (keep mirroring)
|
||||
|
||||
.TP
|
||||
.B RCtrl+Shift+o
|
||||
.B Ctrl+Shift+o
|
||||
Turn device screen on
|
||||
|
||||
.TP
|
||||
.B RCtrl+r
|
||||
.B Ctrl+r
|
||||
Rotate device screen
|
||||
|
||||
.TP
|
||||
.B RCtrl+n
|
||||
.B Ctrl+n
|
||||
Expand notification panel
|
||||
|
||||
.TP
|
||||
.B RCtrl+Shift+n
|
||||
.B Ctrl+Shift+n
|
||||
Collapse notification panel
|
||||
|
||||
.TP
|
||||
.B RCtrl+v
|
||||
Inject computer clipboard text
|
||||
.B Ctrl+c
|
||||
Copy device clipboard to computer
|
||||
|
||||
.TP
|
||||
.B RCtrl+i
|
||||
.B Ctrl+v
|
||||
Paste computer clipboard to device
|
||||
|
||||
.TP
|
||||
.B Ctrl+Shift+v
|
||||
Copy computer clipboard to device (and paste if the device runs Android >= 7)
|
||||
|
||||
.TP
|
||||
.B Ctrl+i
|
||||
Enable/disable FPS counter (print frames/second in logs)
|
||||
|
||||
.TP
|
||||
|
||||
@@ -12,6 +12,11 @@
|
||||
|
||||
void
|
||||
scrcpy_print_usage(const char *arg0) {
|
||||
#ifdef __APPLE__
|
||||
# define CTRL_OR_CMD "Cmd"
|
||||
#else
|
||||
# define CTRL_OR_CMD "Ctrl"
|
||||
#endif
|
||||
fprintf(stderr,
|
||||
"Usage: %s [options]\n"
|
||||
"\n"
|
||||
@@ -40,6 +45,9 @@ scrcpy_print_usage(const char *arg0) {
|
||||
" (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"
|
||||
@@ -181,71 +189,75 @@ scrcpy_print_usage(const char *arg0) {
|
||||
"\n"
|
||||
"Shortcuts:\n"
|
||||
"\n"
|
||||
" RCtrl is the right Ctrl key (the left Ctrl key is forwarded to\n"
|
||||
" the device.\n"
|
||||
"\n"
|
||||
" RCtrl+f\n"
|
||||
" " CTRL_OR_CMD "+f\n"
|
||||
" Switch fullscreen mode\n"
|
||||
"\n"
|
||||
" RCtrl+Left\n"
|
||||
" " CTRL_OR_CMD "+Left\n"
|
||||
" Rotate display left\n"
|
||||
"\n"
|
||||
" RCtrl+Right\n"
|
||||
" " CTRL_OR_CMD "+Right\n"
|
||||
" Rotate display right\n"
|
||||
"\n"
|
||||
" RCtrl+g\n"
|
||||
" " CTRL_OR_CMD "+g\n"
|
||||
" Resize window to 1:1 (pixel-perfect)\n"
|
||||
"\n"
|
||||
" RCtrl+w\n"
|
||||
" " CTRL_OR_CMD "+x\n"
|
||||
" Double-click on black borders\n"
|
||||
" Resize window to remove black borders\n"
|
||||
"\n"
|
||||
" RCtrl+h\n"
|
||||
" Ctrl+h\n"
|
||||
" Middle-click\n"
|
||||
" Click on HOME\n"
|
||||
"\n"
|
||||
" RCtrl+b\n"
|
||||
" RCtrl+Backspace\n"
|
||||
" " CTRL_OR_CMD "+b\n"
|
||||
" " CTRL_OR_CMD "+Backspace\n"
|
||||
" Right-click (when screen is on)\n"
|
||||
" Click on BACK\n"
|
||||
"\n"
|
||||
" RCtrl+s\n"
|
||||
" " CTRL_OR_CMD "+s\n"
|
||||
" Click on APP_SWITCH\n"
|
||||
"\n"
|
||||
" RCtrl+m\n"
|
||||
" Ctrl+m\n"
|
||||
" Click on MENU\n"
|
||||
"\n"
|
||||
" RCtrl+Up\n"
|
||||
" " CTRL_OR_CMD "+Up\n"
|
||||
" Click on VOLUME_UP\n"
|
||||
"\n"
|
||||
" RCtrl+Down\n"
|
||||
" " CTRL_OR_CMD "+Down\n"
|
||||
" Click on VOLUME_DOWN\n"
|
||||
"\n"
|
||||
" RCtrl+p\n"
|
||||
" " CTRL_OR_CMD "+p\n"
|
||||
" Click on POWER (turn screen on/off)\n"
|
||||
"\n"
|
||||
" Right-click (when screen is off)\n"
|
||||
" Power on\n"
|
||||
"\n"
|
||||
" RCtrl+o\n"
|
||||
" " CTRL_OR_CMD "+o\n"
|
||||
" Turn device screen off (keep mirroring)\n"
|
||||
"\n"
|
||||
" RCtrl+Shift+o\n"
|
||||
" " CTRL_OR_CMD "+Shift+o\n"
|
||||
" Turn device screen on\n"
|
||||
"\n"
|
||||
" RCtrl+r\n"
|
||||
" " CTRL_OR_CMD "+r\n"
|
||||
" Rotate device screen\n"
|
||||
"\n"
|
||||
" RCtrl+n\n"
|
||||
" " CTRL_OR_CMD "+n\n"
|
||||
" Expand notification panel\n"
|
||||
"\n"
|
||||
" RCtrl+Shift+n\n"
|
||||
" " CTRL_OR_CMD "+Shift+n\n"
|
||||
" Collapse notification panel\n"
|
||||
"\n"
|
||||
" RCtrl+v\n"
|
||||
" Inject computer clipboard text\n"
|
||||
" " CTRL_OR_CMD "+c\n"
|
||||
" Copy device clipboard to computer\n"
|
||||
"\n"
|
||||
" RCtrl+i\n"
|
||||
" " CTRL_OR_CMD "+v\n"
|
||||
" 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"
|
||||
"\n"
|
||||
" " CTRL_OR_CMD "+i\n"
|
||||
" Enable/disable FPS counter (print frames/second in logs)\n"
|
||||
"\n"
|
||||
" Drag & drop APK file\n"
|
||||
@@ -517,6 +529,7 @@ guess_record_format(const char *filename) {
|
||||
#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[]) {
|
||||
@@ -525,6 +538,8 @@ scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) {
|
||||
{"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},
|
||||
@@ -707,6 +722,9 @@ scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) {
|
||||
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;
|
||||
|
||||
@@ -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_write16be(buf, (uint16_t) len);
|
||||
memcpy(&buf[2], utf8, len);
|
||||
return 2 + len;
|
||||
buffer_write32be(buf, len);
|
||||
memcpy(&buf[4], utf8, len);
|
||||
return 4 + len;
|
||||
}
|
||||
|
||||
static uint16_t
|
||||
@@ -42,8 +42,9 @@ 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.metastate);
|
||||
return 10;
|
||||
buffer_write32be(&buf[6], msg->inject_keycode.repeat);
|
||||
buffer_write32be(&buf[10], msg->inject_keycode.metastate);
|
||||
return 14;
|
||||
case CONTROL_MSG_TYPE_INJECT_TEXT: {
|
||||
size_t len =
|
||||
write_string(msg->inject_text.text,
|
||||
|
||||
@@ -10,10 +10,11 @@
|
||||
#include "android/keycodes.h"
|
||||
#include "common.h"
|
||||
|
||||
#define CONTROL_MSG_MAX_SIZE (1 << 18) // 256k
|
||||
|
||||
#define CONTROL_MSG_INJECT_TEXT_MAX_LENGTH 300
|
||||
#define CONTROL_MSG_CLIPBOARD_TEXT_MAX_LENGTH 4092
|
||||
#define CONTROL_MSG_SERIALIZED_MAX_SIZE \
|
||||
(4 + CONTROL_MSG_CLIPBOARD_TEXT_MAX_LENGTH)
|
||||
// type: 1 byte; paste flag: 1 byte; length: 4 bytes
|
||||
#define CONTROL_MSG_CLIPBOARD_TEXT_MAX_LENGTH (CONTROL_MSG_MAX_SIZE - 6)
|
||||
|
||||
#define POINTER_ID_MOUSE UINT64_C(-1);
|
||||
|
||||
@@ -43,6 +44,7 @@ struct control_msg {
|
||||
struct {
|
||||
enum android_keyevent_action action;
|
||||
enum android_keycode keycode;
|
||||
uint32_t repeat;
|
||||
enum android_metastate metastate;
|
||||
} inject_keycode;
|
||||
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
|
||||
size_t
|
||||
control_msg_serialize(const struct control_msg *msg, unsigned char *buf);
|
||||
|
||||
@@ -60,7 +60,7 @@ controller_push_msg(struct controller *controller,
|
||||
static bool
|
||||
process_msg(struct controller *controller,
|
||||
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);
|
||||
if (!length) {
|
||||
return false;
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
ssize_t
|
||||
device_msg_deserialize(const unsigned char *buf, size_t len,
|
||||
struct device_msg *msg) {
|
||||
if (len < 3) {
|
||||
if (len < 5) {
|
||||
// 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: {
|
||||
uint16_t clipboard_len = buffer_read16be(&buf[1]);
|
||||
if (clipboard_len > len - 3) {
|
||||
size_t clipboard_len = buffer_read32be(&buf[1]);
|
||||
if (clipboard_len > len - 5) {
|
||||
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[3], clipboard_len);
|
||||
memcpy(text, &buf[5], clipboard_len);
|
||||
}
|
||||
text[clipboard_len] = '\0';
|
||||
|
||||
msg->clipboard.text = text;
|
||||
return 3 + clipboard_len;
|
||||
return 5 + clipboard_len;
|
||||
}
|
||||
default:
|
||||
LOGW("Unknown device message type: %d", (int) msg->type);
|
||||
|
||||
@@ -7,8 +7,9 @@
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#define DEVICE_MSG_TEXT_MAX_LENGTH 4093
|
||||
#define DEVICE_MSG_SERIALIZED_MAX_SIZE (3 + DEVICE_MSG_TEXT_MAX_LENGTH)
|
||||
#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)
|
||||
|
||||
enum device_msg_type {
|
||||
DEVICE_MSG_TYPE_CLIPBOARD,
|
||||
|
||||
@@ -92,9 +92,6 @@ convert_keycode(SDL_Keycode from, enum android_keycode *to, uint16_t mod,
|
||||
MAP(SDLK_LEFT, AKEYCODE_DPAD_LEFT);
|
||||
MAP(SDLK_DOWN, AKEYCODE_DPAD_DOWN);
|
||||
MAP(SDLK_UP, AKEYCODE_DPAD_UP);
|
||||
MAP(SDLK_LCTRL, AKEYCODE_CTRL_LEFT);
|
||||
MAP(SDLK_LSHIFT, AKEYCODE_SHIFT_LEFT);
|
||||
MAP(SDLK_RSHIFT, AKEYCODE_SHIFT_RIGHT);
|
||||
}
|
||||
|
||||
if (!(mod & (KMOD_NUM | KMOD_SHIFT))) {
|
||||
@@ -114,7 +111,7 @@ convert_keycode(SDL_Keycode from, enum android_keycode *to, uint16_t mod,
|
||||
}
|
||||
}
|
||||
|
||||
if (prefer_text && !(mod & KMOD_LCTRL)) {
|
||||
if (prefer_text) {
|
||||
// do not forward alpha and space key events
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -101,6 +101,16 @@ collapse_notification_panel(struct controller *controller) {
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
request_device_clipboard(struct controller *controller) {
|
||||
struct control_msg msg;
|
||||
msg.type = CONTROL_MSG_TYPE_GET_CLIPBOARD;
|
||||
|
||||
if (!controller_push_msg(controller, &msg)) {
|
||||
LOGW("Could not request device clipboard");
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
set_device_clipboard(struct controller *controller, bool paste) {
|
||||
char *text = SDL_GetClipboardText();
|
||||
@@ -224,7 +234,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) {
|
||||
bool prefer_text, uint32_t repeat) {
|
||||
to->type = CONTROL_MSG_TYPE_INJECT_KEYCODE;
|
||||
|
||||
if (!convert_keycode_action(from->type, &to->inject_keycode.action)) {
|
||||
@@ -237,30 +247,12 @@ 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;
|
||||
}
|
||||
|
||||
static void
|
||||
inject_as_ctrl(struct input_manager *im, const SDL_KeyboardEvent *event) {
|
||||
struct control_msg msg;
|
||||
|
||||
if (!convert_input_key(event, &msg, false)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Disable RCtrl and Meta
|
||||
msg.inject_keycode.metastate &=
|
||||
~(AMETA_CTRL_RIGHT_ON | AMETA_META_LEFT_ON | AMETA_META_RIGHT_ON);
|
||||
// Enable LCtrl
|
||||
msg.inject_keycode.metastate |= AMETA_CTRL_LEFT_ON;
|
||||
|
||||
if (!controller_push_msg(im->controller, &msg)) {
|
||||
LOGW("Could not request 'inject keycode'");
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
input_manager_process_key(struct input_manager *im,
|
||||
const SDL_KeyboardEvent *event,
|
||||
@@ -268,69 +260,69 @@ input_manager_process_key(struct input_manager *im,
|
||||
// control: indicates the state of the command-line option --no-control
|
||||
// ctrl: the Ctrl key
|
||||
|
||||
bool lctrl = event->keysym.mod & KMOD_LCTRL;
|
||||
bool rctrl = event->keysym.mod & KMOD_RCTRL;
|
||||
bool ctrl = event->keysym.mod & (KMOD_LCTRL | KMOD_RCTRL);
|
||||
bool alt = event->keysym.mod & (KMOD_LALT | KMOD_RALT);
|
||||
bool meta = event->keysym.mod & (KMOD_LGUI | KMOD_RGUI);
|
||||
bool shift = event->keysym.mod & (KMOD_LSHIFT | KMOD_RSHIFT);
|
||||
|
||||
bool shortcut_key = rctrl;
|
||||
// use Cmd on macOS, Ctrl on other platforms
|
||||
#ifdef __APPLE__
|
||||
shortcut_key |= meta;
|
||||
bool cmd = !ctrl && meta;
|
||||
#else
|
||||
if (meta) {
|
||||
// No shortcut involve Meta, and it is not forwarded to the device
|
||||
// no shortcuts involve Meta on platforms other than macOS, and it must
|
||||
// not be forwarded to the device
|
||||
return;
|
||||
}
|
||||
bool cmd = ctrl; // && !meta, already guaranteed
|
||||
#endif
|
||||
|
||||
if (alt) {
|
||||
// No shortcuts involve Alt, and it is not forwarded to the device
|
||||
// no shortcuts involve Alt, and it must not be forwarded to the device
|
||||
return;
|
||||
}
|
||||
|
||||
struct controller *controller = im->controller;
|
||||
|
||||
SDL_Keycode keycode = event->keysym.sym;
|
||||
bool down = event->type == SDL_KEYDOWN;
|
||||
|
||||
// Capture all RCtrl events
|
||||
if (shortcut_key) {
|
||||
// capture all Ctrl events
|
||||
if (ctrl || cmd) {
|
||||
SDL_Keycode keycode = event->keysym.sym;
|
||||
bool down = event->type == SDL_KEYDOWN;
|
||||
int action = down ? ACTION_DOWN : ACTION_UP;
|
||||
bool repeat = event->repeat;
|
||||
bool shift = event->keysym.mod & (KMOD_LSHIFT | KMOD_RSHIFT);
|
||||
switch (keycode) {
|
||||
case SDLK_h:
|
||||
// Ctrl+h on all platform, since Cmd+h is already captured by
|
||||
// the system on macOS to hide the window
|
||||
if (control && !shift && !repeat) {
|
||||
if (control && ctrl && !meta && !shift && !repeat) {
|
||||
action_home(controller, action);
|
||||
}
|
||||
return;
|
||||
case SDLK_b: // fall-through
|
||||
case SDLK_BACKSPACE:
|
||||
if (control && !shift && !repeat) {
|
||||
if (control && cmd && !shift && !repeat) {
|
||||
action_back(controller, action);
|
||||
}
|
||||
return;
|
||||
case SDLK_s:
|
||||
if (control && !shift && !repeat) {
|
||||
if (control && cmd && !shift && !repeat) {
|
||||
action_app_switch(controller, action);
|
||||
}
|
||||
return;
|
||||
case SDLK_m:
|
||||
// Ctrl+m on all platform, since Cmd+m is already captured by
|
||||
// the system on macOS to minimize the window
|
||||
if (control && !shift && !repeat) {
|
||||
if (control && ctrl && !meta && !shift && !repeat) {
|
||||
action_menu(controller, action);
|
||||
}
|
||||
return;
|
||||
case SDLK_p:
|
||||
if (control && !shift && !repeat) {
|
||||
if (control && cmd && !shift && !repeat) {
|
||||
action_power(controller, action);
|
||||
}
|
||||
return;
|
||||
case SDLK_o:
|
||||
if (control && !repeat && down) {
|
||||
if (control && cmd && !repeat && down) {
|
||||
enum screen_power_mode mode = shift
|
||||
? SCREEN_POWER_MODE_NORMAL
|
||||
: SCREEN_POWER_MODE_OFF;
|
||||
@@ -338,57 +330,67 @@ input_manager_process_key(struct input_manager *im,
|
||||
}
|
||||
return;
|
||||
case SDLK_DOWN:
|
||||
if (control && !shift) {
|
||||
if (control && cmd && !shift) {
|
||||
// forward repeated events
|
||||
action_volume_down(controller, action);
|
||||
}
|
||||
return;
|
||||
case SDLK_UP:
|
||||
if (control && !shift) {
|
||||
if (control && cmd && !shift) {
|
||||
// forward repeated events
|
||||
action_volume_up(controller, action);
|
||||
}
|
||||
return;
|
||||
case SDLK_LEFT:
|
||||
if (!shift && !repeat && down) {
|
||||
if (cmd && !shift && !repeat && down) {
|
||||
rotate_client_left(im->screen);
|
||||
}
|
||||
return;
|
||||
case SDLK_RIGHT:
|
||||
if (!shift && !repeat && down) {
|
||||
if (cmd && !shift && !repeat && down) {
|
||||
rotate_client_right(im->screen);
|
||||
}
|
||||
return;
|
||||
case SDLK_c:
|
||||
if (control && cmd && !shift && !repeat && down) {
|
||||
request_device_clipboard(controller);
|
||||
}
|
||||
return;
|
||||
case SDLK_v:
|
||||
if (control && !shift && !repeat && down) {
|
||||
// Inject the text as input events
|
||||
clipboard_paste(controller);
|
||||
if (control && cmd && !repeat && down) {
|
||||
if (shift) {
|
||||
// store the text in the device clipboard and paste
|
||||
set_device_clipboard(controller, true);
|
||||
} else {
|
||||
// inject the text as input events
|
||||
clipboard_paste(controller);
|
||||
}
|
||||
}
|
||||
return;
|
||||
case SDLK_f:
|
||||
if (!shift && !repeat && down) {
|
||||
if (!shift && cmd && !repeat && down) {
|
||||
screen_switch_fullscreen(im->screen);
|
||||
}
|
||||
return;
|
||||
case SDLK_w:
|
||||
if (!shift && !repeat && down) {
|
||||
case SDLK_x:
|
||||
if (!shift && cmd && !repeat && down) {
|
||||
screen_resize_to_fit(im->screen);
|
||||
}
|
||||
return;
|
||||
case SDLK_g:
|
||||
if (!shift && !repeat && down) {
|
||||
if (!shift && cmd && !repeat && down) {
|
||||
screen_resize_to_pixel_perfect(im->screen);
|
||||
}
|
||||
return;
|
||||
case SDLK_i:
|
||||
if (!shift && !repeat && down) {
|
||||
if (!shift && cmd && !repeat && down) {
|
||||
struct fps_counter *fps_counter =
|
||||
im->video_buffer->fps_counter;
|
||||
switch_fps_counter_state(fps_counter);
|
||||
}
|
||||
return;
|
||||
case SDLK_n:
|
||||
if (control && !repeat && down) {
|
||||
if (control && cmd && !repeat && down) {
|
||||
if (shift) {
|
||||
collapse_notification_panel(controller);
|
||||
} else {
|
||||
@@ -397,19 +399,10 @@ input_manager_process_key(struct input_manager *im,
|
||||
}
|
||||
return;
|
||||
case SDLK_r:
|
||||
if (control && !shift && !repeat && down) {
|
||||
if (control && cmd && !shift && !repeat && down) {
|
||||
rotate_device(controller);
|
||||
}
|
||||
return;
|
||||
case SDLK_c:
|
||||
case SDLK_x:
|
||||
if (control && !shift) {
|
||||
// For convenience, forward shortcut_key+c and
|
||||
// shortcut_key+x as Ctrl+c and Ctrl+x (typically "copy" and
|
||||
// "cut", but not always) to the device
|
||||
inject_as_ctrl(im, event);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
return;
|
||||
@@ -419,14 +412,14 @@ input_manager_process_key(struct input_manager *im,
|
||||
return;
|
||||
}
|
||||
|
||||
if (lctrl && !shift && keycode == SDLK_v && down) {
|
||||
// Synchronize the computer clipboard to the device clipboard before
|
||||
// sending Ctrl+V, to allow seamless copy-paste.
|
||||
set_device_clipboard(controller, false);
|
||||
if (event->repeat) {
|
||||
++im->repeat;
|
||||
} else {
|
||||
im->repeat = 0;
|
||||
}
|
||||
|
||||
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)) {
|
||||
LOGW("Could not request 'inject keycode'");
|
||||
}
|
||||
|
||||
@@ -14,6 +14,11 @@ 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;
|
||||
};
|
||||
|
||||
|
||||
@@ -60,28 +60,29 @@ static int
|
||||
run_receiver(void *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;
|
||||
|
||||
for (;;) {
|
||||
assert(head < DEVICE_MSG_SERIALIZED_MAX_SIZE);
|
||||
ssize_t r = net_recv(receiver->control_socket, buf,
|
||||
DEVICE_MSG_SERIALIZED_MAX_SIZE - head);
|
||||
assert(head < DEVICE_MSG_MAX_SIZE);
|
||||
ssize_t r = net_recv(receiver->control_socket, buf + head,
|
||||
DEVICE_MSG_MAX_SIZE - head);
|
||||
if (r <= 0) {
|
||||
LOGD("Receiver stopped");
|
||||
break;
|
||||
}
|
||||
|
||||
ssize_t consumed = process_msgs(buf, r);
|
||||
head += r;
|
||||
ssize_t consumed = process_msgs(buf, head);
|
||||
if (consumed == -1) {
|
||||
// an error occurred
|
||||
break;
|
||||
}
|
||||
|
||||
if (consumed) {
|
||||
head -= consumed;
|
||||
// shift the remaining data in the buffer
|
||||
memmove(buf, &buf[consumed], r - consumed);
|
||||
head = r - consumed;
|
||||
memmove(buf, &buf[consumed], head);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -46,6 +46,7 @@ static struct input_manager input_manager = {
|
||||
.controller = &controller,
|
||||
.video_buffer = &video_buffer,
|
||||
.screen = &screen,
|
||||
.repeat = 0,
|
||||
.prefer_text = false, // initialized later
|
||||
};
|
||||
|
||||
@@ -63,7 +64,8 @@ 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) {
|
||||
sdl_init_and_configure(bool display, const char *render_driver,
|
||||
bool disable_screensaver) {
|
||||
uint32_t flags = display ? SDL_INIT_VIDEO : SDL_INIT_EVENTS;
|
||||
if (SDL_Init(flags)) {
|
||||
LOGC("Could not initialize SDL: %s", SDL_GetError());
|
||||
@@ -112,8 +114,13 @@ sdl_init_and_configure(bool display, const char *render_driver) {
|
||||
LOGW("Could not disable minimize on focus loss");
|
||||
}
|
||||
|
||||
// Do not disable the screensaver when scrcpy is running
|
||||
SDL_EnableScreenSaver();
|
||||
if (disable_screensaver) {
|
||||
LOGD("Screensaver disabled");
|
||||
SDL_DisableScreenSaver();
|
||||
} else {
|
||||
LOGD("Screensaver enabled");
|
||||
SDL_EnableScreenSaver();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -321,7 +328,8 @@ scrcpy(const struct scrcpy_options *options) {
|
||||
bool controller_initialized = 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;
|
||||
}
|
||||
|
||||
|
||||
@@ -43,6 +43,7 @@ struct scrcpy_options {
|
||||
bool mipmaps;
|
||||
bool stay_awake;
|
||||
bool force_adb_forward;
|
||||
bool disable_screensaver;
|
||||
};
|
||||
|
||||
#define SCRCPY_OPTIONS_DEFAULT { \
|
||||
@@ -81,6 +82,7 @@ struct scrcpy_options {
|
||||
.mipmaps = true, \
|
||||
.stay_awake = false, \
|
||||
.force_adb_forward = false, \
|
||||
.disable_screensaver = false, \
|
||||
}
|
||||
|
||||
bool
|
||||
|
||||
@@ -9,18 +9,20 @@ 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_SERIALIZED_MAX_SIZE];
|
||||
unsigned char buf[CONTROL_MSG_MAX_SIZE];
|
||||
int size = control_msg_serialize(&msg, buf);
|
||||
assert(size == 10);
|
||||
assert(size == 14);
|
||||
|
||||
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)));
|
||||
@@ -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);
|
||||
assert(size == 16);
|
||||
assert(size == 18);
|
||||
|
||||
const unsigned char expected[] = {
|
||||
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
|
||||
};
|
||||
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';
|
||||
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);
|
||||
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[1] = 0x01;
|
||||
expected[2] = 0x2c; // text length (16 bits)
|
||||
memset(&expected[3], 'a', CONTROL_MSG_INJECT_TEXT_MAX_LENGTH);
|
||||
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);
|
||||
|
||||
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);
|
||||
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);
|
||||
assert(size == 21);
|
||||
|
||||
@@ -142,7 +146,7 @@ static void test_serialize_back_or_screen_on(void) {
|
||||
.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);
|
||||
assert(size == 1);
|
||||
|
||||
@@ -157,7 +161,7 @@ static void test_serialize_expand_notification_panel(void) {
|
||||
.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);
|
||||
assert(size == 1);
|
||||
|
||||
@@ -172,7 +176,7 @@ static void test_serialize_collapse_notification_panel(void) {
|
||||
.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);
|
||||
assert(size == 1);
|
||||
|
||||
@@ -187,7 +191,7 @@ static void test_serialize_get_clipboard(void) {
|
||||
.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);
|
||||
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);
|
||||
assert(size == 17);
|
||||
assert(size == 19);
|
||||
|
||||
const unsigned char expected[] = {
|
||||
CONTROL_MSG_TYPE_SET_CLIPBOARD,
|
||||
1, // paste
|
||||
0x00, 0x0d, // text length
|
||||
0x00, 0x00, 0x00, 0x0d, // text length
|
||||
'h', 'e', 'l', 'l', 'o', ',', ' ', 'w', 'o', 'r', 'l', 'd', '!', // text
|
||||
};
|
||||
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);
|
||||
assert(size == 2);
|
||||
|
||||
@@ -243,7 +247,7 @@ static void test_serialize_rotate_device(void) {
|
||||
.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);
|
||||
assert(size == 1);
|
||||
|
||||
|
||||
@@ -4,16 +4,17 @@
|
||||
#include "device_msg.h"
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
static void test_deserialize_clipboard(void) {
|
||||
const unsigned char input[] = {
|
||||
DEVICE_MSG_TYPE_CLIPBOARD,
|
||||
0x00, 0x03, // text length
|
||||
0x00, 0x00, 0x00, 0x03, // text length
|
||||
0x41, 0x42, 0x43, // "ABC"
|
||||
};
|
||||
|
||||
struct device_msg msg;
|
||||
ssize_t r = device_msg_deserialize(input, sizeof(input), &msg);
|
||||
assert(r == 6);
|
||||
assert(r == 8);
|
||||
|
||||
assert(msg.type == DEVICE_MSG_TYPE_CLIPBOARD);
|
||||
assert(msg.clipboard.text);
|
||||
@@ -22,7 +23,30 @@ 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;
|
||||
}
|
||||
|
||||
@@ -17,6 +17,8 @@ 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_*
|
||||
@@ -28,16 +30,18 @@ public final class ControlMessage {
|
||||
private Position position;
|
||||
private int hScroll;
|
||||
private int vScroll;
|
||||
private boolean paste;
|
||||
private int flags;
|
||||
private int repeat;
|
||||
|
||||
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();
|
||||
msg.type = TYPE_INJECT_KEYCODE;
|
||||
msg.action = action;
|
||||
msg.keycode = keycode;
|
||||
msg.repeat = repeat;
|
||||
msg.metaState = metaState;
|
||||
return msg;
|
||||
}
|
||||
@@ -73,7 +77,9 @@ public final class ControlMessage {
|
||||
ControlMessage msg = new ControlMessage();
|
||||
msg.type = TYPE_SET_CLIPBOARD;
|
||||
msg.text = text;
|
||||
msg.paste = paste;
|
||||
if (paste) {
|
||||
msg.flags = FLAGS_PASTE;
|
||||
}
|
||||
return msg;
|
||||
}
|
||||
|
||||
@@ -137,7 +143,11 @@ public final class ControlMessage {
|
||||
return vScroll;
|
||||
}
|
||||
|
||||
public boolean getPaste() {
|
||||
return paste;
|
||||
public int getFlags() {
|
||||
return flags;
|
||||
}
|
||||
|
||||
public int getRepeat() {
|
||||
return repeat;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,18 +8,18 @@ import java.nio.charset.StandardCharsets;
|
||||
|
||||
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_SCROLL_EVENT_PAYLOAD_LENGTH = 20;
|
||||
static final int SET_SCREEN_POWER_MODE_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;
|
||||
|
||||
private static final int RAW_BUFFER_SIZE = 4096;
|
||||
|
||||
private final byte[] rawBuffer = new byte[RAW_BUFFER_SIZE];
|
||||
private final byte[] rawBuffer = new byte[MESSAGE_MAX_SIZE];
|
||||
private final ByteBuffer buffer = ByteBuffer.wrap(rawBuffer);
|
||||
|
||||
public ControlMessageReader() {
|
||||
@@ -98,15 +98,16 @@ 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, metaState);
|
||||
return ControlMessage.createInjectKeycode(action, keycode, repeat, metaState);
|
||||
}
|
||||
|
||||
private String parseString() {
|
||||
if (buffer.remaining() < 2) {
|
||||
if (buffer.remaining() < 4) {
|
||||
return null;
|
||||
}
|
||||
int len = toUnsigned(buffer.getShort());
|
||||
int len = buffer.getInt();
|
||||
if (buffer.remaining() < len) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -74,7 +74,7 @@ public class Controller {
|
||||
switch (msg.getType()) {
|
||||
case ControlMessage.TYPE_INJECT_KEYCODE:
|
||||
if (device.supportsInputEvents()) {
|
||||
injectKeycode(msg.getAction(), msg.getKeycode(), msg.getMetaState());
|
||||
injectKeycode(msg.getAction(), msg.getKeycode(), msg.getRepeat(), msg.getMetaState());
|
||||
}
|
||||
break;
|
||||
case ControlMessage.TYPE_INJECT_TEXT:
|
||||
@@ -110,7 +110,8 @@ public class Controller {
|
||||
}
|
||||
break;
|
||||
case ControlMessage.TYPE_SET_CLIPBOARD:
|
||||
setClipboard(msg.getText(), msg.getPaste());
|
||||
boolean paste = (msg.getFlags() & ControlMessage.FLAGS_PASTE) != 0;
|
||||
setClipboard(msg.getText(), paste);
|
||||
break;
|
||||
case ControlMessage.TYPE_SET_SCREEN_POWER_MODE:
|
||||
if (device.supportsInputEvents()) {
|
||||
@@ -129,8 +130,8 @@ public class Controller {
|
||||
}
|
||||
}
|
||||
|
||||
private boolean injectKeycode(int action, int keycode, int metaState) {
|
||||
return device.injectKeyEvent(action, keycode, 0, metaState);
|
||||
private boolean injectKeycode(int action, int keycode, int repeat, int metaState) {
|
||||
return device.injectKeyEvent(action, keycode, repeat, metaState);
|
||||
}
|
||||
|
||||
private boolean injectChar(char c) {
|
||||
|
||||
@@ -207,14 +207,6 @@ public final class Device {
|
||||
}
|
||||
|
||||
public boolean setClipboardText(String text) {
|
||||
String currentClipboard = getClipboardText();
|
||||
if (currentClipboard == null || currentClipboard.equals(text)) {
|
||||
// The clipboard already contains the requested text.
|
||||
// Since pasting text from the computer involves setting the device clipboard, it could be set twice on a copy-paste. This would cause
|
||||
// the clipboard listeners to be notified twice, and that would flood the Android keyboard clipboard history. To workaround this
|
||||
// problem, do not explicitly set the clipboard text if it already contains the expected content.
|
||||
return false;
|
||||
}
|
||||
isSettingClipboard.set(true);
|
||||
boolean ok = serviceManager.getClipboardManager().setText(text);
|
||||
isSettingClipboard.set(false);
|
||||
|
||||
@@ -7,10 +7,10 @@ import java.nio.charset.StandardCharsets;
|
||||
|
||||
public class DeviceMessageWriter {
|
||||
|
||||
public static final int CLIPBOARD_TEXT_MAX_LENGTH = 4093;
|
||||
private static final int MAX_EVENT_SIZE = CLIPBOARD_TEXT_MAX_LENGTH + 3;
|
||||
private 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
|
||||
|
||||
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);
|
||||
|
||||
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.putShort((short) len);
|
||||
buffer.putInt(len);
|
||||
buffer.put(raw, 0, len);
|
||||
output.write(rawBuffer, 0, buffer.position());
|
||||
break;
|
||||
|
||||
@@ -25,6 +25,7 @@ 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();
|
||||
|
||||
@@ -37,6 +38,7 @@ 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());
|
||||
}
|
||||
|
||||
@@ -48,7 +50,7 @@ public class ControlMessageReaderTest {
|
||||
DataOutputStream dos = new DataOutputStream(bos);
|
||||
dos.writeByte(ControlMessage.TYPE_INJECT_TEXT);
|
||||
byte[] text = "testé".getBytes(StandardCharsets.UTF_8);
|
||||
dos.writeShort(text.length);
|
||||
dos.writeInt(text.length);
|
||||
dos.write(text);
|
||||
byte[] packet = bos.toByteArray();
|
||||
|
||||
@@ -68,7 +70,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.writeShort(text.length);
|
||||
dos.writeInt(text.length);
|
||||
dos.write(text);
|
||||
byte[] packet = bos.toByteArray();
|
||||
|
||||
@@ -218,7 +220,7 @@ public class ControlMessageReaderTest {
|
||||
dos.writeByte(ControlMessage.TYPE_SET_CLIPBOARD);
|
||||
dos.writeByte(1); // paste
|
||||
byte[] text = "testé".getBytes(StandardCharsets.UTF_8);
|
||||
dos.writeShort(text.length);
|
||||
dos.writeInt(text.length);
|
||||
dos.write(text);
|
||||
|
||||
byte[] packet = bos.toByteArray();
|
||||
@@ -228,7 +230,9 @@ public class ControlMessageReaderTest {
|
||||
|
||||
Assert.assertEquals(ControlMessage.TYPE_SET_CLIPBOARD, event.getType());
|
||||
Assert.assertEquals("testé", event.getText());
|
||||
Assert.assertTrue(event.getPaste());
|
||||
|
||||
boolean parse = (event.getFlags() & ControlMessage.FLAGS_PASTE) != 0;
|
||||
Assert.assertTrue(parse);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -244,7 +248,7 @@ public class ControlMessageReaderTest {
|
||||
Arrays.fill(rawText, (byte) 'a');
|
||||
String text = new String(rawText, 0, rawText.length);
|
||||
|
||||
dos.writeShort(rawText.length);
|
||||
dos.writeInt(rawText.length);
|
||||
dos.write(rawText);
|
||||
|
||||
byte[] packet = bos.toByteArray();
|
||||
@@ -254,7 +258,9 @@ public class ControlMessageReaderTest {
|
||||
|
||||
Assert.assertEquals(ControlMessage.TYPE_SET_CLIPBOARD, event.getType());
|
||||
Assert.assertEquals(text, event.getText());
|
||||
Assert.assertTrue(event.getPaste());
|
||||
|
||||
boolean parse = (event.getFlags() & ControlMessage.FLAGS_PASTE) != 0;
|
||||
Assert.assertTrue(parse);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -304,11 +310,13 @@ 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();
|
||||
@@ -318,12 +326,14 @@ 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());
|
||||
}
|
||||
|
||||
@@ -337,6 +347,7 @@ 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);
|
||||
@@ -349,6 +360,7 @@ 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();
|
||||
@@ -356,6 +368,7 @@ 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));
|
||||
@@ -365,6 +378,7 @@ 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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ public class DeviceMessageWriterTest {
|
||||
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||
DataOutputStream dos = new DataOutputStream(bos);
|
||||
dos.writeByte(DeviceMessage.TYPE_CLIPBOARD);
|
||||
dos.writeShort(data.length);
|
||||
dos.writeInt(data.length);
|
||||
dos.write(data);
|
||||
|
||||
byte[] expected = bos.toByteArray();
|
||||
|
||||
Reference in New Issue
Block a user