diff --git a/server/src/main/java/com/genymobile/scrcpy/DesktopConnection.java b/server/src/main/java/com/genymobile/scrcpy/DesktopConnection.java index 3c09e725..dcc9863b 100644 --- a/server/src/main/java/com/genymobile/scrcpy/DesktopConnection.java +++ b/server/src/main/java/com/genymobile/scrcpy/DesktopConnection.java @@ -8,6 +8,7 @@ import java.io.Closeable; import java.io.FileDescriptor; import java.io.IOException; import java.io.InputStream; +import java.io.OutputStream; import java.nio.charset.StandardCharsets; public final class DesktopConnection implements Closeable { @@ -21,14 +22,16 @@ public final class DesktopConnection implements Closeable { private final LocalSocket controlSocket; private final InputStream controlInputStream; - + private final OutputStream controlOutputStream; private final ControlEventReader reader = new ControlEventReader(); + private final DeviceEventWriter writer = new DeviceEventWriter(); private DesktopConnection(LocalSocket videoSocket, LocalSocket controlSocket) throws IOException { this.videoSocket = videoSocket; this.controlSocket = controlSocket; controlInputStream = controlSocket.getInputStream(); + controlOutputStream = controlSocket.getOutputStream(); videoFd = videoSocket.getFileDescriptor(); } @@ -109,4 +112,8 @@ public final class DesktopConnection implements Closeable { } return event; } + + public void sendDeviceEvent(DeviceEvent event) throws IOException { + writer.writeTo(event, controlOutputStream); + } } diff --git a/server/src/main/java/com/genymobile/scrcpy/DeviceEventWriter.java b/server/src/main/java/com/genymobile/scrcpy/DeviceEventWriter.java new file mode 100644 index 00000000..e183a221 --- /dev/null +++ b/server/src/main/java/com/genymobile/scrcpy/DeviceEventWriter.java @@ -0,0 +1,34 @@ +package com.genymobile.scrcpy; + +import java.io.IOException; +import java.io.OutputStream; +import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; + +public class DeviceEventWriter { + + 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[MAX_EVENT_SIZE]; + private final ByteBuffer buffer = ByteBuffer.wrap(rawBuffer); + + @SuppressWarnings("checkstyle:MagicNumber") + public void writeTo(DeviceEvent event, OutputStream output) throws IOException { + buffer.clear(); + buffer.put((byte) DeviceEvent.TYPE_GET_CLIPBOARD); + switch (event.getType()) { + case DeviceEvent.TYPE_GET_CLIPBOARD: + String text = event.getText(); + byte[] raw = text.getBytes(StandardCharsets.UTF_8); + int len = StringUtils.getUtf8TruncationIndex(raw, CLIPBOARD_TEXT_MAX_LENGTH); + buffer.putShort((short) len); + buffer.put(raw, 0, len); + output.write(rawBuffer, 0, buffer.position()); + break; + default: + Ln.w("Unknown device event: " + event.getType()); + break; + } + } +} diff --git a/server/src/main/java/com/genymobile/scrcpy/EventController.java b/server/src/main/java/com/genymobile/scrcpy/EventController.java index 5bb79de3..4bda0f3d 100644 --- a/server/src/main/java/com/genymobile/scrcpy/EventController.java +++ b/server/src/main/java/com/genymobile/scrcpy/EventController.java @@ -11,11 +11,11 @@ import android.view.MotionEvent; import java.io.IOException; - public class EventController { private final Device device; private final DesktopConnection connection; + private final EventSender sender; private final KeyCharacterMap charMap = KeyCharacterMap.load(KeyCharacterMap.VIRTUAL_KEYBOARD); @@ -27,6 +27,7 @@ public class EventController { this.device = device; this.connection = connection; initPointer(); + sender = new EventSender(connection); } private void initPointer() { @@ -61,6 +62,10 @@ public class EventController { } } + public EventSender getSender() { + return sender; + } + private void handleEvent() throws IOException { ControlEvent controlEvent = connection.receiveControlEvent(); switch (controlEvent.getType()) { @@ -96,7 +101,7 @@ public class EventController { private boolean injectChar(char c) { String decomposed = KeyComposition.decompose(c); - char[] chars = decomposed != null ? decomposed.toCharArray() : new char[] {c}; + char[] chars = decomposed != null ? decomposed.toCharArray() : new char[]{c}; KeyEvent[] events = charMap.getEvents(chars); if (events == null) { return false; diff --git a/server/src/main/java/com/genymobile/scrcpy/EventSender.java b/server/src/main/java/com/genymobile/scrcpy/EventSender.java new file mode 100644 index 00000000..9f50b16a --- /dev/null +++ b/server/src/main/java/com/genymobile/scrcpy/EventSender.java @@ -0,0 +1,34 @@ +package com.genymobile.scrcpy; + +import java.io.IOException; + +public final class EventSender { + + private final DesktopConnection connection; + + private String clipboardText; + + public EventSender(DesktopConnection connection) { + this.connection = connection; + } + + public synchronized void pushClipboardText(String text) { + clipboardText = text; + notify(); + } + + public void loop() throws IOException, InterruptedException { + while (true) { + String text; + synchronized (this) { + while (clipboardText == null) { + wait(); + } + text = clipboardText; + clipboardText = null; + } + DeviceEvent event = DeviceEvent.createGetClipboardEvent(text); + connection.sendDeviceEvent(event); + } + } +} diff --git a/server/src/main/java/com/genymobile/scrcpy/Server.java b/server/src/main/java/com/genymobile/scrcpy/Server.java index db192dd1..25cb15e6 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Server.java +++ b/server/src/main/java/com/genymobile/scrcpy/Server.java @@ -19,8 +19,11 @@ public final class Server { try (DesktopConnection connection = DesktopConnection.open(device, tunnelForward)) { ScreenEncoder screenEncoder = new ScreenEncoder(options.getSendFrameMeta(), options.getBitRate()); + EventController controller = new EventController(device, connection); + // asynchronous - startEventController(device, connection); + startEventController(controller); + startEventSender(controller.getSender()); try { // synchronous @@ -32,12 +35,12 @@ public final class Server { } } - private static void startEventController(final Device device, final DesktopConnection connection) { + private static void startEventController(final EventController controller) { new Thread(new Runnable() { @Override public void run() { try { - new EventController(device, connection).control(); + controller.control(); } catch (IOException e) { // this is expected on close Ln.d("Event controller stopped"); @@ -46,6 +49,20 @@ public final class Server { }).start(); } + private static void startEventSender(final EventSender sender) { + new Thread(new Runnable() { + @Override + public void run() { + try { + sender.loop(); + } catch (IOException | InterruptedException e) { + // this is expected on close + Ln.d("Event sender stopped"); + } + } + }).start(); + } + @SuppressWarnings("checkstyle:MagicNumber") private static Options createOptions(String... args) { if (args.length != 5) {