Compare commits
18 Commits
left_ctrl
...
clipboard.
Author | SHA1 | Date | |
---|---|---|---|
163c161731 | |||
c452c20662 | |||
bc83d97113 | |||
b5bdc7c245 | |||
70de431798 | |||
34e4f08f06 | |||
7cc68e9031 | |||
e3070fe62c | |||
14211e2e2e | |||
fd0fb71c6a | |||
c80f94e4a8 | |||
cdc6eb8d19 | |||
93e1708b69 | |||
f231488310 | |||
5882d23ee2 | |||
1689bd2bb2 | |||
c65b3f1f0b | |||
0d81d245e2 |
30
README.md
30
README.md
@ -490,17 +490,19 @@ requested orientation.
|
|||||||
|
|
||||||
#### Copy-paste
|
#### Copy-paste
|
||||||
|
|
||||||
It is possible to synchronize clipboards between the computer and the device, in
|
Any time the Android clipboard changes, it is automatically synchronized to the
|
||||||
both directions:
|
computer clipboard.
|
||||||
|
|
||||||
- `RCtrl`+`c` copies the device clipboard to the computer clipboard;
|
`Ctrl`+`c` (copy), `Ctrl`+`x` (cut) and `LCtrl`+`v` (paste) work as you expect.
|
||||||
- `RCtrl`+`Shift`+`v` copies the computer clipboard to the device clipboard
|
|
||||||
(and pastes if the device runs Android >= 7);
|
In addition, `RCtrl`+`v` allows to inject the computer clipboard content as a
|
||||||
- `RCtrl`+`v` _pastes_ the computer clipboard as a sequence of text events (but
|
sequence of text events . This is useful when the component does not accept text
|
||||||
breaks non-ASCII characters).
|
pasting (for example in _Termux_), but it can break non-ASCII content.
|
||||||
|
|
||||||
|
Another shortcut, `RCtrl`+`Shift`+`v`, only copies the computer clipboard to the
|
||||||
|
device clipboard. This is useful when `Ctrl`+`v` does not paste on the device
|
||||||
|
(for example, again, in _Termux_).
|
||||||
|
|
||||||
Moreover, any time the Android clipboard changes, it is automatically
|
|
||||||
synchronized to the computer clipboard.
|
|
||||||
|
|
||||||
#### Text injection preference
|
#### Text injection preference
|
||||||
|
|
||||||
@ -563,13 +565,16 @@ Also see [issue #14].
|
|||||||
`RCtrl` is the right `Ctrl` key (the left `Ctrl` key is forwarded to the
|
`RCtrl` is the right `Ctrl` key (the left `Ctrl` key is forwarded to the
|
||||||
device).
|
device).
|
||||||
|
|
||||||
|
On macOS, `Cmd` also works (for shortcuts which are not already captured by the
|
||||||
|
system).
|
||||||
|
|
||||||
| Action | Shortcut
|
| Action | Shortcut
|
||||||
| ------------------------------------------- |:-----------------------------
|
| ------------------------------------------- |:-----------------------------
|
||||||
| Switch fullscreen mode | `RCtrl`+`f`
|
| Switch fullscreen mode | `RCtrl`+`f`
|
||||||
| Rotate display left | `RCtrl`+`←`
|
| Rotate display left | `RCtrl`+`←`
|
||||||
| Rotate display right | `RCtrl`+`→`
|
| Rotate display right | `RCtrl`+`→`
|
||||||
| Resize window to 1:1 (pixel-perfect) | `RCtrl`+`g`
|
| Resize window to 1:1 (pixel-perfect) | `RCtrl`+`g`
|
||||||
| Resize window to remove black borders | `RCtrl`+`x` \| _Double-click¹_
|
| Resize window to remove black borders | `RCtrl`+`w` \| _Double-click¹_
|
||||||
| Click on `HOME` | `RCtrl`+`h` \| _Middle-click_
|
| Click on `HOME` | `RCtrl`+`h` \| _Middle-click_
|
||||||
| Click on `BACK` | `RCtrl`+`b` \| _Right-click²_
|
| Click on `BACK` | `RCtrl`+`b` \| _Right-click²_
|
||||||
| Click on `APP_SWITCH` | `RCtrl`+`s`
|
| Click on `APP_SWITCH` | `RCtrl`+`s`
|
||||||
@ -583,9 +588,8 @@ device).
|
|||||||
| Rotate device screen | `RCtrl`+`r`
|
| Rotate device screen | `RCtrl`+`r`
|
||||||
| Expand notification panel | `RCtrl`+`n`
|
| Expand notification panel | `RCtrl`+`n`
|
||||||
| Collapse notification panel | `RCtrl`+`Shift`+`n`
|
| Collapse notification panel | `RCtrl`+`Shift`+`n`
|
||||||
| Copy device clipboard to computer | `RCtrl`+`c`
|
| Inject computer clipboard text | `RCtrl`+`v`
|
||||||
| Paste computer clipboard to device | `RCtrl`+`v`
|
| Copy computer clipboard to device | `RCtrl`+`Shift`+`v`
|
||||||
| Copy computer clipboard to device and paste | `RCtrl`+`Shift`+`v`
|
|
||||||
| Enable/disable FPS counter (on stdout) | `RCtrl`+`i`
|
| Enable/disable FPS counter (on stdout) | `RCtrl`+`i`
|
||||||
|
|
||||||
_¹Double-click on black borders to remove them._
|
_¹Double-click on black borders to remove them._
|
||||||
|
@ -164,12 +164,12 @@ if get_option('buildtype') == 'debug'
|
|||||||
'src/cli.c',
|
'src/cli.c',
|
||||||
'src/util/str_util.c',
|
'src/util/str_util.c',
|
||||||
]],
|
]],
|
||||||
['test_control_event_serialize', [
|
['test_control_msg_serialize', [
|
||||||
'tests/test_control_msg_serialize.c',
|
'tests/test_control_msg_serialize.c',
|
||||||
'src/control_msg.c',
|
'src/control_msg.c',
|
||||||
'src/util/str_util.c',
|
'src/util/str_util.c',
|
||||||
]],
|
]],
|
||||||
['test_device_event_deserialize', [
|
['test_device_msg_deserialize', [
|
||||||
'tests/test_device_msg_deserialize.c',
|
'tests/test_device_msg_deserialize.c',
|
||||||
'src/device_msg.c',
|
'src/device_msg.c',
|
||||||
]],
|
]],
|
||||||
|
10
app/scrcpy.1
10
app/scrcpy.1
@ -222,7 +222,7 @@ Rotate display right
|
|||||||
Resize window to 1:1 (pixel\-perfect)
|
Resize window to 1:1 (pixel\-perfect)
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
.B RCtrl+x, Double\-click on black borders
|
.B RCtrl+w, Double\-click on black borders
|
||||||
Resize window to remove black borders
|
Resize window to remove black borders
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
@ -277,17 +277,13 @@ Expand notification panel
|
|||||||
.B RCtrl+Shift+n
|
.B RCtrl+Shift+n
|
||||||
Collapse notification panel
|
Collapse notification panel
|
||||||
|
|
||||||
.TP
|
|
||||||
.B RCtrl+c
|
|
||||||
Copy device clipboard to computer
|
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
.B RCtrl+v
|
.B RCtrl+v
|
||||||
Paste computer clipboard to device
|
Inject computer clipboard text
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
.B RCtrl+Shift+v
|
.B RCtrl+Shift+v
|
||||||
Copy computer clipboard to device (and paste if the device runs Android >= 7)
|
Copy computer clipboard to device
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
.B RCtrl+i
|
.B RCtrl+i
|
||||||
|
@ -196,7 +196,7 @@ scrcpy_print_usage(const char *arg0) {
|
|||||||
" RCtrl+g\n"
|
" RCtrl+g\n"
|
||||||
" Resize window to 1:1 (pixel-perfect)\n"
|
" Resize window to 1:1 (pixel-perfect)\n"
|
||||||
"\n"
|
"\n"
|
||||||
" RCtrl+x\n"
|
" RCtrl+w\n"
|
||||||
" Double-click on black borders\n"
|
" Double-click on black borders\n"
|
||||||
" Resize window to remove black borders\n"
|
" Resize window to remove black borders\n"
|
||||||
"\n"
|
"\n"
|
||||||
@ -242,15 +242,11 @@ scrcpy_print_usage(const char *arg0) {
|
|||||||
" RCtrl+Shift+n\n"
|
" RCtrl+Shift+n\n"
|
||||||
" Collapse notification panel\n"
|
" Collapse notification panel\n"
|
||||||
"\n"
|
"\n"
|
||||||
" RCtrl+c\n"
|
|
||||||
" Copy device clipboard to computer\n"
|
|
||||||
"\n"
|
|
||||||
" RCtrl+v\n"
|
" RCtrl+v\n"
|
||||||
" Paste computer clipboard to device\n"
|
" Inject computer clipboard text\n"
|
||||||
"\n"
|
"\n"
|
||||||
" RCtrl+Shift+v\n"
|
" RCtrl+Shift+v\n"
|
||||||
" Copy computer clipboard to device (and paste if the device\n"
|
" Copy computer clipboard to device\n"
|
||||||
" runs Android >= 7)\n"
|
|
||||||
"\n"
|
"\n"
|
||||||
" RCtrl+i\n"
|
" RCtrl+i\n"
|
||||||
" Enable/disable FPS counter (print frames/second in logs)\n"
|
" Enable/disable FPS counter (print frames/second in logs)\n"
|
||||||
|
@ -20,9 +20,9 @@ write_position(uint8_t *buf, const struct position *position) {
|
|||||||
static size_t
|
static size_t
|
||||||
write_string(const char *utf8, size_t max_len, unsigned char *buf) {
|
write_string(const char *utf8, size_t max_len, unsigned char *buf) {
|
||||||
size_t len = utf8_truncation_index(utf8, max_len);
|
size_t len = utf8_truncation_index(utf8, max_len);
|
||||||
buffer_write16be(buf, (uint16_t) len);
|
buffer_write32be(buf, len);
|
||||||
memcpy(&buf[2], utf8, len);
|
memcpy(&buf[4], utf8, len);
|
||||||
return 2 + len;
|
return 4 + len;
|
||||||
}
|
}
|
||||||
|
|
||||||
static uint16_t
|
static uint16_t
|
||||||
@ -67,11 +67,10 @@ control_msg_serialize(const struct control_msg *msg, unsigned char *buf) {
|
|||||||
(uint32_t) msg->inject_scroll_event.vscroll);
|
(uint32_t) msg->inject_scroll_event.vscroll);
|
||||||
return 21;
|
return 21;
|
||||||
case CONTROL_MSG_TYPE_SET_CLIPBOARD: {
|
case CONTROL_MSG_TYPE_SET_CLIPBOARD: {
|
||||||
buf[1] = !!msg->set_clipboard.paste;
|
|
||||||
size_t len = write_string(msg->set_clipboard.text,
|
size_t len = write_string(msg->set_clipboard.text,
|
||||||
CONTROL_MSG_CLIPBOARD_TEXT_MAX_LENGTH,
|
CONTROL_MSG_CLIPBOARD_TEXT_MAX_LENGTH,
|
||||||
&buf[2]);
|
&buf[1]);
|
||||||
return 2 + len;
|
return 1 + len;
|
||||||
}
|
}
|
||||||
case CONTROL_MSG_TYPE_SET_SCREEN_POWER_MODE:
|
case CONTROL_MSG_TYPE_SET_SCREEN_POWER_MODE:
|
||||||
buf[1] = msg->set_screen_power_mode.mode;
|
buf[1] = msg->set_screen_power_mode.mode;
|
||||||
|
@ -10,10 +10,11 @@
|
|||||||
#include "android/keycodes.h"
|
#include "android/keycodes.h"
|
||||||
#include "common.h"
|
#include "common.h"
|
||||||
|
|
||||||
|
#define CONTROL_MSG_MAX_SIZE (1 << 18) // 256k
|
||||||
|
|
||||||
#define CONTROL_MSG_INJECT_TEXT_MAX_LENGTH 300
|
#define CONTROL_MSG_INJECT_TEXT_MAX_LENGTH 300
|
||||||
#define CONTROL_MSG_CLIPBOARD_TEXT_MAX_LENGTH 4092
|
// type: 1 byte; length: 4 bytes
|
||||||
#define CONTROL_MSG_SERIALIZED_MAX_SIZE \
|
#define CONTROL_MSG_CLIPBOARD_TEXT_MAX_LENGTH (CONTROL_MSG_MAX_SIZE - 5)
|
||||||
(4 + CONTROL_MSG_CLIPBOARD_TEXT_MAX_LENGTH)
|
|
||||||
|
|
||||||
#define POINTER_ID_MOUSE UINT64_C(-1);
|
#define POINTER_ID_MOUSE UINT64_C(-1);
|
||||||
|
|
||||||
@ -62,7 +63,6 @@ struct control_msg {
|
|||||||
} inject_scroll_event;
|
} inject_scroll_event;
|
||||||
struct {
|
struct {
|
||||||
char *text; // owned, to be freed by SDL_free()
|
char *text; // owned, to be freed by SDL_free()
|
||||||
bool paste;
|
|
||||||
} set_clipboard;
|
} set_clipboard;
|
||||||
struct {
|
struct {
|
||||||
enum screen_power_mode mode;
|
enum screen_power_mode mode;
|
||||||
@ -70,7 +70,7 @@ struct control_msg {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
// buf size must be at least CONTROL_MSG_SERIALIZED_MAX_SIZE
|
// buf size must be at least CONTROL_MSG_MAX_SIZE
|
||||||
// return the number of bytes written
|
// return the number of bytes written
|
||||||
size_t
|
size_t
|
||||||
control_msg_serialize(const struct control_msg *msg, unsigned char *buf);
|
control_msg_serialize(const struct control_msg *msg, unsigned char *buf);
|
||||||
|
@ -60,7 +60,7 @@ controller_push_msg(struct controller *controller,
|
|||||||
static bool
|
static bool
|
||||||
process_msg(struct controller *controller,
|
process_msg(struct controller *controller,
|
||||||
const struct control_msg *msg) {
|
const struct control_msg *msg) {
|
||||||
unsigned char serialized_msg[CONTROL_MSG_SERIALIZED_MAX_SIZE];
|
static unsigned char serialized_msg[CONTROL_MSG_MAX_SIZE];
|
||||||
int length = control_msg_serialize(msg, serialized_msg);
|
int length = control_msg_serialize(msg, serialized_msg);
|
||||||
if (!length) {
|
if (!length) {
|
||||||
return false;
|
return false;
|
||||||
|
@ -9,7 +9,7 @@
|
|||||||
ssize_t
|
ssize_t
|
||||||
device_msg_deserialize(const unsigned char *buf, size_t len,
|
device_msg_deserialize(const unsigned char *buf, size_t len,
|
||||||
struct device_msg *msg) {
|
struct device_msg *msg) {
|
||||||
if (len < 3) {
|
if (len < 5) {
|
||||||
// at least type + empty string length
|
// at least type + empty string length
|
||||||
return 0; // not available
|
return 0; // not available
|
||||||
}
|
}
|
||||||
@ -17,8 +17,8 @@ device_msg_deserialize(const unsigned char *buf, size_t len,
|
|||||||
msg->type = buf[0];
|
msg->type = buf[0];
|
||||||
switch (msg->type) {
|
switch (msg->type) {
|
||||||
case DEVICE_MSG_TYPE_CLIPBOARD: {
|
case DEVICE_MSG_TYPE_CLIPBOARD: {
|
||||||
uint16_t clipboard_len = buffer_read16be(&buf[1]);
|
size_t clipboard_len = buffer_read32be(&buf[1]);
|
||||||
if (clipboard_len > len - 3) {
|
if (clipboard_len > len - 5) {
|
||||||
return 0; // not available
|
return 0; // not available
|
||||||
}
|
}
|
||||||
char *text = SDL_malloc(clipboard_len + 1);
|
char *text = SDL_malloc(clipboard_len + 1);
|
||||||
@ -27,12 +27,12 @@ device_msg_deserialize(const unsigned char *buf, size_t len,
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
if (clipboard_len) {
|
if (clipboard_len) {
|
||||||
memcpy(text, &buf[3], clipboard_len);
|
memcpy(text, &buf[5], clipboard_len);
|
||||||
}
|
}
|
||||||
text[clipboard_len] = '\0';
|
text[clipboard_len] = '\0';
|
||||||
|
|
||||||
msg->clipboard.text = text;
|
msg->clipboard.text = text;
|
||||||
return 3 + clipboard_len;
|
return 5 + clipboard_len;
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
LOGW("Unknown device message type: %d", (int) msg->type);
|
LOGW("Unknown device message type: %d", (int) msg->type);
|
||||||
|
@ -7,8 +7,9 @@
|
|||||||
|
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
|
|
||||||
#define DEVICE_MSG_TEXT_MAX_LENGTH 4093
|
#define DEVICE_MSG_MAX_SIZE (1 << 18) // 256k
|
||||||
#define DEVICE_MSG_SERIALIZED_MAX_SIZE (3 + DEVICE_MSG_TEXT_MAX_LENGTH)
|
// type: 1 byte; length: 4 bytes
|
||||||
|
#define DEVICE_MSG_TEXT_MAX_LENGTH (DEVICE_MSG_MAX_SIZE - 5)
|
||||||
|
|
||||||
enum device_msg_type {
|
enum device_msg_type {
|
||||||
DEVICE_MSG_TYPE_CLIPBOARD,
|
DEVICE_MSG_TYPE_CLIPBOARD,
|
||||||
|
@ -102,17 +102,7 @@ collapse_notification_panel(struct controller *controller) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
request_device_clipboard(struct controller *controller) {
|
set_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();
|
char *text = SDL_GetClipboardText();
|
||||||
if (!text) {
|
if (!text) {
|
||||||
LOGW("Could not get clipboard text: %s", SDL_GetError());
|
LOGW("Could not get clipboard text: %s", SDL_GetError());
|
||||||
@ -127,7 +117,6 @@ set_device_clipboard(struct controller *controller, bool paste) {
|
|||||||
struct control_msg msg;
|
struct control_msg msg;
|
||||||
msg.type = CONTROL_MSG_TYPE_SET_CLIPBOARD;
|
msg.type = CONTROL_MSG_TYPE_SET_CLIPBOARD;
|
||||||
msg.set_clipboard.text = text;
|
msg.set_clipboard.text = text;
|
||||||
msg.set_clipboard.paste = paste;
|
|
||||||
|
|
||||||
if (!controller_push_msg(controller, &msg)) {
|
if (!controller_push_msg(controller, &msg)) {
|
||||||
SDL_free(text);
|
SDL_free(text);
|
||||||
@ -252,6 +241,25 @@ convert_input_key(const SDL_KeyboardEvent *from, struct control_msg *to,
|
|||||||
return true;
|
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
|
void
|
||||||
input_manager_process_key(struct input_manager *im,
|
input_manager_process_key(struct input_manager *im,
|
||||||
const SDL_KeyboardEvent *event,
|
const SDL_KeyboardEvent *event,
|
||||||
@ -259,25 +267,36 @@ input_manager_process_key(struct input_manager *im,
|
|||||||
// control: indicates the state of the command-line option --no-control
|
// control: indicates the state of the command-line option --no-control
|
||||||
// ctrl: the Ctrl key
|
// ctrl: the Ctrl key
|
||||||
|
|
||||||
|
bool lctrl = event->keysym.mod & KMOD_LCTRL;
|
||||||
bool rctrl = event->keysym.mod & KMOD_RCTRL;
|
bool rctrl = event->keysym.mod & KMOD_RCTRL;
|
||||||
bool alt = event->keysym.mod & (KMOD_LALT | KMOD_RALT);
|
bool alt = event->keysym.mod & (KMOD_LALT | KMOD_RALT);
|
||||||
bool meta = event->keysym.mod & (KMOD_LGUI | KMOD_RGUI);
|
bool meta = event->keysym.mod & (KMOD_LGUI | KMOD_RGUI);
|
||||||
|
bool shift = event->keysym.mod & (KMOD_LSHIFT | KMOD_RSHIFT);
|
||||||
|
|
||||||
if (alt || meta) {
|
bool shortcut_key = rctrl;
|
||||||
// No shortcuts involve Alt or Meta, and they are not forwarded to the
|
#ifdef __APPLE__
|
||||||
// device
|
shortcut_key |= meta;
|
||||||
|
#else
|
||||||
|
if (meta) {
|
||||||
|
// No shortcut involve Meta, and it is not forwarded to the device
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (alt) {
|
||||||
|
// No shortcuts involve Alt, and it is not forwarded to the device
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct controller *controller = im->controller;
|
struct controller *controller = im->controller;
|
||||||
|
|
||||||
|
SDL_Keycode keycode = event->keysym.sym;
|
||||||
|
bool down = event->type == SDL_KEYDOWN;
|
||||||
|
|
||||||
// Capture all RCtrl events
|
// Capture all RCtrl events
|
||||||
if (rctrl) {
|
if (shortcut_key) {
|
||||||
SDL_Keycode keycode = event->keysym.sym;
|
|
||||||
bool down = event->type == SDL_KEYDOWN;
|
|
||||||
int action = down ? ACTION_DOWN : ACTION_UP;
|
int action = down ? ACTION_DOWN : ACTION_UP;
|
||||||
bool repeat = event->repeat;
|
bool repeat = event->repeat;
|
||||||
bool shift = event->keysym.mod & (KMOD_LSHIFT | KMOD_RSHIFT);
|
|
||||||
switch (keycode) {
|
switch (keycode) {
|
||||||
case SDLK_h:
|
case SDLK_h:
|
||||||
// Ctrl+h on all platform, since Cmd+h is already captured by
|
// Ctrl+h on all platform, since Cmd+h is already captured by
|
||||||
@ -339,16 +358,11 @@ input_manager_process_key(struct input_manager *im,
|
|||||||
rotate_client_right(im->screen);
|
rotate_client_right(im->screen);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
case SDLK_c:
|
|
||||||
if (control && !shift && !repeat && down) {
|
|
||||||
request_device_clipboard(controller);
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
case SDLK_v:
|
case SDLK_v:
|
||||||
if (control && !repeat && down) {
|
if (control && !repeat && down) {
|
||||||
if (shift) {
|
if (shift) {
|
||||||
// store the text in the device clipboard and paste
|
// store the text in the device clipboard
|
||||||
set_device_clipboard(controller, true);
|
set_device_clipboard(controller);
|
||||||
} else {
|
} else {
|
||||||
// inject the text as input events
|
// inject the text as input events
|
||||||
clipboard_paste(controller);
|
clipboard_paste(controller);
|
||||||
@ -360,7 +374,7 @@ input_manager_process_key(struct input_manager *im,
|
|||||||
screen_switch_fullscreen(im->screen);
|
screen_switch_fullscreen(im->screen);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
case SDLK_x:
|
case SDLK_w:
|
||||||
if (!shift && !repeat && down) {
|
if (!shift && !repeat && down) {
|
||||||
screen_resize_to_fit(im->screen);
|
screen_resize_to_fit(im->screen);
|
||||||
}
|
}
|
||||||
@ -391,6 +405,15 @@ input_manager_process_key(struct input_manager *im,
|
|||||||
rotate_device(controller);
|
rotate_device(controller);
|
||||||
}
|
}
|
||||||
return;
|
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;
|
return;
|
||||||
@ -400,6 +423,12 @@ input_manager_process_key(struct input_manager *im,
|
|||||||
return;
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
struct control_msg msg;
|
struct control_msg msg;
|
||||||
if (convert_input_key(event, &msg, im->prefer_text)) {
|
if (convert_input_key(event, &msg, im->prefer_text)) {
|
||||||
if (!controller_push_msg(controller, &msg)) {
|
if (!controller_push_msg(controller, &msg)) {
|
||||||
|
@ -60,28 +60,29 @@ static int
|
|||||||
run_receiver(void *data) {
|
run_receiver(void *data) {
|
||||||
struct receiver *receiver = data;
|
struct receiver *receiver = data;
|
||||||
|
|
||||||
unsigned char buf[DEVICE_MSG_SERIALIZED_MAX_SIZE];
|
static unsigned char buf[DEVICE_MSG_MAX_SIZE];
|
||||||
size_t head = 0;
|
size_t head = 0;
|
||||||
|
|
||||||
for (;;) {
|
for (;;) {
|
||||||
assert(head < DEVICE_MSG_SERIALIZED_MAX_SIZE);
|
assert(head < DEVICE_MSG_MAX_SIZE);
|
||||||
ssize_t r = net_recv(receiver->control_socket, buf,
|
ssize_t r = net_recv(receiver->control_socket, buf + head,
|
||||||
DEVICE_MSG_SERIALIZED_MAX_SIZE - head);
|
DEVICE_MSG_MAX_SIZE - head);
|
||||||
if (r <= 0) {
|
if (r <= 0) {
|
||||||
LOGD("Receiver stopped");
|
LOGD("Receiver stopped");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
ssize_t consumed = process_msgs(buf, r);
|
head += r;
|
||||||
|
ssize_t consumed = process_msgs(buf, head);
|
||||||
if (consumed == -1) {
|
if (consumed == -1) {
|
||||||
// an error occurred
|
// an error occurred
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (consumed) {
|
if (consumed) {
|
||||||
|
head -= consumed;
|
||||||
// shift the remaining data in the buffer
|
// shift the remaining data in the buffer
|
||||||
memmove(buf, &buf[consumed], r - consumed);
|
memmove(buf, &buf[consumed], head);
|
||||||
head = r - consumed;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -13,7 +13,7 @@ static void test_serialize_inject_keycode(void) {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
unsigned char buf[CONTROL_MSG_SERIALIZED_MAX_SIZE];
|
unsigned char buf[CONTROL_MSG_MAX_SIZE];
|
||||||
int size = control_msg_serialize(&msg, buf);
|
int size = control_msg_serialize(&msg, buf);
|
||||||
assert(size == 10);
|
assert(size == 10);
|
||||||
|
|
||||||
@ -34,13 +34,13 @@ static void test_serialize_inject_text(void) {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
unsigned char buf[CONTROL_MSG_SERIALIZED_MAX_SIZE];
|
unsigned char buf[CONTROL_MSG_MAX_SIZE];
|
||||||
int size = control_msg_serialize(&msg, buf);
|
int size = control_msg_serialize(&msg, buf);
|
||||||
assert(size == 16);
|
assert(size == 18);
|
||||||
|
|
||||||
const unsigned char expected[] = {
|
const unsigned char expected[] = {
|
||||||
CONTROL_MSG_TYPE_INJECT_TEXT,
|
CONTROL_MSG_TYPE_INJECT_TEXT,
|
||||||
0x00, 0x0d, // text length
|
0x00, 0x00, 0x00, 0x0d, // text length
|
||||||
'h', 'e', 'l', 'l', 'o', ',', ' ', 'w', 'o', 'r', 'l', 'd', '!', // text
|
'h', 'e', 'l', 'l', 'o', ',', ' ', 'w', 'o', 'r', 'l', 'd', '!', // text
|
||||||
};
|
};
|
||||||
assert(!memcmp(buf, expected, sizeof(expected)));
|
assert(!memcmp(buf, expected, sizeof(expected)));
|
||||||
@ -54,15 +54,17 @@ static void test_serialize_inject_text_long(void) {
|
|||||||
text[CONTROL_MSG_INJECT_TEXT_MAX_LENGTH] = '\0';
|
text[CONTROL_MSG_INJECT_TEXT_MAX_LENGTH] = '\0';
|
||||||
msg.inject_text.text = text;
|
msg.inject_text.text = text;
|
||||||
|
|
||||||
unsigned char buf[CONTROL_MSG_SERIALIZED_MAX_SIZE];
|
unsigned char buf[CONTROL_MSG_MAX_SIZE];
|
||||||
int size = control_msg_serialize(&msg, buf);
|
int size = control_msg_serialize(&msg, buf);
|
||||||
assert(size == 3 + CONTROL_MSG_INJECT_TEXT_MAX_LENGTH);
|
assert(size == 5 + CONTROL_MSG_INJECT_TEXT_MAX_LENGTH);
|
||||||
|
|
||||||
unsigned char expected[3 + CONTROL_MSG_INJECT_TEXT_MAX_LENGTH];
|
unsigned char expected[5 + CONTROL_MSG_INJECT_TEXT_MAX_LENGTH];
|
||||||
expected[0] = CONTROL_MSG_TYPE_INJECT_TEXT;
|
expected[0] = CONTROL_MSG_TYPE_INJECT_TEXT;
|
||||||
expected[1] = 0x01;
|
expected[1] = 0x00;
|
||||||
expected[2] = 0x2c; // text length (16 bits)
|
expected[2] = 0x00;
|
||||||
memset(&expected[3], 'a', CONTROL_MSG_INJECT_TEXT_MAX_LENGTH);
|
expected[3] = 0x01;
|
||||||
|
expected[4] = 0x2c; // text length (32 bits)
|
||||||
|
memset(&expected[5], 'a', CONTROL_MSG_INJECT_TEXT_MAX_LENGTH);
|
||||||
|
|
||||||
assert(!memcmp(buf, expected, sizeof(expected)));
|
assert(!memcmp(buf, expected, sizeof(expected)));
|
||||||
}
|
}
|
||||||
@ -88,7 +90,7 @@ static void test_serialize_inject_touch_event(void) {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
unsigned char buf[CONTROL_MSG_SERIALIZED_MAX_SIZE];
|
unsigned char buf[CONTROL_MSG_MAX_SIZE];
|
||||||
int size = control_msg_serialize(&msg, buf);
|
int size = control_msg_serialize(&msg, buf);
|
||||||
assert(size == 28);
|
assert(size == 28);
|
||||||
|
|
||||||
@ -123,7 +125,7 @@ static void test_serialize_inject_scroll_event(void) {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
unsigned char buf[CONTROL_MSG_SERIALIZED_MAX_SIZE];
|
unsigned char buf[CONTROL_MSG_MAX_SIZE];
|
||||||
int size = control_msg_serialize(&msg, buf);
|
int size = control_msg_serialize(&msg, buf);
|
||||||
assert(size == 21);
|
assert(size == 21);
|
||||||
|
|
||||||
@ -142,7 +144,7 @@ static void test_serialize_back_or_screen_on(void) {
|
|||||||
.type = CONTROL_MSG_TYPE_BACK_OR_SCREEN_ON,
|
.type = CONTROL_MSG_TYPE_BACK_OR_SCREEN_ON,
|
||||||
};
|
};
|
||||||
|
|
||||||
unsigned char buf[CONTROL_MSG_SERIALIZED_MAX_SIZE];
|
unsigned char buf[CONTROL_MSG_MAX_SIZE];
|
||||||
int size = control_msg_serialize(&msg, buf);
|
int size = control_msg_serialize(&msg, buf);
|
||||||
assert(size == 1);
|
assert(size == 1);
|
||||||
|
|
||||||
@ -157,7 +159,7 @@ static void test_serialize_expand_notification_panel(void) {
|
|||||||
.type = CONTROL_MSG_TYPE_EXPAND_NOTIFICATION_PANEL,
|
.type = CONTROL_MSG_TYPE_EXPAND_NOTIFICATION_PANEL,
|
||||||
};
|
};
|
||||||
|
|
||||||
unsigned char buf[CONTROL_MSG_SERIALIZED_MAX_SIZE];
|
unsigned char buf[CONTROL_MSG_MAX_SIZE];
|
||||||
int size = control_msg_serialize(&msg, buf);
|
int size = control_msg_serialize(&msg, buf);
|
||||||
assert(size == 1);
|
assert(size == 1);
|
||||||
|
|
||||||
@ -172,7 +174,7 @@ static void test_serialize_collapse_notification_panel(void) {
|
|||||||
.type = CONTROL_MSG_TYPE_COLLAPSE_NOTIFICATION_PANEL,
|
.type = CONTROL_MSG_TYPE_COLLAPSE_NOTIFICATION_PANEL,
|
||||||
};
|
};
|
||||||
|
|
||||||
unsigned char buf[CONTROL_MSG_SERIALIZED_MAX_SIZE];
|
unsigned char buf[CONTROL_MSG_MAX_SIZE];
|
||||||
int size = control_msg_serialize(&msg, buf);
|
int size = control_msg_serialize(&msg, buf);
|
||||||
assert(size == 1);
|
assert(size == 1);
|
||||||
|
|
||||||
@ -187,7 +189,7 @@ static void test_serialize_get_clipboard(void) {
|
|||||||
.type = CONTROL_MSG_TYPE_GET_CLIPBOARD,
|
.type = CONTROL_MSG_TYPE_GET_CLIPBOARD,
|
||||||
};
|
};
|
||||||
|
|
||||||
unsigned char buf[CONTROL_MSG_SERIALIZED_MAX_SIZE];
|
unsigned char buf[CONTROL_MSG_MAX_SIZE];
|
||||||
int size = control_msg_serialize(&msg, buf);
|
int size = control_msg_serialize(&msg, buf);
|
||||||
assert(size == 1);
|
assert(size == 1);
|
||||||
|
|
||||||
@ -201,19 +203,17 @@ static void test_serialize_set_clipboard(void) {
|
|||||||
struct control_msg msg = {
|
struct control_msg msg = {
|
||||||
.type = CONTROL_MSG_TYPE_SET_CLIPBOARD,
|
.type = CONTROL_MSG_TYPE_SET_CLIPBOARD,
|
||||||
.set_clipboard = {
|
.set_clipboard = {
|
||||||
.paste = true,
|
|
||||||
.text = "hello, world!",
|
.text = "hello, world!",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
unsigned char buf[CONTROL_MSG_SERIALIZED_MAX_SIZE];
|
unsigned char buf[CONTROL_MSG_MAX_SIZE];
|
||||||
int size = control_msg_serialize(&msg, buf);
|
int size = control_msg_serialize(&msg, buf);
|
||||||
assert(size == 17);
|
assert(size == 18);
|
||||||
|
|
||||||
const unsigned char expected[] = {
|
const unsigned char expected[] = {
|
||||||
CONTROL_MSG_TYPE_SET_CLIPBOARD,
|
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
|
'h', 'e', 'l', 'l', 'o', ',', ' ', 'w', 'o', 'r', 'l', 'd', '!', // text
|
||||||
};
|
};
|
||||||
assert(!memcmp(buf, expected, sizeof(expected)));
|
assert(!memcmp(buf, expected, sizeof(expected)));
|
||||||
@ -227,7 +227,7 @@ static void test_serialize_set_screen_power_mode(void) {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
unsigned char buf[CONTROL_MSG_SERIALIZED_MAX_SIZE];
|
unsigned char buf[CONTROL_MSG_MAX_SIZE];
|
||||||
int size = control_msg_serialize(&msg, buf);
|
int size = control_msg_serialize(&msg, buf);
|
||||||
assert(size == 2);
|
assert(size == 2);
|
||||||
|
|
||||||
@ -243,7 +243,7 @@ static void test_serialize_rotate_device(void) {
|
|||||||
.type = CONTROL_MSG_TYPE_ROTATE_DEVICE,
|
.type = CONTROL_MSG_TYPE_ROTATE_DEVICE,
|
||||||
};
|
};
|
||||||
|
|
||||||
unsigned char buf[CONTROL_MSG_SERIALIZED_MAX_SIZE];
|
unsigned char buf[CONTROL_MSG_MAX_SIZE];
|
||||||
int size = control_msg_serialize(&msg, buf);
|
int size = control_msg_serialize(&msg, buf);
|
||||||
assert(size == 1);
|
assert(size == 1);
|
||||||
|
|
||||||
|
@ -4,16 +4,17 @@
|
|||||||
#include "device_msg.h"
|
#include "device_msg.h"
|
||||||
|
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
|
|
||||||
static void test_deserialize_clipboard(void) {
|
static void test_deserialize_clipboard(void) {
|
||||||
const unsigned char input[] = {
|
const unsigned char input[] = {
|
||||||
DEVICE_MSG_TYPE_CLIPBOARD,
|
DEVICE_MSG_TYPE_CLIPBOARD,
|
||||||
0x00, 0x03, // text length
|
0x00, 0x00, 0x00, 0x03, // text length
|
||||||
0x41, 0x42, 0x43, // "ABC"
|
0x41, 0x42, 0x43, // "ABC"
|
||||||
};
|
};
|
||||||
|
|
||||||
struct device_msg msg;
|
struct device_msg msg;
|
||||||
ssize_t r = device_msg_deserialize(input, sizeof(input), &msg);
|
ssize_t r = device_msg_deserialize(input, sizeof(input), &msg);
|
||||||
assert(r == 6);
|
assert(r == 8);
|
||||||
|
|
||||||
assert(msg.type == DEVICE_MSG_TYPE_CLIPBOARD);
|
assert(msg.type == DEVICE_MSG_TYPE_CLIPBOARD);
|
||||||
assert(msg.clipboard.text);
|
assert(msg.clipboard.text);
|
||||||
@ -22,7 +23,30 @@ static void test_deserialize_clipboard(void) {
|
|||||||
device_msg_destroy(&msg);
|
device_msg_destroy(&msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void test_deserialize_clipboard_big(void) {
|
||||||
|
unsigned char input[DEVICE_MSG_MAX_SIZE];
|
||||||
|
input[0] = DEVICE_MSG_TYPE_CLIPBOARD;
|
||||||
|
input[1] = (DEVICE_MSG_TEXT_MAX_LENGTH & 0xff000000u) >> 24;
|
||||||
|
input[2] = (DEVICE_MSG_TEXT_MAX_LENGTH & 0x00ff0000u) >> 16;
|
||||||
|
input[3] = (DEVICE_MSG_TEXT_MAX_LENGTH & 0x0000ff00u) >> 8;
|
||||||
|
input[4] = DEVICE_MSG_TEXT_MAX_LENGTH & 0x000000ffu;
|
||||||
|
|
||||||
|
memset(input + 5, 'a', DEVICE_MSG_TEXT_MAX_LENGTH);
|
||||||
|
|
||||||
|
struct device_msg msg;
|
||||||
|
ssize_t r = device_msg_deserialize(input, sizeof(input), &msg);
|
||||||
|
assert(r == DEVICE_MSG_MAX_SIZE);
|
||||||
|
|
||||||
|
assert(msg.type == DEVICE_MSG_TYPE_CLIPBOARD);
|
||||||
|
assert(msg.clipboard.text);
|
||||||
|
assert(strlen(msg.clipboard.text) == DEVICE_MSG_TEXT_MAX_LENGTH);
|
||||||
|
assert(msg.clipboard.text[0] == 'a');
|
||||||
|
|
||||||
|
device_msg_destroy(&msg);
|
||||||
|
}
|
||||||
|
|
||||||
int main(void) {
|
int main(void) {
|
||||||
test_deserialize_clipboard();
|
test_deserialize_clipboard();
|
||||||
|
test_deserialize_clipboard_big();
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
@ -17,8 +17,6 @@ public final class ControlMessage {
|
|||||||
public static final int TYPE_SET_SCREEN_POWER_MODE = 9;
|
public static final int TYPE_SET_SCREEN_POWER_MODE = 9;
|
||||||
public static final int TYPE_ROTATE_DEVICE = 10;
|
public static final int TYPE_ROTATE_DEVICE = 10;
|
||||||
|
|
||||||
public static final int FLAGS_PASTE = 1;
|
|
||||||
|
|
||||||
private int type;
|
private int type;
|
||||||
private String text;
|
private String text;
|
||||||
private int metaState; // KeyEvent.META_*
|
private int metaState; // KeyEvent.META_*
|
||||||
@ -30,7 +28,6 @@ public final class ControlMessage {
|
|||||||
private Position position;
|
private Position position;
|
||||||
private int hScroll;
|
private int hScroll;
|
||||||
private int vScroll;
|
private int vScroll;
|
||||||
private int flags;
|
|
||||||
|
|
||||||
private ControlMessage() {
|
private ControlMessage() {
|
||||||
}
|
}
|
||||||
@ -71,13 +68,10 @@ public final class ControlMessage {
|
|||||||
return msg;
|
return msg;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ControlMessage createSetClipboard(String text, boolean paste) {
|
public static ControlMessage createSetClipboard(String text) {
|
||||||
ControlMessage msg = new ControlMessage();
|
ControlMessage msg = new ControlMessage();
|
||||||
msg.type = TYPE_SET_CLIPBOARD;
|
msg.type = TYPE_SET_CLIPBOARD;
|
||||||
msg.text = text;
|
msg.text = text;
|
||||||
if (paste) {
|
|
||||||
msg.flags = FLAGS_PASTE;
|
|
||||||
}
|
|
||||||
return msg;
|
return msg;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -140,8 +134,4 @@ public final class ControlMessage {
|
|||||||
public int getVScroll() {
|
public int getVScroll() {
|
||||||
return vScroll;
|
return vScroll;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getFlags() {
|
|
||||||
return flags;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -12,16 +12,14 @@ public class ControlMessageReader {
|
|||||||
static final int INJECT_TOUCH_EVENT_PAYLOAD_LENGTH = 27;
|
static final int INJECT_TOUCH_EVENT_PAYLOAD_LENGTH = 27;
|
||||||
static final int INJECT_SCROLL_EVENT_PAYLOAD_LENGTH = 20;
|
static final int INJECT_SCROLL_EVENT_PAYLOAD_LENGTH = 20;
|
||||||
static final int SET_SCREEN_POWER_MODE_PAYLOAD_LENGTH = 1;
|
static final int SET_SCREEN_POWER_MODE_PAYLOAD_LENGTH = 1;
|
||||||
static final int SET_CLIPBOARD_FIXED_PAYLOAD_LENGTH = 1;
|
|
||||||
|
|
||||||
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 - 5; // type: 1 byte; length: 4 bytes
|
||||||
public static final int INJECT_TEXT_MAX_LENGTH = 300;
|
public static final int INJECT_TEXT_MAX_LENGTH = 300;
|
||||||
|
|
||||||
private static final int RAW_BUFFER_SIZE = 4096;
|
private final byte[] rawBuffer = new byte[MESSAGE_MAX_SIZE];
|
||||||
|
|
||||||
private final byte[] rawBuffer = new byte[RAW_BUFFER_SIZE];
|
|
||||||
private final ByteBuffer buffer = ByteBuffer.wrap(rawBuffer);
|
private final ByteBuffer buffer = ByteBuffer.wrap(rawBuffer);
|
||||||
private final byte[] textBuffer = new byte[CLIPBOARD_TEXT_MAX_LENGTH];
|
|
||||||
|
|
||||||
public ControlMessageReader() {
|
public ControlMessageReader() {
|
||||||
// invariant: the buffer is always in "get" mode
|
// invariant: the buffer is always in "get" mode
|
||||||
@ -104,15 +102,17 @@ public class ControlMessageReader {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private String parseString() {
|
private String parseString() {
|
||||||
if (buffer.remaining() < 2) {
|
if (buffer.remaining() < 4) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
int len = toUnsigned(buffer.getShort());
|
int len = buffer.getInt();
|
||||||
if (buffer.remaining() < len) {
|
if (buffer.remaining() < len) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
buffer.get(textBuffer, 0, len);
|
int position = buffer.position();
|
||||||
return new String(textBuffer, 0, len, StandardCharsets.UTF_8);
|
// Move the buffer position to consume the text
|
||||||
|
buffer.position(position + len);
|
||||||
|
return new String(rawBuffer, position, len, StandardCharsets.UTF_8);
|
||||||
}
|
}
|
||||||
|
|
||||||
private ControlMessage parseInjectText() {
|
private ControlMessage parseInjectText() {
|
||||||
@ -149,15 +149,11 @@ public class ControlMessageReader {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private ControlMessage parseSetClipboard() {
|
private ControlMessage parseSetClipboard() {
|
||||||
if (buffer.remaining() < SET_CLIPBOARD_FIXED_PAYLOAD_LENGTH) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
boolean parse = buffer.get() != 0;
|
|
||||||
String text = parseString();
|
String text = parseString();
|
||||||
if (text == null) {
|
if (text == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return ControlMessage.createSetClipboard(text, parse);
|
return ControlMessage.createSetClipboard(text);
|
||||||
}
|
}
|
||||||
|
|
||||||
private ControlMessage parseSetScreenPowerMode() {
|
private ControlMessage parseSetScreenPowerMode() {
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
package com.genymobile.scrcpy;
|
package com.genymobile.scrcpy;
|
||||||
|
|
||||||
import android.os.Build;
|
|
||||||
import android.os.SystemClock;
|
import android.os.SystemClock;
|
||||||
import android.view.InputDevice;
|
import android.view.InputDevice;
|
||||||
import android.view.KeyCharacterMap;
|
import android.view.KeyCharacterMap;
|
||||||
@ -110,8 +109,7 @@ public class Controller {
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case ControlMessage.TYPE_SET_CLIPBOARD:
|
case ControlMessage.TYPE_SET_CLIPBOARD:
|
||||||
boolean paste = (msg.getFlags() & ControlMessage.FLAGS_PASTE) != 0;
|
setClipboard(msg.getText());
|
||||||
setClipboard(msg.getText(), paste);
|
|
||||||
break;
|
break;
|
||||||
case ControlMessage.TYPE_SET_SCREEN_POWER_MODE:
|
case ControlMessage.TYPE_SET_SCREEN_POWER_MODE:
|
||||||
if (device.supportsInputEvents()) {
|
if (device.supportsInputEvents()) {
|
||||||
@ -229,17 +227,13 @@ public class Controller {
|
|||||||
return device.injectKeycode(keycode);
|
return device.injectKeycode(keycode);
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean setClipboard(String text, boolean paste) {
|
private boolean setClipboard(String text) {
|
||||||
boolean ok = device.setClipboardText(text);
|
boolean ok = device.setClipboardText(text);
|
||||||
if (ok) {
|
if (ok) {
|
||||||
Ln.i("Device clipboard set");
|
Ln.i("Device clipboard set");
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// On Android >= 7, also press the PASTE key if requested
|
return false;
|
||||||
if (paste && Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && device.supportsInputEvents()) {
|
|
||||||
device.injectKeycode(KeyEvent.KEYCODE_PASTE);
|
|
||||||
}
|
|
||||||
|
|
||||||
return ok;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -207,6 +207,14 @@ public final class Device {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public boolean setClipboardText(String text) {
|
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);
|
isSettingClipboard.set(true);
|
||||||
boolean ok = serviceManager.getClipboardManager().setText(text);
|
boolean ok = serviceManager.getClipboardManager().setText(text);
|
||||||
isSettingClipboard.set(false);
|
isSettingClipboard.set(false);
|
||||||
|
@ -7,10 +7,10 @@ import java.nio.charset.StandardCharsets;
|
|||||||
|
|
||||||
public class DeviceMessageWriter {
|
public class DeviceMessageWriter {
|
||||||
|
|
||||||
public static final int CLIPBOARD_TEXT_MAX_LENGTH = 4093;
|
private static final int MESSAGE_MAX_SIZE = 1 << 18; // 256k
|
||||||
private static final int MAX_EVENT_SIZE = CLIPBOARD_TEXT_MAX_LENGTH + 3;
|
public static final int CLIPBOARD_TEXT_MAX_LENGTH = MESSAGE_MAX_SIZE - 5; // type: 1 byte; length: 4 bytes
|
||||||
|
|
||||||
private final byte[] rawBuffer = new byte[MAX_EVENT_SIZE];
|
private final byte[] rawBuffer = new byte[MESSAGE_MAX_SIZE];
|
||||||
private final ByteBuffer buffer = ByteBuffer.wrap(rawBuffer);
|
private final ByteBuffer buffer = ByteBuffer.wrap(rawBuffer);
|
||||||
|
|
||||||
public void writeTo(DeviceMessage msg, OutputStream output) throws IOException {
|
public void writeTo(DeviceMessage msg, OutputStream output) throws IOException {
|
||||||
@ -21,7 +21,7 @@ public class DeviceMessageWriter {
|
|||||||
String text = msg.getText();
|
String text = msg.getText();
|
||||||
byte[] raw = text.getBytes(StandardCharsets.UTF_8);
|
byte[] raw = text.getBytes(StandardCharsets.UTF_8);
|
||||||
int len = StringUtils.getUtf8TruncationIndex(raw, CLIPBOARD_TEXT_MAX_LENGTH);
|
int len = StringUtils.getUtf8TruncationIndex(raw, CLIPBOARD_TEXT_MAX_LENGTH);
|
||||||
buffer.putShort((short) len);
|
buffer.putInt(len);
|
||||||
buffer.put(raw, 0, len);
|
buffer.put(raw, 0, len);
|
||||||
output.write(rawBuffer, 0, buffer.position());
|
output.write(rawBuffer, 0, buffer.position());
|
||||||
break;
|
break;
|
||||||
|
@ -48,7 +48,7 @@ public class ControlMessageReaderTest {
|
|||||||
DataOutputStream dos = new DataOutputStream(bos);
|
DataOutputStream dos = new DataOutputStream(bos);
|
||||||
dos.writeByte(ControlMessage.TYPE_INJECT_TEXT);
|
dos.writeByte(ControlMessage.TYPE_INJECT_TEXT);
|
||||||
byte[] text = "testé".getBytes(StandardCharsets.UTF_8);
|
byte[] text = "testé".getBytes(StandardCharsets.UTF_8);
|
||||||
dos.writeShort(text.length);
|
dos.writeInt(text.length);
|
||||||
dos.write(text);
|
dos.write(text);
|
||||||
byte[] packet = bos.toByteArray();
|
byte[] packet = bos.toByteArray();
|
||||||
|
|
||||||
@ -68,7 +68,7 @@ public class ControlMessageReaderTest {
|
|||||||
dos.writeByte(ControlMessage.TYPE_INJECT_TEXT);
|
dos.writeByte(ControlMessage.TYPE_INJECT_TEXT);
|
||||||
byte[] text = new byte[ControlMessageReader.INJECT_TEXT_MAX_LENGTH];
|
byte[] text = new byte[ControlMessageReader.INJECT_TEXT_MAX_LENGTH];
|
||||||
Arrays.fill(text, (byte) 'a');
|
Arrays.fill(text, (byte) 'a');
|
||||||
dos.writeShort(text.length);
|
dos.writeInt(text.length);
|
||||||
dos.write(text);
|
dos.write(text);
|
||||||
byte[] packet = bos.toByteArray();
|
byte[] packet = bos.toByteArray();
|
||||||
|
|
||||||
@ -216,9 +216,8 @@ public class ControlMessageReaderTest {
|
|||||||
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||||
DataOutputStream dos = new DataOutputStream(bos);
|
DataOutputStream dos = new DataOutputStream(bos);
|
||||||
dos.writeByte(ControlMessage.TYPE_SET_CLIPBOARD);
|
dos.writeByte(ControlMessage.TYPE_SET_CLIPBOARD);
|
||||||
dos.writeByte(1); // paste
|
|
||||||
byte[] text = "testé".getBytes(StandardCharsets.UTF_8);
|
byte[] text = "testé".getBytes(StandardCharsets.UTF_8);
|
||||||
dos.writeShort(text.length);
|
dos.writeInt(text.length);
|
||||||
dos.write(text);
|
dos.write(text);
|
||||||
|
|
||||||
byte[] packet = bos.toByteArray();
|
byte[] packet = bos.toByteArray();
|
||||||
@ -228,9 +227,6 @@ public class ControlMessageReaderTest {
|
|||||||
|
|
||||||
Assert.assertEquals(ControlMessage.TYPE_SET_CLIPBOARD, event.getType());
|
Assert.assertEquals(ControlMessage.TYPE_SET_CLIPBOARD, event.getType());
|
||||||
Assert.assertEquals("testé", event.getText());
|
Assert.assertEquals("testé", event.getText());
|
||||||
|
|
||||||
boolean parse = (event.getFlags() & ControlMessage.FLAGS_PASTE) != 0;
|
|
||||||
Assert.assertTrue(parse);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -242,11 +238,10 @@ public class ControlMessageReaderTest {
|
|||||||
dos.writeByte(ControlMessage.TYPE_SET_CLIPBOARD);
|
dos.writeByte(ControlMessage.TYPE_SET_CLIPBOARD);
|
||||||
|
|
||||||
byte[] rawText = new byte[ControlMessageReader.CLIPBOARD_TEXT_MAX_LENGTH];
|
byte[] rawText = new byte[ControlMessageReader.CLIPBOARD_TEXT_MAX_LENGTH];
|
||||||
dos.writeByte(1); // paste
|
|
||||||
Arrays.fill(rawText, (byte) 'a');
|
Arrays.fill(rawText, (byte) 'a');
|
||||||
String text = new String(rawText, 0, rawText.length);
|
String text = new String(rawText, 0, rawText.length);
|
||||||
|
|
||||||
dos.writeShort(rawText.length);
|
dos.writeInt(rawText.length);
|
||||||
dos.write(rawText);
|
dos.write(rawText);
|
||||||
|
|
||||||
byte[] packet = bos.toByteArray();
|
byte[] packet = bos.toByteArray();
|
||||||
@ -256,9 +251,6 @@ public class ControlMessageReaderTest {
|
|||||||
|
|
||||||
Assert.assertEquals(ControlMessage.TYPE_SET_CLIPBOARD, event.getType());
|
Assert.assertEquals(ControlMessage.TYPE_SET_CLIPBOARD, event.getType());
|
||||||
Assert.assertEquals(text, event.getText());
|
Assert.assertEquals(text, event.getText());
|
||||||
|
|
||||||
boolean parse = (event.getFlags() & ControlMessage.FLAGS_PASTE) != 0;
|
|
||||||
Assert.assertTrue(parse);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -19,7 +19,7 @@ public class DeviceMessageWriterTest {
|
|||||||
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||||
DataOutputStream dos = new DataOutputStream(bos);
|
DataOutputStream dos = new DataOutputStream(bos);
|
||||||
dos.writeByte(DeviceMessage.TYPE_CLIPBOARD);
|
dos.writeByte(DeviceMessage.TYPE_CLIPBOARD);
|
||||||
dos.writeShort(data.length);
|
dos.writeInt(data.length);
|
||||||
dos.write(data);
|
dos.write(data);
|
||||||
|
|
||||||
byte[] expected = bos.toByteArray();
|
byte[] expected = bos.toByteArray();
|
||||||
|
Reference in New Issue
Block a user