Compare commits

...

21 Commits

Author SHA1 Message Date
26ee63d9e9 Build on Windows with libusb support 2022-02-07 13:24:10 +01:00
0d3ed2f669 Fix libusb callback for Windows
Add LIBUSB_CALL so that the callback has the correct signature on
Windows (including __attribute__((stdcall))).
2022-02-07 13:24:10 +01:00
0480581789 Avoid PRIx16 printf format on Windows
Convert uint16_t to unsigned to avoid using PRIx16, which may not exist
on Windows.
2022-02-07 13:24:09 +01:00
c0bc183157 Log device opening errors during listing
Without this log, the user would have no way to know that a USB device
is rejected because it could not be opened (typically due to
insufficient permissions).
2022-02-07 13:21:31 +01:00
dddd960a9e Mention --select-usb and --select-tcpip in README 2022-02-07 13:11:37 +01:00
5b05936093 Add option to select USB or TCP/IP devices
If several devices are connected (as listed by `adb devices`), it was
necessary to provide the explicit serial via -s/--serial.

If only one device is connected via USB (respectively, via TCP/IP), it
might be convenient to select it automatically. For this purpose, two
new options are introduced:
 - -U/--select-usb: select the single device connected over USB
 - -T/--select-tcpip: select the single device connected over TCP/IP
2022-02-07 13:11:37 +01:00
14b608dbcc Introduce adb device selector
Currently, a device is selected either from a specific serial, or if it
is the only one connected.

In order to support selecting the only device connected via USB or via
TCP/IP separately, introduce a new selection structure.
2022-02-07 13:11:37 +01:00
1718bfe00e Execute adb start-server
This does nothing if the adb daemon is already started, but allows to
print any output/errors in the console.

Otherwise, the daemon starting would occur during `adb devices`, which
does not output to the console because the result is parsed.
2022-02-07 13:11:37 +01:00
4ad12755da Remove sc_adb_get_serialno()
The device serial is now retrieved from `adb devices -l`, `adb
get-serialno` is not called anymore.
2022-02-07 13:11:37 +01:00
a6bedb4301 Allow selecting a device from IP without port
Since the previous commit, if a serial is given via -s/--serial (either
a real USB serial or an IP:port), a device is selected if its serial
matches exactly.

In addition, if the user pass an IP without a port, then select any
device with this IP, regardless of the port (so that "192.168.1.1"
matches any "192.168.1.1:port"). This is also the default behavior of
adb.
2022-02-07 13:11:37 +01:00
a432134685 Expose simple API to select a single adb device
Select an adb device from the output of `adb device -l`.
2022-02-07 13:11:37 +01:00
e78eb83cb9 Expose function to test if a serial is TCP/IP
In practice, it just tests if the serial contains a ':', which is
sufficient to distinguish ip:port from a real USB serial.
2022-02-07 13:11:37 +01:00
f5b737857f Add adb devices parser
Add a parser of `adb device -l` output, to extract a list of devices
with their serial, state and model.
2022-02-07 13:11:37 +01:00
4a759f9b55 Refactor device configuration
Depending on the parameters passed to scrcpy, either the initial device
serial is necessary or not. Reorganize to simplify the logic.
2022-02-07 13:11:37 +01:00
c28e616a44 List and select USB devices separately
List all USB devices in a first step, then select the matching one(s).

This allows to report a user-friendly log message containing the list of
devices, with the matching one(s) highlighted.
2022-02-07 13:11:37 +01:00
70a0d9cc37 Expose simple API to select a single USB device
The caller just wants a single device. Handle all cases and error
messages internally.
2022-02-07 13:11:37 +01:00
b11c6550c7 Add move-function for sc_usb_device
Add a function to "move" a sc_usb_device into another instance.

This will avoid unnecessary copies.
2022-02-07 13:11:37 +01:00
7d7953e278 Move SC_PRIsizet to compat.h
Define the printf format macro for size_t in compat.h so that it can be
used from anywhere.
2022-02-07 13:11:37 +01:00
54d48321fd Rename function to destroy a list of USB devices
Rename from "usb_device_" to "usb_devices_".
2022-02-07 13:11:37 +01:00
3456ca19c7 Add generic LOG() macro with level parameter
One log macro was provided for each log level (LOGV(), LOGD(), LOGI(),
LOGW(), LOGE()).

Add a generic macro LOG(LEVEL, ...) taking a log level as parameter, so
that it is possible to write logging wrappers.
2022-02-07 13:11:37 +01:00
79eb78a9e8 Remove LOGC()
It is not clear when to use LOGC() rather than LOGE(). Always use
LOGE().

Moreover, enum sc_log_level has no "critical" log level.
2022-02-07 13:11:37 +01:00
38 changed files with 1054 additions and 223 deletions

View File

