mirror of https://github.com/Genymobile/scrcpy
Add UHID keyboard support
Use the following command: scrcpy --keyboard=uhid Co-authored-by: Romain Vimont <rom@rom1v.com> Signed-off-by: Romain Vimont <rom@rom1v.com>uhid.32
parent
bfe6455183
commit
3fa70d55f1
@ -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