Compare commits

..

3 Commits

Author SHA1 Message Date
Romain Vimont
0e22032710 Update FAQ about Windows scaling behavior
Recommend to update to v1.22 before suggesting manual configuration.

Fixes #3028 <https://github.com/Genymobile/scrcpy/issues/3028>
PR #3032 <https://github.com/Genymobile/scrcpy/pull/3032>
2022-02-18 18:13:35 +01:00
Firq
7a138c6929 Fix links in German README
There were three links that weren't displayed correctly due to incorrect
references:
 - the Windows release link to `README.md#windows`
 - 2 links that expected a German reference but got an English reference

PR #3026 <https://github.com/Genymobile/scrcpy/pull/3026>

Signed-off-by: Romain Vimont <rom@rom1v.com>
2022-02-16 20:44:09 +01:00
Firq
b58b566fa5 Add German translation of README.md
PR #3023 <https://github.com/Genymobile/scrcpy/pull/3023>

Signed-off-by: Romain Vimont <rom@rom1v.com>
2022-02-15 21:52:08 +01:00
55 changed files with 2256 additions and 2084 deletions

20
FAQ.md
View File

@@ -150,22 +150,24 @@ screen, then you might get poor quality, especially visible on text (see [#40]).
[#40]: https://github.com/Genymobile/scrcpy/issues/40 [#40]: https://github.com/Genymobile/scrcpy/issues/40
To improve downscaling quality, trilinear filtering is enabled automatically This problem should be fixed in scrcpy v1.22: **update to the latest version**.
if the renderer is OpenGL and if it supports mipmapping.
On Windows, you might want to force OpenGL: On older versions, you must configure the [scaling behavior]:
```
scrcpy --render-driver=opengl
```
You may also need to configure the [scaling behavior]:
> `scrcpy.exe` > Properties > Compatibility > Change high DPI settings > > `scrcpy.exe` > Properties > Compatibility > Change high DPI settings >
> Override high DPI scaling behavior > Scaling performed by: _Application_. > Override high DPI scaling behavior > Scaling performed by: _Application_.
[scaling behavior]: https://github.com/Genymobile/scrcpy/issues/40#issuecomment-424466723 [scaling behavior]: https://github.com/Genymobile/scrcpy/issues/40#issuecomment-424466723
Also, to improve downscaling quality, trilinear filtering is enabled
automatically if the renderer is OpenGL and if it supports mipmapping.
On Windows, you might want to force OpenGL to enable mipmapping:
```
scrcpy --render-driver=opengl
```
### Issue with Wayland ### Issue with Wayland

1016
README.de.md Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -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)

View File

@@ -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',
]], ]],

View File

@@ -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
View 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
View 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

View File

@@ -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, ':');
}

View File

@@ -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

View File

@@ -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]);
}
}

View File

@@ -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

View File

@@ -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;
}

View File

@@ -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
View 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
View 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

View File

@@ -20,8 +20,8 @@ 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,11 +148,11 @@ 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);
if (!net_close(tunnel->server_socket)) { if (!net_close(tunnel->server_socket)) {

View File

@@ -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.",
@@ -309,7 +309,7 @@ static const struct sc_option options[] = {
{ {
.longopt_id = OPT_PREFER_TEXT, .longopt_id = OPT_PREFER_TEXT,
.longopt = "prefer-text", .longopt = "prefer-text",
.text = "Inject alpha characters and space as text events instead of " .text = "Inject alpha characters and space as text events instead of"
"key events.\n" "key events.\n"
"This avoids issues when combining multiple keys to enter a " "This avoids issues when combining multiple keys to enter a "
"special character, but breaks the expected behavior of alpha " "special character, but breaks the expected behavior of alpha "
@@ -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;
} }

View File

@@ -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:

View File

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

View File

@@ -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;

View File

@@ -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

View File

@@ -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

View File

@@ -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;
} }

View File

@@ -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"

View File

@@ -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,
}; };

View File

@@ -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;

View File

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

View File

@@ -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,30 +351,28 @@ 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) {
recorder->filename = strdup(filename); recorder->filename = strdup(filename);
if (!recorder->filename) { if (!recorder->filename) {
LOG_OOM(); LOG_OOM();
@@ -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);
} }

View File

@@ -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

View File

