mirror of
https://github.com/Genymobile/scrcpy.git
synced 2026-01-26 00:54:27 +01:00
Handle UHID output
Use UHID output reports to synchronize CapsLock and VerrNum states. PR #4473 <https://github.com/Genymobile/scrcpy/pull/4473> Co-authored-by: Romain Vimont <rom@rom1v.com> Signed-off-by: Romain Vimont <rom@rom1v.com>
This commit is contained in:
committed by
Romain Vimont
parent
021c5d371a
commit
87da68ee0d
@@ -52,7 +52,7 @@ public class Controller implements AsyncProcessor {
|
||||
this.powerOn = powerOn;
|
||||
initPointers();
|
||||
sender = new DeviceMessageSender(controlChannel);
|
||||
uhidManager = new UhidManager();
|
||||
uhidManager = new UhidManager(sender);
|
||||
}
|
||||
|
||||
private void initPointers() {
|
||||
|
||||
@@ -4,10 +4,13 @@ public final class DeviceMessage {
|
||||
|
||||
public static final int TYPE_CLIPBOARD = 0;
|
||||
public static final int TYPE_ACK_CLIPBOARD = 1;
|
||||
public static final int TYPE_UHID_OUTPUT = 2;
|
||||
|
||||
private int type;
|
||||
private String text;
|
||||
private long sequence;
|
||||
private int id;
|
||||
private byte[] data;
|
||||
|
||||
private DeviceMessage() {
|
||||
}
|
||||
@@ -26,6 +29,14 @@ public final class DeviceMessage {
|
||||
return event;
|
||||
}
|
||||
|
||||
public static DeviceMessage createUhidOutput(int id, byte[] data) {
|
||||
DeviceMessage event = new DeviceMessage();
|
||||
event.type = TYPE_UHID_OUTPUT;
|
||||
event.id = id;
|
||||
event.data = data;
|
||||
return event;
|
||||
}
|
||||
|
||||
public int getType() {
|
||||
return type;
|
||||
}
|
||||
@@ -37,4 +48,12 @@ public final class DeviceMessage {
|
||||
public long getSequence() {
|
||||
return sequence;
|
||||
}
|
||||
|
||||
public int getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public byte[] getData() {
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,6 +29,13 @@ public class DeviceMessageWriter {
|
||||
buffer.putLong(msg.getSequence());
|
||||
output.write(rawBuffer, 0, buffer.position());
|
||||
break;
|
||||
case DeviceMessage.TYPE_UHID_OUTPUT:
|
||||
buffer.putShort((short) msg.getId());
|
||||
byte[] data = msg.getData();
|
||||
buffer.putShort((short) data.length);
|
||||
buffer.put(data);
|
||||
output.write(rawBuffer, 0, buffer.position());
|
||||
break;
|
||||
default:
|
||||
Ln.w("Unknown device message: " + msg.getType());
|
||||
break;
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
package com.genymobile.scrcpy;
|
||||
|
||||
import android.os.Build;
|
||||
import android.os.HandlerThread;
|
||||
import android.os.MessageQueue;
|
||||
import android.system.ErrnoException;
|
||||
import android.system.Os;
|
||||
import android.system.OsConstants;
|
||||
@@ -7,6 +10,7 @@ import android.util.ArrayMap;
|
||||
|
||||
import java.io.FileDescriptor;
|
||||
import java.io.IOException;
|
||||
import java.io.InterruptedIOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
@@ -14,13 +18,31 @@ import java.nio.charset.StandardCharsets;
|
||||
public final class UhidManager {
|
||||
|
||||
// Linux: include/uapi/linux/uhid.h
|
||||
private static final int UHID_OUTPUT = 6;
|
||||
private static final int UHID_CREATE2 = 11;
|
||||
private static final int UHID_INPUT2 = 12;
|
||||
|
||||
// Linux: include/uapi/linux/input.h
|
||||
private static final short BUS_VIRTUAL = 0x06;
|
||||
|
||||
private static final int SIZE_OF_UHID_EVENT = 4380; // sizeof(struct uhid_event)
|
||||
|
||||
private final ArrayMap<Integer, FileDescriptor> fds = new ArrayMap<>();
|
||||
private final ByteBuffer buffer = ByteBuffer.allocate(SIZE_OF_UHID_EVENT).order(ByteOrder.nativeOrder());
|
||||
|
||||
private final DeviceMessageSender sender;
|
||||
private final HandlerThread thread = new HandlerThread("UHidManager");
|
||||
private final MessageQueue queue;
|
||||
|
||||
public UhidManager(DeviceMessageSender sender) {
|
||||
this.sender = sender;
|
||||
thread.start();
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
queue = thread.getLooper().getQueue();
|
||||
} else {
|
||||
queue = null;
|
||||
}
|
||||
}
|
||||
|
||||
public void open(int id, byte[] reportDesc) throws IOException {
|
||||
try {
|
||||
@@ -34,6 +56,8 @@ public final class UhidManager {
|
||||
|
||||
byte[] req = buildUhidCreate2Req(reportDesc);
|
||||
Os.write(fd, req, 0, req.length);
|
||||
|
||||
registerUhidListener(id, fd);
|
||||
} catch (Exception e) {
|
||||
close(fd);
|
||||
throw e;
|
||||
@@ -43,6 +67,62 @@ public final class UhidManager {
|
||||
}
|
||||
}
|
||||
|
||||
private void registerUhidListener(int id, FileDescriptor fd) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
queue.addOnFileDescriptorEventListener(fd, MessageQueue.OnFileDescriptorEventListener.EVENT_INPUT, (fd2, events) -> {
|
||||
try {
|
||||
buffer.clear();
|
||||
int r = Os.read(fd2, buffer);
|
||||
buffer.flip();
|
||||
if (r > 0) {
|
||||
int type = buffer.getInt();
|
||||
if (type == UHID_OUTPUT) {
|
||||
byte[] data = extractHidOutputData(buffer);
|
||||
if (data != null) {
|
||||
DeviceMessage msg = DeviceMessage.createUhidOutput(id, data);
|
||||
sender.send(msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (ErrnoException | InterruptedIOException e) {
|
||||
Ln.e("Failed to read UHID output", e);
|
||||
return 0;
|
||||
}
|
||||
return events;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private static byte[] extractHidOutputData(ByteBuffer buffer) {
|
||||
/*
|
||||
* #define UHID_DATA_MAX 4096
|
||||
* struct uhid_event {
|
||||
* uint32_t type;
|
||||
* union {
|
||||
* // ...
|
||||
* struct uhid_output_req {
|
||||
* __u8 data[UHID_DATA_MAX];
|
||||
* __u16 size;
|
||||
* __u8 rtype;
|
||||
* };
|
||||
* };
|
||||
* } __attribute__((__packed__));
|
||||
*/
|
||||
|
||||
if (buffer.remaining() < 4099) {
|
||||
Ln.w("Incomplete HID output");
|
||||
return null;
|
||||
}
|
||||
int size = buffer.getShort(buffer.position() + 4096) & 0xFFFF;
|
||||
if (size > 4096) {
|
||||
Ln.w("Incorrect HID output size: " + size);
|
||||
return null;
|
||||
}
|
||||
byte[] data = new byte[size];
|
||||
buffer.get(data);
|
||||
return data;
|
||||
}
|
||||
|
||||
public void writeInput(int id, byte[] data) throws IOException {
|
||||
FileDescriptor fd = fds.get(id);
|
||||
if (fd == null) {
|
||||
|
||||
Reference in New Issue
Block a user