From 32091f0c39981229ec8e139ecb4f7e0fbd274a0a Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 8 Sep 2024 19:57:14 +0200 Subject: [PATCH] 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. --- app/src/control_msg.c | 52 +++++++++++++++---- app/src/control_msg.h | 1 + app/src/hid/hid_event.h | 1 + app/src/hid/hid_gamepad.c | 6 +++ app/src/hid/hid_keyboard.c | 1 + app/src/hid/hid_mouse.c | 1 + app/src/uhid/gamepad_uhid.c | 1 + app/src/uhid/keyboard_uhid.c | 1 + app/src/uhid/mouse_uhid.c | 1 + app/tests/test_control_msg_serialize.c | 7 ++- .../scrcpy/control/ControlMessage.java | 3 +- .../scrcpy/control/ControlMessageReader.java | 9 +++- .../genymobile/scrcpy/control/Controller.java | 2 +- .../scrcpy/control/UhidManager.java | 17 ++++-- .../control/ControlMessageReaderTest.java | 5 +- 15 files changed, 87 insertions(+), 21 deletions(-) diff --git a/app/src/control_msg.c b/app/src/control_msg.c index bef7ab051c..d599b62d77 100644 --- a/app/src/control_msg.c +++ b/app/src/control_msg.c @@ -83,15 +83,34 @@ write_position(uint8_t *buf, const struct sc_position *position) { 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(uint8_t *buf, const char *utf8, size_t max_len) { +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 +write_string(uint8_t *buf, const char *utf8, size_t max_len) { + size_t len = write_string_payload(buf + 4, utf8, max_len); sc_write32be(buf, len); - memcpy(&buf[4], utf8, 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 sc_control_msg_serialize(const struct sc_control_msg *msg, uint8_t *buf) { buf[0] = msg->type; @@ -144,10 +163,18 @@ sc_control_msg_serialize(const struct sc_control_msg *msg, uint8_t *buf) { return 2; case SC_CONTROL_MSG_TYPE_UHID_CREATE: 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, - msg->uhid_create.report_desc_size); - return 5 + msg->uhid_create.report_desc_size; + + 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); + index += msg->uhid_create.report_desc_size; + + return index; case SC_CONTROL_MSG_TYPE_UHID_INPUT: sc_write16be(&buf[1], msg->uhid_input.id); 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: LOG_CMSG("rotate device"); break; - case SC_CONTROL_MSG_TYPE_UHID_CREATE: - LOG_CMSG("UHID create [%" PRIu16 "] report_desc_size=%" PRIu16, - msg->uhid_create.id, msg->uhid_create.report_desc_size); + case SC_CONTROL_MSG_TYPE_UHID_CREATE: { + // Quote only if name is not null + 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; + } case SC_CONTROL_MSG_TYPE_UHID_INPUT: { char *hex = sc_str_to_hex_string(msg->uhid_input.data, msg->uhid_input.size); diff --git a/app/src/control_msg.h b/app/src/control_msg.h index b48d91af35..1ae8cae43c 100644 --- a/app/src/control_msg.h +++ b/app/src/control_msg.h @@ -98,6 +98,7 @@ struct sc_control_msg { } set_screen_power_mode; struct { uint16_t id; + const char *name; // pointer to static data uint16_t report_desc_size; const uint8_t *report_desc; // pointer to static data } uhid_create; diff --git a/app/src/hid/hid_event.h b/app/src/hid/hid_event.h index d6818e3014..37c3611baf 100644 --- a/app/src/hid/hid_event.h +++ b/app/src/hid/hid_event.h @@ -15,6 +15,7 @@ struct sc_hid_input { struct sc_hid_open { uint16_t hid_id; + const char *name; // pointer to static memory const uint8_t *report_desc; // pointer to static memory size_t report_desc_size; }; diff --git a/app/src/hid/hid_gamepad.c b/app/src/hid/hid_gamepad.c index f959e89c3b..bc22bb054e 100644 --- a/app/src/hid/hid_gamepad.c +++ b/app/src/hid/hid_gamepad.c @@ -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); + 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); hid_open->hid_id = hid_id; + hid_open->name = name; hid_open->report_desc = SC_HID_GAMEPAD_REPORT_DESC; hid_open->report_desc_size = sizeof(SC_HID_GAMEPAD_REPORT_DESC); diff --git a/app/src/hid/hid_keyboard.c b/app/src/hid/hid_keyboard.c index 3c8d33caeb..93dc2df6fa 100644 --- a/app/src/hid/hid_keyboard.c +++ b/app/src/hid/hid_keyboard.c @@ -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) { 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_size = sizeof(SC_HID_KEYBOARD_REPORT_DESC); } diff --git a/app/src/hid/hid_mouse.c b/app/src/hid/hid_mouse.c index 8e3d9ac1f5..f3c9ef32d2 100644 --- a/app/src/hid/hid_mouse.c +++ b/app/src/hid/hid_mouse.c @@ -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) { 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_size = sizeof(SC_HID_MOUSE_REPORT_DESC); } diff --git a/app/src/uhid/gamepad_uhid.c b/app/src/uhid/gamepad_uhid.c index 3c8d364311..62b0f65375 100644 --- a/app/src/uhid/gamepad_uhid.c +++ b/app/src/uhid/gamepad_uhid.c @@ -30,6 +30,7 @@ sc_gamepad_uhid_send_open(struct sc_gamepad_uhid *gamepad, struct sc_control_msg msg; msg.type = SC_CONTROL_MSG_TYPE_UHID_CREATE; 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_size = hid_open->report_desc_size; diff --git a/app/src/uhid/keyboard_uhid.c b/app/src/uhid/keyboard_uhid.c index 11d41e400a..9fdf4def33 100644 --- a/app/src/uhid/keyboard_uhid.c +++ b/app/src/uhid/keyboard_uhid.c @@ -152,6 +152,7 @@ sc_keyboard_uhid_init(struct sc_keyboard_uhid *kb, struct sc_control_msg msg; msg.type = SC_CONTROL_MSG_TYPE_UHID_CREATE; 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_size = hid_open.report_desc_size; if (!sc_controller_push_msg(controller, &msg)) { diff --git a/app/src/uhid/mouse_uhid.c b/app/src/uhid/mouse_uhid.c index 9544ab0dd3..1dc02777bd 100644 --- a/app/src/uhid/mouse_uhid.c +++ b/app/src/uhid/mouse_uhid.c @@ -81,6 +81,7 @@ sc_mouse_uhid_init(struct sc_mouse_uhid *mouse, struct sc_control_msg msg; msg.type = SC_CONTROL_MSG_TYPE_UHID_CREATE; 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_size = hid_open.report_desc_size; if (!sc_controller_push_msg(controller, &msg)) { diff --git a/app/tests/test_control_msg_serialize.c b/app/tests/test_control_msg_serialize.c index f88048d8b2..72ec61ee7b 100644 --- a/app/tests/test_control_msg_serialize.c +++ b/app/tests/test_control_msg_serialize.c @@ -329,6 +329,7 @@ static void test_serialize_uhid_create(void) { .type = SC_CONTROL_MSG_TYPE_UHID_CREATE, .uhid_create = { .id = 42, + .name = "ABC", .report_desc_size = sizeof(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]; size_t size = sc_control_msg_serialize(&msg, buf); - assert(size == 16); + assert(size == 20); const uint8_t expected[] = { SC_CONTROL_MSG_TYPE_UHID_CREATE, 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, }; assert(!memcmp(buf, expected, sizeof(expected))); diff --git a/server/src/main/java/com/genymobile/scrcpy/control/ControlMessage.java b/server/src/main/java/com/genymobile/scrcpy/control/ControlMessage.java index ef71353a42..d1406ed0a1 100644 --- a/server/src/main/java/com/genymobile/scrcpy/control/ControlMessage.java +++ b/server/src/main/java/com/genymobile/scrcpy/control/ControlMessage.java @@ -131,10 +131,11 @@ public static ControlMessage createEmpty(int type) { return msg; } - public static ControlMessage createUhidCreate(int id, byte[] reportDesc) { + public static ControlMessage createUhidCreate(int id, String name, byte[] reportDesc) { ControlMessage msg = new ControlMessage(); msg.type = TYPE_UHID_CREATE; msg.id = id; + msg.text = name; msg.data = reportDesc; return msg; } diff --git a/server/src/main/java/com/genymobile/scrcpy/control/ControlMessageReader.java b/server/src/main/java/com/genymobile/scrcpy/control/ControlMessageReader.java index ef7877f141..17ab1e5dae 100644 --- a/server/src/main/java/com/genymobile/scrcpy/control/ControlMessageReader.java +++ b/server/src/main/java/com/genymobile/scrcpy/control/ControlMessageReader.java @@ -75,6 +75,12 @@ private int parseBufferLength(int sizeBytes) throws IOException { return value; } + private String parseString(int sizeBytes) throws IOException { + assert sizeBytes > 0 && sizeBytes <= 4; + byte[] data = parseByteArray(sizeBytes); + return new String(data, StandardCharsets.UTF_8); + } + private String parseString() throws IOException { byte[] data = parseByteArray(4); return new String(data, StandardCharsets.UTF_8); @@ -134,8 +140,9 @@ private ControlMessage parseSetScreenPowerMode() throws IOException { private ControlMessage parseUhidCreate() throws IOException { int id = dis.readUnsignedShort(); + String name = parseString(1); byte[] data = parseByteArray(2); - return ControlMessage.createUhidCreate(id, data); + return ControlMessage.createUhidCreate(id, name, data); } private ControlMessage parseUhidInput() throws IOException { diff --git a/server/src/main/java/com/genymobile/scrcpy/control/Controller.java b/server/src/main/java/com/genymobile/scrcpy/control/Controller.java index e656cbb6e8..3825165522 100644 --- a/server/src/main/java/com/genymobile/scrcpy/control/Controller.java +++ b/server/src/main/java/com/genymobile/scrcpy/control/Controller.java @@ -210,7 +210,7 @@ private boolean handleEvent() throws IOException { device.rotateDevice(); break; case ControlMessage.TYPE_UHID_CREATE: - getUhidManager().open(msg.getId(), msg.getData()); + getUhidManager().open(msg.getId(), msg.getText(), msg.getData()); break; case ControlMessage.TYPE_UHID_INPUT: getUhidManager().writeInput(msg.getId(), msg.getData()); diff --git a/server/src/main/java/com/genymobile/scrcpy/control/UhidManager.java b/server/src/main/java/com/genymobile/scrcpy/control/UhidManager.java index 408dbf5d6f..d8cfd81f66 100644 --- a/server/src/main/java/com/genymobile/scrcpy/control/UhidManager.java +++ b/server/src/main/java/com/genymobile/scrcpy/control/UhidManager.java @@ -1,6 +1,7 @@ package com.genymobile.scrcpy.control; import com.genymobile.scrcpy.util.Ln; +import com.genymobile.scrcpy.util.StringUtils; import android.os.Build; import android.os.HandlerThread; @@ -46,7 +47,7 @@ public UhidManager(DeviceMessageSender sender) { } } - public void open(int id, byte[] reportDesc) throws IOException { + public void open(int id, String name, byte[] reportDesc) throws IOException { try { FileDescriptor fd = Os.open("/dev/uhid", OsConstants.O_RDWR, 0); try { @@ -56,7 +57,7 @@ public void open(int id, byte[] reportDesc) throws IOException { close(old); } - byte[] req = buildUhidCreate2Req(reportDesc); + byte[] req = buildUhidCreate2Req(name, reportDesc); Os.write(fd, req, 0, req.length); registerUhidListener(id, fd); @@ -146,7 +147,7 @@ public void writeInput(int id, byte[] data) throws IOException { } } - private static byte[] buildUhidCreate2Req(byte[] reportDesc) { + private static byte[] buildUhidCreate2Req(String name, byte[] reportDesc) { /* * struct uhid_event { * uint32_t type; @@ -171,8 +172,14 @@ private static byte[] buildUhidCreate2Req(byte[] reportDesc) { 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()); + + 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(BUS_VIRTUAL); buf.putInt(0); // vendor id diff --git a/server/src/test/java/com/genymobile/scrcpy/control/ControlMessageReaderTest.java b/server/src/test/java/com/genymobile/scrcpy/control/ControlMessageReaderTest.java index 0fd5f0acde..f29be2f4be 100644 --- a/server/src/test/java/com/genymobile/scrcpy/control/ControlMessageReaderTest.java +++ b/server/src/test/java/com/genymobile/scrcpy/control/ControlMessageReaderTest.java @@ -324,8 +324,10 @@ public void testParseUhidCreate() throws IOException { DataOutputStream dos = new DataOutputStream(bos); dos.writeByte(ControlMessage.TYPE_UHID_CREATE); 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}; - dos.writeShort(data.length); // size + dos.writeShort(data.length); // report desc size dos.write(data); byte[] packet = bos.toByteArray(); @@ -335,6 +337,7 @@ public void testParseUhidCreate() throws IOException { ControlMessage event = reader.read(); Assert.assertEquals(ControlMessage.TYPE_UHID_CREATE, event.getType()); Assert.assertEquals(42, event.getId()); + Assert.assertEquals("ABC", event.getText()); Assert.assertArrayEquals(data, event.getData()); Assert.assertEquals(-1, bis.read()); // EOS