Expose simple API to select a single adb device

Select an adb device from the output of `adb device -l`.

PR #3005 <https://github.com/Genymobile/scrcpy/pull/3005>
This commit is contained in:
Romain Vimont 2022-02-06 15:04:00 +01:00
parent 02d46b2262
commit 4692d13179
6 changed files with 186 additions and 22 deletions

View file

@ -374,6 +374,166 @@ sc_adb_disconnect(struct sc_intr *intr, const char *ip_port, unsigned flags) {
return process_check_success_intr(intr, pid, "adb disconnect", flags); return process_check_success_intr(intr, pid, "adb disconnect", flags);
} }
static ssize_t
sc_adb_list_devices(struct sc_intr *intr, unsigned flags,
struct sc_adb_device *devices, size_t len) {
const char *const argv[] = SC_ADB_COMMAND("devices", "-l");
sc_pipe pout;
sc_pid pid = sc_adb_execute_p(argv, flags, &pout);
if (pid == SC_PROCESS_NONE) {
LOGE("Could not execute \"adb devices -l\"");
return -1;
}
char buf[4096];
ssize_t r = sc_pipe_read_all_intr(intr, pid, pout, buf, sizeof(buf) - 1);
sc_pipe_close(pout);
bool ok = process_check_success_intr(intr, pid, "adb devices -l", flags);
if (!ok) {
return -1;
}
if (r == -1) {
return -1;
}
assert((size_t) r < sizeof(buf));
if (r == sizeof(buf) - 1) {
// The implementation assumes that the output of "adb devices -l" fits
// in the buffer in a single pass
LOGW("Result of \"adb devices -l\" does not fit in 4Kb. "
"Please report an issue.\n");
return -1;
}
// It is parsed as a NUL-terminated string
buf[r] = '\0';
// List all devices to the output list directly
return sc_adb_parse_devices(buf, devices, len);
}
static bool
sc_adb_accept_device(const struct sc_adb_device *device, const char *serial) {
if (!serial) {
return true;
}
return !strcmp(serial, device->serial);
}
static size_t
sc_adb_devices_select(struct sc_adb_device *devices, size_t len,
const char *serial, size_t *idx_out) {
size_t count = 0;
for (size_t i = 0; i < len; ++i) {
struct sc_adb_device *device = &devices[i];
device->selected = sc_adb_accept_device(device, serial);
if (device->selected) {
if (idx_out && !count) {
*idx_out = i;
}
++count;
}
}
return count;
}
static void
sc_adb_devices_log(enum sc_log_level level, struct sc_adb_device *devices,
size_t count) {
for (size_t i = 0; i < count; ++i) {
struct sc_adb_device *d = &devices[i];
const char *selection = d->selected ? "-->" : " ";
const char *type = sc_adb_is_serial_tcpip(d->serial) ? "(tcpip)"
: " (usb)";
LOG(level, " %s %s %-20s %16s %s",
selection, type, d->serial, d->state, d->model ? d->model : "");
}
}
static bool
sc_adb_device_check_state(struct sc_adb_device *device,
struct sc_adb_device *devices, size_t count) {
const char *state = device->state;
if (!strcmp("device", state)) {
return true;
}
if (!strcmp("unauthorized", state)) {
LOGE("Device is unauthorized:");
sc_adb_devices_log(SC_LOG_LEVEL_ERROR, devices, count);
LOGE("A popup should open on the device to request authorization.");
LOGE("Check the FAQ: "
"<https://github.com/Genymobile/scrcpy/blob/master/FAQ.md>");
}
return false;
}
bool
sc_adb_select_device(struct sc_intr *intr, const char *serial, unsigned flags,
struct sc_adb_device *out_device) {
struct sc_adb_device devices[16];
ssize_t count =
sc_adb_list_devices(intr, flags, devices, ARRAY_LEN(devices));
if (count == -1) {
LOGE("Could not list ADB devices");
return false;
}
if (count == 0) {
LOGE("Could not find any ADB device");
return false;
}
size_t sel_idx; // index of the single matching device if sel_count == 1
size_t sel_count = sc_adb_devices_select(devices, count, serial, &sel_idx);
if (sel_count == 0) {
// if count > 0 && sel_count == 0, then necessarily a serial is provided
assert(serial);
LOGE("Could not find ADB device %s", serial);
sc_adb_devices_log(SC_LOG_LEVEL_ERROR, devices, count);
sc_adb_devices_destroy_all(devices, count);
return false;
}
if (sel_count > 1) {
if (serial) {
LOGE("Multiple (%" SC_PRIsizet ") ADB devices with serial %s:",
sel_count, serial);
} else {
LOGE("Multiple (%" SC_PRIsizet ") ADB devices:", sel_count);
}
sc_adb_devices_log(SC_LOG_LEVEL_ERROR, devices, count);
LOGE("Select a device via -s (--serial)");
sc_adb_devices_destroy_all(devices, count);
return false;
}
assert(sel_count == 1); // sel_idx is valid only if sel_count == 1
struct sc_adb_device *device = &devices[sel_idx];
bool ok = sc_adb_device_check_state(device, devices, count);
if (!ok) {
sc_adb_devices_destroy_all(devices, count);
return false;
}
LOGD("ADB device found:");
sc_adb_devices_log(SC_LOG_LEVEL_DEBUG, devices, count);
// Move devics into out_device (do not destroy device)
sc_adb_device_move(out_device, device);
sc_adb_devices_destroy_all(devices, count);
return true;
}
char * char *
sc_adb_getprop(struct sc_intr *intr, const char *serial, const char *prop, sc_adb_getprop(struct sc_intr *intr, const char *serial, const char *prop,
unsigned flags) { unsigned flags) {

View file

@ -6,6 +6,7 @@
#include <stdbool.h> #include <stdbool.h>
#include <inttypes.h> #include <inttypes.h>
#include "adb_device.h"
#include "util/intr.h" #include "util/intr.h"
#define SC_ADB_NO_STDOUT (1 << 0) #define SC_ADB_NO_STDOUT (1 << 0)
@ -69,6 +70,15 @@ sc_adb_connect(struct sc_intr *intr, const char *ip_port, unsigned flags);
bool bool
sc_adb_disconnect(struct sc_intr *intr, const char *ip_port, unsigned flags); sc_adb_disconnect(struct sc_intr *intr, const char *ip_port, unsigned flags);
/**
* Execute `adb devices` and parse the result to select a device
*
* Return true if a single matching device is found, and write it to out_device.
*/
bool
sc_adb_select_device(struct sc_intr *intr, const char *serial, unsigned flags,
struct sc_adb_device *out_device);
/** /**
* Execute `adb getprop <prop>` * Execute `adb getprop <prop>`
*/ */

View file

@ -10,6 +10,7 @@ struct sc_adb_device {
char *serial; char *serial;
char *state; char *state;
char *model; char *model;
bool selected;
}; };
void void

View file

@ -104,6 +104,8 @@ sc_adb_parse_device(char *line, struct sc_adb_device *device) {
device->model = NULL; device->model = NULL;
} }
device->selected = false;
return true; return true;
} }

