Compare commits

..

10 Commits
repeat ... meta

Author SHA1 Message Date
Romain Vimont
8da10eb4af WIP remove Meta+Shift+v 2020-05-30 01:48:58 +02:00
Romain Vimont
b330ba448b WIP Synchronize keyboard on Ctrl+v 2020-05-30 01:45:59 +02:00
Romain Vimont
a98b74df3d Add Meta+x shortcut for CUT
Add a scrcpy shortcut to send the CUT keycode.
2020-05-29 23:48:14 +02:00
Romain Vimont
567c5a7da7 Change "resize to fit" shortcut to Meta+w
Meta+x will be used for CUT (cut text to clipboard).
2020-05-29 23:47:29 +02:00
Romain Vimont
9badd2bdf0 Press COPY on "get clipboard" request if possible
If the device runs at least Android 7, just press COPY on the device
(the clipboard content will be sent via the clipboard listener).

<https://developer.android.com/reference/android/view/KeyEvent.html#KEYCODE_COPY>
2020-05-29 23:46:54 +02:00
Romain Vimont
31cee2c49f Reformulate seamless paste documentation 2020-05-29 23:46:04 +02:00
Romain Vimont
51d969b20c Simplify PASTE option for "set clipboard"
When the client requests to set the clipboard, it may request to press
the PASTE key in addition. To be a bit generic, it was stored as a flag
in ControlMessage.java.

But flags suggest that it represents a bitwise union, which could become
confusing when we add a COPY option for getting the clipboard.
2020-05-29 23:44:16 +02:00
Romain Vimont
149de2f788 Forward Shift to the device
This allows to select text using Shift+(arrow keys).

Fixes #942 <https://github.com/Genymobile/scrcpy/issues/942>
2020-05-29 23:44:16 +02:00
Romain Vimont
26cadf1022 Forward Ctrl to the device
Now that Meta is used for scrcpy shortcuts, Ctrl can be forwarded to the
device.

This allows to trigger Android shortcuts.

Fixes #555 <https://github.com/Genymobile/scrcpy/issues/555>
2020-05-29 23:44:16 +02:00
Romain Vimont
f650b0195f Replace Ctrl by Meta as shortcut modifier
This paves the way to forward Ctrl to the device.
2020-05-29 23:44:09 +02:00
25 changed files with 285 additions and 335 deletions

3
.gitignore vendored
View File

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

View File

