Compare commits
3 Commits
libusb-all
...
pr3023
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
180984f825 | ||
|
|
f366d4256e | ||
|
|
2398a5aa58 |
1016
README.de.md
Normal file
1016
README.de.md
Normal file
File diff suppressed because it is too large
Load Diff
16
README.md
16
README.md
@@ -422,7 +422,7 @@ scrcpy -b2M -m800 # short version
|
|||||||
|
|
||||||
#### Multi-devices
|
#### Multi-devices
|
||||||
|
|
||||||
If several devices are listed in `adb devices`, you can specify the _serial_:
|
If several devices are listed in `adb devices`, you must specify the _serial_:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
scrcpy --serial 0123456789abcdef
|
scrcpy --serial 0123456789abcdef
|
||||||
@@ -436,19 +436,6 @@ 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
|
||||||
@@ -1123,6 +1110,7 @@ Read the [developers page].
|
|||||||
|
|
||||||
This README is available in other languages:
|
This README is available in other languages:
|
||||||
|
|
||||||
|
- [Deutsch (German, `de`) - v1.22](README.de.md)
|
||||||
- [Indonesian (Indonesia, `id`) - v1.16](README.id.md)
|
- [Indonesian (Indonesia, `id`) - v1.16](README.id.md)
|
||||||
- [Italiano (Italiano, `it`) - v1.19](README.it.md)
|
- [Italiano (Italiano, `it`) - v1.19](README.it.md)
|
||||||
- [日本語 (Japanese, `jp`) - v1.19](README.jp.md)
|
- [日本語 (Japanese, `jp`) - v1.19](README.jp.md)
|
||||||
|
|||||||
@@ -1,16 +1,14 @@
|
|||||||
src = [
|
src = [
|
||||||
'src/main.c',
|
'src/main.c',
|
||||||
'src/adb/adb.c',
|
'src/adb.c',
|
||||||
'src/adb/adb_device.c',
|
'src/adb_parser.c',
|
||||||
'src/adb/adb_parser.c',
|
'src/adb_tunnel.c',
|
||||||
'src/adb/adb_tunnel.c',
|
|
||||||
'src/cli.c',
|
'src/cli.c',
|
||||||
'src/clock.c',
|
'src/clock.c',
|
||||||
'src/compat.c',
|
'src/compat.c',
|
||||||
'src/control_msg.c',
|
'src/control_msg.c',
|
||||||
'src/controller.c',
|
'src/controller.c',
|
||||||
'src/decoder.c',
|
'src/decoder.c',
|
||||||
'src/demuxer.c',
|
|
||||||
'src/device_msg.c',
|
'src/device_msg.c',
|
||||||
'src/icon.c',
|
'src/icon.c',
|
||||||
'src/file_pusher.c',
|
'src/file_pusher.c',
|
||||||
@@ -26,6 +24,7 @@ src = [
|
|||||||
'src/scrcpy.c',
|
'src/scrcpy.c',
|
||||||
'src/screen.c',
|
'src/screen.c',
|
||||||
'src/server.c',
|
'src/server.c',
|
||||||
|
'src/stream.c',
|
||||||
'src/video_buffer.c',
|
'src/video_buffer.c',
|
||||||
'src/util/acksync.c',
|
'src/util/acksync.c',
|
||||||
'src/util/file.c',
|
'src/util/file.c',
|
||||||
@@ -73,7 +72,7 @@ if v4l2_support
|
|||||||
src += [ 'src/v4l2_sink.c' ]
|
src += [ 'src/v4l2_sink.c' ]
|
||||||
endif
|
endif
|
||||||
|
|
||||||
usb_support = true
|
usb_support = host_machine.system() == 'linux'
|
||||||
if usb_support
|
if usb_support
|
||||||
src += [
|
src += [
|
||||||
'src/usb/aoa_hid.c',
|
'src/usb/aoa_hid.c',
|
||||||
@@ -140,22 +139,9 @@ 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')
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -235,8 +221,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_parser.c',
|
||||||
'src/adb/adb_parser.c',
|
|
||||||
'src/util/str.c',
|
'src/util/str.c',
|
||||||
'src/util/strbuf.c',
|
'src/util/strbuf.c',
|
||||||
]],
|
]],
|
||||||
|
|||||||
@@ -273,14 +273,6 @@ 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.
|
||||||
|
|||||||
471
app/src/adb.c
Normal file
471
app/src/adb.c
Normal file
@@ -0,0 +1,471 @@
|
|||||||
|
#include "adb.h"
|
||||||
|
|
||||||
|
#include <assert.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include "adb_parser.h"
|
||||||
|
#include "util/file.h"
|
||||||
|
#include "util/log.h"
|
||||||
|
#include "util/process_intr.h"
|
||||||
|
#include "util/str.h"
|
||||||
|
|
||||||
|
static const char *adb_command;
|
||||||
|
|
||||||
|
static inline const char *
|
||||||
|
get_adb_command(void) {
|
||||||
|
if (!adb_command) {
|
||||||
|
adb_command = getenv("ADB");
|
||||||
|
if (!adb_command)
|
||||||
|
adb_command = "adb";
|
||||||
|
}
|
||||||
|
return adb_command;
|
||||||
|
}
|
||||||
|
|
||||||
|
// serialize argv to string "[arg1], [arg2], [arg3]"
|
||||||
|
static size_t
|
||||||
|
argv_to_string(const char *const *argv, char *buf, size_t bufsize) {
|
||||||
|
size_t idx = 0;
|
||||||
|
bool first = true;
|
||||||
|
while (*argv) {
|
||||||
|
const char *arg = *argv;
|
||||||
|
size_t len = strlen(arg);
|
||||||
|
// count space for "[], ...\0"
|
||||||
|
if (idx + len + 8 >= bufsize) {
|
||||||
|
// not enough space, truncate
|
||||||
|
assert(idx < bufsize - 4);
|
||||||
|
memcpy(&buf[idx], "...", 3);
|
||||||
|
idx += 3;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (first) {
|
||||||
|
first = false;
|
||||||
|
} else {
|
||||||
|
buf[idx++] = ',';
|
||||||
|
buf[idx++] = ' ';
|
||||||
|
}
|
||||||
|
buf[idx++] = '[';
|
||||||
|
memcpy(&buf[idx], arg, len);
|
||||||
|
idx += len;
|
||||||
|
buf[idx++] = ']';
|
||||||
|
argv++;
|
||||||
|
}
|
||||||
|
assert(idx < bufsize);
|
||||||
|
buf[idx] = '\0';
|
||||||
|
return idx;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
show_adb_installation_msg() {
|
||||||
|
#ifndef __WINDOWS__
|
||||||
|
static const struct {
|
||||||
|
const char *binary;
|
||||||
|
const char *command;
|
||||||
|
} pkg_managers[] = {
|
||||||
|
{"apt", "apt install adb"},
|
||||||
|
{"apt-get", "apt-get install adb"},
|
||||||
|
{"brew", "brew cask install android-platform-tools"},
|
||||||
|
{"dnf", "dnf install android-tools"},
|
||||||
|
{"emerge", "emerge dev-util/android-tools"},
|
||||||
|
{"pacman", "pacman -S android-tools"},
|
||||||
|
};
|
||||||
|
for (size_t i = 0; i < ARRAY_LEN(pkg_managers); ++i) {
|
||||||
|
if (sc_file_executable_exists(pkg_managers[i].binary)) {
|
||||||
|
LOGI("You may install 'adb' by \"%s\"", pkg_managers[i].command);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
LOGI("You may download and install 'adb' from "
|
||||||
|
"https://developer.android.com/studio/releases/platform-tools");
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
show_adb_err_msg(enum sc_process_result err, const char *const argv[]) {
|
||||||
|
#define MAX_COMMAND_STRING_LEN 1024
|
||||||
|
char *buf = malloc(MAX_COMMAND_STRING_LEN);
|
||||||
|
if (!buf) {
|
||||||
|
LOG_OOM();
|
||||||
|
LOGE("Failed to execute");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (err) {
|
||||||
|
case SC_PROCESS_ERROR_GENERIC:
|
||||||
|
argv_to_string(argv, buf, MAX_COMMAND_STRING_LEN);
|
||||||
|
LOGE("Failed to execute: %s", buf);
|
||||||
|
break;
|
||||||
|
case SC_PROCESS_ERROR_MISSING_BINARY:
|
||||||
|
argv_to_string(argv, buf, MAX_COMMAND_STRING_LEN);
|
||||||
|
LOGE("Command not found: %s", buf);
|
||||||
|
LOGE("(make 'adb' accessible from your PATH or define its full"
|
||||||
|
"path in the ADB environment variable)");
|
||||||
|
show_adb_installation_msg();
|
||||||
|
break;
|
||||||
|
case SC_PROCESS_SUCCESS:
|
||||||
|
// do nothing
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
free(buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool
|
||||||
|
process_check_success_internal(sc_pid pid, const char *name, bool close,
|
||||||
|
unsigned flags) {
|
||||||
|
bool log_errors = !(flags & SC_ADB_NO_LOGERR);
|
||||||
|
|
||||||
|
if (pid == SC_PROCESS_NONE) {
|
||||||
|
if (log_errors) {
|
||||||
|
LOGE("Could not execute \"%s\"", name);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
sc_exit_code exit_code = sc_process_wait(pid, close);
|
||||||
|
if (exit_code) {
|
||||||
|
if (log_errors) {
|
||||||
|
if (exit_code != SC_EXIT_CODE_NONE) {
|
||||||
|
LOGE("\"%s\" returned with value %" SC_PRIexitcode, name,
|
||||||
|
exit_code);
|
||||||
|
} else {
|
||||||
|
LOGE("\"%s\" exited unexpectedly", name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool
|
||||||
|
process_check_success_intr(struct sc_intr *intr, sc_pid pid, const char *name,
|
||||||
|
unsigned flags) {
|
||||||
|
if (!sc_intr_set_process(intr, pid)) {
|
||||||
|
// Already interrupted
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Always pass close=false, interrupting would be racy otherwise
|
||||||
|
bool ret = process_check_success_internal(pid, name, false, flags);
|
||||||
|
|
||||||
|
sc_intr_set_process(intr, SC_PROCESS_NONE);
|
||||||
|
|
||||||
|
// Close separately
|
||||||
|
sc_process_close(pid);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const char **
|
||||||
|
adb_create_argv(const char *serial, const char *const adb_cmd[], size_t len) {
|
||||||
|
const char **argv = malloc((len + 4) * sizeof(*argv));
|
||||||
|
if (!argv) {
|
||||||
|
LOG_OOM();
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
argv[0] = get_adb_command();
|
||||||
|
int i;
|
||||||
|
if (serial) {
|
||||||
|
argv[1] = "-s";
|
||||||
|
argv[2] = serial;
|
||||||
|
i = 3;
|
||||||
|
} else {
|
||||||
|
i = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
memcpy(&argv[i], adb_cmd, len * sizeof(const char *));
|
||||||
|
argv[len + i] = NULL;
|
||||||
|
return argv;
|
||||||
|
}
|
||||||
|
|
||||||
|
static sc_pid
|
||||||
|
adb_execute_p(const char *serial, const char *const adb_cmd[], size_t len,
|
||||||
|
unsigned flags, sc_pipe *pout) {
|
||||||
|
const char **argv = adb_create_argv(serial, adb_cmd, len);
|
||||||
|
if (!argv) {
|
||||||
|
return SC_PROCESS_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned process_flags = 0;
|
||||||
|
if (flags & SC_ADB_NO_STDOUT) {
|
||||||
|
process_flags |= SC_PROCESS_NO_STDOUT;
|
||||||
|
}
|
||||||
|
if (flags & SC_ADB_NO_STDERR) {
|
||||||
|
process_flags |= SC_PROCESS_NO_STDERR;
|
||||||
|
}
|
||||||
|
|
||||||
|
sc_pid pid;
|
||||||
|
enum sc_process_result r =
|
||||||
|
sc_process_execute_p(argv, &pid, process_flags, NULL, pout, NULL);
|
||||||
|
if (r != SC_PROCESS_SUCCESS) {
|
||||||
|
// If the execution itself failed (not the command exit code), log the
|
||||||
|
// error in all cases
|
||||||
|
show_adb_err_msg(r, argv);
|
||||||
|
pid = SC_PROCESS_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
free(argv);
|
||||||
|
return pid;
|
||||||
|
}
|
||||||
|
|
||||||
|
sc_pid
|
||||||
|
adb_execute(const char *serial, const char *const adb_cmd[], size_t len,
|
||||||
|
unsigned flags) {
|
||||||
|
return adb_execute_p(serial, adb_cmd, len, flags, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
adb_forward(struct sc_intr *intr, const char *serial, uint16_t local_port,
|
||||||
|
const char *device_socket_name, unsigned flags) {
|
||||||
|
char local[4 + 5 + 1]; // tcp:PORT
|
||||||
|
char remote[108 + 14 + 1]; // localabstract:NAME
|
||||||
|
sprintf(local, "tcp:%" PRIu16, local_port);
|
||||||
|
snprintf(remote, sizeof(remote), "localabstract:%s", device_socket_name);
|
||||||
|
const char *const adb_cmd[] = {"forward", local, remote};
|
||||||
|
|
||||||
|
sc_pid pid = adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd), flags);
|
||||||
|
return process_check_success_intr(intr, pid, "adb forward", flags);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
adb_forward_remove(struct sc_intr *intr, const char *serial,
|
||||||
|
uint16_t local_port, unsigned flags) {
|
||||||
|
char local[4 + 5 + 1]; // tcp:PORT
|
||||||
|
sprintf(local, "tcp:%" PRIu16, local_port);
|
||||||
|
const char *const adb_cmd[] = {"forward", "--remove", local};
|
||||||
|
|
||||||
|
sc_pid pid = adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd), flags);
|
||||||
|
return process_check_success_intr(intr, pid, "adb forward --remove", flags);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
adb_reverse(struct sc_intr *intr, const char *serial,
|
||||||
|
const char *device_socket_name, uint16_t local_port,
|
||||||
|
unsigned flags) {
|
||||||
|
char local[4 + 5 + 1]; // tcp:PORT
|
||||||
|
char remote[108 + 14 + 1]; // localabstract:NAME
|
||||||
|
sprintf(local, "tcp:%" PRIu16, local_port);
|
||||||
|
snprintf(remote, sizeof(remote), "localabstract:%s", device_socket_name);
|
||||||
|
const char *const adb_cmd[] = {"reverse", remote, local};
|
||||||
|
|
||||||
|
sc_pid pid = adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd), flags);
|
||||||
|
return process_check_success_intr(intr, pid, "adb reverse", flags);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
adb_reverse_remove(struct sc_intr *intr, const char *serial,
|
||||||
|
const char *device_socket_name, unsigned flags) {
|
||||||
|
char remote[108 + 14 + 1]; // localabstract:NAME
|
||||||
|
snprintf(remote, sizeof(remote), "localabstract:%s", device_socket_name);
|
||||||
|
const char *const adb_cmd[] = {"reverse", "--remove", remote};
|
||||||
|
|
||||||
|
sc_pid pid = adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd), flags);
|
||||||
|
return process_check_success_intr(intr, pid, "adb reverse --remove", flags);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
adb_push(struct sc_intr *intr, const char *serial, const char *local,
|
||||||
|
const char *remote, unsigned flags) {
|
||||||
|
#ifdef __WINDOWS__
|
||||||
|
// Windows will parse the string, so the paths must be quoted
|
||||||
|
// (see sys/win/command.c)
|
||||||
|
local = sc_str_quote(local);
|
||||||
|
if (!local) {
|
||||||
|
return SC_PROCESS_NONE;
|
||||||
|
}
|
||||||
|
remote = sc_str_quote(remote);
|
||||||
|
if (!remote) {
|
||||||
|
free((void *) local);
|
||||||
|
return SC_PROCESS_NONE;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
const char *const adb_cmd[] = {"push", local, remote};
|
||||||
|
sc_pid pid = adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd), flags);
|
||||||
|
|
||||||
|
#ifdef __WINDOWS__
|
||||||
|
free((void *) remote);
|
||||||
|
free((void *) local);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return process_check_success_intr(intr, pid, "adb push", flags);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
adb_install(struct sc_intr *intr, const char *serial, const char *local,
|
||||||
|
unsigned flags) {
|
||||||
|
#ifdef __WINDOWS__
|
||||||
|
// Windows will parse the string, so the local name must be quoted
|
||||||
|
// (see sys/win/command.c)
|
||||||
|
local = sc_str_quote(local);
|
||||||
|
if (!local) {
|
||||||
|
return SC_PROCESS_NONE;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
const char *const adb_cmd[] = {"install", "-r", local};
|
||||||
|
sc_pid pid = adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd), flags);
|
||||||
|
|
||||||
|
#ifdef __WINDOWS__
|
||||||
|
free((void *) local);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return process_check_success_intr(intr, pid, "adb install", flags);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
adb_tcpip(struct sc_intr *intr, const char *serial, uint16_t port,
|
||||||
|
unsigned flags) {
|
||||||
|
char port_string[5 + 1];
|
||||||
|
sprintf(port_string, "%" PRIu16, port);
|
||||||
|
const char *const adb_cmd[] = {"tcpip", port_string};
|
||||||
|
|
||||||
|
sc_pid pid = adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd), flags);
|
||||||
|
return process_check_success_intr(intr, pid, "adb tcpip", flags);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
adb_connect(struct sc_intr *intr, const char *ip_port, unsigned flags) {
|
||||||
|
const char *const adb_cmd[] = {"connect", ip_port};
|
||||||
|
|
||||||
|
sc_pipe pout;
|
||||||
|
sc_pid pid = adb_execute_p(NULL, adb_cmd, ARRAY_LEN(adb_cmd), flags, &pout);
|
||||||
|
if (pid == SC_PROCESS_NONE) {
|
||||||
|
LOGE("Could not execute \"adb connect\"");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// "adb connect" always returns successfully (with exit code 0), even in
|
||||||
|
// case of failure. As a workaround, check if its output starts with
|
||||||
|
// "connected".
|
||||||
|
char buf[128];
|
||||||
|
ssize_t r = sc_pipe_read_all_intr(intr, pid, pout, buf, sizeof(buf));
|
||||||
|
sc_pipe_close(pout);
|
||||||
|
|
||||||
|
bool ok = process_check_success_intr(intr, pid, "adb connect", flags);
|
||||||
|
if (!ok) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (r == -1) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
ok = !strncmp("connected", buf, sizeof("connected") - 1);
|
||||||
|
if (!ok && !(flags & SC_ADB_NO_STDERR)) {
|
||||||
|
// "adb connect" also prints errors to stdout. Since we capture it,
|
||||||
|
// re-print the error to stderr.
|
||||||
|
sc_str_truncate(buf, r, "\r\n");
|
||||||
|
fprintf(stderr, "%s\n", buf);
|
||||||
|
}
|
||||||
|
return ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
adb_disconnect(struct sc_intr *intr, const char *ip_port, unsigned flags) {
|
||||||
|
const char *const adb_cmd[] = {"disconnect", ip_port};
|
||||||
|
size_t len = ip_port ? ARRAY_LEN(adb_cmd)
|
||||||
|
: ARRAY_LEN(adb_cmd) - 1;
|
||||||
|
|
||||||
|
sc_pid pid = adb_execute(NULL, adb_cmd, len, flags);
|
||||||
|
return process_check_success_intr(intr, pid, "adb disconnect", flags);
|
||||||
|
}
|
||||||
|
|
||||||
|
char *
|
||||||
|
adb_getprop(struct sc_intr *intr, const char *serial, const char *prop,
|
||||||
|
unsigned flags) {
|
||||||
|
const char *const adb_cmd[] = {"shell", "getprop", prop};
|
||||||
|
|
||||||
|
sc_pipe pout;
|
||||||
|
sc_pid pid =
|
||||||
|
adb_execute_p(serial, adb_cmd, ARRAY_LEN(adb_cmd), flags, &pout);
|
||||||
|
if (pid == SC_PROCESS_NONE) {
|
||||||
|
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));
|
||||||
|
sc_pipe_close(pout);
|
||||||
|
|
||||||
|
bool ok = process_check_success_intr(intr, pid, "adb getprop", flags);
|
||||||
|
if (!ok) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (r == -1) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
sc_str_truncate(buf, r, " \r\n");
|
||||||
|
|
||||||
|
return strdup(buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
char *
|
||||||
|
adb_get_serialno(struct sc_intr *intr, unsigned flags) {
|
||||||
|
const char *const adb_cmd[] = {"get-serialno"};
|
||||||
|
|
||||||
|
sc_pipe pout;
|
||||||
|
sc_pid pid = adb_execute_p(NULL, adb_cmd, ARRAY_LEN(adb_cmd), 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));
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
sc_str_truncate(buf, r, " \r\n");
|
||||||
|
|
||||||
|
return strdup(buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
char *
|
||||||
|
adb_get_device_ip(struct sc_intr *intr, const char *serial, unsigned flags) {
|
||||||
|
const char *const cmd[] = {"shell", "ip", "route"};
|
||||||
|
|
||||||
|
sc_pipe pout;
|
||||||
|
sc_pid pid = adb_execute_p(serial, cmd, ARRAY_LEN(cmd), flags, &pout);
|
||||||
|
if (pid == SC_PROCESS_NONE) {
|
||||||
|
LOGD("Could not execute \"ip route\"");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
// "adb shell ip route" output should contain only a few lines
|
||||||
|
char buf[1024];
|
||||||
|
ssize_t r = sc_pipe_read_all_intr(intr, pid, pout, buf, sizeof(buf));
|
||||||
|
sc_pipe_close(pout);
|
||||||
|
|
||||||
|
bool ok = process_check_success_intr(intr, pid, "ip route", flags);
|
||||||
|
if (!ok) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (r == -1) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
assert((size_t) r <= sizeof(buf));
|
||||||
|
if (r == sizeof(buf) && buf[sizeof(buf) - 1] != '\0') {
|
||||||
|
// The implementation assumes that the output of "ip route" fits in the
|
||||||
|
// buffer in a single pass
|
||||||
|
LOGW("Result of \"ip route\" does not fit in 1Kb. "
|
||||||
|
"Please report an issue.\n");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
return sc_adb_parse_device_ip_from_output(buf, r);
|
||||||
|
}
|
||||||
94
app/src/adb.h
Normal file
94
app/src/adb.h
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
#ifndef SC_ADB_H
|
||||||
|
#define SC_ADB_H
|
||||||
|
|
||||||
|
#include "common.h"
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <inttypes.h>
|
||||||
|
|
||||||
|
#include "util/intr.h"
|
||||||
|
|
||||||
|
#define SC_ADB_NO_STDOUT (1 << 0)
|
||||||
|
#define SC_ADB_NO_STDERR (1 << 1)
|
||||||
|
#define SC_ADB_NO_LOGERR (1 << 2)
|
||||||
|
|
||||||
|
#define SC_ADB_SILENT (SC_ADB_NO_STDOUT | SC_ADB_NO_STDERR | SC_ADB_NO_LOGERR)
|
||||||
|
|
||||||
|
sc_pid
|
||||||
|
adb_execute(const char *serial, const char *const adb_cmd[], size_t len,
|
||||||
|
unsigned flags);
|
||||||
|
|
||||||
|
bool
|
||||||
|
adb_forward(struct sc_intr *intr, const char *serial, uint16_t local_port,
|
||||||
|
const char *device_socket_name, unsigned flags);
|
||||||
|
|
||||||
|
bool
|
||||||
|
adb_forward_remove(struct sc_intr *intr, const char *serial,
|
||||||
|
uint16_t local_port, unsigned flags);
|
||||||
|
|
||||||
|
bool
|
||||||
|
adb_reverse(struct sc_intr *intr, const char *serial,
|
||||||
|
const char *device_socket_name, uint16_t local_port,
|
||||||
|
unsigned flags);
|
||||||
|
|
||||||
|
bool
|
||||||
|
adb_reverse_remove(struct sc_intr *intr, const char *serial,
|
||||||
|
const char *device_socket_name, unsigned flags);
|
||||||
|
|
||||||
|
bool
|
||||||
|
adb_push(struct sc_intr *intr, const char *serial, const char *local,
|
||||||
|
const char *remote, unsigned flags);
|
||||||
|
|
||||||
|
bool
|
||||||
|
adb_install(struct sc_intr *intr, const char *serial, const char *local,
|
||||||
|
unsigned flags);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute `adb tcpip <port>`
|
||||||
|
*/
|
||||||
|
bool
|
||||||
|
adb_tcpip(struct sc_intr *intr, const char *serial, uint16_t port,
|
||||||
|
unsigned flags);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute `adb connect <ip_port>`
|
||||||
|
*
|
||||||
|
* `ip_port` may not be NULL.
|
||||||
|
*/
|
||||||
|
bool
|
||||||
|
adb_connect(struct sc_intr *intr, const char *ip_port, unsigned flags);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute `adb disconnect [<ip_port>]`
|
||||||
|
*
|
||||||
|
* If `ip_port` is NULL, execute `adb disconnect`.
|
||||||
|
* Otherwise, execute `adb disconnect <ip_port>`.
|
||||||
|
*/
|
||||||
|
bool
|
||||||
|
adb_disconnect(struct sc_intr *intr, const char *ip_port, unsigned flags);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute `adb getprop <prop>`
|
||||||
|
*/
|
||||||
|
char *
|
||||||
|
adb_getprop(struct sc_intr *intr, const char *serial, const char *prop,
|
||||||
|
unsigned flags);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute `adb get-serialno`
|
||||||
|
*
|
||||||
|
* Return the result, to be freed by the caller, or NULL on error.
|
||||||
|
*/
|
||||||
|
char *
|
||||||
|
adb_get_serialno(struct sc_intr *intr, unsigned flags);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attempt to retrieve the device IP
|
||||||
|
*
|
||||||
|
* Return the IP as a string of the form "xxx.xxx.xxx.xxx", to be freed by the
|
||||||
|
* caller, or NULL on error.
|
||||||
|
*/
|
||||||
|
char *
|
||||||
|
adb_get_device_ip(struct sc_intr *intr, const char *serial, unsigned flags);
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -1,693 +0,0 @@
|
|||||||
#include "adb.h"
|
|
||||||
|
|
||||||
#include <assert.h>
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <string.h>
|
|
||||||
|
|
||||||
#include "adb_parser.h"
|
|
||||||
#include "util/file.h"
|
|
||||||
#include "util/log.h"
|
|
||||||
#include "util/process_intr.h"
|
|
||||||
#include "util/str.h"
|
|
||||||
|
|
||||||
/* Convenience macro to expand:
|
|
||||||
*
|
|
||||||
* const char *const argv[] =
|
|
||||||
* SC_ADB_COMMAND("shell", "echo", "hello");
|
|
||||||
*
|
|
||||||
* to:
|
|
||||||
*
|
|
||||||
* const char *const argv[] =
|
|
||||||
* { sc_adb_get_executable(), "shell", "echo", "hello", NULL };
|
|
||||||
*/
|
|
||||||
#define SC_ADB_COMMAND(...) { sc_adb_get_executable(), __VA_ARGS__, NULL }
|
|
||||||
|
|
||||||
static const char *adb_executable;
|
|
||||||
|
|
||||||
const char *
|
|
||||||
sc_adb_get_executable(void) {
|
|
||||||
if (!adb_executable) {
|
|
||||||
adb_executable = getenv("ADB");
|
|
||||||
if (!adb_executable)
|
|
||||||
adb_executable = "adb";
|
|
||||||
}
|
|
||||||
return adb_executable;
|
|
||||||
}
|
|
||||||
|
|
||||||
// serialize argv to string "[arg1], [arg2], [arg3]"
|
|
||||||
static size_t
|
|
||||||
argv_to_string(const char *const *argv, char *buf, size_t bufsize) {
|
|
||||||
size_t idx = 0;
|
|
||||||
bool first = true;
|
|
||||||
while (*argv) {
|
|
||||||
const char *arg = *argv;
|
|
||||||
size_t len = strlen(arg);
|
|
||||||
// count space for "[], ...\0"
|
|
||||||
if (idx + len + 8 >= bufsize) {
|
|
||||||
// not enough space, truncate
|
|
||||||
assert(idx < bufsize - 4);
|
|
||||||
memcpy(&buf[idx], "...", 3);
|
|
||||||
idx += 3;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (first) {
|
|
||||||
first = false;
|
|
||||||
} else {
|
|
||||||
buf[idx++] = ',';
|
|
||||||
buf[idx++] = ' ';
|
|
||||||
}
|
|
||||||
buf[idx++] = '[';
|
|
||||||
memcpy(&buf[idx], arg, len);
|
|
||||||
idx += len;
|
|
||||||
buf[idx++] = ']';
|
|
||||||
argv++;
|
|
||||||
}
|
|
||||||
assert(idx < bufsize);
|
|
||||||
buf[idx] = '\0';
|
|
||||||
return idx;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
show_adb_installation_msg() {
|
|
||||||
#ifndef __WINDOWS__
|
|
||||||
static const struct {
|
|
||||||
const char *binary;
|
|
||||||
const char *command;
|
|
||||||
} pkg_managers[] = {
|
|
||||||
{"apt", "apt install adb"},
|
|
||||||
{"apt-get", "apt-get install adb"},
|
|
||||||
{"brew", "brew cask install android-platform-tools"},
|
|
||||||
{"dnf", "dnf install android-tools"},
|
|
||||||
{"emerge", "emerge dev-util/android-tools"},
|
|
||||||
{"pacman", "pacman -S android-tools"},
|
|
||||||
};
|
|
||||||
for (size_t i = 0; i < ARRAY_LEN(pkg_managers); ++i) {
|
|
||||||
if (sc_file_executable_exists(pkg_managers[i].binary)) {
|
|
||||||
LOGI("You may install 'adb' by \"%s\"", pkg_managers[i].command);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
show_adb_err_msg(enum sc_process_result err, const char *const argv[]) {
|
|
||||||
#define MAX_COMMAND_STRING_LEN 1024
|
|
||||||
char *buf = malloc(MAX_COMMAND_STRING_LEN);
|
|
||||||
if (!buf) {
|
|
||||||
LOG_OOM();
|
|
||||||
LOGE("Failed to execute");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (err) {
|
|
||||||
case SC_PROCESS_ERROR_GENERIC:
|
|
||||||
argv_to_string(argv, buf, MAX_COMMAND_STRING_LEN);
|
|
||||||
LOGE("Failed to execute: %s", buf);
|
|
||||||
break;
|
|
||||||
case SC_PROCESS_ERROR_MISSING_BINARY:
|
|
||||||
argv_to_string(argv, buf, MAX_COMMAND_STRING_LEN);
|
|
||||||
LOGE("Command not found: %s", buf);
|
|
||||||
LOGE("(make 'adb' accessible from your PATH or define its full"
|
|
||||||
"path in the ADB environment variable)");
|
|
||||||
show_adb_installation_msg();
|
|
||||||
break;
|
|
||||||
case SC_PROCESS_SUCCESS:
|
|
||||||
// do nothing
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
free(buf);
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool
|
|
||||||
process_check_success_internal(sc_pid pid, const char *name, bool close,
|
|
||||||
unsigned flags) {
|
|
||||||
bool log_errors = !(flags & SC_ADB_NO_LOGERR);
|
|
||||||
|
|
||||||
if (pid == SC_PROCESS_NONE) {
|
|
||||||
if (log_errors) {
|
|
||||||
LOGE("Could not execute \"%s\"", name);
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
sc_exit_code exit_code = sc_process_wait(pid, close);
|
|
||||||
if (exit_code) {
|
|
||||||
if (log_errors) {
|
|
||||||
if (exit_code != SC_EXIT_CODE_NONE) {
|
|
||||||
LOGE("\"%s\" returned with value %" SC_PRIexitcode, name,
|
|
||||||
exit_code);
|
|
||||||
} else {
|
|
||||||
LOGE("\"%s\" exited unexpectedly", name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool
|
|
||||||
process_check_success_intr(struct sc_intr *intr, sc_pid pid, const char *name,
|
|
||||||
unsigned flags) {
|
|
||||||
if (!sc_intr_set_process(intr, pid)) {
|
|
||||||
// Already interrupted
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Always pass close=false, interrupting would be racy otherwise
|
|
||||||
bool ret = process_check_success_internal(pid, name, false, flags);
|
|
||||||
|
|
||||||
sc_intr_set_process(intr, SC_PROCESS_NONE);
|
|
||||||
|
|
||||||
// Close separately
|
|
||||||
sc_process_close(pid);
|
|
||||||
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
static sc_pid
|
|
||||||
sc_adb_execute_p(const char *const argv[], unsigned flags, sc_pipe *pout) {
|
|
||||||
unsigned process_flags = 0;
|
|
||||||
if (flags & SC_ADB_NO_STDOUT) {
|
|
||||||
process_flags |= SC_PROCESS_NO_STDOUT;
|
|
||||||
}
|
|
||||||
if (flags & SC_ADB_NO_STDERR) {
|
|
||||||
process_flags |= SC_PROCESS_NO_STDERR;
|
|
||||||
}
|
|
||||||
|
|
||||||
sc_pid pid;
|
|
||||||
enum sc_process_result r =
|
|
||||||
sc_process_execute_p(argv, &pid, process_flags, NULL, pout, NULL);
|
|
||||||
if (r != SC_PROCESS_SUCCESS) {
|
|
||||||
// If the execution itself failed (not the command exit code), log the
|
|
||||||
// error in all cases
|
|
||||||
show_adb_err_msg(r, argv);
|
|
||||||
pid = SC_PROCESS_NONE;
|
|
||||||
}
|
|
||||||
|
|
||||||
return pid;
|
|
||||||
}
|
|
||||||
|
|
||||||
sc_pid
|
|
||||||
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) {
|
|
||||||
char local[4 + 5 + 1]; // tcp:PORT
|
|
||||||
char remote[108 + 14 + 1]; // localabstract:NAME
|
|
||||||
sprintf(local, "tcp:%" PRIu16, local_port);
|
|
||||||
snprintf(remote, sizeof(remote), "localabstract:%s", device_socket_name);
|
|
||||||
|
|
||||||
assert(serial);
|
|
||||||
const char *const argv[] =
|
|
||||||
SC_ADB_COMMAND("-s", serial, "forward", local, remote);
|
|
||||||
|
|
||||||
sc_pid pid = sc_adb_execute(argv, flags);
|
|
||||||
return process_check_success_intr(intr, pid, "adb forward", flags);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool
|
|
||||||
sc_adb_forward_remove(struct sc_intr *intr, const char *serial,
|
|
||||||
uint16_t local_port, unsigned flags) {
|
|
||||||
char local[4 + 5 + 1]; // tcp:PORT
|
|
||||||
sprintf(local, "tcp:%" PRIu16, local_port);
|
|
||||||
|
|
||||||
assert(serial);
|
|
||||||
const char *const argv[] =
|
|
||||||
SC_ADB_COMMAND("-s", serial, "forward", "--remove", local);
|
|
||||||
|
|
||||||
sc_pid pid = sc_adb_execute(argv, flags);
|
|
||||||
return process_check_success_intr(intr, pid, "adb forward --remove", flags);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool
|
|
||||||
sc_adb_reverse(struct sc_intr *intr, const char *serial,
|
|
||||||
const char *device_socket_name, uint16_t local_port,
|
|
||||||
unsigned flags) {
|
|
||||||
char local[4 + 5 + 1]; // tcp:PORT
|
|
||||||
char remote[108 + 14 + 1]; // localabstract:NAME
|
|
||||||
sprintf(local, "tcp:%" PRIu16, local_port);
|
|
||||||
snprintf(remote, sizeof(remote), "localabstract:%s", device_socket_name);
|
|
||||||
assert(serial);
|
|
||||||
const char *const argv[] =
|
|
||||||
SC_ADB_COMMAND("-s", serial, "reverse", remote, local);
|
|
||||||
|
|
||||||
sc_pid pid = sc_adb_execute(argv, flags);
|
|
||||||
return process_check_success_intr(intr, pid, "adb reverse", flags);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool
|
|
||||||
sc_adb_reverse_remove(struct sc_intr *intr, const char *serial,
|
|
||||||
const char *device_socket_name, unsigned flags) {
|
|
||||||
char remote[108 + 14 + 1]; // localabstract:NAME
|
|
||||||
snprintf(remote, sizeof(remote), "localabstract:%s", device_socket_name);
|
|
||||||
|
|
||||||
assert(serial);
|
|
||||||
const char *const argv[] =
|
|
||||||
SC_ADB_COMMAND("-s", serial, "reverse", "--remove", remote);
|
|
||||||
|
|
||||||
sc_pid pid = sc_adb_execute(argv, flags);
|
|
||||||
return process_check_success_intr(intr, pid, "adb reverse --remove", flags);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool
|
|
||||||
sc_adb_push(struct sc_intr *intr, const char *serial, const char *local,
|
|
||||||
const char *remote, unsigned flags) {
|
|
||||||
#ifdef __WINDOWS__
|
|
||||||
// Windows will parse the string, so the paths must be quoted
|
|
||||||
// (see sys/win/command.c)
|
|
||||||
local = sc_str_quote(local);
|
|
||||||
if (!local) {
|
|
||||||
return SC_PROCESS_NONE;
|
|
||||||
}
|
|
||||||
remote = sc_str_quote(remote);
|
|
||||||
if (!remote) {
|
|
||||||
free((void *) local);
|
|
||||||
return SC_PROCESS_NONE;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
assert(serial);
|
|
||||||
const char *const argv[] =
|
|
||||||
SC_ADB_COMMAND("-s", serial, "push", local, remote);
|
|
||||||
|
|
||||||
sc_pid pid = sc_adb_execute(argv, flags);
|
|
||||||
|
|
||||||
#ifdef __WINDOWS__
|
|
||||||
free((void *) remote);
|
|
||||||
free((void *) local);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
return process_check_success_intr(intr, pid, "adb push", flags);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool
|
|
||||||
sc_adb_install(struct sc_intr *intr, const char *serial, const char *local,
|
|
||||||
unsigned flags) {
|
|
||||||
#ifdef __WINDOWS__
|
|
||||||
// Windows will parse the string, so the local name must be quoted
|
|
||||||
// (see sys/win/command.c)
|
|
||||||
local = sc_str_quote(local);
|
|
||||||
if (!local) {
|
|
||||||
return SC_PROCESS_NONE;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
assert(serial);
|
|
||||||
const char *const argv[] =
|
|
||||||
SC_ADB_COMMAND("-s", serial, "install", "-r", local);
|
|
||||||
|
|
||||||
sc_pid pid = sc_adb_execute(argv, flags);
|
|
||||||
|
|
||||||
#ifdef __WINDOWS__
|
|
||||||
free((void *) local);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
return process_check_success_intr(intr, pid, "adb install", flags);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool
|
|
||||||
sc_adb_tcpip(struct sc_intr *intr, const char *serial, uint16_t port,
|
|
||||||
unsigned flags) {
|
|
||||||
char port_string[5 + 1];
|
|
||||||
sprintf(port_string, "%" PRIu16, port);
|
|
||||||
|
|
||||||
assert(serial);
|
|
||||||
const char *const argv[] =
|
|
||||||
SC_ADB_COMMAND("-s", serial, "tcpip", port_string);
|
|
||||||
|
|
||||||
sc_pid pid = sc_adb_execute(argv, flags);
|
|
||||||
return process_check_success_intr(intr, pid, "adb tcpip", flags);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool
|
|
||||||
sc_adb_connect(struct sc_intr *intr, const char *ip_port, unsigned flags) {
|
|
||||||
const char *const argv[] = SC_ADB_COMMAND("connect", ip_port);
|
|
||||||
|
|
||||||
sc_pipe pout;
|
|
||||||
sc_pid pid = sc_adb_execute_p(argv, flags, &pout);
|
|
||||||
if (pid == SC_PROCESS_NONE) {
|
|
||||||
LOGE("Could not execute \"adb connect\"");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// "adb connect" always returns successfully (with exit code 0), even in
|
|
||||||
// case of failure. As a workaround, check if its output starts with
|
|
||||||
// "connected".
|
|
||||||
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 connect", flags);
|
|
||||||
if (!ok) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (r == -1) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
assert((size_t) r < sizeof(buf));
|
|
||||||
buf[r] = '\0';
|
|
||||||
|
|
||||||
ok = !strncmp("connected", buf, sizeof("connected") - 1);
|
|
||||||
if (!ok && !(flags & SC_ADB_NO_STDERR)) {
|
|
||||||
// "adb connect" also prints errors to stdout. Since we capture it,
|
|
||||||
// re-print the error to stderr.
|
|
||||||
size_t len = strcspn(buf, "\r\n");
|
|
||||||
buf[len] = '\0';
|
|
||||||
fprintf(stderr, "%s\n", buf);
|
|
||||||
}
|
|
||||||
return ok;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool
|
|
||||||
sc_adb_disconnect(struct sc_intr *intr, const char *ip_port, unsigned flags) {
|
|
||||||
assert(ip_port);
|
|
||||||
const char *const argv[] = SC_ADB_COMMAND("disconnect", ip_port);
|
|
||||||
|
|
||||||
sc_pid pid = sc_adb_execute(argv, 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 *
|
|
||||||
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 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 getprop", 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 *
|
|
||||||
sc_adb_get_device_ip(struct sc_intr *intr, const char *serial, unsigned flags) {
|
|
||||||
assert(serial);
|
|
||||||
const char *const argv[] =
|
|
||||||
SC_ADB_COMMAND("-s", serial, "shell", "ip", "route");
|
|
||||||
|
|
||||||
sc_pipe pout;
|
|
||||||
sc_pid pid = sc_adb_execute_p(argv, flags, &pout);
|
|
||||||
if (pid == SC_PROCESS_NONE) {
|
|
||||||
LOGD("Could not execute \"ip route\"");
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
// "adb shell ip route" output should contain only a few lines
|
|
||||||
char buf[1024];
|
|
||||||
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, "ip route", flags);
|
|
||||||
if (!ok) {
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (r == -1) {
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
assert((size_t) r < sizeof(buf));
|
|
||||||
if (r == sizeof(buf) - 1) {
|
|
||||||
// The implementation assumes that the output of "ip route" fits in the
|
|
||||||
// buffer in a single pass
|
|
||||||
LOGW("Result of \"ip route\" does not fit in 1Kb. "
|
|
||||||
"Please report an issue.\n");
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
// It is parsed as a NUL-terminated string
|
|
||||||
buf[r] = '\0';
|
|
||||||
|
|
||||||
return sc_adb_parse_device_ip_from_output(buf);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool
|
|
||||||
sc_adb_is_serial_tcpip(const char *serial) {
|
|
||||||
return strchr(serial, ':');
|
|
||||||
}
|
|
||||||
@@ -1,123 +0,0 @@
|
|||||||
#ifndef SC_ADB_H
|
|
||||||
#define SC_ADB_H
|
|
||||||
|
|
||||||
#include "common.h"
|
|
||||||
|
|
||||||
#include <stdbool.h>
|
|
||||||
#include <inttypes.h>
|
|
||||||
|
|
||||||
#include "adb_device.h"
|
|
||||||
#include "util/intr.h"
|
|
||||||
|
|
||||||
#define SC_ADB_NO_STDOUT (1 << 0)
|
|
||||||
#define SC_ADB_NO_STDERR (1 << 1)
|
|
||||||
#define SC_ADB_NO_LOGERR (1 << 2)
|
|
||||||
|
|
||||||
#define SC_ADB_SILENT (SC_ADB_NO_STDOUT | SC_ADB_NO_STDERR | SC_ADB_NO_LOGERR)
|
|
||||||
|
|
||||||
const char *
|
|
||||||
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_adb_execute(const char *const argv[], unsigned flags);
|
|
||||||
|
|
||||||
bool
|
|
||||||
sc_adb_start_server(struct sc_intr *intr, unsigned flags);
|
|
||||||
|
|
||||||
bool
|
|
||||||
sc_adb_forward(struct sc_intr *intr, const char *serial, uint16_t local_port,
|
|
||||||
const char *device_socket_name, unsigned flags);
|
|
||||||
|
|
||||||
bool
|
|
||||||
sc_adb_forward_remove(struct sc_intr *intr, const char *serial,
|
|
||||||
uint16_t local_port, unsigned flags);
|
|
||||||
|
|
||||||
bool
|
|
||||||
sc_adb_reverse(struct sc_intr *intr, const char *serial,
|
|
||||||
const char *device_socket_name, uint16_t local_port,
|
|
||||||
unsigned flags);
|
|
||||||
|
|
||||||
bool
|
|
||||||
sc_adb_reverse_remove(struct sc_intr *intr, const char *serial,
|
|
||||||
const char *device_socket_name, unsigned flags);
|
|
||||||
|
|
||||||
bool
|
|
||||||
sc_adb_push(struct sc_intr *intr, const char *serial, const char *local,
|
|
||||||
const char *remote, unsigned flags);
|
|
||||||
|
|
||||||
bool
|
|
||||||
sc_adb_install(struct sc_intr *intr, const char *serial, const char *local,
|
|
||||||
unsigned flags);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Execute `adb tcpip <port>`
|
|
||||||
*/
|
|
||||||
bool
|
|
||||||
sc_adb_tcpip(struct sc_intr *intr, const char *serial, uint16_t port,
|
|
||||||
unsigned flags);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Execute `adb connect <ip_port>`
|
|
||||||
*
|
|
||||||
* `ip_port` may not be NULL.
|
|
||||||
*/
|
|
||||||
bool
|
|
||||||
sc_adb_connect(struct sc_intr *intr, const char *ip_port, unsigned flags);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Execute `adb disconnect [<ip_port>]`
|
|
||||||
*
|
|
||||||
* If `ip_port` is NULL, execute `adb disconnect`.
|
|
||||||
* Otherwise, execute `adb disconnect <ip_port>`.
|
|
||||||
*/
|
|
||||||
bool
|
|
||||||
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>`
|
|
||||||
*/
|
|
||||||
char *
|
|
||||||
sc_adb_getprop(struct sc_intr *intr, const char *serial, const char *prop,
|
|
||||||
unsigned flags);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Attempt to retrieve the device IP
|
|
||||||
*
|
|
||||||
* Return the IP as a string of the form "xxx.xxx.xxx.xxx", to be freed by the
|
|
||||||
* caller, or NULL on error.
|
|
||||||
*/
|
|
||||||
char *
|
|
||||||
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
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
#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]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
#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
|
|
||||||
@@ -1,231 +0,0 @@
|
|||||||
#include "adb_parser.h"
|
|
||||||
|
|
||||||
#include <assert.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <string.h>
|
|
||||||
|
|
||||||
#include "util/log.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 *
|
|
||||||
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_from_output(char *str) {
|
|
||||||
size_t idx_line = 0;
|
|
||||||
while (str[idx_line] != '\0') {
|
|
||||||
char *line = &str[idx_line];
|
|
||||||
size_t len = strcspn(line, "\n");
|
|
||||||
|
|
||||||
// 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
idx_line += len;
|
|
||||||
|
|
||||||
if (str[idx_line] != '\0') {
|
|
||||||
// The next line starts after the '\n'
|
|
||||||
++idx_line;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
#ifndef SC_ADB_PARSER_H
|
|
||||||
#define SC_ADB_PARSER_H
|
|
||||||
|
|
||||||
#include "common.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`
|
|
||||||
*
|
|
||||||
* The parameter must be a NUL-terminated string.
|
|
||||||
*
|
|
||||||
* Warning: this function modifies the buffer for optimization purposes.
|
|
||||||
*/
|
|
||||||
char *
|
|
||||||
sc_adb_parse_device_ip_from_output(char *str);
|
|
||||||
|
|
||||||
#endif
|
|
||||||
65
app/src/adb_parser.c
Normal file
65
app/src/adb_parser.c
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
#include "adb_parser.h"
|
||||||
|
|
||||||
|
#include <assert.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include "util/log.h"
|
||||||
|
#include "util/str.h"
|
||||||
|
|
||||||
|
static char *
|
||||||
|
sc_adb_parse_device_ip_from_line(char *line, size_t len) {
|
||||||
|
// One line from "ip route" looks lile:
|
||||||
|
// "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];
|
||||||
|
sc_str_truncate(dev_name, len - idx_dev_name + 1, " \t");
|
||||||
|
|
||||||
|
char *ip = &line[idx_ip];
|
||||||
|
sc_str_truncate(ip, len - idx_ip + 1, " \t");
|
||||||
|
|
||||||
|
// 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_from_output(char *buf, size_t buf_len) {
|
||||||
|
size_t idx_line = 0;
|
||||||
|
while (idx_line < buf_len && buf[idx_line] != '\0') {
|
||||||
|
char *line = &buf[idx_line];
|
||||||
|
size_t len = sc_str_truncate(line, buf_len - idx_line, "\n");
|
||||||
|
|
||||||
|
// The same, but without any trailing '\r'
|
||||||
|
size_t line_len = sc_str_remove_trailing_cr(line, len);
|
||||||
|
|
||||||
|
char *ip = sc_adb_parse_device_ip_from_line(line, line_len);
|
||||||
|
if (ip) {
|
||||||
|
// Found
|
||||||
|
return ip;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The next line starts after the '\n' (replaced by `\0`)
|
||||||
|
idx_line += len + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
14
app/src/adb_parser.h
Normal file
14
app/src/adb_parser.h
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
#ifndef SC_ADB_PARSER_H
|
||||||
|
#define SC_ADB_PARSER_H
|
||||||
|
|
||||||
|
#include "common.h"
|
||||||
|
|
||||||
|
#include "stddef.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse the ip from the output of `adb shell ip route`
|
||||||
|
*/
|
||||||
|
char *
|
||||||
|
sc_adb_parse_device_ip_from_output(char *buf, size_t buf_len);
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -20,7 +20,7 @@ enable_tunnel_reverse_any_port(struct sc_adb_tunnel *tunnel,
|
|||||||
struct sc_port_range port_range) {
|
struct sc_port_range port_range) {
|
||||||
uint16_t port = port_range.first;
|
uint16_t port = port_range.first;
|
||||||
for (;;) {
|
for (;;) {
|
||||||
if (!sc_adb_reverse(intr, serial, SC_SOCKET_NAME, port,
|
if (!adb_reverse(intr, serial, SC_SOCKET_NAME, port,
|
||||||
SC_ADB_NO_STDOUT)) {
|
SC_ADB_NO_STDOUT)) {
|
||||||
// the command itself failed, it will fail on any port
|
// the command itself failed, it will fail on any port
|
||||||
return false;
|
return false;
|
||||||
@@ -52,7 +52,7 @@ enable_tunnel_reverse_any_port(struct sc_adb_tunnel *tunnel,
|
|||||||
}
|
}
|
||||||
|
|
||||||
// failure, disable tunnel and try another port
|
// failure, disable tunnel and try another port
|
||||||
if (!sc_adb_reverse_remove(intr, serial, SC_SOCKET_NAME,
|
if (!adb_reverse_remove(intr, serial, SC_SOCKET_NAME,
|
||||||
SC_ADB_NO_STDOUT)) {
|
SC_ADB_NO_STDOUT)) {
|
||||||
LOGW("Could not remove reverse tunnel on port %" PRIu16, port);
|
LOGW("Could not remove reverse tunnel on port %" PRIu16, port);
|
||||||
}
|
}
|
||||||
@@ -83,8 +83,7 @@ enable_tunnel_forward_any_port(struct sc_adb_tunnel *tunnel,
|
|||||||
|
|
||||||
uint16_t port = port_range.first;
|
uint16_t port = port_range.first;
|
||||||
for (;;) {
|
for (;;) {
|
||||||
if (sc_adb_forward(intr, serial, port, SC_SOCKET_NAME,
|
if (adb_forward(intr, serial, port, SC_SOCKET_NAME, SC_ADB_NO_STDOUT)) {
|
||||||
SC_ADB_NO_STDOUT)) {
|
|
||||||
// success
|
// success
|
||||||
tunnel->local_port = port;
|
tunnel->local_port = port;
|
||||||
tunnel->enabled = true;
|
tunnel->enabled = true;
|
||||||
@@ -149,10 +148,10 @@ sc_adb_tunnel_close(struct sc_adb_tunnel *tunnel, struct sc_intr *intr,
|
|||||||
|
|
||||||
bool ret;
|
bool ret;
|
||||||
if (tunnel->forward) {
|
if (tunnel->forward) {
|
||||||
ret = sc_adb_forward_remove(intr, serial, tunnel->local_port,
|
ret = adb_forward_remove(intr, serial, tunnel->local_port,
|
||||||
SC_ADB_NO_STDOUT);
|
SC_ADB_NO_STDOUT);
|
||||||
} else {
|
} else {
|
||||||
ret = sc_adb_reverse_remove(intr, serial, SC_SOCKET_NAME,
|
ret = adb_reverse_remove(intr, serial, SC_SOCKET_NAME,
|
||||||
SC_ADB_NO_STDOUT);
|
SC_ADB_NO_STDOUT);
|
||||||
|
|
||||||
assert(tunnel->server_socket != SC_SOCKET_NONE);
|
assert(tunnel->server_socket != SC_SOCKET_NONE);
|
||||||
@@ -288,7 +288,7 @@ static const struct sc_option options[] = {
|
|||||||
"LAlt, LSuper or RSuper toggle the mouse capture mode, to give "
|
"LAlt, LSuper or RSuper toggle the mouse capture mode, to give "
|
||||||
"control of the mouse back to the computer.\n"
|
"control of the mouse back to the computer.\n"
|
||||||
"If any of --hid-keyboard or --hid-mouse is set, only enable "
|
"If any of --hid-keyboard or --hid-mouse is set, only enable "
|
||||||
"keyboard or mouse respectively, otherwise enable both.\n"
|
"keyboard or mouse respectively, otherwise enable both."
|
||||||
"It may only work over USB, and is currently only supported "
|
"It may only work over USB, and is currently only supported "
|
||||||
"on Linux.\n"
|
"on Linux.\n"
|
||||||
"See --hid-keyboard and --hid-mouse.",
|
"See --hid-keyboard and --hid-mouse.",
|
||||||
@@ -416,16 +416,6 @@ 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",
|
||||||
@@ -1411,12 +1401,6 @@ 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;
|
||||||
@@ -1575,16 +1559,8 @@ 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);
|
||||||
|
|
||||||
unsigned selectors = !!opts->serial
|
if (opts->serial && opts->tcpip_dst) {
|
||||||
+ !!opts->tcpip_dst
|
LOGE("Incompatible options: -s/--serial and --tcpip with an argument");
|
||||||
+ 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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,10 +8,8 @@
|
|||||||
|
|
||||||
#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:
|
||||||
|
|||||||
@@ -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) {
|
||||||
LOGE("Could not start controller thread");
|
LOGC("Could not start controller thread");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,10 +9,10 @@
|
|||||||
#include "util/log.h"
|
#include "util/log.h"
|
||||||
|
|
||||||
/** Downcast packet_sink to decoder */
|
/** Downcast packet_sink to decoder */
|
||||||
#define DOWNCAST(SINK) container_of(SINK, struct sc_decoder, packet_sink)
|
#define DOWNCAST(SINK) container_of(SINK, struct decoder, packet_sink)
|
||||||
|
|
||||||
static void
|
static void
|
||||||
sc_decoder_close_first_sinks(struct sc_decoder *decoder, unsigned count) {
|
decoder_close_first_sinks(struct decoder *decoder, unsigned count) {
|
||||||
while (count) {
|
while (count) {
|
||||||
struct sc_frame_sink *sink = decoder->sinks[--count];
|
struct sc_frame_sink *sink = decoder->sinks[--count];
|
||||||
sink->ops->close(sink);
|
sink->ops->close(sink);
|
||||||
@@ -20,17 +20,17 @@ sc_decoder_close_first_sinks(struct sc_decoder *decoder, unsigned count) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static inline void
|
static inline void
|
||||||
sc_decoder_close_sinks(struct sc_decoder *decoder) {
|
decoder_close_sinks(struct decoder *decoder) {
|
||||||
sc_decoder_close_first_sinks(decoder, decoder->sink_count);
|
decoder_close_first_sinks(decoder, decoder->sink_count);
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
sc_decoder_open_sinks(struct sc_decoder *decoder) {
|
decoder_open_sinks(struct decoder *decoder) {
|
||||||
for (unsigned i = 0; i < decoder->sink_count; ++i) {
|
for (unsigned i = 0; i < decoder->sink_count; ++i) {
|
||||||
struct sc_frame_sink *sink = decoder->sinks[i];
|
struct sc_frame_sink *sink = decoder->sinks[i];
|
||||||
if (!sink->ops->open(sink)) {
|
if (!sink->ops->open(sink)) {
|
||||||
LOGE("Could not open frame sink %d", i);
|
LOGE("Could not open frame sink %d", i);
|
||||||
sc_decoder_close_first_sinks(decoder, i);
|
decoder_close_first_sinks(decoder, i);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -39,7 +39,7 @@ sc_decoder_open_sinks(struct sc_decoder *decoder) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
sc_decoder_open(struct sc_decoder *decoder, const AVCodec *codec) {
|
decoder_open(struct decoder *decoder, const AVCodec *codec) {
|
||||||
decoder->codec_ctx = avcodec_alloc_context3(codec);
|
decoder->codec_ctx = avcodec_alloc_context3(codec);
|
||||||
if (!decoder->codec_ctx) {
|
if (!decoder->codec_ctx) {
|
||||||
LOG_OOM();
|
LOG_OOM();
|
||||||
@@ -62,7 +62,7 @@ sc_decoder_open(struct sc_decoder *decoder, const AVCodec *codec) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!sc_decoder_open_sinks(decoder)) {
|
if (!decoder_open_sinks(decoder)) {
|
||||||
LOGE("Could not open decoder sinks");
|
LOGE("Could not open decoder sinks");
|
||||||
av_frame_free(&decoder->frame);
|
av_frame_free(&decoder->frame);
|
||||||
avcodec_close(decoder->codec_ctx);
|
avcodec_close(decoder->codec_ctx);
|
||||||
@@ -74,15 +74,15 @@ sc_decoder_open(struct sc_decoder *decoder, const AVCodec *codec) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
sc_decoder_close(struct sc_decoder *decoder) {
|
decoder_close(struct decoder *decoder) {
|
||||||
sc_decoder_close_sinks(decoder);
|
decoder_close_sinks(decoder);
|
||||||
av_frame_free(&decoder->frame);
|
av_frame_free(&decoder->frame);
|
||||||
avcodec_close(decoder->codec_ctx);
|
avcodec_close(decoder->codec_ctx);
|
||||||
avcodec_free_context(&decoder->codec_ctx);
|
avcodec_free_context(&decoder->codec_ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
push_frame_to_sinks(struct sc_decoder *decoder, const AVFrame *frame) {
|
push_frame_to_sinks(struct decoder *decoder, const AVFrame *frame) {
|
||||||
for (unsigned i = 0; i < decoder->sink_count; ++i) {
|
for (unsigned i = 0; i < decoder->sink_count; ++i) {
|
||||||
struct sc_frame_sink *sink = decoder->sinks[i];
|
struct sc_frame_sink *sink = decoder->sinks[i];
|
||||||
if (!sink->ops->push(sink, frame)) {
|
if (!sink->ops->push(sink, frame)) {
|
||||||
@@ -95,7 +95,7 @@ push_frame_to_sinks(struct sc_decoder *decoder, const AVFrame *frame) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
sc_decoder_push(struct sc_decoder *decoder, const AVPacket *packet) {
|
decoder_push(struct decoder *decoder, const AVPacket *packet) {
|
||||||
bool is_config = packet->pts == AV_NOPTS_VALUE;
|
bool is_config = packet->pts == AV_NOPTS_VALUE;
|
||||||
if (is_config) {
|
if (is_config) {
|
||||||
// nothing to do
|
// nothing to do
|
||||||
@@ -124,40 +124,39 @@ sc_decoder_push(struct sc_decoder *decoder, const AVPacket *packet) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
sc_decoder_packet_sink_open(struct sc_packet_sink *sink, const AVCodec *codec) {
|
decoder_packet_sink_open(struct sc_packet_sink *sink, const AVCodec *codec) {
|
||||||
struct sc_decoder *decoder = DOWNCAST(sink);
|
struct decoder *decoder = DOWNCAST(sink);
|
||||||
return sc_decoder_open(decoder, codec);
|
return decoder_open(decoder, codec);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
sc_decoder_packet_sink_close(struct sc_packet_sink *sink) {
|
decoder_packet_sink_close(struct sc_packet_sink *sink) {
|
||||||
struct sc_decoder *decoder = DOWNCAST(sink);
|
struct decoder *decoder = DOWNCAST(sink);
|
||||||
sc_decoder_close(decoder);
|
decoder_close(decoder);
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
sc_decoder_packet_sink_push(struct sc_packet_sink *sink,
|
decoder_packet_sink_push(struct sc_packet_sink *sink, const AVPacket *packet) {
|
||||||
const AVPacket *packet) {
|
struct decoder *decoder = DOWNCAST(sink);
|
||||||
struct sc_decoder *decoder = DOWNCAST(sink);
|
return decoder_push(decoder, packet);
|
||||||
return sc_decoder_push(decoder, packet);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
sc_decoder_init(struct sc_decoder *decoder) {
|
decoder_init(struct decoder *decoder) {
|
||||||
decoder->sink_count = 0;
|
decoder->sink_count = 0;
|
||||||
|
|
||||||
static const struct sc_packet_sink_ops ops = {
|
static const struct sc_packet_sink_ops ops = {
|
||||||
.open = sc_decoder_packet_sink_open,
|
.open = decoder_packet_sink_open,
|
||||||
.close = sc_decoder_packet_sink_close,
|
.close = decoder_packet_sink_close,
|
||||||
.push = sc_decoder_packet_sink_push,
|
.push = decoder_packet_sink_push,
|
||||||
};
|
};
|
||||||
|
|
||||||
decoder->packet_sink.ops = &ops;
|
decoder->packet_sink.ops = &ops;
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
sc_decoder_add_sink(struct sc_decoder *decoder, struct sc_frame_sink *sink) {
|
decoder_add_sink(struct decoder *decoder, struct sc_frame_sink *sink) {
|
||||||
assert(decoder->sink_count < SC_DECODER_MAX_SINKS);
|
assert(decoder->sink_count < DECODER_MAX_SINKS);
|
||||||
assert(sink);
|
assert(sink);
|
||||||
assert(sink->ops);
|
assert(sink->ops);
|
||||||
decoder->sinks[decoder->sink_count++] = sink;
|
decoder->sinks[decoder->sink_count++] = sink;
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
#ifndef SC_DECODER_H
|
#ifndef DECODER_H
|
||||||
#define SC_DECODER_H
|
#define DECODER_H
|
||||||
|
|
||||||
#include "common.h"
|
#include "common.h"
|
||||||
|
|
||||||
@@ -9,12 +9,12 @@
|
|||||||
#include <libavcodec/avcodec.h>
|
#include <libavcodec/avcodec.h>
|
||||||
#include <libavformat/avformat.h>
|
#include <libavformat/avformat.h>
|
||||||
|
|
||||||
#define SC_DECODER_MAX_SINKS 2
|
#define DECODER_MAX_SINKS 2
|
||||||
|
|
||||||
struct sc_decoder {
|
struct decoder {
|
||||||
struct sc_packet_sink packet_sink; // packet sink trait
|
struct sc_packet_sink packet_sink; // packet sink trait
|
||||||
|
|
||||||
struct sc_frame_sink *sinks[SC_DECODER_MAX_SINKS];
|
struct sc_frame_sink *sinks[DECODER_MAX_SINKS];
|
||||||
unsigned sink_count;
|
unsigned sink_count;
|
||||||
|
|
||||||
AVCodecContext *codec_ctx;
|
AVCodecContext *codec_ctx;
|
||||||
@@ -22,9 +22,9 @@ struct sc_decoder {
|
|||||||
};
|
};
|
||||||
|
|
||||||
void
|
void
|
||||||
sc_decoder_init(struct sc_decoder *decoder);
|
decoder_init(struct decoder *decoder);
|
||||||
|
|
||||||
void
|
void
|
||||||
sc_decoder_add_sink(struct sc_decoder *decoder, struct sc_frame_sink *sink);
|
decoder_add_sink(struct decoder *decoder, struct sc_frame_sink *sink);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -1,51 +0,0 @@
|
|||||||
#ifndef SC_DEMUXER_H
|
|
||||||
#define SC_DEMUXER_H
|
|
||||||
|
|
||||||
#include "common.h"
|
|
||||||
|
|
||||||
#include <stdbool.h>
|
|
||||||
#include <stdint.h>
|
|
||||||
#include <libavcodec/avcodec.h>
|
|
||||||
#include <libavformat/avformat.h>
|
|
||||||
|
|
||||||
#include "trait/packet_sink.h"
|
|
||||||
#include "util/net.h"
|
|
||||||
#include "util/thread.h"
|
|
||||||
|
|
||||||
#define SC_DEMUXER_MAX_SINKS 2
|
|
||||||
|
|
||||||
struct sc_demuxer {
|
|
||||||
sc_socket socket;
|
|
||||||
sc_thread thread;
|
|
||||||
|
|
||||||
struct sc_packet_sink *sinks[SC_DEMUXER_MAX_SINKS];
|
|
||||||
unsigned sink_count;
|
|
||||||
|
|
||||||
AVCodecContext *codec_ctx;
|
|
||||||
AVCodecParserContext *parser;
|
|
||||||
// successive packets may need to be concatenated, until a non-config
|
|
||||||
// packet is available
|
|
||||||
AVPacket *pending;
|
|
||||||
|
|
||||||
const struct sc_demuxer_callbacks *cbs;
|
|
||||||
void *cbs_userdata;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct sc_demuxer_callbacks {
|
|
||||||
void (*on_eos)(struct sc_demuxer *demuxer, void *userdata);
|
|
||||||
};
|
|
||||||
|
|
||||||
void
|
|
||||||
sc_demuxer_init(struct sc_demuxer *demuxer, sc_socket socket,
|
|
||||||
const struct sc_demuxer_callbacks *cbs, void *cbs_userdata);
|
|
||||||
|
|
||||||
void
|
|
||||||
sc_demuxer_add_sink(struct sc_demuxer *demuxer, struct sc_packet_sink *sink);
|
|
||||||
|
|
||||||
bool
|
|
||||||
sc_demuxer_start(struct sc_demuxer *demuxer);
|
|
||||||
|
|
||||||
void
|
|
||||||
sc_demuxer_join(struct sc_demuxer *demuxer);
|
|
||||||
|
|
||||||
#endif
|
|
||||||
@@ -3,7 +3,7 @@
|
|||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
#include "adb/adb.h"
|
#include "adb.h"
|
||||||
#include "util/log.h"
|
#include "util/log.h"
|
||||||
#include "util/process_intr.h"
|
#include "util/process_intr.h"
|
||||||
|
|
||||||
@@ -129,7 +129,7 @@ run_file_pusher(void *data) {
|
|||||||
|
|
||||||
if (req.action == SC_FILE_PUSHER_ACTION_INSTALL_APK) {
|
if (req.action == SC_FILE_PUSHER_ACTION_INSTALL_APK) {
|
||||||
LOGI("Installing %s...", req.file);
|
LOGI("Installing %s...", req.file);
|
||||||
bool ok = sc_adb_install(intr, serial, req.file, 0);
|
bool ok = adb_install(intr, serial, req.file, 0);
|
||||||
if (ok) {
|
if (ok) {
|
||||||
LOGI("%s successfully installed", req.file);
|
LOGI("%s successfully installed", req.file);
|
||||||
} else {
|
} else {
|
||||||
@@ -137,7 +137,7 @@ run_file_pusher(void *data) {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
LOGI("Pushing %s...", req.file);
|
LOGI("Pushing %s...", req.file);
|
||||||
bool ok = sc_adb_push(intr, serial, req.file, push_target, 0);
|
bool ok = adb_push(intr, serial, req.file, push_target, 0);
|
||||||
if (ok) {
|
if (ok) {
|
||||||
LOGI("%s successfully pushed to %s", req.file, push_target);
|
LOGI("%s successfully pushed to %s", req.file, push_target);
|
||||||
} else {
|
} else {
|
||||||
@@ -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) {
|
||||||
LOGE("Could not start file_pusher thread");
|
LOGC("Could not start file_pusher thread");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
|
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
|
|
||||||
|
#include "adb.h"
|
||||||
#include "util/cbuf.h"
|
#include "util/cbuf.h"
|
||||||
#include "util/thread.h"
|
#include "util/thread.h"
|
||||||
#include "util/intr.h"
|
#include "util/intr.h"
|
||||||
|
|||||||
@@ -60,6 +60,4 @@ 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,
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -135,8 +135,6 @@ 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;
|
||||||
|
|||||||
@@ -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) {
|
||||||
LOGE("Could not start receiver thread");
|
LOGC("Could not start receiver thread");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
#include "util/str.h"
|
#include "util/str.h"
|
||||||
|
|
||||||
/** Downcast packet_sink to recorder */
|
/** Downcast packet_sink to recorder */
|
||||||
#define DOWNCAST(SINK) container_of(SINK, struct sc_recorder, packet_sink)
|
#define DOWNCAST(SINK) container_of(SINK, struct recorder, packet_sink)
|
||||||
|
|
||||||
static const AVRational SCRCPY_TIME_BASE = {1, 1000000}; // timestamps in us
|
static const AVRational SCRCPY_TIME_BASE = {1, 1000000}; // timestamps in us
|
||||||
|
|
||||||
@@ -30,9 +30,9 @@ find_muxer(const char *name) {
|
|||||||
return oformat;
|
return oformat;
|
||||||
}
|
}
|
||||||
|
|
||||||
static struct sc_record_packet *
|
static struct record_packet *
|
||||||
sc_record_packet_new(const AVPacket *packet) {
|
record_packet_new(const AVPacket *packet) {
|
||||||
struct sc_record_packet *rec = malloc(sizeof(*rec));
|
struct record_packet *rec = malloc(sizeof(*rec));
|
||||||
if (!rec) {
|
if (!rec) {
|
||||||
LOG_OOM();
|
LOG_OOM();
|
||||||
return NULL;
|
return NULL;
|
||||||
@@ -54,22 +54,22 @@ sc_record_packet_new(const AVPacket *packet) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
sc_record_packet_delete(struct sc_record_packet *rec) {
|
record_packet_delete(struct record_packet *rec) {
|
||||||
av_packet_free(&rec->packet);
|
av_packet_free(&rec->packet);
|
||||||
free(rec);
|
free(rec);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
sc_recorder_queue_clear(struct sc_recorder_queue *queue) {
|
recorder_queue_clear(struct recorder_queue *queue) {
|
||||||
while (!sc_queue_is_empty(queue)) {
|
while (!sc_queue_is_empty(queue)) {
|
||||||
struct sc_record_packet *rec;
|
struct record_packet *rec;
|
||||||
sc_queue_take(queue, next, &rec);
|
sc_queue_take(queue, next, &rec);
|
||||||
sc_record_packet_delete(rec);
|
record_packet_delete(rec);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static const char *
|
static const char *
|
||||||
sc_recorder_get_format_name(enum sc_record_format format) {
|
recorder_get_format_name(enum sc_record_format format) {
|
||||||
switch (format) {
|
switch (format) {
|
||||||
case SC_RECORD_FORMAT_MP4: return "mp4";
|
case SC_RECORD_FORMAT_MP4: return "mp4";
|
||||||
case SC_RECORD_FORMAT_MKV: return "matroska";
|
case SC_RECORD_FORMAT_MKV: return "matroska";
|
||||||
@@ -78,7 +78,7 @@ sc_recorder_get_format_name(enum sc_record_format format) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
sc_recorder_write_header(struct sc_recorder *recorder, const AVPacket *packet) {
|
recorder_write_header(struct recorder *recorder, const AVPacket *packet) {
|
||||||
AVStream *ostream = recorder->ctx->streams[0];
|
AVStream *ostream = recorder->ctx->streams[0];
|
||||||
|
|
||||||
uint8_t *extradata = av_malloc(packet->size * sizeof(uint8_t));
|
uint8_t *extradata = av_malloc(packet->size * sizeof(uint8_t));
|
||||||
@@ -103,19 +103,19 @@ sc_recorder_write_header(struct sc_recorder *recorder, const AVPacket *packet) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
sc_recorder_rescale_packet(struct sc_recorder *recorder, AVPacket *packet) {
|
recorder_rescale_packet(struct recorder *recorder, AVPacket *packet) {
|
||||||
AVStream *ostream = recorder->ctx->streams[0];
|
AVStream *ostream = recorder->ctx->streams[0];
|
||||||
av_packet_rescale_ts(packet, SCRCPY_TIME_BASE, ostream->time_base);
|
av_packet_rescale_ts(packet, SCRCPY_TIME_BASE, ostream->time_base);
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
sc_recorder_write(struct sc_recorder *recorder, AVPacket *packet) {
|
recorder_write(struct recorder *recorder, AVPacket *packet) {
|
||||||
if (!recorder->header_written) {
|
if (!recorder->header_written) {
|
||||||
if (packet->pts != AV_NOPTS_VALUE) {
|
if (packet->pts != AV_NOPTS_VALUE) {
|
||||||
LOGE("The first packet is not a config packet");
|
LOGE("The first packet is not a config packet");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
bool ok = sc_recorder_write_header(recorder, packet);
|
bool ok = recorder_write_header(recorder, packet);
|
||||||
if (!ok) {
|
if (!ok) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -128,13 +128,13 @@ sc_recorder_write(struct sc_recorder *recorder, AVPacket *packet) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
sc_recorder_rescale_packet(recorder, packet);
|
recorder_rescale_packet(recorder, packet);
|
||||||
return av_write_frame(recorder->ctx, packet) >= 0;
|
return av_write_frame(recorder->ctx, packet) >= 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
run_recorder(void *data) {
|
run_recorder(void *data) {
|
||||||
struct sc_recorder *recorder = data;
|
struct recorder *recorder = data;
|
||||||
|
|
||||||
for (;;) {
|
for (;;) {
|
||||||
sc_mutex_lock(&recorder->mutex);
|
sc_mutex_lock(&recorder->mutex);
|
||||||
@@ -148,29 +148,29 @@ run_recorder(void *data) {
|
|||||||
|
|
||||||
if (recorder->stopped && sc_queue_is_empty(&recorder->queue)) {
|
if (recorder->stopped && sc_queue_is_empty(&recorder->queue)) {
|
||||||
sc_mutex_unlock(&recorder->mutex);
|
sc_mutex_unlock(&recorder->mutex);
|
||||||
struct sc_record_packet *last = recorder->previous;
|
struct record_packet *last = recorder->previous;
|
||||||
if (last) {
|
if (last) {
|
||||||
// assign an arbitrary duration to the last packet
|
// assign an arbitrary duration to the last packet
|
||||||
last->packet->duration = 100000;
|
last->packet->duration = 100000;
|
||||||
bool ok = sc_recorder_write(recorder, last->packet);
|
bool ok = recorder_write(recorder, last->packet);
|
||||||
if (!ok) {
|
if (!ok) {
|
||||||
// failing to write the last frame is not very serious, no
|
// failing to write the last frame is not very serious, no
|
||||||
// future frame may depend on it, so the resulting file
|
// future frame may depend on it, so the resulting file
|
||||||
// will still be valid
|
// will still be valid
|
||||||
LOGW("Could not record last packet");
|
LOGW("Could not record last packet");
|
||||||
}
|
}
|
||||||
sc_record_packet_delete(last);
|
record_packet_delete(last);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct sc_record_packet *rec;
|
struct record_packet *rec;
|
||||||
sc_queue_take(&recorder->queue, next, &rec);
|
sc_queue_take(&recorder->queue, next, &rec);
|
||||||
|
|
||||||
sc_mutex_unlock(&recorder->mutex);
|
sc_mutex_unlock(&recorder->mutex);
|
||||||
|
|
||||||
// recorder->previous is only written from this thread, no need to lock
|
// recorder->previous is only written from this thread, no need to lock
|
||||||
struct sc_record_packet *previous = recorder->previous;
|
struct record_packet *previous = recorder->previous;
|
||||||
recorder->previous = rec;
|
recorder->previous = rec;
|
||||||
|
|
||||||
if (!previous) {
|
if (!previous) {
|
||||||
@@ -186,15 +186,15 @@ run_recorder(void *data) {
|
|||||||
rec->packet->pts - previous->packet->pts;
|
rec->packet->pts - previous->packet->pts;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ok = sc_recorder_write(recorder, previous->packet);
|
bool ok = recorder_write(recorder, previous->packet);
|
||||||
sc_record_packet_delete(previous);
|
record_packet_delete(previous);
|
||||||
if (!ok) {
|
if (!ok) {
|
||||||
LOGE("Could not record packet");
|
LOGE("Could not record packet");
|
||||||
|
|
||||||
sc_mutex_lock(&recorder->mutex);
|
sc_mutex_lock(&recorder->mutex);
|
||||||
recorder->failed = true;
|
recorder->failed = true;
|
||||||
// discard pending packets
|
// discard pending packets
|
||||||
sc_recorder_queue_clear(&recorder->queue);
|
recorder_queue_clear(&recorder->queue);
|
||||||
sc_mutex_unlock(&recorder->mutex);
|
sc_mutex_unlock(&recorder->mutex);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -216,7 +216,7 @@ run_recorder(void *data) {
|
|||||||
if (recorder->failed) {
|
if (recorder->failed) {
|
||||||
LOGE("Recording failed to %s", recorder->filename);
|
LOGE("Recording failed to %s", recorder->filename);
|
||||||
} else {
|
} else {
|
||||||
const char *format_name = sc_recorder_get_format_name(recorder->format);
|
const char *format_name = recorder_get_format_name(recorder->format);
|
||||||
LOGI("Recording complete to %s file: %s", format_name,
|
LOGI("Recording complete to %s file: %s", format_name,
|
||||||
recorder->filename);
|
recorder->filename);
|
||||||
}
|
}
|
||||||
@@ -227,7 +227,7 @@ run_recorder(void *data) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
sc_recorder_open(struct sc_recorder *recorder, const AVCodec *input_codec) {
|
recorder_open(struct recorder *recorder, const AVCodec *input_codec) {
|
||||||
bool ok = sc_mutex_init(&recorder->mutex);
|
bool ok = sc_mutex_init(&recorder->mutex);
|
||||||
if (!ok) {
|
if (!ok) {
|
||||||
return false;
|
return false;
|
||||||
@@ -244,7 +244,7 @@ sc_recorder_open(struct sc_recorder *recorder, const AVCodec *input_codec) {
|
|||||||
recorder->header_written = false;
|
recorder->header_written = false;
|
||||||
recorder->previous = NULL;
|
recorder->previous = NULL;
|
||||||
|
|
||||||
const char *format_name = sc_recorder_get_format_name(recorder->format);
|
const char *format_name = recorder_get_format_name(recorder->format);
|
||||||
assert(format_name);
|
assert(format_name);
|
||||||
const AVOutputFormat *format = find_muxer(format_name);
|
const AVOutputFormat *format = find_muxer(format_name);
|
||||||
if (!format) {
|
if (!format) {
|
||||||
@@ -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) {
|
||||||
LOGE("Could not start recorder thread");
|
LOGC("Could not start recorder thread");
|
||||||
goto error_avio_close;
|
goto error_avio_close;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -311,7 +311,7 @@ error_mutex_destroy:
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
sc_recorder_close(struct sc_recorder *recorder) {
|
recorder_close(struct recorder *recorder) {
|
||||||
sc_mutex_lock(&recorder->mutex);
|
sc_mutex_lock(&recorder->mutex);
|
||||||
recorder->stopped = true;
|
recorder->stopped = true;
|
||||||
sc_cond_signal(&recorder->queue_cond);
|
sc_cond_signal(&recorder->queue_cond);
|
||||||
@@ -326,7 +326,7 @@ sc_recorder_close(struct sc_recorder *recorder) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
sc_recorder_push(struct sc_recorder *recorder, const AVPacket *packet) {
|
recorder_push(struct recorder *recorder, const AVPacket *packet) {
|
||||||
sc_mutex_lock(&recorder->mutex);
|
sc_mutex_lock(&recorder->mutex);
|
||||||
assert(!recorder->stopped);
|
assert(!recorder->stopped);
|
||||||
|
|
||||||
@@ -336,7 +336,7 @@ sc_recorder_push(struct sc_recorder *recorder, const AVPacket *packet) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct sc_record_packet *rec = sc_record_packet_new(packet);
|
struct record_packet *rec = record_packet_new(packet);
|
||||||
if (!rec) {
|
if (!rec) {
|
||||||
LOG_OOM();
|
LOG_OOM();
|
||||||
sc_mutex_unlock(&recorder->mutex);
|
sc_mutex_unlock(&recorder->mutex);
|
||||||
@@ -351,27 +351,25 @@ sc_recorder_push(struct sc_recorder *recorder, const AVPacket *packet) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
sc_recorder_packet_sink_open(struct sc_packet_sink *sink,
|
recorder_packet_sink_open(struct sc_packet_sink *sink, const AVCodec *codec) {
|
||||||
const AVCodec *codec) {
|
struct recorder *recorder = DOWNCAST(sink);
|
||||||
struct sc_recorder *recorder = DOWNCAST(sink);
|
return recorder_open(recorder, codec);
|
||||||
return sc_recorder_open(recorder, codec);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
sc_recorder_packet_sink_close(struct sc_packet_sink *sink) {
|
recorder_packet_sink_close(struct sc_packet_sink *sink) {
|
||||||
struct sc_recorder *recorder = DOWNCAST(sink);
|
struct recorder *recorder = DOWNCAST(sink);
|
||||||
sc_recorder_close(recorder);
|
recorder_close(recorder);
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
sc_recorder_packet_sink_push(struct sc_packet_sink *sink,
|
recorder_packet_sink_push(struct sc_packet_sink *sink, const AVPacket *packet) {
|
||||||
const AVPacket *packet) {
|
struct recorder *recorder = DOWNCAST(sink);
|
||||||
struct sc_recorder *recorder = DOWNCAST(sink);
|
return recorder_push(recorder, packet);
|
||||||
return sc_recorder_push(recorder, packet);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool
|
bool
|
||||||
sc_recorder_init(struct sc_recorder *recorder,
|
recorder_init(struct recorder *recorder,
|
||||||
const char *filename,
|
const char *filename,
|
||||||
enum sc_record_format format,
|
enum sc_record_format format,
|
||||||
struct sc_size declared_frame_size) {
|
struct sc_size declared_frame_size) {
|
||||||
@@ -385,9 +383,9 @@ sc_recorder_init(struct sc_recorder *recorder,
|
|||||||
recorder->declared_frame_size = declared_frame_size;
|
recorder->declared_frame_size = declared_frame_size;
|
||||||
|
|
||||||
static const struct sc_packet_sink_ops ops = {
|
static const struct sc_packet_sink_ops ops = {
|
||||||
.open = sc_recorder_packet_sink_open,
|
.open = recorder_packet_sink_open,
|
||||||
.close = sc_recorder_packet_sink_close,
|
.close = recorder_packet_sink_close,
|
||||||
.push = sc_recorder_packet_sink_push,
|
.push = recorder_packet_sink_push,
|
||||||
};
|
};
|
||||||
|
|
||||||
recorder->packet_sink.ops = &ops;
|
recorder->packet_sink.ops = &ops;
|
||||||
@@ -396,6 +394,6 @@ sc_recorder_init(struct sc_recorder *recorder,
|
|||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
sc_recorder_destroy(struct sc_recorder *recorder) {
|
recorder_destroy(struct recorder *recorder) {
|
||||||
free(recorder->filename);
|
free(recorder->filename);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
#ifndef SC_RECORDER_H
|
#ifndef RECORDER_H
|
||||||
#define SC_RECORDER_H
|
#define RECORDER_H
|
||||||
|
|
||||||
#include "common.h"
|
#include "common.h"
|
||||||
|
|
||||||
@@ -12,14 +12,14 @@
|
|||||||
#include "util/queue.h"
|
#include "util/queue.h"
|
||||||
#include "util/thread.h"
|
#include "util/thread.h"
|
||||||
|
|
||||||
struct sc_record_packet {
|
struct record_packet {
|
||||||
AVPacket *packet;
|
AVPacket *packet;
|
||||||
struct sc_record_packet *next;
|
struct record_packet *next;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct sc_recorder_queue SC_QUEUE(struct sc_record_packet);
|
struct recorder_queue SC_QUEUE(struct record_packet);
|
||||||
|
|
||||||
struct sc_recorder {
|
struct recorder {
|
||||||
struct sc_packet_sink packet_sink; // packet sink trait
|
struct sc_packet_sink packet_sink; // packet sink trait
|
||||||
|
|
||||||
char *filename;
|
char *filename;
|
||||||
@@ -33,21 +33,20 @@ struct sc_recorder {
|
|||||||
sc_cond queue_cond;
|
sc_cond queue_cond;
|
||||||
bool stopped; // set on recorder_close()
|
bool stopped; // set on recorder_close()
|
||||||
bool failed; // set on packet write failure
|
bool failed; // set on packet write failure
|
||||||
struct sc_recorder_queue queue;
|
struct recorder_queue queue;
|
||||||
|
|
||||||
// we can write a packet only once we received the next one so that we can
|
// we can write a packet only once we received the next one so that we can
|
||||||
// set its duration (next_pts - current_pts)
|
// set its duration (next_pts - current_pts)
|
||||||
// "previous" is only accessed from the recorder thread, so it does not
|
// "previous" is only accessed from the recorder thread, so it does not
|
||||||
// need to be protected by the mutex
|
// need to be protected by the mutex
|
||||||
struct sc_record_packet *previous;
|
struct record_packet *previous;
|
||||||
};
|
};
|
||||||
|
|
||||||
bool
|
bool
|
||||||
sc_recorder_init(struct sc_recorder *recorder, const char *filename,
|
recorder_init(struct recorder *recorder, const char *filename,
|
||||||
enum sc_record_format format,
|
enum sc_record_format format, struct sc_size declared_frame_size);
|
||||||
struct sc_size declared_frame_size);
|
|
||||||
|
|
||||||
void
|
void
|
||||||
sc_recorder_destroy(struct sc_recorder *recorder);
|
recorder_destroy(struct recorder *recorder);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -15,7 +15,6 @@
|
|||||||
|
|
||||||
#include "controller.h"
|
#include "controller.h"
|
||||||
#include "decoder.h"
|
#include "decoder.h"
|
||||||
#include "demuxer.h"
|
|
||||||
#include "events.h"
|
#include "events.h"
|
||||||
#include "file_pusher.h"
|
#include "file_pusher.h"
|
||||||
#include "keyboard_inject.h"
|
#include "keyboard_inject.h"
|
||||||
@@ -23,6 +22,7 @@
|
|||||||
#include "recorder.h"
|
#include "recorder.h"
|
||||||
#include "screen.h"
|
#include "screen.h"
|
||||||
#include "server.h"
|
#include "server.h"
|
||||||
|
#include "stream.h"
|
||||||
#ifdef HAVE_USB
|
#ifdef HAVE_USB
|
||||||
# include "usb/aoa_hid.h"
|
# include "usb/aoa_hid.h"
|
||||||
# include "usb/hid_keyboard.h"
|
# include "usb/hid_keyboard.h"
|
||||||
@@ -39,9 +39,9 @@
|
|||||||
struct scrcpy {
|
struct scrcpy {
|
||||||
struct sc_server server;
|
struct sc_server server;
|
||||||
struct sc_screen screen;
|
struct sc_screen screen;
|
||||||
struct sc_demuxer demuxer;
|
struct stream stream;
|
||||||
struct sc_decoder decoder;
|
struct decoder decoder;
|
||||||
struct sc_recorder recorder;
|
struct recorder recorder;
|
||||||
#ifdef HAVE_V4L2
|
#ifdef HAVE_V4L2
|
||||||
struct sc_v4l2_sink v4l2_sink;
|
struct sc_v4l2_sink v4l2_sink;
|
||||||
#endif
|
#endif
|
||||||
@@ -143,8 +143,10 @@ sdl_configure(bool display, bool disable_screensaver) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (disable_screensaver) {
|
if (disable_screensaver) {
|
||||||
|
LOGD("Screensaver disabled");
|
||||||
SDL_DisableScreenSaver();
|
SDL_DisableScreenSaver();
|
||||||
} else {
|
} else {
|
||||||
|
LOGD("Screensaver enabled");
|
||||||
SDL_EnableScreenSaver();
|
SDL_EnableScreenSaver();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -229,8 +231,8 @@ av_log_callback(void *avcl, int level, const char *fmt, va_list vl) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
sc_demuxer_on_eos(struct sc_demuxer *demuxer, void *userdata) {
|
stream_on_eos(struct stream *stream, void *userdata) {
|
||||||
(void) demuxer;
|
(void) stream;
|
||||||
(void) userdata;
|
(void) userdata;
|
||||||
|
|
||||||
PUSH_EVENT(EVENT_STREAM_STOPPED);
|
PUSH_EVENT(EVENT_STREAM_STOPPED);
|
||||||
@@ -269,7 +271,7 @@ scrcpy(struct scrcpy_options *options) {
|
|||||||
|
|
||||||
// Minimal SDL initialization
|
// Minimal SDL initialization
|
||||||
if (SDL_Init(SDL_INIT_EVENTS)) {
|
if (SDL_Init(SDL_INIT_EVENTS)) {
|
||||||
LOGE("Could not initialize SDL: %s", SDL_GetError());
|
LOGC("Could not initialize SDL: %s", SDL_GetError());
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -283,7 +285,7 @@ scrcpy(struct scrcpy_options *options) {
|
|||||||
#ifdef HAVE_V4L2
|
#ifdef HAVE_V4L2
|
||||||
bool v4l2_sink_initialized = false;
|
bool v4l2_sink_initialized = false;
|
||||||
#endif
|
#endif
|
||||||
bool demuxer_started = false;
|
bool stream_started = false;
|
||||||
#ifdef HAVE_USB
|
#ifdef HAVE_USB
|
||||||
bool aoa_hid_initialized = false;
|
bool aoa_hid_initialized = false;
|
||||||
bool hid_keyboard_initialized = false;
|
bool hid_keyboard_initialized = false;
|
||||||
@@ -296,9 +298,7 @@ scrcpy(struct scrcpy_options *options) {
|
|||||||
struct sc_acksync *acksync = NULL;
|
struct sc_acksync *acksync = NULL;
|
||||||
|
|
||||||
struct sc_server_params params = {
|
struct sc_server_params params = {
|
||||||
.req_serial = options->serial,
|
.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,
|
||||||
@@ -343,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)) {
|
||||||
LOGE("Could not initialize SDL: %s", SDL_GetError());
|
LOGC("Could not initialize SDL: %s", SDL_GetError());
|
||||||
goto end;
|
goto end;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -357,7 +357,7 @@ scrcpy(struct scrcpy_options *options) {
|
|||||||
// It is necessarily initialized here, since the device is connected
|
// It is necessarily initialized here, since the device is connected
|
||||||
struct sc_server_info *info = &s->server.info;
|
struct sc_server_info *info = &s->server.info;
|
||||||
|
|
||||||
const char *serial = s->server.serial;
|
const char *serial = s->server.params.serial;
|
||||||
assert(serial);
|
assert(serial);
|
||||||
|
|
||||||
struct sc_file_pusher *fp = NULL;
|
struct sc_file_pusher *fp = NULL;
|
||||||
@@ -371,19 +371,19 @@ scrcpy(struct scrcpy_options *options) {
|
|||||||
file_pusher_initialized = true;
|
file_pusher_initialized = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct sc_decoder *dec = NULL;
|
struct decoder *dec = NULL;
|
||||||
bool needs_decoder = options->display;
|
bool needs_decoder = options->display;
|
||||||
#ifdef HAVE_V4L2
|
#ifdef HAVE_V4L2
|
||||||
needs_decoder |= !!options->v4l2_device;
|
needs_decoder |= !!options->v4l2_device;
|
||||||
#endif
|
#endif
|
||||||
if (needs_decoder) {
|
if (needs_decoder) {
|
||||||
sc_decoder_init(&s->decoder);
|
decoder_init(&s->decoder);
|
||||||
dec = &s->decoder;
|
dec = &s->decoder;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct sc_recorder *rec = NULL;
|
struct recorder *rec = NULL;
|
||||||
if (options->record_filename) {
|
if (options->record_filename) {
|
||||||
if (!sc_recorder_init(&s->recorder,
|
if (!recorder_init(&s->recorder,
|
||||||
options->record_filename,
|
options->record_filename,
|
||||||
options->record_format,
|
options->record_format,
|
||||||
info->frame_size)) {
|
info->frame_size)) {
|
||||||
@@ -395,17 +395,17 @@ scrcpy(struct scrcpy_options *options) {
|
|||||||
|
|
||||||
av_log_set_callback(av_log_callback);
|
av_log_set_callback(av_log_callback);
|
||||||
|
|
||||||
static const struct sc_demuxer_callbacks demuxer_cbs = {
|
static const struct stream_callbacks stream_cbs = {
|
||||||
.on_eos = sc_demuxer_on_eos,
|
.on_eos = stream_on_eos,
|
||||||
};
|
};
|
||||||
sc_demuxer_init(&s->demuxer, s->server.video_socket, &demuxer_cbs, NULL);
|
stream_init(&s->stream, s->server.video_socket, &stream_cbs, NULL);
|
||||||
|
|
||||||
if (dec) {
|
if (dec) {
|
||||||
sc_demuxer_add_sink(&s->demuxer, &dec->packet_sink);
|
stream_add_sink(&s->stream, &dec->packet_sink);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (rec) {
|
if (rec) {
|
||||||
sc_demuxer_add_sink(&s->demuxer, &rec->packet_sink);
|
stream_add_sink(&s->stream, &rec->packet_sink);
|
||||||
}
|
}
|
||||||
|
|
||||||
struct sc_controller *controller = NULL;
|
struct sc_controller *controller = NULL;
|
||||||
@@ -432,19 +432,32 @@ scrcpy(struct scrcpy_options *options) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
assert(serial);
|
assert(serial);
|
||||||
struct sc_usb_device usb_device;
|
struct sc_usb_device usb_devices[16];
|
||||||
ok = sc_usb_select_device(&s->usb, serial, &usb_device);
|
ssize_t count = sc_usb_find_devices(&s->usb, serial, usb_devices,
|
||||||
if (!ok) {
|
ARRAY_LEN(usb_devices));
|
||||||
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
LOGI("USB device: %s (%04" PRIx16 ":%04" PRIx16 ") %s %s",
|
if (count > 1) {
|
||||||
usb_device.serial, usb_device.vid, usb_device.pid,
|
LOGE("Multiple (%d) devices with serial %s", (int) count, serial);
|
||||||
usb_device.manufacturer, usb_device.product);
|
sc_usb_device_destroy_all(usb_devices, count);
|
||||||
|
sc_usb_destroy(&s->usb);
|
||||||
|
sc_acksync_destroy(&s->acksync);
|
||||||
|
goto aoa_hid_end;
|
||||||
|
}
|
||||||
|
|
||||||
ok = sc_usb_connect(&s->usb, usb_device.device, NULL, NULL);
|
struct sc_usb_device *usb_device = &usb_devices[0];
|
||||||
sc_usb_device_destroy(&usb_device);
|
|
||||||
|
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, NULL, NULL);
|
||||||
|
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);
|
||||||
@@ -595,7 +608,7 @@ aoa_hid_end:
|
|||||||
}
|
}
|
||||||
screen_initialized = true;
|
screen_initialized = true;
|
||||||
|
|
||||||
sc_decoder_add_sink(&s->decoder, &s->screen.frame_sink);
|
decoder_add_sink(&s->decoder, &s->screen.frame_sink);
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef HAVE_V4L2
|
#ifdef HAVE_V4L2
|
||||||
@@ -605,28 +618,28 @@ aoa_hid_end:
|
|||||||
goto end;
|
goto end;
|
||||||
}
|
}
|
||||||
|
|
||||||
sc_decoder_add_sink(&s->decoder, &s->v4l2_sink.frame_sink);
|
decoder_add_sink(&s->decoder, &s->v4l2_sink.frame_sink);
|
||||||
|
|
||||||
v4l2_sink_initialized = true;
|
v4l2_sink_initialized = true;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// now we consumed the header values, the socket receives the video stream
|
// now we consumed the header values, the socket receives the video stream
|
||||||
// start the demuxer
|
// start the stream
|
||||||
if (!sc_demuxer_start(&s->demuxer)) {
|
if (!stream_start(&s->stream)) {
|
||||||
goto end;
|
goto end;
|
||||||
}
|
}
|
||||||
demuxer_started = true;
|
stream_started = true;
|
||||||
|
|
||||||
ret = event_loop(s);
|
ret = event_loop(s);
|
||||||
LOGD("quit...");
|
LOGD("quit...");
|
||||||
|
|
||||||
// Close the window immediately on closing, because screen_destroy() may
|
// Close the window immediately on closing, because screen_destroy() may
|
||||||
// only be called once the demuxer thread is joined (it may take time)
|
// only be called once the stream thread is joined (it may take time)
|
||||||
sc_screen_hide_window(&s->screen);
|
sc_screen_hide_window(&s->screen);
|
||||||
|
|
||||||
end:
|
end:
|
||||||
// The demuxer is not stopped explicitly, because it will stop by itself on
|
// The stream is not stopped explicitly, because it will stop by itself on
|
||||||
// end-of-stream
|
// end-of-stream
|
||||||
#ifdef HAVE_USB
|
#ifdef HAVE_USB
|
||||||
if (aoa_hid_initialized) {
|
if (aoa_hid_initialized) {
|
||||||
@@ -658,10 +671,10 @@ end:
|
|||||||
sc_server_stop(&s->server);
|
sc_server_stop(&s->server);
|
||||||
}
|
}
|
||||||
|
|
||||||
// now that the sockets are shutdown, the demuxer and controller are
|
// now that the sockets are shutdown, the stream and controller are
|
||||||
// interrupted, we can join them
|
// interrupted, we can join them
|
||||||
if (demuxer_started) {
|
if (stream_started) {
|
||||||
sc_demuxer_join(&s->demuxer);
|
stream_join(&s->stream);
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef HAVE_V4L2
|
#ifdef HAVE_V4L2
|
||||||
@@ -680,7 +693,7 @@ end:
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Destroy the screen only after the demuxer is guaranteed to be finished,
|
// Destroy the screen only after the stream is guaranteed to be finished,
|
||||||
// because otherwise the screen could receive new frames after destruction
|
// because otherwise the screen could receive new frames after destruction
|
||||||
if (screen_initialized) {
|
if (screen_initialized) {
|
||||||
sc_screen_join(&s->screen);
|
sc_screen_join(&s->screen);
|
||||||
@@ -695,7 +708,7 @@ end:
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (recorder_initialized) {
|
if (recorder_initialized) {
|
||||||
sc_recorder_destroy(&s->recorder);
|
recorder_destroy(&s->recorder);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (file_pusher_initialized) {
|
if (file_pusher_initialized) {
|
||||||
|
|||||||
@@ -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) {
|
||||||
LOGE("Could not create window: %s", SDL_GetError());
|
LOGC("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) {
|
||||||
LOGE("Could not create renderer: %s", SDL_GetError());
|
LOGC("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) {
|
||||||
LOGE("Could not create texture: %s", SDL_GetError());
|
LOGC("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) {
|
||||||
LOGE("Could not create texture: %s", SDL_GetError());
|
LOGC("Could not create texture: %s", SDL_GetError());
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
237
app/src/server.c
237
app/src/server.c
@@ -7,7 +7,7 @@
|
|||||||
#include <SDL2/SDL_timer.h>
|
#include <SDL2/SDL_timer.h>
|
||||||
#include <SDL2/SDL_platform.h>
|
#include <SDL2/SDL_platform.h>
|
||||||
|
|
||||||
#include "adb/adb.h"
|
#include "adb.h"
|
||||||
#include "util/file.h"
|
#include "util/file.h"
|
||||||
#include "util/log.h"
|
#include "util/log.h"
|
||||||
#include "util/net_intr.h"
|
#include "util/net_intr.h"
|
||||||
@@ -65,7 +65,7 @@ get_server_path(void) {
|
|||||||
static void
|
static void
|
||||||
sc_server_params_destroy(struct sc_server_params *params) {
|
sc_server_params_destroy(struct sc_server_params *params) {
|
||||||
// The server stores a copy of the params provided by the user
|
// The server stores a copy of the params provided by the user
|
||||||
free((char *) params->req_serial);
|
free((char *) params->serial);
|
||||||
free((char *) params->crop);
|
free((char *) params->crop);
|
||||||
free((char *) params->codec_options);
|
free((char *) params->codec_options);
|
||||||
free((char *) params->encoder_name);
|
free((char *) params->encoder_name);
|
||||||
@@ -89,7 +89,7 @@ sc_server_params_copy(struct sc_server_params *dst,
|
|||||||
} \
|
} \
|
||||||
}
|
}
|
||||||
|
|
||||||
COPY(req_serial);
|
COPY(serial);
|
||||||
COPY(crop);
|
COPY(crop);
|
||||||
COPY(codec_options);
|
COPY(codec_options);
|
||||||
COPY(encoder_name);
|
COPY(encoder_name);
|
||||||
@@ -114,7 +114,7 @@ push_server(struct sc_intr *intr, const char *serial) {
|
|||||||
free(server_path);
|
free(server_path);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
bool ok = sc_adb_push(intr, serial, server_path, SC_DEVICE_SERVER_PATH, 0);
|
bool ok = adb_push(intr, serial, server_path, SC_DEVICE_SERVER_PATH, 0);
|
||||||
free(server_path);
|
free(server_path);
|
||||||
return ok;
|
return ok;
|
||||||
}
|
}
|
||||||
@@ -157,14 +157,8 @@ execute_server(struct sc_server *server,
|
|||||||
const struct sc_server_params *params) {
|
const struct sc_server_params *params) {
|
||||||
sc_pid pid = SC_PROCESS_NONE;
|
sc_pid pid = SC_PROCESS_NONE;
|
||||||
|
|
||||||
const char *serial = server->serial;
|
|
||||||
assert(serial);
|
|
||||||
|
|
||||||
const char *cmd[128];
|
const char *cmd[128];
|
||||||
unsigned count = 0;
|
unsigned count = 0;
|
||||||
cmd[count++] = sc_adb_get_executable();
|
|
||||||
cmd[count++] = "-s";
|
|
||||||
cmd[count++] = serial;
|
|
||||||
cmd[count++] = "shell";
|
cmd[count++] = "shell";
|
||||||
cmd[count++] = "CLASSPATH=" SC_DEVICE_SERVER_PATH;
|
cmd[count++] = "CLASSPATH=" SC_DEVICE_SERVER_PATH;
|
||||||
cmd[count++] = "app_process";
|
cmd[count++] = "app_process";
|
||||||
@@ -246,8 +240,7 @@ execute_server(struct sc_server *server,
|
|||||||
}
|
}
|
||||||
|
|
||||||
#undef ADD_PARAM
|
#undef ADD_PARAM
|
||||||
|
#undef STRBOOL
|
||||||
cmd[count++] = NULL;
|
|
||||||
|
|
||||||
#ifdef SERVER_DEBUGGER
|
#ifdef SERVER_DEBUGGER
|
||||||
LOGI("Server debugger waiting for a client on device port "
|
LOGI("Server debugger waiting for a client on device port "
|
||||||
@@ -261,7 +254,7 @@ execute_server(struct sc_server *server,
|
|||||||
// Then click on "Debug"
|
// Then click on "Debug"
|
||||||
#endif
|
#endif
|
||||||
// Inherit both stdout and stderr (all server logs are printed to stdout)
|
// Inherit both stdout and stderr (all server logs are printed to stdout)
|
||||||
pid = sc_adb_execute(cmd, 0);
|
pid = adb_execute(params->serial, cmd, count, 0);
|
||||||
|
|
||||||
end:
|
end:
|
||||||
for (unsigned i = dyn_idx; i < count; ++i) {
|
for (unsigned i = dyn_idx; i < count; ++i) {
|
||||||
@@ -353,7 +346,6 @@ sc_server_init(struct sc_server *server, const struct sc_server_params *params,
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
server->serial = NULL;
|
|
||||||
server->stopped = false;
|
server->stopped = false;
|
||||||
|
|
||||||
server->video_socket = SC_SOCKET_NONE;
|
server->video_socket = SC_SOCKET_NONE;
|
||||||
@@ -398,9 +390,7 @@ sc_server_connect_to(struct sc_server *server, struct sc_server_info *info) {
|
|||||||
|
|
||||||
assert(tunnel->enabled);
|
assert(tunnel->enabled);
|
||||||
|
|
||||||
const char *serial = server->serial;
|
const char *serial = server->params.serial;
|
||||||
assert(serial);
|
|
||||||
|
|
||||||
bool control = server->params.control;
|
bool control = server->params.control;
|
||||||
|
|
||||||
sc_socket video_socket = SC_SOCKET_NONE;
|
sc_socket video_socket = SC_SOCKET_NONE;
|
||||||
@@ -504,11 +494,32 @@ sc_server_on_terminated(void *userdata) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
is_tcpip_mode_enabled(struct sc_server *server, const char *serial) {
|
sc_server_fill_serial(struct sc_server *server) {
|
||||||
|
// Retrieve the actual device immediately if not provided, so that all
|
||||||
|
// future adb commands are executed for this specific device, even if other
|
||||||
|
// devices are connected afterwards (without "more than one
|
||||||
|
// device/emulator" error)
|
||||||
|
if (!server->params.serial) {
|
||||||
|
// The serial is owned by sc_server_params, and will be freed on destroy
|
||||||
|
server->params.serial = adb_get_serialno(&server->intr, 0);
|
||||||
|
if (!server->params.serial) {
|
||||||
|
LOGE("Could not get device serial");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
LOGD("Device serial: %s", server->params.serial);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool
|
||||||
|
is_tcpip_mode_enabled(struct sc_server *server) {
|
||||||
struct sc_intr *intr = &server->intr;
|
struct sc_intr *intr = &server->intr;
|
||||||
|
const char *serial = server->params.serial;
|
||||||
|
|
||||||
char *current_port =
|
char *current_port =
|
||||||
sc_adb_getprop(intr, serial, "service.adb.tcp.port", SC_ADB_SILENT);
|
adb_getprop(intr, serial, "service.adb.tcp.port", SC_ADB_SILENT);
|
||||||
if (!current_port) {
|
if (!current_port) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -520,9 +531,9 @@ is_tcpip_mode_enabled(struct sc_server *server, const char *serial) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
wait_tcpip_mode_enabled(struct sc_server *server, const char *serial,
|
wait_tcpip_mode_enabled(struct sc_server *server, unsigned attempts,
|
||||||
unsigned attempts, sc_tick delay) {
|
sc_tick delay) {
|
||||||
if (is_tcpip_mode_enabled(server, serial)) {
|
if (is_tcpip_mode_enabled(server)) {
|
||||||
LOGI("TCP/IP mode enabled");
|
LOGI("TCP/IP mode enabled");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -537,7 +548,7 @@ wait_tcpip_mode_enabled(struct sc_server *server, const char *serial,
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (is_tcpip_mode_enabled(server, serial)) {
|
if (is_tcpip_mode_enabled(server)) {
|
||||||
LOGI("TCP/IP mode enabled");
|
LOGI("TCP/IP mode enabled");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -562,30 +573,29 @@ append_port_5555(const char *ip) {
|
|||||||
return ip_port;
|
return ip_port;
|
||||||
}
|
}
|
||||||
|
|
||||||
static char *
|
static bool
|
||||||
sc_server_switch_to_tcpip(struct sc_server *server, const char *serial) {
|
sc_server_switch_to_tcpip(struct sc_server *server, char **out_ip_port) {
|
||||||
|
const char *serial = server->params.serial;
|
||||||
assert(serial);
|
assert(serial);
|
||||||
|
|
||||||
struct sc_intr *intr = &server->intr;
|
struct sc_intr *intr = &server->intr;
|
||||||
|
|
||||||
LOGI("Switching device %s to TCP/IP...", serial);
|
char *ip = adb_get_device_ip(intr, serial, 0);
|
||||||
|
|
||||||
char *ip = sc_adb_get_device_ip(intr, serial, 0);
|
|
||||||
if (!ip) {
|
if (!ip) {
|
||||||
LOGE("Device IP not found");
|
LOGE("Device IP not found");
|
||||||
return NULL;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
char *ip_port = append_port_5555(ip);
|
char *ip_port = append_port_5555(ip);
|
||||||
free(ip);
|
free(ip);
|
||||||
if (!ip_port) {
|
if (!ip_port) {
|
||||||
return NULL;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool tcp_mode = is_tcpip_mode_enabled(server, serial);
|
bool tcp_mode = is_tcpip_mode_enabled(server);
|
||||||
|
|
||||||
if (!tcp_mode) {
|
if (!tcp_mode) {
|
||||||
bool ok = sc_adb_tcpip(intr, serial, 5555, SC_ADB_NO_STDOUT);
|
bool ok = adb_tcpip(intr, serial, 5555, SC_ADB_NO_STDOUT);
|
||||||
if (!ok) {
|
if (!ok) {
|
||||||
LOGE("Could not restart adbd in TCP/IP mode");
|
LOGE("Could not restart adbd in TCP/IP mode");
|
||||||
goto error;
|
goto error;
|
||||||
@@ -593,17 +603,19 @@ sc_server_switch_to_tcpip(struct sc_server *server, const char *serial) {
|
|||||||
|
|
||||||
unsigned attempts = 40;
|
unsigned attempts = 40;
|
||||||
sc_tick delay = SC_TICK_FROM_MS(250);
|
sc_tick delay = SC_TICK_FROM_MS(250);
|
||||||
ok = wait_tcpip_mode_enabled(server, serial, attempts, delay);
|
ok = wait_tcpip_mode_enabled(server, attempts, delay);
|
||||||
if (!ok) {
|
if (!ok) {
|
||||||
goto error;
|
goto error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return ip_port;
|
*out_ip_port = ip_port;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
|
||||||
error:
|
error:
|
||||||
free(ip_port);
|
free(ip_port);
|
||||||
return NULL;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
@@ -611,52 +623,73 @@ sc_server_connect_to_tcpip(struct sc_server *server, const char *ip_port) {
|
|||||||
struct sc_intr *intr = &server->intr;
|
struct sc_intr *intr = &server->intr;
|
||||||
|
|
||||||
// Error expected if not connected, do not report any error
|
// Error expected if not connected, do not report any error
|
||||||
sc_adb_disconnect(intr, ip_port, SC_ADB_SILENT);
|
adb_disconnect(intr, ip_port, SC_ADB_SILENT);
|
||||||
|
|
||||||
LOGI("Connecting to %s...", ip_port);
|
bool ok = adb_connect(intr, ip_port, 0);
|
||||||
|
|
||||||
bool ok = sc_adb_connect(intr, ip_port, 0);
|
|
||||||
if (!ok) {
|
if (!ok) {
|
||||||
LOGE("Could not connect to %s", ip_port);
|
LOGE("Could not connect to %s", ip_port);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Override the serial, owned by the sc_server_params
|
||||||
|
free((void *) server->params.serial);
|
||||||
|
server->params.serial = strdup(ip_port);
|
||||||
|
if (!server->params.serial) {
|
||||||
|
LOG_OOM();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
LOGI("Connected to %s", ip_port);
|
LOGI("Connected to %s", ip_port);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
sc_server_configure_tcpip_known_address(struct sc_server *server,
|
sc_server_configure_tcpip(struct sc_server *server) {
|
||||||
const char *addr) {
|
char *ip_port;
|
||||||
|
|
||||||
|
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->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(addr, ':');
|
bool contains_port = strchr(params->tcpip_dst, ':');
|
||||||
char *ip_port = contains_port ? strdup(addr) : append_port_5555(addr);
|
ip_port = contains_port ? strdup(params->tcpip_dst)
|
||||||
|
: append_port_5555(params->tcpip_dst);
|
||||||
if (!ip_port) {
|
if (!ip_port) {
|
||||||
LOG_OOM();
|
LOG_OOM();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
server->serial = ip_port;
|
// The device IP address must be retrieved from the current
|
||||||
return sc_server_connect_to_tcpip(server, ip_port);
|
// connected device
|
||||||
}
|
if (!sc_server_fill_serial(server)) {
|
||||||
|
|
||||||
static bool
|
|
||||||
sc_server_configure_tcpip_unknown_address(struct sc_server *server,
|
|
||||||
const char *serial) {
|
|
||||||
bool is_already_tcpip = sc_adb_is_serial_tcpip(serial);
|
|
||||||
if (is_already_tcpip) {
|
|
||||||
// Nothing to do
|
|
||||||
LOGI("Device already connected via TCP/IP: %s", serial);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
char *ip_port = sc_server_switch_to_tcpip(server, serial);
|
|
||||||
if (!ip_port) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
server->serial = ip_port;
|
// The serial is either the real serial when connected via USB, or
|
||||||
return sc_server_connect_to_tcpip(server, ip_port);
|
// the IP:PORT when connected over TCP/IP. Only the latter contains
|
||||||
|
// a colon.
|
||||||
|
bool is_already_tcpip = strchr(params->serial, ':');
|
||||||
|
if (is_already_tcpip) {
|
||||||
|
// Nothing to do
|
||||||
|
LOGI("Device already connected via TCP/IP: %s", params->serial);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ok = sc_server_switch_to_tcpip(server, &ip_port);
|
||||||
|
if (!ok) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// On success, this call changes params->serial
|
||||||
|
bool ok = sc_server_connect_to_tcpip(server, ip_port);
|
||||||
|
free(ip_port);
|
||||||
|
return ok;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
@@ -665,83 +698,30 @@ 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
|
if (params->serial) {
|
||||||
// output/errors is correctly printed in the console ("adb devices" output
|
LOGD("Device serial: %s", params->serial);
|
||||||
// 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) {
|
||||||
assert(!params->tcpip_dst);
|
// params->serial may be changed after this call
|
||||||
ok = sc_server_configure_tcpip_unknown_address(server,
|
bool ok = sc_server_configure_tcpip(server);
|
||||||
device.serial);
|
|
||||||
sc_adb_device_destroy(&device);
|
|
||||||
if (!ok) {
|
|
||||||
goto error_connection_failed;
|
|
||||||
}
|
|
||||||
assert(server->serial);
|
|
||||||
} else {
|
|
||||||
// "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 {
|
|
||||||
ok = sc_server_configure_tcpip_known_address(server, params->tcpip_dst);
|
|
||||||
if (!ok) {
|
if (!ok) {
|
||||||
goto error_connection_failed;
|
goto error_connection_failed;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const char *serial = server->serial;
|
// It is ok to call this function even if the device serial has been
|
||||||
assert(serial);
|
// changed by switching over TCP/IP
|
||||||
LOGD("Device serial: %s", serial);
|
if (!sc_server_fill_serial(server)) {
|
||||||
|
goto error_connection_failed;
|
||||||
|
}
|
||||||
|
|
||||||
ok = push_server(&server->intr, serial);
|
bool ok = push_server(&server->intr, params->serial);
|
||||||
if (!ok) {
|
if (!ok) {
|
||||||
goto error_connection_failed;
|
goto error_connection_failed;
|
||||||
}
|
}
|
||||||
|
|
||||||
ok = sc_adb_tunnel_open(&server->tunnel, &server->intr, serial,
|
ok = sc_adb_tunnel_open(&server->tunnel, &server->intr, params->serial,
|
||||||
params->port_range, params->force_adb_forward);
|
params->port_range, params->force_adb_forward);
|
||||||
if (!ok) {
|
if (!ok) {
|
||||||
goto error_connection_failed;
|
goto error_connection_failed;
|
||||||
@@ -750,7 +730,7 @@ run_server(void *data) {
|
|||||||
// server will connect to our server socket
|
// server will connect to our server socket
|
||||||
sc_pid pid = execute_server(server, params);
|
sc_pid pid = execute_server(server, params);
|
||||||
if (pid == SC_PROCESS_NONE) {
|
if (pid == SC_PROCESS_NONE) {
|
||||||
sc_adb_tunnel_close(&server->tunnel, &server->intr, serial);
|
sc_adb_tunnel_close(&server->tunnel, &server->intr, params->serial);
|
||||||
goto error_connection_failed;
|
goto error_connection_failed;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -762,7 +742,7 @@ run_server(void *data) {
|
|||||||
if (!ok) {
|
if (!ok) {
|
||||||
sc_process_terminate(pid);
|
sc_process_terminate(pid);
|
||||||
sc_process_wait(pid, true); // ignore exit code
|
sc_process_wait(pid, true); // ignore exit code
|
||||||
sc_adb_tunnel_close(&server->tunnel, &server->intr, serial);
|
sc_adb_tunnel_close(&server->tunnel, &server->intr, params->serial);
|
||||||
goto error_connection_failed;
|
goto error_connection_failed;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -855,7 +835,6 @@ sc_server_destroy(struct sc_server *server) {
|
|||||||
net_close(server->control_socket);
|
net_close(server->control_socket);
|
||||||
}
|
}
|
||||||
|
|
||||||
free(server->serial);
|
|
||||||
sc_server_params_destroy(&server->params);
|
sc_server_params_destroy(&server->params);
|
||||||
sc_intr_destroy(&server->intr);
|
sc_intr_destroy(&server->intr);
|
||||||
sc_cond_destroy(&server->cond_stopped);
|
sc_cond_destroy(&server->cond_stopped);
|
||||||
|
|||||||
@@ -7,7 +7,8 @@
|
|||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
|
||||||
#include "adb/adb_tunnel.h"
|
#include "adb.h"
|
||||||
|
#include "adb_tunnel.h"
|
||||||
#include "coords.h"
|
#include "coords.h"
|
||||||
#include "options.h"
|
#include "options.h"
|
||||||
#include "util/intr.h"
|
#include "util/intr.h"
|
||||||
@@ -22,7 +23,7 @@ struct sc_server_info {
|
|||||||
};
|
};
|
||||||
|
|
||||||
struct sc_server_params {
|
struct sc_server_params {
|
||||||
const char *req_serial;
|
const char *serial;
|
||||||
enum sc_log_level log_level;
|
enum sc_log_level log_level;
|
||||||
const char *crop;
|
const char *crop;
|
||||||
const char *codec_options;
|
const char *codec_options;
|
||||||
@@ -44,14 +45,11 @@ 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 {
|
||||||
// The internal allocated strings are copies owned by the server
|
// The internal allocated strings are copies owned by the server
|
||||||
struct sc_server_params params;
|
struct sc_server_params params;
|
||||||
char *serial;
|
|
||||||
|
|
||||||
sc_thread thread;
|
sc_thread thread;
|
||||||
struct sc_server_info info; // initialized once connected
|
struct sc_server_info info; // initialized once connected
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
#include "demuxer.h"
|
#include "stream.h"
|
||||||
|
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
#include <libavutil/time.h>
|
#include <libavutil/time.h>
|
||||||
@@ -16,7 +16,7 @@
|
|||||||
#define NO_PTS UINT64_C(-1)
|
#define NO_PTS UINT64_C(-1)
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
sc_demuxer_recv_packet(struct sc_demuxer *demuxer, AVPacket *packet) {
|
stream_recv_packet(struct stream *stream, AVPacket *packet) {
|
||||||
// The video stream contains raw packets, without time information. When we
|
// The video stream contains raw packets, without time information. When we
|
||||||
// record, we retrieve the timestamps separately, from a "meta" header
|
// record, we retrieve the timestamps separately, from a "meta" header
|
||||||
// added by the server before each raw packet.
|
// added by the server before each raw packet.
|
||||||
@@ -30,7 +30,7 @@ sc_demuxer_recv_packet(struct sc_demuxer *demuxer, AVPacket *packet) {
|
|||||||
// It is followed by <packet_size> bytes containing the packet/frame.
|
// It is followed by <packet_size> bytes containing the packet/frame.
|
||||||
|
|
||||||
uint8_t header[HEADER_SIZE];
|
uint8_t header[HEADER_SIZE];
|
||||||
ssize_t r = net_recv_all(demuxer->socket, header, HEADER_SIZE);
|
ssize_t r = net_recv_all(stream->socket, header, HEADER_SIZE);
|
||||||
if (r < HEADER_SIZE) {
|
if (r < HEADER_SIZE) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -45,7 +45,7 @@ sc_demuxer_recv_packet(struct sc_demuxer *demuxer, AVPacket *packet) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
r = net_recv_all(demuxer->socket, packet->data, len);
|
r = net_recv_all(stream->socket, packet->data, len);
|
||||||
if (r < 0 || ((uint32_t) r) < len) {
|
if (r < 0 || ((uint32_t) r) < len) {
|
||||||
av_packet_unref(packet);
|
av_packet_unref(packet);
|
||||||
return false;
|
return false;
|
||||||
@@ -57,9 +57,9 @@ sc_demuxer_recv_packet(struct sc_demuxer *demuxer, AVPacket *packet) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
push_packet_to_sinks(struct sc_demuxer *demuxer, const AVPacket *packet) {
|
push_packet_to_sinks(struct stream *stream, const AVPacket *packet) {
|
||||||
for (unsigned i = 0; i < demuxer->sink_count; ++i) {
|
for (unsigned i = 0; i < stream->sink_count; ++i) {
|
||||||
struct sc_packet_sink *sink = demuxer->sinks[i];
|
struct sc_packet_sink *sink = stream->sinks[i];
|
||||||
if (!sink->ops->push(sink, packet)) {
|
if (!sink->ops->push(sink, packet)) {
|
||||||
LOGE("Could not send config packet to sink %d", i);
|
LOGE("Could not send config packet to sink %d", i);
|
||||||
return false;
|
return false;
|
||||||
@@ -69,13 +69,13 @@ push_packet_to_sinks(struct sc_demuxer *demuxer, const AVPacket *packet) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static bool
|
||||||
sc_demuxer_parse(struct sc_demuxer *demuxer, AVPacket *packet) {
|
stream_parse(struct stream *stream, AVPacket *packet) {
|
||||||
uint8_t *in_data = packet->data;
|
uint8_t *in_data = packet->data;
|
||||||
int in_len = packet->size;
|
int in_len = packet->size;
|
||||||
uint8_t *out_data = NULL;
|
uint8_t *out_data = NULL;
|
||||||
int out_len = 0;
|
int out_len = 0;
|
||||||
int r = av_parser_parse2(demuxer->parser, demuxer->codec_ctx,
|
int r = av_parser_parse2(stream->parser, stream->codec_ctx,
|
||||||
&out_data, &out_len, in_data, in_len,
|
&out_data, &out_len, in_data, in_len,
|
||||||
AV_NOPTS_VALUE, AV_NOPTS_VALUE, -1);
|
AV_NOPTS_VALUE, AV_NOPTS_VALUE, -1);
|
||||||
|
|
||||||
@@ -84,64 +84,13 @@ sc_demuxer_parse(struct sc_demuxer *demuxer, AVPacket *packet) {
|
|||||||
(void) r;
|
(void) r;
|
||||||
assert(out_len == in_len);
|
assert(out_len == in_len);
|
||||||
|
|
||||||
if (demuxer->parser->key_frame == 1) {
|
if (stream->parser->key_frame == 1) {
|
||||||
packet->flags |= AV_PKT_FLAG_KEY;
|
packet->flags |= AV_PKT_FLAG_KEY;
|
||||||
}
|
}
|
||||||
|
|
||||||
packet->dts = packet->pts;
|
packet->dts = packet->pts;
|
||||||
}
|
|
||||||
|
|
||||||
static bool
|
|
||||||
sc_demuxer_push_packet(struct sc_demuxer *demuxer, AVPacket *packet) {
|
|
||||||
bool is_config = packet->pts == AV_NOPTS_VALUE;
|
|
||||||
|
|
||||||
// A config packet must not be decoded immediately (it contains no
|
|
||||||
// frame); instead, it must be concatenated with the future data packet.
|
|
||||||
if (demuxer->pending || is_config) {
|
|
||||||
size_t offset;
|
|
||||||
if (demuxer->pending) {
|
|
||||||
offset = demuxer->pending->size;
|
|
||||||
if (av_grow_packet(demuxer->pending, packet->size)) {
|
|
||||||
LOG_OOM();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
offset = 0;
|
|
||||||
demuxer->pending = av_packet_alloc();
|
|
||||||
if (!demuxer->pending) {
|
|
||||||
LOG_OOM();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (av_new_packet(demuxer->pending, packet->size)) {
|
|
||||||
LOG_OOM();
|
|
||||||
av_packet_free(&demuxer->pending);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
memcpy(demuxer->pending->data + offset, packet->data, packet->size);
|
|
||||||
|
|
||||||
if (!is_config) {
|
|
||||||
// prepare the concat packet to send to the decoder
|
|
||||||
demuxer->pending->pts = packet->pts;
|
|
||||||
demuxer->pending->dts = packet->dts;
|
|
||||||
demuxer->pending->flags = packet->flags;
|
|
||||||
packet = demuxer->pending;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!is_config) {
|
|
||||||
// data packet
|
|
||||||
sc_demuxer_parse(demuxer, packet);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ok = push_packet_to_sinks(demuxer, packet);
|
|
||||||
|
|
||||||
if (!is_config && demuxer->pending) {
|
|
||||||
// the pending packet must be discarded (consumed or error)
|
|
||||||
av_packet_free(&demuxer->pending);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
bool ok = push_packet_to_sinks(stream, packet);
|
||||||
if (!ok) {
|
if (!ok) {
|
||||||
LOGE("Could not process packet");
|
LOGE("Could not process packet");
|
||||||
return false;
|
return false;
|
||||||
@@ -150,26 +99,87 @@ sc_demuxer_push_packet(struct sc_demuxer *demuxer, AVPacket *packet) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool
|
||||||
|
stream_push_packet(struct stream *stream, AVPacket *packet) {
|
||||||
|
bool is_config = packet->pts == AV_NOPTS_VALUE;
|
||||||
|
|
||||||
|
// A config packet must not be decoded immediately (it contains no
|
||||||
|
// frame); instead, it must be concatenated with the future data packet.
|
||||||
|
if (stream->pending || is_config) {
|
||||||
|
size_t offset;
|
||||||
|
if (stream->pending) {
|
||||||
|
offset = stream->pending->size;
|
||||||
|
if (av_grow_packet(stream->pending, packet->size)) {
|
||||||
|
LOG_OOM();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
offset = 0;
|
||||||
|
stream->pending = av_packet_alloc();
|
||||||
|
if (!stream->pending) {
|
||||||
|
LOG_OOM();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (av_new_packet(stream->pending, packet->size)) {
|
||||||
|
LOG_OOM();
|
||||||
|
av_packet_free(&stream->pending);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
memcpy(stream->pending->data + offset, packet->data, packet->size);
|
||||||
|
|
||||||
|
if (!is_config) {
|
||||||
|
// prepare the concat packet to send to the decoder
|
||||||
|
stream->pending->pts = packet->pts;
|
||||||
|
stream->pending->dts = packet->dts;
|
||||||
|
stream->pending->flags = packet->flags;
|
||||||
|
packet = stream->pending;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_config) {
|
||||||
|
// config packet
|
||||||
|
bool ok = push_packet_to_sinks(stream, packet);
|
||||||
|
if (!ok) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// data packet
|
||||||
|
bool ok = stream_parse(stream, packet);
|
||||||
|
|
||||||
|
if (stream->pending) {
|
||||||
|
// the pending packet must be discarded (consumed or error)
|
||||||
|
av_packet_free(&stream->pending);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!ok) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
sc_demuxer_close_first_sinks(struct sc_demuxer *demuxer, unsigned count) {
|
stream_close_first_sinks(struct stream *stream, unsigned count) {
|
||||||
while (count) {
|
while (count) {
|
||||||
struct sc_packet_sink *sink = demuxer->sinks[--count];
|
struct sc_packet_sink *sink = stream->sinks[--count];
|
||||||
sink->ops->close(sink);
|
sink->ops->close(sink);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline void
|
static inline void
|
||||||
sc_demuxer_close_sinks(struct sc_demuxer *demuxer) {
|
stream_close_sinks(struct stream *stream) {
|
||||||
sc_demuxer_close_first_sinks(demuxer, demuxer->sink_count);
|
stream_close_first_sinks(stream, stream->sink_count);
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
sc_demuxer_open_sinks(struct sc_demuxer *demuxer, const AVCodec *codec) {
|
stream_open_sinks(struct stream *stream, const AVCodec *codec) {
|
||||||
for (unsigned i = 0; i < demuxer->sink_count; ++i) {
|
for (unsigned i = 0; i < stream->sink_count; ++i) {
|
||||||
struct sc_packet_sink *sink = demuxer->sinks[i];
|
struct sc_packet_sink *sink = stream->sinks[i];
|
||||||
if (!sink->ops->open(sink, codec)) {
|
if (!sink->ops->open(sink, codec)) {
|
||||||
LOGE("Could not open packet sink %d", i);
|
LOGE("Could not open packet sink %d", i);
|
||||||
sc_demuxer_close_first_sinks(demuxer, i);
|
stream_close_first_sinks(stream, i);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -178,8 +188,8 @@ sc_demuxer_open_sinks(struct sc_demuxer *demuxer, const AVCodec *codec) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
run_demuxer(void *data) {
|
run_stream(void *data) {
|
||||||
struct sc_demuxer *demuxer = data;
|
struct stream *stream = data;
|
||||||
|
|
||||||
const AVCodec *codec = avcodec_find_decoder(AV_CODEC_ID_H264);
|
const AVCodec *codec = avcodec_find_decoder(AV_CODEC_ID_H264);
|
||||||
if (!codec) {
|
if (!codec) {
|
||||||
@@ -187,26 +197,26 @@ run_demuxer(void *data) {
|
|||||||
goto end;
|
goto end;
|
||||||
}
|
}
|
||||||
|
|
||||||
demuxer->codec_ctx = avcodec_alloc_context3(codec);
|
stream->codec_ctx = avcodec_alloc_context3(codec);
|
||||||
if (!demuxer->codec_ctx) {
|
if (!stream->codec_ctx) {
|
||||||
LOG_OOM();
|
LOG_OOM();
|
||||||
goto end;
|
goto end;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!sc_demuxer_open_sinks(demuxer, codec)) {
|
if (!stream_open_sinks(stream, codec)) {
|
||||||
LOGE("Could not open demuxer sinks");
|
LOGE("Could not open stream sinks");
|
||||||
goto finally_free_codec_ctx;
|
goto finally_free_codec_ctx;
|
||||||
}
|
}
|
||||||
|
|
||||||
demuxer->parser = av_parser_init(AV_CODEC_ID_H264);
|
stream->parser = av_parser_init(AV_CODEC_ID_H264);
|
||||||
if (!demuxer->parser) {
|
if (!stream->parser) {
|
||||||
LOGE("Could not initialize parser");
|
LOGE("Could not initialize parser");
|
||||||
goto finally_close_sinks;
|
goto finally_close_sinks;
|
||||||
}
|
}
|
||||||
|
|
||||||
// We must only pass complete frames to av_parser_parse2()!
|
// We must only pass complete frames to av_parser_parse2()!
|
||||||
// It's more complicated, but this allows to reduce the latency by 1 frame!
|
// It's more complicated, but this allows to reduce the latency by 1 frame!
|
||||||
demuxer->parser->flags |= PARSER_FLAG_COMPLETE_FRAMES;
|
stream->parser->flags |= PARSER_FLAG_COMPLETE_FRAMES;
|
||||||
|
|
||||||
AVPacket *packet = av_packet_alloc();
|
AVPacket *packet = av_packet_alloc();
|
||||||
if (!packet) {
|
if (!packet) {
|
||||||
@@ -215,13 +225,13 @@ run_demuxer(void *data) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (;;) {
|
for (;;) {
|
||||||
bool ok = sc_demuxer_recv_packet(demuxer, packet);
|
bool ok = stream_recv_packet(stream, packet);
|
||||||
if (!ok) {
|
if (!ok) {
|
||||||
// end of stream
|
// end of stream
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
ok = sc_demuxer_push_packet(demuxer, packet);
|
ok = stream_push_packet(stream, packet);
|
||||||
av_packet_unref(packet);
|
av_packet_unref(packet);
|
||||||
if (!ok) {
|
if (!ok) {
|
||||||
// cannot process packet (error already logged)
|
// cannot process packet (error already logged)
|
||||||
@@ -231,58 +241,58 @@ run_demuxer(void *data) {
|
|||||||
|
|
||||||
LOGD("End of frames");
|
LOGD("End of frames");
|
||||||
|
|
||||||
if (demuxer->pending) {
|
if (stream->pending) {
|
||||||
av_packet_free(&demuxer->pending);
|
av_packet_free(&stream->pending);
|
||||||
}
|
}
|
||||||
|
|
||||||
av_packet_free(&packet);
|
av_packet_free(&packet);
|
||||||
finally_close_parser:
|
finally_close_parser:
|
||||||
av_parser_close(demuxer->parser);
|
av_parser_close(stream->parser);
|
||||||
finally_close_sinks:
|
finally_close_sinks:
|
||||||
sc_demuxer_close_sinks(demuxer);
|
stream_close_sinks(stream);
|
||||||
finally_free_codec_ctx:
|
finally_free_codec_ctx:
|
||||||
avcodec_free_context(&demuxer->codec_ctx);
|
avcodec_free_context(&stream->codec_ctx);
|
||||||
end:
|
end:
|
||||||
demuxer->cbs->on_eos(demuxer, demuxer->cbs_userdata);
|
stream->cbs->on_eos(stream, stream->cbs_userdata);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
sc_demuxer_init(struct sc_demuxer *demuxer, sc_socket socket,
|
stream_init(struct stream *stream, sc_socket socket,
|
||||||
const struct sc_demuxer_callbacks *cbs, void *cbs_userdata) {
|
const struct stream_callbacks *cbs, void *cbs_userdata) {
|
||||||
demuxer->socket = socket;
|
stream->socket = socket;
|
||||||
demuxer->pending = NULL;
|
stream->pending = NULL;
|
||||||
demuxer->sink_count = 0;
|
stream->sink_count = 0;
|
||||||
|
|
||||||
assert(cbs && cbs->on_eos);
|
assert(cbs && cbs->on_eos);
|
||||||
|
|
||||||
demuxer->cbs = cbs;
|
stream->cbs = cbs;
|
||||||
demuxer->cbs_userdata = cbs_userdata;
|
stream->cbs_userdata = cbs_userdata;
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
sc_demuxer_add_sink(struct sc_demuxer *demuxer, struct sc_packet_sink *sink) {
|
stream_add_sink(struct stream *stream, struct sc_packet_sink *sink) {
|
||||||
assert(demuxer->sink_count < SC_DEMUXER_MAX_SINKS);
|
assert(stream->sink_count < STREAM_MAX_SINKS);
|
||||||
assert(sink);
|
assert(sink);
|
||||||
assert(sink->ops);
|
assert(sink->ops);
|
||||||
demuxer->sinks[demuxer->sink_count++] = sink;
|
stream->sinks[stream->sink_count++] = sink;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool
|
bool
|
||||||
sc_demuxer_start(struct sc_demuxer *demuxer) {
|
stream_start(struct stream *stream) {
|
||||||
LOGD("Starting demuxer thread");
|
LOGD("Starting stream thread");
|
||||||
|
|
||||||
bool ok = sc_thread_create(&demuxer->thread, run_demuxer, "scrcpy-demuxer",
|
bool ok =
|
||||||
demuxer);
|
sc_thread_create(&stream->thread, run_stream, "scrcpy-stream", stream);
|
||||||
if (!ok) {
|
if (!ok) {
|
||||||
LOGE("Could not start demuxer thread");
|
LOGC("Could not start stream thread");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
sc_demuxer_join(struct sc_demuxer *demuxer) {
|
stream_join(struct stream *stream) {
|
||||||
sc_thread_join(&demuxer->thread, NULL);
|
sc_thread_join(&stream->thread, NULL);
|
||||||
}
|
}
|
||||||
@@ -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) {
|
||||||
LOGE("Requested to kill %d, this is an error. Please report the bug.\n",
|
LOGC("Requested to kill %d, this is an error. Please report the bug.\n",
|
||||||
(int) pid);
|
(int) pid);
|
||||||
abort();
|
abort();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -45,6 +45,11 @@ sc_hid_event_destroy(struct sc_hid_event *hid_event) {
|
|||||||
free(hid_event->buffer);
|
free(hid_event->buffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static inline void
|
||||||
|
log_libusb_error(enum libusb_error errcode) {
|
||||||
|
LOGW("libusb error: %s", libusb_strerror(errcode));
|
||||||
|
}
|
||||||
|
|
||||||
bool
|
bool
|
||||||
sc_aoa_init(struct sc_aoa *aoa, struct sc_usb *usb,
|
sc_aoa_init(struct sc_aoa *aoa, struct sc_usb *usb,
|
||||||
struct sc_acksync *acksync) {
|
struct sc_acksync *acksync) {
|
||||||
@@ -94,7 +99,7 @@ sc_aoa_register_hid(struct sc_aoa *aoa, uint16_t accessory_id,
|
|||||||
request, value, index, buffer, length,
|
request, value, index, buffer, length,
|
||||||
DEFAULT_TIMEOUT);
|
DEFAULT_TIMEOUT);
|
||||||
if (result < 0) {
|
if (result < 0) {
|
||||||
LOGE("REGISTER_HID: libusb error: %s", libusb_strerror(result));
|
log_libusb_error((enum libusb_error) result);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -130,7 +135,7 @@ sc_aoa_set_hid_report_desc(struct sc_aoa *aoa, uint16_t accessory_id,
|
|||||||
request, value, index, buffer, length,
|
request, value, index, buffer, length,
|
||||||
DEFAULT_TIMEOUT);
|
DEFAULT_TIMEOUT);
|
||||||
if (result < 0) {
|
if (result < 0) {
|
||||||
LOGE("SET_HID_REPORT_DESC: libusb error: %s", libusb_strerror(result));
|
log_libusb_error((enum libusb_error) result);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -172,7 +177,7 @@ sc_aoa_send_hid_event(struct sc_aoa *aoa, const struct sc_hid_event *event) {
|
|||||||
request, value, index, buffer, length,
|
request, value, index, buffer, length,
|
||||||
DEFAULT_TIMEOUT);
|
DEFAULT_TIMEOUT);
|
||||||
if (result < 0) {
|
if (result < 0) {
|
||||||
LOGE("SEND_HID_EVENT: libusb error: %s", libusb_strerror(result));
|
log_libusb_error((enum libusb_error) result);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -194,7 +199,7 @@ sc_aoa_unregister_hid(struct sc_aoa *aoa, const uint16_t accessory_id) {
|
|||||||
request, value, index, buffer, length,
|
request, value, index, buffer, length,
|
||||||
DEFAULT_TIMEOUT);
|
DEFAULT_TIMEOUT);
|
||||||
if (result < 0) {
|
if (result < 0) {
|
||||||
LOGE("UNREGISTER_HID: libusb error: %s", libusb_strerror(result));
|
log_libusb_error((enum libusb_error) result);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -278,7 +283,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) {
|
||||||
LOGE("Could not start AOA thread");
|
LOGC("Could not start AOA thread");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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)) {
|
||||||
LOGE("Could not initialize SDL: %s", SDL_GetError());
|
LOGC("Could not initialize SDL: %s", SDL_GetError());
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -83,17 +83,50 @@ scrcpy_otg(struct scrcpy_options *options) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct sc_usb_device usb_device;
|
struct sc_usb_device usb_devices[16];
|
||||||
ok = sc_usb_select_device(&s->usb, serial, &usb_device);
|
ssize_t count = sc_usb_find_devices(&s->usb, serial, usb_devices,
|
||||||
if (!ok) {
|
ARRAY_LEN(usb_devices));
|
||||||
|
if (count < 0) {
|
||||||
|
LOGE("Could not list USB devices");
|
||||||
goto end;
|
goto end;
|
||||||
}
|
}
|
||||||
|
|
||||||
LOGI("USB device: %s (%04x:%04x) %s %s", usb_device.serial,
|
if (count == 0) {
|
||||||
(unsigned) usb_device.vid, (unsigned) usb_device.pid,
|
if (serial) {
|
||||||
usb_device.manufacturer, usb_device.product);
|
LOGE("Could not find USB device %s", serial);
|
||||||
|
} else {
|
||||||
|
LOGE("Could not find any USB device");
|
||||||
|
}
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
|
||||||
ok = sc_usb_connect(&s->usb, usb_device.device, &cbs, NULL);
|
if (count > 1) {
|
||||||
|
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;
|
||||||
}
|
}
|
||||||
@@ -140,7 +173,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 = {
|
||||||
@@ -159,7 +192,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);
|
||||||
@@ -190,7 +223,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);
|
||||||
|
|||||||
@@ -4,6 +4,11 @@
|
|||||||
|
|
||||||
#include "util/log.h"
|
#include "util/log.h"
|
||||||
|
|
||||||
|
static inline void
|
||||||
|
log_libusb_error(enum libusb_error errcode) {
|
||||||
|
LOGW("libusb error: %s", libusb_strerror(errcode));
|
||||||
|
}
|
||||||
|
|
||||||
static char *
|
static char *
|
||||||
read_string(libusb_device_handle *handle, uint8_t desc_index) {
|
read_string(libusb_device_handle *handle, uint8_t desc_index) {
|
||||||
char buffer[128];
|
char buffer[128];
|
||||||
@@ -25,7 +30,8 @@ read_string(libusb_device_handle *handle, uint8_t desc_index) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
sc_usb_read_device(libusb_device *device, struct sc_usb_device *out) {
|
accept_device(libusb_device *device, const char *serial,
|
||||||
|
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
|
||||||
|
|
||||||
@@ -38,11 +44,6 @@ sc_usb_read_device(libusb_device *device, struct sc_usb_device *out) {
|
|||||||
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -52,13 +53,22 @@ sc_usb_read_device(libusb_device *device, struct sc_usb_device *out) {
|
|||||||
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);
|
||||||
|
|
||||||
@@ -67,37 +77,26 @@ sc_usb_read_device(libusb_device *device, struct sc_usb_device *out) {
|
|||||||
|
|
||||||
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_move(struct sc_usb_device *dst, struct sc_usb_device *src) {
|
sc_usb_device_destroy_all(struct sc_usb_device *usb_devices, size_t count) {
|
||||||
*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]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static ssize_t
|
ssize_t
|
||||||
sc_usb_list_devices(struct sc_usb *usb, struct sc_usb_device *devices,
|
sc_usb_find_devices(struct sc_usb *usb, const char *serial,
|
||||||
size_t len) {
|
struct sc_usb_device *devices, 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) {
|
||||||
LOGE("List USB devices: libusb error: %s", libusb_strerror(count));
|
log_libusb_error((enum libusb_error) count);
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -105,7 +104,7 @@ sc_usb_list_devices(struct sc_usb *usb, struct sc_usb_device *devices,
|
|||||||
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 (sc_usb_read_device(device, &devices[idx])) {
|
if (accept_device(device, serial, &devices[idx])) {
|
||||||
++idx;
|
++idx;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -114,100 +113,15 @@ sc_usb_list_devices(struct sc_usb *usb, struct sc_usb_device *devices,
|
|||||||
return idx;
|
return idx;
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
static libusb_device_handle *
|
||||||
sc_usb_accept_device(const struct sc_usb_device *device, const char *serial) {
|
sc_usb_open_handle(libusb_device *device) {
|
||||||
if (!serial) {
|
libusb_device_handle *handle;
|
||||||
return true;
|
int result = libusb_open(device, &handle);
|
||||||
|
if (result < 0) {
|
||||||
|
log_libusb_error((enum libusb_error) result);
|
||||||
|
return NULL;
|
||||||
}
|
}
|
||||||
|
return handle;
|
||||||
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
|
||||||
@@ -221,7 +135,7 @@ sc_usb_destroy(struct sc_usb *usb) {
|
|||||||
libusb_exit(usb->context);
|
libusb_exit(usb->context);
|
||||||
}
|
}
|
||||||
|
|
||||||
static LIBUSB_CALL int
|
static 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;
|
||||||
@@ -269,7 +183,8 @@ sc_usb_register_callback(struct sc_usb *usb) {
|
|||||||
struct libusb_device_descriptor desc;
|
struct libusb_device_descriptor desc;
|
||||||
int result = libusb_get_device_descriptor(device, &desc);
|
int result = libusb_get_device_descriptor(device, &desc);
|
||||||
if (result < 0) {
|
if (result < 0) {
|
||||||
LOGE("Device descriptor: libusb error: %s", libusb_strerror(result));
|
log_libusb_error((enum libusb_error) result);
|
||||||
|
LOGW("Could not read USB device descriptor");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -283,8 +198,8 @@ sc_usb_register_callback(struct sc_usb *usb) {
|
|||||||
sc_usb_libusb_callback, usb,
|
sc_usb_libusb_callback, usb,
|
||||||
&usb->callback_handle);
|
&usb->callback_handle);
|
||||||
if (result < 0) {
|
if (result < 0) {
|
||||||
LOGE("Register hotplog callback: libusb error: %s",
|
log_libusb_error((enum libusb_error) result);
|
||||||
libusb_strerror(result));
|
LOGW("Could not register USB callback");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -295,9 +210,8 @@ sc_usb_register_callback(struct sc_usb *usb) {
|
|||||||
bool
|
bool
|
||||||
sc_usb_connect(struct sc_usb *usb, libusb_device *device,
|
sc_usb_connect(struct sc_usb *usb, libusb_device *device,
|
||||||
const struct sc_usb_callbacks *cbs, void *cbs_userdata) {
|
const struct sc_usb_callbacks *cbs, void *cbs_userdata) {
|
||||||
int result = libusb_open(device, &usb->handle);
|
usb->handle = sc_usb_open_handle(device);
|
||||||
if (result < 0) {
|
if (!usb->handle) {
|
||||||
LOGE("Open USB device: libusb error: %s", libusb_strerror(result));
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -35,26 +35,13 @@ 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_move(struct sc_usb_device *dst, struct sc_usb_device *src);
|
sc_usb_device_destroy_all(struct sc_usb_device *usb_devices, size_t count);
|
||||||
|
|
||||||
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);
|
||||||
@@ -62,9 +49,9 @@ sc_usb_init(struct sc_usb *usb);
|
|||||||
void
|
void
|
||||||
sc_usb_destroy(struct sc_usb *usb);
|
sc_usb_destroy(struct sc_usb *usb);
|
||||||
|
|
||||||
bool
|
ssize_t
|
||||||
sc_usb_select_device(struct sc_usb *usb, const char *serial,
|
sc_usb_find_devices(struct sc_usb *usb, const char *serial,
|
||||||
struct sc_usb_device *out_device);
|
struct sc_usb_device *devices, size_t len);
|
||||||
|
|
||||||
bool
|
bool
|
||||||
sc_usb_connect(struct sc_usb *usb, libusb_device *device,
|
sc_usb_connect(struct sc_usb *usb, libusb_device *device,
|
||||||
|
|||||||
@@ -55,16 +55,6 @@ 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) {
|
||||||
|
|||||||
@@ -15,9 +15,10 @@
|
|||||||
#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() \
|
||||||
LOGE("OOM: %s:%d %s()", __FILE__, __LINE__, __func__)
|
LOGC("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);
|
||||||
@@ -25,10 +26,6 @@ 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
|
||||||
|
|||||||
@@ -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) {
|
||||||
LOGE("WSAStartup failed with error %d", res);
|
LOGC("WSAStartup failed with error %d", res);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -12,6 +12,8 @@
|
|||||||
# 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;
|
||||||
@@ -21,6 +23,7 @@
|
|||||||
#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
|
||||||
|
|||||||
@@ -297,6 +297,14 @@ error:
|
|||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
size_t
|
||||||
|
sc_str_truncate(char *data, size_t len, const char *endchars) {
|
||||||
|
data[len - 1] = '\0';
|
||||||
|
size_t idx = strcspn(data, endchars);
|
||||||
|
data[idx] = '\0';
|
||||||
|
return idx;
|
||||||
|
}
|
||||||
|
|
||||||
ssize_t
|
ssize_t
|
||||||
sc_str_index_of_column(const char *s, unsigned col, const char *seps) {
|
sc_str_index_of_column(const char *s, unsigned col, const char *seps) {
|
||||||
size_t colidx = 0;
|
size_t colidx = 0;
|
||||||
|
|||||||
@@ -103,6 +103,17 @@ sc_str_from_wchars(const wchar_t *s);
|
|||||||
char *
|
char *
|
||||||
sc_str_wrap_lines(const char *input, unsigned columns, unsigned indent);
|
sc_str_wrap_lines(const char *input, unsigned columns, unsigned indent);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Truncate the data after any of the characters from `endchars`
|
||||||
|
*
|
||||||
|
* An '\0' is always written at the end of the data, even if no newline
|
||||||
|
* character is encountered.
|
||||||
|
*
|
||||||
|
* Return the size of the resulting line.
|
||||||
|
*/
|
||||||
|
size_t
|
||||||
|
sc_str_truncate(char *data, size_t len, const char *endchars);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Find the start of a column in a string
|
* Find the start of a column in a string
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -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) {
|
||||||
LOGE("Could not lock mutex: %s", SDL_GetError());
|
LOGC("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) {
|
||||||
LOGE("Could not lock mutex: %s", SDL_GetError());
|
LOGC("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) {
|
||||||
LOGE("Could not wait on condition: %s", SDL_GetError());
|
LOGC("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) {
|
||||||
LOGE("Could not wait on condition with timeout: %s", SDL_GetError());
|
LOGC("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) {
|
||||||
LOGE("Could not signal a condition: %s", SDL_GetError());
|
LOGC("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) {
|
||||||
LOGE("Could not broadcast a condition: %s", SDL_GetError());
|
LOGC("Could not broadcast a condition: %s", SDL_GetError());
|
||||||
abort();
|
abort();
|
||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
|
|||||||
@@ -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) {
|
||||||
LOGE("Could not start v4l2 thread");
|
LOGC("Could not start v4l2 thread");
|
||||||
goto error_av_packet_free;
|
goto error_av_packet_free;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,163 +2,13 @@
|
|||||||
|
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
|
|
||||||
#include "adb/adb_device.h"
|
#include "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";
|
||||||
|
|
||||||
char *ip = sc_adb_parse_device_ip_from_output(ip_route);
|
char *ip = sc_adb_parse_device_ip_from_output(ip_route, sizeof(ip_route));
|
||||||
assert(ip);
|
assert(ip);
|
||||||
assert(!strcmp(ip, "192.168.12.34"));
|
assert(!strcmp(ip, "192.168.12.34"));
|
||||||
free(ip);
|
free(ip);
|
||||||
@@ -168,7 +18,7 @@ static void test_get_ip_single_line_without_eol() {
|
|||||||
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";
|
"192.168.12.34";
|
||||||
|
|
||||||
char *ip = sc_adb_parse_device_ip_from_output(ip_route);
|
char *ip = sc_adb_parse_device_ip_from_output(ip_route, sizeof(ip_route));
|
||||||
assert(ip);
|
assert(ip);
|
||||||
assert(!strcmp(ip, "192.168.12.34"));
|
assert(!strcmp(ip, "192.168.12.34"));
|
||||||
free(ip);
|
free(ip);
|
||||||
@@ -178,7 +28,7 @@ static void test_get_ip_single_line_with_trailing_space() {
|
|||||||
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 \n";
|
"192.168.12.34 \n";
|
||||||
|
|
||||||
char *ip = sc_adb_parse_device_ip_from_output(ip_route);
|
char *ip = sc_adb_parse_device_ip_from_output(ip_route, sizeof(ip_route));
|
||||||
assert(ip);
|
assert(ip);
|
||||||
assert(!strcmp(ip, "192.168.12.34"));
|
assert(!strcmp(ip, "192.168.12.34"));
|
||||||
free(ip);
|
free(ip);
|
||||||
@@ -190,7 +40,7 @@ static void test_get_ip_multiline_first_ok() {
|
|||||||
"10.0.0.0/24 dev rmnet proto kernel scope link src "
|
"10.0.0.0/24 dev rmnet proto kernel scope link src "
|
||||||
"10.0.0.2\r\n";
|
"10.0.0.2\r\n";
|
||||||
|
|
||||||
char *ip = sc_adb_parse_device_ip_from_output(ip_route);
|
char *ip = sc_adb_parse_device_ip_from_output(ip_route, sizeof(ip_route));
|
||||||
assert(ip);
|
assert(ip);
|
||||||
assert(!strcmp(ip, "192.168.1.2"));
|
assert(!strcmp(ip, "192.168.1.2"));
|
||||||
free(ip);
|
free(ip);
|
||||||
@@ -202,7 +52,7 @@ static void test_get_ip_multiline_second_ok() {
|
|||||||
"192.168.1.0/24 dev wlan0 proto kernel scope link src "
|
"192.168.1.0/24 dev wlan0 proto kernel scope link src "
|
||||||
"192.168.1.3\r\n";
|
"192.168.1.3\r\n";
|
||||||
|
|
||||||
char *ip = sc_adb_parse_device_ip_from_output(ip_route);
|
char *ip = sc_adb_parse_device_ip_from_output(ip_route, sizeof(ip_route));
|
||||||
assert(ip);
|
assert(ip);
|
||||||
assert(!strcmp(ip, "192.168.1.3"));
|
assert(!strcmp(ip, "192.168.1.3"));
|
||||||
free(ip);
|
free(ip);
|
||||||
@@ -212,15 +62,7 @@ static void test_get_ip_no_wlan() {
|
|||||||
char ip_route[] = "192.168.1.0/24 dev rmnet proto kernel scope link src "
|
char ip_route[] = "192.168.1.0/24 dev rmnet proto kernel scope link src "
|
||||||
"192.168.12.34\r\r\n";
|
"192.168.12.34\r\r\n";
|
||||||
|
|
||||||
char *ip = sc_adb_parse_device_ip_from_output(ip_route);
|
char *ip = sc_adb_parse_device_ip_from_output(ip_route, sizeof(ip_route));
|
||||||
assert(!ip);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void test_get_ip_no_wlan_without_eol() {
|
|
||||||
char ip_route[] = "192.168.1.0/24 dev rmnet proto kernel scope link src "
|
|
||||||
"192.168.12.34";
|
|
||||||
|
|
||||||
char *ip = sc_adb_parse_device_ip_from_output(ip_route);
|
|
||||||
assert(!ip);
|
assert(!ip);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -228,7 +70,7 @@ static void test_get_ip_truncated() {
|
|||||||
char ip_route[] = "192.168.1.0/24 dev rmnet proto kernel scope link src "
|
char ip_route[] = "192.168.1.0/24 dev rmnet proto kernel scope link src "
|
||||||
"\n";
|
"\n";
|
||||||
|
|
||||||
char *ip = sc_adb_parse_device_ip_from_output(ip_route);
|
char *ip = sc_adb_parse_device_ip_from_output(ip_route, sizeof(ip_route));
|
||||||
assert(!ip);
|
assert(!ip);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -236,21 +78,11 @@ 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();
|
||||||
test_get_ip_multiline_first_ok();
|
test_get_ip_multiline_first_ok();
|
||||||
test_get_ip_multiline_second_ok();
|
test_get_ip_multiline_second_ok();
|
||||||
test_get_ip_no_wlan();
|
test_get_ip_no_wlan();
|
||||||
test_get_ip_no_wlan_without_eol();
|
|
||||||
test_get_ip_truncated();
|
test_get_ip_truncated();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -338,6 +338,32 @@ static void test_wrap_lines(void) {
|
|||||||
free(formatted);
|
free(formatted);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void test_truncate(void) {
|
||||||
|
char s[] = "hello\nworld\n!";
|
||||||
|
size_t len = sc_str_truncate(s, sizeof(s), "\n");
|
||||||
|
|
||||||
|
assert(len == 5);
|
||||||
|
assert(!strcmp("hello", s));
|
||||||
|
|
||||||
|
char s2[] = "hello\r\nworkd\r\n!";
|
||||||
|
len = sc_str_truncate(s2, sizeof(s2), "\n\r");
|
||||||
|
|
||||||
|
assert(len == 5);
|
||||||
|
assert(!strcmp("hello", s));
|
||||||
|
|
||||||
|
char s3[] = "hello world\n!";
|
||||||
|
len = sc_str_truncate(s3, sizeof(s3), " \n\r");
|
||||||
|
|
||||||
|
assert(len == 5);
|
||||||
|
assert(!strcmp("hello", s3));
|
||||||
|
|
||||||
|
char s4[] = "hello ";
|
||||||
|
len = sc_str_truncate(s4, sizeof(s4), " \n\r");
|
||||||
|
|
||||||
|
assert(len == 5);
|
||||||
|
assert(!strcmp("hello", s4));
|
||||||
|
}
|
||||||
|
|
||||||
static void test_index_of_column(void) {
|
static void test_index_of_column(void) {
|
||||||
assert(sc_str_index_of_column("a bc d", 0, " ") == 0);
|
assert(sc_str_index_of_column("a bc d", 0, " ") == 0);
|
||||||
assert(sc_str_index_of_column("a bc d", 1, " ") == 2);
|
assert(sc_str_index_of_column("a bc d", 1, " ") == 2);
|
||||||
@@ -391,6 +417,7 @@ int main(int argc, char *argv[]) {
|
|||||||
test_parse_integer_with_suffix();
|
test_parse_integer_with_suffix();
|
||||||
test_strlist_contains();
|
test_strlist_contains();
|
||||||
test_wrap_lines();
|
test_wrap_lines();
|
||||||
|
test_truncate();
|
||||||
test_index_of_column();
|
test_index_of_column();
|
||||||
test_remove_trailing_cr();
|
test_remove_trailing_cr();
|
||||||
return 0;
|
return 0;
|
||||||
|
|||||||
@@ -21,5 +21,3 @@ 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'
|
|
||||||
|
|||||||
@@ -21,5 +21,3 @@ 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'
|
|
||||||
|
|||||||
@@ -1,28 +0,0 @@
|
|||||||
#!/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 /
|
|
||||||
@@ -66,7 +66,6 @@ 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)" && \
|
||||||
@@ -108,7 +107,6 @@ 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)"
|
||||||
@@ -127,7 +125,6 @@ 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)"; \
|
||||||
|
|||||||
@@ -89,15 +89,13 @@ public class ScreenEncoder implements Device.RotationListener {
|
|||||||
Rect unlockedVideoRect = screenInfo.getUnlockedVideoSize().toRect();
|
Rect unlockedVideoRect = screenInfo.getUnlockedVideoSize().toRect();
|
||||||
int videoRotation = screenInfo.getVideoRotation();
|
int videoRotation = screenInfo.getVideoRotation();
|
||||||
int layerStack = device.getLayerStack();
|
int layerStack = device.getLayerStack();
|
||||||
setSize(format, videoRect.width(), videoRect.height());
|
|
||||||
|
|
||||||
Surface surface = null;
|
setSize(format, videoRect.width(), videoRect.height());
|
||||||
try {
|
|
||||||
configure(codec, format);
|
configure(codec, format);
|
||||||
surface = codec.createInputSurface();
|
Surface surface = codec.createInputSurface();
|
||||||
setDisplaySurface(display, surface, videoRotation, contentRect, unlockedVideoRect, layerStack);
|
setDisplaySurface(display, surface, videoRotation, contentRect, unlockedVideoRect, layerStack);
|
||||||
codec.start();
|
codec.start();
|
||||||
|
try {
|
||||||
alive = encode(codec, fd);
|
alive = encode(codec, fd);
|
||||||
// do not call stop() on exception, it would trigger an IllegalStateException
|
// do not call stop() on exception, it would trigger an IllegalStateException
|
||||||
codec.stop();
|
codec.stop();
|
||||||
@@ -121,10 +119,8 @@ public class ScreenEncoder implements Device.RotationListener {
|
|||||||
} finally {
|
} finally {
|
||||||
destroyDisplay(display);
|
destroyDisplay(display);
|
||||||
codec.release();
|
codec.release();
|
||||||
if (surface != null) {
|
|
||||||
surface.release();
|
surface.release();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
} while (alive);
|
} while (alive);
|
||||||
} finally {
|
} finally {
|
||||||
device.setRotationListener(null);
|
device.setRotationListener(null);
|
||||||
|
|||||||
Reference in New Issue
Block a user