View file

@ -503,22 +503,6 @@ sc_server_on_terminated(void *userdata) {
LOGD("Server terminated"); LOGD("Server terminated");
} }
static char *
sc_server_read_serial(struct sc_server *server) {
char *serial;
if (server->params.req_serial) {
// The serial is already known
serial = strdup(server->params.req_serial);
if (!serial) {
LOG_OOM();
}
} else {
serial = sc_adb_get_serialno(&server->intr, 0);
}
return serial;
}
static bool static bool
is_tcpip_mode_enabled(struct sc_server *server, const char *serial) { is_tcpip_mode_enabled(struct sc_server *server, const char *serial) {
struct sc_intr *intr = &server->intr; struct sc_intr *intr = &server->intr;
@ -695,22 +679,28 @@ run_server(void *data) {
bool ok; bool ok;
if (need_initial_serial) { if (need_initial_serial) {
char *serial = sc_server_read_serial(server); struct sc_adb_device device;
if (!serial) { ok = sc_adb_select_device(&server->intr, params->req_serial, 0,
LOGE("Could not get device serial"); &device);
if (!ok) {
goto error_connection_failed; goto error_connection_failed;
} }
if (params->tcpip) { if (params->tcpip) {
assert(!params->tcpip_dst); assert(!params->tcpip_dst);
ok = sc_server_configure_tcpip_unknown_address(server, serial); ok = sc_server_configure_tcpip_unknown_address(server,
free(serial); device.serial);
sc_adb_device_destroy(&device);
if (!ok) { if (!ok) {
goto error_connection_failed; goto error_connection_failed;
} }
assert(server->serial); assert(server->serial);
} else { } else {
server->serial = serial; // "move" the device.serial without copy
server->serial = device.serial;
// the serial must not be freed by the destructor
device.serial = NULL;
sc_adb_device_destroy(&device);
} }
} else { } else {
ok = sc_server_configure_tcpip_known_address(server, params->tcpip_dst); ok = sc_server_configure_tcpip_known_address(server, params->tcpip_dst);

View file

@ -183,6 +183,7 @@ sc_usb_select_device(struct sc_usb *usb, const char *serial,
} else { } else {
LOGE("Multiple (%" SC_PRIsizet ") USB devices:", sel_count); LOGE("Multiple (%" SC_PRIsizet ") USB devices:", sel_count);
} }
sc_usb_devices_log(SC_LOG_LEVEL_ERROR, usb_devices, count);
LOGE("Select a device via -s (--serial)"); LOGE("Select a device via -s (--serial)");
sc_usb_devices_destroy_all(usb_devices, count); sc_usb_devices_destroy_all(usb_devices, count);
return false; return false;