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>
This commit is contained in:
Romain Vimont 2020-05-29 22:46:08 +02:00
commit 9badd2bdf0
11 changed files with 65 additions and 19 deletions

View file

@ -582,7 +582,7 @@ _`Meta` is typically the `Windows` key on the keyboard, or `Cmd` on macOS._
| Rotate device screen | `Meta`+`r` | Rotate device screen | `Meta`+`r`
| Expand notification panel | `Meta`+`n` | Expand notification panel | `Meta`+`n`
| Collapse notification panel | `Meta`+`Shift`+`n` | Collapse notification panel | `Meta`+`Shift`+`n`
| Copy device clipboard to computer | `Meta`+`c` | Press COPY³, then Copy device clipboard to computer | `Meta`+`c`
| Paste computer clipboard to device | `Meta`+`v` | Paste computer clipboard to device | `Meta`+`v`
| Copy computer clipboard to device, then press PASTE³ | `Meta`+`Shift`+`v` | Copy computer clipboard to device, then press PASTE³ | `Meta`+`Shift`+`v`
| Enable/disable FPS counter (on stdout) | `Meta`+`i` | Enable/disable FPS counter (on stdout) | `Meta`+`i`

View file

@ -277,7 +277,7 @@ Collapse notification panel
.TP .TP
.B Meta+c .B Meta+c
Copy device clipboard to computer Press COPY (Android >= 7), then copy device clipboard to computer
.TP .TP
.B Meta+v .B Meta+v

View file

