From b266fdb229dd2d14b47903abf1a36506338f969a Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 6 Sep 2024 23:08:08 +0200 Subject: [PATCH] Add UHID_DESTROY control message This message will be sent on gamepad disconnection. Contrary to keyboard and mouse, which are registered once and are unregistered when scrcpy exists, each gamepad is mapped with its own HID id, and they can be plugged/unplugged dynamically. --- app/src/control_msg.c | 13 ++++++++++-- app/src/control_msg.h | 4 ++++ app/tests/test_control_msg_serialize.c | 20 +++++++++++++++++++ .../scrcpy/control/ControlMessage.java | 10 +++++++++- .../scrcpy/control/ControlMessageReader.java | 12 +++++++++++ .../control/ControlMessageReaderTest.java | 18 +++++++++++++++++ 6 files changed, 74 insertions(+), 3 deletions(-) diff --git a/app/src/control_msg.c b/app/src/control_msg.c index daa3bde7..b9d50222 100644 --- a/app/src/control_msg.c +++ b/app/src/control_msg.c @@ -155,6 +155,9 @@ sc_control_msg_serialize(const struct sc_control_msg *msg, uint8_t *buf) { sc_write16be(&buf[3], msg->uhid_input.size); memcpy(&buf[5], msg->uhid_input.data, msg->uhid_input.size); return 5 + msg->uhid_input.size; + case SC_CONTROL_MSG_TYPE_UHID_DESTROY: + sc_write16be(&buf[1], msg->uhid_destroy.id); + return 3; case SC_CONTROL_MSG_TYPE_EXPAND_NOTIFICATION_PANEL: case SC_CONTROL_MSG_TYPE_EXPAND_SETTINGS_PANEL: case SC_CONTROL_MSG_TYPE_COLLAPSE_PANELS: @@ -269,6 +272,9 @@ sc_control_msg_log(const struct sc_control_msg *msg) { } break; } + case SC_CONTROL_MSG_TYPE_UHID_DESTROY: + LOG_CMSG("UHID destroy [%" PRIu16 "]", msg->uhid_destroy.id); + break; case SC_CONTROL_MSG_TYPE_OPEN_HARD_KEYBOARD_SETTINGS: LOG_CMSG("open hard keyboard settings"); break; @@ -281,8 +287,11 @@ sc_control_msg_log(const struct sc_control_msg *msg) { bool sc_control_msg_is_droppable(const struct sc_control_msg *msg) { // Cannot drop UHID_CREATE messages, because it would cause all further - // UHID_INPUT messages for this device to be invalid - return msg->type != SC_CONTROL_MSG_TYPE_UHID_CREATE; + // UHID_INPUT messages for this device to be invalid. + // Cannot drop UHID_DESTROY messages either, because a further UHID_CREATE + // with the same id may fail. + return msg->type != SC_CONTROL_MSG_TYPE_UHID_CREATE + && msg->type != SC_CONTROL_MSG_TYPE_UHID_DESTROY; } void diff --git a/app/src/control_msg.h b/app/src/control_msg.h index 63670705..b48d91af 100644 --- a/app/src/control_msg.h +++ b/app/src/control_msg.h @@ -39,6 +39,7 @@ enum sc_control_msg_type { SC_CONTROL_MSG_TYPE_ROTATE_DEVICE, SC_CONTROL_MSG_TYPE_UHID_CREATE, SC_CONTROL_MSG_TYPE_UHID_INPUT, + SC_CONTROL_MSG_TYPE_UHID_DESTROY, SC_CONTROL_MSG_TYPE_OPEN_HARD_KEYBOARD_SETTINGS, }; @@ -105,6 +106,9 @@ struct sc_control_msg { uint16_t size; uint8_t data[SC_HID_MAX_SIZE]; } uhid_input; + struct { + uint16_t id; + } uhid_destroy; }; }; diff --git a/app/tests/test_control_msg_serialize.c b/app/tests/test_control_msg_serialize.c index 7a978f2b..f88048d8 100644 --- a/app/tests/test_control_msg_serialize.c +++ b/app/tests/test_control_msg_serialize.c @@ -370,6 +370,25 @@ static void test_serialize_uhid_input(void) { assert(!memcmp(buf, expected, sizeof(expected))); } +static void test_serialize_uhid_destroy(void) { + struct sc_control_msg msg = { + .type = SC_CONTROL_MSG_TYPE_UHID_DESTROY, + .uhid_destroy = { + .id = 42, + }, + }; + + uint8_t buf[SC_CONTROL_MSG_MAX_SIZE]; + size_t size = sc_control_msg_serialize(&msg, buf); + assert(size == 3); + + const uint8_t expected[] = { + SC_CONTROL_MSG_TYPE_UHID_DESTROY, + 0, 42, // id + }; + assert(!memcmp(buf, expected, sizeof(expected))); +} + static void test_serialize_open_hard_keyboard(void) { struct sc_control_msg msg = { .type = SC_CONTROL_MSG_TYPE_OPEN_HARD_KEYBOARD_SETTINGS, @@ -405,6 +424,7 @@ int main(int argc, char *argv[]) { test_serialize_rotate_device(); test_serialize_uhid_create(); test_serialize_uhid_input(); + test_serialize_uhid_destroy(); test_serialize_open_hard_keyboard(); return 0; } 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 c414f2a5..ef71353a 100644 --- a/server/src/main/java/com/genymobile/scrcpy/control/ControlMessage.java +++ b/server/src/main/java/com/genymobile/scrcpy/control/ControlMessage.java @@ -21,7 +21,8 @@ public final class ControlMessage { public static final int TYPE_ROTATE_DEVICE = 11; public static final int TYPE_UHID_CREATE = 12; public static final int TYPE_UHID_INPUT = 13; - public static final int TYPE_OPEN_HARD_KEYBOARD_SETTINGS = 14; + public static final int TYPE_UHID_DESTROY = 14; + public static final int TYPE_OPEN_HARD_KEYBOARD_SETTINGS = 15; public static final long SEQUENCE_INVALID = 0; @@ -146,6 +147,13 @@ public final class ControlMessage { return msg; } + public static ControlMessage createUhidDestroy(int id) { + ControlMessage msg = new ControlMessage(); + msg.type = TYPE_UHID_DESTROY; + msg.id = id; + return msg; + } + public int getType() { return type; } 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 f5cfee75..721a2937 100644 --- a/server/src/main/java/com/genymobile/scrcpy/control/ControlMessageReader.java +++ b/server/src/main/java/com/genymobile/scrcpy/control/ControlMessageReader.java @@ -21,6 +21,7 @@ public class ControlMessageReader { static final int SET_CLIPBOARD_FIXED_PAYLOAD_LENGTH = 9; static final int UHID_CREATE_FIXED_PAYLOAD_LENGTH = 4; static final int UHID_INPUT_FIXED_PAYLOAD_LENGTH = 4; + static final int UHID_DESTROY_PAYLOAD_LENGTH = 2; private static final int MESSAGE_MAX_SIZE = 1 << 18; // 256k @@ -99,6 +100,9 @@ public class ControlMessageReader { case ControlMessage.TYPE_UHID_INPUT: msg = parseUhidInput(); break; + case ControlMessage.TYPE_UHID_DESTROY: + msg = parseUhidDestroy(); + break; default: Ln.w("Unknown event type: " + type); msg = null; @@ -249,6 +253,14 @@ public class ControlMessageReader { return ControlMessage.createUhidInput(id, data); } + private ControlMessage parseUhidDestroy() { + if (buffer.remaining() < UHID_DESTROY_PAYLOAD_LENGTH) { + return null; + } + int id = buffer.getShort(); + return ControlMessage.createUhidDestroy(id); + } + private static Position readPosition(ByteBuffer buffer) { int x = buffer.getInt(); int y = buffer.getInt(); 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 1737730f..71da3d6a 100644 --- a/server/src/test/java/com/genymobile/scrcpy/control/ControlMessageReaderTest.java +++ b/server/src/test/java/com/genymobile/scrcpy/control/ControlMessageReaderTest.java @@ -368,6 +368,24 @@ public class ControlMessageReaderTest { Assert.assertArrayEquals(data, event.getData()); } + @Test + public void testParseUhidDestroy() throws IOException { + ControlMessageReader reader = new ControlMessageReader(); + + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + DataOutputStream dos = new DataOutputStream(bos); + dos.writeByte(ControlMessage.TYPE_UHID_DESTROY); + dos.writeShort(42); // id + + byte[] packet = bos.toByteArray(); + + reader.readFrom(new ByteArrayInputStream(packet)); + ControlMessage event = reader.next(); + + Assert.assertEquals(ControlMessage.TYPE_UHID_DESTROY, event.getType()); + Assert.assertEquals(42, event.getId()); + } + @Test public void testParseOpenHardKeyboardSettings() throws IOException { ControlMessageReader reader = new ControlMessageReader();