Add UHID_DESTROY control message

This message will be sent on gamepad disconnection.

Contrary to keyboard and mouse devices, which are registered once and
unregistered when scrcpy exists, each physical gamepad is mapped with
its own HID id, and they can be plugged and unplugged dynamically.

PR #5270 <https://github.com/Genymobile/scrcpy/pull/5270>
This commit is contained in:
Romain Vimont 2024-09-06 23:08:08 +02:00
parent 5fe884276b
commit 64a25f6e9d
6 changed files with 69 additions and 3 deletions

View file

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

View file

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

View file

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

View file

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

View file

@ -51,6 +51,8 @@ public class ControlMessageReader {
return parseUhidCreate();
case ControlMessage.TYPE_UHID_INPUT:
return parseUhidInput();
case ControlMessage.TYPE_UHID_DESTROY:
return parseUhidDestroy();
default:
throw new ControlProtocolException("Unknown event type: " + type);
}
@ -142,6 +144,11 @@ public class ControlMessageReader {
return ControlMessage.createUhidInput(id, data);
}
private ControlMessage parseUhidDestroy() throws IOException {
int id = dis.readUnsignedShort();
return ControlMessage.createUhidDestroy(id);
}
private Position parsePosition() throws IOException {
int x = dis.readInt();
int y = dis.readInt();

View file

@ -362,6 +362,24 @@ public class ControlMessageReaderTest {
Assert.assertEquals(-1, bis.read()); // EOS
}
@Test
public void testParseUhidDestroy() throws IOException {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
DataOutputStream dos = new DataOutputStream(bos);
dos.writeByte(ControlMessage.TYPE_UHID_DESTROY);
dos.writeShort(42); // id
byte[] packet = bos.toByteArray();
ByteArrayInputStream bis = new ByteArrayInputStream(packet);
ControlMessageReader reader = new ControlMessageReader(bis);
ControlMessage event = reader.read();
Assert.assertEquals(ControlMessage.TYPE_UHID_DESTROY, event.getType());
Assert.assertEquals(42, event.getId());
Assert.assertEquals(-1, bis.read()); // EOS
}
@Test
public void testParseOpenHardKeyboardSettings() throws IOException {
ByteArrayOutputStream bos = new ByteArrayOutputStream();