Implement device-to-computer clipboard copy

On Ctrl+C:
 - the client sends a GET_CLIPBOARD command to the device;
 - the device retrieve its current clipboard text and sends it in a
   GET_CLIPBOARD device event;
 - the client sets this text as the system clipboard text, so that it
   can be pasted in another application.

Fixes <https://github.com/Genymobile/scrcpy/issues/145>
This commit is contained in:
Romain Vimont 2019-05-30 15:23:01 +02:00
commit 63c078ee6c
13 changed files with 109 additions and 0 deletions

View file

@ -12,6 +12,7 @@ public final class ControlEvent {
public static final int TYPE_BACK_OR_SCREEN_ON = 4;
public static final int TYPE_EXPAND_NOTIFICATION_PANEL = 5;
public static final int TYPE_COLLAPSE_NOTIFICATION_PANEL = 6;
public static final int TYPE_GET_CLIPBOARD = 7;
private int type;
private String text;

View file

@ -66,6 +66,7 @@ public class ControlEventReader {
case ControlEvent.TYPE_BACK_OR_SCREEN_ON:
case ControlEvent.TYPE_EXPAND_NOTIFICATION_PANEL:
case ControlEvent.TYPE_COLLAPSE_NOTIFICATION_PANEL:
case ControlEvent.TYPE_GET_CLIPBOARD:
controlEvent = ControlEvent.createSimpleControlEvent(type);
break;
default:

View file

@ -139,6 +139,14 @@ public final class Device {
serviceManager.getStatusBarManager().collapsePanels();
}
public String getClipboardText() {
CharSequence s = serviceManager.getClipboardManager().getText();
if (s == null) {
return null;
}
return s.toString();
}
static Rect flipRect(Rect crop) {
return new Rect(crop.top, crop.left, crop.bottom, crop.right);
}

View file

@ -90,6 +90,10 @@ public class EventController {
case ControlEvent.TYPE_COLLAPSE_NOTIFICATION_PANEL:
device.collapsePanels();
break;
case ControlEvent.TYPE_GET_CLIPBOARD:
String clipboardText = device.getClipboardText();
sender.pushClipboardText(clipboardText);
break;
default:
// do nothing
}

View file

@ -0,0 +1,33 @@
package com.genymobile.scrcpy.wrappers;
import android.content.ClipData;
import android.os.IInterface;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class ClipboardManager {
private final IInterface manager;
private final Method getPrimaryClipMethod;
public ClipboardManager(IInterface manager) {
this.manager = manager;
try {
getPrimaryClipMethod = manager.getClass().getMethod("getPrimaryClip", String.class);
} catch (NoSuchMethodException e) {
throw new AssertionError(e);
}
}
public CharSequence getText() {
try {
ClipData clipData = (ClipData) getPrimaryClipMethod.invoke(manager, "com.android.shell");
if (clipData == null || clipData.getItemCount() == 0) {
return null;
}
return clipData.getItemAt(0).getText();
} catch (InvocationTargetException | IllegalAccessException e) {
throw new AssertionError(e);
}
}
}

View file

@ -15,6 +15,7 @@ public final class ServiceManager {
private InputManager inputManager;
private PowerManager powerManager;
private StatusBarManager statusBarManager;
private ClipboardManager clipboardManager;
public ServiceManager() {
try {
@ -68,4 +69,11 @@ public final class ServiceManager {
}
return statusBarManager;
}
public ClipboardManager getClipboardManager() {
if (clipboardManager == null) {
clipboardManager = new ClipboardManager(getService("clipboard", "android.content.IClipboard"));
}
return clipboardManager;
}
}

View file

@ -174,6 +174,22 @@ public class ControlEventReaderTest {
Assert.assertEquals(ControlEvent.TYPE_COLLAPSE_NOTIFICATION_PANEL, event.getType());
}
@Test
public void testParseGetClipboardEvent() throws IOException {
ControlEventReader reader = new ControlEventReader();
ByteArrayOutputStream bos = new ByteArrayOutputStream();
DataOutputStream dos = new DataOutputStream(bos);
dos.writeByte(ControlEvent.TYPE_GET_CLIPBOARD);
byte[] packet = bos.toByteArray();
reader.readFrom(new ByteArrayInputStream(packet));
ControlEvent event = reader.next();
Assert.assertEquals(ControlEvent.TYPE_GET_CLIPBOARD, event.getType());
}
@Test
public void testMultiEvents() throws IOException {
ControlEventReader reader = new ControlEventReader();