mirror of https://github.com/Genymobile/scrcpy
Add UHID keyboard support
Use the following command: scrcpy --keyboard=uhid 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>uhid.38
parent
4d5b67cc80
commit
840680f546
@ -0,0 +1,72 @@
|
|||||||
|
#include "keyboard_uhid.h"
|
||||||
|
|
||||||
|
#include "util/log.h"
|
||||||
|
|
||||||
|
/** Downcast key processor to keyboard_uhid */
|
||||||
|
#define DOWNCAST(KP) container_of(KP, struct sc_keyboard_uhid, key_processor)
|
||||||
|
|
||||||
|
#define UHID_KEYBOARD_ID 1
|
||||||
|
|
||||||
|
static void
|
||||||
|
sc_key_processor_process_key(struct sc_key_processor *kp,
|
||||||
|
const struct sc_key_event *event,
|
||||||
|
uint64_t ack_to_wait) {
|
||||||
|
(void) ack_to_wait;
|
||||||
|
|
||||||
|
if (event->repeat) {
|
||||||
|
// In USB HID protocol, key repeat is handled by the host (Android), so
|
||||||
|
// just ignore key repeat here.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct sc_keyboard_uhid *kb = DOWNCAST(kp);
|
||||||
|
|
||||||
|
struct sc_hid_event hid_event;
|
||||||
|
|
||||||
|
// Not all keys are supported, just ignore unsupported keys
|
||||||
|
if (sc_hid_keyboard_event_from_key(&kb->hid, &hid_event, event)) {
|
||||||
|
struct sc_control_msg msg;
|
||||||
|
msg.type = SC_CONTROL_MSG_TYPE_UHID_INPUT;
|
||||||
|
msg.uhid_input.id = UHID_KEYBOARD_ID;
|
||||||
|
|
||||||
|
assert(hid_event.size <= SC_HID_MAX_SIZE);
|
||||||
|
memcpy(msg.uhid_input.data, hid_event.data, hid_event.size);
|
||||||
|
msg.uhid_input.size = hid_event.size;
|
||||||
|
|
||||||
|
if (!sc_controller_push_msg(kb->controller, &msg)) {
|
||||||
|
LOGE("Could not send UHID_INPUT message (key)");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
sc_keyboard_uhid_init(struct sc_keyboard_uhid *kb,
|
||||||
|
struct sc_controller *controller) {
|
||||||
|
sc_hid_keyboard_init(&kb->hid);
|
||||||
|
|
||||||
|
kb->controller = controller;
|
||||||
|
|
||||||
|
static const struct sc_key_processor_ops ops = {
|
||||||
|
.process_key = sc_key_processor_process_key,
|
||||||
|
// Never forward text input via HID (all the keys are injected
|
||||||
|
// separately)
|
||||||
|
.process_text = NULL,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Clipboard synchronization is requested over the same control socket, so
|
||||||
|
// there is no need for a specific synchronization mechanism
|
||||||
|
kb->key_processor.async_paste = false;
|
||||||
|
kb->key_processor.ops = &ops;
|
||||||
|
|
||||||
|
struct sc_control_msg msg;
|
||||||
|
msg.type = SC_CONTROL_MSG_TYPE_UHID_CREATE;
|
||||||
|
msg.uhid_create.id = UHID_KEYBOARD_ID;
|
||||||
|
msg.uhid_create.report_desc = SC_HID_KEYBOARD_REPORT_DESC;
|
||||||
|
msg.uhid_create.report_desc_size = SC_HID_KEYBOARD_REPORT_DESC_LEN;
|
||||||
|
if (!sc_controller_push_msg(controller, &msg)) {
|
||||||
|
LOGE("Could not send UHID_CREATE message (keyboard)");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
@ -0,0 +1,23 @@
|
|||||||
|
#ifndef SC_KEYBOARD_UHID_H
|
||||||
|
#define SC_KEYBOARD_UHID_H
|
||||||
|
|
||||||
|
#include "common.h"
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
|
||||||
|
#include "controller.h"
|
||||||
|
#include "hid/hid_keyboard.h"
|
||||||
|
#include "trait/key_processor.h"
|
||||||
|
|
||||||
|
struct sc_keyboard_uhid {
|
||||||
|
struct sc_key_processor key_processor; // key processor trait
|
||||||
|
|
||||||
|
struct sc_hid_keyboard hid;
|
||||||
|
struct sc_controller *controller;
|
||||||
|
};
|
||||||
|
|
||||||
|
bool
|
||||||
|
sc_keyboard_uhid_init(struct sc_keyboard_uhid *kb,
|
||||||
|
struct sc_controller *controller);
|
||||||
|
|
||||||
|
#endif
|
@ -0,0 +1,138 @@
|
|||||||
|
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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue