Add device event sender
Create a separate component to send device events, managed by the controller.
This commit is contained in:
parent
6112095e75
commit
3149e2cf4a
5 changed files with 103 additions and 6 deletions
|
@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
|
34
server/src/main/java/com/genymobile/scrcpy/EventSender.java
Normal file
34
server/src/main/java/com/genymobile/scrcpy/EventSender.java
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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) {
|
||||||
|
|
Loading…
Reference in a new issue