mirror of
https://github.com/Genymobile/scrcpy.git
synced 2026-02-14 18:34:28 +01:00
`adb devices -l` prints one device per line, containing, separated by spaces: - the device serial, - the device state, - a list of key:value pairs. However, the device serial itself may contain spaces, making a simple split ambiguous. To avoid ambiguity, parse the string backwards: - first, parse all the trailing key-value pairs (containing ':'), - then, the preceding token (not containing ':') is the device state, - finally, the remaining leading token is the device serial.
245 lines
6.4 KiB
C
245 lines
6.4 KiB
C
#include "adb_parser.h"
|
|
|
|
#include <assert.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <sys/types.h>
|
|
|
|
#include "util/log.h"
|
|
#include "util/str.h"
|
|
|
|
static size_t
|
|
rstrip_len(const char *s, size_t len) {
|
|
size_t i = len;
|
|
|
|
// Ignore trailing whitespaces
|
|
while (i > 0 && (s[i-1] == ' ' || s[i-1] == '\t')) {
|
|
--i;
|
|
}
|
|
|
|
return i;
|
|
}
|
|
|
|
static void
|
|
locate_last_token(const char *s, size_t len, size_t *start, size_t *end) {
|
|
size_t i = rstrip_len(s, len);
|
|
*end = i; // excluded
|
|
|
|
// The token contains non-whitespace chars
|
|
while (i > 0 && (s[i-1] != ' ' && s[i-1] != '\t')) {
|
|
--i;
|
|
}
|
|
|
|
*start = i; // included
|
|
}
|
|
|
|
static bool
|
|
sc_adb_parse_device(char *line, struct sc_adb_device *device) {
|
|
// One device line looks like:
|
|
// "0123456789abcdef device usb:2-1 product:MyProduct model:MyModel "
|
|
// "device:MyDevice transport_id:1"
|
|
|
|
if (line[0] == '*') {
|
|
// Garbage lines printed by adb daemon while starting start with a '*'
|
|
return false;
|
|
}
|
|
|
|
if (!strncmp("adb server", line, sizeof("adb server") - 1)) {
|
|
// Ignore lines starting with "adb server":
|
|
// adb server version (41) doesn't match this client (39); killing...
|
|
return false;
|
|
}
|
|
|
|
size_t len = strlen(line);
|
|
|
|
size_t start;
|
|
size_t end;
|
|
|
|
// The serial (the first token) may contain spaces, which are also token
|
|
// separators. To avoid ambiguity, parse the string backwards:
|
|
// - first, parse all the trailing key-value pairs (containing ':'),
|
|
// - then, the preceding token (not containing ':') is the device state,
|
|
// - finally, the remaining leading token is the device serial.
|
|
//
|
|
// Refs:
|
|
// - <https://github.com/Genymobile/scrcpy/issues/6248>
|
|
// - <https://github.com/Genymobile/scrcpy/issues/3537>
|
|
const char *state;
|
|
const char *model = NULL;
|
|
for (;;) {
|
|
locate_last_token(line, len, &start, &end);
|
|
if (start == end) {
|
|
// No more tokens, unexpected
|
|
return false;
|
|
}
|
|
|
|
const char *token = &line[start];
|
|
line[end] = '\0';
|
|
|
|
if (!strncmp("model:", token, sizeof("model:") - 1)) {
|
|
model = &token[sizeof("model:") - 1];
|
|
// We only need the model
|
|
} else if (!strchr(token, ':')) {
|
|
// The first non-key:value token, it's the device state
|
|
state = token;
|
|
break;
|
|
}
|
|
|
|
// Remove the trailing parts already handled
|
|
len = start;
|
|
}
|
|
|
|
assert(state);
|
|
|
|
size_t serial_len = rstrip_len(line, start);
|
|
if (!serial_len) {
|
|
// empty serial
|
|
return false;
|
|
}
|
|
char *serial = line;
|
|
line[serial_len] = '\0';
|
|
|
|
device->serial = strdup(serial);
|
|
if (!device->serial) {
|
|
return false;
|
|
}
|
|
|
|
device->state = strdup(state);
|
|
if (!device->state) {
|
|
free(device->serial);
|
|
return false;
|
|
}
|
|
|
|
if (model) {
|
|
device->model = strdup(model);
|
|
if (!device->model) {
|
|
LOG_OOM();
|
|
// model is optional, do not fail
|
|
}
|
|
} else {
|
|
device->model = NULL;
|
|
}
|
|
|
|
device->selected = false;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
sc_adb_parse_devices(char *str, struct sc_vec_adb_devices *out_vec) {
|
|
#define HEADER "List of devices attached"
|
|
#define HEADER_LEN (sizeof(HEADER) - 1)
|
|
bool header_found = false;
|
|
|
|
size_t idx_line = 0;
|
|
while (str[idx_line] != '\0') {
|
|
char *line = &str[idx_line];
|
|
size_t len = strcspn(line, "\n");
|
|
|
|
// The next line starts after the '\n' (replaced by `\0`)
|
|
idx_line += len;
|
|
|
|
if (str[idx_line] != '\0') {
|
|
// The next line starts after the '\n'
|
|
++idx_line;
|
|
}
|
|
|
|
if (!header_found) {
|
|
if (!strncmp(line, HEADER, HEADER_LEN)) {
|
|
header_found = true;
|
|
}
|
|
// Skip everything until the header, there might be garbage lines
|
|
// related to daemon starting before
|
|
continue;
|
|
}
|
|
|
|
// The line, but without any trailing '\r'
|
|
size_t line_len = sc_str_remove_trailing_cr(line, len);
|
|
line[line_len] = '\0';
|
|
|
|
struct sc_adb_device device;
|
|
bool ok = sc_adb_parse_device(line, &device);
|
|
if (!ok) {
|
|
continue;
|
|
}
|
|
|
|
ok = sc_vector_push(out_vec, device);
|
|
if (!ok) {
|
|
LOG_OOM();
|
|
LOGE("Could not push adb_device to vector");
|
|
sc_adb_device_destroy(&device);
|
|
// continue anyway
|
|
continue;
|
|
}
|
|
}
|
|
|
|
assert(header_found || out_vec->size == 0);
|
|
return header_found;
|
|
}
|
|
|
|
static char *
|
|
sc_adb_parse_device_ip_from_line(char *line) {
|
|
// One line from "ip route" looks like:
|
|
// "192.168.1.0/24 dev wlan0 proto kernel scope link src 192.168.1.x"
|
|
|
|
// Get the location of the device name (index of "wlan0" in the example)
|
|
ssize_t idx_dev_name = sc_str_index_of_column(line, 2, " ");
|
|
if (idx_dev_name == -1) {
|
|
return NULL;
|
|
}
|
|
|
|
// Get the location of the ip address (column 8, but column 6 if we start
|
|
// from column 2). Must be computed before truncating individual columns.
|
|
ssize_t idx_ip = sc_str_index_of_column(&line[idx_dev_name], 6, " ");
|
|
if (idx_ip == -1) {
|
|
return NULL;
|
|
}
|
|
// idx_ip is searched from &line[idx_dev_name]
|
|
idx_ip += idx_dev_name;
|
|
|
|
char *dev_name = &line[idx_dev_name];
|
|
size_t dev_name_len = strcspn(dev_name, " \t");
|
|
dev_name[dev_name_len] = '\0';
|
|
|
|
char *ip = &line[idx_ip];
|
|
size_t ip_len = strcspn(ip, " \t");
|
|
ip[ip_len] = '\0';
|
|
|
|
// Only consider lines where the device name starts with "wlan"
|
|
if (strncmp(dev_name, "wlan", sizeof("wlan") - 1)) {
|
|
LOGD("Device ip lookup: ignoring %s (%s)", ip, dev_name);
|
|
return NULL;
|
|
}
|
|
|
|
return strdup(ip);
|
|
}
|
|
|
|
char *
|
|
sc_adb_parse_device_ip(char *str) {
|
|
size_t idx_line = 0;
|
|
while (str[idx_line] != '\0') {
|
|
char *line = &str[idx_line];
|
|
size_t len = strcspn(line, "\n");
|
|
bool is_last_line = line[len] == '\0';
|
|
|
|
// The same, but without any trailing '\r'
|
|
size_t line_len = sc_str_remove_trailing_cr(line, len);
|
|
line[line_len] = '\0';
|
|
|
|
char *ip = sc_adb_parse_device_ip_from_line(line);
|
|
if (ip) {
|
|
// Found
|
|
return ip;
|
|
}
|
|
|
|
if (is_last_line) {
|
|
break;
|
|
}
|
|
|
|
// The next line starts after the '\n'
|
|
idx_line += len + 1;
|
|
}
|
|
|
|
return NULL;
|
|
}
|