@@ -479,17 +479,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
@@ -571,33 +560,36 @@ 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`
_`Meta` is typically the `Windows` key on the keyboard, or `Cmd` on macOS._
| Action | Shortcut
| ---------------------------------------------------- |:-----------------------------
| Switch fullscreen mode | `Meta`+`f`
| Rotate display left | `Meta`+`` _(left)_
| Rotate display right | `Meta`+`` _(right)_
| Resize window to 1:1 (pixel-perfect) | `Meta`+`g`
| Resize window to remove black borders | `Meta`+`w` \| _Double-click¹_
| Click on `HOME` | `Meta`+`h` \| _Middle-click_
| Click on `BACK` | `Meta`+`b` \| _Right-click²_
| Click on `APP_SWITCH` | `Meta`+`s`
| Click on `MENU` | `Meta`+`m`
| Click on `VOLUME_UP` | `Meta`+`` _(up)_
| Click on `VOLUME_DOWN` | `Meta`+`↓` _(down)_
| Click on `POWER` | `Meta`+`p`
| Power on | _Right-click²_
| Turn device screen off (keep mirroring) | `Meta`+`o`
| Turn device screen on | `Meta`+`Shift`+`o`
| Rotate device screen | `Meta`+`r`
| Expand notification panel | `Meta`+`n`
| Collapse notification panel | `Meta`+`Shift`+`n`
| Press COPY³, then Copy device clipboard to computer | `Meta`+`c`
| Press CUT³ | `Meta`+`x`
| Paste computer clipboard to device | `Meta`+`v`
| Enable/disable FPS counter (on stdout) | `Meta`+`i`
_¹Double-click on black borders to remove them._
_²Right-click turns the screen on if it was off, presses BACK otherwise._
_²Right-click turns the screen on if it was off, presses BACK otherwise._
_³Only if the device runs Android >= 7._
## Custom paths

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

@@ -43,10 +43,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.
@@ -208,51 +204,51 @@ Default is 0 (automatic).\n
.SH SHORTCUTS
.TP
.B Ctrl+f
.B Meta+f
Switch fullscreen mode
.TP
.B Ctrl+Left
.B Meta+Left
Rotate display left
.TP
.B Ctrl+Right
.B Meta+Right
Rotate display right
.TP
.B Ctrl+g
.B Meta+g
Resize window to 1:1 (pixel\-perfect)
.TP
.B Ctrl+x, Double\-click on black borders
.B Meta+w, Double\-click on black borders
Resize window to remove black borders
.TP
.B Ctrl+h, Home, Middle\-click
.B Meta+h, Home, Middle\-click
Click on HOME
.TP
.B Ctrl+b, Ctrl+Backspace, Right\-click (when screen is on)
.B Meta+b, Meta+Backspace, Right\-click (when screen is on)
Click on BACK
.TP
.B Ctrl+s
.B Meta+s
Click on APP_SWITCH
.TP
.B Ctrl+m
.B Meta+m
Click on MENU
.TP
.B Ctrl+Up
.B Meta+Up
Click on VOLUME_UP
.TP
.B Ctrl+Down
.B Meta+Down
Click on VOLUME_DOWN
.TP
.B Ctrl+p
.B Meta+p
Click on POWER (turn screen on/off)
.TP
@@ -260,39 +256,39 @@ Click on POWER (turn screen on/off)
Turn screen on
.TP
.B Ctrl+o
.B Meta+o
Turn device screen off (keep mirroring)
.TP
.B Ctrl+Shift+o
.B Meta+Shift+o
Turn device screen on
.TP
.B Ctrl+r
.B Meta+r
Rotate device screen
.TP
.B Ctrl+n
.B Meta+n
Expand notification panel
.TP
.B Ctrl+Shift+n
.B Meta+Shift+n
Collapse notification panel
.TP
.B Ctrl+c
Copy device clipboard to computer
.B Meta+c
Press COPY (Android >= 7), then copy device clipboard to computer
.TP
.B Ctrl+v
.B Meta+x
Press CUT (Android >= 7)
.TP
.B Meta+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
.B Meta+i
Enable/disable FPS counter (print frames/second in logs)
.TP

View File

@@ -13,9 +13,9 @@
void
scrcpy_print_usage(const char *arg0) {
#ifdef __APPLE__
# define CTRL_OR_CMD "Cmd"
# define MOD "Cmd"
#else
# define CTRL_OR_CMD "Ctrl"
# define MOD "Meta"
#endif
fprintf(stderr,
"Usage: %s [options]\n"
@@ -45,9 +45,6 @@ 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"
@@ -189,19 +186,19 @@ scrcpy_print_usage(const char *arg0) {
"\n"
"Shortcuts:\n"
"\n"
" " CTRL_OR_CMD "+f\n"
" " MOD "+f\n"
" Switch fullscreen mode\n"
"\n"
" " CTRL_OR_CMD "+Left\n"
" " MOD "+Left\n"
" Rotate display left\n"
"\n"
" " CTRL_OR_CMD "+Right\n"
" " MOD "+Right\n"
" Rotate display right\n"
"\n"
" " CTRL_OR_CMD "+g\n"
" " MOD "+g\n"
" Resize window to 1:1 (pixel-perfect)\n"
"\n"
" " CTRL_OR_CMD "+x\n"
" " MOD "+w\n"
" Double-click on black borders\n"
" Resize window to remove black borders\n"
"\n"
@@ -209,55 +206,55 @@ scrcpy_print_usage(const char *arg0) {
" Middle-click\n"
" Click on HOME\n"
"\n"
" " CTRL_OR_CMD "+b\n"
" " CTRL_OR_CMD "+Backspace\n"
" " MOD "+b\n"
" " MOD "+Backspace\n"
" Right-click (when screen is on)\n"
" Click on BACK\n"
"\n"
" " CTRL_OR_CMD "+s\n"
" " MOD "+s\n"
" Click on APP_SWITCH\n"
"\n"
" Ctrl+m\n"
" Click on MENU\n"
"\n"
" " CTRL_OR_CMD "+Up\n"
" " MOD "+Up\n"
" Click on VOLUME_UP\n"
"\n"
" " CTRL_OR_CMD "+Down\n"
" " MOD "+Down\n"
" Click on VOLUME_DOWN\n"
"\n"
" " CTRL_OR_CMD "+p\n"
" " MOD "+p\n"
" Click on POWER (turn screen on/off)\n"
"\n"
" Right-click (when screen is off)\n"
" Power on\n"
"\n"
" " CTRL_OR_CMD "+o\n"
" " MOD "+o\n"
" Turn device screen off (keep mirroring)\n"
"\n"
" " CTRL_OR_CMD "+Shift+o\n"
" " MOD "+Shift+o\n"
" Turn device screen on\n"
"\n"
" " CTRL_OR_CMD "+r\n"
" " MOD "+r\n"
" Rotate device screen\n"
"\n"
" " CTRL_OR_CMD "+n\n"
" " MOD "+n\n"
" Expand notification panel\n"
"\n"
" " CTRL_OR_CMD "+Shift+n\n"
" " MOD "+Shift+n\n"
" Collapse notification panel\n"
"\n"
" " CTRL_OR_CMD "+c\n"
" Copy device clipboard to computer\n"
" " MOD "+c\n"
" Press COPY (Android >= 7), then copy device clipboard to\n"
" computer\n"
"\n"
" " CTRL_OR_CMD "+v\n"
" " MOD "+x\n"
" Press CUT (Android >= 7)\n"
"\n"
" " MOD "+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"
" " MOD "+i\n"
" Enable/disable FPS counter (print frames/second in logs)\n"
"\n"
" Drag & drop APK file\n"
@@ -268,6 +265,7 @@ scrcpy_print_usage(const char *arg0) {
DEFAULT_LOCK_VIDEO_ORIENTATION, DEFAULT_LOCK_VIDEO_ORIENTATION >= 0 ? "" : " (unlocked)",
DEFAULT_MAX_SIZE, DEFAULT_MAX_SIZE ? "" : " (unlimited)",
DEFAULT_LOCAL_PORT_RANGE_FIRST, DEFAULT_LOCAL_PORT_RANGE_LAST);
#undef MOD
}
static bool
@@ -529,7 +527,6 @@ 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[]) {
@@ -538,8 +535,6 @@ 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},
@@ -722,9 +717,6 @@ 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;

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,
@@ -67,6 +66,9 @@ control_msg_serialize(const struct control_msg *msg, unsigned char *buf) {
buffer_write32be(&buf[17],
(uint32_t) msg->inject_scroll_event.vscroll);
return 21;
case CONTROL_MSG_TYPE_GET_CLIPBOARD:
buf[1] = msg->get_clipboard.copy;
return 2;
case CONTROL_MSG_TYPE_SET_CLIPBOARD: {
buf[1] = !!msg->set_clipboard.paste;
size_t len = write_string(msg->set_clipboard.text,
@@ -80,7 +82,6 @@ control_msg_serialize(const struct control_msg *msg, unsigned char *buf) {
case CONTROL_MSG_TYPE_BACK_OR_SCREEN_ON:
case CONTROL_MSG_TYPE_EXPAND_NOTIFICATION_PANEL:
case CONTROL_MSG_TYPE_COLLAPSE_NOTIFICATION_PANEL:
case CONTROL_MSG_TYPE_GET_CLIPBOARD:
case CONTROL_MSG_TYPE_ROTATE_DEVICE:
// no additional data
return 1;

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 4092
#define CONTROL_MSG_SERIALIZED_MAX_SIZE \
(4 + 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 {
@@ -62,6 +60,9 @@ struct control_msg {
int32_t hscroll;
int32_t vscroll;
} inject_scroll_event;
struct {
bool copy;
} get_clipboard;
struct {
char *text; // owned, to be freed by SDL_free()
bool paste;
@@ -72,7 +73,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

@@ -92,6 +92,10 @@ 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_RCTRL, AKEYCODE_CTRL_RIGHT);
MAP(SDLK_LSHIFT, AKEYCODE_SHIFT_LEFT);
MAP(SDLK_RSHIFT, AKEYCODE_SHIFT_RIGHT);
}
if (!(mod & (KMOD_NUM | KMOD_SHIFT))) {

View File

@@ -70,6 +70,11 @@ action_menu(struct controller *controller, int actions) {
send_keycode(controller, AKEYCODE_MENU, actions, "MENU");
}
static inline void
action_cut(struct controller *controller, int actions) {
send_keycode(controller, AKEYCODE_CUT, actions, "CUT");
}
// turn the screen on if it was off, press BACK otherwise
static void
press_back_or_turn_screen_on(struct controller *controller) {
@@ -102,9 +107,10 @@ collapse_notification_panel(struct controller *controller) {
}
static void
request_device_clipboard(struct controller *controller) {
request_device_clipboard(struct controller *controller, bool copy) {
struct control_msg msg;
msg.type = CONTROL_MSG_TYPE_GET_CLIPBOARD;
msg.get_clipboard.copy = copy;
if (!controller_push_msg(controller, &msg)) {
LOGW("Could not request device clipboard");
@@ -234,7 +240,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 +253,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;
@@ -263,66 +268,56 @@ input_manager_process_key(struct input_manager *im,
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);
// use Cmd on macOS, Ctrl on other platforms
#ifdef __APPLE__
bool cmd = !ctrl && meta;
#else
if (meta) {
// 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
bool shift = event->keysym.mod & (KMOD_LSHIFT | KMOD_RSHIFT);
if (alt) {
// no shortcuts involve Alt, and it must not be forwarded to the device
// No shortcuts involve Alt, and it is not forwarded to the device
return;
}
struct controller *controller = im->controller;
// capture all Ctrl events
if (ctrl || cmd) {
SDL_Keycode keycode = event->keysym.sym;
bool down = event->type == SDL_KEYDOWN;
SDL_Keycode keycode = event->keysym.sym;
bool down = event->type == SDL_KEYDOWN;
// Capture all Meta events
if (meta) {
if (ctrl) {
// No shortcuts involve Ctrl+Meta
return;
}
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 && ctrl && !meta && !shift && !repeat) {
if (control && !shift && !repeat) {
action_home(controller, action);
}
return;
case SDLK_b: // fall-through
case SDLK_BACKSPACE:
if (control && cmd && !shift && !repeat) {
if (control && !shift && !repeat) {
action_back(controller, action);
}
return;
case SDLK_s:
if (control && cmd && !shift && !repeat) {
if (control && !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 && ctrl && !meta && !shift && !repeat) {
if (control && !shift && !repeat) {
action_menu(controller, action);
}
return;
case SDLK_p:
if (control && cmd && !shift && !repeat) {
if (control && !shift && !repeat) {
action_power(controller, action);
}
return;
case SDLK_o:
if (control && cmd && !repeat && down) {
if (control && !repeat && down) {
enum screen_power_mode mode = shift
? SCREEN_POWER_MODE_NORMAL
: SCREEN_POWER_MODE_OFF;
@@ -330,67 +325,71 @@ input_manager_process_key(struct input_manager *im,
}
return;
case SDLK_DOWN:
if (control && cmd && !shift) {
if (control && !shift) {
// forward repeated events
action_volume_down(controller, action);
}
return;
case SDLK_UP:
if (control && cmd && !shift) {
if (control && !shift) {
// forward repeated events
action_volume_up(controller, action);
}
return;
case SDLK_LEFT:
if (cmd && !shift && !repeat && down) {
if (!shift && !repeat && down) {
rotate_client_left(im->screen);
}
return;
case SDLK_RIGHT:
if (cmd && !shift && !repeat && down) {
if (!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 && 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 && cmd && !repeat && down) {
screen_switch_fullscreen(im->screen);
if (control && !shift && !repeat && down) {
// Press COPY, then get the clipboard content
request_device_clipboard(controller, true);
}
return;
case SDLK_x:
if (!shift && cmd && !repeat && down) {
if (control && !shift && !repeat && down) {
// For convenience (especially on macOS), bind Meta+x to
// CUT (even if it is already accessible by pressing Ctrl+x
// on the device)
action_cut(controller, action);
}
return;
case SDLK_v:
if (control && !shift && !repeat && down) {
// Inject the text as input events
clipboard_paste(controller);
}
return;
case SDLK_f:
if (!shift && !repeat && down) {
screen_switch_fullscreen(im->screen);
}
return;
case SDLK_w:
if (!shift && !repeat && down) {
screen_resize_to_fit(im->screen);
}
return;
case SDLK_g:
if (!shift && cmd && !repeat && down) {
if (!shift && !repeat && down) {
screen_resize_to_pixel_perfect(im->screen);
}
return;
case SDLK_i:
if (!shift && cmd && !repeat && down) {
if (!shift && !repeat && down) {
struct fps_counter *fps_counter =
im->video_buffer->fps_counter;
switch_fps_counter_state(fps_counter);
}
return;
case SDLK_n:
if (control && cmd && !repeat && down) {
if (control && !repeat && down) {
if (shift) {
collapse_notification_panel(controller);
} else {
@@ -399,7 +398,7 @@ input_manager_process_key(struct input_manager *im,
}
return;
case SDLK_r:
if (control && cmd && !shift && !repeat && down) {
if (control && !shift && !repeat && down) {
rotate_device(controller);
}
return;
@@ -412,14 +411,16 @@ input_manager_process_key(struct input_manager *im,
return;
}
if (event->repeat) {
++im->repeat;
} else {
im->repeat = 0;
assert(!meta);
if (ctrl && !shift && keycode == SDLK_v && down) {
// Synchronize the computer clipboard to the device clipboard before
// sending Ctrl+v
set_device_clipboard(controller, false);
}
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'");
}

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

@@ -42,7 +42,7 @@ convert_log_level_to_sdl(enum sc_log_level level) {
return SDL_LOG_PRIORITY_ERROR;
default:
assert(!"unexpected log level");
return SDL_LOG_PRIORITY_INFO;
return SC_LOG_LEVEL_INFO;
}
}
@@ -71,7 +71,7 @@ main(int argc, char *argv[]) {
}
SDL_LogPriority sdl_log = convert_log_level_to_sdl(args.opts.log_level);
SDL_LogSetPriority(SDL_LOG_CATEGORY_APPLICATION, sdl_log);
SDL_LogSetAllPriority(sdl_log);
if (args.help) {
scrcpy_print_usage(argv[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

@@ -46,7 +46,6 @@ static struct input_manager input_manager = {
.controller = &controller,
.video_buffer = &video_buffer,
.screen = &screen,
.repeat = 0,
.prefer_text = false, // initialized later
};
@@ -64,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());
@@ -114,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;
}
@@ -328,8 +321,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

@@ -43,7 +43,6 @@ struct scrcpy_options {
bool mipmaps;
bool stay_awake;
bool force_adb_forward;
bool disable_screensaver;
};
#define SCRCPY_OPTIONS_DEFAULT { \
@@ -82,7 +81,6 @@ struct scrcpy_options {
.mipmaps = true, \
.stay_awake = false, \
.force_adb_forward = false, \
.disable_screensaver = false, \
}
bool

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);
@@ -189,14 +185,18 @@ static void test_serialize_collapse_notification_panel(void) {
static void test_serialize_get_clipboard(void) {
struct control_msg msg = {
.type = CONTROL_MSG_TYPE_GET_CLIPBOARD,
.get_clipboard = {
.copy = true,
},
};
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);
assert(size == 2);
const unsigned char expected[] = {
CONTROL_MSG_TYPE_GET_CLIPBOARD,
1, // copy
};
assert(!memcmp(buf, expected, sizeof(expected)));
}
@@ -210,14 +210,14 @@ static void test_serialize_set_clipboard(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 == 19);
assert(size == 17);
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 +231,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 +247,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

@@ -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,16 @@ public final class ControlMessage {
private Position position;
private int hScroll;
private int vScroll;
private int flags;
private int repeat;
private boolean pressCopyOrPaste;
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 +69,18 @@ public final class ControlMessage {
return msg;
}
public static ControlMessage createGetClipboard(boolean copy) {
ControlMessage msg = new ControlMessage();
msg.type = TYPE_GET_CLIPBOARD;
msg.pressCopyOrPaste = copy;
return msg;
}
public static ControlMessage createSetClipboard(String text, boolean paste) {
ControlMessage msg = new ControlMessage();
msg.type = TYPE_SET_CLIPBOARD;
msg.text = text;
if (paste) {
msg.flags = FLAGS_PASTE;
}
msg.pressCopyOrPaste = paste;
return msg;
}
@@ -143,11 +144,7 @@ public final class ControlMessage {
return vScroll;
}
public int getFlags() {
return flags;
}
public int getRepeat() {
return repeat;
public boolean getPressCopyOrPaste() {
return pressCopyOrPaste;
}
}

View File

@@ -8,19 +8,21 @@ 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 GET_CLIPBOARD_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 = 4092; // 4096 - 1 (type) - 1 (parse flag) - 2 (length)
public static final int INJECT_TEXT_MAX_LENGTH = 300;
private final byte[] rawBuffer = new byte[MESSAGE_MAX_SIZE];
private static final int RAW_BUFFER_SIZE = 4096;
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
@@ -66,6 +68,9 @@ public class ControlMessageReader {
case ControlMessage.TYPE_INJECT_SCROLL_EVENT:
msg = parseInjectScrollEvent();
break;
case ControlMessage.TYPE_GET_CLIPBOARD:
msg = parseGetClipboard();
break;
case ControlMessage.TYPE_SET_CLIPBOARD:
msg = parseSetClipboard();
break;
@@ -75,7 +80,6 @@ public class ControlMessageReader {
case ControlMessage.TYPE_BACK_OR_SCREEN_ON:
case ControlMessage.TYPE_EXPAND_NOTIFICATION_PANEL:
case ControlMessage.TYPE_COLLAPSE_NOTIFICATION_PANEL:
case ControlMessage.TYPE_GET_CLIPBOARD:
case ControlMessage.TYPE_ROTATE_DEVICE:
msg = ControlMessage.createEmpty(type);
break;
@@ -98,23 +102,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() {
@@ -150,6 +151,14 @@ public class ControlMessageReader {
return ControlMessage.createInjectScrollEvent(position, hScroll, vScroll);
}
private ControlMessage parseGetClipboard() {
if (buffer.remaining() < GET_CLIPBOARD_PAYLOAD_LENGTH) {
return null;
}
boolean copy = buffer.get() != 0;
return ControlMessage.createGetClipboard(copy);
}
private ControlMessage parseSetClipboard() {
if (buffer.remaining() < SET_CLIPBOARD_FIXED_PAYLOAD_LENGTH) {
return null;

View File

@@ -74,7 +74,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:
@@ -104,14 +104,10 @@ public class Controller {
device.collapsePanels();
break;
case ControlMessage.TYPE_GET_CLIPBOARD:
String clipboardText = device.getClipboardText();
if (clipboardText != null) {
sender.pushClipboardText(clipboardText);
}
getClipboard(msg.getPressCopyOrPaste());
break;
case ControlMessage.TYPE_SET_CLIPBOARD:
boolean paste = (msg.getFlags() & ControlMessage.FLAGS_PASTE) != 0;
setClipboard(msg.getText(), paste);
setClipboard(msg.getText(), msg.getPressCopyOrPaste());
break;
case ControlMessage.TYPE_SET_SCREEN_POWER_MODE:
if (device.supportsInputEvents()) {
@@ -130,8 +126,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 device.injectKeyEvent(action, keycode, 0, metaState);
}
private boolean injectChar(char c) {
@@ -229,6 +225,23 @@ public class Controller {
return device.injectKeycode(keycode);
}
private boolean getClipboard(boolean copy) {
// On Android >= 7, also press the COPY key if requested
if (copy && Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && device.supportsInputEvents()) {
// If there is something to copy, the clipboard will be automatically sent to the computer clipboard via the ClipboardListener
return device.injectKeycode(KeyEvent.KEYCODE_COPY);
}
// We can't press COPY, so only synchronize the current clipboard
String clipboardText = device.getClipboardText();
if (clipboardText != null) {
sender.pushClipboardText(clipboardText);
return true;
}
return false;
}
private boolean setClipboard(String text, boolean paste) {
boolean ok = device.setClipboardText(text);
if (ok) {

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

@@ -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();
@@ -202,6 +200,7 @@ public class ControlMessageReaderTest {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
DataOutputStream dos = new DataOutputStream(bos);
dos.writeByte(ControlMessage.TYPE_GET_CLIPBOARD);
dos.writeByte(1); // copy
byte[] packet = bos.toByteArray();
@@ -209,6 +208,7 @@ public class ControlMessageReaderTest {
ControlMessage event = reader.next();
Assert.assertEquals(ControlMessage.TYPE_GET_CLIPBOARD, event.getType());
Assert.assertTrue(event.getPressCopyOrPaste());
}
@Test
@@ -220,7 +220,7 @@ public class ControlMessageReaderTest {
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,9 +230,7 @@ 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);
Assert.assertTrue(event.getPressCopyOrPaste());
}
@Test
@@ -248,7 +246,7 @@ public class ControlMessageReaderTest {
Arrays.fill(rawText, (byte) 'a');
String text = new String(rawText, 0, rawText.length);
dos.writeInt(rawText.length);
dos.writeShort(rawText.length);
dos.write(rawText);
byte[] packet = bos.toByteArray();
@@ -258,9 +256,7 @@ public class ControlMessageReaderTest {
Assert.assertEquals(ControlMessage.TYPE_SET_CLIPBOARD, event.getType());
Assert.assertEquals(text, event.getText());
boolean parse = (event.getFlags() & ControlMessage.FLAGS_PASTE) != 0;
Assert.assertTrue(parse);
Assert.assertTrue(event.getPressCopyOrPaste());
}
@Test
@@ -310,13 +306,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 +320,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 +339,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 +351,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 +358,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 +367,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();