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.
This commit is contained in:
Romain Vimont 2024-09-08 19:57:14 +02:00
parent 7f6adc015f
commit 32091f0c39
15 changed files with 87 additions and 21 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);
}
// 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
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);
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);

View file

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

View file

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

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

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

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

View file

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

View file

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

View file

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

View file

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

View file

@ -131,10 +131,11 @@ public final class ControlMessage {
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;
}

View file

@ -75,6 +75,12 @@ public class ControlMessageReader {
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 @@ public class ControlMessageReader {
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 {

View file

@ -210,7 +210,7 @@ public class Controller implements AsyncProcessor {
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());

View file

@ -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 final class UhidManager {
}
}
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 final class UhidManager {
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 final class UhidManager {
}
}
private static byte[] buildUhidCreate2Req(byte[] reportDesc) {
private static byte[] buildUhidCreate2Req(String name, byte[] reportDesc) {
/*
* struct uhid_event {
* uint32_t type;
@ -171,8 +172,14 @@ public final class UhidManager {
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

View file

@ -324,8 +324,10 @@ public class ControlMessageReaderTest {
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 class ControlMessageReaderTest {
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