Add device event sender

Create a separate component to send device events, managed by the
controller.
This commit is contained in:
Romain Vimont 2019-05-30 15:17:05 +02:00
parent 6112095e75
commit 3149e2cf4a
5 changed files with 103 additions and 6 deletions

View File

@ -8,6 +8,7 @@ import java.io.Closeable;
import java.io.FileDescriptor; import java.io.FileDescriptor;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
public final class DesktopConnection implements Closeable { public final class DesktopConnection implements Closeable {
@ -21,14 +22,16 @@ public final class DesktopConnection implements Closeable {
private final LocalSocket controlSocket; private final LocalSocket controlSocket;
private final InputStream controlInputStream; private final InputStream controlInputStream;
private final OutputStream controlOutputStream;
private final ControlEventReader reader = new ControlEventReader(); private final ControlEventReader reader = new ControlEventReader();
private final DeviceEventWriter writer = new DeviceEventWriter();
private DesktopConnection(LocalSocket videoSocket, LocalSocket controlSocket) throws IOException { private DesktopConnection(LocalSocket videoSocket, LocalSocket controlSocket) throws IOException {
this.videoSocket = videoSocket; this.videoSocket = videoSocket;
this.controlSocket = controlSocket; this.controlSocket = controlSocket;
controlInputStream = controlSocket.getInputStream(); controlInputStream = controlSocket.getInputStream();
controlOutputStream = controlSocket.getOutputStream();
videoFd = videoSocket.getFileDescriptor(); videoFd = videoSocket.getFileDescriptor();
} }
@ -109,4 +112,8 @@ public final class DesktopConnection implements Closeable {
} }
return event; return event;
} }
public void sendDeviceEvent(DeviceEvent event) throws IOException {
writer.writeTo(event, controlOutputStream);
}
} }

View File

@ -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;
}
}
}

View File

@ -11,11 +11,11 @@ import android.view.MotionEvent;
import java.io.IOException; import java.io.IOException;
public class EventController { public class EventController {
private final Device device; private final Device device;
private final DesktopConnection connection; private final DesktopConnection connection;
private final EventSender sender;
private final KeyCharacterMap charMap = KeyCharacterMap.load(KeyCharacterMap.VIRTUAL_KEYBOARD); private final KeyCharacterMap charMap = KeyCharacterMap.load(KeyCharacterMap.VIRTUAL_KEYBOARD);
@ -27,6 +27,7 @@ public class EventController {
this.device = device; this.device = device;
this.connection = connection; this.connection = connection;
initPointer(); initPointer();
sender = new EventSender(connection);
} }
private void initPointer() { private void initPointer() {
@ -61,6 +62,10 @@ public class EventController {
} }
} }
public EventSender getSender() {
return sender;
}
private void handleEvent() throws IOException { private void handleEvent() throws IOException {
ControlEvent controlEvent = connection.receiveControlEvent(); ControlEvent controlEvent = connection.receiveControlEvent();
switch (controlEvent.getType()) { switch (controlEvent.getType()) {
@ -96,7 +101,7 @@ public class EventController {
private boolean injectChar(char c) { private boolean injectChar(char c) {
String decomposed = KeyComposition.decompose(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); KeyEvent[] events = charMap.getEvents(chars);
if (events == null) { if (events == null) {
return false; return false;

View File

@ -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);
}
}
}

View File

@ -19,8 +19,11 @@ public final class Server {
try (DesktopConnection connection = DesktopConnection.open(device, tunnelForward)) { try (DesktopConnection connection = DesktopConnection.open(device, tunnelForward)) {
ScreenEncoder screenEncoder = new ScreenEncoder(options.getSendFrameMeta(), options.getBitRate()); ScreenEncoder screenEncoder = new ScreenEncoder(options.getSendFrameMeta(), options.getBitRate());
EventController controller = new EventController(device, connection);
// asynchronous // asynchronous
startEventController(device, connection); startEventController(controller);
startEventSender(controller.getSender());
try { try {
// synchronous // 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() { new Thread(new Runnable() {
@Override @Override
public void run() { public void run() {
try { try {
new EventController(device, connection).control(); controller.control();
} catch (IOException e) { } catch (IOException e) {
// this is expected on close // this is expected on close
Ln.d("Event controller stopped"); Ln.d("Event controller stopped");
@ -46,6 +49,20 @@ public final class Server {
}).start(); }).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") @SuppressWarnings("checkstyle:MagicNumber")
private static Options createOptions(String... args) { private static Options createOptions(String... args) {
if (args.length != 5) { if (args.length != 5) {