@@ -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,22 +371,22 @@ 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)) {
goto end; goto end;
} }
rec = &s->recorder; rec = &s->recorder;
@@ -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) {

View File

@@ -423,14 +423,14 @@ sc_screen_init(struct sc_screen *screen,
screen->window = screen->window =
SDL_CreateWindow(params->window_title, 0, 0, 0, 0, window_flags); SDL_CreateWindow(params->window_title, 0, 0, 0, 0, window_flags);
if (!screen->window) { if (!screen->window) {
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;
} }
} }

View File

@@ -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
sc_server_configure_tcpip_known_address(struct sc_server *server,
const char *addr) {
// Append ":5555" if no port is present
bool contains_port = strchr(addr, ':');
char *ip_port = contains_port ? strdup(addr) : append_port_5555(addr);
if (!ip_port) {
LOG_OOM();
return false;
}
server->serial = ip_port;
return sc_server_connect_to_tcpip(server, ip_port);
}
static bool static bool
sc_server_configure_tcpip_unknown_address(struct sc_server *server, sc_server_configure_tcpip(struct sc_server *server) {
const char *serial) { char *ip_port;
bool is_already_tcpip = sc_adb_is_serial_tcpip(serial);
if (is_already_tcpip) { const struct sc_server_params *params = &server->params;
// Nothing to do
LOGI("Device already connected via TCP/IP: %s", serial); // If tcpip parameter is given, then it must connect to this address.
return true; // 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
bool contains_port = strchr(params->tcpip_dst, ':');
ip_port = contains_port ? strdup(params->tcpip_dst)
: append_port_5555(params->tcpip_dst);
if (!ip_port) {
LOG_OOM();
return false;
}
} else {
// The device IP address must be retrieved from the current
// connected device
if (!sc_server_fill_serial(server)) {
return false;
}
// The serial is either the real serial when connected via USB, or
// 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;
}
} }
char *ip_port = sc_server_switch_to_tcpip(server, serial); // On success, this call changes params->serial
if (!ip_port) { bool ok = sc_server_connect_to_tcpip(server, ip_port);
return false; free(ip_port);
} return ok;
server->serial = ip_port;
return sc_server_connect_to_tcpip(server, ip_port);
} }
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 if (params->tcpip) {
assert(!params->tcpip_dst || params->tcpip); // params->serial may be changed after this call
bool ok = sc_server_configure_tcpip(server);
// 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) {
assert(!params->tcpip_dst);
ok = sc_server_configure_tcpip_unknown_address(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);

View File

@@ -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

View File

@@ -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);
} }

View File

@@ -176,7 +176,7 @@ sc_process_execute_p(const char *const argv[], sc_pid *pid, unsigned flags,
bool bool
sc_process_terminate(pid_t pid) { sc_process_terminate(pid_t pid) {
if (pid <= 0) { if (pid <= 0) {
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();
} }

View File

@@ -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;
} }

View File

@@ -56,7 +56,7 @@ scrcpy_otg(struct scrcpy_options *options) {
// Minimal SDL initialization // Minimal SDL initialization
if (SDL_Init(SDL_INIT_EVENTS)) { if (SDL_Init(SDL_INIT_EVENTS)) {
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);

View File

@@ -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,101 +113,16 @@ 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
sc_usb_init(struct sc_usb *usb) { sc_usb_init(struct sc_usb *usb) {
@@ -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;
} }

View File

@@ -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,

View File

@@ -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) {

View File

@@ -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

View File

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

View File

@@ -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

View File

@@ -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;

View File

@@ -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
* *

View File

@@ -54,7 +54,7 @@ sc_mutex_lock(sc_mutex *mutex) {
int r = SDL_LockMutex(mutex->mutex); int r = SDL_LockMutex(mutex->mutex);
#ifndef NDEBUG #ifndef NDEBUG
if (r) { if (r) {
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

View File

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

View File

@@ -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();
} }

View File

@@ -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;

View File

@@ -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'

View File

@@ -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'

View File

@@ -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 /

View File

@@ -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)"; \

View File

@@ -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()); setSize(format, videoRect.width(), videoRect.height());
configure(codec, format);
Surface surface = null; Surface surface = codec.createInputSurface();
setDisplaySurface(display, surface, videoRotation, contentRect, unlockedVideoRect, layerStack);
codec.start();
try { try {
configure(codec, format);
surface = codec.createInputSurface();
setDisplaySurface(display, surface, videoRotation, contentRect, unlockedVideoRect, layerStack);
codec.start();
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,9 +119,7 @@ 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 {