Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve device selection #3005

Merged
merged 16 commits into from
Feb 9, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 14 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -422,7 +422,7 @@ scrcpy -b2M -m800 # short version

#### Multi-devices

If several devices are listed in `adb devices`, you must specify the _serial_:
If several devices are listed in `adb devices`, you can specify the _serial_:

```bash
scrcpy --serial 0123456789abcdef
Expand All @@ -436,6 +436,19 @@ scrcpy --serial 192.168.0.1:5555
scrcpy -s 192.168.0.1:5555 # short version
```

If only one device is connected via either USB or TCP/IP, it is possible to
select it automatically:

```bash
# Select the only device connected via USB
scrcpy -d # like adb -d
scrcpy --select-usb # long version

# Select the only device connected via TCP/IP
scrcpy -e # like adb -e
scrcpy --select-tcpip # long version
```

You can start several instances of _scrcpy_ for several devices.

#### Autostart on device connection
Expand Down
2 changes: 2 additions & 0 deletions app/meson.build
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
src = [
'src/main.c',
'src/adb/adb.c',
'src/adb/adb_device.c',
'src/adb/adb_parser.c',
'src/adb/adb_tunnel.c',
'src/cli.c',
Expand Down Expand Up @@ -221,6 +222,7 @@ if get_option('buildtype') == 'debug'
tests = [
['test_adb_parser', [
'tests/test_adb_parser.c',
'src/adb/adb_device.c',
'src/adb/adb_parser.c',
'src/util/str.c',
'src/util/strbuf.c',
Expand Down
12 changes: 12 additions & 0 deletions app/scrcpy.1
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,12 @@ The values are expressed in the device natural orientation (typically, portrait
.B \-\-max\-size
value is computed on the cropped size.

.TP
.B \-d, \-\-select\-usb
Use USB device (if there is exactly one, like adb -d).

Also see \fB\-e\fR (\fB\-\-select\-tcpip\fR).

.TP
.BI "\-\-disable-screensaver"
Disable screensaver while scrcpy is running.
Expand All @@ -62,6 +68,12 @@ Add a buffering delay (in milliseconds) before displaying. This increases latenc

Default is 0 (no buffering).

.TP
.B \-e, \-\-select\-tcpip
Use TCP/IP device (if there is exactly one, like adb -e).

Also see \fB\-d\fR (\fB\-\-select\-usb\fR).

.TP
.BI "\-\-encoder " name
Use a specific MediaCodec encoder (must be a H.264 encoder).
Expand Down
246 changes: 227 additions & 19 deletions app/src/adb/adb.c
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,14 @@ sc_adb_execute(const char *const argv[], unsigned flags) {
return sc_adb_execute_p(argv, flags, NULL);
}

bool
sc_adb_start_server(struct sc_intr *intr, unsigned flags) {
const char *const argv[] = SC_ADB_COMMAND("start-server");

sc_pid pid = sc_adb_execute(argv, flags);
return process_check_success_intr(intr, pid, "adb start-server", flags);
}

bool
sc_adb_forward(struct sc_intr *intr, const char *serial, uint16_t local_port,
const char *device_socket_name, unsigned flags) {
Expand Down Expand Up @@ -374,57 +382,252 @@ sc_adb_disconnect(struct sc_intr *intr, const char *ip_port, unsigned flags) {
return process_check_success_intr(intr, pid, "adb disconnect", flags);
}

char *
sc_adb_getprop(struct sc_intr *intr, const char *serial, const char *prop,
unsigned flags) {
assert(serial);
const char *const argv[] =
SC_ADB_COMMAND("-s", serial, "shell", "getprop", prop);
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 getprop\"");
return NULL;
LOGE("Could not execute \"adb devices -l\"");
return -1;
}

char buf[128];
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 getprop", flags);
bool ok = process_check_success_intr(intr, pid, "adb devices -l", flags);
if (!ok) {
return NULL;
return -1;
}

if (r == -1) {
return NULL;
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';
size_t len = strcspn(buf, " \r\n");
buf[len] = '\0';

return strdup(buf);
// 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 struct sc_adb_device_selector *selector) {
switch (selector->type) {
case SC_ADB_DEVICE_SELECT_ALL:
return true;
case SC_ADB_DEVICE_SELECT_SERIAL:
assert(selector->serial);
char *device_serial_colon = strchr(device->serial, ':');
if (device_serial_colon) {
// The device serial is an IP:port...
char *serial_colon = strchr(selector->serial, ':');
if (!serial_colon) {
// But the requested serial has no ':', so only consider
// the IP part of the device serial. This allows to use
// "192.168.1.1" to match any "192.168.1.1:port".
size_t serial_len = strlen(selector->serial);
size_t device_ip_len = device_serial_colon - device->serial;
if (serial_len != device_ip_len) {
// They are not equal, they don't even have the same
// length
return false;
}
return !strncmp(selector->serial, device->serial,
device_ip_len);
}
}
return !strcmp(selector->serial, device->serial);
case SC_ADB_DEVICE_SELECT_USB:
return !sc_adb_is_serial_tcpip(device->serial);
case SC_ADB_DEVICE_SELECT_TCPIP:
return sc_adb_is_serial_tcpip(device->serial);
default:
assert(!"Missing SC_ADB_DEVICE_SELECT_* handling");
break;
}

return false;
}

static size_t
sc_adb_devices_select(struct sc_adb_device *devices, size_t len,
const struct sc_adb_device_selector *selector,
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, selector);
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 struct sc_adb_device_selector *selector,
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, selector, &sel_idx);

if (sel_count == 0) {
// if count > 0 && sel_count == 0, then necessarily a selection is
// requested
assert(selector->type != SC_ADB_DEVICE_SELECT_ALL);

switch (selector->type) {
case SC_ADB_DEVICE_SELECT_SERIAL:
assert(selector->serial);
LOGE("Could not find ADB device %s:", selector->serial);
break;
case SC_ADB_DEVICE_SELECT_USB:
LOGE("Could not find any ADB device over USB:");
break;
case SC_ADB_DEVICE_SELECT_TCPIP:
LOGE("Could not find any ADB device over TCP/IP:");
break;
default:
assert(!"Unexpected selector type");
break;
}

sc_adb_devices_log(SC_LOG_LEVEL_ERROR, devices, count);
sc_adb_devices_destroy_all(devices, count);
return false;
}

if (sel_count > 1) {
switch (selector->type) {
case SC_ADB_DEVICE_SELECT_ALL:
LOGE("Multiple (%" SC_PRIsizet ") ADB devices:", sel_count);
break;
case SC_ADB_DEVICE_SELECT_SERIAL:
assert(selector->serial);
LOGE("Multiple (%" SC_PRIsizet ") ADB devices with serial %s:",
sel_count, selector->serial);
break;
case SC_ADB_DEVICE_SELECT_USB:
LOGE("Multiple (%" SC_PRIsizet ") ADB devices over USB:",
sel_count);
break;
case SC_ADB_DEVICE_SELECT_TCPIP:
LOGE("Multiple (%" SC_PRIsizet ") ADB devices over TCP/IP:",
sel_count);
break;
default:
assert(!"Unexpected selector type");
break;
}
sc_adb_devices_log(SC_LOG_LEVEL_ERROR, devices, count);
LOGE("Select a device via -s (--serial), -d (--select-usb) or -e "
"(--select-tcpip)");
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 *
sc_adb_get_serialno(struct sc_intr *intr, unsigned flags) {
const char *const argv[] = SC_ADB_COMMAND("get-serialno");
sc_adb_getprop(struct sc_intr *intr, const char *serial, const char *prop,
unsigned flags) {
assert(serial);
const char *const argv[] =
SC_ADB_COMMAND("-s", serial, "shell", "getprop", prop);

sc_pipe pout;
sc_pid pid = sc_adb_execute_p(argv, flags, &pout);
if (pid == SC_PROCESS_NONE) {
LOGE("Could not execute \"adb get-serialno\"");
LOGE("Could not execute \"adb getprop\"");
return NULL;
}

char buf[128];
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 get-serialno", flags);
bool ok = process_check_success_intr(intr, pid, "adb getprop", flags);
if (!ok) {
return NULL;
}
Expand Down Expand Up @@ -482,3 +685,8 @@ sc_adb_get_device_ip(struct sc_intr *intr, const char *serial, unsigned flags) {

return sc_adb_parse_device_ip_from_output(buf);
}

bool
sc_adb_is_serial_tcpip(const char *serial) {
return strchr(serial, ':');
}
Loading