@ -245,7 +245,8 @@ scrcpy_print_usage(const char *arg0) {
" Collapse notification panel\n" " Collapse notification panel\n"
"\n" "\n"
" " MOD "+c\n" " " MOD "+c\n"
" Copy device clipboard to computer\n" " Press COPY (Android >= 7), then copy device clipboard to\n"
" computer\n"
"\n" "\n"
" " MOD "+v\n" " " MOD "+v\n"
" Paste computer clipboard to device\n" " Paste computer clipboard to device\n"

View file

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

View file

@ -60,6 +60,9 @@ struct control_msg {
int32_t hscroll; int32_t hscroll;
int32_t vscroll; int32_t vscroll;
} inject_scroll_event; } inject_scroll_event;
struct {
bool copy;
} get_clipboard;
struct { struct {
char *text; // owned, to be freed by SDL_free() char *text; // owned, to be freed by SDL_free()
bool paste; bool paste;

View file

@ -102,9 +102,10 @@ collapse_notification_panel(struct controller *controller) {
} }
static void static void
request_device_clipboard(struct controller *controller) { request_device_clipboard(struct controller *controller, bool copy) {
struct control_msg msg; struct control_msg msg;
msg.type = CONTROL_MSG_TYPE_GET_CLIPBOARD; msg.type = CONTROL_MSG_TYPE_GET_CLIPBOARD;
msg.get_clipboard.copy = copy;
if (!controller_push_msg(controller, &msg)) { if (!controller_push_msg(controller, &msg)) {
LOGW("Could not request device clipboard"); LOGW("Could not request device clipboard");
@ -341,7 +342,8 @@ input_manager_process_key(struct input_manager *im,
return; return;
case SDLK_c: case SDLK_c:
if (control && !shift && !repeat && down) { if (control && !shift && !repeat && down) {
request_device_clipboard(controller); // Press COPY, then get the clipboard content
request_device_clipboard(controller, true);
} }
return; return;
case SDLK_v: case SDLK_v:

View file

@ -185,14 +185,18 @@ static void test_serialize_collapse_notification_panel(void) {
static void test_serialize_get_clipboard(void) { static void test_serialize_get_clipboard(void) {
struct control_msg msg = { struct control_msg msg = {
.type = CONTROL_MSG_TYPE_GET_CLIPBOARD, .type = CONTROL_MSG_TYPE_GET_CLIPBOARD,
.get_clipboard = {
.copy = true,
},
}; };
unsigned char buf[CONTROL_MSG_SERIALIZED_MAX_SIZE]; unsigned char buf[CONTROL_MSG_SERIALIZED_MAX_SIZE];
int size = control_msg_serialize(&msg, buf); int size = control_msg_serialize(&msg, buf);
assert(size == 1); assert(size == 2);
const unsigned char expected[] = { const unsigned char expected[] = {
CONTROL_MSG_TYPE_GET_CLIPBOARD, CONTROL_MSG_TYPE_GET_CLIPBOARD,
1, // copy
}; };
assert(!memcmp(buf, expected, sizeof(expected))); assert(!memcmp(buf, expected, sizeof(expected)));
} }

View file

@ -28,7 +28,7 @@ public final class ControlMessage {
private Position position; private Position position;
private int hScroll; private int hScroll;
private int vScroll; private int vScroll;
private boolean paste; private boolean pressCopyOrPaste;
private ControlMessage() { private ControlMessage() {
} }
@ -69,11 +69,18 @@ public final class ControlMessage {
return msg; 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) { public static ControlMessage createSetClipboard(String text, boolean paste) {
ControlMessage msg = new ControlMessage(); ControlMessage msg = new ControlMessage();
msg.type = TYPE_SET_CLIPBOARD; msg.type = TYPE_SET_CLIPBOARD;
msg.text = text; msg.text = text;
msg.paste = paste; msg.pressCopyOrPaste = paste;
return msg; return msg;
} }
@ -137,7 +144,7 @@ public final class ControlMessage {
return vScroll; return vScroll;
} }
public boolean getPaste() { public boolean getPressCopyOrPaste() {
return paste; return pressCopyOrPaste;
} }
} }

View file

@ -12,6 +12,7 @@ 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 GET_CLIPBOARD_PAYLOAD_LENGTH = 1;
static final int SET_CLIPBOARD_FIXED_PAYLOAD_LENGTH = 1; static final int SET_CLIPBOARD_FIXED_PAYLOAD_LENGTH = 1;
public static final int CLIPBOARD_TEXT_MAX_LENGTH = 4092; // 4096 - 1 (type) - 1 (parse flag) - 2 (length) public static final int CLIPBOARD_TEXT_MAX_LENGTH = 4092; // 4096 - 1 (type) - 1 (parse flag) - 2 (length)
@ -67,6 +68,9 @@ public class ControlMessageReader {
case ControlMessage.TYPE_INJECT_SCROLL_EVENT: case ControlMessage.TYPE_INJECT_SCROLL_EVENT:
msg = parseInjectScrollEvent(); msg = parseInjectScrollEvent();
break; break;
case ControlMessage.TYPE_GET_CLIPBOARD:
msg = parseGetClipboard();
break;
case ControlMessage.TYPE_SET_CLIPBOARD: case ControlMessage.TYPE_SET_CLIPBOARD:
msg = parseSetClipboard(); msg = parseSetClipboard();
break; break;
@ -76,7 +80,6 @@ public class ControlMessageReader {
case ControlMessage.TYPE_BACK_OR_SCREEN_ON: case ControlMessage.TYPE_BACK_OR_SCREEN_ON:
case ControlMessage.TYPE_EXPAND_NOTIFICATION_PANEL: case ControlMessage.TYPE_EXPAND_NOTIFICATION_PANEL:
case ControlMessage.TYPE_COLLAPSE_NOTIFICATION_PANEL: case ControlMessage.TYPE_COLLAPSE_NOTIFICATION_PANEL:
case ControlMessage.TYPE_GET_CLIPBOARD:
case ControlMessage.TYPE_ROTATE_DEVICE: case ControlMessage.TYPE_ROTATE_DEVICE:
msg = ControlMessage.createEmpty(type); msg = ControlMessage.createEmpty(type);
break; break;
@ -148,6 +151,14 @@ public class ControlMessageReader {
return ControlMessage.createInjectScrollEvent(position, hScroll, vScroll); 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() { private ControlMessage parseSetClipboard() {
if (buffer.remaining() < SET_CLIPBOARD_FIXED_PAYLOAD_LENGTH) { if (buffer.remaining() < SET_CLIPBOARD_FIXED_PAYLOAD_LENGTH) {
return null; return null;

View file

@ -104,13 +104,10 @@ public class Controller {
device.collapsePanels(); device.collapsePanels();
break; break;
case ControlMessage.TYPE_GET_CLIPBOARD: case ControlMessage.TYPE_GET_CLIPBOARD:
String clipboardText = device.getClipboardText(); getClipboard(msg.getPressCopyOrPaste());
if (clipboardText != null) {
sender.pushClipboardText(clipboardText);
}
break; break;
case ControlMessage.TYPE_SET_CLIPBOARD: case ControlMessage.TYPE_SET_CLIPBOARD:
setClipboard(msg.getText(), msg.getPaste()); setClipboard(msg.getText(), msg.getPressCopyOrPaste());
break; break;
case ControlMessage.TYPE_SET_SCREEN_POWER_MODE: case ControlMessage.TYPE_SET_SCREEN_POWER_MODE:
if (device.supportsInputEvents()) { if (device.supportsInputEvents()) {
@ -228,6 +225,23 @@ public class Controller {
return device.injectKeycode(keycode); 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) { private boolean setClipboard(String text, boolean paste) {
boolean ok = device.setClipboardText(text); boolean ok = device.setClipboardText(text);
if (ok) { if (ok) {

View file

@ -200,6 +200,7 @@ 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_GET_CLIPBOARD); dos.writeByte(ControlMessage.TYPE_GET_CLIPBOARD);
dos.writeByte(1); // copy
byte[] packet = bos.toByteArray(); byte[] packet = bos.toByteArray();
@ -207,6 +208,7 @@ public class ControlMessageReaderTest {
ControlMessage event = reader.next(); ControlMessage event = reader.next();
Assert.assertEquals(ControlMessage.TYPE_GET_CLIPBOARD, event.getType()); Assert.assertEquals(ControlMessage.TYPE_GET_CLIPBOARD, event.getType());
Assert.assertTrue(event.getPressCopyOrPaste());
} }
@Test @Test
@ -228,7 +230,7 @@ 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());
Assert.assertTrue(event.getPaste()); Assert.assertTrue(event.getPressCopyOrPaste());
} }
@Test @Test
@ -254,7 +256,7 @@ 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());
Assert.assertTrue(event.getPaste()); Assert.assertTrue(event.getPressCopyOrPaste());
} }
@Test @Test