You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
scrcpy/server/src/main/java/com/genymobile/scrcpy/UhidManager.java

139 lines
4.1 KiB
Java

package com.genymobile.scrcpy;
import android.system.ErrnoException;
import android.system.Os;
import android.system.OsConstants;
import android.util.ArrayMap;
import java.io.FileDescriptor;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.charset.StandardCharsets;
public final class UhidManager {
// Linux: include/uapi/linux/uhid.h
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 final ArrayMap<Integer, FileDescriptor> fds = new ArrayMap<>();
public void open(int id, byte[] reportDesc) throws IOException {
try {
FileDescriptor fd = Os.open("/dev/uhid", OsConstants.O_RDWR, 0);
try {
FileDescriptor old = fds.put(id, fd);
if (old != null) {
Ln.w("Duplicate UHID id: " + id);
close(old);
}
byte[] req = buildUhidCreate2Req(reportDesc);
Os.write(fd, req, 0, req.length);
} catch (Exception e) {
close(fd);
throw e;
}
} catch (ErrnoException e) {
throw new IOException(e);
}
}
public void writeInput(int id, byte[] data) throws IOException {
FileDescriptor fd = fds.get(id);
if (fd == null) {
Ln.w("Unknown UHID id: " + id);
return;
}
try {
byte[] req = buildUhidInput2Req(data);
Os.write(fd, req, 0, req.length);
} catch (ErrnoException e) {
throw new IOException(e);
}
}
private static byte[] buildUhidCreate2Req(byte[] reportDesc) {
/*
* struct uhid_event {
* uint32_t type;
* union {
* // ...
* struct uhid_create2_req {
* uint8_t name[128];
* uint8_t phys[64];
* uint8_t uniq[64];
* uint16_t rd_size;
* uint16_t bus;
* uint32_t vendor;
* uint32_t product;
* uint32_t version;
* uint32_t country;
* uint8_t rd_data[HID_MAX_DESCRIPTOR_SIZE];
* };
* };
* } __attribute__((__packed__));
*/
byte[] empty = new byte[256];
ByteBuffer buf = ByteBuffer.allocate(280 + reportDesc.length).order(ByteOrder.nativeOrder());
buf.putInt(UHID_CREATE2);
buf.put("scrcpy".getBytes(StandardCharsets.US_ASCII));
buf.put(empty, 0, 256 - "scrcpy".length());
buf.putShort((short) reportDesc.length);
buf.putShort(BUS_VIRTUAL);
buf.putInt(0); // vendor id
buf.putInt(0); // product id
buf.putInt(0); // version
buf.putInt(0); // country;
buf.put(reportDesc);
return buf.array();
}
private static byte[] buildUhidInput2Req(byte[] data) {
/*
* struct uhid_event {
* uint32_t type;
* union {
* // ...
* struct uhid_input2_req {
* uint16_t size;
* uint8_t data[UHID_DATA_MAX];
* };
* };
* } __attribute__((__packed__));
*/
ByteBuffer buf = ByteBuffer.allocate(6 + data.length).order(ByteOrder.nativeOrder());
buf.putInt(UHID_INPUT2);
buf.putShort((short) data.length);
buf.put(data);
return buf.array();
}
public void close(int id) {
FileDescriptor fd = fds.get(id);
assert fd != null;
close(fd);
}
public void closeAll() {
for (FileDescriptor fd : fds.values()) {
close(fd);
}
}
private static void close(FileDescriptor fd) {
try {
Os.close(fd);
} catch (ErrnoException e) {
Ln.e("Failed to close uhid: " + e.getMessage());
}
}
}