From 9badd2bdf06d1cdea39824b1f61055da19541b53 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 29 May 2020 22:46:08 +0200 Subject: [PATCH] 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). --- README.md | 2 +- app/scrcpy.1 | 2 +- app/src/cli.c | 3 ++- app/src/control_msg.c | 4 +++- app/src/control_msg.h | 3 +++ app/src/input_manager.c | 6 +++-- app/tests/test_control_msg_serialize.c | 6 ++++- .../com/genymobile/scrcpy/ControlMessage.java | 15 ++++++++---- .../scrcpy/ControlMessageReader.java | 13 +++++++++- .../com/genymobile/scrcpy/Controller.java | 24 +++++++++++++++---- .../scrcpy/ControlMessageReaderTest.java | 6 +++-- 11 files changed, 65 insertions(+), 19 deletions(-) diff --git a/README.md b/README.md index 2f1d4f5d..30a90a34 100644 --- a/README.md +++ b/README.md @@ -582,7 +582,7 @@ _`Meta` is typically the `Windows` key on the keyboard, or `Cmd` on macOS._ | Rotate device screen | `Meta`+`r` | Expand notification panel | `Meta`+`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` | Copy computer clipboard to device, then press PASTE³ | `Meta`+`Shift`+`v` | Enable/disable FPS counter (on stdout) | `Meta`+`i` diff --git a/app/scrcpy.1 b/app/scrcpy.1 index a63a44aa..dcef302f 100644 --- a/app/scrcpy.1 +++ b/app/scrcpy.1 @@ -277,7 +277,7 @@ Collapse notification panel .TP .B Meta+c -Copy device clipboard to computer +Press COPY (Android >= 7), then copy device clipboard to computer .TP .B Meta+v diff --git a/app/src/cli.c b/app/src/cli.c index a13d8a44..c2ffdef1 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -245,7 +245,8 @@ scrcpy_print_usage(const char *arg0) { " Collapse notification panel\n" "\n" " " MOD "+c\n" - " Copy device clipboard to computer\n" + " Press COPY (Android >= 7), then copy device clipboard to\n" + " computer\n" "\n" " " MOD "+v\n" " Paste computer clipboard to device\n" diff --git a/app/src/control_msg.c b/app/src/control_msg.c index c5778c02..4bd1c74d 100644 --- a/app/src/control_msg.c +++ b/app/src/control_msg.c @@ -66,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, @@ -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_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; diff --git a/app/src/control_msg.h b/app/src/control_msg.h index 0e85c97e..6aa14a3c 100644 --- a/app/src/control_msg.h +++ b/app/src/control_msg.h @@ -60,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; diff --git a/app/src/input_manager.c b/app/src/input_manager.c index 59734c64..281d4086 100644 --- a/app/src/input_manager.c +++ b/app/src/input_manager.c @@ -102,9 +102,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"); @@ -341,7 +342,8 @@ input_manager_process_key(struct input_manager *im, return; case SDLK_c: if (control && !shift && !repeat && down) { - request_device_clipboard(controller); + // Press COPY, then get the clipboard content + request_device_clipboard(controller, true); } return; case SDLK_v: diff --git a/app/tests/test_control_msg_serialize.c b/app/tests/test_control_msg_serialize.c index c6ff7b2e..3ffd2e6c 100644 --- a/app/tests/test_control_msg_serialize.c +++ b/app/tests/test_control_msg_serialize.c @@ -185,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_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))); } diff --git a/server/src/main/java/com/genymobile/scrcpy/ControlMessage.java b/server/src/main/java/com/genymobile/scrcpy/ControlMessage.java index 8415b046..f07ffef5 100644 --- a/server/src/main/java/com/genymobile/scrcpy/ControlMessage.java +++ b/server/src/main/java/com/genymobile/scrcpy/ControlMessage.java @@ -28,7 +28,7 @@ public final class ControlMessage { private Position position; private int hScroll; private int vScroll; - private boolean paste; + private boolean pressCopyOrPaste; private ControlMessage() { } @@ -69,11 +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; - msg.paste = paste; + msg.pressCopyOrPaste = paste; return msg; } @@ -137,7 +144,7 @@ public final class ControlMessage { return vScroll; } - public boolean getPaste() { - return paste; + public boolean getPressCopyOrPaste() { + return pressCopyOrPaste; } } diff --git a/server/src/main/java/com/genymobile/scrcpy/ControlMessageReader.java b/server/src/main/java/com/genymobile/scrcpy/ControlMessageReader.java index fbf49a61..ec824b5c 100644 --- a/server/src/main/java/com/genymobile/scrcpy/ControlMessageReader.java +++ b/server/src/main/java/com/genymobile/scrcpy/ControlMessageReader.java @@ -12,6 +12,7 @@ public class ControlMessageReader { 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; 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: msg = parseInjectScrollEvent(); break; + case ControlMessage.TYPE_GET_CLIPBOARD: + msg = parseGetClipboard(); + break; case ControlMessage.TYPE_SET_CLIPBOARD: msg = parseSetClipboard(); break; @@ -76,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; @@ -148,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; diff --git a/server/src/main/java/com/genymobile/scrcpy/Controller.java b/server/src/main/java/com/genymobile/scrcpy/Controller.java index 38472441..9abd782a 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Controller.java +++ b/server/src/main/java/com/genymobile/scrcpy/Controller.java @@ -104,13 +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: - setClipboard(msg.getText(), msg.getPaste()); + setClipboard(msg.getText(), msg.getPressCopyOrPaste()); break; case ControlMessage.TYPE_SET_SCREEN_POWER_MODE: if (device.supportsInputEvents()) { @@ -228,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) { diff --git a/server/src/test/java/com/genymobile/scrcpy/ControlMessageReaderTest.java b/server/src/test/java/com/genymobile/scrcpy/ControlMessageReaderTest.java index db92deec..fe3f4c3f 100644 --- a/server/src/test/java/com/genymobile/scrcpy/ControlMessageReaderTest.java +++ b/server/src/test/java/com/genymobile/scrcpy/ControlMessageReaderTest.java @@ -200,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(); @@ -207,6 +208,7 @@ public class ControlMessageReaderTest { ControlMessage event = reader.next(); Assert.assertEquals(ControlMessage.TYPE_GET_CLIPBOARD, event.getType()); + Assert.assertTrue(event.getPressCopyOrPaste()); } @Test @@ -228,7 +230,7 @@ public class ControlMessageReaderTest { Assert.assertEquals(ControlMessage.TYPE_SET_CLIPBOARD, event.getType()); Assert.assertEquals("testé", event.getText()); - Assert.assertTrue(event.getPaste()); + Assert.assertTrue(event.getPressCopyOrPaste()); } @Test @@ -254,7 +256,7 @@ public class ControlMessageReaderTest { Assert.assertEquals(ControlMessage.TYPE_SET_CLIPBOARD, event.getType()); Assert.assertEquals(text, event.getText()); - Assert.assertTrue(event.getPaste()); + Assert.assertTrue(event.getPressCopyOrPaste()); } @Test