@ -422,7 +422,7 @@ scrcpy -b2M -m800 # short version
#### Multi-devices #### 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 ```bash
scrcpy --serial 0123456789abcdef scrcpy --serial 0123456789abcdef
@ -436,6 +436,19 @@ scrcpy --serial 192.168.0.1:5555
scrcpy -s 192.168.0.1:5555 # short version 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 --select-usb
scrcpy -U # short version
# Select the only device connected via TCP/IP
scrcpy --select-tcpip
scrcpy -T # short version
```
You can start several instances of _scrcpy_ for several devices. You can start several instances of _scrcpy_ for several devices.
#### Autostart on device connection #### Autostart on device connection

View File

@ -1,6 +1,7 @@
src = [ src = [
'src/main.c', 'src/main.c',
'src/adb/adb.c', 'src/adb/adb.c',
'src/adb/adb_device.c',
'src/adb/adb_parser.c', 'src/adb/adb_parser.c',
'src/adb/adb_tunnel.c', 'src/adb/adb_tunnel.c',
'src/cli.c', 'src/cli.c',
@ -72,7 +73,7 @@ if v4l2_support
src += [ 'src/v4l2_sink.c' ] src += [ 'src/v4l2_sink.c' ]
endif endif
usb_support = host_machine.system() == 'linux' usb_support = host_machine.system() == 'linux' or host_machine.system() == 'windows'
if usb_support if usb_support
src += [ src += [
'src/usb/aoa_hid.c', 'src/usb/aoa_hid.c',
@ -139,9 +140,22 @@ else
include_directories: include_directories(ffmpeg_include_dir) include_directories: include_directories(ffmpeg_include_dir)
) )
prebuilt_libusb = meson.get_cross_property('prebuilt_libusb')
prebuilt_libusb_root = meson.get_cross_property('prebuilt_libusb_root')
libusb_bin_dir = meson.current_source_dir() + '/../prebuilt-deps/data/' + prebuilt_libusb + '/dll'
libusb_include_dir = '../prebuilt-deps/data/' + prebuilt_libusb_root + '/include'
libusb = declare_dependency(
dependencies: [
cc.find_library('libusb-1.0', dirs: libusb_bin_dir),
],
include_directories: include_directories(libusb_include_dir)
)
dependencies = [ dependencies = [
ffmpeg, ffmpeg,
sdl2, sdl2,
libusb,
cc.find_library('mingw32') cc.find_library('mingw32')
] ]
@ -221,6 +235,7 @@ if get_option('buildtype') == 'debug'
tests = [ tests = [
['test_adb_parser', [ ['test_adb_parser', [
'tests/test_adb_parser.c', 'tests/test_adb_parser.c',
'src/adb/adb_device.c',
'src/adb/adb_parser.c', 'src/adb/adb_parser.c',
'src/util/str.c', 'src/util/str.c',
'src/util/strbuf.c', 'src/util/strbuf.c',

View File

@ -273,6 +273,14 @@ Set the TCP port of the adb tunnel to reach the scrcpy server. This option autom
Default is 0 (not forced): the local port used for establishing the tunnel will be used. Default is 0 (not forced): the local port used for establishing the tunnel will be used.
.TP
.B \-T, \-\-select\-tcpip
Use TCP/IP device (if there is exactly one).
.TP
.B \-U, \-\-select\-usb
Use USB device (if there is exactly one).
.TP .TP
.BI "\-\-v4l2-sink " /dev/videoN .BI "\-\-v4l2-sink " /dev/videoN
Output to v4l2loopback device. Output to v4l2loopback device.

View File

@ -194,6 +194,14 @@ sc_adb_execute(const char *const argv[], unsigned flags) {
return sc_adb_execute_p(argv, flags, NULL); 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 bool
sc_adb_forward(struct sc_intr *intr, const char *serial, uint16_t local_port, sc_adb_forward(struct sc_intr *intr, const char *serial, uint16_t local_port,
const char *device_socket_name, unsigned flags) { const char *device_socket_name, unsigned flags) {
@ -374,6 +382,234 @@ 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 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);
if (selector->type != SC_ADB_DEVICE_SELECT_ALL) {
LOGE("Specify the device via -s or --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) {
@ -409,38 +645,6 @@ sc_adb_getprop(struct sc_intr *intr, const char *serial, const char *prop,
return strdup(buf); return strdup(buf);
} }
char *
sc_adb_get_serialno(struct sc_intr *intr, unsigned flags) {
const char *const argv[] = SC_ADB_COMMAND("get-serialno");
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\"");
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);
if (!ok) {
return NULL;
}
if (r == -1) {
return NULL;
}
assert((size_t) r < sizeof(buf));
buf[r] = '\0';
size_t len = strcspn(buf, " \r\n");
buf[len] = '\0';
return strdup(buf);
}
char * char *
sc_adb_get_device_ip(struct sc_intr *intr, const char *serial, unsigned flags) { sc_adb_get_device_ip(struct sc_intr *intr, const char *serial, unsigned flags) {
assert(serial); assert(serial);
@ -482,3 +686,8 @@ sc_adb_get_device_ip(struct sc_intr *intr, const char *serial, unsigned flags) {
return sc_adb_parse_device_ip_from_output(buf); return sc_adb_parse_device_ip_from_output(buf);
} }
bool
sc_adb_is_serial_tcpip(const char *serial) {
return strchr(serial, ':');
}

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)
@ -17,9 +18,24 @@
const char * const char *
sc_adb_get_executable(void); sc_adb_get_executable(void);
enum sc_adb_device_selector_type {
SC_ADB_DEVICE_SELECT_ALL,
SC_ADB_DEVICE_SELECT_SERIAL,
SC_ADB_DEVICE_SELECT_USB,
SC_ADB_DEVICE_SELECT_TCPIP,
};
struct sc_adb_device_selector {
enum sc_adb_device_selector_type type;
const char *serial;
};
sc_pid sc_pid
sc_adb_execute(const char *const argv[], unsigned flags); sc_adb_execute(const char *const argv[], unsigned flags);
bool
sc_adb_start_server(struct sc_intr *intr, unsigned flags);
bool bool
sc_adb_forward(struct sc_intr *intr, const char *serial, uint16_t local_port, sc_adb_forward(struct sc_intr *intr, const char *serial, uint16_t local_port,
const char *device_socket_name, unsigned flags); const char *device_socket_name, unsigned flags);
@ -69,6 +85,16 @@ 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 struct sc_adb_device_selector *selector,
unsigned flags, struct sc_adb_device *out_device);
/** /**
* Execute `adb getprop <prop>` * Execute `adb getprop <prop>`
*/ */
@ -76,14 +102,6 @@ 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);
/**
* Execute `adb get-serialno`
*
* Return the result, to be freed by the caller, or NULL on error.
*/
char *
sc_adb_get_serialno(struct sc_intr *intr, unsigned flags);
/** /**
* Attempt to retrieve the device IP * Attempt to retrieve the device IP
* *
@ -93,4 +111,13 @@ sc_adb_get_serialno(struct sc_intr *intr, unsigned flags);
char * char *
sc_adb_get_device_ip(struct sc_intr *intr, const char *serial, unsigned flags); sc_adb_get_device_ip(struct sc_intr *intr, const char *serial, unsigned flags);
/**
* Indicate if the serial represents an IP address
*
* In practice, it just returns true if and only if it contains a ':', which is
* sufficient to distinguish an ip:port from a real USB serial.
*/
bool
sc_adb_is_serial_tcpip(const char *serial);
#endif #endif

26
app/src/adb/adb_device.c Normal file
View File

@ -0,0 +1,26 @@
#include "adb_device.h"
#include <stdlib.h>
void
sc_adb_device_destroy(struct sc_adb_device *device) {
free(device->serial);
free(device->state);
free(device->model);
}
void
sc_adb_device_move(struct sc_adb_device *dst, struct sc_adb_device *src) {
*dst = *src;
src->serial = NULL;
src->state = NULL;
src->model = NULL;
}
void
sc_adb_devices_destroy_all(struct sc_adb_device *devices, size_t count) {
for (size_t i = 0; i < count; ++i) {
sc_adb_device_destroy(&devices[i]);
}
}

34
app/src/adb/adb_device.h Normal file
View File

@ -0,0 +1,34 @@
#ifndef SC_ADB_DEVICE_H
#define SC_ADB_DEVICE_H
#include "common.h"
#include <stdbool.h>
#include <stddef.h>
struct sc_adb_device {
char *serial;
char *state;
char *model;
bool selected;
};
void
sc_adb_device_destroy(struct sc_adb_device *device);
/**
* Move src to dest
*
* After this call, the content of src is undefined, except that
* sc_adb_device_destroy() can be called.
*
* This is useful to take a device from a list that will be destroyed, without
* making unnecessary copies.
*/
void
sc_adb_device_move(struct sc_adb_device *dst, struct sc_adb_device *src);
void
sc_adb_devices_destroy_all(struct sc_adb_device *devices, size_t count);
#endif

View File

@ -1,11 +1,170 @@
#include "adb_parser.h" #include "adb_parser.h"
#include <assert.h> #include <assert.h>
#include <stdlib.h>
#include <string.h> #include <string.h>
#include "util/log.h" #include "util/log.h"
#include "util/str.h" #include "util/str.h"
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;
}
char *s = line; // cursor in the line
// After the serial:
// - "adb devices" writes a single '\t'
// - "adb devices -l" writes multiple spaces
// For flexibility, accept both.
size_t serial_len = strcspn(s, " \t");
if (!serial_len) {
// empty serial
return false;
}
bool eol = s[serial_len] == '\0';
if (eol) {
// serial alone is unexpected
return false;
}
s[serial_len] = '\0';
char *serial = s;
s += serial_len + 1;
// After the serial, there might be several spaces
s += strspn(s, " \t"); // consume all separators
size_t state_len = strcspn(s, " ");
if (!state_len) {
// empty state
return false;
}
eol = s[state_len] == '\0';
s[state_len] = '\0';
char *state = s;
char *model = NULL;
if (!eol) {
s += state_len + 1;
// Iterate over all properties "key:value key:value ..."
for (;;) {
size_t token_len = strcspn(s, " ");
if (!token_len) {
break;
}
eol = s[token_len] == '\0';
s[token_len] = '\0';
char *token = s;
if (!strncmp("model:", token, sizeof("model:") - 1)) {
model = &token[sizeof("model:") - 1];
// We only need the model
break;
}
if (eol) {
break;
} else {
s+= token_len + 1;
}
}
}
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;
}
ssize_t
sc_adb_parse_devices(char *str, struct sc_adb_device *devices,
size_t devices_len) {
size_t dev_count = 0;
#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';
bool ok = sc_adb_parse_device(line, &devices[dev_count]);
if (!ok) {
continue;
}
++dev_count;
assert(dev_count <= devices_len);
if (dev_count == devices_len) {
// Max number of devices reached
break;
}
}
if (!header_found) {
return -1;
}
return dev_count;
}
static char * static char *
sc_adb_parse_device_ip_from_line(char *line) { sc_adb_parse_device_ip_from_line(char *line) {
// One line from "ip route" looks like: // One line from "ip route" looks like:
@ -64,7 +223,7 @@ sc_adb_parse_device_ip_from_output(char *str) {
if (str[idx_line] != '\0') { if (str[idx_line] != '\0') {
// The next line starts after the '\n' // The next line starts after the '\n'
idx_line += 1; ++idx_line;
} }
} }

View File

@ -5,6 +5,19 @@
#include <stddef.h> #include <stddef.h>
#include "adb_device.h"
/**
* Parse the available devices from the output of `adb devices`
*
* The parameter must be a NUL-terminated string.
*
* Warning: this function modifies the buffer for optimization purposes.
*/
ssize_t
sc_adb_parse_devices(char *str, struct sc_adb_device *devices,
size_t devices_len);
/** /**
* Parse the ip from the output of `adb shell ip route` * Parse the ip from the output of `adb shell ip route`
* *

View File

@ -416,6 +416,16 @@ static const struct sc_option options[] = {
"Default is 0 (not forced): the local port used for " "Default is 0 (not forced): the local port used for "
"establishing the tunnel will be used.", "establishing the tunnel will be used.",
}, },
{
.shortopt = 'T',
.longopt = "select-tcpip",
.text = "Use TCP/IP device (if there is exactly one).",
},
{
.shortopt = 'U',
.longopt = "select-usb",
.text = "Use USB device (if there is exactly one).",
},
{ {
.longopt_id = OPT_V4L2_SINK, .longopt_id = OPT_V4L2_SINK,
.longopt = "v4l2-sink", .longopt = "v4l2-sink",
@ -1401,6 +1411,12 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
case 't': case 't':
opts->show_touches = true; opts->show_touches = true;
break; break;
case 'T':
opts->select_tcpip = true;
break;
case 'U':
opts->select_usb = true;
break;
case OPT_ALWAYS_ON_TOP: case OPT_ALWAYS_ON_TOP:
opts->always_on_top = true; opts->always_on_top = true;
break; break;
@ -1559,8 +1575,16 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
// If a TCP/IP address is provided, then tcpip must be enabled // If a TCP/IP address is provided, then tcpip must be enabled
assert(opts->tcpip || !opts->tcpip_dst); assert(opts->tcpip || !opts->tcpip_dst);
if (opts->serial && opts->tcpip_dst) { unsigned selectors = !!opts->serial
LOGE("Incompatible options: -s/--serial and --tcpip with an argument"); + !!opts->tcpip_dst
+ opts->select_tcpip
+ opts->select_usb;
if (selectors > 1) {
LOGE("At most one device selector option may be passed, among:\n"
" --serial (-s)\n"
" --select-usb (-U)\n"
" --select-tcpip (-T)\n"
" --tcpip=<addr> (with an argument)");
return false; return false;
} }

View File

@ -8,8 +8,10 @@
#ifndef __WIN32 #ifndef __WIN32
# define PRIu64_ PRIu64 # define PRIu64_ PRIu64
# define SC_PRIsizet "zu"
#else #else
# define PRIu64_ "I64u" // Windows... # define PRIu64_ "I64u" // Windows...
# define SC_PRIsizet "Iu"
#endif #endif
// In ffmpeg/doc/APIchanges: // In ffmpeg/doc/APIchanges:

View File

@ -113,7 +113,7 @@ sc_controller_start(struct sc_controller *controller) {
bool ok = sc_thread_create(&controller->thread, run_controller, bool ok = sc_thread_create(&controller->thread, run_controller,
"scrcpy-ctl", controller); "scrcpy-ctl", controller);
if (!ok) { if (!ok) {
LOGC("Could not start controller thread"); LOGE("Could not start controller thread");
return false; return false;
} }

View File

@ -276,7 +276,7 @@ sc_demuxer_start(struct sc_demuxer *demuxer) {
bool ok = sc_thread_create(&demuxer->thread, run_demuxer, "scrcpy-demuxer", bool ok = sc_thread_create(&demuxer->thread, run_demuxer, "scrcpy-demuxer",
demuxer); demuxer);
if (!ok) { if (!ok) {
LOGC("Could not start demuxer thread"); LOGE("Could not start demuxer thread");
return false; return false;
} }
return true; return true;

View File

@ -156,7 +156,7 @@ sc_file_pusher_start(struct sc_file_pusher *fp) {
bool ok = sc_thread_create(&fp->thread, run_file_pusher, "scrcpy-file", fp); bool ok = sc_thread_create(&fp->thread, run_file_pusher, "scrcpy-file", fp);
if (!ok) { if (!ok) {
LOGC("Could not start file_pusher thread"); LOGE("Could not start file_pusher thread");
return false; return false;
} }

View File

@ -60,4 +60,6 @@ const struct scrcpy_options scrcpy_options_default = {
.downsize_on_error = true, .downsize_on_error = true,
.tcpip = false, .tcpip = false,
.tcpip_dst = NULL, .tcpip_dst = NULL,
.select_tcpip = false,
.select_usb = false,
}; };

View File

@ -135,6 +135,8 @@ struct scrcpy_options {
bool downsize_on_error; bool downsize_on_error;
bool tcpip; bool tcpip;
const char *tcpip_dst; const char *tcpip_dst;
bool select_usb;
bool select_tcpip;
}; };
extern const struct scrcpy_options scrcpy_options_default; extern const struct scrcpy_options scrcpy_options_default;

View File

@ -114,7 +114,7 @@ receiver_start(struct receiver *receiver) {
bool ok = sc_thread_create(&receiver->thread, run_receiver, bool ok = sc_thread_create(&receiver->thread, run_receiver,
"scrcpy-receiver", receiver); "scrcpy-receiver", receiver);
if (!ok) { if (!ok) {
LOGC("Could not start receiver thread"); LOGE("Could not start receiver thread");
return false; return false;
} }

View File

@ -290,7 +290,7 @@ sc_recorder_open(struct sc_recorder *recorder, const AVCodec *input_codec) {
ok = sc_thread_create(&recorder->thread, run_recorder, "scrcpy-recorder", ok = sc_thread_create(&recorder->thread, run_recorder, "scrcpy-recorder",
recorder); recorder);
if (!ok) { if (!ok) {
LOGC("Could not start recorder thread"); LOGE("Could not start recorder thread");
goto error_avio_close; goto error_avio_close;
} }

View File

@ -269,7 +269,7 @@ scrcpy(struct scrcpy_options *options) {
// Minimal SDL initialization // Minimal SDL initialization
if (SDL_Init(SDL_INIT_EVENTS)) { if (SDL_Init(SDL_INIT_EVENTS)) {
LOGC("Could not initialize SDL: %s", SDL_GetError()); LOGE("Could not initialize SDL: %s", SDL_GetError());
return false; return false;
} }
@ -297,6 +297,8 @@ scrcpy(struct scrcpy_options *options) {
struct sc_server_params params = { struct sc_server_params params = {
.req_serial = options->serial, .req_serial = options->serial,
.select_usb = options->select_usb,
.select_tcpip = options->select_tcpip,
.log_level = options->log_level, .log_level = options->log_level,
.crop = options->crop, .crop = options->crop,
.port_range = options->port_range, .port_range = options->port_range,
@ -341,7 +343,7 @@ scrcpy(struct scrcpy_options *options) {
// Initialize SDL video in addition if display is enabled // Initialize SDL video in addition if display is enabled
if (options->display && SDL_Init(SDL_INIT_VIDEO)) { if (options->display && SDL_Init(SDL_INIT_VIDEO)) {
LOGC("Could not initialize SDL: %s", SDL_GetError()); LOGE("Could not initialize SDL: %s", SDL_GetError());
goto end; goto end;
} }
@ -430,32 +432,19 @@ scrcpy(struct scrcpy_options *options) {
} }
assert(serial); assert(serial);
struct sc_usb_device usb_devices[16]; struct sc_usb_device usb_device;
ssize_t count = sc_usb_find_devices(&s->usb, serial, usb_devices, ok = sc_usb_select_device(&s->usb, serial, &usb_device);
ARRAY_LEN(usb_devices)); if (!ok) {
if (count <= 0) {
LOGE("Could not find USB device %s", serial);
sc_usb_destroy(&s->usb); sc_usb_destroy(&s->usb);
sc_acksync_destroy(&s->acksync);
goto aoa_hid_end; goto aoa_hid_end;
} }
if (count > 1) {
LOGE("Multiple (%d) devices with serial %s", (int) count, serial);
sc_usb_device_destroy_all(usb_devices, count);
sc_usb_destroy(&s->usb);
sc_acksync_destroy(&s->acksync);
goto aoa_hid_end;
}
struct sc_usb_device *usb_device = &usb_devices[0];
LOGI("USB device: %s (%04" PRIx16 ":%04" PRIx16 ") %s %s", LOGI("USB device: %s (%04" PRIx16 ":%04" PRIx16 ") %s %s",
usb_device->serial, usb_device->vid, usb_device->pid, usb_device.serial, usb_device.vid, usb_device.pid,
usb_device->manufacturer, usb_device->product); usb_device.manufacturer, usb_device.product);
ok = sc_usb_connect(&s->usb, usb_device->device, NULL, NULL); ok = sc_usb_connect(&s->usb, usb_device.device, NULL, NULL);
sc_usb_device_destroy(usb_device); sc_usb_device_destroy(&usb_device);
if (!ok) { if (!ok) {
LOGE("Failed to connect to USB device %s", serial); LOGE("Failed to connect to USB device %s", serial);
sc_usb_destroy(&s->usb); sc_usb_destroy(&s->usb);

View File

@ -423,14 +423,14 @@ sc_screen_init(struct sc_screen *screen,
screen->window = screen->window =
SDL_CreateWindow(params->window_title, 0, 0, 0, 0, window_flags); SDL_CreateWindow(params->window_title, 0, 0, 0, 0, window_flags);
if (!screen->window) { if (!screen->window) {
LOGC("Could not create window: %s", SDL_GetError()); LOGE("Could not create window: %s", SDL_GetError());
goto error_destroy_fps_counter; goto error_destroy_fps_counter;
} }
screen->renderer = SDL_CreateRenderer(screen->window, -1, screen->renderer = SDL_CreateRenderer(screen->window, -1,
SDL_RENDERER_ACCELERATED); SDL_RENDERER_ACCELERATED);
if (!screen->renderer) { if (!screen->renderer) {
LOGC("Could not create renderer: %s", SDL_GetError()); LOGE("Could not create renderer: %s", SDL_GetError());
goto error_destroy_window; goto error_destroy_window;
} }
@ -479,7 +479,7 @@ sc_screen_init(struct sc_screen *screen,
params->frame_size.height); params->frame_size.height);
screen->texture = create_texture(screen); screen->texture = create_texture(screen);
if (!screen->texture) { if (!screen->texture) {
LOGC("Could not create texture: %s", SDL_GetError()); LOGE("Could not create texture: %s", SDL_GetError());
goto error_destroy_renderer; goto error_destroy_renderer;
} }
@ -666,7 +666,7 @@ prepare_for_frame(struct sc_screen *screen, struct sc_size new_frame_size) {
screen->frame_size.width, screen->frame_size.height); screen->frame_size.width, screen->frame_size.height);
screen->texture = create_texture(screen); screen->texture = create_texture(screen);
if (!screen->texture) { if (!screen->texture) {
LOGC("Could not create texture: %s", SDL_GetError()); LOGE("Could not create texture: %s", SDL_GetError());
return false; return false;
} }
} }

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;
@ -641,52 +625,35 @@ sc_server_connect_to_tcpip(struct sc_server *server, const char *ip_port) {
return true; return true;
} }
static bool static bool
sc_server_configure_tcpip(struct sc_server *server) { sc_server_configure_tcpip_known_address(struct sc_server *server,
char *ip_port; const char *addr) {
const struct sc_server_params *params = &server->params;
// If tcpip parameter is given, then it must connect to this address.
// Therefore, the device is unknown, so serial is meaningless at this point.
assert(!params->req_serial || !params->tcpip_dst);
if (params->tcpip_dst) {
// Append ":5555" if no port is present // Append ":5555" if no port is present
bool contains_port = strchr(params->tcpip_dst, ':'); bool contains_port = strchr(addr, ':');
ip_port = contains_port ? strdup(params->tcpip_dst) char *ip_port = contains_port ? strdup(addr) : append_port_5555(addr);
: append_port_5555(params->tcpip_dst);
if (!ip_port) { if (!ip_port) {
LOG_OOM(); LOG_OOM();
return false; return false;
} }
} else {
// The device IP address must be retrieved from the current server->serial = ip_port;
// connected device return sc_server_connect_to_tcpip(server, ip_port);
char *serial = sc_server_read_serial(server);
if (!serial) {
LOGE("Could not get device serial");
return false;
} }
// The serial is either the real serial when connected via USB, or static bool
// the IP:PORT when connected over TCP/IP. Only the latter contains sc_server_configure_tcpip_unknown_address(struct sc_server *server,
// a colon. const char *serial) {
bool is_already_tcpip = strchr(serial, ':'); bool is_already_tcpip = sc_adb_is_serial_tcpip(serial);
if (is_already_tcpip) { if (is_already_tcpip) {
// Nothing to do // Nothing to do
LOGI("Device already connected via TCP/IP: %s", serial); LOGI("Device already connected via TCP/IP: %s", serial);
free(serial);
return true; return true;
} }
ip_port = sc_server_switch_to_tcpip(server, serial); char *ip_port = sc_server_switch_to_tcpip(server, serial);
free(serial);
if (!ip_port) { if (!ip_port) {
return false; return false;
} }
}
server->serial = ip_port; server->serial = ip_port;
return sc_server_connect_to_tcpip(server, ip_port); return sc_server_connect_to_tcpip(server, ip_port);
@ -698,16 +665,69 @@ run_server(void *data) {
const struct sc_server_params *params = &server->params; const struct sc_server_params *params = &server->params;
// Execute "adb start-server" before "adb devices" so that daemon starting
// output/errors is correctly printed in the console ("adb devices" output
// is parsed, so it is not output)
bool ok = sc_adb_start_server(&server->intr, 0);
if (!ok) {
LOGE("Could not start adb daemon");
goto error_connection_failed;
}
// params->tcpip_dst implies params->tcpip
assert(!params->tcpip_dst || params->tcpip);
// If tcpip_dst parameter is given, then it must connect to this address.
// Therefore, the device is unknown, so serial is meaningless at this point.
assert(!params->req_serial || !params->tcpip_dst);
// A device must be selected via a serial in all cases except when --tcpip=
// is called with a parameter (in that case, the device may initially not
// exist, and scrcpy will execute "adb connect").
bool need_initial_serial = !params->tcpip_dst;
if (need_initial_serial) {
// At most one of the 3 following parameters may be set
assert(!!params->req_serial
+ params->select_usb
+ params->select_tcpip <= 1);
struct sc_adb_device_selector selector;
if (params->req_serial) {
selector.type = SC_ADB_DEVICE_SELECT_SERIAL;
selector.serial = params->req_serial;
} else if (params->select_usb) {
selector.type = SC_ADB_DEVICE_SELECT_USB;
} else if (params->select_tcpip) {
selector.type = SC_ADB_DEVICE_SELECT_TCPIP;
} else {
selector.type = SC_ADB_DEVICE_SELECT_ALL;
}
struct sc_adb_device device;
ok = sc_adb_select_device(&server->intr, &selector, 0, &device);
if (!ok) {
goto error_connection_failed;
}
if (params->tcpip) { if (params->tcpip) {
bool ok = sc_server_configure_tcpip(server); assert(!params->tcpip_dst);
ok = sc_server_configure_tcpip_unknown_address(server,
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 = sc_server_read_serial(server); // "move" the device.serial without copy
if (!server->serial) { server->serial = device.serial;
LOGD("Could not get device serial"); // the serial must not be freed by the destructor
device.serial = NULL;
sc_adb_device_destroy(&device);
}
} else {
ok = sc_server_configure_tcpip_known_address(server, params->tcpip_dst);
if (!ok) {
goto error_connection_failed; goto error_connection_failed;
} }
} }
@ -716,7 +736,7 @@ run_server(void *data) {
assert(serial); assert(serial);
LOGD("Device serial: %s", serial); LOGD("Device serial: %s", serial);
bool ok = push_server(&server->intr, serial); ok = push_server(&server->intr, serial);
if (!ok) { if (!ok) {
goto error_connection_failed; goto error_connection_failed;
} }

View File

@ -44,6 +44,8 @@ struct sc_server_params {
bool downsize_on_error; bool downsize_on_error;
bool tcpip; bool tcpip;
const char *tcpip_dst; const char *tcpip_dst;
bool select_usb;
bool select_tcpip;
}; };
struct sc_server { struct sc_server {

View File

@ -176,7 +176,7 @@ sc_process_execute_p(const char *const argv[], sc_pid *pid, unsigned flags,
bool bool
sc_process_terminate(pid_t pid) { sc_process_terminate(pid_t pid) {
if (pid <= 0) { if (pid <= 0) {
LOGC("Requested to kill %d, this is an error. Please report the bug.\n", LOGE("Requested to kill %d, this is an error. Please report the bug.\n",
(int) pid); (int) pid);
abort(); abort();
} }

View File

@ -278,7 +278,7 @@ sc_aoa_start(struct sc_aoa *aoa) {
bool ok = sc_thread_create(&aoa->thread, run_aoa_thread, "scrcpy-aoa", aoa); bool ok = sc_thread_create(&aoa->thread, run_aoa_thread, "scrcpy-aoa", aoa);
if (!ok) { if (!ok) {
LOGC("Could not start AOA thread"); LOGE("Could not start AOA thread");
return false; return false;
} }

View File

@ -56,7 +56,7 @@ scrcpy_otg(struct scrcpy_options *options) {
// Minimal SDL initialization // Minimal SDL initialization
if (SDL_Init(SDL_INIT_EVENTS)) { if (SDL_Init(SDL_INIT_EVENTS)) {
LOGC("Could not initialize SDL: %s", SDL_GetError()); LOGE("Could not initialize SDL: %s", SDL_GetError());
return false; return false;
} }
@ -83,50 +83,17 @@ scrcpy_otg(struct scrcpy_options *options) {
return false; return false;
} }
struct sc_usb_device usb_devices[16]; struct sc_usb_device usb_device;
ssize_t count = sc_usb_find_devices(&s->usb, serial, usb_devices, ok = sc_usb_select_device(&s->usb, serial, &usb_device);
ARRAY_LEN(usb_devices)); if (!ok) {
if (count < 0) {
LOGE("Could not list USB devices");
goto end; goto end;
} }
if (count == 0) { LOGI("USB device: %s (%04x:%04x) %s %s", usb_device.serial,
if (serial) { (unsigned) usb_device.vid, (unsigned) usb_device.pid,
LOGE("Could not find USB device %s", serial); usb_device.manufacturer, usb_device.product);
} else {
LOGE("Could not find any USB device");
}
goto end;
}
if (count > 1) { ok = sc_usb_connect(&s->usb, usb_device.device, &cbs, NULL);
if (serial) {
LOGE("Multiple (%d) USB devices with serial %s:", (int) count,
serial);
} else {
LOGE("Multiple (%d) USB devices:", (int) count);
}
for (size_t i = 0; i < (size_t) count; ++i) {
struct sc_usb_device *d = &usb_devices[i];
LOGE(" %-18s (%04" PRIx16 ":%04" PRIx16 ") %s %s",
d->serial, d->vid, d->pid, d->manufacturer, d->product);
}
if (!serial) {
LOGE("Specify the device via -s or --serial");
}
sc_usb_device_destroy_all(usb_devices, count);
goto end;
}
usb_device_initialized = true;
struct sc_usb_device *usb_device = &usb_devices[0];
LOGI("USB device: %s (%04" PRIx16 ":%04" PRIx16 ") %s %s",
usb_device->serial, usb_device->vid, usb_device->pid,
usb_device->manufacturer, usb_device->product);
ok = sc_usb_connect(&s->usb, usb_device->device, &cbs, NULL);
if (!ok) { if (!ok) {
goto end; goto end;
} }
@ -173,7 +140,7 @@ scrcpy_otg(struct scrcpy_options *options) {
const char *window_title = options->window_title; const char *window_title = options->window_title;
if (!window_title) { if (!window_title) {
window_title = usb_device->product ? usb_device->product : "scrcpy"; window_title = usb_device.product ? usb_device.product : "scrcpy";
} }
struct sc_screen_otg_params params = { struct sc_screen_otg_params params = {
@ -192,7 +159,7 @@ scrcpy_otg(struct scrcpy_options *options) {
} }
// usb_device not needed anymore // usb_device not needed anymore
sc_usb_device_destroy(usb_device); sc_usb_device_destroy(&usb_device);
usb_device_initialized = false; usb_device_initialized = false;
ret = event_loop(s); ret = event_loop(s);
@ -223,7 +190,7 @@ end:
} }
if (usb_device_initialized) { if (usb_device_initialized) {
sc_usb_device_destroy(usb_device); sc_usb_device_destroy(&usb_device);
} }
sc_usb_destroy(&s->usb); sc_usb_destroy(&s->usb);

View File

@ -25,8 +25,7 @@ read_string(libusb_device_handle *handle, uint8_t desc_index) {
} }
static bool static bool
accept_device(libusb_device *device, const char *serial, sc_usb_read_device(libusb_device *device, struct sc_usb_device *out) {
struct sc_usb_device *out) {
// Do not log any USB error in this function, it is expected that many USB // Do not log any USB error in this function, it is expected that many USB
// devices available on the computer have permission restrictions // devices available on the computer have permission restrictions
@ -39,6 +38,11 @@ accept_device(libusb_device *device, const char *serial,
libusb_device_handle *handle; libusb_device_handle *handle;
result = libusb_open(device, &handle); result = libusb_open(device, &handle);
if (result < 0) { if (result < 0) {
// Log at debug level because it is expected that some non-Android USB
// devices present on the computer require special permissions
LOGD("Open USB device %04x:%04x: libusb error: %s",
(unsigned) desc.idVendor, (unsigned) desc.idProduct,
libusb_strerror(result));
return false; return false;
} }
@ -48,22 +52,13 @@ accept_device(libusb_device *device, const char *serial,
return false; return false;
} }
if (serial) {
// Filter by serial
bool matches = !strcmp(serial, device_serial);
if (!matches) {
free(device_serial);
libusb_close(handle);
return false;
}
}
out->device = libusb_ref_device(device); out->device = libusb_ref_device(device);
out->serial = device_serial; out->serial = device_serial;
out->vid = desc.idVendor; out->vid = desc.idVendor;
out->pid = desc.idProduct; out->pid = desc.idProduct;
out->manufacturer = read_string(handle, desc.iManufacturer); out->manufacturer = read_string(handle, desc.iManufacturer);
out->product = read_string(handle, desc.iProduct); out->product = read_string(handle, desc.iProduct);
out->selected = false;
libusb_close(handle); libusb_close(handle);
@ -72,22 +67,33 @@ accept_device(libusb_device *device, const char *serial,
void void
sc_usb_device_destroy(struct sc_usb_device *usb_device) { sc_usb_device_destroy(struct sc_usb_device *usb_device) {
if (usb_device->device) {
libusb_unref_device(usb_device->device); libusb_unref_device(usb_device->device);
}
free(usb_device->serial); free(usb_device->serial);
free(usb_device->manufacturer); free(usb_device->manufacturer);
free(usb_device->product); free(usb_device->product);
} }
void void
sc_usb_device_destroy_all(struct sc_usb_device *usb_devices, size_t count) { sc_usb_device_move(struct sc_usb_device *dst, struct sc_usb_device *src) {
*dst = *src;
src->device = NULL;
src->serial = NULL;
src->manufacturer = NULL;
src->product = NULL;
}
void
sc_usb_devices_destroy_all(struct sc_usb_device *usb_devices, size_t count) {
for (size_t i = 0; i < count; ++i) { for (size_t i = 0; i < count; ++i) {
sc_usb_device_destroy(&usb_devices[i]); sc_usb_device_destroy(&usb_devices[i]);
} }
} }
ssize_t static ssize_t
sc_usb_find_devices(struct sc_usb *usb, const char *serial, sc_usb_list_devices(struct sc_usb *usb, struct sc_usb_device *devices,
struct sc_usb_device *devices, size_t len) { size_t len) {
libusb_device **list; libusb_device **list;
ssize_t count = libusb_get_device_list(usb->context, &list); ssize_t count = libusb_get_device_list(usb->context, &list);
if (count < 0) { if (count < 0) {
@ -99,7 +105,7 @@ sc_usb_find_devices(struct sc_usb *usb, const char *serial,
for (size_t i = 0; i < (size_t) count && idx < len; ++i) { for (size_t i = 0; i < (size_t) count && idx < len; ++i) {
libusb_device *device = list[i]; libusb_device *device = list[i];
if (accept_device(device, serial, &devices[idx])) { if (sc_usb_read_device(device, &devices[idx])) {
++idx; ++idx;
} }
} }
@ -108,6 +114,102 @@ sc_usb_find_devices(struct sc_usb *usb, const char *serial,
return idx; return idx;
} }
static bool
sc_usb_accept_device(const struct sc_usb_device *device, const char *serial) {
if (!serial) {
return true;
}
return !strcmp(serial, device->serial);
}
static size_t
sc_usb_devices_select(struct sc_usb_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_usb_device *device = &devices[i];
device->selected = sc_usb_accept_device(device, serial);
if (device->selected) {
if (idx_out && !count) {
*idx_out = i;
}
++count;
}
}
return count;
}
static void
sc_usb_devices_log(enum sc_log_level level, struct sc_usb_device *devices,
size_t count) {
for (size_t i = 0; i < count; ++i) {
struct sc_usb_device *d = &devices[i];
const char *selection = d->selected ? "-->" : " ";
// Convert uint16_t to unsigned because PRIx16 may not exist on Windows
LOG(level, " %s %-18s (%04x:%04x) %s %s",
selection, d->serial, (unsigned) d->vid, (unsigned) d->pid,
d->manufacturer, d->product);
}
}
bool
sc_usb_select_device(struct sc_usb *usb, const char *serial,
struct sc_usb_device *out_device) {
struct sc_usb_device usb_devices[16];
ssize_t count =
sc_usb_list_devices(usb, usb_devices, ARRAY_LEN(usb_devices));
if (count == -1) {
LOGE("Could not list USB devices");
return false;
}
if (count == 0) {
LOGE("Could not find any USB device");
return false;
}
size_t sel_idx; // index of the single matching device if sel_count == 1
size_t sel_count =
sc_usb_devices_select(usb_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 USB device %s", serial);
sc_usb_devices_log(SC_LOG_LEVEL_ERROR, usb_devices, count);
sc_usb_devices_destroy_all(usb_devices, count);
return false;
}
if (sel_count > 1) {
if (serial) {
LOGE("Multiple (%" SC_PRIsizet ") USB devices with serial %s:",
sel_count, serial);
} else {
LOGE("Multiple (%" SC_PRIsizet ") USB devices:", sel_count);
}
sc_usb_devices_log(SC_LOG_LEVEL_ERROR, usb_devices, count);
if (!serial) {
LOGE("Specify the device via -s or --serial");
}
sc_usb_devices_destroy_all(usb_devices, count);
return false;
}
assert(sel_count == 1); // sel_idx is valid only if sel_count == 1
struct sc_usb_device *device = &usb_devices[sel_idx];
LOGD("USB device found:");
sc_usb_devices_log(SC_LOG_LEVEL_DEBUG, usb_devices, count);
// Move device into out_device (do not destroy device)
sc_usb_device_move(out_device, device);
sc_usb_devices_destroy_all(usb_devices, count);
return true;
}
bool bool
sc_usb_init(struct sc_usb *usb) { sc_usb_init(struct sc_usb *usb) {
usb->handle = NULL; usb->handle = NULL;
@ -119,7 +221,7 @@ sc_usb_destroy(struct sc_usb *usb) {
libusb_exit(usb->context); libusb_exit(usb->context);
} }
static int static LIBUSB_CALL int
sc_usb_libusb_callback(libusb_context *ctx, libusb_device *device, sc_usb_libusb_callback(libusb_context *ctx, libusb_device *device,
libusb_hotplug_event event, void *userdata) { libusb_hotplug_event event, void *userdata) {
(void) ctx; (void) ctx;

View File

@ -35,13 +35,26 @@ struct sc_usb_device {
char *product; char *product;
uint16_t vid; uint16_t vid;
uint16_t pid; uint16_t pid;
bool selected;
}; };
void void
sc_usb_device_destroy(struct sc_usb_device *usb_device); sc_usb_device_destroy(struct sc_usb_device *usb_device);
/**
* Move src to dest
*
* After this call, the content of src is undefined, except that
* sc_usb_device_destroy() can be called.
*
* This is useful to take a device from a list that will be destroyed, without
* making unnecessary copies.
*/
void void
sc_usb_device_destroy_all(struct sc_usb_device *usb_devices, size_t count); sc_usb_device_move(struct sc_usb_device *dst, struct sc_usb_device *src);
void
sc_usb_devices_destroy_all(struct sc_usb_device *usb_devices, size_t count);
bool bool
sc_usb_init(struct sc_usb *usb); sc_usb_init(struct sc_usb *usb);
@ -49,9 +62,9 @@ sc_usb_init(struct sc_usb *usb);
void void
sc_usb_destroy(struct sc_usb *usb); sc_usb_destroy(struct sc_usb *usb);
ssize_t bool
sc_usb_find_devices(struct sc_usb *usb, const char *serial, sc_usb_select_device(struct sc_usb *usb, const char *serial,
struct sc_usb_device *devices, size_t len); struct sc_usb_device *out_device);
bool bool
sc_usb_connect(struct sc_usb *usb, libusb_device *device, sc_usb_connect(struct sc_usb *usb, libusb_device *device,

View File

@ -55,6 +55,16 @@ sc_get_log_level(void) {
return log_level_sdl_to_sc(sdl_log); return log_level_sdl_to_sc(sdl_log);
} }
void
sc_log(enum sc_log_level level, const char *fmt, ...) {
SDL_LogPriority sdl_level = log_level_sc_to_sdl(level);
va_list ap;
va_start(ap, fmt);
SDL_LogMessageV(SDL_LOG_CATEGORY_APPLICATION, sdl_level, fmt, ap);
va_end(ap);
}
#ifdef _WIN32 #ifdef _WIN32
bool bool
sc_log_windows_error(const char *prefix, int error) { sc_log_windows_error(const char *prefix, int error) {

View File

@ -15,10 +15,9 @@
#define LOGI(...) SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, __VA_ARGS__) #define LOGI(...) SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, __VA_ARGS__)
#define LOGW(...) SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, __VA_ARGS__) #define LOGW(...) SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, __VA_ARGS__)
#define LOGE(...) SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, __VA_ARGS__) #define LOGE(...) SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, __VA_ARGS__)
#define LOGC(...) SDL_LogCritical(SDL_LOG_CATEGORY_APPLICATION, __VA_ARGS__)
#define LOG_OOM() \ #define LOG_OOM() \
LOGC("OOM: %s:%d %s()", __FILE__, __LINE__, __func__) LOGE("OOM: %s:%d %s()", __FILE__, __LINE__, __func__)
void void
sc_set_log_level(enum sc_log_level level); sc_set_log_level(enum sc_log_level level);
@ -26,6 +25,10 @@ sc_set_log_level(enum sc_log_level level);
enum sc_log_level enum sc_log_level
sc_get_log_level(void); sc_get_log_level(void);
void
sc_log(enum sc_log_level level, const char *fmt, ...);
#define LOG(LEVEL, ...) sc_log((LEVEL), __VA_ARGS__)
#ifdef _WIN32 #ifdef _WIN32
// Log system error (typically returned by GetLastError() or similar) // Log system error (typically returned by GetLastError() or similar)
bool bool

View File

@ -33,7 +33,7 @@ net_init(void) {
WSADATA wsa; WSADATA wsa;
int res = WSAStartup(MAKEWORD(2, 2), &wsa) < 0; int res = WSAStartup(MAKEWORD(2, 2), &wsa) < 0;
if (res < 0) { if (res < 0) {
LOGC("WSAStartup failed with error %d", res); LOGE("WSAStartup failed with error %d", res);
return false; return false;
} }
#endif #endif

View File

@ -12,8 +12,6 @@
# include <winsock2.h> # include <winsock2.h>
# include <windows.h> # include <windows.h>
# define SC_PRIexitcode "lu" # define SC_PRIexitcode "lu"
// <https://stackoverflow.com/a/44383330/1987178>
# define SC_PRIsizet "Iu"
# define SC_PROCESS_NONE NULL # define SC_PROCESS_NONE NULL
# define SC_EXIT_CODE_NONE -1UL // max value as unsigned long # define SC_EXIT_CODE_NONE -1UL // max value as unsigned long
typedef HANDLE sc_pid; typedef HANDLE sc_pid;
@ -23,7 +21,6 @@
#else #else
# include <sys/types.h> # include <sys/types.h>
# define SC_PRIsizet "zu"
# define SC_PRIexitcode "d" # define SC_PRIexitcode "d"
# define SC_PROCESS_NONE -1 # define SC_PROCESS_NONE -1
# define SC_EXIT_CODE_NONE -1 # define SC_EXIT_CODE_NONE -1

View File

@ -54,7 +54,7 @@ sc_mutex_lock(sc_mutex *mutex) {
int r = SDL_LockMutex(mutex->mutex); int r = SDL_LockMutex(mutex->mutex);
#ifndef NDEBUG #ifndef NDEBUG
if (r) { if (r) {
LOGC("Could not lock mutex: %s", SDL_GetError()); LOGE("Could not lock mutex: %s", SDL_GetError());
abort(); abort();
} }
@ -74,7 +74,7 @@ sc_mutex_unlock(sc_mutex *mutex) {
int r = SDL_UnlockMutex(mutex->mutex); int r = SDL_UnlockMutex(mutex->mutex);
#ifndef NDEBUG #ifndef NDEBUG
if (r) { if (r) {
LOGC("Could not lock mutex: %s", SDL_GetError()); LOGE("Could not lock mutex: %s", SDL_GetError());
abort(); abort();
} }
#else #else
@ -118,7 +118,7 @@ sc_cond_wait(sc_cond *cond, sc_mutex *mutex) {
int r = SDL_CondWait(cond->cond, mutex->mutex); int r = SDL_CondWait(cond->cond, mutex->mutex);
#ifndef NDEBUG #ifndef NDEBUG
if (r) { if (r) {
LOGC("Could not wait on condition: %s", SDL_GetError()); LOGE("Could not wait on condition: %s", SDL_GetError());
abort(); abort();
} }
@ -140,7 +140,7 @@ sc_cond_timedwait(sc_cond *cond, sc_mutex *mutex, sc_tick deadline) {
int r = SDL_CondWaitTimeout(cond->cond, mutex->mutex, ms); int r = SDL_CondWaitTimeout(cond->cond, mutex->mutex, ms);
#ifndef NDEBUG #ifndef NDEBUG
if (r < 0) { if (r < 0) {
LOGC("Could not wait on condition with timeout: %s", SDL_GetError()); LOGE("Could not wait on condition with timeout: %s", SDL_GetError());
abort(); abort();
} }
@ -156,7 +156,7 @@ sc_cond_signal(sc_cond *cond) {
int r = SDL_CondSignal(cond->cond); int r = SDL_CondSignal(cond->cond);
#ifndef NDEBUG #ifndef NDEBUG
if (r) { if (r) {
LOGC("Could not signal a condition: %s", SDL_GetError()); LOGE("Could not signal a condition: %s", SDL_GetError());
abort(); abort();
} }
#else #else
@ -169,7 +169,7 @@ sc_cond_broadcast(sc_cond *cond) {
int r = SDL_CondBroadcast(cond->cond); int r = SDL_CondBroadcast(cond->cond);
#ifndef NDEBUG #ifndef NDEBUG
if (r) { if (r) {
LOGC("Could not broadcast a condition: %s", SDL_GetError()); LOGE("Could not broadcast a condition: %s", SDL_GetError());
abort(); abort();
} }
#else #else

View File

@ -274,7 +274,7 @@ sc_v4l2_sink_open(struct sc_v4l2_sink *vs) {
LOGD("Starting v4l2 thread"); LOGD("Starting v4l2 thread");
ok = sc_thread_create(&vs->thread, run_v4l2_sink, "scrcpy-v4l2", vs); ok = sc_thread_create(&vs->thread, run_v4l2_sink, "scrcpy-v4l2", vs);
if (!ok) { if (!ok) {
LOGC("Could not start v4l2 thread"); LOGE("Could not start v4l2 thread");
goto error_av_packet_free; goto error_av_packet_free;
} }

View File

@ -2,8 +2,158 @@
#include <assert.h> #include <assert.h>
#include "adb/adb_device.h"
#include "adb/adb_parser.h" #include "adb/adb_parser.h"
static void test_adb_devices() {
char output[] =
"List of devices attached\n"
"0123456789abcdef device usb:2-1 product:MyProduct model:MyModel "
"device:MyDevice transport_id:1\n"
"192.168.1.1:5555 device product:MyWifiProduct model:MyWifiModel "
"device:MyWifiDevice trandport_id:2\n";
struct sc_adb_device devices[16];
ssize_t count = sc_adb_parse_devices(output, devices, ARRAY_LEN(devices));
assert(count == 2);
struct sc_adb_device *device = &devices[0];
assert(!strcmp("0123456789abcdef", device->serial));
assert(!strcmp("device", device->state));
assert(!strcmp("MyModel", device->model));
device = &devices[1];
assert(!strcmp("192.168.1.1:5555", device->serial));
assert(!strcmp("device", device->state));
assert(!strcmp("MyWifiModel", device->model));
sc_adb_devices_destroy_all(devices, count);
}
static void test_adb_devices_cr() {
char output[] =
"List of devices attached\r\n"
"0123456789abcdef device usb:2-1 product:MyProduct model:MyModel "
"device:MyDevice transport_id:1\r\n"
"192.168.1.1:5555 device product:MyWifiProduct model:MyWifiModel "
"device:MyWifiDevice trandport_id:2\r\n";
struct sc_adb_device devices[16];
ssize_t count = sc_adb_parse_devices(output, devices, ARRAY_LEN(devices));
assert(count == 2);
struct sc_adb_device *device = &devices[0];
assert(!strcmp("0123456789abcdef", device->serial));
assert(!strcmp("device", device->state));
assert(!strcmp("MyModel", device->model));
device = &devices[1];
assert(!strcmp("192.168.1.1:5555", device->serial));
assert(!strcmp("device", device->state));
assert(!strcmp("MyWifiModel", device->model));
sc_adb_devices_destroy_all(devices, count);
}
static void test_adb_devices_daemon_start() {
char output[] =
"* daemon not running; starting now at tcp:5037\n"
"* daemon started successfully\n"
"List of devices attached\n"
"0123456789abcdef device usb:2-1 product:MyProduct model:MyModel "
"device:MyDevice transport_id:1\n";
struct sc_adb_device devices[16];
ssize_t count = sc_adb_parse_devices(output, devices, ARRAY_LEN(devices));
assert(count == 1);
struct sc_adb_device *device = &devices[0];
assert(!strcmp("0123456789abcdef", device->serial));
assert(!strcmp("device", device->state));
assert(!strcmp("MyModel", device->model));
sc_adb_device_destroy(device);
}
static void test_adb_devices_daemon_start_mixed() {
char output[] =
"List of devices attached\n"
"adb server version (41) doesn't match this client (39); killing...\n"
"* daemon started successfully *\n"
"0123456789abcdef unauthorized usb:1-1\n"
"87654321 device usb:2-1 product:MyProduct model:MyModel "
"device:MyDevice\n";
struct sc_adb_device devices[16];
ssize_t count = sc_adb_parse_devices(output, devices, ARRAY_LEN(devices));
assert(count == 2);
struct sc_adb_device *device = &devices[0];
assert(!strcmp("0123456789abcdef", device->serial));
assert(!strcmp("unauthorized", device->state));
fprintf(stderr, "==== [%s]\n", device->model);
assert(!device->model);
device = &devices[1];
assert(!strcmp("87654321", device->serial));
assert(!strcmp("device", device->state));
assert(!strcmp("MyModel", device->model));
sc_adb_devices_destroy_all(devices, count);
}
static void test_adb_devices_without_eol() {
char output[] =
"List of devices attached\n"
"0123456789abcdef device usb:2-1 product:MyProduct model:MyModel "
"device:MyDevice transport_id:1";
struct sc_adb_device devices[16];
ssize_t count = sc_adb_parse_devices(output, devices, ARRAY_LEN(devices));
assert(count == 1);
struct sc_adb_device *device = &devices[0];
assert(!strcmp("0123456789abcdef", device->serial));
assert(!strcmp("device", device->state));
assert(!strcmp("MyModel", device->model));
sc_adb_device_destroy(device);
}
static void test_adb_devices_without_header() {
char output[] =
"0123456789abcdef device usb:2-1 product:MyProduct model:MyModel "
"device:MyDevice transport_id:1\n";
struct sc_adb_device devices[16];
ssize_t count = sc_adb_parse_devices(output, devices, ARRAY_LEN(devices));
assert(count == -1);
}
static void test_adb_devices_corrupted() {
char output[] =
"List of devices attached\n"
"corrupted_garbage\n";
struct sc_adb_device devices[16];
ssize_t count = sc_adb_parse_devices(output, devices, ARRAY_LEN(devices));
assert(count == 0);
}
static void test_adb_devices_spaces() {
char output[] =
"List of devices attached\n"
"0123456789abcdef unauthorized usb:1-4 transport_id:3\n";
struct sc_adb_device devices[16];
ssize_t count = sc_adb_parse_devices(output, devices, ARRAY_LEN(devices));
assert(count == 1);
struct sc_adb_device *device = &devices[0];
assert(!strcmp("0123456789abcdef", device->serial));
assert(!strcmp("unauthorized", device->state));
assert(!device->model);
sc_adb_device_destroy(device);
}
static void test_get_ip_single_line() { static void test_get_ip_single_line() {
char ip_route[] = "192.168.1.0/24 dev wlan0 proto kernel scope link src " char ip_route[] = "192.168.1.0/24 dev wlan0 proto kernel scope link src "
"192.168.12.34\r\r\n"; "192.168.12.34\r\r\n";
@ -86,6 +236,15 @@ int main(int argc, char *argv[]) {
(void) argc; (void) argc;
(void) argv; (void) argv;
test_adb_devices();
test_adb_devices_cr();
test_adb_devices_daemon_start();
test_adb_devices_daemon_start_mixed();
test_adb_devices_without_eol();
test_adb_devices_without_header();
test_adb_devices_corrupted();
test_adb_devices_spaces();
test_get_ip_single_line(); test_get_ip_single_line();
test_get_ip_single_line_without_eol(); test_get_ip_single_line_without_eol();
test_get_ip_single_line_with_trailing_space(); test_get_ip_single_line_with_trailing_space();

View File

@ -21,3 +21,5 @@ ffmpeg_avformat = 'avformat-58'
ffmpeg_avutil = 'avutil-56' ffmpeg_avutil = 'avutil-56'
prebuilt_ffmpeg = 'ffmpeg-win32-4.3.1' prebuilt_ffmpeg = 'ffmpeg-win32-4.3.1'
prebuilt_sdl2 = 'SDL2-2.0.20/i686-w64-mingw32' prebuilt_sdl2 = 'SDL2-2.0.20/i686-w64-mingw32'
prebuilt_libusb_root = 'libusb-1.0.25'
prebuilt_libusb = prebuilt_libusb_root + '/MinGW32'

View File

@ -21,3 +21,5 @@ ffmpeg_avformat = 'avformat-59'
ffmpeg_avutil = 'avutil-57' ffmpeg_avutil = 'avutil-57'
prebuilt_ffmpeg = 'ffmpeg-win64-5.0' prebuilt_ffmpeg = 'ffmpeg-win64-5.0'
prebuilt_sdl2 = 'SDL2-2.0.20/x86_64-w64-mingw32' prebuilt_sdl2 = 'SDL2-2.0.20/x86_64-w64-mingw32'
prebuilt_libusb_root = 'libusb-1.0.25'
prebuilt_libusb = prebuilt_libusb_root + '/MinGW64'

28
prebuilt-deps/prepare-libusb.sh Executable file
View File

@ -0,0 +1,28 @@
#!/usr/bin/env bash
set -e
DIR=$(dirname ${BASH_SOURCE[0]})
cd "$DIR"
. common
mkdir -p "$PREBUILT_DATA_DIR"
cd "$PREBUILT_DATA_DIR"
DEP_DIR=libusb-1.0.25
FILENAME=libusb-1.0.25.7z
SHA256SUM=3d1c98416f454026034b2b5d67f8a294053898cb70a8b489874e75b136c6674d
if [[ -d "$DEP_DIR" ]]
then
echo "$DEP_DIR" found
exit 0
fi
get_file "https://github.com/libusb/libusb/releases/download/v1.0.25/$FILENAME" "$FILENAME" "$SHA256SUM"
mkdir "$DEP_DIR"
cd "$DEP_DIR"
7z x "../$FILENAME" \
MinGW32/dll/libusb-1.0.dll \
MinGW64/dll/libusb-1.0.dll \
include /

View File

@ -66,6 +66,7 @@ prepare-deps-win32:
@prebuilt-deps/prepare-adb.sh @prebuilt-deps/prepare-adb.sh
@prebuilt-deps/prepare-sdl.sh @prebuilt-deps/prepare-sdl.sh
@prebuilt-deps/prepare-ffmpeg-win32.sh @prebuilt-deps/prepare-ffmpeg-win32.sh
@prebuilt-deps/prepare-libusb.sh
build-win32: prepare-deps-win32 build-win32: prepare-deps-win32
[ -d "$(WIN32_BUILD_DIR)" ] || ( mkdir "$(WIN32_BUILD_DIR)" && \ [ -d "$(WIN32_BUILD_DIR)" ] || ( mkdir "$(WIN32_BUILD_DIR)" && \
@ -107,6 +108,7 @@ dist-win32: build-server build-win32
cp prebuilt-deps/data/platform-tools-31.0.3/AdbWinApi.dll "$(DIST)/$(WIN32_TARGET_DIR)/" cp prebuilt-deps/data/platform-tools-31.0.3/AdbWinApi.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
cp prebuilt-deps/data/platform-tools-31.0.3/AdbWinUsbApi.dll "$(DIST)/$(WIN32_TARGET_DIR)/" cp prebuilt-deps/data/platform-tools-31.0.3/AdbWinUsbApi.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
cp prebuilt-deps/data/SDL2-2.0.20/i686-w64-mingw32/bin/SDL2.dll "$(DIST)/$(WIN32_TARGET_DIR)/" cp prebuilt-deps/data/SDL2-2.0.20/i686-w64-mingw32/bin/SDL2.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
cp prebuilt-deps/data/libusb-1.0.25/MinGW32/dll/libusb-1.0.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
dist-win64: build-server build-win64 dist-win64: build-server build-win64
mkdir -p "$(DIST)/$(WIN64_TARGET_DIR)" mkdir -p "$(DIST)/$(WIN64_TARGET_DIR)"
@ -125,6 +127,7 @@ dist-win64: build-server build-win64
cp prebuilt-deps/data/platform-tools-31.0.3/AdbWinApi.dll "$(DIST)/$(WIN64_TARGET_DIR)/" cp prebuilt-deps/data/platform-tools-31.0.3/AdbWinApi.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
cp prebuilt-deps/data/platform-tools-31.0.3/AdbWinUsbApi.dll "$(DIST)/$(WIN64_TARGET_DIR)/" cp prebuilt-deps/data/platform-tools-31.0.3/AdbWinUsbApi.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
cp prebuilt-deps/data/SDL2-2.0.20/x86_64-w64-mingw32/bin/SDL2.dll "$(DIST)/$(WIN64_TARGET_DIR)/" cp prebuilt-deps/data/SDL2-2.0.20/x86_64-w64-mingw32/bin/SDL2.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
cp prebuilt-deps/data/libusb-1.0.25/MinGW64/dll/libusb-1.0.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
zip-win32: dist-win32 zip-win32: dist-win32
cd "$(DIST)/$(WIN32_TARGET_DIR)"; \ cd "$(DIST)/$(WIN32_TARGET_DIR)"; \