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:
Romain Vimont 2024-09-08 19:57:14 +02:00
parent 68e27c7357
commit 7f250dd669
15 changed files with 88 additions and 23 deletions

View file

@ -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;
index += write_string_tiny(&buf[index], msg->uhid_create.name, 127);
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); msg->uhid_create.report_desc_size);
return 5 + 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);

View file

@ -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;

View file

@ -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;
}; };

View file

@ -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);

View file

@ -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);
} }

View file

@ -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);
} }

View file

@ -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;

View file

@ -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)) {

View file

@ -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)) {

View file

@ -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)));

View file

@ -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;
} }

View file

@ -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 {

View file

@ -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());

View file

@ -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

View file

@ -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