Mention physical gamepad names for UHID devices
Initialize UHID devices with a custom name: - "scrcpy: $GAMEPAD_NAME" for gamepads - "scrcpy" for keyboard and mouse (or if no gamepad name is available) The name may appear in Android apps. PR #5270 <https://github.com/Genymobile/scrcpy/pull/5270>
This commit is contained in:
parent
d7d0d90b99
commit
8781e68e58
@ -83,15 +83,34 @@ write_position(uint8_t *buf, const struct sc_position *position) {
|
|||||||
sc_write16be(&buf[10], position->screen_size.height);
|
sc_write16be(&buf[10], position->screen_size.height);
|
||||||
}
|
}
|
||||||
|
|
||||||
// write length (4 bytes) + string (non null-terminated)
|
// Write truncated string, and return the size
|
||||||
|
static size_t
|
||||||
|
write_string_payload(uint8_t *payload, const char *utf8, size_t max_len) {
|
||||||
|
if (!utf8) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
size_t len = sc_str_utf8_truncation_index(utf8, max_len);
|
||||||
|
memcpy(payload, utf8, len);
|
||||||
|
return len;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write length (4 bytes) + string (non null-terminated)
|
||||||
static size_t
|
static size_t
|
||||||
write_string(uint8_t *buf, const char *utf8, size_t max_len) {
|
write_string(uint8_t *buf, const char *utf8, size_t max_len) {
|
||||||
size_t len = sc_str_utf8_truncation_index(utf8, max_len);
|
size_t len = write_string_payload(buf + 4, utf8, max_len);
|
||||||
sc_write32be(buf, len);
|
sc_write32be(buf, len);
|
||||||
memcpy(&buf[4], utf8, len);
|
|
||||||
return 4 + len;
|
return 4 + len;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Write length (1 byte) + string (non null-terminated)
|
||||||
|
static size_t
|
||||||
|
write_string_tiny(uint8_t *buf, const char *utf8, size_t max_len) {
|
||||||
|
assert(max_len <= 0xFF);
|
||||||
|
size_t len = write_string_payload(buf + 1, utf8, max_len);
|
||||||
|
buf[0] = len;
|
||||||
|
return 1 + len;
|
||||||
|
}
|
||||||
|
|
||||||
size_t
|
size_t
|
||||||
sc_control_msg_serialize(const struct sc_control_msg *msg, uint8_t *buf) {
|
sc_control_msg_serialize(const struct sc_control_msg *msg, uint8_t *buf) {
|
||||||
buf[0] = msg->type;
|
buf[0] = msg->type;
|
||||||
@ -144,10 +163,18 @@ sc_control_msg_serialize(const struct sc_control_msg *msg, uint8_t *buf) {
|
|||||||
return 2;
|
return 2;
|
||||||
case SC_CONTROL_MSG_TYPE_UHID_CREATE:
|
case SC_CONTROL_MSG_TYPE_UHID_CREATE:
|
||||||
sc_write16be(&buf[1], msg->uhid_create.id);
|
sc_write16be(&buf[1], msg->uhid_create.id);
|
||||||
sc_write16be(&buf[3], msg->uhid_create.report_desc_size);
|
|
||||||
memcpy(&buf[5], msg->uhid_create.report_desc,
|
size_t index = 3;
|
||||||
msg->uhid_create.report_desc_size);
|
index += write_string_tiny(&buf[index], msg->uhid_create.name, 127);
|
||||||
return 5 + msg->uhid_create.report_desc_size;
|
|
||||||
|
sc_write16be(&buf[index], msg->uhid_create.report_desc_size);
|
||||||
|
index += 2;
|
||||||
|
|
||||||
|
memcpy(&buf[index], msg->uhid_create.report_desc,
|
||||||
|
msg->uhid_create.report_desc_size);
|
||||||
|
index += msg->uhid_create.report_desc_size;
|
||||||
|
|
||||||
|
return index;
|
||||||
case SC_CONTROL_MSG_TYPE_UHID_INPUT:
|
case SC_CONTROL_MSG_TYPE_UHID_INPUT:
|
||||||
sc_write16be(&buf[1], msg->uhid_input.id);
|
sc_write16be(&buf[1], msg->uhid_input.id);
|
||||||
sc_write16be(&buf[3], msg->uhid_input.size);
|
sc_write16be(&buf[3], msg->uhid_input.size);
|
||||||
@ -253,10 +280,15 @@ sc_control_msg_log(const struct sc_control_msg *msg) {
|
|||||||
case SC_CONTROL_MSG_TYPE_ROTATE_DEVICE:
|
case SC_CONTROL_MSG_TYPE_ROTATE_DEVICE:
|
||||||
LOG_CMSG("rotate device");
|
LOG_CMSG("rotate device");
|
||||||
break;
|
break;
|
||||||
case SC_CONTROL_MSG_TYPE_UHID_CREATE:
|
case SC_CONTROL_MSG_TYPE_UHID_CREATE: {
|
||||||
LOG_CMSG("UHID create [%" PRIu16 "] report_desc_size=%" PRIu16,
|
// Quote only if name is not null
|
||||||
msg->uhid_create.id, msg->uhid_create.report_desc_size);
|
const char *name = msg->uhid_create.name;
|
||||||
|
const char *quote = name ? "\"" : "";
|
||||||
|
LOG_CMSG("UHID create [%" PRIu16 "] name=%s%s%s "
|
||||||
|
"report_desc_size=%" PRIu16, msg->uhid_create.id,
|
||||||
|
quote, name, quote, msg->uhid_create.report_desc_size);
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
case SC_CONTROL_MSG_TYPE_UHID_INPUT: {
|
case SC_CONTROL_MSG_TYPE_UHID_INPUT: {
|
||||||
char *hex = sc_str_to_hex_string(msg->uhid_input.data,
|
char *hex = sc_str_to_hex_string(msg->uhid_input.data,
|
||||||
msg->uhid_input.size);
|
msg->uhid_input.size);
|
||||||
|
@ -98,6 +98,7 @@ struct sc_control_msg {
|
|||||||
} set_screen_power_mode;
|
} set_screen_power_mode;
|
||||||
struct {
|
struct {
|
||||||
uint16_t id;
|
uint16_t id;
|
||||||
|
const char *name; // pointer to static data
|
||||||
uint16_t report_desc_size;
|
uint16_t report_desc_size;
|
||||||
const uint8_t *report_desc; // pointer to static data
|
const uint8_t *report_desc; // pointer to static data
|
||||||
} uhid_create;
|
} uhid_create;
|
||||||
|
@ -15,6 +15,7 @@ struct sc_hid_input {
|
|||||||
|
|
||||||
struct sc_hid_open {
|
struct sc_hid_open {
|
||||||
uint16_t hid_id;
|
uint16_t hid_id;
|
||||||
|
const char *name; // pointer to static memory
|
||||||
const uint8_t *report_desc; // pointer to static memory
|
const uint8_t *report_desc; // pointer to static memory
|
||||||
size_t report_desc_size;
|
size_t report_desc_size;
|
||||||
};
|
};
|
||||||
|
@ -243,8 +243,14 @@ sc_hid_gamepad_generate_open(struct sc_hid_gamepad *hid,
|
|||||||
|
|
||||||
sc_hid_gamepad_slot_init(&hid->slots[slot_idx], gamepad_id);
|
sc_hid_gamepad_slot_init(&hid->slots[slot_idx], gamepad_id);
|
||||||
|
|
||||||
|
SDL_GameController* game_controller =
|
||||||
|
SDL_GameControllerFromInstanceID(gamepad_id);
|
||||||
|
assert(game_controller);
|
||||||
|
const char *name = SDL_GameControllerName(game_controller);
|
||||||
|
|
||||||
uint16_t hid_id = sc_hid_gamepad_slot_get_id(slot_idx);
|
uint16_t hid_id = sc_hid_gamepad_slot_get_id(slot_idx);
|
||||||
hid_open->hid_id = hid_id;
|
hid_open->hid_id = hid_id;
|
||||||
|
hid_open->name = name;
|
||||||
hid_open->report_desc = SC_HID_GAMEPAD_REPORT_DESC;
|
hid_open->report_desc = SC_HID_GAMEPAD_REPORT_DESC;
|
||||||
hid_open->report_desc_size = sizeof(SC_HID_GAMEPAD_REPORT_DESC);
|
hid_open->report_desc_size = sizeof(SC_HID_GAMEPAD_REPORT_DESC);
|
||||||
|
|
||||||
|
@ -335,6 +335,7 @@ sc_hid_keyboard_generate_input_from_mods(struct sc_hid_input *hid_input,
|
|||||||
|
|
||||||
void sc_hid_keyboard_generate_open(struct sc_hid_open *hid_open) {
|
void sc_hid_keyboard_generate_open(struct sc_hid_open *hid_open) {
|
||||||
hid_open->hid_id = SC_HID_ID_KEYBOARD;
|
hid_open->hid_id = SC_HID_ID_KEYBOARD;
|
||||||
|
hid_open->name = NULL; // No name specified after "scrcpy"
|
||||||
hid_open->report_desc = SC_HID_KEYBOARD_REPORT_DESC;
|
hid_open->report_desc = SC_HID_KEYBOARD_REPORT_DESC;
|
||||||
hid_open->report_desc_size = sizeof(SC_HID_KEYBOARD_REPORT_DESC);
|
hid_open->report_desc_size = sizeof(SC_HID_KEYBOARD_REPORT_DESC);
|
||||||
}
|
}
|
||||||
|
@ -190,6 +190,7 @@ sc_hid_mouse_generate_input_from_scroll(struct sc_hid_input *hid_input,
|
|||||||
|
|
||||||
void sc_hid_mouse_generate_open(struct sc_hid_open *hid_open) {
|
void sc_hid_mouse_generate_open(struct sc_hid_open *hid_open) {
|
||||||
hid_open->hid_id = SC_HID_ID_MOUSE;
|
hid_open->hid_id = SC_HID_ID_MOUSE;
|
||||||
|
hid_open->name = NULL; // No name specified after "scrcpy"
|
||||||
hid_open->report_desc = SC_HID_MOUSE_REPORT_DESC;
|
hid_open->report_desc = SC_HID_MOUSE_REPORT_DESC;
|
||||||
hid_open->report_desc_size = sizeof(SC_HID_MOUSE_REPORT_DESC);
|
hid_open->report_desc_size = sizeof(SC_HID_MOUSE_REPORT_DESC);
|
||||||
}
|
}
|
||||||
|
@ -30,6 +30,7 @@ sc_gamepad_uhid_send_open(struct sc_gamepad_uhid *gamepad,
|
|||||||
struct sc_control_msg msg;
|
struct sc_control_msg msg;
|
||||||
msg.type = SC_CONTROL_MSG_TYPE_UHID_CREATE;
|
msg.type = SC_CONTROL_MSG_TYPE_UHID_CREATE;
|
||||||
msg.uhid_create.id = hid_open->hid_id;
|
msg.uhid_create.id = hid_open->hid_id;
|
||||||
|
msg.uhid_create.name = hid_open->name;
|
||||||
msg.uhid_create.report_desc = hid_open->report_desc;
|
msg.uhid_create.report_desc = hid_open->report_desc;
|
||||||
msg.uhid_create.report_desc_size = hid_open->report_desc_size;
|
msg.uhid_create.report_desc_size = hid_open->report_desc_size;
|
||||||
|
|
||||||
|
@ -152,6 +152,7 @@ sc_keyboard_uhid_init(struct sc_keyboard_uhid *kb,
|
|||||||
struct sc_control_msg msg;
|
struct sc_control_msg msg;
|
||||||
msg.type = SC_CONTROL_MSG_TYPE_UHID_CREATE;
|
msg.type = SC_CONTROL_MSG_TYPE_UHID_CREATE;
|
||||||
msg.uhid_create.id = SC_HID_ID_KEYBOARD;
|
msg.uhid_create.id = SC_HID_ID_KEYBOARD;
|
||||||
|
msg.uhid_create.name = hid_open.name;
|
||||||
msg.uhid_create.report_desc = hid_open.report_desc;
|
msg.uhid_create.report_desc = hid_open.report_desc;
|
||||||
msg.uhid_create.report_desc_size = hid_open.report_desc_size;
|
msg.uhid_create.report_desc_size = hid_open.report_desc_size;
|
||||||
if (!sc_controller_push_msg(controller, &msg)) {
|
if (!sc_controller_push_msg(controller, &msg)) {
|
||||||
|
@ -81,6 +81,7 @@ sc_mouse_uhid_init(struct sc_mouse_uhid *mouse,
|
|||||||
struct sc_control_msg msg;
|
struct sc_control_msg msg;
|
||||||
msg.type = SC_CONTROL_MSG_TYPE_UHID_CREATE;
|
msg.type = SC_CONTROL_MSG_TYPE_UHID_CREATE;
|
||||||
msg.uhid_create.id = SC_HID_ID_MOUSE;
|
msg.uhid_create.id = SC_HID_ID_MOUSE;
|
||||||
|
msg.uhid_create.name = hid_open.name;
|
||||||
msg.uhid_create.report_desc = hid_open.report_desc;
|
msg.uhid_create.report_desc = hid_open.report_desc;
|
||||||
msg.uhid_create.report_desc_size = hid_open.report_desc_size;
|
msg.uhid_create.report_desc_size = hid_open.report_desc_size;
|
||||||
if (!sc_controller_push_msg(controller, &msg)) {
|
if (!sc_controller_push_msg(controller, &msg)) {
|
||||||
|
@ -329,6 +329,7 @@ static void test_serialize_uhid_create(void) {
|
|||||||
.type = SC_CONTROL_MSG_TYPE_UHID_CREATE,
|
.type = SC_CONTROL_MSG_TYPE_UHID_CREATE,
|
||||||
.uhid_create = {
|
.uhid_create = {
|
||||||
.id = 42,
|
.id = 42,
|
||||||
|
.name = "ABC",
|
||||||
.report_desc_size = sizeof(report_desc),
|
.report_desc_size = sizeof(report_desc),
|
||||||
.report_desc = report_desc,
|
.report_desc = report_desc,
|
||||||
},
|
},
|
||||||
@ -336,12 +337,14 @@ static void test_serialize_uhid_create(void) {
|
|||||||
|
|
||||||
uint8_t buf[SC_CONTROL_MSG_MAX_SIZE];
|
uint8_t buf[SC_CONTROL_MSG_MAX_SIZE];
|
||||||
size_t size = sc_control_msg_serialize(&msg, buf);
|
size_t size = sc_control_msg_serialize(&msg, buf);
|
||||||
assert(size == 16);
|
assert(size == 20);
|
||||||
|
|
||||||
const uint8_t expected[] = {
|
const uint8_t expected[] = {
|
||||||
SC_CONTROL_MSG_TYPE_UHID_CREATE,
|
SC_CONTROL_MSG_TYPE_UHID_CREATE,
|
||||||
0, 42, // id
|
0, 42, // id
|
||||||
0, 11, // size
|
3, // name size
|
||||||
|
65, 66, 67, // "ABC"
|
||||||
|
0, 11, // report desc size
|
||||||
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11,
|
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11,
|
||||||
};
|
};
|
||||||
assert(!memcmp(buf, expected, sizeof(expected)));
|
assert(!memcmp(buf, expected, sizeof(expected)));
|
||||||
|
@ -131,10 +131,11 @@ public final class ControlMessage {
|
|||||||
return msg;
|
return msg;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ControlMessage createUhidCreate(int id, byte[] reportDesc) {
|
public static ControlMessage createUhidCreate(int id, String name, byte[] reportDesc) {
|
||||||
ControlMessage msg = new ControlMessage();
|
ControlMessage msg = new ControlMessage();
|
||||||
msg.type = TYPE_UHID_CREATE;
|
msg.type = TYPE_UHID_CREATE;
|
||||||
msg.id = id;
|
msg.id = id;
|
||||||
|
msg.text = name;
|
||||||
msg.data = reportDesc;
|
msg.data = reportDesc;
|
||||||
return msg;
|
return msg;
|
||||||
}
|
}
|
||||||
|
@ -75,11 +75,16 @@ public class ControlMessageReader {
|
|||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
private String parseString() throws IOException {
|
private String parseString(int sizeBytes) throws IOException {
|
||||||
byte[] data = parseByteArray(4);
|
assert sizeBytes > 0 && sizeBytes <= 4;
|
||||||
|
byte[] data = parseByteArray(sizeBytes);
|
||||||
return new String(data, StandardCharsets.UTF_8);
|
return new String(data, StandardCharsets.UTF_8);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private String parseString() throws IOException {
|
||||||
|
return parseString(4);
|
||||||
|
}
|
||||||
|
|
||||||
private byte[] parseByteArray(int sizeBytes) throws IOException {
|
private byte[] parseByteArray(int sizeBytes) throws IOException {
|
||||||
int len = parseBufferLength(sizeBytes);
|
int len = parseBufferLength(sizeBytes);
|
||||||
byte[] data = new byte[len];
|
byte[] data = new byte[len];
|
||||||
@ -134,8 +139,9 @@ public class ControlMessageReader {
|
|||||||
|
|
||||||
private ControlMessage parseUhidCreate() throws IOException {
|
private ControlMessage parseUhidCreate() throws IOException {
|
||||||
int id = dis.readUnsignedShort();
|
int id = dis.readUnsignedShort();
|
||||||
|
String name = parseString(1);
|
||||||
byte[] data = parseByteArray(2);
|
byte[] data = parseByteArray(2);
|
||||||
return ControlMessage.createUhidCreate(id, data);
|
return ControlMessage.createUhidCreate(id, name, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
private ControlMessage parseUhidInput() throws IOException {
|
private ControlMessage parseUhidInput() throws IOException {
|
||||||
|
@ -210,7 +210,7 @@ public class Controller implements AsyncProcessor {
|
|||||||
device.rotateDevice();
|
device.rotateDevice();
|
||||||
break;
|
break;
|
||||||
case ControlMessage.TYPE_UHID_CREATE:
|
case ControlMessage.TYPE_UHID_CREATE:
|
||||||
getUhidManager().open(msg.getId(), msg.getData());
|
getUhidManager().open(msg.getId(), msg.getText(), msg.getData());
|
||||||
break;
|
break;
|
||||||
case ControlMessage.TYPE_UHID_INPUT:
|
case ControlMessage.TYPE_UHID_INPUT:
|
||||||
getUhidManager().writeInput(msg.getId(), msg.getData());
|
getUhidManager().writeInput(msg.getId(), msg.getData());
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package com.genymobile.scrcpy.control;
|
package com.genymobile.scrcpy.control;
|
||||||
|
|
||||||
import com.genymobile.scrcpy.util.Ln;
|
import com.genymobile.scrcpy.util.Ln;
|
||||||
|
import com.genymobile.scrcpy.util.StringUtils;
|
||||||
|
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.HandlerThread;
|
import android.os.HandlerThread;
|
||||||
@ -46,7 +47,7 @@ public final class UhidManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void open(int id, byte[] reportDesc) throws IOException {
|
public void open(int id, String name, byte[] reportDesc) throws IOException {
|
||||||
try {
|
try {
|
||||||
FileDescriptor fd = Os.open("/dev/uhid", OsConstants.O_RDWR, 0);
|
FileDescriptor fd = Os.open("/dev/uhid", OsConstants.O_RDWR, 0);
|
||||||
try {
|
try {
|
||||||
@ -56,7 +57,7 @@ public final class UhidManager {
|
|||||||
close(old);
|
close(old);
|
||||||
}
|
}
|
||||||
|
|
||||||
byte[] req = buildUhidCreate2Req(reportDesc);
|
byte[] req = buildUhidCreate2Req(name, reportDesc);
|
||||||
Os.write(fd, req, 0, req.length);
|
Os.write(fd, req, 0, req.length);
|
||||||
|
|
||||||
registerUhidListener(id, fd);
|
registerUhidListener(id, fd);
|
||||||
@ -146,7 +147,7 @@ public final class UhidManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static byte[] buildUhidCreate2Req(byte[] reportDesc) {
|
private static byte[] buildUhidCreate2Req(String name, byte[] reportDesc) {
|
||||||
/*
|
/*
|
||||||
* struct uhid_event {
|
* struct uhid_event {
|
||||||
* uint32_t type;
|
* uint32_t type;
|
||||||
@ -171,8 +172,14 @@ public final class UhidManager {
|
|||||||
byte[] empty = new byte[256];
|
byte[] empty = new byte[256];
|
||||||
ByteBuffer buf = ByteBuffer.allocate(280 + reportDesc.length).order(ByteOrder.nativeOrder());
|
ByteBuffer buf = ByteBuffer.allocate(280 + reportDesc.length).order(ByteOrder.nativeOrder());
|
||||||
buf.putInt(UHID_CREATE2);
|
buf.putInt(UHID_CREATE2);
|
||||||
buf.put("scrcpy".getBytes(StandardCharsets.US_ASCII));
|
|
||||||
buf.put(empty, 0, 256 - "scrcpy".length());
|
String actualName = name.isEmpty() ? "scrcpy" : "scrcpy: " + name;
|
||||||
|
byte[] utf8Name = actualName.getBytes(StandardCharsets.UTF_8);
|
||||||
|
int len = StringUtils.getUtf8TruncationIndex(utf8Name, 127);
|
||||||
|
assert len <= 127;
|
||||||
|
buf.put(utf8Name, 0, len);
|
||||||
|
buf.put(empty, 0, 256 - len);
|
||||||
|
|
||||||
buf.putShort((short) reportDesc.length);
|
buf.putShort((short) reportDesc.length);
|
||||||
buf.putShort(BUS_VIRTUAL);
|
buf.putShort(BUS_VIRTUAL);
|
||||||
buf.putInt(0); // vendor id
|
buf.putInt(0); // vendor id
|
||||||
|
@ -324,8 +324,10 @@ public class ControlMessageReaderTest {
|
|||||||
DataOutputStream dos = new DataOutputStream(bos);
|
DataOutputStream dos = new DataOutputStream(bos);
|
||||||
dos.writeByte(ControlMessage.TYPE_UHID_CREATE);
|
dos.writeByte(ControlMessage.TYPE_UHID_CREATE);
|
||||||
dos.writeShort(42); // id
|
dos.writeShort(42); // id
|
||||||
|
dos.writeByte(3); // name size
|
||||||
|
dos.write("ABC".getBytes(StandardCharsets.US_ASCII));
|
||||||
byte[] data = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11};
|
byte[] data = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11};
|
||||||
dos.writeShort(data.length); // size
|
dos.writeShort(data.length); // report desc size
|
||||||
dos.write(data);
|
dos.write(data);
|
||||||
byte[] packet = bos.toByteArray();
|
byte[] packet = bos.toByteArray();
|
||||||
|
|
||||||
@ -335,6 +337,7 @@ public class ControlMessageReaderTest {
|
|||||||
ControlMessage event = reader.read();
|
ControlMessage event = reader.read();
|
||||||
Assert.assertEquals(ControlMessage.TYPE_UHID_CREATE, event.getType());
|
Assert.assertEquals(ControlMessage.TYPE_UHID_CREATE, event.getType());
|
||||||
Assert.assertEquals(42, event.getId());
|
Assert.assertEquals(42, event.getId());
|
||||||
|
Assert.assertEquals("ABC", event.getText());
|
||||||
Assert.assertArrayEquals(data, event.getData());
|
Assert.assertArrayEquals(data, event.getData());
|
||||||
|
|
||||||
Assert.assertEquals(-1, bis.read()); // EOS
|
Assert.assertEquals(-1, bis.read()); // EOS
|
||||||
|
Loading…
x
Reference in New Issue
Block a user